summaryrefslogtreecommitdiffstats
path: root/src/pl
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:17:33 +0000
commit5e45211a64149b3c659b90ff2de6fa982a5a93ed (patch)
tree739caf8c461053357daa9f162bef34516c7bf452 /src/pl
parentInitial commit. (diff)
downloadpostgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.tar.xz
postgresql-15-5e45211a64149b3c659b90ff2de6fa982a5a93ed.zip
Adding upstream version 15.5.upstream/15.5
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pl')
-rw-r--r--src/pl/Makefile36
-rw-r--r--src/pl/plperl/.gitignore9
-rw-r--r--src/pl/plperl/GNUmakefile130
-rw-r--r--src/pl/plperl/README10
-rw-r--r--src/pl/plperl/SPI.xs163
-rw-r--r--src/pl/plperl/Util.xs189
-rw-r--r--src/pl/plperl/expected/plperl.out794
-rw-r--r--src/pl/plperl/expected/plperl_array.out276
-rw-r--r--src/pl/plperl/expected/plperl_call.out72
-rw-r--r--src/pl/plperl/expected/plperl_elog.out112
-rw-r--r--src/pl/plperl/expected/plperl_elog_1.out112
-rw-r--r--src/pl/plperl/expected/plperl_init.out40
-rw-r--r--src/pl/plperl/expected/plperl_lc.out10
-rw-r--r--src/pl/plperl/expected/plperl_lc_1.out10
-rw-r--r--src/pl/plperl/expected/plperl_plperlu.out91
-rw-r--r--src/pl/plperl/expected/plperl_setup.out73
-rw-r--r--src/pl/plperl/expected/plperl_shared.out56
-rw-r--r--src/pl/plperl/expected/plperl_transaction.out244
-rw-r--r--src/pl/plperl/expected/plperl_trigger.out392
-rw-r--r--src/pl/plperl/expected/plperl_util.out198
-rw-r--r--src/pl/plperl/expected/plperlu.out15
-rw-r--r--src/pl/plperl/nls.mk6
-rw-r--r--src/pl/plperl/plc_perlboot.pl129
-rw-r--r--src/pl/plperl/plc_trusted.pl32
-rw-r--r--src/pl/plperl/plperl--1.0.sql20
-rw-r--r--src/pl/plperl/plperl.c4252
-rw-r--r--src/pl/plperl/plperl.control8
-rw-r--r--src/pl/plperl/plperl.h227
-rw-r--r--src/pl/plperl/plperl_helpers.h171
-rw-r--r--src/pl/plperl/plperl_opmask.pl65
-rw-r--r--src/pl/plperl/plperlu--1.0.sql17
-rw-r--r--src/pl/plperl/plperlu.control7
-rw-r--r--src/pl/plperl/po/cs.po227
-rw-r--r--src/pl/plperl/po/de.po227
-rw-r--r--src/pl/plperl/po/el.po228
-rw-r--r--src/pl/plperl/po/es.po229
-rw-r--r--src/pl/plperl/po/fr.po264
-rw-r--r--src/pl/plperl/po/it.po238
-rw-r--r--src/pl/plperl/po/ja.po230
-rw-r--r--src/pl/plperl/po/ka.po254
-rw-r--r--src/pl/plperl/po/ko.po242
-rw-r--r--src/pl/plperl/po/pl.po225
-rw-r--r--src/pl/plperl/po/pt_BR.po227
-rw-r--r--src/pl/plperl/po/ru.po261
-rw-r--r--src/pl/plperl/po/sv.po227
-rw-r--r--src/pl/plperl/po/tr.po236
-rw-r--r--src/pl/plperl/po/uk.po227
-rw-r--r--src/pl/plperl/po/vi.po242
-rw-r--r--src/pl/plperl/po/zh_CN.po221
-rw-r--r--src/pl/plperl/ppport.h17925
-rw-r--r--src/pl/plperl/sql/plperl.sql523
-rw-r--r--src/pl/plperl/sql/plperl_array.sql208
-rw-r--r--src/pl/plperl/sql/plperl_call.sql78
-rw-r--r--src/pl/plperl/sql/plperl_elog.sql93
-rw-r--r--src/pl/plperl/sql/plperl_end.sql29
-rw-r--r--src/pl/plperl/sql/plperl_init.sql41
-rw-r--r--src/pl/plperl/sql/plperl_lc.sql8
-rw-r--r--src/pl/plperl/sql/plperl_plperlu.sql58
-rw-r--r--src/pl/plperl/sql/plperl_setup.sql73
-rw-r--r--src/pl/plperl/sql/plperl_shared.sql41
-rw-r--r--src/pl/plperl/sql/plperl_transaction.sql195
-rw-r--r--src/pl/plperl/sql/plperl_trigger.sql259
-rw-r--r--src/pl/plperl/sql/plperl_util.sql121
-rw-r--r--src/pl/plperl/sql/plperlu.sql17
-rw-r--r--src/pl/plperl/text2macro.pl106
-rw-r--r--src/pl/plpgsql/Makefile17
-rw-r--r--src/pl/plpgsql/src/.gitignore8
-rw-r--r--src/pl/plpgsql/src/Makefile117
-rw-r--r--src/pl/plpgsql/src/data/copy1.data3
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_array.out95
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_cache.out67
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_cache_1.out72
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_call.out532
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_control.out683
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_copy.out82
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_domain.out397
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_record.out828
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_simple.out91
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_transaction.out742
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_trap.out255
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_trigger.out36
-rw-r--r--src/pl/plpgsql/src/expected/plpgsql_varprops.out298
-rw-r--r--src/pl/plpgsql/src/generate-plerrcodes.pl40
-rw-r--r--src/pl/plpgsql/src/nls.mk6
-rw-r--r--src/pl/plpgsql/src/pl_comp.c2682
-rw-r--r--src/pl/plpgsql/src/pl_exec.c8855
-rw-r--r--src/pl/plpgsql/src/pl_funcs.c1692
-rw-r--r--src/pl/plpgsql/src/pl_gram.c6316
-rw-r--r--src/pl/plpgsql/src/pl_gram.h270
-rw-r--r--src/pl/plpgsql/src/pl_gram.y4120
-rw-r--r--src/pl/plpgsql/src/pl_handler.c551
-rw-r--r--src/pl/plpgsql/src/pl_reserved_kwlist.h52
-rw-r--r--src/pl/plpgsql/src/pl_reserved_kwlist_d.h114
-rw-r--r--src/pl/plpgsql/src/pl_scanner.c620
-rw-r--r--src/pl/plpgsql/src/pl_unreserved_kwlist.h111
-rw-r--r--src/pl/plpgsql/src/pl_unreserved_kwlist_d.h244
-rw-r--r--src/pl/plpgsql/src/plerrcodes.h998
-rw-r--r--src/pl/plpgsql/src/plpgsql--1.0.sql20
-rw-r--r--src/pl/plpgsql/src/plpgsql.control8
-rw-r--r--src/pl/plpgsql/src/plpgsql.h1343
-rw-r--r--src/pl/plpgsql/src/po/cs.po911
-rw-r--r--src/pl/plpgsql/src/po/de.po850
-rw-r--r--src/pl/plpgsql/src/po/el.po855
-rw-r--r--src/pl/plpgsql/src/po/es.po855
-rw-r--r--src/pl/plpgsql/src/po/fr.po1004
-rw-r--r--src/pl/plpgsql/src/po/it.po874
-rw-r--r--src/pl/plpgsql/src/po/ja.po879
-rw-r--r--src/pl/plpgsql/src/po/ka.po906
-rw-r--r--src/pl/plpgsql/src/po/ko.po891
-rw-r--r--src/pl/plpgsql/src/po/pl.po839
-rw-r--r--src/pl/plpgsql/src/po/pt_BR.po851
-rw-r--r--src/pl/plpgsql/src/po/ru.po968
-rw-r--r--src/pl/plpgsql/src/po/sv.po849
-rw-r--r--src/pl/plpgsql/src/po/tr.po929
-rw-r--r--src/pl/plpgsql/src/po/uk.po852
-rw-r--r--src/pl/plpgsql/src/po/vi.po850
-rw-r--r--src/pl/plpgsql/src/po/zh_CN.po842
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_array.sql79
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_cache.sql50
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_call.sql494
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_control.sql488
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_copy.sql58
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_domain.sql279
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_record.sql531
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_simple.sql82
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_transaction.sql637
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_trap.sql175
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_trigger.sql24
-rw-r--r--src/pl/plpgsql/src/sql/plpgsql_varprops.sql247
-rw-r--r--src/pl/plpython/.gitignore5
-rw-r--r--src/pl/plpython/Makefile156
-rw-r--r--src/pl/plpython/expected/README3
-rw-r--r--src/pl/plpython/expected/plpython_call.out75
-rw-r--r--src/pl/plpython/expected/plpython_composite.out594
-rw-r--r--src/pl/plpython/expected/plpython_do.out8
-rw-r--r--src/pl/plpython/expected/plpython_drop.out5
-rw-r--r--src/pl/plpython/expected/plpython_ereport.out214
-rw-r--r--src/pl/plpython/expected/plpython_error.out460
-rw-r--r--src/pl/plpython/expected/plpython_error_5.out460
-rw-r--r--src/pl/plpython/expected/plpython_global.out52
-rw-r--r--src/pl/plpython/expected/plpython_import.out79
-rw-r--r--src/pl/plpython/expected/plpython_newline.out30
-rw-r--r--src/pl/plpython/expected/plpython_params.out64
-rw-r--r--src/pl/plpython/expected/plpython_populate.out22
-rw-r--r--src/pl/plpython/expected/plpython_quote.out56
-rw-r--r--src/pl/plpython/expected/plpython_record.out373
-rw-r--r--src/pl/plpython/expected/plpython_schema.out33
-rw-r--r--src/pl/plpython/expected/plpython_setof.out200
-rw-r--r--src/pl/plpython/expected/plpython_spi.out466
-rw-r--r--src/pl/plpython/expected/plpython_subtransaction.out401
-rw-r--r--src/pl/plpython/expected/plpython_test.out93
-rw-r--r--src/pl/plpython/expected/plpython_transaction.out250
-rw-r--r--src/pl/plpython/expected/plpython_trigger.out620
-rw-r--r--src/pl/plpython/expected/plpython_types.out1069
-rw-r--r--src/pl/plpython/expected/plpython_unicode.out56
-rw-r--r--src/pl/plpython/expected/plpython_void.out30
-rw-r--r--src/pl/plpython/generate-spiexceptions.pl44
-rw-r--r--src/pl/plpython/nls.mk11
-rw-r--r--src/pl/plpython/plpy_cursorobject.c492
-rw-r--r--src/pl/plpython/plpy_cursorobject.h24
-rw-r--r--src/pl/plpython/plpy_elog.c599
-rw-r--r--src/pl/plpython/plpy_elog.h46
-rw-r--r--src/pl/plpython/plpy_exec.c1100
-rw-r--r--src/pl/plpython/plpy_exec.h13
-rw-r--r--src/pl/plpython/plpy_main.c421
-rw-r--r--src/pl/plpython/plpy_main.h31
-rw-r--r--src/pl/plpython/plpy_planobject.c125
-rw-r--r--src/pl/plpython/plpy_planobject.h27
-rw-r--r--src/pl/plpython/plpy_plpymodule.c561
-rw-r--r--src/pl/plpython/plpy_plpymodule.h18
-rw-r--r--src/pl/plpython/plpy_procedure.c472
-rw-r--r--src/pl/plpython/plpy_procedure.h70
-rw-r--r--src/pl/plpython/plpy_resultobject.c250
-rw-r--r--src/pl/plpython/plpy_resultobject.h27
-rw-r--r--src/pl/plpython/plpy_spi.c666
-rw-r--r--src/pl/plpython/plpy_spi.h29
-rw-r--r--src/pl/plpython/plpy_subxactobject.c186
-rw-r--r--src/pl/plpython/plpy_subxactobject.h33
-rw-r--r--src/pl/plpython/plpy_typeio.c1557
-rw-r--r--src/pl/plpython/plpy_typeio.h175
-rw-r--r--src/pl/plpython/plpy_util.c121
-rw-r--r--src/pl/plpython/plpy_util.h17
-rw-r--r--src/pl/plpython/plpython.h106
-rw-r--r--src/pl/plpython/plpython3u--1.0.sql17
-rw-r--r--src/pl/plpython/plpython3u.control7
-rw-r--r--src/pl/plpython/po/cs.po503
-rw-r--r--src/pl/plpython/po/de.po451
-rw-r--r--src/pl/plpython/po/el.po462
-rw-r--r--src/pl/plpython/po/es.po454
-rw-r--r--src/pl/plpython/po/fr.po590
-rw-r--r--src/pl/plpython/po/it.po470
-rw-r--r--src/pl/plpython/po/ja.po497
-rw-r--r--src/pl/plpython/po/ka.po462
-rw-r--r--src/pl/plpython/po/ko.po501
-rw-r--r--src/pl/plpython/po/pl.po507
-rw-r--r--src/pl/plpython/po/pt_BR.po461
-rw-r--r--src/pl/plpython/po/ru.po561
-rw-r--r--src/pl/plpython/po/sv.po449
-rw-r--r--src/pl/plpython/po/tr.po537
-rw-r--r--src/pl/plpython/po/uk.po452
-rw-r--r--src/pl/plpython/po/vi.po485
-rw-r--r--src/pl/plpython/po/zh_CN.po459
-rw-r--r--src/pl/plpython/spiexceptions.h998
-rw-r--r--src/pl/plpython/sql/plpython_call.sql80
-rw-r--r--src/pl/plpython/sql/plpython_composite.sql224
-rw-r--r--src/pl/plpython/sql/plpython_do.sql3
-rw-r--r--src/pl/plpython/sql/plpython_drop.sql6
-rw-r--r--src/pl/plpython/sql/plpython_ereport.sql135
-rw-r--r--src/pl/plpython/sql/plpython_error.sql357
-rw-r--r--src/pl/plpython/sql/plpython_global.sql38
-rw-r--r--src/pl/plpython/sql/plpython_import.sql68
-rw-r--r--src/pl/plpython/sql/plpython_newline.sql20
-rw-r--r--src/pl/plpython/sql/plpython_params.sql42
-rw-r--r--src/pl/plpython/sql/plpython_populate.sql27
-rw-r--r--src/pl/plpython/sql/plpython_quote.sql33
-rw-r--r--src/pl/plpython/sql/plpython_record.sql163
-rw-r--r--src/pl/plpython/sql/plpython_schema.sql39
-rw-r--r--src/pl/plpython/sql/plpython_setof.sql97
-rw-r--r--src/pl/plpython/sql/plpython_spi.sql322
-rw-r--r--src/pl/plpython/sql/plpython_subtransaction.sql262
-rw-r--r--src/pl/plpython/sql/plpython_test.sql52
-rw-r--r--src/pl/plpython/sql/plpython_transaction.sql182
-rw-r--r--src/pl/plpython/sql/plpython_trigger.sql469
-rw-r--r--src/pl/plpython/sql/plpython_types.sql622
-rw-r--r--src/pl/plpython/sql/plpython_unicode.sql45
-rw-r--r--src/pl/plpython/sql/plpython_void.sql22
-rw-r--r--src/pl/tcl/.gitignore6
-rw-r--r--src/pl/tcl/Makefile103
-rw-r--r--src/pl/tcl/expected/pltcl_call.out72
-rw-r--r--src/pl/tcl/expected/pltcl_queries.out397
-rw-r--r--src/pl/tcl/expected/pltcl_setup.out263
-rw-r--r--src/pl/tcl/expected/pltcl_start_proc.out31
-rw-r--r--src/pl/tcl/expected/pltcl_subxact.out143
-rw-r--r--src/pl/tcl/expected/pltcl_transaction.out149
-rw-r--r--src/pl/tcl/expected/pltcl_trigger.out888
-rw-r--r--src/pl/tcl/expected/pltcl_unicode.out45
-rw-r--r--src/pl/tcl/generate-pltclerrcodes.pl40
-rw-r--r--src/pl/tcl/nls.mk6
-rw-r--r--src/pl/tcl/pltcl--1.0.sql12
-rw-r--r--src/pl/tcl/pltcl.c3291
-rw-r--r--src/pl/tcl/pltcl.control8
-rw-r--r--src/pl/tcl/pltclerrcodes.h998
-rw-r--r--src/pl/tcl/pltclu--1.0.sql9
-rw-r--r--src/pl/tcl/pltclu.control7
-rw-r--r--src/pl/tcl/po/cs.po117
-rw-r--r--src/pl/tcl/po/de.po115
-rw-r--r--src/pl/tcl/po/el.po118
-rw-r--r--src/pl/tcl/po/es.po119
-rw-r--r--src/pl/tcl/po/fr.po140
-rw-r--r--src/pl/tcl/po/it.po127
-rw-r--r--src/pl/tcl/po/ja.po117
-rw-r--r--src/pl/tcl/po/ka.po117
-rw-r--r--src/pl/tcl/po/ko.po118
-rw-r--r--src/pl/tcl/po/pl.po136
-rw-r--r--src/pl/tcl/po/pt_BR.po117
-rw-r--r--src/pl/tcl/po/ru.po135
-rw-r--r--src/pl/tcl/po/sv.po116
-rw-r--r--src/pl/tcl/po/tr.po117
-rw-r--r--src/pl/tcl/po/uk.po115
-rw-r--r--src/pl/tcl/po/vi.po107
-rw-r--r--src/pl/tcl/po/zh_CN.po111
-rw-r--r--src/pl/tcl/sql/pltcl_call.sql78
-rw-r--r--src/pl/tcl/sql/pltcl_queries.sql166
-rw-r--r--src/pl/tcl/sql/pltcl_setup.sql278
-rw-r--r--src/pl/tcl/sql/pltcl_start_proc.sql21
-rw-r--r--src/pl/tcl/sql/pltcl_subxact.sql95
-rw-r--r--src/pl/tcl/sql/pltcl_transaction.sql135
-rw-r--r--src/pl/tcl/sql/pltcl_trigger.sql603
-rw-r--r--src/pl/tcl/sql/pltcl_unicode.sql38
269 files changed, 117910 insertions, 0 deletions
diff --git a/src/pl/Makefile b/src/pl/Makefile
new file mode 100644
index 0000000..c4a0d1c
--- /dev/null
+++ b/src/pl/Makefile
@@ -0,0 +1,36 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/pl (procedural languages)
+#
+# Copyright (c) 1994, Regents of the University of California
+#
+# src/pl/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/pl
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+
+SUBDIRS = plpgsql
+
+ifeq ($(with_perl), yes)
+SUBDIRS += plperl
+else
+ALWAYS_SUBDIRS += plperl
+endif
+
+ifeq ($(with_python), yes)
+SUBDIRS += plpython
+else
+ALWAYS_SUBDIRS += plpython
+endif
+
+ifeq ($(with_tcl), yes)
+SUBDIRS += tcl
+else
+ALWAYS_SUBDIRS += tcl
+endif
+
+$(recurse)
+$(recurse_always)
diff --git a/src/pl/plperl/.gitignore b/src/pl/plperl/.gitignore
new file mode 100644
index 0000000..1a79873
--- /dev/null
+++ b/src/pl/plperl/.gitignore
@@ -0,0 +1,9 @@
+/SPI.c
+/Util.c
+/perlchunks.h
+/plperl_opmask.h
+
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
new file mode 100644
index 0000000..a2e6410
--- /dev/null
+++ b/src/pl/plperl/GNUmakefile
@@ -0,0 +1,130 @@
+# Makefile for PL/Perl
+# src/pl/plperl/GNUmakefile
+
+subdir = src/pl/plperl
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+ifeq ($(PORTNAME), win32)
+override CPPFLAGS += -DPLPERL_HAVE_UID_GID
+# Perl on win32 contains /* within comment all over the header file,
+# so disable this warning.
+override CPPFLAGS += -Wno-comment
+endif
+
+# Note: we need to include the perl_includespec directory last,
+# probably because it sometimes contains some header files with names
+# that clash with some of ours, or with some that we include, notably on
+# Windows.
+override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) $(perl_embed_ccflags) $(perl_includespec)
+
+# this is often, but not always, the same directory named by perl_includespec
+rpathdir = $(perl_archlibexp)/CORE
+
+PGFILEDESC = "PL/Perl - procedural language"
+
+NAME = plperl
+
+OBJS = plperl.o SPI.o Util.o $(WIN32RES)
+
+DATA = plperl.control plperl--1.0.sql \
+ plperlu.control plperlu--1.0.sql
+
+PERLCHUNKS = plc_perlboot.pl plc_trusted.pl
+
+# Perl on win32 ships with import libraries only for Microsoft Visual C++,
+# which are not compatible with mingw gcc. Therefore we need to build a
+# new import library to link with.
+ifeq ($(PORTNAME), win32)
+
+perlwithver := $(subst -l,,$(filter -l%, $(perl_embed_ldflags)))
+PERLDLL := $(dir $(subst ',,$(PERL)))$(perlwithver).dll
+# we no longer want to include the original -l spec in SHLIB_LINK
+override perl_embed_ldflags :=
+
+OBJS += lib$(perlwithver).a
+
+lib$(perlwithver).a: $(perlwithver).def
+ dlltool --dllname $(perlwithver).dll --def $(perlwithver).def --output-lib lib$(perlwithver).a
+
+$(perlwithver).def: $(PERLDLL)
+ gendef - $^ > $@
+
+endif # win32
+
+
+SHLIB_LINK = $(perl_embed_ldflags)
+
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+ plperl_elog plperl_util plperl_init plperlu plperl_array \
+ plperl_call plperl_transaction
+# if Perl can support two interpreters in one backend,
+# test plperl-and-plperlu cases
+ifneq ($(PERL),)
+ifeq ($(shell $(PERL) -V:usemultiplicity), usemultiplicity='define';)
+ REGRESS += plperl_plperlu
+endif
+endif
+
+# where to find xsubpp for building XS.
+XSUBPPDIR = $(shell $(PERL) -e 'use List::Util qw(first); print first { -r "$$_/ExtUtils/xsubpp" } @INC')
+
+include $(top_srcdir)/src/Makefile.shlib
+
+plperl.o: perlchunks.h plperl_opmask.h plperl_helpers.h
+
+plperl_opmask.h: plperl_opmask.pl
+ @if [ x"$(perl_privlibexp)" = x"" ]; then echo "configure switch --with-perl was not specified."; exit 1; fi
+ $(PERL) $< $@
+
+perlchunks.h: $(PERLCHUNKS)
+ @if [ x"$(perl_privlibexp)" = x"" ]; then echo "configure switch --with-perl was not specified."; exit 1; fi
+ $(PERL) $(srcdir)/text2macro.pl --strip='^(\#.*|\s*)$$' $^ > $@
+
+all: all-lib
+
+%.c: %.xs
+ @if [ x"$(perl_privlibexp)" = x"" ]; then echo "configure switch --with-perl was not specified."; exit 1; fi
+# xsubpp -output option is required for coverage+vpath, but requires Perl 5.9.3
+ifeq ($(enable_coverage)$(vpath_build),yesyes)
+ $(PERL) $(XSUBPPDIR)/ExtUtils/xsubpp -typemap $(perl_privlibexp)/ExtUtils/typemap -output $@ $<
+else
+ $(PERL) $(XSUBPPDIR)/ExtUtils/xsubpp -typemap $(perl_privlibexp)/ExtUtils/typemap $< >$@
+endif
+
+
+install: all install-lib install-data
+
+installdirs: installdirs-lib
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
+
+uninstall: uninstall-lib uninstall-data
+
+install-data: installdirs
+ $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(srcdir)/plperl.h $(srcdir)/ppport.h $(srcdir)/plperl_helpers.h '$(DESTDIR)$(includedir_server)'
+
+uninstall-data:
+ rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plperl.h ppport.h)
+
+.PHONY: install-data uninstall-data
+
+
+check: submake
+ $(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
+
+installcheck: submake
+ $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS)
+
+.PHONY: submake
+submake:
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
+
+clean distclean maintainer-clean: clean-lib
+ rm -f SPI.c Util.c $(OBJS) perlchunks.h plperl_opmask.h
+ rm -rf $(pg_regress_clean_files)
+ifeq ($(PORTNAME), win32)
+ rm -f $(perlwithver).def
+endif
diff --git a/src/pl/plperl/README b/src/pl/plperl/README
new file mode 100644
index 0000000..e61dd57
--- /dev/null
+++ b/src/pl/plperl/README
@@ -0,0 +1,10 @@
+src/pl/plperl/README
+
+PL/Perl allows you to write PostgreSQL functions and procedures in
+Perl. To include PL/Perl in the build use './configure --with-perl'.
+To build from this directory use 'make all; make install'. libperl
+must have been built as a shared library, which is usually not the
+case in standard installations.
+
+Consult the PostgreSQL User's Guide and the INSTALL file in the
+top-level directory of the source distribution for more information.
diff --git a/src/pl/plperl/SPI.xs b/src/pl/plperl/SPI.xs
new file mode 100644
index 0000000..b2db3bd
--- /dev/null
+++ b/src/pl/plperl/SPI.xs
@@ -0,0 +1,163 @@
+/**********************************************************************
+ * PostgreSQL::InServer::SPI
+ *
+ * SPI interface for plperl.
+ *
+ * src/pl/plperl/SPI.xs
+ *
+ **********************************************************************/
+
+/* this must be first: */
+#include "postgres.h"
+
+/* perl stuff */
+#define PG_NEED_PERL_XSUB_H
+#include "plperl.h"
+#include "plperl_helpers.h"
+
+
+MODULE = PostgreSQL::InServer::SPI PREFIX = spi_
+
+PROTOTYPES: ENABLE
+VERSIONCHECK: DISABLE
+
+SV*
+spi_spi_exec_query(sv, ...)
+ SV* sv;
+ PREINIT:
+ HV *ret_hash;
+ int limit = 0;
+ char *query;
+ CODE:
+ if (items > 2)
+ croak("Usage: spi_exec_query(query, limit) "
+ "or spi_exec_query(query)");
+ if (items == 2)
+ limit = SvIV(ST(1));
+ query = sv2cstr(sv);
+ ret_hash = plperl_spi_exec(query, limit);
+ pfree(query);
+ RETVAL = newRV_noinc((SV*) ret_hash);
+ OUTPUT:
+ RETVAL
+
+void
+spi_return_next(rv)
+ SV *rv;
+ CODE:
+ plperl_return_next(rv);
+
+SV *
+spi_spi_query(sv)
+ SV *sv;
+ CODE:
+ char* query = sv2cstr(sv);
+ RETVAL = plperl_spi_query(query);
+ pfree(query);
+ OUTPUT:
+ RETVAL
+
+SV *
+spi_spi_fetchrow(sv)
+ SV* sv;
+ CODE:
+ char* cursor = sv2cstr(sv);
+ RETVAL = plperl_spi_fetchrow(cursor);
+ pfree(cursor);
+ OUTPUT:
+ RETVAL
+
+SV*
+spi_spi_prepare(sv, ...)
+ SV* sv;
+ CODE:
+ int i;
+ SV** argv;
+ char* query = sv2cstr(sv);
+ if (items < 1)
+ Perl_croak(aTHX_ "Usage: spi_prepare(query, ...)");
+ argv = ( SV**) palloc(( items - 1) * sizeof(SV*));
+ for ( i = 1; i < items; i++)
+ argv[i - 1] = ST(i);
+ RETVAL = plperl_spi_prepare(query, items - 1, argv);
+ pfree( argv);
+ pfree(query);
+ OUTPUT:
+ RETVAL
+
+SV*
+spi_spi_exec_prepared(sv, ...)
+ SV* sv;
+ PREINIT:
+ HV *ret_hash;
+ CODE:
+ HV *attr = NULL;
+ int i, offset = 1, argc;
+ SV ** argv;
+ char *query = sv2cstr(sv);
+ if ( items < 1)
+ Perl_croak(aTHX_ "Usage: spi_exec_prepared(query, [\\%%attr,] "
+ "[\\@bind_values])");
+ if ( items > 1 && SvROK( ST( 1)) && SvTYPE( SvRV( ST( 1))) == SVt_PVHV)
+ {
+ attr = ( HV*) SvRV(ST(1));
+ offset++;
+ }
+ argc = items - offset;
+ argv = ( SV**) palloc( argc * sizeof(SV*));
+ for ( i = 0; offset < items; offset++, i++)
+ argv[i] = ST(offset);
+ ret_hash = plperl_spi_exec_prepared(query, attr, argc, argv);
+ RETVAL = newRV_noinc((SV*)ret_hash);
+ pfree( argv);
+ pfree(query);
+ OUTPUT:
+ RETVAL
+
+SV*
+spi_spi_query_prepared(sv, ...)
+ SV * sv;
+ CODE:
+ int i;
+ SV ** argv;
+ char *query = sv2cstr(sv);
+ if ( items < 1)
+ Perl_croak(aTHX_ "Usage: spi_query_prepared(query, "
+ "[\\@bind_values])");
+ argv = ( SV**) palloc(( items - 1) * sizeof(SV*));
+ for ( i = 1; i < items; i++)
+ argv[i - 1] = ST(i);
+ RETVAL = plperl_spi_query_prepared(query, items - 1, argv);
+ pfree( argv);
+ pfree(query);
+ OUTPUT:
+ RETVAL
+
+void
+spi_spi_freeplan(sv)
+ SV *sv;
+ CODE:
+ char *query = sv2cstr(sv);
+ plperl_spi_freeplan(query);
+ pfree(query);
+
+void
+spi_spi_cursor_close(sv)
+ SV *sv;
+ CODE:
+ char *cursor = sv2cstr(sv);
+ plperl_spi_cursor_close(cursor);
+ pfree(cursor);
+
+void
+spi_spi_commit()
+ CODE:
+ plperl_spi_commit();
+
+void
+spi_spi_rollback()
+ CODE:
+ plperl_spi_rollback();
+
+BOOT:
+ items = 0; /* avoid 'unused variable' warning */
diff --git a/src/pl/plperl/Util.xs b/src/pl/plperl/Util.xs
new file mode 100644
index 0000000..47eba59
--- /dev/null
+++ b/src/pl/plperl/Util.xs
@@ -0,0 +1,189 @@
+/**********************************************************************
+ * PostgreSQL::InServer::Util
+ *
+ * src/pl/plperl/Util.xs
+ *
+ * Defines plperl interfaces for general-purpose utilities.
+ * This module is bootstrapped as soon as an interpreter is initialized.
+ * Currently doesn't define a PACKAGE= so all subs are in main:: to avoid
+ * the need for explicit importing.
+ *
+ **********************************************************************/
+
+/* this must be first: */
+#include "postgres.h"
+
+#include "fmgr.h"
+#include "utils/builtins.h"
+#include "utils/bytea.h" /* for byteain & byteaout */
+
+/* perl stuff */
+#define PG_NEED_PERL_XSUB_H
+#include "plperl.h"
+#include "plperl_helpers.h"
+
+
+static text *
+sv2text(SV *sv)
+{
+ char *str = sv2cstr(sv);
+ text *text;
+
+ text = cstring_to_text(str);
+ pfree(str);
+ return text;
+}
+
+MODULE = PostgreSQL::InServer::Util PREFIX = util_
+
+PROTOTYPES: ENABLE
+VERSIONCHECK: DISABLE
+
+int
+_aliased_constants()
+ PROTOTYPE:
+ ALIAS:
+ DEBUG = DEBUG2
+ LOG = LOG
+ INFO = INFO
+ NOTICE = NOTICE
+ WARNING = WARNING
+ ERROR = ERROR
+ CODE:
+ /* uses the ALIAS value as the return value */
+ RETVAL = ix;
+ OUTPUT:
+ RETVAL
+
+
+void
+util_elog(level, msg)
+ int level
+ SV *msg
+ CODE:
+ if (level > ERROR) /* no PANIC allowed thanks */
+ level = ERROR;
+ if (level < DEBUG5)
+ level = DEBUG5;
+ plperl_util_elog(level, msg);
+
+SV *
+util_quote_literal(sv)
+ SV *sv
+ CODE:
+ if (!sv || !SvOK(sv)) {
+ RETVAL = &PL_sv_undef;
+ }
+ else {
+ text *arg = sv2text(sv);
+ text *quoted = DatumGetTextPP(DirectFunctionCall1(quote_literal, PointerGetDatum(arg)));
+ char *str;
+
+ pfree(arg);
+ str = text_to_cstring(quoted);
+ RETVAL = cstr2sv(str);
+ pfree(str);
+ }
+ OUTPUT:
+ RETVAL
+
+SV *
+util_quote_nullable(sv)
+ SV *sv
+ CODE:
+ if (!sv || !SvOK(sv))
+ {
+ RETVAL = cstr2sv("NULL");
+ }
+ else
+ {
+ text *arg = sv2text(sv);
+ text *quoted = DatumGetTextPP(DirectFunctionCall1(quote_nullable, PointerGetDatum(arg)));
+ char *str;
+
+ pfree(arg);
+ str = text_to_cstring(quoted);
+ RETVAL = cstr2sv(str);
+ pfree(str);
+ }
+ OUTPUT:
+ RETVAL
+
+SV *
+util_quote_ident(sv)
+ SV *sv
+ PREINIT:
+ text *arg;
+ text *quoted;
+ char *str;
+ CODE:
+ arg = sv2text(sv);
+ quoted = DatumGetTextPP(DirectFunctionCall1(quote_ident, PointerGetDatum(arg)));
+
+ pfree(arg);
+ str = text_to_cstring(quoted);
+ RETVAL = cstr2sv(str);
+ pfree(str);
+ OUTPUT:
+ RETVAL
+
+SV *
+util_decode_bytea(sv)
+ SV *sv
+ PREINIT:
+ char *arg;
+ text *ret;
+ CODE:
+ arg = SvPVbyte_nolen(sv);
+ ret = DatumGetTextPP(DirectFunctionCall1(byteain, PointerGetDatum(arg)));
+ /* not cstr2sv because this is raw bytes not utf8'able */
+ RETVAL = newSVpvn(VARDATA_ANY(ret), VARSIZE_ANY_EXHDR(ret));
+ OUTPUT:
+ RETVAL
+
+SV *
+util_encode_bytea(sv)
+ SV *sv
+ PREINIT:
+ text *arg;
+ char *ret;
+ STRLEN len;
+ CODE:
+ /* not sv2text because this is raw bytes not utf8'able */
+ ret = SvPVbyte(sv, len);
+ arg = cstring_to_text_with_len(ret, len);
+ ret = DatumGetCString(DirectFunctionCall1(byteaout, PointerGetDatum(arg)));
+ RETVAL = cstr2sv(ret);
+ OUTPUT:
+ RETVAL
+
+SV *
+looks_like_number(sv)
+ SV *sv
+ CODE:
+ if (!SvOK(sv))
+ RETVAL = &PL_sv_undef;
+ else if ( looks_like_number(sv) )
+ RETVAL = &PL_sv_yes;
+ else
+ RETVAL = &PL_sv_no;
+ OUTPUT:
+ RETVAL
+
+SV *
+encode_typed_literal(sv, typname)
+ SV *sv
+ char *typname;
+ PREINIT:
+ char *outstr;
+ CODE:
+ outstr = plperl_sv_to_literal(sv, typname);
+ if (outstr == NULL)
+ RETVAL = &PL_sv_undef;
+ else
+ RETVAL = cstr2sv(outstr);
+ OUTPUT:
+ RETVAL
+
+BOOT:
+ items = 0; /* avoid 'unused variable' warning */
diff --git a/src/pl/plperl/expected/plperl.out b/src/pl/plperl/expected/plperl.out
new file mode 100644
index 0000000..e3d7c88
--- /dev/null
+++ b/src/pl/plperl/expected/plperl.out
@@ -0,0 +1,794 @@
+--
+-- Test result value processing
+--
+CREATE OR REPLACE FUNCTION perl_int(int) RETURNS INTEGER AS $$
+return undef;
+$$ LANGUAGE plperl;
+SELECT perl_int(11);
+ perl_int
+----------
+
+(1 row)
+
+SELECT * FROM perl_int(42);
+ perl_int
+----------
+
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_int(int) RETURNS INTEGER AS $$
+return $_[0] + 1;
+$$ LANGUAGE plperl;
+SELECT perl_int(11);
+ perl_int
+----------
+ 12
+(1 row)
+
+SELECT * FROM perl_int(42);
+ perl_int
+----------
+ 43
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
+return undef;
+$$ LANGUAGE plperl;
+SELECT perl_set_int(5);
+ perl_set_int
+--------------
+(0 rows)
+
+SELECT * FROM perl_set_int(5);
+ perl_set_int
+--------------
+(0 rows)
+
+CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
+return [0..$_[0]];
+$$ LANGUAGE plperl;
+SELECT perl_set_int(5);
+ perl_set_int
+--------------
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+(6 rows)
+
+SELECT * FROM perl_set_int(5);
+ perl_set_int
+--------------
+ 0
+ 1
+ 2
+ 3
+ 4
+ 5
+(6 rows)
+
+CREATE TYPE testnestperl AS (f5 integer[]);
+CREATE TYPE testrowperl AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$
+ return undef;
+$$ LANGUAGE plperl;
+SELECT perl_row();
+ perl_row
+----------
+
+(1 row)
+
+SELECT * FROM perl_row();
+ f1 | f2 | f3 | f4
+----+----+----+----
+ | | |
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world', 'f4' => { 'f5' => [[1]] } };
+$$ LANGUAGE plperl;
+SELECT perl_row();
+ perl_row
+---------------------------
+ (1,hello,world,"({{1}})")
+(1 row)
+
+SELECT * FROM perl_row();
+ f1 | f2 | f3 | f4
+----+-------+-------+---------
+ 1 | hello | world | ({{1}})
+(1 row)
+
+-- test returning a composite literal
+CREATE OR REPLACE FUNCTION perl_row_lit() RETURNS testrowperl AS $$
+ return '(1,hello,world,"({{1}})")';
+$$ LANGUAGE plperl;
+SELECT perl_row_lit();
+ perl_row_lit
+---------------------------
+ (1,hello,world,"({{1}})")
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return undef;
+$$ LANGUAGE plperl;
+SELECT perl_set();
+ perl_set
+----------
+(0 rows)
+
+SELECT * FROM perl_set();
+ f1 | f2 | f3 | f4
+----+----+----+----
+(0 rows)
+
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ undef,
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => {} },
+ { f1 => 4, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => undef }},
+ { f1 => 5, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => '{1}' }},
+ { f1 => 6, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => [1] }},
+ ];
+$$ LANGUAGE plperl;
+SELECT perl_set();
+ERROR: SETOF-composite-returning PL/Perl function must call return_next with reference to hash
+CONTEXT: PL/Perl function "perl_set"
+SELECT * FROM perl_set();
+ERROR: SETOF-composite-returning PL/Perl function must call return_next with reference to hash
+CONTEXT: PL/Perl function "perl_set"
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL', 'f4' => undef },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => {} },
+ { f1 => 4, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => undef }},
+ { f1 => 5, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => '{1}' }},
+ { f1 => 6, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => [1] }},
+ { f1 => 7, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => '({1})' },
+ ];
+$$ LANGUAGE plperl;
+SELECT perl_set();
+ perl_set
+---------------------------
+ (1,Hello,World,)
+ (2,Hello,PostgreSQL,)
+ (3,Hello,PL/Perl,"()")
+ (4,Hello,PL/Perl,"()")
+ (5,Hello,PL/Perl,"({1})")
+ (6,Hello,PL/Perl,"({1})")
+ (7,Hello,PL/Perl,"({1})")
+(7 rows)
+
+SELECT * FROM perl_set();
+ f1 | f2 | f3 | f4
+----+-------+------------+-------
+ 1 | Hello | World |
+ 2 | Hello | PostgreSQL |
+ 3 | Hello | PL/Perl | ()
+ 4 | Hello | PL/Perl | ()
+ 5 | Hello | PL/Perl | ({1})
+ 6 | Hello | PL/Perl | ({1})
+ 7 | Hello | PL/Perl | ({1})
+(7 rows)
+
+CREATE OR REPLACE FUNCTION perl_record() RETURNS record AS $$
+ return undef;
+$$ LANGUAGE plperl;
+SELECT perl_record();
+ perl_record
+-------------
+
+(1 row)
+
+SELECT * FROM perl_record();
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: SELECT * FROM perl_record();
+ ^
+SELECT * FROM perl_record() AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+ f1 | f2 | f3 | f4
+----+----+----+----
+ | | |
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_record() RETURNS record AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world', 'f4' => { 'f5' => [1] } };
+$$ LANGUAGE plperl;
+SELECT perl_record();
+ERROR: function returning record called in context that cannot accept type record
+CONTEXT: PL/Perl function "perl_record"
+SELECT * FROM perl_record();
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: SELECT * FROM perl_record();
+ ^
+SELECT * FROM perl_record() AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+ f1 | f2 | f3 | f4
+----+-------+-------+-------
+ 1 | hello | world | ({1})
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return undef;
+$$ LANGUAGE plperl;
+SELECT perl_record_set();
+ perl_record_set
+-----------------
+(0 rows)
+
+SELECT * FROM perl_record_set();
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: SELECT * FROM perl_record_set();
+ ^
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+ f1 | f2 | f3
+----+----+----
+(0 rows)
+
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ undef,
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+SELECT perl_record_set();
+ERROR: function returning record called in context that cannot accept type record
+CONTEXT: PL/Perl function "perl_record_set"
+SELECT * FROM perl_record_set();
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: SELECT * FROM perl_record_set();
+ ^
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+ERROR: SETOF-composite-returning PL/Perl function must call return_next with reference to hash
+CONTEXT: PL/Perl function "perl_record_set"
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+SELECT perl_record_set();
+ERROR: function returning record called in context that cannot accept type record
+CONTEXT: PL/Perl function "perl_record_set"
+SELECT * FROM perl_record_set();
+ERROR: a column definition list is required for functions returning "record"
+LINE 1: SELECT * FROM perl_record_set();
+ ^
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+ f1 | f2 | f3
+----+-------+------------
+ 1 | Hello | World
+ 2 | Hello | PostgreSQL
+ 3 | Hello | PL/Perl
+(3 rows)
+
+CREATE OR REPLACE FUNCTION
+perl_out_params(f1 out integer, f2 out text, f3 out text) AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world'};
+$$ LANGUAGE plperl;
+SELECT perl_out_params();
+ perl_out_params
+-----------------
+ (1,hello,world)
+(1 row)
+
+SELECT * FROM perl_out_params();
+ f1 | f2 | f3
+----+-------+-------
+ 1 | hello | world
+(1 row)
+
+SELECT (perl_out_params()).f2;
+ f2
+-------
+ hello
+(1 row)
+
+CREATE OR REPLACE FUNCTION
+perl_out_params_set(out f1 integer, out f2 text, out f3 text)
+RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+SELECT perl_out_params_set();
+ perl_out_params_set
+----------------------
+ (1,Hello,World)
+ (2,Hello,PostgreSQL)
+ (3,Hello,PL/Perl)
+(3 rows)
+
+SELECT * FROM perl_out_params_set();
+ f1 | f2 | f3
+----+-------+------------
+ 1 | Hello | World
+ 2 | Hello | PostgreSQL
+ 3 | Hello | PL/Perl
+(3 rows)
+
+SELECT (perl_out_params_set()).f3;
+ f3
+------------
+ World
+ PostgreSQL
+ PL/Perl
+(3 rows)
+
+--
+-- Check behavior with erroneous return values
+--
+CREATE TYPE footype AS (x INTEGER, y INTEGER);
+CREATE OR REPLACE FUNCTION foo_good() RETURNS SETOF footype AS $$
+return [
+ {x => 1, y => 2},
+ {x => 3, y => 4}
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_good();
+ x | y
+---+---
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+ return {y => 3, z => 4};
+$$ LANGUAGE plperl;
+SELECT * FROM foo_bad();
+ERROR: Perl hash contains nonexistent column "z"
+CONTEXT: PL/Perl function "foo_bad"
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+return 42;
+$$ LANGUAGE plperl;
+SELECT * FROM foo_bad();
+ERROR: malformed record literal: "42"
+DETAIL: Missing left parenthesis.
+CONTEXT: PL/Perl function "foo_bad"
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+return [
+ [1, 2],
+ [3, 4]
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_bad();
+ERROR: cannot convert Perl array to non-array type footype
+CONTEXT: PL/Perl function "foo_bad"
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+ return 42;
+$$ LANGUAGE plperl;
+SELECT * FROM foo_set_bad();
+ERROR: set-returning PL/Perl function must return reference to array or use return_next
+CONTEXT: PL/Perl function "foo_set_bad"
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+ return {y => 3, z => 4};
+$$ LANGUAGE plperl;
+SELECT * FROM foo_set_bad();
+ERROR: set-returning PL/Perl function must return reference to array or use return_next
+CONTEXT: PL/Perl function "foo_set_bad"
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+return [
+ [1, 2],
+ [3, 4]
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_set_bad();
+ERROR: SETOF-composite-returning PL/Perl function must call return_next with reference to hash
+CONTEXT: PL/Perl function "foo_set_bad"
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+return [
+ {y => 3, z => 4}
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_set_bad();
+ERROR: Perl hash contains nonexistent column "z"
+CONTEXT: PL/Perl function "foo_set_bad"
+CREATE DOMAIN orderedfootype AS footype CHECK ((VALUE).x <= (VALUE).y);
+CREATE OR REPLACE FUNCTION foo_ordered() RETURNS orderedfootype AS $$
+ return {x => 3, y => 4};
+$$ LANGUAGE plperl;
+SELECT * FROM foo_ordered();
+ x | y
+---+---
+ 3 | 4
+(1 row)
+
+CREATE OR REPLACE FUNCTION foo_ordered() RETURNS orderedfootype AS $$
+ return {x => 5, y => 4};
+$$ LANGUAGE plperl;
+SELECT * FROM foo_ordered(); -- fail
+ERROR: value for domain orderedfootype violates check constraint "orderedfootype_check"
+CONTEXT: PL/Perl function "foo_ordered"
+CREATE OR REPLACE FUNCTION foo_ordered_set() RETURNS SETOF orderedfootype AS $$
+return [
+ {x => 3, y => 4},
+ {x => 4, y => 7}
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_ordered_set();
+ x | y
+---+---
+ 3 | 4
+ 4 | 7
+(2 rows)
+
+CREATE OR REPLACE FUNCTION foo_ordered_set() RETURNS SETOF orderedfootype AS $$
+return [
+ {x => 3, y => 4},
+ {x => 9, y => 7}
+];
+$$ LANGUAGE plperl;
+SELECT * FROM foo_ordered_set(); -- fail
+ERROR: value for domain orderedfootype violates check constraint "orderedfootype_check"
+CONTEXT: PL/Perl function "foo_ordered_set"
+--
+-- Check passing a tuple argument
+--
+CREATE OR REPLACE FUNCTION perl_get_field(footype, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+SELECT perl_get_field((11,12), 'x');
+ perl_get_field
+----------------
+ 11
+(1 row)
+
+SELECT perl_get_field((11,12), 'y');
+ perl_get_field
+----------------
+ 12
+(1 row)
+
+SELECT perl_get_field((11,12), 'z');
+ perl_get_field
+----------------
+
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_get_cfield(orderedfootype, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+SELECT perl_get_cfield((11,12), 'x');
+ perl_get_cfield
+-----------------
+ 11
+(1 row)
+
+SELECT perl_get_cfield((11,12), 'y');
+ perl_get_cfield
+-----------------
+ 12
+(1 row)
+
+SELECT perl_get_cfield((12,11), 'x'); -- fail
+ERROR: value for domain orderedfootype violates check constraint "orderedfootype_check"
+CREATE OR REPLACE FUNCTION perl_get_rfield(record, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+SELECT perl_get_rfield((11,12), 'f1');
+ perl_get_rfield
+-----------------
+ 11
+(1 row)
+
+SELECT perl_get_rfield((11,12)::footype, 'y');
+ perl_get_rfield
+-----------------
+ 12
+(1 row)
+
+SELECT perl_get_rfield((11,12)::orderedfootype, 'x');
+ perl_get_rfield
+-----------------
+ 11
+(1 row)
+
+SELECT perl_get_rfield((12,11)::orderedfootype, 'x'); -- fail
+ERROR: value for domain orderedfootype violates check constraint "orderedfootype_check"
+--
+-- Test return_next
+--
+CREATE OR REPLACE FUNCTION perl_srf_rn() RETURNS SETOF RECORD AS $$
+my $i = 0;
+for ("World", "PostgreSQL", "PL/Perl") {
+ return_next({f1=>++$i, f2=>'Hello', f3=>$_});
+}
+return;
+$$ language plperl;
+SELECT * from perl_srf_rn() AS (f1 INTEGER, f2 TEXT, f3 TEXT);
+ f1 | f2 | f3
+----+-------+------------
+ 1 | Hello | World
+ 2 | Hello | PostgreSQL
+ 3 | Hello | PL/Perl
+(3 rows)
+
+--
+-- Test spi_query/spi_fetchrow
+--
+CREATE OR REPLACE FUNCTION perl_spi_func() RETURNS SETOF INTEGER AS $$
+my $x = spi_query("select 1 as a union select 2 as a");
+while (defined (my $y = spi_fetchrow($x))) {
+ return_next($y->{a});
+}
+return;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_func();
+ perl_spi_func
+---------------
+ 1
+ 2
+(2 rows)
+
+--
+-- Test spi_fetchrow abort
+--
+CREATE OR REPLACE FUNCTION perl_spi_func2() RETURNS INTEGER AS $$
+my $x = spi_query("select 1 as a union select 2 as a");
+spi_cursor_close( $x);
+return 0;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_func2();
+ perl_spi_func2
+----------------
+ 0
+(1 row)
+
+---
+--- Test recursion via SPI
+---
+CREATE OR REPLACE FUNCTION recurse(i int) RETURNS SETOF TEXT LANGUAGE plperl
+AS $$
+
+ my $i = shift;
+ foreach my $x (1..$i)
+ {
+ return_next "hello $x";
+ }
+ if ($i > 2)
+ {
+ my $z = $i-1;
+ my $cursor = spi_query("select * from recurse($z)");
+ while (defined(my $row = spi_fetchrow($cursor)))
+ {
+ return_next "recurse $i: $row->{recurse}";
+ }
+ }
+ return undef;
+
+$$;
+SELECT * FROM recurse(2);
+ recurse
+---------
+ hello 1
+ hello 2
+(2 rows)
+
+SELECT * FROM recurse(3);
+ recurse
+--------------------
+ hello 1
+ hello 2
+ hello 3
+ recurse 3: hello 1
+ recurse 3: hello 2
+(5 rows)
+
+---
+--- Test array return
+---
+CREATE OR REPLACE FUNCTION array_of_text() RETURNS TEXT[][]
+LANGUAGE plperl as $$
+ return [['a"b',undef,'c,d'],['e\\f',undef,'g']];
+$$;
+SELECT array_of_text();
+ array_of_text
+---------------------------------------
+ {{"a\"b",NULL,"c,d"},{"e\\f",NULL,g}}
+(1 row)
+
+--
+-- Test spi_prepare/spi_exec_prepared/spi_freeplan
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared(INTEGER) RETURNS INTEGER AS $$
+ my $x = spi_prepare('select $1 AS a', 'INTEGER');
+ my $q = spi_exec_prepared( $x, $_[0] + 1);
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared(42);
+ perl_spi_prepared
+-------------------
+ 43
+(1 row)
+
+--
+-- Test spi_prepare/spi_query_prepared/spi_freeplan
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_set(INTEGER, INTEGER) RETURNS SETOF INTEGER AS $$
+ my $x = spi_prepare('SELECT $1 AS a union select $2 as a', 'INT4', 'INT4');
+ my $q = spi_query_prepared( $x, 1+$_[0], 2+$_[1]);
+ while (defined (my $y = spi_fetchrow($q))) {
+ return_next $y->{a};
+ }
+ spi_freeplan($x);
+ return;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared_set(1,2);
+ perl_spi_prepared_set
+-----------------------
+ 2
+ 4
+(2 rows)
+
+--
+-- Test prepare with a type with spaces
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_double(double precision) RETURNS double precision AS $$
+ my $x = spi_prepare('SELECT 10.0 * $1 AS a', 'DOUBLE PRECISION');
+ my $q = spi_query_prepared($x,$_[0]);
+ my $result;
+ while (defined (my $y = spi_fetchrow($q))) {
+ $result = $y->{a};
+ }
+ spi_freeplan($x);
+ return $result;
+$$ LANGUAGE plperl;
+SELECT perl_spi_prepared_double(4.35) as "double precision";
+ double precision
+------------------
+ 43.5
+(1 row)
+
+--
+-- Test with a bad type
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_bad(double precision) RETURNS double precision AS $$
+ my $x = spi_prepare('SELECT 10.0 * $1 AS a', 'does_not_exist');
+ my $q = spi_query_prepared($x,$_[0]);
+ my $result;
+ while (defined (my $y = spi_fetchrow($q))) {
+ $result = $y->{a};
+ }
+ spi_freeplan($x);
+ return $result;
+$$ LANGUAGE plperl;
+SELECT perl_spi_prepared_bad(4.35) as "double precision";
+ERROR: type "does_not_exist" does not exist at line 2.
+CONTEXT: PL/Perl function "perl_spi_prepared_bad"
+-- Test with a row type
+CREATE OR REPLACE FUNCTION perl_spi_prepared() RETURNS INTEGER AS $$
+ my $x = spi_prepare('select $1::footype AS a', 'footype');
+ my $q = spi_exec_prepared( $x, '(1, 2)');
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a}->{x};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared();
+ perl_spi_prepared
+-------------------
+ 1
+(1 row)
+
+CREATE OR REPLACE FUNCTION perl_spi_prepared_row(footype) RETURNS footype AS $$
+ my $footype = shift;
+ my $x = spi_prepare('select $1 AS a', 'footype');
+ my $q = spi_exec_prepared( $x, {}, $footype );
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared_row('(1, 2)');
+ x | y
+---+---
+ 1 | 2
+(1 row)
+
+-- simple test of a DO block
+DO $$
+ $a = 'This is a test';
+ elog(NOTICE, $a);
+$$ LANGUAGE plperl;
+NOTICE: This is a test
+-- check that restricted operations are rejected in a plperl DO block
+DO $$ system("/nonesuch"); $$ LANGUAGE plperl;
+ERROR: 'system' trapped by operation mask at line 1.
+CONTEXT: PL/Perl anonymous code block
+DO $$ qx("/nonesuch"); $$ LANGUAGE plperl;
+ERROR: 'quoted execution (``, qx)' trapped by operation mask at line 1.
+CONTEXT: PL/Perl anonymous code block
+DO $$ open my $fh, "</nonesuch"; $$ LANGUAGE plperl;
+ERROR: 'open' trapped by operation mask at line 1.
+CONTEXT: PL/Perl anonymous code block
+-- check that eval is allowed and eval'd restricted ops are caught
+DO $$ eval q{chdir '.';}; warn "Caught: $@"; $$ LANGUAGE plperl;
+WARNING: Caught: 'chdir' trapped by operation mask at line 1.
+-- check that compiling do (dofile opcode) is allowed
+-- but that executing it for a file not already loaded (via require) dies
+DO $$ warn do "/dev/null"; $$ LANGUAGE plperl;
+ERROR: Unable to load /dev/null into plperl at line 1.
+CONTEXT: PL/Perl anonymous code block
+-- check that we can't "use" a module that's not been loaded already
+-- compile-time error: "Unable to load blib.pm into plperl"
+DO $$ use blib; $$ LANGUAGE plperl;
+ERROR: Unable to load blib.pm into plperl at line 1.
+BEGIN failed--compilation aborted at line 1.
+CONTEXT: PL/Perl anonymous code block
+-- check that we can "use" a module that has already been loaded
+-- runtime error: "Can't use string ("foo") as a SCALAR ref while "strict refs" in use
+DO $do$ use strict; my $name = "foo"; my $ref = $$name; $do$ LANGUAGE plperl;
+ERROR: Can't use string ("foo") as a SCALAR ref while "strict refs" in use at line 1.
+CONTEXT: PL/Perl anonymous code block
+-- check that we can "use warnings" (in this case to turn a warn into an error)
+-- yields "ERROR: Useless use of sort in void context."
+DO $do$ use warnings FATAL => qw(void) ; my @y; sort @y; 1; $do$ LANGUAGE plperl;
+ERROR: Useless use of sort in void context at line 1.
+CONTEXT: PL/Perl anonymous code block
+-- make sure functions marked as VOID without an explicit return work
+CREATE OR REPLACE FUNCTION myfuncs() RETURNS void AS $$
+ $_SHARED{myquote} = sub {
+ my $arg = shift;
+ $arg =~ s/(['\\])/\\$1/g;
+ return "'$arg'";
+ };
+$$ LANGUAGE plperl;
+SELECT myfuncs();
+ myfuncs
+---------
+
+(1 row)
+
+-- make sure we can't return an array as a scalar
+CREATE OR REPLACE FUNCTION text_arrayref() RETURNS text AS $$
+ return ['array'];
+$$ LANGUAGE plperl;
+SELECT text_arrayref();
+ERROR: cannot convert Perl array to non-array type text
+CONTEXT: PL/Perl function "text_arrayref"
+--- make sure we can't return a hash as a scalar
+CREATE OR REPLACE FUNCTION text_hashref() RETURNS text AS $$
+ return {'hash'=>1};
+$$ LANGUAGE plperl;
+SELECT text_hashref();
+ERROR: cannot convert Perl hash to non-composite type text
+CONTEXT: PL/Perl function "text_hashref"
+---- make sure we can't return a blessed object as a scalar
+CREATE OR REPLACE FUNCTION text_obj() RETURNS text AS $$
+ return bless({}, 'Fake::Object');
+$$ LANGUAGE plperl;
+SELECT text_obj();
+ERROR: cannot convert Perl hash to non-composite type text
+CONTEXT: PL/Perl function "text_obj"
+-- test looking through a scalar ref
+CREATE OR REPLACE FUNCTION text_scalarref() RETURNS text AS $$
+ my $str = 'str';
+ return \$str;
+$$ LANGUAGE plperl;
+SELECT text_scalarref();
+ text_scalarref
+----------------
+ str
+(1 row)
+
+-- check safe behavior when a function body is replaced during execution
+CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS $$
+ spi_exec_query('CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS \'return $_[0] * 3;\' LANGUAGE plperl;');
+ spi_exec_query('select self_modify(42) AS a');
+ return $_[0] * 2;
+$$ LANGUAGE plperl;
+SELECT self_modify(42);
+ self_modify
+-------------
+ 84
+(1 row)
+
+SELECT self_modify(42);
+ self_modify
+-------------
+ 126
+(1 row)
+
diff --git a/src/pl/plperl/expected/plperl_array.out b/src/pl/plperl/expected/plperl_array.out
new file mode 100644
index 0000000..bd04a06
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_array.out
@@ -0,0 +1,276 @@
+CREATE OR REPLACE FUNCTION plperl_sum_array(INTEGER[]) RETURNS text AS $$
+ my $array_arg = shift;
+ my $result = 0;
+ my @arrays;
+
+ push @arrays, @$array_arg;
+
+ while (@arrays > 0) {
+ my $el = shift @arrays;
+ if (is_array_ref($el)) {
+ push @arrays, @$el;
+ } else {
+ $result += $el;
+ }
+ }
+ return $result.' '.$array_arg;
+$$ LANGUAGE plperl;
+select plperl_sum_array('{1,2,NULL}');
+ plperl_sum_array
+------------------
+ 3 {1,2,NULL}
+(1 row)
+
+select plperl_sum_array('{}');
+ plperl_sum_array
+------------------
+ 0 {}
+(1 row)
+
+select plperl_sum_array('{{1,2,3}, {4,5,6}}');
+ plperl_sum_array
+----------------------
+ 21 {{1,2,3},{4,5,6}}
+(1 row)
+
+select plperl_sum_array('{{{1,2,3}, {4,5,6}}, {{7,8,9}, {10,11,12}}}');
+ plperl_sum_array
+---------------------------------------------
+ 78 {{{1,2,3},{4,5,6}},{{7,8,9},{10,11,12}}}
+(1 row)
+
+-- check whether we can handle arrays of maximum dimension (6)
+select plperl_sum_array(ARRAY[[[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],
+[[13,14],[15,16]]]],
+[[[[17,18],[19,20]],[[21,22],[23,24]]],[[[25,26],[27,28]],[[29,30],[31,32]]]]],
+[[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]],
+[[[[17,18],[19,20]],[[21,22],[23,24]]],[[[25,26],[27,28]],[[29,30],[31,32]]]]]]);
+ plperl_sum_array
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ 1056 {{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}},{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}}}
+(1 row)
+
+-- what would we do with the arrays exceeding maximum dimension (7)
+select plperl_sum_array('{{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},
+{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}},
+{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}}},
+{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}},
+{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}}}}'
+);
+ERROR: number of array dimensions (7) exceeds the maximum allowed (6)
+LINE 1: select plperl_sum_array('{{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{...
+ ^
+select plperl_sum_array('{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {10, 11, 12}}}');
+ERROR: malformed array literal: "{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {10, 11, 12}}}"
+LINE 1: select plperl_sum_array('{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {1...
+ ^
+DETAIL: Multidimensional arrays must have sub-arrays with matching dimensions.
+CREATE OR REPLACE FUNCTION plperl_concat(TEXT[]) RETURNS TEXT AS $$
+ my $array_arg = shift;
+ my $result = "";
+ my @arrays;
+
+ push @arrays, @$array_arg;
+ while (@arrays > 0) {
+ my $el = shift @arrays;
+ if (is_array_ref($el)) {
+ push @arrays, @$el;
+ } else {
+ $result .= $el;
+ }
+ }
+ return $result.' '.$array_arg;
+$$ LANGUAGE plperl;
+select plperl_concat('{"NULL","NULL","NULL''"}');
+ plperl_concat
+-------------------------------------
+ NULLNULLNULL' {"NULL","NULL",NULL'}
+(1 row)
+
+select plperl_concat('{{NULL,NULL,NULL}}');
+ plperl_concat
+---------------------
+ {{NULL,NULL,NULL}}
+(1 row)
+
+select plperl_concat('{"hello"," ","world!"}');
+ plperl_concat
+---------------------------------
+ hello world! {hello," ",world!}
+(1 row)
+
+-- array of rows --
+CREATE TYPE foo AS (bar INTEGER, baz TEXT);
+CREATE OR REPLACE FUNCTION plperl_array_of_rows(foo[]) RETURNS TEXT AS $$
+ my $array_arg = shift;
+ my $result = "";
+
+ for my $row_ref (@$array_arg) {
+ die "not a hash reference" unless (ref $row_ref eq "HASH");
+ $result .= $row_ref->{bar}." items of ".$row_ref->{baz}.";";
+ }
+ return $result .' '. $array_arg;
+$$ LANGUAGE plperl;
+select plperl_array_of_rows(ARRAY[ ROW(2, 'coffee'), ROW(0, 'sugar')]::foo[]);
+ plperl_array_of_rows
+----------------------------------------------------------------
+ 2 items of coffee;0 items of sugar; {"(2,coffee)","(0,sugar)"}
+(1 row)
+
+-- composite type containing arrays
+CREATE TYPE rowfoo AS (bar INTEGER, baz INTEGER[]);
+CREATE OR REPLACE FUNCTION plperl_sum_row_elements(rowfoo) RETURNS TEXT AS $$
+ my $row_ref = shift;
+ my $result;
+
+ if (ref $row_ref ne 'HASH') {
+ $result = 0;
+ }
+ else {
+ $result = $row_ref->{bar};
+ die "not an array reference".ref ($row_ref->{baz})
+ unless (is_array_ref($row_ref->{baz}));
+ # process a single-dimensional array
+ foreach my $elem (@{$row_ref->{baz}}) {
+ $result += $elem unless ref $elem;
+ }
+ }
+ return $result;
+$$ LANGUAGE plperl;
+select plperl_sum_row_elements(ROW(1, ARRAY[2,3,4,5,6,7,8,9,10])::rowfoo);
+ plperl_sum_row_elements
+-------------------------
+ 55
+(1 row)
+
+-- composite type containing array of another composite type, which, in order,
+-- contains an array of integers.
+CREATE TYPE rowbar AS (foo rowfoo[]);
+CREATE OR REPLACE FUNCTION plperl_sum_array_of_rows(rowbar) RETURNS TEXT AS $$
+ my $rowfoo_ref = shift;
+ my $result = 0;
+
+ if (ref $rowfoo_ref eq 'HASH') {
+ my $row_array_ref = $rowfoo_ref->{foo};
+ if (is_array_ref($row_array_ref)) {
+ foreach my $row_ref (@{$row_array_ref}) {
+ if (ref $row_ref eq 'HASH') {
+ $result += $row_ref->{bar};
+ die "not an array reference".ref ($row_ref->{baz})
+ unless (is_array_ref($row_ref->{baz}));
+ foreach my $elem (@{$row_ref->{baz}}) {
+ $result += $elem unless ref $elem;
+ }
+ }
+ else {
+ die "element baz is not a reference to a rowfoo";
+ }
+ }
+ } else {
+ die "not a reference to an array of rowfoo elements"
+ }
+ } else {
+ die "not a reference to type rowbar";
+ }
+ return $result;
+$$ LANGUAGE plperl;
+select plperl_sum_array_of_rows(ROW(ARRAY[ROW(1, ARRAY[2,3,4,5,6,7,8,9,10])::rowfoo,
+ROW(11, ARRAY[12,13,14,15,16,17,18,19,20])::rowfoo])::rowbar);
+ plperl_sum_array_of_rows
+--------------------------
+ 210
+(1 row)
+
+-- check arrays as out parameters
+CREATE OR REPLACE FUNCTION plperl_arrays_out(OUT INTEGER[]) AS $$
+ return [[1,2,3],[4,5,6]];
+$$ LANGUAGE plperl;
+select plperl_arrays_out();
+ plperl_arrays_out
+-------------------
+ {{1,2,3},{4,5,6}}
+(1 row)
+
+-- check that we can return the array we passed in
+CREATE OR REPLACE FUNCTION plperl_arrays_inout(INTEGER[]) returns INTEGER[] AS $$
+ return shift;
+$$ LANGUAGE plperl;
+select plperl_arrays_inout('{{1}, {2}, {3}}');
+ plperl_arrays_inout
+---------------------
+ {{1},{2},{3}}
+(1 row)
+
+-- check that we can return an array literal
+CREATE OR REPLACE FUNCTION plperl_arrays_inout_l(INTEGER[]) returns INTEGER[] AS $$
+ return shift.''; # stringify it
+$$ LANGUAGE plperl;
+select plperl_arrays_inout_l('{{1}, {2}, {3}}');
+ plperl_arrays_inout_l
+-----------------------
+ {{1},{2},{3}}
+(1 row)
+
+-- check output of multi-dimensional arrays
+CREATE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [['a'], ['b'], ['c']];
+$$ LANGUAGE plperl;
+select plperl_md_array_out();
+ plperl_md_array_out
+---------------------
+ {{a},{b},{c}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], []];
+$$ LANGUAGE plperl;
+select plperl_md_array_out();
+ plperl_md_array_out
+---------------------
+ {}
+(1 row)
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], [1]];
+$$ LANGUAGE plperl;
+select plperl_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: PL/Perl function "plperl_md_array_out"
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], 1];
+$$ LANGUAGE plperl;
+select plperl_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: PL/Perl function "plperl_md_array_out"
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [1, []];
+$$ LANGUAGE plperl;
+select plperl_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: PL/Perl function "plperl_md_array_out"
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[1], [[]]];
+$$ LANGUAGE plperl;
+select plperl_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: PL/Perl function "plperl_md_array_out"
+-- make sure setof works
+create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
+ my $arr = shift;
+ for my $r (@$arr) {
+ return_next $r;
+ }
+ return undef;
+$$;
+select perl_setof_array('{{1}, {2}, {3}}');
+ perl_setof_array
+------------------
+ {1}
+ {2}
+ {3}
+(3 rows)
+
diff --git a/src/pl/plperl/expected/plperl_call.out b/src/pl/plperl/expected/plperl_call.out
new file mode 100644
index 0000000..a08b9ff
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_call.out
@@ -0,0 +1,72 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE plperl
+AS $$
+undef;
+$$;
+CALL test_proc1();
+CREATE PROCEDURE test_proc2()
+LANGUAGE plperl
+AS $$
+return 5
+$$;
+CALL test_proc2();
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plperl
+AS $$
+spi_exec_query("INSERT INTO test1 VALUES ($_[0])");
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+-- output arguments
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plperl
+AS $$
+my ($a) = @_;
+return { a => "$a+$a" };
+$$;
+CALL test_proc5('abc');
+ a
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plperl
+AS $$
+my ($a, $b, $c) = @_;
+return { b => $b * $a, c => $c * $a };
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c
+---+---
+ 6 | 8
+(1 row)
+
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plperl
+AS $$
+my ($a, $b) = @_;
+elog(NOTICE, "a: $a, b: $b");
+return { b => $a * 2 };
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE: a: 10, b:
+NOTICE: _a: 10, _b: 20
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/plperl/expected/plperl_elog.out b/src/pl/plperl/expected/plperl_elog.out
new file mode 100644
index 0000000..a6d35cb
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_elog.out
@@ -0,0 +1,112 @@
+-- test warnings and errors from plperl
+create or replace function perl_elog(text) returns void language plperl as $$
+
+ my $msg = shift;
+ elog(NOTICE,$msg);
+
+$$;
+select perl_elog('explicit elog');
+NOTICE: explicit elog
+ perl_elog
+-----------
+
+(1 row)
+
+create or replace function perl_warn(text) returns void language plperl as $$
+
+ my $msg = shift;
+ warn($msg);
+
+$$;
+select perl_warn('implicit elog via warn');
+WARNING: implicit elog via warn at line 4.
+ perl_warn
+-----------
+
+(1 row)
+
+-- test strict mode on/off
+SET plperl.use_strict = true;
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global = 2;
+ return 'uses_global worked';
+
+$$;
+ERROR: Global symbol "$global" requires explicit package name at line 3.
+Global symbol "$other_global" requires explicit package name at line 4.
+CONTEXT: compilation of PL/Perl function "uses_global"
+select uses_global();
+ERROR: function uses_global() does not exist
+LINE 1: select uses_global();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SET plperl.use_strict = false;
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global=2;
+ return 'uses_global worked';
+
+$$;
+select uses_global();
+ uses_global
+--------------------
+ uses_global worked
+(1 row)
+
+-- make sure we don't choke on readonly values
+do language plperl $$ elog(NOTICE, ${^TAINT}); $$;
+NOTICE: 0
+-- test recovery after "die"
+create or replace function just_die() returns void language plperl AS $$
+die "just die";
+$$;
+select just_die();
+ERROR: just die at line 2.
+CONTEXT: PL/Perl function "just_die"
+create or replace function die_caller() returns int language plpgsql as $$
+BEGIN
+ BEGIN
+ PERFORM just_die();
+ EXCEPTION WHEN OTHERS THEN
+ RAISE NOTICE 'caught die';
+ END;
+ RETURN 1;
+END;
+$$;
+select die_caller();
+NOTICE: caught die
+ die_caller
+------------
+ 1
+(1 row)
+
+create or replace function indirect_die_caller() returns int language plperl as $$
+my $prepared = spi_prepare('SELECT die_caller() AS fx');
+my $a = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+my $b = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+return $a + $b;
+$$;
+select indirect_die_caller();
+NOTICE: caught die
+NOTICE: caught die
+ indirect_die_caller
+---------------------
+ 2
+(1 row)
+
+-- Test non-ASCII error messages
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+SET client_encoding TO UTF8;
+create or replace function error_with_nbsp() returns void language plperl as $$
+ elog(ERROR, "this message contains a no-break space");
+$$;
+select error_with_nbsp();
+ERROR: this message contains a no-break space at line 2.
+CONTEXT: PL/Perl function "error_with_nbsp"
diff --git a/src/pl/plperl/expected/plperl_elog_1.out b/src/pl/plperl/expected/plperl_elog_1.out
new file mode 100644
index 0000000..85aa460
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_elog_1.out
@@ -0,0 +1,112 @@
+-- test warnings and errors from plperl
+create or replace function perl_elog(text) returns void language plperl as $$
+
+ my $msg = shift;
+ elog(NOTICE,$msg);
+
+$$;
+select perl_elog('explicit elog');
+NOTICE: explicit elog
+ perl_elog
+-----------
+
+(1 row)
+
+create or replace function perl_warn(text) returns void language plperl as $$
+
+ my $msg = shift;
+ warn($msg);
+
+$$;
+select perl_warn('implicit elog via warn');
+WARNING: implicit elog via warn at line 4.
+ perl_warn
+-----------
+
+(1 row)
+
+-- test strict mode on/off
+SET plperl.use_strict = true;
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global = 2;
+ return 'uses_global worked';
+
+$$;
+ERROR: Global symbol "$global" requires explicit package name (did you forget to declare "my $global"?) at line 3.
+Global symbol "$other_global" requires explicit package name (did you forget to declare "my $other_global"?) at line 4.
+CONTEXT: compilation of PL/Perl function "uses_global"
+select uses_global();
+ERROR: function uses_global() does not exist
+LINE 1: select uses_global();
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+SET plperl.use_strict = false;
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global=2;
+ return 'uses_global worked';
+
+$$;
+select uses_global();
+ uses_global
+--------------------
+ uses_global worked
+(1 row)
+
+-- make sure we don't choke on readonly values
+do language plperl $$ elog(NOTICE, ${^TAINT}); $$;
+NOTICE: 0
+-- test recovery after "die"
+create or replace function just_die() returns void language plperl AS $$
+die "just die";
+$$;
+select just_die();
+ERROR: just die at line 2.
+CONTEXT: PL/Perl function "just_die"
+create or replace function die_caller() returns int language plpgsql as $$
+BEGIN
+ BEGIN
+ PERFORM just_die();
+ EXCEPTION WHEN OTHERS THEN
+ RAISE NOTICE 'caught die';
+ END;
+ RETURN 1;
+END;
+$$;
+select die_caller();
+NOTICE: caught die
+ die_caller
+------------
+ 1
+(1 row)
+
+create or replace function indirect_die_caller() returns int language plperl as $$
+my $prepared = spi_prepare('SELECT die_caller() AS fx');
+my $a = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+my $b = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+return $a + $b;
+$$;
+select indirect_die_caller();
+NOTICE: caught die
+NOTICE: caught die
+ indirect_die_caller
+---------------------
+ 2
+(1 row)
+
+-- Test non-ASCII error messages
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+SET client_encoding TO UTF8;
+create or replace function error_with_nbsp() returns void language plperl as $$
+ elog(ERROR, "this message contains a no-break space");
+$$;
+select error_with_nbsp();
+ERROR: this message contains a no-break space at line 2.
+CONTEXT: PL/Perl function "error_with_nbsp"
diff --git a/src/pl/plperl/expected/plperl_init.out b/src/pl/plperl/expected/plperl_init.out
new file mode 100644
index 0000000..4b7e925
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_init.out
@@ -0,0 +1,40 @@
+-- test plperl.on_plperl_init
+-- This test tests setting on_plperl_init after loading plperl
+LOAD 'plperl';
+SET SESSION plperl.on_plperl_init = ' system("/nonesuch"); ';
+SHOW plperl.on_plperl_init;
+ plperl.on_plperl_init
+------------------------
+ system("/nonesuch");
+(1 row)
+
+DO $$ warn 42 $$ language plperl;
+ERROR: 'system' trapped by operation mask at line 1.
+CONTEXT: while executing plperl.on_plperl_init
+PL/Perl anonymous code block
+--
+-- Reconnect (to unload plperl), then test setting on_plperl_init
+-- as an unprivileged user
+--
+\c -
+CREATE ROLE regress_plperl_user;
+SET ROLE regress_plperl_user;
+-- this succeeds, since the GUC isn't known yet
+SET SESSION plperl.on_plperl_init = 'test';
+RESET ROLE;
+LOAD 'plperl';
+WARNING: permission denied to set parameter "plperl.on_plperl_init"
+SHOW plperl.on_plperl_init;
+ plperl.on_plperl_init
+-----------------------
+
+(1 row)
+
+DO $$ warn 42 $$ language plperl;
+WARNING: 42 at line 1.
+-- now we won't be allowed to set it in the first place
+SET ROLE regress_plperl_user;
+SET SESSION plperl.on_plperl_init = 'test';
+ERROR: permission denied to set parameter "plperl.on_plperl_init"
+RESET ROLE;
+DROP ROLE regress_plperl_user;
diff --git a/src/pl/plperl/expected/plperl_lc.out b/src/pl/plperl/expected/plperl_lc.out
new file mode 100644
index 0000000..4f8c08f
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_lc.out
@@ -0,0 +1,10 @@
+--
+-- Make sure strings are validated
+-- Should fail for all encodings, as nul bytes are never permitted.
+--
+CREATE OR REPLACE FUNCTION perl_zerob() RETURNS TEXT AS $$
+ return "abcd\0efg";
+$$ LANGUAGE plperl;
+SELECT perl_zerob();
+ERROR: invalid byte sequence for encoding "UTF8": 0x00
+CONTEXT: PL/Perl function "perl_zerob"
diff --git a/src/pl/plperl/expected/plperl_lc_1.out b/src/pl/plperl/expected/plperl_lc_1.out
new file mode 100644
index 0000000..022c3e2
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_lc_1.out
@@ -0,0 +1,10 @@
+--
+-- Make sure strings are validated
+-- Should fail for all encodings, as nul bytes are never permitted.
+--
+CREATE OR REPLACE FUNCTION perl_zerob() RETURNS TEXT AS $$
+ return "abcd\0efg";
+$$ LANGUAGE plperl;
+SELECT perl_zerob();
+ERROR: invalid byte sequence for encoding "SQL_ASCII": 0x00
+CONTEXT: PL/Perl function "perl_zerob"
diff --git a/src/pl/plperl/expected/plperl_plperlu.out b/src/pl/plperl/expected/plperl_plperlu.out
new file mode 100644
index 0000000..2be955f
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_plperlu.out
@@ -0,0 +1,91 @@
+-- test plperl/plperlu interaction
+-- the language and call ordering of this test sequence is useful
+CREATE OR REPLACE FUNCTION bar() RETURNS integer AS $$
+ #die 'BANG!'; # causes server process to exit(2)
+ # alternative - causes server process to exit(255)
+ spi_exec_query("invalid sql statement");
+$$ language plperl; -- compile plperl code
+CREATE OR REPLACE FUNCTION foo() RETURNS integer AS $$
+ spi_exec_query("SELECT * FROM bar()");
+ return 1;
+$$ LANGUAGE plperlu; -- compile plperlu code
+SELECT * FROM bar(); -- throws exception normally (running plperl)
+ERROR: syntax error at or near "invalid" at line 4.
+CONTEXT: PL/Perl function "bar"
+SELECT * FROM foo(); -- used to cause backend crash (after switching to plperlu)
+ERROR: syntax error at or near "invalid" at line 4. at line 2.
+CONTEXT: PL/Perl function "foo"
+-- test redefinition of specific SP switching languages
+-- http://archives.postgresql.org/pgsql-bugs/2010-01/msg00116.php
+-- plperl first
+create or replace function foo(text) returns text language plperl as 'shift';
+select foo('hey');
+ foo
+-----
+ hey
+(1 row)
+
+create or replace function foo(text) returns text language plperlu as 'shift';
+select foo('hey');
+ foo
+-----
+ hey
+(1 row)
+
+create or replace function foo(text) returns text language plperl as 'shift';
+select foo('hey');
+ foo
+-----
+ hey
+(1 row)
+
+-- plperlu first
+create or replace function bar(text) returns text language plperlu as 'shift';
+select bar('hey');
+ bar
+-----
+ hey
+(1 row)
+
+create or replace function bar(text) returns text language plperl as 'shift';
+select bar('hey');
+ bar
+-----
+ hey
+(1 row)
+
+create or replace function bar(text) returns text language plperlu as 'shift';
+select bar('hey');
+ bar
+-----
+ hey
+(1 row)
+
+--
+-- Make sure we can't use/require things in plperl
+--
+CREATE OR REPLACE FUNCTION use_plperlu() RETURNS void LANGUAGE plperlu
+AS $$
+use Errno;
+$$;
+CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
+AS $$
+use Errno;
+$$;
+ERROR: Unable to load Errno.pm into plperl at line 2.
+BEGIN failed--compilation aborted at line 2.
+CONTEXT: compilation of PL/Perl function "use_plperl"
+-- make sure our overloaded require op gets restored/set correctly
+select use_plperlu();
+ use_plperlu
+-------------
+
+(1 row)
+
+CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
+AS $$
+use Errno;
+$$;
+ERROR: Unable to load Errno.pm into plperl at line 2.
+BEGIN failed--compilation aborted at line 2.
+CONTEXT: compilation of PL/Perl function "use_plperl"
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..5234feb
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,73 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl; -- fail
+ERROR: permission denied to create extension "plperl"
+HINT: Must have CREATE privilege on current database to create this extension.
+CREATE EXTENSION plperlu; -- fail
+ERROR: permission denied to create extension "plperlu"
+HINT: Must be superuser to create this extension.
+RESET ROLE;
+DO $$
+begin
+ execute format('grant create on database %I to regress_user1',
+ current_database());
+end;
+$$;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu; -- fail
+ERROR: permission denied to create extension "plperlu"
+HINT: Must be superuser to create this extension.
+CREATE SCHEMA plperl_setup_scratch;
+SET search_path = plperl_setup_scratch;
+GRANT ALL ON SCHEMA plperl_setup_scratch TO regress_user2;
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+ 1
+(1 row)
+
+-- Must reconnect to avoid failure with non-MULTIPLICITY Perl interpreters
+\c -
+SET search_path = plperl_setup_scratch;
+SET ROLE regress_user1;
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;'; -- fail
+ERROR: permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+ 2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR: cannot drop language plperl because extension plperl requires it
+HINT: You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE: drop cascades to 2 other objects
+DETAIL: drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP OWNED BY regress_user1;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/expected/plperl_shared.out b/src/pl/plperl/expected/plperl_shared.out
new file mode 100644
index 0000000..464e220
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_shared.out
@@ -0,0 +1,56 @@
+-- test plperl.on_plperl_init via the shared hash
+-- (must be done before plperl is first used)
+-- This test tests setting on_plperl_init before loading plperl
+-- testing on_plperl_init gets run, and that it can alter %_SHARED
+SET plperl.on_plperl_init = '$_SHARED{on_init} = 42';
+-- test the shared hash
+create function setme(key text, val text) returns void language plperl as $$
+
+ my $key = shift;
+ my $val = shift;
+ $_SHARED{$key}= $val;
+
+$$;
+create function getme(key text) returns text language plperl as $$
+
+ my $key = shift;
+ return $_SHARED{$key};
+
+$$;
+select setme('ourkey','ourval');
+ setme
+-------
+
+(1 row)
+
+select getme('ourkey');
+ getme
+--------
+ ourval
+(1 row)
+
+select getme('on_init');
+ getme
+-------
+ 42
+(1 row)
+
+-- verify that we can use $_SHARED in strict mode
+create or replace function perl_shared() returns int as $$
+use strict;
+my $val = $_SHARED{'stuff'};
+$_SHARED{'stuff'} = '1';
+return $val;
+$$ language plperl;
+select perl_shared();
+ perl_shared
+-------------
+
+(1 row)
+
+select perl_shared();
+ perl_shared
+-------------
+ 1
+(1 row)
+
diff --git a/src/pl/plperl/expected/plperl_transaction.out b/src/pl/plperl/expected/plperl_transaction.out
new file mode 100644
index 0000000..da4283c
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_transaction.out
@@ -0,0 +1,244 @@
+CREATE TABLE test1 (a int, b text);
+CREATE PROCEDURE transaction_test1()
+LANGUAGE plperl
+AS $$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+CALL transaction_test1();
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+TRUNCATE test1;
+DO
+LANGUAGE plperl
+$$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+TRUNCATE test1;
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plperl
+AS $$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+return 1;
+$$;
+SELECT transaction_test2();
+ERROR: invalid transaction termination at line 5.
+CONTEXT: PL/Perl function "transaction_test2"
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plperl
+AS $$
+spi_exec_query("CALL transaction_test1()");
+return 1;
+$$;
+SELECT transaction_test3();
+ERROR: invalid transaction termination at line 5. at line 2.
+CONTEXT: PL/Perl function "transaction_test3"
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plperl
+AS $$
+spi_exec_query('DO LANGUAGE plperl $x$ spi_commit(); $x$');
+return 1;
+$$;
+SELECT transaction_test4();
+ERROR: invalid transaction termination at line 1. at line 2.
+CONTEXT: PL/Perl function "transaction_test4"
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+TRUNCATE test1;
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ spi_commit();
+}
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+(5 rows)
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (12/(" . $row->{x} . "-2))");
+ spi_commit();
+}
+$$;
+ERROR: division by zero at line 5.
+CONTEXT: PL/Perl anonymous code block
+SELECT * FROM test1;
+ a | b
+-----+---
+ -6 |
+ -12 |
+(2 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ spi_rollback();
+}
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ if ($row->{x} % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+(3 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+DO LANGUAGE plperl $$
+# this insert will fail during commit:
+spi_exec_query("INSERT INTO testfk VALUES (0)");
+spi_commit();
+elog(WARNING, 'should not get here');
+$$;
+ERROR: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey" at line 4.
+CONTEXT: PL/Perl anonymous code block
+SELECT * FROM testpk;
+ id
+----
+(0 rows)
+
+SELECT * FROM testfk;
+ f1
+----
+(0 rows)
+
+DO LANGUAGE plperl $$
+# this insert will fail during commit:
+spi_exec_query("INSERT INTO testfk VALUES (0)");
+eval {
+ spi_commit();
+};
+if ($@) {
+ elog(INFO, $@);
+}
+# these inserts should work:
+spi_exec_query("INSERT INTO testpk VALUES (1)");
+spi_exec_query("INSERT INTO testfk VALUES (1)");
+$$;
+INFO: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey" at line 5.
+
+SELECT * FROM testpk;
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM testfk;
+ f1
+----
+ 1
+(1 row)
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/plperl/expected/plperl_trigger.out b/src/pl/plperl/expected/plperl_trigger.out
new file mode 100644
index 0000000..d4879e2
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_trigger.out
@@ -0,0 +1,392 @@
+-- test plperl triggers
+CREATE TYPE rowcomp as (i int);
+CREATE TYPE rowcompnest as (rfoo rowcomp);
+CREATE TABLE trigger_test (
+ i int,
+ v varchar,
+ foo rowcompnest
+);
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$
+
+ # make sure keys are sorted for consistent results - perl no longer
+ # hashes in repeatable fashion across runs
+
+ sub str {
+ my $val = shift;
+
+ if (!defined $val)
+ {
+ return 'NULL';
+ }
+ elsif (ref $val eq 'HASH')
+ {
+ my $str = '';
+ foreach my $rowkey (sort keys %$val)
+ {
+ $str .= ", " if $str;
+ my $rowval = str($val->{$rowkey});
+ $str .= "'$rowkey' => $rowval";
+ }
+ return '{'. $str .'}';
+ }
+ elsif (ref $val eq 'ARRAY')
+ {
+ my $str = '';
+ for my $argval (@$val)
+ {
+ $str .= ", " if $str;
+ $str .= str($argval);
+ }
+ return '['. $str .']';
+ }
+ else
+ {
+ return "'$val'";
+ }
+ }
+
+ foreach my $key (sort keys %$_TD)
+ {
+
+ my $val = $_TD->{$key};
+
+ # relid is variable, so we can not use it repeatably
+ $val = "bogus:12345" if $key eq 'relid';
+
+ elog(NOTICE, "\$_TD->\{$key\} = ". str($val));
+ }
+ return undef; # allow statement to proceed;
+$$;
+CREATE TRIGGER show_trigger_data_trig
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+insert into trigger_test values(1,'insert', '("(1)")');
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['23', 'skidoo']
+NOTICE: $_TD->{event} = 'INSERT'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test'
+NOTICE: $_TD->{table_name} = 'trigger_test'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+update trigger_test set v = 'update' where i = 1;
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['23', 'skidoo']
+NOTICE: $_TD->{event} = 'UPDATE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'update'}
+NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test'
+NOTICE: $_TD->{table_name} = 'trigger_test'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+delete from trigger_test;
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['23', 'skidoo']
+NOTICE: $_TD->{event} = 'DELETE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'update'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test'
+NOTICE: $_TD->{table_name} = 'trigger_test'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+DROP TRIGGER show_trigger_data_trig on trigger_test;
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+insert into trigger_test_generated (i) values (1);
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'INSERT'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
+NOTICE: $_TD->{new} = {'i' => '1'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'INSERT'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
+NOTICE: $_TD->{new} = {'i' => '1', 'j' => '2'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'AFTER'
+update trigger_test_generated set i = 11 where i = 1;
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'UPDATE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
+NOTICE: $_TD->{new} = {'i' => '11'}
+NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'UPDATE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
+NOTICE: $_TD->{new} = {'i' => '11', 'j' => '22'}
+NOTICE: $_TD->{old} = {'i' => '1', 'j' => '2'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'AFTER'
+delete from trigger_test_generated;
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'DELETE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_before'
+NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'BEFORE'
+NOTICE: $_TD->{argc} = '0'
+NOTICE: $_TD->{event} = 'DELETE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig_after'
+NOTICE: $_TD->{old} = {'i' => '11', 'j' => '22'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_generated'
+NOTICE: $_TD->{table_name} = 'trigger_test_generated'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'AFTER'
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+insert into trigger_test values(1,'insert', '("(1)")');
+CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+CREATE TRIGGER show_trigger_data_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+insert into trigger_test_view values(2,'insert', '("(2)")');
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['24', 'skidoo view']
+NOTICE: $_TD->{event} = 'INSERT'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '2'}}, 'i' => '2', 'v' => 'insert'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_view'
+NOTICE: $_TD->{table_name} = 'trigger_test_view'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'INSTEAD OF'
+update trigger_test_view set v = 'update', foo = '("(3)")' where i = 1;
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['24', 'skidoo view']
+NOTICE: $_TD->{event} = 'UPDATE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{new} = {'foo' => {'rfoo' => {'i' => '3'}}, 'i' => '1', 'v' => 'update'}
+NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_view'
+NOTICE: $_TD->{table_name} = 'trigger_test_view'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'INSTEAD OF'
+delete from trigger_test_view;
+NOTICE: $_TD->{argc} = '2'
+NOTICE: $_TD->{args} = ['24', 'skidoo view']
+NOTICE: $_TD->{event} = 'DELETE'
+NOTICE: $_TD->{level} = 'ROW'
+NOTICE: $_TD->{name} = 'show_trigger_data_trig'
+NOTICE: $_TD->{old} = {'foo' => {'rfoo' => {'i' => '1'}}, 'i' => '1', 'v' => 'insert'}
+NOTICE: $_TD->{relid} = 'bogus:12345'
+NOTICE: $_TD->{relname} = 'trigger_test_view'
+NOTICE: $_TD->{table_name} = 'trigger_test_view'
+NOTICE: $_TD->{table_schema} = 'public'
+NOTICE: $_TD->{when} = 'INSTEAD OF'
+DROP VIEW trigger_test_view;
+delete from trigger_test;
+DROP FUNCTION trigger_data();
+CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$
+
+ if (($_TD->{new}{i}>=100) || ($_TD->{new}{i}<=0))
+ {
+ return "SKIP"; # Skip INSERT/UPDATE command
+ }
+ elsif ($_TD->{new}{v} ne "immortal")
+ {
+ $_TD->{new}{v} .= "(modified by trigger)";
+ $_TD->{new}{foo}{rfoo}{i}++;
+ return "MODIFY"; # Modify tuple and proceed INSERT/UPDATE command
+ }
+ else
+ {
+ return; # Proceed INSERT/UPDATE command
+ }
+$$ LANGUAGE plperl;
+CREATE TRIGGER "test_valid_id_trig" BEFORE INSERT OR UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE "valid_id"();
+INSERT INTO trigger_test (i, v, foo) VALUES (1,'first line', '("(1)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (2,'second line', '("(2)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (3,'third line', '("(3)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (4,'immortal', '("(4)")');
+INSERT INTO trigger_test (i, v) VALUES (101,'bad id');
+SELECT * FROM trigger_test;
+ i | v | foo
+---+----------------------------------+---------
+ 1 | first line(modified by trigger) | ("(2)")
+ 2 | second line(modified by trigger) | ("(3)")
+ 3 | third line(modified by trigger) | ("(4)")
+ 4 | immortal | ("(4)")
+(4 rows)
+
+UPDATE trigger_test SET i = 5 where i=3;
+UPDATE trigger_test SET i = 100 where i=1;
+SELECT * FROM trigger_test;
+ i | v | foo
+---+------------------------------------------------------+---------
+ 1 | first line(modified by trigger) | ("(2)")
+ 2 | second line(modified by trigger) | ("(3)")
+ 4 | immortal | ("(4)")
+ 5 | third line(modified by trigger)(modified by trigger) | ("(5)")
+(4 rows)
+
+DROP TRIGGER "test_valid_id_trig" ON trigger_test;
+CREATE OR REPLACE FUNCTION trigger_recurse() RETURNS trigger AS $$
+ use strict;
+
+ if ($_TD->{new}{i} == 10000)
+ {
+ spi_exec_query("insert into trigger_test (i, v) values (20000, 'child');");
+
+ if ($_TD->{new}{i} != 10000)
+ {
+ die "recursive trigger modified: ". $_TD->{new}{i};
+ }
+ }
+ return;
+$$ LANGUAGE plperl;
+CREATE TRIGGER "test_trigger_recurse" BEFORE INSERT ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE "trigger_recurse"();
+INSERT INTO trigger_test (i, v) values (10000, 'top');
+SELECT * FROM trigger_test;
+ i | v | foo
+-------+------------------------------------------------------+---------
+ 1 | first line(modified by trigger) | ("(2)")
+ 2 | second line(modified by trigger) | ("(3)")
+ 4 | immortal | ("(4)")
+ 5 | third line(modified by trigger)(modified by trigger) | ("(5)")
+ 20000 | child |
+ 10000 | top |
+(6 rows)
+
+CREATE OR REPLACE FUNCTION immortal() RETURNS trigger AS $$
+ if ($_TD->{old}{v} eq $_TD->{args}[0])
+ {
+ return "SKIP"; # Skip DELETE command
+ }
+ else
+ {
+ return; # Proceed DELETE command
+ };
+$$ LANGUAGE plperl;
+CREATE TRIGGER "immortal_trig" BEFORE DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE immortal('immortal');
+DELETE FROM trigger_test;
+SELECT * FROM trigger_test;
+ i | v | foo
+---+----------+---------
+ 4 | immortal | ("(4)")
+(1 row)
+
+CREATE FUNCTION direct_trigger() RETURNS trigger AS $$
+ return;
+$$ LANGUAGE plperl;
+SELECT direct_trigger();
+ERROR: trigger functions can only be called as triggers
+CONTEXT: compilation of PL/Perl function "direct_trigger"
+-- check that SQL run in trigger code can see transition tables
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plperl AS
+$$
+ my $cursor = spi_query("SELECT * FROM old_table");
+ my $row = spi_fetchrow($cursor);
+ defined($row) || die "expected a row";
+ elog(INFO, "old: " . $row->{id} . " -> " . $row->{name});
+ my $row = spi_fetchrow($cursor);
+ !defined($row) || die "expected no more rows";
+
+ my $cursor = spi_query("SELECT * FROM new_table");
+ my $row = spi_fetchrow($cursor);
+ defined($row) || die "expected a row";
+ elog(INFO, "new: " . $row->{id} . " -> " . $row->{name});
+ my $row = spi_fetchrow($cursor);
+ !defined($row) || die "expected no more rows";
+
+ return undef;
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+INFO: old: 1 -> a
+INFO: new: 1 -> b
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
+-- test plperl command triggers
+create or replace function perlsnitch() returns event_trigger language plperl as $$
+ elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
+$$;
+create event trigger perl_a_snitch on ddl_command_start
+ execute procedure perlsnitch();
+create event trigger perl_b_snitch on ddl_command_end
+ execute procedure perlsnitch();
+create or replace function foobar() returns int language sql as $$select 1;$$;
+NOTICE: perlsnitch: ddl_command_start CREATE FUNCTION
+NOTICE: perlsnitch: ddl_command_end CREATE FUNCTION
+alter function foobar() cost 77;
+NOTICE: perlsnitch: ddl_command_start ALTER FUNCTION
+NOTICE: perlsnitch: ddl_command_end ALTER FUNCTION
+drop function foobar();
+NOTICE: perlsnitch: ddl_command_start DROP FUNCTION
+NOTICE: perlsnitch: ddl_command_end DROP FUNCTION
+create table foo();
+NOTICE: perlsnitch: ddl_command_start CREATE TABLE
+NOTICE: perlsnitch: ddl_command_end CREATE TABLE
+drop table foo;
+NOTICE: perlsnitch: ddl_command_start DROP TABLE
+NOTICE: perlsnitch: ddl_command_end DROP TABLE
+drop event trigger perl_a_snitch;
+drop event trigger perl_b_snitch;
+-- dealing with generated columns
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE plperl
+AS $$
+$_TD->{new}{j} = 5; # not allowed
+return 'MODIFY';
+$$;
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+ERROR: cannot set generated column "j"
+CONTEXT: PL/Perl function "generated_test_func1"
+SELECT * FROM trigger_test_generated;
+ i | j
+---+---
+(0 rows)
+
diff --git a/src/pl/plperl/expected/plperl_util.out b/src/pl/plperl/expected/plperl_util.out
new file mode 100644
index 0000000..698a8a1
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_util.out
@@ -0,0 +1,198 @@
+-- test plperl utility functions (defined in Util.xs)
+-- test quote_literal
+create or replace function perl_quote_literal() returns setof text language plperl as $$
+ return_next "undef: ".quote_literal(undef);
+ return_next sprintf"$_: ".quote_literal($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+ return undef;
+$$;
+select perl_quote_literal();
+ perl_quote_literal
+--------------------
+ undef:
+ foo: 'foo'
+ a'b: 'a''b'
+ a"b: 'a"b'
+ c''d: 'c''''d'
+ e\f: E'e\\f'
+ : ''
+(7 rows)
+
+-- test quote_nullable
+create or replace function perl_quote_nullable() returns setof text language plperl as $$
+ return_next "undef: ".quote_nullable(undef);
+ return_next sprintf"$_: ".quote_nullable($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+ return undef;
+$$;
+select perl_quote_nullable();
+ perl_quote_nullable
+---------------------
+ undef: NULL
+ foo: 'foo'
+ a'b: 'a''b'
+ a"b: 'a"b'
+ c''d: 'c''''d'
+ e\f: E'e\\f'
+ : ''
+(7 rows)
+
+-- test quote_ident
+create or replace function perl_quote_ident() returns setof text language plperl as $$
+ return_next "undef: ".quote_ident(undef); # generates undef warning if warnings enabled
+ return_next "$_: ".quote_ident($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{g.h}, q{};
+ return undef;
+$$;
+select perl_quote_ident();
+ perl_quote_ident
+------------------
+ undef: ""
+ foo: foo
+ a'b: "a'b"
+ a"b: "a""b"
+ c''d: "c''d"
+ e\f: "e\f"
+ g.h: "g.h"
+ : ""
+(8 rows)
+
+-- test decode_bytea
+create or replace function perl_decode_bytea() returns setof text language plperl as $$
+ return_next "undef: ".decode_bytea(undef); # generates undef warning if warnings enabled
+ return_next "$_: ".decode_bytea($_)
+ for q{foo}, q{a\047b}, q{};
+ return undef;
+$$;
+select perl_decode_bytea();
+ perl_decode_bytea
+-------------------
+ undef:
+ foo: foo
+ a\047b: a'b
+ :
+(4 rows)
+
+-- test encode_bytea
+create or replace function perl_encode_bytea() returns setof text language plperl as $$
+ return_next encode_bytea(undef); # generates undef warning if warnings enabled
+ return_next encode_bytea($_)
+ for q{@}, qq{@\x01@}, qq{@\x00@}, q{};
+ return undef;
+$$;
+select perl_encode_bytea();
+ perl_encode_bytea
+-------------------
+ \x
+ \x40
+ \x400140
+ \x400040
+ \x
+(5 rows)
+
+-- test encode_array_literal
+create or replace function perl_encode_array_literal() returns setof text language plperl as $$
+ return_next encode_array_literal(undef);
+ return_next encode_array_literal(0);
+ return_next encode_array_literal(42);
+ return_next encode_array_literal($_)
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return_next encode_array_literal($_,'|')
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return undef;
+$$;
+select perl_encode_array_literal();
+ perl_encode_array_literal
+---------------------------
+
+ 0
+ 42
+ {}
+ {"0"}
+ {"1", "2", "3", "4", "5"}
+ {{}}
+ {{"1", "2", {"3"}}, "4"}
+ {}
+ {"0"}
+ {"1"|"2"|"3"|"4"|"5"}
+ {{}}
+ {{"1"|"2"|{"3"}}|"4"}
+(13 rows)
+
+-- test encode_array_constructor
+create or replace function perl_encode_array_constructor() returns setof text language plperl as $$
+ return_next encode_array_constructor(undef);
+ return_next encode_array_constructor(0);
+ return_next encode_array_constructor(42);
+ return_next encode_array_constructor($_)
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return undef;
+$$;
+select perl_encode_array_constructor();
+ perl_encode_array_constructor
+-----------------------------------------
+ NULL
+ '0'
+ '42'
+ ARRAY[]
+ ARRAY['0']
+ ARRAY['1', '2', '3', '4', '5']
+ ARRAY[ARRAY[]]
+ ARRAY[ARRAY['1', '2', ARRAY['3']], '4']
+(8 rows)
+
+-- test looks_like_number
+create or replace function perl_looks_like_number() returns setof text language plperl as $$
+ return_next "undef is undef" if not defined looks_like_number(undef);
+ return_next quote_nullable($_).": ". (looks_like_number($_) ? "number" : "not number")
+ for 'foo', 0, 1, 1.3, '+3.e-4',
+ '42 x', # trailing garbage
+ '99 ', # trailing space
+ ' 99', # leading space
+ ' ', # only space
+ ''; # empty string
+ return undef;
+$$;
+select perl_looks_like_number();
+ perl_looks_like_number
+------------------------
+ undef is undef
+ 'foo': not number
+ '0': number
+ '1': number
+ '1.3': number
+ '+3.e-4': number
+ '42 x': not number
+ '99 ': number
+ ' 99': number
+ ' ': not number
+ '': not number
+(11 rows)
+
+-- test encode_typed_literal
+create type perl_foo as (a integer, b text[]);
+create type perl_bar as (c perl_foo[]);
+create domain perl_foo_pos as perl_foo check((value).a > 0);
+create or replace function perl_encode_typed_literal() returns setof text language plperl as $$
+ return_next encode_typed_literal(undef, 'text');
+ return_next encode_typed_literal([[1,2,3],[3,2,1],[1,3,2]], 'integer[]');
+ return_next encode_typed_literal({a => 1, b => ['PL','/','Perl']}, 'perl_foo');
+ return_next encode_typed_literal({c => [{a => 9, b => ['PostgreSQL']}, {b => ['Postgres'], a => 1}]}, 'perl_bar');
+ return_next encode_typed_literal({a => 1, b => ['PL','/','Perl']}, 'perl_foo_pos');
+$$;
+select perl_encode_typed_literal();
+ perl_encode_typed_literal
+-----------------------------------------------
+
+ {{1,2,3},{3,2,1},{1,3,2}}
+ (1,"{PL,/,Perl}")
+ ("{""(9,{PostgreSQL})"",""(1,{Postgres})""}")
+ (1,"{PL,/,Perl}")
+(5 rows)
+
+create or replace function perl_encode_typed_literal() returns setof text language plperl as $$
+ return_next encode_typed_literal({a => 0, b => ['PL','/','Perl']}, 'perl_foo_pos');
+$$;
+select perl_encode_typed_literal(); -- fail
+ERROR: value for domain perl_foo_pos violates check constraint "perl_foo_pos_check"
+CONTEXT: PL/Perl function "perl_encode_typed_literal"
diff --git a/src/pl/plperl/expected/plperlu.out b/src/pl/plperl/expected/plperlu.out
new file mode 100644
index 0000000..a3edb38
--- /dev/null
+++ b/src/pl/plperl/expected/plperlu.out
@@ -0,0 +1,15 @@
+-- Use ONLY plperlu tests here. For plperl/plerlu combined tests
+-- see plperl_plperlu.sql
+-- This test tests setting on_plperlu_init after loading plperl
+LOAD 'plperl';
+-- Test plperl.on_plperlu_init gets run
+SET plperl.on_plperlu_init = '$_SHARED{init} = 42';
+DO $$ warn $_SHARED{init} $$ language plperlu;
+WARNING: 42 at line 1.
+--
+-- Test compilation of unicode regex - regardless of locale.
+-- This code fails in plain plperl in a non-UTF8 database.
+--
+CREATE OR REPLACE FUNCTION perl_unicode_regex(text) RETURNS INTEGER AS $$
+ return ($_[0] =~ /\x{263A}|happy/i) ? 1 : 0; # unicode smiley
+$$ LANGUAGE plperlu;
diff --git a/src/pl/plperl/nls.mk b/src/pl/plperl/nls.mk
new file mode 100644
index 0000000..6c322de
--- /dev/null
+++ b/src/pl/plperl/nls.mk
@@ -0,0 +1,6 @@
+# src/pl/plperl/nls.mk
+CATALOG_NAME = plperl
+AVAIL_LANGUAGES = cs de el es fr it ja ka ko pl pt_BR ru sv tr uk vi zh_CN
+GETTEXT_FILES = plperl.c SPI.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/pl/plperl/plc_perlboot.pl b/src/pl/plperl/plc_perlboot.pl
new file mode 100644
index 0000000..8fd7f99
--- /dev/null
+++ b/src/pl/plperl/plc_perlboot.pl
@@ -0,0 +1,129 @@
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# src/pl/plperl/plc_perlboot.pl
+
+use strict;
+use warnings;
+
+use 5.008001;
+use vars qw(%_SHARED $_TD);
+
+PostgreSQL::InServer::Util::bootstrap();
+
+# globals
+
+sub ::is_array_ref
+{
+ return ref($_[0]) =~ m/^(?:PostgreSQL::InServer::)?ARRAY$/;
+}
+
+sub ::encode_array_literal
+{
+ my ($arg, $delim) = @_;
+ return $arg unless (::is_array_ref($arg));
+ $delim = ', ' unless defined $delim;
+ my $res = '';
+ foreach my $elem (@$arg)
+ {
+ $res .= $delim if length $res;
+ if (ref $elem)
+ {
+ $res .= ::encode_array_literal($elem, $delim);
+ }
+ elsif (defined $elem)
+ {
+ (my $str = $elem) =~ s/(["\\])/\\$1/g;
+ $res .= qq("$str");
+ }
+ else
+ {
+ $res .= 'NULL';
+ }
+ }
+ return qq({$res});
+}
+
+sub ::encode_array_constructor
+{
+ my $arg = shift;
+ return ::quote_nullable($arg) unless ::is_array_ref($arg);
+ my $res = join ", ",
+ map { (ref $_) ? ::encode_array_constructor($_) : ::quote_nullable($_) }
+ @$arg;
+ return "ARRAY[$res]";
+}
+
+{
+#<<< protect next line from perltidy so perlcritic annotation works
+ package PostgreSQL::InServer; ## no critic (RequireFilenameMatchesPackage)
+#>>>
+ use strict;
+ use warnings;
+
+ sub plperl_warn
+ {
+ (my $msg = shift) =~ s/\(eval \d+\) //g;
+ chomp $msg;
+ &::elog(&::WARNING, $msg);
+ return;
+ }
+ $SIG{__WARN__} = \&plperl_warn;
+
+ sub plperl_die
+ {
+ (my $msg = shift) =~ s/\(eval \d+\) //g;
+ die $msg;
+ }
+ $SIG{__DIE__} = \&plperl_die;
+
+ sub mkfuncsrc
+ {
+ my ($name, $imports, $prolog, $src) = @_;
+
+ my $BEGIN = join "\n", map {
+ my $names = $imports->{$_} || [];
+ "$_->import(qw(@$names));"
+ } sort keys %$imports;
+ $BEGIN &&= "BEGIN { $BEGIN }";
+
+ return qq[ package main; sub { $BEGIN $prolog $src } ];
+ }
+
+ sub mkfunc
+ {
+ ## no critic (ProhibitNoStrict, ProhibitStringyEval);
+ no strict; # default to no strict for the eval
+ no warnings; # default to no warnings for the eval
+ my $ret = eval(mkfuncsrc(@_));
+ $@ =~ s/\(eval \d+\) //g if $@;
+ return $ret;
+ ## use critic
+ }
+
+ 1;
+}
+
+{
+
+ package PostgreSQL::InServer::ARRAY;
+ use strict;
+ use warnings;
+
+ use overload
+ '""' => \&to_str,
+ '@{}' => \&to_arr;
+
+ sub to_str
+ {
+ my $self = shift;
+ return ::encode_typed_literal($self->{'array'}, $self->{'typeoid'});
+ }
+
+ sub to_arr
+ {
+ return shift->{'array'};
+ }
+
+ 1;
+}
diff --git a/src/pl/plperl/plc_trusted.pl b/src/pl/plperl/plc_trusted.pl
new file mode 100644
index 0000000..41b9b6a
--- /dev/null
+++ b/src/pl/plperl/plc_trusted.pl
@@ -0,0 +1,32 @@
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# src/pl/plperl/plc_trusted.pl
+
+#<<< protect next line from perltidy so perlcritic annotation works
+package PostgreSQL::InServer::safe; ## no critic (RequireFilenameMatchesPackage)
+#>>>
+
+# Load widely useful pragmas into plperl to make them available.
+#
+# SECURITY RISKS:
+#
+# Since these modules are free to compile unsafe opcodes they must
+# be trusted to now allow any code containing unsafe opcodes to be abused.
+# That's much harder than it sounds.
+#
+# Be aware that perl provides a wide variety of ways to subvert
+# pre-compiled code. For some examples, see this presentation:
+# http://www.slideshare.net/cdman83/barely-legal-xxx-perl-presentation
+#
+# If in ANY doubt about a module, or ANY of the modules down the chain of
+# dependencies it loads, then DO NOT add it to this list.
+#
+# To check if any of these modules use "unsafe" opcodes you can compile
+# plperl with the PLPERL_ENABLE_OPMASK_EARLY macro defined. See plperl.c
+
+require strict;
+require Carp;
+require Carp::Heavy;
+require warnings;
+require feature if $] >= 5.010000;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
new file mode 100644
index 0000000..5ff31e7
--- /dev/null
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -0,0 +1,20 @@
+/* src/pl/plperl/plperl--1.0.sql */
+
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+ HANDLER plperl_call_handler
+ INLINE plperl_inline_handler
+ VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;
+
+COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
new file mode 100644
index 0000000..58b7725
--- /dev/null
+++ b/src/pl/plperl/plperl.c
@@ -0,0 +1,4252 @@
+/**********************************************************************
+ * plperl.c - perl as a procedural language for PostgreSQL
+ *
+ * src/pl/plperl/plperl.c
+ *
+ **********************************************************************/
+
+#include "postgres.h"
+
+/* system stuff */
+#include <ctype.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+/* postgreSQL stuff */
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/pg_language.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_type.h"
+#include "storage/ipc.h"
+#include "tcop/tcopprot.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/hsearch.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("plperl")
+
+/* perl stuff */
+/* string literal macros defining chunks of perl code */
+#include "perlchunks.h"
+#include "plperl.h"
+#include "plperl_helpers.h"
+/* defines PLPERL_SET_OPMASK */
+#include "plperl_opmask.h"
+
+EXTERN_C void boot_DynaLoader(pTHX_ CV *cv);
+EXTERN_C void boot_PostgreSQL__InServer__Util(pTHX_ CV *cv);
+EXTERN_C void boot_PostgreSQL__InServer__SPI(pTHX_ CV *cv);
+
+PG_MODULE_MAGIC;
+
+/**********************************************************************
+ * Information associated with a Perl interpreter. We have one interpreter
+ * that is used for all plperlu (untrusted) functions. For plperl (trusted)
+ * functions, there is a separate interpreter for each effective SQL userid.
+ * (This is needed to ensure that an unprivileged user can't inject Perl code
+ * that'll be executed with the privileges of some other SQL user.)
+ *
+ * The plperl_interp_desc structs are kept in a Postgres hash table indexed
+ * by userid OID, with OID 0 used for the single untrusted interpreter.
+ * Once created, an interpreter is kept for the life of the process.
+ *
+ * We start out by creating a "held" interpreter, which we initialize
+ * only as far as we can do without deciding if it will be trusted or
+ * untrusted. Later, when we first need to run a plperl or plperlu
+ * function, we complete the initialization appropriately and move the
+ * PerlInterpreter pointer into the plperl_interp_hash hashtable. If after
+ * that we need more interpreters, we create them as needed if we can, or
+ * fail if the Perl build doesn't support multiple interpreters.
+ *
+ * The reason for all the dancing about with a held interpreter is to make
+ * it possible for people to preload a lot of Perl code at postmaster startup
+ * (using plperl.on_init) and then use that code in backends. Of course this
+ * will only work for the first interpreter created in any backend, but it's
+ * still useful with that restriction.
+ **********************************************************************/
+typedef struct plperl_interp_desc
+{
+ Oid user_id; /* Hash key (must be first!) */
+ PerlInterpreter *interp; /* The interpreter */
+ HTAB *query_hash; /* plperl_query_entry structs */
+} plperl_interp_desc;
+
+
+/**********************************************************************
+ * The information we cache about loaded procedures
+ *
+ * The fn_refcount field counts the struct's reference from the hash table
+ * shown below, plus one reference for each function call level that is using
+ * the struct. We can release the struct, and the associated Perl sub, when
+ * the fn_refcount goes to zero. Releasing the struct itself is done by
+ * deleting the fn_cxt, which also gets rid of all subsidiary data.
+ **********************************************************************/
+typedef struct plperl_proc_desc
+{
+ char *proname; /* user name of procedure */
+ MemoryContext fn_cxt; /* memory context for this procedure */
+ unsigned long fn_refcount; /* number of active references */
+ TransactionId fn_xmin; /* xmin/TID of procedure's pg_proc tuple */
+ ItemPointerData fn_tid;
+ SV *reference; /* CODE reference for Perl sub */
+ plperl_interp_desc *interp; /* interpreter it's created in */
+ bool fn_readonly; /* is function readonly (not volatile)? */
+ Oid lang_oid;
+ List *trftypes;
+ bool lanpltrusted; /* is it plperl, rather than plperlu? */
+ bool fn_retistuple; /* true, if function returns tuple */
+ bool fn_retisset; /* true, if function returns set */
+ bool fn_retisarray; /* true if function returns array */
+ /* Conversion info for function's result type: */
+ Oid result_oid; /* Oid of result type */
+ FmgrInfo result_in_func; /* I/O function and arg for result type */
+ Oid result_typioparam;
+ /* Per-argument info for function's argument types: */
+ int nargs;
+ FmgrInfo *arg_out_func; /* output fns for arg types */
+ bool *arg_is_rowtype; /* is each arg composite? */
+ Oid *arg_arraytype; /* InvalidOid if not an array */
+} plperl_proc_desc;
+
+#define increment_prodesc_refcount(prodesc) \
+ ((prodesc)->fn_refcount++)
+#define decrement_prodesc_refcount(prodesc) \
+ do { \
+ Assert((prodesc)->fn_refcount > 0); \
+ if (--((prodesc)->fn_refcount) == 0) \
+ free_plperl_function(prodesc); \
+ } while(0)
+
+/**********************************************************************
+ * For speedy lookup, we maintain a hash table mapping from
+ * function OID + trigger flag + user OID to plperl_proc_desc pointers.
+ * The reason the plperl_proc_desc struct isn't directly part of the hash
+ * entry is to simplify recovery from errors during compile_plperl_function.
+ *
+ * Note: if the same function is called by multiple userIDs within a session,
+ * there will be a separate plperl_proc_desc entry for each userID in the case
+ * of plperl functions, but only one entry for plperlu functions, because we
+ * set user_id = 0 for that case. If the user redeclares the same function
+ * from plperl to plperlu or vice versa, there might be multiple
+ * plperl_proc_ptr entries in the hashtable, but only one is valid.
+ **********************************************************************/
+typedef struct plperl_proc_key
+{
+ Oid proc_id; /* Function OID */
+
+ /*
+ * is_trigger is really a bool, but declare as Oid to ensure this struct
+ * contains no padding
+ */
+ Oid is_trigger; /* is it a trigger function? */
+ Oid user_id; /* User calling the function, or 0 */
+} plperl_proc_key;
+
+typedef struct plperl_proc_ptr
+{
+ plperl_proc_key proc_key; /* Hash key (must be first!) */
+ plperl_proc_desc *proc_ptr;
+} plperl_proc_ptr;
+
+/*
+ * The information we cache for the duration of a single call to a
+ * function.
+ */
+typedef struct plperl_call_data
+{
+ plperl_proc_desc *prodesc;
+ FunctionCallInfo fcinfo;
+ /* remaining fields are used only in a function returning set: */
+ Tuplestorestate *tuple_store;
+ TupleDesc ret_tdesc;
+ Oid cdomain_oid; /* 0 unless returning domain-over-composite */
+ void *cdomain_info;
+ MemoryContext tmp_cxt;
+} plperl_call_data;
+
+/**********************************************************************
+ * The information we cache about prepared and saved plans
+ **********************************************************************/
+typedef struct plperl_query_desc
+{
+ char qname[24];
+ MemoryContext plan_cxt; /* context holding this struct */
+ SPIPlanPtr plan;
+ int nargs;
+ Oid *argtypes;
+ FmgrInfo *arginfuncs;
+ Oid *argtypioparams;
+} plperl_query_desc;
+
+/* hash table entry for query desc */
+
+typedef struct plperl_query_entry
+{
+ char query_name[NAMEDATALEN];
+ plperl_query_desc *query_data;
+} plperl_query_entry;
+
+/**********************************************************************
+ * Information for PostgreSQL - Perl array conversion.
+ **********************************************************************/
+typedef struct plperl_array_info
+{
+ int ndims;
+ bool elem_is_rowtype; /* 't' if element type is a rowtype */
+ Datum *elements;
+ bool *nulls;
+ int *nelems;
+ FmgrInfo proc;
+ FmgrInfo transform_proc;
+} plperl_array_info;
+
+/**********************************************************************
+ * Global data
+ **********************************************************************/
+
+static HTAB *plperl_interp_hash = NULL;
+static HTAB *plperl_proc_hash = NULL;
+static plperl_interp_desc *plperl_active_interp = NULL;
+
+/* If we have an unassigned "held" interpreter, it's stored here */
+static PerlInterpreter *plperl_held_interp = NULL;
+
+/* GUC variables */
+static bool plperl_use_strict = false;
+static char *plperl_on_init = NULL;
+static char *plperl_on_plperl_init = NULL;
+static char *plperl_on_plperlu_init = NULL;
+
+static bool plperl_ending = false;
+static OP *(*pp_require_orig) (pTHX) = NULL;
+static char plperl_opmask[MAXO];
+
+/* this is saved and restored by plperl_call_handler */
+static plperl_call_data *current_call_data = NULL;
+
+/**********************************************************************
+ * Forward declarations
+ **********************************************************************/
+void _PG_init(void);
+
+static PerlInterpreter *plperl_init_interp(void);
+static void plperl_destroy_interp(PerlInterpreter **);
+static void plperl_fini(int code, Datum arg);
+static void set_interp_require(bool trusted);
+
+static Datum plperl_func_handler(PG_FUNCTION_ARGS);
+static Datum plperl_trigger_handler(PG_FUNCTION_ARGS);
+static void plperl_event_trigger_handler(PG_FUNCTION_ARGS);
+
+static void free_plperl_function(plperl_proc_desc *prodesc);
+
+static plperl_proc_desc *compile_plperl_function(Oid fn_oid,
+ bool is_trigger,
+ bool is_event_trigger);
+
+static SV *plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated);
+static SV *plperl_hash_from_datum(Datum attr);
+static void check_spi_usage_allowed(void);
+static SV *plperl_ref_from_pg_array(Datum arg, Oid typid);
+static SV *split_array(plperl_array_info *info, int first, int last, int nest);
+static SV *make_array_ref(plperl_array_info *info, int first, int last);
+static SV *get_perl_array_ref(SV *sv);
+static Datum plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+ FunctionCallInfo fcinfo,
+ FmgrInfo *finfo, Oid typioparam,
+ bool *isnull);
+static void _sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam);
+static Datum plperl_array_to_datum(SV *src, Oid typid, int32 typmod);
+static void array_to_datum_internal(AV *av, ArrayBuildState **astatep,
+ int *ndims, int *dims, int cur_depth,
+ Oid elemtypid, int32 typmod,
+ FmgrInfo *finfo, Oid typioparam);
+static Datum plperl_hash_to_datum(SV *src, TupleDesc td);
+
+static void plperl_init_shared_libs(pTHX);
+static void plperl_trusted_init(void);
+static void plperl_untrusted_init(void);
+static HV *plperl_spi_execute_fetch_result(SPITupleTable *, uint64, int);
+static void plperl_return_next_internal(SV *sv);
+static char *hek2cstr(HE *he);
+static SV **hv_store_string(HV *hv, const char *key, SV *val);
+static SV **hv_fetch_string(HV *hv, const char *key);
+static void plperl_create_sub(plperl_proc_desc *desc, const char *s, Oid fn_oid);
+static SV *plperl_call_perl_func(plperl_proc_desc *desc,
+ FunctionCallInfo fcinfo);
+static void plperl_compile_callback(void *arg);
+static void plperl_exec_callback(void *arg);
+static void plperl_inline_callback(void *arg);
+static char *strip_trailing_ws(const char *msg);
+static OP *pp_require_safe(pTHX);
+static void activate_interpreter(plperl_interp_desc *interp_desc);
+
+#if defined(WIN32) && PERL_VERSION_LT(5, 28, 0)
+static char *setlocale_perl(int category, char *locale);
+#else
+#define setlocale_perl(a,b) Perl_setlocale(a,b)
+#endif /* defined(WIN32) && PERL_VERSION_LT(5, 28, 0) */
+
+/*
+ * Decrement the refcount of the given SV within the active Perl interpreter
+ *
+ * This is handy because it reloads the active-interpreter pointer, saving
+ * some notation in callers that switch the active interpreter.
+ */
+static inline void
+SvREFCNT_dec_current(SV *sv)
+{
+ dTHX;
+
+ SvREFCNT_dec(sv);
+}
+
+/*
+ * convert a HE (hash entry) key to a cstr in the current database encoding
+ */
+static char *
+hek2cstr(HE *he)
+{
+ dTHX;
+ char *ret;
+ SV *sv;
+
+ /*
+ * HeSVKEY_force will return a temporary mortal SV*, so we need to make
+ * sure to free it with ENTER/SAVE/FREE/LEAVE
+ */
+ ENTER;
+ SAVETMPS;
+
+ /*-------------------------
+ * Unfortunately, while HeUTF8 is true for most things > 256, for values
+ * 128..255 it's not, but perl will treat them as unicode code points if
+ * the utf8 flag is not set ( see The "Unicode Bug" in perldoc perlunicode
+ * for more)
+ *
+ * So if we did the expected:
+ * if (HeUTF8(he))
+ * utf_u2e(key...);
+ * else // must be ascii
+ * return HePV(he);
+ * we won't match columns with codepoints from 128..255
+ *
+ * For a more concrete example given a column with the name of the unicode
+ * codepoint U+00ae (registered sign) and a UTF8 database and the perl
+ * return_next { "\N{U+00ae}=>'text } would always fail as heUTF8 returns
+ * 0 and HePV() would give us a char * with 1 byte contains the decimal
+ * value 174
+ *
+ * Perl has the brains to know when it should utf8 encode 174 properly, so
+ * here we force it into an SV so that perl will figure it out and do the
+ * right thing
+ *-------------------------
+ */
+
+ sv = HeSVKEY_force(he);
+ if (HeUTF8(he))
+ SvUTF8_on(sv);
+ ret = sv2cstr(sv);
+
+ /* free sv */
+ FREETMPS;
+ LEAVE;
+
+ return ret;
+}
+
+
+/*
+ * _PG_init() - library load-time initialization
+ *
+ * DO NOT make this static nor change its name!
+ */
+void
+_PG_init(void)
+{
+ /*
+ * Be sure we do initialization only once.
+ *
+ * If initialization fails due to, e.g., plperl_init_interp() throwing an
+ * exception, then we'll return here on the next usage and the user will
+ * get a rather cryptic: ERROR: attempt to redefine parameter
+ * "plperl.use_strict"
+ */
+ static bool inited = false;
+ HASHCTL hash_ctl;
+
+ if (inited)
+ return;
+
+ /*
+ * Support localized messages.
+ */
+ pg_bindtextdomain(TEXTDOMAIN);
+
+ /*
+ * Initialize plperl's GUCs.
+ */
+ DefineCustomBoolVariable("plperl.use_strict",
+ gettext_noop("If true, trusted and untrusted Perl code will be compiled in strict mode."),
+ NULL,
+ &plperl_use_strict,
+ false,
+ PGC_USERSET, 0,
+ NULL, NULL, NULL);
+
+ /*
+ * plperl.on_init is marked PGC_SIGHUP to support the idea that it might
+ * be executed in the postmaster (if plperl is loaded into the postmaster
+ * via shared_preload_libraries). This isn't really right either way,
+ * though.
+ */
+ DefineCustomStringVariable("plperl.on_init",
+ gettext_noop("Perl initialization code to execute when a Perl interpreter is initialized."),
+ NULL,
+ &plperl_on_init,
+ NULL,
+ PGC_SIGHUP, 0,
+ NULL, NULL, NULL);
+
+ /*
+ * plperl.on_plperl_init is marked PGC_SUSET to avoid issues whereby a
+ * user who might not even have USAGE privilege on the plperl language
+ * could nonetheless use SET plperl.on_plperl_init='...' to influence the
+ * behaviour of any existing plperl function that they can execute (which
+ * might be SECURITY DEFINER, leading to a privilege escalation). See
+ * http://archives.postgresql.org/pgsql-hackers/2010-02/msg00281.php and
+ * the overall thread.
+ *
+ * Note that because plperl.use_strict is USERSET, a nefarious user could
+ * set it to be applied against other people's functions. This is judged
+ * OK since the worst result would be an error. Your code oughta pass
+ * use_strict anyway ;-)
+ */
+ DefineCustomStringVariable("plperl.on_plperl_init",
+ gettext_noop("Perl initialization code to execute once when plperl is first used."),
+ NULL,
+ &plperl_on_plperl_init,
+ NULL,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+
+ DefineCustomStringVariable("plperl.on_plperlu_init",
+ gettext_noop("Perl initialization code to execute once when plperlu is first used."),
+ NULL,
+ &plperl_on_plperlu_init,
+ NULL,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+
+ MarkGUCPrefixReserved("plperl");
+
+ /*
+ * Create hash tables.
+ */
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(plperl_interp_desc);
+ plperl_interp_hash = hash_create("PL/Perl interpreters",
+ 8,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+
+ hash_ctl.keysize = sizeof(plperl_proc_key);
+ hash_ctl.entrysize = sizeof(plperl_proc_ptr);
+ plperl_proc_hash = hash_create("PL/Perl procedures",
+ 32,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+
+ /*
+ * Save the default opmask.
+ */
+ PLPERL_SET_OPMASK(plperl_opmask);
+
+ /*
+ * Create the first Perl interpreter, but only partially initialize it.
+ */
+ plperl_held_interp = plperl_init_interp();
+
+ inited = true;
+}
+
+
+static void
+set_interp_require(bool trusted)
+{
+ if (trusted)
+ {
+ PL_ppaddr[OP_REQUIRE] = pp_require_safe;
+ PL_ppaddr[OP_DOFILE] = pp_require_safe;
+ }
+ else
+ {
+ PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+ PL_ppaddr[OP_DOFILE] = pp_require_orig;
+ }
+}
+
+/*
+ * Cleanup perl interpreters, including running END blocks.
+ * Does not fully undo the actions of _PG_init() nor make it callable again.
+ */
+static void
+plperl_fini(int code, Datum arg)
+{
+ HASH_SEQ_STATUS hash_seq;
+ plperl_interp_desc *interp_desc;
+
+ elog(DEBUG3, "plperl_fini");
+
+ /*
+ * Indicate that perl is terminating. Disables use of spi_* functions when
+ * running END/DESTROY code. See check_spi_usage_allowed(). Could be
+ * enabled in future, with care, using a transaction
+ * http://archives.postgresql.org/pgsql-hackers/2010-01/msg02743.php
+ */
+ plperl_ending = true;
+
+ /* Only perform perl cleanup if we're exiting cleanly */
+ if (code)
+ {
+ elog(DEBUG3, "plperl_fini: skipped");
+ return;
+ }
+
+ /* Zap the "held" interpreter, if we still have it */
+ plperl_destroy_interp(&plperl_held_interp);
+
+ /* Zap any fully-initialized interpreters */
+ hash_seq_init(&hash_seq, plperl_interp_hash);
+ while ((interp_desc = hash_seq_search(&hash_seq)) != NULL)
+ {
+ if (interp_desc->interp)
+ {
+ activate_interpreter(interp_desc);
+ plperl_destroy_interp(&interp_desc->interp);
+ }
+ }
+
+ elog(DEBUG3, "plperl_fini: done");
+}
+
+
+/*
+ * Select and activate an appropriate Perl interpreter.
+ */
+static void
+select_perl_context(bool trusted)
+{
+ Oid user_id;
+ plperl_interp_desc *interp_desc;
+ bool found;
+ PerlInterpreter *interp = NULL;
+
+ /* Find or create the interpreter hashtable entry for this userid */
+ if (trusted)
+ user_id = GetUserId();
+ else
+ user_id = InvalidOid;
+
+ interp_desc = hash_search(plperl_interp_hash, &user_id,
+ HASH_ENTER,
+ &found);
+ if (!found)
+ {
+ /* Initialize newly-created hashtable entry */
+ interp_desc->interp = NULL;
+ interp_desc->query_hash = NULL;
+ }
+
+ /* Make sure we have a query_hash for this interpreter */
+ if (interp_desc->query_hash == NULL)
+ {
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = NAMEDATALEN;
+ hash_ctl.entrysize = sizeof(plperl_query_entry);
+ interp_desc->query_hash = hash_create("PL/Perl queries",
+ 32,
+ &hash_ctl,
+ HASH_ELEM | HASH_STRINGS);
+ }
+
+ /*
+ * Quick exit if already have an interpreter
+ */
+ if (interp_desc->interp)
+ {
+ activate_interpreter(interp_desc);
+ return;
+ }
+
+ /*
+ * adopt held interp if free, else create new one if possible
+ */
+ if (plperl_held_interp != NULL)
+ {
+ /* first actual use of a perl interpreter */
+ interp = plperl_held_interp;
+
+ /*
+ * Reset the plperl_held_interp pointer first; if we fail during init
+ * we don't want to try again with the partially-initialized interp.
+ */
+ plperl_held_interp = NULL;
+
+ if (trusted)
+ plperl_trusted_init();
+ else
+ plperl_untrusted_init();
+
+ /* successfully initialized, so arrange for cleanup */
+ on_proc_exit(plperl_fini, 0);
+ }
+ else
+ {
+#ifdef MULTIPLICITY
+
+ /*
+ * plperl_init_interp will change Perl's idea of the active
+ * interpreter. Reset plperl_active_interp temporarily, so that if we
+ * hit an error partway through here, we'll make sure to switch back
+ * to a non-broken interpreter before running any other Perl
+ * functions.
+ */
+ plperl_active_interp = NULL;
+
+ /* Now build the new interpreter */
+ interp = plperl_init_interp();
+
+ if (trusted)
+ plperl_trusted_init();
+ else
+ plperl_untrusted_init();
+#else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot allocate multiple Perl interpreters on this platform")));
+#endif
+ }
+
+ set_interp_require(trusted);
+
+ /*
+ * Since the timing of first use of PL/Perl can't be predicted, any
+ * database interaction during initialization is problematic. Including,
+ * but not limited to, security definer issues. So we only enable access
+ * to the database AFTER on_*_init code has run. See
+ * http://archives.postgresql.org/pgsql-hackers/2010-01/msg02669.php
+ */
+ {
+ dTHX;
+
+ newXS("PostgreSQL::InServer::SPI::bootstrap",
+ boot_PostgreSQL__InServer__SPI, __FILE__);
+
+ eval_pv("PostgreSQL::InServer::SPI::bootstrap()", FALSE);
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while executing PostgreSQL::InServer::SPI::bootstrap")));
+ }
+
+ /* Fully initialized, so mark the hashtable entry valid */
+ interp_desc->interp = interp;
+
+ /* And mark this as the active interpreter */
+ plperl_active_interp = interp_desc;
+}
+
+/*
+ * Make the specified interpreter the active one
+ *
+ * A call with NULL does nothing. This is so that "restoring" to a previously
+ * null state of plperl_active_interp doesn't result in useless thrashing.
+ */
+static void
+activate_interpreter(plperl_interp_desc *interp_desc)
+{
+ if (interp_desc && plperl_active_interp != interp_desc)
+ {
+ Assert(interp_desc->interp);
+ PERL_SET_CONTEXT(interp_desc->interp);
+ /* trusted iff user_id isn't InvalidOid */
+ set_interp_require(OidIsValid(interp_desc->user_id));
+ plperl_active_interp = interp_desc;
+ }
+}
+
+/*
+ * Create a new Perl interpreter.
+ *
+ * We initialize the interpreter as far as we can without knowing whether
+ * it will become a trusted or untrusted interpreter; in particular, the
+ * plperl.on_init code will get executed. Later, either plperl_trusted_init
+ * or plperl_untrusted_init must be called to complete the initialization.
+ */
+static PerlInterpreter *
+plperl_init_interp(void)
+{
+ PerlInterpreter *plperl;
+
+ static char *embedding[3 + 2] = {
+ "", "-e", PLC_PERLBOOT
+ };
+ int nargs = 3;
+
+#ifdef WIN32
+
+ /*
+ * The perl library on startup does horrible things like call
+ * setlocale(LC_ALL,""). We have protected against that on most platforms
+ * by setting the environment appropriately. However, on Windows,
+ * setlocale() does not consult the environment, so we need to save the
+ * existing locale settings before perl has a chance to mangle them and
+ * restore them after its dirty deeds are done.
+ *
+ * MSDN ref:
+ * http://msdn.microsoft.com/library/en-us/vclib/html/_crt_locale.asp
+ *
+ * It appears that we only need to do this on interpreter startup, and
+ * subsequent calls to the interpreter don't mess with the locale
+ * settings.
+ *
+ * We restore them using setlocale_perl(), defined below, so that Perl
+ * doesn't have a different idea of the locale from Postgres.
+ *
+ */
+
+ char *loc;
+ char *save_collate,
+ *save_ctype,
+ *save_monetary,
+ *save_numeric,
+ *save_time;
+
+ loc = setlocale(LC_COLLATE, NULL);
+ save_collate = loc ? pstrdup(loc) : NULL;
+ loc = setlocale(LC_CTYPE, NULL);
+ save_ctype = loc ? pstrdup(loc) : NULL;
+ loc = setlocale(LC_MONETARY, NULL);
+ save_monetary = loc ? pstrdup(loc) : NULL;
+ loc = setlocale(LC_NUMERIC, NULL);
+ save_numeric = loc ? pstrdup(loc) : NULL;
+ loc = setlocale(LC_TIME, NULL);
+ save_time = loc ? pstrdup(loc) : NULL;
+
+#define PLPERL_RESTORE_LOCALE(name, saved) \
+ STMT_START { \
+ if (saved != NULL) { setlocale_perl(name, saved); pfree(saved); } \
+ } STMT_END
+#endif /* WIN32 */
+
+ if (plperl_on_init && *plperl_on_init)
+ {
+ embedding[nargs++] = "-e";
+ embedding[nargs++] = plperl_on_init;
+ }
+
+ /*
+ * The perl API docs state that PERL_SYS_INIT3 should be called before
+ * allocating interpreters. Unfortunately, on some platforms this fails in
+ * the Perl_do_taint() routine, which is called when the platform is using
+ * the system's malloc() instead of perl's own. Other platforms, notably
+ * Windows, fail if PERL_SYS_INIT3 is not called. So we call it if it's
+ * available, unless perl is using the system malloc(), which is true when
+ * MYMALLOC is set.
+ */
+#if defined(PERL_SYS_INIT3) && !defined(MYMALLOC)
+ {
+ static int perl_sys_init_done;
+
+ /* only call this the first time through, as per perlembed man page */
+ if (!perl_sys_init_done)
+ {
+ char *dummy_env[1] = {NULL};
+
+ PERL_SYS_INIT3(&nargs, (char ***) &embedding, (char ***) &dummy_env);
+
+ /*
+ * For unclear reasons, PERL_SYS_INIT3 sets the SIGFPE handler to
+ * SIG_IGN. Aside from being extremely unfriendly behavior for a
+ * library, this is dumb on the grounds that the results of a
+ * SIGFPE in this state are undefined according to POSIX, and in
+ * fact you get a forced process kill at least on Linux. Hence,
+ * restore the SIGFPE handler to the backend's standard setting.
+ * (See Perl bug 114574 for more information.)
+ */
+ pqsignal(SIGFPE, FloatExceptionHandler);
+
+ perl_sys_init_done = 1;
+ /* quiet warning if PERL_SYS_INIT3 doesn't use the third argument */
+ dummy_env[0] = NULL;
+ }
+ }
+#endif
+
+ plperl = perl_alloc();
+ if (!plperl)
+ elog(ERROR, "could not allocate Perl interpreter");
+
+ PERL_SET_CONTEXT(plperl);
+ perl_construct(plperl);
+
+ /*
+ * Run END blocks in perl_destruct instead of perl_run. Note that dTHX
+ * loads up a pointer to the current interpreter, so we have to postpone
+ * it to here rather than put it at the function head.
+ */
+ {
+ dTHX;
+
+ PL_exit_flags |= PERL_EXIT_DESTRUCT_END;
+
+ /*
+ * Record the original function for the 'require' and 'dofile'
+ * opcodes. (They share the same implementation.) Ensure it's used
+ * for new interpreters.
+ */
+ if (!pp_require_orig)
+ pp_require_orig = PL_ppaddr[OP_REQUIRE];
+ else
+ {
+ PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+ PL_ppaddr[OP_DOFILE] = pp_require_orig;
+ }
+
+#ifdef PLPERL_ENABLE_OPMASK_EARLY
+
+ /*
+ * For regression testing to prove that the PLC_PERLBOOT and
+ * PLC_TRUSTED code doesn't even compile any unsafe ops. In future
+ * there may be a valid need for them to do so, in which case this
+ * could be softened (perhaps moved to plperl_trusted_init()) or
+ * removed.
+ */
+ PL_op_mask = plperl_opmask;
+#endif
+
+ if (perl_parse(plperl, plperl_init_shared_libs,
+ nargs, embedding, NULL) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while parsing Perl initialization")));
+
+ if (perl_run(plperl) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while running Perl initialization")));
+
+#ifdef PLPERL_RESTORE_LOCALE
+ PLPERL_RESTORE_LOCALE(LC_COLLATE, save_collate);
+ PLPERL_RESTORE_LOCALE(LC_CTYPE, save_ctype);
+ PLPERL_RESTORE_LOCALE(LC_MONETARY, save_monetary);
+ PLPERL_RESTORE_LOCALE(LC_NUMERIC, save_numeric);
+ PLPERL_RESTORE_LOCALE(LC_TIME, save_time);
+#endif
+ }
+
+ return plperl;
+}
+
+
+/*
+ * Our safe implementation of the require opcode.
+ * This is safe because it's completely unable to load any code.
+ * If the requested file/module has already been loaded it'll return true.
+ * If not, it'll die.
+ * So now "use Foo;" will work iff Foo has already been loaded.
+ */
+static OP *
+pp_require_safe(pTHX)
+{
+ dVAR;
+ dSP;
+ SV *sv,
+ **svp;
+ char *name;
+ STRLEN len;
+
+ sv = POPs;
+ name = SvPV(sv, len);
+ if (!(name && len > 0 && *name))
+ RETPUSHNO;
+
+ svp = hv_fetch(GvHVn(PL_incgv), name, len, 0);
+ if (svp && *svp != &PL_sv_undef)
+ RETPUSHYES;
+
+ DIE(aTHX_ "Unable to load %s into plperl", name);
+
+ /*
+ * In most Perl versions, DIE() expands to a return statement, so the next
+ * line is not necessary. But in versions between but not including
+ * 5.11.1 and 5.13.3 it does not, so the next line is necessary to avoid a
+ * "control reaches end of non-void function" warning from gcc. Other
+ * compilers such as Solaris Studio will, however, issue a "statement not
+ * reached" warning instead.
+ */
+ return NULL;
+}
+
+
+/*
+ * Destroy one Perl interpreter ... actually we just run END blocks.
+ *
+ * Caller must have ensured this interpreter is the active one.
+ */
+static void
+plperl_destroy_interp(PerlInterpreter **interp)
+{
+ if (interp && *interp)
+ {
+ /*
+ * Only a very minimal destruction is performed: - just call END
+ * blocks.
+ *
+ * We could call perl_destruct() but we'd need to audit its actions
+ * very carefully and work-around any that impact us. (Calling
+ * sv_clean_objs() isn't an option because it's not part of perl's
+ * public API so isn't portably available.) Meanwhile END blocks can
+ * be used to perform manual cleanup.
+ */
+ dTHX;
+
+ /* Run END blocks - based on perl's perl_destruct() */
+ if (PL_exit_flags & PERL_EXIT_DESTRUCT_END)
+ {
+ dJMPENV;
+ int x = 0;
+
+ JMPENV_PUSH(x);
+ PERL_UNUSED_VAR(x);
+ if (PL_endav && !PL_minus_c)
+ call_list(PL_scopestack_ix, PL_endav);
+ JMPENV_POP;
+ }
+ LEAVE;
+ FREETMPS;
+
+ *interp = NULL;
+ }
+}
+
+/*
+ * Initialize the current Perl interpreter as a trusted interp
+ */
+static void
+plperl_trusted_init(void)
+{
+ dTHX;
+ HV *stash;
+ SV *sv;
+ char *key;
+ I32 klen;
+
+ /* use original require while we set up */
+ PL_ppaddr[OP_REQUIRE] = pp_require_orig;
+ PL_ppaddr[OP_DOFILE] = pp_require_orig;
+
+ eval_pv(PLC_TRUSTED, FALSE);
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while executing PLC_TRUSTED")));
+
+ /*
+ * Force loading of utf8 module now to prevent errors that can arise from
+ * the regex code later trying to load utf8 modules. See
+ * http://rt.perl.org/rt3/Ticket/Display.html?id=47576
+ */
+ eval_pv("my $a=chr(0x100); return $a =~ /\\xa9/i", FALSE);
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while executing utf8fix")));
+
+ /*
+ * Lock down the interpreter
+ */
+
+ /* switch to the safe require/dofile opcode for future code */
+ PL_ppaddr[OP_REQUIRE] = pp_require_safe;
+ PL_ppaddr[OP_DOFILE] = pp_require_safe;
+
+ /*
+ * prevent (any more) unsafe opcodes being compiled PL_op_mask is per
+ * interpreter, so this only needs to be set once
+ */
+ PL_op_mask = plperl_opmask;
+
+ /* delete the DynaLoader:: namespace so extensions can't be loaded */
+ stash = gv_stashpv("DynaLoader", GV_ADDWARN);
+ hv_iterinit(stash);
+ while ((sv = hv_iternextsv(stash, &key, &klen)))
+ {
+ if (!isGV_with_GP(sv) || !GvCV(sv))
+ continue;
+ SvREFCNT_dec(GvCV(sv)); /* free the CV */
+ GvCV_set(sv, NULL); /* prevent call via GV */
+ }
+ hv_clear(stash);
+
+ /* invalidate assorted caches */
+ ++PL_sub_generation;
+ hv_clear(PL_stashcache);
+
+ /*
+ * Execute plperl.on_plperl_init in the locked-down interpreter
+ */
+ if (plperl_on_plperl_init && *plperl_on_plperl_init)
+ {
+ eval_pv(plperl_on_plperl_init, FALSE);
+ /* XXX need to find a way to determine a better errcode here */
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while executing plperl.on_plperl_init")));
+ }
+}
+
+
+/*
+ * Initialize the current Perl interpreter as an untrusted interp
+ */
+static void
+plperl_untrusted_init(void)
+{
+ dTHX;
+
+ /*
+ * Nothing to do except execute plperl.on_plperlu_init
+ */
+ if (plperl_on_plperlu_init && *plperl_on_plperlu_init)
+ {
+ eval_pv(plperl_on_plperlu_init, FALSE);
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV))),
+ errcontext("while executing plperl.on_plperlu_init")));
+ }
+}
+
+
+/*
+ * Perl likes to put a newline after its error messages; clean up such
+ */
+static char *
+strip_trailing_ws(const char *msg)
+{
+ char *res = pstrdup(msg);
+ int len = strlen(res);
+
+ while (len > 0 && isspace((unsigned char) res[len - 1]))
+ res[--len] = '\0';
+ return res;
+}
+
+
+/* Build a tuple from a hash. */
+
+static HeapTuple
+plperl_build_tuple_result(HV *perlhash, TupleDesc td)
+{
+ dTHX;
+ Datum *values;
+ bool *nulls;
+ HE *he;
+ HeapTuple tup;
+
+ values = palloc0(sizeof(Datum) * td->natts);
+ nulls = palloc(sizeof(bool) * td->natts);
+ memset(nulls, true, sizeof(bool) * td->natts);
+
+ hv_iterinit(perlhash);
+ while ((he = hv_iternext(perlhash)))
+ {
+ SV *val = HeVAL(he);
+ char *key = hek2cstr(he);
+ int attn = SPI_fnumber(td, key);
+ Form_pg_attribute attr = TupleDescAttr(td, attn - 1);
+
+ if (attn == SPI_ERROR_NOATTRIBUTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("Perl hash contains nonexistent column \"%s\"",
+ key)));
+ if (attn <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set system attribute \"%s\"",
+ key)));
+
+ values[attn - 1] = plperl_sv_to_datum(val,
+ attr->atttypid,
+ attr->atttypmod,
+ NULL,
+ NULL,
+ InvalidOid,
+ &nulls[attn - 1]);
+
+ pfree(key);
+ }
+ hv_iterinit(perlhash);
+
+ tup = heap_form_tuple(td, values, nulls);
+ pfree(values);
+ pfree(nulls);
+ return tup;
+}
+
+/* convert a hash reference to a datum */
+static Datum
+plperl_hash_to_datum(SV *src, TupleDesc td)
+{
+ HeapTuple tup = plperl_build_tuple_result((HV *) SvRV(src), td);
+
+ return HeapTupleGetDatum(tup);
+}
+
+/*
+ * if we are an array ref return the reference. this is special in that if we
+ * are a PostgreSQL::InServer::ARRAY object we will return the 'magic' array.
+ */
+static SV *
+get_perl_array_ref(SV *sv)
+{
+ dTHX;
+
+ if (SvOK(sv) && SvROK(sv))
+ {
+ if (SvTYPE(SvRV(sv)) == SVt_PVAV)
+ return sv;
+ else if (sv_isa(sv, "PostgreSQL::InServer::ARRAY"))
+ {
+ HV *hv = (HV *) SvRV(sv);
+ SV **sav = hv_fetch_string(hv, "array");
+
+ if (*sav && SvOK(*sav) && SvROK(*sav) &&
+ SvTYPE(SvRV(*sav)) == SVt_PVAV)
+ return *sav;
+
+ elog(ERROR, "could not get array reference from PostgreSQL::InServer::ARRAY object");
+ }
+ }
+ return NULL;
+}
+
+/*
+ * helper function for plperl_array_to_datum, recurses for multi-D arrays
+ *
+ * The ArrayBuildState is created only when we first find a scalar element;
+ * if we didn't do it like that, we'd need some other convention for knowing
+ * whether we'd already found any scalars (and thus the number of dimensions
+ * is frozen).
+ */
+static void
+array_to_datum_internal(AV *av, ArrayBuildState **astatep,
+ int *ndims, int *dims, int cur_depth,
+ Oid elemtypid, int32 typmod,
+ FmgrInfo *finfo, Oid typioparam)
+{
+ dTHX;
+ int i;
+ int len = av_len(av) + 1;
+
+ for (i = 0; i < len; i++)
+ {
+ /* fetch the array element */
+ SV **svp = av_fetch(av, i, FALSE);
+
+ /* see if this element is an array, if so get that */
+ SV *sav = svp ? get_perl_array_ref(*svp) : NULL;
+
+ /* multi-dimensional array? */
+ if (sav)
+ {
+ AV *nav = (AV *) SvRV(sav);
+
+ /* set size when at first element in this level, else compare */
+ if (i == 0 && *ndims == cur_depth)
+ {
+ /* array after some scalars at same level? */
+ if (*astatep != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+ /* too many dimensions? */
+ if (cur_depth + 1 > MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions (%d) exceeds the maximum allowed (%d)",
+ cur_depth + 1, MAXDIM)));
+ /* OK, add a dimension */
+ dims[*ndims] = av_len(nav) + 1;
+ (*ndims)++;
+ }
+ else if (cur_depth >= *ndims ||
+ av_len(nav) + 1 != dims[cur_depth])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+ /* recurse to fetch elements of this sub-array */
+ array_to_datum_internal(nav, astatep,
+ ndims, dims, cur_depth + 1,
+ elemtypid, typmod,
+ finfo, typioparam);
+ }
+ else
+ {
+ Datum dat;
+ bool isnull;
+
+ /* scalar after some sub-arrays at same level? */
+ if (*ndims != cur_depth)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+ dat = plperl_sv_to_datum(svp ? *svp : NULL,
+ elemtypid,
+ typmod,
+ NULL,
+ finfo,
+ typioparam,
+ &isnull);
+
+ /* Create ArrayBuildState if we didn't already */
+ if (*astatep == NULL)
+ *astatep = initArrayResult(elemtypid,
+ CurrentMemoryContext, true);
+
+ /* ... and save the element value in it */
+ (void) accumArrayResult(*astatep, dat, isnull,
+ elemtypid, CurrentMemoryContext);
+ }
+ }
+}
+
+/*
+ * convert perl array ref to a datum
+ */
+static Datum
+plperl_array_to_datum(SV *src, Oid typid, int32 typmod)
+{
+ dTHX;
+ AV *nav = (AV *) SvRV(src);
+ ArrayBuildState *astate = NULL;
+ Oid elemtypid;
+ FmgrInfo finfo;
+ Oid typioparam;
+ int dims[MAXDIM];
+ int lbs[MAXDIM];
+ int ndims = 1;
+ int i;
+
+ elemtypid = get_element_type(typid);
+ if (!elemtypid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot convert Perl array to non-array type %s",
+ format_type_be(typid))));
+
+ _sv_to_datum_finfo(elemtypid, &finfo, &typioparam);
+
+ memset(dims, 0, sizeof(dims));
+ dims[0] = av_len(nav) + 1;
+
+ array_to_datum_internal(nav, &astate,
+ &ndims, dims, 1,
+ elemtypid, typmod,
+ &finfo, typioparam);
+
+ /* ensure we get zero-D array for no inputs, as per PG convention */
+ if (astate == NULL)
+ return PointerGetDatum(construct_empty_array(elemtypid));
+
+ for (i = 0; i < ndims; i++)
+ lbs[i] = 1;
+
+ return makeMdArrayResult(astate, ndims, dims, lbs,
+ CurrentMemoryContext, true);
+}
+
+/* Get the information needed to convert data to the specified PG type */
+static void
+_sv_to_datum_finfo(Oid typid, FmgrInfo *finfo, Oid *typioparam)
+{
+ Oid typinput;
+
+ /* XXX would be better to cache these lookups */
+ getTypeInputInfo(typid,
+ &typinput, typioparam);
+ fmgr_info(typinput, finfo);
+}
+
+/*
+ * convert Perl SV to PG datum of type typid, typmod typmod
+ *
+ * Pass the PL/Perl function's fcinfo when attempting to convert to the
+ * function's result type; otherwise pass NULL. This is used when we need to
+ * resolve the actual result type of a function returning RECORD.
+ *
+ * finfo and typioparam should be the results of _sv_to_datum_finfo for the
+ * given typid, or NULL/InvalidOid to let this function do the lookups.
+ *
+ * *isnull is an output parameter.
+ */
+static Datum
+plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
+ FunctionCallInfo fcinfo,
+ FmgrInfo *finfo, Oid typioparam,
+ bool *isnull)
+{
+ FmgrInfo tmp;
+ Oid funcid;
+
+ /* we might recurse */
+ check_stack_depth();
+
+ *isnull = false;
+
+ /*
+ * Return NULL if result is undef, or if we're in a function returning
+ * VOID. In the latter case, we should pay no attention to the last Perl
+ * statement's result, and this is a convenient means to ensure that.
+ */
+ if (!sv || !SvOK(sv) || typid == VOIDOID)
+ {
+ /* look up type info if they did not pass it */
+ if (!finfo)
+ {
+ _sv_to_datum_finfo(typid, &tmp, &typioparam);
+ finfo = &tmp;
+ }
+ *isnull = true;
+ /* must call typinput in case it wants to reject NULL */
+ return InputFunctionCall(finfo, NULL, typioparam, typmod);
+ }
+ else if ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ return OidFunctionCall1(funcid, PointerGetDatum(sv));
+ else if (SvROK(sv))
+ {
+ /* handle references */
+ SV *sav = get_perl_array_ref(sv);
+
+ if (sav)
+ {
+ /* handle an arrayref */
+ return plperl_array_to_datum(sav, typid, typmod);
+ }
+ else if (SvTYPE(SvRV(sv)) == SVt_PVHV)
+ {
+ /* handle a hashref */
+ Datum ret;
+ TupleDesc td;
+ bool isdomain;
+
+ if (!type_is_rowtype(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot convert Perl hash to non-composite type %s",
+ format_type_be(typid))));
+
+ td = lookup_rowtype_tupdesc_domain(typid, typmod, true);
+ if (td != NULL)
+ {
+ /* Did we look through a domain? */
+ isdomain = (typid != td->tdtypeid);
+ }
+ else
+ {
+ /* Must be RECORD, try to resolve based on call info */
+ TypeFuncClass funcclass;
+
+ if (fcinfo)
+ funcclass = get_call_result_type(fcinfo, &typid, &td);
+ else
+ funcclass = TYPEFUNC_OTHER;
+ if (funcclass != TYPEFUNC_COMPOSITE &&
+ funcclass != TYPEFUNC_COMPOSITE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ Assert(td);
+ isdomain = (funcclass == TYPEFUNC_COMPOSITE_DOMAIN);
+ }
+
+ ret = plperl_hash_to_datum(sv, td);
+
+ if (isdomain)
+ domain_check(ret, false, typid, NULL, NULL);
+
+ /* Release on the result of get_call_result_type is harmless */
+ ReleaseTupleDesc(td);
+
+ return ret;
+ }
+
+ /*
+ * If it's a reference to something else, such as a scalar, just
+ * recursively look through the reference.
+ */
+ return plperl_sv_to_datum(SvRV(sv), typid, typmod,
+ fcinfo, finfo, typioparam,
+ isnull);
+ }
+ else
+ {
+ /* handle a string/number */
+ Datum ret;
+ char *str = sv2cstr(sv);
+
+ /* did not pass in any typeinfo? look it up */
+ if (!finfo)
+ {
+ _sv_to_datum_finfo(typid, &tmp, &typioparam);
+ finfo = &tmp;
+ }
+
+ ret = InputFunctionCall(finfo, str, typioparam, typmod);
+ pfree(str);
+
+ return ret;
+ }
+}
+
+/* Convert the perl SV to a string returned by the type output function */
+char *
+plperl_sv_to_literal(SV *sv, char *fqtypename)
+{
+ Oid typid;
+ Oid typoutput;
+ Datum datum;
+ bool typisvarlena,
+ isnull;
+
+ check_spi_usage_allowed();
+
+ typid = DirectFunctionCall1(regtypein, CStringGetDatum(fqtypename));
+ if (!OidIsValid(typid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("lookup failed for type %s", fqtypename)));
+
+ datum = plperl_sv_to_datum(sv,
+ typid, -1,
+ NULL, NULL, InvalidOid,
+ &isnull);
+
+ if (isnull)
+ return NULL;
+
+ getTypeOutputInfo(typid,
+ &typoutput, &typisvarlena);
+
+ return OidOutputFunctionCall(typoutput, datum);
+}
+
+/*
+ * Convert PostgreSQL array datum to a perl array reference.
+ *
+ * typid is arg's OID, which must be an array type.
+ */
+static SV *
+plperl_ref_from_pg_array(Datum arg, Oid typid)
+{
+ dTHX;
+ ArrayType *ar = DatumGetArrayTypeP(arg);
+ Oid elementtype = ARR_ELEMTYPE(ar);
+ int16 typlen;
+ bool typbyval;
+ char typalign,
+ typdelim;
+ Oid typioparam;
+ Oid typoutputfunc;
+ Oid transform_funcid;
+ int i,
+ nitems,
+ *dims;
+ plperl_array_info *info;
+ SV *av;
+ HV *hv;
+
+ /*
+ * Currently we make no effort to cache any of the stuff we look up here,
+ * which is bad.
+ */
+ info = palloc0(sizeof(plperl_array_info));
+
+ /* get element type information, including output conversion function */
+ get_type_io_data(elementtype, IOFunc_output,
+ &typlen, &typbyval, &typalign,
+ &typdelim, &typioparam, &typoutputfunc);
+
+ /* Check for a transform function */
+ transform_funcid = get_transform_fromsql(elementtype,
+ current_call_data->prodesc->lang_oid,
+ current_call_data->prodesc->trftypes);
+
+ /* Look up transform or output function as appropriate */
+ if (OidIsValid(transform_funcid))
+ fmgr_info(transform_funcid, &info->transform_proc);
+ else
+ fmgr_info(typoutputfunc, &info->proc);
+
+ info->elem_is_rowtype = type_is_rowtype(elementtype);
+
+ /* Get the number and bounds of array dimensions */
+ info->ndims = ARR_NDIM(ar);
+ dims = ARR_DIMS(ar);
+
+ /* No dimensions? Return an empty array */
+ if (info->ndims == 0)
+ {
+ av = newRV_noinc((SV *) newAV());
+ }
+ else
+ {
+ deconstruct_array(ar, elementtype, typlen, typbyval,
+ typalign, &info->elements, &info->nulls,
+ &nitems);
+
+ /* Get total number of elements in each dimension */
+ info->nelems = palloc(sizeof(int) * info->ndims);
+ info->nelems[0] = nitems;
+ for (i = 1; i < info->ndims; i++)
+ info->nelems[i] = info->nelems[i - 1] / dims[i - 1];
+
+ av = split_array(info, 0, nitems, 0);
+ }
+
+ hv = newHV();
+ (void) hv_store(hv, "array", 5, av, 0);
+ (void) hv_store(hv, "typeoid", 7, newSVuv(typid), 0);
+
+ return sv_bless(newRV_noinc((SV *) hv),
+ gv_stashpv("PostgreSQL::InServer::ARRAY", 0));
+}
+
+/*
+ * Recursively form array references from splices of the initial array
+ */
+static SV *
+split_array(plperl_array_info *info, int first, int last, int nest)
+{
+ dTHX;
+ int i;
+ AV *result;
+
+ /* we should only be called when we have something to split */
+ Assert(info->ndims > 0);
+
+ /* since this function recurses, it could be driven to stack overflow */
+ check_stack_depth();
+
+ /*
+ * Base case, return a reference to a single-dimensional array
+ */
+ if (nest >= info->ndims - 1)
+ return make_array_ref(info, first, last);
+
+ result = newAV();
+ for (i = first; i < last; i += info->nelems[nest + 1])
+ {
+ /* Recursively form references to arrays of lower dimensions */
+ SV *ref = split_array(info, i, i + info->nelems[nest + 1], nest + 1);
+
+ av_push(result, ref);
+ }
+ return newRV_noinc((SV *) result);
+}
+
+/*
+ * Create a Perl reference from a one-dimensional C array, converting
+ * composite type elements to hash references.
+ */
+static SV *
+make_array_ref(plperl_array_info *info, int first, int last)
+{
+ dTHX;
+ int i;
+ AV *result = newAV();
+
+ for (i = first; i < last; i++)
+ {
+ if (info->nulls[i])
+ {
+ /*
+ * We can't use &PL_sv_undef here. See "AVs, HVs and undefined
+ * values" in perlguts.
+ */
+ av_push(result, newSV(0));
+ }
+ else
+ {
+ Datum itemvalue = info->elements[i];
+
+ if (info->transform_proc.fn_oid)
+ av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue)));
+ else if (info->elem_is_rowtype)
+ /* Handle composite type elements */
+ av_push(result, plperl_hash_from_datum(itemvalue));
+ else
+ {
+ char *val = OutputFunctionCall(&info->proc, itemvalue);
+
+ av_push(result, cstr2sv(val));
+ }
+ }
+ }
+ return newRV_noinc((SV *) result);
+}
+
+/* Set up the arguments for a trigger call. */
+static SV *
+plperl_trigger_build_args(FunctionCallInfo fcinfo)
+{
+ dTHX;
+ TriggerData *tdata;
+ TupleDesc tupdesc;
+ int i;
+ char *level;
+ char *event;
+ char *relid;
+ char *when;
+ HV *hv;
+
+ hv = newHV();
+ hv_ksplit(hv, 12); /* pre-grow the hash */
+
+ tdata = (TriggerData *) fcinfo->context;
+ tupdesc = tdata->tg_relation->rd_att;
+
+ relid = DatumGetCString(DirectFunctionCall1(oidout,
+ ObjectIdGetDatum(tdata->tg_relation->rd_id)));
+
+ hv_store_string(hv, "name", cstr2sv(tdata->tg_trigger->tgname));
+ hv_store_string(hv, "relid", cstr2sv(relid));
+
+ /*
+ * Note: In BEFORE trigger, stored generated columns are not computed yet,
+ * so don't make them accessible in NEW row.
+ */
+
+ if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
+ {
+ event = "INSERT";
+ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+ hv_store_string(hv, "new",
+ plperl_hash_from_tuple(tdata->tg_trigtuple,
+ tupdesc,
+ !TRIGGER_FIRED_BEFORE(tdata->tg_event)));
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
+ {
+ event = "DELETE";
+ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+ hv_store_string(hv, "old",
+ plperl_hash_from_tuple(tdata->tg_trigtuple,
+ tupdesc,
+ true));
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
+ {
+ event = "UPDATE";
+ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+ {
+ hv_store_string(hv, "old",
+ plperl_hash_from_tuple(tdata->tg_trigtuple,
+ tupdesc,
+ true));
+ hv_store_string(hv, "new",
+ plperl_hash_from_tuple(tdata->tg_newtuple,
+ tupdesc,
+ !TRIGGER_FIRED_BEFORE(tdata->tg_event)));
+ }
+ }
+ else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+ event = "TRUNCATE";
+ else
+ event = "UNKNOWN";
+
+ hv_store_string(hv, "event", cstr2sv(event));
+ hv_store_string(hv, "argc", newSViv(tdata->tg_trigger->tgnargs));
+
+ if (tdata->tg_trigger->tgnargs > 0)
+ {
+ AV *av = newAV();
+
+ av_extend(av, tdata->tg_trigger->tgnargs);
+ for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
+ av_push(av, cstr2sv(tdata->tg_trigger->tgargs[i]));
+ hv_store_string(hv, "args", newRV_noinc((SV *) av));
+ }
+
+ hv_store_string(hv, "relname",
+ cstr2sv(SPI_getrelname(tdata->tg_relation)));
+
+ hv_store_string(hv, "table_name",
+ cstr2sv(SPI_getrelname(tdata->tg_relation)));
+
+ hv_store_string(hv, "table_schema",
+ cstr2sv(SPI_getnspname(tdata->tg_relation)));
+
+ if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
+ when = "BEFORE";
+ else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
+ when = "AFTER";
+ else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ when = "INSTEAD OF";
+ else
+ when = "UNKNOWN";
+ hv_store_string(hv, "when", cstr2sv(when));
+
+ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+ level = "ROW";
+ else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
+ level = "STATEMENT";
+ else
+ level = "UNKNOWN";
+ hv_store_string(hv, "level", cstr2sv(level));
+
+ return newRV_noinc((SV *) hv);
+}
+
+
+/* Set up the arguments for an event trigger call. */
+static SV *
+plperl_event_trigger_build_args(FunctionCallInfo fcinfo)
+{
+ dTHX;
+ EventTriggerData *tdata;
+ HV *hv;
+
+ hv = newHV();
+
+ tdata = (EventTriggerData *) fcinfo->context;
+
+ hv_store_string(hv, "event", cstr2sv(tdata->event));
+ hv_store_string(hv, "tag", cstr2sv(GetCommandTagName(tdata->tag)));
+
+ return newRV_noinc((SV *) hv);
+}
+
+/* Construct the modified new tuple to be returned from a trigger. */
+static HeapTuple
+plperl_modify_tuple(HV *hvTD, TriggerData *tdata, HeapTuple otup)
+{
+ dTHX;
+ SV **svp;
+ HV *hvNew;
+ HE *he;
+ HeapTuple rtup;
+ TupleDesc tupdesc;
+ int natts;
+ Datum *modvalues;
+ bool *modnulls;
+ bool *modrepls;
+
+ svp = hv_fetch_string(hvTD, "new");
+ if (!svp)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("$_TD->{new} does not exist")));
+ if (!SvOK(*svp) || !SvROK(*svp) || SvTYPE(SvRV(*svp)) != SVt_PVHV)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("$_TD->{new} is not a hash reference")));
+ hvNew = (HV *) SvRV(*svp);
+
+ tupdesc = tdata->tg_relation->rd_att;
+ natts = tupdesc->natts;
+
+ modvalues = (Datum *) palloc0(natts * sizeof(Datum));
+ modnulls = (bool *) palloc0(natts * sizeof(bool));
+ modrepls = (bool *) palloc0(natts * sizeof(bool));
+
+ hv_iterinit(hvNew);
+ while ((he = hv_iternext(hvNew)))
+ {
+ char *key = hek2cstr(he);
+ SV *val = HeVAL(he);
+ int attn = SPI_fnumber(tupdesc, key);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attn - 1);
+
+ if (attn == SPI_ERROR_NOATTRIBUTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("Perl hash contains nonexistent column \"%s\"",
+ key)));
+ if (attn <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set system attribute \"%s\"",
+ key)));
+ if (attr->attgenerated)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("cannot set generated column \"%s\"",
+ key)));
+
+ modvalues[attn - 1] = plperl_sv_to_datum(val,
+ attr->atttypid,
+ attr->atttypmod,
+ NULL,
+ NULL,
+ InvalidOid,
+ &modnulls[attn - 1]);
+ modrepls[attn - 1] = true;
+
+ pfree(key);
+ }
+ hv_iterinit(hvNew);
+
+ rtup = heap_modify_tuple(otup, tupdesc, modvalues, modnulls, modrepls);
+
+ pfree(modvalues);
+ pfree(modnulls);
+ pfree(modrepls);
+
+ return rtup;
+}
+
+
+/*
+ * There are three externally visible pieces to plperl: plperl_call_handler,
+ * plperl_inline_handler, and plperl_validator.
+ */
+
+/*
+ * The call handler is called to run normal functions (including trigger
+ * functions) that are defined in pg_proc.
+ */
+PG_FUNCTION_INFO_V1(plperl_call_handler);
+
+Datum
+plperl_call_handler(PG_FUNCTION_ARGS)
+{
+ Datum retval = (Datum) 0;
+ plperl_call_data *volatile save_call_data = current_call_data;
+ plperl_interp_desc *volatile oldinterp = plperl_active_interp;
+ plperl_call_data this_call_data;
+
+ /* Initialize current-call status record */
+ MemSet(&this_call_data, 0, sizeof(this_call_data));
+ this_call_data.fcinfo = fcinfo;
+
+ PG_TRY();
+ {
+ current_call_data = &this_call_data;
+ if (CALLED_AS_TRIGGER(fcinfo))
+ retval = PointerGetDatum(plperl_trigger_handler(fcinfo));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ plperl_event_trigger_handler(fcinfo);
+ retval = (Datum) 0;
+ }
+ else
+ retval = plperl_func_handler(fcinfo);
+ }
+ PG_FINALLY();
+ {
+ current_call_data = save_call_data;
+ activate_interpreter(oldinterp);
+ if (this_call_data.prodesc)
+ decrement_prodesc_refcount(this_call_data.prodesc);
+ }
+ PG_END_TRY();
+
+ return retval;
+}
+
+/*
+ * The inline handler runs anonymous code blocks (DO blocks).
+ */
+PG_FUNCTION_INFO_V1(plperl_inline_handler);
+
+Datum
+plperl_inline_handler(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(fake_fcinfo, 0);
+ InlineCodeBlock *codeblock = (InlineCodeBlock *) PG_GETARG_POINTER(0);
+ FmgrInfo flinfo;
+ plperl_proc_desc desc;
+ plperl_call_data *volatile save_call_data = current_call_data;
+ plperl_interp_desc *volatile oldinterp = plperl_active_interp;
+ plperl_call_data this_call_data;
+ ErrorContextCallback pl_error_context;
+
+ /* Initialize current-call status record */
+ MemSet(&this_call_data, 0, sizeof(this_call_data));
+
+ /* Set up a callback for error reporting */
+ pl_error_context.callback = plperl_inline_callback;
+ pl_error_context.previous = error_context_stack;
+ pl_error_context.arg = NULL;
+ error_context_stack = &pl_error_context;
+
+ /*
+ * Set up a fake fcinfo and descriptor with just enough info to satisfy
+ * plperl_call_perl_func(). In particular note that this sets things up
+ * with no arguments passed, and a result type of VOID.
+ */
+ MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ MemSet(&desc, 0, sizeof(desc));
+ fake_fcinfo->flinfo = &flinfo;
+ flinfo.fn_oid = InvalidOid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+
+ desc.proname = "inline_code_block";
+ desc.fn_readonly = false;
+
+ desc.lang_oid = codeblock->langOid;
+ desc.trftypes = NIL;
+ desc.lanpltrusted = codeblock->langIsTrusted;
+
+ desc.fn_retistuple = false;
+ desc.fn_retisset = false;
+ desc.fn_retisarray = false;
+ desc.result_oid = InvalidOid;
+ desc.nargs = 0;
+ desc.reference = NULL;
+
+ this_call_data.fcinfo = fake_fcinfo;
+ this_call_data.prodesc = &desc;
+ /* we do not bother with refcounting the fake prodesc */
+
+ PG_TRY();
+ {
+ SV *perlret;
+
+ current_call_data = &this_call_data;
+
+ if (SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ select_perl_context(desc.lanpltrusted);
+
+ plperl_create_sub(&desc, codeblock->source_text, 0);
+
+ if (!desc.reference) /* can this happen? */
+ elog(ERROR, "could not create internal procedure for anonymous code block");
+
+ perlret = plperl_call_perl_func(&desc, fake_fcinfo);
+
+ SvREFCNT_dec_current(perlret);
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+ }
+ PG_FINALLY();
+ {
+ if (desc.reference)
+ SvREFCNT_dec_current(desc.reference);
+ current_call_data = save_call_data;
+ activate_interpreter(oldinterp);
+ }
+ PG_END_TRY();
+
+ error_context_stack = pl_error_context.previous;
+
+ PG_RETURN_VOID();
+}
+
+/*
+ * The validator is called during CREATE FUNCTION to validate the function
+ * being created/replaced. The precise behavior of the validator may be
+ * modified by the check_function_bodies GUC.
+ */
+PG_FUNCTION_INFO_V1(plperl_validator);
+
+Datum
+plperl_validator(PG_FUNCTION_ARGS)
+{
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc proc;
+ char functyptype;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ bool is_trigger = false;
+ bool is_event_trigger = false;
+ int i;
+
+ if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
+ PG_RETURN_VOID();
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcoid);
+ proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+ functyptype = get_typtype(proc->prorettype);
+
+ /* Disallow pseudotype result */
+ /* except for TRIGGER, EVTTRIGGER, RECORD, or VOID */
+ if (functyptype == TYPTYPE_PSEUDO)
+ {
+ if (proc->prorettype == TRIGGEROID)
+ is_trigger = true;
+ else if (proc->prorettype == EVENT_TRIGGEROID)
+ is_event_trigger = true;
+ else if (proc->prorettype != RECORDOID &&
+ proc->prorettype != VOIDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Perl functions cannot return type %s",
+ format_type_be(proc->prorettype))));
+ }
+
+ /* Disallow pseudotypes in arguments (either IN or OUT) */
+ numargs = get_func_arg_info(tuple,
+ &argtypes, &argnames, &argmodes);
+ for (i = 0; i < numargs; i++)
+ {
+ if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO &&
+ argtypes[i] != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Perl functions cannot accept type %s",
+ format_type_be(argtypes[i]))));
+ }
+
+ ReleaseSysCache(tuple);
+
+ /* Postpone body checks if !check_function_bodies */
+ if (check_function_bodies)
+ {
+ (void) compile_plperl_function(funcoid, is_trigger, is_event_trigger);
+ }
+
+ /* the result of a validator is ignored */
+ PG_RETURN_VOID();
+}
+
+
+/*
+ * plperlu likewise requires three externally visible functions:
+ * plperlu_call_handler, plperlu_inline_handler, and plperlu_validator.
+ * These are currently just aliases that send control to the plperl
+ * handler functions, and we decide whether a particular function is
+ * trusted or not by inspecting the actual pg_language tuple.
+ */
+
+PG_FUNCTION_INFO_V1(plperlu_call_handler);
+
+Datum
+plperlu_call_handler(PG_FUNCTION_ARGS)
+{
+ return plperl_call_handler(fcinfo);
+}
+
+PG_FUNCTION_INFO_V1(plperlu_inline_handler);
+
+Datum
+plperlu_inline_handler(PG_FUNCTION_ARGS)
+{
+ return plperl_inline_handler(fcinfo);
+}
+
+PG_FUNCTION_INFO_V1(plperlu_validator);
+
+Datum
+plperlu_validator(PG_FUNCTION_ARGS)
+{
+ /* call plperl validator with our fcinfo so it gets our oid */
+ return plperl_validator(fcinfo);
+}
+
+
+/*
+ * Uses mkfunc to create a subroutine whose text is
+ * supplied in s, and returns a reference to it
+ */
+static void
+plperl_create_sub(plperl_proc_desc *prodesc, const char *s, Oid fn_oid)
+{
+ dTHX;
+ dSP;
+ char subname[NAMEDATALEN + 40];
+ HV *pragma_hv = newHV();
+ SV *subref = NULL;
+ int count;
+
+ sprintf(subname, "%s__%u", prodesc->proname, fn_oid);
+
+ if (plperl_use_strict)
+ hv_store_string(pragma_hv, "strict", (SV *) newAV());
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ EXTEND(SP, 4);
+ PUSHs(sv_2mortal(cstr2sv(subname)));
+ PUSHs(sv_2mortal(newRV_noinc((SV *) pragma_hv)));
+
+ /*
+ * Use 'false' for $prolog in mkfunc, which is kept for compatibility in
+ * case a module such as PostgreSQL::PLPerl::NYTprof replaces the function
+ * compiler.
+ */
+ PUSHs(&PL_sv_no);
+ PUSHs(sv_2mortal(cstr2sv(s)));
+ PUTBACK;
+
+ /*
+ * G_KEEPERR seems to be needed here, else we don't recognize compile
+ * errors properly. Perhaps it's because there's another level of eval
+ * inside mksafefunc?
+ */
+ count = call_pv("PostgreSQL::InServer::mkfunc",
+ G_SCALAR | G_EVAL | G_KEEPERR);
+ SPAGAIN;
+
+ if (count == 1)
+ {
+ SV *sub_rv = (SV *) POPs;
+
+ if (sub_rv && SvROK(sub_rv) && SvTYPE(SvRV(sub_rv)) == SVt_PVCV)
+ {
+ subref = newRV_inc(SvRV(sub_rv));
+ }
+ }
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ if (SvTRUE(ERRSV))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+
+ if (!subref)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("didn't get a CODE reference from compiling function \"%s\"",
+ prodesc->proname)));
+
+ prodesc->reference = subref;
+}
+
+
+/**********************************************************************
+ * plperl_init_shared_libs() -
+ **********************************************************************/
+
+static void
+plperl_init_shared_libs(pTHX)
+{
+ char *file = __FILE__;
+
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+ newXS("PostgreSQL::InServer::Util::bootstrap",
+ boot_PostgreSQL__InServer__Util, file);
+ /* newXS for...::SPI::bootstrap is in select_perl_context() */
+}
+
+
+static SV *
+plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
+{
+ dTHX;
+ dSP;
+ SV *retval;
+ int i;
+ int count;
+ Oid *argtypes = NULL;
+ int nargs = 0;
+
+ ENTER;
+ SAVETMPS;
+
+ PUSHMARK(SP);
+ EXTEND(sp, desc->nargs);
+
+ /* Get signature for true functions; inline blocks have no args. */
+ if (fcinfo->flinfo->fn_oid)
+ get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs);
+ Assert(nargs == desc->nargs);
+
+ for (i = 0; i < desc->nargs; i++)
+ {
+ if (fcinfo->args[i].isnull)
+ PUSHs(&PL_sv_undef);
+ else if (desc->arg_is_rowtype[i])
+ {
+ SV *sv = plperl_hash_from_datum(fcinfo->args[i].value);
+
+ PUSHs(sv_2mortal(sv));
+ }
+ else
+ {
+ SV *sv;
+ Oid funcid;
+
+ if (OidIsValid(desc->arg_arraytype[i]))
+ sv = plperl_ref_from_pg_array(fcinfo->args[i].value, desc->arg_arraytype[i]);
+ else if ((funcid = get_transform_fromsql(argtypes[i], current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, fcinfo->args[i].value));
+ else
+ {
+ char *tmp;
+
+ tmp = OutputFunctionCall(&(desc->arg_out_func[i]),
+ fcinfo->args[i].value);
+ sv = cstr2sv(tmp);
+ pfree(tmp);
+ }
+
+ PUSHs(sv_2mortal(sv));
+ }
+ }
+ PUTBACK;
+
+ /* Do NOT use G_KEEPERR here */
+ count = call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+ SPAGAIN;
+
+ if (count != 1)
+ {
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("didn't get a return item from function")));
+ }
+
+ if (SvTRUE(ERRSV))
+ {
+ (void) POPs;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ /* XXX need to find a way to determine a better errcode here */
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+ }
+
+ retval = newSVsv(POPs);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return retval;
+}
+
+
+static SV *
+plperl_call_perl_trigger_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo,
+ SV *td)
+{
+ dTHX;
+ dSP;
+ SV *retval,
+ *TDsv;
+ int i,
+ count;
+ Trigger *tg_trigger = ((TriggerData *) fcinfo->context)->tg_trigger;
+
+ ENTER;
+ SAVETMPS;
+
+ TDsv = get_sv("main::_TD", 0);
+ if (!TDsv)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("couldn't fetch $_TD")));
+
+ save_item(TDsv); /* local $_TD */
+ sv_setsv(TDsv, td);
+
+ PUSHMARK(sp);
+ EXTEND(sp, tg_trigger->tgnargs);
+
+ for (i = 0; i < tg_trigger->tgnargs; i++)
+ PUSHs(sv_2mortal(cstr2sv(tg_trigger->tgargs[i])));
+ PUTBACK;
+
+ /* Do NOT use G_KEEPERR here */
+ count = call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+ SPAGAIN;
+
+ if (count != 1)
+ {
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("didn't get a return item from trigger function")));
+ }
+
+ if (SvTRUE(ERRSV))
+ {
+ (void) POPs;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ /* XXX need to find a way to determine a better errcode here */
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+ }
+
+ retval = newSVsv(POPs);
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+
+ return retval;
+}
+
+
+static void
+plperl_call_perl_event_trigger_func(plperl_proc_desc *desc,
+ FunctionCallInfo fcinfo,
+ SV *td)
+{
+ dTHX;
+ dSP;
+ SV *retval,
+ *TDsv;
+ int count;
+
+ ENTER;
+ SAVETMPS;
+
+ TDsv = get_sv("main::_TD", 0);
+ if (!TDsv)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("couldn't fetch $_TD")));
+
+ save_item(TDsv); /* local $_TD */
+ sv_setsv(TDsv, td);
+
+ PUSHMARK(sp);
+ PUTBACK;
+
+ /* Do NOT use G_KEEPERR here */
+ count = call_sv(desc->reference, G_SCALAR | G_EVAL);
+
+ SPAGAIN;
+
+ if (count != 1)
+ {
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("didn't get a return item from trigger function")));
+ }
+
+ if (SvTRUE(ERRSV))
+ {
+ (void) POPs;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+ /* XXX need to find a way to determine a better errcode here */
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", strip_trailing_ws(sv2cstr(ERRSV)))));
+ }
+
+ retval = newSVsv(POPs);
+ (void) retval; /* silence compiler warning */
+
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static Datum
+plperl_func_handler(PG_FUNCTION_ARGS)
+{
+ bool nonatomic;
+ plperl_proc_desc *prodesc;
+ SV *perlret;
+ Datum retval = 0;
+ ReturnSetInfo *rsi;
+ ErrorContextCallback pl_error_context;
+
+ nonatomic = fcinfo->context &&
+ IsA(fcinfo->context, CallContext) &&
+ !castNode(CallContext, fcinfo->context)->atomic;
+
+ if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, false);
+ current_call_data->prodesc = prodesc;
+ increment_prodesc_refcount(prodesc);
+
+ /* Set a callback for error reporting */
+ pl_error_context.callback = plperl_exec_callback;
+ pl_error_context.previous = error_context_stack;
+ pl_error_context.arg = prodesc->proname;
+ error_context_stack = &pl_error_context;
+
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (prodesc->fn_retisset)
+ {
+ /* Check context before allowing the call to go through */
+ if (!rsi || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+ }
+
+ activate_interpreter(prodesc->interp);
+
+ perlret = plperl_call_perl_func(prodesc, fcinfo);
+
+ /************************************************************
+ * Disconnect from SPI manager and then create the return
+ * values datum (if the input function does a palloc for it
+ * this must not be allocated in the SPI memory context
+ * because SPI_finish would free it).
+ ************************************************************/
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ if (prodesc->fn_retisset)
+ {
+ SV *sav;
+
+ /*
+ * If the Perl function returned an arrayref, we pretend that it
+ * called return_next() for each element of the array, to handle old
+ * SRFs that didn't know about return_next(). Any other sort of return
+ * value is an error, except undef which means return an empty set.
+ */
+ sav = get_perl_array_ref(perlret);
+ if (sav)
+ {
+ dTHX;
+ int i = 0;
+ SV **svp = 0;
+ AV *rav = (AV *) SvRV(sav);
+
+ while ((svp = av_fetch(rav, i, FALSE)) != NULL)
+ {
+ plperl_return_next_internal(*svp);
+ i++;
+ }
+ }
+ else if (SvOK(perlret))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("set-returning PL/Perl function must return "
+ "reference to array or use return_next")));
+ }
+
+ rsi->returnMode = SFRM_Materialize;
+ if (current_call_data->tuple_store)
+ {
+ rsi->setResult = current_call_data->tuple_store;
+ rsi->setDesc = current_call_data->ret_tdesc;
+ }
+ retval = (Datum) 0;
+ }
+ else if (prodesc->result_oid)
+ {
+ retval = plperl_sv_to_datum(perlret,
+ prodesc->result_oid,
+ -1,
+ fcinfo,
+ &prodesc->result_in_func,
+ prodesc->result_typioparam,
+ &fcinfo->isnull);
+
+ if (fcinfo->isnull && rsi && IsA(rsi, ReturnSetInfo))
+ rsi->isDone = ExprEndResult;
+ }
+
+ /* Restore the previous error callback */
+ error_context_stack = pl_error_context.previous;
+
+ SvREFCNT_dec_current(perlret);
+
+ return retval;
+}
+
+
+static Datum
+plperl_trigger_handler(PG_FUNCTION_ARGS)
+{
+ plperl_proc_desc *prodesc;
+ SV *perlret;
+ Datum retval;
+ SV *svTD;
+ HV *hvTD;
+ ErrorContextCallback pl_error_context;
+ TriggerData *tdata;
+ int rc PG_USED_FOR_ASSERTS_ONLY;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Make transition tables visible to this SPI connection */
+ tdata = (TriggerData *) fcinfo->context;
+ rc = SPI_register_trigger_data(tdata);
+ Assert(rc >= 0);
+
+ /* Find or compile the function */
+ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, true, false);
+ current_call_data->prodesc = prodesc;
+ increment_prodesc_refcount(prodesc);
+
+ /* Set a callback for error reporting */
+ pl_error_context.callback = plperl_exec_callback;
+ pl_error_context.previous = error_context_stack;
+ pl_error_context.arg = prodesc->proname;
+ error_context_stack = &pl_error_context;
+
+ activate_interpreter(prodesc->interp);
+
+ svTD = plperl_trigger_build_args(fcinfo);
+ perlret = plperl_call_perl_trigger_func(prodesc, fcinfo, svTD);
+ hvTD = (HV *) SvRV(svTD);
+
+ /************************************************************
+ * Disconnect from SPI manager and then create the return
+ * values datum (if the input function does a palloc for it
+ * this must not be allocated in the SPI memory context
+ * because SPI_finish would free it).
+ ************************************************************/
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ if (perlret == NULL || !SvOK(perlret))
+ {
+ /* undef result means go ahead with original tuple */
+ TriggerData *trigdata = ((TriggerData *) fcinfo->context);
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ retval = (Datum) trigdata->tg_trigtuple;
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ retval = (Datum) trigdata->tg_newtuple;
+ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ retval = (Datum) trigdata->tg_trigtuple;
+ else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+ retval = (Datum) trigdata->tg_trigtuple;
+ else
+ retval = (Datum) 0; /* can this happen? */
+ }
+ else
+ {
+ HeapTuple trv;
+ char *tmp;
+
+ tmp = sv2cstr(perlret);
+
+ if (pg_strcasecmp(tmp, "SKIP") == 0)
+ trv = NULL;
+ else if (pg_strcasecmp(tmp, "MODIFY") == 0)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ trv = plperl_modify_tuple(hvTD, trigdata,
+ trigdata->tg_trigtuple);
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ trv = plperl_modify_tuple(hvTD, trigdata,
+ trigdata->tg_newtuple);
+ else
+ {
+ ereport(WARNING,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("ignoring modified row in DELETE trigger")));
+ trv = NULL;
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("result of PL/Perl trigger function must be undef, "
+ "\"SKIP\", or \"MODIFY\"")));
+ trv = NULL;
+ }
+ retval = PointerGetDatum(trv);
+ pfree(tmp);
+ }
+
+ /* Restore the previous error callback */
+ error_context_stack = pl_error_context.previous;
+
+ SvREFCNT_dec_current(svTD);
+ if (perlret)
+ SvREFCNT_dec_current(perlret);
+
+ return retval;
+}
+
+
+static void
+plperl_event_trigger_handler(PG_FUNCTION_ARGS)
+{
+ plperl_proc_desc *prodesc;
+ SV *svTD;
+ ErrorContextCallback pl_error_context;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Find or compile the function */
+ prodesc = compile_plperl_function(fcinfo->flinfo->fn_oid, false, true);
+ current_call_data->prodesc = prodesc;
+ increment_prodesc_refcount(prodesc);
+
+ /* Set a callback for error reporting */
+ pl_error_context.callback = plperl_exec_callback;
+ pl_error_context.previous = error_context_stack;
+ pl_error_context.arg = prodesc->proname;
+ error_context_stack = &pl_error_context;
+
+ activate_interpreter(prodesc->interp);
+
+ svTD = plperl_event_trigger_build_args(fcinfo);
+ plperl_call_perl_event_trigger_func(prodesc, fcinfo, svTD);
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ /* Restore the previous error callback */
+ error_context_stack = pl_error_context.previous;
+
+ SvREFCNT_dec_current(svTD);
+}
+
+
+static bool
+validate_plperl_function(plperl_proc_ptr *proc_ptr, HeapTuple procTup)
+{
+ if (proc_ptr && proc_ptr->proc_ptr)
+ {
+ plperl_proc_desc *prodesc = proc_ptr->proc_ptr;
+ bool uptodate;
+
+ /************************************************************
+ * If it's present, must check whether it's still up to date.
+ * This is needed because CREATE OR REPLACE FUNCTION can modify the
+ * function's pg_proc entry without changing its OID.
+ ************************************************************/
+ uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
+
+ if (uptodate)
+ return true;
+
+ /* Otherwise, unlink the obsoleted entry from the hashtable ... */
+ proc_ptr->proc_ptr = NULL;
+ /* ... and release the corresponding refcount, probably deleting it */
+ decrement_prodesc_refcount(prodesc);
+ }
+
+ return false;
+}
+
+
+static void
+free_plperl_function(plperl_proc_desc *prodesc)
+{
+ Assert(prodesc->fn_refcount == 0);
+ /* Release CODE reference, if we have one, from the appropriate interp */
+ if (prodesc->reference)
+ {
+ plperl_interp_desc *oldinterp = plperl_active_interp;
+
+ activate_interpreter(prodesc->interp);
+ SvREFCNT_dec_current(prodesc->reference);
+ activate_interpreter(oldinterp);
+ }
+ /* Release all PG-owned data for this proc */
+ MemoryContextDelete(prodesc->fn_cxt);
+}
+
+
+static plperl_proc_desc *
+compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
+{
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ plperl_proc_key proc_key;
+ plperl_proc_ptr *proc_ptr;
+ plperl_proc_desc *volatile prodesc = NULL;
+ volatile MemoryContext proc_cxt = NULL;
+ plperl_interp_desc *oldinterp = plperl_active_interp;
+ ErrorContextCallback plperl_error_context;
+
+ /* We'll need the pg_proc tuple in any case... */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", fn_oid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * Try to find function in plperl_proc_hash. The reason for this
+ * overcomplicated-seeming lookup procedure is that we don't know whether
+ * it's plperl or plperlu, and don't want to spend a lookup in pg_language
+ * to find out.
+ */
+ proc_key.proc_id = fn_oid;
+ proc_key.is_trigger = is_trigger;
+ proc_key.user_id = GetUserId();
+ proc_ptr = hash_search(plperl_proc_hash, &proc_key,
+ HASH_FIND, NULL);
+ if (validate_plperl_function(proc_ptr, procTup))
+ {
+ /* Found valid plperl entry */
+ ReleaseSysCache(procTup);
+ return proc_ptr->proc_ptr;
+ }
+
+ /* If not found or obsolete, maybe it's plperlu */
+ proc_key.user_id = InvalidOid;
+ proc_ptr = hash_search(plperl_proc_hash, &proc_key,
+ HASH_FIND, NULL);
+ if (validate_plperl_function(proc_ptr, procTup))
+ {
+ /* Found valid plperlu entry */
+ ReleaseSysCache(procTup);
+ return proc_ptr->proc_ptr;
+ }
+
+ /************************************************************
+ * If we haven't found it in the hashtable, we analyze
+ * the function's arguments and return type and store
+ * the in-/out-functions in the prodesc block,
+ * then we load the procedure into the Perl interpreter,
+ * and last we create a new hashtable entry for it.
+ ************************************************************/
+
+ /* Set a callback for reporting compilation errors */
+ plperl_error_context.callback = plperl_compile_callback;
+ plperl_error_context.previous = error_context_stack;
+ plperl_error_context.arg = NameStr(procStruct->proname);
+ error_context_stack = &plperl_error_context;
+
+ PG_TRY();
+ {
+ HeapTuple langTup;
+ HeapTuple typeTup;
+ Form_pg_language langStruct;
+ Form_pg_type typeStruct;
+ Datum protrftypes_datum;
+ Datum prosrcdatum;
+ bool isnull;
+ char *proc_source;
+ MemoryContext oldcontext;
+
+ /************************************************************
+ * Allocate a context that will hold all PG data for the procedure.
+ ************************************************************/
+ proc_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Perl function",
+ ALLOCSET_SMALL_SIZES);
+
+ /************************************************************
+ * Allocate and fill a new procedure description block.
+ * struct prodesc and subsidiary data must all live in proc_cxt.
+ ************************************************************/
+ oldcontext = MemoryContextSwitchTo(proc_cxt);
+ prodesc = (plperl_proc_desc *) palloc0(sizeof(plperl_proc_desc));
+ prodesc->proname = pstrdup(NameStr(procStruct->proname));
+ MemoryContextSetIdentifier(proc_cxt, prodesc->proname);
+ prodesc->fn_cxt = proc_cxt;
+ prodesc->fn_refcount = 0;
+ prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_tid = procTup->t_self;
+ prodesc->nargs = procStruct->pronargs;
+ prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
+ prodesc->arg_is_rowtype = (bool *) palloc0(prodesc->nargs * sizeof(bool));
+ prodesc->arg_arraytype = (Oid *) palloc0(prodesc->nargs * sizeof(Oid));
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
+ /* Fetch protrftypes */
+ protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes, &isnull);
+ MemoryContextSwitchTo(proc_cxt);
+ prodesc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+ MemoryContextSwitchTo(oldcontext);
+
+ /************************************************************
+ * Lookup the pg_language tuple by Oid
+ ************************************************************/
+ langTup = SearchSysCache1(LANGOID,
+ ObjectIdGetDatum(procStruct->prolang));
+ if (!HeapTupleIsValid(langTup))
+ elog(ERROR, "cache lookup failed for language %u",
+ procStruct->prolang);
+ langStruct = (Form_pg_language) GETSTRUCT(langTup);
+ prodesc->lang_oid = langStruct->oid;
+ prodesc->lanpltrusted = langStruct->lanpltrusted;
+ ReleaseSysCache(langTup);
+
+ /************************************************************
+ * Get the required information for input conversion of the
+ * return value.
+ ************************************************************/
+ if (!is_trigger && !is_event_trigger)
+ {
+ Oid rettype = procStruct->prorettype;
+
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettype);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ /* Disallow pseudotype result, except VOID or RECORD */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO)
+ {
+ if (rettype == VOIDOID ||
+ rettype == RECORDOID)
+ /* okay */ ;
+ else if (rettype == TRIGGEROID ||
+ rettype == EVENT_TRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called "
+ "as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Perl functions cannot return type %s",
+ format_type_be(rettype))));
+ }
+
+ prodesc->result_oid = rettype;
+ prodesc->fn_retisset = procStruct->proretset;
+ prodesc->fn_retistuple = type_is_rowtype(rettype);
+ prodesc->fn_retisarray = IsTrueArrayType(typeStruct);
+
+ fmgr_info_cxt(typeStruct->typinput,
+ &(prodesc->result_in_func),
+ proc_cxt);
+ prodesc->result_typioparam = getTypeIOParam(typeTup);
+
+ ReleaseSysCache(typeTup);
+ }
+
+ /************************************************************
+ * Get the required information for output conversion
+ * of all procedure arguments
+ ************************************************************/
+ if (!is_trigger && !is_event_trigger)
+ {
+ int i;
+
+ for (i = 0; i < prodesc->nargs; i++)
+ {
+ Oid argtype = procStruct->proargtypes.values[i];
+
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", argtype);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ /* Disallow pseudotype argument, except RECORD */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO &&
+ argtype != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Perl functions cannot accept type %s",
+ format_type_be(argtype))));
+
+ if (type_is_rowtype(argtype))
+ prodesc->arg_is_rowtype[i] = true;
+ else
+ {
+ prodesc->arg_is_rowtype[i] = false;
+ fmgr_info_cxt(typeStruct->typoutput,
+ &(prodesc->arg_out_func[i]),
+ proc_cxt);
+ }
+
+ /* Identify array-type arguments */
+ if (IsTrueArrayType(typeStruct))
+ prodesc->arg_arraytype[i] = argtype;
+ else
+ prodesc->arg_arraytype[i] = InvalidOid;
+
+ ReleaseSysCache(typeTup);
+ }
+ }
+
+ /************************************************************
+ * create the text of the anonymous subroutine.
+ * we do not use a named subroutine so that we can call directly
+ * through the reference.
+ ************************************************************/
+ prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+ proc_source = TextDatumGetCString(prosrcdatum);
+
+ /************************************************************
+ * Create the procedure in the appropriate interpreter
+ ************************************************************/
+
+ select_perl_context(prodesc->lanpltrusted);
+
+ prodesc->interp = plperl_active_interp;
+
+ plperl_create_sub(prodesc, proc_source, fn_oid);
+
+ activate_interpreter(oldinterp);
+
+ pfree(proc_source);
+
+ if (!prodesc->reference) /* can this happen? */
+ elog(ERROR, "could not create PL/Perl internal procedure");
+
+ /************************************************************
+ * OK, link the procedure into the correct hashtable entry.
+ * Note we assume that the hashtable entry either doesn't exist yet,
+ * or we already cleared its proc_ptr during the validation attempts
+ * above. So no need to decrement an old refcount here.
+ ************************************************************/
+ proc_key.user_id = prodesc->lanpltrusted ? GetUserId() : InvalidOid;
+
+ proc_ptr = hash_search(plperl_proc_hash, &proc_key,
+ HASH_ENTER, NULL);
+ /* We assume these two steps can't throw an error: */
+ proc_ptr->proc_ptr = prodesc;
+ increment_prodesc_refcount(prodesc);
+ }
+ PG_CATCH();
+ {
+ /*
+ * If we got as far as creating a reference, we should be able to use
+ * free_plperl_function() to clean up. If not, then at most we have
+ * some PG memory resources in proc_cxt, which we can just delete.
+ */
+ if (prodesc && prodesc->reference)
+ free_plperl_function(prodesc);
+ else if (proc_cxt)
+ MemoryContextDelete(proc_cxt);
+
+ /* Be sure to restore the previous interpreter, too, for luck */
+ activate_interpreter(oldinterp);
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* restore previous error callback */
+ error_context_stack = plperl_error_context.previous;
+
+ ReleaseSysCache(procTup);
+
+ return prodesc;
+}
+
+/* Build a hash from a given composite/row datum */
+static SV *
+plperl_hash_from_datum(Datum attr)
+{
+ HeapTupleHeader td;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ HeapTupleData tmptup;
+ SV *sv;
+
+ td = DatumGetHeapTupleHeader(attr);
+
+ /* Extract rowtype info and find a tupdesc */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ /* Build a temporary HeapTuple control structure */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ tmptup.t_data = td;
+
+ sv = plperl_hash_from_tuple(&tmptup, tupdesc, true);
+ ReleaseTupleDesc(tupdesc);
+
+ return sv;
+}
+
+/* Build a hash from all attributes of a given tuple. */
+static SV *
+plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc, bool include_generated)
+{
+ dTHX;
+ HV *hv;
+ int i;
+
+ /* since this function recurses, it could be driven to stack overflow */
+ check_stack_depth();
+
+ hv = newHV();
+ hv_ksplit(hv, tupdesc->natts); /* pre-grow the hash */
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Datum attr;
+ bool isnull,
+ typisvarlena;
+ char *attname;
+ Oid typoutput;
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ if (att->attisdropped)
+ continue;
+
+ if (att->attgenerated)
+ {
+ /* don't include unless requested */
+ if (!include_generated)
+ continue;
+ }
+
+ attname = NameStr(att->attname);
+ attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
+
+ if (isnull)
+ {
+ /*
+ * Store (attname => undef) and move on. Note we can't use
+ * &PL_sv_undef here; see "AVs, HVs and undefined values" in
+ * perlguts for an explanation.
+ */
+ hv_store_string(hv, attname, newSV(0));
+ continue;
+ }
+
+ if (type_is_rowtype(att->atttypid))
+ {
+ SV *sv = plperl_hash_from_datum(attr);
+
+ hv_store_string(hv, attname, sv);
+ }
+ else
+ {
+ SV *sv;
+ Oid funcid;
+
+ if (OidIsValid(get_base_element_type(att->atttypid)))
+ sv = plperl_ref_from_pg_array(attr, att->atttypid);
+ else if ((funcid = get_transform_fromsql(att->atttypid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr));
+ else
+ {
+ char *outputstr;
+
+ /* XXX should have a way to cache these lookups */
+ getTypeOutputInfo(att->atttypid, &typoutput, &typisvarlena);
+
+ outputstr = OidOutputFunctionCall(typoutput, attr);
+ sv = cstr2sv(outputstr);
+ pfree(outputstr);
+ }
+
+ hv_store_string(hv, attname, sv);
+ }
+ }
+ return newRV_noinc((SV *) hv);
+}
+
+
+static void
+check_spi_usage_allowed(void)
+{
+ /* see comment in plperl_fini() */
+ if (plperl_ending)
+ {
+ /* simple croak as we don't want to involve PostgreSQL code */
+ croak("SPI functions can not be used in END blocks");
+ }
+
+ /*
+ * Disallow SPI usage if we're not executing a fully-compiled plperl
+ * function. It might seem impossible to get here in that case, but there
+ * are cases where Perl will try to execute code during compilation. If
+ * we proceed we are likely to crash trying to dereference the prodesc
+ * pointer. Working around that might be possible, but it seems unwise
+ * because it'd allow code execution to happen while validating a
+ * function, which is undesirable.
+ */
+ if (current_call_data == NULL || current_call_data->prodesc == NULL)
+ {
+ /* simple croak as we don't want to involve PostgreSQL code */
+ croak("SPI functions can not be used during function compilation");
+ }
+}
+
+
+HV *
+plperl_spi_exec(char *query, int limit)
+{
+ HV *ret_hv;
+
+ /*
+ * Execute the query inside a sub-transaction, so we can cope with errors
+ * sanely
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ int spi_rv;
+
+ pg_verifymbstr(query, strlen(query), false);
+
+ spi_rv = SPI_execute(query, current_call_data->prodesc->fn_readonly,
+ limit);
+ ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed,
+ spi_rv);
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return ret_hv;
+}
+
+
+static HV *
+plperl_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 processed,
+ int status)
+{
+ dTHX;
+ HV *result;
+
+ check_spi_usage_allowed();
+
+ result = newHV();
+
+ hv_store_string(result, "status",
+ cstr2sv(SPI_result_code_string(status)));
+ hv_store_string(result, "processed",
+ (processed > (uint64) UV_MAX) ?
+ newSVnv((NV) processed) :
+ newSVuv((UV) processed));
+
+ if (status > 0 && tuptable)
+ {
+ AV *rows;
+ SV *row;
+ uint64 i;
+
+ /* Prevent overflow in call to av_extend() */
+ if (processed > (uint64) AV_SIZE_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("query result has too many rows to fit in a Perl array")));
+
+ rows = newAV();
+ av_extend(rows, processed);
+ for (i = 0; i < processed; i++)
+ {
+ row = plperl_hash_from_tuple(tuptable->vals[i], tuptable->tupdesc, true);
+ av_push(rows, row);
+ }
+ hv_store_string(result, "rows",
+ newRV_noinc((SV *) rows));
+ }
+
+ SPI_freetuptable(tuptable);
+
+ return result;
+}
+
+
+/*
+ * plperl_return_next catches any error and converts it to a Perl error.
+ * We assume (perhaps without adequate justification) that we need not abort
+ * the current transaction if the Perl code traps the error.
+ */
+void
+plperl_return_next(SV *sv)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ check_spi_usage_allowed();
+
+ PG_TRY();
+ {
+ plperl_return_next_internal(sv);
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Must reset elog.c's state */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+ }
+ PG_END_TRY();
+}
+
+/*
+ * plperl_return_next_internal reports any errors in Postgres fashion
+ * (via ereport).
+ */
+static void
+plperl_return_next_internal(SV *sv)
+{
+ plperl_proc_desc *prodesc;
+ FunctionCallInfo fcinfo;
+ ReturnSetInfo *rsi;
+ MemoryContext old_cxt;
+
+ if (!sv)
+ return;
+
+ prodesc = current_call_data->prodesc;
+ fcinfo = current_call_data->fcinfo;
+ rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!prodesc->fn_retisset)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use return_next in a non-SETOF function")));
+
+ if (!current_call_data->ret_tdesc)
+ {
+ TupleDesc tupdesc;
+
+ Assert(!current_call_data->tuple_store);
+
+ /*
+ * This is the first call to return_next in the current PL/Perl
+ * function call, so identify the output tuple type and create a
+ * tuplestore to hold the result rows.
+ */
+ if (prodesc->fn_retistuple)
+ {
+ TypeFuncClass funcclass;
+ Oid typid;
+
+ funcclass = get_call_result_type(fcinfo, &typid, &tupdesc);
+ if (funcclass != TYPEFUNC_COMPOSITE &&
+ funcclass != TYPEFUNC_COMPOSITE_DOMAIN)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+ /* if domain-over-composite, remember the domain's type OID */
+ if (funcclass == TYPEFUNC_COMPOSITE_DOMAIN)
+ current_call_data->cdomain_oid = typid;
+ }
+ else
+ {
+ tupdesc = rsi->expectedDesc;
+ /* Protect assumption below that we return exactly one column */
+ if (tupdesc == NULL || tupdesc->natts != 1)
+ elog(ERROR, "expected single-column result descriptor for non-composite SETOF result");
+ }
+
+ /*
+ * Make sure the tuple_store and ret_tdesc are sufficiently
+ * long-lived.
+ */
+ old_cxt = MemoryContextSwitchTo(rsi->econtext->ecxt_per_query_memory);
+
+ current_call_data->ret_tdesc = CreateTupleDescCopy(tupdesc);
+ current_call_data->tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ MemoryContextSwitchTo(old_cxt);
+ }
+
+ /*
+ * Producing the tuple we want to return requires making plenty of
+ * palloc() allocations that are not cleaned up. Since this function can
+ * be called many times before the current memory context is reset, we
+ * need to do those allocations in a temporary context.
+ */
+ if (!current_call_data->tmp_cxt)
+ {
+ current_call_data->tmp_cxt =
+ AllocSetContextCreate(CurrentMemoryContext,
+ "PL/Perl return_next temporary cxt",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+
+ old_cxt = MemoryContextSwitchTo(current_call_data->tmp_cxt);
+
+ if (prodesc->fn_retistuple)
+ {
+ HeapTuple tuple;
+
+ if (!(SvOK(sv) && SvROK(sv) && SvTYPE(SvRV(sv)) == SVt_PVHV))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("SETOF-composite-returning PL/Perl function "
+ "must call return_next with reference to hash")));
+
+ tuple = plperl_build_tuple_result((HV *) SvRV(sv),
+ current_call_data->ret_tdesc);
+
+ if (OidIsValid(current_call_data->cdomain_oid))
+ domain_check(HeapTupleGetDatum(tuple), false,
+ current_call_data->cdomain_oid,
+ &current_call_data->cdomain_info,
+ rsi->econtext->ecxt_per_query_memory);
+
+ tuplestore_puttuple(current_call_data->tuple_store, tuple);
+ }
+ else if (prodesc->result_oid)
+ {
+ Datum ret[1];
+ bool isNull[1];
+
+ ret[0] = plperl_sv_to_datum(sv,
+ prodesc->result_oid,
+ -1,
+ fcinfo,
+ &prodesc->result_in_func,
+ prodesc->result_typioparam,
+ &isNull[0]);
+
+ tuplestore_putvalues(current_call_data->tuple_store,
+ current_call_data->ret_tdesc,
+ ret, isNull);
+ }
+
+ MemoryContextSwitchTo(old_cxt);
+ MemoryContextReset(current_call_data->tmp_cxt);
+}
+
+
+SV *
+plperl_spi_query(char *query)
+{
+ SV *cursor;
+
+ /*
+ * Execute the query inside a sub-transaction, so we can cope with errors
+ * sanely
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ SPIPlanPtr plan;
+ Portal portal;
+
+ /* Make sure the query is validly encoded */
+ pg_verifymbstr(query, strlen(query), false);
+
+ /* Create a cursor for the query */
+ plan = SPI_prepare(query, 0, NULL);
+ if (plan == NULL)
+ elog(ERROR, "SPI_prepare() failed:%s",
+ SPI_result_code_string(SPI_result));
+
+ portal = SPI_cursor_open(NULL, plan, NULL, NULL, false);
+ SPI_freeplan(plan);
+ if (portal == NULL)
+ elog(ERROR, "SPI_cursor_open() failed:%s",
+ SPI_result_code_string(SPI_result));
+ cursor = cstr2sv(portal->name);
+
+ PinPortal(portal);
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return cursor;
+}
+
+
+SV *
+plperl_spi_fetchrow(char *cursor)
+{
+ SV *row;
+
+ /*
+ * Execute the FETCH inside a sub-transaction, so we can cope with errors
+ * sanely
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ dTHX;
+ Portal p = SPI_cursor_find(cursor);
+
+ if (!p)
+ {
+ row = &PL_sv_undef;
+ }
+ else
+ {
+ SPI_cursor_fetch(p, true, 1);
+ if (SPI_processed == 0)
+ {
+ UnpinPortal(p);
+ SPI_cursor_close(p);
+ row = &PL_sv_undef;
+ }
+ else
+ {
+ row = plperl_hash_from_tuple(SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc,
+ true);
+ }
+ SPI_freetuptable(SPI_tuptable);
+ }
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return row;
+}
+
+void
+plperl_spi_cursor_close(char *cursor)
+{
+ Portal p;
+
+ check_spi_usage_allowed();
+
+ p = SPI_cursor_find(cursor);
+
+ if (p)
+ {
+ UnpinPortal(p);
+ SPI_cursor_close(p);
+ }
+}
+
+SV *
+plperl_spi_prepare(char *query, int argc, SV **argv)
+{
+ volatile SPIPlanPtr plan = NULL;
+ volatile MemoryContext plan_cxt = NULL;
+ plperl_query_desc *volatile qdesc = NULL;
+ plperl_query_entry *volatile hash_entry = NULL;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ MemoryContext work_cxt;
+ bool found;
+ int i;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ CHECK_FOR_INTERRUPTS();
+
+ /************************************************************
+ * Allocate the new querydesc structure
+ *
+ * The qdesc struct, as well as all its subsidiary data, lives in its
+ * plan_cxt. But note that the SPIPlan does not.
+ ************************************************************/
+ plan_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Perl spi_prepare query",
+ ALLOCSET_SMALL_SIZES);
+ MemoryContextSwitchTo(plan_cxt);
+ qdesc = (plperl_query_desc *) palloc0(sizeof(plperl_query_desc));
+ snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
+ qdesc->plan_cxt = plan_cxt;
+ qdesc->nargs = argc;
+ qdesc->argtypes = (Oid *) palloc(argc * sizeof(Oid));
+ qdesc->arginfuncs = (FmgrInfo *) palloc(argc * sizeof(FmgrInfo));
+ qdesc->argtypioparams = (Oid *) palloc(argc * sizeof(Oid));
+ MemoryContextSwitchTo(oldcontext);
+
+ /************************************************************
+ * Do the following work in a short-lived context so that we don't
+ * leak a lot of memory in the PL/Perl function's SPI Proc context.
+ ************************************************************/
+ work_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "PL/Perl spi_prepare workspace",
+ ALLOCSET_DEFAULT_SIZES);
+ MemoryContextSwitchTo(work_cxt);
+
+ /************************************************************
+ * Resolve argument type names and then look them up by oid
+ * in the system cache, and remember the required information
+ * for input conversion.
+ ************************************************************/
+ for (i = 0; i < argc; i++)
+ {
+ Oid typId,
+ typInput,
+ typIOParam;
+ int32 typmod;
+ char *typstr;
+
+ typstr = sv2cstr(argv[i]);
+ parseTypeString(typstr, &typId, &typmod, false);
+ pfree(typstr);
+
+ getTypeInputInfo(typId, &typInput, &typIOParam);
+
+ qdesc->argtypes[i] = typId;
+ fmgr_info_cxt(typInput, &(qdesc->arginfuncs[i]), plan_cxt);
+ qdesc->argtypioparams[i] = typIOParam;
+ }
+
+ /* Make sure the query is validly encoded */
+ pg_verifymbstr(query, strlen(query), false);
+
+ /************************************************************
+ * Prepare the plan and check for errors
+ ************************************************************/
+ plan = SPI_prepare(query, argc, qdesc->argtypes);
+
+ if (plan == NULL)
+ elog(ERROR, "SPI_prepare() failed:%s",
+ SPI_result_code_string(SPI_result));
+
+ /************************************************************
+ * Save the plan into permanent memory (right now it's in the
+ * SPI procCxt, which will go away at function end).
+ ************************************************************/
+ if (SPI_keepplan(plan))
+ elog(ERROR, "SPI_keepplan() failed");
+ qdesc->plan = plan;
+
+ /************************************************************
+ * Insert a hashtable entry for the plan.
+ ************************************************************/
+ hash_entry = hash_search(plperl_active_interp->query_hash,
+ qdesc->qname,
+ HASH_ENTER, &found);
+ hash_entry->query_data = qdesc;
+
+ /* Get rid of workspace */
+ MemoryContextDelete(work_cxt);
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Drop anything we managed to allocate */
+ if (hash_entry)
+ hash_search(plperl_active_interp->query_hash,
+ qdesc->qname,
+ HASH_REMOVE, NULL);
+ if (plan_cxt)
+ MemoryContextDelete(plan_cxt);
+ if (plan)
+ SPI_freeplan(plan);
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ /************************************************************
+ * Return the query's hash key to the caller.
+ ************************************************************/
+ return cstr2sv(qdesc->qname);
+}
+
+HV *
+plperl_spi_exec_prepared(char *query, HV *attr, int argc, SV **argv)
+{
+ HV *ret_hv;
+ SV **sv;
+ int i,
+ limit,
+ spi_rv;
+ char *nulls;
+ Datum *argvalues;
+ plperl_query_desc *qdesc;
+ plperl_query_entry *hash_entry;
+
+ /*
+ * Execute the query inside a sub-transaction, so we can cope with errors
+ * sanely
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ dTHX;
+
+ /************************************************************
+ * Fetch the saved plan descriptor, see if it's o.k.
+ ************************************************************/
+ hash_entry = hash_search(plperl_active_interp->query_hash, query,
+ HASH_FIND, NULL);
+ if (hash_entry == NULL)
+ elog(ERROR, "spi_exec_prepared: Invalid prepared query passed");
+
+ qdesc = hash_entry->query_data;
+ if (qdesc == NULL)
+ elog(ERROR, "spi_exec_prepared: plperl query_hash value vanished");
+
+ if (qdesc->nargs != argc)
+ elog(ERROR, "spi_exec_prepared: expected %d argument(s), %d passed",
+ qdesc->nargs, argc);
+
+ /************************************************************
+ * Parse eventual attributes
+ ************************************************************/
+ limit = 0;
+ if (attr != NULL)
+ {
+ sv = hv_fetch_string(attr, "limit");
+ if (sv && *sv && SvIOK(*sv))
+ limit = SvIV(*sv);
+ }
+ /************************************************************
+ * Set up arguments
+ ************************************************************/
+ if (argc > 0)
+ {
+ nulls = (char *) palloc(argc);
+ argvalues = (Datum *) palloc(argc * sizeof(Datum));
+ }
+ else
+ {
+ nulls = NULL;
+ argvalues = NULL;
+ }
+
+ for (i = 0; i < argc; i++)
+ {
+ bool isnull;
+
+ argvalues[i] = plperl_sv_to_datum(argv[i],
+ qdesc->argtypes[i],
+ -1,
+ NULL,
+ &qdesc->arginfuncs[i],
+ qdesc->argtypioparams[i],
+ &isnull);
+ nulls[i] = isnull ? 'n' : ' ';
+ }
+
+ /************************************************************
+ * go
+ ************************************************************/
+ spi_rv = SPI_execute_plan(qdesc->plan, argvalues, nulls,
+ current_call_data->prodesc->fn_readonly, limit);
+ ret_hv = plperl_spi_execute_fetch_result(SPI_tuptable, SPI_processed,
+ spi_rv);
+ if (argc > 0)
+ {
+ pfree(argvalues);
+ pfree(nulls);
+ }
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return ret_hv;
+}
+
+SV *
+plperl_spi_query_prepared(char *query, int argc, SV **argv)
+{
+ int i;
+ char *nulls;
+ Datum *argvalues;
+ plperl_query_desc *qdesc;
+ plperl_query_entry *hash_entry;
+ SV *cursor;
+ Portal portal = NULL;
+
+ /*
+ * Execute the query inside a sub-transaction, so we can cope with errors
+ * sanely
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ check_spi_usage_allowed();
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ /************************************************************
+ * Fetch the saved plan descriptor, see if it's o.k.
+ ************************************************************/
+ hash_entry = hash_search(plperl_active_interp->query_hash, query,
+ HASH_FIND, NULL);
+ if (hash_entry == NULL)
+ elog(ERROR, "spi_query_prepared: Invalid prepared query passed");
+
+ qdesc = hash_entry->query_data;
+ if (qdesc == NULL)
+ elog(ERROR, "spi_query_prepared: plperl query_hash value vanished");
+
+ if (qdesc->nargs != argc)
+ elog(ERROR, "spi_query_prepared: expected %d argument(s), %d passed",
+ qdesc->nargs, argc);
+
+ /************************************************************
+ * Set up arguments
+ ************************************************************/
+ if (argc > 0)
+ {
+ nulls = (char *) palloc(argc);
+ argvalues = (Datum *) palloc(argc * sizeof(Datum));
+ }
+ else
+ {
+ nulls = NULL;
+ argvalues = NULL;
+ }
+
+ for (i = 0; i < argc; i++)
+ {
+ bool isnull;
+
+ argvalues[i] = plperl_sv_to_datum(argv[i],
+ qdesc->argtypes[i],
+ -1,
+ NULL,
+ &qdesc->arginfuncs[i],
+ qdesc->argtypioparams[i],
+ &isnull);
+ nulls[i] = isnull ? 'n' : ' ';
+ }
+
+ /************************************************************
+ * go
+ ************************************************************/
+ portal = SPI_cursor_open(NULL, qdesc->plan, argvalues, nulls,
+ current_call_data->prodesc->fn_readonly);
+ if (argc > 0)
+ {
+ pfree(argvalues);
+ pfree(nulls);
+ }
+ if (portal == NULL)
+ elog(ERROR, "SPI_cursor_open() failed:%s",
+ SPI_result_code_string(SPI_result));
+
+ cursor = cstr2sv(portal->name);
+
+ PinPortal(portal);
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+
+ /* Can't get here, but keep compiler quiet */
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return cursor;
+}
+
+void
+plperl_spi_freeplan(char *query)
+{
+ SPIPlanPtr plan;
+ plperl_query_desc *qdesc;
+ plperl_query_entry *hash_entry;
+
+ check_spi_usage_allowed();
+
+ hash_entry = hash_search(plperl_active_interp->query_hash, query,
+ HASH_FIND, NULL);
+ if (hash_entry == NULL)
+ elog(ERROR, "spi_freeplan: Invalid prepared query passed");
+
+ qdesc = hash_entry->query_data;
+ if (qdesc == NULL)
+ elog(ERROR, "spi_freeplan: plperl query_hash value vanished");
+ plan = qdesc->plan;
+
+ /*
+ * free all memory before SPI_freeplan, so if it dies, nothing will be
+ * left over
+ */
+ hash_search(plperl_active_interp->query_hash, query,
+ HASH_REMOVE, NULL);
+
+ MemoryContextDelete(qdesc->plan_cxt);
+
+ SPI_freeplan(plan);
+}
+
+void
+plperl_spi_commit(void)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ check_spi_usage_allowed();
+
+ PG_TRY();
+ {
+ SPI_commit();
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+ }
+ PG_END_TRY();
+}
+
+void
+plperl_spi_rollback(void)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ check_spi_usage_allowed();
+
+ PG_TRY();
+ {
+ SPI_rollback();
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+ }
+ PG_END_TRY();
+}
+
+/*
+ * Implementation of plperl's elog() function
+ *
+ * If the error level is less than ERROR, we'll just emit the message and
+ * return. When it is ERROR, elog() will longjmp, which we catch and
+ * turn into a Perl croak(). Note we are assuming that elog() can't have
+ * any internal failures that are so bad as to require a transaction abort.
+ *
+ * The main reason this is out-of-line is to avoid conflicts between XSUB.h
+ * and the PG_TRY macros.
+ */
+void
+plperl_util_elog(int level, SV *msg)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ char *volatile cmsg = NULL;
+
+ /*
+ * We intentionally omit check_spi_usage_allowed() here, as this seems
+ * safe to allow even in the contexts that that function rejects.
+ */
+
+ PG_TRY();
+ {
+ cmsg = sv2cstr(msg);
+ elog(level, "%s", cmsg);
+ pfree(cmsg);
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Must reset elog.c's state */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ if (cmsg)
+ pfree(cmsg);
+
+ /* Punt the error to Perl */
+ croak_cstr(edata->message);
+ }
+ PG_END_TRY();
+}
+
+/*
+ * Store an SV into a hash table under a key that is a string assumed to be
+ * in the current database's encoding.
+ */
+static SV **
+hv_store_string(HV *hv, const char *key, SV *val)
+{
+ dTHX;
+ int32 hlen;
+ char *hkey;
+ SV **ret;
+
+ hkey = pg_server_to_any(key, strlen(key), PG_UTF8);
+
+ /*
+ * hv_store() recognizes a negative klen parameter as meaning a UTF-8
+ * encoded key.
+ */
+ hlen = -(int) strlen(hkey);
+ ret = hv_store(hv, hkey, hlen, val, 0);
+
+ if (hkey != key)
+ pfree(hkey);
+
+ return ret;
+}
+
+/*
+ * Fetch an SV from a hash table under a key that is a string assumed to be
+ * in the current database's encoding.
+ */
+static SV **
+hv_fetch_string(HV *hv, const char *key)
+{
+ dTHX;
+ int32 hlen;
+ char *hkey;
+ SV **ret;
+
+ hkey = pg_server_to_any(key, strlen(key), PG_UTF8);
+
+ /* See notes in hv_store_string */
+ hlen = -(int) strlen(hkey);
+ ret = hv_fetch(hv, hkey, hlen, 0);
+
+ if (hkey != key)
+ pfree(hkey);
+
+ return ret;
+}
+
+/*
+ * Provide function name for PL/Perl execution errors
+ */
+static void
+plperl_exec_callback(void *arg)
+{
+ char *procname = (char *) arg;
+
+ if (procname)
+ errcontext("PL/Perl function \"%s\"", procname);
+}
+
+/*
+ * Provide function name for PL/Perl compilation errors
+ */
+static void
+plperl_compile_callback(void *arg)
+{
+ char *procname = (char *) arg;
+
+ if (procname)
+ errcontext("compilation of PL/Perl function \"%s\"", procname);
+}
+
+/*
+ * Provide error context for the inline handler
+ */
+static void
+plperl_inline_callback(void *arg)
+{
+ errcontext("PL/Perl anonymous code block");
+}
+
+
+/*
+ * Perl's own setlocale(), copied from POSIX.xs
+ * (needed because of the calls to new_*())
+ *
+ * Starting in 5.28, perl exposes Perl_setlocale to do so.
+ */
+#if defined(WIN32) && PERL_VERSION_LT(5, 28, 0)
+static char *
+setlocale_perl(int category, char *locale)
+{
+ dTHX;
+ char *RETVAL = setlocale(category, locale);
+
+ if (RETVAL)
+ {
+#ifdef USE_LOCALE_CTYPE
+ if (category == LC_CTYPE
+#ifdef LC_ALL
+ || category == LC_ALL
+#endif
+ )
+ {
+ char *newctype;
+
+#ifdef LC_ALL
+ if (category == LC_ALL)
+ newctype = setlocale(LC_CTYPE, NULL);
+ else
+#endif
+ newctype = RETVAL;
+ new_ctype(newctype);
+ }
+#endif /* USE_LOCALE_CTYPE */
+#ifdef USE_LOCALE_COLLATE
+ if (category == LC_COLLATE
+#ifdef LC_ALL
+ || category == LC_ALL
+#endif
+ )
+ {
+ char *newcoll;
+
+#ifdef LC_ALL
+ if (category == LC_ALL)
+ newcoll = setlocale(LC_COLLATE, NULL);
+ else
+#endif
+ newcoll = RETVAL;
+ new_collate(newcoll);
+ }
+#endif /* USE_LOCALE_COLLATE */
+
+#ifdef USE_LOCALE_NUMERIC
+ if (category == LC_NUMERIC
+#ifdef LC_ALL
+ || category == LC_ALL
+#endif
+ )
+ {
+ char *newnum;
+
+#ifdef LC_ALL
+ if (category == LC_ALL)
+ newnum = setlocale(LC_NUMERIC, NULL);
+ else
+#endif
+ newnum = RETVAL;
+ new_numeric(newnum);
+ }
+#endif /* USE_LOCALE_NUMERIC */
+ }
+
+ return RETVAL;
+}
+#endif /* defined(WIN32) && PERL_VERSION_LT(5, 28, 0) */
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
new file mode 100644
index 0000000..3a2230a
--- /dev/null
+++ b/src/pl/plperl/plperl.control
@@ -0,0 +1,8 @@
+# plperl extension
+comment = 'PL/Perl procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/plperl'
+relocatable = false
+schema = pg_catalog
+superuser = true
+trusted = true
diff --git a/src/pl/plperl/plperl.h b/src/pl/plperl/plperl.h
new file mode 100644
index 0000000..ebca895
--- /dev/null
+++ b/src/pl/plperl/plperl.h
@@ -0,0 +1,227 @@
+/*-------------------------------------------------------------------------
+ *
+ * plperl.h
+ * Common include file for PL/Perl files
+ *
+ * This should be included _AFTER_ postgres.h and system include files
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1995, Regents of the University of California
+ *
+ * src/pl/plperl/plperl.h
+ */
+
+#ifndef PL_PERL_H
+#define PL_PERL_H
+
+/* stop perl headers from hijacking stdio and other stuff on Windows */
+#ifdef WIN32
+#define WIN32IO_IS_STDIO
+#endif /* WIN32 */
+
+/*
+ * Supply a value of PERL_UNUSED_DECL that will satisfy gcc - the one
+ * perl itself supplies doesn't seem to.
+ */
+#define PERL_UNUSED_DECL pg_attribute_unused()
+
+/*
+ * Sometimes perl carefully scribbles on our *printf macros.
+ * So we undefine them here and redefine them after it's done its dirty deed.
+ */
+#undef vsnprintf
+#undef snprintf
+#undef vsprintf
+#undef sprintf
+#undef vfprintf
+#undef fprintf
+#undef vprintf
+#undef printf
+
+/*
+ * Perl scribbles on the "_" macro too.
+ */
+#undef _
+
+/*
+ * ActivePerl 5.18 and later are MinGW-built, and their headers use GCC's
+ * __inline__. Translate to something MSVC recognizes. Also, perl.h sometimes
+ * defines isnan, so undefine it here and put back the definition later if
+ * perl.h doesn't.
+ */
+#ifdef _MSC_VER
+#define __inline__ inline
+#ifdef isnan
+#undef isnan
+#endif
+/* Work around for using MSVC and Strawberry Perl >= 5.30. */
+#define __builtin_expect(expr, val) (expr)
+#endif
+
+/*
+ * Regarding bool, both PostgreSQL and Perl might use stdbool.h or not,
+ * depending on configuration. If both agree, things are relatively harmless.
+ * If not, things get tricky. If PostgreSQL does but Perl does not, define
+ * HAS_BOOL here so that Perl does not redefine bool; this avoids compiler
+ * warnings. If PostgreSQL does not but Perl does, we need to undefine bool
+ * after we include the Perl headers; see below.
+ */
+#ifdef PG_USE_STDBOOL
+#define HAS_BOOL 1
+#endif
+
+/*
+ * Newer versions of the perl headers trigger a lot of warnings with our
+ * compiler flags (at least -Wdeclaration-after-statement,
+ * -Wshadow=compatible-local are known to be problematic). The system_header
+ * pragma hides warnings from within the rest of this file, if supported.
+ */
+#ifdef HAVE_PRAGMA_GCC_SYSTEM_HEADER
+#pragma GCC system_header
+#endif
+
+/*
+ * Get the basic Perl API. We use PERL_NO_GET_CONTEXT mode so that our code
+ * can compile against MULTIPLICITY Perl builds without including XSUB.h.
+ */
+#define PERL_NO_GET_CONTEXT
+#include "EXTERN.h"
+#include "perl.h"
+
+/*
+ * We want to include XSUB.h only within .xs files, because on some platforms
+ * it undesirably redefines a lot of libc functions. But it must appear
+ * before ppport.h, so use a #define flag to control inclusion here.
+ */
+#ifdef PG_NEED_PERL_XSUB_H
+/*
+ * On Windows, win32_port.h defines macros for a lot of these same functions.
+ * To avoid compiler warnings when XSUB.h redefines them, #undef our versions.
+ */
+#ifdef WIN32
+#undef accept
+#undef bind
+#undef connect
+#undef fopen
+#undef fstat
+#undef kill
+#undef listen
+#undef lstat
+#undef mkdir
+#undef open
+#undef putenv
+#undef recv
+#undef rename
+#undef select
+#undef send
+#undef socket
+#undef stat
+#undef unlink
+#endif
+
+#include "XSUB.h"
+#endif
+
+/* put back our *printf macros ... this must match src/include/port.h */
+#ifdef vsnprintf
+#undef vsnprintf
+#endif
+#ifdef snprintf
+#undef snprintf
+#endif
+#ifdef vsprintf
+#undef vsprintf
+#endif
+#ifdef sprintf
+#undef sprintf
+#endif
+#ifdef vfprintf
+#undef vfprintf
+#endif
+#ifdef fprintf
+#undef fprintf
+#endif
+#ifdef vprintf
+#undef vprintf
+#endif
+#ifdef printf
+#undef printf
+#endif
+
+#define vsnprintf pg_vsnprintf
+#define snprintf pg_snprintf
+#define vsprintf pg_vsprintf
+#define sprintf pg_sprintf
+#define vfprintf pg_vfprintf
+#define fprintf pg_fprintf
+#define vprintf pg_vprintf
+#define printf(...) pg_printf(__VA_ARGS__)
+
+/*
+ * Put back "_" too; but rather than making it just gettext() as the core
+ * code does, make it dgettext() so that the right things will happen in
+ * loadable modules (if they've set up TEXTDOMAIN correctly). Note that
+ * we can't just set TEXTDOMAIN here, because this file is used by more
+ * extensions than just PL/Perl itself.
+ */
+#undef _
+#define _(x) dgettext(TEXTDOMAIN, x)
+
+/* put back the definition of isnan if needed */
+#ifdef _MSC_VER
+#ifndef isnan
+#define isnan(x) _isnan(x)
+#endif
+#endif
+
+/* perl version and platform portability */
+#include "ppport.h"
+
+/*
+ * perl might have included stdbool.h. If we also did that earlier (see c.h),
+ * then that's fine. If not, we probably rejected it for some reason. In
+ * that case, undef bool and proceed with our own bool. (Note that stdbool.h
+ * makes bool a macro, but our own replacement is a typedef, so the undef
+ * makes ours visible again).
+ */
+#ifndef PG_USE_STDBOOL
+#ifdef bool
+#undef bool
+#endif
+#endif
+
+/* supply HeUTF8 if it's missing - ppport.h doesn't supply it, unfortunately */
+#ifndef HeUTF8
+#define HeUTF8(he) ((HeKLEN(he) == HEf_SVKEY) ? \
+ SvUTF8(HeKEY_sv(he)) : \
+ (U32)HeKUTF8(he))
+#endif
+
+/* supply GvCV_set if it's missing - ppport.h doesn't supply it, unfortunately */
+#ifndef GvCV_set
+#define GvCV_set(gv, cv) (GvCV(gv) = cv)
+#endif
+
+/* Perl 5.19.4 changed array indices from I32 to SSize_t */
+#if PERL_BCDVERSION >= 0x5019004
+#define AV_SIZE_MAX SSize_t_MAX
+#else
+#define AV_SIZE_MAX I32_MAX
+#endif
+
+/* declare routines from plperl.c for access by .xs files */
+HV *plperl_spi_exec(char *, int);
+void plperl_return_next(SV *);
+SV *plperl_spi_query(char *);
+SV *plperl_spi_fetchrow(char *);
+SV *plperl_spi_prepare(char *, int, SV **);
+HV *plperl_spi_exec_prepared(char *, HV *, int, SV **);
+SV *plperl_spi_query_prepared(char *, int, SV **);
+void plperl_spi_freeplan(char *);
+void plperl_spi_cursor_close(char *);
+void plperl_spi_commit(void);
+void plperl_spi_rollback(void);
+char *plperl_sv_to_literal(SV *, char *);
+void plperl_util_elog(int level, SV *msg);
+
+#endif /* PL_PERL_H */
diff --git a/src/pl/plperl/plperl_helpers.h b/src/pl/plperl/plperl_helpers.h
new file mode 100644
index 0000000..1e318b6
--- /dev/null
+++ b/src/pl/plperl/plperl_helpers.h
@@ -0,0 +1,171 @@
+#ifndef PL_PERL_HELPERS_H
+#define PL_PERL_HELPERS_H
+
+#include "mb/pg_wchar.h"
+
+#include "plperl.h"
+
+
+/*
+ * convert from utf8 to database encoding
+ *
+ * Returns a palloc'ed copy of the original string
+ */
+static inline char *
+utf_u2e(char *utf8_str, size_t len)
+{
+ char *ret;
+
+ ret = pg_any_to_server(utf8_str, len, PG_UTF8);
+
+ /* ensure we have a copy even if no conversion happened */
+ if (ret == utf8_str)
+ ret = pstrdup(ret);
+
+ return ret;
+}
+
+/*
+ * convert from database encoding to utf8
+ *
+ * Returns a palloc'ed copy of the original string
+ */
+static inline char *
+utf_e2u(const char *str)
+{
+ char *ret;
+
+ ret = pg_server_to_any(str, strlen(str), PG_UTF8);
+
+ /* ensure we have a copy even if no conversion happened */
+ if (ret == str)
+ ret = pstrdup(ret);
+
+ return ret;
+}
+
+
+/*
+ * Convert an SV to a char * in the current database encoding
+ *
+ * Returns a palloc'ed copy of the original string
+ */
+static inline char *
+sv2cstr(SV *sv)
+{
+ dTHX;
+ char *val,
+ *res;
+ STRLEN len;
+
+ /*
+ * get a utf8 encoded char * out of perl. *note* it may not be valid utf8!
+ */
+
+ /*
+ * SvPVutf8() croaks nastily on certain things, like typeglobs and
+ * readonly objects such as $^V. That's a perl bug - it's not supposed to
+ * happen. To avoid crashing the backend, we make a copy of the sv before
+ * passing it to SvPVutf8(). The copy is garbage collected when we're done
+ * with it.
+ */
+ if (SvREADONLY(sv) ||
+ isGV_with_GP(sv) ||
+ (SvTYPE(sv) > SVt_PVLV && SvTYPE(sv) != SVt_PVFM))
+ sv = newSVsv(sv);
+ else
+ {
+ /*
+ * increase the reference count so we can just SvREFCNT_dec() it when
+ * we are done
+ */
+ SvREFCNT_inc_simple_void(sv);
+ }
+
+ /*
+ * Request the string from Perl, in UTF-8 encoding; but if we're in a
+ * SQL_ASCII database, just request the byte soup without trying to make
+ * it UTF8, because that might fail.
+ */
+ if (GetDatabaseEncoding() == PG_SQL_ASCII)
+ val = SvPV(sv, len);
+ else
+ val = SvPVutf8(sv, len);
+
+ /*
+ * Now convert to database encoding. We use perl's length in the event we
+ * had an embedded null byte to ensure we error out properly.
+ */
+ res = utf_u2e(val, len);
+
+ /* safe now to garbage collect the new SV */
+ SvREFCNT_dec(sv);
+
+ return res;
+}
+
+/*
+ * Create a new SV from a string assumed to be in the current database's
+ * encoding.
+ */
+static inline SV *
+cstr2sv(const char *str)
+{
+ dTHX;
+ SV *sv;
+ char *utf8_str;
+
+ /* no conversion when SQL_ASCII */
+ if (GetDatabaseEncoding() == PG_SQL_ASCII)
+ return newSVpv(str, 0);
+
+ utf8_str = utf_e2u(str);
+
+ sv = newSVpv(utf8_str, 0);
+ SvUTF8_on(sv);
+ pfree(utf8_str);
+
+ return sv;
+}
+
+/*
+ * croak() with specified message, which is given in the database encoding.
+ *
+ * Ideally we'd just write croak("%s", str), but plain croak() does not play
+ * nice with non-ASCII data. In modern Perl versions we can call cstr2sv()
+ * and pass the result to croak_sv(); in versions that don't have croak_sv(),
+ * we have to work harder.
+ */
+static inline void
+croak_cstr(const char *str)
+{
+ dTHX;
+
+#ifdef croak_sv
+ /* Use sv_2mortal() to be sure the transient SV gets freed */
+ croak_sv(sv_2mortal(cstr2sv(str)));
+#else
+
+ /*
+ * The older way to do this is to assign a UTF8-marked value to ERRSV and
+ * then call croak(NULL). But if we leave it to croak() to append the
+ * error location, it does so too late (only after popping the stack) in
+ * some Perl versions. Hence, use mess() to create an SV with the error
+ * location info already appended.
+ */
+ SV *errsv = get_sv("@", GV_ADD);
+ char *utf8_str = utf_e2u(str);
+ SV *ssv;
+
+ ssv = mess("%s", utf8_str);
+ SvUTF8_on(ssv);
+
+ pfree(utf8_str);
+
+ sv_setsv(errsv, ssv);
+
+ croak(NULL);
+#endif /* croak_sv */
+}
+
+#endif /* PL_PERL_HELPERS_H */
diff --git a/src/pl/plperl/plperl_opmask.pl b/src/pl/plperl/plperl_opmask.pl
new file mode 100644
index 0000000..4972043
--- /dev/null
+++ b/src/pl/plperl/plperl_opmask.pl
@@ -0,0 +1,65 @@
+#!perl
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+use Opcode qw(opset opset_to_ops opdesc);
+
+my $plperl_opmask_h = shift
+ or die "Usage: $0 <output_filename.h>\n";
+
+my $plperl_opmask_tmp = $plperl_opmask_h . "tmp";
+END { unlink $plperl_opmask_tmp }
+
+open my $fh, ">", "$plperl_opmask_tmp"
+ or die "Could not write to $plperl_opmask_tmp: $!";
+
+printf $fh "#define PLPERL_SET_OPMASK(opmask) \\\n";
+printf $fh " memset(opmask, 1, MAXO);\t/* disable all */ \\\n";
+printf $fh " /* then allow some... */ \\\n";
+
+my @allowed_ops = (
+
+ # basic set of opcodes
+ qw[:default :base_math !:base_io sort time],
+
+ # require is safe because we redirect the opcode
+ # entereval is safe as the opmask is now permanently set
+ # caller is safe because the entire interpreter is locked down
+ qw[require entereval caller],
+
+ # These are needed for utf8_heavy.pl:
+ # dofile is safe because we redirect the opcode like require above
+ # print is safe because the only writable filehandles are STDOUT & STDERR
+ # prtf (printf) is safe as it's the same as print + sprintf
+ qw[dofile print prtf],
+
+ # Disallow these opcodes that are in the :base_orig optag
+ # (included in :default) but aren't considered sufficiently safe
+ qw[!dbmopen !setpgrp !setpriority],
+
+ # custom is not deemed a likely security risk as it can't be generated from
+ # perl so would only be seen if the DBA had chosen to load a module that
+ # used it. Even then it's unlikely to be seen because it's typically
+ # generated by compiler plugins that operate after PL_op_mask checks.
+ # But we err on the side of caution and disable it
+ qw[!custom],);
+
+printf $fh " /* ALLOWED: @allowed_ops */ \\\n";
+
+foreach my $opname (opset_to_ops(opset(@allowed_ops)))
+{
+ printf $fh qq{ opmask[OP_%-12s] = 0;\t/* %s */ \\\n},
+ uc($opname), opdesc($opname);
+}
+printf $fh " /* end */\n";
+
+close $fh
+ or die "Error closing $plperl_opmask_tmp: $!";
+
+rename $plperl_opmask_tmp, $plperl_opmask_h
+ or die "Error renaming $plperl_opmask_tmp to $plperl_opmask_h: $!";
+
+exit 0;
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
new file mode 100644
index 0000000..10d7594
--- /dev/null
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -0,0 +1,17 @@
+/* src/pl/plperl/plperlu--1.0.sql */
+
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+ HANDLER plperlu_call_handler
+ INLINE plperlu_inline_handler
+ VALIDATOR plperlu_validator;
+
+COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/plperlu.control b/src/pl/plperl/plperlu.control
new file mode 100644
index 0000000..69473ca
--- /dev/null
+++ b/src/pl/plperl/plperlu.control
@@ -0,0 +1,7 @@
+# plperlu extension
+comment = 'PL/PerlU untrusted procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/plperl'
+relocatable = false
+schema = pg_catalog
+superuser = true
diff --git a/src/pl/plperl/po/cs.po b/src/pl/plperl/po/cs.po
new file mode 100644
index 0000000..0bded74
--- /dev/null
+++ b/src/pl/plperl/po/cs.po
@@ -0,0 +1,227 @@
+# Czech message translation file for plperl
+# Copyright (C) 2012 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Tomáš Vondra <tv@fuzzy.cz>, 2012, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl-cs (PostgreSQL 9.3)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-09-27 08:08+0000\n"
+"PO-Revision-Date: 2019-09-27 20:57+0200\n"
+"Last-Translator: Tomas Vondra <tv@fuzzy.cz>\n"
+"Language-Team: Czech <info@cspug.cx>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.3\n"
+
+#: plperl.c:406
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Pokud je true, trusted a untrusted Perl kód bude zkompilován ve striktním módu."
+
+#: plperl.c:420
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl inicializační kód spouštěný při inicializaci Perl interpreteru."
+
+#: plperl.c:442
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Perl inicializační kód spouštěný při prvním použití jazyka plperl."
+
+#: plperl.c:450
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Perl inicializační kód spouštěný při prvním použití jazyka plperlu."
+
+#: plperl.c:647
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "na této platformě nelze alokovat více Perl interpreterů"
+
+#: plperl.c:670 plperl.c:854 plperl.c:860 plperl.c:977 plperl.c:989
+#: plperl.c:1032 plperl.c:1055 plperl.c:2154 plperl.c:2264 plperl.c:2332
+#: plperl.c:2395
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:671
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "během spouštění PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:855
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "během parsování Perl inicializace"
+
+#: plperl.c:861
+#, c-format
+msgid "while running Perl initialization"
+msgstr "během běhu Perl inicializace"
+
+#: plperl.c:978
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "během spouštění PLC_TRUSTED"
+
+#: plperl.c:990
+#, c-format
+msgid "while executing utf8fix"
+msgstr "během spouštění utf8fix"
+
+#: plperl.c:1033
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "během spouštění plperl.on_plperl_init"
+
+#: plperl.c:1056
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "během spouštění plperl.on_plperlu_init"
+
+#: plperl.c:1102 plperl.c:1793
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl hash obsahuje neexistující sloupec \"%s\""
+
+#: plperl.c:1107 plperl.c:1798
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nelze nastavit systémový atribut \"%s\""
+
+#: plperl.c:1195
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "počet rozměrů pole (%d) překračuje povolené maximum (%d)"
+
+#: plperl.c:1207 plperl.c:1224
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "vícerozměrná pole musí mít výrazy s odpovídajícími rozměry"
+
+#: plperl.c:1260
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "Perlové pole nelze převést na typ %s který není pole"
+
+#: plperl.c:1363
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "Perlový hash nelze převést na nekompozitní typ %s"
+
+#: plperl.c:1385 plperl.c:3306
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkce vracející záznam byla zavolána z kontextu, který neumožňuje přijetí záznamu"
+
+#: plperl.c:1444
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "vyhledávání selhalo pro typ %s"
+
+#: plperl.c:1768
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} neexistuje"
+
+#: plperl.c:1772
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} není odkaz na hash"
+
+#: plperl.c:1803
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "nelze přiřazovat do generovaného sloupce \"%s\""
+
+#: plperl.c:2029 plperl.c:2871
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl funkce nemohou vracet datový typ %s"
+
+#: plperl.c:2042 plperl.c:2912
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl funkce nemohou přijímat datový typ %s"
+
+#: plperl.c:2159
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "z kompilované funkce se nepodařilo získat CODE referenci \"%s\""
+
+#: plperl.c:2252
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "z funkce nebyla získána návratová hodnota"
+
+#: plperl.c:2296 plperl.c:2363
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "nelze načíst $_TD"
+
+#: plperl.c:2320 plperl.c:2383
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "z triggeru nebyla získána návratová hodnota"
+
+#: plperl.c:2444
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "funkce vracející tabulku (set-valued) byla zavolána z kontextu, který neumožňuje přijetí tabulky"
+
+#: plperl.c:2489
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "PL/Perl funkce vracející tabulku (set-returned) musí vracet odkaz na pole nebo používat return_next."
+
+#: plperl.c:2610
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ignoruje modifikovaný řádek v DELETE triggeru"
+
+#: plperl.c:2618
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "výsledek PL/Perl trigger funkce musí být undef, \"SKIP\", nebo \"MODIFY\""
+
+#: plperl.c:2866
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funkce pro obsluhu triggerů mohou být volané pouze prostřednictvím triggerů"
+
+#: plperl.c:3213
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "výsledek dotazu má příliš mnoho řádek pro uložení do pole v Perlu"
+
+#: plperl.c:3283
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "return_next nelze použít v non-SETOF funkci (funkci nevracející tabulku)"
+
+#: plperl.c:3357
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "PL/Perl funkce vracející tabulku složených typů (SETOF-composite-returning) musí volat return_next s odkazem na hash"
+
+#: plperl.c:4132
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl funkce \"%s\""
+
+#: plperl.c:4144
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "kompilace PL/Perl funkce \"%s\""
+
+#: plperl.c:4153
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl anonymní blok kódu"
+
+#~ msgid "out of memory"
+#~ msgstr "paměť vyčerpána"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "PL/Perl funkce musí vracet odkaz na hash nebo pole"
diff --git a/src/pl/plperl/po/de.po b/src/pl/plperl/po/de.po
new file mode 100644
index 0000000..91260fe
--- /dev/null
+++ b/src/pl/plperl/po/de.po
@@ -0,0 +1,227 @@
+# German message translation file for plperl
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Peter Eisentraut <peter@eisentraut.org>, 2009 - 2022.
+#
+# Use these quotes: »%s«
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-08 12:09+0000\n"
+"PO-Revision-Date: 2022-04-08 14:39+0200\n"
+"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n"
+"Language-Team: German <pgsql-translators@postgresql.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Wenn wahr, dann wird vertrauenswürdiger und nicht vertrauenswürdiger Perl-Code im »strict«-Modus kompiliert."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl-Initialisierungscode, der ausgeführt wird, wenn der Perl-Interpreter initialisiert wird."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Perl-Initialisierungscode, der ausgeführt wird, wenn plperl zum ersten Mal benutzt wird."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Perl-Initialisierungscode, der ausgeführt wird, wenn plperlu zum ersten Mal benutzt wird."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "auf dieser Plattform können nicht mehrere Perl-Interpreter angelegt werden"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "beim Ausführen von PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "beim Parsen der Perl-Initialisierung"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "beim Ausführen der Perl-Initialisierung"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "beim Ausführen von PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "beim Ausführen von utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "beim Ausführen von plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "beim Ausführen von plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl-Hash enthält nicht existierende Spalte »%s«"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "Systemattribut »%s« kann nicht gesetzt werden"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "Anzahl der Arraydimensionen (%d) überschreitet erlaubtes Maximum (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "mehrdimensionale Arrays müssen Arraysausdrücke mit gleicher Anzahl Dimensionen haben"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "kann Perl-Array nicht in Nicht-Array-Typ %s umwandeln"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "kann Perl-Hash nicht in nicht zusammengesetzten Typ %s umwandeln"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "Funktion, die einen Record zurückgibt, in einem Zusammenhang aufgerufen, der Typ record nicht verarbeiten kann"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "Nachschlagen nach Typ %s fehlgeschlagen"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} existiert nicht"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} ist keine Hash-Referenz"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kann generierte Spalte »%s« nicht setzen"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl-Funktionen können keinen Rückgabetyp %s haben"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl-Funktionen können Typ %s nicht annehmen"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "keine CODE-Referenz erhalten beim Kompilieren von Funktion »%s«"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "keinen Rückgabewert aus Funktion erhalten"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "konnte $_TD nicht auslesen"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "keinen Rückgabewert aus Triggerfunktion erhalten"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "Funktion mit Mengenergebnis in einem Zusammenhang aufgerufen, der keine Mengenergebnisse verarbeiten kann"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "Materialisierungsmodus wird benötigt, ist aber in diesem Zusammenhang nicht erlaubt"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "PL/Perl-Funktionen mit Mengenergebnis müssen eine Referenz auf ein Array zurückgeben oder return_next verwenden"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "geänderte Zeile im DELETE-Trigger wird ignoriert"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "Ergebnis einer PL/Perl-Triggerfunktion muss undef, »SKIP« oder »MODIFY« sein"
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggerfunktionen können nur als Trigger aufgerufen werden"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "Anfrageergebnis hat zu viele Zeilen, um in ein Perl-Array zu passen"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "return_next kann nur in einer Funktion mit SETOF-Rückgabetyp verwendet werden"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "PL/Perl-Funktion, die SETOF eines zusammengesetzten Typs zurückgibt, muss return_next mit einer Referenz auf ein Hash aufrufen"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl-Funktion »%s«"
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "Kompilierung der PL/Perl-Funktion »%s«"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "anonymer PL/Perl-Codeblock"
diff --git a/src/pl/plperl/po/el.po b/src/pl/plperl/po/el.po
new file mode 100644
index 0000000..2be4cfe
--- /dev/null
+++ b/src/pl/plperl/po/el.po
@@ -0,0 +1,228 @@
+# Greek message translation file for plperl
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plperl (PostgreSQL) package.
+# Georgios Kokolatos <gkokolatos@pm.me>, 2021.
+#
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-14 09:09+0000\n"
+"PO-Revision-Date: 2023-04-14 15:00+0200\n"
+"Last-Translator: Georgios Kokolatos <gkokolatos@pm.me>\n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Εάν αληθής, αξιόπιστος και αναξιόπιστος κώδικας Perl θα μεταγλωττιστεί σε αυστηρή λειτουργία."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Κώδικας αρχικοποίησης Perl για να εκτελέσει όταν ένας διερμηνέας Perl αρχικοποιείται."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Κώδικας αρχικοποίησης Perl για να εκτελέσει μία φορά όταν plperl χρησιμοποιείται για πρώτη φορά."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Κώδικας αρχικοποίησης Perl για να εκτελέσει μία φορά όταν plperlu χρησιμοποιείται για πρώτη φορά."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "δεν είναι δυνατή η εκχώρηση πολλαπλών διερμηνέων Perl σε αυτήν την πλατφόρμα"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "κατά την εκτέλεση PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "κατά την ανάλυση αρχικοποίησης Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "κατά την εκτέλεση αρχικοποίησης Perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "κατά την εκτέλεση PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "κατά την εκτέλεση utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "κατά την εκτέλεση plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "κατά την εκτέλεση plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Κατατεμαχιστής Perl περιέχει ανύπαρκτη στήλη «%s»"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός του χαρακτηριστικού συστήματος «%s»"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "ο αριθμός των διαστάσεων συστυχίας (%d) υπερβαίνει το μέγιστο επιτρεπόμενο (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "πολυδιάστατες συστυχίες πρέπει να περιέχουν εκφράσεις συστυχίας με αντίστοιχο αριθμό διαστάσεων"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "δεν είναι δυνατή η μετατροπή συστυχίας Perl σε τύπο μή-συστυχίας %s"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "δεν είναι δυνατή η μετατροπή κατατμητή Perl σε μη-σύνθετος τύπο %s"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "συνάρτηση που επιστρέφει εγγραφή καλείται σε περιεχόμενο που δεν δύναται να αποδεχτεί τύπο εγγραφής"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "αναζήτηση απέτυχε για τύπο %s"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} δεν υπάρχει"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "Το >{new} $_TD δεν είναι αναφορά κατατμητή"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός δημιουργημένης στήλης «%s»"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Tcl συναρτήσεις δεν είναι δυνατό να επιστρέψουν τύπο %s"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Tcl συναρτήσεις δεν είναι δυνατό να δεχτούν τύπο %s"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "δεν έλαβε μία αναφορά CODE από τη μεταγλώττιση της συνάρτησης «%s»"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "δεν έλαβε ένα στοιχείο επιστροφής από συνάρτηση"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "δεν μπόρεσε να ανακτήσει $_TD"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "δεν έλαβε ένα στοιχείο επιστροφής από συνάρτηση ενεργοποίησης"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set-valued συνάρτηση καλείται σε περιεχόμενο που δεν μπορεί να δεχτεί ένα σύνολο"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "επιβάλλεται λειτουργία υλοποίησης, αλλά δεν επιτρέπεται σε αυτό το περιεχόμενο"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "συνάρτηση συνόλου PL/Perl πρέπει να επιστρέφει αναφορά σε συστοιχία ή να χρησιμοποιεί return_next"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "παράβλεψη τροποποιημένης σειράς σε εναύσμα DELETE"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "το αποτέλεσμα της συνάρτησης ενεργοποίησης PL/Perl πρέπει να είναι undef, «SKIP» ή «MODIFY»"
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "συναρτήσεις εναυσμάτων μπορούν να κληθούν μόνο ως εναύσματα"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "το αποτέλεσμα ερωτήματος περιέχει πάρα πολλές σειρές για να χωρέσει σε μία συστοιχία Perl"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "δεν είναι δυνατή η χρήση return_next σε συνάρτηση non-SETOF"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "συνάρτηση SETOF-composite-returning PL/Perl πρέπει να καλεί return_next με αναφορά σε κατάτμηση"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl συνάρτηση «%s»"
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "μεταγλώτιση της συνάρτησης PL/Perl «%s»"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl ανώνυμο μπλοκ κώδικα"
diff --git a/src/pl/plperl/po/es.po b/src/pl/plperl/po/es.po
new file mode 100644
index 0000000..ce1bc7f
--- /dev/null
+++ b/src/pl/plperl/po/es.po
@@ -0,0 +1,229 @@
+# Spanish message translation file for plperl
+#
+# Copyright (c) 2008-2021, PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Emanuel Calvo Franco <postgres.arg@gmail.com>, 2008.
+# Alvaro Herrera <alvherre@alvh.no-ip.org>, 2009-2012
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-07 16:39+0000\n"
+"PO-Revision-Date: 2022-10-20 09:06+0200\n"
+"Last-Translator: Carlos Chapi <carlos.chapi@2ndquadrant.com>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.7\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Si es verdadero, se compilará código Perl confiable y no confiable en modo «strict»."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Código Perl de inicialización a ejecutar cuando un intérprete Perl es inicializado."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Código Perl de inicialización a ejecutar cuando plperl se usa por primera vez."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Código Perl de inicialización a ejecutar cuando plperlu se usa por primera vez."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "no se pueden instanciar múltiples intérpretes Perl en esta plataforma"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2154 plperl.c:2262 plperl.c:2330
+#: plperl.c:2393
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "mientras se ejecutaba PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "mientras se interpretaba la inicialización de Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "mientras se ejecutaba la inicialización de Perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "mientras se ejecutaba PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "mientras se ejecutaba utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "mientras se ejecutaba plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "mientras se ejecutaba plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1807
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "el hash de Perl contiene el columna inexistente «%s»"
+
+#: plperl.c:1106 plperl.c:1812
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "no se puede definir el atributo de sistema «%s»"
+
+#: plperl.c:1202 plperl.c:1217 plperl.c:1234
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "los arrays multidimensionales deben tener expresiones de arrays con dimensiones coincidentes"
+
+#: plperl.c:1207
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "el número de dimensiones del array (%d) excede el máximo permitido (%d)"
+
+#: plperl.c:1277
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "no se puede convertir un array de Perl al tipo no-array %s"
+
+#: plperl.c:1378
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "no se puede convertir un hash de Perl al tipo no compuesto %s"
+
+#: plperl.c:1400 plperl.c:3320
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "se llamó una función que retorna un registro en un contexto que no puede aceptarlo"
+
+#: plperl.c:1461
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "búsqueda del tipo %s falló"
+
+#: plperl.c:1782
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} no existe"
+
+#: plperl.c:1786
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} no es una referencia a un hash"
+
+#: plperl.c:1817
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "no se puede definir la columna generada «%s»"
+
+#: plperl.c:2029 plperl.c:2870
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "las funciones en PL/Perl no pueden retornar el tipo %s"
+
+#: plperl.c:2042 plperl.c:2909
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "funciones de PL/Perl no pueden aceptar el tipo %s"
+
+#: plperl.c:2159
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "no se obtuvo una referencia CODE en la compilación de la función «%s»"
+
+#: plperl.c:2250
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "no se obtuvo un elemento de retorno desde la función"
+
+#: plperl.c:2294 plperl.c:2361
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "no se pudo obtener $_TD"
+
+#: plperl.c:2318 plperl.c:2381
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "no se obtuvo un elemento de retorno desde la función de disparador"
+
+#: plperl.c:2439
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "se llamó a una función que retorna un conjunto en un contexto que no puede aceptarlo"
+
+#: plperl.c:2444
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "se requiere un nodo «materialize», pero no está permitido en este contexto"
+
+#: plperl.c:2488
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "una función PL/Perl que retorna un conjunto debe retornar una referencia a un array o usar return_next"
+
+#: plperl.c:2609
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ignorando la tupla modificada en el disparador DELETE"
+
+#: plperl.c:2617
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "el resultado de la función disparadora en PL/Perl debe ser undef, «SKIP» o «MODIFY»"
+
+#: plperl.c:2865
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "las funciones disparadoras sólo pueden ser llamadas como disparadores"
+
+#: plperl.c:3225
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "el resultado de la consulta tiene demasiados registros y no entran en un array de Perl"
+
+#: plperl.c:3297
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "no se puede utilizar return_next en una función sin SETOF"
+
+#: plperl.c:3371
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "una función Perl que retorna SETOF de un tipo compuesto debe invocar return_next con una referencia a un hash"
+
+#: plperl.c:4153
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "función PL/Perl «%s»"
+
+#: plperl.c:4165
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "compilación de la función PL/Perl «%s»"
+
+#: plperl.c:4174
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "bloque de código anónimo de PL/Perl"
diff --git a/src/pl/plperl/po/fr.po b/src/pl/plperl/po/fr.po
new file mode 100644
index 0000000..fcb6d67
--- /dev/null
+++ b/src/pl/plperl/po/fr.po
@@ -0,0 +1,264 @@
+# LANGUAGE message translation file for plperl
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plperl (PostgreSQL) package.
+#
+# Use these quotes: « %s »
+#
+# Guillaume Lelarge <guillaume@lelarge.info>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-12 05:16+0000\n"
+"PO-Revision-Date: 2022-04-12 17:29+0200\n"
+"Last-Translator: Guillaume Lelarge <guillaume@lelarge.info>\n"
+"Language-Team: French <guillaume@lelarge.info>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr ""
+"Si true, le code Perl de confiance et sans confiance sera compilé en mode\n"
+"strict."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr ""
+"Code d'initialisation Perl à exécuter lorsque un interpréteur Perl est\n"
+"initialisé."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Code d'initialisation Perl à exécuter lorsque plperl est utilisé pour la première fois."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Code d'initialisation Perl à exécuter lorsque plperlu est utilisé pour la première fois."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "ne peut pas allouer plusieurs interpréteurs Perl sur cette plateforme"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "lors de l'exécution de PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "lors de l'analyse de l'initialisation de perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "lors de l'exécution de l'initialisation de perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "lors de l'exécution de PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "lors de l'exécution de utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "lors de l'exécution de plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "lors de l'exécution de plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Le hachage Perl contient la colonne « %s » inexistante"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "ne peut pas initialiser l'attribut système « %s »"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "le nombre de dimensions du tableau (%d) dépasse le maximum autorisé (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr ""
+"les tableaux multidimensionnels doivent avoir des expressions de tableaux\n"
+"avec les dimensions correspondantes"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "ne peut pas convertir le tableau Perl en un type %s qui n'est pas un tableau"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "ne peut pas convertir le hachage Perl en un type %s non composite"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr ""
+"fonction renvoyant le type record appelée dans un contexte qui ne peut pas\n"
+"accepter le type record"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "recherche échouée pour le type %s"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} n'existe pas"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} n'est pas une référence de hachage"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "ne peut pas initialiser la colonne générée « %s »"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "Les fonctions PL/perl ne peuvent pas renvoyer le type %s"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "Les fonctions PL/perl ne peuvent pas accepter le type %s"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "n'a pas obtenu une référence CODE lors de la compilation de la fonction « %s »"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "n'a pas obtenu un élément en retour de la fonction"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "n'a pas pu récupérer $_TD"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "n'a pas obtenu un élément en retour de la fonction trigger"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "la fonction renvoyant un ensemble a été appelée dans un contexte qui n'accepte pas un ensemble"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "mode matérialisé requis mais interdit dans ce contexte"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr ""
+"la fonction PL/perl renvoyant des ensembles doit renvoyer la référence à\n"
+"un tableau ou utiliser return_next"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ignore la ligne modifiée dans le trigger DELETE"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr ""
+"le résultat de la fonction trigger PL/perl doit être undef, « SKIP » ou\n"
+"« MODIFY »"
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "les fonctions trigger peuvent seulement être appelées par des triggers"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "le résultat de la requête contient trop de lignes pour être intégré dans un tableau Perl"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "ne peut pas utiliser return_next dans une fonction non SETOF"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr ""
+"une fonction PL/perl renvoyant des lignes composites doit appeler\n"
+"return_next avec la référence à un hachage"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "fonction PL/Perl « %s »"
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "compilation de la fonction PL/Perl « %s »"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "bloc de code PL/Perl anonyme"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "la fonction PL/perl doit renvoyer la référence à un hachage ou à un tableau"
+
+#~ msgid "composite-returning PL/Perl function must return reference to hash"
+#~ msgstr ""
+#~ "la fonction PL/perl renvoyant des valeurs composites doit renvoyer la\n"
+#~ "référence à un hachage"
+
+#~ msgid "creation of Perl function \"%s\" failed: %s"
+#~ msgstr "échec de la création de la fonction Perl « %s » : %s"
+
+#~ msgid "error from Perl function \"%s\": %s"
+#~ msgstr "échec dans la fonction Perl « %s » : %s"
+
+#~ msgid "out of memory"
+#~ msgstr "mémoire épuisée"
+
+#~ msgid "while executing PLC_SAFE_OK"
+#~ msgstr "lors de l'exécution de PLC_SAFE_OK"
diff --git a/src/pl/plperl/po/it.po b/src/pl/plperl/po/it.po
new file mode 100644
index 0000000..d2be326
--- /dev/null
+++ b/src/pl/plperl/po/it.po
@@ -0,0 +1,238 @@
+#
+# plperl.po
+# Italian message translation file for plperl
+#
+# For development and bug report please use:
+# https://github.com/dvarrazzo/postgresql-it
+#
+# Copyright (C) 2012-2017 PostgreSQL Global Development Group
+# Copyright (C) 2010, Associazione Culturale ITPUG
+#
+# Daniele Varrazzo <daniele.varrazzo@gmail.com>, 2012-2017.
+# Emanuele Zamprogno <emanuele.zamprogno@itpug.org>
+#
+# This file is distributed under the same license as the PostgreSQL package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-26 08:08+0000\n"
+"PO-Revision-Date: 2022-09-26 15:10+0200\n"
+"Last-Translator: Daniele Varrazzo <daniele.varrazzo@gmail.com>\n"
+"Language-Team: https://github.com/dvarrazzo/postgresql-it\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Se vero, il codice Perl affidabile e non affidabile sarà compilato in modalità strict."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Codice Perl di inizializzazione da eseguire quando l'interprete Perl è inizializzato."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Codice Perl di inizializzazione da eseguire una sola volta quando plperl è usato per la prima volta."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Codice Perl di inizializzazione da eseguire una sola volta quando plperlu è usato per la prima volta."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "non è possibile allocare piû interpreti Perl su questa piattaforma"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "nell'esecuzione di PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "durante il parsing dell'inizializzazione Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "durante l'esecuzione dell'inizializzazione Perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "nell'esecuzione di PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "durante l'esecuzione di utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "nell'esecuzione di plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "nell'esecuzione di plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "La struttura hash in Perl contiene la colonna inesistente \"%s\""
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "l'attributo di sistema \"%s\" non si può impostare"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "il numero di dimensioni dell'array (%d) eccede il massimo consentito (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "gli array multidimensionali devono avere espressioni array di dimensioni corrispondenti"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "non è possibile convertire un array Perl nel tipo non-array %s"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "non è possibile convertire un hash Perl nel tipo non composito %s"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "la funzione che restituisce un record è chiamata in un contesto che non può accettare il tipo record"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "ricerca del tipo %s fallita"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} non esiste"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} non è un riferimento ad un hash"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "non è possibile modificare la colonna ereditata \"%s\""
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "la funzione PL/Perl non può restituire il tipo %s"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "la funzione PL/Perl non può accettare il tipo %s"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "non ho ricevuto un riferimento CODE dal compilare la funzione \"%s\""
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "la funzione non ha restituito un elemento"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "lettura di $_TD fallita"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "la funzione trigger non ha restituito un elemento"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "la funzione che restituisce insiemi è chiamata in un contesto che non può accettare un insieme"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "necessaria modalità materializzata, ma non ammessa in questo contesto"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "la funzione PL/Perl che restituisce un insieme deve restituire un riferimento ad un array o usare return_next"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "modifiche alla riga ignorate nel trigger DELETE"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "il risultato della funzione trigger PL/Perl deve essere undef, \"SKIP\" oppure \"MODIFY\""
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "le funzioni trigger possono essere chiamate esclusivamente da trigger"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "il risultato della query ha troppe righe per un array Perl"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "non si può usare return_next in una funzione non-SETOF"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "una funzione PL/Perl che restituisce SETOF di un tipo composito deve chiamare return_next con riferimento ad un hash"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "funzione PL/Perl \"%s\""
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "compilazione della funzione Perl \"%s\""
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "blocco di codice anonimo PL/Perl"
diff --git a/src/pl/plperl/po/ja.po b/src/pl/plperl/po/ja.po
new file mode 100644
index 0000000..522893c
--- /dev/null
+++ b/src/pl/plperl/po/ja.po
@@ -0,0 +1,230 @@
+# Japanese message translation file for plperl
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_archivecleanup (PostgreSQL) package.
+# Honda Shigehiro <honda@postgresql.jp>, 2012
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL 15)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-09 12:01+0900\n"
+"PO-Revision-Date: 2019-06-11 12:08+0900\n"
+"Last-Translator: Kyotaro Horiguchi <horikyota.ntt@gmail.com>\n"
+"Language-Team: jpug-doc <jpug-doc@ml.postgresql.jp>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.5.4\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "true の場合、trusted および untrusted な Perl のコードはいずれも strict モードでコンパイルされます。"
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl のインタプリタが初期化される際に実行されるべき Perl の初期化コード。"
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "plperl が最初に使用される際に一度だけ実行される Perl の初期化コード。"
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "plperlu が最初に使用される際に一度だけ実行される Perl の初期化コード。"
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "このプラットフォームでは複数の Perl インタプリタを設定できません"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "PostgreSQL::InServer::SPI::bootstrap の実行中"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "Perl 初期化処理のパース中"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "Perl 初期化処理の実行中"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "PLC_TRUSTED の実行中"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "utf8fix の実行中"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "plperl.on_plperl_init の実行中"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "plperl.on_plperlu_init の実行中"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl ハッシュに存在しない列 \"%s\" があります"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "システム属性 \"%s\" は変更できません"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "配列の次元数(%d)が制限値(%d)を超えています"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "多次元配列は次元数に合った配列式を持たなければなりません"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "Perl 配列を非配列型 %s に変換できません"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "Perl ハッシュを非複合型 %s に変換できません"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "レコード型を受け付けられないコンテキストでレコードを返す関数が呼び出されました"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "型 %s の検索に失敗しました"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} は存在しません"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} はハッシュへの参照ではありません"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "生成列\"%s\"は変更できません"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl 関数は %s 型を返すことができません"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl 関数は %s 型を受け付けられません"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "関数 \"%s\" のコンパイルからはコード参照を取得しませんでした"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "関数からは戻り項目を取得しませんでした"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "$_TD を取り出せませんでした"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "トリガー関数から項目を取得しませんでした"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "集合を受け付けられないコンテキストで集合値関数が呼ばれました"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "マテリアライズモードが必要ですが、現在のコンテクストで禁止されています"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "集合を返す PL/Perl 関数は、配列への参照を返すかまたは return_next を使う必要があります"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "DELETE トリガーで変更された行を無視しています"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "PL/Perl のトリガー関数の結果は undef、\"SKIP\"、\"MODIFY\" のいずれかでなければなりません"
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "トリガー関数はトリガーとしてのみコールできます"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "問い合わせの結果に含まれる行数が Perl の配列に対して多すぎます"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "集合を返す関数以外で return_next を使うことはできません"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "複合型の集合を返す PL/Perl 関数は、ハッシュへの参照を持つ return_next を呼び出さなければなりません"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl 関数 \"%s\""
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "PL/Perl 関数 \"%s\" のコンパイル"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl の無名コードブロック"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "PL/Perl 関数はハッシュまたは配列への参照を返す必要があります"
diff --git a/src/pl/plperl/po/ka.po b/src/pl/plperl/po/ka.po
new file mode 100644
index 0000000..94ae0c4
--- /dev/null
+++ b/src/pl/plperl/po/ka.po
@@ -0,0 +1,254 @@
+# Georgian message translation file for plperl
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plperl (PostgreSQL) package.
+# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-07-02 04:39+0000\n"
+"PO-Revision-Date: 2022-07-07 10:10+0200\n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
+"Language-Team: Georgian <nothing>\n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1\n"
+
+#: plperl.c:408
+msgid ""
+"If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr ""
+"თუ ჩართულია, Perl-ის სანდრო და არასანდო კოდი მკაცრ რეჟიმში დაკომპილდება."
+
+#: plperl.c:422
+msgid ""
+"Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr ""
+"Perl-ის ინიციალიზაციის კოდი, რომელიც ინტერპრეტატორის ინიციალიზაციისას "
+"ეშვება."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr ""
+"Perl-ის ინიციალიზაციის კოდი, რომელიც plperl-ის პირველი გამოყენებისას ეშვება."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr ""
+"Perl-ის ინიციალიზაციის კოდი, რომელიც plperlu-ის პირველი გამოყენებისას "
+"ეშვება."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "ამ პლატფორმაზე Perl-ის ბევრი ინტერპრეტატორის გამოყოფა არ შეიძლება"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "\"PostgreSQL::InServer::SPI::bootstrap\"-ის შესრულებისას"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "\"Perl\"-ის ინიციალიზაციის დამუშავებისას"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "\"Perl\"-ის ინიციალიზაციის შესრულებისას"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "\"PLC_TRUSTED\"-ის შესრულებისას"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "utf8fix-ის შესრულებისას"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "plperl.on_plperl_init-ის შესრულებისას"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "plperl.on_plperlu_init-ის შესრულებისას"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl-ის ჰეში არარსებულ სვეტს შეიცავს: %s"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "სისტემური ატრიბუტის დაყენების შეცდომა: \"%s\""
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "მასივის ზომების რაოდენობა (%d) მაქსიმუმ დასაშვებზე (%d) დიდია"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid ""
+"multidimensional arrays must have array expressions with matching dimensions"
+msgstr ""
+"მრავალგანზომილებიან მასივებს უნდა ჰქონდეთ მასივის გამოსახულებები შესაბამისი "
+"ზომებით"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "\"Perl\"-ის მასივის არა-მასივის ტიპში გადაყვანა შეუძლებელია: %s"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "\"Perl\"-ის ჰეშის არაკომპოზიტურ ტიპში გადაყვანა შეუძლებელია: %s"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"ფუნქცია, რომელიც ჩანაწერს აბრუნებს, გამოძახებულია კონტექსტში, რომელსაც "
+"ჩანაწერის მიღება არ შეუძლია"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "ტიპის მოძებნის შეცდომა: %s"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} არ არსებობს"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} ჰეშის ბმას არ წარმოადგენს"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "გენერირებული სვეტის დაყენება შეუძლებელია: %s"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl ფუნქციას %s ტიპის დაბრუნება არ შეუძლია"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl ფუნქციას %s ტიპის გამოყენება არ შეუძლია"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "კომპილირებადი ფუნქციის (\"%s\") CODE მიბმა არ მიმიღია"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "ფუნქციას არაფერი დაუბრუნებია"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "$_TD -ის გამოთხოვის შეცდომა"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "ტრიგერის ფუნქციიდან ჩანაწერი არ დაბრუნებულა"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"ფუნქცია, რომელიც სეტს აბრუნებს, გამოძახებულია კონტექსტში, რომელიც სეტებს "
+"ვერ იღებს"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "საჭიროა მატერიალიზებული რეჟიმი, მაგრამ ამ კონტექსტში ეს დაუშვებელია"
+
+#: plperl.c:2472
+#, c-format
+msgid ""
+"set-returning PL/Perl function must return reference to array or use "
+"return_next"
+msgstr ""
+"სეტების დამბრუნებელი PL/Perl ფუნქციებმა უნდა დააბრუნონ ბმული მასივზე, ან "
+"return_next უნდა გამოიყენონ"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "\"DELETE\" ტრიგერში შეცვლილი მწკრივის იგნორი"
+
+#: plperl.c:2601
+#, c-format
+msgid ""
+"result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr ""
+"\"PL/Perl\"-ის ტრიგერის ფუნქციის შედეგი უნდა იყოს undef, \"SKIP\" ან "
+"\"MODIFY\""
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "ტრიგერის ფუნქციების გამოძახება მხოლოდ ტრიგერებად შეიძლება"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "მოთხოვნის შედეგში Perl-ის მასივში ჩასატევად მეტისმეტად ბევრი მწკრივია"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "return_next-ის გამოყენება არა-SETOF ტიპის ფუნქციებში შეუძლებელია"
+
+#: plperl.c:3355
+#, c-format
+msgid ""
+"SETOF-composite-returning PL/Perl function must call return_next with "
+"reference to hash"
+msgstr ""
+"SETOF-კომპოზიტის-დამბრუნებელი PL/Perl ფუნქციამ ჰეშამდე ბმულის მქონე "
+"return_next-ი უნდა გამოიძახოს"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl-ის ფუნქცია \"%s\""
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "\"PL/Perl\" ფუნქციის \"%s\" კომპილაცია"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl ანონიმური კოდის ბლოკი"
diff --git a/src/pl/plperl/po/ko.po b/src/pl/plperl/po/ko.po
new file mode 100644
index 0000000..17f2287
--- /dev/null
+++ b/src/pl/plperl/po/ko.po
@@ -0,0 +1,242 @@
+# LANGUAGE message translation file for plperl
+# Copyright (C) 2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Ioseph Kim <ioseph@uri.sarang.net>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-12 00:39+0000\n"
+"PO-Revision-Date: 2023-04-06 10:02+0900\n"
+"Last-Translator: Ioseph Kim <ioseph@uri.sarang.net>\n"
+"Language-Team: Korean Team <pgsql-kr@postgresql.kr>\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: plperl.c:408
+msgid ""
+"If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "true로 지정하면, Perl 코드가 엄격한 구문 검사로 컴파일 됨"
+
+#: plperl.c:422
+msgid ""
+"Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl 인터프리터가 초기화 될 때 실행할 Perl 초기화 코드"
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "plperl 모듈이 처음 사용될 때 실행할 Perl 초기화 코드"
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "plperlu 모듈이 처음 사용될 때 실행할 Perl 초기화 코드"
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "이 플랫폼에 여러 Perl 인터프리터를 사용할 수 없음"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "PostgreSQL::InServer::SPI::bootstrap 실행 중"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "Perl 초기화 구문 분석 중"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "Perl 초기화 실행 중"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "PLC_TRUSTED 실행 중"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "utf8fix 실행 중"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "plperl.on_plperl_init 실행 중"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "plperl.on_plperlu_init 실행 중"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl 해시에 존재하지 않는 \"%s\" 칼럼이 포함되었습니다"
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" 시스템 속성을 지정할 수 없음"
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "지정한 배열 크기(%d)가 최대치(%d)를 초과했습니다"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid ""
+"multidimensional arrays must have array expressions with matching dimensions"
+msgstr "다차원 배열에는 일치하는 차원이 포함된 배열 식이 있어야 함"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "Perl 배열형을 비배열형 %s 자료형으로 변환할 수 없음"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "Perl 해시 자료형을 비복합 %s 자료형으로 변환할 수 없음"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr "반환 자료형이 record인데 함수가 그 자료형으로 반환하지 않음"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "%s 자료형 찾기 실패"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} 없음"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} 자료형이 해시 참조가 아님"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "\"%s\" 계산된 칼럼을 지정할 수 없음"
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl 함수는 %s 자료형을 반환할 수 없음"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl 함수는 %s 자료형을 사용할 수 없음"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "\"%s\" 함수를 컴파일 하면서 코드 참조를 구할 수 없음"
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "함수에서 반환할 항목을 못 찾음"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "$_TD 못 구함"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "트리거 함수에서 반환할 항목을 못 찾음"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"set-values 함수(테이블 리턴 함수)가 set 정의 없이 사용되었습니다 (테이블과 해"
+"당 열 alias 지정하세요)"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialize 모드가 필요합니다만, 이 구문에서는 허용되지 않습니다"
+
+#: plperl.c:2472
+#, c-format
+msgid ""
+"set-returning PL/Perl function must return reference to array or use "
+"return_next"
+msgstr "집합 반환 PL/Perl 함수는 배열 또는 return_next 를 사용해서 반환해야 함"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "DELETE 트리거에서는 변경된 로우는 무시 함"
+
+#: plperl.c:2601
+#, c-format
+msgid ""
+"result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr ""
+"PL/Perl 트리거 함수의 결과는 undef, \"SKIP\", \"MODIFY\" 중 하나여야 함"
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "트리거 함수는 트리거로만 호출될 수 있음"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "쿼리 결과가 Perl 배열에 담기에는 너무 많습니다"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "SETOF 함수가 아닌 경우에는 return_next 구문을 쓸 수 없음"
+
+#: plperl.c:3355
+#, c-format
+msgid ""
+"SETOF-composite-returning PL/Perl function must call return_next with "
+"reference to hash"
+msgstr ""
+"SETOF-composite-returning PL/Perl 함수는 return_next 에서 해시 자료형을 참조"
+"해야 함"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "\"%s\" PL/Perl 함수"
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "\"%s\" PL/Perl 함수 컴필레이션"
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl 익명 코드 블럭"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "PL/Perl 함수는 해시나 배열 자료형을 참조하게 반환해야 함"
diff --git a/src/pl/plperl/po/pl.po b/src/pl/plperl/po/pl.po
new file mode 100644
index 0000000..d0dd146
--- /dev/null
+++ b/src/pl/plperl/po/pl.po
@@ -0,0 +1,225 @@
+# Polish message translation file for plperl
+# Copyright (C) 2011 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Begina Felicysym <begina.felicysym@wp.eu>, 2011, 2012.
+# grzegorz <begina.felicysym@wp.eu>, 2015, 2016.
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL 9.1)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2017-04-09 21:07+0000\n"
+"PO-Revision-Date: 2016-07-03 18:04+0200\n"
+"Last-Translator: grzegorz <begina.felicysym@wp.eu>\n"
+"Language-Team: begina.felicysym@wp.eu\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+"X-Generator: Virtaal 0.7.1\n"
+
+#: plperl.c:390
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Jeśli prawda, zaufanych i niezaufanych kod Perl zostanie skompilowany w trybie ścisłym."
+
+#: plperl.c:404
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Kod inicjujący Perl do wykonania gdy inicjowany jest interpreter Perl."
+
+#: plperl.c:426
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Kod inicjujący Perl do jednokrotnego wykonania gdy plperl jest użyty po raz pierwszy."
+
+#: plperl.c:434
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Kod inicjujący Perl do jednokrotnego wykonania gdy plperlu jest użyty po raz pierwszy."
+
+#: plperl.c:631
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "nie można przydzielić wielu interpreterów Perl na tej platformie"
+
+#: plperl.c:651 plperl.c:826 plperl.c:832 plperl.c:946 plperl.c:958
+#: plperl.c:1001 plperl.c:1022 plperl.c:2074 plperl.c:2183 plperl.c:2250
+#: plperl.c:2312
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:652
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "podczas wykonania PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:827
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "podczas przetwarzania inicjacji Perl"
+
+#: plperl.c:833
+#, c-format
+msgid "while running Perl initialization"
+msgstr "podczas wykonywania inicjacji Perl"
+
+#: plperl.c:947
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "podczas wykonywania PLC_TRUSTED"
+
+#: plperl.c:959
+#, c-format
+msgid "while executing utf8fix"
+msgstr "podczas wykonywania utf8fix"
+
+#: plperl.c:1002
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "podczas wykonania plperl.on_plperl_init"
+
+#: plperl.c:1023
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "podczas wykonania plperl.on_plperlu_init"
+
+#: plperl.c:1067 plperl.c:1719
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "hasz Perl zawiera nieistniejącą kolumnę \"%s\""
+
+#: plperl.c:1072 plperl.c:1724
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nie można ustawić atrybutu systemowego \"%s\""
+
+#: plperl.c:1157
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "liczba wymiarów tablicy (%d) przekracza maksimum (%d)"
+
+#: plperl.c:1169 plperl.c:1186
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "wielowymiarowe tablice muszą mieć wyrażenia tablicowe z pasującymi wymiarami"
+
+#: plperl.c:1221
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "nie można zmienić typu tablicowego Perl na typ nietablicowy %s"
+
+#: plperl.c:1323
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "nie można przekształcić Perlowego hasza na typ niezłożony %s"
+
+#: plperl.c:1334
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkcja zwracająca rekord w wywołaniu, które nie akceptuje typów złożonych"
+
+#: plperl.c:1349
+#, c-format
+msgid "PL/Perl function must return reference to hash or array"
+msgstr "funkcja PL/Perl musi zwracać referencję do hasza lub tablicy"
+
+#: plperl.c:1386
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "nie dało się wyszukać typu %s"
+
+#: plperl.c:1695
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} nie istnieje"
+
+#: plperl.c:1699
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} nie jest referencją haszu"
+
+#: plperl.c:1950 plperl.c:2785
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "funkcje PL/Perl nie mogą zwracać wartości typu %s"
+
+#: plperl.c:1963 plperl.c:2827
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "funkcje PL/Perl nie obsługują typu %s"
+
+#: plperl.c:2079
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "nie udało się pobrać wskazania CODE z kompilowanej funkcji \"%s\""
+
+#: plperl.c:2171
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "nie odebrano zwracanego elementu z funkcji"
+
+#: plperl.c:2214 plperl.c:2280
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "nie dało się pobrać $_TD"
+
+#: plperl.c:2238 plperl.c:2300
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "nie odebrano zwracanego elementu z funkcji wyzwalacza"
+
+#: plperl.c:2357
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "funkcja zwracająca zbiór rekordów wywołana w kontekście, w którym nie jest to dopuszczalne"
+
+#: plperl.c:2401
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "funkcja PL/Perl zwracająca zbiór rekordów musi zwracać tablicę lub użyć return_next"
+
+#: plperl.c:2522
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ignorowanie modyfikacji wiersza w wyzwalaczy DELETE"
+
+#: plperl.c:2530
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "funkcja wyzwalacza PL/Perl musi zwracać undef, \"SKIP\", lub \"MODIFY\""
+
+#: plperl.c:2780
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "procedury wyzwalaczy mogą być wywoływane jedynie przez wyzwalacze"
+
+#: plperl.c:3120
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "wynik zapytania ma za dużo wierszy by pomieścić w tabeli Perl"
+
+#: plperl.c:3165
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "nie można używać return_next w funkcji nie SETOF"
+
+#: plperl.c:3219
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "funkcja PL/Perl zwracająca grupę wartości złożonych musi wywołać return_next z referencją haszu"
+
+#: plperl.c:3882
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "funkcja PL/Perl \"%s\""
+
+#: plperl.c:3894
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "kompilacja funkcji PL/Perl \"%s\""
+
+#: plperl.c:3903
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "anonimowy blok kodu PL/Perl"
+
+#~ msgid "out of memory"
+#~ msgstr "brak pamięci"
diff --git a/src/pl/plperl/po/pt_BR.po b/src/pl/plperl/po/pt_BR.po
new file mode 100644
index 0000000..72d357e
--- /dev/null
+++ b/src/pl/plperl/po/pt_BR.po
@@ -0,0 +1,227 @@
+# Brazilian Portuguese message translation file for plperl
+#
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Euler Taveira <euler@eulerto.com>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-27 13:15-0300\n"
+"PO-Revision-Date: 2009-05-10 01:12-0300\n"
+"Last-Translator: Euler Taveira <euler@eulerto.com>\n"
+"Language-Team: Brazilian Portuguese <pgsql-translators@postgresql.org>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Se verdadeiro, código Perl confiável e não-confiável será compilado em modo estrito."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Código de inicialização Perl executado quando um interpretador Perl for inicializado."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Código de inicialização Perl executado quando plperl for utilizado pela primeira vez."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Código de inicialização Perl executado quando plperlu for utilizado pela primeira vez."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "não pode alocar múltiplos interpretadores Perl nessa plataforma"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "ao executar PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "ao analisar código de inicialização Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "ao executar código de inicialização Perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "ao executar PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "ao executar utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "ao executar plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "ao executar plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "hash Perl contém coluna inexistente \"%s\""
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "não pode definir atributo do sistema \"%s\""
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "número de dimensões da matriz (%d) excede o máximo permitido (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "matrizes multidimensionais devem ter expressões de matriz com dimensões correspondentes"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "não pode converter array Perl para tipo que não é array %s"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "não pode converter hash Perl para tipo não-composto %s"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "função que retorna record foi chamada em um contexto que não pode aceitar tipo record"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "falhou ao pesquisar por tipo %s"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} não existe"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} não é uma referência hash"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "não pode definir coluna gerada \"%s\""
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "funções PL/Perl não podem retornar tipo %s"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "funções PL/Perl não podem aceitar tipo %s"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "não obteve uma referência CODE da compilação da função \"%s\""
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "não obteve um item de retorno da função"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "não pôde obter $_TD"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "não obteve um item de retorno da função de gatilho"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "função que tem argumento do tipo conjunto foi chamada em um contexto que não pode aceitar um conjunto"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "modo de materialização é requerido, mas ele não é permitido neste contexto"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "funçao PL/Perl que retorna conjunto deve retornar referência para matriz ou usar return_next"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ignorando registro modificado em gatilho DELETE"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "resultado da função de gatilho PL/Perl deve ser undef, \"SKIP\" ou \"MODIFY\""
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funções de gatilho só podem ser chamadas como gatilhos"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "resultado da consulta tem muitos registros para caber em um array Perl"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "não pode utilizar return_next em uma função que não retorna conjunto"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "função PL/Perl que retorna um conjunto de tipo composto deve chamar return_next com referência a um hash"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "função PL/Perl \"%s\""
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "compilação da função PL/Perl \"%s\""
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "bloco de código PL/Perl anônimo"
diff --git a/src/pl/plperl/po/ru.po b/src/pl/plperl/po/ru.po
new file mode 100644
index 0000000..342c79c
--- /dev/null
+++ b/src/pl/plperl/po/ru.po
@@ -0,0 +1,261 @@
+# Russian message translation file for plperl
+# Copyright (C) 2012-2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Alexander Lakhin <exclusion@gmail.com>, 2012-2017, 2019, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL current)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-03 05:56+0300\n"
+"PO-Revision-Date: 2022-09-05 13:38+0300\n"
+"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n"
+"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: plperl.c:408
+msgid ""
+"If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr ""
+"Если этот параметр равен true, доверенный и недоверенный код Perl будет "
+"компилироваться в строгом режиме."
+
+#: plperl.c:422
+msgid ""
+"Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr ""
+"Код инициализации Perl, который выполняется при инициализации интерпретатора "
+"Perl."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr ""
+"Код инициализации Perl, который выполняется один раз, при первом "
+"использовании plperl."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr ""
+"Код инициализации Perl, который выполняется один раз, при первом "
+"использовании plperlu."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "на этой платформе нельзя запустить множество интерпретаторов Perl"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2154 plperl.c:2262 plperl.c:2330
+#: plperl.c:2393
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "при выполнении PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "при разборе параметров инициализации Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "при выполнении инициализации Perl"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "при выполнении PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "при выполнении utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "при выполнении plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "при выполнении plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1807
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl-хеш содержит несуществующий столбец \"%s\""
+
+#: plperl.c:1106 plperl.c:1812
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "присвоить значение системному атрибуту \"%s\" нельзя"
+
+#: plperl.c:1202 plperl.c:1217 plperl.c:1234
+#, c-format
+msgid ""
+"multidimensional arrays must have array expressions with matching dimensions"
+msgstr ""
+"для многомерных массивов должны задаваться выражения с соответствующими "
+"размерностями"
+
+#: plperl.c:1207
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "число размерностей массива (%d) превышает предел (%d)"
+
+#: plperl.c:1277
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "Perl-массив нельзя преобразовать в тип не массива %s"
+
+#: plperl.c:1378
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "Perl-хеш нельзя преобразовать в не составной тип %s"
+
+#: plperl.c:1400 plperl.c:3320
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"функция, возвращающая запись, вызвана в контексте, не допускающем этот тип"
+
+#: plperl.c:1461
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "найти тип %s не удалось"
+
+#: plperl.c:1782
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} не существует"
+
+#: plperl.c:1786
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} - не ссылка на хеш"
+
+#: plperl.c:1817
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "присвоить значение генерируемому столбцу \"%s\" нельзя"
+
+#: plperl.c:2029 plperl.c:2870
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "функции PL/Perl не могут возвращать тип %s"
+
+#: plperl.c:2042 plperl.c:2909
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "функции PL/Perl не могут принимать тип %s"
+
+#: plperl.c:2159
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "не удалось получить ссылку на код после компиляции функции \"%s\""
+
+#: plperl.c:2250
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "не удалось получить возвращаемый элемент от функции"
+
+#: plperl.c:2294 plperl.c:2361
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "не удалось получить $_TD"
+
+#: plperl.c:2318 plperl.c:2381
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "не удалось получить возвращаемый элемент от триггерной функции"
+
+#: plperl.c:2439
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"функция, возвращающая множество, вызвана в контексте, где ему нет места"
+
+#: plperl.c:2444
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "требуется режим материализации, но он недопустим в этом контексте"
+
+#: plperl.c:2488
+#, c-format
+msgid ""
+"set-returning PL/Perl function must return reference to array or use "
+"return_next"
+msgstr ""
+"функция PL/Perl, возвращающая множество, должна возвращать ссылку на массив "
+"или вызывать return_next"
+
+#: plperl.c:2609
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "в триггере DELETE изменённая строка игнорируется"
+
+#: plperl.c:2617
+#, c-format
+msgid ""
+"result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr ""
+"результатом триггерной функции PL/Perl должен быть undef, \"SKIP\" или "
+"\"MODIFY\""
+
+#: plperl.c:2865
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "триггерные функции могут вызываться только в триггерах"
+
+#: plperl.c:3225
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr ""
+"результат запроса содержит слишком много строк для передачи в массиве Perl"
+
+#: plperl.c:3297
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr ""
+"return_next можно использовать только в функциях, возвращающих множества"
+
+#: plperl.c:3371
+#, c-format
+msgid ""
+"SETOF-composite-returning PL/Perl function must call return_next with "
+"reference to hash"
+msgstr ""
+"функция PL/Perl, возвращающая составное множество, должна вызывать "
+"return_next со ссылкой на хеш"
+
+#: plperl.c:4153
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "функция PL/Perl \"%s\""
+
+#: plperl.c:4165
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "компиляция функции PL/Perl \"%s\""
+
+#: plperl.c:4174
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "анонимный блок кода PL/Perl"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "функция PL/Perl должна возвращать ссылку на хеш или массив"
+
+#~ msgid "out of memory"
+#~ msgstr "нехватка памяти"
diff --git a/src/pl/plperl/po/sv.po b/src/pl/plperl/po/sv.po
new file mode 100644
index 0000000..9494466
--- /dev/null
+++ b/src/pl/plperl/po/sv.po
@@ -0,0 +1,227 @@
+# Swedish message translation file for plperl
+# Copyright (C) 2014 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Mats Erik Andersson <bsd@gisladisker.se>, 2014.
+# Dennis Björklund <db@zigo.dhs.org> 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-11 13:38+0000\n"
+"PO-Revision-Date: 2023-03-09 22:40+0100\n"
+"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n"
+"Language-Team: Swedish <pgsql-translators@postgresql.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Om sant, tillförlitlig och otillförlitlig Perl-kod kommer kompileras i strikt läge."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl-kod för initialisering, utföres när perl-tolken förbereds."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Perl-kod för engångs-initialisering då plperl används första gången."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Perl-kod för engångs-initialisering då plperlu används första gången."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "kan inte utnyttja flera Perl-interpretorer på denna plattform"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "vid utförande av PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "vid tolkning av perls initieringssteg"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "vid utförande av perls initieringssteg"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "vid utförande av PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "vid utförande av utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "vid utförande av plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "vid utförande av plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perlhash innehåller en okänd kolumn \"%s\"."
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "kan inte sätta systemattribut \"%s\""
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "antalet array-dimensioner (%d) överskrider det maximalt tillåtna (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "flerdimensionella vektorer måste ha array-uttryck av passande dimensioner"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "kan inte omvandla perlvektor till icke-array av typ \"%s\"."
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "kan inte omvandla en perlhash till icke-composite-typ \"%s\"."
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "en funktion med post som värde anropades i sammanhang där poster inte kan godtagas."
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "uppslag misslyckades för typen \"%s\""
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} finns inte."
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} är inte en hash-referens."
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kan inte sätta genererad kolumn \"%s\""
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "Funktioner i PL/Perl kan inte svara med typ \"%s\"."
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "Funktioner i PL/Perl kan inte hantera typ \"%s\"."
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "fick inte en CODE-referens vid kompilering av funktionen \"%s\"."
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "fick inget returnvärde från funktion"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "kunde inte hämta $_TD"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "fick inget returvärde från triggerfunktion"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "en funktion som returnerar en mängd anropades i kontext som inte godtar en mängd"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialiserat läge krävs, men stöds inte i detta kontext"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "En mängd-returnerande funktion i PL/Perl måste göra det som referens eller med return_next."
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "Lämnar ändrad rad orörd i en DELETE-triggning"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "resultat av en triggningsfunktion i PL/Perl måste vara undef, \"SKIP\" eller \"MODIFY\"."
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggningsfunktioner kan bara anropas vid triggning."
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "frågeresultatet har för många rader för att få plats i en Perl-array"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "får inte nyttja return_next i funktion som ej är SETOF"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "En funktion i PL/Perl med värderetur som SETOF måste anropa return_next med en hashreferens"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl-funktion \"%s\"."
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "kompilering av PL/Perl-funktion \"%s\""
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "Anonymt kodblock i PL/Perl."
diff --git a/src/pl/plperl/po/tr.po b/src/pl/plperl/po/tr.po
new file mode 100644
index 0000000..a8708e4
--- /dev/null
+++ b/src/pl/plperl/po/tr.po
@@ -0,0 +1,236 @@
+# LANGUAGE message translation file for plperl
+# Copyright (C) 2009 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 8.4\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-04-26 13:38+0000\n"
+"PO-Revision-Date: 2019-06-13 17:04+0300\n"
+"Last-Translator: Devrim GÜNDÜZ <devrim@gunduz.org>\n"
+"Language-Team: Turkish <devrim@gunduz.org>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.7.1\n"
+
+#: plperl.c:409
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Doğru ise, trusted ve untrusted Perl kodları strict modda derlenecektir"
+
+#: plperl.c:423
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Perl yorumlayıcısı ilklendirildiğinde çalışacak Perl ilklendirme kodu."
+
+#: plperl.c:445
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "plperl ilk kez kullanıldığında çalışacak Perl ilklendirme kodu"
+
+#: plperl.c:453
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "plperlu ilk kez kullanıldığında çalışacak Perl ilklendirme kodu"
+
+#: plperl.c:650
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "bu platformda birden fazla Perl interpreter ayrılamıyor"
+
+#: plperl.c:673 plperl.c:857 plperl.c:863 plperl.c:980 plperl.c:992
+#: plperl.c:1035 plperl.c:1058 plperl.c:2157 plperl.c:2267 plperl.c:2335
+#: plperl.c:2398
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:674
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "PostgreSQL::InServer::SPI::bootstrap çalıştırılırken"
+
+#: plperl.c:858
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "Perl ilklendirmesi ayrıştırılırken"
+
+#: plperl.c:864
+#, c-format
+msgid "while running Perl initialization"
+msgstr "Perl ilklendirmesi sırasında"
+
+#: plperl.c:981
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr " PLC_TRUSTED çalıştırılırken"
+
+#: plperl.c:993
+#, c-format
+msgid "while executing utf8fix"
+msgstr "utf8fix çalıştırılırken"
+
+#: plperl.c:1036
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "plperl.on_plperl_init çalıştırılırken"
+
+#: plperl.c:1059
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "plperl.on_plperlu_init çalıştırılırken"
+
+#: plperl.c:1105 plperl.c:1796
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl hashi olmayan kolonu içeriyor: \"%s\""
+
+#: plperl.c:1110 plperl.c:1801
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" sistem niteliği ayarlanamıyor"
+
+#: plperl.c:1198
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "dizin boyut sayısı (%d), izin verilern en yüksek değerini (%d) aşmaktadır"
+
+#: plperl.c:1210 plperl.c:1227
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "çok boyutlu dizinler boyut sayısı kadar dizin ifade sayısına sahip olmalıdırlar"
+
+#: plperl.c:1263
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "Perl dizisi (array) dizi olmayan %s tipine dönüştürülemiyor"
+
+#: plperl.c:1366
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "Perl hash'i kompozit olmayan %s tipine dönüştürülemez"
+
+#: plperl.c:1388 plperl.c:3309
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "tip kaydı içermeyen alanda çağırılan ve kayıt döndüren fonksiyon"
+
+#: plperl.c:1447
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "%s tipi için arama (lookup) başarısız oldu"
+
+#: plperl.c:1771
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} mevcut değil"
+
+#: plperl.c:1775
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} hash referansı değil"
+
+#: plperl.c:1806
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "oluşturulan \"%s\" sütunu ayarlanamıyor"
+
+#: plperl.c:2032 plperl.c:2874
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl fonksiyonları %s veri tipini döndüremezler"
+
+#: plperl.c:2045 plperl.c:2915
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl fonksiyonları %s tipini kabul etmez"
+
+#: plperl.c:2162
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "\"%s\" fonksiyonu derlenirken CODE referansı alınamadı"
+
+#: plperl.c:2255
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "fonksiyonden dönüş (return) değeri alınamadı"
+
+#: plperl.c:2299 plperl.c:2366
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "$_TD getirilemedi"
+
+#: plperl.c:2323 plperl.c:2386
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "trigger fonksiyonundan dönüş (return) değeri alınamadı"
+
+#: plperl.c:2447
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set değerini kabul etmediği ortamda set değeri alan fonksiyon çağırılmış"
+
+#: plperl.c:2492
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "se dönen PL/Perl fonksiyonu return_next kullanmalı ya da bir diziye referans dönmelidir"
+
+#: plperl.c:2613
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "DELETE triggerındaki değiştirilmiş satır gözardı ediliyor"
+
+#: plperl.c:2621
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "PL/Perl trigger fonksiyonun sonucu undef, \"SKIP\" ya da \"MODIFY\" olmalıdır"
+
+#: plperl.c:2869
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "trigger fonksiyonları sadece trigger olarak çağırılabilirler"
+
+#: plperl.c:3216
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "sorgu sonucunda bir Perl dizisine (array) sığabilecekten çok fazla satır var"
+
+#: plperl.c:3286
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "SETOF olmayan bir fonksiyonda return_next kullanılamaz"
+
+#: plperl.c:3360
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "SETOF-composite döndüren PL/Perl fonksiyonları return_next'i hash'e referans olarak çağırmalıdır"
+
+#: plperl.c:4135
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "\"%s\" PL/Perl fonksiyonu"
+
+#: plperl.c:4147
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "\"%s\" PL/Perl fonksiyonunun derlenmesi"
+
+#: plperl.c:4156
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl anonim kod bloğu"
+
+#~ msgid "composite-returning PL/Perl function must return reference to hash"
+#~ msgstr "composite döndüren PL/Perl fonksiyonu hash'e referans dönmelidir"
+
+#~ msgid "out of memory"
+#~ msgstr "yetersiz bellek"
+
+#~ msgid "creation of Perl function \"%s\" failed: %s"
+#~ msgstr " \"%s\" Perl fonksiyonunun yaratılması başarısız oldu: %s"
+
+#~ msgid "error from Perl function \"%s\": %s"
+#~ msgstr "Perl fonksiyonunda hata: \"%s\": %s"
+
+#~ msgid "PL/Perl function must return reference to hash or array"
+#~ msgstr "PL/Perl fonksiyonu hash ya da dizine referans dönmelidir"
diff --git a/src/pl/plperl/po/uk.po b/src/pl/plperl/po/uk.po
new file mode 100644
index 0000000..7951649
--- /dev/null
+++ b/src/pl/plperl/po/uk.po
@@ -0,0 +1,227 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: postgresql\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-12 10:39+0000\n"
+"PO-Revision-Date: 2022-09-13 11:52\n"
+"Last-Translator: \n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: postgresql\n"
+"X-Crowdin-Project-ID: 324573\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /REL_15_STABLE/plperl.pot\n"
+"X-Crowdin-File-ID: 920\n"
+
+#: plperl.c:408
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "Якщо увімкнено, надійний і ненадійний код Perl буде скомпільований в суворому режимі."
+
+#: plperl.c:422
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Виконати ініціалізаційний код під час ініціалізації інтерпретатора Perl."
+
+#: plperl.c:444
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Виконати код ініціалізації один раз під час першого використання plperl."
+
+#: plperl.c:452
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Виконати код ініціалізації один раз під час першого використання plperlu."
+
+#: plperl.c:646
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "не можна розмістити декілька Perl інтерпретаторів на цій платформі"
+
+#: plperl.c:669 plperl.c:853 plperl.c:859 plperl.c:976 plperl.c:988
+#: plperl.c:1031 plperl.c:1054 plperl.c:2138 plperl.c:2246 plperl.c:2314
+#: plperl.c:2377
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:670
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "під час виконання PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:854
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "під час обробки ініціалізації Perl"
+
+#: plperl.c:860
+#, c-format
+msgid "while running Perl initialization"
+msgstr "під час запуску Perl ініціалізації"
+
+#: plperl.c:977
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "під час виконання PLC_TRUSTED"
+
+#: plperl.c:989
+#, c-format
+msgid "while executing utf8fix"
+msgstr "під час виконання utf8fix"
+
+#: plperl.c:1032
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "під час виконання plperl.on_plperl_init"
+
+#: plperl.c:1055
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "під час виконання plperl.on_plperlu_init"
+
+#: plperl.c:1101 plperl.c:1791
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "хеш Perl містить неіснуючу колонку \"%s\""
+
+#: plperl.c:1106 plperl.c:1796
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "не вдалося встановити системний атрибут \"%s\""
+
+#: plperl.c:1194
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "число вимірів масива (%d) перевищує ліміт (%d)"
+
+#: plperl.c:1206 plperl.c:1223
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "для багатовимірних масивів повинні задаватись вирази з відповідними вимірами"
+
+#: plperl.c:1259
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "неможливо конвертувати масив Perl у тип не масиву %s"
+
+#: plperl.c:1362
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "неможливо конвертувати хеш Perl у нескладений тип %s"
+
+#: plperl.c:1384 plperl.c:3304
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "функція, що повертає набір, викликана у контексті, що не приймає тип запис"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "неможливо фільтрувати для типу %s"
+
+#: plperl.c:1766
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} не існує"
+
+#: plperl.c:1770
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} не є посиланням на хеш"
+
+#: plperl.c:1801
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "неможливо оновити згенерований стовпець \"%s\""
+
+#: plperl.c:2013 plperl.c:2854
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "функції PL/Perl не можуть повертати тип %s"
+
+#: plperl.c:2026 plperl.c:2893
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "функції PL/Perl не можуть приймати тип %s"
+
+#: plperl.c:2143
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "не отримано посилання CODE з функції компіляції \"%s\""
+
+#: plperl.c:2234
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "не отримано елемент результату з функції"
+
+#: plperl.c:2278 plperl.c:2345
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "не вдалось отримати $_TD"
+
+#: plperl.c:2302 plperl.c:2365
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "не отримано елемент результату з функції-тригеру"
+
+#: plperl.c:2423
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "функція \"set-valued\" викликана в контексті, де йому немає місця"
+
+#: plperl.c:2428
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "необхідний режим матеріалізації (materialize mode), але він неприпустимий у цьому контексті"
+
+#: plperl.c:2472
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "функція PL/Perl, що вертає набір значень, повинна посилатися на масив або використовувати return_next"
+
+#: plperl.c:2593
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "ігнорується змінений рядок у тригері DELETE"
+
+#: plperl.c:2601
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "результат тригерної функції PL/Perl повинен бути undef, \"SKIP\" або \"MODIFY\""
+
+#: plperl.c:2849
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "тригер-функція може викликатися лише як тригер"
+
+#: plperl.c:3209
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "результат запиту має забагато рядків для відповідності в масиві Perl"
+
+#: plperl.c:3281
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "не можна використовувати return_next в функціях, що не повертають набори даних"
+
+#: plperl.c:3355
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "Функція PL/Perl, що повертає набір композитних даних, повинна викликати return_next з посиланням на хеш"
+
+#: plperl.c:4137
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl функція \"%s\""
+
+#: plperl.c:4149
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "компіляція функції PL/Perl \"%s\""
+
+#: plperl.c:4158
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "анонімний блок коду PL/Perl"
+
diff --git a/src/pl/plperl/po/vi.po b/src/pl/plperl/po/vi.po
new file mode 100644
index 0000000..15ba874
--- /dev/null
+++ b/src/pl/plperl/po/vi.po
@@ -0,0 +1,242 @@
+# LANGUAGE message translation file for plperl
+# Copyright (C) 2018 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plperl (PostgreSQL) package.
+# FIRST AUTHOR <kakalot49@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2018-04-22 12:08+0000\n"
+"PO-Revision-Date: 2018-04-29 23:57+0900\n"
+"Language-Team: <pgvn_translators@postgresql.vn>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
+"Last-Translator: Dang Minh Huong <kakalot49@gmail.com>\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"Language: vi_VN\n"
+
+#: plperl.c:409
+msgid ""
+"If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr ""
+"Nếu đúng, mã perl đáng tin cậy(PL/Perl ) và không đáng tin cậy(PL/PerlU) sẽ "
+"được biên dịch trong chế độ strict."
+
+#: plperl.c:423
+msgid ""
+"Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "Mã Perl được thực thi khi trình thông dịch Perl được khởi tạo."
+
+#: plperl.c:445
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "Mã Perl được thực thi khi plperl được sử dụng lần đầu tiên."
+
+#: plperl.c:453
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "Mã Perl được thực thi khi plperlu được sử dụng lần đầu tiên."
+
+#: plperl.c:650
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "không thể cấp phát nhiều trình thông dịch Perl trên hệ điều hành này"
+
+#: plperl.c:673 plperl.c:857 plperl.c:863 plperl.c:980 plperl.c:992
+#: plperl.c:1035 plperl.c:1058 plperl.c:2141 plperl.c:2251 plperl.c:2319
+#: plperl.c:2382
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:674
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "trong khi thực thi PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:858
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "trong khi phân tích cú pháp khởi tạo Perl"
+
+#: plperl.c:864
+#, c-format
+msgid "while running Perl initialization"
+msgstr "trong khi chạy cú pháp khởi tạo Perl"
+
+#: plperl.c:981
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "trong khi chạy PLC_TRUSTED"
+
+#: plperl.c:993
+#, c-format
+msgid "while executing utf8fix"
+msgstr "trong khi thực thi utf8fix"
+
+#: plperl.c:1036
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "trong khi thực thi plperl.on_plperl_init"
+
+#: plperl.c:1059
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "trong khi thực thi plperl.plperlu_init"
+
+#: plperl.c:1105 plperl.c:1785
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Giá trị băm Perl chứa cột không tồn tại \"%s\""
+
+#: plperl.c:1110 plperl.c:1790
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "không thể thiết lập attribute hệ thống \"%s\""
+
+#: plperl.c:1198
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "số lượng chiều của mảng (%d) vượt quá số lượng tối đa cho phép (%d)"
+
+#: plperl.c:1210 plperl.c:1227
+#, c-format
+msgid ""
+"multidimensional arrays must have array expressions with matching dimensions"
+msgstr "mảng đa chiều phải có biểu thức mảng tương ứng với các chiều"
+
+#: plperl.c:1263
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "không thể chuyển đổi mảng Perl thành kiểu không phải mảng %s"
+
+#: plperl.c:1366
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr ""
+"không thể chuyển đổi giá trị băm Perl thành kiểu không phải-composite %s"
+
+#: plperl.c:1388 plperl.c:3286
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"hàm trả về bản ghi được gọi trong ngữ cảnh không thể chấp nhận kiểu bản ghi"
+
+#: plperl.c:1408
+#, c-format
+msgid "PL/Perl function must return reference to hash or array"
+msgstr "Hàm PL/Perl phải trả về tham thiếu tới giá trị băm hoặc mảng"
+
+#: plperl.c:1445
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "không tìm thấy kiểu dữ liệu %s"
+
+#: plperl.c:1760
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new} không tồn tại"
+
+#: plperl.c:1764
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new} không phải là một tham chiếu giá trị băm"
+
+#: plperl.c:2016 plperl.c:2858
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "Hàm PL/Perl không thể trả về kiểu %s"
+
+#: plperl.c:2029 plperl.c:2899
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "Hàm PL/Perl không thể chấp nhận kiểu %s"
+
+#: plperl.c:2146
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "không nhận được tham chiếu CODE từ hàm biên dịch \"%s\""
+
+#: plperl.c:2239
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "không nhận được một mục trả về từ hàm"
+
+#: plperl.c:2283 plperl.c:2350
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "không thể fetch $_TD"
+
+#: plperl.c:2307 plperl.c:2370
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "không nhận được một mục trả về từ hàm trigger"
+
+#: plperl.c:2431
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"hàm thiết lập giá trị được gọi trong ngữ cảnh không thể chấp nhận một tập "
+"hợp"
+
+#: plperl.c:2476
+#, c-format
+msgid ""
+"set-returning PL/Perl function must return reference to array or use "
+"return_next"
+msgstr ""
+"hàm thiết lập-trả về PL/Perl phải trả về tham chiếu tới mảng hay sử dụng "
+"return_next"
+
+#: plperl.c:2597
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "bỏ qua hàng đã sửa đổi trong trigger DELETE"
+
+#: plperl.c:2605
+#, c-format
+msgid ""
+"result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr ""
+"kết quả của hàm trigger PL/Perl phải là undef, \"SKIP\" hoặc \"MODIFY\""
+
+#: plperl.c:2853
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "các hàm trigger chỉ có thể được gọi như những trigger"
+
+#: plperl.c:3193
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "kết quả truy vấn có quá nhiều hàng có thể vừa với một mảng Perl"
+
+#: plperl.c:3263
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "không thể sử dụng return_next trong hàm không phải-SETOF"
+
+#: plperl.c:3337
+#, c-format
+msgid ""
+"SETOF-composite-returning PL/Perl function must call return_next with "
+"reference to hash"
+msgstr ""
+"Hàm PL/Perl trả về SETOF-composite phải gọi return_next với tham chiếu tới "
+"giá trị băm"
+
+#: plperl.c:4115
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "Hàm PL/Perl \"%s\""
+
+#: plperl.c:4127
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "biên dịch hàm PL/Perl \"%s\""
+
+#: plperl.c:4136
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "Khối mã ẩn danh PL/Perl"
diff --git a/src/pl/plperl/po/zh_CN.po b/src/pl/plperl/po/zh_CN.po
new file mode 100644
index 0000000..14e2324
--- /dev/null
+++ b/src/pl/plperl/po/zh_CN.po
@@ -0,0 +1,221 @@
+# LANGUAGE message translation file for plperl
+# Copyright (C) 2010 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plperl (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-14 05:39+0000\n"
+"PO-Revision-Date: 2021-08-15 17:30+0800\n"
+"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n"
+"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.7\n"
+
+#: plperl.c:405
+msgid "If true, trusted and untrusted Perl code will be compiled in strict mode."
+msgstr "如果为真的话,那么信任和非信任的Perl代码将以限制模式编译."
+
+#: plperl.c:419
+msgid "Perl initialization code to execute when a Perl interpreter is initialized."
+msgstr "当初始化一个Perl解释器时候执行Perl初始化代码"
+
+#: plperl.c:441
+msgid "Perl initialization code to execute once when plperl is first used."
+msgstr "在第一次使用plperl的时候执行一次Perl初始化代码"
+
+#: plperl.c:449
+msgid "Perl initialization code to execute once when plperlu is first used."
+msgstr "在plperlu第一次使用的时候执行一次Perl初始化代码"
+
+#: plperl.c:643
+#, c-format
+msgid "cannot allocate multiple Perl interpreters on this platform"
+msgstr "在这个平台上无法分配多个Perl解释器"
+
+#: plperl.c:666 plperl.c:850 plperl.c:856 plperl.c:973 plperl.c:985
+#: plperl.c:1028 plperl.c:1051 plperl.c:2133 plperl.c:2241 plperl.c:2309
+#: plperl.c:2372
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plperl.c:667
+#, c-format
+msgid "while executing PostgreSQL::InServer::SPI::bootstrap"
+msgstr "同时在执行PostgreSQL::InServer::SPI::bootstrap"
+
+#: plperl.c:851
+#, c-format
+msgid "while parsing Perl initialization"
+msgstr "同时在解析Perl初始化"
+
+#: plperl.c:857
+#, c-format
+msgid "while running Perl initialization"
+msgstr "同时在运行Perl初始化"
+
+#: plperl.c:974
+#, c-format
+msgid "while executing PLC_TRUSTED"
+msgstr "同时在执行PLC_TRUSTED"
+
+#: plperl.c:986
+#, c-format
+msgid "while executing utf8fix"
+msgstr "同时在执行utf8fix"
+
+#: plperl.c:1029
+#, c-format
+msgid "while executing plperl.on_plperl_init"
+msgstr "同时在执行plperl.on_plperl_init"
+
+#: plperl.c:1052
+#, c-format
+msgid "while executing plperl.on_plperlu_init"
+msgstr "同时在执行plperl.on_plperlu_init"
+
+#: plperl.c:1098 plperl.c:1786
+#, c-format
+msgid "Perl hash contains nonexistent column \"%s\""
+msgstr "Perl的哈希功能包含不存在的列\"%s\""
+
+#: plperl.c:1103 plperl.c:1791
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "不能设置系统属性\"%s\""
+
+#: plperl.c:1191
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "数组的维数(%d)超过最大允许值(%d)"
+
+#: plperl.c:1203 plperl.c:1220
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "多维数组必须有符合维度的数组表达式"
+
+#: plperl.c:1256
+#, c-format
+msgid "cannot convert Perl array to non-array type %s"
+msgstr "无法将Perl数组转换成非数组类型 %s"
+
+#: plperl.c:1359
+#, c-format
+msgid "cannot convert Perl hash to non-composite type %s"
+msgstr "无法将Perl哈希类型转换成非组合类型 %s"
+
+#: plperl.c:1381 plperl.c:3279
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "返回值类型是记录的函数在不接受使用记录类型的环境中调用"
+
+#: plperl.c:1440
+#, c-format
+msgid "lookup failed for type %s"
+msgstr "类型%s查找失败"
+
+#: plperl.c:1761
+#, c-format
+msgid "$_TD->{new} does not exist"
+msgstr "$_TD->{new}不存在"
+
+#: plperl.c:1765
+#, c-format
+msgid "$_TD->{new} is not a hash reference"
+msgstr "$_TD->{new}不是一个哈希引用"
+
+#: plperl.c:1796
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "无法设置生成的列 \"%s\""
+
+#: plperl.c:2008 plperl.c:2846
+#, c-format
+msgid "PL/Perl functions cannot return type %s"
+msgstr "PL/Perl函数无法返回类型%s"
+
+#: plperl.c:2021 plperl.c:2885
+#, c-format
+msgid "PL/Perl functions cannot accept type %s"
+msgstr "PL/Perl 函数无法使用类型%s"
+
+#: plperl.c:2138
+#, c-format
+msgid "didn't get a CODE reference from compiling function \"%s\""
+msgstr "没有从正在编译的函数 \"%s\"得到CODE参考"
+
+#: plperl.c:2229
+#, c-format
+msgid "didn't get a return item from function"
+msgstr "没有从函数得到一个返回项"
+
+#: plperl.c:2273 plperl.c:2340
+#, c-format
+msgid "couldn't fetch $_TD"
+msgstr "无法取得 $_TD"
+
+#: plperl.c:2297 plperl.c:2360
+#, c-format
+msgid "didn't get a return item from trigger function"
+msgstr "没有从触发器函数得到一个返回项"
+
+#: plperl.c:2419
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "在不能接受使用集合的环境中调用set-valued函数"
+
+#: plperl.c:2464
+#, c-format
+msgid "set-returning PL/Perl function must return reference to array or use return_next"
+msgstr "返回集合的PL/Perl函数必须返回对数组的引用或者使用return_next"
+
+#: plperl.c:2585
+#, c-format
+msgid "ignoring modified row in DELETE trigger"
+msgstr "在DELETE触发器中忽略已修改的记录"
+
+#: plperl.c:2593
+#, c-format
+msgid "result of PL/Perl trigger function must be undef, \"SKIP\", or \"MODIFY\""
+msgstr "PL/Perl 触发器函数的结果必须是undef, \"SKIP\", 或 \"MODIFY\""
+
+#: plperl.c:2841
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "触发器函数只能以触发器的形式调用"
+
+#: plperl.c:3186
+#, c-format
+msgid "query result has too many rows to fit in a Perl array"
+msgstr "查询结果中的行太多,无法放在一个Perl数组中"
+
+#: plperl.c:3256
+#, c-format
+msgid "cannot use return_next in a non-SETOF function"
+msgstr "不能在非SETOF函数中使用return_next"
+
+#: plperl.c:3330
+#, c-format
+msgid "SETOF-composite-returning PL/Perl function must call return_next with reference to hash"
+msgstr "返回SETOF-组合类型值的PL/Perl函数必须调用带有对哈希引用的return_next"
+
+#: plperl.c:4105
+#, c-format
+msgid "PL/Perl function \"%s\""
+msgstr "PL/Perl函数\"%s\""
+
+#: plperl.c:4117
+#, c-format
+msgid "compilation of PL/Perl function \"%s\""
+msgstr "编译PL/Perl函数\"%s\""
+
+#: plperl.c:4126
+#, c-format
+msgid "PL/Perl anonymous code block"
+msgstr "PL/Perl匿名代码块"
diff --git a/src/pl/plperl/ppport.h b/src/pl/plperl/ppport.h
new file mode 100644
index 0000000..1f6cf46
--- /dev/null
+++ b/src/pl/plperl/ppport.h
@@ -0,0 +1,17925 @@
+#if 0
+my $void = <<'SKIP';
+#endif
+/*
+----------------------------------------------------------------------
+
+ ppport.h -- Perl/Pollution/Portability Version 3.63
+
+ Automatically created by Devel::PPPort running under perl 5.034000.
+
+ Do NOT edit this file directly! -- Edit PPPort_pm.PL and the
+ includes in parts/inc/ instead.
+
+ Use 'perldoc ppport.h' to view the documentation below.
+
+----------------------------------------------------------------------
+
+SKIP
+
+=pod
+
+=head1 NAME
+
+ppport.h - Perl/Pollution/Portability version 3.63
+
+=head1 SYNOPSIS
+
+ perl ppport.h [options] [source files]
+
+ Searches current directory for files if no [source files] are given
+
+ --help show short help
+
+ --version show version
+
+ --patch=file write one patch file with changes
+ --copy=suffix write changed copies with suffix
+ --diff=program use diff program and options
+
+ --compat-version=version provide compatibility with Perl version
+ --cplusplus accept C++ comments
+
+ --quiet don't output anything except fatal errors
+ --nodiag don't show diagnostics
+ --nohints don't show hints
+ --nochanges don't suggest changes
+ --nofilter don't filter input files
+
+ --strip strip all script and doc functionality
+ from ppport.h
+
+ --list-provided list provided API
+ --list-unsupported list API that isn't supported all the way
+ back
+ --api-info=name show Perl API portability information
+
+=head1 COMPATIBILITY
+
+This version of F<ppport.h> is designed to support operation with Perl
+installations back to 5.003_07, and has been tested up to 5.35.1.
+
+=head1 OPTIONS
+
+=head2 --help
+
+Display a brief usage summary.
+
+=head2 --version
+
+Display the version of F<ppport.h>.
+
+=head2 --patch=I<file>
+
+If this option is given, a single patch file will be created if
+any changes are suggested. This requires a working diff program
+to be installed on your system.
+
+=head2 --copy=I<suffix>
+
+If this option is given, a copy of each file will be saved with
+the given suffix that contains the suggested changes. This does
+not require any external programs. Note that this does not
+automagically add a dot between the original filename and the
+suffix. If you want the dot, you have to include it in the option
+argument.
+
+If neither C<--patch> or C<--copy> are given, the default is to
+simply print the diffs for each file. This requires either
+C<Text::Diff> or a C<diff> program to be installed.
+
+=head2 --diff=I<program>
+
+Manually set the diff program and options to use. The default
+is to use C<Text::Diff>, when installed, and output unified
+context diffs.
+
+=head2 --compat-version=I<version>
+
+Tell F<ppport.h> to check for compatibility with the given
+Perl version. The default is to check for compatibility with Perl
+version 5.003_07. You can use this option to reduce the output
+of F<ppport.h> if you intend to be backward compatible only
+down to a certain Perl version.
+
+=head2 --cplusplus
+
+Usually, F<ppport.h> will detect C++ style comments and
+replace them with C style comments for portability reasons.
+Using this option instructs F<ppport.h> to leave C++
+comments untouched.
+
+=head2 --quiet
+
+Be quiet. Don't print anything except fatal errors.
+
+=head2 --nodiag
+
+Don't output any diagnostic messages. Only portability
+alerts will be printed.
+
+=head2 --nohints
+
+Don't output any hints. Hints often contain useful portability
+notes. Warnings will still be displayed.
+
+=head2 --nochanges
+
+Don't suggest any changes. Only give diagnostic output and hints
+unless these are also deactivated.
+
+=head2 --nofilter
+
+Don't filter the list of input files. By default, files not looking
+like source code (i.e. not *.xs, *.c, *.cc, *.cpp or *.h) are skipped.
+
+=head2 --strip
+
+Strip all script and documentation functionality from F<ppport.h>.
+This reduces the size of F<ppport.h> dramatically and may be useful
+if you want to include F<ppport.h> in smaller modules without
+increasing their distribution size too much.
+
+The stripped F<ppport.h> will have a C<--unstrip> option that allows
+you to undo the stripping, but only if an appropriate C<Devel::PPPort>
+module is installed.
+
+=head2 --list-provided
+
+Lists the API elements for which compatibility is provided by
+F<ppport.h>. Also lists if it must be explicitly requested,
+if it has dependencies, and if there are hints or warnings for it.
+
+=head2 --list-unsupported
+
+Lists the API elements that are known not to be FULLY supported by F<ppport.h>,
+and below which version of Perl they probably won't be available or work.
+By FULLY, we mean that support isn't provided all the way back to the first
+version of Perl that F<ppport.h> supports at all.
+
+=head2 --api-info=I<name>
+
+Show portability information for elements matching I<name>.
+If I<name> is surrounded by slashes, it is interpreted as a regular
+expression.
+
+Normally, only API elements are shown, but if there are no matching API
+elements but there are some other matching elements, those are shown. This
+allows you to conveniently find when functions internal to the core
+implementation were added; only people working on the core are likely to find
+this last part useful.
+
+=head1 DESCRIPTION
+
+In order for a Perl extension (XS) module to be as portable as possible
+across differing versions of Perl itself, certain steps need to be taken.
+
+=over 4
+
+=item *
+
+Including this header is the first major one. This alone will give you
+access to a large part of the Perl API that hasn't been available in
+earlier Perl releases. Use
+
+ perl ppport.h --list-provided
+
+to see which API elements are provided by ppport.h.
+
+=item *
+
+You should avoid using deprecated parts of the API. For example, using
+global Perl variables without the C<PL_> prefix is deprecated. Also,
+some API functions used to have a C<perl_> prefix. Using this form is
+also deprecated. You can safely use the supported API, as F<ppport.h>
+will provide wrappers for older Perl versions.
+
+=item *
+
+Although the purpose of F<ppport.h> is to keep you from having to concern
+yourself with what version you are running under, there may arise instances
+where you have to do so. These macros, the same ones as in base Perl, are
+available to you in all versions, and are what you should use:
+
+=over 4
+
+=item C<PERL_VERSION_I<xx>(major, minor, patch)>
+
+Returns whether or not the perl currently being compiled has the specified
+relationship I<xx> to the perl given by the parameters. I<xx> is one of
+C<EQ>, C<NE>, C<LT>, C<LE>, C<GT>, C<GE>.
+
+For example,
+
+ #if PERL_VERSION_GT(5,24,2)
+ code that will only be compiled on perls after v5.24.2
+ #else
+ fallback code
+ #endif
+
+Note that this is usable in making compile-time decisions
+
+You may use the special value '*' for the final number to mean ALL possible
+values for it. Thus,
+
+ #if PERL_VERSION_EQ(5,31,'*')
+
+means all perls in the 5.31 series. And
+
+ #if PERL_VERSION_NE(5,24,'*')
+
+means all perls EXCEPT 5.24 ones. And
+
+ #if PERL_VERSION_LE(5,9,'*')
+
+is effectively
+
+ #if PERL_VERSION_LT(5,10,0)
+
+=back
+
+=item *
+
+If you use one of a few functions or variables that were not present in
+earlier versions of Perl, and that can't be provided using a macro, you
+have to explicitly request support for these functions by adding one or
+more C<#define>s in your source code before the inclusion of F<ppport.h>.
+
+These functions or variables will be marked C<explicit> in the list shown
+by C<--list-provided>.
+
+Depending on whether you module has a single or multiple files that
+use such functions or variables, you want either C<static> or global
+variants.
+
+For a C<static> function or variable (used only in a single source
+file), use:
+
+ #define NEED_function
+ #define NEED_variable
+
+For a global function or variable (used in multiple source files),
+use:
+
+ #define NEED_function_GLOBAL
+ #define NEED_variable_GLOBAL
+
+Note that you mustn't have more than one global request for the
+same function or variable in your project.
+
+ Function / Variable Static Request Global Request
+ -----------------------------------------------------------------------------------------
+ caller_cx() NEED_caller_cx NEED_caller_cx_GLOBAL
+ ck_warner() NEED_ck_warner NEED_ck_warner_GLOBAL
+ ck_warner_d() NEED_ck_warner_d NEED_ck_warner_d_GLOBAL
+ croak_xs_usage() NEED_croak_xs_usage NEED_croak_xs_usage_GLOBAL
+ die_sv() NEED_die_sv NEED_die_sv_GLOBAL
+ eval_pv() NEED_eval_pv NEED_eval_pv_GLOBAL
+ grok_bin() NEED_grok_bin NEED_grok_bin_GLOBAL
+ grok_hex() NEED_grok_hex NEED_grok_hex_GLOBAL
+ grok_number() NEED_grok_number NEED_grok_number_GLOBAL
+ grok_numeric_radix() NEED_grok_numeric_radix NEED_grok_numeric_radix_GLOBAL
+ grok_oct() NEED_grok_oct NEED_grok_oct_GLOBAL
+ load_module() NEED_load_module NEED_load_module_GLOBAL
+ mess() NEED_mess NEED_mess_GLOBAL
+ mess_nocontext() NEED_mess_nocontext NEED_mess_nocontext_GLOBAL
+ mess_sv() NEED_mess_sv NEED_mess_sv_GLOBAL
+ mg_findext() NEED_mg_findext NEED_mg_findext_GLOBAL
+ my_snprintf() NEED_my_snprintf NEED_my_snprintf_GLOBAL
+ my_sprintf() NEED_my_sprintf NEED_my_sprintf_GLOBAL
+ my_strlcat() NEED_my_strlcat NEED_my_strlcat_GLOBAL
+ my_strlcpy() NEED_my_strlcpy NEED_my_strlcpy_GLOBAL
+ my_strnlen() NEED_my_strnlen NEED_my_strnlen_GLOBAL
+ newCONSTSUB() NEED_newCONSTSUB NEED_newCONSTSUB_GLOBAL
+ newSVpvn_share() NEED_newSVpvn_share NEED_newSVpvn_share_GLOBAL
+ PL_parser NEED_PL_parser NEED_PL_parser_GLOBAL
+ PL_signals NEED_PL_signals NEED_PL_signals_GLOBAL
+ pv_display() NEED_pv_display NEED_pv_display_GLOBAL
+ pv_escape() NEED_pv_escape NEED_pv_escape_GLOBAL
+ pv_pretty() NEED_pv_pretty NEED_pv_pretty_GLOBAL
+ sv_catpvf_mg() NEED_sv_catpvf_mg NEED_sv_catpvf_mg_GLOBAL
+ sv_catpvf_mg_nocontext() NEED_sv_catpvf_mg_nocontext NEED_sv_catpvf_mg_nocontext_GLOBAL
+ sv_setpvf_mg() NEED_sv_setpvf_mg NEED_sv_setpvf_mg_GLOBAL
+ sv_setpvf_mg_nocontext() NEED_sv_setpvf_mg_nocontext NEED_sv_setpvf_mg_nocontext_GLOBAL
+ sv_unmagicext() NEED_sv_unmagicext NEED_sv_unmagicext_GLOBAL
+ utf8_to_uvchr_buf() NEED_utf8_to_uvchr_buf NEED_utf8_to_uvchr_buf_GLOBAL
+ vload_module() NEED_vload_module NEED_vload_module_GLOBAL
+ vmess() NEED_vmess NEED_vmess_GLOBAL
+ warner() NEED_warner NEED_warner_GLOBAL
+
+To avoid namespace conflicts, you can change the namespace of the
+explicitly exported functions / variables using the C<DPPP_NAMESPACE>
+macro. Just C<#define> the macro before including C<ppport.h>:
+
+ #define DPPP_NAMESPACE MyOwnNamespace_
+ #include "ppport.h"
+
+The default namespace is C<DPPP_>.
+
+=back
+
+The good thing is that most of the above can be checked by running
+F<ppport.h> on your source code. See the next section for
+details.
+
+=head1 EXAMPLES
+
+To verify whether F<ppport.h> is needed for your module, whether you
+should make any changes to your code, and whether any special defines
+should be used, F<ppport.h> can be run as a Perl script to check your
+source code. Simply say:
+
+ perl ppport.h
+
+The result will usually be a list of patches suggesting changes
+that should at least be acceptable, if not necessarily the most
+efficient solution, or a fix for all possible problems.
+
+If you know that your XS module uses features only available in
+newer Perl releases, if you're aware that it uses C++ comments,
+and if you want all suggestions as a single patch file, you could
+use something like this:
+
+ perl ppport.h --compat-version=5.6.0 --cplusplus --patch=test.diff
+
+If you only want your code to be scanned without any suggestions
+for changes, use:
+
+ perl ppport.h --nochanges
+
+You can specify a different C<diff> program or options, using
+the C<--diff> option:
+
+ perl ppport.h --diff='diff -C 10'
+
+This would output context diffs with 10 lines of context.
+
+If you want to create patched copies of your files instead, use:
+
+ perl ppport.h --copy=.new
+
+To display portability information for the C<newSVpvn> function,
+use:
+
+ perl ppport.h --api-info=newSVpvn
+
+Since the argument to C<--api-info> can be a regular expression,
+you can use
+
+ perl ppport.h --api-info=/_nomg$/
+
+to display portability information for all C<_nomg> functions or
+
+ perl ppport.h --api-info=/./
+
+to display information for all known API elements.
+
+=head1 BUGS
+
+Some of the suggested edits and/or generated patches may not compile as-is
+without tweaking manually. This is generally due to the need for an extra
+parameter to be added to the call to prevent buffer overflow.
+
+If this version of F<ppport.h> is causing failure during
+the compilation of this module, please check if newer versions
+of either this module or C<Devel::PPPort> are available on CPAN
+before sending a bug report.
+
+If F<ppport.h> was generated using the latest version of
+C<Devel::PPPort> and is causing failure of this module, please
+file a bug report at L<https://github.com/Dual-Life/Devel-PPPort/issues>
+
+Please include the following information:
+
+=over 4
+
+=item 1.
+
+The complete output from running "perl -V"
+
+=item 2.
+
+This file.
+
+=item 3.
+
+The name and version of the module you were trying to build.
+
+=item 4.
+
+A full log of the build that failed.
+
+=item 5.
+
+Any other information that you think could be relevant.
+
+=back
+
+For the latest version of this code, please get the C<Devel::PPPort>
+module from CPAN.
+
+=head1 COPYRIGHT
+
+Version 3.x, Copyright (c) 2004-2013, Marcus Holland-Moritz.
+
+Version 2.x, Copyright (C) 2001, Paul Marquess.
+
+Version 1.x, Copyright (C) 1999, Kenneth Albanowski.
+
+This program is free software; you can redistribute it and/or
+modify it under the same terms as Perl itself.
+
+=head1 SEE ALSO
+
+See L<Devel::PPPort>.
+
+=cut
+
+# These are tools that must be included in ppport.h. It doesn't work if given
+# a .pl suffix.
+#
+# WARNING: Use only constructs that are legal as far back as D:P handles, as
+# this is run in the perl version being tested.
+
+# What revisions are legal, to be output as-is and converted into a pattern
+# that matches them precisely
+my $r_pat = "[57]";
+
+sub format_version
+{
+ # Given an input version that is acceptable to parse_version(), return a
+ # string of the standard representation of it.
+
+ my($r,$v,$s) = parse_version(shift);
+
+ if ($r < 5 || ($r == 5 && $v < 6)) {
+ my $ver = sprintf "%d.%03d", $r, $v;
+ $s > 0 and $ver .= sprintf "_%02d", $s;
+
+ return $ver;
+ }
+
+ return sprintf "%d.%d.%d", $r, $v, $s;
+}
+
+sub parse_version
+{
+ # Returns a triplet, (revision, major, minor) from the input, treated as a
+ # string, which can be in any of several typical formats.
+
+ my $ver = shift;
+ $ver = "" unless defined $ver;
+
+ my($r,$v,$s);
+
+ if ( ($r, $v, $s) = $ver =~ /^([0-9]+)([0-9]{3})([0-9]{3})$/ # 5029010, from the file
+ # names in our
+ # parts/base/ and
+ # parts/todo directories
+ or ($r, $v, $s) = $ver =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)$/ # 5.25.7
+ or ($r, $v, $s) = $ver =~ /^([0-9]+)\.([0-9]{3})([0-9]{3})$/ # 5.025008, from the
+ # output of $]
+ or ($r, $v, $s) = $ver =~ /^([0-9]+)\.([0-9]{1,3})()$/ # 5.24, 5.004
+ or ($r, $v, $s) = $ver =~ /^([0-9]+)\.(00[1-5])_?([0-9]{2})$/ # 5.003_07
+ ) {
+
+ $s = 0 unless $s;
+
+ die "Only Perl $r_pat are supported '$ver'\n" unless $r =~ / ^ $r_pat $ /x;
+ die "Invalid version number: $ver\n" if $v >= 1000 || $s >= 1000;
+ return (0 +$r, 0 + $v, 0 + $s);
+ }
+
+ # For some safety, don't assume something is a version number if it has a
+ # literal dot as one of the three characters. This will have to be fixed
+ # when we reach x.46 (since 46 is ord('.'))
+ if ($ver !~ /\./ && (($r, $v, $s) = $ver =~ /^(.)(.)(.)$/)) # vstring 5.25.7
+ {
+ $r = ord $r;
+ $v = ord $v;
+ $s = ord $s;
+
+ die "Only Perl $r_pat are supported '$ver'\n" unless $r =~ / ^ $r_pat $ /x;
+ return ($r, $v, $s);
+ }
+
+ my $mesg = "";
+ $mesg = ". (In 5.00x_yz, x must be 1-5.)" if $ver =~ /_/;
+ die "Invalid version number format: '$ver'$mesg\n";
+}
+
+sub int_parse_version
+{
+ # Returns integer 7 digit human-readable version, suitable for use in file
+ # names in parts/todo parts/base.
+
+ return 0 + join "", map { sprintf("%03d", $_) } parse_version(shift);
+}
+
+sub ivers # Shorter name for int_parse_version
+{
+ return int_parse_version(shift);
+}
+
+sub format_version_line
+{
+ # Returns a floating point representation of the input version
+
+ my $version = int_parse_version(shift);
+ $version =~ s/ ^ ( $r_pat ) \B /$1./x;
+ return $version;
+}
+
+BEGIN {
+ if ("$]" < "5.006" ) {
+ # On early perls, the implicit pass by reference doesn't work, so we have
+ # to use the globals to initialize.
+ eval q[sub dictionary_order($$) { _dictionary_order($a, $b) } ];
+ } elsif ("$]" < "5.022" ) {
+ eval q[sub dictionary_order($$) { _dictionary_order(@_) } ];
+ } else {
+ eval q[sub dictionary_order :prototype($$) { _dictionary_order(@_) } ];
+ }
+}
+
+sub _dictionary_order { # Sort caselessly, ignoring punct
+ my ($valid_a, $valid_b) = @_;
+
+ my ($lc_a, $lc_b);
+ my ($squeezed_a, $squeezed_b);
+
+ $valid_a = '' unless defined $valid_a;
+ $valid_b = '' unless defined $valid_b;
+
+ $lc_a = lc $valid_a;
+ $lc_b = lc $valid_b;
+
+ $squeezed_a = $lc_a;
+ $squeezed_a =~ s/^_+//g; # No leading underscores
+ $squeezed_a =~ s/\B_+\B//g; # No connecting underscores
+ $squeezed_a =~ s/[\W]//g; # No punct
+
+ $squeezed_b = $lc_b;
+ $squeezed_b =~ s/^_+//g;
+ $squeezed_b =~ s/\B_+\B//g;
+ $squeezed_b =~ s/[\W]//g;
+
+ return( $squeezed_a cmp $squeezed_b
+ or $lc_a cmp $lc_b
+ or $valid_a cmp $valid_b);
+}
+
+sub sort_api_lines # Sort lines of the form flags|return|name|args...
+ # by 'name'
+{
+ $a =~ / ^ [^|]* \| [^|]* \| ( [^|]* ) /x; # 3rd field '|' is sep
+ my $a_name = $1;
+ $b =~ / ^ [^|]* \| [^|]* \| ( [^|]* ) /x;
+ my $b_name = $1;
+ return dictionary_order($a_name, $b_name);
+}
+
+1;
+
+use strict;
+
+BEGIN { require warnings if "$]" > '5.006' }
+
+# Disable broken TRIE-optimization
+BEGIN { eval '${^RE_TRIE_MAXBUF} = -1' if "$]" >= "5.009004" && "$]" <= "5.009005"}
+
+my $VERSION = 3.63;
+
+my %opt = (
+ quiet => 0,
+ diag => 1,
+ hints => 1,
+ changes => 1,
+ cplusplus => 0,
+ filter => 1,
+ strip => 0,
+ version => 0,
+);
+
+my($ppport) = $0 =~ /([\w.]+)$/;
+my $LF = '(?:\r\n|[\r\n])'; # line feed
+my $HS = "[ \t]"; # horizontal whitespace
+
+# Never use C comments in this file!
+my $ccs = '/'.'*';
+my $cce = '*'.'/';
+my $rccs = quotemeta $ccs;
+my $rcce = quotemeta $cce;
+
+eval {
+ require Getopt::Long;
+ Getopt::Long::GetOptions(\%opt, qw(
+ help quiet diag! filter! hints! changes! cplusplus strip version
+ patch=s copy=s diff=s compat-version=s
+ list-provided list-unsupported api-info=s
+ )) or usage();
+};
+
+if ($@ and grep /^-/, @ARGV) {
+ usage() if "@ARGV" =~ /^--?h(?:elp)?$/;
+ die "Getopt::Long not found. Please don't use any options.\n";
+}
+
+if ($opt{version}) {
+ print "This is $0 $VERSION.\n";
+ exit 0;
+}
+
+usage() if $opt{help};
+strip() if $opt{strip};
+
+$opt{'compat-version'} = 5.003_07 unless exists $opt{'compat-version'};
+$opt{'compat-version'} = int_parse_version($opt{'compat-version'});
+
+my $int_min_perl = int_parse_version(5.003_07);
+
+# Each element of this hash looks something like:
+# 'Poison' => {
+# 'base' => '5.008000',
+# 'provided' => 1,
+# 'todo' => '5.003007'
+# },
+my %API = map { /^(\w+)\|([^|]*)\|([^|]*)\|(\w*)$/
+ ? ( $1 => {
+ ($2 ? ( base => $2 ) : ()),
+ ($3 ? ( todo => $3 ) : ()),
+ (index($4, 'v') >= 0 ? ( varargs => 1 ) : ()),
+ (index($4, 'p') >= 0 ? ( provided => 1 ) : ()),
+ (index($4, 'n') >= 0 ? ( noTHXarg => 1 ) : ()),
+ (index($4, 'c') >= 0 ? ( core_only => 1 ) : ()),
+ (index($4, 'd') >= 0 ? ( deprecated => 1 ) : ()),
+ (index($4, 'i') >= 0 ? ( inaccessible => 1 ) : ()),
+ (index($4, 'x') >= 0 ? ( experimental => 1 ) : ()),
+ (index($4, 'u') >= 0 ? ( undocumented => 1 ) : ()),
+ (index($4, 'o') >= 0 ? ( ppport_fnc => 1 ) : ()),
+ (index($4, 'V') >= 0 ? ( unverified => 1 ) : ()),
+ } )
+ : die "invalid spec: $_" } qw(
+ABDAY_1|5.027010||Viu
+ABDAY_2|5.027010||Viu
+ABDAY_3|5.027010||Viu
+ABDAY_4|5.027010||Viu
+ABDAY_5|5.027010||Viu
+ABDAY_6|5.027010||Viu
+ABDAY_7|5.027010||Viu
+ABMON_10|5.027010||Viu
+ABMON_11|5.027010||Viu
+ABMON_12|5.027010||Viu
+ABMON_1|5.027010||Viu
+ABMON_2|5.027010||Viu
+ABMON_3|5.027010||Viu
+ABMON_4|5.027010||Viu
+ABMON_5|5.027010||Viu
+ABMON_6|5.027010||Viu
+ABMON_7|5.027010||Viu
+ABMON_8|5.027010||Viu
+ABMON_9|5.027010||Viu
+ABORT|5.003007||Viu
+abort|5.005000||Viu
+abort_execution|5.025010||Viu
+accept|5.005000||Viu
+ACCEPT|5.009005||Viu
+ACCEPT_t8_p8|5.033003||Viu
+ACCEPT_t8_pb|5.033003||Viu
+ACCEPT_tb_p8|5.033003||Viu
+ACCEPT_tb_pb|5.033003||Viu
+access|5.005000||Viu
+add_above_Latin1_folds|5.021001||Viu
+add_cp_to_invlist|5.013011||Viu
+add_data|5.005000||Vniu
+add_multi_match|5.021004||Viu
+_add_range_to_invlist|5.016000||cViu
+add_utf16_textfilter|5.011001||Viu
+adjust_size_and_find_bucket|5.019003||Vniu
+advance_one_LB|5.023007||Viu
+advance_one_SB|5.021009||Viu
+advance_one_WB|5.021009||Viu
+AHOCORASICK|5.009005||Viu
+AHOCORASICKC|5.009005||Viu
+AHOCORASICKC_t8_p8|5.033003||Viu
+AHOCORASICKC_t8_pb|5.033003||Viu
+AHOCORASICKC_tb_p8|5.033003||Viu
+AHOCORASICKC_tb_pb|5.033003||Viu
+AHOCORASICK_t8_p8|5.033003||Viu
+AHOCORASICK_t8_pb|5.033003||Viu
+AHOCORASICK_tb_p8|5.033003||Viu
+AHOCORASICK_tb_pb|5.033003||Viu
+alloccopstash|5.017001|5.017001|x
+alloc_LOGOP|5.025004||xViu
+allocmy|5.008001||Viu
+ALLOC_THREAD_KEY|5.005003||Viu
+ALT_DIGITS|5.027010||Viu
+amagic_call|5.003007|5.003007|u
+amagic_cmp|5.009003||Viu
+amagic_cmp_desc|5.031011||Viu
+amagic_cmp_locale|5.009003||Viu
+amagic_cmp_locale_desc|5.031011||Viu
+amagic_deref_call|5.013007|5.013007|u
+amagic_i_ncmp|5.009003||Viu
+amagic_i_ncmp_desc|5.031011||Viu
+amagic_is_enabled|5.015008||Viu
+amagic_ncmp|5.009003||Viu
+amagic_ncmp_desc|5.031011||Viu
+AMG_CALLun|5.003007||Viu
+AMG_CALLunary|5.013009||Viu
+AMGfallNEVER|5.003007||Viu
+AMGfallNO|5.003007||Viu
+AMGfallYES|5.003007||Viu
+AMGf_assign|5.003007||Viu
+AMGf_noleft|5.003007||Viu
+AMGf_noright|5.003007||Viu
+AMGf_numarg|5.021009||Viu
+AMGf_numeric|5.013002||Viu
+AMGf_unary|5.003007||Viu
+AMGf_want_list|5.017002||Viu
+AM_STR|5.027010||Viu
+AMT_AMAGIC|5.004000||Viu
+AMT_AMAGIC_off|5.004000||Viu
+AMT_AMAGIC_on|5.004000||Viu
+AMTf_AMAGIC|5.004000||Viu
+_aMY_CXT|5.009000|5.009000|p
+aMY_CXT|5.009000|5.009000|p
+aMY_CXT_|5.009000|5.009000|p
+anchored_end_shift|5.009005||Viu
+anchored_offset|5.005000||Viu
+anchored_substr|5.005000||Viu
+anchored_utf8|5.008000||Viu
+ANGSTROM_SIGN|5.017003||Viu
+anonymise_cv_maybe|5.013003||Viu
+any_dup|5.006000||Vu
+ANYOF|5.003007||Viu
+ANYOF_ALNUM|5.006000||Viu
+ANYOF_ALNUML|5.004000||Viu
+ANYOF_ALPHA|5.006000||Viu
+ANYOF_ALPHANUMERIC|5.017008||Viu
+ANYOF_ASCII|5.006000||Viu
+ANYOF_BIT|5.004005||Viu
+ANYOF_BITMAP|5.006000||Viu
+ANYOF_BITMAP_BYTE|5.006000||Viu
+ANYOF_BITMAP_CLEAR|5.006000||Viu
+ANYOF_BITMAP_CLEARALL|5.007003||Viu
+ANYOF_BITMAP_SET|5.006000||Viu
+ANYOF_BITMAP_SETALL|5.007003||Viu
+ANYOF_BITMAP_SIZE|5.006000||Viu
+ANYOF_BITMAP_TEST|5.006000||Viu
+ANYOF_BITMAP_ZERO|5.006000||Viu
+ANYOF_BLANK|5.006001||Viu
+ANYOF_CASED|5.017008||Viu
+ANYOF_CLASS_OR|5.017007||Viu
+ANYOF_CLASS_SETALL|5.013011||Viu
+ANYOF_CLASS_TEST_ANY_SET|5.013008||Viu
+ANYOF_CNTRL|5.006000||Viu
+ANYOF_COMMON_FLAGS|5.019008||Viu
+ANYOFD|5.023003||Viu
+ANYOF_DIGIT|5.006000||Viu
+ANYOFD_t8_p8|5.033003||Viu
+ANYOFD_t8_pb|5.033003||Viu
+ANYOFD_tb_p8|5.033003||Viu
+ANYOFD_tb_pb|5.033003||Viu
+ANYOF_FLAGS|5.006000||Viu
+ANYOF_FLAGS_ALL|5.006000||Viu
+ANYOF_GRAPH|5.006000||Viu
+ANYOFH|5.029007||Viu
+ANYOFHb|5.031001||Viu
+ANYOFHb_t8_p8|5.033003||Viu
+ANYOFHb_t8_pb|5.033003||Viu
+ANYOFHb_tb_p8|5.033003||Viu
+ANYOFHb_tb_pb|5.033003||Viu
+ANYOF_HORIZWS|5.009005||Viu
+ANYOFHr|5.031002||Viu
+ANYOFHr_t8_p8|5.033003||Viu
+ANYOFHr_t8_pb|5.033003||Viu
+ANYOFHr_tb_p8|5.033003||Viu
+ANYOFHr_tb_pb|5.033003||Viu
+ANYOFHs|5.031007||Viu
+ANYOFHs_t8_p8|5.033003||Viu
+ANYOFHs_t8_pb|5.033003||Viu
+ANYOFHs_tb_p8|5.033003||Viu
+ANYOFHs_tb_pb|5.033003||Viu
+ANYOFH_t8_p8|5.033003||Viu
+ANYOFH_t8_pb|5.033003||Viu
+ANYOFH_tb_p8|5.033003||Viu
+ANYOFH_tb_pb|5.033003||Viu
+ANYOF_INVERT|5.004000||Viu
+ANYOFL|5.021008||Viu
+ANYOFL_FOLD|5.023007||Viu
+ANYOF_LOCALE_FLAGS|5.019005||Viu
+ANYOF_LOWER|5.006000||Viu
+ANYOFL_SHARED_UTF8_LOCALE_fold_HAS_MATCHES_nonfold_REQD|5.023007||Viu
+ANYOFL_SOME_FOLDS_ONLY_IN_UTF8_LOCALE|5.023007||Viu
+ANYOFL_t8_p8|5.033003||Viu
+ANYOFL_t8_pb|5.033003||Viu
+ANYOFL_tb_p8|5.033003||Viu
+ANYOFL_tb_pb|5.033003||Viu
+ANYOFL_UTF8_LOCALE_REQD|5.023007||Viu
+ANYOFM|5.027009||Viu
+ANYOF_MATCHES_ALL_ABOVE_BITMAP|5.021004||Viu
+ANYOF_MATCHES_POSIXL|5.021004||Viu
+ANYOF_MAX|5.006000||Viu
+ANYOFM_t8_p8|5.033003||Viu
+ANYOFM_t8_pb|5.033003||Viu
+ANYOFM_tb_p8|5.033003||Viu
+ANYOFM_tb_pb|5.033003||Viu
+ANYOF_NALNUM|5.006000||Viu
+ANYOF_NALNUML|5.004000||Viu
+ANYOF_NALPHA|5.006000||Viu
+ANYOF_NALPHANUMERIC|5.017008||Viu
+ANYOF_NASCII|5.006000||Viu
+ANYOF_NBLANK|5.006001||Viu
+ANYOF_NCASED|5.017008||Viu
+ANYOF_NCNTRL|5.006000||Viu
+ANYOF_NDIGIT|5.006000||Viu
+ANYOF_NGRAPH|5.006000||Viu
+ANYOF_NHORIZWS|5.009005||Viu
+ANYOF_NLOWER|5.006000||Viu
+ANYOF_NPRINT|5.006000||Viu
+ANYOF_NPUNCT|5.006000||Viu
+ANYOF_NSPACE|5.006000||Viu
+ANYOF_NSPACEL|5.004000||Viu
+ANYOF_NUPPER|5.006000||Viu
+ANYOF_NVERTWS|5.009005||Viu
+ANYOF_NWORDCHAR|5.017005||Viu
+ANYOF_NXDIGIT|5.006000||Viu
+ANYOF_ONLY_HAS_BITMAP|5.021004||Viu
+ANYOFPOSIXL|5.029004||Viu
+ANYOF_POSIXL_AND|5.019005||Viu
+ANYOF_POSIXL_CLEAR|5.019005||Viu
+ANYOF_POSIXL_MAX|5.019005||Viu
+ANYOF_POSIXL_OR|5.019005||Viu
+ANYOF_POSIXL_SET|5.019005||Viu
+ANYOF_POSIXL_SETALL|5.019005||Viu
+ANYOF_POSIXL_SET_TO_BITMAP|5.029004||Viu
+ANYOF_POSIXL_SSC_TEST_ALL_SET|5.019009||Viu
+ANYOF_POSIXL_SSC_TEST_ANY_SET|5.019009||Viu
+ANYOFPOSIXL_t8_p8|5.033003||Viu
+ANYOFPOSIXL_t8_pb|5.033003||Viu
+ANYOFPOSIXL_tb_p8|5.033003||Viu
+ANYOFPOSIXL_tb_pb|5.033003||Viu
+ANYOF_POSIXL_TEST|5.019005||Viu
+ANYOF_POSIXL_TEST_ALL_SET|5.019005||Viu
+ANYOF_POSIXL_TEST_ANY_SET|5.019005||Viu
+ANYOF_POSIXL_ZERO|5.019005||Viu
+ANYOF_PRINT|5.006000||Viu
+ANYOF_PUNCT|5.006000||Viu
+ANYOFR|5.031007||Viu
+ANYOFRb|5.031007||Viu
+ANYOFRbase|5.031007||Viu
+ANYOFR_BASE_BITS|5.031007||Viu
+ANYOFRb_t8_p8|5.033003||Viu
+ANYOFRb_t8_pb|5.033003||Viu
+ANYOFRb_tb_p8|5.033003||Viu
+ANYOFRb_tb_pb|5.033003||Viu
+ANYOFRdelta|5.031007||Viu
+ANYOFR_t8_p8|5.033003||Viu
+ANYOFR_t8_pb|5.033003||Viu
+ANYOFR_tb_p8|5.033003||Viu
+ANYOFR_tb_pb|5.033003||Viu
+ANYOF_SHARED_d_MATCHES_ALL_NON_UTF8_NON_ASCII_non_d_WARN_SUPER|5.023003||Viu
+ANYOF_SHARED_d_UPPER_LATIN1_UTF8_STRING_MATCHES_non_d_RUNTIME_USER_PROP|5.023006||Viu
+ANYOF_SPACE|5.006000||Viu
+ANYOF_SPACEL|5.004000||Viu
+ANYOF_t8_p8|5.033003||Viu
+ANYOF_t8_pb|5.033003||Viu
+ANYOF_tb_p8|5.033003||Viu
+ANYOF_tb_pb|5.033003||Viu
+ANYOF_UNIPROP|5.017006||Viu
+ANYOF_UPPER|5.006000||Viu
+ANYOF_VERTWS|5.009005||Viu
+ANYOF_WORDCHAR|5.017005||Viu
+ANYOF_XDIGIT|5.006000||Viu
+ao|5.005000||Viu
+_append_range_to_invlist|5.013010||Viu
+append_utf8_from_native_byte|5.019004||cVniu
+apply|5.003007||Viu
+apply_attrs|5.006000||Viu
+apply_attrs_my|5.007003||Viu
+apply_attrs_string|5.006001|5.006001|xu
+ARCHLIB|5.003007|5.003007|Vn
+ARCHLIB_EXP|5.003007|5.003007|Vn
+ARCHNAME|5.004000|5.004000|Vn
+ARG1|5.003007||Viu
+ARG1_LOC|5.005000||Viu
+ARG1_SET|5.005000||Viu
+ARG2|5.003007||Viu
+ARG2L|5.009005||Viu
+ARG2L_LOC|5.009005||Viu
+ARG2_LOC|5.005000||Viu
+ARG2L_SET|5.009005||Viu
+ARG2_SET|5.005000||Viu
+ARG|5.005000||Viu
+ARG_LOC|5.005000||Viu
+ARGp|5.031010||Viu
+ARGp_LOC|5.031010||Viu
+ARGp_SET|5.031010||Viu
+ARG__SET|5.005000||Viu
+ARG_SET|5.005000||Viu
+ARGTARG|5.003007||Viu
+ARG_VALUE|5.005000||Viu
+argvout_final|5.029006||Viu
+ASCIIish|5.005003||Viu
+ASCII_MORE_RESTRICT_PAT_MODS|5.013010||Viu
+ASCII_RESTRICT_PAT_MOD|5.013009||Viu
+ASCII_RESTRICT_PAT_MODS|5.013009||Viu
+ASCII_TO_NATIVE|5.007001||Viu
+ASCII_TO_NEED|5.019004||dcVnu
+asctime|5.009000||Viu
+ASCTIME_R_PROTO|5.008000|5.008000|Vn
+assert|5.003007||Viu
+__ASSERT_|5.019007|5.008008|p
+ASSERT_CURPAD_ACTIVE|5.008001||Viu
+ASSERT_CURPAD_LEGAL|5.008001||Viu
+assert_not_glob|5.009004||Viu
+assert_not_ROK|5.008001||Viu
+assert_uft8_cache_coherent|5.013003||Viu
+assignment_type|5.021005||Viu
+ASSUME|5.019006|5.003007|p
+atfork_lock|5.007002|5.007002|nu
+atfork_unlock|5.007002|5.007002|nu
+aTHX|5.006000|5.003007|p
+aTHX_|5.006000|5.003007|p
+aTHXa|5.017006||Viu
+aTHXo|5.006000||Viu
+aTHXR||5.003007|ponu
+aTHXR_||5.003007|ponu
+aTHXx|5.006000||Viu
+Atof|5.006000||Viu
+Atol|5.006000||Viu
+atoll|5.008000||Viu
+Atoul|5.006000||Viu
+AvALLOC|5.003007||Viu
+AvARRAY|5.003007|5.003007|
+AvARYLEN|5.003007||Viu
+av_arylen_p|||cu
+av_clear|5.003007|5.003007|
+av_count|5.033001|5.003007|p
+av_create_and_push|5.009005|5.009005|x
+av_create_and_unshift_one|5.009005|5.009005|x
+av_delete|5.006000|5.006000|
+av_exists|5.006000|5.006000|
+av_extend|5.003007|5.003007|
+av_extend_guts|5.017004||Viu
+av_fetch|5.003007|5.003007|
+av_fill|5.003007|5.003007|
+AvFILL|5.003007|5.003007|
+AvFILLp|5.004005||pcV
+av_iter_p|||cu
+av_len|5.003007|5.003007|
+av_make|5.003007|5.003007|
+AvMAX|5.003007||Viu
+av_new_alloc|5.035001|5.035001|
+av_nonelem|5.027009||Viu
+av_pop|5.003007|5.003007|
+av_push|5.003007|5.003007|
+AvREAL|5.003007||Viu
+AvREALISH|5.003007||Viu
+AvREAL_off|5.003007||Viu
+AvREAL_on|5.003007||Viu
+AvREAL_only|5.009003||Viu
+AvREIFY|5.003007||Viu
+av_reify|5.004004||cViu
+AvREIFY_off|5.003007||Viu
+AvREIFY_on|5.003007||Viu
+AvREIFY_only|5.009003||Viu
+av_shift|5.003007|5.003007|
+av_store|5.003007|5.003007|
+av_tindex|5.017009|5.003007|p
+av_tindex_skip_len_mg|5.025010||Viu
+av_top_index|5.017009|5.003007|p
+av_top_index_skip_len_mg|5.025010||Viu
+av_undef|5.003007|5.003007|
+av_unshift|5.003007|5.003007|
+ax|5.003007|5.003007|
+backup_one_GCB|5.025003||Viu
+backup_one_LB|5.023007||Viu
+backup_one_SB|5.021009||Viu
+backup_one_WB|5.021009||Viu
+bad_type_gv|5.019002||Viu
+bad_type_pv|5.016000||Viu
+BADVERSION|5.011004||Viu
+BASEOP|5.003007||Viu
+BhkDISABLE|5.013003||xV
+BhkENABLE|5.013003||xV
+BhkENTRY|5.013003||xVi
+BhkENTRY_set|5.013003||xV
+BHKf_bhk_eval|5.013006||Viu
+BHKf_bhk_post_end|5.013006||Viu
+BHKf_bhk_pre_end|5.013006||Viu
+BHKf_bhk_start|5.013006||Viu
+BhkFLAGS|5.013003||xVi
+BIN|5.003007|5.003007|Vn
+bind|5.005000||Viu
+bind_match|5.003007||Viu
+BIN_EXP|5.004000|5.004000|Vn
+BIT_BUCKET|5.003007||Viu
+BIT_DIGITS|5.004000||Viu
+BITMAP_BYTE|5.009005||Viu
+BITMAP_TEST|5.009005||Viu
+blk_eval|5.003007||Viu
+blk_format|5.011000||Viu
+blk_gimme|5.003007||Viu
+blk_givwhen|5.027008||Viu
+blk_loop|5.003007||Viu
+blk_oldcop|5.003007||Viu
+blk_oldmarksp|5.003007||Viu
+blk_oldpm|5.003007||Viu
+blk_oldsaveix|5.023008||Viu
+blk_oldscopesp|5.003007||Viu
+blk_oldsp|5.003007||Viu
+blk_old_tmpsfloor|5.023008||Viu
+blk_sub|5.003007||Viu
+blk_u16|5.011000||Viu
+block_end|5.004000|5.004000|
+block_gimme|5.004000|5.004000|u
+blockhook_register|5.013003|5.013003|x
+block_start|5.004000|5.004000|
+BmFLAGS|5.009005||Viu
+BmPREVIOUS|5.003007||Viu
+BmRARE|5.003007||Viu
+BmUSEFUL|5.003007||Viu
+BOL|5.003007||Viu
+BOL_t8_p8|5.033003||Viu
+BOL_t8_pb|5.033003||Viu
+BOL_tb_p8|5.033003||Viu
+BOL_tb_pb|5.033003||Viu
+BOM_UTF8|5.025005|5.003007|p
+BOM_UTF8_FIRST_BYTE|5.019004||Viu
+BOM_UTF8_TAIL|5.019004||Viu
+bool|5.003007||Viu
+boolSV|5.004000|5.003007|p
+boot_core_mro|5.009005||Viu
+boot_core_PerlIO|5.007002||Viu
+boot_core_UNIVERSAL|5.003007||Viu
+BOUND|5.003007||Viu
+BOUNDA|5.013009||Viu
+BOUNDA_t8_p8|5.033003||Viu
+BOUNDA_t8_pb|5.033003||Viu
+BOUNDA_tb_p8|5.033003||Viu
+BOUNDA_tb_pb|5.033003||Viu
+BOUNDL|5.004000||Viu
+BOUNDL_t8_p8|5.033003||Viu
+BOUNDL_t8_pb|5.033003||Viu
+BOUNDL_tb_p8|5.033003||Viu
+BOUNDL_tb_pb|5.033003||Viu
+BOUND_t8_p8|5.033003||Viu
+BOUND_t8_pb|5.033003||Viu
+BOUND_tb_p8|5.033003||Viu
+BOUND_tb_pb|5.033003||Viu
+BOUNDU|5.013009||Viu
+BOUNDU_t8_p8|5.033003||Viu
+BOUNDU_t8_pb|5.033003||Viu
+BOUNDU_tb_p8|5.033003||Viu
+BOUNDU_tb_pb|5.033003||Viu
+BRANCH|5.003007||Viu
+BRANCHJ|5.005000||Viu
+BRANCHJ_t8_p8|5.033003||Viu
+BRANCHJ_t8_pb|5.033003||Viu
+BRANCHJ_tb_p8|5.033003||Viu
+BRANCHJ_tb_pb|5.033003||Viu
+BRANCH_next|5.009005||Viu
+BRANCH_next_fail|5.009005||Viu
+BRANCH_next_fail_t8_p8|5.033003||Viu
+BRANCH_next_fail_t8_pb|5.033003||Viu
+BRANCH_next_fail_tb_p8|5.033003||Viu
+BRANCH_next_fail_tb_pb|5.033003||Viu
+BRANCH_next_t8_p8|5.033003||Viu
+BRANCH_next_t8_pb|5.033003||Viu
+BRANCH_next_tb_p8|5.033003||Viu
+BRANCH_next_tb_pb|5.033003||Viu
+BRANCH_t8_p8|5.033003||Viu
+BRANCH_t8_pb|5.033003||Viu
+BRANCH_tb_p8|5.033003||Viu
+BRANCH_tb_pb|5.033003||Viu
+BSD_GETPGRP|5.003007||Viu
+BSDish|5.008001||Viu
+BSD_SETPGRP|5.003007||Viu
+BUFSIZ|5.003007||Viu
+_byte_dump_string|5.025006||cViu
+BYTEORDER|5.003007|5.003007|Vn
+bytes_cmp_utf8|5.013007|5.013007|
+bytes_from_utf8|5.007001|5.007001|x
+bytes_from_utf8_loc|5.027001||xcVn
+bytes_to_utf8|5.006001|5.006001|x
+call_argv|5.006000|5.003007|p
+call_atexit|5.006000|5.006000|u
+CALL_BLOCK_HOOKS|5.013003||xVi
+CALL_CHECKER_REQUIRE_GV|5.021004|5.021004|
+caller_cx|5.013005|5.006000|p
+CALL_FPTR|5.006000||Viu
+call_list|5.004000|5.004000|u
+call_method|5.006000|5.003007|p
+calloc|5.007002|5.007002|n
+call_pv|5.006000|5.003007|p
+CALLREGCOMP|5.005000||Viu
+CALLREGCOMP_ENG|5.009005||Viu
+CALLREGDUPE|5.009005||Viu
+CALLREGDUPE_PVT|5.009005||Viu
+CALLREGEXEC|5.005000||Viu
+CALLREGFREE|5.006000||Viu
+CALLREGFREE_PVT|5.009005||Viu
+CALLREG_INTUIT_START|5.006000||Viu
+CALLREG_INTUIT_STRING|5.006000||Viu
+CALLREG_NAMED_BUFF_ALL|5.009005||Viu
+CALLREG_NAMED_BUFF_CLEAR|5.009005||Viu
+CALLREG_NAMED_BUFF_COUNT|5.009005||Viu
+CALLREG_NAMED_BUFF_DELETE|5.009005||Viu
+CALLREG_NAMED_BUFF_EXISTS|5.009005||Viu
+CALLREG_NAMED_BUFF_FETCH|5.009005||Viu
+CALLREG_NAMED_BUFF_FIRSTKEY|5.009005||Viu
+CALLREG_NAMED_BUFF_NEXTKEY|5.009005||Viu
+CALLREG_NAMED_BUFF_SCALAR|5.009005||Viu
+CALLREG_NAMED_BUFF_STORE|5.009005||Viu
+CALLREG_NUMBUF_FETCH|5.009005||Viu
+CALLREG_NUMBUF_LENGTH|5.009005||Viu
+CALLREG_NUMBUF_STORE|5.009005||Viu
+CALLREG_PACKAGE|5.009005||Viu
+CALLRUNOPS|5.005000||Viu
+call_sv|5.006000|5.003007|p
+CAN64BITHASH|5.027001||Viu
+CAN_COW_FLAGS|5.009000||Viu
+CAN_COW_MASK|5.009000||Viu
+cando|5.003007||Viu
+CAN_PROTOTYPE|5.003007||Viu
+C_ARRAY_END|5.013002|5.003007|p
+C_ARRAY_LENGTH|5.008001|5.003007|p
+case_100_SBOX32|5.027001||Viu
+case_101_SBOX32|5.027001||Viu
+case_102_SBOX32|5.027001||Viu
+case_103_SBOX32|5.027001||Viu
+case_104_SBOX32|5.027001||Viu
+case_105_SBOX32|5.027001||Viu
+case_106_SBOX32|5.027001||Viu
+case_107_SBOX32|5.027001||Viu
+case_108_SBOX32|5.027001||Viu
+case_109_SBOX32|5.027001||Viu
+case_10_SBOX32|5.027001||Viu
+case_110_SBOX32|5.027001||Viu
+case_111_SBOX32|5.027001||Viu
+case_112_SBOX32|5.027001||Viu
+case_113_SBOX32|5.027001||Viu
+case_114_SBOX32|5.027001||Viu
+case_115_SBOX32|5.027001||Viu
+case_116_SBOX32|5.027001||Viu
+case_117_SBOX32|5.027001||Viu
+case_118_SBOX32|5.027001||Viu
+case_119_SBOX32|5.027001||Viu
+case_11_SBOX32|5.027001||Viu
+case_120_SBOX32|5.027001||Viu
+case_121_SBOX32|5.027001||Viu
+case_122_SBOX32|5.027001||Viu
+case_123_SBOX32|5.027001||Viu
+case_124_SBOX32|5.027001||Viu
+case_125_SBOX32|5.027001||Viu
+case_126_SBOX32|5.027001||Viu
+case_127_SBOX32|5.027001||Viu
+case_128_SBOX32|5.027001||Viu
+case_129_SBOX32|5.027001||Viu
+case_12_SBOX32|5.027001||Viu
+case_130_SBOX32|5.027001||Viu
+case_131_SBOX32|5.027001||Viu
+case_132_SBOX32|5.027001||Viu
+case_133_SBOX32|5.027001||Viu
+case_134_SBOX32|5.027001||Viu
+case_135_SBOX32|5.027001||Viu
+case_136_SBOX32|5.027001||Viu
+case_137_SBOX32|5.027001||Viu
+case_138_SBOX32|5.027001||Viu
+case_139_SBOX32|5.027001||Viu
+case_13_SBOX32|5.027001||Viu
+case_140_SBOX32|5.027001||Viu
+case_141_SBOX32|5.027001||Viu
+case_142_SBOX32|5.027001||Viu
+case_143_SBOX32|5.027001||Viu
+case_144_SBOX32|5.027001||Viu
+case_145_SBOX32|5.027001||Viu
+case_146_SBOX32|5.027001||Viu
+case_147_SBOX32|5.027001||Viu
+case_148_SBOX32|5.027001||Viu
+case_149_SBOX32|5.027001||Viu
+case_14_SBOX32|5.027001||Viu
+case_150_SBOX32|5.027001||Viu
+case_151_SBOX32|5.027001||Viu
+case_152_SBOX32|5.027001||Viu
+case_153_SBOX32|5.027001||Viu
+case_154_SBOX32|5.027001||Viu
+case_155_SBOX32|5.027001||Viu
+case_156_SBOX32|5.027001||Viu
+case_157_SBOX32|5.027001||Viu
+case_158_SBOX32|5.027001||Viu
+case_159_SBOX32|5.027001||Viu
+case_15_SBOX32|5.027001||Viu
+case_160_SBOX32|5.027001||Viu
+case_161_SBOX32|5.027001||Viu
+case_162_SBOX32|5.027001||Viu
+case_163_SBOX32|5.027001||Viu
+case_164_SBOX32|5.027001||Viu
+case_165_SBOX32|5.027001||Viu
+case_166_SBOX32|5.027001||Viu
+case_167_SBOX32|5.027001||Viu
+case_168_SBOX32|5.027001||Viu
+case_169_SBOX32|5.027001||Viu
+case_16_SBOX32|5.027001||Viu
+case_170_SBOX32|5.027001||Viu
+case_171_SBOX32|5.027001||Viu
+case_172_SBOX32|5.027001||Viu
+case_173_SBOX32|5.027001||Viu
+case_174_SBOX32|5.027001||Viu
+case_175_SBOX32|5.027001||Viu
+case_176_SBOX32|5.027001||Viu
+case_177_SBOX32|5.027001||Viu
+case_178_SBOX32|5.027001||Viu
+case_179_SBOX32|5.027001||Viu
+case_17_SBOX32|5.027001||Viu
+case_180_SBOX32|5.027001||Viu
+case_181_SBOX32|5.027001||Viu
+case_182_SBOX32|5.027001||Viu
+case_183_SBOX32|5.027001||Viu
+case_184_SBOX32|5.027001||Viu
+case_185_SBOX32|5.027001||Viu
+case_186_SBOX32|5.027001||Viu
+case_187_SBOX32|5.027001||Viu
+case_188_SBOX32|5.027001||Viu
+case_189_SBOX32|5.027001||Viu
+case_18_SBOX32|5.027001||Viu
+case_190_SBOX32|5.027001||Viu
+case_191_SBOX32|5.027001||Viu
+case_192_SBOX32|5.027001||Viu
+case_193_SBOX32|5.027001||Viu
+case_194_SBOX32|5.027001||Viu
+case_195_SBOX32|5.027001||Viu
+case_196_SBOX32|5.027001||Viu
+case_197_SBOX32|5.027001||Viu
+case_198_SBOX32|5.027001||Viu
+case_199_SBOX32|5.027001||Viu
+case_19_SBOX32|5.027001||Viu
+case_1_SBOX32|5.027001||Viu
+case_200_SBOX32|5.027001||Viu
+case_201_SBOX32|5.027001||Viu
+case_202_SBOX32|5.027001||Viu
+case_203_SBOX32|5.027001||Viu
+case_204_SBOX32|5.027001||Viu
+case_205_SBOX32|5.027001||Viu
+case_206_SBOX32|5.027001||Viu
+case_207_SBOX32|5.027001||Viu
+case_208_SBOX32|5.027001||Viu
+case_209_SBOX32|5.027001||Viu
+case_20_SBOX32|5.027001||Viu
+case_210_SBOX32|5.027001||Viu
+case_211_SBOX32|5.027001||Viu
+case_212_SBOX32|5.027001||Viu
+case_213_SBOX32|5.027001||Viu
+case_214_SBOX32|5.027001||Viu
+case_215_SBOX32|5.027001||Viu
+case_216_SBOX32|5.027001||Viu
+case_217_SBOX32|5.027001||Viu
+case_218_SBOX32|5.027001||Viu
+case_219_SBOX32|5.027001||Viu
+case_21_SBOX32|5.027001||Viu
+case_220_SBOX32|5.027001||Viu
+case_221_SBOX32|5.027001||Viu
+case_222_SBOX32|5.027001||Viu
+case_223_SBOX32|5.027001||Viu
+case_224_SBOX32|5.027001||Viu
+case_225_SBOX32|5.027001||Viu
+case_226_SBOX32|5.027001||Viu
+case_227_SBOX32|5.027001||Viu
+case_228_SBOX32|5.027001||Viu
+case_229_SBOX32|5.027001||Viu
+case_22_SBOX32|5.027001||Viu
+case_230_SBOX32|5.027001||Viu
+case_231_SBOX32|5.027001||Viu
+case_232_SBOX32|5.027001||Viu
+case_233_SBOX32|5.027001||Viu
+case_234_SBOX32|5.027001||Viu
+case_235_SBOX32|5.027001||Viu
+case_236_SBOX32|5.027001||Viu
+case_237_SBOX32|5.027001||Viu
+case_238_SBOX32|5.027001||Viu
+case_239_SBOX32|5.027001||Viu
+case_23_SBOX32|5.027001||Viu
+case_240_SBOX32|5.027001||Viu
+case_241_SBOX32|5.027001||Viu
+case_242_SBOX32|5.027001||Viu
+case_243_SBOX32|5.027001||Viu
+case_244_SBOX32|5.027001||Viu
+case_245_SBOX32|5.027001||Viu
+case_246_SBOX32|5.027001||Viu
+case_247_SBOX32|5.027001||Viu
+case_248_SBOX32|5.027001||Viu
+case_249_SBOX32|5.027001||Viu
+case_24_SBOX32|5.027001||Viu
+case_250_SBOX32|5.027001||Viu
+case_251_SBOX32|5.027001||Viu
+case_252_SBOX32|5.027001||Viu
+case_253_SBOX32|5.027001||Viu
+case_254_SBOX32|5.027001||Viu
+case_255_SBOX32|5.027001||Viu
+case_256_SBOX32|5.027001||Viu
+case_25_SBOX32|5.027001||Viu
+case_26_SBOX32|5.027001||Viu
+case_27_SBOX32|5.027001||Viu
+case_28_SBOX32|5.027001||Viu
+case_29_SBOX32|5.027001||Viu
+case_2_SBOX32|5.027001||Viu
+case_30_SBOX32|5.027001||Viu
+case_31_SBOX32|5.027001||Viu
+case_32_SBOX32|5.027001||Viu
+case_33_SBOX32|5.027001||Viu
+case_34_SBOX32|5.027001||Viu
+case_35_SBOX32|5.027001||Viu
+case_36_SBOX32|5.027001||Viu
+case_37_SBOX32|5.027001||Viu
+case_38_SBOX32|5.027001||Viu
+case_39_SBOX32|5.027001||Viu
+case_3_SBOX32|5.027001||Viu
+case_40_SBOX32|5.027001||Viu
+case_41_SBOX32|5.027001||Viu
+case_42_SBOX32|5.027001||Viu
+case_43_SBOX32|5.027001||Viu
+case_44_SBOX32|5.027001||Viu
+case_45_SBOX32|5.027001||Viu
+case_46_SBOX32|5.027001||Viu
+case_47_SBOX32|5.027001||Viu
+case_48_SBOX32|5.027001||Viu
+case_49_SBOX32|5.027001||Viu
+case_4_SBOX32|5.027001||Viu
+case_50_SBOX32|5.027001||Viu
+case_51_SBOX32|5.027001||Viu
+case_52_SBOX32|5.027001||Viu
+case_53_SBOX32|5.027001||Viu
+case_54_SBOX32|5.027001||Viu
+case_55_SBOX32|5.027001||Viu
+case_56_SBOX32|5.027001||Viu
+case_57_SBOX32|5.027001||Viu
+case_58_SBOX32|5.027001||Viu
+case_59_SBOX32|5.027001||Viu
+case_5_SBOX32|5.027001||Viu
+case_60_SBOX32|5.027001||Viu
+case_61_SBOX32|5.027001||Viu
+case_62_SBOX32|5.027001||Viu
+case_63_SBOX32|5.027001||Viu
+case_64_SBOX32|5.027001||Viu
+case_65_SBOX32|5.027001||Viu
+case_66_SBOX32|5.027001||Viu
+case_67_SBOX32|5.027001||Viu
+case_68_SBOX32|5.027001||Viu
+case_69_SBOX32|5.027001||Viu
+case_6_SBOX32|5.027001||Viu
+case_70_SBOX32|5.027001||Viu
+case_71_SBOX32|5.027001||Viu
+case_72_SBOX32|5.027001||Viu
+case_73_SBOX32|5.027001||Viu
+case_74_SBOX32|5.027001||Viu
+case_75_SBOX32|5.027001||Viu
+case_76_SBOX32|5.027001||Viu
+case_77_SBOX32|5.027001||Viu
+case_78_SBOX32|5.027001||Viu
+case_79_SBOX32|5.027001||Viu
+case_7_SBOX32|5.027001||Viu
+case_80_SBOX32|5.027001||Viu
+case_81_SBOX32|5.027001||Viu
+case_82_SBOX32|5.027001||Viu
+case_83_SBOX32|5.027001||Viu
+case_84_SBOX32|5.027001||Viu
+case_85_SBOX32|5.027001||Viu
+case_86_SBOX32|5.027001||Viu
+case_87_SBOX32|5.027001||Viu
+case_88_SBOX32|5.027001||Viu
+case_89_SBOX32|5.027001||Viu
+case_8_SBOX32|5.027001||Viu
+case_90_SBOX32|5.027001||Viu
+case_91_SBOX32|5.027001||Viu
+case_92_SBOX32|5.027001||Viu
+case_93_SBOX32|5.027001||Viu
+case_94_SBOX32|5.027001||Viu
+case_95_SBOX32|5.027001||Viu
+case_96_SBOX32|5.027001||Viu
+case_97_SBOX32|5.027001||Viu
+case_98_SBOX32|5.027001||Viu
+case_99_SBOX32|5.027001||Viu
+case_9_SBOX32|5.027001||Viu
+CASE_STD_PMMOD_FLAGS_PARSE_SET|5.009005||Viu
+CASTFLAGS|5.003007|5.003007|Vn
+cast_i32|5.006000||cVnu
+cast_iv|5.006000||cVnu
+CASTNEGFLOAT|5.003007|5.003007|Vn
+cast_ulong|5.003007||cVnu
+cast_uv|5.006000||cVnu
+CAT2|5.003007|5.003007|Vn
+CATCH_GET|5.004000||Viu
+CATCH_SET|5.004000||Viu
+category_name|5.027008||Vniu
+cBINOP|5.003007||Viu
+cBINOPo|5.004005||Viu
+cBINOPx|5.006000||Viu
+cBOOL|5.013000|5.003007|p
+cCOP|5.003007||Viu
+cCOPo|5.004005||Viu
+cCOPx|5.006000||Viu
+C_FAC_POSIX|5.009003||Viu
+cGVOP_gv|5.006000||Viu
+cGVOPo_gv|5.006000||Viu
+cGVOPx_gv|5.006000||Viu
+change_engine_size|5.029004||Viu
+CHANGE_MULTICALL_FLAGS|5.018000||Viu
+CHARBITS|5.011002|5.011002|Vn
+CHARSET_PAT_MODS|5.013010||Viu
+chdir|5.005000||Viu
+checkcomma|5.003007||Viu
+check_end_shift|5.009005||Viu
+check_locale_boundary_crossing|5.015006||Viu
+CHECK_MALLOC_TAINT|5.008001||Viu
+CHECK_MALLOC_TOO_LATE_FOR|5.008001||Viu
+check_offset_max|5.005000||Viu
+check_offset_min|5.005000||Viu
+check_substr|5.005000||Viu
+check_type_and_open|5.009003||Viu
+check_uni|5.003007||Viu
+check_utf8|5.008000||Viu
+check_utf8_print|5.013009||Viu
+child_offset_bits|5.009003||Viu
+chmod|5.005000||Viu
+chsize|5.005000||Viu
+ckDEAD|5.006000||Viu
+ck_entersub_args_core|||iu
+ck_entersub_args_list|5.013006|5.013006|
+ck_entersub_args_proto|5.013006|5.013006|
+ck_entersub_args_proto_or_list|5.013006|5.013006|
+ckWARN2|5.006000|5.003007|p
+ckWARN2_d|5.006000|5.003007|p
+ckWARN3|5.007003|5.003007|p
+ckWARN3_d|5.007003|5.003007|p
+ckWARN4|5.007003|5.003007|p
+ckWARN4_d|5.007003|5.003007|p
+ckWARN|5.006000|5.003007|p
+ckwarn_common|5.011001||Viu
+ckwarn|||cu
+ckWARN_d|5.006000|5.003007|p
+ckwarn_d|||cu
+ck_warner|5.011001|5.004000|pv
+ck_warner_d|5.011001|5.004000|pv
+CLANG_DIAG_IGNORE|5.023006||Viu
+CLANG_DIAG_IGNORE_DECL|5.027007||Viu
+CLANG_DIAG_IGNORE_STMT|5.027007||Viu
+CLANG_DIAG_PRAGMA|5.023006||Viu
+CLANG_DIAG_RESTORE|5.023006||Viu
+CLANG_DIAG_RESTORE_DECL|5.027007||Viu
+CLANG_DIAG_RESTORE_STMT|5.027007||Viu
+CLASS||5.003007|
+CLEAR_ARGARRAY|5.006000||Viu
+clear_defarray|5.023008|5.023008|u
+clearerr|5.003007||Viu
+CLEAR_ERRSV|5.025007|5.025007|
+CLEARFEATUREBITS|5.031006||Viu
+clear_placeholders|5.009004||xViu
+clear_special_blocks|5.021003||Viu
+cLISTOP|5.003007||Viu
+cLISTOPo|5.004005||Viu
+cLISTOPx|5.006000||Viu
+cLOGOP|5.003007||Viu
+cLOGOPo|5.004005||Viu
+cLOGOPx|5.006000||Viu
+CLONEf_CLONE_HOST|5.007002||Viu
+CLONEf_COPY_STACKS|5.007001||Viu
+CLONEf_JOIN_IN|5.008001||Viu
+CLONEf_KEEP_PTR_TABLE|5.007001||Viu
+clone_params_del|5.013002|5.013002|nu
+clone_params_new|5.013002|5.013002|nu
+cLOOP|5.003007||Viu
+cLOOPo|5.004005||Viu
+cLOOPx|5.006000||Viu
+CLOSE|5.003007||Viu
+close|5.005000||Viu
+closedir|5.005000||Viu
+closest_cop|5.007002||Viu
+CLOSE_t8_p8|5.033003||Viu
+CLOSE_t8_pb|5.033003||Viu
+CLOSE_tb_p8|5.033003||Viu
+CLOSE_tb_pb|5.033003||Viu
+CLUMP_2IV|5.006000||Viu
+CLUMP_2UV|5.006000||Viu
+CLUMP|5.006000||Viu
+CLUMP_t8_p8|5.033003||Viu
+CLUMP_t8_pb|5.033003||Viu
+CLUMP_tb_p8|5.033003||Viu
+CLUMP_tb_pb|5.033003||Viu
+cMETHOPx|5.021005||Viu
+cMETHOPx_meth|5.021005||Viu
+cMETHOPx_rclass|5.021007||Viu
+cmpchain_extend|5.031011||Viu
+cmpchain_finish|5.031011||Viu
+cmpchain_start|5.031011||Viu
+cmp_desc|5.031011||Viu
+cmp_locale_desc|5.031011||Viu
+cntrl_to_mnemonic|5.021004||cVniu
+CODESET|5.027010||Viu
+COMBINING_DOT_ABOVE_UTF8|5.029008||Viu
+COMBINING_GRAVE_ACCENT_UTF8|5.017004||Viu
+COMMIT|5.009005||Viu
+COMMIT_next|5.009005||Viu
+COMMIT_next_fail|5.009005||Viu
+COMMIT_next_fail_t8_p8|5.033003||Viu
+COMMIT_next_fail_t8_pb|5.033003||Viu
+COMMIT_next_fail_tb_p8|5.033003||Viu
+COMMIT_next_fail_tb_pb|5.033003||Viu
+COMMIT_next_t8_p8|5.033003||Viu
+COMMIT_next_t8_pb|5.033003||Viu
+COMMIT_next_tb_p8|5.033003||Viu
+COMMIT_next_tb_pb|5.033003||Viu
+COMMIT_t8_p8|5.033003||Viu
+COMMIT_t8_pb|5.033003||Viu
+COMMIT_tb_p8|5.033003||Viu
+COMMIT_tb_pb|5.033003||Viu
+compile_wildcard|5.031010||Viu
+compute_EXACTish|5.017003||Vniu
+COND_BROADCAST|5.005000||Viu
+COND_DESTROY|5.005000||Viu
+COND_INIT|5.005000||Viu
+COND_SIGNAL|5.005000||Viu
+COND_WAIT|5.005000||Viu
+connect|5.005000||Viu
+construct_ahocorasick_from_trie|5.021001||Viu
+CONTINUE_PAT_MOD|5.009005||Viu
+cop_fetch_label|5.015001|5.015001|x
+CopFILE|5.006000|5.003007|p
+CopFILEAV|5.006000|5.003007|p
+CopFILEAVx|5.009003||Viu
+CopFILE_free|5.007003||Viu
+CopFILEGV|5.006000|5.003007|p
+CopFILEGV_set|5.006000|5.003007|p
+CopFILE_set|5.006000|5.003007|p
+CopFILE_setn|5.009005||Viu
+CopFILESV|5.006000|5.003007|p
+cop_free|5.006000||Viu
+cophh_2hv|5.013007|5.013007|x
+cophh_copy|5.013007|5.013007|x
+cophh_delete_pv|5.013007|5.013007|x
+cophh_delete_pvn|5.013007|5.013007|x
+cophh_delete_pvs|5.013007|5.013007|x
+cophh_delete_sv|5.013007|5.013007|x
+COPHH_EXISTS|5.033008||Viu
+cophh_exists_pv|5.033008|5.033008|x
+cophh_exists_pvn|5.033008|5.033008|x
+cophh_exists_pvs|5.033008|5.033008|x
+cophh_exists_sv|5.033008|5.033008|x
+cophh_fetch_pv|5.013007|5.013007|x
+cophh_fetch_pvn|5.013007|5.013007|x
+cophh_fetch_pvs|5.013007|5.013007|x
+cophh_fetch_sv|5.013007|5.013007|x
+cophh_free|5.013007|5.013007|x
+COPHH_KEY_UTF8|5.013007|5.013007|
+cophh_new_empty|5.013007|5.013007|x
+cophh_store_pv|5.013007|5.013007|x
+cophh_store_pvn|5.013007|5.013007|x
+cophh_store_pvs|5.013007|5.013007|x
+cophh_store_sv|5.013007|5.013007|x
+CopHINTHASH_get|5.013007||Viu
+CopHINTHASH_set|5.013007||Viu
+cop_hints_2hv|5.013007|5.013007|
+cop_hints_exists_pv|5.033008|5.033008|
+cop_hints_exists_pvn|5.033008|5.033008|
+cop_hints_exists_pvs|5.033008|5.033008|
+cop_hints_exists_sv|5.033008|5.033008|
+cop_hints_fetch_pv|5.013007|5.013007|
+cop_hints_fetch_pvn|5.013007|5.013007|
+cop_hints_fetch_pvs|5.013007|5.013007|
+cop_hints_fetch_sv|5.013007|5.013007|
+CopHINTS_get|5.009004||Viu
+CopHINTS_set|5.009004||Viu
+CopLABEL|5.009005|5.009005|
+CopLABEL_alloc|5.009005||Viu
+CopLABEL_len|5.016000|5.016000|
+CopLABEL_len_flags|5.016000|5.016000|
+CopLINE|5.006000|5.006000|
+CopLINE_dec|5.006000||Viu
+CopLINE_inc|5.006000||Viu
+CopLINE_set|5.006000||Viu
+COP_SEQMAX_INC|5.021006||Viu
+COP_SEQ_RANGE_HIGH|5.009005||Viu
+COP_SEQ_RANGE_LOW|5.009005||Viu
+CopSTASH|5.006000|5.003007|p
+CopSTASH_eq|5.006000|5.003007|p
+CopSTASH_ne|5.006000||Viu
+CopSTASHPV|5.006000|5.003007|p
+CopSTASHPV_set|5.017001|5.017001|p
+CopSTASH_set|5.006000|5.003007|p
+cop_store_label|5.015001|5.015001|x
+Copy|5.003007|5.003007|
+CopyD|5.009002|5.003007|p
+core_prototype|5.015002||Vi
+coresub_op|5.015003||Viu
+CowREFCNT|5.017007||Viu
+cPADOP|5.006000||Viu
+cPADOPo|5.006000||Viu
+cPADOPx|5.006000||Viu
+CPERLarg|5.005000||Viu
+CPERLscope|5.005000|5.003007|pdV
+cPMOP|5.003007||Viu
+cPMOPo|5.004005||Viu
+cPMOPx|5.006000||Viu
+CPPLAST|5.006000|5.006000|Vn
+CPPMINUS|5.003007|5.003007|Vn
+CPPRUN|5.006000|5.006000|Vn
+CPPSTDIN|5.003007|5.003007|Vn
+cPVOP|5.003007||Viu
+cPVOPo|5.004005||Viu
+cPVOPx|5.006000||Viu
+create_eval_scope|5.009004||xViu
+CR_NATIVE|5.019004||Viu
+CRNCYSTR|5.027010||Viu
+croak|5.006000|5.003007|v
+croak_caller|5.025004||vVniu
+croak_memory_wrap|5.019003||pcVnu
+croak_nocontext|5.006000||pvVn
+croak_no_mem|5.017006||Vniu
+croak_no_modify|5.013003|5.003007|pn
+croak_popstack|5.017008||cVniu
+croak_sv|5.013001|5.003007|p
+croak_xs_usage|5.010001|5.003007|pn
+cr_textfilter|5.006000||Viu
+crypt|5.009000||Viu
+CRYPT_R_PROTO|5.008000|5.008000|Vn
+CSH|5.003007|5.003007|Vn
+csighandler1|5.031007||cVnu
+csighandler3|5.031007||cVnu
+csighandler|5.008001||cVnu
+cSVOP|5.003007||Viu
+cSVOPo|5.004005||Viu
+cSVOPo_sv|5.006000||Viu
+cSVOP_sv|5.006000||Viu
+cSVOPx|5.006000||Viu
+cSVOPx_sv|5.006000||Viu
+cSVOPx_svp|5.006000||Viu
+ctermid|5.009000||Viu
+CTERMID_R_PROTO|5.008000|5.008000|Vn
+ctime|5.009000||Viu
+CTIME_R_PROTO|5.008000|5.008000|Vn
+Ctl|5.003007||Viu
+CTYPE256|5.003007||Viu
+cUNOP|5.003007||Viu
+cUNOP_AUX|5.021007||Viu
+cUNOP_AUXo|5.021007||Viu
+cUNOP_AUXx|5.021007||Viu
+cUNOPo|5.004005||Viu
+cUNOPx|5.006000||Viu
+CURLY|5.003007||Viu
+CURLY_B_max|5.009005||Viu
+CURLY_B_max_fail|5.009005||Viu
+CURLY_B_max_fail_t8_p8|5.033003||Viu
+CURLY_B_max_fail_t8_pb|5.033003||Viu
+CURLY_B_max_fail_tb_p8|5.033003||Viu
+CURLY_B_max_fail_tb_pb|5.033003||Viu
+CURLY_B_max_t8_p8|5.033003||Viu
+CURLY_B_max_t8_pb|5.033003||Viu
+CURLY_B_max_tb_p8|5.033003||Viu
+CURLY_B_max_tb_pb|5.033003||Viu
+CURLY_B_min|5.009005||Viu
+CURLY_B_min_fail|5.009005||Viu
+CURLY_B_min_fail_t8_p8|5.033003||Viu
+CURLY_B_min_fail_t8_pb|5.033003||Viu
+CURLY_B_min_fail_tb_p8|5.033003||Viu
+CURLY_B_min_fail_tb_pb|5.033003||Viu
+CURLY_B_min_t8_p8|5.033003||Viu
+CURLY_B_min_t8_pb|5.033003||Viu
+CURLY_B_min_tb_p8|5.033003||Viu
+CURLY_B_min_tb_pb|5.033003||Viu
+CURLYM|5.005000||Viu
+CURLYM_A|5.009005||Viu
+CURLYM_A_fail|5.009005||Viu
+CURLYM_A_fail_t8_p8|5.033003||Viu
+CURLYM_A_fail_t8_pb|5.033003||Viu
+CURLYM_A_fail_tb_p8|5.033003||Viu
+CURLYM_A_fail_tb_pb|5.033003||Viu
+CURLYM_A_t8_p8|5.033003||Viu
+CURLYM_A_t8_pb|5.033003||Viu
+CURLYM_A_tb_p8|5.033003||Viu
+CURLYM_A_tb_pb|5.033003||Viu
+CURLYM_B|5.009005||Viu
+CURLYM_B_fail|5.009005||Viu
+CURLYM_B_fail_t8_p8|5.033003||Viu
+CURLYM_B_fail_t8_pb|5.033003||Viu
+CURLYM_B_fail_tb_p8|5.033003||Viu
+CURLYM_B_fail_tb_pb|5.033003||Viu
+CURLYM_B_t8_p8|5.033003||Viu
+CURLYM_B_t8_pb|5.033003||Viu
+CURLYM_B_tb_p8|5.033003||Viu
+CURLYM_B_tb_pb|5.033003||Viu
+CURLYM_t8_p8|5.033003||Viu
+CURLYM_t8_pb|5.033003||Viu
+CURLYM_tb_p8|5.033003||Viu
+CURLYM_tb_pb|5.033003||Viu
+CURLYN|5.005000||Viu
+CURLYN_t8_p8|5.033003||Viu
+CURLYN_t8_pb|5.033003||Viu
+CURLYN_tb_p8|5.033003||Viu
+CURLYN_tb_pb|5.033003||Viu
+CURLY_t8_p8|5.033003||Viu
+CURLY_t8_pb|5.033003||Viu
+CURLY_tb_p8|5.033003||Viu
+CURLY_tb_pb|5.033003||Viu
+CURLYX|5.003007||Viu
+CURLYX_end|5.009005||Viu
+CURLYX_end_fail|5.009005||Viu
+CURLYX_end_fail_t8_p8|5.033003||Viu
+CURLYX_end_fail_t8_pb|5.033003||Viu
+CURLYX_end_fail_tb_p8|5.033003||Viu
+CURLYX_end_fail_tb_pb|5.033003||Viu
+CURLYX_end_t8_p8|5.033003||Viu
+CURLYX_end_t8_pb|5.033003||Viu
+CURLYX_end_tb_p8|5.033003||Viu
+CURLYX_end_tb_pb|5.033003||Viu
+CURLYX_t8_p8|5.033003||Viu
+CURLYX_t8_pb|5.033003||Viu
+CURLYX_tb_p8|5.033003||Viu
+CURLYX_tb_pb|5.033003||Viu
+CURRENT_FEATURE_BUNDLE|5.015007||Viu
+CURRENT_HINTS|5.015007||Viu
+current_re_engine|5.017001||cViu
+curse|5.013009||Viu
+custom_op_desc|5.007003|5.007003|d
+custom_op_get_field|5.019006||cViu
+custom_op_name|5.007003|5.007003|d
+custom_op_register|5.013007|5.013007|
+CUTGROUP|5.009005||Viu
+CUTGROUP_next|5.009005||Viu
+CUTGROUP_next_fail|5.009005||Viu
+CUTGROUP_next_fail_t8_p8|5.033003||Viu
+CUTGROUP_next_fail_t8_pb|5.033003||Viu
+CUTGROUP_next_fail_tb_p8|5.033003||Viu
+CUTGROUP_next_fail_tb_pb|5.033003||Viu
+CUTGROUP_next_t8_p8|5.033003||Viu
+CUTGROUP_next_t8_pb|5.033003||Viu
+CUTGROUP_next_tb_p8|5.033003||Viu
+CUTGROUP_next_tb_pb|5.033003||Viu
+CUTGROUP_t8_p8|5.033003||Viu
+CUTGROUP_t8_pb|5.033003||Viu
+CUTGROUP_tb_p8|5.033003||Viu
+CUTGROUP_tb_pb|5.033003||Viu
+CvANON|5.003007||Viu
+CvANONCONST|5.021008||Viu
+CvANONCONST_off|5.021008||Viu
+CvANONCONST_on|5.021008||Viu
+CvANON_off|5.003007||Viu
+CvANON_on|5.003007||Viu
+CvAUTOLOAD|5.015004||Viu
+CvAUTOLOAD_off|5.015004||Viu
+CvAUTOLOAD_on|5.015004||Viu
+cv_ckproto|5.009004||Viu
+cv_ckproto_len_flags|5.015004||xcViu
+cv_clone|5.003007|5.003007|
+CvCLONE|5.003007||Viu
+CvCLONED|5.003007||Viu
+CvCLONED_off|5.003007||Viu
+CvCLONED_on|5.003007||Viu
+cv_clone_into|5.017004||Viu
+CvCLONE_off|5.003007||Viu
+CvCLONE_on|5.003007||Viu
+CvCONST|5.007001||Viu
+CvCONST_off|5.007001||Viu
+CvCONST_on|5.007001||Viu
+cv_const_sv|5.003007|5.003007|n
+cv_const_sv_or_av|5.019003||Vniu
+CvCVGV_RC|5.013003||Viu
+CvCVGV_RC_off|5.013003||Viu
+CvCVGV_RC_on|5.013003||Viu
+CvDEPTH|5.003007|5.003007|nu
+CvDEPTHunsafe|5.021006||Viu
+cv_dump|5.006000||Vi
+CvDYNFILE|5.015002||Viu
+CvDYNFILE_off|5.015002||Viu
+CvDYNFILE_on|5.015002||Viu
+CvEVAL|5.005003||Viu
+CvEVAL_off|5.005003||Viu
+CvEVAL_on|5.005003||Viu
+CVf_ANON|5.003007||Viu
+CVf_ANONCONST|5.021008||Viu
+CVf_AUTOLOAD|5.015004||Viu
+CVf_BUILTIN_ATTRS|5.008000||Viu
+CVf_CLONE|5.003007||Viu
+CVf_CLONED|5.003007||Viu
+CVf_CONST|5.007001||Viu
+CVf_CVGV_RC|5.013003||Viu
+CVf_DYNFILE|5.015002||Viu
+CVf_HASEVAL|5.017002||Viu
+CvFILE|5.006000||Viu
+CvFILEGV|5.003007||Viu
+CvFILE_set_from_cop|5.007002||Viu
+CVf_ISXSUB|5.009004||Viu
+CvFLAGS|5.003007||Viu
+CVf_LEXICAL|5.021004||Viu
+CVf_LVALUE|5.006000||Viu
+CVf_METHOD|5.005000||Viu
+CVf_NAMED|5.017004||Viu
+CVf_NODEBUG|5.004000||Viu
+cv_forget_slab|5.017002||Vi
+CVf_SLABBED|5.017002||Viu
+CVf_UNIQUE|5.004000||Viu
+CVf_WEAKOUTSIDE|5.008001||Viu
+cv_get_call_checker|5.013006|5.013006|
+cv_get_call_checker_flags|5.027003|5.027003|
+CvGV|5.003007|5.003007|
+cvgv_from_hek|||ciu
+cvgv_set|5.013003||cViu
+CvGV_set|5.013003||Viu
+CvHASEVAL|5.017002||Viu
+CvHASEVAL_off|5.017002||Viu
+CvHASEVAL_on|5.017002||Viu
+CvHASGV|5.021004||Viu
+CvHSCXT|5.021006||Viu
+CvISXSUB|5.009004||Viu
+CvISXSUB_off|5.009004||Viu
+CvISXSUB_on|5.009004||Viu
+CvLEXICAL|5.021004||Viu
+CvLEXICAL_off|5.021004||Viu
+CvLEXICAL_on|5.021004||Viu
+CvLVALUE|5.006000||Viu
+CvLVALUE_off|5.006000||Viu
+CvLVALUE_on|5.006000||Viu
+CvMETHOD|5.005000||Viu
+CvMETHOD_off|5.005000||Viu
+CvMETHOD_on|5.005000||Viu
+cv_name|5.021005|5.021005|
+CvNAMED|5.017004||Viu
+CvNAMED_off|5.017004||Viu
+CvNAMED_on|5.017004||Viu
+CvNAME_HEK_set|5.017004||Viu
+CV_NAME_NOTQUAL|5.021005|5.021005|
+CvNODEBUG|5.004000||Viu
+CvNODEBUG_off|5.004000||Viu
+CvNODEBUG_on|5.004000||Viu
+CvOUTSIDE|5.003007||Viu
+CvOUTSIDE_SEQ|5.008001||Viu
+CvPADLIST|5.008001|5.008001|x
+CvPADLIST_set|5.021006||Viu
+CvPROTO|5.015004||Viu
+CvPROTOLEN|5.015004||Viu
+CvROOT|5.003007||Viu
+cv_set_call_checker|5.013006|5.013006|
+cv_set_call_checker_flags|5.021004|5.021004|
+CvSLABBED|5.017002||Viu
+CvSLABBED_off|5.017002||Viu
+CvSLABBED_on|5.017002||Viu
+CvSPECIAL|5.005003||Viu
+CvSPECIAL_off|5.005003||Viu
+CvSPECIAL_on|5.005003||Viu
+CvSTART|5.003007||Viu
+CvSTASH|5.003007|5.003007|
+cvstash_set|5.013007||cViu
+CvSTASH_set|5.013007||Viu
+cv_undef|5.003007|5.003007|
+cv_undef_flags|5.021004||Viu
+CV_UNDEF_KEEP_NAME|5.021004||Viu
+CvUNIQUE|5.004000||Viu
+CvUNIQUE_off|5.004000||Viu
+CvUNIQUE_on|5.004000||Viu
+CvWEAKOUTSIDE|5.008001||Vi
+CvWEAKOUTSIDE_off|5.008001||Viu
+CvWEAKOUTSIDE_on|5.008001||Viu
+CvXSUB|5.003007||Viu
+CvXSUBANY|5.003007||Viu
+CX_CUR|5.023008||Viu
+CX_CURPAD_SAVE|5.008001||Vi
+CX_CURPAD_SV|5.008001||Vi
+CX_DEBUG|5.023008||Viu
+cx_dump|5.003007||cVu
+cx_dup|5.006000||cVu
+CxEVALBLOCK|5.033007||Viu
+CxEVAL_TXT_REFCNTED|5.025007||Viu
+CxFOREACH|5.009003||Viu
+CxHASARGS|5.010001||Viu
+cxinc|5.003007||cVu
+CXINC|5.003007||Viu
+CxITERVAR|5.006000||Viu
+CxLABEL|5.010001||Viu
+CxLABEL_len|5.016000||Viu
+CxLABEL_len_flags|5.016000||Viu
+CX_LEAVE_SCOPE|5.023008||Viu
+CxLVAL|5.010001||Viu
+CxMULTICALL|5.009003||Viu
+CxOLD_IN_EVAL|5.010001||Viu
+CxOLD_OP_TYPE|5.010001||Viu
+CxONCE|5.010001||Viu
+CxPADLOOP|5.006000||Viu
+CXp_EVALBLOCK|5.033007||Viu
+CXp_FOR_DEF|5.027008||Viu
+CXp_FOR_GV|5.023008||Viu
+CXp_FOR_LVREF|5.021005||Viu
+CXp_FOR_PAD|5.023008||Viu
+CXp_HASARGS|5.011000||Viu
+CXp_MULTICALL|5.009003||Viu
+CXp_ONCE|5.011000||Viu
+CX_POP|5.023008||Viu
+cx_popblock|5.023008||xcVu
+cx_popeval|5.023008||xcVu
+cx_popformat|5.023008||xcVu
+cx_popgiven|5.027008||xcVu
+cx_poploop|5.023008||xcVu
+CX_POP_SAVEARRAY|5.023008||Viu
+cx_popsub|5.023008||xcVu
+cx_popsub_args|5.023008||xcVu
+cx_popsub_common|5.023008||xcVu
+CX_POPSUBST|5.023008||Viu
+cx_popwhen|5.027008||xcVu
+CXp_REAL|5.005003||Viu
+CXp_SUB_RE|5.018000||Viu
+CXp_SUB_RE_FAKE|5.018000||Viu
+CXp_TRY|5.033007||Viu
+CXp_TRYBLOCK|5.006000||Viu
+cx_pushblock|5.023008||xcVu
+cx_pusheval|5.023008||xcVu
+cx_pushformat|5.023008||xcVu
+cx_pushgiven|5.027008||xcVu
+cx_pushloop_for|5.023008||xcVu
+cx_pushloop_plain|5.023008||xcVu
+cx_pushsub|5.023008||xcVu
+CX_PUSHSUB_GET_LVALUE_MASK|5.023008||Viu
+CX_PUSHSUBST|5.023008||Viu
+cx_pushtry|5.033007||xcVu
+cx_pushwhen|5.027008||xcVu
+CxREALEVAL|5.005003||Viu
+cxstack|5.005000||Viu
+cxstack_ix|5.005000||Viu
+cxstack_max|5.005000||Viu
+CXt_BLOCK|5.003007||Viu
+CXt_EVAL|5.003007||Viu
+CXt_FORMAT|5.006000||Viu
+CXt_GIVEN|5.027008||Viu
+CXt_LOOP_ARY|5.023008||Viu
+CXt_LOOP_LAZYIV|5.011000||Viu
+CXt_LOOP_LAZYSV|5.011000||Viu
+CXt_LOOP_LIST|5.023008||Viu
+CXt_LOOP_PLAIN|5.011000||Viu
+CXt_NULL|5.003007||Viu
+cx_topblock|5.023008||xcVu
+CxTRY|5.033007||Viu
+CxTRYBLOCK|5.006000||Viu
+CXt_SUB|5.003007||Viu
+CXt_SUBST|5.003007||Viu
+CXt_WHEN|5.027008||Viu
+CxTYPE|5.005003||Viu
+cx_type|5.009005||Viu
+CxTYPE_is_LOOP|5.011000||Viu
+CXTYPEMASK|5.005003||Viu
+dATARGET|5.003007||Viu
+dAX|5.007002|5.003007|p
+dAXMARK|5.009003|5.003007|p
+DAY_1|5.027010||Viu
+DAY_2|5.027010||Viu
+DAY_3|5.027010||Viu
+DAY_4|5.027010||Viu
+DAY_5|5.027010||Viu
+DAY_6|5.027010||Viu
+DAY_7|5.027010||Viu
+DB_Hash_t|5.003007|5.003007|Vn
+DBM_ckFilter|5.008001||Viu
+DBM_setFilter|5.008001||Viu
+DB_Prefix_t|5.003007|5.003007|Vn
+DBVARMG_COUNT|5.021005||Viu
+DBVARMG_SIGNAL|5.021005||Viu
+DBVARMG_SINGLE|5.021005||Viu
+DBVARMG_TRACE|5.021005||Viu
+DB_VERSION_MAJOR_CFG|5.007002|5.007002|Vn
+DB_VERSION_MINOR_CFG|5.007002|5.007002|Vn
+DB_VERSION_PATCH_CFG|5.007002|5.007002|Vn
+deb|5.007003|5.007003|vu
+deb_curcv|5.007002||Viu
+deb_nocontext|5.007003|5.007003|vnu
+debop|5.005000|5.005000|u
+debprof|5.005000||Viu
+debprofdump|5.005000|5.005000|u
+debstack|5.007003|5.007003|u
+deb_stack_all|5.008001||Viu
+deb_stack_n|5.008001||Viu
+debstackptrs|5.007003|5.007003|u
+DEBUG|5.003007||Viu
+DEBUG_A|5.009001||Viu
+DEBUG_A_FLAG|5.009001||Viu
+DEBUG_A_TEST|5.009001||Viu
+DEBUG_B|5.011000||Viu
+DEBUG_B_FLAG|5.011000||Viu
+DEBUG_BOTH_FLAGS_TEST|5.033007||Viu
+DEBUG_B_TEST|5.011000||Viu
+DEBUG_BUFFERS_r|5.009005||Viu
+DEBUG_c|5.003007||Viu
+DEBUG_C|5.009000||Viu
+DEBUG_c_FLAG|5.007001||Viu
+DEBUG_C_FLAG|5.009000||Viu
+DEBUG_COMPILE_r|5.009002||Viu
+DEBUG_c_TEST|5.007001||Viu
+DEBUG_C_TEST|5.009000||Viu
+DEBUG_D|5.003007||Viu
+DEBUG_DB_RECURSE_FLAG|5.007001||Viu
+DEBUG_D_FLAG|5.007001||Viu
+DEBUG_D_TEST|5.007001||Viu
+DEBUG_DUMP_PRE_OPTIMIZE_r|5.031004||Viu
+DEBUG_DUMP_r|5.009004||Viu
+DEBUG_EXECUTE_r|5.009002||Viu
+DEBUG_EXTRA_r|5.009004||Viu
+DEBUG_f|5.003007||Viu
+DEBUG_f_FLAG|5.007001||Viu
+DEBUG_FLAGS_r|5.009005||Viu
+DEBUG_f_TEST|5.007001||Viu
+DEBUG_GPOS_r|5.011000||Viu
+DEBUG_i|5.025002||Viu
+DEBUG_i_FLAG|5.025002||Viu
+DEBUG_INTUIT_r|5.009004||Viu
+DEBUG_i_TEST|5.025002||Viu
+DEBUG_J_FLAG|5.007003||Viu
+DEBUG_J_TEST|5.007003||Viu
+DEBUG_l|5.003007||Viu
+DEBUG_L|5.019009||Viu
+DEBUG_l_FLAG|5.007001||Viu
+DEBUG_L_FLAG|5.019009||Viu
+DEBUG_l_TEST|5.007001||Viu
+DEBUG_L_TEST|5.019009||Viu
+DEBUG_Lv|5.023003||Viu
+DEBUG_Lv_TEST|5.023003||Viu
+DEBUG_m|5.003007||Viu
+DEBUG_M|5.027008||Viu
+DEBUG_MASK|5.007001||Viu
+DEBUG_MATCH_r|5.009004||Viu
+DEBUG_m_FLAG|5.007001||Viu
+DEBUG_M_FLAG|5.027008||Viu
+DEBUG_m_TEST|5.007001||Viu
+DEBUG_M_TEST|5.027008||Viu
+DEBUG_o|5.003007||Viu
+DEBUG_OFFSETS_r|5.009002||Viu
+DEBUG_o_FLAG|5.007001||Viu
+DEBUG_OPTIMISE_MORE_r|5.009005||Viu
+DEBUG_OPTIMISE_r|5.009002||Viu
+DEBUG_o_TEST|5.007001||Viu
+DEBUG_P|5.003007||Viu
+DEBUG_p|5.003007||Viu
+DEBUG_PARSE_r|5.009004||Viu
+DEBUG_P_FLAG|5.007001||Viu
+DEBUG_p_FLAG|5.007001||Viu
+DEBUG_POST_STMTS|5.033008||Viu
+DEBUG_PRE_STMTS|5.033008||Viu
+DEBUG_P_TEST|5.007001||Viu
+DEBUG_p_TEST|5.007001||Viu
+DEBUG_Pv|5.013008||Viu
+DEBUG_Pv_TEST|5.013008||Viu
+DEBUG_q|5.009001||Viu
+DEBUG_q_FLAG|5.009001||Viu
+DEBUG_q_TEST|5.009001||Viu
+DEBUG_r|5.003007||Viu
+DEBUG_R|5.007001||Viu
+DEBUG_R_FLAG|5.007001||Viu
+DEBUG_r_FLAG|5.007001||Viu
+DEBUG_R_TEST|5.007001||Viu
+DEBUG_r_TEST|5.007001||Viu
+DEBUG_s|5.003007||Viu
+DEBUG_S|5.017002||Viu
+DEBUG_SBOX32_HASH|5.027001||Viu
+DEBUG_SCOPE|5.008001||Viu
+DEBUG_s_FLAG|5.007001||Viu
+DEBUG_S_FLAG|5.017002||Viu
+DEBUG_STACK_r|5.009005||Viu
+debug_start_match|5.009004||Viu
+DEBUG_STATE_r|5.009004||Viu
+DEBUG_s_TEST|5.007001||Viu
+DEBUG_S_TEST|5.017002||Viu
+DEBUG_t|5.003007||Viu
+DEBUG_T|5.007001||Viu
+DEBUG_TEST_r|5.021005||Viu
+DEBUG_T_FLAG|5.007001||Viu
+DEBUG_t_FLAG|5.007001||Viu
+DEBUG_TOP_FLAG|5.007001||Viu
+DEBUG_TRIE_COMPILE_MORE_r|5.009002||Viu
+DEBUG_TRIE_COMPILE_r|5.009002||Viu
+DEBUG_TRIE_EXECUTE_MORE_r|5.009002||Viu
+DEBUG_TRIE_EXECUTE_r|5.009002||Viu
+DEBUG_TRIE_r|5.009002||Viu
+DEBUG_T_TEST|5.007001||Viu
+DEBUG_t_TEST|5.007001||Viu
+DEBUG_u|5.003007||Viu
+DEBUG_U|5.009005||Viu
+DEBUG_u_FLAG|5.007001||Viu
+DEBUG_U_FLAG|5.009005||Viu
+DEBUG_u_TEST|5.007001||Viu
+DEBUG_U_TEST|5.009005||Viu
+DEBUG_Uv|5.009005||Viu
+DEBUG_Uv_TEST|5.009005||Viu
+DEBUG_v|5.008001||Viu
+DEBUG_v_FLAG|5.008001||Viu
+DEBUG_v_TEST|5.008001||Viu
+DEBUG_X|5.003007||Viu
+DEBUG_x|5.003007||Viu
+DEBUG_X_FLAG|5.007001||Viu
+DEBUG_x_FLAG|5.007001||Viu
+DEBUG_X_TEST|5.007001||Viu
+DEBUG_x_TEST|5.007001||Viu
+DEBUG_Xv|5.008001||Viu
+DEBUG_Xv_TEST|5.008001||Viu
+DEBUG_y|5.031007||Viu
+DEBUG_y_FLAG|5.031007||Viu
+DEBUG_y_TEST|5.031007||Viu
+DEBUG_yv|5.031007||Viu
+DEBUG_yv_TEST|5.031007||Viu
+DEBUG_ZAPHOD32_HASH|5.027001||Viu
+DECLARATION_FOR_LC_NUMERIC_MANIPULATION|5.021010|5.021010|p
+DECLARE_AND_GET_RE_DEBUG_FLAGS|5.031011||Viu
+DECLARE_AND_GET_RE_DEBUG_FLAGS_NON_REGEX|5.031011||Viu
+DEFAULT_INC_EXCLUDES_DOT|5.025011|5.025011|Vn
+DEFAULT_PAT_MOD|5.013006||Viu
+defelem_target|5.019002||Viu
+DEFINE_INC_MACROS|5.027006||Viu
+DEFINEP|5.009005||Viu
+DEFINEP_t8_p8|5.033003||Viu
+DEFINEP_t8_pb|5.033003||Viu
+DEFINEP_tb_p8|5.033003||Viu
+DEFINEP_tb_pb|5.033003||Viu
+DEFSV|5.004005|5.003007|p
+DEFSV_set|5.010001|5.003007|p
+delete_eval_scope|5.009004||xViu
+delimcpy|5.004000|5.004000|n
+delimcpy_no_escape|5.025005||cVni
+DEL_NATIVE|5.017010||Viu
+del_sv|5.005000||Viu
+DEPENDS_PAT_MOD|5.013009||Viu
+DEPENDS_PAT_MODS|5.013009||Viu
+deprecate|5.011001||Viu
+deprecate_disappears_in|5.025009||Viu
+deprecate_fatal_in|5.025009||Viu
+despatch_signals|5.007001||cVu
+destroy_matcher|5.027008||Viu
+DETACH|5.005000||Viu
+dEXT|5.003007||Viu
+dEXTCONST|5.004000||Viu
+D_FMT|5.027010||Viu
+DIE|5.003007||Viu
+die|5.006000|5.003007|v
+die_nocontext|5.006000||vVn
+die_sv|5.013001|5.003007|p
+die_unwind|5.013001||Viu
+Direntry_t|5.003007|5.003007|Vn
+dirp_dup|5.013007|5.013007|u
+dITEMS|5.007002|5.003007|p
+div128|5.005000||Viu
+dJMPENV|5.004000||Viu
+djSP|5.004005||Vi
+dMARK|5.003007|5.003007|
+DM_ARRAY_ISA|5.013002||Viu
+DM_DELAY|5.003007||Viu
+DM_EGID|5.003007||Viu
+DM_EUID|5.003007||Viu
+DM_GID|5.003007||Viu
+DM_RGID|5.003007||Viu
+DM_RUID|5.003007||Viu
+DM_UID|5.003007||Viu
+dMULTICALL|5.009003|5.009003|
+dMY_CXT|5.009000|5.009000|p
+dMY_CXT_INTERP|5.009003||Viu
+dMY_CXT_SV|5.007003|5.003007|pV
+dNOOP|5.006000|5.003007|p
+do_aexec|5.009003||Viu
+do_aexec5|5.006000||Viu
+do_aspawn|5.008000||Vu
+do_binmode|5.004005|5.004005|du
+docatch|5.005000||Vi
+do_chomp|5.003007||Viu
+do_close|5.003007|5.003007|u
+do_delete_local|5.011000||Viu
+do_dump_pad|5.008001||Vi
+do_eof|5.003007||Viu
+does_utf8_overflow|5.025006||Vniu
+doeval_compile|5.023008||Viu
+do_exec3|5.006000||Viu
+do_exec|5.009003||Viu
+dofile|5.005003||Viu
+dofindlabel|5.003007||Viu
+doform|5.005000||Viu
+do_gv_dump|5.006000||cVu
+do_gvgv_dump|5.006000||cVu
+do_hv_dump|5.006000||cVu
+doing_taint|5.008001||cVnu
+DOINIT|5.003007||Viu
+do_ipcctl|5.003007||Viu
+do_ipcget|5.003007||Viu
+do_join|5.003007|5.003007|u
+do_magic_dump|5.006000||cVu
+do_msgrcv|5.003007||Viu
+do_msgsnd|5.003007||Viu
+do_ncmp|5.015001||Viu
+do_oddball|5.006000||Viu
+dooneliner|5.006000||Viu
+do_op_dump|5.006000||cVu
+do_open|5.003007|5.003007|u
+do_open6|5.019010||xViu
+do_open9|5.006000|5.006000|du
+do_openn|5.007001|5.007001|u
+doopen_pm|5.008001||Viu
+do_open_raw|5.019010||xViu
+doparseform|5.005000||Viu
+do_pmop_dump|5.006000||cVu
+dopoptoeval|5.003007||Viu
+dopoptogivenfor|5.027008||Viu
+dopoptolabel|5.005000||Viu
+dopoptoloop|5.005000||Viu
+dopoptosub_at|5.005000||Viu
+dopoptowhen|5.027008||Viu
+do_print|5.003007||Viu
+do_readline|5.003007||Viu
+doref|5.009003|5.009003|u
+dORIGMARK|5.003007|5.003007|
+do_seek|5.003007||Viu
+do_semop|5.003007||Viu
+do_shmio|5.003007||Viu
+DOSISH|5.003007||Viu
+do_smartmatch|5.027008||Viu
+do_spawn|5.008000||Vu
+do_spawn_nowait|5.008000||Vu
+do_sprintf|5.003007|5.003007|u
+do_sv_dump|5.006000||cVu
+do_sysseek|5.004000||Viu
+do_tell|5.003007||Viu
+do_trans|5.003007||Viu
+do_trans_complex|5.006001||Viu
+do_trans_count|5.006001||Viu
+do_trans_count_invmap|5.031006||Viu
+do_trans_invmap|5.031006||Viu
+do_trans_simple|5.006001||Viu
+DOUBLE_BIG_ENDIAN|5.021009||Viu
+DOUBLE_HAS_INF|5.025003|5.025003|Vn
+DOUBLE_HAS_NAN|5.025003|5.025003|Vn
+DOUBLE_HAS_NEGATIVE_ZERO|5.025007|5.025007|Vn
+DOUBLE_HAS_SUBNORMALS|5.025007|5.025007|Vn
+DOUBLEINFBYTES|5.023000|5.023000|Vn
+DOUBLE_IS_CRAY_SINGLE_64_BIT|5.025006|5.025006|Vn
+DOUBLE_IS_IBM_DOUBLE_64_BIT|5.025006|5.025006|Vn
+DOUBLE_IS_IBM_SINGLE_32_BIT|5.025006|5.025006|Vn
+DOUBLE_IS_IEEE_754_128_BIT_BIG_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_128_BIT_LITTLE_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_32_BIT_BIG_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_32_BIT_LITTLE_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_64_BIT_BIG_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_64_BIT_LITTLE_ENDIAN|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_64_BIT_MIXED_ENDIAN_BE_LE|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_754_64_BIT_MIXED_ENDIAN_LE_BE|5.021006|5.021006|Vn
+DOUBLE_IS_IEEE_FORMAT|5.025003||Viu
+DOUBLE_IS_UNKNOWN_FORMAT|5.021006|5.021006|Vn
+DOUBLE_IS_VAX_D_FLOAT|5.025003|5.025003|Vn
+DOUBLE_IS_VAX_F_FLOAT|5.025003|5.025003|Vn
+DOUBLE_IS_VAX_FLOAT|5.025003||Viu
+DOUBLE_IS_VAX_G_FLOAT|5.025003|5.025003|Vn
+DOUBLEKIND|5.021006|5.021006|Vn
+DOUBLE_LITTLE_ENDIAN|5.021009||Viu
+DOUBLEMANTBITS|5.023000|5.023000|Vn
+DOUBLE_MIX_ENDIAN|5.021009||Viu
+DOUBLENANBYTES|5.023000|5.023000|Vn
+DOUBLESIZE|5.005000|5.005000|Vn
+DOUBLE_STYLE_IEEE|5.025007|5.025007|Vn
+DOUBLE_VAX_ENDIAN|5.025003||Viu
+do_uniprop_match|5.031011||cVniu
+dounwind|5.003007|5.003007|u
+DO_UTF8|5.006000|5.006000|
+do_vecget|5.006000||Viu
+do_vecset|5.003007||Viu
+do_vop|5.003007||Viu
+dowantarray|5.003007|5.003007|u
+dPOPiv|5.003007||Viu
+dPOPnv|5.003007||Viu
+dPOPnv_nomg|5.013002||Viu
+dPOPPOPiirl|5.003007||Viu
+dPOPPOPnnrl|5.003007||Viu
+dPOPPOPssrl|5.003007||Viu
+dPOPss|5.003007||Viu
+dPOPTOPiirl|5.003007||Viu
+dPOPTOPiirl_nomg|5.013002||Viu
+dPOPTOPiirl_ul_nomg|5.013002||Viu
+dPOPTOPnnrl|5.003007||Viu
+dPOPTOPnnrl_nomg|5.013002||Viu
+dPOPTOPssrl|5.003007||Viu
+dPOPuv|5.004000||Viu
+dPOPXiirl|5.004000||Viu
+dPOPXiirl_ul_nomg|5.013002||Viu
+dPOPXnnrl|5.004000||Viu
+dPOPXssrl|5.004000||Viu
+DPTR2FPTR|5.009003||Viu
+Drand01|5.006000|5.006000|
+drand48_init_r|||cniu
+drand48_r|||cniu
+DRAND48_R_PROTO|5.008000|5.008000|Vn
+dSAVEDERRNO|5.010001||Vi
+dSAVE_ERRNO|5.010001||Vi
+dSP|5.003007|5.003007|
+dSS_ADD|5.017007||Viu
+dTARG|5.003007||Viu
+dTARGET|5.003007|5.003007|
+dTARGETSTACKED|5.003007||Viu
+D_T_FMT|5.027010||Viu
+dTHR|5.004005|5.003007|p
+dTHX|5.003007|5.003007|p
+dTHXa|5.006000|5.003007|p
+dTHX_DEBUGGING|5.027009||Viu
+dTHXo|5.006000||Viu
+dTHXoa|5.006001|5.003007|p
+dTHXR||5.003007|ponu
+dTHXs|5.007002||Viu
+dTHXx|5.006000||Viu
+dTOPiv|5.003007||Viu
+dTOPnv|5.003007||Viu
+dTOPss|5.003007||Viu
+dTOPuv|5.004000||Viu
+dtrace_probe_call|||ciu
+dtrace_probe_load|||ciu
+dtrace_probe_op|||ciu
+dtrace_probe_phase|||ciu
+dump_all|5.006000|5.006000|
+dump_all_perl|5.011000||Viu
+dump_c_backtrace|5.021001||V
+dump_eval|5.006000|5.006000|u
+dump_exec_pos|5.009004||Viu
+dump_form|5.006000|5.006000|u
+dump_indent|5.006000||vcVu
+dump_mstats|5.003007||Vu
+dump_packsubs|5.006000|5.006000|
+dump_packsubs_perl|5.011000||Viu
+dump_regex_sets_structures|5.025006||Viu
+dump_sub|5.006000|5.006000|u
+dump_sub_perl|5.011000||Viu
+dump_sv_child|5.009003||Viu
+dump_trie|5.009004||Viu
+dump_trie_interim_list|5.009004||Viu
+dump_trie_interim_table|5.009004||Viu
+dumpuntil|5.005000||Viu
+dump_vindent|5.006000||cVu
+dUNDERBAR|5.009002|5.003007|p
+dup2|5.005000||Viu
+dup|5.005000||Viu
+dup_attrlist|5.006000||Viu
+DUP_WARNINGS|5.009004||Viu
+dup_warnings|||ciu
+dVAR|5.009003|5.003007|p
+dXCPT|5.009002|5.003007|p
+dXSARGS|5.003007|5.003007|
+dXSBOOTARGSAPIVERCHK|5.021006||Viu
+dXSBOOTARGSNOVERCHK|5.021006||Viu
+dXSBOOTARGSXSAPIVERCHK|5.021006||Viu
+dXSFUNCTION|5.005000||Viu
+dXSI32|5.003007|5.003007|V
+dXSTARG|5.006000|5.003007|poVnu
+dXSUB_SYS|5.003007||Viu
+edit_distance|5.023008||Vniu
+EIGHT_BIT_UTF8_TO_NATIVE|5.023003||Viu
+ELEMENT_RANGE_MATCHES_INVLIST|5.023002||Viu
+EMBEDMYMALLOC|5.006000||Viu
+emulate_cop_io|||xciu
+emulate_setlocale|5.027009||Vniu
+END|5.003007||Viu
+END_EXTERN_C|5.005000|5.003007|pV
+endgrent|5.009000||Viu
+ENDGRENT_R_HAS_FPTR|5.008000||Viu
+ENDGRENT_R_PROTO|5.008000|5.008000|Vn
+endhostent|5.005000||Viu
+ENDHOSTENT_R_PROTO|5.008000|5.008000|Vn
+ENDLIKE|5.009005||Viu
+ENDLIKE_t8_p8|5.033003||Viu
+ENDLIKE_t8_pb|5.033003||Viu
+ENDLIKE_tb_p8|5.033003||Viu
+ENDLIKE_tb_pb|5.033003||Viu
+endnetent|5.005000||Viu
+ENDNETENT_R_PROTO|5.008000|5.008000|Vn
+endprotoent|5.005000||Viu
+ENDPROTOENT_R_PROTO|5.008000|5.008000|Vn
+endpwent|5.009000||Viu
+ENDPWENT_R_HAS_FPTR|5.008000||Viu
+ENDPWENT_R_PROTO|5.008000|5.008000|Vn
+endservent|5.005000||Viu
+ENDSERVENT_R_PROTO|5.008000|5.008000|Vn
+END_t8_p8|5.033003||Viu
+END_t8_pb|5.033003||Viu
+END_tb_p8|5.033003||Viu
+END_tb_pb|5.033003||Viu
+ENTER|5.003007|5.003007|
+ENTER_with_name|5.011002|5.011002|
+ENV_INIT|5.031011||Viu
+environ|5.003007||Viu
+ENV_LOCALE_LOCK|5.031011||Viu
+ENV_LOCALE_READ_LOCK|5.031011||Viu
+ENV_LOCALE_READ_UNLOCK|5.031011||Viu
+ENV_LOCALE_UNLOCK|5.031011||Viu
+ENV_LOCK|5.031011||Viu
+ENV_READ_LOCK|5.033005||Viu
+ENV_READ_UNLOCK|5.033005||Viu
+ENV_TERM|5.031011||Viu
+ENV_UNLOCK|5.031011||Viu
+EOF|5.003007||Viu
+EOF_NONBLOCK|5.003007|5.003007|Vn
+EOL|5.003007||Viu
+EOL_t8_p8|5.033003||Viu
+EOL_t8_pb|5.033003||Viu
+EOL_tb_p8|5.033003||Viu
+EOL_tb_pb|5.033003||Viu
+EOS|5.005000||Viu
+EOS_t8_p8|5.033003||Viu
+EOS_t8_pb|5.033003||Viu
+EOS_tb_p8|5.033003||Viu
+EOS_tb_pb|5.033003||Viu
+ERA|5.027010||Viu
+ERA_D_FMT|5.027010||Viu
+ERA_D_T_FMT|5.027010||Viu
+ERA_T_FMT|5.027010||Viu
+ERRSV|5.004005|5.003007|p
+ESC_NATIVE|5.021004||Viu
+EVAL|5.005000||Viu
+EVAL_B|5.025010||Viu
+EVAL_B_fail|5.025010||Viu
+EVAL_B_fail_t8_p8|5.033003||Viu
+EVAL_B_fail_t8_pb|5.033003||Viu
+EVAL_B_fail_tb_p8|5.033003||Viu
+EVAL_B_fail_tb_pb|5.033003||Viu
+EVAL_B_t8_p8|5.033003||Viu
+EVAL_B_t8_pb|5.033003||Viu
+EVAL_B_tb_p8|5.033003||Viu
+EVAL_B_tb_pb|5.033003||Viu
+EVAL_INEVAL|5.006000||Viu
+EVAL_INREQUIRE|5.007001||Viu
+EVAL_KEEPERR|5.006000||Viu
+EVAL_NULL|5.006000||Viu
+EVAL_postponed_AB|5.025010||Viu
+EVAL_postponed_AB_fail|5.025010||Viu
+EVAL_postponed_AB_fail_t8_p8|5.033003||Viu
+EVAL_postponed_AB_fail_t8_pb|5.033003||Viu
+EVAL_postponed_AB_fail_tb_p8|5.033003||Viu
+EVAL_postponed_AB_fail_tb_pb|5.033003||Viu
+EVAL_postponed_AB_t8_p8|5.033003||Viu
+EVAL_postponed_AB_t8_pb|5.033003||Viu
+EVAL_postponed_AB_tb_p8|5.033003||Viu
+EVAL_postponed_AB_tb_pb|5.033003||Viu
+eval_pv|5.006000|5.003007|p
+EVAL_RE_REPARSING|5.017011||Viu
+eval_sv|5.006000|5.003007|p
+EVAL_t8_p8|5.033003||Viu
+EVAL_t8_pb|5.033003||Viu
+EVAL_tb_p8|5.033003||Viu
+EVAL_tb_pb|5.033003||Viu
+EVAL_WARNONLY|5.006000||Viu
+EXACT|5.004000||Viu
+EXACTF|5.004000||Viu
+EXACTFAA|5.027009||Viu
+EXACTFAA_NO_TRIE|5.027009||Viu
+EXACTFAA_NO_TRIE_t8_p8|5.033003||Viu
+EXACTFAA_NO_TRIE_t8_pb|5.033003||Viu
+EXACTFAA_NO_TRIE_tb_p8|5.033003||Viu
+EXACTFAA_NO_TRIE_tb_pb|5.033003||Viu
+EXACTFAA_t8_p8|5.033003||Viu
+EXACTFAA_t8_pb|5.033003||Viu
+EXACTFAA_tb_p8|5.033003||Viu
+EXACTFAA_tb_pb|5.033003||Viu
+EXACTFL|5.004000||Viu
+EXACTFL_t8_p8|5.033003||Viu
+EXACTFL_t8_pb|5.033003||Viu
+EXACTFL_tb_p8|5.033003||Viu
+EXACTFL_tb_pb|5.033003||Viu
+EXACTFLU8|5.021008||Viu
+EXACTFLU8_t8_p8|5.033003||Viu
+EXACTFLU8_t8_pb|5.033003||Viu
+EXACTFLU8_tb_p8|5.033003||Viu
+EXACTFLU8_tb_pb|5.033003||Viu
+EXACTF_t8_p8|5.033003||Viu
+EXACTF_t8_pb|5.033003||Viu
+EXACTF_tb_p8|5.033003||Viu
+EXACTF_tb_pb|5.033003||Viu
+EXACTFU|5.013008||Viu
+EXACTFUP|5.029007||Viu
+EXACTFUP_t8_p8|5.033003||Viu
+EXACTFUP_t8_pb|5.033003||Viu
+EXACTFUP_tb_p8|5.033003||Viu
+EXACTFUP_tb_pb|5.033003||Viu
+EXACTFU_REQ8|5.031006||Viu
+EXACTFU_REQ8_t8_p8|5.033003||Viu
+EXACTFU_REQ8_t8_pb|5.033003||Viu
+EXACTFU_REQ8_tb_p8|5.033003||Viu
+EXACTFU_REQ8_tb_pb|5.033003||Viu
+EXACTFU_S_EDGE|5.029007||Viu
+EXACTFU_S_EDGE_t8_p8|5.033003||Viu
+EXACTFU_S_EDGE_t8_pb|5.033003||Viu
+EXACTFU_S_EDGE_tb_p8|5.033003||Viu
+EXACTFU_S_EDGE_tb_pb|5.033003||Viu
+EXACTFU_t8_p8|5.033003||Viu
+EXACTFU_t8_pb|5.033003||Viu
+EXACTFU_tb_p8|5.033003||Viu
+EXACTFU_tb_pb|5.033003||Viu
+EXACTL|5.021008||Viu
+EXACTL_t8_p8|5.033003||Viu
+EXACTL_t8_pb|5.033003||Viu
+EXACTL_tb_p8|5.033003||Viu
+EXACTL_tb_pb|5.033003||Viu
+EXACT_REQ8|5.031006||Viu
+EXACT_REQ8_t8_p8|5.033003||Viu
+EXACT_REQ8_t8_pb|5.033003||Viu
+EXACT_REQ8_tb_p8|5.033003||Viu
+EXACT_REQ8_tb_pb|5.033003||Viu
+EXACT_t8_p8|5.033003||Viu
+EXACT_t8_pb|5.033003||Viu
+EXACT_tb_p8|5.033003||Viu
+EXACT_tb_pb|5.033003||Viu
+EXEC_ARGV_CAST|5.007001||Viu
+exec_failed|5.009004||Viu
+execl|5.005000||Viu
+EXEC_PAT_MOD|5.009005||Viu
+EXEC_PAT_MODS|5.009005||Viu
+execute_wildcard|5.031010||Viu
+execv|5.005000||Viu
+execvp|5.005000||Viu
+exit|5.005000||Viu
+EXPECT|5.009004||Viu
+expect_number|5.007001||Viu
+EXT|5.003007||Viu
+EXTCONST|5.004000||Viu
+EXTEND|5.003007|5.003007|
+EXTEND_HWM_SET|5.027002||Viu
+EXTEND_MORTAL|5.004000||Viu
+EXTEND_SKIP|5.027002||Viu
+EXTERN_C|5.005000|5.003007|pV
+EXT_MGVTBL|5.009004||Viu
+EXT_PAT_MODS|5.009005||Viu
+EXTRA_SIZE|5.005000||Viu
+EXTRA_STEP_2ARGS|5.005000||Viu
+F0convert|5.009003||Vniu
+FAKE_BIT_BUCKET|5.009005||Viu
+FAKE_DEFAULT_SIGNAL_HANDLERS|5.009003||Viu
+FAKE_PERSISTENT_SIGNAL_HANDLERS|5.009003||Viu
+FALSE|5.003007||Viu
+FATAL_ABOVE_FF_MSG|5.027010||Viu
+F_atan2_amg|5.004000||Viu
+FBMcf_TAIL|5.006000||Viu
+FBMcf_TAIL_DOLLAR|5.006000||Viu
+FBMcf_TAIL_DOLLARM|5.006000||Viu
+FBMcf_TAIL_Z|5.006000||Viu
+FBMcf_TAIL_z|5.006000||Viu
+fbm_compile|5.005000|5.005000|
+fbm_instr|5.005000|5.005000|
+FBMrf_MULTILINE|5.006000||Viu
+fclose|5.003007||Viu
+fcntl|5.006000||Viu
+FCNTL_CAN_LOCK|5.007001|5.007001|Vn
+F_cos_amg|5.004000||Viu
+FD_CLR|5.008000||Viu
+FD_ISSET|5.008000||Viu
+fdopen|5.003007||Viu
+FD_SET|5.008000||Viu
+fd_set|5.008000||Viu
+FD_ZERO|5.008000||Viu
+FEATURE_BAREWORD_FILEHANDLES_BIT|5.033006||Viu
+FEATURE_BAREWORD_FILEHANDLES_IS_ENABLED|5.033006||Viu
+FEATURE_BITWISE_BIT|5.031006||Viu
+FEATURE_BITWISE_IS_ENABLED|5.021009||Viu
+FEATURE_BUNDLE_510|5.015007||Viu
+FEATURE_BUNDLE_511|5.015007||Viu
+FEATURE_BUNDLE_515|5.015007||Viu
+FEATURE_BUNDLE_523|5.023001||Viu
+FEATURE_BUNDLE_527|5.027008||Viu
+FEATURE_BUNDLE_CUSTOM|5.015007||Viu
+FEATURE_BUNDLE_DEFAULT|5.015007||Viu
+FEATURE_EVALBYTES_BIT|5.031006||Viu
+FEATURE_EVALBYTES_IS_ENABLED|5.015007||Viu
+FEATURE_FC_BIT|5.031006||Viu
+FEATURE_FC_IS_ENABLED|5.015008||Viu
+FEATURE_INDIRECT_BIT|5.031010||Viu
+FEATURE_INDIRECT_IS_ENABLED|5.031010||Viu
+FEATURE_ISA_BIT|5.031007||Viu
+FEATURE_ISA_IS_ENABLED|5.031007||Viu
+FEATURE_IS_ENABLED_MASK|5.031006||Viu
+FEATURE_MULTIDIMENSIONAL_BIT|5.033001||Viu
+FEATURE_MULTIDIMENSIONAL_IS_ENABLED|5.033001||Viu
+FEATURE_MYREF_BIT|5.031006||Viu
+FEATURE_MYREF_IS_ENABLED|5.025003||Viu
+FEATURE_POSTDEREF_QQ_BIT|5.031006||Viu
+FEATURE_POSTDEREF_QQ_IS_ENABLED|5.019005||Viu
+FEATURE_REFALIASING_BIT|5.031006||Viu
+FEATURE_REFALIASING_IS_ENABLED|5.021005||Viu
+FEATURE_SAY_BIT|5.031006||Viu
+FEATURE_SAY_IS_ENABLED|5.015007||Viu
+FEATURE_SIGNATURES_BIT|5.031006||Viu
+FEATURE_SIGNATURES_IS_ENABLED|5.019009||Viu
+FEATURE_STATE_BIT|5.031006||Viu
+FEATURE_STATE_IS_ENABLED|5.015007||Viu
+FEATURE___SUB___BIT|5.031006||Viu
+FEATURE___SUB___IS_ENABLED|5.015007||Viu
+FEATURE_SWITCH_BIT|5.031006||Viu
+FEATURE_SWITCH_IS_ENABLED|5.015007||Viu
+FEATURE_TRY_BIT|5.033007||Viu
+FEATURE_TRY_IS_ENABLED|5.033007||Viu
+FEATURE_UNICODE_BIT|5.031006||Viu
+FEATURE_UNICODE_IS_ENABLED|5.015007||Viu
+FEATURE_UNIEVAL_BIT|5.031006||Viu
+FEATURE_UNIEVAL_IS_ENABLED|5.015007||Viu
+feof|5.003007||Viu
+ferror|5.003007||Viu
+FETCHFEATUREBITSHH|5.031006||Viu
+F_exp_amg|5.004000||Viu
+FF_0DECIMAL|5.007001||Viu
+FF_BLANK|5.003007||Viu
+FF_CHECKCHOP|5.003007||Viu
+FF_CHECKNL|5.003007||Viu
+FF_CHOP|5.003007||Viu
+FF_DECIMAL|5.003007||Viu
+FF_END|5.003007||Viu
+FF_FETCH|5.003007||Viu
+FF_HALFSPACE|5.003007||Viu
+FF_ITEM|5.003007||Viu
+FF_LINEGLOB|5.003007||Viu
+FF_LINEMARK|5.003007||Viu
+FF_LINESNGL|5.009001||Viu
+FF_LITERAL|5.003007||Viu
+Fflush|5.003007||Viu
+fflush|5.003007||Viu
+FFLUSH_NULL|5.006000|5.006000|Vn
+FF_MORE|5.003007||Viu
+FF_NEWLINE|5.003007||Viu
+FF_SKIP|5.003007||Viu
+FF_SPACE|5.003007||Viu
+fgetc|5.003007||Viu
+fgetpos|5.003007||Viu
+fgets|5.003007||Viu
+FILE|5.003007||Viu
+FILE_base|5.007000|5.007000|
+FILE_bufsiz|5.007000|5.007000|
+FILE_cnt|5.007000|5.007000|
+fileno|5.003007||Viu
+FILE_ptr|5.007000|5.007000|
+FILL_ADVANCE_NODE_2L_ARG|5.021005||Viu
+FILL_ADVANCE_NODE|5.005000||Viu
+FILL_ADVANCE_NODE_ARG|5.005000||Viu
+FILL_ADVANCE_NODE_ARGp|5.031010||Viu
+FILL_NODE|5.029004||Viu
+filter_add|5.003007|5.003007|
+FILTER_DATA|5.003007||Viu
+filter_del|5.003007|5.003007|u
+filter_gets|5.005000||Viu
+FILTER_ISREADER|5.003007||Viu
+filter_read|5.003007|5.003007|
+FILTER_READ|5.003007||Viu
+finalize_op|5.015002||Viu
+finalize_optree|5.015002||Vi
+find_and_forget_pmops|5.009005||Viu
+find_array_subscript|5.009004||Viu
+find_beginning|5.005000||Viu
+find_byclass|5.006000||Viu
+find_default_stash|5.019004||Viu
+find_first_differing_byte_pos|5.031007||Vniu
+find_hash_subscript|5.009004||Viu
+find_in_my_stash|5.006001||Viu
+find_lexical_cv|5.019001||Viu
+find_next_masked|5.027009||Vniu
+find_runcv|5.008001|5.008001|
+FIND_RUNCV_level_eq|5.017002||Viu
+FIND_RUNCV_padid_eq|5.017004||Viu
+find_runcv_where|5.017002||Viu
+find_rundefsv|5.013002|5.013002|
+find_rundefsvoffset|5.009002|5.009002|d
+find_script|5.004005||Viu
+find_span_end|5.027009||Vniu
+find_span_end_mask|5.027009||Vniu
+find_uninit_var|5.009002||xVi
+FIRST_NON_ASCII_DECIMAL_DIGIT|5.027007||Viu
+first_symbol|5.009003||Vniu
+FITS_IN_8_BITS|5.013005||Viu
+fixup_errno_string|5.019007||Viu
+FLAGS|5.013006||Viu
+FLEXFILENAMES|5.003007|5.003007|Vn
+float_end_shift|5.009005||Viu
+float_max_offset|5.005000||Viu
+float_min_offset|5.005000||Viu
+float_substr|5.005000||Viu
+float_utf8|5.008000||Viu
+flock|5.005000||Viu
+flockfile|5.003007||Viu
+F_log_amg|5.004000||Viu
+FmLINES|5.003007||Viu
+fold_constants|5.003007||Viu
+foldEQ|5.013002|5.013002|n
+foldEQ_latin1|5.013008||cVnu
+foldEQ_latin1_s2_folded|5.029007||Vniu
+foldEQ_locale|5.013002|5.013002|n
+FOLDEQ_LOCALE|5.019009||cV
+FOLDEQ_S1_ALREADY_FOLDED|5.015004||cV
+FOLDEQ_S1_FOLDS_SANE|5.021008||cV
+FOLDEQ_S2_ALREADY_FOLDED|5.015004||cV
+FOLDEQ_S2_FOLDS_SANE|5.021008||cV
+foldEQ_utf8|5.013002|5.007003|p
+foldEQ_utf8_flags|5.013010||cVu
+FOLDEQ_UTF8_NOMIX_ASCII|5.013010||cV
+FOLD_FLAGS_FULL|5.015006||Viu
+FOLD_FLAGS_LOCALE|5.015006||Viu
+FOLD_FLAGS_NOMIX_ASCII|5.017000||Viu
+fopen|5.003007||Viu
+forbid_setid|5.005000||Viu
+force_ident|5.003007||Viu
+force_ident_maybe_lex|5.017004||Viu
+force_list|5.003007||Viu
+force_next|5.003007||Viu
+_force_out_malformed_utf8_message|5.025009||cVu
+force_strict_version|5.011004||Viu
+force_version|5.005000||Viu
+force_word|5.003007||Viu
+forget_pmop|5.017007||Viu
+form|5.006000|5.004000|v
+form_alien_digit_msg|5.031009||cViu
+form_cp_too_large_msg|5.031009||cViu
+form_nocontext|5.006000||vVn
+fp_dup|5.007003|5.007003|u
+Fpos_t|5.003007|5.003007|Vn
+F_pow_amg|5.004000||Viu
+FP_PINF|5.021004||Viu
+FP_QNAN|5.021004||Viu
+fprintf|5.003007||Viu
+fprintf_nocontext|5.006000||vdVnu
+FPTR2DPTR|5.009003||Viu
+fputc|5.003007||Viu
+fputs|5.003007||Viu
+fread|5.003007||Viu
+free|5.003007||Viu
+free_and_set_cop_warnings|5.031011||Viu
+free_c_backtrace|5.021001||Vi
+FreeOp|5.008001||Viu
+Free_t|5.003007|5.003007|Vn
+FREE_THREAD_KEY|5.006001||Viu
+free_tied_hv_pool|5.008001||Viu
+FREETMPS|5.003007|5.003007|
+free_tmps|5.003007||cVu
+freopen|5.003007||Viu
+frewind|5.005000||Viu
+FROM_INTERNAL_SIZE|5.023002||Viu
+fscanf|5.003007||Viu
+fseek|5.003007||Viu
+FSEEKSIZE|5.006000||Viu
+fsetpos|5.003007||Viu
+F_sin_amg|5.004000||Viu
+F_sqrt_amg|5.004000||Viu
+Fstat|5.003007||Viu
+fstat|5.005000||Viu
+ftell|5.003007||Viu
+ftruncate|5.006000||Viu
+ftrylockfile|5.003007||Viu
+FUNCTION|5.009003||Viu
+funlockfile|5.003007||Viu
+fwrite1|5.003007||Viu
+fwrite|5.003007||Viu
+G_ARRAY|5.003007||Viu
+GCB_BREAKABLE|5.025003||Viu
+GCB_EX_then_EM|5.025003||Viu
+GCB_Maybe_Emoji_NonBreak|5.029002||Viu
+GCB_NOBREAK|5.025003||Viu
+GCB_RI_then_RI|5.025003||Viu
+GCC_DIAG_IGNORE|5.019007||Viu
+GCC_DIAG_IGNORE_DECL|5.027007||Viu
+GCC_DIAG_IGNORE_STMT|5.027007||Viu
+GCC_DIAG_PRAGMA|5.021001||Viu
+GCC_DIAG_RESTORE|5.019007||Viu
+GCC_DIAG_RESTORE_DECL|5.027007||Viu
+GCC_DIAG_RESTORE_STMT|5.027007||Viu
+Gconvert|5.003007|5.003007|
+GDBMNDBM_H_USES_PROTOTYPES|5.032001|5.032001|Vn
+G_DISCARD|5.003007|5.003007|
+gen_constant_list|5.003007||Viu
+get_and_check_backslash_N_name|5.017006||cViu
+get_and_check_backslash_N_name_wrapper|5.029009||Viu
+get_ANYOF_cp_list_for_ssc|5.019005||Viu
+get_ANYOFM_contents|5.027009||Viu
+GETATARGET|5.003007||Viu
+get_aux_mg|5.011000||Viu
+get_av|5.006000|5.003007|p
+getc|5.003007||Viu
+get_c_backtrace|5.021001||Vi
+get_c_backtrace_dump|5.021001||V
+get_context|5.006000|5.006000|nu
+getc_unlocked|5.003007||Viu
+get_cv|5.006000|5.003007|p
+get_cvn_flags|5.009005|5.003007|p
+get_cvs|5.011000|5.003007|p
+getcwd_sv|5.007002|5.007002|
+get_db_sub|||iu
+get_debug_opts|5.008001||Viu
+get_deprecated_property_msg|5.031011||cVniu
+getegid|5.005000||Viu
+getenv|5.005000||Viu
+getenv_len|5.006000||Viu
+GETENV_LOCK|5.033005||Viu
+GETENV_PRESERVES_OTHER_THREAD|5.033005|5.033005|Vn
+GETENV_UNLOCK|5.033005||Viu
+geteuid|5.005000||Viu
+getgid|5.005000||Viu
+getgrent|5.009000||Viu
+GETGRENT_R_HAS_BUFFER|5.008000||Viu
+GETGRENT_R_HAS_FPTR|5.008000||Viu
+GETGRENT_R_HAS_PTR|5.008000||Viu
+GETGRENT_R_PROTO|5.008000|5.008000|Vn
+getgrgid|5.009000||Viu
+GETGRGID_R_HAS_BUFFER|5.008000||Viu
+GETGRGID_R_HAS_PTR|5.008000||Viu
+GETGRGID_R_PROTO|5.008000|5.008000|Vn
+getgrnam|5.009000||Viu
+GETGRNAM_R_HAS_BUFFER|5.008000||Viu
+GETGRNAM_R_HAS_PTR|5.008000||Viu
+GETGRNAM_R_PROTO|5.008000|5.008000|Vn
+get_hash_seed|5.008001||Viu
+gethostbyaddr|5.005000||Viu
+GETHOSTBYADDR_R_HAS_BUFFER|5.008000||Viu
+GETHOSTBYADDR_R_HAS_ERRNO|5.008000||Viu
+GETHOSTBYADDR_R_HAS_PTR|5.008000||Viu
+GETHOSTBYADDR_R_PROTO|5.008000|5.008000|Vn
+gethostbyname|5.005000||Viu
+GETHOSTBYNAME_R_HAS_BUFFER|5.008000||Viu
+GETHOSTBYNAME_R_HAS_ERRNO|5.008000||Viu
+GETHOSTBYNAME_R_HAS_PTR|5.008000||Viu
+GETHOSTBYNAME_R_PROTO|5.008000|5.008000|Vn
+gethostent|5.005000||Viu
+GETHOSTENT_R_HAS_BUFFER|5.008000||Viu
+GETHOSTENT_R_HAS_ERRNO|5.008000||Viu
+GETHOSTENT_R_HAS_PTR|5.008000||Viu
+GETHOSTENT_R_PROTO|5.008000|5.008000|Vn
+gethostname|5.005000||Viu
+get_hv|5.006000|5.003007|p
+get_invlist_iter_addr|5.015001||Vniu
+get_invlist_offset_addr|5.019002||Vniu
+get_invlist_previous_index_addr|5.017004||Vniu
+getlogin|5.005000||Viu
+GETLOGIN_R_PROTO|5.008000|5.008000|Vn
+get_mstats|5.006000||Vu
+getnetbyaddr|5.005000||Viu
+GETNETBYADDR_R_HAS_BUFFER|5.008000||Viu
+GETNETBYADDR_R_HAS_ERRNO|5.008000||Viu
+GETNETBYADDR_R_HAS_PTR|5.008000||Viu
+GETNETBYADDR_R_PROTO|5.008000|5.008000|Vn
+getnetbyname|5.005000||Viu
+GETNETBYNAME_R_HAS_BUFFER|5.008000||Viu
+GETNETBYNAME_R_HAS_ERRNO|5.008000||Viu
+GETNETBYNAME_R_HAS_PTR|5.008000||Viu
+GETNETBYNAME_R_PROTO|5.008000|5.008000|Vn
+getnetent|5.005000||Viu
+GETNETENT_R_HAS_BUFFER|5.008000||Viu
+GETNETENT_R_HAS_ERRNO|5.008000||Viu
+GETNETENT_R_HAS_PTR|5.008000||Viu
+GETNETENT_R_PROTO|5.008000|5.008000|Vn
+get_no_modify|5.005000||Viu
+get_num|5.008001||Viu
+get_opargs|5.005000||Viu
+get_op_descs|5.005000|5.005000|u
+get_op_names|5.005000|5.005000|u
+getpeername|5.005000||Viu
+getpid|5.006000||Viu
+get_ppaddr|5.006000|5.006000|u
+get_prop_definition|5.031011||cViu
+get_prop_values|5.031011||cVniu
+getprotobyname|5.005000||Viu
+GETPROTOBYNAME_R_HAS_BUFFER|5.008000||Viu
+GETPROTOBYNAME_R_HAS_PTR|5.008000||Viu
+GETPROTOBYNAME_R_PROTO|5.008000|5.008000|Vn
+getprotobynumber|5.005000||Viu
+GETPROTOBYNUMBER_R_HAS_BUFFER|5.008000||Viu
+GETPROTOBYNUMBER_R_HAS_PTR|5.008000||Viu
+GETPROTOBYNUMBER_R_PROTO|5.008000|5.008000|Vn
+getprotoent|5.005000||Viu
+GETPROTOENT_R_HAS_BUFFER|5.008000||Viu
+GETPROTOENT_R_HAS_PTR|5.008000||Viu
+GETPROTOENT_R_PROTO|5.008000|5.008000|Vn
+getpwent|5.009000||Viu
+GETPWENT_R_HAS_BUFFER|5.008000||Viu
+GETPWENT_R_HAS_FPTR|5.008000||Viu
+GETPWENT_R_HAS_PTR|5.008000||Viu
+GETPWENT_R_PROTO|5.008000|5.008000|Vn
+getpwnam|5.009000||Viu
+GETPWNAM_R_HAS_BUFFER|5.008000||Viu
+GETPWNAM_R_HAS_PTR|5.008000||Viu
+GETPWNAM_R_PROTO|5.008000|5.008000|Vn
+getpwuid|5.009000||Viu
+GETPWUID_R_HAS_PTR|5.008000||Viu
+GETPWUID_R_PROTO|5.008000|5.008000|Vn
+get_quantifier_value|5.033006||Viu
+get_re_arg|||xciu
+get_re_gclass_nonbitmap_data|5.031011||Viu
+get_regclass_nonbitmap_data|5.031011||Viu
+get_regex_charset_name|5.031004||Vniu
+getservbyname|5.005000||Viu
+GETSERVBYNAME_R_HAS_BUFFER|5.008000||Viu
+GETSERVBYNAME_R_HAS_PTR|5.008000||Viu
+GETSERVBYNAME_R_PROTO|5.008000|5.008000|Vn
+getservbyport|5.005000||Viu
+GETSERVBYPORT_R_HAS_BUFFER|5.008000||Viu
+GETSERVBYPORT_R_HAS_PTR|5.008000||Viu
+GETSERVBYPORT_R_PROTO|5.008000|5.008000|Vn
+getservent|5.005000||Viu
+GETSERVENT_R_HAS_BUFFER|5.008000||Viu
+GETSERVENT_R_HAS_PTR|5.008000||Viu
+GETSERVENT_R_PROTO|5.008000|5.008000|Vn
+getsockname|5.005000||Viu
+getsockopt|5.005000||Viu
+getspnam|5.009000||Viu
+GETSPNAM_R_HAS_BUFFER|5.031011||Viu
+GETSPNAM_R_HAS_PTR|5.008000||Viu
+GETSPNAM_R_PROTO|5.008000|5.008000|Vn
+get_sv|5.006000|5.003007|p
+GETTARGET|5.003007||Viu
+GETTARGETSTACKED|5.003007||Viu
+gettimeofday|5.008000||Viu
+getuid|5.005000||Viu
+get_vtbl|5.005003|5.005003|u
+getw|5.003007||Viu
+G_EVAL|5.003007|5.003007|
+G_FAKINGEVAL|5.009004||Viu
+Gid_t|5.003007|5.003007|Vn
+Gid_t_f|5.006000|5.006000|Vn
+Gid_t_sign|5.006000|5.006000|Vn
+Gid_t_size|5.006000|5.006000|Vn
+GIMME|5.003007|5.003007|d
+GIMME_V|5.004000|5.004000|
+gimme_V|5.031005||xcVu
+G_KEEPERR|5.003007|5.003007|
+G_LIST|5.035001|5.003007|
+glob_2number|5.009004||Viu
+GLOBAL_PAT_MOD|5.009005||Viu
+glob_assign_glob|5.009004||Viu
+G_METHOD|5.006001|5.003007|p
+G_METHOD_NAMED|5.019002|5.019002|
+gmtime|5.031011||Viu
+GMTIME_MAX|5.010001|5.010001|Vn
+GMTIME_MIN|5.010001|5.010001|Vn
+GMTIME_R_PROTO|5.008000|5.008000|Vn
+G_NOARGS|5.003007|5.003007|
+G_NODEBUG|5.004005||Viu
+GOSUB|5.009005||Viu
+GOSUB_t8_p8|5.033003||Viu
+GOSUB_t8_pb|5.033003||Viu
+GOSUB_tb_p8|5.033003||Viu
+GOSUB_tb_pb|5.033003||Viu
+gp_dup|5.007003|5.007003|u
+gp_free|5.003007|5.003007|u
+GPOS|5.004000||Viu
+GPOS_t8_p8|5.033003||Viu
+GPOS_t8_pb|5.033003||Viu
+GPOS_tb_p8|5.033003||Viu
+GPOS_tb_pb|5.033003||Viu
+gp_ref|5.003007|5.003007|u
+GREEK_CAPITAL_LETTER_MU|5.013011||Viu
+GREEK_SMALL_LETTER_MU|5.013008||Viu
+G_RE_REPARSING|5.017011||Viu
+G_RETHROW|5.031002|5.003007|p
+grok_atoUV|5.021010||cVni
+grok_bin|5.007003|5.003007|p
+grok_bin_oct_hex|5.031008||cVu
+grok_bslash_c|5.013001||cViu
+grok_bslash_N|5.017003||Viu
+grok_bslash_o|5.013003||cViu
+grok_bslash_x|5.017002||cViu
+grok_hex|5.007003|5.003007|p
+grok_infnan|5.021004|5.021004|
+grok_number|5.007002|5.003007|p
+grok_number_flags|5.021002|5.021002|
+GROK_NUMERIC_RADIX|5.007002|5.003007|p
+grok_numeric_radix|5.007002|5.003007|p
+grok_oct|5.007003|5.003007|p
+group_end|5.007003||Viu
+GROUPP|5.005000||Viu
+GROUPPN|5.031001||Viu
+GROUPPN_t8_p8|5.033003||Viu
+GROUPPN_t8_pb|5.033003||Viu
+GROUPPN_tb_p8|5.033003||Viu
+GROUPPN_tb_pb|5.033003||Viu
+GROUPP_t8_p8|5.033003||Viu
+GROUPP_t8_pb|5.033003||Viu
+GROUPP_tb_p8|5.033003||Viu
+GROUPP_tb_pb|5.033003||Viu
+Groups_t|5.003007|5.003007|Vn
+GRPASSWD|5.005000|5.005000|Vn
+G_SCALAR|5.003007|5.003007|
+G_UNDEF_FILL|5.013001||Viu
+GV_ADD|5.003007|5.003007|
+gv_add_by_type|5.011000|5.011000|u
+GV_ADDMG|5.015003|5.015003|
+GV_ADDMULTI|5.003007|5.003007|
+GV_ADDWARN|5.003007|5.003007|
+Gv_AMG|5.003007||Viu
+Gv_AMupdate|5.011000|5.011000|u
+GvASSUMECV|5.003007||Viu
+GvASSUMECV_off|5.003007||Viu
+GvASSUMECV_on|5.003007||Viu
+gv_autoload4|5.004000|5.004000|
+GV_AUTOLOAD|5.011000||Viu
+GV_AUTOLOAD_ISMETHOD|5.015004||Viu
+gv_autoload_pv|5.015004|5.015004|u
+gv_autoload_pvn|5.015004|5.015004|u
+gv_autoload_sv|5.015004|5.015004|u
+GvAV|5.003007|5.003007|
+gv_AVadd|5.003007|5.003007|u
+GvAVn|5.003007||Viu
+GV_CACHE_ONLY|5.021004||Vi
+gv_check|5.003007||cVu
+gv_const_sv|5.009003|5.009003|
+GV_CROAK|5.011000||Viu
+GvCV|5.003007|5.003007|
+GvCVGEN|5.003007||Viu
+GvCV_set|5.013010||Viu
+GvCVu|5.004000||Viu
+gv_dump|5.006000|5.006000|u
+gv_efullname3|5.003007|5.003007|u
+gv_efullname4|5.006001|5.006001|u
+gv_efullname|5.003007|5.003007|du
+GvEGV|5.003007||Viu
+GvEGVx|5.013000||Viu
+GvENAME|5.003007||Viu
+GvENAME_HEK|5.015004||Viu
+GvENAMELEN|5.015004||Viu
+GvENAMEUTF8|5.015004||Viu
+GvESTASH|5.003007||Viu
+GVf_ASSUMECV|5.003007||Viu
+gv_fetchfile|5.003007|5.003007|
+gv_fetchfile_flags|5.009005|5.009005|
+gv_fetchmeth|5.003007|5.003007|
+gv_fetchmeth_autoload|5.007003|5.007003|
+gv_fetchmeth_internal|5.021007||Viu
+gv_fetchmethod|5.003007|5.003007|
+gv_fetchmethod_autoload|5.004000|5.004000|
+gv_fetchmethod_flags|5.015004||Viu
+gv_fetchmethod_pv_flags|5.015004|5.015004|xu
+gv_fetchmethod_pvn_flags|5.015004|5.015004|xu
+gv_fetchmethod_sv_flags|5.015004|5.015004|xu
+gv_fetchmeth_pv|5.015004|5.015004|
+gv_fetchmeth_pv_autoload|5.015004|5.015004|
+gv_fetchmeth_pvn|5.015004|5.015004|
+gv_fetchmeth_pvn_autoload|5.015004|5.015004|
+gv_fetchmeth_sv|5.015004|5.015004|
+gv_fetchmeth_sv_autoload|5.015004|5.015004|
+gv_fetchpv|5.003007|5.003007|
+gv_fetchpvn|5.013006|5.013006|
+gv_fetchpvn_flags|5.009002|5.003007|p
+gv_fetchpvs|5.009004|5.003007|p
+gv_fetchsv|5.009002|5.003007|p
+gv_fetchsv_nomg|5.015003|5.015003|
+GvFILE|5.006000||Viu
+GvFILEGV|5.003007||Viu
+GvFILE_HEK|5.009004||Viu
+GvFILEx|5.019006||Viu
+GVf_IMPORTED|5.003007||Viu
+GVf_IMPORTED_AV|5.003007||Viu
+GVf_IMPORTED_CV|5.003007||Viu
+GVf_IMPORTED_HV|5.003007||Viu
+GVf_IMPORTED_SV|5.003007||Viu
+GVf_INTRO|5.003007||Viu
+GvFLAGS|5.003007||Viu
+GVf_MULTI|5.003007||Viu
+GvFORM|5.003007||Viu
+gv_fullname3|5.003007|5.003007|u
+gv_fullname4|5.006001|5.006001|u
+gv_fullname|5.003007|5.003007|du
+GvGP|5.003007||Viu
+GvGPFLAGS|5.021004||Viu
+GvGP_set|5.013010||Viu
+gv_handler|5.007001|5.007001|u
+GvHV|5.003007|5.003007|
+gv_HVadd|5.003007|5.003007|u
+GvHVn|5.003007||Viu
+GvIMPORTED|5.003007||Viu
+GvIMPORTED_AV|5.003007||Viu
+GvIMPORTED_AV_off|5.003007||Viu
+GvIMPORTED_AV_on|5.003007||Viu
+GvIMPORTED_CV|5.003007||Viu
+GvIMPORTED_CV_off|5.003007||Viu
+GvIMPORTED_CV_on|5.003007||Viu
+GvIMPORTED_HV|5.003007||Viu
+GvIMPORTED_HV_off|5.003007||Viu
+GvIMPORTED_HV_on|5.003007||Viu
+GvIMPORTED_off|5.003007||Viu
+GvIMPORTED_on|5.003007||Viu
+GvIMPORTED_SV|5.003007||Viu
+GvIMPORTED_SV_off|5.003007||Viu
+GvIMPORTED_SV_on|5.003007||Viu
+gv_init|5.003007|5.003007|
+gv_init_pv|5.015004|5.015004|
+gv_init_pvn|5.015004|5.003007|p
+gv_init_sv|5.015004|5.015004|
+gv_init_svtype|5.015004||Viu
+GvIN_PAD|5.006000||Viu
+GvIN_PAD_off|5.006000||Viu
+GvIN_PAD_on|5.006000||Viu
+GvINTRO|5.003007||Viu
+GvINTRO_off|5.003007||Viu
+GvINTRO_on|5.003007||Viu
+GvIO|5.003007||Viu
+gv_IOadd|5.003007|5.003007|u
+GvIOn|5.003007||Viu
+GvIOp|5.003007||Viu
+gv_is_in_main|5.019004||Viu
+GvLINE|5.003007||Viu
+gv_magicalize|5.019004||Viu
+gv_magicalize_isa|5.013005||Viu
+gv_method_changed|5.017007||Viu
+GvMULTI|5.003007||Viu
+GvMULTI_off|5.003007||Viu
+GvMULTI_on|5.003007||Viu
+GvNAME|5.003007||Viu
+GvNAME_get|5.009004||Viu
+GvNAME_HEK|5.009004||Viu
+GvNAMELEN|5.003007||Viu
+GvNAMELEN_get|5.009004||Viu
+gv_name_set|5.009004|5.009004|u
+GvNAMEUTF8|5.015004||Viu
+GV_NOADD_MASK|5.009005||Viu
+GV_NOADD_NOINIT|5.009003|5.009003|
+GV_NOEXPAND|5.009003|5.009003|
+GV_NOINIT|5.004005|5.004005|
+GV_NO_SVGMAGIC|5.015003|5.015003|
+GV_NOTQUAL|5.009004|5.009004|
+GV_NOUNIVERSAL|5.033009||Viu
+G_VOID|5.004000|5.004000|
+gv_override|5.019006||Viu
+GvREFCNT|5.003007||Viu
+gv_setref|5.021005||Viu
+GvSTASH|5.003007||Viu
+gv_stashpv|5.003007|5.003007|
+gv_stashpvn|5.003007|5.003007|p
+gv_stashpvn_internal|5.021004||Viu
+gv_stashpvs|5.009003|5.003007|p
+gv_stashsv|5.003007|5.003007|
+gv_stashsvpvn_cached|5.021004||Vi
+GV_SUPER|5.017004|5.017004|
+GvSV|5.003007|5.003007|
+gv_SVadd|5.011000||Vu
+GvSVn|5.009003|5.003007|p
+gv_try_downgrade|5.011002||xcVi
+GvXPVGV|5.003007||Viu
+G_WANT|5.010001||Viu
+G_WARN_ALL_MASK|5.006000||Viu
+G_WARN_ALL_OFF|5.006000||Viu
+G_WARN_ALL_ON|5.006000||Viu
+G_WARN_OFF|5.006000||Viu
+G_WARN_ON|5.006000||Viu
+G_WARN_ONCE|5.006000||Viu
+G_WRITING_TO_STDERR|5.013009||Viu
+handle_named_backref|5.023008||Viu
+handle_names_wildcard|5.031011||Viu
+handle_possible_posix|5.023008||Viu
+handle_regex_sets|5.017009||Viu
+handle_user_defined_property|5.029008||Viu
+HAS_ACCEPT4|5.027008|5.027008|Vn
+HAS_ACCESS|5.006000|5.006000|Vn
+HAS_ACOSH|5.021004|5.021004|Vn
+HAS_ALARM|5.003007|5.003007|Vn
+HAS_ASCTIME_R|5.010000|5.010000|Vn
+HAS_ASINH|5.021006|5.021006|Vn
+HAS_ATANH|5.021006|5.021006|Vn
+HAS_ATOLL|5.006000|5.006000|Vn
+HASATTRIBUTE_ALWAYS_INLINE|5.031007|5.031007|Vn
+HASATTRIBUTE_DEPRECATED|5.010001|5.010001|Vn
+HASATTRIBUTE_FORMAT|5.009003|5.009003|Vn
+HASATTRIBUTE_MALLOC|5.009003|5.009003|Vn
+HASATTRIBUTE_NONNULL|5.009003|5.009003|Vn
+HASATTRIBUTE_NORETURN|5.009003|5.009003|Vn
+HASATTRIBUTE_PURE|5.009003|5.009003|Vn
+HASATTRIBUTE_UNUSED|5.009003|5.009003|Vn
+HASATTRIBUTE_WARN_UNUSED_RESULT|5.009003|5.009003|Vn
+HAS_BACKTRACE|5.021001|5.021001|Vn
+HAS_BOOL|5.003007||Viu
+HAS_BUILTIN_CHOOSE_EXPR|5.009004|5.009004|Vn
+HAS_BUILTIN_EXPECT|5.010001|5.010001|Vn
+__has_builtin|||piu
+HAS_BUILTIN_UNREACHABLE|5.033003||Viu
+HAS_C99|5.021004||Viu
+HAS_C99_VARIADIC_MACROS|5.009004|5.009004|Vn
+HAS_CBRT|5.021006|5.021006|Vn
+HAS_CF_AUX_TABLES|5.027011||Viu
+HAS_CHOWN|5.003007|5.003007|Vn
+HAS_CHROOT|5.003007|5.003007|Vn
+HAS_CHSIZE|5.004005|5.004005|Vn
+HAS_CLEARENV|5.009003|5.009003|Vn
+HAS_COPYSIGN|5.021006|5.021006|Vn
+HAS_COPYSIGNL|5.008001|5.008001|Vn
+HAS_CRYPT|5.003007|5.003007|Vn
+HAS_CRYPT_R|5.010000|5.010000|Vn
+HAS_CSH|5.005000|5.005000|Vn
+HAS_CTERMID|5.009005|5.009005|Vn
+HAS_CTIME_R|5.010000|5.010000|Vn
+HAS_CUSERID|5.003007|5.003007|Vn
+HAS_DBMINIT_PROTO|5.032001|5.032001|Vn
+HAS_DIFFTIME|5.003007|5.003007|Vn
+HAS_DIRFD|5.007003|5.007003|Vn
+HAS_DLADDR|5.021001|5.021001|Vn
+HAS_DLERROR|5.003007|5.003007|Vn
+HAS_DRAND48_PROTO|5.006000|5.006000|Vn
+HAS_DRAND48_R|5.010000|5.010000|Vn
+HAS_DUP2|5.003007|5.003007|Vn
+HAS_DUP3|5.027008|5.027008|Vn
+HAS_DUPLOCALE|5.027011|5.027011|Vn
+HAS_EACCESS|5.006000|5.006000|Vn
+HAS_ENDGRENT|5.005000|5.005000|Vn
+HAS_ENDHOSTENT|5.005000|5.005000|Vn
+HAS_ENDNETENT|5.005000|5.005000|Vn
+HAS_ENDPROTOENT|5.005000|5.005000|Vn
+HAS_ENDPWENT|5.005000|5.005000|Vn
+HAS_ENDSERVENT|5.005000|5.005000|Vn
+HAS_ERF|5.021006|5.021006|Vn
+HAS_ERFC|5.021006|5.021006|Vn
+HAS_EXP2|5.021006|5.021006|Vn
+HAS_EXPM1|5.021006|5.021006|Vn
+HAS_FAST_STDIO|5.008001|5.008001|Vn
+HAS_FCHDIR|5.007002|5.007002|Vn
+HAS_FCHMOD|5.003007|5.003007|Vn
+HAS_FCHMODAT|5.027004|5.027004|Vn
+HAS_FCHOWN|5.003007|5.003007|Vn
+HAS_FCNTL|5.003007|5.003007|Vn
+HAS_FDIM|5.021006|5.021006|Vn
+HAS_FD_SET|5.006000|5.006000|Vn
+HAS_FEGETROUND|5.021004|5.021004|Vn
+HAS_FFS|5.035001|5.035001|Vn
+HAS_FFSL|5.035001|5.035001|Vn
+HAS_FGETPOS|5.003007|5.003007|Vn
+HAS_FINITE|5.007003|5.007003|Vn
+HAS_FINITEL|5.007003|5.007003|Vn
+HAS_FLOCK|5.003007|5.003007|Vn
+HAS_FLOCK_PROTO|5.007002|5.007002|Vn
+HAS_FMA|5.021006|5.021006|Vn
+HAS_FMAX|5.021006|5.021006|Vn
+HAS_FMIN|5.021006|5.021006|Vn
+HAS_FORK|5.003007|5.003007|Vn
+HAS_FPATHCONF|5.003007|5.003007|Vn
+HAS_FPCLASSIFY|5.021004|5.021004|Vn
+HAS_FREELOCALE|5.023009|5.023009|Vn
+HAS_FREXPL|5.006001|5.006001|Vn
+HAS_FSEEKO|5.006000|5.006000|Vn
+HAS_FSETPOS|5.003007|5.003007|Vn
+HAS_FSTATFS|5.023005|5.023005|Vn
+HAS_FSTATVFS|5.023005|5.023005|Vn
+HAS_FSYNC|5.007001|5.007001|Vn
+HAS_FTELLO|5.006000|5.006000|Vn
+HAS_FUTIMES|5.009003|5.009003|Vn
+HAS_GAI_STRERROR|5.025004|5.025004|Vn
+HAS_GETADDRINFO|5.010001|5.010001|Vn
+HAS_GETCWD|5.006000|5.006000|Vn
+HAS_GETGRENT|5.005000|5.005000|Vn
+HAS_GETGRENT_R|5.010000|5.010000|Vn
+HAS_GETGRGID_R|5.010000|5.010000|Vn
+HAS_GETGRNAM_R|5.010000|5.010000|Vn
+HAS_GETGROUPS|5.003007|5.003007|Vn
+HAS_GETHOSTBYADDR|5.005000|5.005000|Vn
+HAS_GETHOSTBYADDR_R|5.010000|5.010000|Vn
+HAS_GETHOSTBYNAME|5.005000|5.005000|Vn
+HAS_GETHOSTBYNAME_R|5.010000|5.010000|Vn
+HAS_GETHOSTENT|5.003007|5.003007|Vn
+HAS_GETHOSTENT_R|5.010000|5.010000|Vn
+HAS_GETHOSTNAME|5.006000|5.006000|Vn
+HAS_GETHOST_PROTOS|5.005000|5.005000|Vn
+HAS_GETITIMER|5.007001|5.007001|Vn
+HAS_GETLOGIN|5.003007|5.003007|Vn
+HAS_GETLOGIN_R|5.010000|5.010000|Vn
+HAS_GETMNTENT|5.023005|5.023005|Vn
+HAS_GETNAMEINFO|5.010001|5.010001|Vn
+HAS_GETNETBYADDR|5.005000|5.005000|Vn
+HAS_GETNETBYADDR_R|5.010000|5.010000|Vn
+HAS_GETNETBYNAME|5.005000|5.005000|Vn
+HAS_GETNETBYNAME_R|5.010000|5.010000|Vn
+HAS_GETNETENT|5.005000|5.005000|Vn
+HAS_GETNETENT_R|5.010000|5.010000|Vn
+HAS_GETNET_PROTOS|5.005000|5.005000|Vn
+HAS_GETPAGESIZE|5.007001|5.007001|Vn
+HAS_GETPGID|5.003007|5.003007|Vn
+HAS_GETPGRP|5.003007|5.003007|Vn
+HAS_GETPPID|5.003007|5.003007|Vn
+HAS_GETPRIORITY|5.003007|5.003007|Vn
+HAS_GETPROTOBYNAME|5.005000|5.005000|Vn
+HAS_GETPROTOBYNAME_R|5.010000|5.010000|Vn
+HAS_GETPROTOBYNUMBER|5.005000|5.005000|Vn
+HAS_GETPROTOBYNUMBER_R|5.010000|5.010000|Vn
+HAS_GETPROTOENT|5.005000|5.005000|Vn
+HAS_GETPROTOENT_R|5.010000|5.010000|Vn
+HAS_GETPROTO_PROTOS|5.005000|5.005000|Vn
+HAS_GETPWENT|5.005000|5.005000|Vn
+HAS_GETPWENT_R|5.010000|5.010000|Vn
+HAS_GETPWNAM_R|5.010000|5.010000|Vn
+HAS_GETPWUID_R|5.010000|5.010000|Vn
+HAS_GETSERVBYNAME|5.005000|5.005000|Vn
+HAS_GETSERVBYNAME_R|5.010000|5.010000|Vn
+HAS_GETSERVBYPORT|5.005000|5.005000|Vn
+HAS_GETSERVBYPORT_R|5.010000|5.010000|Vn
+HAS_GETSERVENT|5.005000|5.005000|Vn
+HAS_GETSERVENT_R|5.010000|5.010000|Vn
+HAS_GETSERV_PROTOS|5.005000|5.005000|Vn
+HAS_GETSPNAM|5.006000|5.006000|Vn
+HAS_GETSPNAM_R|5.010000|5.010000|Vn
+HAS_GETTIMEOFDAY|5.004000|5.004000|Vn
+HAS_GMTIME_R|5.010000|5.010000|Vn
+HAS_GNULIBC|5.004005|5.004005|Vn
+HAS_GROUP|5.003007||Viu
+HAS_HASMNTOPT|5.023005|5.023005|Vn
+HAS_HTONL|5.003007|5.003007|Vn
+HAS_HTONS|5.003007|5.003007|Vn
+HAS_HYPOT|5.021006|5.021006|Vn
+HAS_ILOGB|5.021006|5.021006|Vn
+HAS_ILOGBL|5.008001|5.008001|Vn
+HAS_INET_ATON|5.004000|5.004000|Vn
+HAS_INETNTOP|5.010001|5.010001|Vn
+HAS_INETPTON|5.010001|5.010001|Vn
+HAS_INT64_T|5.006000|5.006000|Vn
+HAS_IOCTL|5.003007||Viu
+HAS_IP_MREQ|5.017002|5.017002|Vn
+HAS_IP_MREQ_SOURCE|5.017004|5.017004|Vn
+HAS_IPV6_MREQ|5.015008|5.015008|Vn
+HAS_ISASCII|5.003007|5.003007|Vn
+HAS_ISBLANK|5.015007|5.015007|Vn
+HAS_ISFINITE|5.021004|5.021004|Vn
+HAS_ISINF|5.007003|5.007003|Vn
+HAS_ISINFL|5.021004|5.021004|Vn
+HAS_ISLESS|5.031007|5.031007|Vn
+HAS_ISNAN|5.006001|5.006001|Vn
+HAS_ISNANL|5.006001|5.006001|Vn
+HAS_ISNORMAL|5.021006|5.021006|Vn
+HAS_IVCF_AUX_TABLES|5.027011||Viu
+HAS_J0|5.021004|5.021004|Vn
+HAS_J0L|5.021004|5.021004|Vn
+HAS_KILL|5.003007||Viu
+HAS_KILLPG|5.003007|5.003007|Vn
+HAS_LC_AUX_TABLES|5.027011||Viu
+HAS_LCHOWN|5.005000|5.005000|Vn
+HAS_LC_MONETARY_2008|5.021005|5.021005|Vn
+HAS_LDBL_DIG|5.006000|5.006000|Vn
+HAS_LDEXPL|5.021003|5.021003|Vn
+HAS_LGAMMA|5.021006|5.021006|Vn
+HAS_LGAMMA_R|5.021006|5.021006|Vn
+HAS_LINK|5.003007|5.003007|Vn
+HAS_LINKAT|5.027004|5.027004|Vn
+HAS_LLRINT|5.021006|5.021006|Vn
+HAS_LLRINTL|5.021009|5.021009|Vn
+HAS_LLROUND|5.021006|5.021006|Vn
+HAS_LLROUNDL|5.021009|5.021009|Vn
+HAS_LOCALECONV|5.003007|5.003007|Vn
+HAS_LOCALTIME_R|5.010000|5.010000|Vn
+HAS_LOCKF|5.003007|5.003007|Vn
+HAS_LOG1P|5.021006|5.021006|Vn
+HAS_LOG2|5.021006|5.021006|Vn
+HAS_LOGB|5.021006|5.021006|Vn
+HAS_LONG_DOUBLE|5.005000|5.005000|Vn
+HAS_LONG_LONG|5.005000|5.005000|Vn
+HAS_LRINT|5.021006|5.021006|Vn
+HAS_LRINTL|5.021009|5.021009|Vn
+HAS_LROUND|5.021006|5.021006|Vn
+HAS_LROUNDL|5.021009|5.021009|Vn
+HAS_LSEEK_PROTO|5.006000|5.006000|Vn
+HAS_LSTAT|5.003007|5.003007|Vn
+HAS_MADVISE|5.006000|5.006000|Vn
+HAS_MBLEN|5.003007|5.003007|Vn
+HAS_MBRLEN|5.027006|5.027006|Vn
+HAS_MBRTOWC|5.027006|5.027006|Vn
+HAS_MBSTOWCS|5.003007|5.003007|Vn
+HAS_MBTOWC|5.003007|5.003007|Vn
+HAS_MEMMEM|5.024000|5.024000|Vn
+HAS_MEMRCHR|5.027005|5.027005|Vn
+HAS_MKDIR|5.003007|5.003007|Vn
+HAS_MKDTEMP|5.006000|5.006000|Vn
+HAS_MKFIFO|5.003007|5.003007|Vn
+HAS_MKOSTEMP|5.027008|5.027008|Vn
+HAS_MKSTEMP|5.006000|5.006000|Vn
+HAS_MKSTEMPS|5.006000|5.006000|Vn
+HAS_MKTIME|5.003007|5.003007|Vn
+HAS_MMAP|5.006000|5.006000|Vn
+HAS_MODFL|5.006001|5.006001|Vn
+HAS_MODFL_PROTO|5.009003|5.009003|Vn
+HAS_MPROTECT|5.006000|5.006000|Vn
+HAS_MSG|5.003007|5.003007|Vn
+HAS_MSYNC|5.006000|5.006000|Vn
+HAS_MUNMAP|5.006000|5.006000|Vn
+HAS_NAN|5.021006|5.021006|Vn
+HAS_NANOSLEEP|5.027006|5.027006|Vn
+HAS_NEARBYINT|5.021006|5.021006|Vn
+HAS_NEWLOCALE|5.023009|5.023009|Vn
+HAS_NEXTAFTER|5.021006|5.021006|Vn
+HAS_NEXTTOWARD|5.021006|5.021006|Vn
+HAS_NICE|5.003007|5.003007|Vn
+HAS_NL_LANGINFO|5.007002|5.007002|Vn
+HAS_NL_LANGINFO_L|5.035001|5.035001|Vn
+HAS_NON_INT_BITFIELDS|5.035001|5.035001|Vn
+HAS_NONLATIN1_FOLD_CLOSURE|5.033005||Viu
+HAS_NONLATIN1_SIMPLE_FOLD_CLOSURE|5.033005||Viu
+HAS_NTOHL|5.003007|5.003007|Vn
+HAS_NTOHS|5.003007|5.003007|Vn
+HAS_OFF64_T|5.010000|5.010000|Vn
+HAS_OPEN3|5.003007|5.003007|Vn
+HAS_OPENAT|5.027004|5.027004|Vn
+HAS_PASSWD|5.003007||Viu
+HAS_PATHCONF|5.003007|5.003007|Vn
+HAS_PAUSE|5.003007|5.003007|Vn
+HAS_PIPE2|5.027008|5.027008|Vn
+HAS_PIPE|5.003007|5.003007|Vn
+HAS_POLL|5.003007|5.003007|Vn
+HAS_POSIX_2008_LOCALE|5.027003||Viu
+HAS_PRCTL|5.013000|5.013000|Vn
+HAS_PRCTL_SET_NAME|5.013000|5.013000|Vn
+HAS_PROCSELFEXE|5.007003|5.007003|Vn
+HAS_PTHREAD_ATFORK|5.010000|5.010000|Vn
+HAS_PTHREAD_ATTR_SETSCOPE|5.008001|5.008001|Vn
+HAS_PTHREAD_UNCHECKED_GETSPECIFIC_NP|5.007002||Viu
+HAS_PTHREAD_YIELD|5.009005|5.009005|Vn
+HAS_PTRDIFF_T|5.021001|5.021001|Vn
+HAS_QUAD|5.003007|5.003007|Vn
+HAS_RANDOM_R|5.010000|5.010000|Vn
+HAS_READDIR|5.003007|5.003007|Vn
+HAS_READDIR64_R|5.010000|5.010000|Vn
+HAS_READDIR_R|5.010000|5.010000|Vn
+HAS_READLINK|5.003007|5.003007|Vn
+HAS_READV|5.007001|5.007001|Vn
+HAS_RECVMSG|5.007001|5.007001|Vn
+HAS_REGCOMP|5.021007|5.021007|Vn
+HAS_REMAINDER|5.021006|5.021006|Vn
+HAS_REMQUO|5.021006|5.021006|Vn
+HAS_RENAME|5.003007|5.003007|Vn
+HAS_RENAMEAT|5.027004|5.027004|Vn
+HAS_REWINDDIR|5.003007|5.003007|Vn
+HAS_RINT|5.021006|5.021006|Vn
+HAS_RMDIR|5.003007|5.003007|Vn
+HAS_ROUND|5.021006|5.021006|Vn
+HAS_SBRK_PROTO|5.007001|5.007001|Vn
+HAS_SCALBN|5.021006|5.021006|Vn
+HAS_SCALBNL|5.008001|5.008001|Vn
+HAS_SCHED_YIELD|5.005000|5.005000|Vn
+HAS_SCX_AUX_TABLES|5.027008||Viu
+HAS_SEEKDIR|5.003007|5.003007|Vn
+HAS_SELECT|5.003007|5.003007|Vn
+HAS_SEM|5.003007|5.003007|Vn
+HAS_SENDMSG|5.007001|5.007001|Vn
+HAS_SETEGID|5.003007|5.003007|Vn
+HAS_SETEUID|5.003007|5.003007|Vn
+HAS_SETGRENT|5.005000|5.005000|Vn
+HAS_SETGROUPS|5.004000|5.004000|Vn
+HAS_SETHOSTENT|5.005000|5.005000|Vn
+HAS_SETITIMER|5.007001|5.007001|Vn
+HAS_SETLINEBUF|5.003007|5.003007|Vn
+HAS_SETLOCALE|5.003007|5.003007|Vn
+HAS_SETNETENT|5.005000|5.005000|Vn
+HAS_SETPGID|5.003007|5.003007|Vn
+HAS_SETPGRP|5.003007|5.003007|Vn
+HAS_SETPRIORITY|5.003007|5.003007|Vn
+HAS_SETPROTOENT|5.005000|5.005000|Vn
+HAS_SETPWENT|5.005000|5.005000|Vn
+HAS_SETREGID|5.003007|5.003007|Vn
+HAS_SETRESGID|5.003007|5.003007|Vn
+HAS_SETRESGID_PROTO|5.010000|5.010000|Vn
+HAS_SETRESUID|5.003007|5.003007|Vn
+HAS_SETRESUID_PROTO|5.010000|5.010000|Vn
+HAS_SETREUID|5.003007|5.003007|Vn
+HAS_SETSERVENT|5.005000|5.005000|Vn
+HAS_SETSID|5.003007|5.003007|Vn
+HAS_SETVBUF|5.005000|5.005000|Vn
+HAS_SHM|5.003007|5.003007|Vn
+HAS_SHMAT_PROTOTYPE|5.003007|5.003007|Vn
+HAS_SIGACTION|5.003007|5.003007|Vn
+HAS_SIGINFO_SI_ADDR|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_BAND|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_ERRNO|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_PID|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_STATUS|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_UID|5.023008|5.023008|Vn
+HAS_SIGINFO_SI_VALUE|5.023008|5.023008|Vn
+HAS_SIGNBIT|5.009005|5.009005|Vn
+HAS_SIGPROCMASK|5.007001|5.007001|Vn
+HAS_SIGSETJMP|5.003007|5.003007|Vn
+HAS_SIN6_SCOPE_ID|5.013009|5.013009|Vn
+HAS_SKIP_LOCALE_INIT|5.019002||Viu
+HAS_SNPRINTF|5.009003|5.009003|Vn
+HAS_SOCKADDR_IN6|5.015008|5.015008|Vn
+HAS_SOCKADDR_STORAGE|5.032001|5.032001|Vn
+HAS_SOCKATMARK|5.007001|5.007001|Vn
+HAS_SOCKATMARK_PROTO|5.007002|5.007002|Vn
+HAS_SOCKET|5.003007|5.003007|Vn
+HAS_SOCKETPAIR|5.003007|5.003007|Vn
+HAS_SQRTL|5.006000|5.006000|Vn
+HAS_SRAND48_R|5.010000|5.010000|Vn
+HAS_SRANDOM_R|5.010000|5.010000|Vn
+HAS_STAT|5.021007|5.021007|Vn
+HAS_STATIC_INLINE|5.013004|5.013004|Vn
+HAS_STRCOLL|5.003007|5.003007|Vn
+HAS_STRERROR_L|5.025002|5.025002|Vn
+HAS_STRERROR_R|5.010000|5.010000|Vn
+HAS_STRFTIME|5.007002|5.007002|Vn
+HAS_STRNLEN|5.027006|5.027006|Vn
+HAS_STRTOD|5.004000|5.004000|Vn
+HAS_STRTOD_L|5.027011|5.027011|Vn
+HAS_STRTOL|5.004000|5.004000|Vn
+HAS_STRTOLD|5.006000|5.006000|Vn
+HAS_STRTOLD_L|5.027006|5.027006|Vn
+HAS_STRTOLL|5.006000|5.006000|Vn
+HAS_STRTOQ|5.007001|5.007001|Vn
+HAS_STRTOUL|5.004000|5.004000|Vn
+HAS_STRTOULL|5.006000|5.006000|Vn
+HAS_STRTOUQ|5.006000|5.006000|Vn
+HAS_STRUCT_CMSGHDR|5.007001|5.007001|Vn
+HAS_STRUCT_MSGHDR|5.007001|5.007001|Vn
+HAS_STRUCT_STATFS|5.023005|5.023005|Vn
+HAS_STRUCT_STATFS_F_FLAGS|5.023005|5.023005|Vn
+HAS_STRXFRM|5.003007|5.003007|Vn
+HAS_STRXFRM_L|5.035001|5.035001|Vn
+HAS_SYMLINK|5.003007|5.003007|Vn
+HAS_SYSCALL|5.003007|5.003007|Vn
+HAS_SYSCALL_PROTO|5.007002|5.007002|Vn
+HAS_SYSCONF|5.003007|5.003007|Vn
+HAS_SYS_ERRLIST|5.003007|5.003007|Vn
+HAS_SYSTEM|5.003007|5.003007|Vn
+HAS_TC_AUX_TABLES|5.027011||Viu
+HAS_TCGETPGRP|5.003007|5.003007|Vn
+HAS_TCSETPGRP|5.003007|5.003007|Vn
+HAS_TELLDIR|5.003007|5.003007|Vn
+HAS_TELLDIR_PROTO|5.006000|5.006000|Vn
+HAS_TGAMMA|5.021006|5.021006|Vn
+HAS_THREAD_SAFE_NL_LANGINFO_L|5.027006|5.027006|Vn
+HAS_TIME|5.008000|5.008000|Vn
+HAS_TIMEGM|5.010001|5.010001|Vn
+HAS_TIMES|5.003007|5.003007|Vn
+HAS_TMPNAM_R|5.010000|5.010000|Vn
+HAS_TM_TM_GMTOFF|5.008001|5.008001|Vn
+HAS_TM_TM_ZONE|5.008000|5.008000|Vn
+HAS_TOWLOWER|5.029009|5.029009|Vn
+HAS_TOWUPPER|5.029009|5.029009|Vn
+HAS_TRUNC|5.021006|5.021006|Vn
+HAS_TRUNCATE|5.003007|5.003007|Vn
+HAS_TRUNCL|5.021004|5.021004|Vn
+HAS_TTYNAME_R|5.010000|5.010000|Vn
+HAS_TZNAME|5.003007|5.003007|Vn
+HAS_UALARM|5.007001|5.007001|Vn
+HAS_UC_AUX_TABLES|5.027011||Viu
+HAS_UMASK|5.003007|5.003007|Vn
+HAS_UNAME|5.003007|5.003007|Vn
+HAS_UNLINKAT|5.027004|5.027004|Vn
+HAS_UNSETENV|5.009003|5.009003|Vn
+HAS_USELOCALE|5.023009|5.023009|Vn
+HAS_USLEEP|5.007001|5.007001|Vn
+HAS_USLEEP_PROTO|5.007002|5.007002|Vn
+HAS_USTAT|5.023005|5.023005|Vn
+HAS_UTIME|5.003007||Viu
+HAS_VSNPRINTF|5.009003|5.009003|Vn
+HAS_WAIT4|5.003007|5.003007|Vn
+HAS_WAIT|5.003007||Viu
+HAS_WAITPID|5.003007|5.003007|Vn
+HAS_WCRTOMB|5.031007|5.031007|Vn
+HAS_WCSCMP|5.021001|5.021001|Vn
+HAS_WCSTOMBS|5.003007|5.003007|Vn
+HAS_WCSXFRM|5.021001|5.021001|Vn
+HAS_WCTOMB|5.003007|5.003007|Vn
+HAS_WRITEV|5.007001|5.007001|Vn
+he_dup|5.007003|5.007003|u
+HEf_SVKEY|5.003007|5.003007|p
+HeHASH|5.003007|5.003007|
+HEK_BASESIZE|5.004000||Viu
+hek_dup|5.009000|5.009000|u
+HeKEY|5.003007|5.003007|
+HeKEY_hek|5.004000||Viu
+HeKEY_sv|5.004000||Viu
+HEKf256|5.015004||Viu
+HEKf|5.015004||Viu
+HEKfARG|5.015004||Viu
+HEK_FLAGS|5.008000||Viu
+HeKFLAGS|5.008000||Viu
+HEK_HASH|5.004000||Viu
+HEK_KEY|5.004000||Viu
+HeKLEN|5.003007|5.003007|
+HEK_LEN|5.004000||Viu
+HeKLEN_UTF8|5.007001||Viu
+HEK_UTF8|5.007001||Viu
+HeKUTF8|5.007001||Viu
+HEK_UTF8_off|5.008000||Viu
+HEK_UTF8_on|5.008000||Viu
+HEK_WASUTF8|5.008000||Viu
+HeKWASUTF8|5.008000||Viu
+HEK_WASUTF8_off|5.008000||Viu
+HEK_WASUTF8_on|5.008000||Viu
+HeNEXT|5.003007||Viu
+HePV|5.004000|5.004000|
+HeSVKEY|5.003007|5.003007|
+HeSVKEY_force|5.003007|5.003007|
+HeSVKEY_set|5.004000|5.004000|
+HE_SVSLOT|5.009003||Viu
+HeUTF8|5.010001|5.008000|p
+HeVAL|5.003007|5.003007|
+hfree_next_entry|||iu
+HIGHEST_ANYOF_HRx_BYTE|5.031002||Viu
+HIGHEST_CASE_CHANGING_CP|5.033005||Viu
+HINT_ALL_STRICT|5.033002||Viu
+HINT_BLOCK_SCOPE|5.003007||Viu
+HINT_BYTES|5.007002||Viu
+HINT_EXPLICIT_STRICT_REFS|5.016000||Viu
+HINT_EXPLICIT_STRICT_SUBS|5.016000||Viu
+HINT_EXPLICIT_STRICT_VARS|5.016000||Viu
+HINT_FEATURE_MASK|5.015007||Viu
+HINT_FEATURE_SHIFT|5.015007||Viu
+HINT_FILETEST_ACCESS|5.006000||Viu
+HINT_INTEGER|5.003007||Viu
+HINT_LEXICAL_IO_IN|5.009005||Viu
+HINT_LEXICAL_IO_OUT|5.009005||Viu
+HINT_LOCALE|5.004000||Viu
+HINT_LOCALE_PARTIAL|5.021001||Viu
+HINT_LOCALIZE_HH|5.005000||Viu
+HINT_NEW_BINARY|5.005000||Viu
+HINT_NEW_FLOAT|5.005000||Viu
+HINT_NEW_INTEGER|5.005000||Viu
+HINT_NEW_RE|5.005000||Viu
+HINT_NEW_STRING|5.005000||Viu
+HINT_NO_AMAGIC|5.010001||Viu
+HINT_RE_EVAL|5.005000||Viu
+HINT_RE_FLAGS|5.013007||Viu
+HINT_RE_TAINT|5.004005||Viu
+HINTS_DEFAULT|5.033002||Viu
+HINT_SORT_STABLE|5.007003||Viu
+HINT_SORT_UNSTABLE|5.027004||Viu
+HINTS_REFCNT_INIT|5.009004||Viu
+HINTS_REFCNT_LOCK|5.009004||Viu
+HINTS_REFCNT_TERM|5.009004||Viu
+HINTS_REFCNT_UNLOCK|5.009004||Viu
+HINT_STRICT_REFS|5.003007||Viu
+HINT_STRICT_SUBS|5.003007||Viu
+HINT_STRICT_VARS|5.003007||Viu
+HINT_UNI_8_BIT|5.011002||Viu
+HINT_UTF8|5.006000||Viu
+H_PERL|5.003007||Viu
+HS_APIVERLEN_MAX|5.021006||Viu
+HS_CXT|5.021006||Viu
+HSf_IMP_CXT|5.021006||Viu
+HSf_NOCHK|5.021006||Viu
+HSf_POPMARK|5.021006||Viu
+HSf_SETXSUBFN|5.021006||Viu
+HS_GETAPIVERLEN|5.021006||Viu
+HS_GETINTERPSIZE|5.021006||Viu
+HS_GETXSVERLEN|5.021006||Viu
+HS_KEY|5.021006||Viu
+HS_KEYp|5.021006||Viu
+HSm_APIVERLEN|5.021006||Viu
+HSm_INTRPSIZE|5.021006||Viu
+HSm_KEY_MATCH|5.021006||Viu
+HSm_XSVERLEN|5.021006||Viu
+hsplit|5.005000||Viu
+HS_XSVERLEN_MAX|5.021006||Viu
+htoni|5.003007||Viu
+htonl|5.003007||Viu
+htons|5.003007||Viu
+htovl|5.003007||Viu
+htovs|5.003007||Viu
+HvAMAGIC|5.017000||Viu
+HvAMAGIC_off|5.017000||Viu
+HvAMAGIC_on|5.017000||Viu
+HvARRAY|5.003007||Viu
+hv_assert|5.008009|5.008009|
+HvAUX|5.009003||Viu
+HvAUXf_NO_DEREF|5.019010||Viu
+HvAUXf_SCAN_STASH|5.019010||Viu
+hv_auxinit|5.009003||Viu
+hv_auxinit_internal|5.019010||Vniu
+hv_backreferences_p|||xiu
+hv_bucket_ratio|5.025003|5.025003|x
+hv_clear|5.003007|5.003007|
+hv_clear_placeholders|5.009001|5.009001|
+hv_common|5.010000||cVu
+hv_common_key_len|5.010000||cVu
+hv_copy_hints_hv|5.009004|5.009004|
+hv_delayfree_ent|5.004000|5.004000|u
+hv_delete|5.003007|5.003007|
+HV_DELETE|5.009005||Viu
+hv_delete_common|5.009001||xViu
+hv_delete_ent|5.003007|5.003007|
+hv_deletehek|5.019006||Viu
+hv_deletes|5.025006||Viu
+HV_DISABLE_UVAR_XKEY|5.010000||Viu
+HvEITER|5.003007||Viu
+HvEITER_get|5.009003||Viu
+hv_eiter_p|5.009003|5.009003|u
+hv_eiter_set|5.009003|5.009003|u
+HvEITER_set|5.009003||Viu
+HvENAME|5.013007|5.013007|
+hv_ename_add|5.013007||Vi
+hv_ename_delete|5.013007||Vi
+HvENAME_get|5.013007||Viu
+HvENAME_HEK|5.013007||Viu
+HvENAME_HEK_NN|5.013007||Viu
+HvENAMELEN|5.015004|5.015004|
+HvENAMELEN_get|5.013007||Viu
+HvENAMEUTF8|5.015004|5.015004|
+hv_exists|5.003007|5.003007|
+hv_exists_ent|5.003007|5.003007|
+hv_existss|5.025006||Viu
+hv_fetch|5.003007|5.003007|
+HV_FETCH_EMPTY_HE|5.013007||Viu
+hv_fetch_ent|5.003007|5.003007|
+hv_fetchhek|5.019006||Viu
+HV_FETCH_ISEXISTS|5.009005||Viu
+HV_FETCH_ISSTORE|5.009005||Viu
+HV_FETCH_JUST_SV|5.009005||Viu
+HV_FETCH_LVALUE|5.009005||Viu
+hv_fetchs|5.009003|5.003007|p
+HvFILL|5.003007|5.003007|
+hv_fill|5.013002|5.013002|
+hv_free_ent|5.004000|5.004000|u
+hv_free_ent_ret|5.015000||Viu
+hv_free_entries|5.027002||Viu
+HvHASKFLAGS|5.008000||Viu
+HvHASKFLAGS_off|5.008000||Viu
+HvHASKFLAGS_on|5.008000||Viu
+HVhek_ENABLEHVKFLAGS|5.008002||Viu
+HVhek_FREEKEY|5.008000||Viu
+HVhek_KEYCANONICAL|5.010001||Viu
+HVhek_MASK|5.008000||Viu
+HVhek_PLACEHOLD|5.008000||Viu
+HVhek_UNSHARED|5.009004||Viu
+HVhek_UTF8|5.008000||Viu
+HVhek_WASUTF8|5.008000||Viu
+hv_iterinit|5.003007|5.003007|
+hv_iterkey|5.003007|5.003007|
+hv_iterkeysv|5.003007|5.003007|
+hv_iternext|5.003007|5.003007|
+hv_iternext_flags|5.008000|5.008000|x
+hv_iternextsv|5.003007|5.003007|
+HV_ITERNEXT_WANTPLACEHOLDERS|5.008000|5.008000|
+hv_iterval|5.003007|5.003007|
+HvKEYS|5.003007||Viu
+hv_kill_backrefs|||xiu
+hv_ksplit|5.003007|5.003007|u
+HvLASTRAND_get|5.017011||Viu
+HvLAZYDEL|5.003007||Viu
+HvLAZYDEL_off|5.003007||Viu
+HvLAZYDEL_on|5.003007||Viu
+hv_magic|5.003007|5.003007|
+hv_magic_check|5.006000||Vniu
+HvMAX|5.003007||Viu
+HvMROMETA|5.010001|5.010001|
+HvNAME|5.003007|5.003007|
+HvNAME_get|5.009003||pcV
+HvNAME_HEK|5.009003||Viu
+HvNAME_HEK_NN|5.013007||Viu
+HvNAMELEN|5.015004|5.015004|
+HvNAMELEN_get|5.009003|5.003007|p
+hv_name_set|5.009003|5.009003|u
+HV_NAME_SETALL|5.013008||Viu
+hv_name_sets|5.025006||Viu
+HvNAMEUTF8|5.015004|5.015004|
+hv_notallowed|5.008000||Viu
+HvPLACEHOLDERS|5.007003||Viu
+hv_placeholders_get|5.009003|5.009003|u
+HvPLACEHOLDERS_get|5.009003||Viu
+hv_placeholders_p|||ciu
+hv_placeholders_set|5.009003|5.009003|u
+HvPLACEHOLDERS_set|5.009003||Viu
+hv_pushkv|5.027003||Viu
+HvRAND_get|5.017011||Viu
+hv_rand_set|5.018000|5.018000|u
+HVrhek_delete|5.009004||Viu
+HVrhek_IV|5.009004||Viu
+HVrhek_PV|5.009004||Viu
+HVrhek_PV_UTF8|5.009005||Viu
+HVrhek_typemask|5.009004||Viu
+HVrhek_undef|5.009004||Viu
+HVrhek_UV|5.009004||Viu
+HvRITER|5.003007||Viu
+HvRITER_get|5.009003||Viu
+hv_riter_p|5.009003|5.009003|u
+hv_riter_set|5.009003|5.009003|u
+HvRITER_set|5.009003||Viu
+hv_scalar|5.009001|5.009001|
+HvSHAREKEYS|5.003007||Viu
+HvSHAREKEYS_off|5.003007||Viu
+HvSHAREKEYS_on|5.003007||Viu
+hv_store|5.003007|5.003007|
+hv_store_ent|5.003007|5.003007|
+hv_store_flags|5.008000|5.008000|xu
+hv_storehek|5.019006||Viu
+hv_stores|5.009004|5.003007|p
+HvTOTALKEYS|5.007003||Viu
+hv_undef|5.003007|5.003007|
+hv_undef_flags|||ciu
+HvUSEDKEYS|5.007003||Viu
+HYPHEN_UTF8|5.017004||Viu
+I16_MAX|5.003007||Viu
+I16_MIN|5.003007||Viu
+I16SIZE|5.006000|5.006000|Vn
+I16TYPE|5.006000|5.006000|Vn
+I_32|5.006000|5.003007|
+I32_MAX|5.003007||Viu
+I32_MAX_P1|5.007002||Viu
+I32_MIN|5.003007||Viu
+I32SIZE|5.006000|5.006000|Vn
+I32TYPE|5.006000|5.006000|Vn
+I64SIZE|5.006000|5.006000|Vn
+I64TYPE|5.006000|5.006000|Vn
+I8SIZE|5.006000|5.006000|Vn
+I8_TO_NATIVE|5.015006||Viu
+I8_TO_NATIVE_UTF8|5.019004||Viu
+I8TYPE|5.006000|5.006000|Vn
+I_ARPA_INET|5.005000|5.005000|Vn
+ibcmp|5.003007|5.003007|
+ibcmp_locale|5.004000|5.004000|
+ibcmp_utf8|5.007003|5.007003|
+I_CRYPT|5.008000|5.008000|Vn
+I_DBM|5.032001|5.032001|Vn
+I_DIRENT|5.003007|5.003007|Vn
+I_DLFCN|5.003007|5.003007|Vn
+I_EXECINFO|5.021001|5.021001|Vn
+I_FENV|5.021004|5.021004|Vn
+IFMATCH|5.003007||Viu
+IFMATCH_A|5.009005||Viu
+IFMATCH_A_fail|5.009005||Viu
+IFMATCH_A_fail_t8_p8|5.033003||Viu
+IFMATCH_A_fail_t8_pb|5.033003||Viu
+IFMATCH_A_fail_tb_p8|5.033003||Viu
+IFMATCH_A_fail_tb_pb|5.033003||Viu
+IFMATCH_A_t8_p8|5.033003||Viu
+IFMATCH_A_t8_pb|5.033003||Viu
+IFMATCH_A_tb_p8|5.033003||Viu
+IFMATCH_A_tb_pb|5.033003||Viu
+IFMATCH_t8_p8|5.033003||Viu
+IFMATCH_t8_pb|5.033003||Viu
+IFMATCH_tb_p8|5.033003||Viu
+IFMATCH_tb_pb|5.033003||Viu
+IFTHEN|5.005000||Viu
+IFTHEN_t8_p8|5.033003||Viu
+IFTHEN_t8_pb|5.033003||Viu
+IFTHEN_tb_p8|5.033003||Viu
+IFTHEN_tb_pb|5.033003||Viu
+I_GDBM|5.021007|5.021007|Vn
+I_GDBMNDBM|5.021007|5.021007|Vn
+IGNORE_PAT_MOD|5.009005||Viu
+I_GRP|5.003007|5.003007|Vn
+I_INTTYPES|5.006000|5.006000|Vn
+I_LANGINFO|5.007002|5.007002|Vn
+I_LIMITS|5.003007||Viu
+ILLEGAL_UTF8_BYTE|5.019004||Viu
+I_LOCALE|5.003007|5.003007|Vn
+I_MNTENT|5.023005|5.023005|Vn
+IN_BYTES|5.007002||Viu
+incline|5.005000||Viu
+INCLUDE_PROTOTYPES|5.007001||Viu
+INCMARK|5.023005||Viu
+incpush|5.005000||Viu
+INCPUSH_APPLLIB_EXP|5.027006||Viu
+INCPUSH_APPLLIB_OLD_EXP|5.027006||Viu
+INCPUSH_ARCHLIB_EXP|5.027006||Viu
+incpush_if_exists|5.009003||Viu
+INCPUSH_PERL5LIB|5.027006||Viu
+INCPUSH_PERL_OTHERLIBDIRS|5.027006||Viu
+INCPUSH_PERL_OTHERLIBDIRS_ARCHONLY|5.027006||Viu
+INCPUSH_PERL_VENDORARCH_EXP|5.027006||Viu
+INCPUSH_PERL_VENDORLIB_EXP|5.027006||Viu
+INCPUSH_PERL_VENDORLIB_STEM|5.027006||Viu
+INCPUSH_PRIVLIB_EXP|5.027006||Viu
+INCPUSH_SITEARCH_EXP|5.027006||Viu
+INCPUSH_SITELIB_EXP|5.027006||Viu
+INCPUSH_SITELIB_STEM|5.027006||Viu
+incpush_use_sep|5.011000||Viu
+I_NDBM|5.032001|5.032001|Vn
+inet_addr|5.005000||Viu
+I_NETDB|5.005000|5.005000|Vn
+I_NETINET_IN|5.003007|5.003007|Vn
+I_NETINET_TCP|5.006000|5.006000|Vn
+inet_ntoa|5.005000||Viu
+INFNAN_NV_U8_DECL|5.023000||Viu
+INFNAN_U8_NV_DECL|5.023000||Viu
+ingroup|5.003007||Viu
+INIT|5.003007||Viu
+init_argv_symbols|5.007003||Viu
+init_constants|5.017003||Viu
+init_dbargs|||iu
+init_debugger|5.005000||Viu
+init_i18nl10n|5.006000||cVu
+init_i18nl14n|5.006000||dcVu
+initialize_invlist_guts|5.029002||Viu
+init_ids|5.005000||Viu
+init_interp|5.005000||Viu
+init_main_stash|5.005000||Viu
+init_named_cv|5.027010||cViu
+init_os_extras|5.005000||Viu
+init_perllib|5.005000||Viu
+init_postdump_symbols|5.005000||Viu
+init_predump_symbols|5.005000||Viu
+init_stacks|5.005000|5.005000|u
+INIT_THREADS|5.005000||Viu
+init_tm|5.007002|5.007002|u
+INIT_TRACK_MEMPOOL|5.009004||Viu
+init_uniprops|5.027011||Viu
+IN_LC|5.021001||Viu
+IN_LC_ALL_COMPILETIME|5.021001||Viu
+IN_LC_ALL_RUNTIME|5.021001||Viu
+IN_LC_COMPILETIME|5.021001||Viu
+IN_LC_PARTIAL_COMPILETIME|5.021001||Viu
+IN_LC_PARTIAL_RUNTIME|5.021001||Viu
+IN_LC_RUNTIME|5.021001||Viu
+IN_LOCALE|5.007002|5.004000|p
+IN_LOCALE_COMPILETIME|5.007002|5.004000|p
+IN_LOCALE_RUNTIME|5.007002|5.004000|p
+IN_PERL_COMPILETIME|5.008001|5.003007|p
+IN_PERL_RUNTIME|5.008001|5.008001|
+inplace_aassign|5.015003||Viu
+inRANGE|5.029010||Viu
+inRANGE_helper|5.033005||Viu
+IN_SOME_LOCALE_FORM|5.015008||Viu
+IN_SOME_LOCALE_FORM_COMPILETIME|5.015008||Viu
+IN_SOME_LOCALE_FORM_RUNTIME|5.015008||Viu
+instr|5.003007|5.003007|n
+INSUBP|5.009005||Viu
+INSUBP_t8_p8|5.033003||Viu
+INSUBP_t8_pb|5.033003||Viu
+INSUBP_tb_p8|5.033003||Viu
+INSUBP_tb_pb|5.033003||Viu
+INT16_C|5.003007|5.003007|
+INT2PTR|5.006000|5.003007|p
+INT32_C|5.003007|5.003007|
+INT32_MIN|5.007002||Viu
+INT64_C|5.023002|5.023002|
+INT64_MIN|5.007002||Viu
+INT_64_T|5.011000||Viu
+INTMAX_C|5.003007|5.003007|
+INT_PAT_MODS|5.009005||Viu
+intro_my|5.004000|5.004000|
+INTSIZE|5.003007|5.003007|Vn
+intuit_method|5.005000||Viu
+intuit_more|5.003007||Viu
+IN_UNI_8_BIT|5.011002||Viu
+IN_UTF8_CTYPE_LOCALE|5.019009||Viu
+_inverse_folds|5.027011||cViu
+invert|5.003007||Viu
+invlist_array|5.013010||Vniu
+_invlist_array_init|5.015001||Vniu
+invlist_clear|5.023009||Viu
+invlist_clone|5.015001||cViu
+_invlist_contains_cp|5.017003||Vniu
+invlist_contents|5.023008||Viu
+_invlist_dump|5.019003||cViu
+_invlistEQ|5.023006||cViu
+invlist_extend|5.013010||Viu
+invlist_highest|5.017002||Vniu
+_invlist_intersection|5.015001||Viu
+_invlist_intersection_maybe_complement_2nd|5.015008||cViu
+_invlist_invert|5.015001||cViu
+invlist_is_iterating|5.017008||Vniu
+invlist_iterfinish|5.017008||Vniu
+invlist_iterinit|5.015001||Vniu
+invlist_iternext|5.015001||Vniu
+_invlist_len|5.017004||Vniu
+invlist_lowest|5.031007||xVniu
+invlist_max|5.013010||Vniu
+invlist_previous_index|5.017004||Vniu
+invlist_replace_list_destroys_src|5.023009||Viu
+_invlist_search|5.017003||cVniu
+invlist_set_len|5.013010||Viu
+invlist_set_previous_index|5.017004||Vniu
+_invlist_subtract|5.015001||Viu
+invlist_trim|5.013010||Vniu
+_invlist_union|5.015001||cVu
+_invlist_union_maybe_complement_2nd|5.015008||cViu
+invmap_dump|5.031006||Viu
+invoke_exception_hook|5.013001||Viu
+IoANY|5.006001||Viu
+IoBOTTOM_GV|5.003007||Viu
+IoBOTTOM_NAME|5.003007||Viu
+io_close|5.003007||Viu
+IOCPARM_LEN|5.003007||Viu
+ioctl|5.005000||Viu
+IoDIRP|5.003007||Viu
+IOf_ARGV|5.003007||Viu
+IOf_DIDTOP|5.003007||Viu
+IOf_FAKE_DIRP|5.006000||Viu
+IOf_FLUSH|5.003007||Viu
+IoFLAGS|5.003007||Viu
+IoFMT_GV|5.003007||Viu
+IoFMT_NAME|5.003007||Viu
+IOf_NOLINE|5.005003||Viu
+IOf_START|5.003007||Viu
+IOf_UNTAINT|5.003007||Viu
+IoIFP|5.003007||Viu
+IoLINES|5.003007||Viu
+IoLINES_LEFT|5.003007||Viu
+IoOFP|5.003007||Viu
+IoPAGE|5.003007||Viu
+IoPAGE_LEN|5.003007||Viu
+IoTOP_GV|5.003007||Viu
+IoTOP_NAME|5.003007||Viu
+IoTYPE|5.003007||Viu
+IoTYPE_APPEND|5.006001||Viu
+IoTYPE_CLOSED|5.006001||Viu
+IoTYPE_IMPLICIT|5.008001||Viu
+IoTYPE_NUMERIC|5.008001||Viu
+IoTYPE_PIPE|5.006001||Viu
+IoTYPE_RDONLY|5.006001||Viu
+IoTYPE_RDWR|5.006001||Viu
+IoTYPE_SOCKET|5.006001||Viu
+IoTYPE_STD|5.006001||Viu
+IoTYPE_WRONLY|5.006001||Viu
+I_POLL|5.006000|5.006000|Vn
+I_PTHREAD|5.005003|5.005003|Vn
+I_PWD|5.003007|5.003007|Vn
+isALNUM|5.003007|5.003007|p
+isALNUM_A|5.031003|5.003007|p
+isALNUMC|5.006000|5.003007|p
+isALNUMC_A|5.013006|5.003007|p
+isALNUMC_L1|5.013006|5.003007|p
+isALNUMC_LC|5.006000|5.006000|
+isALNUMC_LC_utf8_safe|5.031007||Viu
+isALNUMC_LC_uvchr|5.017007|5.017007|
+isALNUMC_uni|5.017007||Viu
+isALNUMC_utf8|5.017007||Viu
+isALNUMC_utf8_safe|5.031007||Viu
+isALNUM_lazy_if_safe|5.031007||Viu
+isALNUM_LC|5.004000|5.004000|
+isALNUM_LC_utf8|5.006000||Viu
+isALNUM_LC_utf8_safe|5.031007||Viu
+isALNUM_LC_uvchr|5.007001|5.007001|
+isALNUMU|5.011005||Viu
+isALNUM_uni|5.006000||Viu
+isALNUM_utf8|5.006000||Viu
+isALNUM_utf8_safe|5.031007||Viu
+isa_lookup|5.005000||Viu
+isALPHA|5.003007|5.003007|p
+isALPHA_A|5.013006|5.003007|p
+isALPHA_FOLD_EQ|5.021004||Viu
+isALPHA_FOLD_NE|5.021004||Viu
+isALPHA_L1|5.013006|5.003007|p
+isALPHA_LC|5.004000|5.004000|
+isALPHA_LC_utf8|5.006000||Viu
+isALPHA_LC_utf8_safe|5.025009|5.006000|p
+isALPHA_LC_uvchr|5.007001|5.007001|
+isALPHANUMERIC|5.017008|5.003007|p
+isALPHANUMERIC_A|5.017008|5.003007|p
+isALPHANUMERIC_L1|5.017008|5.003007|p
+isALPHANUMERIC_LC|5.017008|5.004000|p
+isALPHANUMERIC_LC_utf8|5.017008||Viu
+isALPHANUMERIC_LC_utf8_safe|5.025009|5.006000|p
+isALPHANUMERIC_LC_uvchr|5.017008|5.017008|
+isALPHANUMERIC_uni|5.017008||Viu
+isALPHANUMERIC_utf8|5.031005|5.031005|
+isALPHANUMERIC_utf8_safe|5.025009|5.006000|p
+isALPHANUMERIC_uvchr|5.023009|5.006000|p
+isALPHAU|5.011005||Viu
+isALPHA_uni|5.006000||Viu
+isALPHA_utf8|5.031005|5.031005|
+isALPHA_utf8_safe|5.025009|5.006000|p
+isALPHA_uvchr|5.023009|5.006000|p
+is_an_int|5.005000||Viu
+is_ANYOF_SYNTHETIC|5.019009||Viu
+IS_ANYOF_TRIE|5.009005||Viu
+isASCII|5.006000|5.003007|p
+isASCII_A|5.013006|5.003007|p
+isASCII_L1|5.015004|5.003007|p
+isASCII_LC|5.015008|5.003007|p
+isASCII_LC_utf8|5.017007||Viu
+isASCII_LC_utf8_safe|5.025009|5.025009|
+isASCII_LC_uvchr|5.017007|5.017007|
+is_ascii_string|5.011000|5.011000|n
+isASCII_uni|5.006000||Viu
+isASCII_utf8|5.031005|5.031005|
+isASCII_utf8_safe|5.025009|5.003007|p
+isASCII_uvchr|5.023009|5.003007|p
+isatty|5.005000||Viu
+ISA_VERSION_OBJ|5.019008||Viu
+isBLANK|5.006001|5.003007|p
+isBLANK_A|5.013006|5.003007|p
+isBLANK_L1|5.013006|5.003007|p
+isBLANK_LC|5.006001|5.003007|p
+isBLANK_LC_uni|5.006001||Viu
+isBLANK_LC_utf8|5.006001||Viu
+isBLANK_LC_utf8_safe|5.025009|5.006000|p
+isBLANK_LC_uvchr|5.017007|5.017007|
+isBLANK_uni|5.006001||Viu
+isBLANK_utf8|5.031005|5.031005|
+isBLANK_utf8_safe|5.025009|5.006000|p
+isBLANK_uvchr|5.023009|5.006000|p
+isC9_STRICT_UTF8_CHAR|5.025005|5.025005|n
+is_C9_STRICT_UTF8_CHAR_utf8_no_length_checks|5.025005||Viu
+is_C9_STRICT_UTF8_CHAR_utf8_no_length_checks_part0|5.025008||Viu
+is_C9_STRICT_UTF8_CHAR_utf8_no_length_checks_part1|5.025008||Viu
+is_c9strict_utf8_string|5.025006|5.025006|n
+is_c9strict_utf8_string_loc|5.025006|5.025006|n
+is_c9strict_utf8_string_loclen|5.025006|5.025006|n
+isCHARNAME_CONT|5.011005||Viu
+isCNTRL|5.006000|5.003007|p
+isCNTRL_A|5.013006|5.003007|p
+isCNTRL_L1|5.013006|5.003007|p
+isCNTRL_LC|5.006000|5.006000|
+isCNTRL_LC_utf8|5.006000||Viu
+isCNTRL_LC_utf8_safe|5.025009|5.006000|p
+isCNTRL_LC_uvchr|5.007001|5.007001|
+isCNTRL_uni|5.006000||Viu
+isCNTRL_utf8|5.031005|5.031005|
+isCNTRL_utf8_safe|5.025009|5.006000|p
+isCNTRL_uvchr|5.023009|5.006000|p
+_is_cur_LC_category_utf8|5.021001||cVu
+isDEBUG_WILDCARD|5.031011||Viu
+isDIGIT|5.003007|5.003007|p
+isDIGIT_A|5.013006|5.003007|p
+isDIGIT_L1|5.013006|5.003007|p
+isDIGIT_LC|5.004000|5.004000|
+isDIGIT_LC_utf8|5.006000||Viu
+isDIGIT_LC_utf8_safe|5.025009|5.006000|p
+isDIGIT_LC_uvchr|5.007001|5.007001|
+isDIGIT_uni|5.006000||Viu
+isDIGIT_utf8|5.031005|5.031005|
+isDIGIT_utf8_safe|5.025009|5.006000|p
+isDIGIT_uvchr|5.023009|5.006000|p
+isEXACTFish|5.033003||Viu
+isEXACT_REQ8|5.033003||Viu
+isFF_OVERLONG|5.025007||Vniu
+is_FOLDS_TO_MULTI_utf8|5.019009||Viu
+isFOO_lc|5.017007||Viu
+isFOO_utf8_lc|5.017008||Viu
+isGCB|5.021009||Viu
+isGRAPH|5.006000|5.003007|p
+isGRAPH_A|5.013006|5.003007|p
+is_grapheme|5.031007||Viu
+isGRAPH_L1|5.013006|5.003007|p
+isGRAPH_LC|5.006000|5.006000|
+isGRAPH_LC_utf8|5.006000||Viu
+isGRAPH_LC_utf8_safe|5.025009|5.006000|p
+isGRAPH_LC_uvchr|5.007001|5.007001|
+isGRAPH_uni|5.006000||Viu
+isGRAPH_utf8|5.031005|5.031005|
+isGRAPH_utf8_safe|5.025009|5.006000|p
+isGRAPH_uvchr|5.023009|5.006000|p
+isGV|5.003007||Viu
+isGV_or_RVCV|5.027005||Viu
+isGV_with_GP|5.009004|5.003007|p
+isGV_with_GP_off|5.009005||Viu
+isGV_with_GP_on|5.009005||Viu
+I_SHADOW|5.006000|5.006000|Vn
+is_handle_constructor|5.006000||Vniu
+is_HANGUL_ED_utf8_safe|5.029001||Viu
+is_HORIZWS_cp_high|5.017006||Viu
+is_HORIZWS_high|5.017006||Viu
+isIDCONT|5.017008|5.003007|p
+isIDCONT_A|5.017008|5.003007|p
+isIDCONT_L1|5.017008|5.003007|p
+isIDCONT_LC|5.017008|5.004000|p
+isIDCONT_LC_utf8|5.017008||Viu
+isIDCONT_LC_utf8_safe|5.025009|5.006000|p
+isIDCONT_LC_uvchr|5.017008|5.017008|
+isIDCONT_uni|5.017008||Viu
+isIDCONT_utf8|5.031005|5.031005|
+isIDCONT_utf8_safe|5.025009|5.006000|p
+isIDCONT_uvchr|5.023009|5.006000|p
+isIDFIRST|5.003007|5.003007|p
+isIDFIRST_A|5.013006|5.003007|p
+isIDFIRST_L1|5.013006|5.003007|p
+isIDFIRST_lazy_if_safe|5.025009||Viu
+isIDFIRST_LC|5.004000|5.004000|p
+isIDFIRST_LC_utf8|5.006000||Viu
+isIDFIRST_LC_utf8_safe|5.025009|5.006000|p
+isIDFIRST_LC_uvchr|5.007001|5.007001|
+isIDFIRST_uni|5.006000||Viu
+isIDFIRST_utf8|5.031005|5.031005|
+isIDFIRST_utf8_safe|5.025009|5.006000|p
+isIDFIRST_uvchr|5.023009|5.006000|p
+isinfnan|5.021004|5.021004|n
+isinfnansv|5.021005||Vi
+_is_in_locale_category|5.021001||cViu
+IS_IN_SOME_FOLD_L1|5.033005||Viu
+is_invariant_string|5.021007|5.011000|pn
+is_invlist|5.029002||Vniu
+is_LAX_VERSION|5.011004||Viu
+isLB|5.023007||Viu
+isLEXWARN_off|5.006000||Viu
+isLEXWARN_on|5.006000||Viu
+is_LNBREAK_latin1_safe|5.009005||Viu
+is_LNBREAK_safe|5.009005||Viu
+is_LNBREAK_utf8_safe|5.009005||Viu
+isLOWER|5.003007|5.003007|p
+isLOWER_A|5.013006|5.003007|p
+isLOWER_L1|5.013006|5.003007|p
+isLOWER_LC|5.004000|5.004000|
+isLOWER_LC_utf8|5.006000||Viu
+isLOWER_LC_utf8_safe|5.025009|5.006000|p
+isLOWER_LC_uvchr|5.007001|5.007001|
+isLOWER_uni|5.006000||Viu
+isLOWER_utf8|5.031005|5.031005|
+isLOWER_utf8_safe|5.025009|5.006000|p
+isLOWER_uvchr|5.023009|5.006000|p
+is_lvalue_sub|5.007001|5.007001|u
+isMNEMONIC_CNTRL|5.031009||Viu
+is_MULTI_CHAR_FOLD_latin1_safe|5.019010||Viu
+is_MULTI_CHAR_FOLD_utf8_safe|5.019010||Viu
+is_MULTI_CHAR_FOLD_utf8_safe_part0|5.019010||Viu
+is_MULTI_CHAR_FOLD_utf8_safe_part1|5.019010||Viu
+is_MULTI_CHAR_FOLD_utf8_safe_part2|5.025008||Viu
+is_MULTI_CHAR_FOLD_utf8_safe_part3|5.025008||Viu
+is_NONCHAR_utf8_safe|5.025005||Viu
+IS_NON_FINAL_FOLD|5.033005||Viu
+isnormal|5.021004||Viu
+IS_NUMBER_GREATER_THAN_UV_MAX|5.007002|5.003007|p
+IS_NUMBER_INFINITY|5.007002|5.003007|p
+IS_NUMBER_IN_UV|5.007002|5.003007|p
+IS_NUMBER_NAN|5.007003|5.003007|p
+IS_NUMBER_NEG|5.007002|5.003007|p
+IS_NUMBER_NOT_INT|5.007002|5.003007|p
+IS_NUMBER_TRAILING|5.021002||Viu
+IS_NUMERIC_RADIX|5.006000||Viu
+isOCTAL|5.013005|5.003007|p
+isOCTAL_A|5.013006|5.003007|p
+isOCTAL_L1|5.013006|5.003007|p
+IS_PADCONST|5.006000||Viu
+IS_PADGV|5.006000||Viu
+is_PATWS_safe|5.017008||Viu
+isPOWER_OF_2|5.029006||Viu
+isPRINT|5.004000|5.003007|p
+isPRINT_A|5.013006|5.003007|p
+isPRINT_L1|5.013006|5.003007|p
+isPRINT_LC|5.004000|5.004000|
+isPRINT_LC_utf8|5.006000||Viu
+isPRINT_LC_utf8_safe|5.025009|5.006000|p
+isPRINT_LC_uvchr|5.007001|5.007001|
+isPRINT_uni|5.006000||Viu
+isPRINT_utf8|5.031005|5.031005|
+isPRINT_utf8_safe|5.025009|5.006000|p
+isPRINT_uvchr|5.023009|5.006000|p
+is_PROBLEMATIC_LOCALE_FOLD_cp|5.019009||Viu
+is_PROBLEMATIC_LOCALE_FOLDEDS_START_cp|5.019009||Viu
+is_PROBLEMATIC_LOCALE_FOLDEDS_START_utf8|5.019009||Viu
+is_PROBLEMATIC_LOCALE_FOLD_utf8|5.019009||Viu
+isPSXSPC|5.006001|5.003007|p
+isPSXSPC_A|5.013006|5.003007|p
+isPSXSPC_L1|5.013006|5.003007|p
+isPSXSPC_LC|5.006001|5.006001|
+isPSXSPC_LC_utf8|5.006001||Viu
+isPSXSPC_LC_utf8_safe|5.025009|5.006000|p
+isPSXSPC_LC_uvchr|5.017007|5.017007|
+isPSXSPC_uni|5.006001||Viu
+isPSXSPC_utf8|5.031005|5.031005|
+isPSXSPC_utf8_safe|5.025009|5.006000|p
+isPSXSPC_uvchr|5.023009|5.006000|p
+isPUNCT|5.006000|5.003007|p
+isPUNCT_A|5.013006|5.003007|p
+isPUNCT_L1|5.013006|5.003007|p
+isPUNCT_LC|5.006000|5.006000|
+isPUNCT_LC_utf8|5.006000||Viu
+isPUNCT_LC_utf8_safe|5.025009|5.006000|p
+isPUNCT_LC_uvchr|5.007001|5.007001|
+isPUNCT_uni|5.006000||Viu
+isPUNCT_utf8|5.031005|5.031005|
+isPUNCT_utf8_safe|5.025009|5.006000|p
+isPUNCT_uvchr|5.023009|5.006000|p
+is_QUOTEMETA_high|5.017004||Viu
+is_QUOTEMETA_high_part0|5.021001||Viu
+is_QUOTEMETA_high_part1|5.021001||Viu
+isREGEXP|5.017006||Viu
+IS_SAFE_PATHNAME|5.019004||Viu
+IS_SAFE_SYSCALL|5.019004|5.019004|
+is_safe_syscall|5.019004|5.019004|
+isSB|5.021009||Viu
+isSCRIPT_RUN|5.027008||cVi
+isSPACE|5.003007|5.003007|p
+isSPACE_A|5.013006|5.003007|p
+isSPACE_L1|5.013006|5.003007|p
+isSPACE_LC|5.004000|5.004000|
+isSPACE_LC_utf8|5.006000||Viu
+isSPACE_LC_utf8_safe|5.025009|5.006000|p
+isSPACE_LC_uvchr|5.007001|5.007001|
+isSPACE_uni|5.006000||Viu
+isSPACE_utf8|5.031005|5.031005|
+isSPACE_utf8_safe|5.025009|5.006000|p
+isSPACE_uvchr|5.023009|5.006000|p
+is_ssc_worth_it|5.021005||Vniu
+isSTRICT_UTF8_CHAR|5.025005|5.025005|n
+is_STRICT_UTF8_CHAR_utf8_no_length_checks|5.025005||Viu
+is_STRICT_UTF8_CHAR_utf8_no_length_checks_part0|5.025005||Viu
+is_STRICT_UTF8_CHAR_utf8_no_length_checks_part1|5.025005||Viu
+is_STRICT_UTF8_CHAR_utf8_no_length_checks_part2|5.025008||Viu
+is_STRICT_UTF8_CHAR_utf8_no_length_checks_part3|5.025008||Viu
+is_strict_utf8_string|5.025006|5.025006|n
+is_strict_utf8_string_loc|5.025006|5.025006|n
+is_strict_utf8_string_loclen|5.025006|5.025006|n
+is_STRICT_VERSION|5.011004||Viu
+is_SURROGATE_utf8_safe|5.025005||Viu
+I_STDARG|5.003007||Viu
+I_STDBOOL|5.015003|5.015003|Vn
+I_STDINT|5.021004|5.021004|Vn
+is_THREE_CHAR_FOLD_HEAD_latin1_safe|5.031007||Viu
+is_THREE_CHAR_FOLD_HEAD_utf8_safe|5.031007||Viu
+is_THREE_CHAR_FOLD_latin1_safe|5.031007||Viu
+is_THREE_CHAR_FOLD_utf8_safe|5.031007||Viu
+IS_TRIE_AC|5.009005||Viu
+_is_uni_FOO|5.017008||cVu
+_is_uni_perl_idcont|5.017008||cVu
+_is_uni_perl_idstart|5.017007||cVu
+isUPPER|5.003007|5.003007|p
+isUPPER_A|5.013006|5.003007|p
+isUPPER_L1|5.013006|5.003007|p
+isUPPER_LC|5.004000|5.004000|
+isUPPER_LC_utf8|5.006000||Viu
+isUPPER_LC_utf8_safe|5.025009|5.006000|p
+isUPPER_LC_uvchr|5.007001|5.007001|
+isUPPER_uni|5.006000||Viu
+isUPPER_utf8|5.031005|5.031005|
+isUPPER_utf8_safe|5.025009|5.006000|p
+isUPPER_uvchr|5.023009|5.006000|p
+is_utf8_char|5.006000|5.006000|dn
+IS_UTF8_CHAR|5.009003||Viu
+isUTF8_CHAR|5.021001|5.006001|pn
+is_utf8_char_buf|5.015008|5.015008|n
+isUTF8_CHAR_flags|5.025005|5.025005|
+is_utf8_char_helper|5.031004||cVnu
+is_UTF8_CHAR_utf8_no_length_checks|5.021001||Viu
+is_utf8_common|5.009003||Viu
+is_utf8_cp_above_31_bits|5.025005||Vniu
+is_utf8_fixed_width_buf_flags|5.025006|5.025006|n
+is_utf8_fixed_width_buf_loc_flags|5.025006|5.025006|n
+is_utf8_fixed_width_buf_loclen_flags|5.025006|5.025006|n
+_is_utf8_FOO|5.031006||cVu
+is_utf8_invariant_string|5.025005|5.011000|pn
+is_utf8_invariant_string_loc|5.027001|5.027001|n
+is_utf8_non_invariant_string|5.027007||cVni
+is_utf8_overlong_given_start_byte_ok|5.025006||Vniu
+_is_utf8_perl_idcont|5.031006||cVu
+_is_utf8_perl_idstart|5.031006||cVu
+isUTF8_POSSIBLY_PROBLEMATIC|5.023003||Viu
+is_utf8_string|5.006001|5.006001|n
+is_utf8_string_flags|5.025006|5.025006|n
+is_utf8_string_loc|5.008001|5.008001|n
+is_utf8_string_loc_flags|5.025006|5.025006|n
+is_utf8_string_loclen|5.009003|5.009003|n
+is_utf8_string_loclen_flags|5.025006|5.025006|n
+is_utf8_valid_partial_char|5.025005|5.025005|n
+is_utf8_valid_partial_char_flags|5.025005|5.025005|n
+is_VERTWS_cp_high|5.017006||Viu
+is_VERTWS_high|5.017006||Viu
+isVERTWS_uni|5.017006||Viu
+isVERTWS_utf8|5.017006||Viu
+isVERTWS_utf8_safe|5.025009||Viu
+isVERTWS_uvchr|5.023009||Viu
+isWARNf_on|5.006001||Viu
+isWARN_on|5.006000||Viu
+isWARN_ONCE|5.006000||Viu
+isWB|5.021009||Viu
+isWORDCHAR|5.013006|5.003007|p
+isWORDCHAR_A|5.013006|5.003007|p
+isWORDCHAR_L1|5.013006|5.003007|p
+isWORDCHAR_lazy_if_safe|5.025009||Viu
+isWORDCHAR_LC|5.017007|5.004000|p
+isWORDCHAR_LC_utf8|5.017007||Viu
+isWORDCHAR_LC_utf8_safe|5.025009|5.006000|p
+isWORDCHAR_LC_uvchr|5.017007|5.017007|
+isWORDCHAR_uni|5.017006||Viu
+isWORDCHAR_utf8|5.031005|5.031005|
+isWORDCHAR_utf8_safe|5.025009|5.006000|p
+isWORDCHAR_uvchr|5.023009|5.006000|p
+isXDIGIT|5.006000|5.003007|p
+isXDIGIT_A|5.013006|5.003007|p
+is_XDIGIT_cp_high|5.017006||Viu
+is_XDIGIT_high|5.017006||Viu
+isXDIGIT_L1|5.013006|5.003007|p
+isXDIGIT_LC|5.017007|5.003007|p
+isXDIGIT_LC_utf8|5.017007||Viu
+isXDIGIT_LC_utf8_safe|5.025009|5.006000|p
+isXDIGIT_LC_uvchr|5.017007|5.017007|
+isXDIGIT_uni|5.006000||Viu
+isXDIGIT_utf8|5.031005|5.031005|
+isXDIGIT_utf8_safe|5.025009|5.006000|p
+isXDIGIT_uvchr|5.023009|5.006000|p
+is_XPERLSPACE_cp_high|5.017006||Viu
+is_XPERLSPACE_high|5.017006||Viu
+I_SYS_DIR|5.003007|5.003007|Vn
+I_SYS_FILE|5.003007|5.003007|Vn
+I_SYS_IOCTL|5.003007|5.003007|Vn
+I_SYSLOG|5.006000|5.006000|Vn
+I_SYS_MOUNT|5.023005|5.023005|Vn
+I_SYS_PARAM|5.003007|5.003007|Vn
+I_SYS_POLL|5.010001|5.010001|Vn
+I_SYS_RESOURCE|5.003007|5.003007|Vn
+I_SYS_SELECT|5.003007|5.003007|Vn
+I_SYS_STAT|5.003007|5.003007|Vn
+I_SYS_STATFS|5.023005|5.023005|Vn
+I_SYS_STATVFS|5.023005|5.023005|Vn
+I_SYS_TIME|5.003007|5.003007|Vn
+I_SYS_TIMES|5.003007|5.003007|Vn
+I_SYS_TYPES|5.003007|5.003007|Vn
+I_SYSUIO|5.006000|5.006000|Vn
+I_SYS_UN|5.003007|5.003007|Vn
+I_SYSUTSNAME|5.006000|5.006000|Vn
+I_SYS_VFS|5.023005|5.023005|Vn
+I_SYS_WAIT|5.003007|5.003007|Vn
+items||5.003007|
+I_TERMIOS|5.003007|5.003007|Vn
+I_TIME|5.003007|5.003007|Vn
+I_UNISTD|5.003007|5.003007|Vn
+I_USTAT|5.023005|5.023005|Vn
+I_UTIME|5.003007|5.003007|Vn
+I_V|5.006000|5.003007|
+IVdf|5.006000|5.003007|poVn
+IV_DIG|5.006000||Viu
+IV_IS_QUAD|5.006000||Viu
+IV_MAX|5.003007|5.003007|
+IV_MAX_P1|5.007002||Viu
+IV_MIN|5.003007|5.003007|
+IVSIZE|5.006000|5.003007|poVn
+IVTYPE|5.006000|5.003007|poVn
+I_WCHAR|5.027006|5.027006|Vn
+I_WCTYPE|5.029009|5.029009|Vn
+ix||5.003007|
+I_XLOCALE|5.025004|5.025004|Vn
+JE_OLD_STACK_HWM_restore|5.027002||Viu
+JE_OLD_STACK_HWM_save|5.027002||Viu
+JE_OLD_STACK_HWM_zero|5.027002||Viu
+jmaybe|5.003007||Viu
+JMPENV_BOOTSTRAP|5.006000||Viu
+JMPENV_JUMP|5.004000|5.004000|
+JMPENV_POP|5.004000||Viu
+JMPENV_PUSH|5.004000||Viu
+JOIN|5.005000||Viu
+join_exact|5.009004||Viu
+kBINOP|5.003007||Viu
+kCOP|5.003007||Viu
+KEEPCOPY_PAT_MOD|5.009005||Viu
+KEEPCOPY_PAT_MODS|5.009005||Viu
+KEEPS|5.009005||Viu
+KEEPS_next|5.009005||Viu
+KEEPS_next_fail|5.009005||Viu
+KEEPS_next_fail_t8_p8|5.033003||Viu
+KEEPS_next_fail_t8_pb|5.033003||Viu
+KEEPS_next_fail_tb_p8|5.033003||Viu
+KEEPS_next_fail_tb_pb|5.033003||Viu
+KEEPS_next_t8_p8|5.033003||Viu
+KEEPS_next_t8_pb|5.033003||Viu
+KEEPS_next_tb_p8|5.033003||Viu
+KEEPS_next_tb_pb|5.033003||Viu
+KEEPS_t8_p8|5.033003||Viu
+KEEPS_t8_pb|5.033003||Viu
+KEEPS_tb_p8|5.033003||Viu
+KEEPS_tb_pb|5.033003||Viu
+KELVIN_SIGN|5.017003||Viu
+KERNEL|5.003007||Viu
+KEY_abs|5.003007||Viu
+KEY_accept|5.003007||Viu
+KEY_alarm|5.003007||Viu
+KEY_and|5.003007||Viu
+KEY_atan2|5.003007||Viu
+KEY_AUTOLOAD|5.003007||Viu
+KEY_BEGIN|5.003007||Viu
+KEY_bind|5.003007||Viu
+KEY_binmode|5.003007||Viu
+KEY_bless|5.003007||Viu
+KEY_break|5.027008||Viu
+KEY_caller|5.003007||Viu
+KEY_catch|5.033007||Viu
+KEY_chdir|5.003007||Viu
+KEY_CHECK|5.006000||Viu
+KEY_chmod|5.003007||Viu
+KEY_chomp|5.003007||Viu
+KEY_chop|5.003007||Viu
+KEY_chown|5.003007||Viu
+KEY_chr|5.003007||Viu
+KEY_chroot|5.003007||Viu
+KEY_close|5.003007||Viu
+KEY_closedir|5.003007||Viu
+KEY_cmp|5.003007||Viu
+KEY_connect|5.003007||Viu
+KEY_continue|5.003007||Viu
+KEY_cos|5.003007||Viu
+KEY_crypt|5.003007||Viu
+KEY___DATA|5.003007||Viu
+KEY_dbmclose|5.003007||Viu
+KEY_dbmopen|5.003007||Viu
+KEY_default|5.027008||Viu
+KEY_defined|5.003007||Viu
+KEY_delete|5.003007||Viu
+KEY_DESTROY|5.003007||Viu
+KEY_die|5.003007||Viu
+KEY_do|5.003007||Viu
+KEY_dump|5.003007||Viu
+KEY_each|5.003007||Viu
+KEY_else|5.003007||Viu
+KEY_elsif|5.003007||Viu
+KEY___END|5.003007||Viu
+KEY_END|5.003007||Viu
+KEY_endgrent|5.003007||Viu
+KEY_endhostent|5.003007||Viu
+KEY_endnetent|5.003007||Viu
+KEY_endprotoent|5.003007||Viu
+KEY_endpwent|5.003007||Viu
+KEY_endservent|5.003007||Viu
+KEY_eof|5.003007||Viu
+KEY_eq|5.003007||Viu
+KEY_eval|5.003007||Viu
+KEY_evalbytes|5.015005||Viu
+KEY_exec|5.003007||Viu
+KEY_exists|5.003007||Viu
+KEY_exit|5.003007||Viu
+KEY_exp|5.003007||Viu
+KEY_fc|5.015008||Viu
+KEY_fcntl|5.003007||Viu
+KEY___FILE|5.003007||Viu
+KEY_fileno|5.003007||Viu
+KEY_flock|5.003007||Viu
+KEY_for|5.003007||Viu
+KEY_foreach|5.003007||Viu
+KEY_fork|5.003007||Viu
+KEY_format|5.003007||Viu
+KEY_formline|5.003007||Viu
+KEY_ge|5.003007||Viu
+KEY_getc|5.003007||Viu
+KEY_getgrent|5.003007||Viu
+KEY_getgrgid|5.003007||Viu
+KEY_getgrnam|5.003007||Viu
+KEY_gethostbyaddr|5.003007||Viu
+KEY_gethostbyname|5.003007||Viu
+KEY_gethostent|5.003007||Viu
+KEY_getlogin|5.003007||Viu
+KEY_getnetbyaddr|5.003007||Viu
+KEY_getnetbyname|5.003007||Viu
+KEY_getnetent|5.003007||Viu
+KEY_getpeername|5.003007||Viu
+KEY_getpgrp|5.003007||Viu
+KEY_getppid|5.003007||Viu
+KEY_getpriority|5.003007||Viu
+KEY_getprotobyname|5.003007||Viu
+KEY_getprotobynumber|5.003007||Viu
+KEY_getprotoent|5.003007||Viu
+KEY_getpwent|5.003007||Viu
+KEY_getpwnam|5.003007||Viu
+KEY_getpwuid|5.003007||Viu
+KEY_getservbyname|5.003007||Viu
+KEY_getservbyport|5.003007||Viu
+KEY_getservent|5.003007||Viu
+KEY_getsockname|5.003007||Viu
+KEY_getsockopt|5.003007||Viu
+KEY_getspnam|5.031011||Viu
+KEY_given|5.009003||Viu
+KEY_glob|5.003007||Viu
+KEY_gmtime|5.003007||Viu
+KEY_goto|5.003007||Viu
+KEY_grep|5.003007||Viu
+KEY_gt|5.003007||Viu
+KEY_hex|5.003007||Viu
+KEY_if|5.003007||Viu
+KEY_index|5.003007||Viu
+KEY_INIT|5.005000||Viu
+KEY_int|5.003007||Viu
+KEY_ioctl|5.003007||Viu
+KEY_isa|5.031007||Viu
+KEY_join|5.003007||Viu
+KEY_keys|5.003007||Viu
+KEY_kill|5.003007||Viu
+KEY_last|5.003007||Viu
+KEY_lc|5.003007||Viu
+KEY_lcfirst|5.003007||Viu
+KEY_le|5.003007||Viu
+KEY_length|5.003007||Viu
+KEY___LINE|5.003007||Viu
+KEY_link|5.003007||Viu
+KEY_listen|5.003007||Viu
+KEY_local|5.003007||Viu
+KEY_localtime|5.003007||Viu
+KEY_lock|5.005000||Viu
+KEY_log|5.003007||Viu
+KEY_lstat|5.003007||Viu
+KEY_lt|5.003007||Viu
+KEY_m|5.003007||Viu
+KEY_map|5.003007||Viu
+KEY_mkdir|5.003007||Viu
+KEY_msgctl|5.003007||Viu
+KEY_msgget|5.003007||Viu
+KEY_msgrcv|5.003007||Viu
+KEY_msgsnd|5.003007||Viu
+KEY_my|5.003007||Viu
+KEY_ne|5.003007||Viu
+KEY_next|5.003007||Viu
+KEY_no|5.003007||Viu
+KEY_not|5.003007||Viu
+KEY_NULL|5.003007||Viu
+KEY_oct|5.003007||Viu
+KEY_open|5.003007||Viu
+KEY_opendir|5.003007||Viu
+KEY_or|5.003007||Viu
+KEY_ord|5.003007||Viu
+KEY_our|5.006000||Viu
+KEY_pack|5.003007||Viu
+KEY_package|5.003007||Viu
+KEY___PACKAGE|5.004000||Viu
+KEY_pipe|5.003007||Viu
+KEY_pop|5.003007||Viu
+KEY_pos|5.003007||Viu
+KEY_print|5.003007||Viu
+KEY_printf|5.003007||Viu
+KEY_prototype|5.003007||Viu
+KEY_push|5.003007||Viu
+KEY_q|5.003007||Viu
+KEY_qq|5.003007||Viu
+KEY_qr|5.005000||Viu
+KEY_quotemeta|5.003007||Viu
+KEY_qw|5.003007||Viu
+KEY_qx|5.003007||Viu
+KEY_rand|5.003007||Viu
+KEY_read|5.003007||Viu
+KEY_readdir|5.003007||Viu
+KEY_readline|5.003007||Viu
+KEY_readlink|5.003007||Viu
+KEY_readpipe|5.003007||Viu
+KEY_recv|5.003007||Viu
+KEY_redo|5.003007||Viu
+KEY_ref|5.003007||Viu
+KEY_rename|5.003007||Viu
+KEY_require|5.003007||Viu
+KEY_reset|5.003007||Viu
+KEY_return|5.003007||Viu
+KEY_reverse|5.003007||Viu
+KEY_rewinddir|5.003007||Viu
+KEY_rindex|5.003007||Viu
+KEY_rmdir|5.003007||Viu
+KEY_s|5.003007||Viu
+KEY_say|5.009003||Viu
+KEY_scalar|5.003007||Viu
+KEY_seek|5.003007||Viu
+KEY_seekdir|5.003007||Viu
+KEY_select|5.003007||Viu
+KEY_semctl|5.003007||Viu
+KEY_semget|5.003007||Viu
+KEY_semop|5.003007||Viu
+KEY_send|5.003007||Viu
+KEY_setgrent|5.003007||Viu
+KEY_sethostent|5.003007||Viu
+KEY_setnetent|5.003007||Viu
+KEY_setpgrp|5.003007||Viu
+KEY_setpriority|5.003007||Viu
+KEY_setprotoent|5.003007||Viu
+KEY_setpwent|5.003007||Viu
+KEY_setservent|5.003007||Viu
+KEY_setsockopt|5.003007||Viu
+KEY_shift|5.003007||Viu
+KEY_shmctl|5.003007||Viu
+KEY_shmget|5.003007||Viu
+KEY_shmread|5.003007||Viu
+KEY_shmwrite|5.003007||Viu
+KEY_shutdown|5.003007||Viu
+KEY_sigvar|5.025004||Viu
+KEY_sin|5.003007||Viu
+KEY_sleep|5.003007||Viu
+KEY_socket|5.003007||Viu
+KEY_socketpair|5.003007||Viu
+KEY_sort|5.003007||Viu
+KEY_splice|5.003007||Viu
+KEY_split|5.003007||Viu
+KEY_sprintf|5.003007||Viu
+KEY_sqrt|5.003007||Viu
+KEY_srand|5.003007||Viu
+KEY_stat|5.003007||Viu
+KEY_state|5.009004||Viu
+KEY_study|5.003007||Viu
+KEY_sub|5.003007||Viu
+KEY___SUB|5.015006||Viu
+KEY_substr|5.003007||Viu
+KEY_symlink|5.003007||Viu
+KEY_syscall|5.003007||Viu
+KEY_sysopen|5.003007||Viu
+KEY_sysread|5.003007||Viu
+KEY_sysseek|5.004000||Viu
+KEY_system|5.003007||Viu
+KEY_syswrite|5.003007||Viu
+KEY_tell|5.003007||Viu
+KEY_telldir|5.003007||Viu
+KEY_tie|5.003007||Viu
+KEY_tied|5.003007||Viu
+KEY_time|5.003007||Viu
+KEY_times|5.003007||Viu
+KEY_tr|5.003007||Viu
+KEY_truncate|5.003007||Viu
+KEY_try|5.033007||Viu
+KEY_uc|5.003007||Viu
+KEY_ucfirst|5.003007||Viu
+KEY_umask|5.003007||Viu
+KEY_undef|5.003007||Viu
+KEY_UNITCHECK|5.009005||Viu
+KEY_unless|5.003007||Viu
+KEY_unlink|5.003007||Viu
+KEY_unpack|5.003007||Viu
+KEY_unshift|5.003007||Viu
+KEY_untie|5.003007||Viu
+KEY_until|5.003007||Viu
+KEY_use|5.003007||Viu
+KEY_utime|5.003007||Viu
+KEY_values|5.003007||Viu
+KEY_vec|5.003007||Viu
+KEY_wait|5.003007||Viu
+KEY_waitpid|5.003007||Viu
+KEY_wantarray|5.003007||Viu
+KEY_warn|5.003007||Viu
+KEY_when|5.027008||Viu
+KEY_while|5.003007||Viu
+keyword|5.003007||Viu
+KEYWORD_PLUGIN_DECLINE|5.011002||Viu
+KEYWORD_PLUGIN_EXPR|5.011002||Viu
+KEYWORD_PLUGIN_MUTEX_INIT|5.027006||Viu
+KEYWORD_PLUGIN_MUTEX_LOCK|5.027006||Viu
+KEYWORD_PLUGIN_MUTEX_TERM|5.027006||Viu
+KEYWORD_PLUGIN_MUTEX_UNLOCK|5.027006||Viu
+keyword_plugin_standard|||iu
+KEYWORD_PLUGIN_STMT|5.011002||Viu
+KEY_write|5.003007||Viu
+KEY_x|5.003007||Viu
+KEY_xor|5.003007||Viu
+KEY_y|5.003007||Viu
+kGVOP_gv|5.006000||Viu
+kill|5.005000||Viu
+killpg|5.005000||Viu
+kLISTOP|5.003007||Viu
+kLOGOP|5.003007||Viu
+kLOOP|5.003007||Viu
+kPADOP|5.006000||Viu
+kPMOP|5.003007||Viu
+kPVOP|5.003007||Viu
+kSVOP|5.003007||Viu
+kSVOP_sv|5.006000||Viu
+kUNOP|5.003007||Viu
+kUNOP_AUX|5.021007||Viu
+LATIN1_TO_NATIVE|5.019004|5.003007|p
+LATIN_CAPITAL_LETTER_A_WITH_RING_ABOVE|5.013011||Viu
+LATIN_CAPITAL_LETTER_A_WITH_RING_ABOVE_NATIVE|5.017004||Viu
+LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE|5.023002||Viu
+LATIN_CAPITAL_LETTER_I_WITH_DOT_ABOVE_UTF8|5.023002||Viu
+LATIN_CAPITAL_LETTER_SHARP_S|5.014000||Viu
+LATIN_CAPITAL_LETTER_SHARP_S_UTF8|5.019001||Viu
+LATIN_CAPITAL_LETTER_Y_WITH_DIAERESIS|5.013011||Viu
+LATIN_SMALL_LETTER_A_WITH_RING_ABOVE|5.013011||Viu
+LATIN_SMALL_LETTER_A_WITH_RING_ABOVE_NATIVE|5.017004||Viu
+LATIN_SMALL_LETTER_DOTLESS_I|5.023002||Viu
+LATIN_SMALL_LETTER_DOTLESS_I_UTF8|5.023002||Viu
+LATIN_SMALL_LETTER_LONG_S|5.017003||Viu
+LATIN_SMALL_LETTER_LONG_S_UTF8|5.019001||Viu
+LATIN_SMALL_LETTER_SHARP_S|5.011002||Viu
+LATIN_SMALL_LETTER_SHARP_S_NATIVE|5.017004||Viu
+LATIN_SMALL_LETTER_SHARP_S_UTF8|5.033003||Viu
+LATIN_SMALL_LETTER_Y_WITH_DIAERESIS|5.011002||Viu
+LATIN_SMALL_LETTER_Y_WITH_DIAERESIS_NATIVE|5.017004||Viu
+LATIN_SMALL_LIGATURE_LONG_S_T|5.019004||Viu
+LATIN_SMALL_LIGATURE_LONG_S_T_UTF8|5.019004||Viu
+LATIN_SMALL_LIGATURE_ST|5.019004||Viu
+LATIN_SMALL_LIGATURE_ST_UTF8|5.019004||Viu
+LB_BREAKABLE|5.023007||Viu
+LB_CM_ZWJ_foo|5.025003||Viu
+LB_HY_or_BA_then_foo|5.023007||Viu
+LB_NOBREAK|5.023007||Viu
+LB_NOBREAK_EVEN_WITH_SP_BETWEEN|5.023007||Viu
+LB_PR_or_PO_then_OP_or_HY|5.023007||Viu
+LB_RI_then_RI|5.025003||Viu
+LB_SP_foo|5.023007||Viu
+LB_SY_or_IS_then_various|5.023007||Viu
+LB_various_then_PO_or_PR|5.023007||Viu
+LC_NUMERIC_LOCK|5.027009||pVu
+LC_NUMERIC_UNLOCK|5.027009||pVu
+LDBL_DIG|5.006000||Viu
+LEAVE|5.003007|5.003007|
+leave_adjust_stacks|5.023008|5.023008|xu
+leave_scope|5.003007|5.003007|u
+LEAVE_SCOPE|5.003007||Viu
+LEAVE_with_name|5.011002|5.011002|
+LEXACT|5.031005||Viu
+LEXACT_REQ8|5.031006||Viu
+LEXACT_REQ8_t8_p8|5.033003||Viu
+LEXACT_REQ8_t8_pb|5.033003||Viu
+LEXACT_REQ8_tb_p8|5.033003||Viu
+LEXACT_REQ8_tb_pb|5.033003||Viu
+LEXACT_t8_p8|5.033003||Viu
+LEXACT_t8_pb|5.033003||Viu
+LEXACT_tb_p8|5.033003||Viu
+LEXACT_tb_pb|5.033003||Viu
+lex_bufutf8|5.011002|5.011002|x
+lex_discard_to|5.011002|5.011002|x
+LEX_DONT_CLOSE_RSFP|5.015009||Viu
+LEX_EVALBYTES|5.015005||Viu
+lex_grow_linestr|5.011002|5.011002|x
+LEX_IGNORE_UTF8_HINTS|5.015005||Viu
+LEX_KEEP_PREVIOUS|5.011002|5.011002|
+lex_next_chunk|5.011002|5.011002|x
+LEX_NOTPARSING|5.004004||Viu
+lex_peek_unichar|5.011002|5.011002|x
+lex_read_space|5.011002|5.011002|x
+lex_read_to|5.011002|5.011002|x
+lex_read_unichar|5.011002|5.011002|x
+lex_start|5.009005|5.009005|x
+LEX_START_COPIED|5.015005||Viu
+LEX_START_FLAGS|5.015005||Viu
+LEX_START_SAME_FILTER|5.014000||Viu
+lex_stuff_pv|5.013006|5.013006|x
+lex_stuff_pvn|5.011002|5.011002|x
+lex_stuff_pvs|5.013005|5.013005|x
+lex_stuff_sv|5.011002|5.011002|x
+LEX_STUFF_UTF8|5.011002|5.011002|
+lex_unstuff|5.011002|5.011002|x
+LF_NATIVE|5.019004||Viu
+LIB_INVARG|5.008001||Viu
+LIBM_LIB_VERSION|5.009003|5.009003|Vn
+LIKELY|5.009004|5.003007|p
+link|5.006000||Viu
+LINKLIST|5.013006|5.013006|
+list|5.003007||Viu
+listen|5.005000||Viu
+listkids|5.003007||Viu
+LNBREAK|5.009005||Viu
+LNBREAK_t8_p8|5.033003||Viu
+LNBREAK_t8_pb|5.033003||Viu
+LNBREAK_tb_p8|5.033003||Viu
+LNBREAK_tb_pb|5.033003||Viu
+load_charnames|5.031010||cViu
+load_module|5.006000|5.003007|pv
+load_module_nocontext|5.006000||vVn
+LOCALECONV_LOCK|5.033005||Viu
+LOCALECONV_UNLOCK|5.033005||Viu
+LOCALE_INIT|5.024000||Viu
+LOCALE_INIT_LC_NUMERIC|5.033005||Viu
+LOCALE_LOCK|5.024000||Viu
+LOCALE_PAT_MOD|5.013006||Viu
+LOCALE_PAT_MODS|5.013006||Viu
+LOCALE_READ_LOCK|5.033005||Viu
+LOCALE_READ_UNLOCK|5.033005||Viu
+LOCALE_TERM|5.024000||Viu
+LOCALE_TERM_LC_NUMERIC|5.033005||Viu
+LOCALE_TERM_POSIX_2008|5.033005||Viu
+LOCALE_UNLOCK|5.024000||Viu
+localize|5.003007||Viu
+LOCAL_PATCH_COUNT|5.003007||Viu
+localtime|5.031011||Viu
+LOCALTIME_MAX|5.010001|5.010001|Vn
+LOCALTIME_MIN|5.010001|5.010001|Vn
+LOCALTIME_R_NEEDS_TZSET|5.010000|5.010000|Vn
+LOCALTIME_R_PROTO|5.008000|5.008000|Vn
+LOCK_DOLLARZERO_MUTEX|5.008001||Viu
+lockf|5.006000||Viu
+LOCK_LC_NUMERIC_STANDARD|5.021010||poVnu
+LOCK_NUMERIC_STANDARD|||piu
+LOC_SED|5.003007|5.003007|Vn
+LOGICAL|5.005000||Viu
+LOGICAL_t8_p8|5.033003||Viu
+LOGICAL_t8_pb|5.033003||Viu
+LOGICAL_tb_p8|5.033003||Viu
+LOGICAL_tb_pb|5.033003||Viu
+LONGDBLINFBYTES|5.023000|5.023000|Vn
+LONGDBLMANTBITS|5.023000|5.023000|Vn
+LONGDBLNANBYTES|5.023000|5.023000|Vn
+LONGDOUBLE_BIG_ENDIAN|5.021009||Viu
+LONGDOUBLE_DOUBLEDOUBLE|5.021009||Viu
+LONG_DOUBLE_EQUALS_DOUBLE|5.007001||Viu
+LONG_DOUBLE_IS_DOUBLE|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_BE_BE|5.023006|5.023006|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_BE_LE|5.023006|5.023006|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_BIG_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_LE_BE|5.023006|5.023006|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_LE_LE|5.023006|5.023006|Vn
+LONG_DOUBLE_IS_DOUBLEDOUBLE_128_BIT_LITTLE_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_IEEE_754_128_BIT_BIG_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_IEEE_754_128_BIT_LITTLE_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_UNKNOWN_FORMAT|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_VAX_H_FLOAT|5.025004|5.025004|Vn
+LONG_DOUBLE_IS_X86_80_BIT_BIG_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLE_IS_X86_80_BIT_LITTLE_ENDIAN|5.021003|5.021003|Vn
+LONG_DOUBLEKIND|5.021003|5.021003|Vn
+LONGDOUBLE_LITTLE_ENDIAN|5.021009||Viu
+LONGDOUBLE_MIX_ENDIAN|5.023006||Viu
+LONG_DOUBLESIZE|5.005000|5.005000|Vn
+LONG_DOUBLE_STYLE_IEEE|5.025007|5.025007|Vn
+LONG_DOUBLE_STYLE_IEEE_EXTENDED|5.025007|5.025007|Vn
+LONGDOUBLE_VAX_ENDIAN|5.025004||Viu
+LONGDOUBLE_X86_80_BIT|5.021009||Viu
+LONGJMP|5.005000||Viu
+longjmp|5.005000||Viu
+LONGJMP_t8_p8|5.033003||Viu
+LONGJMP_t8_pb|5.033003||Viu
+LONGJMP_tb_p8|5.033003||Viu
+LONGJMP_tb_pb|5.033003||Viu
+LONGLONGSIZE|5.005000|5.005000|Vn
+LONGSIZE|5.004000|5.003007|oVn
+looks_like_bool|5.027008||Viu
+looks_like_number|5.003007|5.003007|
+LOOP_PAT_MODS|5.009005||Viu
+lop|5.005000||Viu
+lossless_NV_to_IV|5.031001||Vniu
+LOWEST_ANYOF_HRx_BYTE|5.031002||Viu
+L_R_TZSET|5.009005|5.009005|Vn
+lseek|5.005000||Viu
+LSEEKSIZE|5.006000|5.006000|Vn
+lstat|5.005000||Viu
+LvFLAGS|5.015006||Viu
+LVf_NEG_LEN|5.027001||Viu
+LVf_NEG_OFF|5.027001||Viu
+LVf_OUT_OF_RANGE|5.027001||Viu
+LVRET|5.007001||Vi
+LvSTARGOFF|5.019004||Viu
+LvTARG|5.003007||Viu
+LvTARGLEN|5.003007||Viu
+LvTARGOFF|5.003007||Viu
+LvTYPE|5.003007||Viu
+magic_clear_all_env|5.004001||Viu
+magic_cleararylen_p|5.017002||Viu
+magic_clearenv|5.003007||Viu
+magic_clearhint|5.009004||Vi
+magic_clearhints|5.011000||Vi
+magic_clearisa|5.010001||Viu
+magic_clearpack|5.003007||Viu
+magic_clearsig|5.003007||Viu
+magic_copycallchecker|5.017000||Viu
+magic_dump|5.006000|5.006000|u
+magic_existspack|5.003007||Viu
+magic_freearylen_p|5.009003||Viu
+magic_freecollxfrm|5.033004||Viu
+magic_freemglob|5.033004||Viu
+magic_freeovrld|5.007001||Viu
+magic_freeutf8|5.033004||Viu
+magic_get|5.003007||Viu
+magic_getarylen|5.003007||Viu
+magic_getdebugvar|5.021005||Viu
+magic_getdefelem|5.004000||Viu
+magic_getnkeys|5.004005||Viu
+magic_getpack|5.003007||Viu
+magic_getpos|5.003007||Viu
+magic_getsig|5.003007||Viu
+magic_getsubstr|5.004005||Viu
+magic_gettaint|5.003007||Viu
+magic_getuvar|5.003007||Viu
+magic_getvec|5.004005||Viu
+magic_killbackrefs|5.006000||Viu
+magic_methcall1|5.013001||Viu
+magic_methcall|||vi
+magic_methpack|5.005000||Viu
+magic_nextpack|5.003007||Viu
+magic_regdata_cnt|5.006000||Viu
+magic_regdatum_get|5.006000||Viu
+magic_regdatum_set|5.006001||Viu
+magic_scalarpack|5.009001||Viu
+magic_set|5.003007||Viu
+magic_set_all_env|5.004004||Viu
+magic_setarylen|5.003007||Viu
+magic_setcollxfrm|5.004000||Viu
+magic_setdbline|5.003007||Viu
+magic_setdebugvar|5.021005||Viu
+magic_setdefelem|5.004000||Viu
+magic_setenv|5.003007||Viu
+magic_sethint|5.009004||Vi
+magic_sethint_feature|5.031007||Viu
+magic_setisa|5.003007||Viu
+magic_setlvref|5.021005||Viu
+magic_setmglob|5.003007||Viu
+magic_setnkeys|5.003007||Viu
+magic_setnonelem|5.027009||Viu
+magic_setpack|5.003007||Viu
+magic_setpos|5.003007||Viu
+magic_setregexp|5.008001||Viu
+magic_setsig|5.003007||Viu
+magic_setsigall|5.035001||Viu
+magic_setsubstr|5.003007||Viu
+magic_settaint|5.003007||Viu
+magic_setutf8|5.008001||Viu
+magic_setuvar|5.003007||Viu
+magic_setvec|5.003007||Viu
+magic_sizepack|5.005000||Viu
+magic_wipepack|5.003007||Viu
+make_exactf_invlist|5.031006||Viu
+make_matcher|5.027008||Viu
+make_trie|5.009002||Viu
+malloc|5.007002|5.007002|n
+MALLOC_CHECK_TAINT2|5.008001||Viu
+MALLOC_CHECK_TAINT|5.008001||Viu
+malloced_size|5.005000||Vniu
+malloc_good_size|5.010001||Vniu
+MALLOC_INIT|5.005000||Viu
+MALLOC_OVERHEAD|5.006000||Viu
+Malloc_t|5.003007|5.003007|Vn
+MALLOC_TERM|5.005000||Viu
+MALLOC_TOO_LATE_FOR|5.008001||Viu
+MARK|5.003007|5.003007|
+MARKPOINT|5.009005||Viu
+MARKPOINT_next|5.009005||Viu
+MARKPOINT_next_fail|5.009005||Viu
+MARKPOINT_next_fail_t8_p8|5.033003||Viu
+MARKPOINT_next_fail_t8_pb|5.033003||Viu
+MARKPOINT_next_fail_tb_p8|5.033003||Viu
+MARKPOINT_next_fail_tb_pb|5.033003||Viu
+MARKPOINT_next_t8_p8|5.033003||Viu
+MARKPOINT_next_t8_pb|5.033003||Viu
+MARKPOINT_next_tb_p8|5.033003||Viu
+MARKPOINT_next_tb_pb|5.033003||Viu
+MARKPOINT_t8_p8|5.033003||Viu
+MARKPOINT_t8_pb|5.033003||Viu
+MARKPOINT_tb_p8|5.033003||Viu
+MARKPOINT_tb_pb|5.033003||Viu
+markstack_grow|5.021001|5.021001|u
+matcher_matches_sv|5.027008||Viu
+MAX|5.025006||Viu
+MAX_ANYOF_HRx_BYTE|5.031002||Viu
+MAXARG|5.003007||Viu
+MAX_CHARSET_NAME_LENGTH|5.013009||Viu
+MAX_FEATURE_LEN|5.013010||Viu
+MAX_FOLD_FROMS|5.029006||Viu
+MAX_LEGAL_CP|5.029002||Viu
+MAX_MATCHES|5.033005||Viu
+MAXO|5.003007||Viu
+MAXPATHLEN|5.006000||Viu
+MAX_PORTABLE_UTF8_TWO_BYTE|5.011002||Viu
+MAX_PRINT_A|5.033005||Viu
+MAX_RECURSE_EVAL_NOCHANGE_DEPTH|5.009005||Viu
+MAXSYSFD|5.003007||Viu
+MAX_UNICODE_UTF8|5.027006||Viu
+MAX_UNI_KEYWORD_INDEX|5.027011||Viu
+MAX_UTF8_TWO_BYTE|5.019004||Viu
+MAYBE_DEREF_GV|5.015003||Viu
+MAYBE_DEREF_GV_flags|5.015003||Viu
+MAYBE_DEREF_GV_nomg|5.015003||Viu
+maybe_multimagic_gv|5.019004||Viu
+mayberelocate|5.015006||Viu
+MBLEN_LOCK|5.033005||Viu
+MBLEN_UNLOCK|5.033005||Viu
+MBOL|5.003007||Viu
+MBOL_t8_p8|5.033003||Viu
+MBOL_t8_pb|5.033003||Viu
+MBOL_tb_p8|5.033003||Viu
+MBOL_tb_pb|5.033003||Viu
+MBTOWC_LOCK|5.033005||Viu
+MBTOWC_UNLOCK|5.033005||Viu
+MDEREF_ACTION_MASK|5.021007||Viu
+MDEREF_AV_gvav_aelem|5.021007||Viu
+MDEREF_AV_gvsv_vivify_rv2av_aelem|5.021007||Viu
+MDEREF_AV_padav_aelem|5.021007||Viu
+MDEREF_AV_padsv_vivify_rv2av_aelem|5.021007||Viu
+MDEREF_AV_pop_rv2av_aelem|5.021007||Viu
+MDEREF_AV_vivify_rv2av_aelem|5.021007||Viu
+MDEREF_FLAG_last|5.021007||Viu
+MDEREF_HV_gvhv_helem|5.021007||Viu
+MDEREF_HV_gvsv_vivify_rv2hv_helem|5.021007||Viu
+MDEREF_HV_padhv_helem|5.021007||Viu
+MDEREF_HV_padsv_vivify_rv2hv_helem|5.021007||Viu
+MDEREF_HV_pop_rv2hv_helem|5.021007||Viu
+MDEREF_HV_vivify_rv2hv_helem|5.021007||Viu
+MDEREF_INDEX_const|5.021007||Viu
+MDEREF_INDEX_gvsv|5.021007||Viu
+MDEREF_INDEX_MASK|5.021007||Viu
+MDEREF_INDEX_none|5.021007||Viu
+MDEREF_INDEX_padsv|5.021007||Viu
+MDEREF_MASK|5.021007||Viu
+MDEREF_reload|5.021007||Viu
+MDEREF_SHIFT|5.021007||Viu
+measure_struct|5.007003||Viu
+MEM_ALIGNBYTES|5.003007|5.003007|Vn
+memBEGINPs|5.027006||Viu
+memBEGINs|5.027006||Viu
+MEMBER_TO_FPTR|5.006000||Viu
+memCHRs|5.031008|5.003007|p
+mem_collxfrm|5.003007||dViu
+_mem_collxfrm|5.025002||Viu
+memENDPs|5.027006||Viu
+memENDs|5.027006||Viu
+memEQ|5.004000|5.003007|p
+memEQs|5.009005|5.003007|p
+memGE|5.025005||Viu
+memGT|5.025005||Viu
+memLE|5.025005||Viu
+MEM_LOG_ALLOC|5.009003||Viu
+mem_log_alloc|5.024000||Vniu
+mem_log_common|5.010001||Vniu
+MEM_LOG_FREE|5.009003||Viu
+mem_log_free|5.024000||Vniu
+MEM_LOG_REALLOC|5.009003||Viu
+mem_log_realloc|5.024000||Vniu
+memLT|5.025005||Viu
+memNE|5.004000|5.003007|p
+memNEs|5.009005|5.003007|p
+MEM_SIZE|5.003007||Viu
+MEM_SIZE_MAX|5.009005||Viu
+MEM_WRAP_CHECK_1|5.009002||Viu
+MEM_WRAP_CHECK|5.009002||Viu
+MEM_WRAP_CHECK_s|5.027010||Viu
+memzero|5.003007|5.003007|
+MEOL|5.003007||Viu
+MEOL_t8_p8|5.033003||Viu
+MEOL_t8_pb|5.033003||Viu
+MEOL_tb_p8|5.033003||Viu
+MEOL_tb_pb|5.033003||Viu
+mess|5.006000|5.004000|pv
+mess_alloc|5.005000||Viu
+mess_nocontext|5.006000||pvVn
+mess_sv|5.013001|5.004000|p
+MEXTEND|5.003007||Viu
+mfree|5.007002|5.007002|nu
+MgBYTEPOS|5.019004||Viu
+MgBYTEPOS_set|5.019004||Viu
+mg_clear|5.003007|5.003007|
+mg_copy|5.003007|5.003007|
+mg_dup|5.007003|5.007003|u
+MGf_BYTES|5.019004||Viu
+MGf_COPY|5.007003||Viu
+MGf_DUP|5.007003||Viu
+MGf_GSKIP|5.003007||Viu
+mg_find|5.003007|5.003007|n
+mg_findext|5.013008|5.003007|pn
+mg_find_mglob|5.019002||cViu
+MGf_LOCAL|5.009003||Viu
+MGf_MINMATCH|5.003007||Viu
+MGf_PERSIST|5.021005||Viu
+mg_free|5.003007|5.003007|
+mg_freeext|5.027004|5.027004|
+mg_free_type|5.013006|5.013006|
+MGf_REFCOUNTED|5.003007||Viu
+MGf_REQUIRE_GV|5.021004||Viu
+MGf_TAINTEDDIR|5.003007||Viu
+mg_get|5.003007|5.003007|
+mg_length|5.005000|5.005000|d
+mg_localize|5.009003||Vi
+mg_magical|5.003007|5.003007|n
+MgPV|5.003007||Viu
+MgPV_const|5.009003||Viu
+MgPV_nolen_const|5.009003||Viu
+mg_set|5.003007|5.003007|
+mg_size|5.005000|5.005000|u
+MgSV|5.033009||Viu
+MgTAINTEDDIR|5.003007||Viu
+MgTAINTEDDIR_off|5.004000||Viu
+MgTAINTEDDIR_on|5.003007||Viu
+MICRO_SIGN|5.011002||Viu
+MICRO_SIGN_NATIVE|5.017004||Viu
+MICRO_SIGN_UTF8|5.033003||Viu
+MIN|5.025006||Viu
+mini_mktime|5.007002|5.007002|n
+MINMOD|5.003007||Viu
+MINMOD_t8_p8|5.033003||Viu
+MINMOD_t8_pb|5.033003||Viu
+MINMOD_tb_p8|5.033003||Viu
+MINMOD_tb_pb|5.033003||Viu
+minus_v|5.015006||Viu
+missingterm|5.005000||Viu
+MJD_OFFSET_DEBUG|5.009004||Viu
+Mkdir|5.004000||Viu
+mkdir|5.005000||Viu
+mktemp|5.005000||Viu
+Mmap_t|5.006000|5.006000|Vn
+mode_from_discipline|5.006000||Viu
+Mode_t|5.003007|5.003007|Vn
+modkids|5.003007||Viu
+MON_10|5.027010||Viu
+MON_11|5.027010||Viu
+MON_12|5.027010||Viu
+MON_1|5.027010||Viu
+MON_2|5.027010||Viu
+MON_3|5.027010||Viu
+MON_4|5.027010||Viu
+MON_5|5.027010||Viu
+MON_6|5.027010||Viu
+MON_7|5.027010||Viu
+MON_8|5.027010||Viu
+MON_9|5.027010||Viu
+more_bodies|||iu
+more_sv|5.009004||Viu
+moreswitches|5.003007||cVu
+mortal_getenv|5.031011||cVnu
+Move|5.003007|5.003007|
+MoveD|5.009002|5.003007|p
+move_proto_attr|5.019005||Viu
+M_PAT_MODS|5.009005||Viu
+MPH_BUCKETS|5.027011||Viu
+MPH_RSHIFT|5.027011||Viu
+MPH_VALt|5.027011||Viu
+mPUSHi|5.009002|5.003007|p
+mPUSHn|5.009002|5.003007|p
+mPUSHp|5.009002|5.003007|p
+mPUSHs|5.010001|5.003007|p
+mPUSHu|5.009002|5.003007|p
+mro_clean_isarev|5.013007||Viu
+mro_gather_and_rename|5.013007||Viu
+mro_get_from_name|5.010001|5.010001|u
+mro_get_linear_isa|5.009005|5.009005|
+mro_get_linear_isa_c3|||i
+mro_get_linear_isa_dfs|5.009005||Vi
+MRO_GET_PRIVATE_DATA|5.010001|5.010001|
+mro_get_private_data|||cu
+mro_isa_changed_in|5.009005||Vi
+mro_meta_dup|5.009005||Viu
+mro_meta_init|||ciu
+mro_method_changed_in|5.009005|5.009005|
+mro_package_moved|5.013006||Vi
+mro_register|5.010001|5.010001|
+mro_set_mro|5.010001|5.010001|u
+mro_set_private_data|5.010001|5.010001|
+MSPAGAIN|5.003007||Viu
+MSVC_DIAG_IGNORE|5.029010||Viu
+MSVC_DIAG_IGNORE_DECL|5.029010||Viu
+MSVC_DIAG_IGNORE_STMT|5.029010||Viu
+MSVC_DIAG_RESTORE|5.029010||Viu
+MSVC_DIAG_RESTORE_DECL|5.029010||Viu
+MSVC_DIAG_RESTORE_STMT|5.029010||Viu
+mul128|5.005000||Viu
+MULTICALL|5.009003|5.009003|
+multiconcat_stringify|5.027006||cViu
+multideref_stringify|5.021009||cViu
+MULTILINE_PAT_MOD|5.009005||Viu
+MULTIPLICITY|5.006000|5.006000|Vn
+MUTABLE_AV|5.010001|5.003007|p
+MUTABLE_CV|5.010001|5.003007|p
+MUTABLE_GV|5.010001|5.003007|p
+MUTABLE_HV|5.010001|5.003007|p
+MUTABLE_IO|5.010001|5.003007|p
+MUTABLE_PTR|5.010001|5.003007|p
+MUTABLE_SV|5.010001|5.003007|p
+MUTEX_DESTROY|5.005000||Viu
+MUTEX_INIT|5.005000||Viu
+MUTEX_INIT_NEEDS_MUTEX_ZEROED|5.005003||Viu
+MUTEX_LOCK|5.005000||Viu
+MUTEX_UNLOCK|5.005000||Viu
+mXPUSHi|5.009002|5.003007|p
+mXPUSHn|5.009002|5.003007|p
+mXPUSHp|5.009002|5.003007|p
+mXPUSHs|5.010001|5.003007|p
+mXPUSHu|5.009002|5.003007|p
+my|5.011000||Viu
+my_atof2|5.029000||cVu
+my_atof3|5.029000||cVu
+my_atof|5.006000|5.006000|
+my_attrs|5.006000||Viu
+my_binmode|5.006000||Viu
+my_bytes_to_utf8|5.021009||Vniu
+my_chsize|5.003007||Vu
+my_clearenv|5.009003||Viu
+MY_CXT|5.009000|5.009000|p
+MY_CXT_CLONE|5.009002|5.009000|p
+MY_CXT_INDEX|5.009005||Viu
+MY_CXT_INIT|5.009000|5.009000|p
+my_cxt_init|5.009000|5.009000|u
+MY_CXT_INIT_ARG|5.013005||Viu
+MY_CXT_INIT_INTERP|5.009003||Viu
+my_dirfd|5.009005|5.009005|nu
+my_exit|5.003007|5.003007|
+my_exit_jump|5.005000||Viu
+my_failure_exit|5.004000|5.004000|u
+my_fflush_all|5.006000|5.006000|u
+my_fork|5.007003|5.007003|nu
+my_kid|5.006000||Viu
+my_lstat|5.013003||Viu
+my_lstat_flags|5.013003||cViu
+my_memrchr|5.027006||Vniu
+my_mkostemp_cloexec|||niu
+my_mkostemp|||niu
+my_mkstemp_cloexec|||niu
+my_mkstemp|||niu
+my_nl_langinfo|5.027006||Vniu
+my_pclose|5.003007|5.003007|u
+my_popen|5.003007|5.003007|u
+my_popen_list|5.007001|5.007001|u
+my_setenv|5.003007|5.003007|
+my_snprintf|5.009004|5.003007|pvn
+my_socketpair|5.007003|5.007003|nu
+my_sprintf|5.009003|5.003007|pdn
+my_stat|5.013003||Viu
+my_stat_flags|5.013003||cViu
+my_strerror|5.021001||Viu
+my_strftime|5.007002|5.007002|
+my_strlcat|5.009004|5.003007|pn
+my_strlcpy|5.009004|5.003007|pn
+my_strnlen|5.027006|5.003007|pn
+my_strtod|5.029010|5.029010|n
+my_unexec|5.003007||Viu
+my_vsnprintf|5.009004|5.009004|n
+N0|5.029001||Viu
+N10|5.029001||Viu
+N11|5.029001||Viu
+N1|5.029001||Viu
+N2|5.029001||Viu
+N3|5.029001||Viu
+N4|5.029001||Viu
+N5|5.029001||Viu
+N6|5.029001||Viu
+N7|5.029001||Viu
+N8|5.029001||Viu
+N9|5.029001||Viu
+NAN_COMPARE_BROKEN|5.021005||Viu
+NANYOFM|5.029005||Viu
+NANYOFM_t8_p8|5.033003||Viu
+NANYOFM_t8_pb|5.033003||Viu
+NANYOFM_tb_p8|5.033003||Viu
+NANYOFM_tb_pb|5.033003||Viu
+NATIVE8_TO_UNI|5.011000||Viu
+NATIVE_BYTE_IS_INVARIANT|5.019004||Viu
+NATIVE_SKIP|5.019004||Viu
+NATIVE_TO_ASCII|5.007001||Viu
+NATIVE_TO_I8|5.015006||Viu
+NATIVE_TO_LATIN1|5.019004|5.003007|p
+NATIVE_TO_NEED|5.019004||dcVnu
+NATIVE_TO_UNI|5.007001|5.003007|p
+NATIVE_TO_UTF|5.007001||Viu
+NATIVE_UTF8_TO_I8|5.019004||Viu
+nBIT_MASK|5.033001||Viu
+nBIT_UMAX|5.033001||Viu
+NBOUND|5.003007||Viu
+NBOUNDA|5.013009||Viu
+NBOUNDA_t8_p8|5.033003||Viu
+NBOUNDA_t8_pb|5.033003||Viu
+NBOUNDA_tb_p8|5.033003||Viu
+NBOUNDA_tb_pb|5.033003||Viu
+NBOUNDL|5.004000||Viu
+NBOUNDL_t8_p8|5.033003||Viu
+NBOUNDL_t8_pb|5.033003||Viu
+NBOUNDL_tb_p8|5.033003||Viu
+NBOUNDL_tb_pb|5.033003||Viu
+NBOUND_t8_p8|5.033003||Viu
+NBOUND_t8_pb|5.033003||Viu
+NBOUND_tb_p8|5.033003||Viu
+NBOUND_tb_pb|5.033003||Viu
+NBOUNDU|5.013009||Viu
+NBOUNDU_t8_p8|5.033003||Viu
+NBOUNDU_t8_pb|5.033003||Viu
+NBOUNDU_tb_p8|5.033003||Viu
+NBOUNDU_tb_pb|5.033003||Viu
+NBSP_NATIVE|5.021001||Viu
+NBSP_UTF8|5.021001||Viu
+NDBM_H_USES_PROTOTYPES|5.032001|5.032001|Vn
+NDEBUG|5.021007||Viu
+NEED_PTHREAD_INIT|5.005000||Viu
+need_utf8|5.009003||Vniu
+NEED_VA_COPY|5.007001|5.007001|Vn
+NEGATIVE_INDICES_VAR|5.008001||Viu
+Netdb_hlen_t|5.005000|5.005000|Vn
+Netdb_host_t|5.005000|5.005000|Vn
+Netdb_name_t|5.005000|5.005000|Vn
+Netdb_net_t|5.005000|5.005000|Vn
+NETDB_R_OBSOLETE|5.008000||Viu
+New|5.003007||Viu
+newANONATTRSUB|5.006000|5.006000|u
+newANONHASH|5.003007|5.003007|u
+newANONLIST|5.003007|5.003007|u
+newANONSUB|5.003007|5.003007|u
+newASSIGNOP|5.003007|5.003007|
+newATTRSUB|5.006000|5.006000|
+newATTRSUB_x|5.019008||cVi
+newAV|5.003007|5.003007|
+newAV_alloc_x|5.035001|5.035001|
+newAV_alloc_xz|5.035001|5.035001|
+newAVREF|5.003007|5.003007|u
+newBINOP|5.003007|5.003007|
+Newc|5.003007||Viu
+new_collate|5.006000||Viu
+newCONDOP|5.003007|5.003007|
+new_constant|||iu
+newCONSTSUB|5.004005|5.003007|p
+newCONSTSUB_flags|5.015006|5.015006|
+new_ctype|5.006000||Viu
+newCVREF|5.003007|5.003007|u
+newDEFSVOP|5.021006|5.021006|
+newFORM|5.003007|5.003007|u
+newFOROP|5.013007|5.013007|
+newGIVENOP|5.009003|5.009003|
+newGIVWHENOP|5.027008||Viu
+newGP|||xiu
+newGVgen|5.003007|5.003007|u
+newGVgen_flags|5.015004|5.015004|u
+newGVOP|5.003007|5.003007|
+newGVREF|5.003007|5.003007|u
+new_he|5.005000||Viu
+newHV|5.003007|5.003007|
+newHVhv|5.005000|5.005000|u
+newHVREF|5.003007|5.003007|u
+_new_invlist|5.013010||cViu
+_new_invlist_C_array|5.015008||cViu
+newIO|5.003007|5.003007|u
+newLISTOP|5.003007|5.003007|
+newLOGOP|5.003007|5.003007|
+new_logop|5.005000||Viu
+newLOOPEX|5.003007|5.003007|
+newLOOPOP|5.003007|5.003007|
+newMETHOP|5.021005|5.021005|
+newMETHOP_internal|5.021005||Viu
+newMETHOP_named|5.021005|5.021005|
+new_msg_hv|5.027009||Viu
+newMYSUB|5.017004|5.017004|u
+newNULLLIST|5.003007|5.003007|
+new_numeric|5.006000||Viu
+newOP|5.003007|5.003007|
+NewOp|5.008001||Viu
+newPADNAMELIST|5.021007|5.021007|xn
+newPADNAMEouter|5.021007|5.021007|xn
+newPADNAMEpvn|5.021007|5.021007|xn
+newPADOP|5.006000||V
+newPMOP|5.003007|5.003007|
+newPROG|5.003007|5.003007|u
+newPVOP|5.003007|5.003007|
+newRANGE|5.003007|5.003007|
+newRV|5.003007|5.003007|
+newRV_inc|5.004000|5.003007|p
+newRV_noinc|5.004000|5.003007|p
+newSLICEOP|5.003007|5.003007|
+new_stackinfo|5.005000|5.005000|u
+newSTATEOP|5.003007|5.003007|
+newSTUB|5.017001||Viu
+newSUB|5.003007|5.003007|
+newSV|5.003007|5.003007|
+NEWSV|5.003007||Viu
+newSVavdefelem|5.019004||Viu
+newSVhek|5.009003|5.009003|
+newSViv|5.003007|5.003007|
+newSVnv|5.006000|5.003007|
+newSVOP|5.003007|5.003007|
+newSVpadname|5.017004|5.017004|x
+newSVpv|5.003007|5.003007|
+newSVpvf|5.006000|5.004000|v
+newSVpvf_nocontext|5.006000||vVn
+newSVpvn|5.004005|5.003007|p
+newSVpvn_flags|5.010001|5.003007|p
+newSVpvn_share|5.007001|5.003007|p
+newSVpvn_utf8|5.010001|5.003007|p
+newSVpvs|5.009003|5.003007|p
+newSVpvs_flags|5.010001|5.003007|p
+newSVpv_share|5.013006|5.013006|
+newSVpvs_share|5.009003|5.003007|p
+newSVREF|5.003007|5.003007|u
+newSVrv|5.003007|5.003007|
+newSVsv|5.003007|5.003007|
+newSVsv_flags|5.029009|5.003007|p
+newSVsv_nomg|5.029009|5.003007|p
+newSV_type|5.009005|5.003007|p
+newSVuv|5.006000|5.003007|p
+newTRYCATCHOP|5.033007|5.033007|x
+newUNOP|5.003007|5.003007|
+newUNOP_AUX|5.021007|5.021007|
+new_version|5.009000|5.009000|
+NEW_VERSION|5.019008||Viu
+new_warnings_bitfield|||xciu
+newWHENOP|5.027008|5.027008|
+newWHILEOP|5.013007|5.013007|
+Newx|5.009003|5.003007|p
+Newxc|5.009003|5.003007|p
+newXS|5.006000|5.006000|
+newXS_deffile|5.021006||cViu
+newXS_flags|5.009004|5.009004|xu
+newXS_len_flags|5.015006||Vi
+newXSproto|5.006000|5.006000|
+Newxz|5.009003|5.003007|p
+Newz|5.003007||Viu
+nextargv|5.003007||Viu
+nextchar|5.005000||Viu
+NEXT_LINE_CHAR|5.007003||Viu
+NEXT_OFF|5.005000||Viu
+NEXTOPER|5.003007||Viu
+next_symbol|5.007003||Viu
+ninstr|5.003007|5.003007|n
+NL_LANGINFO_LOCK|5.033005||Viu
+NL_LANGINFO_UNLOCK|5.033005||Viu
+no_bareword_allowed|5.005004||Viu
+no_bareword_filehandle|5.033006||Viu
+NOCAPTURE_PAT_MOD|5.021008||Viu
+NOCAPTURE_PAT_MODS|5.021008||Viu
+NODE_ALIGN|5.005000||Viu
+NODE_ALIGN_FILL|5.005000||Viu
+NODE_STEP_REGNODE|5.005000||Viu
+NODE_SZ_STR|5.006000||Viu
+NO_ENV_ARRAY_IN_MAIN|5.009004||Viu
+NOEXPR|5.027010||Viu
+NofAMmeth|5.003007||Viu
+no_fh_allowed|5.003007||Viu
+NOLINE|5.003007||Viu
+NO_LOCALE|5.007000||Viu
+NO_LOCALECONV_MON_THOUSANDS_SEP|5.005000||Viu
+NONDESTRUCT_PAT_MOD|5.013002||Viu
+NONDESTRUCT_PAT_MODS|5.013002||Viu
+NON_OTHER_COUNT|5.033005||Viu
+no_op|5.003007||Viu
+NOOP|5.005000|5.003007|p
+noperl_die|5.021006||vVniu
+NORETURN_FUNCTION_END|5.009003||Viu
+NORMAL|5.003007||Viu
+NOSTR|5.027010||Viu
+NO_TAINT_SUPPORT|5.017006||Viu
+not_a_number|5.005000||Viu
+NOTE3|5.027001||Viu
+NOTHING|5.003007||Viu
+NOTHING_t8_p8|5.033003||Viu
+NOTHING_t8_pb|5.033003||Viu
+NOTHING_tb_p8|5.033003||Viu
+NOTHING_tb_pb|5.033003||Viu
+nothreadhook|5.008000|5.008000|
+notify_parser_that_changed_to_utf8|5.025010||Viu
+not_incrementable|5.021002||Viu
+NOT_IN_PAD|5.005000||Viu
+NOT_REACHED|5.019006|5.003007|poVnu
+NPOSIXA|5.017003||Viu
+NPOSIXA_t8_p8|5.033003||Viu
+NPOSIXA_t8_pb|5.033003||Viu
+NPOSIXA_tb_p8|5.033003||Viu
+NPOSIXA_tb_pb|5.033003||Viu
+NPOSIXD|5.017003||Viu
+NPOSIXD_t8_p8|5.033003||Viu
+NPOSIXD_t8_pb|5.033003||Viu
+NPOSIXD_tb_p8|5.033003||Viu
+NPOSIXD_tb_pb|5.033003||Viu
+NPOSIXL|5.017003||Viu
+NPOSIXL_t8_p8|5.033003||Viu
+NPOSIXL_t8_pb|5.033003||Viu
+NPOSIXL_tb_p8|5.033003||Viu
+NPOSIXL_tb_pb|5.033003||Viu
+NPOSIXU|5.017003||Viu
+NPOSIXU_t8_p8|5.033003||Viu
+NPOSIXU_t8_pb|5.033003||Viu
+NPOSIXU_tb_p8|5.033003||Viu
+NPOSIXU_tb_pb|5.033003||Viu
+NSIG|5.009003||Viu
+ntohi|5.003007||Viu
+ntohl|5.003007||Viu
+ntohs|5.003007||Viu
+nuke_stacks|5.005000||Viu
+Null|5.003007||Viu
+Nullav|5.003007|5.003007|d
+Nullch|5.003007|5.003007|
+Nullcv|5.003007|5.003007|d
+Nullfp|5.003007||Viu
+Nullgv|5.003007||Viu
+Nullhe|5.003007||Viu
+Nullhek|5.004000||Viu
+Nullhv|5.003007|5.003007|d
+Nullop|5.003007||Viu
+Nullsv|5.003007|5.003007|
+NUM2PTR|5.006000||pVu
+NUM_ANYOF_CODE_POINTS|5.021004||Viu
+NUM_CLASSES|5.029001||Viu
+num_overflow|5.009001||Vniu
+NV_BIG_ENDIAN|5.021009||Viu
+NV_DIG|5.006000||Viu
+NVef|5.006001|5.003007|poVn
+NV_EPSILON|5.007003||Viu
+NVff|5.006001|5.003007|poVn
+NVgf|5.006001|5.003007|poVn
+NV_IMPLICIT_BIT|5.021009||Viu
+NV_INF|5.007003||Viu
+NV_LITTLE_ENDIAN|5.021009||Viu
+NVMANTBITS|5.023000|5.023000|Vn
+NV_MANT_DIG|5.006001||Viu
+NV_MAX_10_EXP|5.007003||Viu
+NV_MAX|5.006001||Viu
+NV_MAX_EXP|5.021003||Viu
+NV_MIN_10_EXP|5.007003||Viu
+NV_MIN|5.006001||Viu
+NV_MIN_EXP|5.021003||Viu
+NV_MIX_ENDIAN|5.021009||Viu
+NV_NAN|5.007003||Viu
+NV_NAN_BITS|5.023000||Viu
+NV_NAN_IS_QUIET|5.023000||Viu
+NV_NAN_IS_SIGNALING|5.023000||Viu
+NV_NAN_PAYLOAD_MASK|5.023000||Viu
+NV_NAN_PAYLOAD_MASK_IEEE_754_128_BE|5.023000||Viu
+NV_NAN_PAYLOAD_MASK_IEEE_754_128_LE|5.023000||Viu
+NV_NAN_PAYLOAD_MASK_IEEE_754_64_BE|5.023000||Viu
+NV_NAN_PAYLOAD_MASK_IEEE_754_64_LE|5.023000||Viu
+NV_NAN_PAYLOAD_MASK_SKIP_EIGHT|5.023006||Viu
+NV_NAN_PAYLOAD_PERM_0_TO_7|5.023000||Viu
+NV_NAN_PAYLOAD_PERM|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_7_TO_0|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_IEEE_754_128_BE|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_IEEE_754_128_LE|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_IEEE_754_64_BE|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_IEEE_754_64_LE|5.023000||Viu
+NV_NAN_PAYLOAD_PERM_SKIP_EIGHT|5.023006||Viu
+NV_NAN_QS_BIT|5.023000||Viu
+NV_NAN_QS_BIT_OFFSET|5.023000||Viu
+NV_NAN_QS_BIT_SHIFT|5.023000||Viu
+NV_NAN_QS_BYTE|5.023000||Viu
+NV_NAN_QS_BYTE_OFFSET|5.023000||Viu
+NV_NAN_QS_QUIET|5.023000||Viu
+NV_NAN_QS_SIGNALING|5.023000||Viu
+NV_NAN_QS_TEST|5.023000||Viu
+NV_NAN_QS_XOR|5.023000||Viu
+NV_NAN_SET_QUIET|5.023000||Viu
+NV_NAN_SET_SIGNALING|5.023000||Viu
+NV_OVERFLOWS_INTEGERS_AT|5.010001|5.010001|Vn
+NV_PRESERVES_UV_BITS|5.006001|5.006001|Vn
+NVSIZE|5.006001|5.006001|Vn
+NVTYPE|5.006000|5.003007|poVn
+NV_VAX_ENDIAN|5.025003||Viu
+NV_WITHIN_IV|5.006000||Viu
+NV_WITHIN_UV|5.006000||Viu
+NV_X86_80_BIT|5.025004||Viu
+NV_ZERO_IS_ALLBITS_ZERO|5.035001|5.035001|Vn
+OA_AVREF|5.003007||Viu
+OA_BASEOP|5.005000||Viu
+OA_BASEOP_OR_UNOP|5.005000||Viu
+OA_BINOP|5.005000||Viu
+OA_CLASS_MASK|5.005000||Viu
+OA_COP|5.005000||Viu
+OA_CVREF|5.003007||Viu
+OA_DANGEROUS|5.003007||Viu
+OA_DEFGV|5.003007||Viu
+OA_FILEREF|5.003007||Viu
+OA_FILESTATOP|5.005000||Viu
+OA_FOLDCONST|5.003007||Viu
+OA_HVREF|5.003007||Viu
+OA_LIST|5.003007||Viu
+OA_LISTOP|5.005000||Viu
+OA_LOGOP|5.005000||Viu
+OA_LOOP|5.005000||Viu
+OA_LOOPEXOP|5.005000||Viu
+OA_MARK|5.003007||Viu
+OA_METHOP|5.021005||Viu
+OA_OPTIONAL|5.003007||Viu
+OA_OTHERINT|5.003007||Viu
+OA_PADOP|5.006000||Viu
+OA_PMOP|5.005000||Viu
+OA_PVOP_OR_SVOP|5.006000||Viu
+OA_RETSCALAR|5.003007||Viu
+OA_SCALAR|5.003007||Viu
+OA_SCALARREF|5.003007||Viu
+OASHIFT|5.003007||Viu
+OA_SVOP|5.005000||Viu
+OA_TARGET|5.003007||Viu
+OA_TARGLEX|5.006000||Viu
+OA_UNOP|5.005000||Viu
+OA_UNOP_AUX|5.021007||Viu
+O_BINARY|5.006000||Viu
+O_CREAT|5.006000||Viu
+OCSHIFT|5.006000||Viu
+OCTAL_VALUE|5.019008||Viu
+Off_t|5.003007|5.003007|Vn
+Off_t_size|5.006000|5.006000|Vn
+OFFUNI_IS_INVARIANT|5.023003||Viu
+OFFUNISKIP|5.019004||Viu
+ONCE_PAT_MOD|5.009005||Viu
+ONCE_PAT_MODS|5.009005||Viu
+oopsAV|5.003007||Viu
+oopsHV|5.003007||Viu
+OP|5.003007||Viu
+op_append_elem|5.013006|5.013006|
+op_append_list|5.013006|5.013006|
+opASSIGN|5.003007||Viu
+OP_BINARY|5.004000||Viu
+OP_CHECK_MUTEX_INIT|5.015008||Viu
+OP_CHECK_MUTEX_LOCK|5.015008||Viu
+OP_CHECK_MUTEX_TERM|5.015008||Viu
+OP_CHECK_MUTEX_UNLOCK|5.015008||Viu
+OP_CLASS|5.013007|5.013007|
+op_class|5.025010|5.025010|
+op_clear|5.006000||cViu
+OPCODE|5.003007||Viu
+op_contextualize|5.013006|5.013006|
+op_convert_list|5.021006|5.021006|
+OP_DESC|5.007003|5.007003|
+op_dump|5.006000|5.006000|
+OPEN|5.003007||Viu
+open|5.005000||Viu
+opendir|5.005000||Viu
+openn_cleanup|5.019010||Viu
+openn_setup|5.019010||Viu
+open_script|5.005000||Viu
+OPEN_t8_p8|5.033003||Viu
+OPEN_t8_pb|5.033003||Viu
+OPEN_tb_p8|5.033003||Viu
+OPEN_tb_pb|5.033003||Viu
+OPERAND|5.003007||Viu
+OPERANDl|5.031005||Viu
+OPERANDs|5.031005||Viu
+OPFAIL|5.009005||Viu
+OPFAIL_t8_p8|5.033003||Viu
+OPFAIL_t8_pb|5.033003||Viu
+OPFAIL_tb_p8|5.033003||Viu
+OPFAIL_tb_pb|5.033003||Viu
+OPf_FOLDED|5.021007||Viu
+OPf_KIDS|5.003007|5.003007|
+OPf_KNOW|5.003007||Viu
+OPf_LIST|5.003007||Viu
+OPf_MOD|5.003007||Viu
+OPf_PARENS|5.003007||Viu
+op_free|5.003007|5.003007|
+OP_FREED|5.017002||Viu
+OPf_REF|5.003007||Viu
+OPf_SPECIAL|5.003007||Viu
+OPf_STACKED|5.003007||Viu
+OPf_WANT|5.004000||Viu
+OPf_WANT_LIST|5.004000||Viu
+OPf_WANT_SCALAR|5.004000||Viu
+OPf_WANT_VOID|5.004000||Viu
+OP_GIMME|5.004000||Viu
+OP_GIMME_REVERSE|5.010001||Viu
+OpHAS_SIBLING|5.021007|5.003007|p
+op_integerize|5.015003||Viu
+OP_IS_DIRHOP|5.015003||Viu
+OP_IS_FILETEST|5.006001||Viu
+OP_IS_FILETEST_ACCESS|5.008001||Viu
+OP_IS_INFIX_BIT|5.021009||Viu
+OP_IS_NUMCOMPARE|5.015003||Viu
+OP_IS_SOCKET|5.006001||Viu
+OP_IS_STAT|5.031001||Viu
+OpLASTSIB_set|5.021011|5.003007|p
+op_linklist|5.013006|5.013006|
+op_lvalue|5.013007|5.013007|x
+op_lvalue_flags|||ciu
+OP_LVALUE_NO_CROAK|5.015001||Viu
+OpMAYBESIB_set|5.021011|5.003007|p
+opmethod_stash|5.021007||Viu
+OpMORESIB_set|5.021011|5.003007|p
+OP_NAME|5.007003|5.007003|
+op_null|5.007002|5.007002|
+OPpALLOW_FAKE|5.015006||Viu
+op_parent|5.025001|5.025001|n
+OPpARG1_MASK|5.021004||Viu
+OPpARG2_MASK|5.021004||Viu
+OPpARG3_MASK|5.021004||Viu
+OPpARG4_MASK|5.021004||Viu
+OPpARGELEM_AV|5.025004||Viu
+OPpARGELEM_HV|5.025004||Viu
+OPpARGELEM_MASK|5.025004||Viu
+OPpARGELEM_SV|5.025004||Viu
+OPpASSIGN_BACKWARDS|5.003007||Viu
+OPpASSIGN_COMMON_AGG|5.023002||Viu
+OPpASSIGN_COMMON_RC1|5.023002||Viu
+OPpASSIGN_COMMON_SCALAR|5.023002||Viu
+OPpASSIGN_CV_TO_GV|5.009003||Viu
+OPpASSIGN_TRUEBOOL|5.027003||Viu
+OPpAVHVSWITCH_MASK|5.025006||Viu
+OPpCONCAT_NESTED|5.027007||Viu
+OPpCONST_BARE|5.003007||Viu
+OPpCONST_ENTERED|5.003007||Viu
+OPpCONST_NOVER|5.009003||Viu
+OPpCONST_SHORTCIRCUIT|5.009001||Viu
+OPpCONST_STRICT|5.005004||Viu
+OPpCOREARGS_DEREF1|5.015003||Viu
+OPpCOREARGS_DEREF2|5.015003||Viu
+OPpCOREARGS_PUSHMARK|5.015003||Viu
+OPpCOREARGS_SCALARMOD|5.015003||Viu
+OPpDEREF|5.004000||Viu
+OPpDEREF_AV|5.003007||Viu
+OPpDEREF_HV|5.003007||Viu
+OPpDEREF_SV|5.004000||Viu
+OPpDONT_INIT_GV|5.009003||Viu
+OPpEARLY_CV|5.006000|5.006000|
+OPpENTERSUB_AMPER|5.003007|5.003007|
+OPpENTERSUB_DB|5.003007||Viu
+OPpENTERSUB_HASTARG|5.006000||Viu
+OPpENTERSUB_INARGS|5.006000||Viu
+OPpENTERSUB_LVAL_MASK|5.015001||Viu
+OPpENTERSUB_NOPAREN|5.005004||Viu
+OPpEVAL_BYTES|5.015005||Viu
+OPpEVAL_COPHH|5.015005||Viu
+OPpEVAL_HAS_HH|5.009003||Viu
+OPpEVAL_RE_REPARSING|5.017011||Viu
+OPpEVAL_UNICODE|5.015005||Viu
+OPpEXISTS_SUB|5.006000||Viu
+OPpFLIP_LINENUM|5.003007||Viu
+OPpFT_ACCESS|5.008001||Viu
+OPpFT_AFTER_t|5.015008||Viu
+OPpFT_STACKED|5.009001||Viu
+OPpFT_STACKING|5.015001||Viu
+OPpHINT_STRICT_REFS|5.021004||Viu
+OPpHUSH_VMSISH|5.007003||Viu
+OPpINDEX_BOOLNEG|5.027003||Viu
+OPpITER_DEF|5.027008||Viu
+OPpITER_REVERSED|5.009002||Viu
+OPpKVSLICE|5.027001||Viu
+OPpLIST_GUESSED|5.003007||Viu
+OPpLVAL_DEFER|5.004000||Viu
+OPpLVAL_INTRO|5.003007||Viu
+OPpLVALUE|5.019006||Viu
+OPpLVREF_AV|5.021005||Viu
+OPpLVREF_CV|5.021005||Viu
+OPpLVREF_ELEM|5.021005||Viu
+OPpLVREF_HV|5.021005||Viu
+OPpLVREF_ITER|5.021005||Viu
+OPpLVREF_SV|5.021005||Viu
+OPpLVREF_TYPE|5.021005||Viu
+OPpMAYBE_LVSUB|5.007001||Viu
+OPpMAYBE_TRUEBOOL|5.017004||Viu
+OPpMAY_RETURN_CONSTANT|5.009003||Viu
+OPpMULTICONCAT_APPEND|5.027006||Viu
+OPpMULTICONCAT_FAKE|5.027006||Viu
+OPpMULTICONCAT_STRINGIFY|5.027006||Viu
+OPpMULTIDEREF_DELETE|5.021007||Viu
+OPpMULTIDEREF_EXISTS|5.021007||Viu
+OPpOFFBYONE|5.015002||Viu
+OPpOPEN_IN_CRLF|5.006000||Viu
+OPpOPEN_IN_RAW|5.006000||Viu
+OPpOPEN_OUT_CRLF|5.006000||Viu
+OPpOPEN_OUT_RAW|5.006000||Viu
+OPpOUR_INTRO|5.006000||Viu
+OPpPADHV_ISKEYS|5.027003||Viu
+OPpPADRANGE_COUNTMASK|5.017006||Viu
+OPpPADRANGE_COUNTSHIFT|5.017006||Viu
+OPpPAD_STATE|5.009004||Viu
+OPpPV_IS_UTF8|5.016000||Viu
+OPpREFCOUNTED|5.006000||Viu
+OPpREPEAT_DOLIST|5.003007||Viu
+op_prepend_elem|5.013006|5.013006|
+OPpREVERSE_INPLACE|5.011002||Viu
+OPpRV2HV_ISKEYS|5.027003||Viu
+OPpSLICE|5.004000||Viu
+OPpSLICEWARNING|5.019004||Viu
+OPpSORT_DESCEND|5.009002||Viu
+OPpSORT_INPLACE|5.009001||Viu
+OPpSORT_INTEGER|5.006000||Viu
+OPpSORT_NUMERIC|5.006000||Viu
+OPpSORT_REVERSE|5.006000||Viu
+OPpSORT_STABLE|5.009003||Viu
+OPpSORT_UNSTABLE|5.027004||Viu
+OPpSPLIT_ASSIGN|5.025006||Viu
+OPpSPLIT_IMPLIM|5.019002||Viu
+OPpSPLIT_LEX|5.025006||Viu
+OPpSUBSTR_REPL_FIRST|5.015006||Viu
+OPpTARGET_MY|5.006000||Viu
+OPpTRANS_ALL|5.009001||Viu
+OPpTRANS_CAN_FORCE_UTF8|5.031006||Viu
+OPpTRANS_COMPLEMENT|5.003007||Viu
+OPpTRANS_DELETE|5.003007||Viu
+OPpTRANS_FROM_UTF|5.006000||Viu
+OPpTRANS_GROWS|5.006000||Viu
+OPpTRANS_IDENTICAL|5.006000||Viu
+OPpTRANS_SQUASH|5.003007||Viu
+OPpTRANS_TO_UTF|5.006000||Viu
+OPpTRANS_USE_SVOP|5.031006||Viu
+OPpTRUEBOOL|5.017004||Viu
+OpREFCNT_dec|5.006000||Viu
+op_refcnt_dec|||xiu
+OpREFCNT_inc|5.006000||Viu
+op_refcnt_inc|||xiu
+OP_REFCNT_INIT|5.006000||Viu
+OP_REFCNT_LOCK|5.006000||Viu
+op_refcnt_lock|5.009002|5.009002|u
+OpREFCNT_set|5.006000||Viu
+OP_REFCNT_TERM|5.006000||Viu
+OP_REFCNT_UNLOCK|5.006000||Viu
+op_refcnt_unlock|5.009002|5.009002|u
+op_relocate_sv|5.021005||Viu
+op_scope|5.013007|5.013007|x
+OP_SIBLING|5.021002||Viu
+OpSIBLING|5.021007|5.003007|p
+op_sibling_splice|5.021002|5.021002|n
+OpSLAB|5.017002||Viu
+opslab_force_free|5.017002||Viu
+opslab_free|5.017002||Viu
+opslab_free_nopad|5.017002||Viu
+OpslabREFCNT_dec|5.017002||Viu
+OpslabREFCNT_dec_padok|5.017002||Viu
+OpSLOT|5.017002||Viu
+OPSLOT_HEADER|5.017002||Viu
+OpSLOToff|5.033001||Viu
+op_std_init|5.015003||Viu
+OPTIMIZED|5.005000||Viu
+OPTIMIZED_t8_p8|5.033003||Viu
+OPTIMIZED_t8_pb|5.033003||Viu
+OPTIMIZED_tb_p8|5.033003||Viu
+OPTIMIZED_tb_pb|5.033003||Viu
+optimize_op|5.027006||Viu
+optimize_optree|5.027006||Vi
+optimize_regclass|5.035001||Viu
+OP_TYPE_IS|5.019007|5.019007|
+OP_TYPE_IS_NN|5.019010||Viu
+OP_TYPE_ISNT|5.019010||Viu
+OP_TYPE_ISNT_AND_WASNT|5.019010||Viu
+OP_TYPE_ISNT_AND_WASNT_NN|5.019010||Viu
+OP_TYPE_ISNT_NN|5.019010||Viu
+OP_TYPE_IS_OR_WAS|5.019010|5.019010|
+OP_TYPE_IS_OR_WAS_NN|5.019010||Viu
+op_unscope|5.017003||xViu
+O_RDONLY|5.006000||Viu
+O_RDWR|5.006000||Viu
+ORIGMARK|5.003007|5.003007|
+OSNAME|5.003007|5.003007|Vn
+OSVERS|5.007002|5.007002|Vn
+O_TEXT|5.006000||Viu
+OutCopFILE|5.007003||Viu
+output_non_portable|5.031008||Viu
+output_posix_warnings|5.029005||Viu
+O_VMS_DELETEONCLOSE|5.031002||Viu
+O_WRONLY|5.006000||Viu
+package|5.003007||Viu
+package_version|5.011001||Viu
+pack_cat|5.007003|5.007003|d
+packlist|5.008001|5.008001|
+pack_rec|5.008001||Viu
+packWARN2|5.007003|5.003007|p
+packWARN3|5.007003|5.003007|p
+packWARN4|5.007003|5.003007|p
+packWARN|5.007003|5.003007|p
+pad_add_anon|5.008001|5.008001|
+pad_add_name_pv|5.015001|5.015001|
+pad_add_name_pvn|5.015001|5.015001|
+pad_add_name_pvs|5.015001|5.015001|
+pad_add_name_sv|5.015001|5.015001|
+padadd_NO_DUP_CHECK|5.011002||Viu
+padadd_OUR|5.011002||Viu
+padadd_STALEOK|5.017003||Viu
+padadd_STATE|5.011002||Viu
+pad_add_weakref|5.021007||Viu
+pad_alloc|5.003007|5.003007|x
+pad_alloc_name|5.015001||Vi
+PadARRAY|5.017004|5.017004|x
+PAD_BASE_SV|5.008001||Vi
+pad_block_start|5.008001||Vi
+pad_check_dup|5.008001||Vi
+PAD_CLONE_VARS|5.008001||Vi
+PAD_COMPNAME|5.017004||Viu
+PAD_COMPNAME_FLAGS|5.008001||Vi
+PAD_COMPNAME_FLAGS_isOUR|5.009004||Viu
+PAD_COMPNAME_GEN|5.008001||Vi
+PAD_COMPNAME_GEN_set|5.009003||Vi
+PAD_COMPNAME_OURSTASH|5.008001||Vi
+PAD_COMPNAME_PV|5.008001||Vi
+PAD_COMPNAME_SV|5.009005||Viu
+PAD_COMPNAME_TYPE|5.008001||Vi
+pad_compname_type|5.009003|5.009003|d
+PAD_FAKELEX_ANON|5.009005||Viu
+PAD_FAKELEX_MULTI|5.009005||Viu
+pad_findlex|5.005000||Vi
+pad_findmy_pv|5.015001|5.015001|
+pad_findmy_pvn|5.015001|5.015001|
+pad_findmy_pvs|5.015001|5.015001|
+pad_findmy_sv|5.015001|5.015001|
+pad_fixup_inner_anons|5.008001||Vi
+pad_free|5.003007||Vi
+pad_leavemy|5.003007||Vi
+PadlistARRAY|5.017004|5.017004|x
+padlist_dup|5.013002||Vi
+PadlistMAX|5.017004|5.017004|x
+PadlistNAMES|5.017004|5.017004|x
+PadlistNAMESARRAY|5.017004|5.017004|x
+PadlistNAMESMAX|5.017004|5.017004|x
+PadlistREFCNT|5.017004|5.017004|x
+padlist_store|5.017004||Viu
+PadMAX|5.017004|5.017004|x
+padname_dup|5.021007||Vi
+PadnameFLAGS|5.021007||Viu
+padname_free|||ciu
+PADNAME_FROM_PV|5.021007||Viu
+PadnameIN_SCOPE|5.031004||Vniu
+PadnameIsOUR|5.017004||Vi
+PadnameIsSTATE|5.017004||Vi
+PadnameIsSTATE_on|5.021007||Viu
+PadnameLEN|5.017004|5.017004|x
+PadnamelistARRAY|5.017004|5.017004|x
+padnamelist_dup|5.021007||Vi
+padnamelist_fetch|5.021007|5.021007|xn
+padnamelist_free|||ciu
+PadnamelistMAX|5.017004|5.017004|x
+PadnamelistMAXNAMED|5.019003||Viu
+PadnamelistREFCNT|5.021007|5.021007|x
+PadnamelistREFCNT_dec|5.021007|5.021007|x
+padnamelist_store|5.021007|5.021007|x
+PadnameLVALUE|5.021006||Viu
+PadnameLVALUE_on|5.021006||Viu
+PadnameOURSTASH|5.017004||Vi
+PadnameOURSTASH_set|5.021007||Viu
+PadnameOUTER|5.017004||Vi
+PadnamePROTOCV|5.021007||Viu
+PadnamePV|5.017004|5.017004|x
+PadnameREFCNT|5.021007|5.021007|x
+PadnameREFCNT_dec|5.021007|5.021007|x
+PadnameSV|5.017004|5.017004|x
+PADNAMEt_LVALUE|5.021007||Viu
+PADNAMEt_OUR|5.021007||Viu
+PADNAMEt_OUTER|5.021007|5.021007|
+PADNAMEt_STATE|5.021007||Viu
+PADNAMEt_TYPED|5.021007||Viu
+PadnameTYPE|5.017004||Vi
+PadnameTYPE_set|5.021007||Viu
+PadnameUTF8|5.017004|5.017004|x
+pad_new|5.008001|5.008001|
+padnew_CLONE|5.008001||Viu
+padnew_SAVE|5.008001||Viu
+padnew_SAVESUB|5.008001||Viu
+pad_peg|5.009004||Viu
+pad_push|5.008001||cVi
+pad_reset|5.003007||Vi
+PAD_RESTORE_LOCAL|5.008001||Vi
+PAD_SAVE_LOCAL|5.008001||Vi
+PAD_SAVE_SETNULLPAD|5.008001||Vi
+PAD_SET_CUR|5.008001||Vi
+PAD_SET_CUR_NOSAVE|5.008002||Vi
+pad_setsv|5.008001||cV
+PAD_SETSV|5.008001||Vi
+pad_sv|5.003007||cV
+PAD_SV|5.003007||Vi
+PAD_SVl|5.008001||Vi
+pad_swipe|5.003007||Vi
+pad_tidy|5.008001|5.008001|x
+panic_write2|5.008001||Viu
+PARENT_FAKELEX_FLAGS|5.009005||Viu
+PARENT_PAD_INDEX|5.009005||Viu
+parse_arithexpr|5.013008|5.013008|x
+parse_barestmt|5.013007|5.013007|x
+parse_block|5.013007|5.013007|x
+parse_body|5.006000||Viu
+parse_fullexpr|5.013008|5.013008|x
+parse_fullstmt|5.013005|5.013005|x
+parse_gv_stash_name|5.019004||Viu
+parse_ident|5.017010||Viu
+parse_label|5.013007|5.013007|x
+parse_listexpr|5.013008|5.013008|x
+parse_lparen_question_flags|5.017009||Viu
+PARSE_OPTIONAL|5.013007|5.013007|
+parser_dup|5.009000|5.009000|u
+parser_free|5.009005||Viu
+parser_free_nexttoke_ops|5.017006||Viu
+parse_stmtseq|5.013006|5.013006|x
+parse_subsignature|5.031003|5.031003|x
+parse_termexpr|5.013008|5.013008|x
+parse_unicode_opts|5.008001||Viu
+parse_uniprop_string|5.027011||Viu
+PATCHLEVEL|5.003007||Viu
+path_is_searchable|5.019001||Vniu
+Pause|5.003007||Viu
+pause|5.005000||Viu
+pclose|5.003007||Viu
+peep|5.003007||Viu
+pending_ident|5.017004||Viu
+PERL_ABS|5.008001|5.003007|p
+Perl_acos|5.021004|5.021004|n
+perl_alloc|5.003007|5.003007|n
+PERL_ALLOC_CHECK|5.006000||Viu
+perl_alloc_using|5.006000||Vnu
+PERL_ANY_COW|5.017007||Viu
+PERL_API_REVISION|5.006000||Viu
+PERL_API_SUBVERSION|5.006000||Viu
+PERL_API_VERSION|5.006000||Viu
+PERL_API_VERSION_STRING|5.013004||Viu
+PERL_ARENA_ROOTS_SIZE|5.009004||Viu
+PERL_ARENA_SIZE|5.009003||Viu
+PERL_ARGS_ASSERT_CROAK_XS_USAGE|||ponu
+Perl_asin|5.021004|5.021004|n
+Perl_assert|5.011000||Viu
+perl_assert_ptr|5.027004||Viu
+PERL_ASYNC_CHECK|5.006000|5.006000|
+Perl_atan2|5.006000|5.006000|n
+Perl_atan|5.021004|5.021004|n
+Perl_atof2|5.006001||Viu
+Perl_atof|5.006000||Viu
+PERL_BCDVERSION||5.003007|onu
+PERL_BISON_VERSION|5.023008||Viu
+PERL_BITFIELD16|5.010001||Viu
+PERL_BITFIELD32|5.010001||Viu
+PERL_BITFIELD8|5.010001||Viu
+PERL_CALLCONV|5.005002||Viu
+PERL_CALLCONV_NO_RET|5.017002||Viu
+Perl_calloc|5.006000||Viu
+Perl_ceil|5.009001|5.009001|n
+PERL_CKDEF|5.006000||Viu
+perl_clone|5.006000||Vn
+perl_clone_using|5.006000||Vnu
+perl_construct|5.003007|5.003007|n
+PERL_COP_SEQMAX|5.013010||Viu
+PERL_COPY_ON_WRITE|5.023001||Viu
+Perl_cos|5.006000|5.006000|n
+Perl_cosh|5.021004|5.021004|n
+PERL_COUNT_MULTIPLIER|5.027007||Viu
+Perl_custom_op_xop|5.019006||V
+PERLDB_ALL|5.004002||Viu
+PERLDBf_GOTO|5.004005||Viu
+PERLDBf_INTER|5.004002||Viu
+PERLDBf_LINE|5.004002||Viu
+PERLDBf_NAMEANON|5.006000||Viu
+PERLDBf_NAMEEVAL|5.006000||Viu
+PERLDBf_NONAME|5.004005||Viu
+PERLDBf_NOOPT|5.004002||Viu
+PERLDBf_SAVESRC|5.010001||Viu
+PERLDBf_SAVESRC_INVALID|5.010001||Viu
+PERLDBf_SAVESRC_NOSUBS|5.010001||Viu
+PERLDBf_SINGLE|5.004002||Viu
+PERLDBf_SUB|5.004002||Viu
+PERLDBf_SUBLINE|5.004002||Viu
+PERLDB_GOTO|5.004005||Viu
+PERLDB_INTER|5.004002||Viu
+PERLDB_LINE|5.004002||Viu
+PERLDB_LINE_OR_SAVESRC|5.023002||Viu
+PERLDB_NAMEANON|5.006000||Viu
+PERLDB_NAMEEVAL|5.006000||Viu
+PERLDB_NOOPT|5.004002||Viu
+PERLDB_SAVESRC|5.010001||Viu
+PERLDB_SAVESRC_INVALID|5.010001||Viu
+PERLDB_SAVESRC_NOSUBS|5.010001||Viu
+PERLDB_SINGLE|5.004002||Viu
+PERLDB_SUB|5.004002||Viu
+PERLDB_SUBLINE|5.004002||Viu
+PERLDB_SUB_NN|5.004005||Viu
+PERL_DEB2|5.021007||Viu
+PERL_DEB|5.008001||Viu
+PERL_DEBUG|5.008001||Viu
+Perl_debug_log|5.003007||Viu
+PERL_DEBUG_PAD|5.007003||Viu
+PERL_DEBUG_PAD_ZERO|5.007003||Viu
+PERL_DECIMAL_VERSION|5.019008||Viu
+PERL_DEFAULT_DO_EXEC3_IMPLEMENTATION|5.009003||Viu
+perl_destruct|5.007003|5.007003|n
+PerlDir_chdir|5.005000||Viu
+PerlDir_close|5.005000||Viu
+PerlDir_mapA|5.006000||Viu
+PerlDir_mapW|5.006000||Viu
+PerlDir_mkdir|5.005000||Viu
+PerlDir_open|5.005000||Viu
+PerlDir_read|5.005000||Viu
+PerlDir_rewind|5.005000||Viu
+PerlDir_rmdir|5.005000||Viu
+PerlDir_seek|5.005000||Viu
+PerlDir_tell|5.005000||Viu
+PERL_DONT_CREATE_GVSV|5.009003||Viu
+Perl_drand48|5.019004||Viu
+Perl_drand48_init|5.019004||Viu
+PERL_DRAND48_QUAD|5.019004||Viu
+PERL_DTRACE_PROBE_ENTRY|5.023009||Viu
+PERL_DTRACE_PROBE_FILE_LOADED|5.023009||Viu
+PERL_DTRACE_PROBE_FILE_LOADING|5.023009||Viu
+PERL_DTRACE_PROBE_OP|5.023009||Viu
+PERL_DTRACE_PROBE_PHASE|5.023009||Viu
+PERL_DTRACE_PROBE_RETURN|5.023009||Viu
+PERL_EBCDIC_TABLES_H|5.027001||Viu
+PERL_ENABLE_EXPERIMENTAL_REGEX_OPTIMISATIONS|5.009004||Viu
+PERL_ENABLE_EXTENDED_TRIE_OPTIMISATION|5.009004||Viu
+PERL_ENABLE_POSITIVE_ASSERTION_STUDY|5.009005||Viu
+PERL_ENABLE_TRIE_OPTIMISATION|5.009004||Viu
+PerlEnv_clearenv|5.006000||Viu
+PerlEnv_ENVgetenv|5.006000||Viu
+PerlEnv_ENVgetenv_len|5.006000||Viu
+PerlEnv_free_childdir|5.006000||Viu
+PerlEnv_free_childenv|5.006000||Viu
+PerlEnv_get_childdir|5.006000||Viu
+PerlEnv_get_childenv|5.006000||Viu
+PerlEnv_get_child_IO|5.006000||Viu
+PerlEnv_getenv|5.005000||Viu
+PerlEnv_getenv_len|5.006000||Viu
+PerlEnv_lib_path|5.005000||Viu
+PerlEnv_os_id|5.006000||Viu
+PerlEnv_putenv|5.005000||Viu
+PerlEnv_sitelib_path|5.005000||Viu
+PerlEnv_uname|5.005004||Viu
+PerlEnv_vendorlib_path|5.006000||Viu
+Perl_error_log|5.006000||Viu
+Perl_eval_pv||5.003007|onu
+Perl_eval_sv||5.003007|onu
+PERL_EXIT_ABORT|5.019003|5.019003|
+PERL_EXIT_DESTRUCT_END|5.007003|5.007003|
+PERL_EXIT_EXPECTED|5.006000|5.006000|
+PERL_EXIT_WARN|5.019003|5.019003|
+Perl_exp|5.006000|5.006000|n
+PERL_FEATURE_H|5.029006||Viu
+PERL_FILE_IS_ABSOLUTE|5.006000||Viu
+PERL_FILTER_EXISTS|5.009005||Viu
+Perl_floor|5.006000|5.006000|n
+PERL_FLUSHALL_FOR_CHILD|5.006000||Viu
+Perl_fmod|5.006000|5.006000|n
+Perl_fp_class|5.007003||Viu
+Perl_fp_class_denorm|5.007003||Viu
+Perl_fp_class_inf|5.007003||Viu
+Perl_fp_class_nan|5.007003||Viu
+Perl_fp_class_ndenorm|5.007003||Viu
+Perl_fp_class_ninf|5.007003||Viu
+Perl_fp_class_nnorm|5.007003||Viu
+Perl_fp_class_norm|5.007003||Viu
+Perl_fp_class_nzero|5.007003||Viu
+Perl_fp_class_pdenorm|5.007003||Viu
+Perl_fp_class_pinf|5.007003||Viu
+Perl_fp_class_pnorm|5.007003||Viu
+Perl_fp_class_pzero|5.007003||Viu
+Perl_fp_class_qnan|5.007003||Viu
+Perl_fp_class_snan|5.007003||Viu
+Perl_fp_class_zero|5.007003||Viu
+PERL_FPU_INIT|5.007002||Viu
+PERL_FPU_POST_EXEC|5.008001||Viu
+PERL_FPU_PRE_EXEC|5.008001||Viu
+perl_free|5.003007|5.003007|n
+Perl_free_c_backtrace|5.021001||Viu
+Perl_frexp|5.006000|5.006000|n
+PERL_FS_VER_FMT|5.006000||Viu
+PERL_FS_VERSION|5.010001||Viu
+PERL_GCC_BRACE_GROUPS_FORBIDDEN|5.008001||Viu
+PERL_GET_CONTEXT|5.006000||Viu
+PERL_GET_INTERP|5.006000||Viu
+PERL_GET_THX|5.006000||Viu
+PERL_GIT_UNPUSHED_COMMITS|5.010001||Viu
+PERL_GPROF_MONCONTROL|5.007002||Viu
+PERL_HANDY_H|5.027001||Viu
+PERL_HASH|5.003007|5.003007|p
+PERL_HASH_DEFAULT_HvMAX|5.017011||Viu
+PERL_HASH_FUNC|5.017006||Viu
+PERL_HASH_FUNC_SIPHASH13|5.033007||Viu
+PERL_HASH_FUNC_ZAPHOD32|5.027001||Viu
+PERL_HASH_INTERNAL|5.008002||Viu
+PERL_HASH_ITER_BUCKET|5.018000||Viu
+PERL_HASH_RANDOMIZE_KEYS|5.018000||Viu
+PERL_HASH_SEED|5.008001||Viu
+PERL_HASH_SEED_BYTES|5.017006||Viu
+PERL_HASH_SEED_STATE|5.027001||Viu
+PERL_HASH_SEED_WORDS|5.033007||Viu
+PERL_HASH_STATE_BYTES|5.027001||Viu
+PERL_HASH_STATE_WORDS|5.033007||Viu
+PERL_HASH_USE_SBOX32_ALSO|5.027001||Viu
+PERL_HASH_WITH_SEED|5.021001||Viu
+PERL_HASH_WITH_STATE|5.027001||Viu
+PERL_HV_ALLOC_AUX_SIZE|5.019010||Viu
+PERL_HV_ARRAY_ALLOC_BYTES|5.006000||Viu
+PERL___I|5.009005||Viu
+PERL_IMPLICIT_CONTEXT|5.006000||Viu
+PERL_INC_VERSION_LIST|5.035001|5.035001|Vn
+Perl_internal_drand48|5.027004||Viu
+PERL_INTERPRETER_SIZE_UPTO_MEMBER|5.010000||Viu
+PERL_INT_MAX|5.003007|5.003007|p
+PERL_INT_MIN|5.003007|5.003007|p
+PERL_INVLIST_INLINE_H|5.029006||Viu
+PerlIO|5.003007||Viu
+PerlIO_apply_layers|5.007001|5.007001|
+PerlIOArg|5.007001||Viu
+PerlIOBase|5.007001||Viu
+PerlIO_binmode|5.007001|5.007001|
+PERLIOBUF_DEFAULT_BUFSIZ|5.013007||Viu
+PerlIO_canset_cnt|5.003007|5.003007|n
+PerlIO_clearerr|5.007003|5.007003|
+PerlIO_close|5.007003|5.007003|
+PerlIO_context_layers|5.009004|5.009004|u
+PerlIO_debug|5.007001|5.007001|
+PERLIO_DUP_CLONE|5.007003||Viu
+PERLIO_DUP_FD|5.007003||Viu
+PerlIO_eof|5.007003|5.007003|
+PerlIO_error|5.007003|5.007003|
+PerlIO_exportFILE|5.003007|5.003007|n
+PERLIO_F_APPEND|5.007001|5.007001|
+PerlIO_fast_gets|5.003007|5.003007|n
+PERLIO_F_CANREAD|5.007001|5.007001|
+PERLIO_F_CANWRITE|5.007001|5.007001|
+PERLIO_F_CLEARED|5.013008||Viu
+PERLIO_F_CRLF|5.007001|5.007001|
+PerlIO_fdopen|5.003007|5.003007|n
+PERLIO_F_EOF|5.007001|5.007001|
+PERLIO_F_ERROR|5.007001|5.007001|
+PERLIO_F_FASTGETS|5.007001|5.007001|
+PerlIO_fileno|5.007003|5.007003|
+PerlIO_fill|5.007003|5.007003|u
+PerlIO_findFILE|5.003007|5.003007|n
+PERLIO_F_LINEBUF|5.007001|5.007001|
+PerlIO_flush|5.007003|5.007003|
+PERLIO_F_NOTREG|5.008001||Viu
+PERLIO_F_OPEN|5.007001|5.007001|
+PERLIO_F_RDBUF|5.007001|5.007001|
+PERLIO_F_TEMP|5.007001|5.007001|
+PERLIO_F_TRUNCATE|5.007001|5.007001|
+PERLIO_F_TTY|5.007001||Viu
+PERLIO_F_UNBUF|5.007001|5.007001|
+PERLIO_FUNCS_CAST|5.009003||pVu
+PERLIO_FUNCS_DECL|5.009003|5.009003|pVu
+PERLIO_F_UTF8|5.007001|5.007001|
+PERLIO_F_WRBUF|5.007001|5.007001|
+PerlIO_get_base|5.007003|5.007003|
+PerlIO_get_bufsiz|5.007003|5.007003|
+PerlIO_getc|5.003007|5.003007|n
+PerlIO_get_cnt|5.007003|5.007003|
+PerlIO_getpos|5.003007|5.003007|n
+PerlIO_get_ptr|5.007003|5.007003|
+PERLIO_H|5.027001||Viu
+PerlIO_has_base|5.003007|5.003007|n
+PerlIO_has_cntptr|5.003007|5.003007|n
+PerlIO_importFILE|5.003007|5.003007|n
+PERLIO_INIT|5.009005||Viu
+PERLIO_K_BUFFERED|5.007001|5.007001|
+PERLIO_K_CANCRLF|5.007001|5.007001|
+PERLIO_K_DESTRUCT|5.007001||Viu
+PERLIO_K_DUMMY|5.007001||Viu
+PERLIO_K_FASTGETS|5.007001|5.007001|
+PERLIO_K_MULTIARG|5.007003|5.007003|
+PERLIO_K_RAW|5.007001|5.007001|
+PERLIO_K_UTF8|5.007001||Viu
+PERLIO_LAYERS|5.007001||Viu
+PERLIOL_H|5.027001||Viu
+PerlIONext|5.007001||Viu
+PERLIO_NOT_STDIO|5.003007||Viu
+PerlIO_open|5.003007|5.003007|n
+PerlIO_printf|5.006000|5.003007|
+PerlIO_putc|5.003007|5.003007|n
+PerlIO_puts|5.003007|5.003007|n
+PerlIO_read|5.007003|5.007003|
+PerlIO_releaseFILE|5.003007|5.003007|n
+PerlIO_reopen|5.003007|5.003007|n
+PerlIO_restore_errno|5.021006||cViu
+PerlIO_rewind|5.003007|5.003007|n
+PerlIO_save_errno|5.021006||cViu
+PerlIO_seek|5.007003|5.007003|
+PerlIOSelf|5.007001||Viu
+PerlIO_set_cnt|5.007003|5.007003|
+PerlIO_setlinebuf|5.007003|5.007003|
+PerlIO_setpos|5.003007|5.003007|n
+PerlIO_set_ptrcnt|5.007003|5.007003|
+PerlIO_stderr|5.007003|5.007003|
+PerlIO_stdin|5.007003|5.007003|
+PerlIO_stdout|5.007003|5.007003|
+PerlIO_stdoutf|5.006000|5.003007|
+PERLIO_STDTEXT|5.007001||Viu
+PerlIO_tell|5.007003|5.007003|
+PERLIO_TERM|5.009005||Viu
+PerlIO_ungetc|5.003007|5.003007|n
+PerlIO_unread|5.007003|5.007003|u
+PERLIO_USING_CRLF|5.007003||Viu
+PerlIOValid|5.007003||Viu
+PerlIO_vprintf|5.003007|5.003007|n
+PerlIO_write|5.007003|5.007003|
+Perl_isfinite|5.007003|5.007003|n
+Perl_isfinitel|5.021004||Viu
+PERL_IS_GCC|5.032001||Viu
+Perl_isinf|5.007003|5.007003|n
+Perl_isnan|5.006001|5.006001|n
+PERL_IS_SUBWORD_ADDR|5.027007||Viu
+PERL_JNP_TO_DECIMAL|5.033001||Viu
+Perl_langinfo|5.027004|5.027004|n
+PERL_LANGINFO_H|5.027004||Viu
+PERL_LAST_5_18_0_INTERP_MEMBER|5.017009||Viu
+Perl_ldexp|5.021003|5.021003|n
+PerlLIO_access|5.005000||Viu
+PerlLIO_chmod|5.005000||Viu
+PerlLIO_chown|5.005000||Viu
+PerlLIO_chsize|5.005000||Viu
+PerlLIO_close|5.005000||Viu
+PerlLIO_dup2|5.005000||Viu
+PerlLIO_dup2_cloexec|5.027008||Viu
+PerlLIO_dup|5.005000||Viu
+PerlLIO_dup_cloexec|5.027008||Viu
+PerlLIO_flock|5.005000||Viu
+PerlLIO_fstat|5.005000||Viu
+PerlLIO_ioctl|5.005000||Viu
+PerlLIO_isatty|5.005000||Viu
+PerlLIO_link|5.006000||Viu
+PerlLIO_lseek|5.005000||Viu
+PerlLIO_lstat|5.005000||Viu
+PerlLIO_mktemp|5.005000||Viu
+PerlLIO_open3|5.005000||Viu
+PerlLIO_open3_cloexec|5.027008||Viu
+PerlLIO_open|5.005000||Viu
+PerlLIO_open_cloexec|5.027008||Viu
+PerlLIO_read|5.005000||Viu
+PerlLIO_readlink|5.033005||Viu
+PerlLIO_rename|5.005000||Viu
+PerlLIO_setmode|5.005000||Viu
+PerlLIO_stat|5.005000||Viu
+PerlLIO_symlink|5.033005||Viu
+PerlLIO_tmpnam|5.005000||Viu
+PerlLIO_umask|5.005000||Viu
+PerlLIO_unlink|5.005000||Viu
+PerlLIO_utime|5.005000||Viu
+PerlLIO_write|5.005000||Viu
+PERL_LOADMOD_DENY|5.006000|5.003007|
+PERL_LOADMOD_IMPORT_OPS|5.006000|5.003007|
+PERL_LOADMOD_NOIMPORT|5.006000|5.003007|
+Perl_log10|5.021004|5.021004|n
+Perl_log|5.006000|5.006000|n
+PERL_LONG_MAX|5.003007|5.003007|p
+PERL_LONG_MIN|5.003007|5.003007|p
+PERL_MAGIC_arylen|5.007002|5.003007|p
+PERL_MAGIC_arylen_p|5.009003|5.009003|
+PERL_MAGIC_backref|5.007002|5.003007|p
+PERL_MAGIC_bm|5.007002|5.003007|p
+PERL_MAGIC_checkcall|5.013006|5.013006|
+PERL_MAGIC_collxfrm|5.007002|5.003007|p
+PERL_MAGIC_dbfile|5.007002|5.003007|p
+PERL_MAGIC_dbline|5.007002|5.003007|p
+PERL_MAGIC_debugvar|5.021005|5.021005|
+PERL_MAGIC_defelem|5.007002|5.003007|p
+PERL_MAGIC_env|5.007002|5.003007|p
+PERL_MAGIC_envelem|5.007002|5.003007|p
+PERL_MAGIC_ext|5.007002|5.003007|p
+PERL_MAGIC_fm|5.007002|5.003007|p
+PERL_MAGIC_glob||5.003007|ponu
+PERL_MAGIC_hints|5.009004|5.009004|
+PERL_MAGIC_hintselem|5.009004|5.009004|
+PERL_MAGIC_isa|5.007002|5.003007|p
+PERL_MAGIC_isaelem|5.007002|5.003007|p
+PERL_MAGIC_lvref|5.021005|5.021005|
+PERL_MAGIC_mutex||5.003007|ponu
+PERL_MAGIC_nkeys|5.007002|5.003007|p
+PERL_MAGIC_nonelem|5.027009|5.027009|
+PERL_MAGIC_overload||5.003007|ponu
+PERL_MAGIC_overload_elem||5.003007|ponu
+PERL_MAGIC_overload_table|5.007002|5.003007|p
+PERL_MAGIC_pos|5.007002|5.003007|p
+PERL_MAGIC_qr|5.007002|5.003007|p
+PERL_MAGIC_READONLY_ACCEPTABLE|5.015000||Viu
+PERL_MAGIC_regdata|5.007002|5.003007|p
+PERL_MAGIC_regdatum|5.007002|5.003007|p
+PERL_MAGIC_regex_global|5.007002|5.003007|p
+PERL_MAGIC_rhash|5.009003|5.009003|
+PERL_MAGIC_shared|5.007003|5.003007|p
+PERL_MAGIC_shared_scalar|5.007003|5.003007|p
+PERL_MAGIC_sig|5.007002|5.003007|p
+PERL_MAGIC_sigelem|5.007002|5.003007|p
+PERL_MAGIC_substr|5.007002|5.003007|p
+PERL_MAGIC_sv|5.007002|5.003007|p
+PERL_MAGIC_symtab|5.009003|5.009003|
+PERL_MAGIC_taint|5.007002|5.003007|p
+PERL_MAGIC_tied|5.007002|5.003007|p
+PERL_MAGIC_tiedelem|5.007002|5.003007|p
+PERL_MAGIC_tiedscalar|5.007002|5.003007|p
+PERL_MAGIC_TYPE_IS_VALUE_MAGIC|5.015000||Viu
+PERL_MAGIC_TYPE_READONLY_ACCEPTABLE|5.015000||Viu
+PERL_MAGIC_utf8|5.008001|5.003007|p
+PERL_MAGIC_UTF8_CACHESIZE|5.008001||Viu
+PERL_MAGIC_uvar|5.007002|5.003007|p
+PERL_MAGIC_uvar_elem|5.007003|5.003007|p
+PERL_MAGIC_VALUE_MAGIC|5.015000||Viu
+PERL_MAGIC_vec|5.007002|5.003007|p
+PERL_MAGIC_vstring|5.008001|5.003007|p
+PERL_MAGIC_VTABLE_MASK|5.015000||Viu
+Perl_malloc|5.006000||Viu
+PERL_MALLOC_CTL_H|5.027001||Viu
+Perl_malloc_good_size|5.010001||Viu
+PERL_MALLOC_WRAP|5.009002|5.009002|Vn
+PerlMem_calloc|5.006000||Viu
+PerlMem_free|5.005000||Viu
+PerlMem_free_lock|5.006000||Viu
+PerlMem_get_lock|5.006000||Viu
+PerlMem_is_locked|5.006000||Viu
+PerlMem_malloc|5.005000||Viu
+PERL_MEMORY_DEBUG_HEADER_SIZE|5.019009||Viu
+PerlMemParse_calloc|5.006000||Viu
+PerlMemParse_free|5.006000||Viu
+PerlMemParse_free_lock|5.006000||Viu
+PerlMemParse_get_lock|5.006000||Viu
+PerlMemParse_is_locked|5.006000||Viu
+PerlMemParse_malloc|5.006000||Viu
+PerlMemParse_realloc|5.006000||Viu
+PerlMem_realloc|5.005000||Viu
+PerlMemShared_calloc|5.006000||Viu
+PerlMemShared_free|5.006000||Viu
+PerlMemShared_free_lock|5.006000||Viu
+PerlMemShared_get_lock|5.006000||Viu
+PerlMemShared_is_locked|5.006000||Viu
+PerlMemShared_malloc|5.006000||Viu
+PerlMemShared_realloc|5.006000||Viu
+Perl_mfree|5.006000||Viu
+PERL_MG_UFUNC|5.007001||Viu
+Perl_modf|5.006000|5.006000|n
+PERL_MULTICONCAT_HEADER_SIZE|5.027006||Viu
+PERL_MULTICONCAT_IX_LENGTHS|5.027006||Viu
+PERL_MULTICONCAT_IX_NARGS|5.027006||Viu
+PERL_MULTICONCAT_IX_PLAIN_LEN|5.027006||Viu
+PERL_MULTICONCAT_IX_PLAIN_PV|5.027006||Viu
+PERL_MULTICONCAT_IX_UTF8_LEN|5.027006||Viu
+PERL_MULTICONCAT_IX_UTF8_PV|5.027006||Viu
+PERL_MULTICONCAT_MAXARG|5.027006||Viu
+Perl_my_mkostemp|5.027008||Viu
+Perl_my_mkstemp|5.027004||Viu
+PERL_MY_SNPRINTF_GUARDED|5.009004||Viu
+PERL_MY_SNPRINTF_POST_GUARD|5.021002||Viu
+PERL_MY_VSNPRINTF_GUARDED|5.009004||Viu
+PERL_MY_VSNPRINTF_POST_GUARD|5.021002||Viu
+PERL_NO_DEV_RANDOM|5.009004||Viu
+PERL_OBJECT_THIS|5.005000||Viu
+PERL_OP_PARENT|5.025001||Viu
+PERL_PADNAME_MINIMAL|5.021007||Viu
+PERL_PADSEQ_INTRO|5.013010||Viu
+perl_parse|5.006000|5.006000|n
+PERL_PATCHLEVEL_H_IMPLICIT|5.006000||Viu
+PERL_PATCHNUM|5.010001||Viu
+PERL_POISON_EXPR|5.019006||Viu
+Perl_pow|5.006000|5.006000|n
+Perl_pp_accept|5.013009||Viu
+Perl_pp_aelemfast_lex|5.015000||Viu
+Perl_pp_andassign|5.013009||Viu
+Perl_pp_avalues|5.013009||Viu
+Perl_pp_bind|5.013009||Viu
+Perl_pp_bit_xor|5.013009||Viu
+Perl_pp_chmod|5.013009||Viu
+Perl_pp_chomp|5.013009||Viu
+Perl_pp_connect|5.013009||Viu
+Perl_pp_cos|5.013009||Viu
+Perl_pp_custom|5.013009||Viu
+Perl_pp_dbmclose|5.013009||Viu
+PERL_PPDEF|5.006000||Viu
+Perl_pp_dofile|5.013009||Viu
+Perl_pp_dor|5.013009||Viu
+Perl_pp_dorassign|5.013009||Viu
+Perl_pp_dump|5.013009||Viu
+Perl_pp_egrent|5.013009||Viu
+Perl_pp_enetent|5.013009||Viu
+Perl_pp_eprotoent|5.013009||Viu
+Perl_pp_epwent|5.013009||Viu
+Perl_pp_eservent|5.013009||Viu
+Perl_pp_exp|5.013009||Viu
+Perl_pp_fcntl|5.013009||Viu
+Perl_pp_ftatime|5.013009||Viu
+Perl_pp_ftbinary|5.013009||Viu
+Perl_pp_ftblk|5.013009||Viu
+Perl_pp_ftchr|5.013009||Viu
+Perl_pp_ftctime|5.013009||Viu
+Perl_pp_ftdir|5.013009||Viu
+Perl_pp_fteexec|5.013009||Viu
+Perl_pp_fteowned|5.013009||Viu
+Perl_pp_fteread|5.013009||Viu
+Perl_pp_ftewrite|5.013009||Viu
+Perl_pp_ftfile|5.013009||Viu
+Perl_pp_ftmtime|5.013009||Viu
+Perl_pp_ftpipe|5.013009||Viu
+Perl_pp_ftrexec|5.013009||Viu
+Perl_pp_ftrwrite|5.013009||Viu
+Perl_pp_ftsgid|5.013009||Viu
+Perl_pp_ftsize|5.013009||Viu
+Perl_pp_ftsock|5.013009||Viu
+Perl_pp_ftsuid|5.013009||Viu
+Perl_pp_ftsvtx|5.013009||Viu
+Perl_pp_ftzero|5.013009||Viu
+Perl_pp_getpeername|5.013009||Viu
+Perl_pp_getsockname|5.013009||Viu
+Perl_pp_ggrgid|5.013009||Viu
+Perl_pp_ggrnam|5.013009||Viu
+Perl_pp_ghbyaddr|5.013009||Viu
+Perl_pp_ghbyname|5.013009||Viu
+Perl_pp_gnbyaddr|5.013009||Viu
+Perl_pp_gnbyname|5.013009||Viu
+Perl_pp_gpbyname|5.013009||Viu
+Perl_pp_gpbynumber|5.013009||Viu
+Perl_pp_gpwnam|5.013009||Viu
+Perl_pp_gpwuid|5.013009||Viu
+Perl_pp_gsbyname|5.013009||Viu
+Perl_pp_gsbyport|5.013009||Viu
+Perl_pp_gsockopt|5.013009||Viu
+Perl_pp_hex|5.013009||Viu
+Perl_pp_i_postdec|5.006000||Viu
+Perl_pp_i_postinc|5.006000||Viu
+Perl_pp_i_predec|5.006000||Viu
+Perl_pp_i_preinc|5.006000||Viu
+Perl_pp_keys|5.013009||Viu
+Perl_pp_kill|5.013009||Viu
+Perl_pp_lcfirst|5.013009||Viu
+Perl_pp_lineseq|5.013009||Viu
+Perl_pp_listen|5.013009||Viu
+Perl_pp_localtime|5.013009||Viu
+Perl_pp_log|5.013009||Viu
+Perl_pp_lstat|5.013009||Viu
+Perl_pp_mapstart|5.013009||Viu
+Perl_pp_msgctl|5.013009||Viu
+Perl_pp_msgget|5.013009||Viu
+Perl_pp_msgrcv|5.013009||Viu
+Perl_pp_msgsnd|5.013009||Viu
+Perl_pp_nbit_xor|5.021009||Viu
+Perl_pp_orassign|5.013009||Viu
+Perl_pp_padany|5.013009||Viu
+Perl_pp_pop|5.013009||Viu
+Perl_pp_read|5.013009||Viu
+Perl_pp_recv|5.013009||Viu
+Perl_pp_regcmaybe|5.013009||Viu
+Perl_pp_rindex|5.013009||Viu
+Perl_pp_rv2hv|5.013009||Viu
+Perl_pp_say|5.013009||Viu
+Perl_pp_sbit_xor|5.021009||Viu
+Perl_pp_scalar|5.013009||Viu
+Perl_pp_schomp|5.013009||Viu
+Perl_pp_scope|5.013009||Viu
+Perl_pp_seek|5.013009||Viu
+Perl_pp_semop|5.013009||Viu
+Perl_pp_send|5.013009||Viu
+Perl_pp_sge|5.013009||Viu
+Perl_pp_sgrent|5.013009||Viu
+Perl_pp_sgt|5.013009||Viu
+Perl_pp_shmctl|5.013009||Viu
+Perl_pp_shmget|5.013009||Viu
+Perl_pp_shmread|5.013009||Viu
+Perl_pp_shutdown|5.013009||Viu
+Perl_pp_slt|5.013009||Viu
+Perl_pp_snetent|5.013009||Viu
+Perl_pp_socket|5.013009||Viu
+Perl_pp_sprotoent|5.013009||Viu
+Perl_pp_spwent|5.013009||Viu
+Perl_pp_sqrt|5.013009||Viu
+Perl_pp_sservent|5.013009||Viu
+Perl_pp_ssockopt|5.013009||Viu
+Perl_pp_symlink|5.013009||Viu
+Perl_pp_transr|5.013009||Viu
+Perl_pp_unlink|5.013009||Viu
+Perl_pp_utime|5.013009||Viu
+Perl_pp_values|5.013009||Viu
+PERL_PRESERVE_IVUV|5.007001||Viu
+PERL_PRIeldbl|5.006001|5.006001|Vn
+PERL_PRIfldbl|5.006000|5.006000|Vn
+PERL_PRIgldbl|5.006000|5.006000|Vn
+PerlProc_abort|5.005000||Viu
+PerlProc_crypt|5.005000||Viu
+PerlProc_DynaLoad|5.006000||Viu
+PerlProc_execl|5.005000||Viu
+PerlProc_execv|5.005000||Viu
+PerlProc_execvp|5.005000||Viu
+PerlProc__exit|5.005000||Viu
+PerlProc_exit|5.005000||Viu
+PerlProc_fork|5.006000||Viu
+PerlProc_getegid|5.005000||Viu
+PerlProc_geteuid|5.005000||Viu
+PerlProc_getgid|5.005000||Viu
+PerlProc_getlogin|5.005000||Viu
+PerlProc_GetOSError|5.006000||Viu
+PerlProc_getpid|5.006000||Viu
+PerlProc_gettimeofday|5.008000||Viu
+PerlProc_getuid|5.005000||Viu
+PerlProc_kill|5.005000||Viu
+PerlProc_killpg|5.005000||Viu
+PerlProc_lasthost|5.007001||Viu
+PerlProc_longjmp|5.005000||Viu
+PerlProc_pause|5.005000||Viu
+PerlProc_pclose|5.005000||Viu
+PerlProc_pipe|5.005000||Viu
+PerlProc_pipe_cloexec|5.027008||Viu
+PerlProc_popen|5.005000||Viu
+PerlProc_popen_list|5.007001||Viu
+PerlProc_setgid|5.005000||Viu
+PerlProc_setjmp|5.005000||Viu
+PerlProc_setuid|5.005000||Viu
+PerlProc_signal|5.005000||Viu
+PerlProc_sleep|5.005000||Viu
+PerlProc_spawnvp|5.008000||Viu
+PerlProc_times|5.005000||Viu
+PerlProc_wait|5.005000||Viu
+PerlProc_waitpid|5.005000||Viu
+perl_pthread_mutex_lock|5.023006||Viu
+perl_pthread_mutex_unlock|5.023006||Viu
+PERL_PV_ESCAPE_ALL|5.009004|5.003007|p
+PERL_PV_ESCAPE_DWIM|5.019008||Viu
+PERL_PV_ESCAPE_FIRSTCHAR|5.009004|5.003007|p
+PERL_PV_ESCAPE_NOBACKSLASH|5.009004|5.003007|p
+PERL_PV_ESCAPE_NOCLEAR|5.009004|5.003007|p
+PERL_PV_ESCAPE_NONASCII|5.013009|5.013009|
+PERL_PV_ESCAPE_QUOTE|5.009004|5.003007|p
+PERL_PV_ESCAPE_RE|5.009005|5.003007|p
+PERL_PV_ESCAPE_UNI|5.009004|5.003007|p
+PERL_PV_ESCAPE_UNI_DETECT|5.009004|5.003007|p
+PERL_PV_PRETTY_DUMP|5.009004||pcV
+PERL_PV_PRETTY_ELLIPSES|5.010000|5.003007|p
+PERL_PV_PRETTY_EXACTSIZE|5.021005||Viu
+PERL_PV_PRETTY_LTGT|5.009004|5.003007|p
+PERL_PV_PRETTY_NOCLEAR|5.010000||pcV
+PERL_PV_PRETTY_QUOTE|5.009004|5.003007|p
+PERL_PV_PRETTY_REGPROP|5.009004||pcV
+PERL_QUAD_MAX|5.003007|5.003007|p
+PERL_QUAD_MIN|5.003007|5.003007|p
+PERL_READ_LOCK|5.033005||Viu
+PERL_READ_UNLOCK|5.033005||Viu
+Perl_realloc|5.006000||Viu
+PERL_REENTR_API|5.009005||Viu
+PERL_REENTR_H|5.027001||Viu
+PERL_REENTR_USING_ASCTIME_R|5.031011||Viu
+PERL_REENTR_USING_CRYPT_R|5.031011||Viu
+PERL_REENTR_USING_CTERMID_R|5.031011||Viu
+PERL_REENTR_USING_CTIME_R|5.031011||Viu
+PERL_REENTR_USING_ENDGRENT_R|5.031011||Viu
+PERL_REENTR_USING_ENDHOSTENT_R|5.031011||Viu
+PERL_REENTR_USING_ENDNETENT_R|5.031011||Viu
+PERL_REENTR_USING_ENDPROTOENT_R|5.031011||Viu
+PERL_REENTR_USING_ENDPWENT_R|5.031011||Viu
+PERL_REENTR_USING_ENDSERVENT_R|5.031011||Viu
+PERL_REENTR_USING_GETGRENT_R|5.031011||Viu
+PERL_REENTR_USING_GETGRGID_R|5.031011||Viu
+PERL_REENTR_USING_GETGRNAM_R|5.031011||Viu
+PERL_REENTR_USING_GETHOSTBYADDR_R|5.031011||Viu
+PERL_REENTR_USING_GETHOSTBYNAME_R|5.031011||Viu
+PERL_REENTR_USING_GETHOSTENT_R|5.031011||Viu
+PERL_REENTR_USING_GETLOGIN_R|5.031011||Viu
+PERL_REENTR_USING_GETNETBYADDR_R|5.031011||Viu
+PERL_REENTR_USING_GETNETBYNAME_R|5.031011||Viu
+PERL_REENTR_USING_GETNETENT_R|5.031011||Viu
+PERL_REENTR_USING_GETPROTOBYNAME_R|5.031011||Viu
+PERL_REENTR_USING_GETPROTOBYNUMBER_R|5.031011||Viu
+PERL_REENTR_USING_GETPROTOENT_R|5.031011||Viu
+PERL_REENTR_USING_GETPWENT_R|5.031011||Viu
+PERL_REENTR_USING_GETPWNAM_R|5.031011||Viu
+PERL_REENTR_USING_GETPWUID_R|5.031011||Viu
+PERL_REENTR_USING_GETSERVBYNAME_R|5.031011||Viu
+PERL_REENTR_USING_GETSERVBYPORT_R|5.031011||Viu
+PERL_REENTR_USING_GETSERVENT_R|5.031011||Viu
+PERL_REENTR_USING_GETSPNAM_R|5.031011||Viu
+PERL_REENTR_USING_GMTIME_R|5.031011||Viu
+PERL_REENTR_USING_LOCALTIME_R|5.031011||Viu
+PERL_REENTR_USING_READDIR64_R|5.031011||Viu
+PERL_REENTR_USING_READDIR_R|5.031011||Viu
+PERL_REENTR_USING_SETGRENT_R|5.031011||Viu
+PERL_REENTR_USING_SETHOSTENT_R|5.031011||Viu
+PERL_REENTR_USING_SETLOCALE_R|5.031011||Viu
+PERL_REENTR_USING_SETNETENT_R|5.031011||Viu
+PERL_REENTR_USING_SETPROTOENT_R|5.031011||Viu
+PERL_REENTR_USING_SETPWENT_R|5.031011||Viu
+PERL_REENTR_USING_SETSERVENT_R|5.031011||Viu
+PERL_REENTR_USING_STRERROR_R|5.031011||Viu
+PERL_REENTR_USING_TMPNAM_R|5.031011||Viu
+PERL_REENTR_USING_TTYNAME_R|5.031011||Viu
+PERL_REGCHARCLASS_H|5.027001||Viu
+PERL_REGCOMP_H|5.029006||Viu
+PERL_REGMATCH_SLAB_SLOTS|5.009004||Viu
+PERL_RELOCATABLE_INC|5.017002|5.017002|Vn
+PERL_REVISION|5.006000|5.006000|d
+perl_run|5.003007|5.003007|n
+PERL_RW_MUTEX_DESTROY|5.033005||Viu
+PERL_RW_MUTEX_INIT|5.033005||Viu
+Perl_safesysmalloc_size|5.010001||Viu
+PERL_SAWAMPERSAND|5.017010||Viu
+PERL_SCAN_ALLOW_MEDIAL_UNDERSCORES|5.031009||Viu
+PERL_SCAN_ALLOW_UNDERSCORES|5.007003|5.003007|p
+PERL_SCAN_DISALLOW_PREFIX|5.007003|5.003007|p
+PERL_SCAN_GREATER_THAN_UV_MAX|5.007003|5.003007|p
+PERL_SCAN_NOTIFY_ILLDIGIT|5.031008||Viu
+PERL_SCAN_SILENT_ILLDIGIT|5.008001|5.003007|p
+PERL_SCAN_SILENT_NON_PORTABLE|5.015001||Viu
+PERL_SCAN_SILENT_OVERFLOW|5.031009||Viu
+PERL_SCAN_TRAILING|5.021002|5.021002|
+PERL_SCNfldbl|5.006001|5.006001|Vn
+PERL_SCRIPT_MODE|5.004005||Viu
+PERL_SEEN_HV_FUNC_H|5.017010||Viu
+PERL_SEEN_HV_MACRO_H|5.027001||Viu
+PERL_SET_CONTEXT|5.006000||Viu
+PERL_SET_INTERP|5.006000||Viu
+Perl_setlocale|5.027002|5.027002|n
+PERL_SET_PHASE|5.015001||Viu
+PERL_SET_THX|5.006000||Viu
+Perl_sharepvn|5.006000||Viu
+PERL_SHORT_MAX|5.003007|5.003007|p
+PERL_SHORT_MIN|5.003007|5.003007|p
+PERLSI_DESTROY|5.005000||Viu
+PERLSI_DIEHOOK|5.005000||Viu
+PERL_SIGNALS_UNSAFE_FLAG|5.008001|5.003007|p
+Perl_signbit|5.009005|5.009005|xn
+PERLSI_MAGIC|5.005000||Viu
+PERLSI_MAIN|5.005000||Viu
+PERLSI_MULTICALL|5.023000||Viu
+Perl_sin|5.006000|5.006000|n
+Perl_sinh|5.021004|5.021004|n
+PerlSIO_canset_cnt|5.007001||Viu
+PerlSIO_clearerr|5.007001||Viu
+PerlSIO_fast_gets|5.007001||Viu
+PerlSIO_fclose|5.007001||Viu
+PerlSIO_fdopen|5.007001||Viu
+PerlSIO_fdupopen|5.007001||Viu
+PerlSIO_feof|5.007001||Viu
+PerlSIO_ferror|5.007001||Viu
+PerlSIO_fflush|5.007001||Viu
+PerlSIO_fgetc|5.007001||Viu
+PerlSIO_fgetpos|5.007001||Viu
+PerlSIO_fgets|5.007001||Viu
+PerlSIO_fileno|5.007001||Viu
+PerlSIO_fopen|5.007001||Viu
+PerlSIO_fputc|5.007001||Viu
+PerlSIO_fputs|5.007001||Viu
+PerlSIO_fread|5.007001||Viu
+PerlSIO_freopen|5.007001||Viu
+PerlSIO_fseek|5.007001||Viu
+PerlSIO_fsetpos|5.007001||Viu
+PerlSIO_ftell|5.007001||Viu
+PerlSIO_fwrite|5.007001||Viu
+PerlSIO_get_base|5.007001||Viu
+PerlSIO_get_bufsiz|5.007001||Viu
+PerlSIO_get_cnt|5.007001||Viu
+PerlSIO_get_ptr|5.007001||Viu
+PerlSIO_has_base|5.007001||Viu
+PerlSIO_has_cntptr|5.007001||Viu
+PerlSIO_init|5.007001||Viu
+PerlSIO_printf|5.007001||Viu
+PerlSIO_rewind|5.007001||Viu
+PerlSIO_setbuf|5.007001||Viu
+PerlSIO_set_cnt|5.007001||Viu
+PerlSIO_setlinebuf|5.007001||Viu
+PerlSIO_set_ptr|5.007001||Viu
+PerlSIO_setvbuf|5.007001||Viu
+PerlSIO_stderr|5.007001||Viu
+PerlSIO_stdin|5.007001||Viu
+PerlSIO_stdout|5.007001||Viu
+PerlSIO_stdoutf|5.007001||Viu
+PerlSIO_tmpfile|5.007001||Viu
+PerlSIO_ungetc|5.007001||Viu
+PERLSI_OVERLOAD|5.005000||Viu
+PerlSIO_vprintf|5.007001||Viu
+PERL_SIPHASH_FNC|5.025008||Viu
+PERLSI_REGCOMP|5.031011||Viu
+PERLSI_REQUIRE|5.005000||Viu
+PERLSI_SIGNAL|5.005000||Viu
+PERLSI_SORT|5.005000||Viu
+PERLSI_UNDEF|5.005000||Viu
+PERLSI_UNKNOWN|5.005000||Viu
+PERLSI_WARNHOOK|5.005000||Viu
+PERL_SMALL_MACRO_BUFFER|5.023008||Viu
+PERL_SNPRINTF_CHECK|5.021002||Viu
+PerlSock_accept|5.005000||Viu
+PerlSock_accept_cloexec|5.027008||Viu
+PerlSock_bind|5.005000||Viu
+PerlSock_closesocket|5.006000||Viu
+PerlSock_connect|5.005000||Viu
+PerlSock_endhostent|5.005000||Viu
+PerlSock_endnetent|5.005000||Viu
+PerlSock_endprotoent|5.005000||Viu
+PerlSock_endservent|5.005000||Viu
+PerlSock_gethostbyaddr|5.005000||Viu
+PerlSock_gethostbyname|5.005000||Viu
+PerlSock_gethostent|5.005000||Viu
+PerlSock_gethostname|5.005000||Viu
+PerlSock_getnetbyaddr|5.005000||Viu
+PerlSock_getnetbyname|5.005000||Viu
+PerlSock_getnetent|5.005000||Viu
+PerlSock_getpeername|5.005000||Viu
+PerlSock_getprotobyname|5.005000||Viu
+PerlSock_getprotobynumber|5.005000||Viu
+PerlSock_getprotoent|5.005000||Viu
+PerlSock_getservbyname|5.005000||Viu
+PerlSock_getservbyport|5.005000||Viu
+PerlSock_getservent|5.005000||Viu
+PerlSock_getsockname|5.005000||Viu
+PerlSock_getsockopt|5.005000||Viu
+PerlSock_htonl|5.005000||Viu
+PerlSock_htons|5.005000||Viu
+PerlSock_inet_addr|5.005000||Viu
+PerlSock_inet_ntoa|5.005000||Viu
+PerlSock_listen|5.005000||Viu
+PerlSock_ntohl|5.005000||Viu
+PerlSock_ntohs|5.005000||Viu
+PerlSock_recv|5.005000||Viu
+PerlSock_recvfrom|5.005000||Viu
+PerlSock_select|5.005000||Viu
+PerlSock_send|5.005000||Viu
+PerlSock_sendto|5.005000||Viu
+PerlSock_sethostent|5.005000||Viu
+PerlSock_setnetent|5.005000||Viu
+PerlSock_setprotoent|5.005000||Viu
+PerlSock_setservent|5.005000||Viu
+PerlSock_setsockopt|5.005000||Viu
+PerlSock_shutdown|5.005000||Viu
+PERL_SOCKS_NEED_PROTOTYPES|5.007001||Viu
+PerlSock_socket|5.005000||Viu
+PerlSock_socket_cloexec|5.027008||Viu
+PerlSock_socketpair|5.005000||Viu
+PerlSock_socketpair_cloexec|5.027008||Viu
+Perl_sqrt|5.006000|5.006000|n
+PERL_STACK_OVERFLOW_CHECK|5.006000||Viu
+PERL_STATIC_FORCE_INLINE|5.031011||Viu
+PERL_STATIC_FORCE_INLINE_NO_RET|5.031011||Viu
+PERL_STATIC_INLINE|5.013004|5.013004|Vn
+PERL_STATIC_INLINE_NO_RET|5.017005||Viu
+PERL_STATIC_NO_RET|5.017005||Viu
+PERL_STRLEN_EXPAND_SHIFT|5.013004||Viu
+PERL_STRLEN_ROUNDUP|5.009003||Viu
+PERL_STRLEN_ROUNDUP_QUANTUM|5.009003||Viu
+Perl_strtod|5.021004||Viu
+PERL_SUB_DEPTH_WARN|5.010001||Viu
+PERL_SUBVERSION|5.006000|5.003007|d
+PERL_SYS_FPU_INIT|5.021005||Viu
+PERL_SYS_INIT3|5.006000|5.006000|
+PERL_SYS_INIT3_BODY|5.010000||Viu
+PERL_SYS_INIT|5.003007|5.003007|
+PERL_SYS_INIT_BODY|5.010000||Viu
+PERL_SYS_TERM|5.003007|5.003007|
+PERL_SYS_TERM_BODY|5.010000||Viu
+Perl_tan|5.021004|5.021004|n
+Perl_tanh|5.021004|5.021004|n
+PERL_TARGETARCH|5.007002|5.007002|Vn
+PERL_TIME64_CONFIG_H|5.027001||Viu
+PERL_TIME64_H|5.027001||Viu
+PERL_TRACK_MEMPOOL|5.009003||Viu
+PERL_TSA|5.023006||Viu
+PERL_TSA_ACQUIRE|5.023006||Viu
+PERL_TSA_ACTIVE|5.023006||Viu
+PERL_TSA_CAPABILITY|5.023006||Viu
+PERL_TSA_EXCLUDES|5.023006||Viu
+PERL_TSA_GUARDED_BY|5.023006||Viu
+PERL_TSA_NO_TSA|5.023006||Viu
+PERL_TSA_PT_GUARDED_BY|5.023006||Viu
+PERL_TSA_RELEASE|5.023006||Viu
+PERL_TSA_REQUIRES|5.023006||Viu
+PERL_UCHAR_MAX|5.003007|5.003007|p
+PERL_UCHAR_MIN|5.003007|5.003007|p
+PERL_UINT_MAX|5.003007|5.003007|p
+PERL_UINT_MIN|5.003007|5.003007|p
+PERL_ULONG_MAX|5.003007|5.003007|p
+PERL_ULONG_MIN|5.003007|5.003007|p
+PERL_UNICODE_ALL_FLAGS|5.008001||Viu
+PERL_UNICODE_ARGV|5.008001||Viu
+PERL_UNICODE_ARGV_FLAG|5.008001||Viu
+PERL_UNICODE_CONSTANTS_H|5.027001||Viu
+PERL_UNICODE_DEFAULT_FLAGS|5.008001||Viu
+PERL_UNICODE_IN|5.008001||Viu
+PERL_UNICODE_IN_FLAG|5.008001||Viu
+PERL_UNICODE_INOUT|5.008001||Viu
+PERL_UNICODE_INOUT_FLAG|5.008001||Viu
+PERL_UNICODE_LOCALE|5.008001||Viu
+PERL_UNICODE_LOCALE_FLAG|5.008001||Viu
+PERL_UNICODE_MAX|5.007003||Viu
+PERL_UNICODE_OUT|5.008001||Viu
+PERL_UNICODE_OUT_FLAG|5.008001||Viu
+PERL_UNICODE_STD|5.008001||Viu
+PERL_UNICODE_STDERR|5.008001||Viu
+PERL_UNICODE_STDERR_FLAG|5.008001||Viu
+PERL_UNICODE_STD_FLAG|5.008001||Viu
+PERL_UNICODE_STDIN|5.008001||Viu
+PERL_UNICODE_STDIN_FLAG|5.008001||Viu
+PERL_UNICODE_STDOUT|5.008001||Viu
+PERL_UNICODE_STDOUT_FLAG|5.008001||Viu
+PERL_UNICODE_UTF8CACHEASSERT|5.009004||Viu
+PERL_UNICODE_UTF8CACHEASSERT_FLAG|5.009004||Viu
+PERL_UNICODE_WIDESYSCALLS|5.008001||Viu
+PERL_UNICODE_WIDESYSCALLS_FLAG|5.008001||Viu
+PERL_UNLOCK_HOOK|5.009004||Viu
+PERL_UNUSED_ARG|5.009003|5.003007|p
+PERL_UNUSED_CONTEXT|5.009004|5.003007|p
+PERL_UNUSED_DECL|5.007002|5.003007|p
+PERL_UNUSED_RESULT|5.021001|5.003007|p
+PERL_UNUSED_VAR|5.007002|5.003007|p
+PERL_UQUAD_MAX|5.003007|5.003007|p
+PERL_UQUAD_MIN|5.003007|5.003007|p
+PERL_USE_DEVEL|5.010001|5.010001|Vn
+PERL_USE_GCC_BRACE_GROUPS|5.009004|5.003007|pV
+PERL_USES_PL_PIDSTATUS|5.009003||Viu
+PERL_USHORT_MAX|5.003007|5.003007|p
+PERL_USHORT_MIN|5.003007|5.003007|p
+PERL_UTF8_H|5.027001||Viu
+PERL_UTIL_H|5.025012||Viu
+Perl_va_copy|5.007001||Viu
+PERLVAR|5.005000||Viu
+PERLVARA|5.006000||Viu
+PERLVARI|5.005000||Viu
+PERL_VARIANTS_WORD_MASK|5.027007||Viu
+PERLVARIC|5.005000||Viu
+PERL_VERSION|5.006000|5.003007|d
+PERL_VERSION_EQ|5.033001||p
+PERL_VERSION_GE|5.033001|5.003007|p
+PERL_VERSION_GT|5.033001|5.003007|p
+PERL_VERSION_LE|5.033001|5.003007|p
+PERL_VERSION_LT|5.033001|5.003007|p
+PERL_VERSION_MAJOR|5.033001||Viu
+PERL_VERSION_MINOR|5.033001||Viu
+PERL_VERSION_NE|5.033001||p
+PERL_VERSION_PATCH|5.033001||Viu
+PERL_VERSION_STRING|5.010001||Viu
+PERL_WAIT_FOR_CHILDREN|5.006000||Viu
+Perl_Warn_Bit|5.033003||Viu
+Perl_warner_nocontext||5.004000|ponu
+PERL_WARNHOOK_FATAL|5.009004||Viu
+Perl_Warn_Off|5.033003||Viu
+PERL_WORD_BOUNDARY_MASK|5.027007||Viu
+PERL_WORDSIZE|5.027007||Viu
+PERL_WRITE_LOCK|5.033005||Viu
+PERL_WRITE_MSG_TO_CONSOLE|5.007003||Viu
+PERL_WRITE_UNLOCK|5.033005||Viu
+PERL_XSUB_H|5.027001||Viu
+perly_sighandler|5.031007||cVnu
+PHOSTNAME|5.006000|5.006000|Vn
+pidgone|5.003007||Viu
+Pid_t|5.005000|5.005000|Vn
+pipe|5.005000||Viu
+PIPE_OPEN_MODE|5.008002||Viu
+PIPESOCK_MODE|5.008001||Viu
+PL_AboveLatin1|5.015008||Viu
+PL_amagic_generation|5.005000||Viu
+PL_an|5.005000||Viu
+PL_argvgv|5.005000||Viu
+PL_argvoutgv|5.005000||Viu
+PL_argvout_stack|5.006000||Viu
+PL_Assigned_invlist|5.025009||Viu
+PL_basetime|5.005000||Viu
+PL_beginav|5.005000||Viu
+PL_beginav_save|5.006001||Viu
+PL_blockhooks|5.013003||Viu
+PL_body_arenas|5.009004||Viu
+PL_body_roots|5.009003||Viu
+PL_bodytarget|5.005000||Viu
+PL_breakable_sub_gen|5.010001||Viu
+PL_bufend||5.003007|ponu
+PL_bufptr||5.003007|ponu
+PL_CCC_non0_non230|5.029008||Viu
+PL_check|5.009003|5.006000|
+PL_checkav|5.006000||Viu
+PL_checkav_save|5.008001||Viu
+PL_chopset|5.005000||Viu
+PL_clocktick|5.008001||Viu
+PL_collation_ix|5.005000||Viu
+PL_collation_name|5.005000||Viu
+PL_collation_standard|5.005000||Viu
+PL_collxfrm_base|5.005000||Viu
+PL_collxfrm_mult|5.005000||Viu
+PL_colors|5.005000||Viu
+PL_colorset|5.005000||Viu
+PL_compcv|5.005000||Viu
+PL_compiling|5.005000|5.003007|poVnu
+PL_comppad|5.008001|5.008001|x
+PL_comppad_name|5.017004|5.017004|x
+PL_comppad_name_fill|5.005000||Viu
+PL_comppad_name_floor|5.005000||Viu
+PL_constpadix|5.021004||Viu
+PL_copline||5.003007|ponu
+PL_cop_seqmax|5.005000||Viu
+PL_cshlen|5.005000||Viu
+PL_curcop|5.004005|5.003007|p
+PL_curcopdb|5.005000||Viu
+PL_curlocales|5.027009||Viu
+PL_curpad|5.005000|5.005000|x
+PL_curpm|5.005000||Viu
+PL_curpm_under|5.025007||Viu
+PL_curstack|5.005000||Viu
+PL_curstackinfo|5.005000||Viu
+PL_curstash|5.004005|5.003007|p
+PL_curstname|5.005000||Viu
+PL_custom_op_descs|5.007003||Viu
+PL_custom_op_names|5.007003||Viu
+PL_custom_ops|5.013007||Viu
+PL_cv_has_eval|5.009000||Viu
+PL_dbargs|5.005000||Viu
+PL_DBcontrol|5.021005||Viu
+PL_DBcv|5.005000||Viu
+PL_DBgv|5.005000||Viu
+PL_DBline|5.005000||Viu
+PL_DBsignal|5.005000|5.003007|poVnu
+PL_DBsignal_iv|5.021005||Viu
+PL_DBsingle|5.005000||pV
+PL_DBsingle_iv|5.021005||Viu
+PL_DBsub|5.005000||pV
+PL_DBtrace|5.005000||pV
+PL_DBtrace_iv|5.021005||Viu
+PL_debstash|5.005000|5.003007|poVnu
+PL_debug|5.005000||Viu
+PL_debug_pad|5.007003||Viu
+PL_defgv|5.004005|5.003007|p
+PL_def_layerlist|5.007003||Viu
+PL_defoutgv|5.005000||Viu
+PL_defstash|5.005000||Viu
+PL_delaymagic|5.005000||Viu
+PL_delaymagic_egid|5.015008||Viu
+PL_delaymagic_euid|5.015008||Viu
+PL_delaymagic_gid|5.015008||Viu
+PL_delaymagic_uid|5.015008||Viu
+PL_destroyhook|5.010000||Viu
+PL_diehook|5.005000|5.003007|poVnu
+PL_Dir|5.006000||Viu
+PL_dirty|5.005000|5.003007|poVnu
+PL_doswitches|5.005000||Viu
+PL_dowarn|5.005000||pV
+PL_dumper_fd|5.009003||Viu
+PL_dumpindent|5.006000||Viu
+PL_dump_re_max_len|5.023008||Viu
+PL_efloatbuf|5.006000||Viu
+PL_efloatsize|5.006000||Viu
+PL_E_FORMAT_PRECISION|5.029000||Viu
+PL_encoding|5.007003||Viu
+PL_endav|5.005000||Viu
+PL_Env|5.006000||Viu
+PL_envgv|5.005000||Viu
+PL_errgv|5.004005|5.003007|p
+PL_error_count||5.003007|ponu
+PL_errors|5.006000||Viu
+PL_e_script|5.005000||Viu
+PL_eval_root|5.005000||Viu
+PL_evalseq|5.005000||Viu
+PL_eval_start|5.005000||Viu
+PL_exit_flags|5.006000|5.006000|
+PL_exitlist|5.005000||Viu
+PL_exitlistlen|5.005000||Viu
+PL_expect||5.003007|ponu
+PL_fdpid|5.005000||Viu
+PL_filemode|5.005000||Viu
+PL_firstgv|5.005000||Viu
+PL_forkprocess|5.005000||Viu
+PL_formtarget|5.005000||Viu
+PL_GCB_invlist|5.021009||Viu
+PL_generation|5.005000||Viu
+PL_gensym|5.005000||Viu
+PL_globalstash|5.005000||Viu
+PL_globhook|5.015005||Viu
+PL_hash_rand_bits|5.017010||Viu
+PL_HASH_RAND_BITS_ENABLED|5.018000||Viu
+PL_hash_rand_bits_enabled|5.018000||Viu
+PL_hash_seed|5.033007||Viu
+PL_hash_state|5.033007||Viu
+PL_HasMultiCharFold|5.017005||Viu
+PL_hexdigit||5.003007|pn
+PL_hintgv|5.005000||Viu
+PL_hints|5.005000|5.003007|poVnu
+PL_hv_fetch_ent_mh|5.005000||Viu
+PL_incgv|5.005000||Viu
+PL_in_clean_all|5.005000||Viu
+PL_in_clean_objs|5.005000||Viu
+PL_in_eval|5.005000||Viu
+PL_initav|5.005000||Viu
+PL_in_load_module|5.008001||Viu
+PL_in_my||5.003007|ponu
+PL_in_my_stash||5.005000|ponu
+PL_inplace|5.005000||Viu
+PL_in_some_fold|5.029007||Viu
+PL_internal_random_state|5.027004||Viu
+PL_in_utf8_COLLATE_locale|5.025002||Viu
+PL_in_utf8_CTYPE_locale|5.019009||Viu
+PL_in_utf8_turkic_locale|5.029008||Viu
+PL_isarev|5.009005||Viu
+PL_keyword_plugin|5.011002|5.011002|x
+PL_known_layers|5.007003||Viu
+PL_langinfo_buf|5.027004||Viu
+PL_langinfo_bufsize|5.027004||Viu
+PL_lastfd|5.005000||Viu
+PL_lastgotoprobe|5.005000||Viu
+PL_last_in_gv|5.005000||Vi
+PL_laststatval|5.005000|5.003007|poVnu
+PL_laststype|5.005000||Viu
+PL_Latin1|5.015008||Viu
+PL_LB_invlist|5.023007||Viu
+PL_lc_numeric_mutex_depth|5.027009||Viu
+PL_lex_state||5.003007|ponu
+PL_lex_stuff||5.003007|ponu
+PL_linestr||5.003007|ponu
+PL_LIO|5.006000||Viu
+PL_locale_utf8ness|5.027009||Viu
+PL_localizing|5.005000||Viu
+PL_localpatches|5.005000||Viu
+PL_lockhook|5.007003||Viu
+PL_main_cv|5.005000||Viu
+PL_main_root|5.005000||Viu
+PL_mainstack|5.005000||Viu
+PL_main_start|5.005000||Viu
+PL_markstack|5.005000||Viu
+PL_markstack_max|5.005000||Viu
+PL_markstack_ptr|5.005000||Viu
+PL_max_intro_pending|5.005000||Viu
+PL_maxo|5.005000||Viu
+PL_maxsysfd|5.005000|5.005000|
+PL_mbrlen_ps|5.031010||Viu
+PL_mbrtowc_ps|5.031010||Viu
+PL_Mem|5.006000||Viu
+PL_mem_log|5.033005||Viu
+PL_memory_debug_header|5.009004||Viu
+PL_MemParse|5.006000||Viu
+PL_MemShared|5.006000||Viu
+PL_mess_sv|5.005000|5.004000|poVnu
+PL_min_intro_pending|5.005000||Viu
+PL_minus_a|5.005000||Viu
+PL_minus_c|5.005000||Viu
+PL_minus_E|5.009003||Viu
+PL_minus_F|5.005000||Viu
+PL_minus_l|5.005000||Viu
+PL_minus_n|5.005000||Viu
+PL_minus_p|5.005000||Viu
+PL_modcount|5.005000||Viu
+PL_modglobal|5.005000|5.005000|
+PL_multideref_pc|5.021007||Viu
+PL_my_cxt_list|5.009003||Viu
+PL_my_cxt_size|5.009003||Viu
+PL_na|5.004005|5.003007|p
+PL_nomemok|5.005000||Viu
+PL_no_modify||5.003007|ponu
+PL_numeric_name|5.005000||Viu
+PL_numeric_radix_sv|5.007002||Viu
+PL_numeric_standard|5.005000||Viu
+PL_numeric_underlying|5.027006||Viu
+PL_numeric_underlying_is_standard|5.027009||Viu
+PL_ofsgv|5.011000||Vi
+PL_oldname|5.005000||Viu
+PL_op|5.005000||Viu
+PL_op_exec_cnt|5.019002||Viu
+PL_opfreehook|5.011000|5.011000|
+PL_op_mask|5.005000||Viu
+PL_origalen|5.005000||Viu
+PL_origargc|5.005000||Viu
+PL_origargv|5.005000||Viu
+PL_origenviron|5.005000||Viu
+PL_origfilename|5.005000||Viu
+PL_ors_sv|5.007001||Viu
+PL_osname|5.005000||Viu
+PL_padix|5.005000||Viu
+PL_padix_floor|5.005000||Viu
+PL_padlist_generation|5.021007||Viu
+PL_padname_const|5.021007||Viu
+PL_padname_undef|5.021007||Viu
+PL_pad_reset_pending|5.005000||Viu
+PL_parser|5.009005|5.003007|p
+PL_patchlevel|5.005000||Viu
+PL_peepp|5.007003|5.007003|
+PL_perldb|5.005000|5.003007|poVnu
+PL_perl_destruct_level|5.004005|5.003007|p
+PL_perlio|5.007003||Viu
+PL_phase|5.013007|5.013007|
+PL_pidstatus|5.005000||Viu
+PL_Posix_ptrs|5.029000||Viu
+PL_ppaddr||5.003007|ponu
+PL_preambleav|5.005000||Viu
+PL_Private_Use|5.029009||Viu
+PL_Proc|5.006000||Viu
+PL_profiledata|5.005000||Viu
+PL_psig_name|5.006000||Viu
+PL_psig_pend|5.007001||Viu
+PL_psig_ptr|5.006000||Viu
+PL_ptr_table|5.006000||Viu
+PL_random_state|5.019004||Viu
+PL_RANDOM_STATE_TYPE|5.019004||Viu
+PL_reentrant_buffer|5.007002||Viu
+PL_reentrant_retint|5.008001||Viu
+PL_reg_curpm|5.006000||Viu
+PL_regex_pad|5.007002||Viu
+PL_regex_padav|5.007002||Viu
+PL_registered_mros|5.010001||Viu
+PL_regmatch_slab|5.009004||Viu
+PL_regmatch_state|5.009004||Viu
+PL_replgv|5.005000||Viu
+PL_restartjmpenv|5.013001||Viu
+PL_restartop|5.005000|5.005000|
+PL_rpeepp|5.013005|5.013005|
+PL_rs|5.005000||Vi
+PL_rsfp||5.003007|ponu
+PL_rsfp_filters||5.003007|ponu
+PL_runops|5.006000|5.006000|
+PL_savebegin|5.007003||Viu
+PL_savestack|5.005000||Viu
+PL_savestack_ix|5.005000||Viu
+PL_savestack_max|5.005000||Viu
+PL_sawampersand|5.005000||Viu
+PL_SB_invlist|5.021009||Viu
+PL_scopestack|5.005000||Viu
+PL_scopestack_ix|5.005000||Viu
+PL_scopestack_max|5.005000||Viu
+PL_scopestack_name|5.011002||Viu
+PL_SCX_invlist|5.027008||Viu
+PL_secondgv|5.005000||Viu
+PL_setlocale_buf|5.027009||Viu
+PL_setlocale_bufsize|5.027009||Viu
+PL_sharehook|5.007003||Viu
+PL_sighandler1p|5.031007||Viu
+PL_sighandler3p|5.031007||Viu
+PL_sighandlerp|5.005000||Viu
+PL_signalhook|5.013002||Viu
+PL_signals|5.008001|5.003007|poVnu
+PL_sig_pending|5.007001||Viu
+PL_Sock|5.006000||Viu
+PL_sortcop|5.005000||Viu
+PL_sortstash|5.005000||Viu
+PL_splitstr|5.005000||Viu
+PL_srand_called|5.006000||Viu
+PL_stack_base|5.005000|5.003007|poVnu
+PL_stack_max|5.005000||Viu
+PL_stack_sp|5.005000|5.003007|poVnu
+PL_start_env|5.005000||Viu
+PL_stashcache|5.008001||Viu
+PL_stashpad|5.017001||Viu
+PL_stashpadix|5.017001||Viu
+PL_stashpadmax|5.017001||Viu
+PL_statcache|5.005000|5.003007|poVnu
+PL_statgv|5.005000||Viu
+PL_statname|5.005000||Viu
+PL_statusvalue|5.005000||Viu
+PL_statusvalue_posix|5.009003||Viu
+PL_statusvalue_vms|5.005000||Viu
+PL_stderrgv|5.006000||Viu
+PL_stdingv|5.005000|5.003007|poVnu
+PL_StdIO|5.006000||Viu
+PL_strtab|5.005000||Viu
+PL_strxfrm_is_behaved|5.025002||Viu
+PL_strxfrm_max_cp|5.025002||Viu
+PL_strxfrm_NUL_replacement|5.025008||Viu
+PL_sub_generation|5.005000||Viu
+PL_subline|5.005000||Viu
+PL_subname|5.005000||Viu
+PL_Sv|5.005000||pcV
+PL_sv_arenaroot|5.005000|5.003007|poVnu
+PL_sv_consts|5.019002||Viu
+PL_sv_count|5.005000||Viu
+PL_sv_immortals|5.027003||Viu
+PL_sv_no|5.004005|5.003007|p
+PL_sv_root|5.005000||Viu
+PL_sv_serial|5.010001||Viu
+PL_sv_undef|5.004005|5.003007|p
+PL_sv_yes|5.004005|5.003007|p
+PL_sv_zero|5.027003|5.027003|
+PL_sys_intern|5.005000||Viu
+PL_tainted|5.005000|5.003007|poVnu
+PL_tainting|5.005000|5.003007|poVnu
+PL_taint_warn|5.007003||Viu
+PL_threadhook|5.008000||Viu
+PL_tmps_floor|5.005000||Viu
+PL_tmps_ix|5.005000||Viu
+PL_tmps_max|5.005000||Viu
+PL_tmps_stack|5.005000||Viu
+PL_tokenbuf||5.003007|ponu
+PL_top_env|5.005000||Viu
+PL_toptarget|5.005000||Viu
+PL_TR_SPECIAL_HANDLING_UTF8|5.031006||Viu
+PL_underlying_numeric_obj|5.027009||Viu
+PL_unicode|5.008001||Viu
+PL_unitcheckav|5.009005||Viu
+PL_unitcheckav_save|5.009005||Viu
+PL_unlockhook|5.007003||Viu
+PL_unsafe|5.005000||Viu
+PL_UpperLatin1|5.019005||Viu
+PLUS|5.003007||Viu
+PLUS_t8_p8|5.033003||Viu
+PLUS_t8_pb|5.033003||Viu
+PLUS_tb_p8|5.033003||Viu
+PLUS_tb_pb|5.033003||Viu
+PL_utf8cache|5.009004||Viu
+PL_utf8_charname_begin|5.017006||Viu
+PL_utf8_charname_continue|5.017006||Viu
+PL_utf8_foldclosures|5.013007||Viu
+PL_utf8_idcont|5.008000||Viu
+PL_utf8_idstart|5.008000||Viu
+PL_utf8locale|5.008001||Viu
+PL_utf8_mark|5.006000||Viu
+PL_utf8_perl_idcont|5.017008||Viu
+PL_utf8_perl_idstart|5.015004||Viu
+PL_utf8_tofold|5.007003||Viu
+PL_utf8_tolower|5.006000||Viu
+PL_utf8_tosimplefold|5.027011||Viu
+PL_utf8_totitle|5.006000||Viu
+PL_utf8_toupper|5.006000||Viu
+PL_utf8_xidcont|5.013010||Viu
+PL_utf8_xidstart|5.013010||Viu
+PL_vtbl_arylen|5.015000||Viu
+PL_vtbl_arylen_p|5.015000||Viu
+PL_vtbl_backref|5.015000||Viu
+PL_vtbl_bm|5.015000||Viu
+PL_vtbl_checkcall|5.017000||Viu
+PL_vtbl_collxfrm|5.015000||Viu
+PL_vtbl_dbline|5.015000||Viu
+PL_vtbl_debugvar|5.021005||Viu
+PL_vtbl_defelem|5.015000||Viu
+PL_vtbl_env|5.015000||Viu
+PL_vtbl_envelem|5.015000||Viu
+PL_vtbl_fm|5.015000||Viu
+PL_vtbl_hints|5.015000||Viu
+PL_vtbl_hintselem|5.015000||Viu
+PL_vtbl_isa|5.015000||Viu
+PL_vtbl_isaelem|5.015000||Viu
+PL_vtbl_lvref|5.021005||Viu
+PL_vtbl_mglob|5.015000||Viu
+PL_vtbl_nkeys|5.015000||Viu
+PL_vtbl_nonelem|5.027009||Viu
+PL_vtbl_ovrld|5.015000||Viu
+PL_vtbl_pack|5.015000||Viu
+PL_vtbl_packelem|5.015000||Viu
+PL_vtbl_pos|5.015000||Viu
+PL_vtbl_regdata|5.015000||Viu
+PL_vtbl_regdatum|5.015000||Viu
+PL_vtbl_regexp|5.015000||Viu
+PL_vtbl_sig|5.035001||Viu
+PL_vtbl_sigelem|5.015000||Viu
+PL_vtbl_substr|5.015000||Viu
+PL_vtbl_sv|5.015000||Viu
+PL_vtbl_taint|5.015000||Viu
+PL_vtbl_utf8|5.015000||Viu
+PL_vtbl_uvar|5.015000||Viu
+PL_vtbl_vec|5.015000||Viu
+PL_warnhook|5.005000||Viu
+PL_warn_locale|5.021008||Viu
+PL_watchaddr|5.006000||Viu
+PL_watchok|5.006000||Viu
+PL_WB_invlist|5.021009||Viu
+PL_wcrtomb_ps|5.031010||Viu
+PL_XPosix_ptrs|5.017008||Viu
+PL_Xpv|5.005000|5.003007|poVnu
+PL_xsubfilename|5.021006||Viu
+pm_description|5.009004||Viu
+PMf_BASE_SHIFT|5.013004||Viu
+PMf_CHARSET|5.017011||Viu
+PMf_CODELIST_PRIVATE|5.017001||Viu
+PMf_CONST|5.003007||Viu
+PMf_CONTINUE|5.004000||Viu
+PMf_EVAL|5.003007||Viu
+PMf_EXTENDED|5.003007||Viu
+PMf_EXTENDED_MORE|5.021005||Viu
+PMf_FOLD|5.003007||Viu
+PMf_GLOBAL|5.003007||Viu
+PMf_HAS_CV|5.017001||Viu
+PMf_HAS_ERROR|5.025010||Viu
+PMf_IS_QR|5.017001||Viu
+PMf_KEEP|5.003007||Viu
+PMf_KEEPCOPY|5.009005||Viu
+PMf_MULTILINE|5.003007||Viu
+PMf_NOCAPTURE|5.021008||Viu
+PMf_NONDESTRUCT|5.013002||Viu
+PMf_ONCE|5.003007||Viu
+PMf_RETAINT|5.004005||Viu
+PMf_SINGLELINE|5.003007||Viu
+PMf_SPLIT|5.017011||Viu
+PMf_STRICT|5.021008||Viu
+PMf_USED|5.009005||Viu
+PMf_USE_RE_EVAL|5.017001||Viu
+PMf_WILDCARD|5.031010||Viu
+PM_GETRE|5.007002||Viu
+pmop_dump|5.006000|5.006000|u
+PmopSTASH|5.007001||Viu
+PmopSTASHPV|5.007001||Viu
+PmopSTASHPV_set|5.007001||Viu
+PmopSTASH_set|5.007001||Viu
+pmruntime|5.003007||Viu
+PM_SETRE|5.007002||Viu
+PM_STR|5.027010||Viu
+pmtrans|5.003007||Viu
+pMY_CXT|5.009000|5.009000|p
+_pMY_CXT||5.009000|p
+pMY_CXT_||5.009000|p
+PNf|5.021007||Viu
+PNfARG|5.021007||Viu
+Poison|5.008000|5.003007|p
+PoisonFree|5.009004|5.003007|p
+PoisonNew|5.009004|5.003007|p
+PoisonPADLIST|5.021006||Viu
+PoisonWith|5.009004|5.003007|p
+popen|5.003007||Viu
+POPi|5.003007|5.003007|
+POPl|5.003007|5.003007|
+POPMARK|5.003007||cViu
+POP_MULTICALL|5.009003|5.009003|
+POPn|5.006000|5.003007|
+POPp|5.003007|5.003007|
+POPpbytex|5.007001|5.007001|
+POPpconstx|5.009003||Viu
+POPpx|5.005003|5.005003|
+POPs|5.003007|5.003007|
+pop_scope|5.003007|5.003007|u
+POPSTACK|5.005000||Viu
+POPSTACK_TO|5.005000||Viu
+POPu|5.004000|5.004000|
+POPul|5.006000|5.006000|
+populate_ANYOF_from_invlist|5.019005||Viu
+populate_isa|||viu
+POSIXA|5.017003||Viu
+POSIXA_t8_p8|5.033003||Viu
+POSIXA_t8_pb|5.033003||Viu
+POSIXA_tb_p8|5.033003||Viu
+POSIXA_tb_pb|5.033003||Viu
+POSIX_CC_COUNT|5.017008||Viu
+POSIXD|5.017003||Viu
+POSIXD_t8_p8|5.033003||Viu
+POSIXD_t8_pb|5.033003||Viu
+POSIXD_tb_p8|5.033003||Viu
+POSIXD_tb_pb|5.033003||Viu
+POSIXL|5.017003||Viu
+POSIXL_CLEAR|5.029004||Viu
+POSIXL_SET|5.029004||Viu
+POSIXL_t8_p8|5.033003||Viu
+POSIXL_t8_pb|5.033003||Viu
+POSIXL_tb_p8|5.033003||Viu
+POSIXL_tb_pb|5.033003||Viu
+POSIXL_TEST|5.029004||Viu
+POSIXL_ZERO|5.029004||Viu
+POSIXU|5.017003||Viu
+POSIXU_t8_p8|5.033003||Viu
+POSIXU_t8_pb|5.033003||Viu
+POSIXU_tb_p8|5.033003||Viu
+POSIXU_tb_pb|5.033003||Viu
+PP|5.003007||Viu
+pregcomp|5.009005|5.009005|
+pregexec|5.003007|5.003007|
+PREGf_ANCH|5.019009||Viu
+PREGf_ANCH_GPOS|5.019009||Viu
+PREGf_ANCH_MBOL|5.019009||Viu
+PREGf_ANCH_SBOL|5.019009||Viu
+PREGf_CUTGROUP_SEEN|5.009005||Viu
+PREGf_GPOS_FLOAT|5.019009||Viu
+PREGf_GPOS_SEEN|5.019009||Viu
+PREGf_IMPLICIT|5.009005||Viu
+PREGf_NAUGHTY|5.009005||Viu
+PREGf_NOSCAN|5.019009||Viu
+PREGf_RECURSE_SEEN|5.023009||Viu
+pregfree2|5.011000||cVu
+pregfree|5.003007|5.003007|u
+PREGf_SKIP|5.009005||Viu
+PREGf_USE_RE_EVAL|5.017001||Viu
+PREGf_VERBARG_SEEN|5.009005||Viu
+prepare_SV_for_RV|5.010001||Viu
+prescan_version|5.011004|5.011004|
+PRESCAN_VERSION|5.019008||Viu
+PREVOPER|5.003007||Viu
+PREV_RANGE_MATCHES_INVLIST|5.023002||Viu
+printbuf|5.009004||Viu
+print_bytes_for_locale|5.027002||Viu
+print_collxfrm_input_and_return|5.025004||Viu
+printf|5.003007||Viu
+PRINTF_FORMAT_NULL_OK|5.009005|5.009005|Vn
+printf_nocontext|5.007001|5.007001|vdnu
+PRIVLIB|5.003007|5.003007|Vn
+PRIVLIB_EXP|5.003007|5.003007|Vn
+PRIVSHIFT|5.003007||Viu
+process_special_blocks|5.009005||Viu
+PROCSELFEXE_PATH|5.007003|5.007003|Vn
+PRUNE|5.009005||Viu
+PRUNE_t8_p8|5.033003||Viu
+PRUNE_t8_pb|5.033003||Viu
+PRUNE_tb_p8|5.033003||Viu
+PRUNE_tb_pb|5.033003||Viu
+PSEUDO|5.009004||Viu
+PSEUDO_t8_p8|5.033003||Viu
+PSEUDO_t8_pb|5.033003||Viu
+PSEUDO_tb_p8|5.033003||Viu
+PSEUDO_tb_pb|5.033003||Viu
+pthread_addr_t|5.005000||Viu
+PTHREAD_ATFORK|5.007002||Viu
+pthread_attr_init|5.006000||Viu
+PTHREAD_ATTR_SETDETACHSTATE|5.006000||Viu
+pthread_condattr_default|5.005000||Viu
+PTHREAD_CREATE|5.006000||Viu
+pthread_create|5.008001||Viu
+PTHREAD_CREATE_JOINABLE|5.005000||Viu
+PTHREAD_GETSPECIFIC|5.007002||Viu
+PTHREAD_GETSPECIFIC_INT|5.006000||Viu
+pthread_key_create|5.005000||Viu
+pthread_keycreate|5.008001||Viu
+pthread_mutexattr_default|5.005000||Viu
+pthread_mutexattr_init|5.005000||Viu
+pthread_mutexattr_settype|5.005000||Viu
+pTHX_12|5.019010||Viu
+pTHX_1|5.006000||Viu
+pTHX_2|5.006000||Viu
+pTHX_3|5.006000||Viu
+pTHX_4|5.006000||Viu
+pTHX|5.006000|5.003007|p
+pTHX_5|5.009003||Viu
+pTHX_6|5.009003||Viu
+pTHX_7|5.009003||Viu
+pTHX_8|5.009003||Viu
+pTHX_9|5.009003||Viu
+pTHX_||5.003007|p
+pTHX__FORMAT|5.009002||Viu
+pTHX_FORMAT|5.009002||Viu
+pTHXo|5.006000||Viu
+pTHX__VALUE|5.009002||Viu
+pTHX_VALUE|5.009002||Viu
+pTHXx|5.006000||Viu
+PTR2IV|5.006000|5.003007|p
+PTR2nat|5.009003|5.003007|p
+PTR2NV|5.006000|5.003007|p
+PTR2ul|5.007001|5.003007|p
+PTR2UV|5.006000|5.003007|p
+Ptrdiff_t|5.029003||Viu
+ptr_hash|5.017010||Vniu
+PTRSIZE|5.005000|5.005000|Vn
+ptr_table_clear|5.009005|5.009005|du
+ptr_table_fetch|5.009005|5.009005|u
+ptr_table_find|5.009004||Vniu
+ptr_table_free|5.009005|5.009005|u
+ptr_table_new|5.009005|5.009005|u
+ptr_table_split|5.009005|5.009005|u
+ptr_table_store|5.009005|5.009005|u
+PTRV|5.006000|5.003007|poVnu
+PUSHi|5.003007|5.003007|
+PUSHMARK|5.003007|5.003007|
+PUSHmortal|5.009002|5.003007|p
+PUSH_MULTICALL|5.011000|5.011000|
+PUSH_MULTICALL_FLAGS|5.018000||Viu
+PUSHn|5.006000|5.003007|
+PUSHp|5.003007|5.003007|
+PUSHs|5.003007|5.003007|
+push_scope|5.003007|5.003007|u
+PUSHSTACK|5.005000||Viu
+PUSHSTACKi|5.005000||Viu
+PUSHSTACK_INIT_HWM|5.027002||Viu
+PUSHTARG|5.003007||Viu
+PUSHu|5.004000|5.003007|p
+PUTBACK|5.003007|5.003007|
+putc|5.003007||Viu
+put_charclass_bitmap_innards|5.021004||Viu
+put_charclass_bitmap_innards_common|5.023008||Viu
+put_charclass_bitmap_innards_invlist|5.023008||Viu
+put_code_point|5.021004||Viu
+putc_unlocked|5.003007||Viu
+putenv|5.005000||Viu
+put_range|5.019009||Viu
+putw|5.003007||Viu
+pv_display|5.006000|5.003007|p
+pv_escape|5.009004|5.003007|p
+pv_pretty|5.009004|5.003007|p
+pv_uni_display|5.007003|5.007003|
+pWARN_ALL|5.006000||Viu
+pWARN_NONE|5.006000||Viu
+pWARN_STD|5.006000||Viu
+PWGECOS|5.004005|5.004005|Vn
+PWPASSWD|5.005000|5.005000|Vn
+qerror|5.006000||cViu
+QR_PAT_MODS|5.009005||Viu
+QUAD_IS_INT|5.006000|5.006000|Vn
+QUAD_IS___INT64|5.015003|5.015003|Vn
+QUAD_IS_INT64_T|5.006000|5.006000|Vn
+QUAD_IS_LONG|5.006000|5.006000|Vn
+QUAD_IS_LONG_LONG|5.006000|5.006000|Vn
+QUADKIND|5.006000|5.006000|Vn
+quadmath_format_needed|5.021004||Vni
+quadmath_format_valid|5.031007||Vni
+Quad_t|5.003007|5.003007|Vn
+QUESTION_MARK_CTRL|5.021001||Viu
+RADIXCHAR|5.027010||Viu
+RANDBITS|5.003007|5.003007|Vn
+RANDOM_R_PROTO|5.008000|5.008000|Vn
+Rand_seed_t|5.006000|5.006000|Vn
+RANGE_INDICATOR|5.031006||Viu
+rck_elide_nothing|5.032001||Viu
+RD_NODATA|5.003007|5.003007|Vn
+read|5.005000||Viu
+readdir|5.005000||Viu
+readdir64|5.009000||Viu
+READDIR64_R_PROTO|5.008000|5.008000|Vn
+READDIR_R_PROTO|5.008000|5.008000|Vn
+READ_XDIGIT|5.017006|5.017006|
+realloc|5.007002|5.007002|n
+ReANY|5.017006||cVnu
+re_compile|5.009005|5.009005|u
+RE_COMPILE_RECURSION_INIT|5.029009||Viu
+RE_COMPILE_RECURSION_LIMIT|5.029009||Viu
+re_croak|||iu
+recv|5.006000||Viu
+recvfrom|5.005000||Viu
+RE_DEBUG_COMPILE_DUMP|5.009004||Viu
+RE_DEBUG_COMPILE_FLAGS|5.009005||Viu
+RE_DEBUG_COMPILE_MASK|5.009004||Viu
+RE_DEBUG_COMPILE_OPTIMISE|5.009004||Viu
+RE_DEBUG_COMPILE_PARSE|5.009004||Viu
+RE_DEBUG_COMPILE_TEST|5.021005||Viu
+RE_DEBUG_COMPILE_TRIE|5.009004||Viu
+RE_DEBUG_EXECUTE_INTUIT|5.009004||Viu
+RE_DEBUG_EXECUTE_MASK|5.009004||Viu
+RE_DEBUG_EXECUTE_MATCH|5.009004||Viu
+RE_DEBUG_EXECUTE_TRIE|5.009004||Viu
+RE_DEBUG_EXTRA_BUFFERS|5.009005||Viu
+RE_DEBUG_EXTRA_DUMP_PRE_OPTIMIZE|5.031004||Viu
+RE_DEBUG_EXTRA_GPOS|5.011000||Viu
+RE_DEBUG_EXTRA_MASK|5.009004||Viu
+RE_DEBUG_EXTRA_OFFDEBUG|5.009005||Viu
+RE_DEBUG_EXTRA_OFFSETS|5.009004||Viu
+RE_DEBUG_EXTRA_OPTIMISE|5.009005||Viu
+RE_DEBUG_EXTRA_STACK|5.009005||Viu
+RE_DEBUG_EXTRA_STATE|5.009004||Viu
+RE_DEBUG_EXTRA_TRIE|5.009004||Viu
+RE_DEBUG_EXTRA_WILDCARD|5.031011||Viu
+RE_DEBUG_FLAG|5.009004||Viu
+RE_DEBUG_FLAGS|5.009002||Viu
+re_dup_guts|5.011000|5.011000|
+reentrant_free|5.008000||cVu
+reentrant_init|5.008000||cVu
+REENTRANT_PROTO_B_B|5.008000||Viu
+REENTRANT_PROTO_B_BI|5.008000||Viu
+REENTRANT_PROTO_B_BW|5.008000||Viu
+REENTRANT_PROTO_B_CCD|5.008000||Viu
+REENTRANT_PROTO_B_CCS|5.008000||Viu
+REENTRANT_PROTO_B_IBI|5.008000||Viu
+REENTRANT_PROTO_B_IBW|5.008000||Viu
+REENTRANT_PROTO_B_SB|5.008000||Viu
+REENTRANT_PROTO_B_SBI|5.008000||Viu
+REENTRANT_PROTO_I_BI|5.008000||Viu
+REENTRANT_PROTO_I_BW|5.008000||Viu
+REENTRANT_PROTO_I_CCSBWR|5.008000||Viu
+REENTRANT_PROTO_I_CCSD|5.008000||Viu
+REENTRANT_PROTO_I_CII|5.008000||Viu
+REENTRANT_PROTO_I_CIISD|5.008000||Viu
+REENTRANT_PROTO_I_CSBI|5.008000||Viu
+REENTRANT_PROTO_I_CSBIR|5.008000||Viu
+REENTRANT_PROTO_I_CSBWR|5.008000||Viu
+REENTRANT_PROTO_I_CSBWRE|5.008000||Viu
+REENTRANT_PROTO_I_CSD|5.008000||Viu
+REENTRANT_PROTO_I_CWISBWRE|5.008000||Viu
+REENTRANT_PROTO_I_CWISD|5.008000||Viu
+REENTRANT_PROTO_I_D|5.008000||Viu
+REENTRANT_PROTO_I_H|5.008000||Viu
+REENTRANT_PROTO_I_IBI|5.008000||Viu
+REENTRANT_PROTO_I_IBW|5.008000||Viu
+REENTRANT_PROTO_I_ICBI|5.008000||Viu
+REENTRANT_PROTO_I_ICSBWR|5.008000||Viu
+REENTRANT_PROTO_I_ICSD|5.008000||Viu
+REENTRANT_PROTO_I_ID|5.008000||Viu
+REENTRANT_PROTO_I_IISD|5.008000||Viu
+REENTRANT_PROTO_I_ISBWR|5.008000||Viu
+REENTRANT_PROTO_I_ISD|5.008000||Viu
+REENTRANT_PROTO_I_LISBI|5.008000||Viu
+REENTRANT_PROTO_I_LISD|5.008000||Viu
+REENTRANT_PROTO_I_SB|5.008000||Viu
+REENTRANT_PROTO_I_SBI|5.008000||Viu
+REENTRANT_PROTO_I_SBIE|5.008000||Viu
+REENTRANT_PROTO_I_SBIH|5.008000||Viu
+REENTRANT_PROTO_I_SBIR|5.008000||Viu
+REENTRANT_PROTO_I_SBWR|5.008000||Viu
+REENTRANT_PROTO_I_SBWRE|5.008000||Viu
+REENTRANT_PROTO_I_SD|5.008000||Viu
+REENTRANT_PROTO_I_TISD|5.008000||Viu
+REENTRANT_PROTO_I_TS|5.008000||Viu
+REENTRANT_PROTO_I_TSBI|5.008000||Viu
+REENTRANT_PROTO_I_TSBIR|5.008000||Viu
+REENTRANT_PROTO_I_TSBWR|5.008000||Viu
+REENTRANT_PROTO_I_TsISBWRE|5.008001||Viu
+REENTRANT_PROTO_I_TSR|5.008000||Viu
+REENTRANT_PROTO_I_UISBWRE|5.008000||Viu
+REENTRANT_PROTO_I_uISBWRE|5.008001||Viu
+REENTRANT_PROTO_S_CBI|5.008000||Viu
+REENTRANT_PROTO_S_CCSBI|5.008000||Viu
+REENTRANT_PROTO_S_CIISBIE|5.008000||Viu
+REENTRANT_PROTO_S_CSBI|5.008000||Viu
+REENTRANT_PROTO_S_CSBIE|5.008000||Viu
+REENTRANT_PROTO_S_CWISBIE|5.008000||Viu
+REENTRANT_PROTO_S_CWISBWIE|5.008000||Viu
+REENTRANT_PROTO_S_ICSBI|5.008000||Viu
+REENTRANT_PROTO_S_ISBI|5.008000||Viu
+REENTRANT_PROTO_S_LISBI|5.008000||Viu
+REENTRANT_PROTO_S_SBI|5.008000||Viu
+REENTRANT_PROTO_S_SBIE|5.008000||Viu
+REENTRANT_PROTO_S_SBW|5.008000||Viu
+REENTRANT_PROTO_S_TISBI|5.008000||Viu
+REENTRANT_PROTO_S_TS|5.031011||Viu
+REENTRANT_PROTO_S_TSBI|5.008000||Viu
+REENTRANT_PROTO_S_TSBIE|5.008000||Viu
+REENTRANT_PROTO_S_TWISBIE|5.008000||Viu
+REENTRANT_PROTO_V_D|5.008000||Viu
+REENTRANT_PROTO_V_H|5.008000||Viu
+REENTRANT_PROTO_V_ID|5.008000||Viu
+reentrant_retry|5.008000||vcVnu
+reentrant_size|5.008000||cVu
+REENTR_MEMZERO|5.009003||Viu
+re_exec_indentf|5.023009||vViu
+REF|5.003007||Viu
+ref|5.009003||Viu
+ref_array_or_hash|5.027008||Viu
+refcounted_he_chain_2hv|5.013007||cVi
+REFCOUNTED_HE_EXISTS|5.015007||Viu
+refcounted_he_fetch_pv|5.013007||cVi
+refcounted_he_fetch_pvn|5.013007||cVi
+refcounted_he_fetch_pvs|5.013007||Vi
+refcounted_he_fetch_sv|5.013007||cVi
+refcounted_he_free|5.013007||cVi
+refcounted_he_inc|5.013007||cVi
+REFCOUNTED_HE_KEY_UTF8|5.013007||Viu
+refcounted_he_new_pv|5.013007||cVi
+refcounted_he_new_pvn|5.013007||cVi
+refcounted_he_new_pvs|5.013007||Vi
+refcounted_he_new_sv|5.013007||cVi
+refcounted_he_value|5.009004||Viu
+REFF|5.004001||Viu
+REFFA|5.013010||Viu
+REFFAN|5.031001||Viu
+REFFAN_t8_p8|5.033003||Viu
+REFFAN_t8_pb|5.033003||Viu
+REFFAN_tb_p8|5.033003||Viu
+REFFAN_tb_pb|5.033003||Viu
+REFFA_t8_p8|5.033003||Viu
+REFFA_t8_pb|5.033003||Viu
+REFFA_tb_p8|5.033003||Viu
+REFFA_tb_pb|5.033003||Viu
+REFFL|5.004001||Viu
+REFFLN|5.031001||Viu
+REFFLN_t8_p8|5.033003||Viu
+REFFLN_t8_pb|5.033003||Viu
+REFFLN_tb_p8|5.033003||Viu
+REFFLN_tb_pb|5.033003||Viu
+REFFL_t8_p8|5.033003||Viu
+REFFL_t8_pb|5.033003||Viu
+REFFL_tb_p8|5.033003||Viu
+REFFL_tb_pb|5.033003||Viu
+REFFN|5.031001||Viu
+REFFN_t8_p8|5.033003||Viu
+REFFN_t8_pb|5.033003||Viu
+REFFN_tb_p8|5.033003||Viu
+REFFN_tb_pb|5.033003||Viu
+REFF_t8_p8|5.033003||Viu
+REFF_t8_pb|5.033003||Viu
+REFF_tb_p8|5.033003||Viu
+REFF_tb_pb|5.033003||Viu
+REFFU|5.013008||Viu
+REFFUN|5.031001||Viu
+REFFUN_t8_p8|5.033003||Viu
+REFFUN_t8_pb|5.033003||Viu
+REFFUN_tb_p8|5.033003||Viu
+REFFUN_tb_pb|5.033003||Viu
+REFFU_t8_p8|5.033003||Viu
+REFFU_t8_pb|5.033003||Viu
+REFFU_tb_p8|5.033003||Viu
+REFFU_tb_pb|5.033003||Viu
+REF_HE_KEY|5.009005||Viu
+refkids|5.003007||Viu
+REFN|5.031001||Viu
+REFN_t8_p8|5.033003||Viu
+REFN_t8_pb|5.033003||Viu
+REFN_tb_p8|5.033003||Viu
+REFN_tb_pb|5.033003||Viu
+REF_t8_p8|5.033003||Viu
+REF_t8_pb|5.033003||Viu
+REF_tb_p8|5.033003||Viu
+REF_tb_pb|5.033003||Viu
+refto|5.005000||Viu
+reg2Lanode|5.021005||Viu
+reg|5.005000||Viu
+reganode|5.005000||Viu
+REG_ANY|5.006000||Viu
+REG_ANY_t8_p8|5.033003||Viu
+REG_ANY_t8_pb|5.033003||Viu
+REG_ANY_tb_p8|5.033003||Viu
+REG_ANY_tb_pb|5.033003||Viu
+regatom|5.005000||Viu
+regbranch|5.005000||Viu
+reg_check_named_buff_matched|5.009005||Vniu
+regclass|5.005000||Viu
+regcppop|5.005000||Viu
+regcppush|5.005000||Viu
+regcp_restore|5.025006||Viu
+regcurly|5.013010||cVniu
+REG_CUTGROUP_SEEN|5.019009||Viu
+regdump|5.005000|5.005000|u
+regdump_extflags|5.009005||Viu
+regdump_intflags|5.019002||Viu
+regdupe_internal|5.009005||cVu
+regexec_flags|5.005000||cVu
+REGEX_SET|5.031010||Viu
+regex_set_precedence|5.021010||Vniu
+REGEX_SET_t8_p8|5.033003||Viu
+REGEX_SET_t8_pb|5.033003||Viu
+REGEX_SET_tb_p8|5.033003||Viu
+REGEX_SET_tb_pb|5.033003||Viu
+REG_EXTFLAGS_NAME_SIZE|5.020000||Viu
+regfree_internal|5.009005||cVu
+REG_GPOS_SEEN|5.019009||Viu
+reghop3|5.007001||Vniu
+reghop4|5.009005||Vniu
+reghopmaybe3|5.007001||Vniu
+reginclass|5.005000||Viu
+REG_INFTY|5.004005||Viu
+reginitcolors|5.006000||cVu
+reginsert|5.005000||Viu
+REG_INTFLAGS_NAME_SIZE|5.020000||Viu
+register|5.003007||Viu
+REG_LOOKBEHIND_SEEN|5.019009||Viu
+REG_MAGIC|5.006000||Viu
+regmatch|5.005000||Viu
+REGMATCH_STATE_MAX|5.009005||Viu
+reg_named_buff|5.009005||cViu
+reg_named_buff_all|5.009005||cVu
+reg_named_buff_exists|5.009005||cVu
+reg_named_buff_fetch|5.009005||cVu
+reg_named_buff_firstkey|5.009005||cVu
+reg_named_buff_iter|5.009005||cViu
+reg_named_buff_nextkey|5.009005||cVu
+reg_named_buff_scalar|5.009005||cVu
+regnext|5.003007||cVu
+reg_node|5.005000||Viu
+regnode_guts|5.021005||Viu
+REGNODE_MAX|5.009004||Viu
+REGNODE_SIMPLE|5.013002||Viu
+REGNODE_VARIES|5.013002||Viu
+reg_numbered_buff_fetch|5.009005||cViu
+reg_numbered_buff_length|5.009005||cViu
+reg_numbered_buff_store|5.009005||cViu
+regpiece|5.005000||Viu
+regpnode|5.031010||Viu
+regprop|5.003007||Viu
+reg_qr_package|5.009005||cViu
+REG_RECURSE_SEEN|5.019009||Viu
+regrepeat|5.005000||Viu
+REG_RUN_ON_COMMENT_SEEN|5.019009||Viu
+reg_scan_name|5.009005||Viu
+reg_skipcomment|5.009005||Vniu
+regtail|5.005000||Viu
+regtail_study|5.009004||Viu
+reg_temp_copy|5.009005||cViu
+REG_TOP_LEVEL_BRANCHES_SEEN|5.019009||Viu
+regtry|5.005000||Viu
+REG_UNBOUNDED_QUANTIFIER_SEEN|5.019009||Viu
+REG_UNFOLDED_MULTI_SEEN|5.019009||Viu
+REG_VERBARG_SEEN|5.019009||Viu
+REG_ZERO_LEN_SEEN|5.019009||Viu
+re_indentf|5.023009||vViu
+re_intuit_start|5.006000||cVu
+re_intuit_string|5.006000||cVu
+rename|5.005000||Viu
+Renew|5.003007|5.003007|
+Renewc|5.003007|5.003007|
+RENUM|5.005000||Viu
+RENUM_t8_p8|5.033003||Viu
+RENUM_t8_pb|5.033003||Viu
+RENUM_tb_p8|5.033003||Viu
+RENUM_tb_pb|5.033003||Viu
+re_op_compile|5.017001||Viu
+repeatcpy|5.003007|5.003007|nu
+REPLACEMENT_CHARACTER_UTF8|5.025005|5.003007|p
+report_evil_fh|5.006001||Viu
+report_redefined_cv|5.015006||Viu
+report_uninit|5.006000||cVi
+report_wrongway_fh|5.013009||Viu
+re_printf|5.023009||vViu
+RE_PV_COLOR_DECL|5.009004||Viu
+RE_PV_QUOTED_DECL|5.009004||Viu
+require_pv|5.006000|5.006000|
+require_tie_mod|5.009005||Viu
+ReREFCNT_dec|5.005000||Viu
+ReREFCNT_inc|5.005000||Viu
+RESTORE_ERRNO|5.010001||Vi
+RESTORE_LC_NUMERIC|5.021010|5.021010|p
+restore_magic|5.009003||Viu
+restore_switched_locale|5.027009||Viu
+RE_SV_DUMPLEN|5.009004||Viu
+RE_SV_ESCAPE|5.009004||Viu
+RE_SV_TAIL|5.009004||Viu
+RETPUSHNO|5.003007||Viu
+RETPUSHUNDEF|5.003007||Viu
+RETPUSHYES|5.003007||Viu
+RE_TRACK_PATTERN_OFFSETS|5.009005||Viu
+RE_TRIE_MAXBUF_INIT|5.009002||Viu
+RE_TRIE_MAXBUF_NAME|5.009002||Viu
+RETSETNO|5.003007||Viu
+RETSETTARG|5.021009||Viu
+RETSETUNDEF|5.003007||Viu
+RETSETYES|5.003007||Viu
+RETURN|5.003007||Viu
+RETURNOP|5.003007||Viu
+RETURNX|5.003007||Viu
+RETVAL|5.003007|5.003007|V
+rewind|5.003007||Viu
+rewinddir|5.005000||Viu
+REXEC_CHECKED|5.005000||Viu
+REXEC_COPY_SKIP_POST|5.017004||Viu
+REXEC_COPY_SKIP_PRE|5.017004||Viu
+REXEC_COPY_STR|5.005000||Viu
+REXEC_FAIL_ON_UNDERFLOW|5.019003||Viu
+REXEC_IGNOREPOS|5.006000||Viu
+REXEC_NOT_FIRST|5.006000||Viu
+REXEC_SCREAM|5.006000||Viu
+rmdir|5.005000||Viu
+RMS_DIR|5.008001||Viu
+RMS_FAC|5.008001||Viu
+RMS_FEX|5.008001||Viu
+RMS_FNF|5.008001||Viu
+RMS_IFI|5.008001||Viu
+RMS_ISI|5.008001||Viu
+RMS_PRV|5.008001||Viu
+rninstr|5.003007|5.003007|n
+ROTL32|5.017010||Viu
+ROTL64|5.017010||Viu
+ROTL_UV|5.017010||Viu
+ROTR32|5.027001||Viu
+ROTR64|5.027001||Viu
+ROTR_UV|5.027001||Viu
+rpeep|5.013005||Viu
+rsignal|5.004000|5.004000|
+rsignal_restore|5.004000||Viu
+rsignal_save|5.004000||Viu
+rsignal_state|5.004000|5.004000|u
+RsPARA|5.003007||Viu
+RsRECORD|5.005000||Viu
+RsSIMPLE|5.003007||Viu
+RsSNARF|5.003007||Viu
+run_body|5.006000||Viu
+runops_debug|5.005000||cVu
+RUNOPS_DEFAULT|5.005000||Viu
+runops_standard|5.005000||cVu
+run_user_filter|5.009003||Viu
+rv2cv_op_cv|5.013006|5.013006|
+RV2CVOPCV_FLAG_MASK|5.021004||Viu
+RV2CVOPCV_MARK_EARLY|5.013006|5.013006|
+RV2CVOPCV_MAYBE_NAME_GV|5.021004||Viu
+RV2CVOPCV_RETURN_NAME_GV|5.013006|5.013006|
+RV2CVOPCV_RETURN_STUB|5.021004||Viu
+rvpv_dup|5.007003|5.007003|u
+RX_ANCHORED_SUBSTR|5.010001||Viu
+RX_ANCHORED_UTF8|5.010001||Viu
+RXapif_ALL|5.009005||Viu
+RXapif_CLEAR|5.009005||Viu
+RXapif_DELETE|5.009005||Viu
+RXapif_EXISTS|5.009005||Viu
+RXapif_FETCH|5.009005||Viu
+RXapif_FIRSTKEY|5.009005||Viu
+RXapif_NEXTKEY|5.009005||Viu
+RXapif_ONE|5.009005||Viu
+RXapif_REGNAME|5.009005||Viu
+RXapif_REGNAMES|5.009005||Viu
+RXapif_REGNAMES_COUNT|5.009005||Viu
+RXapif_SCALAR|5.009005||Viu
+RXapif_STORE|5.009005||Viu
+RX_BUFF_IDX_CARET_FULLMATCH|5.017004||Viu
+RX_BUFF_IDX_CARET_POSTMATCH|5.017004||Viu
+RX_BUFF_IDX_CARET_PREMATCH|5.017004||Viu
+RX_BUFF_IDX_FULLMATCH|5.009005||Viu
+RX_BUFF_IDX_POSTMATCH|5.009005||Viu
+RX_BUFF_IDX_PREMATCH|5.009005||Viu
+RX_CHECK_SUBSTR|5.010001||Viu
+RX_COMPFLAGS|5.017011||Viu
+RX_ENGINE|5.010001||Viu
+RX_EXTFLAGS|5.010001||Viu
+RXf_BASE_SHIFT|5.013004||Viu
+RXf_CHECK_ALL|5.009005||Viu
+RXf_COPY_DONE|5.009005||Viu
+RXf_EVAL_SEEN|5.009005||Viu
+RXf_INTUIT_TAIL|5.009005||Viu
+RXf_IS_ANCHORED|5.019009||Viu
+RX_FLOAT_SUBSTR|5.010001||Viu
+RX_FLOAT_UTF8|5.010001||Viu
+RXf_MATCH_UTF8|5.009005||Viu
+RXf_NO_INPLACE_SUBST|5.017011||Viu
+RXf_NULL|5.010000||Viu
+RXf_PMf_CHARSET|5.013009||Viu
+RXf_PMf_COMPILETIME|5.009005||Viu
+RXf_PMf_EXTENDED|5.009005||Viu
+RXf_PMf_EXTENDED_MORE|5.021005||Viu
+RXf_PMf_FLAGCOPYMASK|5.017011||Viu
+RXf_PMf_FOLD|5.009005||Viu
+RXf_PMf_KEEPCOPY|5.009005||Viu
+RXf_PMf_MULTILINE|5.009005||Viu
+RXf_PMf_NOCAPTURE|5.021008||Viu
+RXf_PMf_SINGLELINE|5.009005||Viu
+RXf_PMf_SPLIT|5.017011||Viu
+RXf_PMf_STD_PMMOD|5.009005||Viu
+RXf_PMf_STD_PMMOD_SHIFT|5.010001||Viu
+RXf_PMf_STRICT|5.021008||Viu
+RXf_SKIPWHITE|5.009005||Viu
+RXf_SPLIT|5.009005||Viu
+RXf_START_ONLY|5.009005||Viu
+RXf_TAINTED|5.009005||Viu
+RXf_TAINTED_SEEN|5.009005||Viu
+RXf_UNBOUNDED_QUANTIFIER_SEEN|5.019009||Viu
+RXf_USE_INTUIT|5.009005||Viu
+RXf_USE_INTUIT_ML|5.009005||Viu
+RXf_USE_INTUIT_NOML|5.009005||Viu
+RXf_WHITE|5.009005||Viu
+RX_GOFS|5.010001||Viu
+RXi_GET|5.009005||Viu
+RXi_GET_DECL|5.009005||Viu
+RX_INTFLAGS|5.019009||Viu
+RXi_SET|5.009005||Viu
+RX_ISTAINTED|5.017006||Viu
+RX_LASTCLOSEPAREN|5.010001||Viu
+RX_LASTPAREN|5.010001||Viu
+RX_MATCH_COPIED|5.006000||Viu
+RX_MATCH_COPIED_off|5.006000||Viu
+RX_MATCH_COPIED_on|5.006000||Viu
+RX_MATCH_COPIED_set|5.006000||Viu
+RX_MATCH_COPY_FREE|5.009000||Viu
+RX_MATCH_TAINTED|5.005000||Viu
+RX_MATCH_TAINTED_off|5.005000||Viu
+RX_MATCH_TAINTED_on|5.005000||Viu
+RX_MATCH_TAINTED_set|5.005000||Viu
+RX_MATCH_UTF8|5.008001||Viu
+RX_MATCH_UTF8_off|5.008001||Viu
+RX_MATCH_UTF8_on|5.008001||Viu
+RX_MATCH_UTF8_set|5.008001||Viu
+RX_MINLEN|5.010001||Viu
+RX_MINLENRET|5.010001||Viu
+RX_NPARENS|5.010001||Viu
+RX_OFFS|5.010001||Viu
+RXp_COMPFLAGS|5.017011||Viu
+RXp_ENGINE|5.027003||Viu
+RXp_EXTFLAGS|5.010001||Viu
+RXp_GOFS|5.027003||Viu
+RXp_HAS_CUTGROUP|5.027003||Viu
+RXp_INTFLAGS|5.019009||Viu
+RXp_ISTAINTED|5.027003||Viu
+RXp_MATCH_COPIED|5.010001||Viu
+RXp_MATCH_COPIED_off|5.010001||Viu
+RXp_MATCH_COPIED_on|5.010001||Viu
+RXp_MATCH_COPY_FREE|5.027003||Viu
+RXp_MATCH_TAINTED|5.010001||Viu
+RXp_MATCH_TAINTED_off|5.027003||Viu
+RXp_MATCH_TAINTED_on|5.017008||Viu
+RXp_MATCH_UTF8|5.010001||Viu
+RXp_MATCH_UTF8_off|5.027003||Viu
+RXp_MATCH_UTF8_on|5.027003||Viu
+RXp_MATCH_UTF8_set|5.027003||Viu
+RXp_MINLEN|5.027003||Viu
+RXp_MINLENRET|5.027003||Viu
+RXp_NPARENS|5.027003||Viu
+RXp_OFFS|5.027003||Viu
+RXp_PAREN_NAMES|5.010001||Viu
+RX_PRECOMP|5.010001||Viu
+RX_PRECOMP_const|5.010001||Viu
+RX_PRELEN|5.010001||Viu
+RXp_SAVED_COPY|5.027003||Viu
+RXp_SUBBEG|5.027003||Viu
+RXp_SUBOFFSET|5.027003||Viu
+RXp_ZERO_LEN|5.027003||Viu
+RX_REFCNT|5.010001||Viu
+rxres_free|5.004000||Viu
+rxres_restore|5.004000||Viu
+rxres_save|5.004000||Viu
+RX_SAVED_COPY|5.011000||Viu
+RX_SUBBEG|5.010001||Viu
+RX_SUBCOFFSET|5.017004||Viu
+RX_SUBLEN|5.010001||Viu
+RX_SUBOFFSET|5.017004||Viu
+RX_TAINT_on|5.017006||Viu
+RX_UTF8|5.010001||Viu
+RX_WRAPLEN|5.010001||Viu
+RX_WRAPPED|5.010001||Viu
+RX_WRAPPED_const|5.011000||Viu
+RX_ZERO_LEN|5.019003||Viu
+safecalloc|5.003007||Viu
+Safefree|5.003007|5.003007|
+safefree|5.003007||Viu
+safemalloc|5.003007||Viu
+saferealloc|5.003007||Viu
+safesyscalloc|5.006000|5.006000|n
+safesysfree|5.006000|5.006000|n
+safesysmalloc|5.006000|5.006000|n
+safesysrealloc|5.006000|5.006000|n
+SAFE_TRIE_NODENUM|5.009002||Viu
+same_dirent|5.003007||Viu
+SANE_ERRSV|5.031003|5.031003|
+SANY|5.003007||Viu
+SANY_t8_p8|5.033003||Viu
+SANY_t8_pb|5.033003||Viu
+SANY_tb_p8|5.033003||Viu
+SANY_tb_pb|5.033003||Viu
+save_adelete|5.011000|5.011000|u
+SAVEADELETE|5.011000||Viu
+save_aelem|5.004005|5.004005|u
+save_aelem_flags|5.011000|5.011000|u
+save_alloc|5.006000|5.006000|u
+save_aptr|5.003007|5.003007|
+save_ary|5.003007|5.003007|
+SAVEBOOL|5.008001|5.008001|
+save_bool|5.008001||cVu
+save_clearsv|5.003007||cVu
+SAVECLEARSV|5.003007||Vi
+SAVECOMPILEWARNINGS|5.009004||Viu
+SAVECOMPPAD|5.006000||Vi
+SAVECOPFILE|5.006000||Viu
+SAVECOPFILE_FREE|5.006001||Viu
+SAVECOPLINE|5.006000||Viu
+SAVECOPSTASH_FREE|5.006001||Viu
+SAVE_DEFSV|5.004005|5.003007|p
+SAVEDELETE|5.003007|5.003007|
+save_delete|5.003007||cVu
+save_destructor|5.003007||cVu
+SAVEDESTRUCTOR|5.006000|5.006000|
+SAVEDESTRUCTOR_X|5.006000|5.006000|
+save_destructor_x|5.006000||cVu
+SAVE_ERRNO|5.010001||Vi
+SAVEFEATUREBITS|5.031006||Viu
+SAVEf_KEEPOLDELEM|5.011000||Viu
+SAVEFREECOPHH|5.013007||Viu
+SAVEFREEOP|5.010001|5.010001|
+save_freeop|5.010001||cVu
+SAVEFREEPADNAME|5.021007||Viu
+SAVEFREEPV|5.003007|5.003007|
+save_freepv|5.010001||cVu
+SAVEFREESV|5.003007|5.003007|
+save_freesv|5.010001||cVu
+SAVEf_SETMAGIC|5.011000||Viu
+SAVEGENERICPV|5.006001||Viu
+save_generic_pvref|5.006001|5.006001|u
+SAVEGENERICSV|5.005003||Viu
+save_generic_svref|5.005003|5.005003|u
+save_gp|5.004000|5.004000|
+save_hash|5.003007|5.003007|
+save_hdelete|5.011000|5.011000|u
+SAVEHDELETE|5.011000||Viu
+save_hek_flags|5.008000||Vniu
+save_helem|5.004005|5.004005|u
+save_helem_flags|5.011000|5.011000|u
+SAVEHINTS|5.005000||Viu
+save_hints|5.010001|5.010001|u
+save_hptr|5.003007|5.003007|
+SAVEI16|5.004000|5.004000|
+save_I16|5.004000||cVu
+SAVEI32|5.003007|5.003007|
+save_I32|5.003007||cVu
+SAVEI8|5.006000|5.006000|
+save_I8|5.006000||cVu
+SAVEINT|5.003007|5.003007|
+save_int|5.003007||cVu
+save_item|5.003007|5.003007|
+SAVEIV|5.003007|5.003007|
+save_iv|5.004000||cVu
+save_lines|5.005000||Viu
+save_list|5.003007|5.003007|d
+SAVELONG|5.003007|5.003007|
+save_long|5.003007||dcVu
+save_magic_flags|5.019002||Viu
+SAVE_MASK|5.013001||Viu
+SAVEMORTALIZESV|5.007001|5.007001|
+save_mortalizesv|5.010001||cVu
+save_nogv|5.003007|5.003007|du
+SAVEOP|5.005000||Viu
+save_op|5.010001|5.010001|u
+save_padsv_and_mortalize|5.010001|5.010001|u
+SAVEPADSVANDMORTALIZE|5.010001||Viu
+SAVEPADSV|||i
+SAVEPARSER|5.009005||Viu
+SAVEPPTR|5.003007|5.003007|
+save_pptr|5.003007||cVu
+save_pushi32ptr|5.010001|5.010001|u
+save_pushptr|5.010001|5.010001|u
+save_pushptri32ptr|5.010001||Viu
+save_pushptrptr|5.010001|5.010001|u
+savepv|5.003007|5.003007|
+savepvn|5.003007|5.003007|
+savepvs|5.009003|5.009003|
+save_re_context|5.006000||cVu
+save_scalar|5.003007|5.003007|
+save_scalar_at|5.005000||Viu
+save_set_svflags|5.009000|5.009000|u
+SAVESETSVFLAGS|5.009000||Viu
+savesharedpv|5.007003|5.007003|
+SAVESHAREDPV|5.007003||Viu
+savesharedpvn|5.009005|5.009005|
+save_shared_pvref|5.007003|5.007003|u
+savesharedpvs|5.013006|5.013006|
+savesharedsvpv|5.013006|5.013006|
+SAVESPTR|5.003007|5.003007|
+save_sptr|5.003007||cVu
+savestack_grow|5.003007|5.003007|u
+savestack_grow_cnt|5.008001|5.008001|u
+SAVESTACK_POS|5.004000|5.004000|
+save_strlen|5.019004||cViu
+savesvpv|5.009002|5.009002|
+save_svref|5.003007|5.003007|
+SAVESWITCHSTACK|5.009002||Viu
+SAVEt_ADELETE|5.011000||Viu
+SAVEt_AELEM|5.004005||Viu
+SAVEt_ALLOC|5.006000||Viu
+SAVEt_APTR|5.003007||Viu
+SAVEt_AV|5.003007||Viu
+SAVEt_BOOL|5.008001||Viu
+SAVEt_CLEARPADRANGE|5.017006||Viu
+SAVEt_CLEARSV|5.003007||Viu
+SAVEt_COMPILE_WARNINGS|5.009004||Viu
+SAVEt_COMPPAD|5.006000||Viu
+SAVEt_DELETE|5.003007||Viu
+SAVEt_DESTRUCTOR|5.003007||Viu
+SAVEt_DESTRUCTOR_X|5.006000||Viu
+SAVEt_FREECOPHH|5.013007||Viu
+SAVEt_FREEOP|5.003007||Viu
+SAVEt_FREEPADNAME|5.021007||Viu
+SAVEt_FREEPV|5.003007||Viu
+SAVEt_FREESV|5.003007||Viu
+SAVEt_GENERIC_PVREF|5.006001||Viu
+SAVEt_GENERIC_SVREF|5.005003||Viu
+SAVEt_GP|5.003007||Viu
+SAVEt_GVSLOT|5.017007||Viu
+SAVEt_GVSV|5.013005||Viu
+SAVEt_HELEM|5.004005||Viu
+SAVEt_HINTS|5.005000||Viu
+SAVEt_HINTS_HH|5.033001||Viu
+SAVEt_HPTR|5.003007||Viu
+SAVEt_HV|5.003007||Viu
+SAVEt_I16|5.004000||Viu
+SAVEt_I32|5.003007||Viu
+SAVEt_I32_SMALL|5.013001||Viu
+SAVEt_I8|5.006000||Viu
+SAVE_TIGHT_SHIFT|5.013001||Viu
+SAVEt_INT|5.003007||Viu
+SAVEt_INT_SMALL|5.013001||Viu
+SAVEt_ITEM|5.003007||Viu
+SAVEt_IV|5.003007||Viu
+SAVEt_LONG|5.003007||Viu
+SAVEt_MORTALIZESV|5.007001||Viu
+SAVETMPS|5.003007|5.003007|
+savetmps|5.023008|5.023008|xu
+SAVEt_NSTAB|5.003007||Viu
+save_to_buffer|5.027004||Vniu
+SAVEt_OP|5.005000||Viu
+SAVEt_PADSV_AND_MORTALIZE|5.010001||Viu
+SAVEt_PARSER|5.009005||Viu
+SAVEt_PPTR|5.003007||Viu
+SAVEt_READONLY_OFF|5.019002||Viu
+SAVEt_REGCONTEXT|5.003007||Viu
+SAVEt_SAVESWITCHSTACK|5.009002||Viu
+SAVEt_SET_SVFLAGS|5.009000||Viu
+SAVEt_SHARED_PVREF|5.007003||Viu
+SAVEt_SPTR|5.003007||Viu
+SAVEt_STACK_POS|5.004000||Viu
+SAVEt_STRLEN|5.019004||Viu
+SAVEt_STRLEN_SMALL|5.033005||Viu
+SAVEt_SV|5.003007||Viu
+SAVEt_SVREF|5.003007||Viu
+SAVEt_TMPSFLOOR|5.023008||Viu
+SAVEt_VPTR|5.006000||Viu
+save_vptr|5.006000|5.006000|u
+SAVEVPTR|5.006000||Viu
+SAWAMPERSAND_LEFT|5.017004||Viu
+SAWAMPERSAND_MIDDLE|5.017004||Viu
+SAWAMPERSAND_RIGHT|5.017004||Viu
+sawparens|5.003007||Viu
+sb_dstr|5.003007||Viu
+sb_iters|5.003007||Viu
+sb_m|5.003007||Viu
+sb_maxiters|5.003007||Viu
+SBOL|5.003007||Viu
+SBOL_t8_p8|5.033003||Viu
+SBOL_t8_pb|5.033003||Viu
+SBOL_tb_p8|5.033003||Viu
+SBOL_tb_pb|5.033003||Viu
+sb_orig|5.003007||Viu
+SBOX32_CHURN_ROUNDS|5.027001||Viu
+SBOX32_MAX_LEN|5.027001||Viu
+SBOX32_MIX3|5.027001||Viu
+SBOX32_MIX4|5.027001||Viu
+SBOX32_SCRAMBLE32|5.027001||Viu
+SBOX32_SKIP_MASK|5.027001||Viu
+SBOX32_STATE_BITS|5.027001||Viu
+SBOX32_STATE_BYTES|5.027001||Viu
+SBOX32_STATE_WORDS|5.027001||Viu
+SBOX32_STATIC_INLINE|5.027001||Viu
+SBOX32_WARN2|5.027001||Viu
+SBOX32_WARN3|5.027001||Viu
+SBOX32_WARN4|5.027001||Viu
+SBOX32_WARN5|5.027001||Viu
+SBOX32_WARN6|5.027001||Viu
+sb_rflags|5.006000||Viu
+sb_rx|5.003007||Viu
+sb_rxres|5.004000||Viu
+sb_rxtainted|5.004000||Viu
+sb_s|5.003007||Viu
+sb_strend|5.003007||Viu
+sb_targ|5.003007||Viu
+scalar|5.003007||Viu
+scalarboolean|5.005000||Viu
+scalarkids|5.003007||Viu
+scalar_mod_type|5.006000||Vniu
+scalarvoid|5.003007||Viu
+scan_bin|5.006000|5.006000|
+scan_commit|5.005000||Viu
+scan_const|5.003007||Viu
+SCAN_DEF|5.003007||Viu
+scan_formline|5.003007||Viu
+scan_heredoc|5.003007||Viu
+scan_hex|5.006000|5.003007|
+scan_ident|5.003007||Viu
+scan_inputsymbol|5.003007||Viu
+scan_num|5.003007||cVu
+scan_oct|5.006000|5.003007|
+scan_pat|5.003007||Viu
+SCAN_REPL|5.003007||Viu
+scan_str|5.003007||xcViu
+scan_subst|5.003007||Viu
+SCAN_TR|5.003007||Viu
+scan_trans|5.003007||Viu
+scan_version|5.009001|5.009001|
+SCAN_VERSION|5.019008||Viu
+scan_vstring|5.009005|5.009005|u
+scan_word|5.003007||xcViu
+SCHED_YIELD|5.006000|5.006000|Vn
+SCOPE_SAVES_SIGNAL_MASK|5.007001||Viu
+search_const|5.010001||Viu
+seed|5.008001|5.008001|u
+seedDrand01|5.006000|5.006000|
+SEEK_CUR|5.003007||Viu
+seekdir|5.005000||Viu
+SEEK_END|5.003007||Viu
+SEEK_SET|5.003007||Viu
+select|5.005000||Viu
+Select_fd_set_t|5.003007|5.003007|Vn
+SELECT_MIN_BITS|5.005003|5.005003|Vn
+Semctl|5.004005||Viu
+semun|5.006000||Viu
+send|5.005000||Viu
+sendto|5.005000||Viu
+SEOL|5.003007||Viu
+SEOL_t8_p8|5.033003||Viu
+SEOL_t8_pb|5.033003||Viu
+SEOL_tb_p8|5.033003||Viu
+SEOL_tb_pb|5.033003||Viu
+sequence_num|5.009003||Viu
+set_ANYOF_arg|5.019005||Viu
+set_ANYOF_SYNTHETIC|5.019009||Viu
+setbuf|5.003007||Viu
+set_caret_X|5.019006||Viu
+set_context|5.006000|5.006000|nu
+setdefout|5.003007|5.003007|
+SETERRNO|5.003007||Vi
+setfd_cloexec|5.027008||Vniu
+setfd_cloexec_for_nonsysfd|5.027008||Viu
+setfd_cloexec_or_inhexec_by_sysfdness|5.027008||Viu
+setfd_inhexec|5.027008||Vniu
+setfd_inhexec_for_sysfd|5.027008||Viu
+setgid|5.005000||Viu
+setgrent|5.009000||Viu
+SETGRENT_R_HAS_FPTR|5.008000||Viu
+SETGRENT_R_PROTO|5.008000|5.008000|Vn
+sethostent|5.005000||Viu
+SETHOSTENT_R_PROTO|5.008000|5.008000|Vn
+SETi|5.003007||Viu
+setjmp|5.005000||Viu
+setlinebuf|5.005000||Viu
+setlocale|5.009000||Viu
+setlocale_debug_string|5.027002||Vniu
+SETLOCALE_LOCK|5.033005||Viu
+SETLOCALE_R_PROTO|5.008000|5.008000|Vn
+SETLOCALE_UNLOCK|5.033005||Viu
+SET_MARK_OFFSET|5.006000||Viu
+setmode|5.005000||Viu
+SETn|5.003007||Viu
+setnetent|5.005000||Viu
+SETNETENT_R_PROTO|5.008000|5.008000|Vn
+set_numeric_radix|5.006000||Viu
+SET_NUMERIC_STANDARD|5.004000||Viu
+set_numeric_standard|5.006000||cViu
+SET_NUMERIC_UNDERLYING|5.021010||Viu
+set_numeric_underlying|5.027006||cViu
+SETp|5.003007||Viu
+set_padlist|5.021006||cVniu
+setprotoent|5.005000||Viu
+SETPROTOENT_R_PROTO|5.008000|5.008000|Vn
+setpwent|5.009000||Viu
+SETPWENT_R_HAS_FPTR|5.008000||Viu
+SETPWENT_R_PROTO|5.008000|5.008000|Vn
+set_regex_pv|5.029004||Viu
+setregid|5.003007||Viu
+setreuid|5.003007||Viu
+SETs|5.003007||Viu
+setservent|5.005000||Viu
+SETSERVENT_R_PROTO|5.008000|5.008000|Vn
+setsockopt|5.005000||Viu
+setSTR_LEN|5.031005||Viu
+SET_SVANY_FOR_BODYLESS_IV|5.023008||Viu
+SET_SVANY_FOR_BODYLESS_NV|5.023008||Viu
+SETTARG|5.003007||Viu
+SET_THR|5.005000||Viu
+SET_THREAD_SELF|5.005003||Viu
+SETu|5.004000||Viu
+setuid|5.005000||Viu
+_setup_canned_invlist|5.019008||cViu
+setvbuf|5.003007||Viu
+share_hek|5.004000|5.004000|u
+share_hek_flags|5.008000||Viu
+share_hek_hek|5.009003||Viu
+sharepvn|5.005000||Viu
+SHARP_S_SKIP|5.007003||Viu
+Shmat_t|5.003007|5.003007|Vn
+SHORTSIZE|5.004000|5.004000|Vn
+should_warn_nl|5.021001||Vniu
+should_we_output_Debug_r|5.031011||Viu
+SH_PATH|5.003007|5.003007|Vn
+shutdown|5.005000||Viu
+si_dup|5.007003|5.007003|u
+S_IEXEC|5.006000||Viu
+S_IFIFO|5.011000||Viu
+S_IFMT|5.003007||Viu
+SIGABRT|5.003007||Viu
+sighandler1|5.031007||Vniu
+sighandler3|5.031007||Vniu
+sighandler|5.003007||Vniu
+SIGILL|5.003007||Viu
+Sigjmp_buf|5.003007|5.003007|Vn
+Siglongjmp|5.003007|5.003007|
+signal|5.005000||Viu
+Signal_t|5.003007|5.003007|Vn
+SIG_NAME|5.003007|5.003007|Vn
+SIG_NUM|5.003007|5.003007|Vn
+Sigsetjmp|5.003007|5.003007|
+SIG_SIZE|5.007001|5.007001|Vn
+simplify_sort|5.006000||Viu
+SINGLE_PAT_MOD|5.009005||Viu
+SIPHASH_SEED_STATE|5.027001||Viu
+SIPROUND|5.017006||Viu
+S_IREAD|5.006000||Viu
+S_IRGRP|5.003007||Viu
+S_IROTH|5.003007||Viu
+S_IRUSR|5.003007||Viu
+S_IRWXG|5.006000||Viu
+S_IRWXO|5.006000||Viu
+S_IRWXU|5.006000||Viu
+S_ISBLK|5.003007||Viu
+S_ISCHR|5.003007||Viu
+S_ISDIR|5.003007||Viu
+S_ISFIFO|5.003007||Viu
+S_ISGID|5.003007||Viu
+S_ISLNK|5.003007||Viu
+S_ISREG|5.003007||Viu
+S_ISSOCK|5.003007||Viu
+S_ISUID|5.003007||Viu
+SITEARCH|5.003007|5.003007|Vn
+SITEARCH_EXP|5.003007|5.003007|Vn
+SITELIB|5.003007|5.003007|Vn
+SITELIB_EXP|5.003007|5.003007|Vn
+SITELIB_STEM|5.006000|5.006000|Vn
+S_IWGRP|5.003007||Viu
+S_IWOTH|5.003007||Viu
+S_IWRITE|5.006000||Viu
+S_IWUSR|5.003007||Viu
+S_IXGRP|5.003007||Viu
+S_IXOTH|5.003007||Viu
+S_IXUSR|5.003007||Viu
+SIZE_ALIGN|5.005000||Viu
+Size_t|5.003007|5.003007|Vn
+Size_t_MAX|5.021003||Viu
+Size_t_size|5.006000|5.006000|Vn
+SKIP|5.009005||Viu
+SKIP_next|5.009005||Viu
+SKIP_next_fail|5.009005||Viu
+SKIP_next_fail_t8_p8|5.033003||Viu
+SKIP_next_fail_t8_pb|5.033003||Viu
+SKIP_next_fail_tb_p8|5.033003||Viu
+SKIP_next_fail_tb_pb|5.033003||Viu
+SKIP_next_t8_p8|5.033003||Viu
+SKIP_next_t8_pb|5.033003||Viu
+SKIP_next_tb_p8|5.033003||Viu
+SKIP_next_tb_pb|5.033003||Viu
+skipspace_flags|5.019002||xcViu
+SKIP_t8_p8|5.033003||Viu
+SKIP_t8_pb|5.033003||Viu
+SKIP_tb_p8|5.033003||Viu
+SKIP_tb_pb|5.033003||Viu
+skip_to_be_ignored_text|5.023004||Viu
+Slab_Alloc|5.006000||cViu
+Slab_Free|5.007003||cViu
+Slab_to_ro|5.017002||Viu
+Slab_to_rw|5.009005||Viu
+sleep|5.005000||Viu
+SLOPPYDIVIDE|5.003007||Viu
+socket|5.005000||Viu
+SOCKET_OPEN_MODE|5.008002||Viu
+socketpair|5.005000||Viu
+Sock_size_t|5.006000|5.006000|Vn
+softref2xv|||iu
+sortcv|5.009003||Viu
+sortcv_stacked|5.009003||Viu
+sortcv_xsub|5.009003||Viu
+sortsv|5.007003|5.007003|
+sortsv_flags|5.009003|5.009003|
+sortsv_flags_impl|5.031011||Viu
+SP|5.003007|5.003007|
+space_join_names_mortal|5.009004||Viu
+SPAGAIN|5.003007|5.003007|
+S_PAT_MODS|5.009005||Viu
+specialWARN|5.006000||Viu
+SRAND48_R_PROTO|5.008000|5.008000|Vn
+SRANDOM_R_PROTO|5.008000|5.008000|Vn
+SRCLOSE|5.027008||Viu
+SRCLOSE_t8_p8|5.033003||Viu
+SRCLOSE_t8_pb|5.033003||Viu
+SRCLOSE_tb_p8|5.033003||Viu
+SRCLOSE_tb_pb|5.033003||Viu
+SROPEN|5.027008||Viu
+SROPEN_t8_p8|5.033003||Viu
+SROPEN_t8_pb|5.033003||Viu
+SROPEN_tb_p8|5.033003||Viu
+SROPEN_tb_pb|5.033003||Viu
+SS_ACCVIO|5.008001||Viu
+SS_ADD_BOOL|5.017007||Viu
+SS_ADD_DPTR|5.017007||Viu
+SS_ADD_DXPTR|5.017007||Viu
+SS_ADD_END|5.017007||Viu
+SS_ADD_INT|5.017007||Viu
+SS_ADD_IV|5.017007||Viu
+SS_ADD_LONG|5.017007||Viu
+SS_ADD_PTR|5.017007||Viu
+SS_ADD_UV|5.017007||Viu
+SS_BUFFEROVF|5.021009||Viu
+ssc_add_range|5.019005||Viu
+ssc_and|5.019005||Viu
+ssc_anything|5.019005||Viu
+ssc_clear_locale|5.019005||Vniu
+ssc_cp_and|5.019005||Viu
+ssc_finalize|5.019005||Viu
+SSCHECK|5.003007||Viu
+ssc_init|5.019005||Viu
+ssc_intersection|5.019005||Viu
+ssc_is_anything|5.019005||Vniu
+ssc_is_cp_posixl_init|5.019005||Vniu
+SSC_MATCHES_EMPTY_STRING|5.021004||Viu
+ssc_or|5.019005||Viu
+ssc_union|5.019005||Viu
+SS_DEVOFFLINE|5.008001||Viu
+ss_dup|5.007003|5.007003|u
+SSGROW|5.008001||Viu
+SS_IVCHAN|5.008001||Viu
+SSize_t|5.003007|5.003007|Vn
+SSize_t_MAX|5.019004||Viu
+SS_MAXPUSH|5.017007||Viu
+SSNEW|5.006000||Viu
+SSNEWa|5.006000||Viu
+SSNEWat|5.007001||Viu
+SSNEWt|5.007001||Viu
+SS_NOPRIV|5.021001||Viu
+SS_NORMAL|5.008001||Viu
+SSPOPBOOL|5.008001||Viu
+SSPOPDPTR|5.003007||Viu
+SSPOPDXPTR|5.006000||Viu
+SSPOPINT|5.003007||Viu
+SSPOPIV|5.003007||Viu
+SSPOPLONG|5.003007||Viu
+SSPOPPTR|5.003007||Viu
+SSPOPUV|5.013001||Viu
+SSPTR|5.006000||Viu
+SSPTRt|5.007001||Viu
+SSPUSHBOOL|5.008001||Viu
+SSPUSHDPTR|5.003007||Viu
+SSPUSHDXPTR|5.006000||Viu
+SSPUSHINT|5.003007||Viu
+SSPUSHIV|5.003007||Viu
+SSPUSHLONG|5.003007||Viu
+SSPUSHPTR|5.003007||Viu
+SSPUSHUV|5.013001||Viu
+ST|5.003007|5.003007|
+stack_grow|5.003007||cVu
+STANDARD_C|5.003007||Viu
+STAR|5.003007||Viu
+STAR_t8_p8|5.033003||Viu
+STAR_t8_pb|5.033003||Viu
+STAR_tb_p8|5.033003||Viu
+STAR_tb_pb|5.033003||Viu
+START_EXTERN_C|5.005000|5.003007|pV
+start_glob|||xi
+START_MY_CXT|5.010000|5.010000|p
+STARTPERL|5.003007|5.003007|Vn
+start_subparse|5.004000|5.003007|pu
+StashHANDLER|5.007001||Viu
+Stat|5.003007||Viu
+stat|5.005000||Viu
+STATIC|5.005000||Viu
+STATIC_ASSERT_1|5.021007||Viu
+STATIC_ASSERT_2|5.021007||Viu
+STATIC_ASSERT_DECL|5.027001||Viu
+STATIC_ASSERT_STMT|5.021007||Viu
+Stat_t|5.004005||Viu
+STATUS_ALL_FAILURE|5.004000||Viu
+STATUS_ALL_SUCCESS|5.004000||Viu
+STATUS_CURRENT|5.004000||Viu
+STATUS_EXIT|5.009003||Viu
+STATUS_EXIT_SET|5.009003||Viu
+STATUS_NATIVE|5.004000||Viu
+STATUS_NATIVE_CHILD_SET|5.009003||Viu
+STATUS_UNIX|5.009003||Viu
+STATUS_UNIX_EXIT_SET|5.009003||Viu
+STATUS_UNIX_SET|5.009003||Viu
+STDCHAR|5.003007|5.003007|Vn
+stderr|5.003007||Viu
+stdin|5.003007||Viu
+STDIO_PTR_LVAL_SETS_CNT|5.007001|5.007001|Vn
+STDIO_PTR_LVALUE|5.006000|5.006000|Vn
+STDIO_STREAM_ARRAY|5.006000|5.006000|Vn
+stdize_locale|5.007001||Viu
+stdout|5.003007||Viu
+stdoutf|5.005000||Viu
+STD_PAT_MODS|5.009005||Viu
+STD_PMMOD_FLAGS_CLEAR|5.013006||Viu
+ST_INO_SIGN|5.015002|5.015002|Vn
+ST_INO_SIZE|5.015002|5.015002|Vn
+STMT_END|5.003007|5.003007|pV
+STMT_START|5.003007|5.003007|pV
+STOREFEATUREBITSHH|5.031006||Viu
+STORE_LC_NUMERIC_FORCE_TO_UNDERLYING|5.021010|5.021010|
+STORE_LC_NUMERIC_SET_STANDARD|5.027009||pVu
+STORE_LC_NUMERIC_SET_TO_NEEDED|5.021010|5.021010|
+STORE_LC_NUMERIC_SET_TO_NEEDED_IN|5.031003|5.031003|
+STORE_NUMERIC_SET_STANDARD|||piu
+strBEGINs|5.027006||Viu
+strEQ|5.003007|5.003007|
+Strerror|5.003007||Viu
+strerror|5.009000||Viu
+STRERROR_R_PROTO|5.008000|5.008000|Vn
+strGE|5.003007|5.003007|
+strGT|5.003007|5.003007|
+STRING|5.006000||Viu
+STRINGIFY|5.003007|5.003007|Vn
+STRINGl|5.031005||Viu
+STRINGs|5.031005||Viu
+strip_return|5.009003||Viu
+strLE|5.003007|5.003007|
+STR_LEN|5.006000||Viu
+STRLEN|5.027001||Viu
+STR_LENl|5.031005||Viu
+STR_LENs|5.031005||Viu
+strLT|5.003007|5.003007|
+strNE|5.003007|5.003007|
+strnEQ|5.003007|5.003007|
+strnNE|5.003007|5.003007|
+STR_SZ|5.006000||Viu
+Strtod|5.029010|5.029010|n
+Strtol|5.006000|5.006000|n
+strtoll|5.006000||Viu
+Strtoul|5.006000|5.006000|n
+strtoull|5.006000||Viu
+str_to_version|5.006000||cVu
+StructCopy|5.003007|5.003007|V
+STRUCT_OFFSET|5.004000||Viu
+STRUCT_SV|5.007001||Viu
+STR_WITH_LEN|5.009003|5.003007|pV
+study_chunk|5.005000||Viu
+sub_crush_depth|5.004000||Viu
+sublex_done|5.005000||Viu
+sublex_push|5.005000||Viu
+sublex_start|5.005000||Viu
+SUBST_TAINT_BOOLRET|5.013010||Viu
+SUBST_TAINT_PAT|5.013010||Viu
+SUBST_TAINT_REPL|5.013010||Viu
+SUBST_TAINT_RETAINT|5.013010||Viu
+SUBST_TAINT_STR|5.013010||Viu
+SUBVERSION|5.003007||Viu
+SUCCEED|5.003007||Viu
+SUCCEED_t8_p8|5.033003||Viu
+SUCCEED_t8_pb|5.033003||Viu
+SUCCEED_tb_p8|5.033003||Viu
+SUCCEED_tb_pb|5.033003||Viu
+SUSPEND|5.005000||Viu
+SUSPEND_t8_p8|5.033003||Viu
+SUSPEND_t8_pb|5.033003||Viu
+SUSPEND_tb_p8|5.033003||Viu
+SUSPEND_tb_pb|5.033003||Viu
+sv_2bool|5.013006||cV
+sv_2bool_flags|5.013006||cV
+sv_2bool_nomg|5.017002||Viu
+sv_2cv|5.003007|5.003007|
+sv_2io|5.003007|5.003007|
+sv_2iuv_common|5.009004||Viu
+sv_2iuv_non_preserve|5.007001||Viu
+sv_2iv|5.009001||cVu
+sv_2iv_flags|5.009001|5.009001|
+sv_2mortal|5.003007|5.003007|
+sv_2num|5.010000||xVi
+sv_2nv|5.013001||Viu
+sv_2nv_flags|5.013001|5.013001|
+sv_2pv|5.005000||cVu
+sv_2pvbyte|5.006000|5.003007|p
+sv_2pvbyte_flags|5.031004|5.031004|u
+sv_2pvbyte_nolen|5.009003||pcV
+sv_2pv_flags|5.007002||pcV
+sv_2pv_nolen|5.009003||pcV
+sv_2pv_nomg|5.007002||Viu
+sv_2pvutf8|5.006000|5.006000|
+sv_2pvutf8_flags|5.031004|5.031004|u
+sv_2pvutf8_nolen|5.009003||cV
+sv_2uv|5.009001||pcVu
+sv_2uv_flags|5.009001|5.009001|
+sv_add_arena|5.003007||Vi
+sv_add_backref|||iu
+SvAMAGIC|5.003007||Viu
+SvAMAGIC_off|5.031004|5.031004|nu
+SvAMAGIC_on|5.031004|5.031004|nu
+SvANY|5.003007||Viu
+sv_backoff|5.003007|5.003007|n
+sv_bless|5.003007|5.003007|
+sv_buf_to_ro|5.019008||Viu
+sv_buf_to_rw|5.019008||Viu
+SvCANCOW|5.017007||Viu
+SvCANEXISTDELETE|5.011000||Viu
+SV_CATBYTES|5.021005|5.021005|
+sv_cat_decode|5.008001|5.008001|
+sv_cathek|5.021004||Viu
+sv_catpv|5.003007|5.003007|
+sv_catpvf|5.006000|5.004000|v
+sv_catpv_flags|5.013006|5.013006|
+sv_catpvf_mg|5.006000|5.004000|pv
+sv_catpvf_mg_nocontext|5.006000||pvVn
+sv_catpvf_nocontext|5.006000||vVn
+sv_catpv_mg|5.004005|5.003007|p
+sv_catpvn|5.003007|5.003007|
+sv_catpvn_flags|5.007002|5.007002|
+sv_catpvn_mg|5.004005|5.003007|p
+sv_catpvn_nomg|5.007002|5.003007|p
+sv_catpvn_nomg_maybeutf8|5.017005||Viu
+sv_catpvn_nomg_utf8_upgrade|5.017002||Viu
+sv_catpv_nomg|5.013006|5.013006|
+sv_catpvs|5.009003|5.003007|p
+sv_catpvs_flags|5.013006|5.013006|
+sv_catpvs_mg|5.013006|5.013006|
+sv_catpvs_nomg|5.013006|5.013006|
+sv_catsv|5.003007|5.003007|
+sv_catsv_flags|5.007002|5.007002|
+sv_catsv_mg|5.004005|5.003007|p
+sv_catsv_nomg|5.007002|5.003007|p
+SV_CATUTF8|5.021005|5.021005|
+sv_catxmlpvs|5.013006||Viu
+SV_CHECK_THINKFIRST|5.008001||Viu
+SV_CHECK_THINKFIRST_COW_DROP|5.009000||Viu
+sv_chop|5.003007|5.003007|
+sv_clean_all|5.003007||Vi
+sv_clean_objs|5.003007||Vi
+sv_clear|5.003007|5.003007|
+sv_cmp|5.003007|5.003007|
+sv_cmp_flags|5.013006|5.013006|
+sv_cmp_locale|5.004000|5.004000|
+sv_cmp_locale_flags|5.013006|5.013006|
+sv_collxfrm|5.013006||V
+sv_collxfrm_flags|5.013006|5.013006|
+SvCOMPILED|5.003007||Viu
+SvCOMPILED_off|5.003007||Viu
+SvCOMPILED_on|5.003007||Viu
+SV_CONST|5.019002||Viu
+SV_CONST_BINMODE|5.019002||Viu
+SV_CONST_CLEAR|5.019002||Viu
+SV_CONST_CLOSE|5.019002||Viu
+SV_CONST_DELETE|5.019002||Viu
+SV_CONST_DESTROY|5.019002||Viu
+SV_CONST_EOF|5.019002||Viu
+SV_CONST_EXISTS|5.019002||Viu
+SV_CONST_EXTEND|5.019002||Viu
+SV_CONST_FETCH|5.019002||Viu
+SV_CONST_FETCHSIZE|5.019002||Viu
+SV_CONST_FILENO|5.019002||Viu
+SV_CONST_FIRSTKEY|5.019002||Viu
+SV_CONST_GETC|5.019002||Viu
+SV_CONST_NEXTKEY|5.019002||Viu
+SV_CONST_OPEN|5.019002||Viu
+SV_CONST_POP|5.019002||Viu
+SV_CONST_PRINT|5.019002||Viu
+SV_CONST_PRINTF|5.019002||Viu
+SV_CONST_PUSH|5.019002||Viu
+SV_CONST_READ|5.019002||Viu
+SV_CONST_READLINE|5.019002||Viu
+SV_CONST_RETURN|5.009003|5.003007|poVnu
+SV_CONST_SCALAR|5.019002||Viu
+SV_CONSTS_COUNT|5.019002||Viu
+SV_CONST_SEEK|5.019002||Viu
+SV_CONST_SHIFT|5.019002||Viu
+SV_CONST_SPLICE|5.019002||Viu
+SV_CONST_STORE|5.019002||Viu
+SV_CONST_STORESIZE|5.019002||Viu
+SV_CONST_TELL|5.019002||Viu
+SV_CONST_TIEARRAY|5.019002||Viu
+SV_CONST_TIEHANDLE|5.019002||Viu
+SV_CONST_TIEHASH|5.019002||Viu
+SV_CONST_TIESCALAR|5.019002||Viu
+SV_CONST_UNSHIFT|5.019002||Viu
+SV_CONST_UNTIE|5.019002||Viu
+SV_CONST_WRITE|5.019002||Viu
+sv_copypv|5.007003|5.007003|
+sv_copypv_flags|5.017002|5.017002|
+sv_copypv_nomg|5.017002|5.017002|
+SV_COW_DROP_PV|5.008001|5.003007|p
+SV_COW_OTHER_PVS|5.009005||Viu
+SV_COW_REFCNT_MAX|5.017007||Viu
+SV_COW_SHARED_HASH_KEYS|5.009005|5.003007|poVnu
+SvCUR|5.003007|5.003007|
+SvCUR_set|5.003007|5.003007|
+sv_dec|5.003007|5.003007|
+sv_dec_nomg|5.013002|5.013002|
+sv_del_backref|5.006000||cViu
+sv_derived_from|5.004000|5.004000|
+sv_derived_from_pv|5.015004|5.015004|
+sv_derived_from_pvn|5.015004|5.015004|
+sv_derived_from_sv|5.015004|5.015004|
+sv_derived_from_svpvn|5.031006||Viu
+sv_destroyable|5.010000|5.010000|
+SvDESTROYABLE|5.010000||Viu
+sv_display|5.021002||Viu
+SV_DO_COW_SVSETSV|5.009005||Viu
+sv_does|5.009004|5.009004|
+sv_does_pv|5.015004|5.015004|
+sv_does_pvn|5.015004|5.015004|
+sv_does_sv|5.015004|5.015004|
+sv_dump|5.003007|5.003007|
+sv_dup|5.007003|5.007003|u
+sv_dup_common|5.013002||Viu
+sv_dup_inc|5.013002|5.013002|u
+sv_dup_inc_multiple|5.011000||Viu
+SvEND|5.003007|5.003007|
+SvEND_set|5.003007||Viu
+SvENDx|5.003007||Viu
+sv_eq|5.003007|5.003007|
+sv_eq_flags|5.013006|5.013006|
+sv_exp_grow|5.009003||Viu
+SVf256|5.008001||Viu
+SVf32|5.009002||Viu
+SVf|5.006000|5.003007|p
+SvFAKE|5.003007||Viu
+SvFAKE_off|5.003007||Viu
+SvFAKE_on|5.003007||Viu
+SVf_AMAGIC|5.003007||Viu
+SVfARG|5.009005|5.003007|p
+SVf_BREAK|5.003007||Viu
+SVf_FAKE|5.003007||Viu
+SVf_IOK|5.003007||Viu
+SVf_IsCOW|5.017006||Viu
+SVf_IVisUV|5.006000||Viu
+SvFLAGS|5.003007||Viu
+SVf_NOK|5.003007||Viu
+SVf_OK|5.003007||Viu
+SVf_OOK|5.003007||Viu
+sv_force_normal|5.006000|5.006000|
+sv_force_normal_flags|5.007001|5.007001|
+SV_FORCE_UTF8_UPGRADE|5.011000|5.011000|
+SVf_POK|5.003007||Viu
+SVf_PROTECT|5.021005||Viu
+SVf_READONLY|5.003007||Viu
+sv_free2|||xciu
+sv_free|5.003007|5.003007|
+sv_free_arenas|5.003007||Vi
+SVf_ROK|5.003007||Viu
+SVf_THINKFIRST|5.003007||Viu
+SVf_UTF8|5.006000|5.003007|p
+SvGAMAGIC|5.006001|5.006001|
+sv_get_backrefs|5.021008|5.021008|xn
+SvGETMAGIC|5.004005|5.003007|p
+sv_gets|5.003007|5.003007|
+SvGID|5.019001||Viu
+SV_GMAGIC|5.007002|5.003007|p
+SvGMAGICAL|5.003007||Viu
+SvGMAGICAL_off|5.003007||Viu
+SvGMAGICAL_on|5.003007||Viu
+SvGROW|5.003007|5.003007|
+sv_grow|5.003007||cV
+Sv_Grow|5.003007||Viu
+SvGROW_mutable|5.009003||Viu
+SV_HAS_TRAILING_NUL|5.009004|5.003007|p
+SV_IMMEDIATE_UNREF|5.007001|5.003007|p
+SvIMMORTAL|5.004000||Viu
+SvIMMORTAL_INTERP|5.027003||Viu
+SvIMMORTAL_TRUE|5.027003||Viu
+sv_inc|5.003007|5.003007|
+sv_i_ncmp|5.009003||Viu
+sv_i_ncmp_desc|5.031011||Viu
+sv_inc_nomg|5.013002|5.013002|
+sv_insert|5.003007|5.003007|
+sv_insert_flags|5.010001|5.010001|
+SvIOK|5.003007|5.003007|
+SvIOK_nog|5.017002||Viu
+SvIOK_nogthink|5.017002||Viu
+SvIOK_notUV|5.006000|5.006000|
+SvIOK_off|5.003007|5.003007|
+SvIOK_on|5.003007|5.003007|
+SvIOK_only|5.003007|5.003007|
+SvIOK_only_UV|5.006000|5.006000|
+SvIOKp|5.003007|5.003007|
+SvIOKp_on|5.003007||Viu
+SvIOK_UV|5.006000|5.006000|
+sv_isa|5.003007|5.003007|
+sv_isa_sv|5.031007|5.031007|x
+SvIsCOW|5.008003|5.008003|
+SvIsCOW_shared_hash|5.008003|5.008003|
+SvIS_FREED|5.009003||Viu
+sv_isobject|5.003007|5.003007|
+SvIV|5.003007|5.003007|
+sv_iv|5.005000||dcV
+SvIV_nomg|5.009001|5.003007|p
+SvIV_please|5.007001||Viu
+SvIV_please_nomg|5.013002||Viu
+SvIV_set|5.003007|5.003007|
+SvIVX|5.003007|5.003007|
+SvIVx|5.003007|5.003007|
+SvIVXx|5.003007||Viu
+sv_kill_backrefs|||xiu
+sv_len|5.003007|5.003007|
+SvLEN|5.003007|5.003007|
+SvLEN_set|5.003007|5.003007|
+sv_len_utf8|5.006000|5.006000|p
+sv_len_utf8_nomg|5.017004||pViu
+SvLENx|5.003007||Viu
+SvLOCK|5.007003|5.007003|
+sv_magic|5.003007|5.003007|
+SvMAGIC|5.003007||Viu
+SvMAGICAL|5.003007||Viu
+SvMAGICAL_off|5.003007||Viu
+SvMAGICAL_on|5.003007||Viu
+sv_magicext|5.007003|5.007003|
+sv_magicext_mglob|5.019002||cViu
+sv_magic_portable||5.004000|pou
+SvMAGIC_set|5.009003|5.003007|p
+sv_mortalcopy|5.003007|5.003007|
+sv_mortalcopy_flags|5.017005|5.003007|p
+SV_MUTABLE_RETURN|5.009003|5.003007|poVnu
+sv_ncmp|5.009003||Viu
+sv_ncmp_desc|5.031011||Viu
+sv_newmortal|5.003007|5.003007|
+sv_newref|5.003007||cV
+SvNIOK|5.003007|5.003007|
+SvNIOK_nog|5.017002||Viu
+SvNIOK_nogthink|5.017002||Viu
+SvNIOK_off|5.003007|5.003007|
+SvNIOKp|5.003007|5.003007|
+SvNOK|5.003007|5.003007|
+SvNOK_nog|5.017002||Viu
+SvNOK_nogthink|5.017002||Viu
+SvNOK_off|5.003007|5.003007|
+SvNOK_on|5.003007|5.003007|
+SvNOK_only|5.003007|5.003007|
+SvNOKp|5.003007|5.003007|
+SvNOKp_on|5.003007||Viu
+sv_nolocking|5.007003|5.007003|d
+sv_nosharing|5.007003|5.007003|
+SV_NOSTEAL|5.009002|5.003007|p
+sv_nounlocking|5.007003|5.007003|d
+sv_nv|5.005000||dcV
+SvNV|5.006000|5.003007|
+SvNV_nomg|5.013002|5.003007|p
+SvNV_set|5.006000|5.003007|
+SvNVX|5.006000|5.003007|
+SvNVx|5.006000|5.003007|
+SvNVXx|5.003007||Viu
+SvOBJECT|5.003007||Viu
+SvOBJECT_off|5.003007||Viu
+SvOBJECT_on|5.003007||Viu
+SvOK|5.003007|5.003007|
+SvOK_off|5.003007||Viu
+SvOK_off_exc_UV|5.006000||Viu
+SvOKp|5.003007||Viu
+sv_only_taint_gmagic|5.021010||Vniu
+SvOOK|5.003007|5.003007|
+SvOOK_off|5.003007|5.003007|
+SvOOK_offset|5.011000|5.011000|
+SvOOK_on|5.003007||Viu
+sv_or_pv_len_utf8|5.017005||Viu
+sv_or_pv_pos_u2b|5.019004||Viu
+SvOURSTASH|5.009005||Viu
+SvOURSTASH_set|5.009005||Viu
+SvPADMY|5.003007||Viu
+SvPADMY_on|5.003007||Viu
+SVpad_OUR|5.006000||Viu
+SvPAD_OUR|5.009004||Viu
+SvPAD_OUR_on|5.009004||Viu
+SvPADSTALE|5.009000||Viu
+SvPADSTALE_off|5.009000||Viu
+SvPADSTALE_on|5.009000||Viu
+SVpad_STATE|5.009004||Viu
+SvPAD_STATE|5.009004||Viu
+SvPAD_STATE_on|5.009004||Viu
+SvPADTMP|5.003007||Viu
+SvPADTMP_off|5.003007||Viu
+SvPADTMP_on|5.003007||Viu
+SVpad_TYPED|5.007002||Viu
+SvPAD_TYPED|5.009004||Viu
+SvPAD_TYPED_on|5.009004||Viu
+SVpav_REAL|5.009003||Viu
+SVpav_REIFY|5.009003||Viu
+SvPCS_IMPORTED|5.009005||Viu
+SvPCS_IMPORTED_off|5.009005||Viu
+SvPCS_IMPORTED_on|5.009005||Viu
+SvPEEK|5.003007||Viu
+sv_peek|5.005000|5.005000|u
+SVpgv_GP|5.009005||Viu
+SVphv_CLONEABLE|5.009003||Viu
+SVphv_HASKFLAGS|5.008000||Viu
+SVphv_LAZYDEL|5.003007||Viu
+SVphv_SHAREKEYS|5.003007||Viu
+SVp_IOK|5.003007||Viu
+SVp_NOK|5.003007||Viu
+SvPOK|5.003007|5.003007|
+SvPOK_byte_nog|5.017002||Viu
+SvPOK_byte_nogthink|5.017002||Viu
+SvPOK_byte_pure_nogthink|5.017003||Viu
+SvPOK_nog|5.017002||Viu
+SvPOK_nogthink|5.017002||Viu
+SvPOK_off|5.003007|5.003007|
+SvPOK_on|5.003007|5.003007|
+SvPOK_only|5.003007|5.003007|
+SvPOK_only_UTF8|5.006000|5.006000|
+SvPOKp|5.003007|5.003007|
+SvPOKp_on|5.003007||Viu
+SvPOK_pure_nogthink|5.017003||Viu
+SvPOK_utf8_nog|5.017002||Viu
+SvPOK_utf8_nogthink|5.017002||Viu
+SvPOK_utf8_pure_nogthink|5.017003||Viu
+sv_pos_b2u|5.006000|5.006000|
+sv_pos_b2u_flags|5.019003|5.019003|
+sv_pos_b2u_midway|5.009004||Viu
+sv_pos_u2b|5.006000|5.006000|
+sv_pos_u2b_cached|5.009004||Viu
+sv_pos_u2b_flags|5.011005|5.011005|
+sv_pos_u2b_forwards|5.009004||Vniu
+sv_pos_u2b_midway|5.009004||Vniu
+SVp_POK|5.003007||Viu
+SVprv_PCS_IMPORTED|5.009005||Viu
+SVprv_WEAKREF|5.006000||Viu
+SVp_SCREAM|5.003007||Viu
+SvPV|5.003007|5.003007|
+sv_pv|5.008000||cV
+SvPVbyte|5.006000|5.003007|p
+sv_pvbyte|5.008000||cV
+SvPVbyte_force|5.009002|5.009002|
+sv_pvbyten|5.006000||dcV
+sv_pvbyten_force|5.006000||cV
+SvPVbyte_nolen|5.006000|5.006000|
+SvPVbyte_nomg|5.031004|5.031004|
+SvPVbyte_or_null|5.031004|5.031004|
+SvPVbyte_or_null_nomg|5.031004|5.031004|
+SvPVbytex|5.006000|5.006000|
+SvPVbytex_force|5.006000|5.006000|
+SvPVbytex_nolen|5.009003|5.009003|
+SvPVCLEAR|5.025006|5.025006|p
+SvPV_const|5.009003|5.003007|p
+SvPV_flags|5.007002|5.003007|p
+SvPV_flags_const|5.009003|5.003007|p
+SvPV_flags_const_nolen|5.009003||pVu
+SvPV_flags_mutable|5.009003|5.003007|p
+SvPV_force|5.003007|5.003007|p
+SvPV_force_flags|5.007002|5.003007|p
+SvPV_force_flags_mutable|5.009003|5.003007|p
+SvPV_force_flags_nolen|5.009003|5.003007|p
+SvPV_force_mutable|5.009003|5.003007|p
+SvPV_force_nolen|5.009003|5.003007|p
+SvPV_force_nomg|5.007002|5.003007|p
+SvPV_force_nomg_nolen|5.009003|5.003007|p
+SvPV_free|5.009003|5.009003|
+SvPV_mutable|5.009003|5.003007|p
+sv_pvn|5.004000||dcV
+sv_pvn_force|5.005000||cV
+sv_pvn_force_flags|5.007002|5.003007|p
+sv_pvn_force_nomg|5.007002||Viu
+sv_pvn_nomg|5.007003|5.005000|pdu
+SvPV_nolen|5.006000|5.003007|p
+SvPV_nolen_const|5.009003|5.003007|p
+SvPV_nomg|5.007002|5.003007|p
+SvPV_nomg_const|5.009003|5.003007|p
+SvPV_nomg_const_nolen|5.009003|5.003007|p
+SvPV_nomg_nolen|5.013007|5.003007|p
+SvPV_renew|5.009003|5.003007|p
+SvPV_set|5.003007|5.003007|
+SvPV_shrink_to_cur|5.009003||Viu
+SvPVutf8|5.006000|5.006000|
+sv_pvutf8|5.008000||cV
+SvPVutf8_force|5.006000|5.006000|
+sv_pvutf8n|5.006000||dcV
+sv_pvutf8n_force|5.006000||cV
+SvPVutf8_nolen|5.006000|5.006000|
+SvPVutf8_nomg|5.031004|5.031004|
+SvPVutf8_or_null|5.031004|5.031004|
+SvPVutf8_or_null_nomg|5.031004|5.031004|
+SvPVutf8x|5.006000|5.006000|
+SvPVutf8x_force|5.006000|5.006000|
+SvPVX|5.003007|5.003007|
+SvPVx|5.003007|5.003007|
+SvPVX_const|5.009003|5.003007|p
+SvPVx_const|5.009003|5.009003|
+SvPVx_force|5.005000|5.005000|
+SvPVX_mutable|5.009003|5.003007|p
+SvPVx_nolen|5.009003|5.009003|
+SvPVx_nolen_const|5.009003|5.003007|p
+SvPVXtrue|5.017002||Viu
+SvPVXx|5.003007|5.003007|
+SvREADONLY|5.003007|5.003007|
+SvREADONLY_off|5.003007|5.003007|
+SvREADONLY_on|5.003007|5.003007|
+sv_recode_to_utf8|5.007003|5.007003|
+sv_ref|5.015004|5.015004|
+SvREFCNT|5.003007|5.003007|
+SvREFCNT_dec|5.003007|5.003007|
+SvREFCNT_dec_NN|5.017007|5.017007|
+SvREFCNT_IMMORTAL|5.017008||Viu
+SvREFCNT_inc|5.003007|5.003007|pn
+SvREFCNT_inc_NN|5.009004|5.003007|pn
+SvREFCNT_inc_simple|5.009004|5.003007|pn
+SvREFCNT_inc_simple_NN|5.009004|5.003007|pn
+SvREFCNT_inc_simple_void|5.009004|5.003007|pn
+SvREFCNT_inc_simple_void_NN|5.009004|5.003007|pn
+SvREFCNT_inc_void|5.009004|5.003007|pn
+SvREFCNT_inc_void_NN|5.009004|5.003007|pn
+sv_reftype|5.003007|5.003007|
+sv_replace|5.003007|5.003007|
+sv_report_used|5.003007|5.003007|
+sv_reset|5.003007|5.003007|
+sv_resetpvn|5.017005||Viu
+SvRMAGICAL|5.003007||Viu
+SvRMAGICAL_off|5.003007||Viu
+SvRMAGICAL_on|5.003007||Viu
+SvROK|5.003007|5.003007|
+SvROK_off|5.003007|5.003007|
+SvROK_on|5.003007|5.003007|
+SvRV|5.003007|5.003007|
+SvRV_const|5.010001||Viu
+SvRV_set|5.009003|5.003007|p
+sv_rvunweaken|5.027004|5.027004|
+sv_rvweaken|5.006000|5.006000|
+SvRVx|5.003007||Viu
+SvRX|5.009005|5.003007|p
+SvRXOK|5.009005|5.003007|p
+SV_SAVED_COPY|5.009005||Viu
+SvSCREAM|5.003007||Viu
+SvSCREAM_off|5.003007||Viu
+SvSCREAM_on|5.003007||Viu
+sv_setgid|5.019001||Viu
+sv_sethek|5.015004||cViu
+sv_setiv|5.003007|5.003007|
+sv_setiv_mg|5.004005|5.003007|p
+SvSETMAGIC|5.003007|5.003007|
+SvSetMagicSV|5.004000|5.004000|
+SvSetMagicSV_nosteal|5.004000|5.004000|
+sv_setnv|5.006000|5.003007|
+sv_setnv_mg|5.006000|5.003007|p
+sv_setpv|5.003007|5.003007|
+sv_setpv_bufsize|5.025006|5.025006|
+sv_setpvf|5.006000|5.004000|v
+sv_setpvf_mg|5.006000|5.004000|pv
+sv_setpvf_mg_nocontext|5.006000||pvVn
+sv_setpvf_nocontext|5.006000||vVn
+sv_setpviv|5.008001|5.008001|d
+sv_setpviv_mg|5.008001|5.008001|d
+sv_setpv_mg|5.004005|5.003007|p
+sv_setpvn|5.003007|5.003007|
+sv_setpvn_mg|5.004005|5.003007|p
+sv_setpvs|5.009004|5.003007|p
+sv_setpvs_mg|5.013006|5.013006|
+sv_setref_iv|5.003007|5.003007|
+sv_setref_nv|5.006000|5.003007|
+sv_setref_pv|5.003007|5.003007|
+sv_setref_pvn|5.003007|5.003007|
+sv_setref_pvs|5.013006|5.013006|
+sv_setref_uv|5.007001|5.007001|
+sv_setsv|5.003007|5.003007|
+SvSetSV|5.003007|5.003007|
+sv_setsv_cow|5.009000||xcViu
+sv_setsv_flags|5.007002|5.003007|p
+sv_setsv_mg|5.004005|5.003007|p
+sv_setsv_nomg|5.007002|5.003007|p
+SvSetSV_nosteal|5.004000|5.004000|
+sv_setuid|5.019001||Viu
+sv_set_undef|5.025008|5.025008|
+sv_setuv|5.004000|5.003007|p
+sv_setuv_mg|5.004005|5.003007|p
+SVs_GMG|5.003007||Viu
+SvSHARE|5.007003|5.007003|
+SvSHARED_HASH|5.009003|5.003007|p
+SvSHARED_HEK_FROM_PV|5.009003||Viu
+SV_SKIP_OVERLOAD|5.013001||Viu
+SV_SMAGIC|5.009003|5.003007|p
+SvSMAGICAL|5.003007||Viu
+SvSMAGICAL_off|5.003007||Viu
+SvSMAGICAL_on|5.003007||Viu
+SVs_OBJECT|5.003007||Viu
+SVs_PADMY|5.003007||Viu
+SVs_PADSTALE|5.009000|5.009000|
+SVs_PADTMP|5.003007||Viu
+SVs_RMG|5.003007||Viu
+SVs_SMG|5.003007||Viu
+SvSTASH|5.003007|5.003007|
+SvSTASH_set|5.009003|5.003007|p
+SVs_TEMP|5.003007|5.003007|
+sv_string_from_errnum|5.027003|5.027003|
+SvTAIL|5.003007||Viu
+SvTAINT|5.003007|5.003007|
+sv_taint|5.009003||cV
+SvTAINTED|5.004000|5.004000|
+sv_tainted|5.004000||cV
+SvTAINTED_off|5.004000|5.004000|
+SvTAINTED_on|5.004000|5.004000|
+SvTEMP|5.003007||Viu
+SvTEMP_off|5.003007||Viu
+SvTEMP_on|5.003007||Viu
+SVt_FIRST|5.021005||Viu
+SvTHINKFIRST|5.003007||Vi
+SvTIED_mg|5.005003||Viu
+SvTIED_obj|5.005003|5.005003|
+SVt_INVLIST|||c
+SVt_IV|5.003007|5.003007|
+SVt_MASK|5.015001||Viu
+SVt_NULL|5.003007|5.003007|
+SVt_NV|5.003007|5.003007|
+SVt_PV|5.003007|5.003007|
+SVt_PVAV|5.003007|5.003007|
+SVt_PVBM|5.009005||Viu
+SVt_PVCV|5.003007|5.003007|
+SVt_PVFM|5.003007|5.003007|
+SVt_PVGV|5.003007|5.003007|
+SVt_PVHV|5.003007|5.003007|
+SVt_PVIO|5.003007|5.003007|
+SVt_PVIV|5.003007|5.003007|
+SVt_PVLV|5.003007|5.003007|
+SVt_PVMG|5.003007|5.003007|
+SVt_PVNV|5.003007|5.003007|
+SVt_REGEXP|5.011000|5.011000|
+SvTRUE|5.003007|5.003007|
+sv_true|5.005000||cV
+SvTRUE_common|5.033005||cVu
+SvTRUE_NN|5.017007|5.017007|
+SvTRUE_nomg|5.013006|5.003007|p
+SvTRUE_nomg_NN|5.017007|5.017007|
+SvTRUEx|5.003007|5.003007|
+SvTRUEx_nomg|5.017002||Viu
+SVt_RV|5.011000||Viu
+SvTYPE|5.003007|5.003007|
+SVTYPEMASK|5.003007||Viu
+SvUID|5.019001||Viu
+SV_UNDEF_RETURNS_NULL|5.011000||Viu
+sv_unglob|5.005000||Viu
+sv_uni_display|5.007003|5.007003|
+SvUNLOCK|5.007003|5.007003|
+sv_unmagic|5.003007|5.003007|
+sv_unmagicext|5.013008|5.003007|p
+sv_unref|5.003007|5.003007|
+sv_unref_flags|5.007001|5.007001|
+sv_untaint|5.004000||cV
+SvUOK|5.007001|5.006000|p
+SvUOK_nog|5.017002||Viu
+SvUOK_nogthink|5.017002||Viu
+sv_upgrade|5.003007|5.003007|
+SvUPGRADE|5.003007|5.003007|
+sv_usepvn|5.003007|5.003007|
+sv_usepvn_flags|5.009004|5.009004|
+sv_usepvn_mg|5.004005|5.003007|p
+SvUTF8|5.006000|5.003007|p
+sv_utf8_decode|5.006000|5.006000|
+sv_utf8_downgrade|5.006000|5.006000|
+sv_utf8_downgrade_flags|5.031004|5.031004|
+sv_utf8_downgrade_nomg|5.031004|5.031004|
+sv_utf8_encode|5.006000|5.006000|
+SV_UTF8_NO_ENCODING|5.008001|5.003007|pd
+SvUTF8_off|5.006000|5.006000|
+SvUTF8_on|5.006000|5.006000|
+sv_utf8_upgrade|5.007001|5.007001|
+sv_utf8_upgrade_flags|5.007002|5.007002|
+sv_utf8_upgrade_flags_grow|5.011000|5.011000|
+sv_utf8_upgrade_nomg|5.007002|5.007002|
+SvUV|5.004000|5.003007|p
+sv_uv|5.005000||pdcV
+SvUV_nomg|5.009001|5.003007|p
+SvUV_set|5.009003|5.003007|p
+SvUVX|5.004000|5.003007|p
+SvUVx|5.004000|5.003007|p
+SvUVXx|5.004000|5.003007|pd
+SvVALID|5.003007||Viu
+sv_vcatpvf|5.006000|5.004000|p
+sv_vcatpvf_mg|5.006000|5.004000|p
+sv_vcatpvfn|5.004000|5.004000|
+sv_vcatpvfn_flags|5.017002|5.017002|
+SvVOK|5.008001|5.008001|
+sv_vsetpvf|5.006000|5.004000|p
+sv_vsetpvf_mg|5.006000|5.004000|p
+sv_vsetpvfn|5.004000|5.004000|
+SvVSTRING_mg|5.009004|5.003007|p
+SvWEAKREF|5.006000||Viu
+SvWEAKREF_off|5.006000||Viu
+SvWEAKREF_on|5.006000||Viu
+swallow_bom|5.006001||Viu
+switch_category_locale_to_template|5.027009||Viu
+SWITCHSTACK|5.003007||Viu
+switch_to_global_locale|5.027009|5.003007|pn
+sync_locale|5.027009|5.003007|pn
+sys_init3|||cnu
+sys_init|||cnu
+sys_intern_clear|5.006001||Vu
+sys_intern_dup|5.006000||Vu
+sys_intern_init|5.006001||Vu
+SYSTEM_GMTIME_MAX|5.011000||Viu
+SYSTEM_GMTIME_MIN|5.011000||Viu
+SYSTEM_LOCALTIME_MAX|5.011000||Viu
+SYSTEM_LOCALTIME_MIN|5.011000||Viu
+sys_term|||cnu
+TAIL|5.005000||Viu
+TAIL_t8_p8|5.033003||Viu
+TAIL_t8_pb|5.033003||Viu
+TAIL_tb_p8|5.033003||Viu
+TAIL_tb_pb|5.033003||Viu
+TAINT|5.004000||Viu
+taint_env|5.003007|5.003007|u
+TAINT_ENV|5.003007||Viu
+TAINT_get|5.017006||Viu
+TAINT_IF|5.003007||Viu
+TAINTING_get|5.017006||Viu
+TAINTING_set|5.017006||Viu
+TAINT_NOT|5.003007||Viu
+taint_proper|5.003007|5.003007|u
+TAINT_PROPER|5.003007||Viu
+TAINT_set|5.017006||Viu
+TAINT_WARN_get|5.017006||Viu
+TAINT_WARN_set|5.017006||Viu
+TARG|5.003007|5.003007|
+TARGi|5.023005||Viu
+TARGn|5.023005||Viu
+TARGu|5.023005||Viu
+telldir|5.005000||Viu
+T_FMT|5.027010||Viu
+T_FMT_AMPM|5.027010||Viu
+THIS|5.003007|5.003007|V
+THOUSEP|5.027010||Viu
+THR|5.005000||Viu
+THREAD_CREATE_NEEDS_STACK|5.007002||Viu
+thread_locale_init|5.027009|5.027009|xnu
+thread_locale_term|5.027009|5.027009|xnu
+THREAD_RET_TYPE|5.005000||Viu
+tied_method|5.013009||vViu
+TIED_METHOD_ARGUMENTS_ON_STACK|5.013009||Viu
+TIED_METHOD_MORTALIZE_NOT_NEEDED|5.013009||Viu
+TIED_METHOD_SAY|5.013009||Viu
+times|5.005000||Viu
+Time_t|5.003007|5.003007|Vn
+Timeval|5.004000|5.004000|Vn
+TM|5.011000||Viu
+tmpfile|5.003007||Viu
+tmpnam|5.005000||Viu
+TMPNAM_R_PROTO|5.008000|5.008000|Vn
+tmps_grow_p|5.021005||cViu
+to_byte_substr|5.008000||Viu
+toCTRL|5.004000||Viu
+toFOLD|5.019001|5.019001|
+toFOLD_A|5.019001||Viu
+_to_fold_latin1|5.015005||cVniu
+toFOLD_LC|5.019001||Viu
+toFOLD_uni|5.007003||Viu
+toFOLD_utf8|5.031005|5.031005|
+toFOLD_utf8_safe|5.025009|5.006000|p
+toFOLD_uvchr|5.023009|5.006000|p
+TO_INTERNAL_SIZE|5.023002||Viu
+tokenize_use|5.009003||Viu
+tokeq|5.005000||Viu
+tokereport|5.007001||Viu
+toLOWER|5.003007|5.003007|
+toLOWER_A|5.019001|5.019001|
+toLOWER_L1|5.019001|5.019001|
+toLOWER_LATIN1|5.013006|5.011002|
+to_lower_latin1|5.015005||Vniu
+toLOWER_LC|5.004000|5.004000|
+toLOWER_uni|5.006000||Viu
+toLOWER_utf8|5.031005|5.031005|
+toLOWER_utf8_safe|5.025009|5.006000|p
+toLOWER_uvchr|5.023009|5.006000|p
+too_few_arguments_pv|5.016000||Viu
+TOO_LATE_FOR|5.008001||Viu
+too_many_arguments_pv|5.016000||Viu
+TOPi|5.003007||Viu
+TOPl|5.003007||Viu
+TOPm1s|5.007001||Viu
+TOPMARK|5.003007||cViu
+TOPn|5.003007||Viu
+TOPp1s|5.007001||Viu
+TOPp|5.003007||Viu
+TOPpx|5.005003||Viu
+TOPs|5.003007||Viu
+TOPu|5.004000||Viu
+TOPul|5.006000||Viu
+toTITLE|5.019001|5.019001|
+toTITLE_A|5.019001||Viu
+toTITLE_uni|5.006000||Viu
+toTITLE_utf8|5.031005|5.031005|
+toTITLE_utf8_safe|5.025009|5.006000|p
+toTITLE_uvchr|5.023009|5.006000|p
+to_uni_fold|5.014000||cVu
+_to_uni_fold_flags|5.014000||cVu
+to_uni_lower|5.006000||cVu
+to_uni_title|5.006000||cVu
+to_uni_upper|5.006000||cVu
+toUPPER|5.003007|5.003007|
+toUPPER_A|5.019001||Viu
+toUPPER_LATIN1_MOD|5.011002||Viu
+toUPPER_LC|5.004000||Viu
+_to_upper_title_latin1|5.015005||Viu
+toUPPER_uni|5.006000||Viu
+toUPPER_utf8|5.031005|5.031005|
+toUPPER_utf8_safe|5.025009|5.006000|p
+toUPPER_uvchr|5.023009|5.006000|p
+_to_utf8_case|5.023006||Viu
+_to_utf8_fold_flags|5.014000||cVu
+_to_utf8_lower_flags|5.015006||cVu
+to_utf8_substr|5.008000||Viu
+_to_utf8_title_flags|5.015006||cVu
+_to_utf8_upper_flags|5.015006||cVu
+translate_substr_offsets|5.015006||Vniu
+traverse_op_tree|5.029008||Vi
+TR_DELETE|5.031006||Viu
+TRIE|5.009002||Viu
+TRIE_BITMAP|5.009004||Viu
+TRIE_BITMAP_BYTE|5.009004||Viu
+TRIE_BITMAP_CLEAR|5.009004||Viu
+TRIE_BITMAP_SET|5.009004||Viu
+TRIE_BITMAP_TEST|5.009004||Viu
+TRIEC|5.009004||Viu
+TRIE_CHARCOUNT|5.009004||Viu
+TRIEC_t8_p8|5.033003||Viu
+TRIEC_t8_pb|5.033003||Viu
+TRIEC_tb_p8|5.033003||Viu
+TRIEC_tb_pb|5.033003||Viu
+TRIE_next|5.009005||Viu
+TRIE_next_fail|5.009005||Viu
+TRIE_next_fail_t8_p8|5.033003||Viu
+TRIE_next_fail_t8_pb|5.033003||Viu
+TRIE_next_fail_tb_p8|5.033003||Viu
+TRIE_next_fail_tb_pb|5.033003||Viu
+TRIE_next_t8_p8|5.033003||Viu
+TRIE_next_t8_pb|5.033003||Viu
+TRIE_next_tb_p8|5.033003||Viu
+TRIE_next_tb_pb|5.033003||Viu
+TRIE_NODEIDX|5.009002||Viu
+TRIE_NODENUM|5.009002||Viu
+TRIE_t8_p8|5.033003||Viu
+TRIE_t8_pb|5.033003||Viu
+TRIE_tb_p8|5.033003||Viu
+TRIE_tb_pb|5.033003||Viu
+TRIE_WORDS_OFFSET|5.009005||Viu
+TR_OOB|5.031006||Viu
+TR_R_EMPTY|5.031006||Viu
+TR_SPECIAL_HANDLING|5.031006||Viu
+TRUE|5.003007||Viu
+truncate|5.006000||Viu
+TR_UNLISTED|5.031006||Viu
+TR_UNMAPPED|5.031006||Viu
+try_amagic_bin|||ciu
+tryAMAGICbin_MG|5.013002||Viu
+try_amagic_un|||ciu
+tryAMAGICunDEREF|5.006000||Viu
+tryAMAGICun_MG|5.013002||Viu
+tryAMAGICunTARGETlist|5.017002||Viu
+TS_W32_BROKEN_LOCALECONV|5.027010||Viu
+tTHX|5.009003||Viu
+ttyname|5.009000||Viu
+TTYNAME_R_PROTO|5.008000|5.008000|Vn
+turkic_fc|5.029008||Viu
+turkic_lc|5.029008||Viu
+turkic_uc|5.029008||Viu
+TWO_BYTE_UTF8_TO_NATIVE|5.019004||Viu
+TWO_BYTE_UTF8_TO_UNI|5.013008||Viu
+TYPE_CHARS|5.004000||Viu
+TYPE_DIGITS|5.004000||Viu
+U16|5.027001||Viu
+U16_MAX|5.003007||Viu
+U16_MIN|5.003007||Viu
+U16SIZE|5.006000|5.006000|Vn
+U16TYPE|5.006000|5.006000|Vn
+U_32|5.007002|5.007002|
+U32|5.027001||Viu
+U32_ALIGNMENT_REQUIRED|5.007001|5.007001|Vn
+U32_MAX|5.003007||Viu
+U32_MAX_P1|5.007002||Viu
+U32_MAX_P1_HALF|5.007002||Viu
+U32_MIN|5.003007||Viu
+U32SIZE|5.006000|5.006000|Vn
+U32TYPE|5.006000|5.006000|Vn
+U64|5.023002||Viu
+U64SIZE|5.006000|5.006000|Vn
+U64TYPE|5.006000|5.006000|Vn
+U8|5.027001||Viu
+U8_MAX|5.003007||Viu
+U8_MIN|5.003007||Viu
+U8SIZE|5.006000|5.006000|Vn
+U8TO16_LE|5.017010||Viu
+U8TO32_LE|5.017010||Viu
+U8TO64_LE|5.017006||Viu
+U8TYPE|5.006000|5.006000|Vn
+UCHARAT|5.003007||Viu
+U_I|5.003007||Viu
+Uid_t|5.003007|5.003007|Vn
+Uid_t_f|5.006000|5.006000|Vn
+Uid_t_sign|5.006000|5.006000|Vn
+Uid_t_size|5.006000|5.006000|Vn
+UINT16_C|5.003007|5.003007|
+UINT32_C|5.003007|5.003007|
+UINT32_MIN|5.006000||Viu
+UINT64_C|5.023002|5.023002|
+UINT64_MIN|5.006000||Viu
+UINTMAX_C|5.003007|5.003007|
+uiv_2buf|5.009003||Vniu
+U_L|5.003007||Viu
+umask|5.005000||Viu
+uname|5.005004||Viu
+UNDERBAR|5.009002|5.003007|p
+unexpected_non_continuation_text|5.025006||Viu
+ungetc|5.003007||Viu
+UNI_age_values_index|5.029009||Viu
+UNI_AHEX|5.029002||Viu
+UNI_ahex_values_index|5.029009||Viu
+UNI_ALNUM|5.029002||Viu
+UNI_ALPHA|5.029002||Viu
+UNI_ALPHABETIC|5.029002||Viu
+UNI_alpha_values_index|5.029009||Viu
+UNI_ASCIIHEXDIGIT|5.029002||Viu
+UNI_BASICLATIN|5.029002||Viu
+UNI_bc_values_index|5.029009||Viu
+UNI_bidic_values_index|5.029009||Viu
+UNI_bidim_values_index|5.029009||Viu
+UNI_BLANK|5.029002||Viu
+UNI_blk_values_index|5.029009||Viu
+UNI_bpt_values_index|5.029009||Viu
+UNI_cased_values_index|5.029009||Viu
+UNI_CC|5.029002||Viu
+UNI_ccc_values_index|5.029009||Viu
+UNI_ce_values_index|5.029009||Viu
+UNI_ci_values_index|5.029009||Viu
+UNI_CNTRL|5.029002||Viu
+UNICODE_ALLOW_ABOVE_IV_MAX|5.031006||Viu
+UNICODE_ALLOW_ANY|5.007003||Viu
+UNICODE_ALLOW_SUPER|5.007003||Viu
+UNICODE_ALLOW_SURROGATE|5.007003||Viu
+UNICODE_BYTE_ORDER_MARK|5.008000||Viu
+UNICODE_DISALLOW_ABOVE_31_BIT|5.023006|5.023006|
+UNICODE_DISALLOW_ILLEGAL_C9_INTERCHANGE|5.025005|5.025005|
+UNICODE_DISALLOW_ILLEGAL_INTERCHANGE|5.013009|5.013009|
+UNICODE_DISALLOW_NONCHAR|5.013009|5.013009|
+UNICODE_DISALLOW_PERL_EXTENDED|5.027002|5.027002|
+UNICODE_DISALLOW_SUPER|5.013009|5.013009|
+UNICODE_DISALLOW_SURROGATE|5.013009|5.013009|
+UNICODE_DOT_DOT_VERSION|5.023002||Viu
+UNICODE_DOT_VERSION|5.023002||Viu
+UNICODE_GOT_NONCHAR|5.027009||Viu
+UNICODE_GOT_PERL_EXTENDED|5.027009||Viu
+UNICODE_GOT_SUPER|5.027009||Viu
+UNICODE_GOT_SURROGATE|5.027009||Viu
+UNICODE_GREEK_CAPITAL_LETTER_SIGMA|5.007003||Viu
+UNICODE_GREEK_SMALL_LETTER_FINAL_SIGMA|5.007003||Viu
+UNICODE_GREEK_SMALL_LETTER_SIGMA|5.007003||Viu
+UNICODE_IS_32_CONTIGUOUS_NONCHARS|5.023006||Viu
+UNICODE_IS_BYTE_ORDER_MARK|5.007001||Viu
+UNICODE_IS_END_PLANE_NONCHAR_GIVEN_NOT_SUPER|5.023006||Viu
+UNICODE_IS_NONCHAR|5.013009||Viu
+UNICODE_IS_PERL_EXTENDED|5.027002||Viu
+UNICODE_IS_REPLACEMENT|5.007001||Viu
+UNICODE_IS_SUPER|5.013009||Viu
+UNICODE_IS_SURROGATE|5.007001||Viu
+UNICODE_MAJOR_VERSION|5.023002||Viu
+UNICODE_PAT_MOD|5.013006||Viu
+UNICODE_PAT_MODS|5.013006||Viu
+UNICODE_REPLACEMENT|5.007001|5.003007|p
+UNICODE_SURROGATE_FIRST|5.007001||Viu
+UNICODE_SURROGATE_LAST|5.007001||Viu
+UNICODE_WARN_ABOVE_31_BIT|5.023006|5.023006|
+UNICODE_WARN_ILLEGAL_C9_INTERCHANGE|5.025005|5.025005|
+UNICODE_WARN_ILLEGAL_INTERCHANGE|5.013009|5.013009|
+UNICODE_WARN_NONCHAR|5.013009|5.013009|
+UNICODE_WARN_PERL_EXTENDED|5.027002|5.027002|
+UNICODE_WARN_SUPER|5.013009|5.013009|
+UNICODE_WARN_SURROGATE|5.013009|5.013009|
+UNI_compex_values_index|5.029009||Viu
+UNI_CONTROL|5.029002||Viu
+UNI_cwcf_values_index|5.029009||Viu
+UNI_cwcm_values_index|5.029009||Viu
+UNI_cwkcf_values_index|5.029009||Viu
+UNI_cwl_values_index|5.029009||Viu
+UNI_cwt_values_index|5.029009||Viu
+UNI_cwu_values_index|5.029009||Viu
+UNI_dash_values_index|5.029009||Viu
+UNI_DECIMALNUMBER|5.029002||Viu
+UNI_dep_values_index|5.029009||Viu
+UNI_dia_values_index|5.029009||Viu
+UNI_DIGIT|5.029002||Viu
+UNI_DISPLAY_BACKSLASH|5.007003|5.007003|
+UNI_DISPLAY_BACKSPACE|5.031009|5.031009|
+UNI_DISPLAY_ISPRINT|5.007003|5.007003|
+UNI_DISPLAY_QQ|5.007003|5.007003|
+UNI_DISPLAY_REGEX|5.007003|5.007003|
+UNI_di_values_index|5.029009||Viu
+UNI_dt_values_index|5.029009||Viu
+UNI_ea_values_index|5.029009||Viu
+UNI_ebase_values_index|5.031010||Viu
+UNI_ecomp_values_index|5.031010||Viu
+UNI_emod_values_index|5.031010||Viu
+UNI_emoji_values_index|5.031010||Viu
+UNI_epres_values_index|5.031010||Viu
+UNI_extpict_values_index|5.031010||Viu
+UNI_ext_values_index|5.029009||Viu
+UNI_gcb_values_index|5.029009||Viu
+UNI_gc_values_index|5.029009||Viu
+UNI_GRAPH|5.029002||Viu
+UNI_grbase_values_index|5.029009||Viu
+UNI_grext_values_index|5.029009||Viu
+UNI_HEX|5.029002||Viu
+UNI_HEXDIGIT|5.029002||Viu
+UNI_hex_values_index|5.029009||Viu
+UNI_HORIZSPACE|5.029002||Viu
+UNI_hst_values_index|5.029009||Viu
+UNI_HYPHEN|5.029002||Viu
+UNI_hyphen_values_index|5.029009||Viu
+UNI_idc_values_index|5.029009||Viu
+UNI_identifierstatus_values_index|5.031010||Viu
+UNI_identifiertype_values_index|5.031010||Viu
+UNI_ideo_values_index|5.029009||Viu
+UNI_idsb_values_index|5.029009||Viu
+UNI_idst_values_index|5.029009||Viu
+UNI_ids_values_index|5.029009||Viu
+UNI_inpc_values_index|5.029009||Viu
+UNI_insc_values_index|5.029009||Viu
+UNI_in_values_index|5.029009||Viu
+UNI_IS_INVARIANT|5.007001||Viu
+UNI_jg_values_index|5.029009||Viu
+UNI_joinc_values_index|5.029009||Viu
+UNI_jt_values_index|5.029009||Viu
+UNI_L|5.029002||Viu
+UNI_L_AMP|5.029002||Viu
+UNI_LB__SG|5.029002||Viu
+UNI_lb_values_index|5.029009||Viu
+UNI_LC|5.029002||Viu
+UNI_LL|5.029002||Viu
+UNI_loe_values_index|5.029009||Viu
+UNI_LOWER|5.029002||Viu
+UNI_LOWERCASE|5.029002||Viu
+UNI_lower_values_index|5.029009||Viu
+UNI_LT|5.029002||Viu
+UNI_LU|5.029002||Viu
+UNI_math_values_index|5.029009||Viu
+UNI_nchar_values_index|5.029009||Viu
+UNI_ND|5.029002||Viu
+UNI_nfcqc_values_index|5.029009||Viu
+UNI_nfdqc_values_index|5.029009||Viu
+UNI_nfkcqc_values_index|5.029009||Viu
+UNI_nfkdqc_values_index|5.029009||Viu
+UNI_nt_values_index|5.029009||Viu
+UNI_nv_values_index|5.029009||Viu
+UNI_patsyn_values_index|5.029009||Viu
+UNI_patws_values_index|5.029009||Viu
+UNI_pcm_values_index|5.029009||Viu
+UNI_PERLSPACE|5.029002||Viu
+UNI_PERLWORD|5.029002||Viu
+UNI_PRINT|5.029002||Viu
+UNI_qmark_values_index|5.029009||Viu
+UNI_radical_values_index|5.029009||Viu
+UNI_ri_values_index|5.029009||Viu
+UNI_sb_values_index|5.029009||Viu
+UNI_sc_values_index|5.029009||Viu
+UNI_scx_values_index|5.029009||Viu
+UNI_sd_values_index|5.029009||Viu
+UNISKIP|5.007001||Viu
+UNI_SPACE|5.029002||Viu
+UNI_SPACEPERL|5.029002||Viu
+UNI_sterm_values_index|5.029009||Viu
+UNI_term_values_index|5.029009||Viu
+UNI_TITLECASE|5.029002||Viu
+UNI_TITLECASELETTER|5.029002||Viu
+UNI_TO_NATIVE|5.007001|5.003007|p
+UNI_uideo_values_index|5.029009||Viu
+UNI_UPPER|5.029002||Viu
+UNI_UPPERCASE|5.029002||Viu
+UNI_upper_values_index|5.029009||Viu
+UNI_vo_values_index|5.029009||Viu
+UNI_vs_values_index|5.029009||Viu
+UNI_wb_values_index|5.029009||Viu
+UNI_WHITESPACE|5.029002||Viu
+UNI_WORD|5.029002||Viu
+UNI_WSPACE|5.029002||Viu
+UNI_wspace_values_index|5.029009||Viu
+UNI_XDIGIT|5.029002||Viu
+UNI_xidc_values_index|5.029009||Viu
+UNI_xids_values_index|5.029009||Viu
+UNI_XPERLSPACE|5.029002||Viu
+UNKNOWN_ERRNO_MSG|5.019007||Viu
+UNLESSM|5.003007||Viu
+UNLESSM_t8_p8|5.033003||Viu
+UNLESSM_t8_pb|5.033003||Viu
+UNLESSM_tb_p8|5.033003||Viu
+UNLESSM_tb_pb|5.033003||Viu
+UNLIKELY|5.009004|5.003007|p
+UNLINK|5.003007||Viu
+unlink|5.005000||Viu
+unlnk|5.003007||cVu
+UNLOCK_DOLLARZERO_MUTEX|5.008001||Viu
+UNLOCK_LC_NUMERIC_STANDARD|5.021010||poVnu
+UNLOCK_NUMERIC_STANDARD|||piu
+UNOP_AUX_item_sv|5.021007||Viu
+unpack_rec|5.008001||Viu
+unpack_str|5.007003|5.007003|d
+unpackstring|5.008001|5.008001|
+unpackWARN1|5.007003||Viu
+unpackWARN2|5.007003||Viu
+unpackWARN3|5.007003||Viu
+unpackWARN4|5.007003||Viu
+unreferenced_to_tmp_stack|5.013002||Viu
+unshare_hek|5.004000||Viu
+unshare_hek_or_pvn|5.008000||Viu
+unsharepvn|5.003007|5.003007|u
+unwind_handler_stack|5.009003||Viu
+update_debugger_info|5.009005||Viu
+upg_version|5.009005|5.009005|
+UPG_VERSION|5.019008||Viu
+Uquad_t|5.006000|5.006000|Vn
+U_S|5.003007||Viu
+usage|5.005000||Viu
+USE_64_BIT_ALL|5.006000|5.006000|Vn
+USE_64_BIT_INT|5.006000|5.006000|Vn
+USE_64_BIT_RAWIO|5.006000||Viu
+USE_64_BIT_STDIO|5.006000||Viu
+USE_BSDPGRP|5.003007||Viu
+USE_C_BACKTRACE|5.035001|5.035001|Vn
+USE_DYNAMIC_LOADING|5.003007|5.003007|Vn
+USE_ENVIRON_ARRAY|5.007001||Viu
+USE_GRENT_BUFFER|5.008000||Viu
+USE_GRENT_FPTR|5.008000||Viu
+USE_GRENT_PTR|5.008000||Viu
+USE_HASH_SEED|5.008001||Viu
+USE_HOSTENT_BUFFER|5.008000||Viu
+USE_HOSTENT_ERRNO|5.008000||Viu
+USE_HOSTENT_PTR|5.008000||Viu
+USE_ITHREADS|5.010000|5.010000|Vn
+USE_LARGE_FILES|5.006000|5.006000|Vn
+USE_LEFT|5.004000||Viu
+USE_LOCALE|5.004000||Viu
+USE_LOCALE_ADDRESS|5.027009||Viu
+USE_LOCALE_COLLATE|5.004000||Viu
+USE_LOCALE_CTYPE|5.004000||Viu
+USE_LOCALE_IDENTIFICATION|5.027009||Viu
+USE_LOCALE_MEASUREMENT|5.027009||Viu
+USE_LOCALE_MESSAGES|5.019002||Viu
+USE_LOCALE_MONETARY|5.019002||Viu
+USE_LOCALE_NUMERIC|5.004000||Viu
+USE_LOCALE_PAPER|5.027009||Viu
+USE_LOCALE_SYNTAX|5.033001||Viu
+USE_LOCALE_TELEPHONE|5.027009||Viu
+USE_LOCALE_TIME|5.021002||Viu
+USE_LOCALE_TOD|5.033001||Viu
+USEMYBINMODE|5.006000||Viu
+USE_NETENT_BUFFER|5.008000||Viu
+USE_NETENT_ERRNO|5.008000||Viu
+USE_NETENT_PTR|5.008000||Viu
+USE_PERL_ATOF|5.008000||Viu
+USE_PERLIO|5.007001|5.007001|Vn
+USE_PERL_PERTURB_KEYS|5.018000||Viu
+USE_POSIX_2008_LOCALE|5.027003||Viu
+USE_PROTOENT_BUFFER|5.008000||Viu
+USE_PROTOENT_PTR|5.008000||Viu
+USE_PWENT_BUFFER|5.008000||Viu
+USE_PWENT_FPTR|5.008000||Viu
+USE_PWENT_PTR|5.008000||Viu
+USE_REENTRANT_API|5.007003||Viu
+USER_PROP_MUTEX_INIT|5.029008||Viu
+USER_PROP_MUTEX_LOCK|5.029008||Viu
+USER_PROP_MUTEX_TERM|5.029008||Viu
+USER_PROP_MUTEX_UNLOCK|5.029008||Viu
+USE_SEMCTL_SEMID_DS|5.004005|5.004005|Vn
+USE_SEMCTL_SEMUN|5.004005|5.004005|Vn
+USE_SERVENT_BUFFER|5.008000||Viu
+USE_SERVENT_PTR|5.008000||Viu
+USE_SPENT_BUFFER|5.031011||Viu
+USE_SPENT_PTR|5.008000||Viu
+USE_STAT_BLOCKS|5.005003|5.005003|Vn
+USE_STAT_RDEV|5.003007||Viu
+USE_STDIO|5.003007||Viu
+USE_STDIO_BASE|5.006000|5.006000|Vn
+USE_STDIO_PTR|5.006000|5.006000|Vn
+USE_SYSTEM_GMTIME|5.011000||Viu
+USE_SYSTEM_LOCALTIME|5.011000||Viu
+USE_THREADS|5.006000|5.006000|Vn
+USE_THREAD_SAFE_LOCALE|5.025004||Viu
+USE_TM64|5.011000||Viu
+USE_UTF8_IN_NAMES|5.007003||Viu
+utf16_textfilter|5.011001||Viu
+utf16_to_utf8|5.006000||cViu
+utf16_to_utf8_reversed|5.006000||cViu
+UTF8_ACCUMULATE|5.007001||Viu
+UTF8_ALLOW_ANY|5.007001||Viu
+UTF8_ALLOW_ANYUV|5.007001||Viu
+UTF8_ALLOW_CONTINUATION|5.007001||Viu
+UTF8_ALLOW_DEFAULT|5.009004||Viu
+UTF8_ALLOW_EMPTY|5.007001||Viu
+UTF8_ALLOW_FE_FF|5.027009||Viu
+UTF8_ALLOW_FFFF|5.007001||Viu
+UTF8_ALLOW_LONG|5.007001||Viu
+UTF8_ALLOW_LONG_AND_ITS_VALUE|5.025009||Viu
+UTF8_ALLOW_NON_CONTINUATION|5.007001||Viu
+UTF8_ALLOW_OVERFLOW|5.025009||Viu
+UTF8_ALLOW_SHORT|5.007001||Viu
+UTF8_ALLOW_SURROGATE|5.007001||Viu
+UTF8_CHECK_ONLY|5.007001|5.007001|
+UTF8_CHK_SKIP|5.031006|5.006000|p
+UTF8_DISALLOW_ABOVE_31_BIT|5.023006||Viu
+UTF8_DISALLOW_FE_FF|5.013009||Viu
+UTF8_DISALLOW_ILLEGAL_C9_INTERCHANGE|5.025005|5.025005|
+UTF8_DISALLOW_ILLEGAL_INTERCHANGE|5.013009|5.013009|
+UTF8_DISALLOW_NONCHAR|5.013009|5.013009|
+UTF8_DISALLOW_PERL_EXTENDED|5.027002|5.027002|
+UTF8_DISALLOW_SUPER|5.013009|5.013009|
+UTF8_DISALLOW_SURROGATE|5.013009|5.013009|
+utf8_distance|5.006000|5.006000|
+UTF8_EIGHT_BIT_HI|5.007001||Viu
+UTF8_EIGHT_BIT_LO|5.007001||Viu
+UTF8f|5.019001|5.003007|p
+UTF8fARG|5.019002|5.003007|p
+UTF8_GOT_ABOVE_31_BIT|5.025006||Viu
+UTF8_GOT_CONTINUATION|5.025006|5.025006|
+UTF8_GOT_EMPTY|5.025006|5.025006|
+UTF8_GOT_LONG|5.025006|5.025006|
+UTF8_GOT_NONCHAR|5.025006|5.025006|
+UTF8_GOT_NON_CONTINUATION|5.025006|5.025006|
+UTF8_GOT_OVERFLOW|5.025006|5.025006|
+UTF8_GOT_PERL_EXTENDED|5.027002|5.027002|
+UTF8_GOT_SHORT|5.025006|5.025006|
+UTF8_GOT_SUPER|5.025006|5.025006|
+UTF8_GOT_SURROGATE|5.025006|5.025006|
+utf8_hop|5.006000|5.006000|n
+utf8_hop_back|5.025007|5.025007|n
+utf8_hop_forward|5.025007|5.025007|n
+utf8_hop_safe|5.025007|5.025007|n
+UTF8_IS_ABOVE_LATIN1|5.017004||Viu
+UTF8_IS_ABOVE_LATIN1_START|5.023003||Viu
+UTF8_IS_CONTINUATION|5.007001||Viu
+UTF8_IS_CONTINUED|5.007001||Viu
+UTF8_IS_DOWNGRADEABLE_START|5.007001||Viu
+UTF8_IS_INVARIANT|5.007001|5.003007|p
+UTF8_IS_NEXT_CHAR_DOWNGRADEABLE|5.017006||Viu
+UTF8_IS_NONCHAR|5.023002|5.023002|
+UTF8_IS_NONCHAR_GIVEN_THAT_NON_SUPER_AND_GE_PROBLEMATIC|5.013009||Viu
+UTF8_IS_REPLACEMENT|5.017000||Viu
+UTF8_IS_START|5.007001||Viu
+UTF8_IS_START_base|5.031007||Viu
+UTF8_IS_SUPER|5.023002|5.023002|
+UTF8_IS_SURROGATE|5.023002|5.023002|
+utf8_length|5.007001|5.007001|
+UTF8_MAXBYTES|5.009002|5.006000|p
+UTF8_MAXBYTES_CASE|5.009002|5.003007|p
+UTF8_MAX_FOLD_CHAR_EXPAND|5.013009||Viu
+UTF8_MAXLEN|5.006000||Viu
+utf8_mg_len_cache_update|5.013003||Viu
+utf8_mg_pos_cache_update|5.009004||Viu
+utf8n_to_uvchr|5.007001|5.007001|n
+utf8n_to_uvchr_error|5.025006|5.025006|n
+utf8n_to_uvchr_msgs|5.027009|5.027009|n
+_utf8n_to_uvchr_msgs_helper|5.029001||cVnu
+utf8n_to_uvuni|5.007001||dcV
+UTF8_SAFE_SKIP|5.029009|5.006000|p
+UTF8SKIP|5.006000|5.006000|
+UTF8_SKIP|5.023002|5.006000|p
+utf8_to_bytes|5.006001|5.006001|x
+utf8_to_uvchr|5.007001|5.006001|pd
+utf8_to_uvchr_buf|5.015009|5.006001|p
+utf8_to_uvchr_buf_helper|5.031004||cVu
+utf8_to_uvuni|5.007001||dcV
+utf8_to_uvuni_buf|5.015009||dcV
+UTF8_TWO_BYTE_HI|5.011002||Viu
+UTF8_TWO_BYTE_HI_nocast|5.011002||Viu
+UTF8_TWO_BYTE_LO|5.011002||Viu
+UTF8_TWO_BYTE_LO_nocast|5.011002||Viu
+UTF8_WARN_ABOVE_31_BIT|5.023006||Viu
+UTF8_WARN_FE_FF|5.013009||Viu
+UTF8_WARN_ILLEGAL_C9_INTERCHANGE|5.025005|5.025005|
+UTF8_WARN_ILLEGAL_INTERCHANGE|5.013009|5.013009|
+UTF8_WARN_NONCHAR|5.013009|5.013009|
+UTF8_WARN_PERL_EXTENDED|5.027002|5.027002|
+UTF8_WARN_SUPER|5.013009|5.013009|
+UTF8_WARN_SURROGATE|5.013009|5.013009|
+UTF_ACCUMULATION_SHIFT|5.007001||Viu
+UTF_CONTINUATION_MARK|5.007001||Viu
+UTF_CONTINUATION_MASK|5.007001||Viu
+UTF_IS_CONTINUATION_MASK|5.023006||Viu
+UTF_MIN_ABOVE_LATIN1_BYTE|5.031006||Viu
+UTF_MIN_START_BYTE|5.031006||Viu
+UTF_START_MARK|5.007001||Viu
+UTF_START_MASK|5.007001||Viu
+UTF_TO_NATIVE|5.007001||Viu
+utilize|5.003007||Viu
+utime|5.005000||Viu
+U_V|5.006000|5.003007|
+UVCHR_IS_INVARIANT|5.019004|5.003007|p
+UVCHR_SKIP|5.022000|5.003007|p
+uvchr_to_utf8|5.007001|5.007001|
+uvchr_to_utf8_flags|5.007003|5.007003|
+uvchr_to_utf8_flags_msgs|5.027009|5.027009|
+UV_DIG|5.006000||Viu
+UVf|5.010000|5.010000|d
+UV_IS_QUAD|5.006000||Viu
+UV_MAX|5.003007|5.003007|
+UV_MAX_P1|5.007002||Viu
+UV_MAX_P1_HALF|5.007002||Viu
+UV_MIN|5.003007|5.003007|
+UVof|5.006000|5.003007|poVn
+uvoffuni_to_utf8_flags|5.027009||cV
+uvoffuni_to_utf8_flags_msgs|5.027009||cVu
+UVSIZE|5.006000|5.003007|poVn
+UVTYPE|5.006000|5.003007|poVn
+UVuf|5.006000|5.003007|poVn
+uvuni_to_utf8|5.019004||cVu
+uvuni_to_utf8_flags|5.007003||dcV
+UVxf|5.006000|5.003007|poVn
+UVXf|5.007001|5.007001|poVn
+VAL_EAGAIN|5.003007|5.003007|Vn
+validate_proto|5.019002||xcVi
+validate_suid|||iu
+valid_utf8_to_uvchr|5.015009||cVn
+valid_utf8_to_uvuni|5.015009||dcVu
+VAL_O_NONBLOCK|5.003007|5.003007|Vn
+variant_byte_number|5.031004||cVnu
+variant_under_utf8_count|5.027007||Vni
+varname|5.009003||Viu
+vcmp|5.009000|5.009000|
+VCMP|5.019008||Viu
+vcroak|5.006000|5.006000|
+vdeb|5.007003|5.007003|u
+VERB|5.009005||Viu
+VERB_t8_p8|5.033003||Viu
+VERB_t8_pb|5.033003||Viu
+VERB_tb_p8|5.033003||Viu
+VERB_tb_pb|5.033003||Viu
+vform|5.006000|5.006000|
+vfprintf|5.003007||Viu
+visit|5.005000||Viu
+vivify_defelem|5.004000||cViu
+vivify_ref|5.004000||Viu
+vload_module|5.006000|5.003007|p
+vmess|5.006000|5.004000|p
+vnewSVpvf|5.006000|5.004000|p
+vnormal|5.009002|5.009002|
+VNORMAL|5.019008||Viu
+vnumify|5.009000|5.009000|
+VNUMIFY|5.019008||Viu
+voidnonfinal|||iu
+VOL|5.003007||Viu
+vstringify|5.009000|5.009000|
+VSTRINGIFY|5.019008||Viu
+VTBL_amagic|5.005003||Viu
+VTBL_amagicelem|5.005003||Viu
+VTBL_arylen|5.005003||Viu
+VTBL_bm|5.005003||Viu
+VTBL_collxfrm|5.005003||Viu
+VTBL_dbline|5.005003||Viu
+VTBL_defelem|5.005003||Viu
+VTBL_env|5.005003||Viu
+VTBL_envelem|5.005003||Viu
+VTBL_fm|5.005003||Viu
+VTBL_glob|5.005003||Viu
+VTBL_isa|5.005003||Viu
+VTBL_isaelem|5.005003||Viu
+VTBL_mglob|5.005003||Viu
+VTBL_nkeys|5.005003||Viu
+VTBL_pack|5.005003||Viu
+VTBL_packelem|5.005003||Viu
+VTBL_pos|5.005003||Viu
+VTBL_regdata|5.006000||Viu
+VTBL_regdatum|5.006000||Viu
+VTBL_regexp|5.005003||Viu
+VTBL_sigelem|5.005003||Viu
+VTBL_substr|5.005003||Viu
+VTBL_sv|5.005003||Viu
+VTBL_taint|5.005003||Viu
+VTBL_uvar|5.005003||Viu
+VTBL_vec|5.005003||Viu
+vTHX|5.006000||Viu
+VT_NATIVE|5.021004||Viu
+vtohl|5.003007||Viu
+vtohs|5.003007||Viu
+VUTIL_REPLACE_CORE|5.019008||Viu
+vverify|5.009003|5.009003|
+VVERIFY|5.019008||Viu
+vwarn|5.006000|5.006000|
+vwarner|5.006000|5.004000|p
+wait4pid|5.003007||Viu
+wait|5.005000||Viu
+want_vtbl_bm|5.015000||Viu
+want_vtbl_fm|5.015000||Viu
+warn|5.006000|5.003007|v
+WARN_ALL|5.006000|5.003007|p
+WARN_ALLstring|5.006000||Viu
+WARN_AMBIGUOUS|5.006000|5.003007|p
+WARN_ASSERTIONS||5.003007|ponu
+WARN_BAREWORD|5.006000|5.003007|p
+WARN_CLOSED|5.006000|5.003007|p
+WARN_CLOSURE|5.006000|5.003007|p
+WARN_DEBUGGING|5.006000|5.003007|p
+WARN_DEPRECATED|5.006000|5.003007|p
+WARN_DIGIT|5.006000|5.003007|p
+warner|5.006000|5.004000|pv
+warner_nocontext|5.006000||vVn
+WARN_EXEC|5.006000|5.003007|p
+WARN_EXITING|5.006000|5.003007|p
+WARN_EXPERIMENTAL|5.017004|5.017004|
+WARN_EXPERIMENTAL__ALPHA_ASSERTIONS|5.027009|5.027009|
+WARN_EXPERIMENTAL__BITWISE|5.021009|5.021009|
+WARN_EXPERIMENTAL__CONST_ATTR|5.021008|5.021008|
+WARN_EXPERIMENTAL__DECLARED_REFS|5.025003|5.025003|
+WARN_EXPERIMENTAL__ISA|5.031007|5.031007|
+WARN_EXPERIMENTAL__LEXICAL_SUBS|5.017005|5.017005|
+WARN_EXPERIMENTAL__POSTDEREF|5.019005|5.019005|
+WARN_EXPERIMENTAL__PRIVATE_USE|5.029009|5.029009|
+WARN_EXPERIMENTAL__REFALIASING|5.021005|5.021005|
+WARN_EXPERIMENTAL__REGEX_SETS|5.017008|5.017008|
+WARN_EXPERIMENTAL__RE_STRICT|5.021008|5.021008|
+WARN_EXPERIMENTAL__SCRIPT_RUN|5.027008|5.027008|
+WARN_EXPERIMENTAL__SIGNATURES|5.019009|5.019009|
+WARN_EXPERIMENTAL__SMARTMATCH|5.017011|5.017011|
+WARN_EXPERIMENTAL__TRY|5.033007|5.033007|
+WARN_EXPERIMENTAL__UNIPROP_WILDCARDS|5.029009|5.029009|
+WARN_EXPERIMENTAL__VLB|5.029009|5.029009|
+WARN_EXPERIMENTAL__WIN32_PERLIO|5.021001|5.021001|
+WARN_GLOB|5.006000|5.003007|p
+WARN_ILLEGALPROTO|5.011004|5.011004|
+WARN_IMPRECISION|5.011000|5.011000|
+WARN_INPLACE|5.006000|5.003007|p
+WARN_INTERNAL|5.006000|5.003007|p
+WARN_IO|5.006000|5.003007|p
+WARN_LAYER|5.008000|5.003007|p
+WARN_LOCALE|5.021006|5.021006|
+WARN_MALLOC|5.006000|5.003007|p
+WARN_MISC|5.006000|5.003007|p
+WARN_MISSING|5.021002|5.021002|
+WARN_NEWLINE|5.006000|5.003007|p
+warn_nocontext|5.006000||pvVn
+WARN_NONCHAR|5.013010|5.013010|
+WARN_NONEstring|5.006000||Viu
+WARN_NON_UNICODE|5.013010|5.013010|
+WARN_NUMERIC|5.006000|5.003007|p
+WARN_ONCE|5.006000|5.003007|p
+warn_on_first_deprecated_use|5.025009||Viu
+WARN_OVERFLOW|5.006000|5.003007|p
+WARN_PACK|5.006000|5.003007|p
+WARN_PARENTHESIS|5.006000|5.003007|p
+WARN_PIPE|5.006000|5.003007|p
+WARN_PORTABLE|5.006000|5.003007|p
+WARN_PRECEDENCE|5.006000|5.003007|p
+WARN_PRINTF|5.006000|5.003007|p
+_warn_problematic_locale|5.021008||cVniu
+WARN_PROTOTYPE|5.006000|5.003007|p
+WARN_QW|5.006000|5.003007|p
+WARN_RECURSION|5.006000|5.003007|p
+WARN_REDEFINE|5.006000|5.003007|p
+WARN_REDUNDANT|5.021002|5.021002|
+WARN_REGEXP|5.006000|5.003007|p
+WARN_RESERVED|5.006000|5.003007|p
+WARN_SEMICOLON|5.006000|5.003007|p
+WARN_SEVERE|5.006000|5.003007|p
+WARN_SHADOW|5.027007|5.027007|
+WARNshift|5.011001||Viu
+WARN_SIGNAL|5.006000|5.003007|p
+WARNsize|5.006000||Viu
+WARN_SUBSTR|5.006000|5.003007|p
+WARN_SURROGATE|5.013010|5.013010|
+warn_sv|5.013001|5.003007|p
+WARN_SYNTAX|5.006000|5.003007|p
+WARN_SYSCALLS|5.019004|5.019004|
+WARN_TAINT|5.006000|5.003007|p
+WARN_THREADS|5.008000|5.003007|p
+WARN_UNINITIALIZED|5.006000|5.003007|p
+WARN_UNOPENED|5.006000|5.003007|p
+WARN_UNPACK|5.006000|5.003007|p
+WARN_UNTIE|5.006000|5.003007|p
+WARN_UTF8|5.006000|5.003007|p
+WARN_VOID|5.006000|5.003007|p
+was_lvalue_sub|||ciu
+watch|5.003007||Viu
+WB_BREAKABLE|5.023008||Viu
+WB_DQ_then_HL|5.023008||Viu
+WB_Ex_or_FO_or_ZWJ_then_foo|5.025003||Viu
+WB_HL_then_DQ|5.023008||Viu
+WB_hs_then_hs|5.023008||Viu
+WB_LE_or_HL_then_MB_or_ML_or_SQ|5.023008||Viu
+WB_MB_or_ML_or_SQ_then_LE_or_HL|5.023008||Viu
+WB_MB_or_MN_or_SQ_then_NU|5.023008||Viu
+WB_NOBREAK|5.023008||Viu
+WB_NU_then_MB_or_MN_or_SQ|5.023008||Viu
+WB_RI_then_RI|5.025003||Viu
+WCTOMB_LOCK|5.033005||Viu
+WCTOMB_UNLOCK|5.033005||Viu
+WEXITSTATUS|5.008001||Viu
+what_MULTI_CHAR_FOLD_latin1_safe|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part0|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part1|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part2|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part3|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part4|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part5|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part6|5.033005||Viu
+what_MULTI_CHAR_FOLD_utf8_safe_part7|5.033005||Viu
+whichsig|5.003007|5.003007|
+whichsig_pv|5.015004|5.015004|
+whichsig_pvn|5.015004|5.015004|
+whichsig_sv|5.015004|5.015004|
+WHILEM|5.003007||Viu
+WHILEM_A_max|5.009005||Viu
+WHILEM_A_max_fail|5.009005||Viu
+WHILEM_A_max_fail_t8_p8|5.033003||Viu
+WHILEM_A_max_fail_t8_pb|5.033003||Viu
+WHILEM_A_max_fail_tb_p8|5.033003||Viu
+WHILEM_A_max_fail_tb_pb|5.033003||Viu
+WHILEM_A_max_t8_p8|5.033003||Viu
+WHILEM_A_max_t8_pb|5.033003||Viu
+WHILEM_A_max_tb_p8|5.033003||Viu
+WHILEM_A_max_tb_pb|5.033003||Viu
+WHILEM_A_min|5.009005||Viu
+WHILEM_A_min_fail|5.009005||Viu
+WHILEM_A_min_fail_t8_p8|5.033003||Viu
+WHILEM_A_min_fail_t8_pb|5.033003||Viu
+WHILEM_A_min_fail_tb_p8|5.033003||Viu
+WHILEM_A_min_fail_tb_pb|5.033003||Viu
+WHILEM_A_min_t8_p8|5.033003||Viu
+WHILEM_A_min_t8_pb|5.033003||Viu
+WHILEM_A_min_tb_p8|5.033003||Viu
+WHILEM_A_min_tb_pb|5.033003||Viu
+WHILEM_A_pre|5.009005||Viu
+WHILEM_A_pre_fail|5.009005||Viu
+WHILEM_A_pre_fail_t8_p8|5.033003||Viu
+WHILEM_A_pre_fail_t8_pb|5.033003||Viu
+WHILEM_A_pre_fail_tb_p8|5.033003||Viu
+WHILEM_A_pre_fail_tb_pb|5.033003||Viu
+WHILEM_A_pre_t8_p8|5.033003||Viu
+WHILEM_A_pre_t8_pb|5.033003||Viu
+WHILEM_A_pre_tb_p8|5.033003||Viu
+WHILEM_A_pre_tb_pb|5.033003||Viu
+WHILEM_B_max|5.009005||Viu
+WHILEM_B_max_fail|5.009005||Viu
+WHILEM_B_max_fail_t8_p8|5.033003||Viu
+WHILEM_B_max_fail_t8_pb|5.033003||Viu
+WHILEM_B_max_fail_tb_p8|5.033003||Viu
+WHILEM_B_max_fail_tb_pb|5.033003||Viu
+WHILEM_B_max_t8_p8|5.033003||Viu
+WHILEM_B_max_t8_pb|5.033003||Viu
+WHILEM_B_max_tb_p8|5.033003||Viu
+WHILEM_B_max_tb_pb|5.033003||Viu
+WHILEM_B_min|5.009005||Viu
+WHILEM_B_min_fail|5.009005||Viu
+WHILEM_B_min_fail_t8_p8|5.033003||Viu
+WHILEM_B_min_fail_t8_pb|5.033003||Viu
+WHILEM_B_min_fail_tb_p8|5.033003||Viu
+WHILEM_B_min_fail_tb_pb|5.033003||Viu
+WHILEM_B_min_t8_p8|5.033003||Viu
+WHILEM_B_min_t8_pb|5.033003||Viu
+WHILEM_B_min_tb_p8|5.033003||Viu
+WHILEM_B_min_tb_pb|5.033003||Viu
+WHILEM_t8_p8|5.033003||Viu
+WHILEM_t8_pb|5.033003||Viu
+WHILEM_tb_p8|5.033003||Viu
+WHILEM_tb_pb|5.033003||Viu
+WIDEST_UTYPE|5.015004|5.003007|poVnu
+WIFEXITED|5.008001||Viu
+WIFSIGNALED|5.008001||Viu
+WIFSTOPPED|5.008001||Viu
+win32_croak_not_implemented|5.017006||Vniu
+WIN32SCK_IS_STDSCK|5.007001||Viu
+win32_setlocale|5.027006||Viu
+withinCOUNT|5.031004||Viu
+withinCOUNT_KNOWN_VALID|5.033005||Viu
+WITH_LC_NUMERIC_SET_TO_NEEDED|5.031003|5.031003|
+WITH_LC_NUMERIC_SET_TO_NEEDED_IN|5.031003|5.031003|
+with_queued_errors|5.013001||Viu
+with_tp_UTF8ness|5.033003||Viu
+WNOHANG|5.008001||Viu
+wrap_keyword_plugin|5.027006|5.027006|x
+wrap_op_checker|5.015008|5.015008|
+write|5.005000||Viu
+write_to_stderr|5.008001||Viu
+WSTOPSIG|5.008001||Viu
+WTERMSIG|5.008001||Viu
+WUNTRACED|5.008001||Viu
+XCPT_CATCH|5.009002|5.003007|p
+XCPT_RETHROW|5.009002|5.003007|p
+XCPT_TRY_END|5.009002|5.003007|p
+XCPT_TRY_START|5.009002|5.003007|p
+XDIGIT_VALUE|5.019008||Viu
+XHvTOTALKEYS|5.007003||Viu
+xio_any|5.006001||Viu
+xio_dirp|5.006001||Viu
+xiv_iv|5.009003||Viu
+xlv_targoff|5.019004||Viu
+XopDISABLE|5.013007|5.013007|V
+XOPd_xop_class|5.013007||Viu
+XOPd_xop_desc|5.013007||Viu
+XOPd_xop_name|5.013007||Viu
+XOPd_xop_peep|5.013007||Viu
+XopENABLE|5.013007|5.013007|V
+XopENTRY|5.013007|5.013007|V
+XopENTRYCUSTOM|5.019006|5.013007|V
+XopENTRY_set|5.013007|5.013007|V
+XopFLAGS|5.013007|5.013007|
+XOPf_xop_class|5.013007||Viu
+XOPf_xop_desc|5.013007||Viu
+XOPf_xop_name|5.013007||Viu
+XOPf_xop_peep|5.013007||Viu
+XORSHIFT128_set|5.027001||Viu
+XORSHIFT96_set|5.027001||Viu
+XPUSHi|5.003007|5.003007|
+XPUSHmortal|5.009002|5.003007|p
+XPUSHn|5.006000|5.003007|
+XPUSHp|5.003007|5.003007|
+XPUSHs|5.003007|5.003007|
+XPUSHTARG|5.003007||Viu
+XPUSHu|5.004000|5.003007|p
+XPUSHundef|5.006000||Viu
+xpv_len|5.017006||Viu
+XS|5.003007|5.003007|Vu
+XSANY|5.003007||Viu
+XS_APIVERSION_BOOTCHECK|5.013004|5.013004|
+XS_APIVERSION_POPMARK_BOOTCHECK|5.021006||Viu
+XS_APIVERSION_SETXSUBFN_POPMARK_BOOTCHECK|5.021006||Viu
+xs_boot_epilog|5.021006||cViu
+XS_BOTHVERSION_BOOTCHECK|5.021006||Viu
+XS_BOTHVERSION_POPMARK_BOOTCHECK|5.021006||Viu
+XS_BOTHVERSION_SETXSUBFN_POPMARK_BOOTCHECK|5.021006||Viu
+XS_DYNAMIC_FILENAME|5.009004||Viu
+XS_EXTERNAL|5.015002|5.015002|Vu
+xs_handshake|||vcniu
+XSINTERFACE_CVT|5.005000||Viu
+XSINTERFACE_CVT_ANON|5.010000||Viu
+XSINTERFACE_FUNC|5.005000||Viu
+XSINTERFACE_FUNC_SET|5.005000||Viu
+XS_INTERNAL|5.015002|5.015002|Vu
+XSprePUSH|5.006000|5.003007|poVnu
+XSPROTO|5.010000|5.003007|pVu
+XSRETURN|5.003007|5.003007|p
+XSRETURN_EMPTY|5.003007|5.003007|
+XSRETURN_IV|5.003007|5.003007|
+XSRETURN_NO|5.003007|5.003007|
+XSRETURN_NV|5.006000|5.003007|
+XSRETURN_PV|5.003007|5.003007|
+XSRETURN_PVN|5.006000||Viu
+XSRETURN_UNDEF|5.003007|5.003007|
+XSRETURN_UV|5.008001|5.003007|p
+XSRETURN_YES|5.003007|5.003007|
+XS_SETXSUBFN_POPMARK|5.021006||Viu
+XST_mIV|5.003007|5.003007|
+XST_mNO|5.003007|5.003007|
+XST_mNV|5.006000|5.003007|
+XST_mPV|5.003007|5.003007|
+XST_mPVN|5.006000||Viu
+XST_mUNDEF|5.003007|5.003007|
+XST_mUV|5.008001|5.003007|p
+XST_mYES|5.003007|5.003007|
+XS_VERSION|5.003007|5.003007|
+XS_VERSION_BOOTCHECK|5.003007|5.003007|
+xs_version_bootcheck|||iu
+XTENDED_PAT_MOD|5.009005||Viu
+xuv_uv|5.009003||Viu
+YESEXPR|5.027010||Viu
+YESSTR|5.027010||Viu
+YIELD|5.005000||Viu
+YYDEBUG|5.025006||Viu
+YYEMPTY|5.009005||Viu
+yyerror|5.003007||Viu
+yyerror_pv|5.016000||Viu
+yyerror_pvn|5.016000||Viu
+yylex|5.003007||cViu
+yyparse|5.003007||Viu
+yyquit|5.025010||Viu
+YYSTYPE_IS_DECLARED|5.009001||Viu
+YYSTYPE_IS_TRIVIAL|5.009001||Viu
+YYTOKENTYPE|5.009001||Viu
+yyunlex|5.013005||Viu
+yywarn|5.003007||Viu
+ZAPHOD32_FINALIZE|5.027001||Viu
+ZAPHOD32_MIX|5.027001||Viu
+ZAPHOD32_SCRAMBLE32|5.027001||Viu
+ZAPHOD32_STATIC_INLINE|5.027001||Viu
+ZAPHOD32_WARN2|5.027001||Viu
+ZAPHOD32_WARN3|5.027001||Viu
+ZAPHOD32_WARN4|5.027001||Viu
+ZAPHOD32_WARN5|5.027001||Viu
+ZAPHOD32_WARN6|5.027001||Viu
+Zero|5.003007|5.003007|
+ZeroD|5.009002|5.003007|p
+);
+
+if (exists $opt{'list-unsupported'}) {
+ my $f;
+ for $f (sort dictionary_order keys %API) {
+ next if $API{$f}{core_only};
+ next if $API{$f}{beyond_depr};
+ next if $API{$f}{inaccessible};
+ next if $API{$f}{experimental};
+ next unless $API{$f}{todo};
+ next if int_parse_version($API{$f}{todo}) <= $int_min_perl;
+ my $repeat = 40 - length($f);
+ $repeat = 0 if $repeat < 0;
+ print "$f ", '.'x $repeat, " ", format_version($API{$f}{todo}), "\n";
+ }
+ exit 0;
+}
+
+# Scan for hints, possible replacement candidates, etc.
+
+my(%replace, %need, %hints, %warnings, %depends);
+my $replace = 0;
+my($hint, $define, $function);
+
+sub find_api
+{
+ BEGIN { 'warnings'->unimport('uninitialized') if "$]" > '5.006' }
+ my $code = shift;
+ $code =~ s{
+ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+ | "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*' }{}egsx;
+ grep { exists $API{$_} } $code =~ /(\w+)/mg;
+}
+
+while (<DATA>) {
+ if ($hint) {
+
+ # Here, we are in the middle of accumulating a hint or warning.
+ my $end_of_hint = 0;
+
+ # A line containing a comment end marker closes the hint. Remove that
+ # marker for processing below.
+ if (s/\s*$rcce(.*?)\s*$//) {
+ die "Nothing can follow the end of comment in '$_'\n" if length $1 > 0;
+ $end_of_hint = 1;
+ }
+
+ # Set $h to the hash of which type.
+ my $h = $hint->[0] eq 'Hint' ? \%hints : \%warnings;
+
+ # Ignore any leading and trailing white space, and an optional star comment
+ # continuation marker, then place the meat of the line into $1
+ m/^\s*(?:\*\s*)?(.*?)\s*$/;
+
+ # Add the meat of this line to the hash value of each API element it
+ # applies to
+ for (@{$hint->[1]}) {
+ $h->{$_} ||= ''; # avoid the warning older perls generate
+ $h->{$_} .= "$1\n";
+ }
+
+ # If the line had a comment close, we are through with this hint
+ undef $hint if $end_of_hint;
+
+ next;
+ }
+
+ # Set up $hint if this is the beginning of a Hint: or Warning:
+ # These are from a multi-line C comment in the file, with the first line
+ # looking like (a space has been inserted because this file can't have C
+ # comment markers in it):
+ # / * Warning: PL_expect, PL_copline, PL_rsfp
+ #
+ # $hint becomes
+ # [
+ # 'Warning',
+ # [
+ # 'PL_expect',
+ # 'PL_copline',
+ # 'PL_rsfp',
+ # ],
+ # ]
+ if (m{^\s*$rccs\s+(Hint|Warning):\s+(\w+(?:,?\s+\w+)*)\s*$}) {
+ $hint = [$1, [split /,?\s+/, $2]];
+ next;
+ }
+
+ if ($define) { # If in the middle of a definition...
+
+ # append a continuation line ending with backslash.
+ if ($define->[1] =~ /\\$/) {
+ $define->[1] .= $_;
+ }
+ else { # Otherwise this line ends the definition, make foo depend on bar
+ # (and what bar depends on) if its not one of ppp's own constructs
+ if (exists $API{$define->[0]} && $define->[1] !~ /^DPPP_\(/) {
+ my @n = find_api($define->[1]);
+ push @{$depends{$define->[0]}}, @n if @n
+ }
+ undef $define;
+ }
+ }
+
+ # For '#define foo bar' or '#define foo(a,b,c) bar', $define becomes a
+ # reference to [ foo, bar ]
+ $define = [$1, $2] if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(.*)};
+
+ if ($function) {
+ if (/^}/) {
+ if (exists $API{$function->[0]}) {
+ my @n = find_api($function->[1]);
+ push @{$depends{$function->[0]}}, @n if @n
+ }
+ undef $function;
+ }
+ else {
+ $function->[1] .= $_;
+ }
+ }
+
+ $function = [$1, ''] if m{^DPPP_\(my_(\w+)\)};
+
+ # Set $replace to the number given for lines that look like
+ # / * Replace: \d+ * /
+ # Thus setting it to 1 starts a region where replacements are automatically
+ # done, and setting it to 0 ends that region.
+ $replace = $1 if m{^\s*$rccs\s+Replace:\s+(\d+)\s+$rcce\s*$};
+
+ # Add bar => foo to %replace for lines like '#define foo bar in a region
+ # where $replace is non-zero
+ $replace{$2} = $1 if $replace and m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+)};
+
+ # Add bar => foo to %replace for lines like '#define foo bar / * Replace * /
+ $replace{$2} = $1 if m{^\s*#\s*define\s+(\w+)(?:\([^)]*\))?\s+(\w+).*$rccs\s+Replace\s+$rcce};
+
+ # Add foo => bar to %replace for lines like / * Replace foo with bar * /
+ $replace{$1} = $2 if m{^\s*$rccs\s+Replace (\w+) with (\w+.*?)\s+$rcce\s*$};
+
+ # For lines like / * foo, bar depends on baz, bat * /
+ # create a list of the elements on the rhs, and make that list apply to each
+ # element in the lhs, which becomes a key in \%depends.
+ if (m{^\s*$rccs\s+(\w+(\s*,\s*\w+)*)\s+depends\s+on\s+(\w+(\s*,\s*\w+)*)\s+$rcce\s*$}) {
+ my @deps = map { s/\s+//g; $_ } split /,/, $3;
+ my $d;
+ for $d (map { s/\s+//g; $_ } split /,/, $1) {
+ push @{$depends{$d}}, @deps;
+ }
+ }
+
+ $need{$1} = 1 if m{^#if\s+defined\(NEED_(\w+)(?:_GLOBAL)?\)};
+}
+
+for (values %depends) {
+ my %seen;
+ $_ = [sort dictionary_order grep !$seen{$_}++, @$_];
+}
+
+if (exists $opt{'api-info'}) {
+ my $f;
+ my $count = 0;
+ my $match = $opt{'api-info'} =~ m!^/(.*)/$! ? $1 : "^\Q$opt{'api-info'}\E\$";
+
+ # Sort the names, and split into two classes; one for things that are part of
+ # the API; a second for things that aren't.
+ my @ok_to_use;
+ my @shouldnt_use;
+ for $f (sort dictionary_order keys %API) {
+ next unless $f =~ /$match/;
+ my $base = int_parse_version($API{$f}{base}) if $API{$f}{base};
+ if ($base && ! $API{$f}{inaccessible} && ! $API{$f}{core_only}) {
+ push @ok_to_use, $f;
+ }
+ else {
+ push @shouldnt_use, $f;
+ }
+ }
+
+ # We normally suppress non-API items. But if the search matched no API
+ # items, output the non-ones. This allows someone to get the info for an
+ # item if they ask for it specifically enough, but doesn't normally clutter
+ # the output with irrelevant results.
+ @ok_to_use = @shouldnt_use unless @ok_to_use;
+
+ for $f (@ok_to_use) {
+ print "\n=== $f ===\n";
+ my $info = 0;
+ my $base;
+ $base = int_parse_version($API{$f}{base}) if $API{$f}{base};
+ my $todo;
+ $todo = int_parse_version($API{$f}{todo}) if $API{$f}{todo};
+
+ # Output information
+ if ($base) {
+ my $with_or= "";
+ if ( $base <= $int_min_perl
+ || ( (! $API{$f}{provided} && ! $todo)
+ || ($todo && $todo >= $base)))
+ {
+ $with_or= " with or";
+ }
+
+ my $Supported = ($API{$f}{undocumented}) ? 'Available' : 'Supported';
+ print "\n$Supported at least since perl-",
+ format_version($base), ",$with_or without $ppport.";
+ if ($API{$f}{unverified}) {
+ print "\nThis information is based on inspection of the source code",
+ " and has not been\n",
+ "verified by successful compilation.";
+ }
+ print "\n";
+ $info++;
+ }
+ if ($API{$f}{provided} || $todo) {
+ print "\nThis is only supported by $ppport, and NOT by perl versions going forward.\n" unless $base;
+ if ($todo) {
+ if (! $base || $todo < $base) {
+ my $additionally = "";
+ $additionally .= " additionally" if $base;
+ print "$ppport$additionally provides support at least back to perl-",
+ format_version($todo),
+ ".\n";
+ }
+ }
+ elsif (! $base || $base > $int_min_perl) {
+ if (exists $depends{$f}) {
+ my $max = 0;
+ for (@{$depends{$f}}) {
+ $max = int_parse_version($API{$_}{todo}) if $API{$_}{todo} && $API{$_}{todo} > $max;
+ # XXX What to assume unspecified values are? This effectively makes them MIN_PERL
+ }
+ $todo = $max if $max;
+ }
+ print "\n$ppport provides support for this, but ironically, does not",
+ " currently know,\n",
+ "for this report, the minimum version it supports for this";
+ if ($API{$f}{undocumented}) {
+ print " and many things\n",
+ "it provides that are implemented as macros and aren't",
+ " documented. You can\n",
+ "help by submitting a documentation patch";
+ }
+ print ".\n";
+ if ($todo) {
+ if ($todo <= $int_min_perl) {
+ print "It may very well be supported all the way back to ",
+ format_version(5.003_07), ".\n";
+ }
+ else {
+ print "But given the things $f depends on, it's a good",
+ " guess that it isn't\n",
+ "supported prior to ", format_version($todo), ".\n";
+ }
+ }
+ }
+ }
+ if ($API{$f}{provided}) {
+ print "Support needs to be explicitly requested by #define NEED_$f\n",
+ "(or #define NEED_${f}_GLOBAL).\n" if exists $need{$f};
+ $info++;
+ }
+
+ if ($base || ! $API{$f}{ppport_fnc}) {
+ my $email = "Send email to perl5-porters\@perl.org if you need to have this functionality.\n";
+ if ($API{$f}{inaccessible}) {
+ print "\nThis is not part of the public API, and may not even be accessible to XS code.\n";
+ $info++;
+ }
+ elsif ($API{$f}{core_only}) {
+ print "\nThis is not part of the public API, and should not be used by XS code.\n";
+ $info++;
+ }
+ elsif ($API{$f}{deprecated}) {
+ print "\nThis is deprecated and should not be used. Convert existing uses.\n";
+ $info++;
+ }
+ elsif ($API{$f}{experimental}) {
+ print "\nThe API for this is unstable and should not be used by XS code.\n", $email;
+ $info++;
+ }
+ elsif ($API{$f}{undocumented}) {
+ print "\nSince this is undocumented, the API should be considered unstable.\n";
+ if ($API{$f}{provided}) {
+ print "Consider bringing this up on the list: perl5-porters\@perl.org.\n";
+ }
+ else {
+ print "It may be that this is not intended for XS use, or it may just be\n",
+ "that no one has gotten around to documenting it.\n", $email;
+ }
+ $info++;
+ }
+ unless ($info) {
+ print "No portability information available. Check your spelling; or",
+ " this could be\na bug in Devel::PPPort. To report an issue:\n",
+ "https://github.com/Dual-Life/Devel-PPPort/issues/new\n";
+ }
+ }
+
+ print "\nDepends on: ", join(', ', @{$depends{$f}}), ".\n"
+ if exists $depends{$f};
+ if (exists $hints{$f} || exists $warnings{$f}) {
+ print "\n$hints{$f}" if exists $hints{$f};
+ print "\nWARNING:\n$warnings{$f}" if exists $warnings{$f};
+ $info++;
+ }
+ $count++;
+ }
+
+ $count or print "\nFound no API matching '$opt{'api-info'}'.";
+ print "\n";
+ exit 0;
+}
+
+if (exists $opt{'list-provided'}) {
+ my $f;
+ for $f (sort dictionary_order keys %API) {
+ next unless $API{$f}{provided};
+ my @flags;
+ push @flags, 'explicit' if exists $need{$f};
+ push @flags, 'depend' if exists $depends{$f};
+ push @flags, 'hint' if exists $hints{$f};
+ push @flags, 'warning' if exists $warnings{$f};
+ my $flags = @flags ? ' ['.join(', ', @flags).']' : '';
+ print "$f$flags\n";
+ }
+ exit 0;
+}
+
+my @files;
+my @srcext = qw( .xs .c .h .cc .cpp -c.inc -xs.inc );
+my $srcext = join '|', map { quotemeta $_ } @srcext;
+
+if (@ARGV) {
+ my %seen;
+ for (@ARGV) {
+ if (-e) {
+ if (-f) {
+ push @files, $_ unless $seen{$_}++;
+ }
+ else { warn "'$_' is not a file.\n" }
+ }
+ else {
+ my @new = grep { -f } glob $_
+ or warn "'$_' does not exist.\n";
+ push @files, grep { !$seen{$_}++ } @new;
+ }
+ }
+}
+else {
+ eval {
+ require File::Find;
+ File::Find::find(sub {
+ $File::Find::name =~ /($srcext)$/i
+ and push @files, $File::Find::name;
+ }, '.');
+ };
+ if ($@) {
+ @files = map { glob "*$_" } @srcext;
+ }
+}
+
+if (!@ARGV || $opt{filter}) {
+ my(@in, @out);
+ my %xsc = map { /(.*)\.xs$/ ? ("$1.c" => 1, "$1.cc" => 1) : () } @files;
+ for (@files) {
+ my $out = exists $xsc{$_} || /\b\Q$ppport\E$/i || !/($srcext)$/i;
+ push @{ $out ? \@out : \@in }, $_;
+ }
+ if (@ARGV && @out) {
+ warning("Skipping the following files (use --nofilter to avoid this):\n| ", join "\n| ", @out);
+ }
+ @files = @in;
+}
+
+die "No input files given!\n" unless @files;
+
+my(%files, %global, %revreplace);
+%revreplace = reverse %replace;
+my $filename;
+my $patch_opened = 0;
+
+for $filename (@files) {
+ unless (open IN, "<$filename") {
+ warn "Unable to read from $filename: $!\n";
+ next;
+ }
+
+ info("Scanning $filename ...");
+
+ my $c = do { local $/; <IN> };
+ close IN;
+
+ my %file = (orig => $c, changes => 0);
+
+ # Temporarily remove C/XS comments and strings from the code
+ my @ccom;
+
+ $c =~ s{
+ ( ^$HS*\#$HS*include\b[^\r\n]+\b(?:\Q$ppport\E|XSUB\.h)\b[^\r\n]*
+ | ^$HS*\#$HS*(?:define|elif|if(?:def)?)\b[^\r\n]* )
+ | ( ^$HS*\#[^\r\n]*
+ | "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*'
+ | / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]* ) )
+ }{ defined $2 and push @ccom, $2;
+ defined $1 ? $1 : "$ccs$#ccom$cce" }mgsex;
+
+ $file{ccom} = \@ccom;
+ $file{code} = $c;
+ $file{has_inc_ppport} = $c =~ /^$HS*#$HS*include[^\r\n]+\b\Q$ppport\E\b/m;
+
+ my $func;
+
+ for $func (keys %API) {
+ my $match = $func;
+ $match .= "|$revreplace{$func}" if exists $revreplace{$func};
+ if ($c =~ /\b(?:Perl_)?($match)\b/) {
+ $file{uses_replace}{$1}++ if exists $revreplace{$func} && $1 eq $revreplace{$func};
+ $file{uses_Perl}{$func}++ if $c =~ /\bPerl_$func\b/;
+ if (exists $API{$func}{provided}) {
+ $file{uses_provided}{$func}++;
+ if ( ! exists $API{$func}{base}
+ || int_parse_version($API{$func}{base}) > $opt{'compat-version'})
+ {
+ $file{uses}{$func}++;
+ my @deps = rec_depend($func);
+ if (@deps) {
+ $file{uses_deps}{$func} = \@deps;
+ for (@deps) {
+ $file{uses}{$_} = 0 unless exists $file{uses}{$_};
+ }
+ }
+ for ($func, @deps) {
+ $file{needs}{$_} = 'static' if exists $need{$_};
+ }
+ }
+ }
+ if ( exists $API{$func}{todo}
+ && int_parse_version($API{$func}{todo}) > $opt{'compat-version'})
+ {
+ if ($c =~ /\b$func\b/) {
+ $file{uses_todo}{$func}++;
+ }
+ }
+ }
+ }
+
+ while ($c =~ /^$HS*#$HS*define$HS+(NEED_(\w+?)(_GLOBAL)?)\b/mg) {
+ if (exists $need{$2}) {
+ $file{defined $3 ? 'needed_global' : 'needed_static'}{$2}++;
+ }
+ else { warning("Possibly wrong #define $1 in $filename") }
+ }
+
+ for (qw(uses needs uses_todo needed_global needed_static)) {
+ for $func (keys %{$file{$_}}) {
+ push @{$global{$_}{$func}}, $filename;
+ }
+ }
+
+ $files{$filename} = \%file;
+}
+
+# Globally resolve NEED_'s
+my $need;
+for $need (keys %{$global{needs}}) {
+ if (@{$global{needs}{$need}} > 1) {
+ my @targets = @{$global{needs}{$need}};
+ my @t = grep $files{$_}{needed_global}{$need}, @targets;
+ @targets = @t if @t;
+ @t = grep /\.xs$/i, @targets;
+ @targets = @t if @t;
+ my $target = shift @targets;
+ $files{$target}{needs}{$need} = 'global';
+ for (@{$global{needs}{$need}}) {
+ $files{$_}{needs}{$need} = 'extern' if $_ ne $target;
+ }
+ }
+}
+
+for $filename (@files) {
+ exists $files{$filename} or next;
+
+ info("=== Analyzing $filename ===");
+
+ my %file = %{$files{$filename}};
+ my $func;
+ my $c = $file{code};
+ my $warnings = 0;
+
+ for $func (sort dictionary_order keys %{$file{uses_Perl}}) {
+ if ($API{$func}{varargs}) {
+ unless ($API{$func}{noTHXarg}) {
+ my $changes = ($c =~ s{\b(Perl_$func\s*\(\s*)(?!aTHX_?)(\)|[^\s)]*\))}
+ { $1 . ($2 eq ')' ? 'aTHX' : 'aTHX_ ') . $2 }ge);
+ if ($changes) {
+ warning("Doesn't pass interpreter argument aTHX to Perl_$func");
+ $file{changes} += $changes;
+ }
+ }
+ }
+ else {
+ warning("Uses Perl_$func instead of $func");
+ $file{changes} += ($c =~ s{\bPerl_$func(\s*)\((\s*aTHX_?)?\s*}
+ {$func$1(}g);
+ }
+ }
+
+ for $func (sort dictionary_order keys %{$file{uses_replace}}) {
+ warning("Uses $func instead of $replace{$func}");
+ $file{changes} += ($c =~ s/\b$func\b/$replace{$func}/g);
+ }
+
+ for $func (sort dictionary_order keys %{$file{uses_provided}}) {
+ if ($file{uses}{$func}) {
+ if (exists $file{uses_deps}{$func}) {
+ diag("Uses $func, which depends on ", join(', ', @{$file{uses_deps}{$func}}));
+ }
+ else {
+ diag("Uses $func");
+ }
+ }
+ $warnings += (hint($func) || 0);
+ }
+
+ unless ($opt{quiet}) {
+ for $func (sort dictionary_order keys %{$file{uses_todo}}) {
+ next if int_parse_version($API{$func}{todo}) <= $int_min_perl;
+ print "*** WARNING: Uses $func, which may not be portable below perl ",
+ format_version($API{$func}{todo}), ", even with '$ppport'\n";
+ $warnings++;
+ }
+ }
+
+ for $func (sort dictionary_order keys %{$file{needed_static}}) {
+ my $message = '';
+ if (not exists $file{uses}{$func}) {
+ $message = "No need to define NEED_$func if $func is never used";
+ }
+ elsif (exists $file{needs}{$func} && $file{needs}{$func} ne 'static') {
+ $message = "No need to define NEED_$func when already needed globally";
+ }
+ if ($message) {
+ diag($message);
+ $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_$func\b.*$LF//mg);
+ }
+ }
+
+ for $func (sort dictionary_order keys %{$file{needed_global}}) {
+ my $message = '';
+ if (not exists $global{uses}{$func}) {
+ $message = "No need to define NEED_${func}_GLOBAL if $func is never used";
+ }
+ elsif (exists $file{needs}{$func}) {
+ if ($file{needs}{$func} eq 'extern') {
+ $message = "No need to define NEED_${func}_GLOBAL when already needed globally";
+ }
+ elsif ($file{needs}{$func} eq 'static') {
+ $message = "No need to define NEED_${func}_GLOBAL when only used in this file";
+ }
+ }
+ if ($message) {
+ diag($message);
+ $file{changes} += ($c =~ s/^$HS*#$HS*define$HS+NEED_${func}_GLOBAL\b.*$LF//mg);
+ }
+ }
+
+ $file{needs_inc_ppport} = keys %{$file{uses}};
+
+ if ($file{needs_inc_ppport}) {
+ my $pp = '';
+
+ for $func (sort dictionary_order keys %{$file{needs}}) {
+ my $type = $file{needs}{$func};
+ next if $type eq 'extern';
+ my $suffix = $type eq 'global' ? '_GLOBAL' : '';
+ unless (exists $file{"needed_$type"}{$func}) {
+ if ($type eq 'global') {
+ diag("Files [@{$global{needs}{$func}}] need $func, adding global request");
+ }
+ else {
+ diag("File needs $func, adding static request");
+ }
+ $pp .= "#define NEED_$func$suffix\n";
+ }
+ }
+
+ if ($pp && ($c =~ s/^(?=$HS*#$HS*define$HS+NEED_\w+)/$pp/m)) {
+ $pp = '';
+ $file{changes}++;
+ }
+
+ unless ($file{has_inc_ppport}) {
+ diag("Needs to include '$ppport'");
+ $pp .= qq(#include "$ppport"\n)
+ }
+
+ if ($pp) {
+ $file{changes} += ($c =~ s/^($HS*#$HS*define$HS+NEED_\w+.*?)^/$1$pp/ms)
+ || ($c =~ s/^(?=$HS*#$HS*include.*\Q$ppport\E)/$pp/m)
+ || ($c =~ s/^($HS*#$HS*include.*XSUB.*\s*?)^/$1$pp/m)
+ || ($c =~ s/^/$pp/);
+ }
+ }
+ else {
+ if ($file{has_inc_ppport}) {
+ diag("No need to include '$ppport'");
+ $file{changes} += ($c =~ s/^$HS*?#$HS*include.*\Q$ppport\E.*?$LF//m);
+ }
+ }
+
+ # put back in our C comments
+ my $ix;
+ my $cppc = 0;
+ my @ccom = @{$file{ccom}};
+ for $ix (0 .. $#ccom) {
+ if (!$opt{cplusplus} && $ccom[$ix] =~ s!^//!!) {
+ $cppc++;
+ $file{changes} += $c =~ s/$rccs$ix$rcce/$ccs$ccom[$ix] $cce/;
+ }
+ else {
+ $c =~ s/$rccs$ix$rcce/$ccom[$ix]/;
+ }
+ }
+
+ if ($cppc) {
+ my $s = $cppc != 1 ? 's' : '';
+ warning("Uses $cppc C++ style comment$s, which is not portable");
+ }
+
+ my $s = $warnings != 1 ? 's' : '';
+ my $warn = $warnings ? " ($warnings warning$s)" : '';
+ info("Analysis completed$warn");
+
+ if ($file{changes}) {
+ if (exists $opt{copy}) {
+ my $newfile = "$filename$opt{copy}";
+ if (-e $newfile) {
+ error("'$newfile' already exists, refusing to write copy of '$filename'");
+ }
+ else {
+ local *F;
+ if (open F, ">$newfile") {
+ info("Writing copy of '$filename' with changes to '$newfile'");
+ print F $c;
+ close F;
+ }
+ else {
+ error("Cannot open '$newfile' for writing: $!");
+ }
+ }
+ }
+ elsif (exists $opt{patch} || $opt{changes}) {
+ if (exists $opt{patch}) {
+ unless ($patch_opened) {
+ if (open PATCH, ">$opt{patch}") {
+ $patch_opened = 1;
+ }
+ else {
+ error("Cannot open '$opt{patch}' for writing: $!");
+ delete $opt{patch};
+ $opt{changes} = 1;
+ goto fallback;
+ }
+ }
+ mydiff(\*PATCH, $filename, $c);
+ }
+ else {
+fallback:
+ info("Suggested changes:");
+ mydiff(\*STDOUT, $filename, $c);
+ }
+ }
+ else {
+ my $s = $file{changes} == 1 ? '' : 's';
+ info("$file{changes} potentially required change$s detected");
+ }
+ }
+ else {
+ info("Looks good");
+ }
+}
+
+close PATCH if $patch_opened;
+
+exit 0;
+
+
+sub try_use { eval "use @_;"; return $@ eq '' }
+
+sub mydiff
+{
+ local *F = shift;
+ my($file, $str) = @_;
+ my $diff;
+
+ if (exists $opt{diff}) {
+ $diff = run_diff($opt{diff}, $file, $str);
+ }
+
+ if (!defined $diff and try_use('Text::Diff')) {
+ $diff = Text::Diff::diff($file, \$str, { STYLE => 'Unified' });
+ $diff = <<HEADER . $diff;
+--- $file
++++ $file.patched
+HEADER
+ }
+
+ if (!defined $diff) {
+ $diff = run_diff('diff -u', $file, $str);
+ }
+
+ if (!defined $diff) {
+ $diff = run_diff('diff', $file, $str);
+ }
+
+ if (!defined $diff) {
+ error("Cannot generate a diff. Please install Text::Diff or use --copy.");
+ return;
+ }
+
+ print F $diff;
+}
+
+sub run_diff
+{
+ my($prog, $file, $str) = @_;
+ my $tmp = 'dppptemp';
+ my $suf = 'aaa';
+ my $diff = '';
+ local *F;
+
+ while (-e "$tmp.$suf") { $suf++ }
+ $tmp = "$tmp.$suf";
+
+ if (open F, ">$tmp") {
+ print F $str;
+ close F;
+
+ if (open F, "$prog $file $tmp |") {
+ while (<F>) {
+ s/\Q$tmp\E/$file.patched/;
+ $diff .= $_;
+ }
+ close F;
+ unlink $tmp;
+ return $diff;
+ }
+
+ unlink $tmp;
+ }
+ else {
+ error("Cannot open '$tmp' for writing: $!");
+ }
+
+ return undef;
+}
+
+sub rec_depend
+{
+ my($func, $seen) = @_;
+ return () unless exists $depends{$func};
+ $seen = {%{$seen||{}}};
+ return () if $seen->{$func}++;
+ my %s;
+ grep !$s{$_}++, map { ($_, rec_depend($_, $seen)) } @{$depends{$func}};
+}
+
+sub info
+{
+ $opt{quiet} and return;
+ print @_, "\n";
+}
+
+sub diag
+{
+ $opt{quiet} and return;
+ $opt{diag} and print @_, "\n";
+}
+
+sub warning
+{
+ $opt{quiet} and return;
+ print "*** ", @_, "\n";
+}
+
+sub error
+{
+ print "*** ERROR: ", @_, "\n";
+}
+
+my %given_hints;
+my %given_warnings;
+sub hint
+{
+ $opt{quiet} and return;
+ my $func = shift;
+ my $rv = 0;
+ if (exists $warnings{$func} && !$given_warnings{$func}++) {
+ my $warn = $warnings{$func};
+ $warn =~ s!^!*** !mg;
+ print "*** WARNING: $func\n", $warn;
+ $rv++;
+ }
+ if ($opt{hints} && exists $hints{$func} && !$given_hints{$func}++) {
+ my $hint = $hints{$func};
+ $hint =~ s/^/ /mg;
+ print " --- hint for $func ---\n", $hint;
+ }
+ $rv || 0;
+}
+
+sub usage
+{
+ my($usage) = do { local(@ARGV,$/)=($0); <> } =~ /^=head\d$HS+SYNOPSIS\s*^(.*?)\s*^=/ms;
+ my %M = ( 'I' => '*' );
+ $usage =~ s/^\s*perl\s+\S+/$^X $0/;
+ $usage =~ s/([A-Z])<([^>]+)>/$M{$1}$2$M{$1}/g;
+
+ print <<ENDUSAGE;
+
+Usage: $usage
+
+See perldoc $0 for details.
+
+ENDUSAGE
+
+ exit 2;
+}
+
+sub strip
+{
+ my $self = do { local(@ARGV,$/)=($0); <> };
+ my($copy) = $self =~ /^=head\d\s+COPYRIGHT\s*^(.*?)^=\w+/ms;
+ $copy =~ s/^(?=\S+)/ /gms;
+ $self =~ s/^$HS+Do NOT edit.*?(?=^-)/$copy/ms;
+ $self =~ s/^SKIP.*(?=^__DATA__)/SKIP
+if (\@ARGV && \$ARGV[0] eq '--unstrip') {
+ eval { require Devel::PPPort };
+ \$@ and die "Cannot require Devel::PPPort, please install.\\n";
+ if (eval \$Devel::PPPort::VERSION < $VERSION) {
+ die "$0 was originally generated with Devel::PPPort $VERSION.\\n"
+ . "Your Devel::PPPort is only version \$Devel::PPPort::VERSION.\\n"
+ . "Please install a newer version, or --unstrip will not work.\\n";
+ }
+ Devel::PPPort::WriteFile(\$0);
+ exit 0;
+}
+print <<END;
+
+Sorry, but this is a stripped version of \$0.
+
+To be able to use its original script and doc functionality,
+please try to regenerate this file using:
+
+ \$^X \$0 --unstrip
+
+END
+/ms;
+ my($pl, $c) = $self =~ /(.*^__DATA__)(.*)/ms;
+ $c =~ s{
+ / (?: \*[^*]*\*+(?:[^$ccs][^*]*\*+)* / | /[^\r\n]*)
+ | ( "[^"\\]*(?:\\.[^"\\]*)*"
+ | '[^'\\]*(?:\\.[^'\\]*)*' )
+ | ($HS+) }{ defined $2 ? ' ' : ($1 || '') }gsex;
+ $c =~ s!\s+$!!mg;
+ $c =~ s!^$LF!!mg;
+ $c =~ s!^\s*#\s*!#!mg;
+ $c =~ s!^\s+!!mg;
+
+ open OUT, ">$0" or die "cannot strip $0: $!\n";
+ print OUT "$pl$c\n";
+
+ exit 0;
+}
+
+__DATA__
+*/
+
+#ifndef _P_P_PORTABILITY_H_
+#define _P_P_PORTABILITY_H_
+
+#ifndef DPPP_NAMESPACE
+# define DPPP_NAMESPACE DPPP_
+#endif
+
+#define DPPP_CAT2(x,y) CAT2(x,y)
+#define DPPP_(name) DPPP_CAT2(DPPP_NAMESPACE, name)
+
+#define D_PPP_RELEASE_DATE 1625616000 /* 2021-07-07 */
+
+#if ! defined(PERL_REVISION) && ! defined(PERL_VERSION_MAJOR)
+# if ! defined(__PATCHLEVEL_H_INCLUDED__) \
+ && ! ( defined(PATCHLEVEL) && defined(SUBVERSION))
+# define PERL_PATCHLEVEL_H_IMPLICIT
+# include <patchlevel.h>
+# endif
+# if ! defined(PERL_VERSION) \
+ && ! defined(PERL_VERSION_MAJOR) \
+ && ( ! defined(SUBVERSION) || ! defined(PATCHLEVEL) )
+# include <could_not_find_Perl_patchlevel.h>
+# endif
+#endif
+
+#ifdef PERL_VERSION_MAJOR
+# define D_PPP_MAJOR PERL_VERSION_MAJOR
+#elif defined(PERL_REVISION)
+# define D_PPP_MAJOR PERL_REVISION
+#else
+# define D_PPP_MAJOR 5
+#endif
+
+#ifdef PERL_VERSION_MINOR
+# define D_PPP_MINOR PERL_VERSION_MINOR
+#elif defined(PERL_VERSION)
+# define D_PPP_MINOR PERL_VERSION
+#elif defined(PATCHLEVEL)
+# define D_PPP_MINOR PATCHLEVEL
+# define PERL_VERSION PATCHLEVEL /* back-compat */
+#else
+# error Could not find a source for PERL_VERSION_MINOR
+#endif
+
+#ifdef PERL_VERSION_PATCH
+# define D_PPP_PATCH PERL_VERSION_PATCH
+#elif defined(PERL_SUBVERSION)
+# define D_PPP_PATCH PERL_SUBVERSION
+#elif defined(SUBVERSION)
+# define D_PPP_PATCH SUBVERSION
+# define PERL_SUBVERSION SUBVERSION /* back-compat */
+#else
+# error Could not find a source for PERL_VERSION_PATCH
+#endif
+
+#if D_PPP_MAJOR < 5 || D_PPP_MAJOR == 6
+# error Devel::PPPort works only on Perl 5, Perl 7, ...
+#elif D_PPP_MAJOR != 5
+ /* Perl 7 and above: the old forms are deprecated, set up so that they
+ * assume Perl 5, and will make this look like 5.201.201.
+ *
+ * 201 is used so will be well above anything that would come from a 5
+ * series if we unexpectedly have to continue it, but still gives plenty of
+ * room, up to 255, of numbers that will fit into a byte in case there is
+ * something else unforeseen */
+# undef PERL_REVISION
+# undef PERL_VERSION
+# undef PERL_SUBVERSION
+# define D_PPP_REVISION 5
+# define D_PPP_VERSION 201
+# define D_PPP_SUBVERSION 201
+# if (defined(__clang__) /* _Pragma here doesn't work with gcc */ \
+ && ( (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) \
+ || defined(_STDC_C99) \
+ || defined(__c99)))
+# define D_PPP_STRINGIFY(x) #x
+# define D_PPP_deprecate(xyz) _Pragma(D_PPP_STRINGIFY(GCC warning(D_PPP_STRINGIFY(xyz) " is deprecated")))
+# define PERL_REVISION (D_PPP_REVISION D_PPP_deprecate(PERL_REVISION))
+# define PERL_VERSION (D_PPP_REVISION D_PPP_deprecate(PERL_VERSION))
+# define PERL_SUBVERSION (D_PPP_SUBVERSION D_PPP_deprecate(PERL_SUBVERSION))
+# else
+# define PERL_REVISION D_PPP_REVISION
+# define PERL_VERSION D_PPP_REVISION
+# define PERL_SUBVERSION D_PPP_SUBVERSION
+# endif
+#endif
+
+/* Warning: PERL_PATCHLEVEL PATCHLEVEL SUBVERSION PERL_REVISION PERL_VERSION
+ * PERL_SUBVERSION PERL_BCDVERSION
+ *
+ * You should be using PERL_VERSION_xy(maj,min,ptch) instead of this, where xy
+ * is one of EQ, NE, LE, GT, LT, GE
+ */
+
+/* Replace PERL_PATCHLEVEL with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+/* Replace PATCHLEVEL with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+/* Replace SUBVERSION with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+/* Replace PERL_REVISION with PERL_VERSION_xy(a,b,c) (where xy is EQ,LT,GT...) */
+/* Replace PERL_VERSION with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+/* Replace PERL_SUBVERSION with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+/* Replace PERL_BCDVERSION with PERL_VERSION_xy(5,a,b) (where xy is EQ,LT,GT...) */
+
+#define D_PPP_DEC2BCD(dec) ((((dec)/100)<<8)|((((dec)%100)/10)<<4)|((dec)%10))
+#define D_PPP_JNP_TO_BCD(j,n,p) ((D_PPP_DEC2BCD(j)<<24)|(D_PPP_DEC2BCD(n)<<12)|D_PPP_DEC2BCD(p))
+#define PERL_BCDVERSION D_PPP_JNP_TO_BCD(D_PPP_MAJOR, \
+ D_PPP_MINOR, \
+ D_PPP_PATCH)
+
+/* These differ from the versions outside D:P in using PERL_BCDVERSION instead
+ * of PERL_DECIMAL_VERSION. The formats printing in this module assume BCD, so
+ * always use it */
+#undef PERL_VERSION_EQ
+#undef PERL_VERSION_NE
+#undef PERL_VERSION_LT
+#undef PERL_VERSION_GE
+#undef PERL_VERSION_LE
+#undef PERL_VERSION_GT
+
+/* N.B. These don't work if the patch number is 42 or 92, as those are what '*'
+ * is in ASCII and EBCDIC respectively */
+#ifndef PERL_VERSION_EQ
+# define PERL_VERSION_EQ(j,n,p) \
+ (((p) == '*') ? ( (j) == D_PPP_VERSION_MAJOR \
+ && (n) == D_PPP_VERSION_MINOR) \
+ : (PERL_BCDVERSION == D_PPP_JNP_TO_BCD(j,n,p)))
+#endif
+
+#ifndef PERL_VERSION_NE
+# define PERL_VERSION_NE(j,n,p) (! PERL_VERSION_EQ(j,n,p))
+#endif
+#ifndef PERL_VERSION_LT
+# define PERL_VERSION_LT(j,n,p) /* p=='*' means _LT(j,n,0) */ \
+ (PERL_BCDVERSION < D_PPP_JNP_TO_BCD( (j), \
+ (n), \
+ (((p) == '*') ? 0 : (p))))
+#endif
+
+#ifndef PERL_VERSION_GE
+# define PERL_VERSION_GE(j,n,p) (! PERL_VERSION_LT(j,n,p))
+#endif
+#ifndef PERL_VERSION_LE
+# define PERL_VERSION_LE(j,n,p) /* p=='*' means _LT(j,n+1,0) */ \
+ (PERL_BCDVERSION < D_PPP_JNP_TO_BCD( (j), \
+ (((p) == '*') ? ((n)+1) : (n)), \
+ (((p) == '*') ? 0 : (p))))
+#endif
+
+#ifndef PERL_VERSION_GT
+# define PERL_VERSION_GT(j,n,p) (! PERL_VERSION_LE(j,n,p))
+#endif
+#ifndef dTHR
+# define dTHR dNOOP
+#endif
+#ifndef dTHX
+# define dTHX dNOOP
+#endif
+
+/* Hint: dTHX
+
+ For pre-5.6.0 thread compatibility, instead use dTHXR, available only through
+ ppport.h */
+#ifndef dTHXa
+# define dTHXa(x) dNOOP
+#endif
+#ifndef pTHX
+# define pTHX void
+#endif
+
+#ifndef pTHX_
+# define pTHX_
+#endif
+
+#ifndef aTHX
+# define aTHX
+#endif
+
+/* Hint: aTHX
+
+ For pre-5.6.0 thread compatibility, instead use aTHXR, available only through
+ ppport.h */
+#ifndef aTHX_
+# define aTHX_
+#endif
+
+/* Hint: aTHX_
+
+ For pre-5.6.0 thread compatibility, instead use aTHXR_, available only
+ through ppport.h */
+
+#if (PERL_BCDVERSION < 0x5006000)
+# ifdef USE_THREADS
+# define aTHXR thr
+# define aTHXR_ thr,
+# else
+# define aTHXR
+# define aTHXR_
+# endif
+# define dTHXR dTHR
+#else
+# define aTHXR aTHX
+# define aTHXR_ aTHX_
+# define dTHXR dTHX
+#endif
+#ifndef dTHXoa
+# define dTHXoa(x) dTHXa(x)
+#endif
+
+#ifdef I_LIMITS
+# include <limits.h>
+#endif
+
+#ifndef PERL_UCHAR_MIN
+# define PERL_UCHAR_MIN ((unsigned char)0)
+#endif
+
+#ifndef PERL_UCHAR_MAX
+# ifdef UCHAR_MAX
+# define PERL_UCHAR_MAX ((unsigned char)UCHAR_MAX)
+# else
+# ifdef MAXUCHAR
+# define PERL_UCHAR_MAX ((unsigned char)MAXUCHAR)
+# else
+# define PERL_UCHAR_MAX ((unsigned char)~(unsigned)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_USHORT_MIN
+# define PERL_USHORT_MIN ((unsigned short)0)
+#endif
+
+#ifndef PERL_USHORT_MAX
+# ifdef USHORT_MAX
+# define PERL_USHORT_MAX ((unsigned short)USHORT_MAX)
+# else
+# ifdef MAXUSHORT
+# define PERL_USHORT_MAX ((unsigned short)MAXUSHORT)
+# else
+# ifdef USHRT_MAX
+# define PERL_USHORT_MAX ((unsigned short)USHRT_MAX)
+# else
+# define PERL_USHORT_MAX ((unsigned short)~(unsigned)0)
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_SHORT_MAX
+# ifdef SHORT_MAX
+# define PERL_SHORT_MAX ((short)SHORT_MAX)
+# else
+# ifdef MAXSHORT /* Often used in <values.h> */
+# define PERL_SHORT_MAX ((short)MAXSHORT)
+# else
+# ifdef SHRT_MAX
+# define PERL_SHORT_MAX ((short)SHRT_MAX)
+# else
+# define PERL_SHORT_MAX ((short) (PERL_USHORT_MAX >> 1))
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_SHORT_MIN
+# ifdef SHORT_MIN
+# define PERL_SHORT_MIN ((short)SHORT_MIN)
+# else
+# ifdef MINSHORT
+# define PERL_SHORT_MIN ((short)MINSHORT)
+# else
+# ifdef SHRT_MIN
+# define PERL_SHORT_MIN ((short)SHRT_MIN)
+# else
+# define PERL_SHORT_MIN (-PERL_SHORT_MAX - ((3 & -1) == 3))
+# endif
+# endif
+# endif
+#endif
+
+#ifndef PERL_UINT_MAX
+# ifdef UINT_MAX
+# define PERL_UINT_MAX ((unsigned int)UINT_MAX)
+# else
+# ifdef MAXUINT
+# define PERL_UINT_MAX ((unsigned int)MAXUINT)
+# else
+# define PERL_UINT_MAX (~(unsigned int)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_UINT_MIN
+# define PERL_UINT_MIN ((unsigned int)0)
+#endif
+
+#ifndef PERL_INT_MAX
+# ifdef INT_MAX
+# define PERL_INT_MAX ((int)INT_MAX)
+# else
+# ifdef MAXINT /* Often used in <values.h> */
+# define PERL_INT_MAX ((int)MAXINT)
+# else
+# define PERL_INT_MAX ((int)(PERL_UINT_MAX >> 1))
+# endif
+# endif
+#endif
+
+#ifndef PERL_INT_MIN
+# ifdef INT_MIN
+# define PERL_INT_MIN ((int)INT_MIN)
+# else
+# ifdef MININT
+# define PERL_INT_MIN ((int)MININT)
+# else
+# define PERL_INT_MIN (-PERL_INT_MAX - ((3 & -1) == 3))
+# endif
+# endif
+#endif
+
+#ifndef PERL_ULONG_MAX
+# ifdef ULONG_MAX
+# define PERL_ULONG_MAX ((unsigned long)ULONG_MAX)
+# else
+# ifdef MAXULONG
+# define PERL_ULONG_MAX ((unsigned long)MAXULONG)
+# else
+# define PERL_ULONG_MAX (~(unsigned long)0)
+# endif
+# endif
+#endif
+
+#ifndef PERL_ULONG_MIN
+# define PERL_ULONG_MIN ((unsigned long)0L)
+#endif
+
+#ifndef PERL_LONG_MAX
+# ifdef LONG_MAX
+# define PERL_LONG_MAX ((long)LONG_MAX)
+# else
+# ifdef MAXLONG
+# define PERL_LONG_MAX ((long)MAXLONG)
+# else
+# define PERL_LONG_MAX ((long) (PERL_ULONG_MAX >> 1))
+# endif
+# endif
+#endif
+
+#ifndef PERL_LONG_MIN
+# ifdef LONG_MIN
+# define PERL_LONG_MIN ((long)LONG_MIN)
+# else
+# ifdef MINLONG
+# define PERL_LONG_MIN ((long)MINLONG)
+# else
+# define PERL_LONG_MIN (-PERL_LONG_MAX - ((3 & -1) == 3))
+# endif
+# endif
+#endif
+
+#if defined(HAS_QUAD) && (defined(convex) || defined(uts))
+# ifndef PERL_UQUAD_MAX
+# ifdef ULONGLONG_MAX
+# define PERL_UQUAD_MAX ((unsigned long long)ULONGLONG_MAX)
+# else
+# ifdef MAXULONGLONG
+# define PERL_UQUAD_MAX ((unsigned long long)MAXULONGLONG)
+# else
+# define PERL_UQUAD_MAX (~(unsigned long long)0)
+# endif
+# endif
+# endif
+
+# ifndef PERL_UQUAD_MIN
+# define PERL_UQUAD_MIN ((unsigned long long)0L)
+# endif
+
+# ifndef PERL_QUAD_MAX
+# ifdef LONGLONG_MAX
+# define PERL_QUAD_MAX ((long long)LONGLONG_MAX)
+# else
+# ifdef MAXLONGLONG
+# define PERL_QUAD_MAX ((long long)MAXLONGLONG)
+# else
+# define PERL_QUAD_MAX ((long long) (PERL_UQUAD_MAX >> 1))
+# endif
+# endif
+# endif
+
+# ifndef PERL_QUAD_MIN
+# ifdef LONGLONG_MIN
+# define PERL_QUAD_MIN ((long long)LONGLONG_MIN)
+# else
+# ifdef MINLONGLONG
+# define PERL_QUAD_MIN ((long long)MINLONGLONG)
+# else
+# define PERL_QUAD_MIN (-PERL_QUAD_MAX - ((3 & -1) == 3))
+# endif
+# endif
+# endif
+#endif
+
+/* This is based on code from 5.003 perl.h */
+#ifdef HAS_QUAD
+# ifdef cray
+#ifndef IVTYPE
+# define IVTYPE int
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_INT_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_INT_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_UINT_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_UINT_MAX
+#endif
+
+# ifdef INTSIZE
+#ifndef IVSIZE
+# define IVSIZE INTSIZE
+#endif
+
+# endif
+# else
+# if defined(convex) || defined(uts)
+#ifndef IVTYPE
+# define IVTYPE long long
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_QUAD_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_QUAD_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_UQUAD_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_UQUAD_MAX
+#endif
+
+# ifdef LONGLONGSIZE
+#ifndef IVSIZE
+# define IVSIZE LONGLONGSIZE
+#endif
+
+# endif
+# else
+#ifndef IVTYPE
+# define IVTYPE long
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_ULONG_MAX
+#endif
+
+# ifdef LONGSIZE
+#ifndef IVSIZE
+# define IVSIZE LONGSIZE
+#endif
+
+# endif
+# endif
+# endif
+#ifndef IVSIZE
+# define IVSIZE 8
+#endif
+
+#ifndef LONGSIZE
+# define LONGSIZE 8
+#endif
+
+#ifndef PERL_QUAD_MIN
+# define PERL_QUAD_MIN IV_MIN
+#endif
+
+#ifndef PERL_QUAD_MAX
+# define PERL_QUAD_MAX IV_MAX
+#endif
+
+#ifndef PERL_UQUAD_MIN
+# define PERL_UQUAD_MIN UV_MIN
+#endif
+
+#ifndef PERL_UQUAD_MAX
+# define PERL_UQUAD_MAX UV_MAX
+#endif
+
+#else
+#ifndef IVTYPE
+# define IVTYPE long
+#endif
+
+#ifndef LONGSIZE
+# define LONGSIZE 4
+#endif
+
+#ifndef IV_MIN
+# define IV_MIN PERL_LONG_MIN
+#endif
+
+#ifndef IV_MAX
+# define IV_MAX PERL_LONG_MAX
+#endif
+
+#ifndef UV_MIN
+# define UV_MIN PERL_ULONG_MIN
+#endif
+
+#ifndef UV_MAX
+# define UV_MAX PERL_ULONG_MAX
+#endif
+
+#endif
+
+#ifndef IVSIZE
+# ifdef LONGSIZE
+# define IVSIZE LONGSIZE
+# else
+# define IVSIZE 4 /* A bold guess, but the best we can make. */
+# endif
+#endif
+#ifndef UVTYPE
+# define UVTYPE unsigned IVTYPE
+#endif
+
+#ifndef UVSIZE
+# define UVSIZE IVSIZE
+#endif
+
+#ifndef PERL_SIGNALS_UNSAFE_FLAG
+
+#define PERL_SIGNALS_UNSAFE_FLAG 0x0001
+
+#if (PERL_BCDVERSION < 0x5008000)
+# define D_PPP_PERL_SIGNALS_INIT PERL_SIGNALS_UNSAFE_FLAG
+#else
+# define D_PPP_PERL_SIGNALS_INIT 0
+#endif
+
+#if defined(NEED_PL_signals)
+static U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#elif defined(NEED_PL_signals_GLOBAL)
+U32 DPPP_(my_PL_signals) = D_PPP_PERL_SIGNALS_INIT;
+#else
+extern U32 DPPP_(my_PL_signals);
+#endif
+#define PL_signals DPPP_(my_PL_signals)
+
+#endif
+
+/* Hint: PL_ppaddr
+ * Calling an op via PL_ppaddr requires passing a context argument
+ * for threaded builds. Since the context argument is different for
+ * 5.005 perls, you can use aTHXR (supplied by ppport.h), which will
+ * automatically be defined as the correct argument.
+ */
+
+#if (PERL_BCDVERSION <= 0x5005005)
+/* Replace: 1 */
+# define PL_ppaddr ppaddr
+# define PL_no_modify no_modify
+/* Replace: 0 */
+#endif
+
+#if (PERL_BCDVERSION <= 0x5004005)
+/* Replace: 1 */
+# define PL_DBsignal DBsignal
+# define PL_DBsingle DBsingle
+# define PL_DBsub DBsub
+# define PL_DBtrace DBtrace
+# define PL_Sv Sv
+# define PL_Xpv Xpv
+# define PL_bufend bufend
+# define PL_bufptr bufptr
+# define PL_compiling compiling
+# define PL_copline copline
+# define PL_curcop curcop
+# define PL_curstash curstash
+# define PL_debstash debstash
+# define PL_defgv defgv
+# define PL_diehook diehook
+# define PL_dirty dirty
+# define PL_dowarn dowarn
+# define PL_errgv errgv
+# define PL_error_count error_count
+# define PL_expect expect
+# define PL_hexdigit hexdigit
+# define PL_hints hints
+# define PL_in_my in_my
+# define PL_laststatval laststatval
+# define PL_lex_state lex_state
+# define PL_lex_stuff lex_stuff
+# define PL_linestr linestr
+# define PL_na na
+# define PL_perl_destruct_level perl_destruct_level
+# define PL_perldb perldb
+# define PL_rsfp_filters rsfp_filters
+# define PL_rsfp rsfp
+# define PL_stack_base stack_base
+# define PL_stack_sp stack_sp
+# define PL_statcache statcache
+# define PL_stdingv stdingv
+# define PL_sv_arenaroot sv_arenaroot
+# define PL_sv_no sv_no
+# define PL_sv_undef sv_undef
+# define PL_sv_yes sv_yes
+# define PL_tainted tainted
+# define PL_tainting tainting
+# define PL_tokenbuf tokenbuf
+# define PL_mess_sv mess_sv
+/* Replace: 0 */
+#endif
+
+/* Warning: PL_parser
+ * For perl versions earlier than 5.9.5, this is an always
+ * non-NULL dummy. Also, it cannot be dereferenced. Don't
+ * use it if you can avoid it, and unless you absolutely know
+ * what you're doing.
+ * If you always check that PL_parser is non-NULL, you can
+ * define DPPP_PL_parser_NO_DUMMY to avoid the creation of
+ * a dummy parser structure.
+ */
+
+#if (PERL_BCDVERSION >= 0x5009005)
+# ifdef DPPP_PL_parser_NO_DUMMY
+# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+ (croak("panic: PL_parser == NULL in %s:%d", \
+ __FILE__, __LINE__), (yy_parser *) NULL))->var)
+# else
+# ifdef DPPP_PL_parser_NO_DUMMY_WARNING
+# define D_PPP_parser_dummy_warning(var)
+# else
+# define D_PPP_parser_dummy_warning(var) \
+ warn("warning: dummy PL_" #var " used in %s:%d", __FILE__, __LINE__),
+# endif
+# define D_PPP_my_PL_parser_var(var) ((PL_parser ? PL_parser : \
+ (D_PPP_parser_dummy_warning(var) &DPPP_(dummy_PL_parser)))->var)
+#if defined(NEED_PL_parser)
+static yy_parser DPPP_(dummy_PL_parser);
+#elif defined(NEED_PL_parser_GLOBAL)
+yy_parser DPPP_(dummy_PL_parser);
+#else
+extern yy_parser DPPP_(dummy_PL_parser);
+#endif
+
+# endif
+
+/* PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf depends on PL_parser */
+/* Warning: PL_expect, PL_copline, PL_rsfp, PL_rsfp_filters, PL_linestr, PL_bufptr, PL_bufend, PL_lex_state, PL_lex_stuff, PL_tokenbuf
+ * Do not use this variable unless you know exactly what you're
+ * doing. It is internal to the perl parser and may change or even
+ * be removed in the future. As of perl 5.9.5, you have to check
+ * for (PL_parser != NULL) for this variable to have any effect.
+ * An always non-NULL PL_parser dummy is provided for earlier
+ * perl versions.
+ * If PL_parser is NULL when you try to access this variable, a
+ * dummy is being accessed instead and a warning is issued unless
+ * you define DPPP_PL_parser_NO_DUMMY_WARNING.
+ * If DPPP_PL_parser_NO_DUMMY is defined, the code trying to access
+ * this variable will croak with a panic message.
+ */
+
+# define PL_expect D_PPP_my_PL_parser_var(expect)
+# define PL_copline D_PPP_my_PL_parser_var(copline)
+# define PL_rsfp D_PPP_my_PL_parser_var(rsfp)
+# define PL_rsfp_filters D_PPP_my_PL_parser_var(rsfp_filters)
+# define PL_linestr D_PPP_my_PL_parser_var(linestr)
+# define PL_bufptr D_PPP_my_PL_parser_var(bufptr)
+# define PL_bufend D_PPP_my_PL_parser_var(bufend)
+# define PL_lex_state D_PPP_my_PL_parser_var(lex_state)
+# define PL_lex_stuff D_PPP_my_PL_parser_var(lex_stuff)
+# define PL_tokenbuf D_PPP_my_PL_parser_var(tokenbuf)
+# define PL_in_my D_PPP_my_PL_parser_var(in_my)
+# define PL_in_my_stash D_PPP_my_PL_parser_var(in_my_stash)
+# define PL_error_count D_PPP_my_PL_parser_var(error_count)
+
+
+#else
+
+/* ensure that PL_parser != NULL and cannot be dereferenced */
+# define PL_parser ((void *) 1)
+
+#endif
+
+#if (PERL_BCDVERSION <= 0x5003022)
+# undef start_subparse
+# if (PERL_BCDVERSION < 0x5003022)
+#ifndef start_subparse
+# define start_subparse(a, b) Perl_start_subparse()
+#endif
+
+# else
+#ifndef start_subparse
+# define start_subparse(a, b) Perl_start_subparse(b)
+#endif
+
+# endif
+
+#if (PERL_BCDVERSION < 0x5003007)
+foo
+#endif
+#endif
+
+/* newCONSTSUB from IO.xs is in the core starting with 5.004_63 */
+#if (PERL_BCDVERSION < 0x5004063) && (PERL_BCDVERSION != 0x5004005)
+
+/* And before that, we need to make sure this gets compiled for the functions
+ * that rely on it */
+#define NEED_newCONSTSUB
+
+#if defined(NEED_newCONSTSUB)
+static CV * DPPP_(my_newCONSTSUB)(HV * stash, const char * name, SV * sv);
+static
+#else
+extern CV * DPPP_(my_newCONSTSUB)(HV * stash, const char * name, SV * sv);
+#endif
+
+#if defined(NEED_newCONSTSUB) || defined(NEED_newCONSTSUB_GLOBAL)
+
+#ifdef newCONSTSUB
+# undef newCONSTSUB
+#endif
+#define newCONSTSUB(a,b,c) DPPP_(my_newCONSTSUB)(aTHX_ a,b,c)
+#define Perl_newCONSTSUB DPPP_(my_newCONSTSUB)
+
+
+/* This is just a trick to avoid a dependency of newCONSTSUB on PL_parser */
+/* (There's no PL_parser in perl < 5.005, so this is completely safe) */
+#define D_PPP_PL_copline PL_copline
+
+CV *
+DPPP_(my_newCONSTSUB)(HV *stash, const char *name, SV *sv)
+{
+ CV *cv;
+ U32 oldhints = PL_hints;
+ HV *old_cop_stash = PL_curcop->cop_stash;
+ HV *old_curstash = PL_curstash;
+ line_t oldline = PL_curcop->cop_line;
+ PL_curcop->cop_line = D_PPP_PL_copline;
+
+ PL_hints &= ~HINT_BLOCK_SCOPE;
+ if (stash)
+ PL_curstash = PL_curcop->cop_stash = stash;
+
+ cv = newSUB(
+
+ start_subparse(FALSE, 0),
+
+ newSVOP(OP_CONST, 0, newSVpv((char *) name, 0)),
+ newSVOP(OP_CONST, 0, &PL_sv_no), /* SvPV(&PL_sv_no) == "" -- GMB */
+ newSTATEOP(0, Nullch, newSVOP(OP_CONST, 0, sv))
+ );
+
+ PL_hints = oldhints;
+ PL_curcop->cop_stash = old_cop_stash;
+ PL_curstash = old_curstash;
+ PL_curcop->cop_line = oldline;
+
+ return cv;
+}
+#endif
+#endif
+#ifndef PERL_MAGIC_sv
+# define PERL_MAGIC_sv '\0'
+#endif
+
+#ifndef PERL_MAGIC_overload
+# define PERL_MAGIC_overload 'A'
+#endif
+
+#ifndef PERL_MAGIC_overload_elem
+# define PERL_MAGIC_overload_elem 'a'
+#endif
+
+#ifndef PERL_MAGIC_overload_table
+# define PERL_MAGIC_overload_table 'c'
+#endif
+
+#ifndef PERL_MAGIC_bm
+# define PERL_MAGIC_bm 'B'
+#endif
+
+#ifndef PERL_MAGIC_regdata
+# define PERL_MAGIC_regdata 'D'
+#endif
+
+#ifndef PERL_MAGIC_regdatum
+# define PERL_MAGIC_regdatum 'd'
+#endif
+
+#ifndef PERL_MAGIC_env
+# define PERL_MAGIC_env 'E'
+#endif
+
+#ifndef PERL_MAGIC_envelem
+# define PERL_MAGIC_envelem 'e'
+#endif
+
+#ifndef PERL_MAGIC_fm
+# define PERL_MAGIC_fm 'f'
+#endif
+
+#ifndef PERL_MAGIC_regex_global
+# define PERL_MAGIC_regex_global 'g'
+#endif
+
+#ifndef PERL_MAGIC_isa
+# define PERL_MAGIC_isa 'I'
+#endif
+
+#ifndef PERL_MAGIC_isaelem
+# define PERL_MAGIC_isaelem 'i'
+#endif
+
+#ifndef PERL_MAGIC_nkeys
+# define PERL_MAGIC_nkeys 'k'
+#endif
+
+#ifndef PERL_MAGIC_dbfile
+# define PERL_MAGIC_dbfile 'L'
+#endif
+
+#ifndef PERL_MAGIC_dbline
+# define PERL_MAGIC_dbline 'l'
+#endif
+
+#ifndef PERL_MAGIC_mutex
+# define PERL_MAGIC_mutex 'm'
+#endif
+
+#ifndef PERL_MAGIC_shared
+# define PERL_MAGIC_shared 'N'
+#endif
+
+#ifndef PERL_MAGIC_shared_scalar
+# define PERL_MAGIC_shared_scalar 'n'
+#endif
+
+#ifndef PERL_MAGIC_collxfrm
+# define PERL_MAGIC_collxfrm 'o'
+#endif
+
+#ifndef PERL_MAGIC_tied
+# define PERL_MAGIC_tied 'P'
+#endif
+
+#ifndef PERL_MAGIC_tiedelem
+# define PERL_MAGIC_tiedelem 'p'
+#endif
+
+#ifndef PERL_MAGIC_tiedscalar
+# define PERL_MAGIC_tiedscalar 'q'
+#endif
+
+#ifndef PERL_MAGIC_qr
+# define PERL_MAGIC_qr 'r'
+#endif
+
+#ifndef PERL_MAGIC_sig
+# define PERL_MAGIC_sig 'S'
+#endif
+
+#ifndef PERL_MAGIC_sigelem
+# define PERL_MAGIC_sigelem 's'
+#endif
+
+#ifndef PERL_MAGIC_taint
+# define PERL_MAGIC_taint 't'
+#endif
+
+#ifndef PERL_MAGIC_uvar
+# define PERL_MAGIC_uvar 'U'
+#endif
+
+#ifndef PERL_MAGIC_uvar_elem
+# define PERL_MAGIC_uvar_elem 'u'
+#endif
+
+#ifndef PERL_MAGIC_vstring
+# define PERL_MAGIC_vstring 'V'
+#endif
+
+#ifndef PERL_MAGIC_vec
+# define PERL_MAGIC_vec 'v'
+#endif
+
+#ifndef PERL_MAGIC_utf8
+# define PERL_MAGIC_utf8 'w'
+#endif
+
+#ifndef PERL_MAGIC_substr
+# define PERL_MAGIC_substr 'x'
+#endif
+
+#ifndef PERL_MAGIC_defelem
+# define PERL_MAGIC_defelem 'y'
+#endif
+
+#ifndef PERL_MAGIC_glob
+# define PERL_MAGIC_glob '*'
+#endif
+
+#ifndef PERL_MAGIC_arylen
+# define PERL_MAGIC_arylen '#'
+#endif
+
+#ifndef PERL_MAGIC_pos
+# define PERL_MAGIC_pos '.'
+#endif
+
+#ifndef PERL_MAGIC_backref
+# define PERL_MAGIC_backref '<'
+#endif
+
+#ifndef PERL_MAGIC_ext
+# define PERL_MAGIC_ext '~'
+#endif
+#ifndef cBOOL
+# define cBOOL(cbool) ((cbool) ? (bool)1 : (bool)0)
+#endif
+
+#ifndef OpHAS_SIBLING
+# define OpHAS_SIBLING(o) (cBOOL((o)->op_sibling))
+#endif
+
+#ifndef OpSIBLING
+# define OpSIBLING(o) (0 + (o)->op_sibling)
+#endif
+
+#ifndef OpMORESIB_set
+# define OpMORESIB_set(o, sib) ((o)->op_sibling = (sib))
+#endif
+
+#ifndef OpLASTSIB_set
+# define OpLASTSIB_set(o, parent) ((o)->op_sibling = NULL)
+#endif
+
+#ifndef OpMAYBESIB_set
+# define OpMAYBESIB_set(o, sib, parent) ((o)->op_sibling = (sib))
+#endif
+
+#ifndef HEf_SVKEY
+# define HEf_SVKEY -2
+#endif
+
+#if defined(DEBUGGING) && !defined(__COVERITY__)
+#ifndef __ASSERT_
+# define __ASSERT_(statement) assert(statement),
+#endif
+
+#else
+#ifndef __ASSERT_
+# define __ASSERT_(statement)
+#endif
+
+#endif
+#ifndef __has_builtin
+# define __has_builtin(x) 0
+#endif
+
+#if __has_builtin(__builtin_unreachable)
+# define D_PPP_HAS_BUILTIN_UNREACHABLE
+#elif (defined(__GNUC__) && ( __GNUC__ > 4 \
+ || __GNUC__ == 4 && __GNUC_MINOR__ >= 5))
+# define D_PPP_HAS_BUILTIN_UNREACHABLE
+#endif
+
+#ifndef ASSUME
+# ifdef DEBUGGING
+# define ASSUME(x) assert(x)
+# elif defined(_MSC_VER)
+# define ASSUME(x) __assume(x)
+# elif defined(__ARMCC_VERSION)
+# define ASSUME(x) __promise(x)
+# elif defined(D_PPP_HAS_BUILTIN_UNREACHABLE)
+# define ASSUME(x) ((x) ? (void) 0 : __builtin_unreachable())
+# else
+# define ASSUME(x) assert(x)
+# endif
+#endif
+
+#ifndef NOT_REACHED
+# ifdef D_PPP_HAS_BUILTIN_UNREACHABLE
+# define NOT_REACHED \
+ STMT_START { \
+ ASSUME(!"UNREACHABLE"); __builtin_unreachable(); \
+ } STMT_END
+# elif ! defined(__GNUC__) && (defined(__sun) || defined(__hpux))
+# define NOT_REACHED
+# else
+# define NOT_REACHED ASSUME(!"UNREACHABLE")
+# endif
+#endif
+
+#ifndef WIDEST_UTYPE
+# ifdef QUADKIND
+# ifdef U64TYPE
+# define WIDEST_UTYPE U64TYPE
+# else
+# define WIDEST_UTYPE unsigned Quad_t
+# endif
+# else
+# define WIDEST_UTYPE U32
+# endif
+#endif
+
+/* These could become provided if/when they become part of the public API */
+#ifndef withinCOUNT
+# define withinCOUNT(c, l, n) \
+ (((WIDEST_UTYPE) (((c)) - ((l) | 0))) <= (((WIDEST_UTYPE) ((n) | 0))))
+#endif
+
+#ifndef inRANGE
+# define inRANGE(c, l, u) \
+ ( (sizeof(c) == sizeof(U8)) ? withinCOUNT(((U8) (c)), (l), ((u) - (l))) \
+ : (sizeof(c) == sizeof(U32)) ? withinCOUNT(((U32) (c)), (l), ((u) - (l))) \
+ : (withinCOUNT(((WIDEST_UTYPE) (c)), (l), ((u) - (l)))))
+#endif
+
+/* The '| 0' part ensures a compiler error if c is not integer (like e.g., a
+ * pointer) */
+#undef FITS_IN_8_BITS /* handy.h version uses a core-only constant */
+#ifndef FITS_IN_8_BITS
+# define FITS_IN_8_BITS(c) ( (sizeof(c) == 1) \
+ || !(((WIDEST_UTYPE)((c) | 0)) & ~0xFF))
+#endif
+
+/* Create the macro for "is'macro'_utf8_safe(s, e)". For code points below
+ * 256, it calls the equivalent _L1 macro by converting the UTF-8 to code
+ * point. That is so that it can automatically get the bug fixes done in this
+ * file. */
+#define D_PPP_IS_GENERIC_UTF8_SAFE(s, e, macro) \
+ (((e) - (s)) <= 0 \
+ ? 0 \
+ : UTF8_IS_INVARIANT((s)[0]) \
+ ? is ## macro ## _L1((s)[0]) \
+ : (((e) - (s)) < UTF8SKIP(s)) \
+ ? 0 \
+ : UTF8_IS_DOWNGRADEABLE_START((s)[0]) \
+ /* The cast in the line below is only to silence warnings */ \
+ ? is ## macro ## _L1((WIDEST_UTYPE) LATIN1_TO_NATIVE( \
+ UTF8_ACCUMULATE(NATIVE_UTF8_TO_I8((s)[0]) \
+ & UTF_START_MASK(2), \
+ (s)[1]))) \
+ : is ## macro ## _utf8(s))
+
+/* Create the macro for "is'macro'_LC_utf8_safe(s, e)". For code points below
+ * 256, it calls the equivalent _L1 macro by converting the UTF-8 to code
+ * point. That is so that it can automatically get the bug fixes done in this
+ * file. */
+#define D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, macro) \
+ (((e) - (s)) <= 0 \
+ ? 0 \
+ : UTF8_IS_INVARIANT((s)[0]) \
+ ? is ## macro ## _LC((s)[0]) \
+ : (((e) - (s)) < UTF8SKIP(s)) \
+ ? 0 \
+ : UTF8_IS_DOWNGRADEABLE_START((s)[0]) \
+ /* The cast in the line below is only to silence warnings */ \
+ ? is ## macro ## _LC((WIDEST_UTYPE) LATIN1_TO_NATIVE( \
+ UTF8_ACCUMULATE(NATIVE_UTF8_TO_I8((s)[0]) \
+ & UTF_START_MASK(2), \
+ (s)[1]))) \
+ : is ## macro ## _utf8(s))
+
+/* A few of the early functions are broken. For these and the non-LC case,
+ * machine generated code is substituted. But that code doesn't work for
+ * locales. This is just like the above macro, but at the end, we call the
+ * macro we've generated for the above 255 case, which is correct since locale
+ * isn't involved. This will generate extra code to handle the 0-255 inputs,
+ * but hopefully it will be optimized out by the C compiler. But just in case
+ * it isn't, this macro is only used on the few versions that are broken */
+
+#define D_PPP_IS_GENERIC_LC_UTF8_SAFE_BROKEN(s, e, macro) \
+ (((e) - (s)) <= 0 \
+ ? 0 \
+ : UTF8_IS_INVARIANT((s)[0]) \
+ ? is ## macro ## _LC((s)[0]) \
+ : (((e) - (s)) < UTF8SKIP(s)) \
+ ? 0 \
+ : UTF8_IS_DOWNGRADEABLE_START((s)[0]) \
+ /* The cast in the line below is only to silence warnings */ \
+ ? is ## macro ## _LC((WIDEST_UTYPE) LATIN1_TO_NATIVE( \
+ UTF8_ACCUMULATE(NATIVE_UTF8_TO_I8((s)[0]) \
+ & UTF_START_MASK(2), \
+ (s)[1]))) \
+ : is ## macro ## _utf8_safe(s, e))
+#ifndef SvRX
+# define SvRX(rv) (SvROK((rv)) ? (SvMAGICAL(SvRV((rv))) ? (mg_find(SvRV((rv)), PERL_MAGIC_qr) ? mg_find(SvRV((rv)), PERL_MAGIC_qr)->mg_obj : NULL) : NULL) : NULL)
+#endif
+
+#ifndef SvRXOK
+# define SvRXOK(sv) (!!SvRX(sv))
+#endif
+
+#ifndef PERL_UNUSED_DECL
+# ifdef HASATTRIBUTE
+# if (defined(__GNUC__) && defined(__cplusplus)) || defined(__INTEL_COMPILER)
+# define PERL_UNUSED_DECL
+# else
+# define PERL_UNUSED_DECL __attribute__((unused))
+# endif
+# else
+# define PERL_UNUSED_DECL
+# endif
+#endif
+
+#ifndef PERL_UNUSED_ARG
+# if defined(lint) && defined(S_SPLINT_S) /* www.splint.org */
+# include <note.h>
+# define PERL_UNUSED_ARG(x) NOTE(ARGUNUSED(x))
+# else
+# define PERL_UNUSED_ARG(x) ((void)x)
+# endif
+#endif
+
+#ifndef PERL_UNUSED_VAR
+# define PERL_UNUSED_VAR(x) ((void)x)
+#endif
+
+#ifndef PERL_UNUSED_CONTEXT
+# ifdef USE_ITHREADS
+# define PERL_UNUSED_CONTEXT PERL_UNUSED_ARG(my_perl)
+# else
+# define PERL_UNUSED_CONTEXT
+# endif
+#endif
+
+#ifndef PERL_UNUSED_RESULT
+# if defined(__GNUC__) && defined(HASATTRIBUTE_WARN_UNUSED_RESULT)
+# define PERL_UNUSED_RESULT(v) STMT_START { __typeof__(v) z = (v); (void)sizeof(z); } STMT_END
+# else
+# define PERL_UNUSED_RESULT(v) ((void)(v))
+# endif
+#endif
+#ifndef NOOP
+# define NOOP /*EMPTY*/(void)0
+#endif
+
+#ifndef dNOOP
+# define dNOOP extern int /*@unused@*/ Perl___notused PERL_UNUSED_DECL
+#endif
+
+#ifndef NVTYPE
+# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE)
+# define NVTYPE long double
+# else
+# define NVTYPE double
+# endif
+typedef NVTYPE NV;
+#endif
+
+#ifndef INT2PTR
+# if (IVSIZE == PTRSIZE) && (UVSIZE == PTRSIZE)
+# define PTRV UV
+# define INT2PTR(any,d) (any)(d)
+# else
+# if PTRSIZE == LONGSIZE
+# define PTRV unsigned long
+# else
+# define PTRV unsigned
+# endif
+# define INT2PTR(any,d) (any)(PTRV)(d)
+# endif
+#endif
+
+#ifndef PTR2ul
+# if PTRSIZE == LONGSIZE
+# define PTR2ul(p) (unsigned long)(p)
+# else
+# define PTR2ul(p) INT2PTR(unsigned long,p)
+# endif
+#endif
+#ifndef PTR2nat
+# define PTR2nat(p) (PTRV)(p)
+#endif
+
+#ifndef NUM2PTR
+# define NUM2PTR(any,d) (any)PTR2nat(d)
+#endif
+
+#ifndef PTR2IV
+# define PTR2IV(p) INT2PTR(IV,p)
+#endif
+
+#ifndef PTR2UV
+# define PTR2UV(p) INT2PTR(UV,p)
+#endif
+
+#ifndef PTR2NV
+# define PTR2NV(p) NUM2PTR(NV,p)
+#endif
+
+#undef START_EXTERN_C
+#undef END_EXTERN_C
+#undef EXTERN_C
+#ifdef __cplusplus
+# define START_EXTERN_C extern "C" {
+# define END_EXTERN_C }
+# define EXTERN_C extern "C"
+#else
+# define START_EXTERN_C
+# define END_EXTERN_C
+# define EXTERN_C extern
+#endif
+
+#if (PERL_BCDVERSION < 0x5004000) || defined(PERL_GCC_PEDANTIC)
+# ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN
+#ifndef PERL_GCC_BRACE_GROUPS_FORBIDDEN
+# define PERL_GCC_BRACE_GROUPS_FORBIDDEN
+#endif
+
+# endif
+#endif
+
+#if defined(__GNUC__) && !defined(PERL_GCC_BRACE_GROUPS_FORBIDDEN) && !defined(__cplusplus)
+# ifndef PERL_USE_GCC_BRACE_GROUPS
+# define PERL_USE_GCC_BRACE_GROUPS
+# endif
+#endif
+
+#undef STMT_START
+#undef STMT_END
+#ifdef PERL_USE_GCC_BRACE_GROUPS
+# define STMT_START (void)( /* gcc supports ``({ STATEMENTS; })'' */
+# define STMT_END )
+#else
+# if defined(VOIDFLAGS) && (VOIDFLAGS) && (defined(sun) || defined(__sun__)) && !defined(__GNUC__)
+# define STMT_START if (1)
+# define STMT_END else (void)0
+# else
+# define STMT_START do
+# define STMT_END while (0)
+# endif
+#endif
+#ifndef boolSV
+# define boolSV(b) ((b) ? &PL_sv_yes : &PL_sv_no)
+#endif
+
+/* DEFSV appears first in 5.004_56 */
+#ifndef DEFSV
+# define DEFSV GvSV(PL_defgv)
+#endif
+
+#ifndef SAVE_DEFSV
+# define SAVE_DEFSV SAVESPTR(GvSV(PL_defgv))
+#endif
+
+#ifndef DEFSV_set
+# define DEFSV_set(sv) (DEFSV = (sv))
+#endif
+
+/* Older perls (<=5.003) lack AvFILLp */
+#ifndef AvFILLp
+# define AvFILLp AvFILL
+#endif
+#ifndef av_tindex
+# define av_tindex AvFILL
+#endif
+
+#ifndef av_top_index
+# define av_top_index AvFILL
+#endif
+
+#ifndef av_count
+# define av_count(av) (AvFILL(av)+1)
+#endif
+#ifndef ERRSV
+# define ERRSV get_sv("@",FALSE)
+#endif
+
+/* Hint: gv_stashpvn
+ * This function's backport doesn't support the length parameter, but
+ * rather ignores it. Portability can only be ensured if the length
+ * parameter is used for speed reasons, but the length can always be
+ * correctly computed from the string argument.
+ */
+#ifndef gv_stashpvn
+# define gv_stashpvn(str,len,create) gv_stashpv(str,create)
+#endif
+
+/* Replace: 1 */
+#ifndef get_cv
+# define get_cv perl_get_cv
+#endif
+
+#ifndef get_sv
+# define get_sv perl_get_sv
+#endif
+
+#ifndef get_av
+# define get_av perl_get_av
+#endif
+
+#ifndef get_hv
+# define get_hv perl_get_hv
+#endif
+
+/* Replace: 0 */
+#ifndef dUNDERBAR
+# define dUNDERBAR dNOOP
+#endif
+
+#ifndef UNDERBAR
+# define UNDERBAR DEFSV
+#endif
+#ifndef dAX
+# define dAX I32 ax = MARK - PL_stack_base + 1
+#endif
+
+#ifndef dITEMS
+# define dITEMS I32 items = SP - MARK
+#endif
+#ifndef dXSTARG
+# define dXSTARG SV * targ = sv_newmortal()
+#endif
+#ifndef dAXMARK
+# define dAXMARK I32 ax = POPMARK; \
+ SV ** const mark = PL_stack_base + ax++
+#endif
+#ifndef XSprePUSH
+# define XSprePUSH (sp = PL_stack_base + ax - 1)
+#endif
+
+#if (PERL_BCDVERSION < 0x5005000)
+# undef XSRETURN
+# define XSRETURN(off) \
+ STMT_START { \
+ PL_stack_sp = PL_stack_base + ax + ((off) - 1); \
+ return; \
+ } STMT_END
+#endif
+#ifndef XSPROTO
+# define XSPROTO(name) void name(pTHX_ CV* cv)
+#endif
+
+#ifndef SVfARG
+# define SVfARG(p) ((void*)(p))
+#endif
+#ifndef PERL_ABS
+# define PERL_ABS(x) ((x) < 0 ? -(x) : (x))
+#endif
+#ifndef dVAR
+# define dVAR dNOOP
+#endif
+#ifndef SVf
+# define SVf "_"
+#endif
+#ifndef CPERLscope
+# define CPERLscope(x) x
+#endif
+#ifndef PERL_HASH
+# define PERL_HASH(hash,str,len) \
+ STMT_START { \
+ const char *s_PeRlHaSh = str; \
+ I32 i_PeRlHaSh = len; \
+ U32 hash_PeRlHaSh = 0; \
+ while (i_PeRlHaSh--) \
+ hash_PeRlHaSh = hash_PeRlHaSh * 33 + *s_PeRlHaSh++; \
+ (hash) = hash_PeRlHaSh; \
+ } STMT_END
+#endif
+
+#ifndef PERLIO_FUNCS_DECL
+# ifdef PERLIO_FUNCS_CONST
+# define PERLIO_FUNCS_DECL(funcs) const PerlIO_funcs funcs
+# define PERLIO_FUNCS_CAST(funcs) (PerlIO_funcs*)(funcs)
+# else
+# define PERLIO_FUNCS_DECL(funcs) PerlIO_funcs funcs
+# define PERLIO_FUNCS_CAST(funcs) (funcs)
+# endif
+#endif
+
+/* provide these typedefs for older perls */
+#if (PERL_BCDVERSION < 0x5009003)
+
+# ifdef ARGSproto
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(ARGSproto);
+# else
+typedef OP* (CPERLscope(*Perl_ppaddr_t))(pTHX);
+# endif
+
+typedef OP* (CPERLscope(*Perl_check_t)) (pTHX_ OP*);
+
+#endif
+
+/* On versions without NATIVE_TO_ASCII, only ASCII is supported */
+#if defined(EBCDIC) && defined(NATIVE_TO_ASCI)
+#ifndef NATIVE_TO_LATIN1
+# define NATIVE_TO_LATIN1(c) NATIVE_TO_ASCII(c)
+#endif
+
+#ifndef LATIN1_TO_NATIVE
+# define LATIN1_TO_NATIVE(c) ASCII_TO_NATIVE(c)
+#endif
+
+#ifndef NATIVE_TO_UNI
+# define NATIVE_TO_UNI(c) ((c) > 255 ? (c) : NATIVE_TO_LATIN1(c))
+#endif
+
+#ifndef UNI_TO_NATIVE
+# define UNI_TO_NATIVE(c) ((c) > 255 ? (c) : LATIN1_TO_NATIVE(c))
+#endif
+
+#else
+#ifndef NATIVE_TO_LATIN1
+# define NATIVE_TO_LATIN1(c) (c)
+#endif
+
+#ifndef LATIN1_TO_NATIVE
+# define LATIN1_TO_NATIVE(c) (c)
+#endif
+
+#ifndef NATIVE_TO_UNI
+# define NATIVE_TO_UNI(c) (c)
+#endif
+
+#ifndef UNI_TO_NATIVE
+# define UNI_TO_NATIVE(c) (c)
+#endif
+
+#endif
+
+/* Warning: LATIN1_TO_NATIVE, NATIVE_TO_LATIN1 NATIVE_TO_UNI UNI_TO_NATIVE
+ EBCDIC is not supported on versions earlier than 5.7.1
+ */
+
+/* The meaning of this changed; use the modern version */
+#undef isPSXSPC
+#undef isPSXSPC_A
+#undef isPSXSPC_L1
+
+/* Hint: isPSXSPC, isPSXSPC_A, isPSXSPC_L1, isPSXSPC_utf8_safe
+ This is equivalent to the corresponding isSPACE-type macro. On perls
+ before 5.18, this matched a vertical tab and SPACE didn't. But the
+ ppport.h SPACE version does match VT in all perl releases. Since VT's are
+ extremely rarely found in real-life files, this difference effectively
+ doesn't matter */
+
+/* Hint: isSPACE, isSPACE_A, isSPACE_L1, isSPACE_utf8_safe
+ Until Perl 5.18, this did not match the vertical tab (VT). The ppport.h
+ version does match it in all perl releases. Since VT's are extremely rarely
+ found in real-life files, this difference effectively doesn't matter */
+
+#ifdef EBCDIC
+
+/* This is the first version where these macros are fully correct on EBCDIC
+ * platforms. Relying on the C library functions, as earlier releases did,
+ * causes problems with locales */
+# if (PERL_BCDVERSION < 0x5022000)
+# undef isALNUM
+# undef isALNUM_A
+# undef isALNUM_L1
+# undef isALNUMC
+# undef isALNUMC_A
+# undef isALNUMC_L1
+# undef isALPHA
+# undef isALPHA_A
+# undef isALPHA_L1
+# undef isALPHANUMERIC
+# undef isALPHANUMERIC_A
+# undef isALPHANUMERIC_L1
+# undef isASCII
+# undef isASCII_A
+# undef isASCII_L1
+# undef isBLANK
+# undef isBLANK_A
+# undef isBLANK_L1
+# undef isCNTRL
+# undef isCNTRL_A
+# undef isCNTRL_L1
+# undef isDIGIT
+# undef isDIGIT_A
+# undef isDIGIT_L1
+# undef isGRAPH
+# undef isGRAPH_A
+# undef isGRAPH_L1
+# undef isIDCONT
+# undef isIDCONT_A
+# undef isIDCONT_L1
+# undef isIDFIRST
+# undef isIDFIRST_A
+# undef isIDFIRST_L1
+# undef isLOWER
+# undef isLOWER_A
+# undef isLOWER_L1
+# undef isOCTAL
+# undef isOCTAL_A
+# undef isOCTAL_L1
+# undef isPRINT
+# undef isPRINT_A
+# undef isPRINT_L1
+# undef isPUNCT
+# undef isPUNCT_A
+# undef isPUNCT_L1
+# undef isSPACE
+# undef isSPACE_A
+# undef isSPACE_L1
+# undef isUPPER
+# undef isUPPER_A
+# undef isUPPER_L1
+# undef isWORDCHAR
+# undef isWORDCHAR_A
+# undef isWORDCHAR_L1
+# undef isXDIGIT
+# undef isXDIGIT_A
+# undef isXDIGIT_L1
+# endif
+#ifndef isASCII
+# define isASCII(c) (isCNTRL(c) || isPRINT(c))
+#endif
+
+ /* The below is accurate for all EBCDIC code pages supported by
+ * all the versions of Perl overridden by this */
+#ifndef isCNTRL
+# define isCNTRL(c) ( (c) == '\0' || (c) == '\a' || (c) == '\b' \
+ || (c) == '\f' || (c) == '\n' || (c) == '\r' \
+ || (c) == '\t' || (c) == '\v' \
+ || ((c) <= 3 && (c) >= 1) /* SOH, STX, ETX */ \
+ || (c) == 7 /* U+7F DEL */ \
+ || ((c) <= 0x13 && (c) >= 0x0E) /* SO, SI */ \
+ /* DLE, DC[1-3] */ \
+ || (c) == 0x18 /* U+18 CAN */ \
+ || (c) == 0x19 /* U+19 EOM */ \
+ || ((c) <= 0x1F && (c) >= 0x1C) /* [FGRU]S */ \
+ || (c) == 0x26 /* U+17 ETB */ \
+ || (c) == 0x27 /* U+1B ESC */ \
+ || (c) == 0x2D /* U+05 ENQ */ \
+ || (c) == 0x2E /* U+06 ACK */ \
+ || (c) == 0x32 /* U+16 SYN */ \
+ || (c) == 0x37 /* U+04 EOT */ \
+ || (c) == 0x3C /* U+14 DC4 */ \
+ || (c) == 0x3D /* U+15 NAK */ \
+ || (c) == 0x3F /* U+1A SUB */ \
+ )
+#endif
+
+#if '^' == 106 /* EBCDIC POSIX-BC */
+# define D_PPP_OUTLIER_CONTROL 0x5F
+#else /* EBCDIC 1047 037 */
+# define D_PPP_OUTLIER_CONTROL 0xFF
+#endif
+
+/* The controls are everything below blank, plus one outlier */
+#ifndef isCNTRL_L1
+# define isCNTRL_L1(c) ((WIDEST_UTYPE) (c) < ' ' \
+ || (WIDEST_UTYPE) (c) == D_PPP_OUTLIER_CONTROL)
+#endif
+
+/* The ordering of the tests in this and isUPPER are to exclude most characters
+ * early */
+#ifndef isLOWER
+# define isLOWER(c) ( (c) >= 'a' && (c) <= 'z' \
+ && ( (c) <= 'i' \
+ || ((c) >= 'j' && (c) <= 'r') \
+ || (c) >= 's'))
+#endif
+
+#ifndef isUPPER
+# define isUPPER(c) ( (c) >= 'A' && (c) <= 'Z' \
+ && ( (c) <= 'I' \
+ || ((c) >= 'J' && (c) <= 'R') \
+ || (c) >= 'S'))
+#endif
+
+#else /* Above is EBCDIC; below is ASCII */
+
+# if (PERL_BCDVERSION < 0x5004000)
+/* The implementation of these in older perl versions can give wrong results if
+ * the C program locale is set to other than the C locale */
+# undef isALNUM
+# undef isALNUM_A
+# undef isALPHA
+# undef isALPHA_A
+# undef isDIGIT
+# undef isDIGIT_A
+# undef isIDFIRST
+# undef isIDFIRST_A
+# undef isLOWER
+# undef isLOWER_A
+# undef isUPPER
+# undef isUPPER_A
+# endif
+
+# if (PERL_BCDVERSION == 0x5007000) /* this perl made space GRAPH */
+# undef isGRAPH
+# endif
+
+# if (PERL_BCDVERSION < 0x5008000) /* earlier perls omitted DEL */
+# undef isCNTRL
+# endif
+
+# if (PERL_BCDVERSION < 0x5010000)
+/* earlier perls included all of the isSPACE() characters, which is wrong. The
+ * version provided by Devel::PPPort always overrides an existing buggy
+ * version. */
+# undef isPRINT
+# undef isPRINT_A
+# endif
+
+# if (PERL_BCDVERSION < 0x5014000)
+/* earlier perls always returned true if the parameter was a signed char */
+# undef isASCII
+# undef isASCII_A
+# endif
+
+# if (PERL_BCDVERSION < 0x5017008) /* earlier perls didn't include PILCROW, SECTION SIGN */
+# undef isPUNCT_L1
+# endif
+
+# if (PERL_BCDVERSION < 0x5013007) /* khw didn't investigate why this failed */
+# undef isALNUMC_L1
+#endif
+
+# if (PERL_BCDVERSION < 0x5020000) /* earlier perls didn't include \v */
+# undef isSPACE
+# undef isSPACE_A
+# undef isSPACE_L1
+
+# endif
+#ifndef isASCII
+# define isASCII(c) ((WIDEST_UTYPE) (c) <= 127)
+#endif
+
+#ifndef isCNTRL
+# define isCNTRL(c) ((WIDEST_UTYPE) (c) < ' ' || (c) == 127)
+#endif
+
+#ifndef isCNTRL_L1
+# define isCNTRL_L1(c) ( (WIDEST_UTYPE) (c) < ' ' \
+ || inRANGE((c), 0x7F, 0x9F))
+#endif
+
+#ifndef isLOWER
+# define isLOWER(c) inRANGE((c), 'a', 'z')
+#endif
+
+#ifndef isUPPER
+# define isUPPER(c) inRANGE((c), 'A', 'Z')
+#endif
+
+#endif /* Below are definitions common to EBCDIC and ASCII */
+#ifndef isASCII_L1
+# define isASCII_L1(c) isASCII(c)
+#endif
+
+#ifndef isASCII_LC
+# define isASCII_LC(c) isASCII(c)
+#endif
+
+#ifndef isALNUM
+# define isALNUM(c) isWORDCHAR(c)
+#endif
+
+#ifndef isALNUMC
+# define isALNUMC(c) isALPHANUMERIC(c)
+#endif
+
+#ifndef isALNUMC_L1
+# define isALNUMC_L1(c) isALPHANUMERIC_L1(c)
+#endif
+
+#ifndef isALPHA
+# define isALPHA(c) (isUPPER(c) || isLOWER(c))
+#endif
+
+#ifndef isALPHA_L1
+# define isALPHA_L1(c) (isUPPER_L1(c) || isLOWER_L1(c))
+#endif
+
+#ifndef isALPHANUMERIC
+# define isALPHANUMERIC(c) (isALPHA(c) || isDIGIT(c))
+#endif
+
+#ifndef isALPHANUMERIC_L1
+# define isALPHANUMERIC_L1(c) (isALPHA_L1(c) || isDIGIT(c))
+#endif
+
+#ifndef isALPHANUMERIC_LC
+# define isALPHANUMERIC_LC(c) (isALPHA_LC(c) || isDIGIT_LC(c))
+#endif
+
+#ifndef isBLANK
+# define isBLANK(c) ((c) == ' ' || (c) == '\t')
+#endif
+
+#ifndef isBLANK_L1
+# define isBLANK_L1(c) ( isBLANK(c) \
+ || ( FITS_IN_8_BITS(c) \
+ && NATIVE_TO_LATIN1((U8) c) == 0xA0))
+#endif
+
+#ifndef isBLANK_LC
+# define isBLANK_LC(c) isBLANK(c)
+#endif
+
+#ifndef isDIGIT
+# define isDIGIT(c) inRANGE(c, '0', '9')
+#endif
+
+#ifndef isDIGIT_L1
+# define isDIGIT_L1(c) isDIGIT(c)
+#endif
+
+#ifndef isGRAPH
+# define isGRAPH(c) (isWORDCHAR(c) || isPUNCT(c))
+#endif
+
+#ifndef isGRAPH_L1
+# define isGRAPH_L1(c) ( isPRINT_L1(c) \
+ && (c) != ' ' \
+ && NATIVE_TO_LATIN1((U8) c) != 0xA0)
+#endif
+
+#ifndef isIDCONT
+# define isIDCONT(c) isWORDCHAR(c)
+#endif
+
+#ifndef isIDCONT_L1
+# define isIDCONT_L1(c) isWORDCHAR_L1(c)
+#endif
+
+#ifndef isIDCONT_LC
+# define isIDCONT_LC(c) isWORDCHAR_LC(c)
+#endif
+
+#ifndef isIDFIRST
+# define isIDFIRST(c) (isALPHA(c) || (c) == '_')
+#endif
+
+#ifndef isIDFIRST_L1
+# define isIDFIRST_L1(c) (isALPHA_L1(c) || (U8) (c) == '_')
+#endif
+
+#ifndef isIDFIRST_LC
+# define isIDFIRST_LC(c) (isALPHA_LC(c) || (U8) (c) == '_')
+#endif
+
+#ifndef isLOWER_L1
+# define isLOWER_L1(c) ( isLOWER(c) \
+ || ( FITS_IN_8_BITS(c) \
+ && ( ( NATIVE_TO_LATIN1((U8) c) >= 0xDF \
+ && NATIVE_TO_LATIN1((U8) c) != 0xF7) \
+ || NATIVE_TO_LATIN1((U8) c) == 0xAA \
+ || NATIVE_TO_LATIN1((U8) c) == 0xBA \
+ || NATIVE_TO_LATIN1((U8) c) == 0xB5)))
+#endif
+
+#ifndef isOCTAL
+# define isOCTAL(c) (((WIDEST_UTYPE)((c)) & ~7) == '0')
+#endif
+
+#ifndef isOCTAL_L1
+# define isOCTAL_L1(c) isOCTAL(c)
+#endif
+
+#ifndef isPRINT
+# define isPRINT(c) (isGRAPH(c) || (c) == ' ')
+#endif
+
+#ifndef isPRINT_L1
+# define isPRINT_L1(c) (FITS_IN_8_BITS(c) && ! isCNTRL_L1(c))
+#endif
+
+#ifndef isPSXSPC
+# define isPSXSPC(c) isSPACE(c)
+#endif
+
+#ifndef isPSXSPC_L1
+# define isPSXSPC_L1(c) isSPACE_L1(c)
+#endif
+
+#ifndef isPUNCT
+# define isPUNCT(c) ( (c) == '-' || (c) == '!' || (c) == '"' \
+ || (c) == '#' || (c) == '$' || (c) == '%' \
+ || (c) == '&' || (c) == '\'' || (c) == '(' \
+ || (c) == ')' || (c) == '*' || (c) == '+' \
+ || (c) == ',' || (c) == '.' || (c) == '/' \
+ || (c) == ':' || (c) == ';' || (c) == '<' \
+ || (c) == '=' || (c) == '>' || (c) == '?' \
+ || (c) == '@' || (c) == '[' || (c) == '\\' \
+ || (c) == ']' || (c) == '^' || (c) == '_' \
+ || (c) == '`' || (c) == '{' || (c) == '|' \
+ || (c) == '}' || (c) == '~')
+#endif
+
+#ifndef isPUNCT_L1
+# define isPUNCT_L1(c) ( isPUNCT(c) \
+ || ( FITS_IN_8_BITS(c) \
+ && ( NATIVE_TO_LATIN1((U8) c) == 0xA1 \
+ || NATIVE_TO_LATIN1((U8) c) == 0xA7 \
+ || NATIVE_TO_LATIN1((U8) c) == 0xAB \
+ || NATIVE_TO_LATIN1((U8) c) == 0xB6 \
+ || NATIVE_TO_LATIN1((U8) c) == 0xB7 \
+ || NATIVE_TO_LATIN1((U8) c) == 0xBB \
+ || NATIVE_TO_LATIN1((U8) c) == 0xBF)))
+#endif
+
+#ifndef isSPACE
+# define isSPACE(c) ( isBLANK(c) || (c) == '\n' || (c) == '\r' \
+ || (c) == '\v' || (c) == '\f')
+#endif
+
+#ifndef isSPACE_L1
+# define isSPACE_L1(c) ( isSPACE(c) \
+ || (FITS_IN_8_BITS(c) \
+ && ( NATIVE_TO_LATIN1((U8) c) == 0x85 \
+ || NATIVE_TO_LATIN1((U8) c) == 0xA0)))
+#endif
+
+#ifndef isUPPER_L1
+# define isUPPER_L1(c) ( isUPPER(c) \
+ || (FITS_IN_8_BITS(c) \
+ && ( NATIVE_TO_LATIN1((U8) c) >= 0xC0 \
+ && NATIVE_TO_LATIN1((U8) c) <= 0xDE \
+ && NATIVE_TO_LATIN1((U8) c) != 0xD7)))
+#endif
+
+#ifndef isWORDCHAR
+# define isWORDCHAR(c) (isALPHANUMERIC(c) || (c) == '_')
+#endif
+
+#ifndef isWORDCHAR_L1
+# define isWORDCHAR_L1(c) (isIDFIRST_L1(c) || isDIGIT(c))
+#endif
+
+#ifndef isWORDCHAR_LC
+# define isWORDCHAR_LC(c) (isIDFIRST_LC(c) || isDIGIT_LC(c))
+#endif
+
+#ifndef isXDIGIT
+# define isXDIGIT(c) ( isDIGIT(c) \
+ || inRANGE((c), 'a', 'f') \
+ || inRANGE((c), 'A', 'F'))
+#endif
+
+#ifndef isXDIGIT_L1
+# define isXDIGIT_L1(c) isXDIGIT(c)
+#endif
+
+#ifndef isXDIGIT_LC
+# define isXDIGIT_LC(c) isxdigit(c)
+#endif
+#ifndef isALNUM_A
+# define isALNUM_A(c) isALNUM(c)
+#endif
+
+#ifndef isALNUMC_A
+# define isALNUMC_A(c) isALNUMC(c)
+#endif
+
+#ifndef isALPHA_A
+# define isALPHA_A(c) isALPHA(c)
+#endif
+
+#ifndef isALPHANUMERIC_A
+# define isALPHANUMERIC_A(c) isALPHANUMERIC(c)
+#endif
+
+#ifndef isASCII_A
+# define isASCII_A(c) isASCII(c)
+#endif
+
+#ifndef isBLANK_A
+# define isBLANK_A(c) isBLANK(c)
+#endif
+
+#ifndef isCNTRL_A
+# define isCNTRL_A(c) isCNTRL(c)
+#endif
+
+#ifndef isDIGIT_A
+# define isDIGIT_A(c) isDIGIT(c)
+#endif
+
+#ifndef isGRAPH_A
+# define isGRAPH_A(c) isGRAPH(c)
+#endif
+
+#ifndef isIDCONT_A
+# define isIDCONT_A(c) isIDCONT(c)
+#endif
+
+#ifndef isIDFIRST_A
+# define isIDFIRST_A(c) isIDFIRST(c)
+#endif
+
+#ifndef isLOWER_A
+# define isLOWER_A(c) isLOWER(c)
+#endif
+
+#ifndef isOCTAL_A
+# define isOCTAL_A(c) isOCTAL(c)
+#endif
+
+#ifndef isPRINT_A
+# define isPRINT_A(c) isPRINT(c)
+#endif
+
+#ifndef isPSXSPC_A
+# define isPSXSPC_A(c) isPSXSPC(c)
+#endif
+
+#ifndef isPUNCT_A
+# define isPUNCT_A(c) isPUNCT(c)
+#endif
+
+#ifndef isSPACE_A
+# define isSPACE_A(c) isSPACE(c)
+#endif
+
+#ifndef isUPPER_A
+# define isUPPER_A(c) isUPPER(c)
+#endif
+
+#ifndef isWORDCHAR_A
+# define isWORDCHAR_A(c) isWORDCHAR(c)
+#endif
+
+#ifndef isXDIGIT_A
+# define isXDIGIT_A(c) isXDIGIT(c)
+#endif
+#ifndef isASCII_utf8_safe
+# define isASCII_utf8_safe(s,e) (((e) - (s)) <= 0 ? 0 : isASCII(*(s)))
+#endif
+
+#ifndef isASCII_uvchr
+# define isASCII_uvchr(c) (FITS_IN_8_BITS(c) ? isASCII_L1(c) : 0)
+#endif
+
+#if (PERL_BCDVERSION >= 0x5006000)
+# ifdef isALPHA_uni /* If one defined, all are; this is just an exemplar */
+# define D_PPP_is_ctype(upper, lower, c) \
+ (FITS_IN_8_BITS(c) \
+ ? is ## upper ## _L1(c) \
+ : is ## upper ## _uni((UV) (c))) /* _uni is old synonym */
+# else
+# define D_PPP_is_ctype(upper, lower, c) \
+ (FITS_IN_8_BITS(c) \
+ ? is ## upper ## _L1(c) \
+ : is_uni_ ## lower((UV) (c))) /* is_uni_ is even older */
+# endif
+#ifndef isALPHA_uvchr
+# define isALPHA_uvchr(c) D_PPP_is_ctype(ALPHA, alpha, c)
+#endif
+
+#ifndef isALPHANUMERIC_uvchr
+# define isALPHANUMERIC_uvchr(c) (isALPHA_uvchr(c) || isDIGIT_uvchr(c))
+#endif
+
+# ifdef is_uni_blank
+#ifndef isBLANK_uvchr
+# define isBLANK_uvchr(c) D_PPP_is_ctype(BLANK, blank, c)
+#endif
+
+# else
+#ifndef isBLANK_uvchr
+# define isBLANK_uvchr(c) (FITS_IN_8_BITS(c) \
+ ? isBLANK_L1(c) \
+ : ( (UV) (c) == 0x1680 /* Unicode 3.0 */ \
+ || inRANGE((UV) (c), 0x2000, 0x200A) \
+ || (UV) (c) == 0x202F /* Unicode 3.0 */\
+ || (UV) (c) == 0x205F /* Unicode 3.2 */\
+ || (UV) (c) == 0x3000))
+#endif
+
+# endif
+#ifndef isCNTRL_uvchr
+# define isCNTRL_uvchr(c) D_PPP_is_ctype(CNTRL, cntrl, c)
+#endif
+
+#ifndef isDIGIT_uvchr
+# define isDIGIT_uvchr(c) D_PPP_is_ctype(DIGIT, digit, c)
+#endif
+
+#ifndef isGRAPH_uvchr
+# define isGRAPH_uvchr(c) D_PPP_is_ctype(GRAPH, graph, c)
+#endif
+
+#ifndef isIDCONT_uvchr
+# define isIDCONT_uvchr(c) isWORDCHAR_uvchr(c)
+#endif
+
+#ifndef isIDFIRST_uvchr
+# define isIDFIRST_uvchr(c) D_PPP_is_ctype(IDFIRST, idfirst, c)
+#endif
+
+#ifndef isLOWER_uvchr
+# define isLOWER_uvchr(c) D_PPP_is_ctype(LOWER, lower, c)
+#endif
+
+#ifndef isPRINT_uvchr
+# define isPRINT_uvchr(c) D_PPP_is_ctype(PRINT, print, c)
+#endif
+
+#ifndef isPSXSPC_uvchr
+# define isPSXSPC_uvchr(c) isSPACE_uvchr(c)
+#endif
+
+#ifndef isPUNCT_uvchr
+# define isPUNCT_uvchr(c) D_PPP_is_ctype(PUNCT, punct, c)
+#endif
+
+#ifndef isSPACE_uvchr
+# define isSPACE_uvchr(c) D_PPP_is_ctype(SPACE, space, c)
+#endif
+
+#ifndef isUPPER_uvchr
+# define isUPPER_uvchr(c) D_PPP_is_ctype(UPPER, upper, c)
+#endif
+
+#ifndef isXDIGIT_uvchr
+# define isXDIGIT_uvchr(c) D_PPP_is_ctype(XDIGIT, xdigit, c)
+#endif
+
+#ifndef isWORDCHAR_uvchr
+# define isWORDCHAR_uvchr(c) (FITS_IN_8_BITS(c) \
+ ? isWORDCHAR_L1(c) : isALPHANUMERIC_uvchr(c))
+#endif
+#ifndef isALPHA_utf8_safe
+# define isALPHA_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, ALPHA)
+#endif
+
+# ifdef isALPHANUMERIC_utf8
+#ifndef isALPHANUMERIC_utf8_safe
+# define isALPHANUMERIC_utf8_safe(s,e) \
+ D_PPP_IS_GENERIC_UTF8_SAFE(s, e, ALPHANUMERIC)
+#endif
+
+# else
+#ifndef isALPHANUMERIC_utf8_safe
+# define isALPHANUMERIC_utf8_safe(s,e) \
+ (isALPHA_utf8_safe(s,e) || isDIGIT_utf8_safe(s,e))
+#endif
+
+# endif
+
+/* This was broken before 5.18, and just use this instead of worrying about
+ * which releases the official works on */
+# if 'A' == 65
+#ifndef isBLANK_utf8_safe
+# define isBLANK_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? /* Machine generated */ \
+ ( ( 0x09 == ((const U8*)s)[0] || 0x20 == ((const U8*)s)[0] ) ? 1 \
+ : ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) ? \
+ ( ( 0xC2 == ((const U8*)s)[0] ) ? \
+ ( ( 0xA0 == ((const U8*)s)[1] ) ? 2 : 0 ) \
+ : ( 0xE1 == ((const U8*)s)[0] ) ? \
+ ( ( ( 0x9A == ((const U8*)s)[1] ) && ( 0x80 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( 0xE2 == ((const U8*)s)[0] ) ? \
+ ( ( 0x80 == ((const U8*)s)[1] ) ? \
+ ( ( inRANGE(((const U8*)s)[2], 0x80, 0x8A ) || 0xAF == ((const U8*)s)[2] ) ? 3 : 0 )\
+ : ( ( 0x81 == ((const U8*)s)[1] ) && ( 0x9F == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( ( ( 0xE3 == ((const U8*)s)[0] ) && ( 0x80 == ((const U8*)s)[1] ) ) && ( 0x80 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : 0 ) \
+ : 0 )
+#endif
+
+# elif 'A' == 193 && '^' == 95 /* EBCDIC 1047 */
+#ifndef isBLANK_utf8_safe
+# define isBLANK_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? \
+ ( ( 0x05 == ((const U8*)s)[0] || 0x40 == ((const U8*)s)[0] ) ? 1 \
+ : ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) ? \
+ ( ( 0x80 == ((const U8*)s)[0] ) ? \
+ ( ( 0x41 == ((const U8*)s)[1] ) ? 2 : 0 ) \
+ : ( 0xBC == ((const U8*)s)[0] ) ? \
+ ( ( ( 0x63 == ((const U8*)s)[1] ) && ( 0x41 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( 0xCA == ((const U8*)s)[0] ) ? \
+ ( ( 0x41 == ((const U8*)s)[1] ) ? \
+ ( ( inRANGE(((const U8*)s)[2], 0x41, 0x4A ) || 0x51 == ((const U8*)s)[2] ) ? 3 : 0 )\
+ : ( 0x42 == ((const U8*)s)[1] ) ? \
+ ( ( 0x56 == ((const U8*)s)[2] ) ? 3 : 0 ) \
+ : ( ( 0x43 == ((const U8*)s)[1] ) && ( 0x73 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( ( ( 0xCE == ((const U8*)s)[0] ) && ( 0x41 == ((const U8*)s)[1] ) ) && ( 0x41 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : 0 ) \
+: 0 )
+#endif
+
+# elif 'A' == 193 && '^' == 176 /* EBCDIC 037 */
+#ifndef isBLANK_utf8_safe
+# define isBLANK_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? \
+ ( ( 0x05 == ((const U8*)s)[0] || 0x40 == ((const U8*)s)[0] ) ? 1 \
+ : ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) ? \
+ ( ( 0x78 == ((const U8*)s)[0] ) ? \
+ ( ( 0x41 == ((const U8*)s)[1] ) ? 2 : 0 ) \
+ : ( 0xBD == ((const U8*)s)[0] ) ? \
+ ( ( ( 0x62 == ((const U8*)s)[1] ) && ( 0x41 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( 0xCA == ((const U8*)s)[0] ) ? \
+ ( ( 0x41 == ((const U8*)s)[1] ) ? \
+ ( ( inRANGE(((const U8*)s)[2], 0x41, 0x4A ) || 0x51 == ((const U8*)s)[2] ) ? 3 : 0 )\
+ : ( 0x42 == ((const U8*)s)[1] ) ? \
+ ( ( 0x56 == ((const U8*)s)[2] ) ? 3 : 0 ) \
+ : ( ( 0x43 == ((const U8*)s)[1] ) && ( 0x72 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : ( ( ( 0xCE == ((const U8*)s)[0] ) && ( 0x41 == ((const U8*)s)[1] ) ) && ( 0x41 == ((const U8*)s)[2] ) ) ? 3 : 0 )\
+ : 0 ) \
+: 0 )
+#endif
+
+# else
+# error Unknown character set
+# endif
+#ifndef isCNTRL_utf8_safe
+# define isCNTRL_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, CNTRL)
+#endif
+
+#ifndef isDIGIT_utf8_safe
+# define isDIGIT_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, DIGIT)
+#endif
+
+#ifndef isGRAPH_utf8_safe
+# define isGRAPH_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, GRAPH)
+#endif
+
+# ifdef isIDCONT_utf8
+#ifndef isIDCONT_utf8_safe
+# define isIDCONT_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, IDCONT)
+#endif
+
+# else
+#ifndef isIDCONT_utf8_safe
+# define isIDCONT_utf8_safe(s,e) isWORDCHAR_utf8_safe(s,e)
+#endif
+
+# endif
+#ifndef isIDFIRST_utf8_safe
+# define isIDFIRST_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, IDFIRST)
+#endif
+
+#ifndef isLOWER_utf8_safe
+# define isLOWER_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, LOWER)
+#endif
+
+#ifndef isPRINT_utf8_safe
+# define isPRINT_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, PRINT)
+#endif
+
+# undef isPSXSPC_utf8_safe /* Use the modern definition */
+#ifndef isPSXSPC_utf8_safe
+# define isPSXSPC_utf8_safe(s,e) isSPACE_utf8_safe(s,e)
+#endif
+#ifndef isPUNCT_utf8_safe
+# define isPUNCT_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, PUNCT)
+#endif
+
+#ifndef isSPACE_utf8_safe
+# define isSPACE_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, SPACE)
+#endif
+
+#ifndef isUPPER_utf8_safe
+# define isUPPER_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, UPPER)
+#endif
+
+# ifdef isWORDCHAR_utf8
+#ifndef isWORDCHAR_utf8_safe
+# define isWORDCHAR_utf8_safe(s,e) D_PPP_IS_GENERIC_UTF8_SAFE(s, e, WORDCHAR)
+#endif
+
+# else
+#ifndef isWORDCHAR_utf8_safe
+# define isWORDCHAR_utf8_safe(s,e) \
+ (isALPHANUMERIC_utf8_safe(s,e) || (*(s)) == '_')
+#endif
+
+# endif
+
+/* This was broken before 5.12, and just use this instead of worrying about
+ * which releases the official works on */
+# if 'A' == 65
+#ifndef isXDIGIT_utf8_safe
+# define isXDIGIT_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? \
+ ( ( inRANGE(((const U8*)s)[0], 0x30, 0x39 ) || inRANGE(((const U8*)s)[0], 0x41, 0x46 ) || inRANGE(((const U8*)s)[0], 0x61, 0x66 ) ) ? 1\
+ : ( ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) && ( 0xEF == ((const U8*)s)[0] ) ) ? ( ( 0xBC == ((const U8*)s)[1] ) ?\
+ ( ( inRANGE(((const U8*)s)[2], 0x90, 0x99 ) || inRANGE(((const U8*)s)[2], 0xA1, 0xA6 ) ) ? 3 : 0 )\
+ : ( ( 0xBD == ((const U8*)s)[1] ) && ( inRANGE(((const U8*)s)[2], 0x81, 0x86 ) ) ) ? 3 : 0 ) : 0 )\
+: 0 )
+#endif
+
+# elif 'A' == 193 && '^' == 95 /* EBCDIC 1047 */
+#ifndef isXDIGIT_utf8_safe
+# define isXDIGIT_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? \
+ ( ( inRANGE(((const U8*)s)[0], 0x81, 0x86 ) || inRANGE(((const U8*)s)[0], 0xC1, 0xC6 ) || inRANGE(((const U8*)s)[0], 0xF0, 0xF9 ) ) ? 1\
+ : ( ( ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) && ( 0xDD == ((const U8*)s)[0] ) ) && ( 0x73 == ((const U8*)s)[1] ) ) ? ( ( 0x67 == ((const U8*)s)[2] ) ?\
+ ( ( inRANGE(((const U8*)s)[3], 0x57, 0x59 ) || inRANGE(((const U8*)s)[3], 0x62, 0x68 ) ) ? 4 : 0 )\
+ : ( ( inRANGE(((const U8*)s)[2], 0x68, 0x69 ) ) && ( inRANGE(((const U8*)s)[3], 0x42, 0x47 ) ) ) ? 4 : 0 ) : 0 )\
+: 0 )
+#endif
+
+# elif 'A' == 193 && '^' == 176 /* EBCDIC 037 */
+#ifndef isXDIGIT_utf8_safe
+# define isXDIGIT_utf8_safe(s,e) \
+( ( LIKELY((e) > (s)) ) ? \
+ ( ( inRANGE(((const U8*)s)[0], 0x81, 0x86 ) || inRANGE(((const U8*)s)[0], 0xC1, 0xC6 ) || inRANGE(((const U8*)s)[0], 0xF0, 0xF9 ) ) ? 1\
+ : ( ( ( LIKELY(((e) - (s)) >= UTF8SKIP(s)) ) && ( 0xDD == ((const U8*)s)[0] ) ) && ( 0x72 == ((const U8*)s)[1] ) ) ? ( ( 0x66 == ((const U8*)s)[2] ) ?\
+ ( ( inRANGE(((const U8*)s)[3], 0x57, 0x59 ) || 0x5F == ((const U8*)s)[3] || inRANGE(((const U8*)s)[3], 0x62, 0x67 ) ) ? 4 : 0 )\
+ : ( ( inRANGE(((const U8*)s)[2], 0x67, 0x68 ) ) && ( inRANGE(((const U8*)s)[3], 0x42, 0x47 ) ) ) ? 4 : 0 ) : 0 )\
+: 0 )
+#endif
+
+# else
+# error Unknown character set
+# endif
+#ifndef isALPHA_LC_utf8_safe
+# define isALPHA_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, ALPHA)
+#endif
+
+# ifdef isALPHANUMERIC_utf8
+#ifndef isALPHANUMERIC_LC_utf8_safe
+# define isALPHANUMERIC_LC_utf8_safe(s,e) \
+ D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, ALPHANUMERIC)
+#endif
+
+# else
+#ifndef isALPHANUMERIC_LC_utf8_safe
+# define isALPHANUMERIC_LC_utf8_safe(s,e) \
+ (isALPHA_LC_utf8_safe(s,e) || isDIGIT_LC_utf8_safe(s,e))
+#endif
+
+# endif
+#ifndef isBLANK_LC_utf8_safe
+# define isBLANK_LC_utf8_safe(s,e) \
+ D_PPP_IS_GENERIC_LC_UTF8_SAFE_BROKEN(s, e, BLANK)
+#endif
+
+#ifndef isCNTRL_LC_utf8_safe
+# define isCNTRL_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, CNTRL)
+#endif
+
+#ifndef isDIGIT_LC_utf8_safe
+# define isDIGIT_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, DIGIT)
+#endif
+
+#ifndef isGRAPH_LC_utf8_safe
+# define isGRAPH_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, GRAPH)
+#endif
+
+# ifdef isIDCONT_utf8
+#ifndef isIDCONT_LC_utf8_safe
+# define isIDCONT_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, IDCONT)
+#endif
+
+# else
+#ifndef isIDCONT_LC_utf8_safe
+# define isIDCONT_LC_utf8_safe(s,e) isWORDCHAR_LC_utf8_safe(s,e)
+#endif
+
+# endif
+#ifndef isIDFIRST_LC_utf8_safe
+# define isIDFIRST_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, IDFIRST)
+#endif
+
+#ifndef isLOWER_LC_utf8_safe
+# define isLOWER_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, LOWER)
+#endif
+
+#ifndef isPRINT_LC_utf8_safe
+# define isPRINT_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, PRINT)
+#endif
+
+# undef isPSXSPC_LC_utf8_safe /* Use the modern definition */
+#ifndef isPSXSPC_LC_utf8_safe
+# define isPSXSPC_LC_utf8_safe(s,e) isSPACE_LC_utf8_safe(s,e)
+#endif
+#ifndef isPUNCT_LC_utf8_safe
+# define isPUNCT_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, PUNCT)
+#endif
+
+#ifndef isSPACE_LC_utf8_safe
+# define isSPACE_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, SPACE)
+#endif
+
+#ifndef isUPPER_LC_utf8_safe
+# define isUPPER_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, UPPER)
+#endif
+
+# ifdef isWORDCHAR_utf8
+#ifndef isWORDCHAR_LC_utf8_safe
+# define isWORDCHAR_LC_utf8_safe(s,e) D_PPP_IS_GENERIC_LC_UTF8_SAFE(s, e, WORDCHAR)
+#endif
+
+# else
+#ifndef isWORDCHAR_LC_utf8_safe
+# define isWORDCHAR_LC_utf8_safe(s,e) \
+ (isALPHANUMERIC_LC_utf8_safe(s,e) || (*(s)) == '_')
+#endif
+
+# endif
+#ifndef isXDIGIT_LC_utf8_safe
+# define isXDIGIT_LC_utf8_safe(s,e) \
+ D_PPP_IS_GENERIC_LC_UTF8_SAFE_BROKEN(s, e, XDIGIT)
+#endif
+
+/* Warning: isALPHANUMERIC_utf8_safe, isALPHA_utf8_safe, isASCII_utf8_safe,
+ * isBLANK_utf8_safe, isCNTRL_utf8_safe, isDIGIT_utf8_safe, isGRAPH_utf8_safe,
+ * isIDCONT_utf8_safe, isIDFIRST_utf8_safe, isLOWER_utf8_safe,
+ * isPRINT_utf8_safe, isPSXSPC_utf8_safe, isPUNCT_utf8_safe, isSPACE_utf8_safe,
+ * isUPPER_utf8_safe, isWORDCHAR_utf8_safe, isWORDCHAR_utf8_safe,
+ * isXDIGIT_utf8_safe,
+ * isALPHANUMERIC_LC_utf8_safe, isALPHA_LC_utf8_safe, isASCII_LC_utf8_safe,
+ * isBLANK_LC_utf8_safe, isCNTRL_LC_utf8_safe, isDIGIT_LC_utf8_safe,
+ * isGRAPH_LC_utf8_safe, isIDCONT_LC_utf8_safe, isIDFIRST_LC_utf8_safe,
+ * isLOWER_LC_utf8_safe, isPRINT_LC_utf8_safe, isPSXSPC_LC_utf8_safe,
+ * isPUNCT_LC_utf8_safe, isSPACE_LC_utf8_safe, isUPPER_LC_utf8_safe,
+ * isWORDCHAR_LC_utf8_safe, isWORDCHAR_LC_utf8_safe, isXDIGIT_LC_utf8_safe,
+ * isALPHANUMERIC_uvchr, isALPHA_uvchr, isASCII_uvchr, isBLANK_uvchr,
+ * isCNTRL_uvchr, isDIGIT_uvchr, isGRAPH_uvchr, isIDCONT_uvchr,
+ * isIDFIRST_uvchr, isLOWER_uvchr, isPRINT_uvchr, isPSXSPC_uvchr,
+ * isPUNCT_uvchr, isSPACE_uvchr, isUPPER_uvchr, isWORDCHAR_uvchr,
+ * isWORDCHAR_uvchr, isXDIGIT_uvchr
+ *
+ * The UTF-8 handling is buggy in early Perls, and this can give inaccurate
+ * results for code points above 0xFF, until the implementation started
+ * settling down in 5.12 and 5.14 */
+
+#endif
+
+#define D_PPP_TOO_SHORT_MSG "Malformed UTF-8 character starting with:" \
+ " \\x%02x (too short; %d bytes available, need" \
+ " %d)\n"
+/* Perls starting here had a new API which handled multi-character results */
+#if (PERL_BCDVERSION >= 0x5007003)
+#ifndef toLOWER_uvchr
+# define toLOWER_uvchr(c, s, l) UNI_TO_NATIVE(to_uni_lower(NATIVE_TO_UNI(c), s, l))
+#endif
+
+#ifndef toUPPER_uvchr
+# define toUPPER_uvchr(c, s, l) UNI_TO_NATIVE(to_uni_upper(NATIVE_TO_UNI(c), s, l))
+#endif
+
+#ifndef toTITLE_uvchr
+# define toTITLE_uvchr(c, s, l) UNI_TO_NATIVE(to_uni_title(NATIVE_TO_UNI(c), s, l))
+#endif
+
+#ifndef toFOLD_uvchr
+# define toFOLD_uvchr(c, s, l) UNI_TO_NATIVE(to_uni_fold( NATIVE_TO_UNI(c), s, l))
+#endif
+
+# if (PERL_BCDVERSION != 0x5015006) /* Just this version is broken */
+
+ /* Prefer the macro to the function */
+# if defined toLOWER_utf8
+# define D_PPP_TO_LOWER_CALLEE(s,r,l) toLOWER_utf8(s,r,l)
+# else
+# define D_PPP_TO_LOWER_CALLEE(s,r,l) to_utf8_lower(s,r,l)
+# endif
+# if defined toTITLE_utf8
+# define D_PPP_TO_TITLE_CALLEE(s,r,l) toTITLE_utf8(s,r,l)
+# else
+# define D_PPP_TO_TITLE_CALLEE(s,r,l) to_utf8_title(s,r,l)
+# endif
+# if defined toUPPER_utf8
+# define D_PPP_TO_UPPER_CALLEE(s,r,l) toUPPER_utf8(s,r,l)
+# else
+# define D_PPP_TO_UPPER_CALLEE(s,r,l) to_utf8_upper(s,r,l)
+# endif
+# if defined toFOLD_utf8
+# define D_PPP_TO_FOLD_CALLEE(s,r,l) toFOLD_utf8(s,r,l)
+# else
+# define D_PPP_TO_FOLD_CALLEE(s,r,l) to_utf8_fold(s,r,l)
+# endif
+# else /* Below is 5.15.6, which failed to make the macros available
+# outside of core, so we have to use the 'Perl_' form. khw
+# decided it was easier to just handle this case than have to
+# document the exception, and make an exception in the tests below
+# */
+# define D_PPP_TO_LOWER_CALLEE(s,r,l) \
+ Perl__to_utf8_lower_flags(aTHX_ s, r, l, 0, NULL)
+# define D_PPP_TO_TITLE_CALLEE(s,r,l) \
+ Perl__to_utf8_title_flags(aTHX_ s, r, l, 0, NULL)
+# define D_PPP_TO_UPPER_CALLEE(s,r,l) \
+ Perl__to_utf8_upper_flags(aTHX_ s, r, l, 0, NULL)
+# define D_PPP_TO_FOLD_CALLEE(s,r,l) \
+ Perl__to_utf8_fold_flags(aTHX_ s, r, l, FOLD_FLAGS_FULL, NULL)
+# endif
+
+/* The actual implementation of the backported macros. If too short, croak,
+ * otherwise call the original that doesn't have an upper limit parameter */
+# define D_PPP_GENERIC_MULTI_ARG_TO(name, s, e,r,l) \
+ (((((e) - (s)) <= 0) \
+ /* We could just do nothing, but modern perls croak */ \
+ ? (croak("Attempting case change on zero length string"), \
+ 0) /* So looks like it returns something, and will compile */ \
+ : ((e) - (s)) < UTF8SKIP(s)) \
+ ? (croak(D_PPP_TOO_SHORT_MSG, \
+ s[0], (int) ((e) - (s)), (int) UTF8SKIP(s)), \
+ 0) \
+ : D_PPP_TO_ ## name ## _CALLEE(s,r,l))
+#ifndef toUPPER_utf8_safe
+# define toUPPER_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_MULTI_ARG_TO(UPPER,s,e,r,l)
+#endif
+
+#ifndef toLOWER_utf8_safe
+# define toLOWER_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_MULTI_ARG_TO(LOWER,s,e,r,l)
+#endif
+
+#ifndef toTITLE_utf8_safe
+# define toTITLE_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_MULTI_ARG_TO(TITLE,s,e,r,l)
+#endif
+
+#ifndef toFOLD_utf8_safe
+# define toFOLD_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_MULTI_ARG_TO(FOLD,s,e,r,l)
+#endif
+
+#elif (PERL_BCDVERSION >= 0x5006000)
+
+/* Here we have UTF-8 support, but using the original API where the case
+ * changing functions merely returned the changed code point; hence they
+ * couldn't handle multi-character results. */
+
+# ifdef uvchr_to_utf8
+# define D_PPP_UV_TO_UTF8 uvchr_to_utf8
+# else
+# define D_PPP_UV_TO_UTF8 uv_to_utf8
+# endif
+
+ /* Get the utf8 of the case changed value, and store its length; then have
+ * to re-calculate the changed case value in order to return it */
+# define D_PPP_GENERIC_SINGLE_ARG_TO_UVCHR(name, c, s, l) \
+ (*(l) = (D_PPP_UV_TO_UTF8(s, \
+ UNI_TO_NATIVE(to_uni_ ## name(NATIVE_TO_UNI(c)))) - (s)), \
+ UNI_TO_NATIVE(to_uni_ ## name(NATIVE_TO_UNI(c))))
+#ifndef toLOWER_uvchr
+# define toLOWER_uvchr(c, s, l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UVCHR(lower, c, s, l)
+#endif
+
+#ifndef toUPPER_uvchr
+# define toUPPER_uvchr(c, s, l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UVCHR(upper, c, s, l)
+#endif
+
+#ifndef toTITLE_uvchr
+# define toTITLE_uvchr(c, s, l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UVCHR(title, c, s, l)
+#endif
+
+#ifndef toFOLD_uvchr
+# define toFOLD_uvchr(c, s, l) toLOWER_uvchr(c, s, l)
+#endif
+
+# define D_PPP_GENERIC_SINGLE_ARG_TO_UTF8(name, s, e, r, l) \
+ (((((e) - (s)) <= 0) \
+ ? (croak("Attempting case change on zero length string"), \
+ 0) /* So looks like it returns something, and will compile */ \
+ : ((e) - (s)) < UTF8SKIP(s)) \
+ ? (croak(D_PPP_TOO_SHORT_MSG, \
+ s[0], (int) ((e) - (s)), (int) UTF8SKIP(s)), \
+ 0) \
+ /* Get the changed code point and store its UTF-8 */ \
+ : D_PPP_UV_TO_UTF8(r, to_utf8_ ## name(s)), \
+ /* Then store its length, and re-get code point for return */ \
+ *(l) = UTF8SKIP(r), to_utf8_ ## name(r))
+
+/* Warning: toUPPER_utf8_safe, toLOWER_utf8_safe, toTITLE_utf8_safe,
+ * toUPPER_uvchr, toLOWER_uvchr, toTITLE_uvchr
+ The UTF-8 case changing operations had bugs before around 5.12 or 5.14;
+ this backport does not correct them.
+
+ In perls before 7.3, multi-character case changing is not implemented; this
+ backport uses the simple case changes available in those perls. */
+#ifndef toUPPER_utf8_safe
+# define toUPPER_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UTF8(upper, s, e, r, l)
+#endif
+
+#ifndef toLOWER_utf8_safe
+# define toLOWER_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UTF8(lower, s, e, r, l)
+#endif
+
+#ifndef toTITLE_utf8_safe
+# define toTITLE_utf8_safe(s,e,r,l) \
+ D_PPP_GENERIC_SINGLE_ARG_TO_UTF8(title, s, e, r, l)
+#endif
+
+ /* Warning: toFOLD_utf8_safe, toFOLD_uvchr
+ The UTF-8 case changing operations had bugs before around 5.12 or 5.14;
+ this backport does not correct them.
+
+ In perls before 7.3, case folding is not implemented; instead, this
+ backport substitutes simple (not multi-character, which isn't available)
+ lowercasing. This gives the correct result in most, but not all, instances
+ */
+#ifndef toFOLD_utf8_safe
+# define toFOLD_utf8_safe(s,e,r,l) toLOWER_utf8_safe(s,e,r,l)
+#endif
+
+#endif
+
+/* Until we figure out how to support this in older perls... */
+#if (PERL_BCDVERSION >= 0x5008000)
+#ifndef HeUTF8
+# define HeUTF8(he) ((HeKLEN(he) == HEf_SVKEY) ? \
+ SvUTF8(HeKEY_sv(he)) : \
+ (U32)HeKUTF8(he))
+#endif
+
+#endif
+#ifndef C_ARRAY_LENGTH
+# define C_ARRAY_LENGTH(a) (sizeof(a)/sizeof((a)[0]))
+#endif
+
+#ifndef C_ARRAY_END
+# define C_ARRAY_END(a) ((a) + C_ARRAY_LENGTH(a))
+#endif
+#ifndef LIKELY
+# define LIKELY(x) (x)
+#endif
+
+#ifndef UNLIKELY
+# define UNLIKELY(x) (x)
+#endif
+
+#ifndef MUTABLE_PTR
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define MUTABLE_PTR(p) ({ void *_p = (p); _p; })
+#else
+# define MUTABLE_PTR(p) ((void *) (p))
+#endif
+#endif
+#ifndef MUTABLE_AV
+# define MUTABLE_AV(p) ((AV *)MUTABLE_PTR(p))
+#endif
+
+#ifndef MUTABLE_CV
+# define MUTABLE_CV(p) ((CV *)MUTABLE_PTR(p))
+#endif
+
+#ifndef MUTABLE_GV
+# define MUTABLE_GV(p) ((GV *)MUTABLE_PTR(p))
+#endif
+
+#ifndef MUTABLE_HV
+# define MUTABLE_HV(p) ((HV *)MUTABLE_PTR(p))
+#endif
+
+#ifndef MUTABLE_IO
+# define MUTABLE_IO(p) ((IO *)MUTABLE_PTR(p))
+#endif
+
+#ifndef MUTABLE_SV
+# define MUTABLE_SV(p) ((SV *)MUTABLE_PTR(p))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(vnewSVpvf)
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define vnewSVpvf(pat, args) ({ SV *_sv = newSV(0); sv_vsetpvfn(_sv, (pat), strlen((pat)), (args), Null(SV**), 0, Null(bool*)); _sv; })
+#else
+# define vnewSVpvf(pat, args) ((PL_Sv = newSV(0)), sv_vsetpvfn(PL_Sv, (pat), strlen((pat)), (args), Null(SV**), 0, Null(bool*)), PL_Sv)
+#endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf)
+# define sv_vcatpvf(sv, pat, args) sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf)
+# define sv_vsetpvf(sv, pat, args) sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*))
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg)
+#if defined(NEED_sv_catpvf_mg)
+static void DPPP_(my_sv_catpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...);
+#endif
+
+#if defined(NEED_sv_catpvf_mg) || defined(NEED_sv_catpvf_mg_GLOBAL)
+
+#define Perl_sv_catpvf_mg DPPP_(my_sv_catpvf_mg)
+
+
+void
+DPPP_(my_sv_catpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_catpvf_mg_nocontext)
+#if defined(NEED_sv_catpvf_mg_nocontext)
+static void DPPP_(my_sv_catpvf_mg_nocontext)(SV * const sv, const char * const pat, ...);
+static
+#else
+extern void DPPP_(my_sv_catpvf_mg_nocontext)(SV * const sv, const char * const pat, ...);
+#endif
+
+#if defined(NEED_sv_catpvf_mg_nocontext) || defined(NEED_sv_catpvf_mg_nocontext_GLOBAL)
+
+#define sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+#define Perl_sv_catpvf_mg_nocontext DPPP_(my_sv_catpvf_mg_nocontext)
+
+
+void
+DPPP_(my_sv_catpvf_mg_nocontext)(SV * const sv, const char * const pat, ...)
+{
+ dTHX;
+ va_list args;
+ va_start(args, pat);
+ sv_vcatpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_catpvf_mg depends on sv_catpvf_mg_nocontext */
+#ifndef sv_catpvf_mg
+# ifdef PERL_IMPLICIT_CONTEXT
+# define sv_catpvf_mg Perl_sv_catpvf_mg_nocontext
+# else
+# define sv_catpvf_mg Perl_sv_catpvf_mg
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vcatpvf_mg)
+# define sv_vcatpvf_mg(sv, pat, args) \
+ STMT_START { \
+ sv_vcatpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \
+ SvSETMAGIC(sv); \
+ } STMT_END
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg)
+#if defined(NEED_sv_setpvf_mg)
+static void DPPP_(my_sv_setpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...);
+#endif
+
+#if defined(NEED_sv_setpvf_mg) || defined(NEED_sv_setpvf_mg_GLOBAL)
+
+#define Perl_sv_setpvf_mg DPPP_(my_sv_setpvf_mg)
+
+
+void
+DPPP_(my_sv_setpvf_mg)(pTHX_ SV * const sv, const char * const pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+
+#ifdef PERL_IMPLICIT_CONTEXT
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_setpvf_mg_nocontext)
+#if defined(NEED_sv_setpvf_mg_nocontext)
+static void DPPP_(my_sv_setpvf_mg_nocontext)(SV * const sv, const char * const pat, ...);
+static
+#else
+extern void DPPP_(my_sv_setpvf_mg_nocontext)(SV * const sv, const char * const pat, ...);
+#endif
+
+#if defined(NEED_sv_setpvf_mg_nocontext) || defined(NEED_sv_setpvf_mg_nocontext_GLOBAL)
+
+#define sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+#define Perl_sv_setpvf_mg_nocontext DPPP_(my_sv_setpvf_mg_nocontext)
+
+
+void
+DPPP_(my_sv_setpvf_mg_nocontext)(SV * const sv, const char * const pat, ...)
+{
+ dTHX;
+ va_list args;
+ va_start(args, pat);
+ sv_vsetpvfn(sv, pat, strlen(pat), &args, Null(SV**), 0, Null(bool*));
+ SvSETMAGIC(sv);
+ va_end(args);
+}
+
+#endif
+#endif
+#endif
+
+/* sv_setpvf_mg depends on sv_setpvf_mg_nocontext */
+#ifndef sv_setpvf_mg
+# ifdef PERL_IMPLICIT_CONTEXT
+# define sv_setpvf_mg Perl_sv_setpvf_mg_nocontext
+# else
+# define sv_setpvf_mg Perl_sv_setpvf_mg
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(sv_vsetpvf_mg)
+# define sv_vsetpvf_mg(sv, pat, args) \
+ STMT_START { \
+ sv_vsetpvfn(sv, pat, strlen(pat), args, Null(SV**), 0, Null(bool*)); \
+ SvSETMAGIC(sv); \
+ } STMT_END
+#endif
+
+/* Hint: sv_2pv_nolen
+ * Use the SvPV_nolen() or SvPV_nolen_const() macros instead of sv_2pv_nolen().
+ */
+#ifndef sv_2pv_nolen
+# define sv_2pv_nolen(sv) SvPV_nolen(sv)
+#endif
+
+#ifdef SvPVbyte
+
+/* Hint: SvPVbyte
+ * Does not work in perl-5.6.1, ppport.h implements a version
+ * borrowed from perl-5.7.3.
+ */
+
+#if (PERL_BCDVERSION < 0x5007000)
+#ifndef sv_2pvbyte
+# define sv_2pvbyte(sv, lp) (sv_utf8_downgrade((sv), 0), SvPV((sv), *(lp)))
+#endif
+
+/* Hint: sv_2pvbyte
+ * Use the SvPVbyte() macro instead of sv_2pvbyte().
+ */
+
+/* Replace sv_2pvbyte with SvPVbyte */
+
+#undef SvPVbyte
+
+#define SvPVbyte(sv, lp) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_UTF8)) == (SVf_POK) \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pvbyte(sv, &lp))
+
+#endif
+
+#else
+
+# define SvPVbyte SvPV
+# define sv_2pvbyte sv_2pv
+
+#endif
+#ifndef sv_2pvbyte_nolen
+# define sv_2pvbyte_nolen(sv) sv_2pv_nolen(sv)
+#endif
+
+/* Hint: sv_pvn
+ * Always use the SvPV() macro instead of sv_pvn().
+ */
+
+/* Replace sv_pvn with SvPV */
+
+/* Hint: sv_pvn_force
+ * Always use the SvPV_force() macro instead of sv_pvn_force().
+ */
+
+/* Replace sv_pvn_force with SvPV_force */
+
+/* If these are undefined, they're not handled by the core anyway */
+#ifndef SV_IMMEDIATE_UNREF
+# define SV_IMMEDIATE_UNREF 0
+#endif
+
+#ifndef SV_GMAGIC
+# define SV_GMAGIC 0
+#endif
+
+#ifndef SV_COW_DROP_PV
+# define SV_COW_DROP_PV 0
+#endif
+
+#ifndef SV_UTF8_NO_ENCODING
+# define SV_UTF8_NO_ENCODING 0
+#endif
+
+#ifndef SV_CONST_RETURN
+# define SV_CONST_RETURN 0
+#endif
+
+#ifndef SV_MUTABLE_RETURN
+# define SV_MUTABLE_RETURN 0
+#endif
+
+#ifndef SV_SMAGIC
+# define SV_SMAGIC 0
+#endif
+
+#ifndef SV_HAS_TRAILING_NUL
+# define SV_HAS_TRAILING_NUL 0
+#endif
+
+#ifndef SV_COW_SHARED_HASH_KEYS
+# define SV_COW_SHARED_HASH_KEYS 0
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef sv_2pv_flags
+# define sv_2pv_flags(sv, lp, flags) ({ SV *_sv = (sv); const I32 _flags = (flags); STRLEN *_lp = lp; _lp = _lp ? : &PL_na; (!(_flags & SV_GMAGIC) && SvGMAGICAL(_sv)) ? ({ char *_pv; SvGMAGICAL_off(_sv); _pv = sv_2pv(_sv, _lp); SvGMAGICAL_on(_sv); _pv; }) : sv_2pv(_sv, _lp); })
+#endif
+
+#ifndef sv_pvn_force_flags
+# define sv_pvn_force_flags(sv, lp, flags) ({ SV *_sv = (sv); const I32 _flags = (flags); STRLEN *_lp = lp; _lp = _lp ? : &PL_na; (!(_flags & SV_GMAGIC) && SvGMAGICAL(_sv)) ? ({ char *_pv; SvGMAGICAL_off(_sv); _pv = sv_pvn_force(_sv, _lp); SvGMAGICAL_on(_sv); _pv; }) : sv_pvn_force(_sv, _lp); })
+#endif
+
+#else
+#ifndef sv_2pv_flags
+# define sv_2pv_flags(sv, lp, flags) ((PL_Sv = (sv)), (!((flags) & SV_GMAGIC) && SvGMAGICAL(PL_Sv)) ? (SvGMAGICAL_off(PL_Sv), (PL_Xpv = (XPV *)sv_2pv(PL_Sv, (lp) ? (lp) : &PL_na)), SvGMAGICAL_on(PL_Sv), (char *)PL_Xpv) : sv_2pv(PL_Sv, (lp) ? (lp) : &PL_na))
+#endif
+
+#ifndef sv_pvn_force_flags
+# define sv_pvn_force_flags(sv, lp, flags) ((PL_Sv = (sv)), (!((flags) & SV_GMAGIC) && SvGMAGICAL(PL_Sv)) ? (SvGMAGICAL_off(PL_Sv), (PL_Xpv = (XPV *)sv_pvn_force(PL_Sv, (lp) ? (lp) : &PL_na)), SvGMAGICAL_on(PL_Sv), (char *)PL_Xpv) : sv_pvn_force(PL_Sv, (lp) ? (lp) : &PL_na))
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION < 0x5008008) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009003) )
+# define D_PPP_SVPV_NOLEN_LP_ARG &PL_na
+#else
+# define D_PPP_SVPV_NOLEN_LP_ARG 0
+#endif
+#ifndef SvPV_const
+# define SvPV_const(sv, lp) SvPV_flags_const(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_mutable
+# define SvPV_mutable(sv, lp) SvPV_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+#ifndef SvPV_flags
+# define SvPV_flags(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_2pv_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_flags_const
+# define SvPV_flags_const(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_const(sv)) : \
+ (const char*) sv_2pv_flags(sv, &lp, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_const_nolen
+# define SvPV_flags_const_nolen(sv, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX_const(sv) : \
+ (const char*) sv_2pv_flags(sv, D_PPP_SVPV_NOLEN_LP_ARG, flags|SV_CONST_RETURN))
+#endif
+#ifndef SvPV_flags_mutable
+# define SvPV_flags_mutable(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) : \
+ sv_2pv_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_force
+# define SvPV_force(sv, lp) SvPV_force_flags(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nolen
+# define SvPV_force_nolen(sv) SvPV_force_flags_nolen(sv, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_mutable
+# define SvPV_force_mutable(sv, lp) SvPV_force_flags_mutable(sv, lp, SV_GMAGIC)
+#endif
+
+#ifndef SvPV_force_nomg
+# define SvPV_force_nomg(sv, lp) SvPV_force_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_force_nomg_nolen
+# define SvPV_force_nomg_nolen(sv) SvPV_force_flags_nolen(sv, 0)
+#endif
+#ifndef SvPV_force_flags
+# define SvPV_force_flags(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX(sv)) : sv_pvn_force_flags(sv, &lp, flags))
+#endif
+#ifndef SvPV_force_flags_nolen
+# define SvPV_force_flags_nolen(sv, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? SvPVX(sv) : sv_pvn_force_flags(sv, D_PPP_SVPV_NOLEN_LP_ARG, flags))
+#endif
+#ifndef SvPV_force_flags_mutable
+# define SvPV_force_flags_mutable(sv, lp, flags) \
+ ((SvFLAGS(sv) & (SVf_POK|SVf_THINKFIRST)) == SVf_POK \
+ ? ((lp = SvCUR(sv)), SvPVX_mutable(sv)) \
+ : sv_pvn_force_flags(sv, &lp, flags|SV_MUTABLE_RETURN))
+#endif
+#ifndef SvPV_nolen
+# define SvPV_nolen(sv) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX(sv) : sv_2pv_flags(sv, D_PPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC))
+#endif
+#ifndef SvPV_nolen_const
+# define SvPV_nolen_const(sv) \
+ ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX_const(sv) : sv_2pv_flags(sv, D_PPP_SVPV_NOLEN_LP_ARG, SV_GMAGIC|SV_CONST_RETURN))
+#endif
+
+# if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef SvPVx_nolen_const
+# define SvPVx_nolen_const(sv) ({SV *sV_ = (sv); SvPV_nolen_const(sV_); })
+#endif
+
+# else
+#ifndef SvPVx_nolen_const
+# define SvPVx_nolen_const(sv) (PL_Sv = sv, SvPV_nolen_const(PL_Sv))
+#endif
+
+# endif
+#ifndef SvPV_nomg
+# define SvPV_nomg(sv, lp) SvPV_flags(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const
+# define SvPV_nomg_const(sv, lp) SvPV_flags_const(sv, lp, 0)
+#endif
+
+#ifndef SvPV_nomg_const_nolen
+# define SvPV_nomg_const_nolen(sv) SvPV_flags_const_nolen(sv, 0)
+#endif
+
+#ifndef SvPV_nomg_nolen
+# define SvPV_nomg_nolen(sv) ((SvFLAGS(sv) & (SVf_POK)) == SVf_POK \
+ ? SvPVX(sv) : sv_2pv_flags(sv, D_PPP_SVPV_NOLEN_LP_ARG, 0))
+#endif
+#ifndef SvPV_renew
+# define SvPV_renew(sv,n) STMT_START { SvLEN_set(sv, n); \
+ SvPV_set((sv), (char *) saferealloc( \
+ (Malloc_t)SvPVX(sv), (MEM_SIZE)((n)))); \
+ } STMT_END
+#endif
+#ifndef SvPVCLEAR
+# define SvPVCLEAR(sv) sv_setpvs((sv), "")
+#endif
+#ifndef WARN_ALL
+# define WARN_ALL 0
+#endif
+
+#ifndef WARN_CLOSURE
+# define WARN_CLOSURE 1
+#endif
+
+#ifndef WARN_DEPRECATED
+# define WARN_DEPRECATED 2
+#endif
+
+#ifndef WARN_EXITING
+# define WARN_EXITING 3
+#endif
+
+#ifndef WARN_GLOB
+# define WARN_GLOB 4
+#endif
+
+#ifndef WARN_IO
+# define WARN_IO 5
+#endif
+
+#ifndef WARN_CLOSED
+# define WARN_CLOSED 6
+#endif
+
+#ifndef WARN_EXEC
+# define WARN_EXEC 7
+#endif
+
+#ifndef WARN_LAYER
+# define WARN_LAYER 8
+#endif
+
+#ifndef WARN_NEWLINE
+# define WARN_NEWLINE 9
+#endif
+
+#ifndef WARN_PIPE
+# define WARN_PIPE 10
+#endif
+
+#ifndef WARN_UNOPENED
+# define WARN_UNOPENED 11
+#endif
+
+#ifndef WARN_MISC
+# define WARN_MISC 12
+#endif
+
+#ifndef WARN_NUMERIC
+# define WARN_NUMERIC 13
+#endif
+
+#ifndef WARN_ONCE
+# define WARN_ONCE 14
+#endif
+
+#ifndef WARN_OVERFLOW
+# define WARN_OVERFLOW 15
+#endif
+
+#ifndef WARN_PACK
+# define WARN_PACK 16
+#endif
+
+#ifndef WARN_PORTABLE
+# define WARN_PORTABLE 17
+#endif
+
+#ifndef WARN_RECURSION
+# define WARN_RECURSION 18
+#endif
+
+#ifndef WARN_REDEFINE
+# define WARN_REDEFINE 19
+#endif
+
+#ifndef WARN_REGEXP
+# define WARN_REGEXP 20
+#endif
+
+#ifndef WARN_SEVERE
+# define WARN_SEVERE 21
+#endif
+
+#ifndef WARN_DEBUGGING
+# define WARN_DEBUGGING 22
+#endif
+
+#ifndef WARN_INPLACE
+# define WARN_INPLACE 23
+#endif
+
+#ifndef WARN_INTERNAL
+# define WARN_INTERNAL 24
+#endif
+
+#ifndef WARN_MALLOC
+# define WARN_MALLOC 25
+#endif
+
+#ifndef WARN_SIGNAL
+# define WARN_SIGNAL 26
+#endif
+
+#ifndef WARN_SUBSTR
+# define WARN_SUBSTR 27
+#endif
+
+#ifndef WARN_SYNTAX
+# define WARN_SYNTAX 28
+#endif
+
+#ifndef WARN_AMBIGUOUS
+# define WARN_AMBIGUOUS 29
+#endif
+
+#ifndef WARN_BAREWORD
+# define WARN_BAREWORD 30
+#endif
+
+#ifndef WARN_DIGIT
+# define WARN_DIGIT 31
+#endif
+
+#ifndef WARN_PARENTHESIS
+# define WARN_PARENTHESIS 32
+#endif
+
+#ifndef WARN_PRECEDENCE
+# define WARN_PRECEDENCE 33
+#endif
+
+#ifndef WARN_PRINTF
+# define WARN_PRINTF 34
+#endif
+
+#ifndef WARN_PROTOTYPE
+# define WARN_PROTOTYPE 35
+#endif
+
+#ifndef WARN_QW
+# define WARN_QW 36
+#endif
+
+#ifndef WARN_RESERVED
+# define WARN_RESERVED 37
+#endif
+
+#ifndef WARN_SEMICOLON
+# define WARN_SEMICOLON 38
+#endif
+
+#ifndef WARN_TAINT
+# define WARN_TAINT 39
+#endif
+
+#ifndef WARN_THREADS
+# define WARN_THREADS 40
+#endif
+
+#ifndef WARN_UNINITIALIZED
+# define WARN_UNINITIALIZED 41
+#endif
+
+#ifndef WARN_UNPACK
+# define WARN_UNPACK 42
+#endif
+
+#ifndef WARN_UNTIE
+# define WARN_UNTIE 43
+#endif
+
+#ifndef WARN_UTF8
+# define WARN_UTF8 44
+#endif
+
+#ifndef WARN_VOID
+# define WARN_VOID 45
+#endif
+
+#ifndef WARN_ASSERTIONS
+# define WARN_ASSERTIONS 46
+#endif
+#ifndef packWARN
+# define packWARN(a) (a)
+#endif
+
+#ifndef packWARN2
+# define packWARN2(a,b) (packWARN(a) << 8 | (b))
+#endif
+
+#ifndef packWARN3
+# define packWARN3(a,b,c) (packWARN2(a,b) << 8 | (c))
+#endif
+
+#ifndef packWARN4
+# define packWARN4(a,b,c,d) (packWARN3(a,b,c) << 8 | (d))
+#endif
+
+#ifndef ckWARN
+# ifdef G_WARN_ON
+# define ckWARN(a) (PL_dowarn & G_WARN_ON)
+# else
+# define ckWARN(a) PL_dowarn
+# endif
+#endif
+#ifndef ckWARN2
+# define ckWARN2(a,b) (ckWARN(a) || ckWARN(b))
+#endif
+
+#ifndef ckWARN3
+# define ckWARN3(a,b,c) (ckWARN(c) || ckWARN2(a,b))
+#endif
+
+#ifndef ckWARN4
+# define ckWARN4(a,b,c,d) (ckWARN(d) || ckWARN3(a,b,c))
+#endif
+
+#ifndef ckWARN_d
+# ifdef isLEXWARN_off
+# define ckWARN_d(a) (isLEXWARN_off || ckWARN(a))
+# else
+# define ckWARN_d(a) 1
+# endif
+#endif
+#ifndef ckWARN2_d
+# define ckWARN2_d(a,b) (ckWARN_d(a) || ckWARN_d(b))
+#endif
+
+#ifndef ckWARN3_d
+# define ckWARN3_d(a,b,c) (ckWARN_d(c) || ckWARN2_d(a,b))
+#endif
+
+#ifndef ckWARN4_d
+# define ckWARN4_d(a,b,c,d) (ckWARN_d(d) || ckWARN3_d(a,b,c))
+#endif
+#ifndef vwarner
+# define vwarner(err, pat, argsp) \
+ STMT_START { SV *sv; \
+ PERL_UNUSED_ARG(err); \
+ sv = vnewSVpvf(pat, argsp); \
+ sv_2mortal(sv); \
+ warn("%s", SvPV_nolen(sv)); \
+ } STMT_END
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(warner)
+# if defined(NEED_warner)
+static void DPPP_(my_warner)(U32 err, const char * pat, ...);
+static
+#else
+extern void DPPP_(my_warner)(U32 err, const char * pat, ...);
+#endif
+
+#if defined(NEED_warner) || defined(NEED_warner_GLOBAL)
+
+#define Perl_warner DPPP_(my_warner)
+
+
+void
+DPPP_(my_warner)(U32 err, const char *pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ vwarner(err, pat, &args);
+ va_end(args);
+}
+
+# define warner Perl_warner
+
+# define Perl_warner_nocontext Perl_warner
+
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(ck_warner)
+# if defined(NEED_ck_warner)
+static void DPPP_(my_ck_warner)(pTHX_ U32 err, const char * pat, ...);
+static
+#else
+extern void DPPP_(my_ck_warner)(pTHX_ U32 err, const char * pat, ...);
+#endif
+
+#if defined(NEED_ck_warner) || defined(NEED_ck_warner_GLOBAL)
+
+#define Perl_ck_warner DPPP_(my_ck_warner)
+
+
+void
+DPPP_(my_ck_warner)(pTHX_ U32 err, const char *pat, ...)
+{
+ va_list args;
+
+ if ( ! ckWARN((err ) & 0xFF)
+ && ! ckWARN((err >> 8) & 0xFF)
+ && ! ckWARN((err >> 16) & 0xFF)
+ && ! ckWARN((err >> 24) & 0xFF))
+ {
+ return;
+ }
+
+ va_start(args, pat);
+ vwarner(err, pat, &args);
+ va_end(args);
+}
+
+# define ck_warner Perl_ck_warner
+# endif
+#endif
+
+#if (PERL_BCDVERSION >= 0x5004000) && !defined(ck_warner_d)
+# if defined(NEED_ck_warner_d)
+static void DPPP_(my_ck_warner_d)(pTHX_ U32 err, const char * pat, ...);
+static
+#else
+extern void DPPP_(my_ck_warner_d)(pTHX_ U32 err, const char * pat, ...);
+#endif
+
+#if defined(NEED_ck_warner_d) || defined(NEED_ck_warner_d_GLOBAL)
+
+#define Perl_ck_warner_d DPPP_(my_ck_warner_d)
+
+
+void
+DPPP_(my_ck_warner_d)(pTHX_ U32 err, const char *pat, ...)
+{
+ va_list args;
+
+ if ( ! ckWARN_d((err ) & 0xFF)
+ && ! ckWARN_d((err >> 8) & 0xFF)
+ && ! ckWARN_d((err >> 16) & 0xFF)
+ && ! ckWARN_d((err >> 24) & 0xFF))
+ {
+ return;
+ }
+
+ va_start(args, pat);
+ vwarner(err, pat, &args);
+ va_end(args);
+}
+
+# define ck_warner_d Perl_ck_warner_d
+
+
+# endif
+#endif
+
+#ifndef IVdf
+# if IVSIZE == LONGSIZE
+# define IVdf "ld"
+# define UVuf "lu"
+# define UVof "lo"
+# define UVxf "lx"
+# define UVXf "lX"
+# elif IVSIZE == INTSIZE
+# define IVdf "d"
+# define UVuf "u"
+# define UVof "o"
+# define UVxf "x"
+# define UVXf "X"
+# else
+# error "cannot define IV/UV formats"
+# endif
+#endif
+
+#ifndef NVef
+# if defined(USE_LONG_DOUBLE) && defined(HAS_LONG_DOUBLE) && \
+ defined(PERL_PRIfldbl) && (PERL_BCDVERSION != 0x5006000)
+ /* Not very likely, but let's try anyway. */
+# define NVef PERL_PRIeldbl
+# define NVff PERL_PRIfldbl
+# define NVgf PERL_PRIgldbl
+# else
+# define NVef "e"
+# define NVff "f"
+# define NVgf "g"
+# endif
+#endif
+#ifndef sv_setuv
+# define sv_setuv(sv, uv) \
+ STMT_START { \
+ UV TeMpUv = uv; \
+ if (TeMpUv <= IV_MAX) \
+ sv_setiv(sv, TeMpUv); \
+ else \
+ sv_setnv(sv, (double)TeMpUv); \
+ } STMT_END
+#endif
+#ifndef newSVuv
+# define newSVuv(uv) ((uv) <= IV_MAX ? newSViv((IV)uv) : newSVnv((NV)uv))
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef sv_2uv
+# define sv_2uv(sv) ({ SV *_sv = (sv); (UV) (SvNOK(_sv) ? SvNV(_sv) : sv_2nv(_sv)); })
+#endif
+
+#else
+#ifndef sv_2uv
+# define sv_2uv(sv) ((PL_Sv = (sv)), (UV) (SvNOK(PL_Sv) ? SvNV(PL_Sv) : sv_2nv(PL_Sv)))
+#endif
+
+#endif
+#ifndef SvUVX
+# define SvUVX(sv) ((UV)SvIVX(sv))
+#endif
+
+#ifndef SvUVXx
+# define SvUVXx(sv) SvUVX(sv)
+#endif
+
+#ifndef SvUV
+# define SvUV(sv) (SvIOK(sv) ? SvUVX(sv) : sv_2uv(sv))
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef SvUVx
+# define SvUVx(sv) ({ SV *_sv = (sv)); SvUV(_sv); })
+#endif
+
+#else
+#ifndef SvUVx
+# define SvUVx(sv) ((PL_Sv = (sv)), SvUV(PL_Sv))
+#endif
+
+#endif
+
+/* Hint: sv_uv
+ * Always use the SvUVx() macro instead of sv_uv().
+ */
+/* Replace sv_uv with SvUVx */
+#ifndef sv_uv
+# define sv_uv(sv) SvUVx(sv)
+#endif
+
+#if !defined(SvUOK) && defined(SvIOK_UV)
+# define SvUOK(sv) SvIOK_UV(sv)
+#endif
+#ifndef XST_mUV
+# define XST_mUV(i,v) (ST(i) = sv_2mortal(newSVuv(v)) )
+#endif
+
+#ifndef XSRETURN_UV
+# define XSRETURN_UV(v) STMT_START { XST_mUV(0,v); XSRETURN(1); } STMT_END
+#endif
+#ifndef PUSHu
+# define PUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); PUSHTARG; } STMT_END
+#endif
+
+#ifndef XPUSHu
+# define XPUSHu(u) STMT_START { sv_setuv(TARG, (UV)(u)); XPUSHTARG; } STMT_END
+#endif
+
+#if !defined(my_strnlen)
+#if defined(NEED_my_strnlen)
+static Size_t DPPP_(my_my_strnlen)(const char * str, Size_t maxlen);
+static
+#else
+extern Size_t DPPP_(my_my_strnlen)(const char * str, Size_t maxlen);
+#endif
+
+#if defined(NEED_my_strnlen) || defined(NEED_my_strnlen_GLOBAL)
+
+#define my_strnlen DPPP_(my_my_strnlen)
+#define Perl_my_strnlen DPPP_(my_my_strnlen)
+
+
+Size_t
+DPPP_(my_my_strnlen)(const char *str, Size_t maxlen)
+{
+ const char *p = str;
+
+ while(maxlen-- && *p)
+ p++;
+
+ return p - str;
+}
+
+#endif
+#endif
+
+#ifdef HAS_MEMCMP
+#ifndef memNE
+# define memNE(s1,s2,l) (memcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+# define memEQ(s1,s2,l) (!memcmp(s1,s2,l))
+#endif
+
+#else
+#ifndef memNE
+# define memNE(s1,s2,l) (bcmp(s1,s2,l))
+#endif
+
+#ifndef memEQ
+# define memEQ(s1,s2,l) (!bcmp(s1,s2,l))
+#endif
+
+#endif
+#ifndef memEQs
+# define memEQs(s1, l, s2) \
+ (sizeof(s2)-1 == l && memEQ(s1, (s2 ""), (sizeof(s2)-1)))
+#endif
+
+#ifndef memNEs
+# define memNEs(s1, l, s2) !memEQs(s1, l, s2)
+#endif
+#ifndef memCHRs
+# define memCHRs(s, c) ((const char *) memchr("" s "" , c, sizeof(s)-1))
+#endif
+#ifndef MoveD
+# define MoveD(s,d,n,t) memmove((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifndef CopyD
+# define CopyD(s,d,n,t) memcpy((char*)(d),(char*)(s), (n) * sizeof(t))
+#endif
+
+#ifdef HAS_MEMSET
+#ifndef ZeroD
+# define ZeroD(d,n,t) memzero((char*)(d), (n) * sizeof(t))
+#endif
+
+#else
+#ifndef ZeroD
+# define ZeroD(d,n,t) ((void)memzero((char*)(d), (n) * sizeof(t)), d)
+#endif
+
+#endif
+#ifndef PoisonWith
+# define PoisonWith(d,n,t,b) (void)memset((char*)(d), (U8)(b), (n) * sizeof(t))
+#endif
+
+#ifndef PoisonNew
+# define PoisonNew(d,n,t) PoisonWith(d,n,t,0xAB)
+#endif
+
+#ifndef PoisonFree
+# define PoisonFree(d,n,t) PoisonWith(d,n,t,0xEF)
+#endif
+
+#ifndef Poison
+# define Poison(d,n,t) PoisonFree(d,n,t)
+#endif
+#ifndef Newx
+# define Newx(v,n,t) New(0,v,n,t)
+#endif
+
+#ifndef Newxc
+# define Newxc(v,n,t,c) Newc(0,v,n,t,c)
+#endif
+
+#ifndef Newxz
+# define Newxz(v,n,t) Newz(0,v,n,t)
+#endif
+
+#ifdef NEED_mess_sv
+#define NEED_mess
+#endif
+
+#ifdef NEED_mess
+#define NEED_mess_nocontext
+#define NEED_vmess
+#endif
+
+#ifndef croak_sv
+#if (PERL_BCDVERSION >= 0x5007003) || ( (PERL_BCDVERSION >= 0x5006001) && (PERL_BCDVERSION < 0x5007000) )
+# if ( (PERL_BCDVERSION >= 0x5008000) && (PERL_BCDVERSION < 0x5008009) ) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5010001) )
+# define D_PPP_FIX_UTF8_ERRSV_FOR_SV(sv) \
+ STMT_START { \
+ SV *_errsv = ERRSV; \
+ SvFLAGS(_errsv) = (SvFLAGS(_errsv) & ~SVf_UTF8) | \
+ (SvFLAGS(sv) & SVf_UTF8); \
+ } STMT_END
+# else
+# define D_PPP_FIX_UTF8_ERRSV_FOR_SV(sv) STMT_START {} STMT_END
+# endif
+# define croak_sv(sv) \
+ STMT_START { \
+ SV *_sv = (sv); \
+ if (SvROK(_sv)) { \
+ sv_setsv(ERRSV, _sv); \
+ croak(NULL); \
+ } else { \
+ D_PPP_FIX_UTF8_ERRSV_FOR_SV(_sv); \
+ croak("%" SVf, SVfARG(_sv)); \
+ } \
+ } STMT_END
+#elif (PERL_BCDVERSION >= 0x5004000)
+# define croak_sv(sv) croak("%" SVf, SVfARG(sv))
+#else
+# define croak_sv(sv) croak("%s", SvPV_nolen(sv))
+#endif
+#endif
+
+#ifndef die_sv
+#if defined(NEED_die_sv)
+static OP * DPPP_(my_die_sv)(pTHX_ SV * baseex);
+static
+#else
+extern OP * DPPP_(my_die_sv)(pTHX_ SV * baseex);
+#endif
+
+#if defined(NEED_die_sv) || defined(NEED_die_sv_GLOBAL)
+
+#ifdef die_sv
+# undef die_sv
+#endif
+#define die_sv(a) DPPP_(my_die_sv)(aTHX_ a)
+#define Perl_die_sv DPPP_(my_die_sv)
+
+OP *
+DPPP_(my_die_sv)(pTHX_ SV *baseex)
+{
+ croak_sv(baseex);
+ return (OP *)NULL;
+}
+#endif
+#endif
+
+#ifndef warn_sv
+#if (PERL_BCDVERSION >= 0x5004000)
+# define warn_sv(sv) warn("%" SVf, SVfARG(sv))
+#else
+# define warn_sv(sv) warn("%s", SvPV_nolen(sv))
+#endif
+#endif
+
+#if ! defined vmess && (PERL_BCDVERSION >= 0x5004000)
+# if defined(NEED_vmess)
+static SV * DPPP_(my_vmess)(pTHX_ const char * pat, va_list * args);
+static
+#else
+extern SV * DPPP_(my_vmess)(pTHX_ const char * pat, va_list * args);
+#endif
+
+#if defined(NEED_vmess) || defined(NEED_vmess_GLOBAL)
+
+#ifdef vmess
+# undef vmess
+#endif
+#define vmess(a,b) DPPP_(my_vmess)(aTHX_ a,b)
+#define Perl_vmess DPPP_(my_vmess)
+
+
+SV*
+DPPP_(my_vmess)(pTHX_ const char* pat, va_list* args)
+{
+ mess(pat, args);
+ return PL_mess_sv;
+}
+# endif
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000) && (PERL_BCDVERSION >= 0x5004000)
+#undef mess
+#endif
+
+#if !defined(mess_nocontext) && !defined(Perl_mess_nocontext) && (PERL_BCDVERSION >= 0x5004000)
+#if defined(NEED_mess_nocontext)
+static SV * DPPP_(my_mess_nocontext)(const char * pat, ...);
+static
+#else
+extern SV * DPPP_(my_mess_nocontext)(const char * pat, ...);
+#endif
+
+#if defined(NEED_mess_nocontext) || defined(NEED_mess_nocontext_GLOBAL)
+
+#define mess_nocontext DPPP_(my_mess_nocontext)
+#define Perl_mess_nocontext DPPP_(my_mess_nocontext)
+
+SV*
+DPPP_(my_mess_nocontext)(const char* pat, ...)
+{
+ dTHX;
+ SV *sv;
+ va_list args;
+ va_start(args, pat);
+ sv = vmess(pat, &args);
+ va_end(args);
+ return sv;
+}
+#endif
+#endif
+
+#ifndef mess
+#if defined(NEED_mess)
+static SV * DPPP_(my_mess)(pTHX_ const char * pat, ...);
+static
+#else
+extern SV * DPPP_(my_mess)(pTHX_ const char * pat, ...);
+#endif
+
+#if defined(NEED_mess) || defined(NEED_mess_GLOBAL)
+
+#define Perl_mess DPPP_(my_mess)
+
+SV*
+DPPP_(my_mess)(pTHX_ const char* pat, ...)
+{
+ SV *sv;
+ va_list args;
+ va_start(args, pat);
+ sv = vmess(pat, &args);
+ va_end(args);
+ return sv;
+}
+#ifdef mess_nocontext
+#define mess mess_nocontext
+#else
+#define mess Perl_mess_nocontext
+#endif
+#endif
+#endif
+
+#if ! defined mess_sv && (PERL_BCDVERSION >= 0x5004000)
+#if defined(NEED_mess_sv)
+static SV * DPPP_(my_mess_sv)(pTHX_ SV * basemsg, bool consume);
+static
+#else
+extern SV * DPPP_(my_mess_sv)(pTHX_ SV * basemsg, bool consume);
+#endif
+
+#if defined(NEED_mess_sv) || defined(NEED_mess_sv_GLOBAL)
+
+#ifdef mess_sv
+# undef mess_sv
+#endif
+#define mess_sv(a,b) DPPP_(my_mess_sv)(aTHX_ a,b)
+#define Perl_mess_sv DPPP_(my_mess_sv)
+
+SV *
+DPPP_(my_mess_sv)(pTHX_ SV *basemsg, bool consume)
+{
+ SV *tmp;
+ SV *ret;
+
+ if (SvPOK(basemsg) && SvCUR(basemsg) && *(SvEND(basemsg)-1) == '\n') {
+ if (consume)
+ return basemsg;
+ ret = mess("");
+ SvSetSV_nosteal(ret, basemsg);
+ return ret;
+ }
+
+ if (consume) {
+ sv_catsv(basemsg, mess(""));
+ return basemsg;
+ }
+
+ ret = mess("");
+ tmp = newSVsv(ret);
+ SvSetSV_nosteal(ret, basemsg);
+ sv_catsv(ret, tmp);
+ sv_dec(tmp);
+ return ret;
+}
+#endif
+#endif
+
+#ifndef warn_nocontext
+#define warn_nocontext warn
+#endif
+
+#ifndef croak_nocontext
+#define croak_nocontext croak
+#endif
+
+#ifndef croak_no_modify
+#define croak_no_modify() croak_nocontext("%s", PL_no_modify)
+#define Perl_croak_no_modify() croak_no_modify()
+#endif
+
+#ifndef croak_memory_wrap
+#if (PERL_BCDVERSION >= 0x5009002) || ( (PERL_BCDVERSION >= 0x5008006) && (PERL_BCDVERSION < 0x5009000) )
+# define croak_memory_wrap() croak_nocontext("%s", PL_memory_wrap)
+#else
+# define croak_memory_wrap() croak_nocontext("panic: memory wrap")
+#endif
+#endif
+
+#ifndef croak_xs_usage
+#if defined(NEED_croak_xs_usage)
+static void DPPP_(my_croak_xs_usage)(const CV * const cv, const char * const params);
+static
+#else
+extern void DPPP_(my_croak_xs_usage)(const CV * const cv, const char * const params);
+#endif
+
+#if defined(NEED_croak_xs_usage) || defined(NEED_croak_xs_usage_GLOBAL)
+
+#define croak_xs_usage DPPP_(my_croak_xs_usage)
+#define Perl_croak_xs_usage DPPP_(my_croak_xs_usage)
+
+#ifndef PERL_ARGS_ASSERT_CROAK_XS_USAGE
+#define PERL_ARGS_ASSERT_CROAK_XS_USAGE assert(cv); assert(params)
+
+void
+DPPP_(my_croak_xs_usage)(const CV *const cv, const char *const params)
+{
+ dTHX;
+ const GV *const gv = CvGV(cv);
+
+ PERL_ARGS_ASSERT_CROAK_XS_USAGE;
+
+ if (gv) {
+ const char *const gvname = GvNAME(gv);
+ const HV *const stash = GvSTASH(gv);
+ const char *const hvname = stash ? HvNAME(stash) : NULL;
+
+ if (hvname)
+ croak("Usage: %s::%s(%s)", hvname, gvname, params);
+ else
+ croak("Usage: %s(%s)", gvname, params);
+ } else {
+ /* Pants. I don't think that it should be possible to get here. */
+ croak("Usage: CODE(0x%" UVxf ")(%s)", PTR2UV(cv), params);
+ }
+}
+#endif
+#endif
+#endif
+#ifndef mPUSHs
+# define mPUSHs(s) PUSHs(sv_2mortal(s))
+#endif
+
+#ifndef PUSHmortal
+# define PUSHmortal PUSHs(sv_newmortal())
+#endif
+
+#ifndef mPUSHp
+# define mPUSHp(p,l) sv_setpvn(PUSHmortal, (p), (l))
+#endif
+
+#ifndef mPUSHn
+# define mPUSHn(n) sv_setnv(PUSHmortal, (NV)(n))
+#endif
+
+#ifndef mPUSHi
+# define mPUSHi(i) sv_setiv(PUSHmortal, (IV)(i))
+#endif
+
+#ifndef mPUSHu
+# define mPUSHu(u) sv_setuv(PUSHmortal, (UV)(u))
+#endif
+#ifndef mXPUSHs
+# define mXPUSHs(s) XPUSHs(sv_2mortal(s))
+#endif
+
+#ifndef XPUSHmortal
+# define XPUSHmortal XPUSHs(sv_newmortal())
+#endif
+
+#ifndef mXPUSHp
+# define mXPUSHp(p,l) STMT_START { EXTEND(sp,1); sv_setpvn(PUSHmortal, (p), (l)); } STMT_END
+#endif
+
+#ifndef mXPUSHn
+# define mXPUSHn(n) STMT_START { EXTEND(sp,1); sv_setnv(PUSHmortal, (NV)(n)); } STMT_END
+#endif
+
+#ifndef mXPUSHi
+# define mXPUSHi(i) STMT_START { EXTEND(sp,1); sv_setiv(PUSHmortal, (IV)(i)); } STMT_END
+#endif
+
+#ifndef mXPUSHu
+# define mXPUSHu(u) STMT_START { EXTEND(sp,1); sv_setuv(PUSHmortal, (UV)(u)); } STMT_END
+#endif
+
+/* Replace: 1 */
+#ifndef call_sv
+# define call_sv perl_call_sv
+#endif
+
+#ifndef call_pv
+# define call_pv perl_call_pv
+#endif
+
+#ifndef call_argv
+# define call_argv perl_call_argv
+#endif
+
+#ifndef call_method
+# define call_method perl_call_method
+#endif
+
+#ifndef eval_sv
+# define eval_sv perl_eval_sv
+#endif
+
+#if (PERL_BCDVERSION >= 0x5003098) && (PERL_BCDVERSION < 0x5006000)
+#ifndef eval_pv
+# define eval_pv perl_eval_pv
+#endif
+
+#endif
+/* Replace: 0 */
+
+#if (PERL_BCDVERSION < 0x5006000)
+#ifndef Perl_eval_sv
+# define Perl_eval_sv perl_eval_sv
+#endif
+
+#if (PERL_BCDVERSION >= 0x5003098)
+#ifndef Perl_eval_pv
+# define Perl_eval_pv perl_eval_pv
+#endif
+
+#endif
+#endif
+#ifndef G_LIST
+# define G_LIST G_ARRAY /* Replace */
+#endif
+#ifndef PERL_LOADMOD_DENY
+# define PERL_LOADMOD_DENY 0x1
+#endif
+
+#ifndef PERL_LOADMOD_NOIMPORT
+# define PERL_LOADMOD_NOIMPORT 0x2
+#endif
+
+#ifndef PERL_LOADMOD_IMPORT_OPS
+# define PERL_LOADMOD_IMPORT_OPS 0x4
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define D_PPP_CROAK_IF_ERROR(cond) ({ SV *_errsv; ((cond) && (_errsv = ERRSV) && (SvROK(_errsv) || SvTRUE(_errsv)) && (croak_sv(_errsv), 1)); })
+#else
+# define D_PPP_CROAK_IF_ERROR(cond) ((cond) && (SvROK(ERRSV) || SvTRUE(ERRSV)) && (croak_sv(ERRSV), 1))
+#endif
+
+#ifndef G_METHOD
+# define G_METHOD 64
+# ifdef call_sv
+# undef call_sv
+# endif
+# if (PERL_BCDVERSION < 0x5006000)
+# define call_sv(sv, flags) ((flags) & G_METHOD ? perl_call_method((char *) SvPV_nolen_const(sv), \
+ (flags) & ~G_METHOD) : perl_call_sv(sv, flags))
+# else
+# define call_sv(sv, flags) ((flags) & G_METHOD ? Perl_call_method(aTHX_ (char *) SvPV_nolen_const(sv), \
+ (flags) & ~G_METHOD) : Perl_call_sv(aTHX_ sv, flags))
+# endif
+#endif
+
+#ifndef G_RETHROW
+# define G_RETHROW 8192
+# ifdef eval_sv
+# undef eval_sv
+# endif
+# if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define eval_sv(sv, flags) ({ I32 _flags = (flags); I32 _ret = Perl_eval_sv(aTHX_ sv, (_flags & ~G_RETHROW)); D_PPP_CROAK_IF_ERROR(_flags & G_RETHROW); _ret; })
+# else
+# define eval_sv(sv, flags) ((PL_na = Perl_eval_sv(aTHX_ sv, ((flags) & ~G_RETHROW))), D_PPP_CROAK_IF_ERROR((flags) & G_RETHROW), (I32)PL_na)
+# endif
+#endif
+
+/*
+ * This implementation of eval_pv fails on compilers that don't allow
+ * statements nested within expressions. However, we don't care about the bug
+ * it's trying to fix, because we only call eval_pv with croak_on_error=0.
+ * So, pending an upstream fix for this, just remove it.
+ */
+#ifdef NOT_USED
+/* Older Perl versions have broken croak_on_error=1 */
+#if (PERL_BCDVERSION < 0x5031002)
+# ifdef eval_pv
+# undef eval_pv
+# if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define eval_pv(p, croak_on_error) ({ SV *_sv = Perl_eval_pv(aTHX_ p, 0); D_PPP_CROAK_IF_ERROR(croak_on_error); _sv; })
+# else
+# define eval_pv(p, croak_on_error) ((PL_Sv = Perl_eval_pv(aTHX_ p, 0)), D_PPP_CROAK_IF_ERROR(croak_on_error), PL_Sv)
+# endif
+# endif
+#endif
+#endif /* NOT_USED */
+
+/* This is backport for Perl 5.3.97d and older which do not provide perl_eval_pv */
+#ifndef eval_pv
+#if defined(NEED_eval_pv)
+static SV * DPPP_(my_eval_pv)(const char * p, I32 croak_on_error);
+static
+#else
+extern SV * DPPP_(my_eval_pv)(const char * p, I32 croak_on_error);
+#endif
+
+#if defined(NEED_eval_pv) || defined(NEED_eval_pv_GLOBAL)
+
+#ifdef eval_pv
+# undef eval_pv
+#endif
+#define eval_pv(a,b) DPPP_(my_eval_pv)(aTHX_ a,b)
+#define Perl_eval_pv DPPP_(my_eval_pv)
+
+
+SV*
+DPPP_(my_eval_pv)(const char *p, I32 croak_on_error)
+{
+ dSP;
+ SV* sv = newSVpv(p, 0);
+
+ PUSHMARK(sp);
+ eval_sv(sv, G_SCALAR);
+ SvREFCNT_dec(sv);
+
+ SPAGAIN;
+ sv = POPs;
+ PUTBACK;
+
+ D_PPP_CROAK_IF_ERROR(croak_on_error);
+
+ return sv;
+}
+
+#endif
+#endif
+
+#if ! defined(vload_module) && defined(start_subparse)
+#if defined(NEED_vload_module)
+static void DPPP_(my_vload_module)(U32 flags, SV * name, SV * ver, va_list * args);
+static
+#else
+extern void DPPP_(my_vload_module)(U32 flags, SV * name, SV * ver, va_list * args);
+#endif
+
+#if defined(NEED_vload_module) || defined(NEED_vload_module_GLOBAL)
+
+#ifdef vload_module
+# undef vload_module
+#endif
+#define vload_module(a,b,c,d) DPPP_(my_vload_module)(aTHX_ a,b,c,d)
+#define Perl_vload_module DPPP_(my_vload_module)
+
+
+void
+DPPP_(my_vload_module)(U32 flags, SV *name, SV *ver, va_list *args)
+{
+ dTHR;
+ dVAR;
+ OP *veop, *imop;
+
+ OP * const modname = newSVOP(OP_CONST, 0, name);
+ /* 5.005 has a somewhat hacky force_normal that doesn't croak on
+ SvREADONLY() if PL_compiling is true. Current perls take care in
+ ck_require() to correctly turn off SvREADONLY before calling
+ force_normal_flags(). This seems a better fix than fudging PL_compiling
+ */
+ SvREADONLY_off(((SVOP*)modname)->op_sv);
+ modname->op_private |= OPpCONST_BARE;
+ if (ver) {
+ veop = newSVOP(OP_CONST, 0, ver);
+ }
+ else
+ veop = NULL;
+ if (flags & PERL_LOADMOD_NOIMPORT) {
+ imop = sawparens(newNULLLIST());
+ }
+ else if (flags & PERL_LOADMOD_IMPORT_OPS) {
+ imop = va_arg(*args, OP*);
+ }
+ else {
+ SV *sv;
+ imop = NULL;
+ sv = va_arg(*args, SV*);
+ while (sv) {
+ imop = append_elem(OP_LIST, imop, newSVOP(OP_CONST, 0, sv));
+ sv = va_arg(*args, SV*);
+ }
+ }
+ {
+ const line_t ocopline = PL_copline;
+ COP * const ocurcop = PL_curcop;
+ const int oexpect = PL_expect;
+
+ utilize(!(flags & PERL_LOADMOD_DENY), start_subparse(FALSE, 0),
+#if (PERL_BCDVERSION > 0x5003000)
+ veop,
+#endif
+ modname, imop);
+ PL_expect = oexpect;
+ PL_copline = ocopline;
+ PL_curcop = ocurcop;
+ }
+}
+
+#endif
+#endif
+
+#ifndef load_module
+#if defined(NEED_load_module)
+static void DPPP_(my_load_module)(U32 flags, SV * name, SV * ver, ...);
+static
+#else
+extern void DPPP_(my_load_module)(U32 flags, SV * name, SV * ver, ...);
+#endif
+
+#if defined(NEED_load_module) || defined(NEED_load_module_GLOBAL)
+
+#ifdef load_module
+# undef load_module
+#endif
+#define load_module DPPP_(my_load_module)
+#define Perl_load_module DPPP_(my_load_module)
+
+
+void
+DPPP_(my_load_module)(U32 flags, SV *name, SV *ver, ...)
+{
+ va_list args;
+ va_start(args, ver);
+ vload_module(flags, name, ver, &args);
+ va_end(args);
+}
+
+#endif
+#endif
+#ifndef newRV_inc
+# define newRV_inc(sv) newRV(sv) /* Replace */
+#endif
+
+#ifndef newRV_noinc
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define newRV_noinc(sv) ({ SV *_sv = (SV *)newRV((sv)); SvREFCNT_dec((sv)); _sv; })
+#else
+# define newRV_noinc(sv) ((PL_Sv = (SV *)newRV((sv))), SvREFCNT_dec((sv)), PL_Sv)
+#endif
+#endif
+
+/*
+ * Boilerplate macros for initializing and accessing interpreter-local
+ * data from C. All statics in extensions should be reworked to use
+ * this, if you want to make the extension thread-safe. See ext/re/re.xs
+ * for an example of the use of these macros.
+ *
+ * Code that uses these macros is responsible for the following:
+ * 1. #define MY_CXT_KEY to a unique string, e.g. "DynaLoader_guts"
+ * 2. Declare a typedef named my_cxt_t that is a structure that contains
+ * all the data that needs to be interpreter-local.
+ * 3. Use the START_MY_CXT macro after the declaration of my_cxt_t.
+ * 4. Use the MY_CXT_INIT macro such that it is called exactly once
+ * (typically put in the BOOT: section).
+ * 5. Use the members of the my_cxt_t structure everywhere as
+ * MY_CXT.member.
+ * 6. Use the dMY_CXT macro (a declaration) in all the functions that
+ * access MY_CXT.
+ */
+
+#if defined(MULTIPLICITY) || defined(PERL_OBJECT) || \
+ defined(PERL_CAPI) || defined(PERL_IMPLICIT_CONTEXT)
+
+#ifndef START_MY_CXT
+
+/* This must appear in all extensions that define a my_cxt_t structure,
+ * right after the definition (i.e. at file scope). The non-threads
+ * case below uses it to declare the data as static. */
+#define START_MY_CXT
+
+#if (PERL_BCDVERSION < 0x5004068)
+/* Fetches the SV that keeps the per-interpreter data. */
+#define dMY_CXT_SV \
+ SV *my_cxt_sv = get_sv(MY_CXT_KEY, FALSE)
+#else /* >= perl5.004_68 */
+#define dMY_CXT_SV \
+ SV *my_cxt_sv = *hv_fetch(PL_modglobal, MY_CXT_KEY, \
+ sizeof(MY_CXT_KEY)-1, TRUE)
+#endif /* < perl5.004_68 */
+
+/* This declaration should be used within all functions that use the
+ * interpreter-local data. */
+#define dMY_CXT \
+ dMY_CXT_SV; \
+ my_cxt_t *my_cxtp = INT2PTR(my_cxt_t*,SvUV(my_cxt_sv))
+
+/* Creates and zeroes the per-interpreter data.
+ * (We allocate my_cxtp in a Perl SV so that it will be released when
+ * the interpreter goes away.) */
+#define MY_CXT_INIT \
+ dMY_CXT_SV; \
+ /* newSV() allocates one more than needed */ \
+ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+ Zero(my_cxtp, 1, my_cxt_t); \
+ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+
+/* This macro must be used to access members of the my_cxt_t structure.
+ * e.g. MYCXT.some_data */
+#define MY_CXT (*my_cxtp)
+
+/* Judicious use of these macros can reduce the number of times dMY_CXT
+ * is used. Use is similar to pTHX, aTHX etc. */
+#define pMY_CXT my_cxt_t *my_cxtp
+#define pMY_CXT_ pMY_CXT,
+#define _pMY_CXT ,pMY_CXT
+#define aMY_CXT my_cxtp
+#define aMY_CXT_ aMY_CXT,
+#define _aMY_CXT ,aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+/* Clones the per-interpreter data. */
+#define MY_CXT_CLONE \
+ dMY_CXT_SV; \
+ my_cxt_t *my_cxtp = (my_cxt_t*)SvPVX(newSV(sizeof(my_cxt_t)-1));\
+ Copy(INT2PTR(my_cxt_t*, SvUV(my_cxt_sv)), my_cxtp, 1, my_cxt_t);\
+ sv_setuv(my_cxt_sv, PTR2UV(my_cxtp))
+#endif
+
+#else /* single interpreter */
+
+#ifndef START_MY_CXT
+
+#define START_MY_CXT static my_cxt_t my_cxt;
+#define dMY_CXT_SV dNOOP
+#define dMY_CXT dNOOP
+#define MY_CXT_INIT NOOP
+#define MY_CXT my_cxt
+
+#define pMY_CXT void
+#define pMY_CXT_
+#define _pMY_CXT
+#define aMY_CXT
+#define aMY_CXT_
+#define _aMY_CXT
+
+#endif /* START_MY_CXT */
+
+#ifndef MY_CXT_CLONE
+#define MY_CXT_CLONE NOOP
+#endif
+
+#endif
+
+#ifndef SvREFCNT_inc
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ if (_sv) \
+ (SvREFCNT(_sv))++; \
+ _sv; \
+ })
+# else
+# define SvREFCNT_inc(sv) \
+ ((PL_Sv=(SV*)(sv)) ? (++(SvREFCNT(PL_Sv)),PL_Sv) : NULL)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_simple
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_simple(sv) \
+ ({ \
+ if (sv) \
+ (SvREFCNT(sv))++; \
+ (SV *)(sv); \
+ })
+# else
+# define SvREFCNT_inc_simple(sv) \
+ ((sv) ? (SvREFCNT(sv)++,(SV*)(sv)) : NULL)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_NN
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_NN(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ SvREFCNT(_sv)++; \
+ _sv; \
+ })
+# else
+# define SvREFCNT_inc_NN(sv) \
+ (PL_Sv=(SV*)(sv),++(SvREFCNT(PL_Sv)),PL_Sv)
+# endif
+#endif
+
+#ifndef SvREFCNT_inc_void
+# ifdef PERL_USE_GCC_BRACE_GROUPS
+# define SvREFCNT_inc_void(sv) \
+ ({ \
+ SV * const _sv = (SV*)(sv); \
+ if (_sv) \
+ (void)(SvREFCNT(_sv)++); \
+ })
+# else
+# define SvREFCNT_inc_void(sv) \
+ (void)((PL_Sv=(SV*)(sv)) ? ++(SvREFCNT(PL_Sv)) : 0)
+# endif
+#endif
+#ifndef SvREFCNT_inc_simple_void
+# define SvREFCNT_inc_simple_void(sv) STMT_START { if (sv) SvREFCNT(sv)++; } STMT_END
+#endif
+
+#ifndef SvREFCNT_inc_simple_NN
+# define SvREFCNT_inc_simple_NN(sv) (++SvREFCNT(sv), (SV*)(sv))
+#endif
+
+#ifndef SvREFCNT_inc_void_NN
+# define SvREFCNT_inc_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef SvREFCNT_inc_simple_void_NN
+# define SvREFCNT_inc_simple_void_NN(sv) (void)(++SvREFCNT((SV*)(sv)))
+#endif
+
+#ifndef newSV_type
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define newSV_type(t) ({ SV *_sv = newSV(0); sv_upgrade(_sv, (t)); _sv; })
+#else
+# define newSV_type(t) ((PL_Sv = newSV(0)), sv_upgrade(PL_Sv, (t)), PL_Sv)
+#endif
+#endif
+
+#if (PERL_BCDVERSION < 0x5006000)
+# define D_PPP_CONSTPV_ARG(x) ((char *) (x))
+#else
+# define D_PPP_CONSTPV_ARG(x) (x)
+#endif
+#ifndef newSVpvn
+# define newSVpvn(data,len) ((data) \
+ ? ((len) ? newSVpv((data), (len)) : newSVpv("", 0)) \
+ : newSV(0))
+#endif
+#ifndef newSVpvn_utf8
+# define newSVpvn_utf8(s, len, u) newSVpvn_flags((s), (len), (u) ? SVf_UTF8 : 0)
+#endif
+#ifndef SVf_UTF8
+# define SVf_UTF8 0
+#endif
+
+#ifndef newSVpvn_flags
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define newSVpvn_flags(s, len, flags) ({ SV *_sv = newSVpvn(D_PPP_CONSTPV_ARG((s)), (len)); SvFLAGS(_sv) |= ((flags) & SVf_UTF8); ((flags) & SVs_TEMP) ? sv_2mortal(_sv) : _sv; })
+#else
+# define newSVpvn_flags(s, len, flags) ((PL_Sv = newSVpvn(D_PPP_CONSTPV_ARG((s)), (len))), SvFLAGS(PL_Sv) |= ((flags) & SVf_UTF8), (((flags) & SVs_TEMP) ? sv_2mortal(PL_Sv) : PL_Sv))
+#endif
+#endif
+#ifndef SV_NOSTEAL
+# define SV_NOSTEAL 16
+#endif
+
+#if ( (PERL_BCDVERSION >= 0x5007003) && (PERL_BCDVERSION < 0x5008007) ) || ( (PERL_BCDVERSION >= 0x5009000) && (PERL_BCDVERSION < 0x5009002) )
+#undef sv_setsv_flags
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#define sv_setsv_flags(dstr, sstr, flags) \
+ STMT_START { \
+ if (((flags) & SV_NOSTEAL) && (sstr) && (SvFLAGS((SV *)(sstr)) & SVs_TEMP)) { \
+ SvTEMP_off((SV *)(sstr)); \
+ Perl_sv_setsv_flags(aTHX_ (dstr), (sstr), (flags) & ~SV_NOSTEAL); \
+ SvTEMP_on((SV *)(sstr)); \
+ } else { \
+ Perl_sv_setsv_flags(aTHX_ (dstr), (sstr), (flags) & ~SV_NOSTEAL); \
+ } \
+ } STMT_END
+#else
+ ( \
+ (((flags) & SV_NOSTEAL) && (sstr) && (SvFLAGS((SV *)(sstr)) & SVs_TEMP)) ? ( \
+ SvTEMP_off((SV *)(sstr)), \
+ Perl_sv_setsv_flags(aTHX_ (dstr), (sstr), (flags) & ~SV_NOSTEAL), \
+ SvTEMP_on((SV *)(sstr)), \
+ 1 \
+ ) : ( \
+ Perl_sv_setsv_flags(aTHX_ (dstr), (sstr), (flags) & ~SV_NOSTEAL), \
+ 1 \
+ ) \
+ )
+#endif
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef sv_setsv_flags
+# define sv_setsv_flags(dstr, sstr, flags) \
+ STMT_START { \
+ if (((flags) & SV_NOSTEAL) && (sstr) && (SvFLAGS((SV *)(sstr)) & SVs_TEMP)) { \
+ SvTEMP_off((SV *)(sstr)); \
+ if (!((flags) & SV_GMAGIC) && (sstr) && SvGMAGICAL((SV *)(sstr))) { \
+ SvGMAGICAL_off((SV *)(sstr)); \
+ sv_setsv((dstr), (sstr)); \
+ SvGMAGICAL_on((SV *)(sstr)); \
+ } else { \
+ sv_setsv((dstr), (sstr)); \
+ } \
+ SvTEMP_on((SV *)(sstr)); \
+ } else { \
+ if (!((flags) & SV_GMAGIC) && (sstr) && SvGMAGICAL((SV *)(sstr))) { \
+ SvGMAGICAL_off((SV *)(sstr)); \
+ sv_setsv((dstr), (sstr)); \
+ SvGMAGICAL_on((SV *)(sstr)); \
+ } else { \
+ sv_setsv((dstr), (sstr)); \
+ } \
+ } \
+ } STMT_END
+#endif
+
+#else
+#ifndef sv_setsv_flags
+# define sv_setsv_flags(dstr, sstr, flags) \
+ ( \
+ (((flags) & SV_NOSTEAL) && (sstr) && (SvFLAGS((SV *)(sstr)) & SVs_TEMP)) ? ( \
+ SvTEMP_off((SV *)(sstr)), \
+ (!((flags) & SV_GMAGIC) && (sstr) && SvGMAGICAL((SV *)(sstr))) ? ( \
+ SvGMAGICAL_off((SV *)(sstr)), \
+ sv_setsv((dstr), (sstr)), \
+ SvGMAGICAL_on((SV *)(sstr)), \
+ 1 \
+ ) : ( \
+ sv_setsv((dstr), (sstr)), \
+ 1 \
+ ), \
+ SvTEMP_on((SV *)(sstr)), \
+ 1 \
+ ) : ( \
+ (!((flags) & SV_GMAGIC) && (sstr) && SvGMAGICAL((SV *)(sstr))) ? ( \
+ SvGMAGICAL_off((SV *)(sstr)), \
+ sv_setsv((dstr), (sstr)), \
+ SvGMAGICAL_on((SV *)(sstr)), \
+ 1 \
+ ) : ( \
+ sv_setsv((dstr), (sstr)), \
+ 1 \
+ ) \
+ ) \
+ )
+#endif
+
+#endif
+
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef newSVsv_flags
+# define newSVsv_flags(sv, flags) ({ SV *_sv = newSV(0); sv_setsv_flags(_sv, (sv), (flags)); _sv; })
+#endif
+
+#else
+#ifndef newSVsv_flags
+# define newSVsv_flags(sv, flags) ((PL_Sv = newSV(0)), sv_setsv_flags(PL_Sv, (sv), (flags)), PL_Sv)
+#endif
+
+#endif
+#ifndef newSVsv_nomg
+# define newSVsv_nomg(sv) newSVsv_flags((sv), SV_NOSTEAL)
+#endif
+
+#if (PERL_BCDVERSION >= 0x5017005)
+#ifndef sv_mortalcopy_flags
+# define sv_mortalcopy_flags(sv, flags) Perl_sv_mortalcopy_flags(aTHX_ (sv), (flags))
+#endif
+
+#else
+#ifndef sv_mortalcopy_flags
+# define sv_mortalcopy_flags(sv, flags) sv_2mortal(newSVsv_flags((sv), (flags)))
+#endif
+
+#endif
+#ifndef SvMAGIC_set
+# define SvMAGIC_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+ (((XPVMG*) SvANY(sv))->xmg_magic = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5009003)
+#ifndef SvPVX_const
+# define SvPVX_const(sv) ((const char*) (0 + SvPVX(sv)))
+#endif
+
+#ifndef SvPVX_mutable
+# define SvPVX_mutable(sv) (0 + SvPVX(sv))
+#endif
+#ifndef SvRV_set
+# define SvRV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \
+ (((XRV*) SvANY(sv))->xrv_rv = (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvPVX_const
+# define SvPVX_const(sv) ((const char*)((sv)->sv_u.svu_pv))
+#endif
+
+#ifndef SvPVX_mutable
+# define SvPVX_mutable(sv) ((sv)->sv_u.svu_pv)
+#endif
+#ifndef SvRV_set
+# define SvRV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_RV); \
+ ((sv)->sv_u.svu_rv = (val)); } STMT_END
+#endif
+
+#endif
+#ifndef SvSTASH_set
+# define SvSTASH_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) >= SVt_PVMG); \
+ (((XPVMG*) SvANY(sv))->xmg_stash = (val)); } STMT_END
+#endif
+
+#if (PERL_BCDVERSION < 0x5004000)
+#ifndef SvUV_set
+# define SvUV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+ (((XPVIV*) SvANY(sv))->xiv_iv = (IV) (val)); } STMT_END
+#endif
+
+#else
+#ifndef SvUV_set
+# define SvUV_set(sv, val) \
+ STMT_START { assert(SvTYPE(sv) == SVt_IV || SvTYPE(sv) >= SVt_PVIV); \
+ (((XPVUV*) SvANY(sv))->xuv_uv = (val)); } STMT_END
+#endif
+
+#endif
+
+/* Hint: newSVpvn_share
+ * The SVs created by this function only mimic the behaviour of
+ * shared PVs without really being shared. Only use if you know
+ * what you're doing.
+ */
+
+#ifndef newSVpvn_share
+
+#if defined(NEED_newSVpvn_share)
+static SV * DPPP_(my_newSVpvn_share)(pTHX_ const char * s, I32 len, U32 hash);
+static
+#else
+extern SV * DPPP_(my_newSVpvn_share)(pTHX_ const char * s, I32 len, U32 hash);
+#endif
+
+#if defined(NEED_newSVpvn_share) || defined(NEED_newSVpvn_share_GLOBAL)
+
+#ifdef newSVpvn_share
+# undef newSVpvn_share
+#endif
+#define newSVpvn_share(a,b,c) DPPP_(my_newSVpvn_share)(aTHX_ a,b,c)
+#define Perl_newSVpvn_share DPPP_(my_newSVpvn_share)
+
+
+SV *
+DPPP_(my_newSVpvn_share)(pTHX_ const char *s, I32 len, U32 hash)
+{
+ SV *sv;
+ if (len < 0)
+ len = -len;
+ if (!hash)
+ PERL_HASH(hash, (char*) s, len);
+ sv = newSVpvn((char *) s, len);
+ sv_upgrade(sv, SVt_PVIV);
+ SvIVX(sv) = hash;
+ SvREADONLY_on(sv);
+ SvPOK_on(sv);
+ return sv;
+}
+
+#endif
+
+#endif
+#ifndef SvSHARED_HASH
+# define SvSHARED_HASH(sv) (0 + SvUVX(sv))
+#endif
+#ifndef HvNAME_get
+# define HvNAME_get(hv) HvNAME(hv)
+#endif
+#ifndef HvNAMELEN_get
+# define HvNAMELEN_get(hv) (HvNAME_get(hv) ? (I32)strlen(HvNAME_get(hv)) : 0)
+#endif
+
+#if (PERL_BCDVERSION >= 0x5009002) && (PERL_BCDVERSION <= 0x5009003) /* 5.9.2 and 5.9.3 ignore the length param */
+#undef gv_fetchpvn_flags
+#endif
+
+#ifdef GV_NOADD_MASK
+# define D_PPP_GV_NOADD_MASK GV_NOADD_MASK
+#else
+# define D_PPP_GV_NOADD_MASK 0xE0
+#endif
+#ifndef gv_fetchpvn_flags
+# define gv_fetchpvn_flags(name, len, flags, sv_type) gv_fetchpv(SvPVX(sv_2mortal(newSVpvn((name), (len)))), ((flags) & D_PPP_GV_NOADD_MASK) ? FALSE : TRUE, (I32)(sv_type))
+#endif
+#ifndef GvSVn
+# define GvSVn(gv) GvSV(gv)
+#endif
+
+#ifndef isGV_with_GP
+# define isGV_with_GP(gv) isGV(gv)
+#endif
+
+#ifndef gv_fetchsv
+# define gv_fetchsv(name, flags, svt) gv_fetchpv(SvPV_nolen_const(name), flags, svt)
+#endif
+#ifndef get_cvn_flags
+# define get_cvn_flags(name, namelen, flags) get_cv(name, flags)
+#endif
+
+#ifndef gv_init_pvn
+# define gv_init_pvn(gv, stash, ptr, len, flags) gv_init(gv, stash, ptr, len, flags & GV_ADDMULTI ? TRUE : FALSE)
+#endif
+
+/* concatenating with "" ensures that only literal strings are accepted as argument
+ * note that STR_WITH_LEN() can't be used as argument to macros or functions that
+ * under some configurations might be macros
+ */
+#ifndef STR_WITH_LEN
+# define STR_WITH_LEN(s) (s ""), (sizeof(s)-1)
+#endif
+#ifndef newSVpvs
+# define newSVpvs(str) newSVpvn(str "", sizeof(str) - 1)
+#endif
+
+#ifndef newSVpvs_flags
+# define newSVpvs_flags(str, flags) newSVpvn_flags(str "", sizeof(str) - 1, flags)
+#endif
+
+#ifndef newSVpvs_share
+# define newSVpvs_share(str) newSVpvn_share(str "", sizeof(str) - 1, 0)
+#endif
+
+#ifndef sv_catpvs
+# define sv_catpvs(sv, str) sv_catpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef sv_setpvs
+# define sv_setpvs(sv, str) sv_setpvn(sv, str "", sizeof(str) - 1)
+#endif
+
+#ifndef hv_fetchs
+# define hv_fetchs(hv, key, lval) hv_fetch(hv, key "", sizeof(key) - 1, lval)
+#endif
+
+#ifndef hv_stores
+# define hv_stores(hv, key, val) hv_store(hv, key "", sizeof(key) - 1, val, 0)
+#endif
+#ifndef gv_fetchpvs
+# define gv_fetchpvs(name, flags, svt) gv_fetchpvn_flags(name "", sizeof(name) - 1, flags, svt)
+#endif
+
+#ifndef gv_stashpvs
+# define gv_stashpvs(name, flags) gv_stashpvn(name "", sizeof(name) - 1, flags)
+#endif
+#ifndef get_cvs
+# define get_cvs(name, flags) get_cvn_flags(name "", sizeof(name)-1, flags)
+#endif
+#ifndef SvGETMAGIC
+# define SvGETMAGIC(x) STMT_START { if (SvGMAGICAL(x)) mg_get(x); } STMT_END
+#endif
+
+/* That's the best we can do... */
+#ifndef sv_catpvn_nomg
+# define sv_catpvn_nomg sv_catpvn
+#endif
+
+#ifndef sv_catsv_nomg
+# define sv_catsv_nomg sv_catsv
+#endif
+
+#ifndef sv_setsv_nomg
+# define sv_setsv_nomg sv_setsv
+#endif
+
+#ifndef sv_pvn_nomg
+# define sv_pvn_nomg sv_pvn
+#endif
+
+#ifdef SVf_IVisUV
+#if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef SvIV_nomg
+# define SvIV_nomg(sv) (!SvGMAGICAL((sv)) ? SvIV((sv)) : ({ SV *_sviv = sv_mortalcopy_flags((sv), SV_NOSTEAL); IV _iv = SvIV(_sviv); SvFLAGS((sv)) = (SvFLAGS((sv)) & ~SVf_IVisUV) | (SvFLAGS(_sviv) & SVf_IVisUV); _iv; }))
+#endif
+
+#ifndef SvUV_nomg
+# define SvUV_nomg(sv) (!SvGMAGICAL((sv)) ? SvUV((sv)) : ({ SV *_svuv = sv_mortalcopy_flags((sv), SV_NOSTEAL); UV _uv = SvUV(_svuv); SvFLAGS((sv)) = (SvFLAGS((sv)) & ~SVf_IVisUV) | (SvFLAGS(_svuv) & SVf_IVisUV); _uv; }))
+#endif
+
+#else
+#ifndef SvIV_nomg
+# define SvIV_nomg(sv) (!SvGMAGICAL((sv)) ? SvIV((sv)) : ((PL_Sv = sv_mortalcopy_flags((sv), SV_NOSTEAL)), sv_upgrade(PL_Sv, SVt_PVIV), (SvIVX(PL_Sv) = SvIV(PL_Sv)), (SvFLAGS((sv)) = (SvFLAGS((sv)) & ~SVf_IVisUV) | (SvFLAGS(PL_Sv) & SVf_IVisUV)), SvIVX(PL_Sv)))
+#endif
+
+#ifndef SvUV_nomg
+# define SvUV_nomg(sv) (!SvGMAGICAL((sv)) ? SvIV((sv)) : ((PL_Sv = sv_mortalcopy_flags((sv), SV_NOSTEAL)), sv_upgrade(PL_Sv, SVt_PVIV), (SvUVX(PL_Sv) = SvUV(PL_Sv)), (SvFLAGS((sv)) = (SvFLAGS((sv)) & ~SVf_IVisUV) | (SvFLAGS(PL_Sv) & SVf_IVisUV)), SvUVX(PL_Sv)))
+#endif
+
+#endif
+#else
+#ifndef SvIV_nomg
+# define SvIV_nomg(sv) (!SvGMAGICAL((sv)) ? SvIV((sv)) : SvIVx(sv_mortalcopy_flags((sv), SV_NOSTEAL)))
+#endif
+
+#ifndef SvUV_nomg
+# define SvUV_nomg(sv) (!SvGMAGICAL((sv)) ? SvUV((sv)) : SvUVx(sv_mortalcopy_flags((sv), SV_NOSTEAL)))
+#endif
+
+#endif
+#ifndef SvNV_nomg
+# define SvNV_nomg(sv) (!SvGMAGICAL((sv)) ? SvNV((sv)) : SvNVx(sv_mortalcopy_flags((sv), SV_NOSTEAL)))
+#endif
+
+#ifndef SvTRUE_nomg
+# define SvTRUE_nomg(sv) (!SvGMAGICAL((sv)) ? SvTRUE((sv)) : SvTRUEx(sv_mortalcopy_flags((sv), SV_NOSTEAL)))
+#endif
+
+#ifndef sv_catpv_mg
+# define sv_catpv_mg(sv, ptr) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_catpv(TeMpSv,ptr); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_catpvn_mg
+# define sv_catpvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_catpvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_catsv_mg
+# define sv_catsv_mg(dsv, ssv) \
+ STMT_START { \
+ SV *TeMpSv = dsv; \
+ sv_catsv(TeMpSv,ssv); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setiv_mg
+# define sv_setiv_mg(sv, i) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setiv(TeMpSv,i); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setnv_mg
+# define sv_setnv_mg(sv, num) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setnv(TeMpSv,num); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setpv_mg
+# define sv_setpv_mg(sv, ptr) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setpv(TeMpSv,ptr); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setpvn_mg
+# define sv_setpvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setpvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setsv_mg
+# define sv_setsv_mg(dsv, ssv) \
+ STMT_START { \
+ SV *TeMpSv = dsv; \
+ sv_setsv(TeMpSv,ssv); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_setuv_mg
+# define sv_setuv_mg(sv, i) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_setuv(TeMpSv,i); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+
+#ifndef sv_usepvn_mg
+# define sv_usepvn_mg(sv, ptr, len) \
+ STMT_START { \
+ SV *TeMpSv = sv; \
+ sv_usepvn(TeMpSv,ptr,len); \
+ SvSETMAGIC(TeMpSv); \
+ } STMT_END
+#endif
+#ifndef SvVSTRING_mg
+# define SvVSTRING_mg(sv) (SvMAGICAL(sv) ? mg_find(sv, PERL_MAGIC_vstring) : NULL)
+#endif
+
+/* Hint: sv_magic_portable
+ * This is a compatibility function that is only available with
+ * Devel::PPPort. It is NOT in the perl core.
+ * Its purpose is to mimic the 5.8.0 behaviour of sv_magic() when
+ * it is being passed a name pointer with namlen == 0. In that
+ * case, perl 5.8.0 and later store the pointer, not a copy of it.
+ * The compatibility can be provided back to perl 5.004. With
+ * earlier versions, the code will not compile.
+ */
+
+#if (PERL_BCDVERSION < 0x5004000)
+
+ /* code that uses sv_magic_portable will not compile */
+
+#elif (PERL_BCDVERSION < 0x5008000)
+
+# define sv_magic_portable(sv, obj, how, name, namlen) \
+ STMT_START { \
+ SV *SvMp_sv = (sv); \
+ char *SvMp_name = (char *) (name); \
+ I32 SvMp_namlen = (namlen); \
+ if (SvMp_name && SvMp_namlen == 0) \
+ { \
+ MAGIC *mg; \
+ sv_magic(SvMp_sv, obj, how, 0, 0); \
+ mg = SvMAGIC(SvMp_sv); \
+ mg->mg_len = -42; /* XXX: this is the tricky part */ \
+ mg->mg_ptr = SvMp_name; \
+ } \
+ else \
+ { \
+ sv_magic(SvMp_sv, obj, how, SvMp_name, SvMp_namlen); \
+ } \
+ } STMT_END
+
+#else
+
+# define sv_magic_portable(a, b, c, d, e) sv_magic(a, b, c, d, e)
+
+#endif
+
+#if !defined(mg_findext)
+#if defined(NEED_mg_findext)
+static MAGIC * DPPP_(my_mg_findext)(const SV * sv, int type, const MGVTBL * vtbl);
+static
+#else
+extern MAGIC * DPPP_(my_mg_findext)(const SV * sv, int type, const MGVTBL * vtbl);
+#endif
+
+#if defined(NEED_mg_findext) || defined(NEED_mg_findext_GLOBAL)
+
+#define mg_findext DPPP_(my_mg_findext)
+#define Perl_mg_findext DPPP_(my_mg_findext)
+
+
+MAGIC *
+DPPP_(my_mg_findext)(const SV * sv, int type, const MGVTBL *vtbl) {
+ if (sv) {
+ MAGIC *mg;
+
+#ifdef AvPAD_NAMELIST
+ assert(!(SvTYPE(sv) == SVt_PVAV && AvPAD_NAMELIST(sv)));
+#endif
+
+ for (mg = SvMAGIC (sv); mg; mg = mg->mg_moremagic) {
+ if (mg->mg_type == type && mg->mg_virtual == vtbl)
+ return mg;
+ }
+ }
+
+ return NULL;
+}
+
+#endif
+#endif
+
+#if !defined(sv_unmagicext)
+#if defined(NEED_sv_unmagicext)
+static int DPPP_(my_sv_unmagicext)(pTHX_ SV * const sv, const int type, MGVTBL * vtbl);
+static
+#else
+extern int DPPP_(my_sv_unmagicext)(pTHX_ SV * const sv, const int type, MGVTBL * vtbl);
+#endif
+
+#if defined(NEED_sv_unmagicext) || defined(NEED_sv_unmagicext_GLOBAL)
+
+#ifdef sv_unmagicext
+# undef sv_unmagicext
+#endif
+#define sv_unmagicext(a,b,c) DPPP_(my_sv_unmagicext)(aTHX_ a,b,c)
+#define Perl_sv_unmagicext DPPP_(my_sv_unmagicext)
+
+
+int
+DPPP_(my_sv_unmagicext)(pTHX_ SV *const sv, const int type, MGVTBL *vtbl)
+{
+ MAGIC* mg;
+ MAGIC** mgp;
+
+ if (SvTYPE(sv) < SVt_PVMG || !SvMAGIC(sv))
+ return 0;
+ mgp = &(SvMAGIC(sv));
+ for (mg = *mgp; mg; mg = *mgp) {
+ const MGVTBL* const virt = mg->mg_virtual;
+ if (mg->mg_type == type && virt == vtbl) {
+ *mgp = mg->mg_moremagic;
+ if (virt && virt->svt_free)
+ virt->svt_free(aTHX_ sv, mg);
+ if (mg->mg_ptr && mg->mg_type != PERL_MAGIC_regex_global) {
+ if (mg->mg_len > 0)
+ Safefree(mg->mg_ptr);
+ else if (mg->mg_len == HEf_SVKEY) /* Questionable on older perls... */
+ SvREFCNT_dec(MUTABLE_SV(mg->mg_ptr));
+ else if (mg->mg_type == PERL_MAGIC_utf8)
+ Safefree(mg->mg_ptr);
+ }
+ if (mg->mg_flags & MGf_REFCOUNTED)
+ SvREFCNT_dec(mg->mg_obj);
+ Safefree(mg);
+ }
+ else
+ mgp = &mg->mg_moremagic;
+ }
+ if (SvMAGIC(sv)) {
+ if (SvMAGICAL(sv)) /* if we're under save_magic, wait for restore_magic; */
+ mg_magical(sv); /* else fix the flags now */
+ }
+ else {
+ SvMAGICAL_off(sv);
+ SvFLAGS(sv) |= (SvFLAGS(sv) & (SVp_IOK|SVp_NOK|SVp_POK)) >> PRIVSHIFT;
+ }
+ return 0;
+}
+
+#endif
+#endif
+
+#ifdef USE_ITHREADS
+#ifndef CopFILE
+# define CopFILE(c) ((c)->cop_file)
+#endif
+
+#ifndef CopFILEGV
+# define CopFILEGV(c) (CopFILE(c) ? gv_fetchfile(CopFILE(c)) : Nullgv)
+#endif
+
+#ifndef CopFILE_set
+# define CopFILE_set(c,pv) ((c)->cop_file = savepv(pv))
+#endif
+
+#ifndef CopFILESV
+# define CopFILESV(c) (CopFILE(c) ? GvSV(gv_fetchfile(CopFILE(c))) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+# define CopFILEAV(c) (CopFILE(c) ? GvAV(gv_fetchfile(CopFILE(c))) : Nullav)
+#endif
+
+#ifndef CopSTASHPV
+# define CopSTASHPV(c) ((c)->cop_stashpv)
+#endif
+
+#ifndef CopSTASHPV_set
+# define CopSTASHPV_set(c,pv) ((c)->cop_stashpv = ((pv) ? savepv(pv) : Nullch))
+#endif
+
+#ifndef CopSTASH
+# define CopSTASH(c) (CopSTASHPV(c) ? gv_stashpv(CopSTASHPV(c),GV_ADD) : Nullhv)
+#endif
+
+#ifndef CopSTASH_set
+# define CopSTASH_set(c,hv) CopSTASHPV_set(c, (hv) ? HvNAME(hv) : Nullch)
+#endif
+
+#ifndef CopSTASH_eq
+# define CopSTASH_eq(c,hv) ((hv) && (CopSTASHPV(c) == HvNAME(hv) \
+ || (CopSTASHPV(c) && HvNAME(hv) \
+ && strEQ(CopSTASHPV(c), HvNAME(hv)))))
+#endif
+
+#else
+#ifndef CopFILEGV
+# define CopFILEGV(c) ((c)->cop_filegv)
+#endif
+
+#ifndef CopFILEGV_set
+# define CopFILEGV_set(c,gv) ((c)->cop_filegv = (GV*)SvREFCNT_inc(gv))
+#endif
+
+#ifndef CopFILE_set
+# define CopFILE_set(c,pv) CopFILEGV_set((c), gv_fetchfile(pv))
+#endif
+
+#ifndef CopFILESV
+# define CopFILESV(c) (CopFILEGV(c) ? GvSV(CopFILEGV(c)) : Nullsv)
+#endif
+
+#ifndef CopFILEAV
+# define CopFILEAV(c) (CopFILEGV(c) ? GvAV(CopFILEGV(c)) : Nullav)
+#endif
+
+#ifndef CopFILE
+# define CopFILE(c) (CopFILESV(c) ? SvPVX(CopFILESV(c)) : Nullch)
+#endif
+
+#ifndef CopSTASH
+# define CopSTASH(c) ((c)->cop_stash)
+#endif
+
+#ifndef CopSTASH_set
+# define CopSTASH_set(c,hv) ((c)->cop_stash = (hv))
+#endif
+
+#ifndef CopSTASHPV
+# define CopSTASHPV(c) (CopSTASH(c) ? HvNAME(CopSTASH(c)) : Nullch)
+#endif
+
+#ifndef CopSTASHPV_set
+# define CopSTASHPV_set(c,pv) CopSTASH_set((c), gv_stashpv(pv,GV_ADD))
+#endif
+
+#ifndef CopSTASH_eq
+# define CopSTASH_eq(c,hv) (CopSTASH(c) == (hv))
+#endif
+
+#endif /* USE_ITHREADS */
+
+#if (PERL_BCDVERSION >= 0x5006000)
+#ifndef caller_cx
+
+# if defined(NEED_caller_cx) || defined(NEED_caller_cx_GLOBAL)
+static I32
+DPPP_dopoptosub_at(const PERL_CONTEXT *cxstk, I32 startingblock)
+{
+ I32 i;
+
+ for (i = startingblock; i >= 0; i--) {
+ const PERL_CONTEXT * const cx = &cxstk[i];
+ switch (CxTYPE(cx)) {
+ default:
+ continue;
+ case CXt_EVAL:
+ case CXt_SUB:
+ case CXt_FORMAT:
+ return i;
+ }
+ }
+ return i;
+}
+# endif
+
+# if defined(NEED_caller_cx)
+static const PERL_CONTEXT * DPPP_(my_caller_cx)(pTHX_ I32 level, const PERL_CONTEXT * * dbcxp);
+static
+#else
+extern const PERL_CONTEXT * DPPP_(my_caller_cx)(pTHX_ I32 level, const PERL_CONTEXT * * dbcxp);
+#endif
+
+#if defined(NEED_caller_cx) || defined(NEED_caller_cx_GLOBAL)
+
+#ifdef caller_cx
+# undef caller_cx
+#endif
+#define caller_cx(a,b) DPPP_(my_caller_cx)(aTHX_ a,b)
+#define Perl_caller_cx DPPP_(my_caller_cx)
+
+
+const PERL_CONTEXT *
+DPPP_(my_caller_cx)(pTHX_ I32 level, const PERL_CONTEXT **dbcxp)
+{
+ I32 cxix = DPPP_dopoptosub_at(cxstack, cxstack_ix);
+ const PERL_CONTEXT *cx;
+ const PERL_CONTEXT *ccstack = cxstack;
+ const PERL_SI *top_si = PL_curstackinfo;
+
+ for (;;) {
+ /* we may be in a higher stacklevel, so dig down deeper */
+ while (cxix < 0 && top_si->si_type != PERLSI_MAIN) {
+ top_si = top_si->si_prev;
+ ccstack = top_si->si_cxstack;
+ cxix = DPPP_dopoptosub_at(ccstack, top_si->si_cxix);
+ }
+ if (cxix < 0)
+ return NULL;
+ /* caller() should not report the automatic calls to &DB::sub */
+ if (PL_DBsub && GvCV(PL_DBsub) && cxix >= 0 &&
+ ccstack[cxix].blk_sub.cv == GvCV(PL_DBsub))
+ level++;
+ if (!level--)
+ break;
+ cxix = DPPP_dopoptosub_at(ccstack, cxix - 1);
+ }
+
+ cx = &ccstack[cxix];
+ if (dbcxp) *dbcxp = cx;
+
+ if (CxTYPE(cx) == CXt_SUB || CxTYPE(cx) == CXt_FORMAT) {
+ const I32 dbcxix = DPPP_dopoptosub_at(ccstack, cxix - 1);
+ /* We expect that ccstack[dbcxix] is CXt_SUB, anyway, the
+ field below is defined for any cx. */
+ /* caller() should not report the automatic calls to &DB::sub */
+ if (PL_DBsub && GvCV(PL_DBsub) && dbcxix >= 0 && ccstack[dbcxix].blk_sub.cv == GvCV(PL_DBsub))
+ cx = &ccstack[dbcxix];
+ }
+
+ return cx;
+}
+
+# endif
+#endif /* caller_cx */
+#endif /* 5.6.0 */
+#ifndef IN_PERL_COMPILETIME
+# define IN_PERL_COMPILETIME (PL_curcop == &PL_compiling)
+#endif
+
+#ifndef IN_LOCALE_RUNTIME
+# define IN_LOCALE_RUNTIME (PL_curcop->op_private & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE_COMPILETIME
+# define IN_LOCALE_COMPILETIME (PL_hints & HINT_LOCALE)
+#endif
+
+#ifndef IN_LOCALE
+# define IN_LOCALE (IN_PERL_COMPILETIME ? IN_LOCALE_COMPILETIME : IN_LOCALE_RUNTIME)
+#endif
+#ifndef IS_NUMBER_IN_UV
+# define IS_NUMBER_IN_UV 0x01
+#endif
+
+#ifndef IS_NUMBER_GREATER_THAN_UV_MAX
+# define IS_NUMBER_GREATER_THAN_UV_MAX 0x02
+#endif
+
+#ifndef IS_NUMBER_NOT_INT
+# define IS_NUMBER_NOT_INT 0x04
+#endif
+
+#ifndef IS_NUMBER_NEG
+# define IS_NUMBER_NEG 0x08
+#endif
+
+#ifndef IS_NUMBER_INFINITY
+# define IS_NUMBER_INFINITY 0x10
+#endif
+
+#ifndef IS_NUMBER_NAN
+# define IS_NUMBER_NAN 0x20
+#endif
+#ifndef GROK_NUMERIC_RADIX
+# define GROK_NUMERIC_RADIX(sp, send) grok_numeric_radix(sp, send)
+#endif
+#ifndef PERL_SCAN_GREATER_THAN_UV_MAX
+# define PERL_SCAN_GREATER_THAN_UV_MAX 0x02
+#endif
+
+#ifndef PERL_SCAN_SILENT_ILLDIGIT
+# define PERL_SCAN_SILENT_ILLDIGIT 0x04
+#endif
+
+#ifndef PERL_SCAN_ALLOW_UNDERSCORES
+# define PERL_SCAN_ALLOW_UNDERSCORES 0x01
+#endif
+
+#ifndef PERL_SCAN_DISALLOW_PREFIX
+# define PERL_SCAN_DISALLOW_PREFIX 0x02
+#endif
+
+#ifndef grok_numeric_radix
+#if defined(NEED_grok_numeric_radix)
+static bool DPPP_(my_grok_numeric_radix)(pTHX_ const char * * sp, const char * send);
+static
+#else
+extern bool DPPP_(my_grok_numeric_radix)(pTHX_ const char * * sp, const char * send);
+#endif
+
+#if defined(NEED_grok_numeric_radix) || defined(NEED_grok_numeric_radix_GLOBAL)
+
+#ifdef grok_numeric_radix
+# undef grok_numeric_radix
+#endif
+#define grok_numeric_radix(a,b) DPPP_(my_grok_numeric_radix)(aTHX_ a,b)
+#define Perl_grok_numeric_radix DPPP_(my_grok_numeric_radix)
+
+bool
+DPPP_(my_grok_numeric_radix)(pTHX_ const char **sp, const char *send)
+{
+#ifdef USE_LOCALE_NUMERIC
+#ifdef PL_numeric_radix_sv
+ if (PL_numeric_radix_sv && IN_LOCALE) {
+ STRLEN len;
+ char* radix = SvPV(PL_numeric_radix_sv, len);
+ if (*sp + len <= send && memEQ(*sp, radix, len)) {
+ *sp += len;
+ return TRUE;
+ }
+ }
+#else
+ /* older perls don't have PL_numeric_radix_sv so the radix
+ * must manually be requested from locale.h
+ */
+#include <locale.h>
+ dTHR; /* needed for older threaded perls */
+ struct lconv *lc = localeconv();
+ char *radix = lc->decimal_point;
+ if (radix && IN_LOCALE) {
+ STRLEN len = strlen(radix);
+ if (*sp + len <= send && memEQ(*sp, radix, len)) {
+ *sp += len;
+ return TRUE;
+ }
+ }
+#endif
+#endif /* USE_LOCALE_NUMERIC */
+ /* always try "." if numeric radix didn't match because
+ * we may have data from different locales mixed */
+ if (*sp < send && **sp == '.') {
+ ++*sp;
+ return TRUE;
+ }
+ return FALSE;
+}
+#endif
+#endif
+
+#ifndef grok_number
+#if defined(NEED_grok_number)
+static int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+static
+#else
+extern int DPPP_(my_grok_number)(pTHX_ const char * pv, STRLEN len, UV * valuep);
+#endif
+
+#if defined(NEED_grok_number) || defined(NEED_grok_number_GLOBAL)
+
+#ifdef grok_number
+# undef grok_number
+#endif
+#define grok_number(a,b,c) DPPP_(my_grok_number)(aTHX_ a,b,c)
+#define Perl_grok_number DPPP_(my_grok_number)
+
+int
+DPPP_(my_grok_number)(pTHX_ const char *pv, STRLEN len, UV *valuep)
+{
+ const char *s = pv;
+ const char *send = pv + len;
+ const UV max_div_10 = UV_MAX / 10;
+ const char max_mod_10 = UV_MAX % 10;
+ int numtype = 0;
+ int sawinf = 0;
+ int sawnan = 0;
+
+ while (s < send && isSPACE(*s))
+ s++;
+ if (s == send) {
+ return 0;
+ } else if (*s == '-') {
+ s++;
+ numtype = IS_NUMBER_NEG;
+ }
+ else if (*s == '+')
+ s++;
+
+ if (s == send)
+ return 0;
+
+ /* next must be digit or the radix separator or beginning of infinity */
+ if (isDIGIT(*s)) {
+ /* UVs are at least 32 bits, so the first 9 decimal digits cannot
+ overflow. */
+ UV value = *s - '0';
+ /* This construction seems to be more optimiser friendly.
+ (without it gcc does the isDIGIT test and the *s - '0' separately)
+ With it gcc on arm is managing 6 instructions (6 cycles) per digit.
+ In theory the optimiser could deduce how far to unroll the loop
+ before checking for overflow. */
+ if (++s < send) {
+ int digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ digit = *s - '0';
+ if (digit >= 0 && digit <= 9) {
+ value = value * 10 + digit;
+ if (++s < send) {
+ /* Now got 9 digits, so need to check
+ each time for overflow. */
+ digit = *s - '0';
+ while (digit >= 0 && digit <= 9
+ && (value < max_div_10
+ || (value == max_div_10
+ && digit <= max_mod_10))) {
+ value = value * 10 + digit;
+ if (++s < send)
+ digit = *s - '0';
+ else
+ break;
+ }
+ if (digit >= 0 && digit <= 9
+ && (s < send)) {
+ /* value overflowed.
+ skip the remaining digits, don't
+ worry about setting *valuep. */
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ numtype |=
+ IS_NUMBER_GREATER_THAN_UV_MAX;
+ goto skip_value;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ numtype |= IS_NUMBER_IN_UV;
+ if (valuep)
+ *valuep = value;
+
+ skip_value:
+ if (GROK_NUMERIC_RADIX(&s, send)) {
+ numtype |= IS_NUMBER_NOT_INT;
+ while (s < send && isDIGIT(*s)) /* optional digits after the radix */
+ s++;
+ }
+ }
+ else if (GROK_NUMERIC_RADIX(&s, send)) {
+ numtype |= IS_NUMBER_NOT_INT | IS_NUMBER_IN_UV; /* valuep assigned below */
+ /* no digits before the radix means we need digits after it */
+ if (s < send && isDIGIT(*s)) {
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ if (valuep) {
+ /* integer approximation is valid - it's 0. */
+ *valuep = 0;
+ }
+ }
+ else
+ return 0;
+ } else if (*s == 'I' || *s == 'i') {
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++; if (s == send || (*s != 'F' && *s != 'f')) return 0;
+ s++; if (s < send && (*s == 'I' || *s == 'i')) {
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++; if (s == send || (*s != 'I' && *s != 'i')) return 0;
+ s++; if (s == send || (*s != 'T' && *s != 't')) return 0;
+ s++; if (s == send || (*s != 'Y' && *s != 'y')) return 0;
+ s++;
+ }
+ sawinf = 1;
+ } else if (*s == 'N' || *s == 'n') {
+ /* XXX TODO: There are signaling NaNs and quiet NaNs. */
+ s++; if (s == send || (*s != 'A' && *s != 'a')) return 0;
+ s++; if (s == send || (*s != 'N' && *s != 'n')) return 0;
+ s++;
+ sawnan = 1;
+ } else
+ return 0;
+
+ if (sawinf) {
+ numtype &= IS_NUMBER_NEG; /* Keep track of sign */
+ numtype |= IS_NUMBER_INFINITY | IS_NUMBER_NOT_INT;
+ } else if (sawnan) {
+ numtype &= IS_NUMBER_NEG; /* Keep track of sign */
+ numtype |= IS_NUMBER_NAN | IS_NUMBER_NOT_INT;
+ } else if (s < send) {
+ /* we can have an optional exponent part */
+ if (*s == 'e' || *s == 'E') {
+ /* The only flag we keep is sign. Blow away any "it's UV" */
+ numtype &= IS_NUMBER_NEG;
+ numtype |= IS_NUMBER_NOT_INT;
+ s++;
+ if (s < send && (*s == '-' || *s == '+'))
+ s++;
+ if (s < send && isDIGIT(*s)) {
+ do {
+ s++;
+ } while (s < send && isDIGIT(*s));
+ }
+ else
+ return 0;
+ }
+ }
+ while (s < send && isSPACE(*s))
+ s++;
+ if (s >= send)
+ return numtype;
+ if (len == 10 && memEQ(pv, "0 but true", 10)) {
+ if (valuep)
+ *valuep = 0;
+ return IS_NUMBER_IN_UV;
+ }
+ return 0;
+}
+#endif
+#endif
+
+/*
+ * The grok_* routines have been modified to use warn() instead of
+ * Perl_warner(). Also, 'hexdigit' was the former name of PL_hexdigit,
+ * which is why the stack variable has been renamed to 'xdigit'.
+ */
+
+#ifndef grok_bin
+#if defined(NEED_grok_bin)
+static UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_bin)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#if defined(NEED_grok_bin) || defined(NEED_grok_bin_GLOBAL)
+
+#ifdef grok_bin
+# undef grok_bin
+#endif
+#define grok_bin(a,b,c,d) DPPP_(my_grok_bin)(aTHX_ a,b,c,d)
+#define Perl_grok_bin DPPP_(my_grok_bin)
+
+UV
+DPPP_(my_grok_bin)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_2 = UV_MAX / 2;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+
+ if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+ /* strip off leading b or 0b.
+ for compatibility silently suffer "b" and "0b" as valid binary
+ numbers. */
+ if (len >= 1) {
+ if (s[0] == 'b') {
+ s++;
+ len--;
+ }
+ else if (len >= 2 && s[0] == '0' && s[1] == 'b') {
+ s+=2;
+ len-=2;
+ }
+ }
+ }
+
+ for (; len-- && *s; s++) {
+ char bit = *s;
+ if (bit == '0' || bit == '1') {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ With gcc seems to be much straighter code than old scan_bin. */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_2) {
+ value = (value << 1) | (bit - '0');
+ continue;
+ }
+ /* Bah. We're just overflowed. */
+ warn("Integer overflow in binary number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 2.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount. */
+ value_nv += (NV)(bit - '0');
+ continue;
+ }
+ if (bit == '_' && len && allow_underscores && (bit = s[1])
+ && (bit == '0' || bit == '1'))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal binary digit '%c' ignored", *s);
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Binary number > 0b11111111111111111111111111111111 non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_hex
+#if defined(NEED_grok_hex)
+static UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_hex)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#if defined(NEED_grok_hex) || defined(NEED_grok_hex_GLOBAL)
+
+#ifdef grok_hex
+# undef grok_hex
+#endif
+#define grok_hex(a,b,c,d) DPPP_(my_grok_hex)(aTHX_ a,b,c,d)
+#define Perl_grok_hex DPPP_(my_grok_hex)
+
+UV
+DPPP_(my_grok_hex)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_16 = UV_MAX / 16;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+ const char *xdigit;
+
+ if (!(*flags & PERL_SCAN_DISALLOW_PREFIX)) {
+ /* strip off leading x or 0x.
+ for compatibility silently suffer "x" and "0x" as valid hex numbers.
+ */
+ if (len >= 1) {
+ if (s[0] == 'x') {
+ s++;
+ len--;
+ }
+ else if (len >= 2 && s[0] == '0' && s[1] == 'x') {
+ s+=2;
+ len-=2;
+ }
+ }
+ }
+
+ for (; len-- && *s; s++) {
+ xdigit = strchr((char *) PL_hexdigit, *s);
+ if (xdigit) {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ With gcc seems to be much straighter code than old scan_hex. */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_16) {
+ value = (value << 4) | ((xdigit - PL_hexdigit) & 15);
+ continue;
+ }
+ warn("Integer overflow in hexadecimal number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 16.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount of 16-tuples. */
+ value_nv += (NV)((xdigit - PL_hexdigit) & 15);
+ continue;
+ }
+ if (*s == '_' && len && allow_underscores && s[1]
+ && (xdigit = strchr((char *) PL_hexdigit, s[1])))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal hexadecimal digit '%c' ignored", *s);
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Hexadecimal number > 0xffffffff non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#ifndef grok_oct
+#if defined(NEED_grok_oct)
+static UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+static
+#else
+extern UV DPPP_(my_grok_oct)(pTHX_ const char * start, STRLEN * len_p, I32 * flags, NV * result);
+#endif
+
+#if defined(NEED_grok_oct) || defined(NEED_grok_oct_GLOBAL)
+
+#ifdef grok_oct
+# undef grok_oct
+#endif
+#define grok_oct(a,b,c,d) DPPP_(my_grok_oct)(aTHX_ a,b,c,d)
+#define Perl_grok_oct DPPP_(my_grok_oct)
+
+UV
+DPPP_(my_grok_oct)(pTHX_ const char *start, STRLEN *len_p, I32 *flags, NV *result)
+{
+ const char *s = start;
+ STRLEN len = *len_p;
+ UV value = 0;
+ NV value_nv = 0;
+
+ const UV max_div_8 = UV_MAX / 8;
+ bool allow_underscores = *flags & PERL_SCAN_ALLOW_UNDERSCORES;
+ bool overflowed = FALSE;
+
+ for (; len-- && *s; s++) {
+ /* gcc 2.95 optimiser not smart enough to figure that this subtraction
+ out front allows slicker code. */
+ int digit = *s - '0';
+ if (digit >= 0 && digit <= 7) {
+ /* Write it in this wonky order with a goto to attempt to get the
+ compiler to make the common case integer-only loop pretty tight.
+ */
+ redo:
+ if (!overflowed) {
+ if (value <= max_div_8) {
+ value = (value << 3) | digit;
+ continue;
+ }
+ /* Bah. We're just overflowed. */
+ warn("Integer overflow in octal number");
+ overflowed = TRUE;
+ value_nv = (NV) value;
+ }
+ value_nv *= 8.0;
+ /* If an NV has not enough bits in its mantissa to
+ * represent a UV this summing of small low-order numbers
+ * is a waste of time (because the NV cannot preserve
+ * the low-order bits anyway): we could just remember when
+ * did we overflow and in the end just multiply value_nv by the
+ * right amount of 8-tuples. */
+ value_nv += (NV)digit;
+ continue;
+ }
+ if (digit == ('_' - '0') && len && allow_underscores
+ && (digit = s[1] - '0') && (digit >= 0 && digit <= 7))
+ {
+ --len;
+ ++s;
+ goto redo;
+ }
+ /* Allow \octal to work the DWIM way (that is, stop scanning
+ * as soon as non-octal characters are seen, complain only iff
+ * someone seems to want to use the digits eight and nine). */
+ if (digit == 8 || digit == 9) {
+ if (!(*flags & PERL_SCAN_SILENT_ILLDIGIT))
+ warn("Illegal octal digit '%c' ignored", *s);
+ }
+ break;
+ }
+
+ if ( ( overflowed && value_nv > 4294967295.0)
+#if UVSIZE > 4
+ || (!overflowed && value > 0xffffffff )
+#endif
+ ) {
+ warn("Octal number > 037777777777 non-portable");
+ }
+ *len_p = s - start;
+ if (!overflowed) {
+ *flags = 0;
+ return value;
+ }
+ *flags = PERL_SCAN_GREATER_THAN_UV_MAX;
+ if (result)
+ *result = value_nv;
+ return UV_MAX;
+}
+#endif
+#endif
+
+#if !defined(my_snprintf)
+#if defined(NEED_my_snprintf)
+static int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+static
+#else
+extern int DPPP_(my_my_snprintf)(char * buffer, const Size_t len, const char * format, ...);
+#endif
+
+#if defined(NEED_my_snprintf) || defined(NEED_my_snprintf_GLOBAL)
+
+#define my_snprintf DPPP_(my_my_snprintf)
+#define Perl_my_snprintf DPPP_(my_my_snprintf)
+
+
+int
+DPPP_(my_my_snprintf)(char *buffer, const Size_t len, const char *format, ...)
+{
+ dTHX;
+ int retval;
+ va_list ap;
+ va_start(ap, format);
+#ifdef HAS_VSNPRINTF
+ retval = vsnprintf(buffer, len, format, ap);
+#else
+ retval = vsprintf(buffer, format, ap);
+#endif
+ va_end(ap);
+ if (retval < 0 || (len > 0 && (Size_t)retval >= len))
+ Perl_croak(aTHX_ "panic: my_snprintf buffer overflow");
+ return retval;
+}
+
+#endif
+#endif
+
+#if !defined(my_sprintf)
+#if defined(NEED_my_sprintf)
+static int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+static
+#else
+extern int DPPP_(my_my_sprintf)(char * buffer, const char * pat, ...);
+#endif
+
+#if defined(NEED_my_sprintf) || defined(NEED_my_sprintf_GLOBAL)
+
+#define my_sprintf DPPP_(my_my_sprintf)
+
+
+/* Warning: my_sprintf
+ It's safer to use my_snprintf instead
+*/
+
+/* Replace my_sprintf with my_snprintf */
+
+int
+DPPP_(my_my_sprintf)(char *buffer, const char* pat, ...)
+{
+ va_list args;
+ va_start(args, pat);
+ vsprintf(buffer, pat, args);
+ va_end(args);
+ return strlen(buffer);
+}
+
+#endif
+#endif
+
+#ifdef NO_XSLOCKS
+# ifdef dJMPENV
+# define dXCPT dJMPENV; int rEtV = 0
+# define XCPT_TRY_START JMPENV_PUSH(rEtV); if (rEtV == 0)
+# define XCPT_TRY_END JMPENV_POP;
+# define XCPT_CATCH if (rEtV != 0)
+# define XCPT_RETHROW JMPENV_JUMP(rEtV)
+# else
+# define dXCPT Sigjmp_buf oldTOP; int rEtV = 0
+# define XCPT_TRY_START Copy(top_env, oldTOP, 1, Sigjmp_buf); rEtV = Sigsetjmp(top_env, 1); if (rEtV == 0)
+# define XCPT_TRY_END Copy(oldTOP, top_env, 1, Sigjmp_buf);
+# define XCPT_CATCH if (rEtV != 0)
+# define XCPT_RETHROW Siglongjmp(top_env, rEtV)
+# endif
+#endif
+
+#if !defined(my_strlcat)
+#if defined(NEED_my_strlcat)
+static Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcat)(char * dst, const char * src, Size_t size);
+#endif
+
+#if defined(NEED_my_strlcat) || defined(NEED_my_strlcat_GLOBAL)
+
+#define my_strlcat DPPP_(my_my_strlcat)
+#define Perl_my_strlcat DPPP_(my_my_strlcat)
+
+
+Size_t
+DPPP_(my_my_strlcat)(char *dst, const char *src, Size_t size)
+{
+ Size_t used, length, copy;
+
+ used = strlen(dst);
+ length = strlen(src);
+ if (size > 0 && used < size - 1) {
+ copy = (length >= size - used) ? size - used - 1 : length;
+ memcpy(dst + used, src, copy);
+ dst[used + copy] = '\0';
+ }
+ return used + length;
+}
+#endif
+#endif
+
+#if !defined(my_strlcpy)
+#if defined(NEED_my_strlcpy)
+static Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+static
+#else
+extern Size_t DPPP_(my_my_strlcpy)(char * dst, const char * src, Size_t size);
+#endif
+
+#if defined(NEED_my_strlcpy) || defined(NEED_my_strlcpy_GLOBAL)
+
+#define my_strlcpy DPPP_(my_my_strlcpy)
+#define Perl_my_strlcpy DPPP_(my_my_strlcpy)
+
+
+Size_t
+DPPP_(my_my_strlcpy)(char *dst, const char *src, Size_t size)
+{
+ Size_t length, copy;
+
+ length = strlen(src);
+ if (size > 0) {
+ copy = (length >= size) ? size - 1 : length;
+ memcpy(dst, src, copy);
+ dst[copy] = '\0';
+ }
+ return length;
+}
+
+#endif
+#endif
+
+#ifdef SVf_UTF8
+#ifndef SvUTF8
+# define SvUTF8(sv) (SvFLAGS(sv) & SVf_UTF8)
+#endif
+
+#endif
+
+#if (PERL_BCDVERSION == 0x5019001) /* 5.19.1 does not have UTF8fARG, only broken UTF8f */
+#undef UTF8f
+#endif
+
+#ifdef SVf_UTF8
+#ifndef UTF8f
+# define UTF8f SVf
+#endif
+
+#ifndef UTF8fARG
+# define UTF8fARG(u,l,p) newSVpvn_flags((p), (l), ((u) ? SVf_UTF8 : 0) | SVs_TEMP)
+#endif
+
+#endif
+
+#define D_PPP_MIN(a,b) (((a) <= (b)) ? (a) : (b))
+#ifndef UNICODE_REPLACEMENT
+# define UNICODE_REPLACEMENT 0xFFFD
+#endif
+
+#ifdef UTF8_MAXLEN
+#ifndef UTF8_MAXBYTES
+# define UTF8_MAXBYTES UTF8_MAXLEN
+#endif
+
+#endif
+#ifndef UTF_START_MARK
+# define UTF_START_MARK(len) \
+ (((len) > 7) ? 0xFF : (0xFF & (0xFE << (7-(len)))))
+#endif
+
+/* On non-EBCDIC was valid for some releases earlier than this, but easier to
+ * just do one check */
+#if (PERL_BCDVERSION < 0x5018000)
+# undef UTF8_MAXBYTES_CASE
+#endif
+
+#if 'A' == 65
+# define D_PPP_BYTE_INFO_BITS 6 /* 6 bits meaningful in continuation bytes */
+#ifndef UTF8_MAXBYTES_CASE
+# define UTF8_MAXBYTES_CASE 13
+#endif
+
+#else
+# define D_PPP_BYTE_INFO_BITS 5 /* 5 bits meaningful in continuation bytes */
+#ifndef UTF8_MAXBYTES_CASE
+# define UTF8_MAXBYTES_CASE 15
+#endif
+
+#endif
+#ifndef UTF_ACCUMULATION_SHIFT
+# define UTF_ACCUMULATION_SHIFT D_PPP_BYTE_INFO_BITS
+#endif
+
+#ifdef NATIVE_TO_UTF
+#ifndef NATIVE_UTF8_TO_I8
+# define NATIVE_UTF8_TO_I8(c) NATIVE_TO_UTF(c)
+#endif
+
+#else /* System doesn't support EBCDIC */
+#ifndef NATIVE_UTF8_TO_I8
+# define NATIVE_UTF8_TO_I8(c) (c)
+#endif
+
+#endif
+
+#ifdef UTF_TO_NATIVE
+#ifndef I8_TO_NATIVE_UTF8
+# define I8_TO_NATIVE_UTF8(c) UTF_TO_NATIVE(c)
+#endif
+
+#else /* System doesn't support EBCDIC */
+#ifndef I8_TO_NATIVE_UTF8
+# define I8_TO_NATIVE_UTF8(c) (c)
+#endif
+
+#endif
+#ifndef UTF_START_MASK
+# define UTF_START_MASK(len) \
+ (((len) >= 7) ? 0x00 : (0x1F >> ((len)-2)))
+#endif
+
+#ifndef UTF_IS_CONTINUATION_MASK
+# define UTF_IS_CONTINUATION_MASK \
+ ((U8) (0xFF << UTF_ACCUMULATION_SHIFT))
+#endif
+
+#ifndef UTF_CONTINUATION_MARK
+# define UTF_CONTINUATION_MARK \
+ (UTF_IS_CONTINUATION_MASK & 0xB0)
+#endif
+
+#ifndef UTF_MIN_START_BYTE
+# define UTF_MIN_START_BYTE \
+ ((UTF_CONTINUATION_MARK >> UTF_ACCUMULATION_SHIFT) | UTF_START_MARK(2))
+#endif
+#ifndef UTF_MIN_ABOVE_LATIN1_BYTE
+# define UTF_MIN_ABOVE_LATIN1_BYTE \
+ ((0x100 >> UTF_ACCUMULATION_SHIFT) | UTF_START_MARK(2))
+#endif
+
+#if (PERL_BCDVERSION < 0x5007000) /* Was the complement of what should have been */
+# undef UTF8_IS_DOWNGRADEABLE_START
+#endif
+#ifndef UTF8_IS_DOWNGRADEABLE_START
+# define UTF8_IS_DOWNGRADEABLE_START(c) \
+ inRANGE(NATIVE_UTF8_TO_I8(c), \
+ UTF_MIN_START_BYTE, UTF_MIN_ABOVE_LATIN1_BYTE - 1)
+#endif
+
+#ifndef UTF_CONTINUATION_MASK
+# define UTF_CONTINUATION_MASK \
+ ((U8) ((1U << UTF_ACCUMULATION_SHIFT) - 1))
+#endif
+#ifndef UTF8_ACCUMULATE
+# define UTF8_ACCUMULATE(base, added) \
+ (((base) << UTF_ACCUMULATION_SHIFT) \
+ | ((NATIVE_UTF8_TO_I8(added)) \
+ & UTF_CONTINUATION_MASK))
+#endif
+#ifndef UTF8_ALLOW_ANYUV
+# define UTF8_ALLOW_ANYUV 0
+#endif
+
+#ifndef UTF8_ALLOW_EMPTY
+# define UTF8_ALLOW_EMPTY 0x0001
+#endif
+
+#ifndef UTF8_ALLOW_CONTINUATION
+# define UTF8_ALLOW_CONTINUATION 0x0002
+#endif
+
+#ifndef UTF8_ALLOW_NON_CONTINUATION
+# define UTF8_ALLOW_NON_CONTINUATION 0x0004
+#endif
+
+#ifndef UTF8_ALLOW_SHORT
+# define UTF8_ALLOW_SHORT 0x0008
+#endif
+
+#ifndef UTF8_ALLOW_LONG
+# define UTF8_ALLOW_LONG 0x0010
+#endif
+
+#ifndef UTF8_ALLOW_OVERFLOW
+# define UTF8_ALLOW_OVERFLOW 0x0080
+#endif
+
+#ifndef UTF8_ALLOW_ANY
+# define UTF8_ALLOW_ANY ( UTF8_ALLOW_CONTINUATION \
+ |UTF8_ALLOW_NON_CONTINUATION \
+ |UTF8_ALLOW_SHORT \
+ |UTF8_ALLOW_LONG \
+ |UTF8_ALLOW_OVERFLOW)
+#endif
+
+#if defined UTF8SKIP
+
+/* Don't use official versions because they use MIN, which may not be available */
+#undef UTF8_SAFE_SKIP
+#undef UTF8_CHK_SKIP
+#ifndef UTF8_SAFE_SKIP
+# define UTF8_SAFE_SKIP(s, e) ( \
+ ((((e) - (s)) <= 0) \
+ ? 0 \
+ : D_PPP_MIN(((e) - (s)), UTF8SKIP(s))))
+#endif
+#ifndef UTF8_CHK_SKIP
+# define UTF8_CHK_SKIP(s) \
+ (s[0] == '\0' ? 1 : ((U8) D_PPP_MIN(my_strnlen((char *) (s), UTF8SKIP(s)), \
+ UTF8SKIP(s))))
+#endif
+
+/* UTF8_CHK_SKIP depends on my_strnlen */
+#ifndef UTF8_SKIP
+# define UTF8_SKIP(s) UTF8SKIP(s)
+#endif
+
+#endif
+
+#if 'A' == 65
+#ifndef UTF8_IS_INVARIANT
+# define UTF8_IS_INVARIANT(c) isASCII(c)
+#endif
+
+#else
+#ifndef UTF8_IS_INVARIANT
+# define UTF8_IS_INVARIANT(c) (isASCII(c) || isCNTRL_L1(c))
+#endif
+
+#endif
+#ifndef UVCHR_IS_INVARIANT
+# define UVCHR_IS_INVARIANT(c) UTF8_IS_INVARIANT(c)
+#endif
+
+#ifdef UVCHR_IS_INVARIANT
+# if 'A' != 65 || UVSIZE < 8
+ /* 32 bit platform, which includes UTF-EBCDIC on the releases this is
+ * backported to */
+# define D_PPP_UVCHR_SKIP_UPPER(c) 7
+# else
+# define D_PPP_UVCHR_SKIP_UPPER(c) \
+ (((WIDEST_UTYPE) (c)) < \
+ (((WIDEST_UTYPE) 1) << (6 * D_PPP_BYTE_INFO_BITS)) ? 7 : 13)
+# endif
+#ifndef UVCHR_SKIP
+# define UVCHR_SKIP(c) \
+ UVCHR_IS_INVARIANT(c) ? 1 : \
+ (WIDEST_UTYPE) (c) < (32 * (1U << ( D_PPP_BYTE_INFO_BITS))) ? 2 : \
+ (WIDEST_UTYPE) (c) < (16 * (1U << (2 * D_PPP_BYTE_INFO_BITS))) ? 3 : \
+ (WIDEST_UTYPE) (c) < ( 8 * (1U << (3 * D_PPP_BYTE_INFO_BITS))) ? 4 : \
+ (WIDEST_UTYPE) (c) < ( 4 * (1U << (4 * D_PPP_BYTE_INFO_BITS))) ? 5 : \
+ (WIDEST_UTYPE) (c) < ( 2 * (1U << (5 * D_PPP_BYTE_INFO_BITS))) ? 6 : \
+ D_PPP_UVCHR_SKIP_UPPER(c)
+#endif
+
+#endif
+
+#ifdef is_ascii_string
+#ifndef is_invariant_string
+# define is_invariant_string(s,l) is_ascii_string(s,l)
+#endif
+
+#ifndef is_utf8_invariant_string
+# define is_utf8_invariant_string(s,l) is_ascii_string(s,l)
+#endif
+
+/* Hint: is_ascii_string, is_invariant_string
+ is_utf8_invariant_string() does the same thing and is preferred because its
+ name is more accurate as to what it does */
+#endif
+
+#ifdef ibcmp_utf8
+#ifndef foldEQ_utf8
+# define foldEQ_utf8(s1,pe1,l1,u1,s2,pe2,l2,u2) \
+ cBOOL(! ibcmp_utf8(s1,pe1,l1,u1,s2,pe2,l2,u2))
+#endif
+
+#endif
+
+#if defined(is_utf8_string) && defined(UTF8SKIP)
+#ifndef isUTF8_CHAR
+# define isUTF8_CHAR(s, e) ( \
+ (e) <= (s) || ! is_utf8_string(s, UTF8_SAFE_SKIP(s, e)) \
+ ? 0 \
+ : UTF8SKIP(s))
+#endif
+
+#endif
+
+#if 'A' == 65
+#ifndef BOM_UTF8
+# define BOM_UTF8 "\xEF\xBB\xBF"
+#endif
+
+#ifndef REPLACEMENT_CHARACTER_UTF8
+# define REPLACEMENT_CHARACTER_UTF8 "\xEF\xBF\xBD"
+#endif
+
+#elif '^' == 95
+#ifndef BOM_UTF8
+# define BOM_UTF8 "\xDD\x73\x66\x73"
+#endif
+
+#ifndef REPLACEMENT_CHARACTER_UTF8
+# define REPLACEMENT_CHARACTER_UTF8 "\xDD\x73\x73\x71"
+#endif
+
+#elif '^' == 176
+#ifndef BOM_UTF8
+# define BOM_UTF8 "\xDD\x72\x65\x72"
+#endif
+
+#ifndef REPLACEMENT_CHARACTER_UTF8
+# define REPLACEMENT_CHARACTER_UTF8 "\xDD\x72\x72\x70"
+#endif
+
+#else
+# error Unknown character set
+#endif
+
+#if (PERL_BCDVERSION < 0x5031004)
+ /* Versions prior to this accepted things that are now considered
+ * malformations, and didn't return -1 on error with warnings enabled
+ * */
+# undef utf8_to_uvchr_buf
+#endif
+
+/* This implementation brings modern, generally more restricted standards to
+ * utf8_to_uvchr_buf. Some of these are security related, and clearly must
+ * be done. But its arguable that the others need not, and hence should not.
+ * The reason they're here is that a module that intends to play with the
+ * latest perls should be able to work the same in all releases. An example is
+ * that perl no longer accepts any UV for a code point, but limits them to
+ * IV_MAX or below. This is for future internal use of the larger code points.
+ * If it turns out that some of these changes are breaking code that isn't
+ * intended to work with modern perls, the tighter restrictions could be
+ * relaxed. khw thinks this is unlikely, but has been wrong in the past. */
+
+/* 5.6.0 is the first release with UTF-8, and we don't implement this function
+ * there due to its likely lack of still being in use, and the underlying
+ * implementation is very different from later ones, without the later
+ * safeguards, so would require extra work to deal with */
+#if (PERL_BCDVERSION >= 0x5006001) && ! defined(utf8_to_uvchr_buf)
+ /* Choose which underlying implementation to use. At least one must be
+ * present or the perl is too early to handle this function */
+# if defined(utf8n_to_uvchr) || defined(utf8_to_uvchr) || defined(utf8_to_uv)
+# if defined(utf8n_to_uvchr) /* This is the preferred implementation */
+# define D_PPP_utf8_to_uvchr_buf_callee utf8n_to_uvchr
+# elif /* Must be at least 5.6.1 from #if above; \
+ If have both regular and _simple, regular has all args */ \
+ defined(utf8_to_uv) && defined(utf8_to_uv_simple)
+# define D_PPP_utf8_to_uvchr_buf_callee utf8_to_uv
+# elif defined(utf8_to_uvchr) /* The below won't work well on error input */
+# define D_PPP_utf8_to_uvchr_buf_callee(s, curlen, retlen, flags) \
+ utf8_to_uvchr((U8 *)(s), (retlen))
+# else
+# define D_PPP_utf8_to_uvchr_buf_callee(s, curlen, retlen, flags) \
+ utf8_to_uv((U8 *)(s), (retlen))
+# endif
+# endif
+
+# if defined(NEED_utf8_to_uvchr_buf)
+static UV DPPP_(my_utf8_to_uvchr_buf)(pTHX_ const U8 * s, const U8 * send, STRLEN * retlen);
+static
+#else
+extern UV DPPP_(my_utf8_to_uvchr_buf)(pTHX_ const U8 * s, const U8 * send, STRLEN * retlen);
+#endif
+
+#if defined(NEED_utf8_to_uvchr_buf) || defined(NEED_utf8_to_uvchr_buf_GLOBAL)
+
+#ifdef utf8_to_uvchr_buf
+# undef utf8_to_uvchr_buf
+#endif
+#define utf8_to_uvchr_buf(a,b,c) DPPP_(my_utf8_to_uvchr_buf)(aTHX_ a,b,c)
+#define Perl_utf8_to_uvchr_buf DPPP_(my_utf8_to_uvchr_buf)
+
+
+UV
+DPPP_(my_utf8_to_uvchr_buf)(pTHX_ const U8 *s, const U8 *send, STRLEN *retlen)
+{
+ UV ret;
+ STRLEN curlen;
+ bool overflows = 0;
+ const U8 *cur_s = s;
+ const bool do_warnings = ckWARN_d(WARN_UTF8);
+# if (PERL_BCDVERSION < 0x5026000) && ! defined(EBCDIC)
+ STRLEN overflow_length = 0;
+# endif
+
+ if (send > s) {
+ curlen = send - s;
+ }
+ else {
+ assert(0); /* Modern perls die under this circumstance */
+ curlen = 0;
+ if (! do_warnings) { /* Handle empty here if no warnings needed */
+ if (retlen) *retlen = 0;
+ return UNICODE_REPLACEMENT;
+ }
+ }
+
+# if (PERL_BCDVERSION < 0x5026000) && ! defined(EBCDIC)
+
+ /* Perl did not properly detect overflow for much of its history on
+ * non-EBCDIC platforms, often returning an overlong value which may or may
+ * not have been tolerated in the call. Also, earlier versions, when they
+ * did detect overflow, may have disallowed it completely. Modern ones can
+ * replace it with the REPLACEMENT CHARACTER, depending on calling
+ * parameters. Therefore detect it ourselves in releases it was
+ * problematic in. */
+
+ if (curlen > 0 && UNLIKELY(*s >= 0xFE)) {
+
+ /* First, on a 32-bit machine the first byte being at least \xFE
+ * automatically is overflow, as it indicates something requiring more
+ * than 31 bits */
+ if (sizeof(ret) < 8) {
+ overflows = 1;
+ overflow_length = (*s == 0xFE) ? 7 : 13;
+ }
+ else {
+ const U8 highest[] = /* 2*63-1 */
+ "\xFF\x80\x87\xBF\xBF\xBF\xBF\xBF\xBF\xBF\xBF\xBF\xBF";
+ const U8 *cur_h = highest;
+
+ for (cur_s = s; cur_s < send; cur_s++, cur_h++) {
+ if (UNLIKELY(*cur_s == *cur_h)) {
+ continue;
+ }
+
+ /* If this byte is larger than the corresponding highest UTF-8
+ * byte, the sequence overflows; otherwise the byte is less
+ * than (as we handled the equality case above), and so the
+ * sequence doesn't overflow */
+ overflows = *cur_s > *cur_h;
+ break;
+
+ }
+
+ /* Here, either we set the bool and broke out of the loop, or got
+ * to the end and all bytes are the same which indicates it doesn't
+ * overflow. If it did overflow, it would be this number of bytes
+ * */
+ overflow_length = 13;
+ }
+ }
+
+ if (UNLIKELY(overflows)) {
+ ret = 0;
+
+ if (! do_warnings && retlen) {
+ *retlen = overflow_length;
+ }
+ }
+ else
+
+# endif /* < 5.26 */
+
+ /* Here, we are either in a release that properly detects overflow, or
+ * we have checked for overflow and the next statement is executing as
+ * part of the above conditional where we know we don't have overflow.
+ *
+ * The modern versions allow anything that evaluates to a legal UV, but
+ * not overlongs nor an empty input */
+ ret = D_PPP_utf8_to_uvchr_buf_callee(
+ (U8 *) /* Early perls: no const */
+ s, curlen, retlen, (UTF8_ALLOW_ANYUV
+ & ~(UTF8_ALLOW_LONG|UTF8_ALLOW_EMPTY)));
+
+# if (PERL_BCDVERSION >= 0x5026000) && (PERL_BCDVERSION < 0x5028000)
+
+ /* But actually, more modern versions restrict the UV to being no more than
+ * what an IV can hold, so it could still have gotten it wrong about
+ * overflowing. */
+ if (UNLIKELY(ret > IV_MAX)) {
+ overflows = 1;
+ }
+
+# endif
+
+ if (UNLIKELY(overflows)) {
+ if (! do_warnings) {
+ if (retlen) {
+ *retlen = D_PPP_MIN(*retlen, UTF8SKIP(s));
+ *retlen = D_PPP_MIN(*retlen, curlen);
+ }
+ return UNICODE_REPLACEMENT;
+ }
+ else {
+
+ /* We use the error message in use from 5.8-5.26 */
+ Perl_warner(aTHX_ packWARN(WARN_UTF8),
+ "Malformed UTF-8 character (overflow at 0x%" UVxf
+ ", byte 0x%02x, after start byte 0x%02x)",
+ ret, *cur_s, *s);
+ if (retlen) {
+ *retlen = (STRLEN) -1;
+ }
+ return 0;
+ }
+ }
+
+ /* Here, did not overflow, but if it failed for some other reason, and
+ * warnings are off, to emulate the behavior of the real utf8_to_uvchr(),
+ * try again, allowing anything. (Note a return of 0 is ok if the input
+ * was '\0') */
+ if (UNLIKELY(ret == 0 && (curlen == 0 || *s != '\0'))) {
+
+ /* If curlen is 0, we already handled the case where warnings are
+ * disabled, so this 'if' will be true, and so later on, we know that
+ * 's' is dereferencible */
+ if (do_warnings) {
+ if (retlen) {
+ *retlen = (STRLEN) -1;
+ }
+ }
+ else {
+ ret = D_PPP_utf8_to_uvchr_buf_callee(
+ (U8 *) /* Early perls: no const */
+ s, curlen, retlen, UTF8_ALLOW_ANY);
+ /* Override with the REPLACEMENT character, as that is what the
+ * modern version of this function returns */
+ ret = UNICODE_REPLACEMENT;
+
+# if (PERL_BCDVERSION < 0x5016000)
+
+ /* Versions earlier than this don't necessarily return the proper
+ * length. It should not extend past the end of string, nor past
+ * what the first byte indicates the length is, nor past the
+ * continuation characters */
+ if (retlen && (IV) *retlen >= 0) {
+ unsigned int i = 1;
+
+ *retlen = D_PPP_MIN(*retlen, curlen);
+ *retlen = D_PPP_MIN(*retlen, UTF8SKIP(s));
+ do {
+# ifdef UTF8_IS_CONTINUATION
+ if (! UTF8_IS_CONTINUATION(s[i]))
+# else /* Versions without the above don't support EBCDIC anyway */
+ if (s[i] < 0x80 || s[i] > 0xBF)
+# endif
+ {
+ *retlen = i;
+ break;
+ }
+ } while (++i < *retlen);
+ }
+
+# endif
+
+ }
+ }
+
+ return ret;
+}
+
+# endif
+#endif
+
+#if defined(UTF8SKIP) && defined(utf8_to_uvchr_buf)
+#undef utf8_to_uvchr /* Always redefine this unsafe function so that it refuses
+ to read past a NUL, making it much less likely to read
+ off the end of the buffer. A NUL indicates the start
+ of the next character anyway. If the input isn't
+ NUL-terminated, the function remains unsafe, as it
+ always has been. */
+#ifndef utf8_to_uvchr
+# define utf8_to_uvchr(s, lp) \
+ ((*(s) == '\0') \
+ ? utf8_to_uvchr_buf(s,((s)+1), lp) /* Handle single NUL specially */ \
+ : utf8_to_uvchr_buf(s, (s) + UTF8_CHK_SKIP(s), (lp)))
+#endif
+
+#endif
+
+/* Hint: utf8_to_uvchr
+ Use utf8_to_uvchr_buf() instead. But ONLY if you KNOW the upper bound
+ of the input string (not resorting to using UTF8SKIP, etc., to infer it).
+ The backported utf8_to_uvchr() will do a better job to prevent most cases
+ of trying to read beyond the end of the buffer */
+
+/* Replace utf8_to_uvchr with utf8_to_uvchr_buf */
+
+#ifdef sv_len_utf8
+ /* Older Perl versions have broken sv_len_utf8() when passed sv does not have SVf_UTF8 flag set */
+ /* Also note that SvGETMAGIC() may change presence of SVf_UTF8 flag */
+# if (PERL_BCDVERSION < 0x5017005)
+# undef sv_len_utf8
+# if defined(PERL_USE_GCC_BRACE_GROUPS)
+# define sv_len_utf8_nomg(sv) ({ SV *_sv2 = (sv); (SvUTF8(_sv2) ? Perl_sv_len_utf8(aTHX_ (!SvGMAGICAL(_sv2) ? _sv2 : sv_mortalcopy_flags(_sv2, SV_NOSTEAL))) : ({ STRLEN _len; SvPV_nomg(_sv2, _len); _len; })); })
+# define sv_len_utf8(sv) ({ SV *_sv1 = (sv); SvGETMAGIC(_sv1); sv_len_utf8_nomg(_sv1); })
+# else
+# define sv_len_utf8_nomg(sv) (PL_Sv = (sv), (SvUTF8(PL_Sv) ? Perl_sv_len_utf8(aTHX_ (!SvGMAGICAL(PL_Sv) ? PL_Sv : sv_mortalcopy_flags(PL_Sv, SV_NOSTEAL))) : (SvPV_nomg(PL_Sv, PL_na), PL_na)))
+# define sv_len_utf8(sv) (PL_Sv = (sv), SvGETMAGIC(PL_Sv), sv_len_utf8_nomg(PL_Sv))
+# endif
+# endif
+# if defined(PERL_USE_GCC_BRACE_GROUPS)
+#ifndef sv_len_utf8_nomg
+# define sv_len_utf8_nomg(sv) ({ SV *_sv = (sv); sv_len_utf8(!SvGMAGICAL(_sv) ? _sv : sv_mortalcopy_flags(_sv, SV_NOSTEAL)); })
+#endif
+
+# else
+#ifndef sv_len_utf8_nomg
+# define sv_len_utf8_nomg(sv) ((PL_Sv = (sv)), sv_len_utf8(!SvGMAGICAL(PL_Sv) ? PL_Sv : sv_mortalcopy_flags(PL_Sv, SV_NOSTEAL)))
+#endif
+
+# endif
+#endif
+#ifndef PERL_PV_ESCAPE_QUOTE
+# define PERL_PV_ESCAPE_QUOTE 0x0001
+#endif
+
+#ifndef PERL_PV_PRETTY_QUOTE
+# define PERL_PV_PRETTY_QUOTE PERL_PV_ESCAPE_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_ELLIPSES
+# define PERL_PV_PRETTY_ELLIPSES 0x0002
+#endif
+
+#ifndef PERL_PV_PRETTY_LTGT
+# define PERL_PV_PRETTY_LTGT 0x0004
+#endif
+
+#ifndef PERL_PV_ESCAPE_FIRSTCHAR
+# define PERL_PV_ESCAPE_FIRSTCHAR 0x0008
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI
+# define PERL_PV_ESCAPE_UNI 0x0100
+#endif
+
+#ifndef PERL_PV_ESCAPE_UNI_DETECT
+# define PERL_PV_ESCAPE_UNI_DETECT 0x0200
+#endif
+
+#ifndef PERL_PV_ESCAPE_ALL
+# define PERL_PV_ESCAPE_ALL 0x1000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOBACKSLASH
+# define PERL_PV_ESCAPE_NOBACKSLASH 0x2000
+#endif
+
+#ifndef PERL_PV_ESCAPE_NOCLEAR
+# define PERL_PV_ESCAPE_NOCLEAR 0x4000
+#endif
+
+#ifndef PERL_PV_ESCAPE_RE
+# define PERL_PV_ESCAPE_RE 0x8000
+#endif
+
+#ifndef PERL_PV_PRETTY_NOCLEAR
+# define PERL_PV_PRETTY_NOCLEAR PERL_PV_ESCAPE_NOCLEAR
+#endif
+#ifndef PERL_PV_PRETTY_DUMP
+# define PERL_PV_PRETTY_DUMP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_QUOTE
+#endif
+
+#ifndef PERL_PV_PRETTY_REGPROP
+# define PERL_PV_PRETTY_REGPROP PERL_PV_PRETTY_ELLIPSES|PERL_PV_PRETTY_LTGT|PERL_PV_ESCAPE_RE
+#endif
+
+/* Hint: pv_escape
+ * Note that unicode functionality is only backported to
+ * those perl versions that support it. For older perl
+ * versions, the implementation will fall back to bytes.
+ */
+
+#ifndef pv_escape
+#if defined(NEED_pv_escape)
+static char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_escape)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, STRLEN * const escaped, const U32 flags);
+#endif
+
+#if defined(NEED_pv_escape) || defined(NEED_pv_escape_GLOBAL)
+
+#ifdef pv_escape
+# undef pv_escape
+#endif
+#define pv_escape(a,b,c,d,e,f) DPPP_(my_pv_escape)(aTHX_ a,b,c,d,e,f)
+#define Perl_pv_escape DPPP_(my_pv_escape)
+
+
+char *
+DPPP_(my_pv_escape)(pTHX_ SV *dsv, char const * const str,
+ const STRLEN count, const STRLEN max,
+ STRLEN * const escaped, const U32 flags)
+{
+ const char esc = flags & PERL_PV_ESCAPE_RE ? '%' : '\\';
+ const char dq = flags & PERL_PV_ESCAPE_QUOTE ? '"' : esc;
+ char octbuf[32] = "%123456789ABCDF";
+ STRLEN wrote = 0;
+ STRLEN chsize = 0;
+ STRLEN readsize = 1;
+#if defined(is_utf8_string) && defined(utf8_to_uvchr_buf)
+ bool isuni = flags & PERL_PV_ESCAPE_UNI ? 1 : 0;
+#endif
+ const char *pv = str;
+ const char * const end = pv + count;
+ octbuf[0] = esc;
+
+ if (!(flags & PERL_PV_ESCAPE_NOCLEAR))
+ sv_setpvs(dsv, "");
+
+#if defined(is_utf8_string) && defined(utf8_to_uvchr_buf)
+ if ((flags & PERL_PV_ESCAPE_UNI_DETECT) && is_utf8_string((U8*)pv, count))
+ isuni = 1;
+#endif
+
+ for (; pv < end && (!max || wrote < max) ; pv += readsize) {
+ const UV u =
+#if defined(is_utf8_string) && defined(utf8_to_uvchr_buf)
+ isuni ? utf8_to_uvchr_buf((U8*)pv, end, &readsize) :
+#endif
+ (U8)*pv;
+ const U8 c = (U8)u & 0xFF;
+
+ if (u > 255 || (flags & PERL_PV_ESCAPE_ALL)) {
+ if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+ chsize = my_snprintf(octbuf, sizeof octbuf,
+ "%" UVxf, u);
+ else
+ chsize = my_snprintf(octbuf, sizeof octbuf,
+ "%cx{%" UVxf "}", esc, u);
+ } else if (flags & PERL_PV_ESCAPE_NOBACKSLASH) {
+ chsize = 1;
+ } else {
+ if (c == dq || c == esc || !isPRINT(c)) {
+ chsize = 2;
+ switch (c) {
+ case '\\' : /* fallthrough */
+ case '%' : if (c == esc)
+ octbuf[1] = esc;
+ else
+ chsize = 1;
+ break;
+ case '\v' : octbuf[1] = 'v'; break;
+ case '\t' : octbuf[1] = 't'; break;
+ case '\r' : octbuf[1] = 'r'; break;
+ case '\n' : octbuf[1] = 'n'; break;
+ case '\f' : octbuf[1] = 'f'; break;
+ case '"' : if (dq == '"')
+ octbuf[1] = '"';
+ else
+ chsize = 1;
+ break;
+ default: chsize = my_snprintf(octbuf, sizeof octbuf,
+ pv < end && isDIGIT((U8)*(pv+readsize))
+ ? "%c%03o" : "%c%o", esc, c);
+ }
+ } else {
+ chsize = 1;
+ }
+ }
+ if (max && wrote + chsize > max) {
+ break;
+ } else if (chsize > 1) {
+ sv_catpvn(dsv, octbuf, chsize);
+ wrote += chsize;
+ } else {
+ char tmp[2];
+ my_snprintf(tmp, sizeof tmp, "%c", c);
+ sv_catpvn(dsv, tmp, 1);
+ wrote++;
+ }
+ if (flags & PERL_PV_ESCAPE_FIRSTCHAR)
+ break;
+ }
+ if (escaped != NULL)
+ *escaped= pv - str;
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_pretty
+#if defined(NEED_pv_pretty)
+static char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+static
+#else
+extern char * DPPP_(my_pv_pretty)(pTHX_ SV * dsv, char const * const str, const STRLEN count, const STRLEN max, char const * const start_color, char const * const end_color, const U32 flags);
+#endif
+
+#if defined(NEED_pv_pretty) || defined(NEED_pv_pretty_GLOBAL)
+
+#ifdef pv_pretty
+# undef pv_pretty
+#endif
+#define pv_pretty(a,b,c,d,e,f,g) DPPP_(my_pv_pretty)(aTHX_ a,b,c,d,e,f,g)
+#define Perl_pv_pretty DPPP_(my_pv_pretty)
+
+
+char *
+DPPP_(my_pv_pretty)(pTHX_ SV *dsv, char const * const str, const STRLEN count,
+ const STRLEN max, char const * const start_color, char const * const end_color,
+ const U32 flags)
+{
+ const U8 dq = (flags & PERL_PV_PRETTY_QUOTE) ? '"' : '%';
+ STRLEN escaped;
+
+ if (!(flags & PERL_PV_PRETTY_NOCLEAR))
+ sv_setpvs(dsv, "");
+
+ if (dq == '"')
+ sv_catpvs(dsv, "\"");
+ else if (flags & PERL_PV_PRETTY_LTGT)
+ sv_catpvs(dsv, "<");
+
+ if (start_color != NULL)
+ sv_catpv(dsv, D_PPP_CONSTPV_ARG(start_color));
+
+ pv_escape(dsv, str, count, max, &escaped, flags | PERL_PV_ESCAPE_NOCLEAR);
+
+ if (end_color != NULL)
+ sv_catpv(dsv, D_PPP_CONSTPV_ARG(end_color));
+
+ if (dq == '"')
+ sv_catpvs(dsv, "\"");
+ else if (flags & PERL_PV_PRETTY_LTGT)
+ sv_catpvs(dsv, ">");
+
+ if ((flags & PERL_PV_PRETTY_ELLIPSES) && escaped < count)
+ sv_catpvs(dsv, "...");
+
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#ifndef pv_display
+#if defined(NEED_pv_display)
+static char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+static
+#else
+extern char * DPPP_(my_pv_display)(pTHX_ SV * dsv, const char * pv, STRLEN cur, STRLEN len, STRLEN pvlim);
+#endif
+
+#if defined(NEED_pv_display) || defined(NEED_pv_display_GLOBAL)
+
+#ifdef pv_display
+# undef pv_display
+#endif
+#define pv_display(a,b,c,d,e) DPPP_(my_pv_display)(aTHX_ a,b,c,d,e)
+#define Perl_pv_display DPPP_(my_pv_display)
+
+
+char *
+DPPP_(my_pv_display)(pTHX_ SV *dsv, const char *pv, STRLEN cur, STRLEN len, STRLEN pvlim)
+{
+ pv_pretty(dsv, pv, cur, pvlim, NULL, NULL, PERL_PV_PRETTY_DUMP);
+ if (len > cur && pv[cur] == '\0')
+ sv_catpvs(dsv, "\\0");
+ return SvPVX(dsv);
+}
+
+#endif
+#endif
+
+#if PERL_VERSION_LT(5,27,9)
+#ifndef LC_NUMERIC_LOCK
+# define LC_NUMERIC_LOCK
+#endif
+
+#ifndef LC_NUMERIC_UNLOCK
+# define LC_NUMERIC_UNLOCK
+#endif
+
+# if PERL_VERSION_LT(5,19,0)
+# undef STORE_LC_NUMERIC_SET_STANDARD
+# undef RESTORE_LC_NUMERIC
+# undef DECLARATION_FOR_LC_NUMERIC_MANIPULATION
+# ifdef USE_LOCALE
+#ifndef DECLARATION_FOR_LC_NUMERIC_MANIPULATION
+# define DECLARATION_FOR_LC_NUMERIC_MANIPULATION char *LoC_
+#endif
+
+#ifndef STORE_NUMERIC_SET_STANDARD
+# define STORE_NUMERIC_SET_STANDARD() \
+ LoC_ = savepv(setlocale(LC_NUMERIC, NULL)); \
+ SAVEFREEPV(LoC_); \
+ setlocale(LC_NUMERIC, "C");
+#endif
+
+#ifndef RESTORE_LC_NUMERIC
+# define RESTORE_LC_NUMERIC() \
+ setlocale(LC_NUMERIC, LoC_);
+#endif
+
+# else
+#ifndef DECLARATION_FOR_LC_NUMERIC_MANIPULATION
+# define DECLARATION_FOR_LC_NUMERIC_MANIPULATION
+#endif
+
+#ifndef STORE_LC_NUMERIC_SET_STANDARD
+# define STORE_LC_NUMERIC_SET_STANDARD()
+#endif
+
+#ifndef RESTORE_LC_NUMERIC
+# define RESTORE_LC_NUMERIC()
+#endif
+
+# endif
+# endif
+#endif
+
+#ifndef LOCK_NUMERIC_STANDARD
+# define LOCK_NUMERIC_STANDARD()
+#endif
+
+#ifndef UNLOCK_NUMERIC_STANDARD
+# define UNLOCK_NUMERIC_STANDARD()
+#endif
+
+/* The names of these changed in 5.28 */
+#ifndef LOCK_LC_NUMERIC_STANDARD
+# define LOCK_LC_NUMERIC_STANDARD LOCK_NUMERIC_STANDARD
+#endif
+
+#ifndef UNLOCK_LC_NUMERIC_STANDARD
+# define UNLOCK_LC_NUMERIC_STANDARD UNLOCK_NUMERIC_STANDARD
+#endif
+
+/* If this doesn't exist, it's not needed, so is void noop */
+#ifndef switch_to_global_locale
+# define switch_to_global_locale()
+#endif
+
+/* Originally, this didn't return a value, but in perls like that, the value
+ * should always be TRUE. Add a return to Perl_sync_locale() when it's
+ * available. And actually do a sync when its not, if locales are available on
+ * this system. */
+#ifdef sync_locale
+# if (PERL_BCDVERSION < 0x5027009)
+# if (PERL_BCDVERSION >= 0x5021003)
+# undef sync_locale
+# define sync_locale() (Perl_sync_locale(aTHX), 1)
+# elif defined(sync_locale) /* These should only be the 5.20 maints*/
+# undef sync_locale /* Just copy their defn and return 1 */
+# define sync_locale() (new_ctype(setlocale(LC_CTYPE, NULL)), \
+ new_collate(setlocale(LC_COLLATE, NULL)), \
+ set_numeric_local(), \
+ new_numeric(setlocale(LC_NUMERIC, NULL)), \
+ 1)
+# elif defined(new_ctype) && defined(LC_CTYPE)
+# define sync_locale() (new_ctype(setlocale(LC_CTYPE, NULL)), 1)
+# endif
+# endif
+#endif
+#ifndef sync_locale
+# define sync_locale() 1
+#endif
+
+#endif /* _P_P_PORTABILITY_H_ */
+
+/* End of File ppport.h */
diff --git a/src/pl/plperl/sql/plperl.sql b/src/pl/plperl/sql/plperl.sql
new file mode 100644
index 0000000..bb0b8ce
--- /dev/null
+++ b/src/pl/plperl/sql/plperl.sql
@@ -0,0 +1,523 @@
+--
+-- Test result value processing
+--
+
+CREATE OR REPLACE FUNCTION perl_int(int) RETURNS INTEGER AS $$
+return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_int(11);
+SELECT * FROM perl_int(42);
+
+CREATE OR REPLACE FUNCTION perl_int(int) RETURNS INTEGER AS $$
+return $_[0] + 1;
+$$ LANGUAGE plperl;
+
+SELECT perl_int(11);
+SELECT * FROM perl_int(42);
+
+
+CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
+return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_set_int(5);
+SELECT * FROM perl_set_int(5);
+
+CREATE OR REPLACE FUNCTION perl_set_int(int) RETURNS SETOF INTEGER AS $$
+return [0..$_[0]];
+$$ LANGUAGE plperl;
+
+SELECT perl_set_int(5);
+SELECT * FROM perl_set_int(5);
+
+
+CREATE TYPE testnestperl AS (f5 integer[]);
+CREATE TYPE testrowperl AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+
+CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$
+ return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_row();
+SELECT * FROM perl_row();
+
+
+CREATE OR REPLACE FUNCTION perl_row() RETURNS testrowperl AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world', 'f4' => { 'f5' => [[1]] } };
+$$ LANGUAGE plperl;
+
+SELECT perl_row();
+SELECT * FROM perl_row();
+
+-- test returning a composite literal
+CREATE OR REPLACE FUNCTION perl_row_lit() RETURNS testrowperl AS $$
+ return '(1,hello,world,"({{1}})")';
+$$ LANGUAGE plperl;
+
+SELECT perl_row_lit();
+
+
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_set();
+SELECT * FROM perl_set();
+
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ undef,
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => {} },
+ { f1 => 4, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => undef }},
+ { f1 => 5, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => '{1}' }},
+ { f1 => 6, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => [1] }},
+ ];
+$$ LANGUAGE plperl;
+
+SELECT perl_set();
+SELECT * FROM perl_set();
+
+CREATE OR REPLACE FUNCTION perl_set() RETURNS SETOF testrowperl AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL', 'f4' => undef },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => {} },
+ { f1 => 4, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => undef }},
+ { f1 => 5, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => '{1}' }},
+ { f1 => 6, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => { 'f5' => [1] }},
+ { f1 => 7, f2 => 'Hello', f3 => 'PL/Perl', 'f4' => '({1})' },
+ ];
+$$ LANGUAGE plperl;
+
+SELECT perl_set();
+SELECT * FROM perl_set();
+
+CREATE OR REPLACE FUNCTION perl_record() RETURNS record AS $$
+ return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_record();
+SELECT * FROM perl_record();
+SELECT * FROM perl_record() AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+
+CREATE OR REPLACE FUNCTION perl_record() RETURNS record AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world', 'f4' => { 'f5' => [1] } };
+$$ LANGUAGE plperl;
+
+SELECT perl_record();
+SELECT * FROM perl_record();
+SELECT * FROM perl_record() AS (f1 integer, f2 text, f3 text, f4 testnestperl);
+
+
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return undef;
+$$ LANGUAGE plperl;
+
+SELECT perl_record_set();
+SELECT * FROM perl_record_set();
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ undef,
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+
+SELECT perl_record_set();
+SELECT * FROM perl_record_set();
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+
+CREATE OR REPLACE FUNCTION perl_record_set() RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+
+SELECT perl_record_set();
+SELECT * FROM perl_record_set();
+SELECT * FROM perl_record_set() AS (f1 integer, f2 text, f3 text);
+
+CREATE OR REPLACE FUNCTION
+perl_out_params(f1 out integer, f2 out text, f3 out text) AS $$
+ return {f2 => 'hello', f1 => 1, f3 => 'world'};
+$$ LANGUAGE plperl;
+
+SELECT perl_out_params();
+SELECT * FROM perl_out_params();
+SELECT (perl_out_params()).f2;
+
+CREATE OR REPLACE FUNCTION
+perl_out_params_set(out f1 integer, out f2 text, out f3 text)
+RETURNS SETOF record AS $$
+ return [
+ { f1 => 1, f2 => 'Hello', f3 => 'World' },
+ { f1 => 2, f2 => 'Hello', f3 => 'PostgreSQL' },
+ { f1 => 3, f2 => 'Hello', f3 => 'PL/Perl' }
+ ];
+$$ LANGUAGE plperl;
+
+SELECT perl_out_params_set();
+SELECT * FROM perl_out_params_set();
+SELECT (perl_out_params_set()).f3;
+
+--
+-- Check behavior with erroneous return values
+--
+
+CREATE TYPE footype AS (x INTEGER, y INTEGER);
+
+CREATE OR REPLACE FUNCTION foo_good() RETURNS SETOF footype AS $$
+return [
+ {x => 1, y => 2},
+ {x => 3, y => 4}
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_good();
+
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+ return {y => 3, z => 4};
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_bad();
+
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+return 42;
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_bad();
+
+CREATE OR REPLACE FUNCTION foo_bad() RETURNS footype AS $$
+return [
+ [1, 2],
+ [3, 4]
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_bad();
+
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+ return 42;
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_set_bad();
+
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+ return {y => 3, z => 4};
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_set_bad();
+
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+return [
+ [1, 2],
+ [3, 4]
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_set_bad();
+
+CREATE OR REPLACE FUNCTION foo_set_bad() RETURNS SETOF footype AS $$
+return [
+ {y => 3, z => 4}
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_set_bad();
+
+CREATE DOMAIN orderedfootype AS footype CHECK ((VALUE).x <= (VALUE).y);
+
+CREATE OR REPLACE FUNCTION foo_ordered() RETURNS orderedfootype AS $$
+ return {x => 3, y => 4};
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_ordered();
+
+CREATE OR REPLACE FUNCTION foo_ordered() RETURNS orderedfootype AS $$
+ return {x => 5, y => 4};
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_ordered(); -- fail
+
+CREATE OR REPLACE FUNCTION foo_ordered_set() RETURNS SETOF orderedfootype AS $$
+return [
+ {x => 3, y => 4},
+ {x => 4, y => 7}
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_ordered_set();
+
+CREATE OR REPLACE FUNCTION foo_ordered_set() RETURNS SETOF orderedfootype AS $$
+return [
+ {x => 3, y => 4},
+ {x => 9, y => 7}
+];
+$$ LANGUAGE plperl;
+
+SELECT * FROM foo_ordered_set(); -- fail
+
+--
+-- Check passing a tuple argument
+--
+
+CREATE OR REPLACE FUNCTION perl_get_field(footype, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+
+SELECT perl_get_field((11,12), 'x');
+SELECT perl_get_field((11,12), 'y');
+SELECT perl_get_field((11,12), 'z');
+
+CREATE OR REPLACE FUNCTION perl_get_cfield(orderedfootype, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+
+SELECT perl_get_cfield((11,12), 'x');
+SELECT perl_get_cfield((11,12), 'y');
+SELECT perl_get_cfield((12,11), 'x'); -- fail
+
+CREATE OR REPLACE FUNCTION perl_get_rfield(record, text) RETURNS integer AS $$
+ return $_[0]->{$_[1]};
+$$ LANGUAGE plperl;
+
+SELECT perl_get_rfield((11,12), 'f1');
+SELECT perl_get_rfield((11,12)::footype, 'y');
+SELECT perl_get_rfield((11,12)::orderedfootype, 'x');
+SELECT perl_get_rfield((12,11)::orderedfootype, 'x'); -- fail
+
+--
+-- Test return_next
+--
+
+CREATE OR REPLACE FUNCTION perl_srf_rn() RETURNS SETOF RECORD AS $$
+my $i = 0;
+for ("World", "PostgreSQL", "PL/Perl") {
+ return_next({f1=>++$i, f2=>'Hello', f3=>$_});
+}
+return;
+$$ language plperl;
+SELECT * from perl_srf_rn() AS (f1 INTEGER, f2 TEXT, f3 TEXT);
+
+--
+-- Test spi_query/spi_fetchrow
+--
+
+CREATE OR REPLACE FUNCTION perl_spi_func() RETURNS SETOF INTEGER AS $$
+my $x = spi_query("select 1 as a union select 2 as a");
+while (defined (my $y = spi_fetchrow($x))) {
+ return_next($y->{a});
+}
+return;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_func();
+
+--
+-- Test spi_fetchrow abort
+--
+CREATE OR REPLACE FUNCTION perl_spi_func2() RETURNS INTEGER AS $$
+my $x = spi_query("select 1 as a union select 2 as a");
+spi_cursor_close( $x);
+return 0;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_func2();
+
+
+---
+--- Test recursion via SPI
+---
+
+
+CREATE OR REPLACE FUNCTION recurse(i int) RETURNS SETOF TEXT LANGUAGE plperl
+AS $$
+
+ my $i = shift;
+ foreach my $x (1..$i)
+ {
+ return_next "hello $x";
+ }
+ if ($i > 2)
+ {
+ my $z = $i-1;
+ my $cursor = spi_query("select * from recurse($z)");
+ while (defined(my $row = spi_fetchrow($cursor)))
+ {
+ return_next "recurse $i: $row->{recurse}";
+ }
+ }
+ return undef;
+
+$$;
+
+SELECT * FROM recurse(2);
+SELECT * FROM recurse(3);
+
+
+---
+--- Test array return
+---
+CREATE OR REPLACE FUNCTION array_of_text() RETURNS TEXT[][]
+LANGUAGE plperl as $$
+ return [['a"b',undef,'c,d'],['e\\f',undef,'g']];
+$$;
+
+SELECT array_of_text();
+
+--
+-- Test spi_prepare/spi_exec_prepared/spi_freeplan
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared(INTEGER) RETURNS INTEGER AS $$
+ my $x = spi_prepare('select $1 AS a', 'INTEGER');
+ my $q = spi_exec_prepared( $x, $_[0] + 1);
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared(42);
+
+--
+-- Test spi_prepare/spi_query_prepared/spi_freeplan
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_set(INTEGER, INTEGER) RETURNS SETOF INTEGER AS $$
+ my $x = spi_prepare('SELECT $1 AS a union select $2 as a', 'INT4', 'INT4');
+ my $q = spi_query_prepared( $x, 1+$_[0], 2+$_[1]);
+ while (defined (my $y = spi_fetchrow($q))) {
+ return_next $y->{a};
+ }
+ spi_freeplan($x);
+ return;
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared_set(1,2);
+
+--
+-- Test prepare with a type with spaces
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_double(double precision) RETURNS double precision AS $$
+ my $x = spi_prepare('SELECT 10.0 * $1 AS a', 'DOUBLE PRECISION');
+ my $q = spi_query_prepared($x,$_[0]);
+ my $result;
+ while (defined (my $y = spi_fetchrow($q))) {
+ $result = $y->{a};
+ }
+ spi_freeplan($x);
+ return $result;
+$$ LANGUAGE plperl;
+SELECT perl_spi_prepared_double(4.35) as "double precision";
+
+--
+-- Test with a bad type
+--
+CREATE OR REPLACE FUNCTION perl_spi_prepared_bad(double precision) RETURNS double precision AS $$
+ my $x = spi_prepare('SELECT 10.0 * $1 AS a', 'does_not_exist');
+ my $q = spi_query_prepared($x,$_[0]);
+ my $result;
+ while (defined (my $y = spi_fetchrow($q))) {
+ $result = $y->{a};
+ }
+ spi_freeplan($x);
+ return $result;
+$$ LANGUAGE plperl;
+SELECT perl_spi_prepared_bad(4.35) as "double precision";
+
+-- Test with a row type
+CREATE OR REPLACE FUNCTION perl_spi_prepared() RETURNS INTEGER AS $$
+ my $x = spi_prepare('select $1::footype AS a', 'footype');
+ my $q = spi_exec_prepared( $x, '(1, 2)');
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a}->{x};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared();
+
+CREATE OR REPLACE FUNCTION perl_spi_prepared_row(footype) RETURNS footype AS $$
+ my $footype = shift;
+ my $x = spi_prepare('select $1 AS a', 'footype');
+ my $q = spi_exec_prepared( $x, {}, $footype );
+ spi_freeplan($x);
+return $q->{rows}->[0]->{a};
+$$ LANGUAGE plperl;
+SELECT * from perl_spi_prepared_row('(1, 2)');
+
+-- simple test of a DO block
+DO $$
+ $a = 'This is a test';
+ elog(NOTICE, $a);
+$$ LANGUAGE plperl;
+
+-- check that restricted operations are rejected in a plperl DO block
+DO $$ system("/nonesuch"); $$ LANGUAGE plperl;
+DO $$ qx("/nonesuch"); $$ LANGUAGE plperl;
+DO $$ open my $fh, "</nonesuch"; $$ LANGUAGE plperl;
+
+-- check that eval is allowed and eval'd restricted ops are caught
+DO $$ eval q{chdir '.';}; warn "Caught: $@"; $$ LANGUAGE plperl;
+
+-- check that compiling do (dofile opcode) is allowed
+-- but that executing it for a file not already loaded (via require) dies
+DO $$ warn do "/dev/null"; $$ LANGUAGE plperl;
+
+-- check that we can't "use" a module that's not been loaded already
+-- compile-time error: "Unable to load blib.pm into plperl"
+DO $$ use blib; $$ LANGUAGE plperl;
+
+-- check that we can "use" a module that has already been loaded
+-- runtime error: "Can't use string ("foo") as a SCALAR ref while "strict refs" in use
+DO $do$ use strict; my $name = "foo"; my $ref = $$name; $do$ LANGUAGE plperl;
+
+-- check that we can "use warnings" (in this case to turn a warn into an error)
+-- yields "ERROR: Useless use of sort in void context."
+DO $do$ use warnings FATAL => qw(void) ; my @y; sort @y; 1; $do$ LANGUAGE plperl;
+
+-- make sure functions marked as VOID without an explicit return work
+CREATE OR REPLACE FUNCTION myfuncs() RETURNS void AS $$
+ $_SHARED{myquote} = sub {
+ my $arg = shift;
+ $arg =~ s/(['\\])/\\$1/g;
+ return "'$arg'";
+ };
+$$ LANGUAGE plperl;
+
+SELECT myfuncs();
+
+-- make sure we can't return an array as a scalar
+CREATE OR REPLACE FUNCTION text_arrayref() RETURNS text AS $$
+ return ['array'];
+$$ LANGUAGE plperl;
+
+SELECT text_arrayref();
+
+--- make sure we can't return a hash as a scalar
+CREATE OR REPLACE FUNCTION text_hashref() RETURNS text AS $$
+ return {'hash'=>1};
+$$ LANGUAGE plperl;
+
+SELECT text_hashref();
+
+---- make sure we can't return a blessed object as a scalar
+CREATE OR REPLACE FUNCTION text_obj() RETURNS text AS $$
+ return bless({}, 'Fake::Object');
+$$ LANGUAGE plperl;
+
+SELECT text_obj();
+
+-- test looking through a scalar ref
+CREATE OR REPLACE FUNCTION text_scalarref() RETURNS text AS $$
+ my $str = 'str';
+ return \$str;
+$$ LANGUAGE plperl;
+
+SELECT text_scalarref();
+
+-- check safe behavior when a function body is replaced during execution
+CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS $$
+ spi_exec_query('CREATE OR REPLACE FUNCTION self_modify(INTEGER) RETURNS INTEGER AS \'return $_[0] * 3;\' LANGUAGE plperl;');
+ spi_exec_query('select self_modify(42) AS a');
+ return $_[0] * 2;
+$$ LANGUAGE plperl;
+
+SELECT self_modify(42);
+SELECT self_modify(42);
diff --git a/src/pl/plperl/sql/plperl_array.sql b/src/pl/plperl/sql/plperl_array.sql
new file mode 100644
index 0000000..ca63b5d
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_array.sql
@@ -0,0 +1,208 @@
+CREATE OR REPLACE FUNCTION plperl_sum_array(INTEGER[]) RETURNS text AS $$
+ my $array_arg = shift;
+ my $result = 0;
+ my @arrays;
+
+ push @arrays, @$array_arg;
+
+ while (@arrays > 0) {
+ my $el = shift @arrays;
+ if (is_array_ref($el)) {
+ push @arrays, @$el;
+ } else {
+ $result += $el;
+ }
+ }
+ return $result.' '.$array_arg;
+$$ LANGUAGE plperl;
+
+select plperl_sum_array('{1,2,NULL}');
+select plperl_sum_array('{}');
+select plperl_sum_array('{{1,2,3}, {4,5,6}}');
+select plperl_sum_array('{{{1,2,3}, {4,5,6}}, {{7,8,9}, {10,11,12}}}');
+
+-- check whether we can handle arrays of maximum dimension (6)
+select plperl_sum_array(ARRAY[[[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],
+[[13,14],[15,16]]]],
+[[[[17,18],[19,20]],[[21,22],[23,24]]],[[[25,26],[27,28]],[[29,30],[31,32]]]]],
+[[[[[1,2],[3,4]],[[5,6],[7,8]]],[[[9,10],[11,12]],[[13,14],[15,16]]]],
+[[[[17,18],[19,20]],[[21,22],[23,24]]],[[[25,26],[27,28]],[[29,30],[31,32]]]]]]);
+
+-- what would we do with the arrays exceeding maximum dimension (7)
+select plperl_sum_array('{{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},
+{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}},
+{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}}},
+{{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}},
+{{{{{1,2},{3,4}},{{5,6},{7,8}}},{{{9,10},{11,12}},{{13,14},{15,16}}}},
+{{{{17,18},{19,20}},{{21,22},{23,24}}},{{{25,26},{27,28}},{{29,30},{31,32}}}}}}}'
+);
+
+select plperl_sum_array('{{{1,2,3}, {4,5,6,7}}, {{7,8,9}, {10, 11, 12}}}');
+
+CREATE OR REPLACE FUNCTION plperl_concat(TEXT[]) RETURNS TEXT AS $$
+ my $array_arg = shift;
+ my $result = "";
+ my @arrays;
+
+ push @arrays, @$array_arg;
+ while (@arrays > 0) {
+ my $el = shift @arrays;
+ if (is_array_ref($el)) {
+ push @arrays, @$el;
+ } else {
+ $result .= $el;
+ }
+ }
+ return $result.' '.$array_arg;
+$$ LANGUAGE plperl;
+
+select plperl_concat('{"NULL","NULL","NULL''"}');
+select plperl_concat('{{NULL,NULL,NULL}}');
+select plperl_concat('{"hello"," ","world!"}');
+
+-- array of rows --
+CREATE TYPE foo AS (bar INTEGER, baz TEXT);
+CREATE OR REPLACE FUNCTION plperl_array_of_rows(foo[]) RETURNS TEXT AS $$
+ my $array_arg = shift;
+ my $result = "";
+
+ for my $row_ref (@$array_arg) {
+ die "not a hash reference" unless (ref $row_ref eq "HASH");
+ $result .= $row_ref->{bar}." items of ".$row_ref->{baz}.";";
+ }
+ return $result .' '. $array_arg;
+$$ LANGUAGE plperl;
+
+select plperl_array_of_rows(ARRAY[ ROW(2, 'coffee'), ROW(0, 'sugar')]::foo[]);
+
+-- composite type containing arrays
+CREATE TYPE rowfoo AS (bar INTEGER, baz INTEGER[]);
+
+CREATE OR REPLACE FUNCTION plperl_sum_row_elements(rowfoo) RETURNS TEXT AS $$
+ my $row_ref = shift;
+ my $result;
+
+ if (ref $row_ref ne 'HASH') {
+ $result = 0;
+ }
+ else {
+ $result = $row_ref->{bar};
+ die "not an array reference".ref ($row_ref->{baz})
+ unless (is_array_ref($row_ref->{baz}));
+ # process a single-dimensional array
+ foreach my $elem (@{$row_ref->{baz}}) {
+ $result += $elem unless ref $elem;
+ }
+ }
+ return $result;
+$$ LANGUAGE plperl;
+
+select plperl_sum_row_elements(ROW(1, ARRAY[2,3,4,5,6,7,8,9,10])::rowfoo);
+
+-- composite type containing array of another composite type, which, in order,
+-- contains an array of integers.
+CREATE TYPE rowbar AS (foo rowfoo[]);
+
+CREATE OR REPLACE FUNCTION plperl_sum_array_of_rows(rowbar) RETURNS TEXT AS $$
+ my $rowfoo_ref = shift;
+ my $result = 0;
+
+ if (ref $rowfoo_ref eq 'HASH') {
+ my $row_array_ref = $rowfoo_ref->{foo};
+ if (is_array_ref($row_array_ref)) {
+ foreach my $row_ref (@{$row_array_ref}) {
+ if (ref $row_ref eq 'HASH') {
+ $result += $row_ref->{bar};
+ die "not an array reference".ref ($row_ref->{baz})
+ unless (is_array_ref($row_ref->{baz}));
+ foreach my $elem (@{$row_ref->{baz}}) {
+ $result += $elem unless ref $elem;
+ }
+ }
+ else {
+ die "element baz is not a reference to a rowfoo";
+ }
+ }
+ } else {
+ die "not a reference to an array of rowfoo elements"
+ }
+ } else {
+ die "not a reference to type rowbar";
+ }
+ return $result;
+$$ LANGUAGE plperl;
+
+select plperl_sum_array_of_rows(ROW(ARRAY[ROW(1, ARRAY[2,3,4,5,6,7,8,9,10])::rowfoo,
+ROW(11, ARRAY[12,13,14,15,16,17,18,19,20])::rowfoo])::rowbar);
+
+-- check arrays as out parameters
+CREATE OR REPLACE FUNCTION plperl_arrays_out(OUT INTEGER[]) AS $$
+ return [[1,2,3],[4,5,6]];
+$$ LANGUAGE plperl;
+
+select plperl_arrays_out();
+
+-- check that we can return the array we passed in
+CREATE OR REPLACE FUNCTION plperl_arrays_inout(INTEGER[]) returns INTEGER[] AS $$
+ return shift;
+$$ LANGUAGE plperl;
+
+select plperl_arrays_inout('{{1}, {2}, {3}}');
+
+-- check that we can return an array literal
+CREATE OR REPLACE FUNCTION plperl_arrays_inout_l(INTEGER[]) returns INTEGER[] AS $$
+ return shift.''; # stringify it
+$$ LANGUAGE plperl;
+
+select plperl_arrays_inout_l('{{1}, {2}, {3}}');
+
+-- check output of multi-dimensional arrays
+CREATE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [['a'], ['b'], ['c']];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out();
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], []];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out();
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], [1]];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[], 1];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [1, []];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION plperl_md_array_out() RETURNS text[] AS $$
+ return [[1], [[]]];
+$$ LANGUAGE plperl;
+
+select plperl_md_array_out(); -- fail
+
+-- make sure setof works
+create or replace function perl_setof_array(integer[]) returns setof integer[] language plperl as $$
+ my $arr = shift;
+ for my $r (@$arr) {
+ return_next $r;
+ }
+ return undef;
+$$;
+
+select perl_setof_array('{{1}, {2}, {3}}');
diff --git a/src/pl/plperl/sql/plperl_call.sql b/src/pl/plperl/sql/plperl_call.sql
new file mode 100644
index 0000000..bbea85f
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_call.sql
@@ -0,0 +1,78 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE plperl
+AS $$
+undef;
+$$;
+
+CALL test_proc1();
+
+
+CREATE PROCEDURE test_proc2()
+LANGUAGE plperl
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plperl
+AS $$
+spi_exec_query("INSERT INTO test1 VALUES ($_[0])");
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+-- output arguments
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plperl
+AS $$
+my ($a) = @_;
+return { a => "$a+$a" };
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plperl
+AS $$
+my ($a, $b, $c) = @_;
+return { b => $b * $a, c => $c * $a };
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plperl
+AS $$
+my ($a, $b) = @_;
+elog(NOTICE, "a: $a, b: $b");
+return { b => $a * 2 };
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/pl/plperl/sql/plperl_elog.sql b/src/pl/plperl/sql/plperl_elog.sql
new file mode 100644
index 0000000..9ea1350
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_elog.sql
@@ -0,0 +1,93 @@
+-- test warnings and errors from plperl
+
+create or replace function perl_elog(text) returns void language plperl as $$
+
+ my $msg = shift;
+ elog(NOTICE,$msg);
+
+$$;
+
+select perl_elog('explicit elog');
+
+create or replace function perl_warn(text) returns void language plperl as $$
+
+ my $msg = shift;
+ warn($msg);
+
+$$;
+
+select perl_warn('implicit elog via warn');
+
+-- test strict mode on/off
+
+SET plperl.use_strict = true;
+
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global = 2;
+ return 'uses_global worked';
+
+$$;
+
+select uses_global();
+
+SET plperl.use_strict = false;
+
+create or replace function uses_global() returns text language plperl as $$
+
+ $global = 1;
+ $other_global=2;
+ return 'uses_global worked';
+
+$$;
+
+select uses_global();
+
+-- make sure we don't choke on readonly values
+do language plperl $$ elog(NOTICE, ${^TAINT}); $$;
+
+-- test recovery after "die"
+
+create or replace function just_die() returns void language plperl AS $$
+die "just die";
+$$;
+
+select just_die();
+
+create or replace function die_caller() returns int language plpgsql as $$
+BEGIN
+ BEGIN
+ PERFORM just_die();
+ EXCEPTION WHEN OTHERS THEN
+ RAISE NOTICE 'caught die';
+ END;
+ RETURN 1;
+END;
+$$;
+
+select die_caller();
+
+create or replace function indirect_die_caller() returns int language plperl as $$
+my $prepared = spi_prepare('SELECT die_caller() AS fx');
+my $a = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+my $b = spi_exec_prepared($prepared)->{rows}->[0]->{fx};
+return $a + $b;
+$$;
+
+select indirect_die_caller();
+
+-- Test non-ASCII error messages
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+
+SET client_encoding TO UTF8;
+
+create or replace function error_with_nbsp() returns void language plperl as $$
+ elog(ERROR, "this message contains a no-break space");
+$$;
+
+select error_with_nbsp();
diff --git a/src/pl/plperl/sql/plperl_end.sql b/src/pl/plperl/sql/plperl_end.sql
new file mode 100644
index 0000000..90f49dc
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_end.sql
@@ -0,0 +1,29 @@
+-- test END block handling
+
+-- Not included in the normal testing
+-- because it's beyond the scope of the test harness.
+-- Available here for manual developer testing.
+
+DO $do$
+ my $testlog = "/tmp/pgplperl_test.log";
+
+ warn "Run test, then examine contents of $testlog (which must already exist)\n";
+ return unless -f $testlog;
+
+ use IO::Handle; # for autoflush
+ open my $fh, '>', $testlog
+ or die "Can't write to $testlog: $!";
+ $fh->autoflush(1);
+
+ print $fh "# you should see just 3 'Warn: ...' lines: PRE, END and SPI ...\n";
+ $SIG{__WARN__} = sub { print $fh "Warn: @_" };
+ $SIG{__DIE__} = sub { print $fh "Die: @_" unless $^S; die @_ };
+
+ END {
+ warn "END\n";
+ eval { spi_exec_query("select 1") };
+ warn $@;
+ }
+ warn "PRE\n";
+
+$do$ language plperlu;
diff --git a/src/pl/plperl/sql/plperl_init.sql b/src/pl/plperl/sql/plperl_init.sql
new file mode 100644
index 0000000..2aa3811
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_init.sql
@@ -0,0 +1,41 @@
+-- test plperl.on_plperl_init
+
+-- This test tests setting on_plperl_init after loading plperl
+LOAD 'plperl';
+
+SET SESSION plperl.on_plperl_init = ' system("/nonesuch"); ';
+
+SHOW plperl.on_plperl_init;
+
+DO $$ warn 42 $$ language plperl;
+
+--
+-- Reconnect (to unload plperl), then test setting on_plperl_init
+-- as an unprivileged user
+--
+
+\c -
+
+CREATE ROLE regress_plperl_user;
+
+SET ROLE regress_plperl_user;
+
+-- this succeeds, since the GUC isn't known yet
+SET SESSION plperl.on_plperl_init = 'test';
+
+RESET ROLE;
+
+LOAD 'plperl';
+
+SHOW plperl.on_plperl_init;
+
+DO $$ warn 42 $$ language plperl;
+
+-- now we won't be allowed to set it in the first place
+SET ROLE regress_plperl_user;
+
+SET SESSION plperl.on_plperl_init = 'test';
+
+RESET ROLE;
+
+DROP ROLE regress_plperl_user;
diff --git a/src/pl/plperl/sql/plperl_lc.sql b/src/pl/plperl/sql/plperl_lc.sql
new file mode 100644
index 0000000..a4a06e7
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_lc.sql
@@ -0,0 +1,8 @@
+--
+-- Make sure strings are validated
+-- Should fail for all encodings, as nul bytes are never permitted.
+--
+CREATE OR REPLACE FUNCTION perl_zerob() RETURNS TEXT AS $$
+ return "abcd\0efg";
+$$ LANGUAGE plperl;
+SELECT perl_zerob();
diff --git a/src/pl/plperl/sql/plperl_plperlu.sql b/src/pl/plperl/sql/plperl_plperlu.sql
new file mode 100644
index 0000000..bbd79b6
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_plperlu.sql
@@ -0,0 +1,58 @@
+-- test plperl/plperlu interaction
+
+-- the language and call ordering of this test sequence is useful
+
+CREATE OR REPLACE FUNCTION bar() RETURNS integer AS $$
+ #die 'BANG!'; # causes server process to exit(2)
+ # alternative - causes server process to exit(255)
+ spi_exec_query("invalid sql statement");
+$$ language plperl; -- compile plperl code
+
+CREATE OR REPLACE FUNCTION foo() RETURNS integer AS $$
+ spi_exec_query("SELECT * FROM bar()");
+ return 1;
+$$ LANGUAGE plperlu; -- compile plperlu code
+
+SELECT * FROM bar(); -- throws exception normally (running plperl)
+SELECT * FROM foo(); -- used to cause backend crash (after switching to plperlu)
+
+-- test redefinition of specific SP switching languages
+-- http://archives.postgresql.org/pgsql-bugs/2010-01/msg00116.php
+
+-- plperl first
+create or replace function foo(text) returns text language plperl as 'shift';
+select foo('hey');
+create or replace function foo(text) returns text language plperlu as 'shift';
+select foo('hey');
+create or replace function foo(text) returns text language plperl as 'shift';
+select foo('hey');
+
+-- plperlu first
+create or replace function bar(text) returns text language plperlu as 'shift';
+select bar('hey');
+create or replace function bar(text) returns text language plperl as 'shift';
+select bar('hey');
+create or replace function bar(text) returns text language plperlu as 'shift';
+select bar('hey');
+
+--
+-- Make sure we can't use/require things in plperl
+--
+
+CREATE OR REPLACE FUNCTION use_plperlu() RETURNS void LANGUAGE plperlu
+AS $$
+use Errno;
+$$;
+
+CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
+AS $$
+use Errno;
+$$;
+
+-- make sure our overloaded require op gets restored/set correctly
+select use_plperlu();
+
+CREATE OR REPLACE FUNCTION use_plperl() RETURNS void LANGUAGE plperl
+AS $$
+use Errno;
+$$;
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..a89cf56
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,73 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl; -- fail
+CREATE EXTENSION plperlu; -- fail
+
+RESET ROLE;
+
+DO $$
+begin
+ execute format('grant create on database %I to regress_user1',
+ current_database());
+end;
+$$;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu; -- fail
+CREATE SCHEMA plperl_setup_scratch;
+SET search_path = plperl_setup_scratch;
+GRANT ALL ON SCHEMA plperl_setup_scratch TO regress_user2;
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Must reconnect to avoid failure with non-MULTIPLICITY Perl interpreters
+\c -
+SET search_path = plperl_setup_scratch;
+
+SET ROLE regress_user1;
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;'; -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP OWNED BY regress_user1;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/sql/plperl_shared.sql b/src/pl/plperl/sql/plperl_shared.sql
new file mode 100644
index 0000000..b60e114
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_shared.sql
@@ -0,0 +1,41 @@
+-- test plperl.on_plperl_init via the shared hash
+-- (must be done before plperl is first used)
+
+-- This test tests setting on_plperl_init before loading plperl
+
+-- testing on_plperl_init gets run, and that it can alter %_SHARED
+SET plperl.on_plperl_init = '$_SHARED{on_init} = 42';
+
+-- test the shared hash
+
+create function setme(key text, val text) returns void language plperl as $$
+
+ my $key = shift;
+ my $val = shift;
+ $_SHARED{$key}= $val;
+
+$$;
+
+create function getme(key text) returns text language plperl as $$
+
+ my $key = shift;
+ return $_SHARED{$key};
+
+$$;
+
+select setme('ourkey','ourval');
+
+select getme('ourkey');
+
+select getme('on_init');
+
+-- verify that we can use $_SHARED in strict mode
+create or replace function perl_shared() returns int as $$
+use strict;
+my $val = $_SHARED{'stuff'};
+$_SHARED{'stuff'} = '1';
+return $val;
+$$ language plperl;
+
+select perl_shared();
+select perl_shared();
diff --git a/src/pl/plperl/sql/plperl_transaction.sql b/src/pl/plperl/sql/plperl_transaction.sql
new file mode 100644
index 0000000..d10c8be
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_transaction.sql
@@ -0,0 +1,195 @@
+CREATE TABLE test1 (a int, b text);
+
+
+CREATE PROCEDURE transaction_test1()
+LANGUAGE plperl
+AS $$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+
+CALL transaction_test1();
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+DO
+LANGUAGE plperl
+$$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plperl
+AS $$
+foreach my $i (0..9) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES ($i)");
+ if ($i % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+return 1;
+$$;
+
+SELECT transaction_test2();
+
+SELECT * FROM test1;
+
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plperl
+AS $$
+spi_exec_query("CALL transaction_test1()");
+return 1;
+$$;
+
+SELECT transaction_test3();
+
+SELECT * FROM test1;
+
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plperl
+AS $$
+spi_exec_query('DO LANGUAGE plperl $x$ spi_commit(); $x$');
+return 1;
+$$;
+
+SELECT transaction_test4();
+
+
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+
+TRUNCATE test1;
+
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ spi_commit();
+}
+$$;
+
+SELECT * FROM test1;
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (12/(" . $row->{x} . "-2))");
+ spi_commit();
+}
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ spi_rollback();
+}
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plperl $$
+my $sth = spi_query("SELECT * FROM test2 ORDER BY x");
+my $row;
+while (defined($row = spi_fetchrow($sth))) {
+ spi_exec_query("INSERT INTO test1 (a) VALUES (" . $row->{x} . ")");
+ if ($row->{x} % 2 == 0) {
+ spi_commit();
+ } else {
+ spi_rollback();
+ }
+}
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+
+DO LANGUAGE plperl $$
+# this insert will fail during commit:
+spi_exec_query("INSERT INTO testfk VALUES (0)");
+spi_commit();
+elog(WARNING, 'should not get here');
+$$;
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+DO LANGUAGE plperl $$
+# this insert will fail during commit:
+spi_exec_query("INSERT INTO testfk VALUES (0)");
+eval {
+ spi_commit();
+};
+if ($@) {
+ elog(INFO, $@);
+}
+# these inserts should work:
+spi_exec_query("INSERT INTO testpk VALUES (1)");
+spi_exec_query("INSERT INTO testfk VALUES (1)");
+$$;
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/plperl/sql/plperl_trigger.sql b/src/pl/plperl/sql/plperl_trigger.sql
new file mode 100644
index 0000000..4adddeb
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_trigger.sql
@@ -0,0 +1,259 @@
+-- test plperl triggers
+
+CREATE TYPE rowcomp as (i int);
+CREATE TYPE rowcompnest as (rfoo rowcomp);
+CREATE TABLE trigger_test (
+ i int,
+ v varchar,
+ foo rowcompnest
+);
+
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+
+CREATE OR REPLACE FUNCTION trigger_data() RETURNS trigger LANGUAGE plperl AS $$
+
+ # make sure keys are sorted for consistent results - perl no longer
+ # hashes in repeatable fashion across runs
+
+ sub str {
+ my $val = shift;
+
+ if (!defined $val)
+ {
+ return 'NULL';
+ }
+ elsif (ref $val eq 'HASH')
+ {
+ my $str = '';
+ foreach my $rowkey (sort keys %$val)
+ {
+ $str .= ", " if $str;
+ my $rowval = str($val->{$rowkey});
+ $str .= "'$rowkey' => $rowval";
+ }
+ return '{'. $str .'}';
+ }
+ elsif (ref $val eq 'ARRAY')
+ {
+ my $str = '';
+ for my $argval (@$val)
+ {
+ $str .= ", " if $str;
+ $str .= str($argval);
+ }
+ return '['. $str .']';
+ }
+ else
+ {
+ return "'$val'";
+ }
+ }
+
+ foreach my $key (sort keys %$_TD)
+ {
+
+ my $val = $_TD->{$key};
+
+ # relid is variable, so we can not use it repeatably
+ $val = "bogus:12345" if $key eq 'relid';
+
+ elog(NOTICE, "\$_TD->\{$key\} = ". str($val));
+ }
+ return undef; # allow statement to proceed;
+$$;
+
+CREATE TRIGGER show_trigger_data_trig
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+
+insert into trigger_test values(1,'insert', '("(1)")');
+update trigger_test set v = 'update' where i = 1;
+delete from trigger_test;
+
+DROP TRIGGER show_trigger_data_trig on trigger_test;
+
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+
+insert into trigger_test_generated (i) values (1);
+update trigger_test_generated set i = 11 where i = 1;
+delete from trigger_test_generated;
+
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+
+insert into trigger_test values(1,'insert', '("(1)")');
+CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+
+CREATE TRIGGER show_trigger_data_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+
+insert into trigger_test_view values(2,'insert', '("(2)")');
+update trigger_test_view set v = 'update', foo = '("(3)")' where i = 1;
+delete from trigger_test_view;
+
+DROP VIEW trigger_test_view;
+delete from trigger_test;
+
+DROP FUNCTION trigger_data();
+
+CREATE OR REPLACE FUNCTION valid_id() RETURNS trigger AS $$
+
+ if (($_TD->{new}{i}>=100) || ($_TD->{new}{i}<=0))
+ {
+ return "SKIP"; # Skip INSERT/UPDATE command
+ }
+ elsif ($_TD->{new}{v} ne "immortal")
+ {
+ $_TD->{new}{v} .= "(modified by trigger)";
+ $_TD->{new}{foo}{rfoo}{i}++;
+ return "MODIFY"; # Modify tuple and proceed INSERT/UPDATE command
+ }
+ else
+ {
+ return; # Proceed INSERT/UPDATE command
+ }
+$$ LANGUAGE plperl;
+
+CREATE TRIGGER "test_valid_id_trig" BEFORE INSERT OR UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE "valid_id"();
+
+INSERT INTO trigger_test (i, v, foo) VALUES (1,'first line', '("(1)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (2,'second line', '("(2)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (3,'third line', '("(3)")');
+INSERT INTO trigger_test (i, v, foo) VALUES (4,'immortal', '("(4)")');
+
+INSERT INTO trigger_test (i, v) VALUES (101,'bad id');
+
+SELECT * FROM trigger_test;
+
+UPDATE trigger_test SET i = 5 where i=3;
+
+UPDATE trigger_test SET i = 100 where i=1;
+
+SELECT * FROM trigger_test;
+
+DROP TRIGGER "test_valid_id_trig" ON trigger_test;
+
+CREATE OR REPLACE FUNCTION trigger_recurse() RETURNS trigger AS $$
+ use strict;
+
+ if ($_TD->{new}{i} == 10000)
+ {
+ spi_exec_query("insert into trigger_test (i, v) values (20000, 'child');");
+
+ if ($_TD->{new}{i} != 10000)
+ {
+ die "recursive trigger modified: ". $_TD->{new}{i};
+ }
+ }
+ return;
+$$ LANGUAGE plperl;
+
+CREATE TRIGGER "test_trigger_recurse" BEFORE INSERT ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE "trigger_recurse"();
+
+INSERT INTO trigger_test (i, v) values (10000, 'top');
+
+SELECT * FROM trigger_test;
+
+CREATE OR REPLACE FUNCTION immortal() RETURNS trigger AS $$
+ if ($_TD->{old}{v} eq $_TD->{args}[0])
+ {
+ return "SKIP"; # Skip DELETE command
+ }
+ else
+ {
+ return; # Proceed DELETE command
+ };
+$$ LANGUAGE plperl;
+
+CREATE TRIGGER "immortal_trig" BEFORE DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE immortal('immortal');
+
+DELETE FROM trigger_test;
+
+SELECT * FROM trigger_test;
+
+CREATE FUNCTION direct_trigger() RETURNS trigger AS $$
+ return;
+$$ LANGUAGE plperl;
+
+SELECT direct_trigger();
+
+-- check that SQL run in trigger code can see transition tables
+
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plperl AS
+$$
+ my $cursor = spi_query("SELECT * FROM old_table");
+ my $row = spi_fetchrow($cursor);
+ defined($row) || die "expected a row";
+ elog(INFO, "old: " . $row->{id} . " -> " . $row->{name});
+ my $row = spi_fetchrow($cursor);
+ !defined($row) || die "expected no more rows";
+
+ my $cursor = spi_query("SELECT * FROM new_table");
+ my $row = spi_fetchrow($cursor);
+ defined($row) || die "expected a row";
+ elog(INFO, "new: " . $row->{id} . " -> " . $row->{name});
+ my $row = spi_fetchrow($cursor);
+ !defined($row) || die "expected no more rows";
+
+ return undef;
+$$;
+
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
+
+-- test plperl command triggers
+create or replace function perlsnitch() returns event_trigger language plperl as $$
+ elog(NOTICE, "perlsnitch: " . $_TD->{event} . " " . $_TD->{tag} . " ");
+$$;
+
+create event trigger perl_a_snitch on ddl_command_start
+ execute procedure perlsnitch();
+create event trigger perl_b_snitch on ddl_command_end
+ execute procedure perlsnitch();
+
+create or replace function foobar() returns int language sql as $$select 1;$$;
+alter function foobar() cost 77;
+drop function foobar();
+
+create table foo();
+drop table foo;
+
+drop event trigger perl_a_snitch;
+drop event trigger perl_b_snitch;
+
+-- dealing with generated columns
+
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE plperl
+AS $$
+$_TD->{new}{j} = 5; # not allowed
+return 'MODIFY';
+$$;
+
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+SELECT * FROM trigger_test_generated;
diff --git a/src/pl/plperl/sql/plperl_util.sql b/src/pl/plperl/sql/plperl_util.sql
new file mode 100644
index 0000000..5b31605
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_util.sql
@@ -0,0 +1,121 @@
+-- test plperl utility functions (defined in Util.xs)
+
+-- test quote_literal
+
+create or replace function perl_quote_literal() returns setof text language plperl as $$
+ return_next "undef: ".quote_literal(undef);
+ return_next sprintf"$_: ".quote_literal($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+ return undef;
+$$;
+
+select perl_quote_literal();
+
+-- test quote_nullable
+
+create or replace function perl_quote_nullable() returns setof text language plperl as $$
+ return_next "undef: ".quote_nullable(undef);
+ return_next sprintf"$_: ".quote_nullable($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{};
+ return undef;
+$$;
+
+select perl_quote_nullable();
+
+-- test quote_ident
+
+create or replace function perl_quote_ident() returns setof text language plperl as $$
+ return_next "undef: ".quote_ident(undef); # generates undef warning if warnings enabled
+ return_next "$_: ".quote_ident($_)
+ for q{foo}, q{a'b}, q{a"b}, q{c''d}, q{e\f}, q{g.h}, q{};
+ return undef;
+$$;
+
+select perl_quote_ident();
+
+-- test decode_bytea
+
+create or replace function perl_decode_bytea() returns setof text language plperl as $$
+ return_next "undef: ".decode_bytea(undef); # generates undef warning if warnings enabled
+ return_next "$_: ".decode_bytea($_)
+ for q{foo}, q{a\047b}, q{};
+ return undef;
+$$;
+
+select perl_decode_bytea();
+
+-- test encode_bytea
+
+create or replace function perl_encode_bytea() returns setof text language plperl as $$
+ return_next encode_bytea(undef); # generates undef warning if warnings enabled
+ return_next encode_bytea($_)
+ for q{@}, qq{@\x01@}, qq{@\x00@}, q{};
+ return undef;
+$$;
+
+select perl_encode_bytea();
+
+-- test encode_array_literal
+
+create or replace function perl_encode_array_literal() returns setof text language plperl as $$
+ return_next encode_array_literal(undef);
+ return_next encode_array_literal(0);
+ return_next encode_array_literal(42);
+ return_next encode_array_literal($_)
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return_next encode_array_literal($_,'|')
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return undef;
+$$;
+
+select perl_encode_array_literal();
+
+-- test encode_array_constructor
+
+create or replace function perl_encode_array_constructor() returns setof text language plperl as $$
+ return_next encode_array_constructor(undef);
+ return_next encode_array_constructor(0);
+ return_next encode_array_constructor(42);
+ return_next encode_array_constructor($_)
+ for [], [0], [1..5], [[]], [[1,2,[3]],4];
+ return undef;
+$$;
+
+select perl_encode_array_constructor();
+
+-- test looks_like_number
+
+create or replace function perl_looks_like_number() returns setof text language plperl as $$
+ return_next "undef is undef" if not defined looks_like_number(undef);
+ return_next quote_nullable($_).": ". (looks_like_number($_) ? "number" : "not number")
+ for 'foo', 0, 1, 1.3, '+3.e-4',
+ '42 x', # trailing garbage
+ '99 ', # trailing space
+ ' 99', # leading space
+ ' ', # only space
+ ''; # empty string
+ return undef;
+$$;
+
+select perl_looks_like_number();
+
+-- test encode_typed_literal
+create type perl_foo as (a integer, b text[]);
+create type perl_bar as (c perl_foo[]);
+create domain perl_foo_pos as perl_foo check((value).a > 0);
+
+create or replace function perl_encode_typed_literal() returns setof text language plperl as $$
+ return_next encode_typed_literal(undef, 'text');
+ return_next encode_typed_literal([[1,2,3],[3,2,1],[1,3,2]], 'integer[]');
+ return_next encode_typed_literal({a => 1, b => ['PL','/','Perl']}, 'perl_foo');
+ return_next encode_typed_literal({c => [{a => 9, b => ['PostgreSQL']}, {b => ['Postgres'], a => 1}]}, 'perl_bar');
+ return_next encode_typed_literal({a => 1, b => ['PL','/','Perl']}, 'perl_foo_pos');
+$$;
+
+select perl_encode_typed_literal();
+
+create or replace function perl_encode_typed_literal() returns setof text language plperl as $$
+ return_next encode_typed_literal({a => 0, b => ['PL','/','Perl']}, 'perl_foo_pos');
+$$;
+
+select perl_encode_typed_literal(); -- fail
diff --git a/src/pl/plperl/sql/plperlu.sql b/src/pl/plperl/sql/plperlu.sql
new file mode 100644
index 0000000..be43df5
--- /dev/null
+++ b/src/pl/plperl/sql/plperlu.sql
@@ -0,0 +1,17 @@
+-- Use ONLY plperlu tests here. For plperl/plerlu combined tests
+-- see plperl_plperlu.sql
+
+-- This test tests setting on_plperlu_init after loading plperl
+LOAD 'plperl';
+
+-- Test plperl.on_plperlu_init gets run
+SET plperl.on_plperlu_init = '$_SHARED{init} = 42';
+DO $$ warn $_SHARED{init} $$ language plperlu;
+
+--
+-- Test compilation of unicode regex - regardless of locale.
+-- This code fails in plain plperl in a non-UTF8 database.
+--
+CREATE OR REPLACE FUNCTION perl_unicode_regex(text) RETURNS INTEGER AS $$
+ return ($_[0] =~ /\x{263A}|happy/i) ? 1 : 0; # unicode smiley
+$$ LANGUAGE plperlu;
diff --git a/src/pl/plperl/text2macro.pl b/src/pl/plperl/text2macro.pl
new file mode 100644
index 0000000..70b7c3f
--- /dev/null
+++ b/src/pl/plperl/text2macro.pl
@@ -0,0 +1,106 @@
+
+# Copyright (c) 2021-2022, PostgreSQL Global Development Group
+
+# src/pl/plperl/text2macro.pl
+
+=head1 NAME
+
+text2macro.pl - convert text files into C string-literal macro definitions
+
+=head1 SYNOPSIS
+
+ text2macro [options] file ... > output.h
+
+Options:
+
+ --prefix=S - add prefix S to the names of the macros
+ --name=S - use S as the macro name (assumes only one file)
+ --strip=S - don't include lines that match perl regex S
+
+=head1 DESCRIPTION
+
+Reads one or more text files and outputs a corresponding series of C
+pre-processor macro definitions. Each macro defines a string literal that
+contains the contents of the corresponding text file. The basename of the text
+file as capitalized and used as the name of the macro, along with an optional prefix.
+
+=cut
+
+use strict;
+use warnings;
+
+use Getopt::Long;
+
+GetOptions(
+ 'prefix=s' => \my $opt_prefix,
+ 'name=s' => \my $opt_name,
+ 'strip=s' => \my $opt_strip,
+ 'selftest!' => sub { exit selftest() },) or exit 1;
+
+die "No text files specified"
+ unless @ARGV;
+
+print qq{
+/*
+ * DO NOT EDIT - THIS FILE IS AUTOGENERATED - CHANGES WILL BE LOST
+ * Generated by src/pl/plperl/text2macro.pl
+ */
+};
+
+for my $src_file (@ARGV)
+{
+
+ (my $macro = $src_file) =~ s/ .*? (\w+) (?:\.\w+) $/$1/x;
+
+ open my $src_fh, '<', $src_file
+ or die "Can't open $src_file: $!";
+
+ printf qq{#define %s%s \\\n},
+ $opt_prefix || '',
+ ($opt_name) ? $opt_name : uc $macro;
+ while (<$src_fh>)
+ {
+ chomp;
+
+ next if $opt_strip and m/$opt_strip/o;
+
+ # escape the text to suite C string literal rules
+ s/\\/\\\\/g;
+ s/"/\\"/g;
+
+ printf qq{"%s\\n" \\\n}, $_;
+ }
+ print qq{""\n\n};
+}
+
+print "/* end */\n";
+
+exit 0;
+
+
+sub selftest
+{
+ my $tmp = "text2macro_tmp";
+ my $string = q{a '' '\\'' "" "\\"" "\\\\" "\\\\n" b};
+
+ open my $fh, '>', "$tmp.pl" or die;
+ print $fh $string;
+ close $fh;
+
+ system("perl $0 --name=X $tmp.pl > $tmp.c") == 0 or die;
+ open $fh, '>>', "$tmp.c";
+ print $fh "#include <stdio.h>\n";
+ print $fh "int main() { puts(X); return 0; }\n";
+ close $fh;
+ system("cat -n $tmp.c");
+
+ system("make $tmp") == 0 or die;
+ open $fh, '<', "./$tmp |" or die;
+ my $result = <$fh>;
+ unlink <$tmp.*>;
+
+ warn "Test string: $string\n";
+ warn "Result : $result";
+ die "Failed!" if $result ne "$string\n";
+ return;
+}
diff --git a/src/pl/plpgsql/Makefile b/src/pl/plpgsql/Makefile
new file mode 100644
index 0000000..6588625
--- /dev/null
+++ b/src/pl/plpgsql/Makefile
@@ -0,0 +1,17 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for src/pl/plpgsql (PostgreSQL's SQL procedural language)
+#
+# Copyright (c) 1994, Regents of the University of California
+#
+# src/pl/plpgsql/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/pl/plpgsql
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+SUBDIRS = src
+
+$(recurse)
diff --git a/src/pl/plpgsql/src/.gitignore b/src/pl/plpgsql/src/.gitignore
new file mode 100644
index 0000000..3ab9a22
--- /dev/null
+++ b/src/pl/plpgsql/src/.gitignore
@@ -0,0 +1,8 @@
+/pl_gram.c
+/pl_gram.h
+/pl_reserved_kwlist_d.h
+/pl_unreserved_kwlist_d.h
+/plerrcodes.h
+/log/
+/results/
+/tmp_check/
diff --git a/src/pl/plpgsql/src/Makefile b/src/pl/plpgsql/src/Makefile
new file mode 100644
index 0000000..f7eb42d
--- /dev/null
+++ b/src/pl/plpgsql/src/Makefile
@@ -0,0 +1,117 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for the PL/pgSQL procedural language
+#
+# src/pl/plpgsql/src/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/pl/plpgsql/src
+top_builddir = ../../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "PL/pgSQL - procedural language"
+
+# Shared library parameters
+NAME= plpgsql
+
+override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS)
+SHLIB_LINK = $(filter -lintl, $(LIBS))
+rpath =
+
+OBJS = \
+ $(WIN32RES) \
+ pl_comp.o \
+ pl_exec.o \
+ pl_funcs.o \
+ pl_gram.o \
+ pl_handler.o \
+ pl_scanner.o
+
+DATA = plpgsql.control plpgsql--1.0.sql
+
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+
+REGRESS = plpgsql_array plpgsql_call plpgsql_control plpgsql_copy plpgsql_domain \
+ plpgsql_record plpgsql_cache plpgsql_simple plpgsql_transaction \
+ plpgsql_trap plpgsql_trigger plpgsql_varprops
+
+# where to find gen_keywordlist.pl and subsidiary files
+TOOLSDIR = $(top_srcdir)/src/tools
+GEN_KEYWORDLIST = $(PERL) -I $(TOOLSDIR) $(TOOLSDIR)/gen_keywordlist.pl
+GEN_KEYWORDLIST_DEPS = $(TOOLSDIR)/gen_keywordlist.pl $(TOOLSDIR)/PerfectHash.pm
+
+all: all-lib
+
+# Shared library stuff
+include $(top_srcdir)/src/Makefile.shlib
+
+
+install: all install-lib install-data install-headers
+
+installdirs: installdirs-lib
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+ $(MKDIR_P) '$(DESTDIR)$(includedir_server)'
+
+uninstall: uninstall-lib uninstall-data uninstall-headers
+
+install-data: installdirs
+ $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+
+# The plpgsql.h header file is needed by instrumentation plugins
+install-headers: installdirs
+ $(INSTALL_DATA) '$(srcdir)/plpgsql.h' '$(DESTDIR)$(includedir_server)'
+
+uninstall-data:
+ rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+
+uninstall-headers:
+ rm -f '$(DESTDIR)$(includedir_server)/plpgsql.h'
+
+.PHONY: install-data install-headers uninstall-data uninstall-headers
+
+
+# Force these dependencies to be known even without dependency info built:
+pl_gram.o pl_handler.o pl_comp.o pl_exec.o pl_funcs.o pl_scanner.o: plpgsql.h pl_gram.h plerrcodes.h
+pl_scanner.o: pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
+
+# See notes in src/backend/parser/Makefile about the following two rules
+pl_gram.h: pl_gram.c
+ touch $@
+
+pl_gram.c: BISONFLAGS += -d
+
+# generate plerrcodes.h from src/backend/utils/errcodes.txt
+plerrcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-plerrcodes.pl
+ $(PERL) $(srcdir)/generate-plerrcodes.pl $< > $@
+
+# generate keyword headers for the scanner
+pl_reserved_kwlist_d.h: pl_reserved_kwlist.h $(GEN_KEYWORDLIST_DEPS)
+ $(GEN_KEYWORDLIST) --varname ReservedPLKeywords $<
+
+pl_unreserved_kwlist_d.h: pl_unreserved_kwlist.h $(GEN_KEYWORDLIST_DEPS)
+ $(GEN_KEYWORDLIST) --varname UnreservedPLKeywords $<
+
+
+check: submake
+ $(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
+
+installcheck: submake
+ $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS)
+
+.PHONY: submake
+submake:
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
+
+
+distprep: pl_gram.h pl_gram.c plerrcodes.h pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
+
+# pl_gram.c, pl_gram.h, plerrcodes.h, pl_reserved_kwlist_d.h, and
+# pl_unreserved_kwlist_d.h are in the distribution tarball, so they
+# are not cleaned here.
+clean distclean: clean-lib
+ rm -f $(OBJS)
+ rm -rf $(pg_regress_clean_files)
+
+maintainer-clean: distclean
+ rm -f pl_gram.c pl_gram.h plerrcodes.h pl_reserved_kwlist_d.h pl_unreserved_kwlist_d.h
diff --git a/src/pl/plpgsql/src/data/copy1.data b/src/pl/plpgsql/src/data/copy1.data
new file mode 100644
index 0000000..5d8478f
--- /dev/null
+++ b/src/pl/plpgsql/src/data/copy1.data
@@ -0,0 +1,3 @@
+1 1.1
+2 2.2
+3 3.3
diff --git a/src/pl/plpgsql/src/expected/plpgsql_array.out b/src/pl/plpgsql/src/expected/plpgsql_array.out
new file mode 100644
index 0000000..9e22e56
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_array.out
@@ -0,0 +1,95 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,4}
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [3:3]={4}
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+NOTICE: a = [1:1][4:4]={{4}}
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+NOTICE: a = {23}
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3,4}
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,3}
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+NOTICE: a = {3,4}
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+ERROR: malformed array literal: "4"
+DETAIL: Array value must start with "{" or dimension information.
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+NOTICE: a = {"(1,11)"}
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+NOTICE: a = {"(,11)"}, a[1].i = 11
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+ERROR: cannot assign to field "i" of column "a" because its type complex[] is not a composite type
+LINE 1: a[1:2].i := array[11,12]
+ ^
+QUERY: a[1:2].i := array[11,12]
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+NOTICE: a = ("{""(,11)""}",), a.c1[1].i = 11
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2,3}
+create temp table onecol as select array[1,2] as f1;
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+-- error cases:
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 0 columns
+CONTEXT: PL/pgSQL assignment "a := from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: assignment source returned 2 columns
+CONTEXT: PL/pgSQL assignment "a := f1, f1 from onecol"
+PL/pgSQL function inline_code_block line 2 at assignment
+insert into onecol values(array[11]);
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+ERROR: query returned more than one row
+CONTEXT: query: a := f1 from onecol
+PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+NOTICE: a = {1,2}
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type real because it does not support subscripting
+LINE 1: a[1] := 2
+ ^
+QUERY: a[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
+ERROR: cannot subscript type double precision because it does not support subscripting
+LINE 1: a.r[1] := 2
+ ^
+QUERY: a.r[1] := 2
+CONTEXT: PL/pgSQL function inline_code_block line 2 at assignment
diff --git a/src/pl/plpgsql/src/expected/plpgsql_cache.out b/src/pl/plpgsql/src/expected/plpgsql_cache.out
new file mode 100644
index 0000000..9df188c
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_cache.out
@@ -0,0 +1,67 @@
+--
+-- Cache-behavior-dependent test cases
+--
+-- These tests logically belong in plpgsql_record.sql, and perhaps someday
+-- can be merged back into it. For now, however, their results are different
+-- depending on debug_discard_caches, so we must have two expected-output
+-- files to cover both cases. To minimize the maintenance effort resulting
+-- from that, this file should contain only tests that do have different
+-- results under debug_discard_caches.
+--
+-- check behavior with changes of a named rowtype
+create table c_mutable(f1 int, f2 text);
+create function c_sillyaddone(int) returns int language plpgsql as
+$$ declare r c_mutable; begin r.f1 := $1; return r.f1 + 1; end $$;
+select c_sillyaddone(42);
+ c_sillyaddone
+---------------
+ 43
+(1 row)
+
+alter table c_mutable drop column f1;
+alter table c_mutable add column f1 float8;
+-- currently, this fails due to cached plan for "r.f1 + 1" expression
+-- (but if debug_discard_caches is on, it will succeed)
+select c_sillyaddone(42);
+ERROR: type of parameter 4 (double precision) does not match that when preparing the plan (integer)
+CONTEXT: PL/pgSQL function c_sillyaddone(integer) line 1 at RETURN
+-- but it's OK if we force plan rebuilding
+discard plans;
+select c_sillyaddone(42);
+ c_sillyaddone
+---------------
+ 43
+(1 row)
+
+-- check behavior with changes in a record rowtype
+create function show_result_type(text) returns text language plpgsql as
+$$
+ declare
+ r record;
+ t text;
+ begin
+ execute $1 into r;
+ select pg_typeof(r.a) into t;
+ return format('type %s value %s', t, r.a::text);
+ end;
+$$;
+select show_result_type('select 1 as a');
+ show_result_type
+----------------------
+ type integer value 1
+(1 row)
+
+-- currently this fails due to cached plan for pg_typeof expression
+-- (but if debug_discard_caches is on, it will succeed)
+select show_result_type('select 2.0 as a');
+ERROR: type of parameter 5 (numeric) does not match that when preparing the plan (integer)
+CONTEXT: SQL statement "select pg_typeof(r.a)"
+PL/pgSQL function show_result_type(text) line 7 at SQL statement
+-- but it's OK if we force plan rebuilding
+discard plans;
+select show_result_type('select 2.0 as a');
+ show_result_type
+------------------------
+ type numeric value 2.0
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_cache_1.out b/src/pl/plpgsql/src/expected/plpgsql_cache_1.out
new file mode 100644
index 0000000..3b8c73a
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_cache_1.out
@@ -0,0 +1,72 @@
+--
+-- Cache-behavior-dependent test cases
+--
+-- These tests logically belong in plpgsql_record.sql, and perhaps someday
+-- can be merged back into it. For now, however, their results are different
+-- depending on debug_discard_caches, so we must have two expected-output
+-- files to cover both cases. To minimize the maintenance effort resulting
+-- from that, this file should contain only tests that do have different
+-- results under debug_discard_caches.
+--
+-- check behavior with changes of a named rowtype
+create table c_mutable(f1 int, f2 text);
+create function c_sillyaddone(int) returns int language plpgsql as
+$$ declare r c_mutable; begin r.f1 := $1; return r.f1 + 1; end $$;
+select c_sillyaddone(42);
+ c_sillyaddone
+---------------
+ 43
+(1 row)
+
+alter table c_mutable drop column f1;
+alter table c_mutable add column f1 float8;
+-- currently, this fails due to cached plan for "r.f1 + 1" expression
+-- (but if debug_discard_caches is on, it will succeed)
+select c_sillyaddone(42);
+ c_sillyaddone
+---------------
+ 43
+(1 row)
+
+-- but it's OK if we force plan rebuilding
+discard plans;
+select c_sillyaddone(42);
+ c_sillyaddone
+---------------
+ 43
+(1 row)
+
+-- check behavior with changes in a record rowtype
+create function show_result_type(text) returns text language plpgsql as
+$$
+ declare
+ r record;
+ t text;
+ begin
+ execute $1 into r;
+ select pg_typeof(r.a) into t;
+ return format('type %s value %s', t, r.a::text);
+ end;
+$$;
+select show_result_type('select 1 as a');
+ show_result_type
+----------------------
+ type integer value 1
+(1 row)
+
+-- currently this fails due to cached plan for pg_typeof expression
+-- (but if debug_discard_caches is on, it will succeed)
+select show_result_type('select 2.0 as a');
+ show_result_type
+------------------------
+ type numeric value 2.0
+(1 row)
+
+-- but it's OK if we force plan rebuilding
+discard plans;
+select show_result_type('select 2.0 as a');
+ show_result_type
+------------------------
+ type numeric value 2.0
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_call.out b/src/pl/plpgsql/src/expected/plpgsql_call.out
new file mode 100644
index 0000000..ab16416
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_call.out
@@ -0,0 +1,532 @@
+--
+-- Tests for procedures / CALL syntax
+--
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ NULL;
+END;
+$$;
+CALL test_proc1();
+-- error: can't return non-NULL
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN 5;
+END;
+$$;
+ERROR: RETURN cannot have a parameter in a procedure
+LINE 5: RETURN 5;
+ ^
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO test1 VALUES (x);
+END;
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+-- Check that plan revalidation doesn't prevent setting transaction properties
+-- (bug #18059). This test must include the first temp-object creation in
+-- this script, or it won't exercise the bug scenario. Hence we put it early.
+CREATE PROCEDURE test_proc3a()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ RAISE NOTICE 'done';
+END;
+$$;
+CALL test_proc3a();
+NOTICE: done
+CREATE TEMP TABLE tt1(f1 int);
+CALL test_proc3a();
+NOTICE: done
+-- nested CALL
+TRUNCATE TABLE test1;
+CREATE PROCEDURE test_proc4(y int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL test_proc3(y);
+ CALL test_proc3($1);
+END;
+$$;
+CALL test_proc4(66);
+SELECT * FROM test1;
+ a
+----
+ 66
+ 66
+(2 rows)
+
+CALL test_proc4(66);
+SELECT * FROM test1;
+ a
+----
+ 66
+ 66
+ 66
+ 66
+(4 rows)
+
+-- output arguments
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ a := a || '+' || a;
+END;
+$$;
+CALL test_proc5('abc');
+ a
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ b := b * a;
+ c := c * a;
+END;
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c
+---+---
+ 6 | 8
+(1 row)
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+ CALL test_proc6(2, c => y, b => x);
+ RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+INFO: x = 6, y = 8
+INFO: x = 12, y = 16
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x + 1, y); -- error
+ RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+ERROR: procedure parameter "b" is an output parameter but corresponding argument is not writable
+CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x constant int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x, y); -- error because x is constant
+END;
+$$;
+ERROR: variable "x" is declared CONSTANT
+CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ FOR i IN 1..5 LOOP
+ CALL test_proc6(i, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+ END LOOP;
+END;
+$$;
+INFO: x = 3, y = 4
+INFO: x = 6, y = 8
+INFO: x = 18, y = 24
+INFO: x = 72, y = 96
+INFO: x = 360, y = 480
+-- recursive with output arguments
+CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+IF x > 1 THEN
+ a := x / 10;
+ b := x / 2;
+ CALL test_proc7(b::int, a, b);
+END IF;
+END;
+$$;
+CALL test_proc7(100, -1, -1);
+ a | b
+---+---
+ 0 | 1
+(1 row)
+
+-- inner COMMIT with output arguments
+CREATE PROCEDURE test_proc7c(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ a := x / 10;
+ b := x / 2;
+ COMMIT;
+END;
+$$;
+CREATE PROCEDURE test_proc7cc(_x int)
+LANGUAGE plpgsql
+AS $$
+DECLARE _a int; _b numeric;
+BEGIN
+ CALL test_proc7c(_x, _a, _b);
+ RAISE NOTICE '_x: %,_a: %, _b: %', _x, _a, _b;
+END
+$$;
+CALL test_proc7cc(10);
+NOTICE: _x: 10,_a: 1, _b: 5
+-- named parameters and defaults
+CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := a * 10;
+ b := b + 10;
+END;
+$$;
+CALL test_proc8a(10, 20);
+NOTICE: a: 10, b: 20
+ a | b
+-----+----
+ 100 | 30
+(1 row)
+
+CALL test_proc8a(b => 20, a => 10);
+NOTICE: a: 10, b: 20
+ a | b
+-----+----
+ 100 | 30
+(1 row)
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc8a(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+ CALL test_proc8a(b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE: a: 10, b: 30
+NOTICE: _a: 100, _b: 40
+NOTICE: a: 100, b: 40
+NOTICE: _a: 1000, _b: 50
+CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := a * 10;
+ b := b + 10;
+ c := c * -10;
+END;
+$$;
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8b(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ CALL test_proc8b(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+NOTICE: a: 10, b: 30, c: 50
+NOTICE: _a: 100, _b: 40, _c: -500
+NOTICE: a: 100, b: 40, c: -500
+NOTICE: _a: 1000, _b: 50, _c: 5000
+CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := a * 10;
+ b := b + 10;
+ c := c * -10;
+END;
+$$;
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(c => _c, b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+NOTICE: a: 10, b: 30, c: 50
+NOTICE: _a: 100, _b: 40, _c: -500
+NOTICE: a: 10, b: 30, c: 50
+NOTICE: _a: 100, _b: 40, _c: -500
+NOTICE: a: 10, b: 30, c: 50
+NOTICE: _a: 100, _b: 40, _c: -500
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, _b); -- fail, no output argument for c
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+ERROR: procedure parameter "c" is an output parameter but corresponding argument is not writable
+CONTEXT: PL/pgSQL function inline_code_block line 5 at CALL
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, b => _b); -- fail, no output argument for c
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+ERROR: procedure parameter "c" is an output parameter but corresponding argument is not writable
+CONTEXT: PL/pgSQL function inline_code_block line 5 at CALL
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ b := a * 2;
+END;
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE: a: 10, b: <NULL>
+NOTICE: _a: 10, _b: 20
+CREATE PROCEDURE test_proc10(IN a int, OUT b int, IN c int DEFAULT 11)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ b := a - c;
+END;
+$$;
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b, c => _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(a => _a, b => _b, c => _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+NOTICE: a: 10, b: <NULL>, c: 7
+NOTICE: _a: 10, _b: 3, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 7
+NOTICE: _a: 10, _b: 3, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 7
+NOTICE: _a: 10, _b: 3, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 7
+NOTICE: _a: 10, _b: 3, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 11
+NOTICE: _a: 10, _b: -1, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 11
+NOTICE: _a: 10, _b: -1, _c: 7
+NOTICE: a: 10, b: <NULL>, c: 11
+NOTICE: _a: 10, _b: -1, _c: 7
+-- OUT + VARIADIC
+CREATE PROCEDURE test_proc11(a OUT int, VARIADIC b int[])
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := b[1] + b[2];
+END;
+$$;
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc11(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+NOTICE: a: <NULL>, b: {30,7}
+NOTICE: _a: 37, _b: 30, _c: 7
+-- transition variable assignment
+TRUNCATE test1;
+CREATE FUNCTION triggerfunc1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ z int := 0;
+BEGIN
+ CALL test_proc6(2, NEW.a, NEW.a);
+ RETURN NEW;
+END;
+$$;
+CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
+INSERT INTO test1 VALUES (1), (2), (3);
+UPDATE test1 SET a = 22 WHERE a = 2;
+SELECT * FROM test1 ORDER BY a;
+ a
+----
+ 1
+ 3
+ 22
+(3 rows)
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc3;
+DROP PROCEDURE test_proc4;
+DROP TABLE test1;
+-- more checks for named-parameter handling
+CREATE PROCEDURE p1(v_cnt int, v_Text inout text = NULL)
+AS $$
+BEGIN
+ v_Text := 'v_cnt = ' || v_cnt;
+END
+$$ LANGUAGE plpgsql;
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer := 42;
+BEGIN
+ CALL p1(v_cnt := v_cnt); -- error, must supply something for v_Text
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+ERROR: procedure parameter "v_text" is an output parameter but corresponding argument is not writable
+CONTEXT: PL/pgSQL function inline_code_block line 6 at CALL
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer := 42;
+BEGIN
+ CALL p1(v_cnt := v_cnt, v_Text := v_Text);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+NOTICE: v_cnt = 42
+DO $$
+DECLARE
+ v_Text text;
+BEGIN
+ CALL p1(10, v_Text := v_Text);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+NOTICE: v_cnt = 10
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer;
+BEGIN
+ CALL p1(v_Text := v_Text, v_cnt := v_cnt);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+NOTICE: <NULL>
+-- check that we detect change of dependencies in CALL
+-- atomic and non-atomic call sites used to do this differently, so check both
+CREATE PROCEDURE inner_p (f1 int)
+AS $$
+BEGIN
+ RAISE NOTICE 'inner_p(%)', f1;
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 1 $$ LANGUAGE sql;
+CREATE PROCEDURE outer_p (f1 int)
+AS $$
+BEGIN
+ RAISE NOTICE 'outer_p(%)', f1;
+ CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+CREATE FUNCTION outer_f (f1 int) RETURNS void
+AS $$
+BEGIN
+ RAISE NOTICE 'outer_f(%)', f1;
+ CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+CALL outer_p(42);
+NOTICE: outer_p(42)
+NOTICE: inner_p(43)
+SELECT outer_f(42);
+NOTICE: outer_f(42)
+NOTICE: inner_p(43)
+ outer_f
+---------
+
+(1 row)
+
+DROP FUNCTION f(int);
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 2 $$ LANGUAGE sql;
+CALL outer_p(42);
+NOTICE: outer_p(42)
+NOTICE: inner_p(44)
+SELECT outer_f(42);
+NOTICE: outer_f(42)
+NOTICE: inner_p(44)
+ outer_f
+---------
+
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_control.out b/src/pl/plpgsql/src/expected/plpgsql_control.out
new file mode 100644
index 0000000..328bd48
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_control.out
@@ -0,0 +1,683 @@
+--
+-- Tests for PL/pgSQL control structures
+--
+-- integer FOR loop
+do $$
+begin
+ -- basic case
+ for i in 1..3 loop
+ raise notice '1..3: i = %', i;
+ end loop;
+ -- with BY, end matches exactly
+ for i in 1..10 by 3 loop
+ raise notice '1..10 by 3: i = %', i;
+ end loop;
+ -- with BY, end does not match
+ for i in 1..11 by 3 loop
+ raise notice '1..11 by 3: i = %', i;
+ end loop;
+ -- zero iterations
+ for i in 1..0 by 3 loop
+ raise notice '1..0 by 3: i = %', i;
+ end loop;
+ -- REVERSE
+ for i in reverse 10..0 by 3 loop
+ raise notice 'reverse 10..0 by 3: i = %', i;
+ end loop;
+ -- potential overflow
+ for i in 2147483620..2147483647 by 10 loop
+ raise notice '2147483620..2147483647 by 10: i = %', i;
+ end loop;
+ -- potential overflow, reverse direction
+ for i in reverse -2147483620..-2147483647 by 10 loop
+ raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;
+ end loop;
+end$$;
+NOTICE: 1..3: i = 1
+NOTICE: 1..3: i = 2
+NOTICE: 1..3: i = 3
+NOTICE: 1..10 by 3: i = 1
+NOTICE: 1..10 by 3: i = 4
+NOTICE: 1..10 by 3: i = 7
+NOTICE: 1..10 by 3: i = 10
+NOTICE: 1..11 by 3: i = 1
+NOTICE: 1..11 by 3: i = 4
+NOTICE: 1..11 by 3: i = 7
+NOTICE: 1..11 by 3: i = 10
+NOTICE: reverse 10..0 by 3: i = 10
+NOTICE: reverse 10..0 by 3: i = 7
+NOTICE: reverse 10..0 by 3: i = 4
+NOTICE: reverse 10..0 by 3: i = 1
+NOTICE: 2147483620..2147483647 by 10: i = 2147483620
+NOTICE: 2147483620..2147483647 by 10: i = 2147483630
+NOTICE: 2147483620..2147483647 by 10: i = 2147483640
+NOTICE: reverse -2147483620..-2147483647 by 10: i = -2147483620
+NOTICE: reverse -2147483620..-2147483647 by 10: i = -2147483630
+NOTICE: reverse -2147483620..-2147483647 by 10: i = -2147483640
+-- BY can't be zero or negative
+do $$
+begin
+ for i in 1..3 by 0 loop
+ raise notice '1..3 by 0: i = %', i;
+ end loop;
+end$$;
+ERROR: BY value of FOR loop must be greater than zero
+CONTEXT: PL/pgSQL function inline_code_block line 3 at FOR with integer loop variable
+do $$
+begin
+ for i in 1..3 by -1 loop
+ raise notice '1..3 by -1: i = %', i;
+ end loop;
+end$$;
+ERROR: BY value of FOR loop must be greater than zero
+CONTEXT: PL/pgSQL function inline_code_block line 3 at FOR with integer loop variable
+do $$
+begin
+ for i in reverse 1..3 by -1 loop
+ raise notice 'reverse 1..3 by -1: i = %', i;
+ end loop;
+end$$;
+ERROR: BY value of FOR loop must be greater than zero
+CONTEXT: PL/pgSQL function inline_code_block line 3 at FOR with integer loop variable
+-- CONTINUE statement
+create table conttesttbl(idx serial, v integer);
+insert into conttesttbl(v) values(10);
+insert into conttesttbl(v) values(20);
+insert into conttesttbl(v) values(30);
+insert into conttesttbl(v) values(40);
+create function continue_test1() returns void as $$
+declare _i integer = 0; _r record;
+begin
+ raise notice '---1---';
+ loop
+ _i := _i + 1;
+ raise notice '%', _i;
+ continue when _i < 10;
+ exit;
+ end loop;
+
+ raise notice '---2---';
+ <<lbl>>
+ loop
+ _i := _i - 1;
+ loop
+ raise notice '%', _i;
+ continue lbl when _i > 0;
+ exit lbl;
+ end loop;
+ end loop;
+
+ raise notice '---3---';
+ <<the_loop>>
+ while _i < 10 loop
+ _i := _i + 1;
+ continue the_loop when _i % 2 = 0;
+ raise notice '%', _i;
+ end loop;
+
+ raise notice '---4---';
+ for _i in 1..10 loop
+ begin
+ -- applies to outer loop, not the nested begin block
+ continue when _i < 5;
+ raise notice '%', _i;
+ end;
+ end loop;
+
+ raise notice '---5---';
+ for _r in select * from conttesttbl loop
+ continue when _r.v <= 20;
+ raise notice '%', _r.v;
+ end loop;
+
+ raise notice '---6---';
+ for _r in execute 'select * from conttesttbl' loop
+ continue when _r.v <= 20;
+ raise notice '%', _r.v;
+ end loop;
+
+ raise notice '---7---';
+ <<looplabel>>
+ for _i in 1..3 loop
+ continue looplabel when _i = 2;
+ raise notice '%', _i;
+ end loop;
+
+ raise notice '---8---';
+ _i := 1;
+ while _i <= 3 loop
+ raise notice '%', _i;
+ _i := _i + 1;
+ continue when _i = 3;
+ end loop;
+
+ raise notice '---9---';
+ for _r in select * from conttesttbl order by v limit 1 loop
+ raise notice '%', _r.v;
+ continue;
+ end loop;
+
+ raise notice '---10---';
+ for _r in execute 'select * from conttesttbl order by v limit 1' loop
+ raise notice '%', _r.v;
+ continue;
+ end loop;
+
+ raise notice '---11---';
+ <<outerlooplabel>>
+ for _i in 1..2 loop
+ raise notice 'outer %', _i;
+ <<innerlooplabel>>
+ for _j in 1..3 loop
+ continue outerlooplabel when _j = 2;
+ raise notice 'inner %', _j;
+ end loop;
+ end loop;
+end; $$ language plpgsql;
+select continue_test1();
+NOTICE: ---1---
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: 4
+NOTICE: 5
+NOTICE: 6
+NOTICE: 7
+NOTICE: 8
+NOTICE: 9
+NOTICE: 10
+NOTICE: ---2---
+NOTICE: 9
+NOTICE: 8
+NOTICE: 7
+NOTICE: 6
+NOTICE: 5
+NOTICE: 4
+NOTICE: 3
+NOTICE: 2
+NOTICE: 1
+NOTICE: 0
+NOTICE: ---3---
+NOTICE: 1
+NOTICE: 3
+NOTICE: 5
+NOTICE: 7
+NOTICE: 9
+NOTICE: ---4---
+NOTICE: 5
+NOTICE: 6
+NOTICE: 7
+NOTICE: 8
+NOTICE: 9
+NOTICE: 10
+NOTICE: ---5---
+NOTICE: 30
+NOTICE: 40
+NOTICE: ---6---
+NOTICE: 30
+NOTICE: 40
+NOTICE: ---7---
+NOTICE: 1
+NOTICE: 3
+NOTICE: ---8---
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: ---9---
+NOTICE: 10
+NOTICE: ---10---
+NOTICE: 10
+NOTICE: ---11---
+NOTICE: outer 1
+NOTICE: inner 1
+NOTICE: outer 2
+NOTICE: inner 1
+ continue_test1
+----------------
+
+(1 row)
+
+-- should fail: CONTINUE is only legal inside a loop
+create function continue_error1() returns void as $$
+begin
+ begin
+ continue;
+ end;
+end;
+$$ language plpgsql;
+ERROR: CONTINUE cannot be used outside a loop
+LINE 4: continue;
+ ^
+-- should fail: unlabeled EXIT is only legal inside a loop
+create function exit_error1() returns void as $$
+begin
+ begin
+ exit;
+ end;
+end;
+$$ language plpgsql;
+ERROR: EXIT cannot be used outside a loop, unless it has a label
+LINE 4: exit;
+ ^
+-- should fail: no such label
+create function continue_error2() returns void as $$
+begin
+ begin
+ loop
+ continue no_such_label;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+ERROR: there is no label "no_such_label" attached to any block or loop enclosing this statement
+LINE 5: continue no_such_label;
+ ^
+-- should fail: no such label
+create function exit_error2() returns void as $$
+begin
+ begin
+ loop
+ exit no_such_label;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+ERROR: there is no label "no_such_label" attached to any block or loop enclosing this statement
+LINE 5: exit no_such_label;
+ ^
+-- should fail: CONTINUE can't reference the label of a named block
+create function continue_error3() returns void as $$
+begin
+ <<begin_block1>>
+ begin
+ loop
+ continue begin_block1;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+ERROR: block label "begin_block1" cannot be used in CONTINUE
+LINE 6: continue begin_block1;
+ ^
+-- On the other hand, EXIT *can* reference the label of a named block
+create function exit_block1() returns void as $$
+begin
+ <<begin_block1>>
+ begin
+ loop
+ exit begin_block1;
+ raise exception 'should not get here';
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+select exit_block1();
+ exit_block1
+-------------
+
+(1 row)
+
+-- verbose end block and end loop
+create function end_label1() returns void as $$
+<<blbl>>
+begin
+ <<flbl1>>
+ for i in 1 .. 10 loop
+ raise notice 'i = %', i;
+ exit flbl1;
+ end loop flbl1;
+ <<flbl2>>
+ for j in 1 .. 10 loop
+ raise notice 'j = %', j;
+ exit flbl2;
+ end loop;
+end blbl;
+$$ language plpgsql;
+select end_label1();
+NOTICE: i = 1
+NOTICE: j = 1
+ end_label1
+------------
+
+(1 row)
+
+-- should fail: undefined end label
+create function end_label2() returns void as $$
+begin
+ for _i in 1 .. 10 loop
+ exit;
+ end loop flbl1;
+end;
+$$ language plpgsql;
+ERROR: end label "flbl1" specified for unlabeled block
+LINE 5: end loop flbl1;
+ ^
+-- should fail: end label does not match start label
+create function end_label3() returns void as $$
+<<outer_label>>
+begin
+ <<inner_label>>
+ for _i in 1 .. 10 loop
+ exit;
+ end loop outer_label;
+end;
+$$ language plpgsql;
+ERROR: end label "outer_label" differs from block's label "inner_label"
+LINE 7: end loop outer_label;
+ ^
+-- should fail: end label on a block without a start label
+create function end_label4() returns void as $$
+<<outer_label>>
+begin
+ for _i in 1 .. 10 loop
+ exit;
+ end loop outer_label;
+end;
+$$ language plpgsql;
+ERROR: end label "outer_label" specified for unlabeled block
+LINE 6: end loop outer_label;
+ ^
+-- unlabeled exit matches no blocks
+do $$
+begin
+for i in 1..10 loop
+ <<innerblock>>
+ begin
+ begin -- unlabeled block
+ exit;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+ end;
+ raise notice 'nor here';
+end loop;
+raise notice 'should get here';
+end$$;
+NOTICE: should get here
+-- check exit out of an unlabeled block to a labeled one
+do $$
+<<outerblock>>
+begin
+ <<innerblock>>
+ begin
+ <<moreinnerblock>>
+ begin
+ begin -- unlabeled block
+ exit innerblock;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+ end;
+ raise notice 'nor here';
+ end;
+ raise notice 'should get here';
+end$$;
+NOTICE: should get here
+-- check exit out of outermost block
+do $$
+<<outerblock>>
+begin
+ <<innerblock>>
+ begin
+ exit outerblock;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+end$$;
+-- unlabeled exit does match a while loop
+do $$
+begin
+ <<outermostwhile>>
+ while 1 > 0 loop
+ <<outerwhile>>
+ while 1 > 0 loop
+ <<innerwhile>>
+ while 1 > 0 loop
+ exit;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should get here';
+ exit outermostwhile;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'nor here';
+ end loop;
+ raise notice 'should get here, too';
+end$$;
+NOTICE: should get here
+NOTICE: should get here, too
+-- check exit out of an unlabeled while to a labeled one
+do $$
+begin
+ <<outerwhile>>
+ while 1 > 0 loop
+ while 1 > 0 loop
+ exit outerwhile;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'should get here';
+end$$;
+NOTICE: should get here
+-- continue to an outer while
+do $$
+declare i int := 0;
+begin
+ <<outermostwhile>>
+ while i < 2 loop
+ raise notice 'outermostwhile, i = %', i;
+ i := i + 1;
+ <<outerwhile>>
+ while 1 > 0 loop
+ <<innerwhile>>
+ while 1 > 0 loop
+ continue outermostwhile;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'nor here';
+ end loop;
+ raise notice 'out of outermostwhile, i = %', i;
+end$$;
+NOTICE: outermostwhile, i = 0
+NOTICE: outermostwhile, i = 1
+NOTICE: out of outermostwhile, i = 2
+-- return out of a while
+create function return_from_while() returns int language plpgsql as $$
+declare i int := 0;
+begin
+ while i < 10 loop
+ if i > 2 then
+ return i;
+ end if;
+ i := i + 1;
+ end loop;
+ return null;
+end$$;
+select return_from_while();
+ return_from_while
+-------------------
+ 3
+(1 row)
+
+-- using list of scalars in fori and fore stmts
+create function for_vect() returns void as $proc$
+<<lbl>>declare a integer; b varchar; c varchar; r record;
+begin
+ -- fori
+ for i in 1 .. 3 loop
+ raise notice '%', i;
+ end loop;
+ -- fore with record var
+ for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
+ raise notice '% % %', r.aa, r.bb, r.cc;
+ end loop;
+ -- fore with single scalar
+ for a in select gs from generate_series(1,4) gs loop
+ raise notice '%', a;
+ end loop;
+ -- fore with multiple scalars
+ for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
+ raise notice '% % %', a, b, c;
+ end loop;
+ -- using qualified names in fors, fore is enabled, disabled only for fori
+ for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
+ raise notice '% % %', a, b, c;
+ end loop;
+end;
+$proc$ language plpgsql;
+select for_vect();
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: 1 BB CC
+NOTICE: 2 BB CC
+NOTICE: 3 BB CC
+NOTICE: 4 BB CC
+NOTICE: 1
+NOTICE: 2
+NOTICE: 3
+NOTICE: 4
+NOTICE: 1 BB CC
+NOTICE: 2 BB CC
+NOTICE: 3 BB CC
+NOTICE: 4 BB CC
+NOTICE: 1 bb cc
+NOTICE: 2 bb cc
+NOTICE: 3 bb cc
+NOTICE: 4 bb cc
+ for_vect
+----------
+
+(1 row)
+
+-- CASE statement
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+ b int = 1;
+begin
+ case $1
+ when 1 then
+ return 'one';
+ when 2 then
+ return 'two';
+ when 3,4,3+5 then
+ return 'three, four or eight';
+ when a then
+ return 'ten';
+ when a+b, a+b+1 then
+ return 'eleven, twelve';
+ end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test
+-----------
+ two
+(1 row)
+
+select case_test(3);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(4);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(5); -- fails
+ERROR: case not found
+HINT: CASE statement is missing ELSE part.
+CONTEXT: PL/pgSQL function case_test(bigint) line 5 at CASE
+select case_test(8);
+ case_test
+----------------------
+ three, four or eight
+(1 row)
+
+select case_test(10);
+ case_test
+-----------
+ ten
+(1 row)
+
+select case_test(11);
+ case_test
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(12);
+ case_test
+----------------
+ eleven, twelve
+(1 row)
+
+select case_test(13); -- fails
+ERROR: case not found
+HINT: CASE statement is missing ELSE part.
+CONTEXT: PL/pgSQL function case_test(bigint) line 5 at CASE
+create or replace function catch() returns void as $$
+begin
+ raise notice '%', case_test(6);
+exception
+ when case_not_found then
+ raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+select catch();
+NOTICE: caught case_not_found 20000 case not found
+ catch
+-------
+
+(1 row)
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+ case
+ when $1 = 1 then
+ return 'one';
+ when $1 = a + 2 then
+ return 'twelve';
+ else
+ return 'other';
+ end case;
+end;
+$$ language plpgsql immutable;
+select case_test(1);
+ case_test
+-----------
+ one
+(1 row)
+
+select case_test(2);
+ case_test
+-----------
+ other
+(1 row)
+
+select case_test(12);
+ case_test
+-----------
+ twelve
+(1 row)
+
+select case_test(13);
+ case_test
+-----------
+ other
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_copy.out b/src/pl/plpgsql/src/expected/plpgsql_copy.out
new file mode 100644
index 0000000..bc834be
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_copy.out
@@ -0,0 +1,82 @@
+-- directory paths are passed to us in environment variables
+\getenv abs_srcdir PG_ABS_SRCDIR
+\getenv abs_builddir PG_ABS_BUILDDIR
+-- set up file names to use
+\set srcfilename :abs_srcdir '/data/copy1.data'
+\set destfilename :abs_builddir '/results/copy1.data'
+CREATE TABLE copy1 (a int, b float);
+-- COPY TO/FROM not authorized from client.
+DO LANGUAGE plpgsql $$
+BEGIN
+ COPY copy1 TO stdout;
+END;
+$$;
+ERROR: cannot COPY to/from client in PL/pgSQL
+CONTEXT: PL/pgSQL function inline_code_block line 3 at SQL statement
+DO LANGUAGE plpgsql $$
+BEGIN
+ COPY copy1 FROM stdin;
+END;
+$$;
+ERROR: cannot COPY to/from client in PL/pgSQL
+CONTEXT: PL/pgSQL function inline_code_block line 3 at SQL statement
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COPY copy1 TO stdout';
+END;
+$$;
+ERROR: cannot COPY to/from client in PL/pgSQL
+CONTEXT: PL/pgSQL function inline_code_block line 3 at EXECUTE
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COPY copy1 FROM stdin';
+END;
+$$;
+ERROR: cannot COPY to/from client in PL/pgSQL
+CONTEXT: PL/pgSQL function inline_code_block line 3 at EXECUTE
+-- Valid cases
+-- COPY FROM
+\set dobody 'BEGIN COPY copy1 FROM ' :'srcfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+SELECT * FROM copy1 ORDER BY 1;
+ a | b
+---+-----
+ 1 | 1.1
+ 2 | 2.2
+ 3 | 3.3
+(3 rows)
+
+TRUNCATE copy1;
+\set cmd 'COPY copy1 FROM ' :'srcfilename'
+\set dobody 'BEGIN EXECUTE ' :'cmd' '; END'
+DO LANGUAGE plpgsql :'dobody';
+SELECT * FROM copy1 ORDER BY 1;
+ a | b
+---+-----
+ 1 | 1.1
+ 2 | 2.2
+ 3 | 3.3
+(3 rows)
+
+-- COPY TO
+-- Copy the data externally once, then process it back to the table.
+\set dobody 'BEGIN COPY copy1 TO ' :'destfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+TRUNCATE copy1;
+\set dobody 'BEGIN COPY copy1 FROM ' :'destfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+\set cmd 'COPY copy1 FROM ' :'destfilename'
+\set dobody 'BEGIN EXECUTE ' :'cmd' '; END'
+DO LANGUAGE plpgsql :'dobody';
+SELECT * FROM copy1 ORDER BY 1;
+ a | b
+---+-----
+ 1 | 1.1
+ 1 | 1.1
+ 2 | 2.2
+ 2 | 2.2
+ 3 | 3.3
+ 3 | 3.3
+(6 rows)
+
+DROP TABLE copy1;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_domain.out b/src/pl/plpgsql/src/expected/plpgsql_domain.out
new file mode 100644
index 0000000..516c2b9
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_domain.out
@@ -0,0 +1,397 @@
+--
+-- Tests for PL/pgSQL's behavior with domain types
+--
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+CREATE FUNCTION test_argresult_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_booltrue(true, true);
+ test_argresult_booltrue
+-------------------------
+ t
+(1 row)
+
+SELECT * FROM test_argresult_booltrue(false, true);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+SELECT * FROM test_argresult_booltrue(true, false);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT: PL/pgSQL function test_argresult_booltrue(booltrue,boolean) while casting return value to function's return type
+CREATE FUNCTION test_assign_booltrue(x bool, y bool) RETURNS booltrue AS $$
+declare v booltrue := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_booltrue(true, true);
+ test_assign_booltrue
+----------------------
+ t
+(1 row)
+
+SELECT * FROM test_assign_booltrue(false, true);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT: PL/pgSQL function test_assign_booltrue(boolean,boolean) line 2 during statement block local variable initialization
+SELECT * FROM test_assign_booltrue(true, false);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT: PL/pgSQL function test_assign_booltrue(boolean,boolean) line 4 at assignment
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+CREATE FUNCTION test_argresult_uint2(x uint2, y int) RETURNS uint2 AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_uint2(100::uint2, 50);
+ test_argresult_uint2
+----------------------
+ 50
+(1 row)
+
+SELECT * FROM test_argresult_uint2(100::uint2, -50);
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: PL/pgSQL function test_argresult_uint2(uint2,integer) while casting return value to function's return type
+SELECT * FROM test_argresult_uint2(null, 1);
+ test_argresult_uint2
+----------------------
+ 1
+(1 row)
+
+CREATE FUNCTION test_assign_uint2(x int, y int) RETURNS uint2 AS $$
+declare v uint2 := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_uint2(100, 50);
+ test_assign_uint2
+-------------------
+ 50
+(1 row)
+
+SELECT * FROM test_assign_uint2(100, -50);
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: PL/pgSQL function test_assign_uint2(integer,integer) line 4 at assignment
+SELECT * FROM test_assign_uint2(-100, 50);
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: PL/pgSQL function test_assign_uint2(integer,integer) line 2 during statement block local variable initialization
+SELECT * FROM test_assign_uint2(null, 1);
+ test_assign_uint2
+-------------------
+ 1
+(1 row)
+
+CREATE DOMAIN nnint AS int NOT NULL;
+CREATE FUNCTION test_argresult_nnint(x nnint, y int) RETURNS nnint AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_nnint(10, 20);
+ test_argresult_nnint
+----------------------
+ 20
+(1 row)
+
+SELECT * FROM test_argresult_nnint(null, 20);
+ERROR: domain nnint does not allow null values
+SELECT * FROM test_argresult_nnint(10, null);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_argresult_nnint(nnint,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_nnint(x int, y int) RETURNS nnint AS $$
+declare v nnint := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint(10, 20);
+ test_assign_nnint
+-------------------
+ 20
+(1 row)
+
+SELECT * FROM test_assign_nnint(null, 20);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_assign_nnint(integer,integer) line 2 during statement block local variable initialization
+SELECT * FROM test_assign_nnint(10, null);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_assign_nnint(integer,integer) line 4 at assignment
+--
+-- Domains over arrays
+--
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+CREATE FUNCTION test_argresult_array_domain(x ordered_pair_domain)
+ RETURNS ordered_pair_domain AS $$
+begin
+return x;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+ test_argresult_array_domain
+-----------------------------
+ {0,100}
+(1 row)
+
+SELECT * FROM test_argresult_array_domain(NULL::ordered_pair_domain);
+ test_argresult_array_domain
+-----------------------------
+
+(1 row)
+
+CREATE FUNCTION test_argresult_array_domain_check_violation()
+ RETURNS ordered_pair_domain AS $$
+begin
+return array[2,1];
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_argresult_array_domain_check_violation();
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT: PL/pgSQL function test_argresult_array_domain_check_violation() while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_pair_domain(x int, y int, z int) RETURNS ordered_pair_domain AS $$
+declare v ordered_pair_domain := array[x, y];
+begin
+v[2] := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_pair_domain(1,2,3);
+ test_assign_ordered_pair_domain
+---------------------------------
+ {1,3}
+(1 row)
+
+SELECT * FROM test_assign_ordered_pair_domain(1,2,0);
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT: PL/pgSQL function test_assign_ordered_pair_domain(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_ordered_pair_domain(2,1,3);
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT: PL/pgSQL function test_assign_ordered_pair_domain(integer,integer,integer) line 2 during statement block local variable initialization
+--
+-- Arrays of domains
+--
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_read_uint2_array(array[1::uint2]);
+ test_read_uint2_array
+-----------------------
+ 1
+(1 row)
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+begin
+return array[x, x];
+end
+$$ LANGUAGE plpgsql;
+select test_build_uint2_array(1::int2);
+ test_build_uint2_array
+------------------------
+ {1,1}
+(1 row)
+
+select test_build_uint2_array(-1::int2); -- fail
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: PL/pgSQL function test_build_uint2_array(smallint) while casting return value to function's return type
+CREATE FUNCTION test_argresult_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+begin
+return array[x::ordered_pair_domain, x::ordered_pair_domain];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_domain_array(array[2,4]);
+ test_argresult_domain_array
+-----------------------------
+ {"{2,4}","{2,4}"}
+(1 row)
+
+select test_argresult_domain_array(array[4,2]); -- fail
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT: PL/pgSQL function test_argresult_domain_array(integer[]) line 3 at RETURN
+CREATE FUNCTION test_argresult_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_domain_array2(array[2,4]);
+ test_argresult_domain_array2
+------------------------------
+ 2
+(1 row)
+
+select test_argresult_domain_array2(array[4,2]); -- fail
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CREATE FUNCTION test_argresult_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+select test_argresult_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+ test_argresult_array_domain_array
+-----------------------------------
+ {2,4}
+(1 row)
+
+--
+-- Domains within composite
+--
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+CREATE FUNCTION test_result_nnint_container(x int, y int)
+ RETURNS nnint_container AS $$
+begin
+return row(x, y)::nnint_container;
+end
+$$ LANGUAGE plpgsql;
+SELECT test_result_nnint_container(null, 3);
+ test_result_nnint_container
+-----------------------------
+ (,3)
+(1 row)
+
+SELECT test_result_nnint_container(3, null); -- fail
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_result_nnint_container(integer,integer) line 3 at RETURN
+CREATE FUNCTION test_assign_nnint_container(x int, y int, z int)
+ RETURNS nnint_container AS $$
+declare v nnint_container := row(x, y);
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint_container(1,2,3);
+ f1 | f2
+----+----
+ 1 | 3
+(1 row)
+
+SELECT * FROM test_assign_nnint_container(1,2,null);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_assign_nnint_container(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_nnint_container(1,null,3);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_assign_nnint_container(integer,integer,integer) line 2 during statement block local variable initialization
+-- Since core system allows this:
+SELECT null::nnint_container;
+ nnint_container
+-----------------
+
+(1 row)
+
+-- so should PL/PgSQL
+CREATE FUNCTION test_assign_nnint_container2(x int, y int, z int)
+ RETURNS nnint_container AS $$
+declare v nnint_container;
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_nnint_container2(1,2,3);
+ f1 | f2
+----+----
+ | 3
+(1 row)
+
+SELECT * FROM test_assign_nnint_container2(1,2,null);
+ERROR: domain nnint does not allow null values
+CONTEXT: PL/pgSQL function test_assign_nnint_container2(integer,integer,integer) line 4 at assignment
+--
+-- Domains of composite
+--
+CREATE TYPE named_pair AS (
+ i integer,
+ j integer
+);
+CREATE DOMAIN ordered_named_pair AS named_pair CHECK((VALUE).i <= (VALUE).j);
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+begin
+return p.i + p.j;
+end
+$$ LANGUAGE plpgsql;
+SELECT read_ordered_named_pair(row(1, 2));
+ read_ordered_named_pair
+-------------------------
+ 3
+(1 row)
+
+SELECT read_ordered_named_pair(row(2, 1)); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+begin
+return row(i, j);
+end
+$$ LANGUAGE plpgsql;
+SELECT build_ordered_named_pair(1,2);
+ build_ordered_named_pair
+--------------------------
+ (1,2)
+(1 row)
+
+SELECT build_ordered_named_pair(2,1); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: PL/pgSQL function build_ordered_named_pair(integer,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_named_pair(x int, y int, z int)
+ RETURNS ordered_named_pair AS $$
+declare v ordered_named_pair := row(x, y);
+begin
+v.j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_named_pair(1,2,3);
+ i | j
+---+---
+ 1 | 3
+(1 row)
+
+SELECT * FROM test_assign_ordered_named_pair(1,2,0);
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: PL/pgSQL function test_assign_ordered_named_pair(integer,integer,integer) line 4 at assignment
+SELECT * FROM test_assign_ordered_named_pair(2,1,3);
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: PL/pgSQL function test_assign_ordered_named_pair(integer,integer,integer) line 2 during statement block local variable initialization
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+begin
+return array[row(i, j), row(i, j+1)];
+end
+$$ LANGUAGE plpgsql;
+SELECT build_ordered_named_pairs(1,2);
+ build_ordered_named_pairs
+---------------------------
+ {"(1,2)","(1,3)"}
+(1 row)
+
+SELECT build_ordered_named_pairs(2,1); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: PL/pgSQL function build_ordered_named_pairs(integer,integer) while casting return value to function's return type
+CREATE FUNCTION test_assign_ordered_named_pairs(x int, y int, z int)
+ RETURNS ordered_named_pair[] AS $$
+declare v ordered_named_pair[] := array[row(x, y)];
+begin
+-- ideally this would work, but it doesn't yet:
+-- v[1].j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+SELECT * FROM test_assign_ordered_named_pairs(1,2,3);
+ test_assign_ordered_named_pairs
+---------------------------------
+ {"(1,2)"}
+(1 row)
+
+SELECT * FROM test_assign_ordered_named_pairs(2,1,3);
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: PL/pgSQL function test_assign_ordered_named_pairs(integer,integer,integer) line 2 during statement block local variable initialization
+SELECT * FROM test_assign_ordered_named_pairs(1,2,0); -- should fail someday
+ test_assign_ordered_named_pairs
+---------------------------------
+ {"(1,2)"}
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
new file mode 100644
index 0000000..afb922d
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -0,0 +1,828 @@
+--
+-- Tests for PL/pgSQL handling of composite (record) variables
+--
+create type two_int4s as (f1 int4, f2 int4);
+create type more_int4s as (f0 text, f1 int4, f2 int4);
+create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
+-- base-case return of a composite type
+create function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1,1)::two_int8s; end $$;
+select retc(42);
+ retc
+--------
+ (42,1)
+(1 row)
+
+-- ok to return a matching record type
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1::int8, 1::int8); end $$;
+select retc(42);
+ retc
+--------
+ (42,1)
+(1 row)
+
+-- we don't currently support implicit casting
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1,1); end $$;
+select retc(42);
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 1.
+CONTEXT: PL/pgSQL function retc(integer) while casting return value to function's return type
+-- nor extra columns
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1::int8, 1::int8, 42); end $$;
+select retc(42);
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (3) does not match expected column count (2).
+CONTEXT: PL/pgSQL function retc(integer) while casting return value to function's return type
+-- same cases with an intermediate "record" variable
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1::int8, 1::int8); return r; end $$;
+select retc(42);
+ retc
+--------
+ (42,1)
+(1 row)
+
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1,1); return r; end $$;
+select retc(42);
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 1.
+CONTEXT: PL/pgSQL function retc(integer) while casting return value to function's return type
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1::int8, 1::int8, 42); return r; end $$;
+select retc(42);
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (3) does not match expected column count (2).
+CONTEXT: PL/pgSQL function retc(integer) while casting return value to function's return type
+-- but, for mostly historical reasons, we do convert when assigning
+-- to a named-composite-type variable
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r two_int8s; begin r := row($1::int8, 1::int8, 42); return r; end $$;
+select retc(42);
+ retc
+--------
+ (42,1)
+(1 row)
+
+do $$ declare c two_int8s;
+begin c := row(1,2); raise notice 'c = %', c; end$$;
+NOTICE: c = (1,2)
+do $$ declare c two_int8s;
+begin for c in select 1,2 loop raise notice 'c = %', c; end loop; end$$;
+NOTICE: c = (1,2)
+do $$ declare c4 two_int4s; c8 two_int8s;
+begin
+ c8 := row(1,2);
+ c4 := c8;
+ c8 := c4;
+ raise notice 'c4 = %', c4;
+ raise notice 'c8 = %', c8;
+end$$;
+NOTICE: c4 = (1,2)
+NOTICE: c8 = (1,2)
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: c = (1,2), d = ("(1,2)","(1,3)")
+NOTICE: c = (10,2), d = ("(11,12)","(1,42)")
+NOTICE: c.q1 = 10, d.c2 = (1,42)
+NOTICE: (d).c2.q2 = 42
+NOTICE: (d.c2).q2 = 42
+-- block-qualified naming
+do $$ <<b>> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+NOTICE: b.c = (1,2), b.d = ("(1,2)","(1,3)")
+NOTICE: b.c = (10,2), b.d = ("(11,12)","(1,42)")
+NOTICE: b.c.q1 = 10, b.d.c2 = (1,42)
+NOTICE: (b.d).c2.q2 = 42
+NOTICE: (b.d.c2).q2 = 42
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "c" because there is no such column in data type two_int8s
+LINE 1: c.c2.x = 1
+ ^
+QUERY: c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ERROR: "d.c2.x" is not a known variable
+LINE 1: do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+ ^
+do $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+ERROR: record "c" has no field "x"
+CONTEXT: PL/pgSQL assignment "b.c.x.q1 = 1"
+PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+ERROR: cannot assign to field "x" of column "b" because there is no such column in data type two_int8s
+LINE 1: b.c.c2.x = 1
+ ^
+QUERY: b.c.c2.x = 1
+CONTEXT: PL/pgSQL function inline_code_block line 1 at assignment
+do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+ERROR: "b.d.c2" is not a known variable
+LINE 1: do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end ...
+ ^
+-- check passing composite result to another function
+create function getq1(two_int8s) returns int8 language plpgsql as $$
+declare r two_int8s; begin r := $1; return r.q1; end $$;
+select getq1(retc(344));
+ getq1
+-------
+ 344
+(1 row)
+
+select getq1(row(1,2));
+ getq1
+-------
+ 1
+(1 row)
+
+do $$
+declare r1 two_int8s; r2 record; x int8;
+begin
+ r1 := retc(345);
+ perform getq1(r1);
+ x := getq1(r1);
+ raise notice 'x = %', x;
+ r2 := retc(346);
+ perform getq1(r2);
+ x := getq1(r2);
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = 345
+NOTICE: x = 346
+-- check assignments of composites
+do $$
+declare r1 two_int8s; r2 two_int8s; r3 record; r4 record;
+begin
+ r1 := row(1,2);
+ raise notice 'r1 = %', r1;
+ r1 := r1; -- shouldn't do anything
+ raise notice 'r1 = %', r1;
+ r2 := r1;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r2.q2 = r1.q1 + 3; -- check that r2 has distinct storage
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r1 := null;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r1 := row(7,11)::two_int8s;
+ r2 := r1;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r3 := row(1,2);
+ r4 := r3;
+ raise notice 'r3 = %', r3;
+ raise notice 'r4 = %', r4;
+ r4.f1 := r4.f1 + 3; -- check that r4 has distinct storage
+ raise notice 'r3 = %', r3;
+ raise notice 'r4 = %', r4;
+ r1 := r3;
+ raise notice 'r1 = %', r1;
+ r4 := r1;
+ raise notice 'r4 = %', r4;
+ r4.q2 := r4.q2 + 1; -- r4's field names have changed
+ raise notice 'r4 = %', r4;
+end$$;
+NOTICE: r1 = (1,2)
+NOTICE: r1 = (1,2)
+NOTICE: r1 = (1,2)
+NOTICE: r2 = (1,2)
+NOTICE: r1 = (1,2)
+NOTICE: r2 = (1,4)
+NOTICE: r1 = <NULL>
+NOTICE: r2 = (1,4)
+NOTICE: r1 = (7,11)
+NOTICE: r2 = (7,11)
+NOTICE: r3 = (1,2)
+NOTICE: r4 = (1,2)
+NOTICE: r3 = (1,2)
+NOTICE: r4 = (4,2)
+NOTICE: r1 = (1,2)
+NOTICE: r4 = (1,2)
+NOTICE: r4 = (1,3)
+-- fields of named-type vars read as null if uninitialized
+do $$
+declare r1 two_int8s;
+begin
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.q1 = %', r1.q1;
+ raise notice 'r1.q2 = %', r1.q2;
+ raise notice 'r1 = %', r1;
+end$$;
+NOTICE: r1 = <NULL>
+NOTICE: r1.q1 = <NULL>
+NOTICE: r1.q2 = <NULL>
+NOTICE: r1 = <NULL>
+do $$
+declare r1 two_int8s;
+begin
+ raise notice 'r1.q1 = %', r1.q1;
+ raise notice 'r1.q2 = %', r1.q2;
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.nosuchfield = %', r1.nosuchfield;
+end$$;
+NOTICE: r1.q1 = <NULL>
+NOTICE: r1.q2 = <NULL>
+NOTICE: r1 = <NULL>
+ERROR: record "r1" has no field "nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
+PL/pgSQL function inline_code_block line 7 at RAISE
+-- records, not so much
+do $$
+declare r1 record;
+begin
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.f1 = %', r1.f1;
+ raise notice 'r1.f2 = %', r1.f2;
+ raise notice 'r1 = %', r1;
+end$$;
+NOTICE: r1 = <NULL>
+ERROR: record "r1" is not assigned yet
+DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
+CONTEXT: SQL expression "r1.f1"
+PL/pgSQL function inline_code_block line 5 at RAISE
+-- but OK if you assign first
+do $$
+declare r1 record;
+begin
+ raise notice 'r1 = %', r1;
+ r1 := row(1,2);
+ raise notice 'r1.f1 = %', r1.f1;
+ raise notice 'r1.f2 = %', r1.f2;
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.nosuchfield = %', r1.nosuchfield;
+end$$;
+NOTICE: r1 = <NULL>
+NOTICE: r1.f1 = 1
+NOTICE: r1.f2 = 2
+NOTICE: r1 = (1,2)
+ERROR: record "r1" has no field "nosuchfield"
+CONTEXT: SQL expression "r1.nosuchfield"
+PL/pgSQL function inline_code_block line 9 at RAISE
+-- check repeated assignments to composite fields
+create table some_table (id int, data text);
+do $$
+declare r some_table;
+begin
+ r := (23, 'skidoo');
+ for i in 1 .. 10 loop
+ r.id := r.id + i;
+ r.data := r.data || ' ' || i;
+ end loop;
+ raise notice 'r = %', r;
+end$$;
+NOTICE: r = (78,"skidoo 1 2 3 4 5 6 7 8 9 10")
+-- check behavior of function declared to return "record"
+create function returnsrecord(int) returns record language plpgsql as
+$$ begin return row($1,$1+1); end $$;
+select returnsrecord(42);
+ returnsrecord
+---------------
+ (42,43)
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int);
+ x | y
+----+----
+ 42 | 43
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (2) does not match expected column count (3).
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 2.
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+-- same with an intermediate record variable
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ declare r record; begin r := row($1,$1+1); return r; end $$;
+select returnsrecord(42);
+ returnsrecord
+---------------
+ (42,43)
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int);
+ x | y
+----+----
+ 42 | 43
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (2) does not match expected column count (3).
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 2.
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+-- should work the same with a missing column in the actual result value
+create table has_hole(f1 int, f2 int, f3 int);
+alter table has_hole drop column f2;
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ begin return row($1,$1+1)::has_hole; end $$;
+select returnsrecord(42);
+ returnsrecord
+---------------
+ (42,43)
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int);
+ x | y
+----+----
+ 42 | 43
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (2) does not match expected column count (3).
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 2.
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+-- same with an intermediate record variable
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ declare r record; begin r := row($1,$1+1)::has_hole; return r; end $$;
+select returnsrecord(42);
+ returnsrecord
+---------------
+ (42,43)
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int);
+ x | y
+----+----
+ 42 | 43
+(1 row)
+
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (2) does not match expected column count (3).
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+ERROR: returned record type does not match expected record type
+DETAIL: Returned type integer does not match expected type bigint in column 2.
+CONTEXT: PL/pgSQL function returnsrecord(integer) while casting return value to function's return type
+-- check access to a field of an argument declared "record"
+create function getf1(x record) returns int language plpgsql as
+$$ begin return x.f1; end $$;
+select getf1(1);
+ERROR: function getf1(integer) does not exist
+LINE 1: select getf1(1);
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+select getf1(row(1,2));
+ getf1
+-------
+ 1
+(1 row)
+
+select getf1(row(1,2)::two_int4s);
+ getf1
+-------
+ 1
+(1 row)
+
+select getf1(row('foo',123,456)::more_int4s);
+ getf1
+-------
+ 123
+(1 row)
+
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select getf1(row(1,2)::two_int8s);
+ERROR: record "x" has no field "f1"
+\set SHOW_CONTEXT errors
+select getf1(row(1,2));
+ getf1
+-------
+ 1
+(1 row)
+
+-- this seemingly-equivalent case behaves a bit differently,
+-- because the core parser's handling of $N symbols is simplistic
+create function getf2(record) returns int language plpgsql as
+$$ begin return $1.f2; end $$;
+select getf2(row(1,2)); -- ideally would work, but does not
+ERROR: could not identify column "f2" in record data type
+LINE 1: $1.f2
+ ^
+QUERY: $1.f2
+CONTEXT: PL/pgSQL function getf2(record) line 1 at RETURN
+select getf2(row(1,2)::two_int4s);
+ getf2
+-------
+ 2
+(1 row)
+
+select getf2(row('foo',123,456)::more_int4s);
+ getf2
+-------
+ 456
+(1 row)
+
+-- check behavior when assignment to FOR-loop variable requires coercion
+do $$
+declare r two_int8s;
+begin
+ for r in select i, i+1 from generate_series(1,4) i
+ loop
+ raise notice 'r = %', r;
+ end loop;
+end$$;
+NOTICE: r = (1,2)
+NOTICE: r = (2,3)
+NOTICE: r = (3,4)
+NOTICE: r = (4,5)
+-- check behavior when returning setof composite
+create function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+declare r record;
+ h has_hole;
+begin
+ return next h;
+ r := (1,2);
+ h := (3,4);
+ return next r;
+ return next h;
+ return next row(5,6);
+ return next row(7,8)::has_hole;
+end$$;
+select returnssetofholes();
+ returnssetofholes
+-------------------
+ (,)
+ (1,2)
+ (3,4)
+ (5,6)
+ (7,8)
+(5 rows)
+
+create or replace function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+declare r record;
+begin
+ return next r; -- fails, not assigned yet
+end$$;
+select returnssetofholes();
+ERROR: record "r" is not assigned yet
+DETAIL: The tuple structure of a not-yet-assigned record is indeterminate.
+CONTEXT: PL/pgSQL function returnssetofholes() line 4 at RETURN NEXT
+create or replace function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+begin
+ return next row(1,2,3); -- fails
+end$$;
+select returnssetofholes();
+ERROR: returned record type does not match expected record type
+DETAIL: Number of returned columns (3) does not match expected column count (2).
+CONTEXT: PL/pgSQL function returnssetofholes() line 3 at RETURN NEXT
+-- check behavior with changes of a named rowtype
+create table mutable(f1 int, f2 text);
+create function sillyaddone(int) returns int language plpgsql as
+$$ declare r mutable; begin r.f1 := $1; return r.f1 + 1; end $$;
+select sillyaddone(42);
+ sillyaddone
+-------------
+ 43
+(1 row)
+
+-- test for change of type of column f1 should be here someday;
+-- for now see plpgsql_cache test
+alter table mutable drop column f1;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select sillyaddone(42); -- fail
+ERROR: record "r" has no field "f1"
+\set SHOW_CONTEXT errors
+create function getf3(x mutable) returns int language plpgsql as
+$$ begin return x.f3; end $$;
+select getf3(null::mutable); -- doesn't work yet
+ERROR: record "x" has no field "f3"
+CONTEXT: SQL expression "x.f3"
+PL/pgSQL function getf3(mutable) line 1 at RETURN
+alter table mutable add column f3 int;
+select getf3(null::mutable); -- now it works
+ getf3
+-------
+
+(1 row)
+
+alter table mutable drop column f3;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select getf3(null::mutable); -- fails again
+ERROR: record "x" has no field "f3"
+\set SHOW_CONTEXT errors
+-- check behavior with creating/dropping a named rowtype
+set check_function_bodies = off; -- else reference to nonexistent type fails
+create function sillyaddtwo(int) returns int language plpgsql as
+$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$;
+reset check_function_bodies;
+select sillyaddtwo(42); -- fail
+ERROR: type "mutable2" does not exist
+LINE 1: declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end
+ ^
+QUERY: declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end
+CONTEXT: compilation of PL/pgSQL function "sillyaddtwo" near line 1
+create table mutable2(f1 int, f2 text);
+select sillyaddtwo(42);
+ sillyaddtwo
+-------------
+ 44
+(1 row)
+
+drop table mutable2;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select sillyaddtwo(42); -- fail
+ERROR: type "mutable2" does not exist
+\set SHOW_CONTEXT errors
+create table mutable2(f0 text, f1 int, f2 text);
+select sillyaddtwo(42);
+ sillyaddtwo
+-------------
+ 44
+(1 row)
+
+select sillyaddtwo(43);
+ sillyaddtwo
+-------------
+ 45
+(1 row)
+
+-- check access to system columns in a record variable
+create function sillytrig() returns trigger language plpgsql as
+$$begin
+ raise notice 'old.ctid = %', old.ctid;
+ raise notice 'old.tableoid = %', old.tableoid::regclass;
+ return new;
+end$$;
+create trigger mutable_trig before update on mutable for each row
+execute procedure sillytrig();
+insert into mutable values ('foo'), ('bar');
+update mutable set f2 = f2 || ' baz';
+NOTICE: old.ctid = (0,1)
+NOTICE: old.tableoid = mutable
+NOTICE: old.ctid = (0,2)
+NOTICE: old.tableoid = mutable
+table mutable;
+ f2
+---------
+ foo baz
+ bar baz
+(2 rows)
+
+-- check returning a composite datum from a trigger
+create or replace function sillytrig() returns trigger language plpgsql as
+$$begin
+ return row(new.*);
+end$$;
+update mutable set f2 = f2 || ' baz';
+table mutable;
+ f2
+-------------
+ foo baz baz
+ bar baz baz
+(2 rows)
+
+create or replace function sillytrig() returns trigger language plpgsql as
+$$declare r record;
+begin
+ r := row(new.*);
+ return r;
+end$$;
+update mutable set f2 = f2 || ' baz';
+table mutable;
+ f2
+-----------------
+ foo baz baz baz
+ bar baz baz baz
+(2 rows)
+
+--
+-- Domains of composite
+--
+create domain ordered_int8s as two_int8s check((value).q1 <= (value).q2);
+create function read_ordered_int8s(p ordered_int8s) returns int8 as $$
+begin return p.q1 + p.q2; end
+$$ language plpgsql;
+select read_ordered_int8s(row(1, 2));
+ read_ordered_int8s
+--------------------
+ 3
+(1 row)
+
+select read_ordered_int8s(row(2, 1)); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+create function build_ordered_int8s(i int8, j int8) returns ordered_int8s as $$
+begin return row(i,j); end
+$$ language plpgsql;
+select build_ordered_int8s(1,2);
+ build_ordered_int8s
+---------------------
+ (1,2)
+(1 row)
+
+select build_ordered_int8s(2,1); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function build_ordered_int8s(bigint,bigint) while casting return value to function's return type
+create function build_ordered_int8s_2(i int8, j int8) returns ordered_int8s as $$
+declare r record; begin r := row(i,j); return r; end
+$$ language plpgsql;
+select build_ordered_int8s_2(1,2);
+ build_ordered_int8s_2
+-----------------------
+ (1,2)
+(1 row)
+
+select build_ordered_int8s_2(2,1); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function build_ordered_int8s_2(bigint,bigint) while casting return value to function's return type
+create function build_ordered_int8s_3(i int8, j int8) returns ordered_int8s as $$
+declare r two_int8s; begin r := row(i,j); return r; end
+$$ language plpgsql;
+select build_ordered_int8s_3(1,2);
+ build_ordered_int8s_3
+-----------------------
+ (1,2)
+(1 row)
+
+select build_ordered_int8s_3(2,1); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function build_ordered_int8s_3(bigint,bigint) while casting return value to function's return type
+create function build_ordered_int8s_4(i int8, j int8) returns ordered_int8s as $$
+declare r ordered_int8s; begin r := row(i,j); return r; end
+$$ language plpgsql;
+select build_ordered_int8s_4(1,2);
+ build_ordered_int8s_4
+-----------------------
+ (1,2)
+(1 row)
+
+select build_ordered_int8s_4(2,1); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function build_ordered_int8s_4(bigint,bigint) line 2 at assignment
+create function build_ordered_int8s_a(i int8, j int8) returns ordered_int8s[] as $$
+begin return array[row(i,j), row(i,j+1)]; end
+$$ language plpgsql;
+select build_ordered_int8s_a(1,2);
+ build_ordered_int8s_a
+-----------------------
+ {"(1,2)","(1,3)"}
+(1 row)
+
+select build_ordered_int8s_a(2,1); -- fail
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function build_ordered_int8s_a(bigint,bigint) while casting return value to function's return type
+-- check field assignment
+do $$
+declare r ordered_int8s;
+begin
+ r.q1 := null;
+ r.q2 := 43;
+ r.q1 := 42;
+ r.q2 := 41; -- fail
+end$$;
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+-- check whole-row assignment
+do $$
+declare r ordered_int8s;
+begin
+ r := null;
+ r := row(null,null);
+ r := row(1,2);
+ r := row(2,1); -- fail
+end$$;
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+-- check assignment in for-loop
+do $$
+declare r ordered_int8s;
+begin
+ for r in values (1,2),(3,4),(6,5) loop
+ raise notice 'r = %', r;
+ end loop;
+end$$;
+NOTICE: r = (1,2)
+NOTICE: r = (3,4)
+ERROR: value for domain ordered_int8s violates check constraint "ordered_int8s_check"
+CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows
+-- check behavior with toastable fields, too
+create type two_texts as (f1 text, f2 text);
+create domain ordered_texts as two_texts check((value).f1 <= (value).f2);
+create table sometable (id int, a text, b text);
+-- b should be compressed, but in-line
+insert into sometable values (1, 'a', repeat('ffoob',1000));
+-- this b should be out-of-line
+insert into sometable values (2, 'a', repeat('ffoob',100000));
+-- this pair should fail the domain check
+insert into sometable values (3, 'z', repeat('ffoob',100000));
+do $$
+declare d ordered_texts;
+begin
+ for d in select a, b from sometable loop
+ raise notice 'succeeded at "%"', d.f1;
+ end loop;
+end$$;
+NOTICE: succeeded at "a"
+NOTICE: succeeded at "a"
+ERROR: value for domain ordered_texts violates check constraint "ordered_texts_check"
+CONTEXT: PL/pgSQL function inline_code_block line 4 at FOR over SELECT rows
+do $$
+declare r record; d ordered_texts;
+begin
+ for r in select * from sometable loop
+ raise notice 'processing row %', r.id;
+ d := row(r.a, r.b);
+ end loop;
+end$$;
+NOTICE: processing row 1
+NOTICE: processing row 2
+NOTICE: processing row 3
+ERROR: value for domain ordered_texts violates check constraint "ordered_texts_check"
+CONTEXT: PL/pgSQL function inline_code_block line 6 at assignment
+do $$
+declare r record; d ordered_texts;
+begin
+ for r in select * from sometable loop
+ raise notice 'processing row %', r.id;
+ d := null;
+ d.f1 := r.a;
+ d.f2 := r.b;
+ end loop;
+end$$;
+NOTICE: processing row 1
+NOTICE: processing row 2
+NOTICE: processing row 3
+ERROR: value for domain ordered_texts violates check constraint "ordered_texts_check"
+CONTEXT: PL/pgSQL function inline_code_block line 8 at assignment
+-- check coercion of a record result to named-composite function output type
+create function compresult(int8) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1,$1); return r; end $$;
+create table two_int8s_tab (f1 two_int8s);
+insert into two_int8s_tab values (compresult(42));
+-- reconnect so we lose any local knowledge of anonymous record types
+\c -
+table two_int8s_tab;
+ f1
+---------
+ (42,42)
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_simple.out b/src/pl/plpgsql/src/expected/plpgsql_simple.out
new file mode 100644
index 0000000..e1f5d67
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_simple.out
@@ -0,0 +1,91 @@
+--
+-- Tests for plpgsql's handling of "simple" expressions
+--
+-- Check that changes to an inline-able function are handled correctly
+create function simplesql(int) returns int language sql
+as 'select $1';
+create function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simplesql(n);
+ if n = 5 then
+ create or replace function simplesql(int) returns int language sql
+ as 'select $1 + 100';
+ end if;
+ end loop;
+ return sum;
+end$$;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- Check that changes in search path are dealt with correctly
+create schema simple1;
+create function simple1.simpletarget(int) returns int language plpgsql
+as $$begin return $1; end$$;
+create function simpletarget(int) returns int language plpgsql
+as $$begin return $1 + 100; end$$;
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simpletarget(n);
+ if n = 5 then
+ set local search_path = 'simple1';
+ end if;
+ end loop;
+ return sum;
+end$$;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- try it with non-volatile functions, too
+alter function simple1.simpletarget(int) immutable;
+alter function simpletarget(int) immutable;
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- make sure flushing local caches changes nothing
+\c -
+select simplecaller();
+ simplecaller
+--------------
+ 555
+(1 row)
+
+-- Check case where first attempt to determine if it's simple fails
+create function simplesql() returns int language sql
+as $$select 1 / 0$$;
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare x int;
+begin
+ select simplesql() into x;
+ return x;
+end$$;
+select simplecaller(); -- division by zero occurs during simple-expr check
+ERROR: division by zero
+CONTEXT: SQL function "simplesql" during inlining
+SQL statement "select simplesql()"
+PL/pgSQL function simplecaller() line 4 at SQL statement
+create or replace function simplesql() returns int language sql
+as $$select 2 + 2$$;
+select simplecaller();
+ simplecaller
+--------------
+ 4
+(1 row)
+
diff --git a/src/pl/plpgsql/src/expected/plpgsql_transaction.out b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
new file mode 100644
index 0000000..254e5b7
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_transaction.out
@@ -0,0 +1,742 @@
+CREATE TABLE test1 (a int, b text);
+CREATE PROCEDURE transaction_test1(x int, y text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ FOR i IN 0..x LOOP
+ INSERT INTO test1 (a, b) VALUES (i, y);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END
+$$;
+CALL transaction_test1(9, 'foo');
+SELECT * FROM test1;
+ a | b
+---+-----
+ 0 | foo
+ 2 | foo
+ 4 | foo
+ 6 | foo
+ 8 | foo
+(5 rows)
+
+TRUNCATE test1;
+DO
+LANGUAGE plpgsql
+$$
+BEGIN
+ FOR i IN 0..9 LOOP
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+-- transaction commands not allowed when called in transaction block
+START TRANSACTION;
+CALL transaction_test1(9, 'error');
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
+COMMIT;
+START TRANSACTION;
+DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$;
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT
+COMMIT;
+TRUNCATE test1;
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ FOR i IN 0..9 LOOP
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+ RETURN 1;
+END
+$$;
+SELECT transaction_test2();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test2() line 6 at COMMIT
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL transaction_test1(9, 'error');
+ RETURN 1;
+END;
+$$;
+SELECT transaction_test3();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
+SQL statement "CALL transaction_test1(9, 'error')"
+PL/pgSQL function transaction_test3() line 3 at CALL
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ EXECUTE 'DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$';
+ RETURN 1;
+END;
+$$;
+SELECT transaction_test4();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function inline_code_block line 1 at COMMIT
+SQL statement "DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$"
+PL/pgSQL function transaction_test4() line 3 at EXECUTE
+-- proconfig settings currently disallow transaction statements
+CREATE PROCEDURE transaction_test5()
+LANGUAGE plpgsql
+SET work_mem = 555
+AS $$
+BEGIN
+ COMMIT;
+END;
+$$;
+CALL transaction_test5();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test5() line 3 at COMMIT
+-- SECURITY DEFINER currently disallow transaction statements
+CREATE PROCEDURE transaction_test5b()
+LANGUAGE plpgsql
+SECURITY DEFINER
+AS $$
+BEGIN
+ COMMIT;
+END;
+$$;
+CALL transaction_test5b();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test5b() line 3 at COMMIT
+TRUNCATE test1;
+-- nested procedure calls
+CREATE PROCEDURE transaction_test6(c text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL transaction_test1(9, c);
+END;
+$$;
+CALL transaction_test6('bar');
+SELECT * FROM test1;
+ a | b
+---+-----
+ 0 | bar
+ 2 | bar
+ 4 | bar
+ 6 | bar
+ 8 | bar
+(5 rows)
+
+TRUNCATE test1;
+CREATE PROCEDURE transaction_test7()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ DO 'BEGIN CALL transaction_test1(9, $x$baz$x$); END;';
+END;
+$$;
+CALL transaction_test7();
+SELECT * FROM test1;
+ a | b
+---+-----
+ 0 | baz
+ 2 | baz
+ 4 | baz
+ 6 | baz
+ 8 | baz
+(5 rows)
+
+CREATE PROCEDURE transaction_test8()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ EXECUTE 'CALL transaction_test1(10, $x$baz$x$)';
+END;
+$$;
+CALL transaction_test8();
+ERROR: invalid transaction termination
+CONTEXT: PL/pgSQL function transaction_test1(integer,text) line 6 at COMMIT
+SQL statement "CALL transaction_test1(10, $x$baz$x$)"
+PL/pgSQL function transaction_test8() line 3 at EXECUTE
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ COMMIT;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+(5 rows)
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (12/(r.x-2));
+ COMMIT;
+ END LOOP;
+END;
+$$;
+ERROR: division by zero
+CONTEXT: SQL statement "INSERT INTO test1 (a) VALUES (12/(r.x-2))"
+PL/pgSQL function inline_code_block line 6 at SQL statement
+SELECT * FROM test1;
+ a | b
+-----+---
+ -6 |
+ -12 |
+(2 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ ROLLBACK;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ IF r.x % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+(3 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN UPDATE test2 SET x = x * 2 RETURNING x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ ROLLBACK;
+ END LOOP;
+END;
+$$;
+ERROR: cannot perform transaction commands inside a cursor loop that is not read-only
+CONTEXT: PL/pgSQL function inline_code_block line 7 at ROLLBACK
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM test2;
+ x
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+(5 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- interaction of FOR UPDATE cursor with subsequent updates (bug #17050)
+TRUNCATE test1;
+INSERT INTO test1 VALUES (1,'one'), (2,'two'), (3,'three');
+DO LANGUAGE plpgsql $$
+DECLARE
+ l_cur CURSOR FOR SELECT a FROM test1 ORDER BY 1 FOR UPDATE;
+BEGIN
+ FOR r IN l_cur LOOP
+ UPDATE test1 SET b = b || ' ' || b WHERE a = r.a;
+ COMMIT;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+---+-------------
+ 1 | one one
+ 2 | two two
+ 3 | three three
+(3 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- like bug #17050, but with implicit cursor
+TRUNCATE test1;
+INSERT INTO test1 VALUES (1,'one'), (2,'two'), (3,'three');
+DO LANGUAGE plpgsql $$
+DECLARE r RECORD;
+BEGIN
+ FOR r IN SELECT a FROM test1 FOR UPDATE LOOP
+ UPDATE test1 SET b = b || ' ' || b WHERE a = r.a;
+ COMMIT;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+---+-------------
+ 1 | one one
+ 2 | two two
+ 3 | three three
+(3 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- commit inside block with exception handler
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+ BEGIN
+ INSERT INTO test1 (a) VALUES (1);
+ COMMIT;
+ INSERT INTO test1 (a) VALUES (1/0);
+ COMMIT;
+ EXCEPTION
+ WHEN division_by_zero THEN
+ RAISE NOTICE 'caught division_by_zero';
+ END;
+END;
+$$;
+ERROR: cannot commit while a subtransaction is active
+CONTEXT: PL/pgSQL function inline_code_block line 5 at COMMIT
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- rollback inside block with exception handler
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+ BEGIN
+ INSERT INTO test1 (a) VALUES (1);
+ ROLLBACK;
+ INSERT INTO test1 (a) VALUES (1/0);
+ ROLLBACK;
+ EXCEPTION
+ WHEN division_by_zero THEN
+ RAISE NOTICE 'caught division_by_zero';
+ END;
+END;
+$$;
+ERROR: cannot roll back while a subtransaction is active
+CONTEXT: PL/pgSQL function inline_code_block line 5 at ROLLBACK
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- test commit/rollback inside exception handler, too
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+ FOR i IN 1..10 LOOP
+ BEGIN
+ INSERT INTO test1 VALUES (i, 'good');
+ INSERT INTO test1 VALUES (i/0, 'bad');
+ EXCEPTION
+ WHEN division_by_zero THEN
+ INSERT INTO test1 VALUES (i, 'exception');
+ IF (i % 3) > 0 THEN COMMIT; ELSE ROLLBACK; END IF;
+ END;
+ END LOOP;
+END;
+$$;
+SELECT * FROM test1;
+ a | b
+----+-----------
+ 1 | exception
+ 2 | exception
+ 4 | exception
+ 5 | exception
+ 7 | exception
+ 8 | exception
+ 10 | exception
+(7 rows)
+
+-- detoast result of simple expression after commit
+CREATE TEMP TABLE test4(f1 text);
+ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression
+INSERT INTO test4 SELECT repeat('xyzzy', 2000);
+-- immutable mark is a bit of a lie, but it serves to make call a simple expr
+-- that will return a still-toasted value
+CREATE FUNCTION data_source(i int) RETURNS TEXT LANGUAGE sql
+AS 'select f1 from test4' IMMUTABLE;
+DO $$
+declare x text;
+begin
+ for i in 1..3 loop
+ x := data_source(i);
+ commit;
+ end loop;
+ raise notice 'length(x) = %', length(x);
+end $$;
+NOTICE: length(x) = 10000
+-- operations on composite types vs. internal transactions
+DO LANGUAGE plpgsql $$
+declare
+ c test1 := row(42, 'hello');
+ r bool;
+begin
+ for i in 1..3 loop
+ r := c is not null;
+ raise notice 'r = %', r;
+ commit;
+ end loop;
+ for i in 1..3 loop
+ r := c is null;
+ raise notice 'r = %', r;
+ rollback;
+ end loop;
+end
+$$;
+NOTICE: r = t
+NOTICE: r = t
+NOTICE: r = t
+NOTICE: r = f
+NOTICE: r = f
+NOTICE: r = f
+-- COMMIT failures
+DO LANGUAGE plpgsql $$
+BEGIN
+ CREATE TABLE test3 (y int UNIQUE DEFERRABLE INITIALLY DEFERRED);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (1);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (1);
+ INSERT INTO test3 (y) VALUES (2);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (3); -- won't get here
+END;
+$$;
+ERROR: duplicate key value violates unique constraint "test3_y_key"
+DETAIL: Key (y)=(1) already exists.
+CONTEXT: PL/pgSQL function inline_code_block line 9 at COMMIT
+SELECT * FROM test3;
+ y
+---
+ 1
+(1 row)
+
+-- failure while trying to persist a cursor across a transaction (bug #15703)
+CREATE PROCEDURE cursor_fail_during_commit()
+ LANGUAGE plpgsql
+AS $$
+ DECLARE id int;
+ BEGIN
+ FOR id IN SELECT 1/(x-1000) FROM generate_series(1,1000) x LOOP
+ INSERT INTO test1 VALUES(id);
+ COMMIT;
+ END LOOP;
+ END;
+$$;
+TRUNCATE test1;
+CALL cursor_fail_during_commit();
+ERROR: division by zero
+CONTEXT: PL/pgSQL function cursor_fail_during_commit() line 6 at COMMIT
+-- note that error occurs during first COMMIT, hence nothing is in test1
+SELECT count(*) FROM test1;
+ count
+-------
+ 0
+(1 row)
+
+CREATE PROCEDURE cursor_fail_during_rollback()
+ LANGUAGE plpgsql
+AS $$
+ DECLARE id int;
+ BEGIN
+ FOR id IN SELECT 1/(x-1000) FROM generate_series(1,1000) x LOOP
+ INSERT INTO test1 VALUES(id);
+ ROLLBACK;
+ END LOOP;
+ END;
+$$;
+TRUNCATE test1;
+CALL cursor_fail_during_rollback();
+ERROR: division by zero
+CONTEXT: PL/pgSQL function cursor_fail_during_rollback() line 6 at ROLLBACK
+SELECT count(*) FROM test1;
+ count
+-------
+ 0
+(1 row)
+
+-- SET TRANSACTION
+DO LANGUAGE plpgsql $$
+BEGIN
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ RESET TRANSACTION ISOLATION LEVEL;
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+END;
+$$;
+INFO: read committed
+INFO: repeatable read
+INFO: read committed
+-- error cases
+DO LANGUAGE plpgsql $$
+BEGIN
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+END;
+$$;
+ERROR: SET TRANSACTION ISOLATION LEVEL must be called before any query
+CONTEXT: SQL statement "SET TRANSACTION ISOLATION LEVEL REPEATABLE READ"
+PL/pgSQL function inline_code_block line 3 at SQL statement
+DO LANGUAGE plpgsql $$
+BEGIN
+ SAVEPOINT foo;
+END;
+$$;
+ERROR: unsupported transaction command in PL/pgSQL
+CONTEXT: PL/pgSQL function inline_code_block line 3 at SQL statement
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COMMIT';
+END;
+$$;
+ERROR: EXECUTE of transaction commands is not implemented
+CONTEXT: PL/pgSQL function inline_code_block line 3 at EXECUTE
+-- snapshot handling test
+TRUNCATE test2;
+CREATE PROCEDURE transaction_test9()
+LANGUAGE SQL
+AS $$
+INSERT INTO test2 VALUES (42);
+$$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ ROLLBACK;
+ CALL transaction_test9();
+END
+$$;
+SELECT * FROM test2;
+ x
+----
+ 42
+(1 row)
+
+-- another snapshot handling case: argument expressions of a CALL need
+-- to be evaluated with an up-to-date snapshot
+CREATE FUNCTION report_count() RETURNS int
+STABLE LANGUAGE sql
+AS $$ SELECT COUNT(*) FROM test2 $$;
+CREATE PROCEDURE transaction_test9b(cnt int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'count = %', cnt;
+END
+$$;
+DO $$
+BEGIN
+ CALL transaction_test9b(report_count());
+ INSERT INTO test2 VALUES(43);
+ CALL transaction_test9b(report_count());
+END
+$$;
+NOTICE: count = 1
+NOTICE: count = 2
+-- Test transaction in procedure with output parameters. This uses a
+-- different portal strategy and different code paths in pquery.c.
+CREATE PROCEDURE transaction_test10a(INOUT x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ x := x + 1;
+ COMMIT;
+END;
+$$;
+CALL transaction_test10a(10);
+ x
+----
+ 11
+(1 row)
+
+CREATE PROCEDURE transaction_test10b(INOUT x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ x := x - 1;
+ ROLLBACK;
+END;
+$$;
+CALL transaction_test10b(10);
+ x
+---
+ 9
+(1 row)
+
+-- transaction timestamp vs. statement timestamp
+CREATE PROCEDURE transaction_test11()
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ s1 timestamp with time zone;
+ s2 timestamp with time zone;
+ s3 timestamp with time zone;
+ t1 timestamp with time zone;
+ t2 timestamp with time zone;
+ t3 timestamp with time zone;
+BEGIN
+ s1 := statement_timestamp();
+ t1 := transaction_timestamp();
+ ASSERT s1 = t1;
+ PERFORM pg_sleep(0.001);
+ COMMIT;
+ s2 := statement_timestamp();
+ t2 := transaction_timestamp();
+ ASSERT s2 = s1;
+ ASSERT t2 > t1;
+ PERFORM pg_sleep(0.001);
+ ROLLBACK;
+ s3 := statement_timestamp();
+ t3 := transaction_timestamp();
+ ASSERT s3 = s1;
+ ASSERT t3 > t2;
+END;
+$$;
+CALL transaction_test11();
+-- transaction chain
+TRUNCATE test1;
+DO LANGUAGE plpgsql $$
+BEGIN
+ ROLLBACK;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ FOR i IN 0..3 LOOP
+ RAISE INFO 'transaction_isolation = %', current_setting('transaction_isolation');
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT AND CHAIN;
+ ELSE
+ ROLLBACK AND CHAIN;
+ END IF;
+ END LOOP;
+END
+$$;
+INFO: transaction_isolation = repeatable read
+INFO: transaction_isolation = repeatable read
+INFO: transaction_isolation = repeatable read
+INFO: transaction_isolation = repeatable read
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+(2 rows)
+
+DROP TABLE test1;
+DROP TABLE test2;
+DROP TABLE test3;
diff --git a/src/pl/plpgsql/src/expected/plpgsql_trap.out b/src/pl/plpgsql/src/expected/plpgsql_trap.out
new file mode 100644
index 0000000..90cf6c2
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_trap.out
@@ -0,0 +1,255 @@
+--
+-- Test error trapping
+--
+create function trap_zero_divide(int) returns int as $$
+declare x int;
+ sx smallint;
+begin
+ begin -- start a subtransaction
+ raise notice 'should see this';
+ x := 100 / $1;
+ raise notice 'should see this only if % <> 0', $1;
+ sx := $1;
+ raise notice 'should see this only if % fits in smallint', $1;
+ if $1 < 0 then
+ raise exception '% is less than zero', $1;
+ end if;
+ exception
+ when division_by_zero then
+ raise notice 'caught division_by_zero';
+ x := -1;
+ when NUMERIC_VALUE_OUT_OF_RANGE then
+ raise notice 'caught numeric_value_out_of_range';
+ x := -2;
+ end;
+ return x;
+end$$ language plpgsql;
+select trap_zero_divide(50);
+NOTICE: should see this
+NOTICE: should see this only if 50 <> 0
+NOTICE: should see this only if 50 fits in smallint
+ trap_zero_divide
+------------------
+ 2
+(1 row)
+
+select trap_zero_divide(0);
+NOTICE: should see this
+NOTICE: caught division_by_zero
+ trap_zero_divide
+------------------
+ -1
+(1 row)
+
+select trap_zero_divide(100000);
+NOTICE: should see this
+NOTICE: should see this only if 100000 <> 0
+NOTICE: caught numeric_value_out_of_range
+ trap_zero_divide
+------------------
+ -2
+(1 row)
+
+select trap_zero_divide(-100);
+NOTICE: should see this
+NOTICE: should see this only if -100 <> 0
+NOTICE: should see this only if -100 fits in smallint
+ERROR: -100 is less than zero
+CONTEXT: PL/pgSQL function trap_zero_divide(integer) line 12 at RAISE
+create table match_source as
+ select x as id, x*10 as data, x/10 as ten from generate_series(1,100) x;
+create function trap_matching_test(int) returns int as $$
+declare x int;
+ sx smallint;
+ y int;
+begin
+ begin -- start a subtransaction
+ x := 100 / $1;
+ sx := $1;
+ select into y data from match_source where id =
+ (select id from match_source b where ten = $1);
+ exception
+ when data_exception then -- category match
+ raise notice 'caught data_exception';
+ x := -1;
+ when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then
+ raise notice 'caught numeric_value_out_of_range or cardinality_violation';
+ x := -2;
+ end;
+ return x;
+end$$ language plpgsql;
+select trap_matching_test(50);
+ trap_matching_test
+--------------------
+ 2
+(1 row)
+
+select trap_matching_test(0);
+NOTICE: caught data_exception
+ trap_matching_test
+--------------------
+ -1
+(1 row)
+
+select trap_matching_test(100000);
+NOTICE: caught data_exception
+ trap_matching_test
+--------------------
+ -1
+(1 row)
+
+select trap_matching_test(1);
+NOTICE: caught numeric_value_out_of_range or cardinality_violation
+ trap_matching_test
+--------------------
+ -2
+(1 row)
+
+create temp table foo (f1 int);
+create function subxact_rollback_semantics() returns int as $$
+declare x int;
+begin
+ x := 1;
+ insert into foo values(x);
+ begin
+ x := x + 1;
+ insert into foo values(x);
+ raise exception 'inner';
+ exception
+ when others then
+ x := x * 10;
+ end;
+ insert into foo values(x);
+ return x;
+end$$ language plpgsql;
+select subxact_rollback_semantics();
+ subxact_rollback_semantics
+----------------------------
+ 20
+(1 row)
+
+select * from foo;
+ f1
+----
+ 1
+ 20
+(2 rows)
+
+drop table foo;
+create function trap_timeout() returns void as $$
+begin
+ declare x int;
+ begin
+ -- we assume this will take longer than 1 second:
+ select count(*) into x from generate_series(1, 1000000000000);
+ exception
+ when others then
+ raise notice 'caught others?';
+ when query_canceled then
+ raise notice 'nyeah nyeah, can''t stop me';
+ end;
+ -- Abort transaction to abandon the statement_timeout setting. Otherwise,
+ -- the next top-level statement would be vulnerable to the timeout.
+ raise exception 'end of function';
+end$$ language plpgsql;
+begin;
+set statement_timeout to 1000;
+select trap_timeout();
+NOTICE: nyeah nyeah, can't stop me
+ERROR: end of function
+CONTEXT: PL/pgSQL function trap_timeout() line 15 at RAISE
+rollback;
+-- Test for pass-by-ref values being stored in proper context
+create function test_variable_storage() returns text as $$
+declare x text;
+begin
+ x := '1234';
+ begin
+ x := x || '5678';
+ -- force error inside subtransaction SPI context
+ perform trap_zero_divide(-100);
+ exception
+ when others then
+ x := x || '9012';
+ end;
+ return x;
+end$$ language plpgsql;
+select test_variable_storage();
+NOTICE: should see this
+NOTICE: should see this only if -100 <> 0
+NOTICE: should see this only if -100 fits in smallint
+ test_variable_storage
+-----------------------
+ 123456789012
+(1 row)
+
+--
+-- test foreign key error trapping
+--
+create temp table root(f1 int primary key);
+create temp table leaf(f1 int references root deferrable);
+insert into root values(1);
+insert into leaf values(1);
+insert into leaf values(2); -- fails
+ERROR: insert or update on table "leaf" violates foreign key constraint "leaf_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "root".
+create function trap_foreign_key(int) returns int as $$
+begin
+ begin -- start a subtransaction
+ insert into leaf values($1);
+ exception
+ when foreign_key_violation then
+ raise notice 'caught foreign_key_violation';
+ return 0;
+ end;
+ return 1;
+end$$ language plpgsql;
+create function trap_foreign_key_2() returns int as $$
+begin
+ begin -- start a subtransaction
+ set constraints all immediate;
+ exception
+ when foreign_key_violation then
+ raise notice 'caught foreign_key_violation';
+ return 0;
+ end;
+ return 1;
+end$$ language plpgsql;
+select trap_foreign_key(1);
+ trap_foreign_key
+------------------
+ 1
+(1 row)
+
+select trap_foreign_key(2); -- detects FK violation
+NOTICE: caught foreign_key_violation
+ trap_foreign_key
+------------------
+ 0
+(1 row)
+
+begin;
+ set constraints all deferred;
+ select trap_foreign_key(2); -- should not detect FK violation
+ trap_foreign_key
+------------------
+ 1
+(1 row)
+
+ savepoint x;
+ set constraints all immediate; -- fails
+ERROR: insert or update on table "leaf" violates foreign key constraint "leaf_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "root".
+ rollback to x;
+ select trap_foreign_key_2(); -- detects FK violation
+NOTICE: caught foreign_key_violation
+ trap_foreign_key_2
+--------------------
+ 0
+(1 row)
+
+commit; -- still fails
+ERROR: insert or update on table "leaf" violates foreign key constraint "leaf_f1_fkey"
+DETAIL: Key (f1)=(2) is not present in table "root".
+drop function trap_foreign_key(int);
+drop function trap_foreign_key_2();
diff --git a/src/pl/plpgsql/src/expected/plpgsql_trigger.out b/src/pl/plpgsql/src/expected/plpgsql_trigger.out
new file mode 100644
index 0000000..3cc67ba
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_trigger.out
@@ -0,0 +1,36 @@
+-- Simple test to verify accessibility of the OLD and NEW trigger variables
+create table testtr (a int, b text);
+create function testtr_trigger() returns trigger language plpgsql as
+$$begin
+ raise notice 'tg_op = %', tg_op;
+ raise notice 'old(%) = %', old.a, row(old.*);
+ raise notice 'new(%) = %', new.a, row(new.*);
+ if (tg_op = 'DELETE') then
+ return old;
+ else
+ return new;
+ end if;
+end$$;
+create trigger testtr_trigger before insert or delete or update on testtr
+ for each row execute function testtr_trigger();
+insert into testtr values (1, 'one'), (2, 'two');
+NOTICE: tg_op = INSERT
+NOTICE: old(<NULL>) = (,)
+NOTICE: new(1) = (1,one)
+NOTICE: tg_op = INSERT
+NOTICE: old(<NULL>) = (,)
+NOTICE: new(2) = (2,two)
+update testtr set a = a + 1;
+NOTICE: tg_op = UPDATE
+NOTICE: old(1) = (1,one)
+NOTICE: new(2) = (2,one)
+NOTICE: tg_op = UPDATE
+NOTICE: old(2) = (2,two)
+NOTICE: new(3) = (3,two)
+delete from testtr;
+NOTICE: tg_op = DELETE
+NOTICE: old(2) = (2,one)
+NOTICE: new(<NULL>) = (,)
+NOTICE: tg_op = DELETE
+NOTICE: old(3) = (3,two)
+NOTICE: new(<NULL>) = (,)
diff --git a/src/pl/plpgsql/src/expected/plpgsql_varprops.out b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
new file mode 100644
index 0000000..25115a0
--- /dev/null
+++ b/src/pl/plpgsql/src/expected/plpgsql_varprops.out
@@ -0,0 +1,298 @@
+--
+-- Tests for PL/pgSQL variable properties: CONSTANT, NOT NULL, initializers
+--
+create type var_record as (f1 int4, f2 int4);
+create domain int_nn as int not null;
+create domain var_record_nn as var_record not null;
+create domain var_record_colnn as var_record check((value).f2 is not null);
+-- CONSTANT
+do $$
+declare x constant int := 42;
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = 42
+do $$
+declare x constant int;
+begin
+ x := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x := 42; -- fail
+ ^
+do $$
+declare x constant int; y int;
+begin
+ for x, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: for x, y in select 1, 2 loop -- fail
+ ^
+do $$
+declare x constant int[];
+begin
+ x[1] := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x[1] := 42; -- fail
+ ^
+do $$
+declare x constant int[]; y int;
+begin
+ for x[1], y in select 1, 2 loop -- fail (currently, unsupported syntax)
+ end loop;
+end$$;
+ERROR: syntax error at or near "["
+LINE 4: for x[1], y in select 1, 2 loop -- fail (currently, unsup...
+ ^
+do $$
+declare x constant var_record;
+begin
+ x.f1 := 42; -- fail
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: x.f1 := 42; -- fail
+ ^
+do $$
+declare x constant var_record; y int;
+begin
+ for x.f1, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+ERROR: variable "x" is declared CONSTANT
+LINE 4: for x.f1, y in select 1, 2 loop -- fail
+ ^
+-- initializer expressions
+do $$
+declare x int := sin(0);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = 0
+do $$
+declare x int := 1/0; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: division by zero
+CONTEXT: SQL expression "1/0"
+PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x bigint[] := array[1,3,5];
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = {1,3,5}
+do $$
+declare x record := row(1,2,3);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = (1,2,3)
+do $$
+declare x var_record := row(1,2);
+begin
+ raise notice 'x = %', x;
+end$$;
+NOTICE: x = (1,2)
+-- NOT NULL
+do $$
+declare x int not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x int not null; -- fail
+ ^
+do $$
+declare x int not null := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = 42
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x int not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x record not null; -- fail
+ ^
+do $$
+declare x record not null := row(42);
+begin
+ raise notice 'x = %', x;
+ x := row(null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (42)
+NOTICE: x = ()
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+do $$
+declare x record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: variable "x" must have a default value, since it's declared NOT NULL
+LINE 2: declare x var_record not null; -- fail
+ ^
+do $$
+declare x var_record not null := row(41,42);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (41,42)
+NOTICE: x = (,)
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 7 at assignment
+do $$
+declare x var_record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: null value cannot be assigned to variable "x" declared NOT NULL
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+-- Check that variables are reinitialized on block re-entry.
+do $$
+begin
+ for i in 1..3 loop
+ declare
+ x int;
+ y int := i;
+ r record;
+ c var_record;
+ begin
+ if i = 1 then
+ x := 42;
+ r := row(i, i+1);
+ c := row(i, i+1);
+ end if;
+ raise notice 'x = %', x;
+ raise notice 'y = %', y;
+ raise notice 'r = %', r;
+ raise notice 'c = %', c;
+ end;
+ end loop;
+end$$;
+NOTICE: x = 42
+NOTICE: y = 1
+NOTICE: r = (1,2)
+NOTICE: c = (1,2)
+NOTICE: x = <NULL>
+NOTICE: y = 2
+NOTICE: r = <NULL>
+NOTICE: c = <NULL>
+NOTICE: x = <NULL>
+NOTICE: y = 3
+NOTICE: r = <NULL>
+NOTICE: c = <NULL>
+-- Check enforcement of domain constraints during initialization
+do $$
+declare x int_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x int_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x int_nn := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = 42
+ERROR: domain int_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x var_record_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record_nn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ x := null; -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: domain var_record_nn does not allow null values
+CONTEXT: PL/pgSQL function inline_code_block line 6 at assignment
+do $$
+declare x var_record_colnn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record_colnn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record_colnn := row(1,null); -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 2 during statement block local variable initialization
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- fail
+end$$;
+NOTICE: x = (1,2)
+ERROR: value for domain var_record_colnn violates check constraint "var_record_colnn_check"
+CONTEXT: PL/pgSQL function inline_code_block line 5 at assignment
diff --git a/src/pl/plpgsql/src/generate-plerrcodes.pl b/src/pl/plpgsql/src/generate-plerrcodes.pl
new file mode 100644
index 0000000..30e5732
--- /dev/null
+++ b/src/pl/plpgsql/src/generate-plerrcodes.pl
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+#
+# Generate the plerrcodes.h header from errcodes.txt
+# Copyright (c) 2000-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+print
+ "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+print "/* there is deliberately not an #ifndef PLERRCODES_H here */\n";
+
+open my $errcodes, '<', $ARGV[0] or die;
+
+while (<$errcodes>)
+{
+ chomp;
+
+ # Skip comments
+ next if /^#/;
+ next if /^\s*$/;
+
+ # Skip section headers
+ next if /^Section:/;
+
+ die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+
+ (my $sqlstate, my $type, my $errcode_macro, my $condition_name) =
+ ($1, $2, $3, $4);
+
+ # Skip non-errors
+ next unless $type eq 'E';
+
+ # Skip lines without PL/pgSQL condition names
+ next unless defined($condition_name);
+
+ print "\n{\n\t\"$condition_name\", $errcode_macro\n},\n";
+}
+
+close $errcodes;
diff --git a/src/pl/plpgsql/src/nls.mk b/src/pl/plpgsql/src/nls.mk
new file mode 100644
index 0000000..611aa8c
--- /dev/null
+++ b/src/pl/plpgsql/src/nls.mk
@@ -0,0 +1,6 @@
+# src/pl/plpgsql/src/nls.mk
+CATALOG_NAME = plpgsql
+AVAIL_LANGUAGES = cs de el es fr it ja ka ko pl pt_BR ru sv tr uk vi zh_CN
+GETTEXT_FILES = pl_comp.c pl_exec.c pl_gram.c pl_funcs.c pl_handler.c pl_scanner.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) yyerror plpgsql_yyerror
+GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c
new file mode 100644
index 0000000..65f259a
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_comp.c
@@ -0,0 +1,2682 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_comp.c - Compiler part of the PL/pgSQL
+ * procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_comp.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "access/htup_details.h"
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_type.h"
+#include "plpgsql.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/regproc.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+/* ----------
+ * Our own local and global variables
+ * ----------
+ */
+PLpgSQL_stmt_block *plpgsql_parse_result;
+
+static int datums_alloc;
+int plpgsql_nDatums;
+PLpgSQL_datum **plpgsql_Datums;
+static int datums_last;
+
+char *plpgsql_error_funcname;
+bool plpgsql_DumpExecTree = false;
+bool plpgsql_check_syntax = false;
+
+PLpgSQL_function *plpgsql_curr_compile;
+
+/* A context appropriate for short-term allocs during compilation */
+MemoryContext plpgsql_compile_tmp_cxt;
+
+/* ----------
+ * Hash table for compiled functions
+ * ----------
+ */
+static HTAB *plpgsql_HashTable = NULL;
+
+typedef struct plpgsql_hashent
+{
+ PLpgSQL_func_hashkey key;
+ PLpgSQL_function *function;
+} plpgsql_HashEnt;
+
+#define FUNCS_PER_USER 128 /* initial table size */
+
+/* ----------
+ * Lookup table for EXCEPTION condition names
+ * ----------
+ */
+typedef struct
+{
+ const char *label;
+ int sqlerrstate;
+} ExceptionLabelMap;
+
+static const ExceptionLabelMap exception_label_map[] = {
+#include "plerrcodes.h" /* pgrminclude ignore */
+ {NULL, 0}
+};
+
+
+/* ----------
+ * static prototypes
+ * ----------
+ */
+static PLpgSQL_function *do_compile(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator);
+static void plpgsql_compile_error_callback(void *arg);
+static void add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name);
+static void add_dummy_return(PLpgSQL_function *function);
+static Node *plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref);
+static Node *plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var);
+static Node *plpgsql_param_ref(ParseState *pstate, ParamRef *pref);
+static Node *resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
+ ColumnRef *cref, bool error_if_no_field);
+static Node *make_datum_param(PLpgSQL_expr *expr, int dno, int location);
+static PLpgSQL_row *build_row_from_vars(PLpgSQL_variable **vars, int numvars);
+static PLpgSQL_type *build_datatype(HeapTuple typeTup, int32 typmod,
+ Oid collation, TypeName *origtypname);
+static void plpgsql_start_datums(void);
+static void plpgsql_finish_datums(PLpgSQL_function *function);
+static void compute_function_hashkey(FunctionCallInfo fcinfo,
+ Form_pg_proc procStruct,
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator);
+static void plpgsql_resolve_polymorphic_argtypes(int numargs,
+ Oid *argtypes, char *argmodes,
+ Node *call_expr, bool forValidator,
+ const char *proname);
+static PLpgSQL_function *plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key);
+static void plpgsql_HashTableInsert(PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *func_key);
+static void plpgsql_HashTableDelete(PLpgSQL_function *function);
+static void delete_function(PLpgSQL_function *func);
+
+/* ----------
+ * plpgsql_compile Make an execution tree for a PL/pgSQL function.
+ *
+ * If forValidator is true, we're only compiling for validation purposes,
+ * and so some checks are skipped.
+ *
+ * Note: it's important for this to fall through quickly if the function
+ * has already been compiled.
+ * ----------
+ */
+PLpgSQL_function *
+plpgsql_compile(FunctionCallInfo fcinfo, bool forValidator)
+{
+ Oid funcOid = fcinfo->flinfo->fn_oid;
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ PLpgSQL_function *function;
+ PLpgSQL_func_hashkey hashkey;
+ bool function_valid = false;
+ bool hashkey_valid = false;
+
+ /*
+ * Lookup the pg_proc tuple by Oid; we'll need it in any case
+ */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcOid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", funcOid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * See if there's already a cache entry for the current FmgrInfo. If not,
+ * try to find one in the hash table.
+ */
+ function = (PLpgSQL_function *) fcinfo->flinfo->fn_extra;
+
+recheck:
+ if (!function)
+ {
+ /* Compute hashkey using function signature and actual arg types */
+ compute_function_hashkey(fcinfo, procStruct, &hashkey, forValidator);
+ hashkey_valid = true;
+
+ /* And do the lookup */
+ function = plpgsql_HashTableLookup(&hashkey);
+ }
+
+ if (function)
+ {
+ /* We have a compiled function, but is it still valid? */
+ if (function->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&function->fn_tid, &procTup->t_self))
+ function_valid = true;
+ else
+ {
+ /*
+ * Nope, so remove it from hashtable and try to drop associated
+ * storage (if not done already).
+ */
+ delete_function(function);
+
+ /*
+ * If the function isn't in active use then we can overwrite the
+ * func struct with new data, allowing any other existing fn_extra
+ * pointers to make use of the new definition on their next use.
+ * If it is in use then just leave it alone and make a new one.
+ * (The active invocations will run to completion using the
+ * previous definition, and then the cache entry will just be
+ * leaked; doesn't seem worth adding code to clean it up, given
+ * what a corner case this is.)
+ *
+ * If we found the function struct via fn_extra then it's possible
+ * a replacement has already been made, so go back and recheck the
+ * hashtable.
+ */
+ if (function->use_count != 0)
+ {
+ function = NULL;
+ if (!hashkey_valid)
+ goto recheck;
+ }
+ }
+ }
+
+ /*
+ * If the function wasn't found or was out-of-date, we have to compile it
+ */
+ if (!function_valid)
+ {
+ /*
+ * Calculate hashkey if we didn't already; we'll need it to store the
+ * completed function.
+ */
+ if (!hashkey_valid)
+ compute_function_hashkey(fcinfo, procStruct, &hashkey,
+ forValidator);
+
+ /*
+ * Do the hard part.
+ */
+ function = do_compile(fcinfo, procTup, function,
+ &hashkey, forValidator);
+ }
+
+ ReleaseSysCache(procTup);
+
+ /*
+ * Save pointer in FmgrInfo to avoid search on subsequent calls
+ */
+ fcinfo->flinfo->fn_extra = (void *) function;
+
+ /*
+ * Finally return the compiled function
+ */
+ return function;
+}
+
+/*
+ * This is the slow part of plpgsql_compile().
+ *
+ * The passed-in "function" pointer is either NULL or an already-allocated
+ * function struct to overwrite.
+ *
+ * While compiling a function, the CurrentMemoryContext is the
+ * per-function memory context of the function we are compiling. That
+ * means a palloc() will allocate storage with the same lifetime as
+ * the function itself.
+ *
+ * Because palloc()'d storage will not be immediately freed, temporary
+ * allocations should either be performed in a short-lived memory
+ * context or explicitly pfree'd. Since not all backend functions are
+ * careful about pfree'ing their allocations, it is also wise to
+ * switch into a short-term context before calling into the
+ * backend. An appropriate context for performing short-term
+ * allocations is the plpgsql_compile_tmp_cxt.
+ *
+ * NB: this code is not re-entrant. We assume that nothing we do here could
+ * result in the invocation of another plpgsql function.
+ */
+static PLpgSQL_function *
+do_compile(FunctionCallInfo fcinfo,
+ HeapTuple procTup,
+ PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator)
+{
+ Form_pg_proc procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ bool is_dml_trigger = CALLED_AS_TRIGGER(fcinfo);
+ bool is_event_trigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
+ Datum prosrcdatum;
+ bool isnull;
+ char *proc_source;
+ HeapTuple typeTup;
+ Form_pg_type typeStruct;
+ PLpgSQL_variable *var;
+ PLpgSQL_rec *rec;
+ int i;
+ ErrorContextCallback plerrcontext;
+ int parse_rc;
+ Oid rettypeid;
+ int numargs;
+ int num_in_args = 0;
+ int num_out_args = 0;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int *in_arg_varnos = NULL;
+ PLpgSQL_variable **out_arg_variables;
+ MemoryContext func_cxt;
+
+ /*
+ * Setup the scanner input and error info. We assume that this function
+ * cannot be invoked recursively, so there's no need to save and restore
+ * the static variables used here.
+ */
+ prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+ proc_source = TextDatumGetCString(prosrcdatum);
+ plpgsql_scanner_init(proc_source);
+
+ plpgsql_error_funcname = pstrdup(NameStr(procStruct->proname));
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_compile_error_callback;
+ plerrcontext.arg = forValidator ? proc_source : NULL;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Do extra syntax checks when validating the function definition. We skip
+ * this when actually compiling functions for execution, for performance
+ * reasons.
+ */
+ plpgsql_check_syntax = forValidator;
+
+ /*
+ * Create the new function struct, if not done already. The function
+ * structs are never thrown away, so keep them in TopMemoryContext.
+ */
+ if (function == NULL)
+ {
+ function = (PLpgSQL_function *)
+ MemoryContextAllocZero(TopMemoryContext, sizeof(PLpgSQL_function));
+ }
+ else
+ {
+ /* re-using a previously existing struct, so clear it out */
+ memset(function, 0, sizeof(PLpgSQL_function));
+ }
+ plpgsql_curr_compile = function;
+
+ /*
+ * All the permanent output of compilation (e.g. parse tree) is kept in a
+ * per-function memory context, so it can be reclaimed easily.
+ */
+ func_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/pgSQL function",
+ ALLOCSET_DEFAULT_SIZES);
+ plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
+
+ function->fn_signature = format_procedure(fcinfo->flinfo->fn_oid);
+ MemoryContextSetIdentifier(func_cxt, function->fn_signature);
+ function->fn_oid = fcinfo->flinfo->fn_oid;
+ function->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ function->fn_tid = procTup->t_self;
+ function->fn_input_collation = fcinfo->fncollation;
+ function->fn_cxt = func_cxt;
+ function->out_param_varno = -1; /* set up for no OUT param */
+ function->resolve_option = plpgsql_variable_conflict;
+ function->print_strict_params = plpgsql_print_strict_params;
+ /* only promote extra warnings and errors at CREATE FUNCTION time */
+ function->extra_warnings = forValidator ? plpgsql_extra_warnings : 0;
+ function->extra_errors = forValidator ? plpgsql_extra_errors : 0;
+
+ if (is_dml_trigger)
+ function->fn_is_trigger = PLPGSQL_DML_TRIGGER;
+ else if (is_event_trigger)
+ function->fn_is_trigger = PLPGSQL_EVENT_TRIGGER;
+ else
+ function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
+
+ function->fn_prokind = procStruct->prokind;
+
+ function->nstatements = 0;
+ function->requires_procedure_resowner = false;
+
+ /*
+ * Initialize the compiler, particularly the namespace stack. The
+ * outermost namespace contains function parameters and other special
+ * variables (such as FOUND), and is named after the function itself.
+ */
+ plpgsql_ns_init();
+ plpgsql_ns_push(NameStr(procStruct->proname), PLPGSQL_LABEL_BLOCK);
+ plpgsql_DumpExecTree = false;
+ plpgsql_start_datums();
+
+ switch (function->fn_is_trigger)
+ {
+ case PLPGSQL_NOT_TRIGGER:
+
+ /*
+ * Fetch info about the procedure's parameters. Allocations aren't
+ * needed permanently, so make them in tmp cxt.
+ *
+ * We also need to resolve any polymorphic input or output
+ * argument types. In validation mode we won't be able to, so we
+ * arbitrarily assume we are dealing with integers.
+ */
+ MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+
+ numargs = get_func_arg_info(procTup,
+ &argtypes, &argnames, &argmodes);
+
+ plpgsql_resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+ fcinfo->flinfo->fn_expr,
+ forValidator,
+ plpgsql_error_funcname);
+
+ in_arg_varnos = (int *) palloc(numargs * sizeof(int));
+ out_arg_variables = (PLpgSQL_variable **) palloc(numargs * sizeof(PLpgSQL_variable *));
+
+ MemoryContextSwitchTo(func_cxt);
+
+ /*
+ * Create the variables for the procedure's parameters.
+ */
+ for (i = 0; i < numargs; i++)
+ {
+ char buf[32];
+ Oid argtypeid = argtypes[i];
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+ PLpgSQL_type *argdtype;
+ PLpgSQL_variable *argvariable;
+ PLpgSQL_nsitem_type argitemtype;
+
+ /* Create $n name for variable */
+ snprintf(buf, sizeof(buf), "$%d", i + 1);
+
+ /* Create datatype info */
+ argdtype = plpgsql_build_datatype(argtypeid,
+ -1,
+ function->fn_input_collation,
+ NULL);
+
+ /* Disallow pseudotype argument */
+ /* (note we already replaced polymorphic types) */
+ /* (build_variable would do this, but wrong message) */
+ if (argdtype->ttype == PLPGSQL_TTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot accept type %s",
+ format_type_be(argtypeid))));
+
+ /*
+ * Build variable and add to datum list. If there's a name
+ * for the argument, use that as refname, else use $n name.
+ */
+ argvariable = plpgsql_build_variable((argnames &&
+ argnames[i][0] != '\0') ?
+ argnames[i] : buf,
+ 0, argdtype, false);
+
+ if (argvariable->dtype == PLPGSQL_DTYPE_VAR)
+ {
+ argitemtype = PLPGSQL_NSTYPE_VAR;
+ }
+ else
+ {
+ Assert(argvariable->dtype == PLPGSQL_DTYPE_REC);
+ argitemtype = PLPGSQL_NSTYPE_REC;
+ }
+
+ /* Remember arguments in appropriate arrays */
+ if (argmode == PROARGMODE_IN ||
+ argmode == PROARGMODE_INOUT ||
+ argmode == PROARGMODE_VARIADIC)
+ in_arg_varnos[num_in_args++] = argvariable->dno;
+ if (argmode == PROARGMODE_OUT ||
+ argmode == PROARGMODE_INOUT ||
+ argmode == PROARGMODE_TABLE)
+ out_arg_variables[num_out_args++] = argvariable;
+
+ /* Add to namespace under the $n name */
+ add_parameter_name(argitemtype, argvariable->dno, buf);
+
+ /* If there's a name for the argument, make an alias */
+ if (argnames && argnames[i][0] != '\0')
+ add_parameter_name(argitemtype, argvariable->dno,
+ argnames[i]);
+ }
+
+ /*
+ * If there's just one OUT parameter, out_param_varno points
+ * directly to it. If there's more than one, build a row that
+ * holds all of them. Procedures return a row even for one OUT
+ * parameter.
+ */
+ if (num_out_args > 1 ||
+ (num_out_args == 1 && function->fn_prokind == PROKIND_PROCEDURE))
+ {
+ PLpgSQL_row *row = build_row_from_vars(out_arg_variables,
+ num_out_args);
+
+ plpgsql_adddatum((PLpgSQL_datum *) row);
+ function->out_param_varno = row->dno;
+ }
+ else if (num_out_args == 1)
+ function->out_param_varno = out_arg_variables[0]->dno;
+
+ /*
+ * Check for a polymorphic returntype. If found, use the actual
+ * returntype type from the caller's FuncExpr node, if we have
+ * one. (In validation mode we arbitrarily assume we are dealing
+ * with integers.)
+ *
+ * Note: errcode is FEATURE_NOT_SUPPORTED because it should always
+ * work; if it doesn't we're in some context that fails to make
+ * the info available.
+ */
+ rettypeid = procStruct->prorettype;
+ if (IsPolymorphicType(rettypeid))
+ {
+ if (forValidator)
+ {
+ if (rettypeid == ANYARRAYOID ||
+ rettypeid == ANYCOMPATIBLEARRAYOID)
+ rettypeid = INT4ARRAYOID;
+ else if (rettypeid == ANYRANGEOID ||
+ rettypeid == ANYCOMPATIBLERANGEOID)
+ rettypeid = INT4RANGEOID;
+ else if (rettypeid == ANYMULTIRANGEOID)
+ rettypeid = INT4MULTIRANGEOID;
+ else /* ANYELEMENT or ANYNONARRAY or ANYCOMPATIBLE */
+ rettypeid = INT4OID;
+ /* XXX what could we use for ANYENUM? */
+ }
+ else
+ {
+ rettypeid = get_fn_expr_rettype(fcinfo->flinfo);
+ if (!OidIsValid(rettypeid))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not determine actual return type "
+ "for polymorphic function \"%s\"",
+ plpgsql_error_funcname)));
+ }
+ }
+
+ /*
+ * Normal function has a defined returntype
+ */
+ function->fn_rettype = rettypeid;
+ function->fn_retset = procStruct->proretset;
+
+ /*
+ * Lookup the function's return type
+ */
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettypeid));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettypeid);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ /* Disallow pseudotype result, except VOID or RECORD */
+ /* (note we already replaced polymorphic types) */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO)
+ {
+ if (rettypeid == VOIDOID ||
+ rettypeid == RECORDOID)
+ /* okay */ ;
+ else if (rettypeid == TRIGGEROID || rettypeid == EVENT_TRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot return type %s",
+ format_type_be(rettypeid))));
+ }
+
+ function->fn_retistuple = type_is_rowtype(rettypeid);
+ function->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
+ function->fn_retbyval = typeStruct->typbyval;
+ function->fn_rettyplen = typeStruct->typlen;
+
+ /*
+ * install $0 reference, but only for polymorphic return types,
+ * and not when the return is specified through an output
+ * parameter.
+ */
+ if (IsPolymorphicType(procStruct->prorettype) &&
+ num_out_args == 0)
+ {
+ (void) plpgsql_build_variable("$0", 0,
+ build_datatype(typeTup,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ }
+
+ ReleaseSysCache(typeTup);
+ break;
+
+ case PLPGSQL_DML_TRIGGER:
+ /* Trigger procedure's return type is unknown yet */
+ function->fn_rettype = InvalidOid;
+ function->fn_retbyval = false;
+ function->fn_retistuple = true;
+ function->fn_retisdomain = false;
+ function->fn_retset = false;
+
+ /* shouldn't be any declared arguments */
+ if (procStruct->pronargs != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("trigger functions cannot have declared arguments"),
+ errhint("The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead.")));
+
+ /* Add the record for referencing NEW ROW */
+ rec = plpgsql_build_record("new", 0, NULL, RECORDOID, true);
+ function->new_varno = rec->dno;
+
+ /* Add the record for referencing OLD ROW */
+ rec = plpgsql_build_record("old", 0, NULL, RECORDOID, true);
+ function->old_varno = rec->dno;
+
+ /* Add the variable tg_name */
+ var = plpgsql_build_variable("tg_name", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NAME;
+
+ /* Add the variable tg_when */
+ var = plpgsql_build_variable("tg_when", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_WHEN;
+
+ /* Add the variable tg_level */
+ var = plpgsql_build_variable("tg_level", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_LEVEL;
+
+ /* Add the variable tg_op */
+ var = plpgsql_build_variable("tg_op", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_OP;
+
+ /* Add the variable tg_relid */
+ var = plpgsql_build_variable("tg_relid", 0,
+ plpgsql_build_datatype(OIDOID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_RELID;
+
+ /* Add the variable tg_relname */
+ var = plpgsql_build_variable("tg_relname", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
+
+ /* tg_table_name is now preferred to tg_relname */
+ var = plpgsql_build_variable("tg_table_name", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_NAME;
+
+ /* add the variable tg_table_schema */
+ var = plpgsql_build_variable("tg_table_schema", 0,
+ plpgsql_build_datatype(NAMEOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TABLE_SCHEMA;
+
+ /* Add the variable tg_nargs */
+ var = plpgsql_build_variable("tg_nargs", 0,
+ plpgsql_build_datatype(INT4OID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_NARGS;
+
+ /* Add the variable tg_argv */
+ var = plpgsql_build_variable("tg_argv", 0,
+ plpgsql_build_datatype(TEXTARRAYOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_ARGV;
+
+ break;
+
+ case PLPGSQL_EVENT_TRIGGER:
+ function->fn_rettype = VOIDOID;
+ function->fn_retbyval = false;
+ function->fn_retistuple = true;
+ function->fn_retisdomain = false;
+ function->fn_retset = false;
+
+ /* shouldn't be any declared arguments */
+ if (procStruct->pronargs != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("event trigger functions cannot have declared arguments")));
+
+ /* Add the variable tg_event */
+ var = plpgsql_build_variable("tg_event", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_EVENT;
+
+ /* Add the variable tg_tag */
+ var = plpgsql_build_variable("tg_tag", 0,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ function->fn_input_collation,
+ NULL),
+ true);
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+ var->dtype = PLPGSQL_DTYPE_PROMISE;
+ ((PLpgSQL_var *) var)->promise = PLPGSQL_PROMISE_TG_TAG;
+
+ break;
+
+ default:
+ elog(ERROR, "unrecognized function typecode: %d",
+ (int) function->fn_is_trigger);
+ break;
+ }
+
+ /* Remember if function is STABLE/IMMUTABLE */
+ function->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+
+ /*
+ * Create the magic FOUND variable.
+ */
+ var = plpgsql_build_variable("found", 0,
+ plpgsql_build_datatype(BOOLOID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ function->found_varno = var->dno;
+
+ /*
+ * Now parse the function's text
+ */
+ parse_rc = plpgsql_yyparse();
+ if (parse_rc != 0)
+ elog(ERROR, "plpgsql parser returned %d", parse_rc);
+ function->action = plpgsql_parse_result;
+
+ plpgsql_scanner_finish();
+ pfree(proc_source);
+
+ /*
+ * If it has OUT parameters or returns VOID or returns a set, we allow
+ * control to fall off the end without an explicit RETURN statement. The
+ * easiest way to implement this is to add a RETURN statement to the end
+ * of the statement list during parsing.
+ */
+ if (num_out_args > 0 || function->fn_rettype == VOIDOID ||
+ function->fn_retset)
+ add_dummy_return(function);
+
+ /*
+ * Complete the function's info
+ */
+ function->fn_nargs = procStruct->pronargs;
+ for (i = 0; i < function->fn_nargs; i++)
+ function->fn_argvarnos[i] = in_arg_varnos[i];
+
+ plpgsql_finish_datums(function);
+
+ /* Debug dump for completed functions */
+ if (plpgsql_DumpExecTree)
+ plpgsql_dumptree(function);
+
+ /*
+ * add it to the hash table
+ */
+ plpgsql_HashTableInsert(function, hashkey);
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+ plpgsql_error_funcname = NULL;
+
+ plpgsql_check_syntax = false;
+
+ MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+ plpgsql_compile_tmp_cxt = NULL;
+ return function;
+}
+
+/* ----------
+ * plpgsql_compile_inline Make an execution tree for an anonymous code block.
+ *
+ * Note: this is generally parallel to do_compile(); is it worth trying to
+ * merge the two?
+ *
+ * Note: we assume the block will be thrown away so there is no need to build
+ * persistent data structures.
+ * ----------
+ */
+PLpgSQL_function *
+plpgsql_compile_inline(char *proc_source)
+{
+ char *func_name = "inline_code_block";
+ PLpgSQL_function *function;
+ ErrorContextCallback plerrcontext;
+ PLpgSQL_variable *var;
+ int parse_rc;
+ MemoryContext func_cxt;
+
+ /*
+ * Setup the scanner input and error info. We assume that this function
+ * cannot be invoked recursively, so there's no need to save and restore
+ * the static variables used here.
+ */
+ plpgsql_scanner_init(proc_source);
+
+ plpgsql_error_funcname = func_name;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_compile_error_callback;
+ plerrcontext.arg = proc_source;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /* Do extra syntax checking if check_function_bodies is on */
+ plpgsql_check_syntax = check_function_bodies;
+
+ /* Function struct does not live past current statement */
+ function = (PLpgSQL_function *) palloc0(sizeof(PLpgSQL_function));
+
+ plpgsql_curr_compile = function;
+
+ /*
+ * All the rest of the compile-time storage (e.g. parse tree) is kept in
+ * its own memory context, so it can be reclaimed easily.
+ */
+ func_cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "PL/pgSQL inline code context",
+ ALLOCSET_DEFAULT_SIZES);
+ plpgsql_compile_tmp_cxt = MemoryContextSwitchTo(func_cxt);
+
+ function->fn_signature = pstrdup(func_name);
+ function->fn_is_trigger = PLPGSQL_NOT_TRIGGER;
+ function->fn_input_collation = InvalidOid;
+ function->fn_cxt = func_cxt;
+ function->out_param_varno = -1; /* set up for no OUT param */
+ function->resolve_option = plpgsql_variable_conflict;
+ function->print_strict_params = plpgsql_print_strict_params;
+
+ /*
+ * don't do extra validation for inline code as we don't want to add spam
+ * at runtime
+ */
+ function->extra_warnings = 0;
+ function->extra_errors = 0;
+
+ function->nstatements = 0;
+ function->requires_procedure_resowner = false;
+
+ plpgsql_ns_init();
+ plpgsql_ns_push(func_name, PLPGSQL_LABEL_BLOCK);
+ plpgsql_DumpExecTree = false;
+ plpgsql_start_datums();
+
+ /* Set up as though in a function returning VOID */
+ function->fn_rettype = VOIDOID;
+ function->fn_retset = false;
+ function->fn_retistuple = false;
+ function->fn_retisdomain = false;
+ function->fn_prokind = PROKIND_FUNCTION;
+ /* a bit of hardwired knowledge about type VOID here */
+ function->fn_retbyval = true;
+ function->fn_rettyplen = sizeof(int32);
+
+ /*
+ * Remember if function is STABLE/IMMUTABLE. XXX would it be better to
+ * set this true inside a read-only transaction? Not clear.
+ */
+ function->fn_readonly = false;
+
+ /*
+ * Create the magic FOUND variable.
+ */
+ var = plpgsql_build_variable("found", 0,
+ plpgsql_build_datatype(BOOLOID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ function->found_varno = var->dno;
+
+ /*
+ * Now parse the function's text
+ */
+ parse_rc = plpgsql_yyparse();
+ if (parse_rc != 0)
+ elog(ERROR, "plpgsql parser returned %d", parse_rc);
+ function->action = plpgsql_parse_result;
+
+ plpgsql_scanner_finish();
+
+ /*
+ * If it returns VOID (always true at the moment), we allow control to
+ * fall off the end without an explicit RETURN statement.
+ */
+ if (function->fn_rettype == VOIDOID)
+ add_dummy_return(function);
+
+ /*
+ * Complete the function's info
+ */
+ function->fn_nargs = 0;
+
+ plpgsql_finish_datums(function);
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+ plpgsql_error_funcname = NULL;
+
+ plpgsql_check_syntax = false;
+
+ MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+ plpgsql_compile_tmp_cxt = NULL;
+ return function;
+}
+
+
+/*
+ * error context callback to let us supply a call-stack traceback.
+ * If we are validating or executing an anonymous code block, the function
+ * source text is passed as an argument.
+ */
+static void
+plpgsql_compile_error_callback(void *arg)
+{
+ if (arg)
+ {
+ /*
+ * Try to convert syntax error position to reference text of original
+ * CREATE FUNCTION or DO command.
+ */
+ if (function_parse_error_transpose((const char *) arg))
+ return;
+
+ /*
+ * Done if a syntax error position was reported; otherwise we have to
+ * fall back to a "near line N" report.
+ */
+ }
+
+ if (plpgsql_error_funcname)
+ errcontext("compilation of PL/pgSQL function \"%s\" near line %d",
+ plpgsql_error_funcname, plpgsql_latest_lineno());
+}
+
+
+/*
+ * Add a name for a function parameter to the function's namespace
+ */
+static void
+add_parameter_name(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
+{
+ /*
+ * Before adding the name, check for duplicates. We need this even though
+ * functioncmds.c has a similar check, because that code explicitly
+ * doesn't complain about conflicting IN and OUT parameter names. In
+ * plpgsql, such names are in the same namespace, so there is no way to
+ * disambiguate.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ name, NULL, NULL,
+ NULL) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("parameter name \"%s\" used more than once",
+ name)));
+
+ /* OK, add the name */
+ plpgsql_ns_additem(itemtype, itemno, name);
+}
+
+/*
+ * Add a dummy RETURN statement to the given function's body
+ */
+static void
+add_dummy_return(PLpgSQL_function *function)
+{
+ /*
+ * If the outer block has an EXCEPTION clause, we need to make a new outer
+ * block, since the added RETURN shouldn't act like it is inside the
+ * EXCEPTION clause. Likewise, if it has a label, wrap it in a new outer
+ * block so that EXIT doesn't skip the RETURN.
+ */
+ if (function->action->exceptions != NULL ||
+ function->action->label != NULL)
+ {
+ PLpgSQL_stmt_block *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_block));
+ new->cmd_type = PLPGSQL_STMT_BLOCK;
+ new->stmtid = ++function->nstatements;
+ new->body = list_make1(function->action);
+
+ function->action = new;
+ }
+ if (function->action->body == NIL ||
+ ((PLpgSQL_stmt *) llast(function->action->body))->cmd_type != PLPGSQL_STMT_RETURN)
+ {
+ PLpgSQL_stmt_return *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return));
+ new->cmd_type = PLPGSQL_STMT_RETURN;
+ new->stmtid = ++function->nstatements;
+ new->expr = NULL;
+ new->retvarno = function->out_param_varno;
+
+ function->action->body = lappend(function->action->body, new);
+ }
+}
+
+
+/*
+ * plpgsql_parser_setup set up parser hooks for dynamic parameters
+ *
+ * Note: this routine, and the hook functions it prepares for, are logically
+ * part of plpgsql parsing. But they actually run during function execution,
+ * when we are ready to evaluate a SQL query or expression that has not
+ * previously been parsed and planned.
+ */
+void
+plpgsql_parser_setup(struct ParseState *pstate, PLpgSQL_expr *expr)
+{
+ pstate->p_pre_columnref_hook = plpgsql_pre_column_ref;
+ pstate->p_post_columnref_hook = plpgsql_post_column_ref;
+ pstate->p_paramref_hook = plpgsql_param_ref;
+ /* no need to use p_coerce_param_hook */
+ pstate->p_ref_hook_state = (void *) expr;
+}
+
+/*
+ * plpgsql_pre_column_ref parser callback before parsing a ColumnRef
+ */
+static Node *
+plpgsql_pre_column_ref(ParseState *pstate, ColumnRef *cref)
+{
+ PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
+
+ if (expr->func->resolve_option == PLPGSQL_RESOLVE_VARIABLE)
+ return resolve_column_ref(pstate, expr, cref, false);
+ else
+ return NULL;
+}
+
+/*
+ * plpgsql_post_column_ref parser callback after parsing a ColumnRef
+ */
+static Node *
+plpgsql_post_column_ref(ParseState *pstate, ColumnRef *cref, Node *var)
+{
+ PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
+ Node *myvar;
+
+ if (expr->func->resolve_option == PLPGSQL_RESOLVE_VARIABLE)
+ return NULL; /* we already found there's no match */
+
+ if (expr->func->resolve_option == PLPGSQL_RESOLVE_COLUMN && var != NULL)
+ return NULL; /* there's a table column, prefer that */
+
+ /*
+ * If we find a record/row variable but can't match a field name, throw
+ * error if there was no core resolution for the ColumnRef either. In
+ * that situation, the reference is inevitably going to fail, and
+ * complaining about the record/row variable is likely to be more on-point
+ * than the core parser's error message. (It's too bad we don't have
+ * access to transformColumnRef's internal crerr state here, as in case of
+ * a conflict with a table name this could still be less than the most
+ * helpful error message possible.)
+ */
+ myvar = resolve_column_ref(pstate, expr, cref, (var == NULL));
+
+ if (myvar != NULL && var != NULL)
+ {
+ /*
+ * We could leave it to the core parser to throw this error, but we
+ * can add a more useful detail message than the core could.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_AMBIGUOUS_COLUMN),
+ errmsg("column reference \"%s\" is ambiguous",
+ NameListToString(cref->fields)),
+ errdetail("It could refer to either a PL/pgSQL variable or a table column."),
+ parser_errposition(pstate, cref->location)));
+ }
+
+ return myvar;
+}
+
+/*
+ * plpgsql_param_ref parser callback for ParamRefs ($n symbols)
+ */
+static Node *
+plpgsql_param_ref(ParseState *pstate, ParamRef *pref)
+{
+ PLpgSQL_expr *expr = (PLpgSQL_expr *) pstate->p_ref_hook_state;
+ char pname[32];
+ PLpgSQL_nsitem *nse;
+
+ snprintf(pname, sizeof(pname), "$%d", pref->number);
+
+ nse = plpgsql_ns_lookup(expr->ns, false,
+ pname, NULL, NULL,
+ NULL);
+
+ if (nse == NULL)
+ return NULL; /* name not known to plpgsql */
+
+ return make_datum_param(expr, nse->itemno, pref->location);
+}
+
+/*
+ * resolve_column_ref attempt to resolve a ColumnRef as a plpgsql var
+ *
+ * Returns the translated node structure, or NULL if name not found
+ *
+ * error_if_no_field tells whether to throw error or quietly return NULL if
+ * we are able to match a record/row name but don't find a field name match.
+ */
+static Node *
+resolve_column_ref(ParseState *pstate, PLpgSQL_expr *expr,
+ ColumnRef *cref, bool error_if_no_field)
+{
+ PLpgSQL_execstate *estate;
+ PLpgSQL_nsitem *nse;
+ const char *name1;
+ const char *name2 = NULL;
+ const char *name3 = NULL;
+ const char *colname = NULL;
+ int nnames;
+ int nnames_scalar = 0;
+ int nnames_wholerow = 0;
+ int nnames_field = 0;
+
+ /*
+ * We use the function's current estate to resolve parameter data types.
+ * This is really pretty bogus because there is no provision for updating
+ * plans when those types change ...
+ */
+ estate = expr->func->cur_estate;
+
+ /*----------
+ * The allowed syntaxes are:
+ *
+ * A Scalar variable reference, or whole-row record reference.
+ * A.B Qualified scalar or whole-row reference, or field reference.
+ * A.B.C Qualified record field reference.
+ * A.* Whole-row record reference.
+ * A.B.* Qualified whole-row record reference.
+ *----------
+ */
+ switch (list_length(cref->fields))
+ {
+ case 1:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+
+ Assert(IsA(field1, String));
+ name1 = strVal(field1);
+ nnames_scalar = 1;
+ nnames_wholerow = 1;
+ break;
+ }
+ case 2:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+ Node *field2 = (Node *) lsecond(cref->fields);
+
+ Assert(IsA(field1, String));
+ name1 = strVal(field1);
+
+ /* Whole-row reference? */
+ if (IsA(field2, A_Star))
+ {
+ /* Set name2 to prevent matches to scalar variables */
+ name2 = "*";
+ nnames_wholerow = 1;
+ break;
+ }
+
+ Assert(IsA(field2, String));
+ name2 = strVal(field2);
+ colname = name2;
+ nnames_scalar = 2;
+ nnames_wholerow = 2;
+ nnames_field = 1;
+ break;
+ }
+ case 3:
+ {
+ Node *field1 = (Node *) linitial(cref->fields);
+ Node *field2 = (Node *) lsecond(cref->fields);
+ Node *field3 = (Node *) lthird(cref->fields);
+
+ Assert(IsA(field1, String));
+ name1 = strVal(field1);
+ Assert(IsA(field2, String));
+ name2 = strVal(field2);
+
+ /* Whole-row reference? */
+ if (IsA(field3, A_Star))
+ {
+ /* Set name3 to prevent matches to scalar variables */
+ name3 = "*";
+ nnames_wholerow = 2;
+ break;
+ }
+
+ Assert(IsA(field3, String));
+ name3 = strVal(field3);
+ colname = name3;
+ nnames_field = 2;
+ break;
+ }
+ default:
+ /* too many names, ignore */
+ return NULL;
+ }
+
+ nse = plpgsql_ns_lookup(expr->ns, false,
+ name1, name2, name3,
+ &nnames);
+
+ if (nse == NULL)
+ return NULL; /* name not known to plpgsql */
+
+ switch (nse->itemtype)
+ {
+ case PLPGSQL_NSTYPE_VAR:
+ if (nnames == nnames_scalar)
+ return make_datum_param(expr, nse->itemno, cref->location);
+ break;
+ case PLPGSQL_NSTYPE_REC:
+ if (nnames == nnames_wholerow)
+ return make_datum_param(expr, nse->itemno, cref->location);
+ if (nnames == nnames_field)
+ {
+ /* colname could be a field in this record */
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate->datums[nse->itemno];
+ int i;
+
+ /* search for a datum referencing this field */
+ i = rec->firstfield;
+ while (i >= 0)
+ {
+ PLpgSQL_recfield *fld = (PLpgSQL_recfield *) estate->datums[i];
+
+ Assert(fld->dtype == PLPGSQL_DTYPE_RECFIELD &&
+ fld->recparentno == nse->itemno);
+ if (strcmp(fld->fieldname, colname) == 0)
+ {
+ return make_datum_param(expr, i, cref->location);
+ }
+ i = fld->nextfield;
+ }
+
+ /*
+ * We should not get here, because a RECFIELD datum should
+ * have been built at parse time for every possible qualified
+ * reference to fields of this record. But if we do, handle
+ * it like field-not-found: throw error or return NULL.
+ */
+ if (error_if_no_field)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ (nnames_field == 1) ? name1 : name2,
+ colname),
+ parser_errposition(pstate, cref->location)));
+ }
+ break;
+ default:
+ elog(ERROR, "unrecognized plpgsql itemtype: %d", nse->itemtype);
+ }
+
+ /* Name format doesn't match the plpgsql variable type */
+ return NULL;
+}
+
+/*
+ * Helper for columnref parsing: build a Param referencing a plpgsql datum,
+ * and make sure that that datum is listed in the expression's paramnos.
+ */
+static Node *
+make_datum_param(PLpgSQL_expr *expr, int dno, int location)
+{
+ PLpgSQL_execstate *estate;
+ PLpgSQL_datum *datum;
+ Param *param;
+ MemoryContext oldcontext;
+
+ /* see comment in resolve_column_ref */
+ estate = expr->func->cur_estate;
+ Assert(dno >= 0 && dno < estate->ndatums);
+ datum = estate->datums[dno];
+
+ /*
+ * Bitmapset must be allocated in function's permanent memory context
+ */
+ oldcontext = MemoryContextSwitchTo(expr->func->fn_cxt);
+ expr->paramnos = bms_add_member(expr->paramnos, dno);
+ MemoryContextSwitchTo(oldcontext);
+
+ param = makeNode(Param);
+ param->paramkind = PARAM_EXTERN;
+ param->paramid = dno + 1;
+ plpgsql_exec_get_datum_type_info(estate,
+ datum,
+ &param->paramtype,
+ &param->paramtypmod,
+ &param->paramcollid);
+ param->location = location;
+
+ return (Node *) param;
+}
+
+
+/* ----------
+ * plpgsql_parse_word The scanner calls this to postparse
+ * any single word that is not a reserved keyword.
+ *
+ * word1 is the downcased/dequoted identifier; it must be palloc'd in the
+ * function's long-term memory context.
+ *
+ * yytxt is the original token text; we need this to check for quoting,
+ * so that later checks for unreserved keywords work properly.
+ *
+ * We attempt to recognize the token as a variable only if lookup is true
+ * and the plpgsql_IdentifierLookup context permits it.
+ *
+ * If recognized as a variable, fill in *wdatum and return true;
+ * if not recognized, fill in *word and return false.
+ * (Note: those two pointers actually point to members of the same union,
+ * but for notational reasons we pass them separately.)
+ * ----------
+ */
+bool
+plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
+ PLwdatum *wdatum, PLword *word)
+{
+ PLpgSQL_nsitem *ns;
+
+ /*
+ * We should not lookup variables in DECLARE sections. In SQL
+ * expressions, there's no need to do so either --- lookup will happen
+ * when the expression is compiled.
+ */
+ if (lookup && plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_NORMAL)
+ {
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ word1, NULL, NULL,
+ NULL);
+
+ if (ns != NULL)
+ {
+ switch (ns->itemtype)
+ {
+ case PLPGSQL_NSTYPE_VAR:
+ case PLPGSQL_NSTYPE_REC:
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ wdatum->ident = word1;
+ wdatum->quoted = (yytxt[0] == '"');
+ wdatum->idents = NIL;
+ return true;
+
+ default:
+ /* plpgsql_ns_lookup should never return anything else */
+ elog(ERROR, "unrecognized plpgsql itemtype: %d",
+ ns->itemtype);
+ }
+ }
+ }
+
+ /*
+ * Nothing found - up to now it's a word without any special meaning for
+ * us.
+ */
+ word->ident = word1;
+ word->quoted = (yytxt[0] == '"');
+ return false;
+}
+
+
+/* ----------
+ * plpgsql_parse_dblword Same lookup for two words
+ * separated by a dot.
+ * ----------
+ */
+bool
+plpgsql_parse_dblword(char *word1, char *word2,
+ PLwdatum *wdatum, PLcword *cword)
+{
+ PLpgSQL_nsitem *ns;
+ List *idents;
+ int nnames;
+
+ idents = list_make2(makeString(word1),
+ makeString(word2));
+
+ /*
+ * We should do nothing in DECLARE sections. In SQL expressions, we
+ * really only need to make sure that RECFIELD datums are created when
+ * needed. In all the cases handled by this function, returning a T_DATUM
+ * with a two-word idents string is the right thing.
+ */
+ if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
+ {
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ word1, word2, NULL,
+ &nnames);
+ if (ns != NULL)
+ {
+ switch (ns->itemtype)
+ {
+ case PLPGSQL_NSTYPE_VAR:
+ /* Block-qualified reference to scalar variable. */
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
+
+ case PLPGSQL_NSTYPE_REC:
+ if (nnames == 1)
+ {
+ /*
+ * First word is a record name, so second word could
+ * be a field in this record. We build a RECFIELD
+ * datum whether it is or not --- any error will be
+ * detected later.
+ */
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *new;
+
+ rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
+ new = plpgsql_build_recfield(rec, word2);
+
+ wdatum->datum = (PLpgSQL_datum *) new;
+ }
+ else
+ {
+ /* Block-qualified reference to record variable. */
+ wdatum->datum = plpgsql_Datums[ns->itemno];
+ }
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Nothing found */
+ cword->idents = idents;
+ return false;
+}
+
+
+/* ----------
+ * plpgsql_parse_tripword Same lookup for three words
+ * separated by dots.
+ * ----------
+ */
+bool
+plpgsql_parse_tripword(char *word1, char *word2, char *word3,
+ PLwdatum *wdatum, PLcword *cword)
+{
+ PLpgSQL_nsitem *ns;
+ List *idents;
+ int nnames;
+
+ /*
+ * We should do nothing in DECLARE sections. In SQL expressions, we need
+ * to make sure that RECFIELD datums are created when needed, and we need
+ * to be careful about how many names are reported as belonging to the
+ * T_DATUM: the third word could be a sub-field reference, which we don't
+ * care about here.
+ */
+ if (plpgsql_IdentifierLookup != IDENTIFIER_LOOKUP_DECLARE)
+ {
+ /*
+ * Do a lookup in the current namespace stack. Must find a record
+ * reference, else ignore.
+ */
+ ns = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ word1, word2, word3,
+ &nnames);
+ if (ns != NULL)
+ {
+ switch (ns->itemtype)
+ {
+ case PLPGSQL_NSTYPE_REC:
+ {
+ PLpgSQL_rec *rec;
+ PLpgSQL_recfield *new;
+
+ rec = (PLpgSQL_rec *) (plpgsql_Datums[ns->itemno]);
+ if (nnames == 1)
+ {
+ /*
+ * First word is a record name, so second word
+ * could be a field in this record (and the third,
+ * a sub-field). We build a RECFIELD datum
+ * whether it is or not --- any error will be
+ * detected later.
+ */
+ new = plpgsql_build_recfield(rec, word2);
+ idents = list_make2(makeString(word1),
+ makeString(word2));
+ }
+ else
+ {
+ /* Block-qualified reference to record variable. */
+ new = plpgsql_build_recfield(rec, word3);
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
+ }
+ wdatum->datum = (PLpgSQL_datum *) new;
+ wdatum->ident = NULL;
+ wdatum->quoted = false; /* not used */
+ wdatum->idents = idents;
+ return true;
+ }
+
+ default:
+ break;
+ }
+ }
+ }
+
+ /* Nothing found */
+ idents = list_make3(makeString(word1),
+ makeString(word2),
+ makeString(word3));
+ cword->idents = idents;
+ return false;
+}
+
+
+/* ----------
+ * plpgsql_parse_wordtype The scanner found word%TYPE. word can be
+ * a variable name or a basetype.
+ *
+ * Returns datatype struct, or NULL if no match found for word.
+ * ----------
+ */
+PLpgSQL_type *
+plpgsql_parse_wordtype(char *ident)
+{
+ PLpgSQL_type *dtype;
+ PLpgSQL_nsitem *nse;
+ TypeName *typeName;
+ HeapTuple typeTup;
+
+ /*
+ * Do a lookup in the current namespace stack
+ */
+ nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ ident, NULL, NULL,
+ NULL);
+
+ if (nse != NULL)
+ {
+ switch (nse->itemtype)
+ {
+ case PLPGSQL_NSTYPE_VAR:
+ return ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
+
+ /* XXX perhaps allow REC/ROW here? */
+
+ default:
+ return NULL;
+ }
+ }
+
+ /*
+ * Word wasn't found in the namespace stack. Try to find a data type with
+ * that name, but ignore shell types and complex types.
+ */
+ typeName = makeTypeName(ident);
+ typeTup = LookupTypeName(NULL, typeName, NULL, false);
+ if (typeTup)
+ {
+ Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ if (!typeStruct->typisdefined ||
+ typeStruct->typrelid != InvalidOid)
+ {
+ ReleaseSysCache(typeTup);
+ return NULL;
+ }
+
+ dtype = build_datatype(typeTup, -1,
+ plpgsql_curr_compile->fn_input_collation,
+ typeName);
+
+ ReleaseSysCache(typeTup);
+ return dtype;
+ }
+
+ /*
+ * Nothing found - up to now it's a word without any special meaning for
+ * us.
+ */
+ return NULL;
+}
+
+
+/* ----------
+ * plpgsql_parse_cwordtype Same lookup for compositeword%TYPE
+ * ----------
+ */
+PLpgSQL_type *
+plpgsql_parse_cwordtype(List *idents)
+{
+ PLpgSQL_type *dtype = NULL;
+ PLpgSQL_nsitem *nse;
+ const char *fldname;
+ Oid classOid;
+ HeapTuple classtup = NULL;
+ HeapTuple attrtup = NULL;
+ HeapTuple typetup = NULL;
+ Form_pg_class classStruct;
+ Form_pg_attribute attrStruct;
+ MemoryContext oldCxt;
+
+ /* Avoid memory leaks in the long-term function context */
+ oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+
+ if (list_length(idents) == 2)
+ {
+ /*
+ * Do a lookup in the current namespace stack. We don't need to check
+ * number of names matched, because we will only consider scalar
+ * variables.
+ */
+ nse = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ strVal(linitial(idents)),
+ strVal(lsecond(idents)),
+ NULL,
+ NULL);
+
+ if (nse != NULL && nse->itemtype == PLPGSQL_NSTYPE_VAR)
+ {
+ dtype = ((PLpgSQL_var *) (plpgsql_Datums[nse->itemno]))->datatype;
+ goto done;
+ }
+
+ /*
+ * First word could also be a table name
+ */
+ classOid = RelnameGetRelid(strVal(linitial(idents)));
+ if (!OidIsValid(classOid))
+ goto done;
+ fldname = strVal(lsecond(idents));
+ }
+ else if (list_length(idents) == 3)
+ {
+ RangeVar *relvar;
+
+ relvar = makeRangeVar(strVal(linitial(idents)),
+ strVal(lsecond(idents)),
+ -1);
+ /* Can't lock relation - we might not have privileges. */
+ classOid = RangeVarGetRelid(relvar, NoLock, true);
+ if (!OidIsValid(classOid))
+ goto done;
+ fldname = strVal(lthird(idents));
+ }
+ else
+ goto done;
+
+ classtup = SearchSysCache1(RELOID, ObjectIdGetDatum(classOid));
+ if (!HeapTupleIsValid(classtup))
+ goto done;
+ classStruct = (Form_pg_class) GETSTRUCT(classtup);
+
+ /*
+ * It must be a relation, sequence, view, materialized view, composite
+ * type, or foreign table
+ */
+ if (classStruct->relkind != RELKIND_RELATION &&
+ classStruct->relkind != RELKIND_SEQUENCE &&
+ classStruct->relkind != RELKIND_VIEW &&
+ classStruct->relkind != RELKIND_MATVIEW &&
+ classStruct->relkind != RELKIND_COMPOSITE_TYPE &&
+ classStruct->relkind != RELKIND_FOREIGN_TABLE &&
+ classStruct->relkind != RELKIND_PARTITIONED_TABLE)
+ goto done;
+
+ /*
+ * Fetch the named table field and its type
+ */
+ attrtup = SearchSysCacheAttName(classOid, fldname);
+ if (!HeapTupleIsValid(attrtup))
+ goto done;
+ attrStruct = (Form_pg_attribute) GETSTRUCT(attrtup);
+
+ typetup = SearchSysCache1(TYPEOID,
+ ObjectIdGetDatum(attrStruct->atttypid));
+ if (!HeapTupleIsValid(typetup))
+ elog(ERROR, "cache lookup failed for type %u", attrStruct->atttypid);
+
+ /*
+ * Found that - build a compiler type struct in the caller's cxt and
+ * return it. Note that we treat the type as being found-by-OID; no
+ * attempt to re-look-up the type name will happen during invalidations.
+ */
+ MemoryContextSwitchTo(oldCxt);
+ dtype = build_datatype(typetup,
+ attrStruct->atttypmod,
+ attrStruct->attcollation,
+ NULL);
+ MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+
+done:
+ if (HeapTupleIsValid(classtup))
+ ReleaseSysCache(classtup);
+ if (HeapTupleIsValid(attrtup))
+ ReleaseSysCache(attrtup);
+ if (HeapTupleIsValid(typetup))
+ ReleaseSysCache(typetup);
+
+ MemoryContextSwitchTo(oldCxt);
+ return dtype;
+}
+
+/* ----------
+ * plpgsql_parse_wordrowtype Scanner found word%ROWTYPE.
+ * So word must be a table name.
+ * ----------
+ */
+PLpgSQL_type *
+plpgsql_parse_wordrowtype(char *ident)
+{
+ Oid classOid;
+ Oid typOid;
+
+ /*
+ * Look up the relation. Note that because relation rowtypes have the
+ * same names as their relations, this could be handled as a type lookup
+ * equally well; we use the relation lookup code path only because the
+ * errors thrown here have traditionally referred to relations not types.
+ * But we'll make a TypeName in case we have to do re-look-up of the type.
+ */
+ classOid = RelnameGetRelid(ident);
+ if (!OidIsValid(classOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_TABLE),
+ errmsg("relation \"%s\" does not exist", ident)));
+
+ /* Some relkinds lack type OIDs */
+ typOid = get_rel_type_id(classOid);
+ if (!OidIsValid(typOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("relation \"%s\" does not have a composite type",
+ ident)));
+
+ /* Build and return the row type struct */
+ return plpgsql_build_datatype(typOid, -1, InvalidOid,
+ makeTypeName(ident));
+}
+
+/* ----------
+ * plpgsql_parse_cwordrowtype Scanner found compositeword%ROWTYPE.
+ * So word must be a namespace qualified table name.
+ * ----------
+ */
+PLpgSQL_type *
+plpgsql_parse_cwordrowtype(List *idents)
+{
+ Oid classOid;
+ Oid typOid;
+ RangeVar *relvar;
+ MemoryContext oldCxt;
+
+ /*
+ * As above, this is a relation lookup but could be a type lookup if we
+ * weren't being backwards-compatible about error wording.
+ */
+ if (list_length(idents) != 2)
+ return NULL;
+
+ /* Avoid memory leaks in long-term function context */
+ oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+
+ /* Look up relation name. Can't lock it - we might not have privileges. */
+ relvar = makeRangeVar(strVal(linitial(idents)),
+ strVal(lsecond(idents)),
+ -1);
+ classOid = RangeVarGetRelid(relvar, NoLock, false);
+
+ /* Some relkinds lack type OIDs */
+ typOid = get_rel_type_id(classOid);
+ if (!OidIsValid(typOid))
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("relation \"%s\" does not have a composite type",
+ strVal(lsecond(idents)))));
+
+ MemoryContextSwitchTo(oldCxt);
+
+ /* Build and return the row type struct */
+ return plpgsql_build_datatype(typOid, -1, InvalidOid,
+ makeTypeNameFromNameList(idents));
+}
+
+/*
+ * plpgsql_build_variable - build a datum-array entry of a given
+ * datatype
+ *
+ * The returned struct may be a PLpgSQL_var or PLpgSQL_rec
+ * depending on the given datatype, and is allocated via
+ * palloc. The struct is automatically added to the current datum
+ * array, and optionally to the current namespace.
+ */
+PLpgSQL_variable *
+plpgsql_build_variable(const char *refname, int lineno, PLpgSQL_type *dtype,
+ bool add2namespace)
+{
+ PLpgSQL_variable *result;
+
+ switch (dtype->ttype)
+ {
+ case PLPGSQL_TTYPE_SCALAR:
+ {
+ /* Ordinary scalar datatype */
+ PLpgSQL_var *var;
+
+ var = palloc0(sizeof(PLpgSQL_var));
+ var->dtype = PLPGSQL_DTYPE_VAR;
+ var->refname = pstrdup(refname);
+ var->lineno = lineno;
+ var->datatype = dtype;
+ /* other fields are left as 0, might be changed by caller */
+
+ /* preset to NULL */
+ var->value = 0;
+ var->isnull = true;
+ var->freeval = false;
+
+ plpgsql_adddatum((PLpgSQL_datum *) var);
+ if (add2namespace)
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_VAR,
+ var->dno,
+ refname);
+ result = (PLpgSQL_variable *) var;
+ break;
+ }
+ case PLPGSQL_TTYPE_REC:
+ {
+ /* Composite type -- build a record variable */
+ PLpgSQL_rec *rec;
+
+ rec = plpgsql_build_record(refname, lineno,
+ dtype, dtype->typoid,
+ add2namespace);
+ result = (PLpgSQL_variable *) rec;
+ break;
+ }
+ case PLPGSQL_TTYPE_PSEUDO:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("variable \"%s\" has pseudo-type %s",
+ refname, format_type_be(dtype->typoid))));
+ result = NULL; /* keep compiler quiet */
+ break;
+ default:
+ elog(ERROR, "unrecognized ttype: %d", dtype->ttype);
+ result = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Build empty named record variable, and optionally add it to namespace
+ */
+PLpgSQL_rec *
+plpgsql_build_record(const char *refname, int lineno,
+ PLpgSQL_type *dtype, Oid rectypeid,
+ bool add2namespace)
+{
+ PLpgSQL_rec *rec;
+
+ rec = palloc0(sizeof(PLpgSQL_rec));
+ rec->dtype = PLPGSQL_DTYPE_REC;
+ rec->refname = pstrdup(refname);
+ rec->lineno = lineno;
+ /* other fields are left as 0, might be changed by caller */
+ rec->datatype = dtype;
+ rec->rectypeid = rectypeid;
+ rec->firstfield = -1;
+ rec->erh = NULL;
+ plpgsql_adddatum((PLpgSQL_datum *) rec);
+ if (add2namespace)
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_REC, rec->dno, rec->refname);
+
+ return rec;
+}
+
+/*
+ * Build a row-variable data structure given the component variables.
+ * Include a rowtupdesc, since we will need to materialize the row result.
+ */
+static PLpgSQL_row *
+build_row_from_vars(PLpgSQL_variable **vars, int numvars)
+{
+ PLpgSQL_row *row;
+ int i;
+
+ row = palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = -1;
+ row->rowtupdesc = CreateTemplateTupleDesc(numvars);
+ row->nfields = numvars;
+ row->fieldnames = palloc(numvars * sizeof(char *));
+ row->varnos = palloc(numvars * sizeof(int));
+
+ for (i = 0; i < numvars; i++)
+ {
+ PLpgSQL_variable *var = vars[i];
+ Oid typoid;
+ int32 typmod;
+ Oid typcoll;
+
+ /* Member vars of a row should never be const */
+ Assert(!var->isconst);
+
+ switch (var->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ typoid = ((PLpgSQL_var *) var)->datatype->typoid;
+ typmod = ((PLpgSQL_var *) var)->datatype->atttypmod;
+ typcoll = ((PLpgSQL_var *) var)->datatype->collation;
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ /* shouldn't need to revalidate rectypeid already... */
+ typoid = ((PLpgSQL_rec *) var)->rectypeid;
+ typmod = -1; /* don't know typmod, if it's used at all */
+ typcoll = InvalidOid; /* composite types have no collation */
+ break;
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", var->dtype);
+ typoid = InvalidOid; /* keep compiler quiet */
+ typmod = 0;
+ typcoll = InvalidOid;
+ break;
+ }
+
+ row->fieldnames[i] = var->refname;
+ row->varnos[i] = var->dno;
+
+ TupleDescInitEntry(row->rowtupdesc, i + 1,
+ var->refname,
+ typoid, typmod,
+ 0);
+ TupleDescInitEntryCollation(row->rowtupdesc, i + 1, typcoll);
+ }
+
+ return row;
+}
+
+/*
+ * Build a RECFIELD datum for the named field of the specified record variable
+ *
+ * If there's already such a datum, just return it; we don't need duplicates.
+ */
+PLpgSQL_recfield *
+plpgsql_build_recfield(PLpgSQL_rec *rec, const char *fldname)
+{
+ PLpgSQL_recfield *recfield;
+ int i;
+
+ /* search for an existing datum referencing this field */
+ i = rec->firstfield;
+ while (i >= 0)
+ {
+ PLpgSQL_recfield *fld = (PLpgSQL_recfield *) plpgsql_Datums[i];
+
+ Assert(fld->dtype == PLPGSQL_DTYPE_RECFIELD &&
+ fld->recparentno == rec->dno);
+ if (strcmp(fld->fieldname, fldname) == 0)
+ return fld;
+ i = fld->nextfield;
+ }
+
+ /* nope, so make a new one */
+ recfield = palloc0(sizeof(PLpgSQL_recfield));
+ recfield->dtype = PLPGSQL_DTYPE_RECFIELD;
+ recfield->fieldname = pstrdup(fldname);
+ recfield->recparentno = rec->dno;
+ recfield->rectupledescid = INVALID_TUPLEDESC_IDENTIFIER;
+
+ plpgsql_adddatum((PLpgSQL_datum *) recfield);
+
+ /* now we can link it into the parent's chain */
+ recfield->nextfield = rec->firstfield;
+ rec->firstfield = recfield->dno;
+
+ return recfield;
+}
+
+/*
+ * plpgsql_build_datatype
+ * Build PLpgSQL_type struct given type OID, typmod, collation,
+ * and type's parsed name.
+ *
+ * If collation is not InvalidOid then it overrides the type's default
+ * collation. But collation is ignored if the datatype is non-collatable.
+ *
+ * origtypname is the parsed form of what the user wrote as the type name.
+ * It can be NULL if the type could not be a composite type, or if it was
+ * identified by OID to begin with (e.g., it's a function argument type).
+ */
+PLpgSQL_type *
+plpgsql_build_datatype(Oid typeOid, int32 typmod,
+ Oid collation, TypeName *origtypname)
+{
+ HeapTuple typeTup;
+ PLpgSQL_type *typ;
+
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typeOid));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", typeOid);
+
+ typ = build_datatype(typeTup, typmod, collation, origtypname);
+
+ ReleaseSysCache(typeTup);
+
+ return typ;
+}
+
+/*
+ * Utility subroutine to make a PLpgSQL_type struct given a pg_type entry
+ * and additional details (see comments for plpgsql_build_datatype).
+ */
+static PLpgSQL_type *
+build_datatype(HeapTuple typeTup, int32 typmod,
+ Oid collation, TypeName *origtypname)
+{
+ Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+ PLpgSQL_type *typ;
+
+ if (!typeStruct->typisdefined)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("type \"%s\" is only a shell",
+ NameStr(typeStruct->typname))));
+
+ typ = (PLpgSQL_type *) palloc(sizeof(PLpgSQL_type));
+
+ typ->typname = pstrdup(NameStr(typeStruct->typname));
+ typ->typoid = typeStruct->oid;
+ switch (typeStruct->typtype)
+ {
+ case TYPTYPE_BASE:
+ case TYPTYPE_ENUM:
+ case TYPTYPE_RANGE:
+ case TYPTYPE_MULTIRANGE:
+ typ->ttype = PLPGSQL_TTYPE_SCALAR;
+ break;
+ case TYPTYPE_COMPOSITE:
+ typ->ttype = PLPGSQL_TTYPE_REC;
+ break;
+ case TYPTYPE_DOMAIN:
+ if (type_is_rowtype(typeStruct->typbasetype))
+ typ->ttype = PLPGSQL_TTYPE_REC;
+ else
+ typ->ttype = PLPGSQL_TTYPE_SCALAR;
+ break;
+ case TYPTYPE_PSEUDO:
+ if (typ->typoid == RECORDOID)
+ typ->ttype = PLPGSQL_TTYPE_REC;
+ else
+ typ->ttype = PLPGSQL_TTYPE_PSEUDO;
+ break;
+ default:
+ elog(ERROR, "unrecognized typtype: %d",
+ (int) typeStruct->typtype);
+ break;
+ }
+ typ->typlen = typeStruct->typlen;
+ typ->typbyval = typeStruct->typbyval;
+ typ->typtype = typeStruct->typtype;
+ typ->collation = typeStruct->typcollation;
+ if (OidIsValid(collation) && OidIsValid(typ->collation))
+ typ->collation = collation;
+ /* Detect if type is true array, or domain thereof */
+ /* NB: this is only used to decide whether to apply expand_array */
+ if (typeStruct->typtype == TYPTYPE_BASE)
+ {
+ /*
+ * This test should include what get_element_type() checks. We also
+ * disallow non-toastable array types (i.e. oidvector and int2vector).
+ */
+ typ->typisarray = (IsTrueArrayType(typeStruct) &&
+ typeStruct->typstorage != TYPSTORAGE_PLAIN);
+ }
+ else if (typeStruct->typtype == TYPTYPE_DOMAIN)
+ {
+ /* we can short-circuit looking up base types if it's not varlena */
+ typ->typisarray = (typeStruct->typlen == -1 &&
+ typeStruct->typstorage != TYPSTORAGE_PLAIN &&
+ OidIsValid(get_base_element_type(typeStruct->typbasetype)));
+ }
+ else
+ typ->typisarray = false;
+ typ->atttypmod = typmod;
+
+ /*
+ * If it's a named composite type (or domain over one), find the typcache
+ * entry and record the current tupdesc ID, so we can detect changes
+ * (including drops). We don't currently support on-the-fly replacement
+ * of non-composite types, else we might want to do this for them too.
+ */
+ if (typ->ttype == PLPGSQL_TTYPE_REC && typ->typoid != RECORDOID)
+ {
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(typ->typoid,
+ TYPECACHE_TUPDESC |
+ TYPECACHE_DOMAIN_BASE_INFO);
+ if (typentry->typtype == TYPTYPE_DOMAIN)
+ typentry = lookup_type_cache(typentry->domainBaseType,
+ TYPECACHE_TUPDESC);
+ if (typentry->tupDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(typ->typoid))));
+
+ typ->origtypname = origtypname;
+ typ->tcache = typentry;
+ typ->tupdesc_id = typentry->tupDesc_identifier;
+ }
+ else
+ {
+ typ->origtypname = NULL;
+ typ->tcache = NULL;
+ typ->tupdesc_id = 0;
+ }
+
+ return typ;
+}
+
+/*
+ * plpgsql_recognize_err_condition
+ * Check condition name and translate it to SQLSTATE.
+ *
+ * Note: there are some cases where the same condition name has multiple
+ * entries in the table. We arbitrarily return the first match.
+ */
+int
+plpgsql_recognize_err_condition(const char *condname, bool allow_sqlstate)
+{
+ int i;
+
+ if (allow_sqlstate)
+ {
+ if (strlen(condname) == 5 &&
+ strspn(condname, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
+ return MAKE_SQLSTATE(condname[0],
+ condname[1],
+ condname[2],
+ condname[3],
+ condname[4]);
+ }
+
+ for (i = 0; exception_label_map[i].label != NULL; i++)
+ {
+ if (strcmp(condname, exception_label_map[i].label) == 0)
+ return exception_label_map[i].sqlerrstate;
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("unrecognized exception condition \"%s\"",
+ condname)));
+ return 0; /* keep compiler quiet */
+}
+
+/*
+ * plpgsql_parse_err_condition
+ * Generate PLpgSQL_condition entry(s) for an exception condition name
+ *
+ * This has to be able to return a list because there are some duplicate
+ * names in the table of error code names.
+ */
+PLpgSQL_condition *
+plpgsql_parse_err_condition(char *condname)
+{
+ int i;
+ PLpgSQL_condition *new;
+ PLpgSQL_condition *prev;
+
+ /*
+ * XXX Eventually we will want to look for user-defined exception names
+ * here.
+ */
+
+ /*
+ * OTHERS is represented as code 0 (which would map to '00000', but we
+ * have no need to represent that as an exception condition).
+ */
+ if (strcmp(condname, "others") == 0)
+ {
+ new = palloc(sizeof(PLpgSQL_condition));
+ new->sqlerrstate = 0;
+ new->condname = condname;
+ new->next = NULL;
+ return new;
+ }
+
+ prev = NULL;
+ for (i = 0; exception_label_map[i].label != NULL; i++)
+ {
+ if (strcmp(condname, exception_label_map[i].label) == 0)
+ {
+ new = palloc(sizeof(PLpgSQL_condition));
+ new->sqlerrstate = exception_label_map[i].sqlerrstate;
+ new->condname = condname;
+ new->next = prev;
+ prev = new;
+ }
+ }
+
+ if (!prev)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("unrecognized exception condition \"%s\"",
+ condname)));
+
+ return prev;
+}
+
+/* ----------
+ * plpgsql_start_datums Initialize datum list at compile startup.
+ * ----------
+ */
+static void
+plpgsql_start_datums(void)
+{
+ datums_alloc = 128;
+ plpgsql_nDatums = 0;
+ /* This is short-lived, so needn't allocate in function's cxt */
+ plpgsql_Datums = MemoryContextAlloc(plpgsql_compile_tmp_cxt,
+ sizeof(PLpgSQL_datum *) * datums_alloc);
+ /* datums_last tracks what's been seen by plpgsql_add_initdatums() */
+ datums_last = 0;
+}
+
+/* ----------
+ * plpgsql_adddatum Add a variable, record or row
+ * to the compiler's datum list.
+ * ----------
+ */
+void
+plpgsql_adddatum(PLpgSQL_datum *newdatum)
+{
+ if (plpgsql_nDatums == datums_alloc)
+ {
+ datums_alloc *= 2;
+ plpgsql_Datums = repalloc(plpgsql_Datums, sizeof(PLpgSQL_datum *) * datums_alloc);
+ }
+
+ newdatum->dno = plpgsql_nDatums;
+ plpgsql_Datums[plpgsql_nDatums++] = newdatum;
+}
+
+/* ----------
+ * plpgsql_finish_datums Copy completed datum info into function struct.
+ * ----------
+ */
+static void
+plpgsql_finish_datums(PLpgSQL_function *function)
+{
+ Size copiable_size = 0;
+ int i;
+
+ function->ndatums = plpgsql_nDatums;
+ function->datums = palloc(sizeof(PLpgSQL_datum *) * plpgsql_nDatums);
+ for (i = 0; i < plpgsql_nDatums; i++)
+ {
+ function->datums[i] = plpgsql_Datums[i];
+
+ /* This must agree with copy_plpgsql_datums on what is copiable */
+ switch (function->datums[i]->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ copiable_size += MAXALIGN(sizeof(PLpgSQL_var));
+ break;
+ case PLPGSQL_DTYPE_REC:
+ copiable_size += MAXALIGN(sizeof(PLpgSQL_rec));
+ break;
+ default:
+ break;
+ }
+ }
+ function->copiable_size = copiable_size;
+}
+
+
+/* ----------
+ * plpgsql_add_initdatums Make an array of the datum numbers of
+ * all the initializable datums created since the last call
+ * to this function.
+ *
+ * If varnos is NULL, we just forget any datum entries created since the
+ * last call.
+ *
+ * This is used around a DECLARE section to create a list of the datums
+ * that have to be initialized at block entry. Note that datums can also
+ * be created elsewhere than DECLARE, eg by a FOR-loop, but it is then
+ * the responsibility of special-purpose code to initialize them.
+ * ----------
+ */
+int
+plpgsql_add_initdatums(int **varnos)
+{
+ int i;
+ int n = 0;
+
+ /*
+ * The set of dtypes recognized here must match what exec_stmt_block()
+ * cares about (re)initializing at block entry.
+ */
+ for (i = datums_last; i < plpgsql_nDatums; i++)
+ {
+ switch (plpgsql_Datums[i]->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_REC:
+ n++;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ if (varnos != NULL)
+ {
+ if (n > 0)
+ {
+ *varnos = (int *) palloc(sizeof(int) * n);
+
+ n = 0;
+ for (i = datums_last; i < plpgsql_nDatums; i++)
+ {
+ switch (plpgsql_Datums[i]->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_REC:
+ (*varnos)[n++] = plpgsql_Datums[i]->dno;
+
+ default:
+ break;
+ }
+ }
+ }
+ else
+ *varnos = NULL;
+ }
+
+ datums_last = plpgsql_nDatums;
+ return n;
+}
+
+
+/*
+ * Compute the hashkey for a given function invocation
+ *
+ * The hashkey is returned into the caller-provided storage at *hashkey.
+ */
+static void
+compute_function_hashkey(FunctionCallInfo fcinfo,
+ Form_pg_proc procStruct,
+ PLpgSQL_func_hashkey *hashkey,
+ bool forValidator)
+{
+ /* Make sure any unused bytes of the struct are zero */
+ MemSet(hashkey, 0, sizeof(PLpgSQL_func_hashkey));
+
+ /* get function OID */
+ hashkey->funcOid = fcinfo->flinfo->fn_oid;
+
+ /* get call context */
+ hashkey->isTrigger = CALLED_AS_TRIGGER(fcinfo);
+ hashkey->isEventTrigger = CALLED_AS_EVENT_TRIGGER(fcinfo);
+
+ /*
+ * If DML trigger, include trigger's OID in the hash, so that each trigger
+ * usage gets a different hash entry, allowing for e.g. different relation
+ * rowtypes or transition table names. In validation mode we do not know
+ * what relation or transition table names are intended to be used, so we
+ * leave trigOid zero; the hash entry built in this case will never be
+ * used for any actual calls.
+ *
+ * We don't currently need to distinguish different event trigger usages
+ * in the same way, since the special parameter variables don't vary in
+ * type in that case.
+ */
+ if (hashkey->isTrigger && !forValidator)
+ {
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+
+ hashkey->trigOid = trigdata->tg_trigger->tgoid;
+ }
+
+ /* get input collation, if known */
+ hashkey->inputCollation = fcinfo->fncollation;
+
+ if (procStruct->pronargs > 0)
+ {
+ /* get the argument types */
+ memcpy(hashkey->argtypes, procStruct->proargtypes.values,
+ procStruct->pronargs * sizeof(Oid));
+
+ /* resolve any polymorphic argument types */
+ plpgsql_resolve_polymorphic_argtypes(procStruct->pronargs,
+ hashkey->argtypes,
+ NULL,
+ fcinfo->flinfo->fn_expr,
+ forValidator,
+ NameStr(procStruct->proname));
+ }
+}
+
+/*
+ * This is the same as the standard resolve_polymorphic_argtypes() function,
+ * except that:
+ * 1. We go ahead and report the error if we can't resolve the types.
+ * 2. We treat RECORD-type input arguments (not output arguments) as if
+ * they were polymorphic, replacing their types with the actual input
+ * types if we can determine those. This allows us to create a separate
+ * function cache entry for each named composite type passed to such an
+ * argument.
+ * 3. In validation mode, we have no inputs to look at, so assume that
+ * polymorphic arguments are integer, integer-array or integer-range.
+ */
+static void
+plpgsql_resolve_polymorphic_argtypes(int numargs,
+ Oid *argtypes, char *argmodes,
+ Node *call_expr, bool forValidator,
+ const char *proname)
+{
+ int i;
+
+ if (!forValidator)
+ {
+ int inargno;
+
+ /* normal case, pass to standard routine */
+ if (!resolve_polymorphic_argtypes(numargs, argtypes, argmodes,
+ call_expr))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("could not determine actual argument "
+ "type for polymorphic function \"%s\"",
+ proname)));
+ /* also, treat RECORD inputs (but not outputs) as polymorphic */
+ inargno = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ char argmode = argmodes ? argmodes[i] : PROARGMODE_IN;
+
+ if (argmode == PROARGMODE_OUT || argmode == PROARGMODE_TABLE)
+ continue;
+ if (argtypes[i] == RECORDOID || argtypes[i] == RECORDARRAYOID)
+ {
+ Oid resolvedtype = get_call_expr_argtype(call_expr,
+ inargno);
+
+ if (OidIsValid(resolvedtype))
+ argtypes[i] = resolvedtype;
+ }
+ inargno++;
+ }
+ }
+ else
+ {
+ /* special validation case (no need to do anything for RECORD) */
+ for (i = 0; i < numargs; i++)
+ {
+ switch (argtypes[i])
+ {
+ case ANYELEMENTOID:
+ case ANYNONARRAYOID:
+ case ANYENUMOID: /* XXX dubious */
+ case ANYCOMPATIBLEOID:
+ case ANYCOMPATIBLENONARRAYOID:
+ argtypes[i] = INT4OID;
+ break;
+ case ANYARRAYOID:
+ case ANYCOMPATIBLEARRAYOID:
+ argtypes[i] = INT4ARRAYOID;
+ break;
+ case ANYRANGEOID:
+ case ANYCOMPATIBLERANGEOID:
+ argtypes[i] = INT4RANGEOID;
+ break;
+ case ANYMULTIRANGEOID:
+ argtypes[i] = INT4MULTIRANGEOID;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * delete_function - clean up as much as possible of a stale function cache
+ *
+ * We can't release the PLpgSQL_function struct itself, because of the
+ * possibility that there are fn_extra pointers to it. We can release
+ * the subsidiary storage, but only if there are no active evaluations
+ * in progress. Otherwise we'll just leak that storage. Since the
+ * case would only occur if a pg_proc update is detected during a nested
+ * recursive call on the function, a leak seems acceptable.
+ *
+ * Note that this can be called more than once if there are multiple fn_extra
+ * pointers to the same function cache. Hence be careful not to do things
+ * twice.
+ */
+static void
+delete_function(PLpgSQL_function *func)
+{
+ /* remove function from hash table (might be done already) */
+ plpgsql_HashTableDelete(func);
+
+ /* release the function's storage if safe and not done already */
+ if (func->use_count == 0)
+ plpgsql_free_function_memory(func);
+}
+
+/* exported so we can call it from _PG_init() */
+void
+plpgsql_HashTableInit(void)
+{
+ HASHCTL ctl;
+
+ /* don't allow double-initialization */
+ Assert(plpgsql_HashTable == NULL);
+
+ ctl.keysize = sizeof(PLpgSQL_func_hashkey);
+ ctl.entrysize = sizeof(plpgsql_HashEnt);
+ plpgsql_HashTable = hash_create("PLpgSQL function hash",
+ FUNCS_PER_USER,
+ &ctl,
+ HASH_ELEM | HASH_BLOBS);
+}
+
+static PLpgSQL_function *
+plpgsql_HashTableLookup(PLpgSQL_func_hashkey *func_key)
+{
+ plpgsql_HashEnt *hentry;
+
+ hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
+ (void *) func_key,
+ HASH_FIND,
+ NULL);
+ if (hentry)
+ return hentry->function;
+ else
+ return NULL;
+}
+
+static void
+plpgsql_HashTableInsert(PLpgSQL_function *function,
+ PLpgSQL_func_hashkey *func_key)
+{
+ plpgsql_HashEnt *hentry;
+ bool found;
+
+ hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
+ (void *) func_key,
+ HASH_ENTER,
+ &found);
+ if (found)
+ elog(WARNING, "trying to insert a function that already exists");
+
+ hentry->function = function;
+ /* prepare back link from function to hashtable key */
+ function->fn_hashkey = &hentry->key;
+}
+
+static void
+plpgsql_HashTableDelete(PLpgSQL_function *function)
+{
+ plpgsql_HashEnt *hentry;
+
+ /* do nothing if not in table */
+ if (function->fn_hashkey == NULL)
+ return;
+
+ hentry = (plpgsql_HashEnt *) hash_search(plpgsql_HashTable,
+ (void *) function->fn_hashkey,
+ HASH_REMOVE,
+ NULL);
+ if (hentry == NULL)
+ elog(WARNING, "trying to delete function that does not exist");
+
+ /* remove back link, which no longer points to allocated storage */
+ function->fn_hashkey = NULL;
+}
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
new file mode 100644
index 0000000..61fa04b
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -0,0 +1,8855 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_exec.c - Executor for the PL/pgSQL
+ * procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_exec.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include <ctype.h>
+
+#include "access/detoast.h"
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "access/tupconvert.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/defrem.h"
+#include "executor/execExpr.h"
+#include "executor/spi.h"
+#include "executor/tstoreReceiver.h"
+#include "funcapi.h"
+#include "mb/stringinfo_mb.h"
+#include "miscadmin.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/optimizer.h"
+#include "parser/parse_coerce.h"
+#include "parser/parse_type.h"
+#include "parser/scansup.h"
+#include "plpgsql.h"
+#include "storage/proc.h"
+#include "tcop/cmdtag.h"
+#include "tcop/pquery.h"
+#include "tcop/tcopprot.h"
+#include "tcop/utility.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/datum.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/snapmgr.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+/*
+ * All plpgsql function executions within a single transaction share the same
+ * executor EState for evaluating "simple" expressions. Each function call
+ * creates its own "eval_econtext" ExprContext within this estate for
+ * per-evaluation workspace. eval_econtext is freed at normal function exit,
+ * and the EState is freed at transaction end (in case of error, we assume
+ * that the abort mechanisms clean it all up). Furthermore, any exception
+ * block within a function has to have its own eval_econtext separate from
+ * the containing function's, so that we can clean up ExprContext callbacks
+ * properly at subtransaction exit. We maintain a stack that tracks the
+ * individual econtexts so that we can clean up correctly at subxact exit.
+ *
+ * This arrangement is a bit tedious to maintain, but it's worth the trouble
+ * so that we don't have to re-prepare simple expressions on each trip through
+ * a function. (We assume the case to optimize is many repetitions of a
+ * function within a transaction.)
+ *
+ * However, there's no value in trying to amortize simple expression setup
+ * across multiple executions of a DO block (inline code block), since there
+ * can never be any. If we use the shared EState for a DO block, the expr
+ * state trees are effectively leaked till end of transaction, and that can
+ * add up if the user keeps on submitting DO blocks. Therefore, each DO block
+ * has its own simple-expression EState, which is cleaned up at exit from
+ * plpgsql_inline_handler(). DO blocks still use the simple_econtext_stack,
+ * though, so that subxact abort cleanup does the right thing.
+ *
+ * (However, if a DO block executes COMMIT or ROLLBACK, then exec_stmt_commit
+ * or exec_stmt_rollback will unlink it from the DO's simple-expression EState
+ * and create a new shared EState that will be used thenceforth. The original
+ * EState will be cleaned up when we get back to plpgsql_inline_handler. This
+ * is a bit ugly, but it isn't worth doing better, since scenarios like this
+ * can't result in indefinite accumulation of state trees.)
+ */
+typedef struct SimpleEcontextStackEntry
+{
+ ExprContext *stack_econtext; /* a stacked econtext */
+ SubTransactionId xact_subxid; /* ID for current subxact */
+ struct SimpleEcontextStackEntry *next; /* next stack entry up */
+} SimpleEcontextStackEntry;
+
+static EState *shared_simple_eval_estate = NULL;
+static SimpleEcontextStackEntry *simple_econtext_stack = NULL;
+
+/*
+ * In addition to the shared simple-eval EState, we have a shared resource
+ * owner that holds refcounts on the CachedPlans for any "simple" expressions
+ * we have evaluated in the current transaction. This allows us to avoid
+ * continually grabbing and releasing a plan refcount when a simple expression
+ * is used over and over. (DO blocks use their own resowner, in exactly the
+ * same way described above for shared_simple_eval_estate.)
+ */
+static ResourceOwner shared_simple_eval_resowner = NULL;
+
+/*
+ * Memory management within a plpgsql function generally works with three
+ * contexts:
+ *
+ * 1. Function-call-lifespan data, such as variable values, is kept in the
+ * "main" context, a/k/a the "SPI Proc" context established by SPI_connect().
+ * This is usually the CurrentMemoryContext while running code in this module
+ * (which is not good, because careless coding can easily cause
+ * function-lifespan memory leaks, but we live with it for now).
+ *
+ * 2. Some statement-execution routines need statement-lifespan workspace.
+ * A suitable context is created on-demand by get_stmt_mcontext(), and must
+ * be reset at the end of the requesting routine. Error recovery will clean
+ * it up automatically. Nested statements requiring statement-lifespan
+ * workspace will result in a stack of such contexts, see push_stmt_mcontext().
+ *
+ * 3. We use the eval_econtext's per-tuple memory context for expression
+ * evaluation, and as a general-purpose workspace for short-lived allocations.
+ * Such allocations usually aren't explicitly freed, but are left to be
+ * cleaned up by a context reset, typically done by exec_eval_cleanup().
+ *
+ * These macros are for use in making short-lived allocations:
+ */
+#define get_eval_mcontext(estate) \
+ ((estate)->eval_econtext->ecxt_per_tuple_memory)
+#define eval_mcontext_alloc(estate, sz) \
+ MemoryContextAlloc(get_eval_mcontext(estate), sz)
+#define eval_mcontext_alloc0(estate, sz) \
+ MemoryContextAllocZero(get_eval_mcontext(estate), sz)
+
+/*
+ * We use two session-wide hash tables for caching cast information.
+ *
+ * cast_expr_hash entries (of type plpgsql_CastExprHashEntry) hold compiled
+ * expression trees for casts. These survive for the life of the session and
+ * are shared across all PL/pgSQL functions and DO blocks. At some point it
+ * might be worth invalidating them after pg_cast changes, but for the moment
+ * we don't bother.
+ *
+ * There is a separate hash table shared_cast_hash (with entries of type
+ * plpgsql_CastHashEntry) containing evaluation state trees for these
+ * expressions, which are managed in the same way as simple expressions
+ * (i.e., we assume cast expressions are always simple).
+ *
+ * As with simple expressions, DO blocks don't use the shared_cast_hash table
+ * but must have their own evaluation state trees. This isn't ideal, but we
+ * don't want to deal with multiple simple_eval_estates within a DO block.
+ */
+typedef struct /* lookup key for cast info */
+{
+ /* NB: we assume this struct contains no padding bytes */
+ Oid srctype; /* source type for cast */
+ Oid dsttype; /* destination type for cast */
+ int32 srctypmod; /* source typmod for cast */
+ int32 dsttypmod; /* destination typmod for cast */
+} plpgsql_CastHashKey;
+
+typedef struct /* cast_expr_hash table entry */
+{
+ plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
+ Expr *cast_expr; /* cast expression, or NULL if no-op cast */
+ CachedExpression *cast_cexpr; /* cached expression backing the above */
+} plpgsql_CastExprHashEntry;
+
+typedef struct /* cast_hash table entry */
+{
+ plpgsql_CastHashKey key; /* hash key --- MUST BE FIRST */
+ plpgsql_CastExprHashEntry *cast_centry; /* link to matching expr entry */
+ /* ExprState is valid only when cast_lxid matches current LXID */
+ ExprState *cast_exprstate; /* expression's eval tree */
+ bool cast_in_use; /* true while we're executing eval tree */
+ LocalTransactionId cast_lxid;
+} plpgsql_CastHashEntry;
+
+static HTAB *cast_expr_hash = NULL;
+static HTAB *shared_cast_hash = NULL;
+
+/*
+ * LOOP_RC_PROCESSING encapsulates common logic for looping statements to
+ * handle return/exit/continue result codes from the loop body statement(s).
+ * It's meant to be used like this:
+ *
+ * int rc = PLPGSQL_RC_OK;
+ * for (...)
+ * {
+ * ...
+ * rc = exec_stmts(estate, stmt->body);
+ * LOOP_RC_PROCESSING(stmt->label, break);
+ * ...
+ * }
+ * return rc;
+ *
+ * If execution of the loop should terminate, LOOP_RC_PROCESSING will execute
+ * "exit_action" (typically a "break" or "goto"), after updating "rc" to the
+ * value the current statement should return. If execution should continue,
+ * LOOP_RC_PROCESSING will do nothing except reset "rc" to PLPGSQL_RC_OK.
+ *
+ * estate and rc are implicit arguments to the macro.
+ * estate->exitlabel is examined and possibly updated.
+ */
+#define LOOP_RC_PROCESSING(looplabel, exit_action) \
+ if (rc == PLPGSQL_RC_RETURN) \
+ { \
+ /* RETURN, so propagate RC_RETURN out */ \
+ exit_action; \
+ } \
+ else if (rc == PLPGSQL_RC_EXIT) \
+ { \
+ if (estate->exitlabel == NULL) \
+ { \
+ /* unlabeled EXIT terminates this loop */ \
+ rc = PLPGSQL_RC_OK; \
+ exit_action; \
+ } \
+ else if ((looplabel) != NULL && \
+ strcmp(looplabel, estate->exitlabel) == 0) \
+ { \
+ /* labeled EXIT matching this loop, so terminate loop */ \
+ estate->exitlabel = NULL; \
+ rc = PLPGSQL_RC_OK; \
+ exit_action; \
+ } \
+ else \
+ { \
+ /* non-matching labeled EXIT, propagate RC_EXIT out */ \
+ exit_action; \
+ } \
+ } \
+ else if (rc == PLPGSQL_RC_CONTINUE) \
+ { \
+ if (estate->exitlabel == NULL) \
+ { \
+ /* unlabeled CONTINUE matches this loop, so continue in loop */ \
+ rc = PLPGSQL_RC_OK; \
+ } \
+ else if ((looplabel) != NULL && \
+ strcmp(looplabel, estate->exitlabel) == 0) \
+ { \
+ /* labeled CONTINUE matching this loop, so continue in loop */ \
+ estate->exitlabel = NULL; \
+ rc = PLPGSQL_RC_OK; \
+ } \
+ else \
+ { \
+ /* non-matching labeled CONTINUE, propagate RC_CONTINUE out */ \
+ exit_action; \
+ } \
+ } \
+ else \
+ Assert(rc == PLPGSQL_RC_OK)
+
+/************************************************************
+ * Local function forward declarations
+ ************************************************************/
+static void coerce_function_result_tuple(PLpgSQL_execstate *estate,
+ TupleDesc tupdesc);
+static void plpgsql_exec_error_callback(void *arg);
+static void copy_plpgsql_datums(PLpgSQL_execstate *estate,
+ PLpgSQL_function *func);
+static void plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
+ PLpgSQL_var *var);
+static MemoryContext get_stmt_mcontext(PLpgSQL_execstate *estate);
+static void push_stmt_mcontext(PLpgSQL_execstate *estate);
+static void pop_stmt_mcontext(PLpgSQL_execstate *estate);
+
+static int exec_toplevel_block(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_block *block);
+static int exec_stmt_block(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_block *block);
+static int exec_stmts(PLpgSQL_execstate *estate,
+ List *stmts);
+static int exec_stmt_assign(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_assign *stmt);
+static int exec_stmt_perform(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_perform *stmt);
+static int exec_stmt_call(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_call *stmt);
+static int exec_stmt_getdiag(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_getdiag *stmt);
+static int exec_stmt_if(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_if *stmt);
+static int exec_stmt_case(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_case *stmt);
+static int exec_stmt_loop(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_loop *stmt);
+static int exec_stmt_while(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_while *stmt);
+static int exec_stmt_fori(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_fori *stmt);
+static int exec_stmt_fors(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_fors *stmt);
+static int exec_stmt_forc(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_forc *stmt);
+static int exec_stmt_foreach_a(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_foreach_a *stmt);
+static int exec_stmt_open(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_open *stmt);
+static int exec_stmt_fetch(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_fetch *stmt);
+static int exec_stmt_close(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_close *stmt);
+static int exec_stmt_exit(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_exit *stmt);
+static int exec_stmt_return(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return *stmt);
+static int exec_stmt_return_next(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return_next *stmt);
+static int exec_stmt_return_query(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return_query *stmt);
+static int exec_stmt_raise(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_raise *stmt);
+static int exec_stmt_assert(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_assert *stmt);
+static int exec_stmt_execsql(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_execsql *stmt);
+static int exec_stmt_dynexecute(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_dynexecute *stmt);
+static int exec_stmt_dynfors(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_dynfors *stmt);
+static int exec_stmt_commit(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_commit *stmt);
+static int exec_stmt_rollback(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_rollback *stmt);
+
+static void plpgsql_estate_setup(PLpgSQL_execstate *estate,
+ PLpgSQL_function *func,
+ ReturnSetInfo *rsi,
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner);
+static void exec_eval_cleanup(PLpgSQL_execstate *estate);
+
+static void exec_prepare_plan(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, int cursorOptions);
+static void exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr);
+static void exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan);
+static void exec_check_rw_parameter(PLpgSQL_expr *expr);
+static void exec_check_assignable(PLpgSQL_execstate *estate, int dno);
+static bool exec_eval_simple_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ Datum *result,
+ bool *isNull,
+ Oid *rettype,
+ int32 *rettypmod);
+
+static void exec_assign_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ PLpgSQL_expr *expr);
+static void exec_assign_c_string(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ const char *str);
+static void exec_assign_value(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ Datum value, bool isNull,
+ Oid valtype, int32 valtypmod);
+static void exec_eval_datum(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum,
+ Oid *typeid,
+ int32 *typetypmod,
+ Datum *value,
+ bool *isnull);
+static int exec_eval_integer(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull);
+static bool exec_eval_boolean(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull);
+static Datum exec_eval_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull,
+ Oid *rettype,
+ int32 *rettypmod);
+static int exec_run_select(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, long maxtuples, Portal *portalP);
+static int exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
+ Portal portal, bool prefetch_ok);
+static ParamListInfo setup_param_list(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr);
+static ParamExternData *plpgsql_param_fetch(ParamListInfo params,
+ int paramid, bool speculative,
+ ParamExternData *workspace);
+static void plpgsql_param_compile(ParamListInfo params, Param *param,
+ ExprState *state,
+ Datum *resv, bool *resnull);
+static void plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_recfield(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_generic(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void plpgsql_param_eval_generic_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext);
+static void exec_move_row(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ HeapTuple tup, TupleDesc tupdesc);
+static void revalidate_rectypeid(PLpgSQL_rec *rec);
+static ExpandedRecordHeader *make_expanded_record_for_rec(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec,
+ TupleDesc srctupdesc,
+ ExpandedRecordHeader *srcerh);
+static void exec_move_row_from_fields(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ ExpandedRecordHeader *newerh,
+ Datum *values, bool *nulls,
+ TupleDesc tupdesc);
+static bool compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc);
+static HeapTuple make_tuple_from_row(PLpgSQL_execstate *estate,
+ PLpgSQL_row *row,
+ TupleDesc tupdesc);
+static TupleDesc deconstruct_composite_datum(Datum value,
+ HeapTupleData *tmptup);
+static void exec_move_row_from_datum(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ Datum value);
+static void instantiate_empty_record_variable(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec);
+static char *convert_value_to_string(PLpgSQL_execstate *estate,
+ Datum value, Oid valtype);
+static inline Datum exec_cast_value(PLpgSQL_execstate *estate,
+ Datum value, bool *isnull,
+ Oid valtype, int32 valtypmod,
+ Oid reqtype, int32 reqtypmod);
+static Datum do_cast_value(PLpgSQL_execstate *estate,
+ Datum value, bool *isnull,
+ Oid valtype, int32 valtypmod,
+ Oid reqtype, int32 reqtypmod);
+static plpgsql_CastHashEntry *get_cast_hashentry(PLpgSQL_execstate *estate,
+ Oid srctype, int32 srctypmod,
+ Oid dsttype, int32 dsttypmod);
+static void exec_init_tuple_store(PLpgSQL_execstate *estate);
+static void exec_set_found(PLpgSQL_execstate *estate, bool state);
+static void plpgsql_create_econtext(PLpgSQL_execstate *estate);
+static void plpgsql_destroy_econtext(PLpgSQL_execstate *estate);
+static void assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
+ Datum newvalue, bool isnull, bool freeable);
+static void assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
+ const char *str);
+static void assign_record_var(PLpgSQL_execstate *estate, PLpgSQL_rec *rec,
+ ExpandedRecordHeader *erh);
+static ParamListInfo exec_eval_using_params(PLpgSQL_execstate *estate,
+ List *params);
+static Portal exec_dynquery_with_params(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *dynquery, List *params,
+ const char *portalname, int cursorOptions);
+static char *format_expr_params(PLpgSQL_execstate *estate,
+ const PLpgSQL_expr *expr);
+static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
+ ParamListInfo paramLI);
+static PLpgSQL_variable *make_callstmt_target(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr);
+
+
+/* ----------
+ * plpgsql_exec_function Called by the call handler for
+ * function execution.
+ *
+ * This is also used to execute inline code blocks (DO blocks). The only
+ * difference that this code is aware of is that for a DO block, we want
+ * to use a private simple_eval_estate and a private simple_eval_resowner,
+ * which are created and passed in by the caller. For regular functions,
+ * pass NULL, which implies using shared_simple_eval_estate and
+ * shared_simple_eval_resowner. (When using a private simple_eval_estate,
+ * we must also use a private cast hashtable, but that's taken care of
+ * within plpgsql_estate_setup.)
+ * procedure_resowner is a resowner that will survive for the duration
+ * of execution of this function/procedure. It is needed only if we
+ * are doing non-atomic execution and there are CALL or DO statements
+ * in the function; otherwise it can be NULL. We use it to hold refcounts
+ * on the CALL/DO statements' plans.
+ * ----------
+ */
+Datum
+plpgsql_exec_function(PLpgSQL_function *func, FunctionCallInfo fcinfo,
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner,
+ ResourceOwner procedure_resowner,
+ bool atomic)
+{
+ PLpgSQL_execstate estate;
+ ErrorContextCallback plerrcontext;
+ int i;
+ int rc;
+
+ /*
+ * Setup the execution state
+ */
+ plpgsql_estate_setup(&estate, func, (ReturnSetInfo *) fcinfo->resultinfo,
+ simple_eval_estate, simple_eval_resowner);
+ estate.procedure_resowner = procedure_resowner;
+ estate.atomic = atomic;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_exec_error_callback;
+ plerrcontext.arg = &estate;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Make local execution copies of all the datums
+ */
+ estate.err_text = gettext_noop("during initialization of execution state");
+ copy_plpgsql_datums(&estate, func);
+
+ /*
+ * Store the actual call argument values into the appropriate variables
+ */
+ estate.err_text = gettext_noop("while storing call arguments into local variables");
+ for (i = 0; i < func->fn_nargs; i++)
+ {
+ int n = func->fn_argvarnos[i];
+
+ switch (estate.datums[n]->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) estate.datums[n];
+
+ assign_simple_var(&estate, var,
+ fcinfo->args[i].value,
+ fcinfo->args[i].isnull,
+ false);
+
+ /*
+ * Force any array-valued parameter to be stored in
+ * expanded form in our local variable, in hopes of
+ * improving efficiency of uses of the variable. (This is
+ * a hack, really: why only arrays? Need more thought
+ * about which cases are likely to win. See also
+ * typisarray-specific heuristic in exec_assign_value.)
+ *
+ * Special cases: If passed a R/W expanded pointer, assume
+ * we can commandeer the object rather than having to copy
+ * it. If passed a R/O expanded pointer, just keep it as
+ * the value of the variable for the moment. (We'll force
+ * it to R/W if the variable gets modified, but that may
+ * very well never happen.)
+ */
+ if (!var->isnull && var->datatype->typisarray)
+ {
+ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(var->value)))
+ {
+ /* take ownership of R/W object */
+ assign_simple_var(&estate, var,
+ TransferExpandedObject(var->value,
+ estate.datum_context),
+ false,
+ true);
+ }
+ else if (VARATT_IS_EXTERNAL_EXPANDED_RO(DatumGetPointer(var->value)))
+ {
+ /* R/O pointer, keep it as-is until assigned to */
+ }
+ else
+ {
+ /* flat array, so force to expanded form */
+ assign_simple_var(&estate, var,
+ expand_array(var->value,
+ estate.datum_context,
+ NULL),
+ false,
+ true);
+ }
+ }
+ }
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) estate.datums[n];
+
+ if (!fcinfo->args[i].isnull)
+ {
+ /* Assign row value from composite datum */
+ exec_move_row_from_datum(&estate,
+ (PLpgSQL_variable *) rec,
+ fcinfo->args[i].value);
+ }
+ else
+ {
+ /* If arg is null, set variable to null */
+ exec_move_row(&estate, (PLpgSQL_variable *) rec,
+ NULL, NULL);
+ }
+ /* clean up after exec_move_row() */
+ exec_eval_cleanup(&estate);
+ }
+ break;
+
+ default:
+ /* Anything else should not be an argument variable */
+ elog(ERROR, "unrecognized dtype: %d", func->datums[i]->dtype);
+ }
+ }
+
+ estate.err_text = gettext_noop("during function entry");
+
+ /*
+ * Set the magic variable FOUND to false
+ */
+ exec_set_found(&estate, false);
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
+ ((*plpgsql_plugin_ptr)->func_beg) (&estate, func);
+
+ /*
+ * Now call the toplevel block of statements
+ */
+ estate.err_text = NULL;
+ rc = exec_toplevel_block(&estate, func->action);
+ if (rc != PLPGSQL_RC_RETURN)
+ {
+ estate.err_text = NULL;
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of function without RETURN")));
+ }
+
+ /*
+ * We got a return value - process it
+ */
+ estate.err_text = gettext_noop("while casting return value to function's return type");
+
+ fcinfo->isnull = estate.retisnull;
+
+ if (estate.retisset)
+ {
+ ReturnSetInfo *rsi = estate.rsi;
+
+ /* Check caller can handle a set result */
+ if (!rsi || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ rsi->returnMode = SFRM_Materialize;
+
+ /* If we produced any tuples, send back the result */
+ if (estate.tuple_store)
+ {
+ MemoryContext oldcxt;
+
+ rsi->setResult = estate.tuple_store;
+ oldcxt = MemoryContextSwitchTo(estate.tuple_store_cxt);
+ rsi->setDesc = CreateTupleDescCopy(estate.tuple_store_desc);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ estate.retval = (Datum) 0;
+ fcinfo->isnull = true;
+ }
+ else if (!estate.retisnull)
+ {
+ /*
+ * Cast result value to function's declared result type, and copy it
+ * out to the upper executor memory context. We must treat tuple
+ * results specially in order to deal with cases like rowtypes
+ * involving dropped columns.
+ */
+ if (estate.retistuple)
+ {
+ /* Don't need coercion if rowtype is known to match */
+ if (func->fn_rettype == estate.rettype &&
+ func->fn_rettype != RECORDOID)
+ {
+ /*
+ * Copy the tuple result into upper executor memory context.
+ * However, if we have a R/W expanded datum, we can just
+ * transfer its ownership out to the upper context.
+ */
+ estate.retval = SPI_datumTransfer(estate.retval,
+ false,
+ -1);
+ }
+ else
+ {
+ /*
+ * Need to look up the expected result type. XXX would be
+ * better to cache the tupdesc instead of repeating
+ * get_call_result_type(), but the only easy place to save it
+ * is in the PLpgSQL_function struct, and that's too
+ * long-lived: composite types could change during the
+ * existence of a PLpgSQL_function.
+ */
+ Oid resultTypeId;
+ TupleDesc tupdesc;
+
+ switch (get_call_result_type(fcinfo, &resultTypeId, &tupdesc))
+ {
+ case TYPEFUNC_COMPOSITE:
+ /* got the expected result rowtype, now coerce it */
+ coerce_function_result_tuple(&estate, tupdesc);
+ break;
+ case TYPEFUNC_COMPOSITE_DOMAIN:
+ /* got the expected result rowtype, now coerce it */
+ coerce_function_result_tuple(&estate, tupdesc);
+ /* and check domain constraints */
+ /* XXX allowing caching here would be good, too */
+ domain_check(estate.retval, false, resultTypeId,
+ NULL, NULL);
+ break;
+ case TYPEFUNC_RECORD:
+
+ /*
+ * Failed to determine actual type of RECORD. We
+ * could raise an error here, but what this means in
+ * practice is that the caller is expecting any old
+ * generic rowtype, so we don't really need to be
+ * restrictive. Pass back the generated result as-is.
+ */
+ estate.retval = SPI_datumTransfer(estate.retval,
+ false,
+ -1);
+ break;
+ default:
+ /* shouldn't get here if retistuple is true ... */
+ elog(ERROR, "return type must be a row type");
+ break;
+ }
+ }
+ }
+ else
+ {
+ /* Scalar case: use exec_cast_value */
+ estate.retval = exec_cast_value(&estate,
+ estate.retval,
+ &fcinfo->isnull,
+ estate.rettype,
+ -1,
+ func->fn_rettype,
+ -1);
+
+ /*
+ * If the function's return type isn't by value, copy the value
+ * into upper executor memory context. However, if we have a R/W
+ * expanded datum, we can just transfer its ownership out to the
+ * upper executor context.
+ */
+ if (!fcinfo->isnull && !func->fn_retbyval)
+ estate.retval = SPI_datumTransfer(estate.retval,
+ false,
+ func->fn_rettyplen);
+ }
+ }
+ else
+ {
+ /*
+ * We're returning a NULL, which normally requires no conversion work
+ * regardless of datatypes. But, if we are casting it to a domain
+ * return type, we'd better check that the domain's constraints pass.
+ */
+ if (func->fn_retisdomain)
+ estate.retval = exec_cast_value(&estate,
+ estate.retval,
+ &fcinfo->isnull,
+ estate.rettype,
+ -1,
+ func->fn_rettype,
+ -1);
+ }
+
+ estate.err_text = gettext_noop("during function exit");
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
+ ((*plpgsql_plugin_ptr)->func_end) (&estate, func);
+
+ /* Clean up any leftover temporary memory */
+ plpgsql_destroy_econtext(&estate);
+ exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+
+ /*
+ * Return the function's result
+ */
+ return estate.retval;
+}
+
+/*
+ * Helper for plpgsql_exec_function: coerce composite result to the specified
+ * tuple descriptor, and copy it out to upper executor memory. This is split
+ * out mostly for cosmetic reasons --- the logic would be very deeply nested
+ * otherwise.
+ *
+ * estate->retval is updated in-place.
+ */
+static void
+coerce_function_result_tuple(PLpgSQL_execstate *estate, TupleDesc tupdesc)
+{
+ HeapTuple rettup;
+ TupleDesc retdesc;
+ TupleConversionMap *tupmap;
+
+ /* We assume exec_stmt_return verified that result is composite */
+ Assert(type_is_rowtype(estate->rettype));
+
+ /* We can special-case expanded records for speed */
+ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(estate->retval)))
+ {
+ ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(estate->retval);
+
+ Assert(erh->er_magic == ER_MAGIC);
+
+ /* Extract record's TupleDesc */
+ retdesc = expanded_record_get_tupdesc(erh);
+
+ /* check rowtype compatibility */
+ tupmap = convert_tuples_by_position(retdesc,
+ tupdesc,
+ gettext_noop("returned record type does not match expected record type"));
+
+ /* it might need conversion */
+ if (tupmap)
+ {
+ rettup = expanded_record_get_tuple(erh);
+ Assert(rettup);
+ rettup = execute_attr_map_tuple(rettup, tupmap);
+
+ /*
+ * Copy tuple to upper executor memory, as a tuple Datum. Make
+ * sure it is labeled with the caller-supplied tuple type.
+ */
+ estate->retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
+ /* no need to free map, we're about to return anyway */
+ }
+ else if (!(tupdesc->tdtypeid == erh->er_decltypeid ||
+ (tupdesc->tdtypeid == RECORDOID &&
+ !ExpandedRecordIsDomain(erh))))
+ {
+ /*
+ * The expanded record has the right physical tupdesc, but the
+ * wrong type ID. (Typically, the expanded record is RECORDOID
+ * but the function is declared to return a named composite type.
+ * As in exec_move_row_from_datum, we don't allow returning a
+ * composite-domain record from a function declared to return
+ * RECORD.) So we must flatten the record to a tuple datum and
+ * overwrite its type fields with the right thing. spi.c doesn't
+ * provide any easy way to deal with this case, so we end up
+ * duplicating the guts of datumCopy() :-(
+ */
+ Size resultsize;
+ HeapTupleHeader tuphdr;
+
+ resultsize = EOH_get_flat_size(&erh->hdr);
+ tuphdr = (HeapTupleHeader) SPI_palloc(resultsize);
+ EOH_flatten_into(&erh->hdr, (void *) tuphdr, resultsize);
+ HeapTupleHeaderSetTypeId(tuphdr, tupdesc->tdtypeid);
+ HeapTupleHeaderSetTypMod(tuphdr, tupdesc->tdtypmod);
+ estate->retval = PointerGetDatum(tuphdr);
+ }
+ else
+ {
+ /*
+ * We need only copy result into upper executor memory context.
+ * However, if we have a R/W expanded datum, we can just transfer
+ * its ownership out to the upper executor context.
+ */
+ estate->retval = SPI_datumTransfer(estate->retval,
+ false,
+ -1);
+ }
+ }
+ else
+ {
+ /* Convert composite datum to a HeapTuple and TupleDesc */
+ HeapTupleData tmptup;
+
+ retdesc = deconstruct_composite_datum(estate->retval, &tmptup);
+ rettup = &tmptup;
+
+ /* check rowtype compatibility */
+ tupmap = convert_tuples_by_position(retdesc,
+ tupdesc,
+ gettext_noop("returned record type does not match expected record type"));
+
+ /* it might need conversion */
+ if (tupmap)
+ rettup = execute_attr_map_tuple(rettup, tupmap);
+
+ /*
+ * Copy tuple to upper executor memory, as a tuple Datum. Make sure
+ * it is labeled with the caller-supplied tuple type.
+ */
+ estate->retval = PointerGetDatum(SPI_returntuple(rettup, tupdesc));
+
+ /* no need to free map, we're about to return anyway */
+
+ ReleaseTupleDesc(retdesc);
+ }
+}
+
+
+/* ----------
+ * plpgsql_exec_trigger Called by the call handler for
+ * trigger execution.
+ * ----------
+ */
+HeapTuple
+plpgsql_exec_trigger(PLpgSQL_function *func,
+ TriggerData *trigdata)
+{
+ PLpgSQL_execstate estate;
+ ErrorContextCallback plerrcontext;
+ int rc;
+ TupleDesc tupdesc;
+ PLpgSQL_rec *rec_new,
+ *rec_old;
+ HeapTuple rettup;
+
+ /*
+ * Setup the execution state
+ */
+ plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
+ estate.trigdata = trigdata;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_exec_error_callback;
+ plerrcontext.arg = &estate;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Make local execution copies of all the datums
+ */
+ estate.err_text = gettext_noop("during initialization of execution state");
+ copy_plpgsql_datums(&estate, func);
+
+ /*
+ * Put the OLD and NEW tuples into record variables
+ *
+ * We set up expanded records for both variables even though only one may
+ * have a value. This allows record references to succeed in functions
+ * that are used for multiple trigger types. For example, we might have a
+ * test like "if (TG_OP = 'INSERT' and NEW.foo = 'xyz')", which should
+ * work regardless of the current trigger type. If a value is actually
+ * fetched from an unsupplied tuple, it will read as NULL.
+ */
+ tupdesc = RelationGetDescr(trigdata->tg_relation);
+
+ rec_new = (PLpgSQL_rec *) (estate.datums[func->new_varno]);
+ rec_old = (PLpgSQL_rec *) (estate.datums[func->old_varno]);
+
+ rec_new->erh = make_expanded_record_from_tupdesc(tupdesc,
+ estate.datum_context);
+ rec_old->erh = make_expanded_record_from_exprecord(rec_new->erh,
+ estate.datum_context);
+
+ if (!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ {
+ /*
+ * Per-statement triggers don't use OLD/NEW variables
+ */
+ }
+ else if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ {
+ expanded_record_set_tuple(rec_new->erh, trigdata->tg_trigtuple,
+ false, false);
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ expanded_record_set_tuple(rec_new->erh, trigdata->tg_newtuple,
+ false, false);
+ expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple,
+ false, false);
+
+ /*
+ * In BEFORE trigger, stored generated columns are not computed yet,
+ * so make them null in the NEW row. (Only needed in UPDATE branch;
+ * in the INSERT case, they are already null, but in UPDATE, the field
+ * still contains the old value.) Alternatively, we could construct a
+ * whole new row structure without the generated columns, but this way
+ * seems more efficient and potentially less confusing.
+ */
+ if (tupdesc->constr && tupdesc->constr->has_generated_stored &&
+ TRIGGER_FIRED_BEFORE(trigdata->tg_event))
+ {
+ for (int i = 0; i < tupdesc->natts; i++)
+ if (TupleDescAttr(tupdesc, i)->attgenerated == ATTRIBUTE_GENERATED_STORED)
+ expanded_record_set_field_internal(rec_new->erh,
+ i + 1,
+ (Datum) 0,
+ true, /* isnull */
+ false, false);
+ }
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ {
+ expanded_record_set_tuple(rec_old->erh, trigdata->tg_trigtuple,
+ false, false);
+ }
+ else
+ elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, or UPDATE");
+
+ /* Make transition tables visible to this SPI connection */
+ rc = SPI_register_trigger_data(trigdata);
+ Assert(rc >= 0);
+
+ estate.err_text = gettext_noop("during function entry");
+
+ /*
+ * Set the magic variable FOUND to false
+ */
+ exec_set_found(&estate, false);
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
+ ((*plpgsql_plugin_ptr)->func_beg) (&estate, func);
+
+ /*
+ * Now call the toplevel block of statements
+ */
+ estate.err_text = NULL;
+ rc = exec_toplevel_block(&estate, func->action);
+ if (rc != PLPGSQL_RC_RETURN)
+ {
+ estate.err_text = NULL;
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of trigger procedure without RETURN")));
+ }
+
+ estate.err_text = gettext_noop("during function exit");
+
+ if (estate.retisset)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("trigger procedure cannot return a set")));
+
+ /*
+ * Check that the returned tuple structure has the same attributes, the
+ * relation that fired the trigger has. A per-statement trigger always
+ * needs to return NULL, so we ignore any return value the function itself
+ * produces (XXX: is this a good idea?)
+ *
+ * XXX This way it is possible, that the trigger returns a tuple where
+ * attributes don't have the correct atttypmod's length. It's up to the
+ * trigger's programmer to ensure that this doesn't happen. Jan
+ */
+ if (estate.retisnull || !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ rettup = NULL;
+ else
+ {
+ TupleDesc retdesc;
+ TupleConversionMap *tupmap;
+
+ /* We assume exec_stmt_return verified that result is composite */
+ Assert(type_is_rowtype(estate.rettype));
+
+ /* We can special-case expanded records for speed */
+ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(estate.retval)))
+ {
+ ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(estate.retval);
+
+ Assert(erh->er_magic == ER_MAGIC);
+
+ /* Extract HeapTuple and TupleDesc */
+ rettup = expanded_record_get_tuple(erh);
+ Assert(rettup);
+ retdesc = expanded_record_get_tupdesc(erh);
+
+ if (retdesc != RelationGetDescr(trigdata->tg_relation))
+ {
+ /* check rowtype compatibility */
+ tupmap = convert_tuples_by_position(retdesc,
+ RelationGetDescr(trigdata->tg_relation),
+ gettext_noop("returned row structure does not match the structure of the triggering table"));
+ /* it might need conversion */
+ if (tupmap)
+ rettup = execute_attr_map_tuple(rettup, tupmap);
+ /* no need to free map, we're about to return anyway */
+ }
+
+ /*
+ * Copy tuple to upper executor memory. But if user just did
+ * "return new" or "return old" without changing anything, there's
+ * no need to copy; we can return the original tuple (which will
+ * save a few cycles in trigger.c as well as here).
+ */
+ if (rettup != trigdata->tg_newtuple &&
+ rettup != trigdata->tg_trigtuple)
+ rettup = SPI_copytuple(rettup);
+ }
+ else
+ {
+ /* Convert composite datum to a HeapTuple and TupleDesc */
+ HeapTupleData tmptup;
+
+ retdesc = deconstruct_composite_datum(estate.retval, &tmptup);
+ rettup = &tmptup;
+
+ /* check rowtype compatibility */
+ tupmap = convert_tuples_by_position(retdesc,
+ RelationGetDescr(trigdata->tg_relation),
+ gettext_noop("returned row structure does not match the structure of the triggering table"));
+ /* it might need conversion */
+ if (tupmap)
+ rettup = execute_attr_map_tuple(rettup, tupmap);
+
+ ReleaseTupleDesc(retdesc);
+ /* no need to free map, we're about to return anyway */
+
+ /* Copy tuple to upper executor memory */
+ rettup = SPI_copytuple(rettup);
+ }
+ }
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
+ ((*plpgsql_plugin_ptr)->func_end) (&estate, func);
+
+ /* Clean up any leftover temporary memory */
+ plpgsql_destroy_econtext(&estate);
+ exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+
+ /*
+ * Return the trigger's result
+ */
+ return rettup;
+}
+
+/* ----------
+ * plpgsql_exec_event_trigger Called by the call handler for
+ * event trigger execution.
+ * ----------
+ */
+void
+plpgsql_exec_event_trigger(PLpgSQL_function *func, EventTriggerData *trigdata)
+{
+ PLpgSQL_execstate estate;
+ ErrorContextCallback plerrcontext;
+ int rc;
+
+ /*
+ * Setup the execution state
+ */
+ plpgsql_estate_setup(&estate, func, NULL, NULL, NULL);
+ estate.evtrigdata = trigdata;
+
+ /*
+ * Setup error traceback support for ereport()
+ */
+ plerrcontext.callback = plpgsql_exec_error_callback;
+ plerrcontext.arg = &estate;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * Make local execution copies of all the datums
+ */
+ estate.err_text = gettext_noop("during initialization of execution state");
+ copy_plpgsql_datums(&estate, func);
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_beg)
+ ((*plpgsql_plugin_ptr)->func_beg) (&estate, func);
+
+ /*
+ * Now call the toplevel block of statements
+ */
+ estate.err_text = NULL;
+ rc = exec_toplevel_block(&estate, func->action);
+ if (rc != PLPGSQL_RC_RETURN)
+ {
+ estate.err_text = NULL;
+ ereport(ERROR,
+ (errcode(ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT),
+ errmsg("control reached end of trigger procedure without RETURN")));
+ }
+
+ estate.err_text = gettext_noop("during function exit");
+
+ /*
+ * Let the instrumentation plugin peek at this function
+ */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->func_end)
+ ((*plpgsql_plugin_ptr)->func_end) (&estate, func);
+
+ /* Clean up any leftover temporary memory */
+ plpgsql_destroy_econtext(&estate);
+ exec_eval_cleanup(&estate);
+ /* stmt_mcontext will be destroyed when function's main context is */
+
+ /*
+ * Pop the error context stack
+ */
+ error_context_stack = plerrcontext.previous;
+}
+
+/*
+ * error context callback to let us supply a call-stack traceback
+ */
+static void
+plpgsql_exec_error_callback(void *arg)
+{
+ PLpgSQL_execstate *estate = (PLpgSQL_execstate *) arg;
+ int err_lineno;
+
+ /*
+ * If err_var is set, report the variable's declaration line number.
+ * Otherwise, if err_stmt is set, report the err_stmt's line number. When
+ * err_stmt is not set, we're in function entry/exit, or some such place
+ * not attached to a specific line number.
+ */
+ if (estate->err_var != NULL)
+ err_lineno = estate->err_var->lineno;
+ else if (estate->err_stmt != NULL)
+ err_lineno = estate->err_stmt->lineno;
+ else
+ err_lineno = 0;
+
+ if (estate->err_text != NULL)
+ {
+ /*
+ * We don't expend the cycles to run gettext() on err_text unless we
+ * actually need it. Therefore, places that set up err_text should
+ * use gettext_noop() to ensure the strings get recorded in the
+ * message dictionary.
+ */
+ if (err_lineno > 0)
+ {
+ /*
+ * translator: last %s is a phrase such as "during statement block
+ * local variable initialization"
+ */
+ errcontext("PL/pgSQL function %s line %d %s",
+ estate->func->fn_signature,
+ err_lineno,
+ _(estate->err_text));
+ }
+ else
+ {
+ /*
+ * translator: last %s is a phrase such as "while storing call
+ * arguments into local variables"
+ */
+ errcontext("PL/pgSQL function %s %s",
+ estate->func->fn_signature,
+ _(estate->err_text));
+ }
+ }
+ else if (estate->err_stmt != NULL && err_lineno > 0)
+ {
+ /* translator: last %s is a plpgsql statement type name */
+ errcontext("PL/pgSQL function %s line %d at %s",
+ estate->func->fn_signature,
+ err_lineno,
+ plpgsql_stmt_typename(estate->err_stmt));
+ }
+ else
+ errcontext("PL/pgSQL function %s",
+ estate->func->fn_signature);
+}
+
+
+/* ----------
+ * Support function for initializing local execution variables
+ * ----------
+ */
+static void
+copy_plpgsql_datums(PLpgSQL_execstate *estate,
+ PLpgSQL_function *func)
+{
+ int ndatums = estate->ndatums;
+ PLpgSQL_datum **indatums;
+ PLpgSQL_datum **outdatums;
+ char *workspace;
+ char *ws_next;
+ int i;
+
+ /* Allocate local datum-pointer array */
+ estate->datums = (PLpgSQL_datum **)
+ palloc(sizeof(PLpgSQL_datum *) * ndatums);
+
+ /*
+ * To reduce palloc overhead, we make a single palloc request for all the
+ * space needed for locally-instantiated datums.
+ */
+ workspace = palloc(func->copiable_size);
+ ws_next = workspace;
+
+ /* Fill datum-pointer array, copying datums into workspace as needed */
+ indatums = func->datums;
+ outdatums = estate->datums;
+ for (i = 0; i < ndatums; i++)
+ {
+ PLpgSQL_datum *indatum = indatums[i];
+ PLpgSQL_datum *outdatum;
+
+ /* This must agree with plpgsql_finish_datums on what is copiable */
+ switch (indatum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ outdatum = (PLpgSQL_datum *) ws_next;
+ memcpy(outdatum, indatum, sizeof(PLpgSQL_var));
+ ws_next += MAXALIGN(sizeof(PLpgSQL_var));
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ outdatum = (PLpgSQL_datum *) ws_next;
+ memcpy(outdatum, indatum, sizeof(PLpgSQL_rec));
+ ws_next += MAXALIGN(sizeof(PLpgSQL_rec));
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ case PLPGSQL_DTYPE_RECFIELD:
+
+ /*
+ * These datum records are read-only at runtime, so no need to
+ * copy them (well, RECFIELD contains cached data, but we'd
+ * just as soon centralize the caching anyway).
+ */
+ outdatum = indatum;
+ break;
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", indatum->dtype);
+ outdatum = NULL; /* keep compiler quiet */
+ break;
+ }
+
+ outdatums[i] = outdatum;
+ }
+
+ Assert(ws_next == workspace + func->copiable_size);
+}
+
+/*
+ * If the variable has an armed "promise", compute the promised value
+ * and assign it to the variable.
+ * The assignment automatically disarms the promise.
+ */
+static void
+plpgsql_fulfill_promise(PLpgSQL_execstate *estate,
+ PLpgSQL_var *var)
+{
+ MemoryContext oldcontext;
+
+ if (var->promise == PLPGSQL_PROMISE_NONE)
+ return; /* nothing to do */
+
+ /*
+ * This will typically be invoked in a short-lived context such as the
+ * mcontext. We must create variable values in the estate's datum
+ * context. This quick-and-dirty solution risks leaking some additional
+ * cruft there, but since any one promise is honored at most once per
+ * function call, it's probably not worth being more careful.
+ */
+ oldcontext = MemoryContextSwitchTo(estate->datum_context);
+
+ switch (var->promise)
+ {
+ case PLPGSQL_PROMISE_TG_NAME:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ assign_simple_var(estate, var,
+ DirectFunctionCall1(namein,
+ CStringGetDatum(estate->trigdata->tg_trigger->tgname)),
+ false, true);
+ break;
+
+ case PLPGSQL_PROMISE_TG_WHEN:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ if (TRIGGER_FIRED_BEFORE(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "BEFORE");
+ else if (TRIGGER_FIRED_AFTER(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "AFTER");
+ else if (TRIGGER_FIRED_INSTEAD(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "INSTEAD OF");
+ else
+ elog(ERROR, "unrecognized trigger execution time: not BEFORE, AFTER, or INSTEAD OF");
+ break;
+
+ case PLPGSQL_PROMISE_TG_LEVEL:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ if (TRIGGER_FIRED_FOR_ROW(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "ROW");
+ else if (TRIGGER_FIRED_FOR_STATEMENT(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "STATEMENT");
+ else
+ elog(ERROR, "unrecognized trigger event type: not ROW or STATEMENT");
+ break;
+
+ case PLPGSQL_PROMISE_TG_OP:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ if (TRIGGER_FIRED_BY_INSERT(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "INSERT");
+ else if (TRIGGER_FIRED_BY_UPDATE(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "UPDATE");
+ else if (TRIGGER_FIRED_BY_DELETE(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "DELETE");
+ else if (TRIGGER_FIRED_BY_TRUNCATE(estate->trigdata->tg_event))
+ assign_text_var(estate, var, "TRUNCATE");
+ else
+ elog(ERROR, "unrecognized trigger action: not INSERT, DELETE, UPDATE, or TRUNCATE");
+ break;
+
+ case PLPGSQL_PROMISE_TG_RELID:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ assign_simple_var(estate, var,
+ ObjectIdGetDatum(estate->trigdata->tg_relation->rd_id),
+ false, false);
+ break;
+
+ case PLPGSQL_PROMISE_TG_TABLE_NAME:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ assign_simple_var(estate, var,
+ DirectFunctionCall1(namein,
+ CStringGetDatum(RelationGetRelationName(estate->trigdata->tg_relation))),
+ false, true);
+ break;
+
+ case PLPGSQL_PROMISE_TG_TABLE_SCHEMA:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ assign_simple_var(estate, var,
+ DirectFunctionCall1(namein,
+ CStringGetDatum(get_namespace_name(RelationGetNamespace(estate->trigdata->tg_relation)))),
+ false, true);
+ break;
+
+ case PLPGSQL_PROMISE_TG_NARGS:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ assign_simple_var(estate, var,
+ Int16GetDatum(estate->trigdata->tg_trigger->tgnargs),
+ false, false);
+ break;
+
+ case PLPGSQL_PROMISE_TG_ARGV:
+ if (estate->trigdata == NULL)
+ elog(ERROR, "trigger promise is not in a trigger function");
+ if (estate->trigdata->tg_trigger->tgnargs > 0)
+ {
+ /*
+ * For historical reasons, tg_argv[] subscripts start at zero
+ * not one. So we can't use construct_array().
+ */
+ int nelems = estate->trigdata->tg_trigger->tgnargs;
+ Datum *elems;
+ int dims[1];
+ int lbs[1];
+ int i;
+
+ elems = palloc(sizeof(Datum) * nelems);
+ for (i = 0; i < nelems; i++)
+ elems[i] = CStringGetTextDatum(estate->trigdata->tg_trigger->tgargs[i]);
+ dims[0] = nelems;
+ lbs[0] = 0;
+
+ assign_simple_var(estate, var,
+ PointerGetDatum(construct_md_array(elems, NULL,
+ 1, dims, lbs,
+ TEXTOID,
+ -1, false, TYPALIGN_INT)),
+ false, true);
+ }
+ else
+ {
+ assign_simple_var(estate, var, (Datum) 0, true, false);
+ }
+ break;
+
+ case PLPGSQL_PROMISE_TG_EVENT:
+ if (estate->evtrigdata == NULL)
+ elog(ERROR, "event trigger promise is not in an event trigger function");
+ assign_text_var(estate, var, estate->evtrigdata->event);
+ break;
+
+ case PLPGSQL_PROMISE_TG_TAG:
+ if (estate->evtrigdata == NULL)
+ elog(ERROR, "event trigger promise is not in an event trigger function");
+ assign_text_var(estate, var, GetCommandTagName(estate->evtrigdata->tag));
+ break;
+
+ default:
+ elog(ERROR, "unrecognized promise type: %d", var->promise);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+}
+
+/*
+ * Create a memory context for statement-lifespan variables, if we don't
+ * have one already. It will be a child of stmt_mcontext_parent, which is
+ * either the function's main context or a pushed-down outer stmt_mcontext.
+ */
+static MemoryContext
+get_stmt_mcontext(PLpgSQL_execstate *estate)
+{
+ if (estate->stmt_mcontext == NULL)
+ {
+ estate->stmt_mcontext =
+ AllocSetContextCreate(estate->stmt_mcontext_parent,
+ "PLpgSQL per-statement data",
+ ALLOCSET_DEFAULT_SIZES);
+ }
+ return estate->stmt_mcontext;
+}
+
+/*
+ * Push down the current stmt_mcontext so that called statements won't use it.
+ * This is needed by statements that have statement-lifespan data and need to
+ * preserve it across some inner statements. The caller should eventually do
+ * pop_stmt_mcontext().
+ */
+static void
+push_stmt_mcontext(PLpgSQL_execstate *estate)
+{
+ /* Should have done get_stmt_mcontext() first */
+ Assert(estate->stmt_mcontext != NULL);
+ /* Assert we've not messed up the stack linkage */
+ Assert(MemoryContextGetParent(estate->stmt_mcontext) == estate->stmt_mcontext_parent);
+ /* Push it down to become the parent of any nested stmt mcontext */
+ estate->stmt_mcontext_parent = estate->stmt_mcontext;
+ /* And make it not available for use directly */
+ estate->stmt_mcontext = NULL;
+}
+
+/*
+ * Undo push_stmt_mcontext(). We assume this is done just before or after
+ * resetting the caller's stmt_mcontext; since that action will also delete
+ * any child contexts, there's no need to explicitly delete whatever context
+ * might currently be estate->stmt_mcontext.
+ */
+static void
+pop_stmt_mcontext(PLpgSQL_execstate *estate)
+{
+ /* We need only pop the stack */
+ estate->stmt_mcontext = estate->stmt_mcontext_parent;
+ estate->stmt_mcontext_parent = MemoryContextGetParent(estate->stmt_mcontext);
+}
+
+
+/*
+ * Subroutine for exec_stmt_block: does any condition in the condition list
+ * match the current exception?
+ */
+static bool
+exception_matches_conditions(ErrorData *edata, PLpgSQL_condition *cond)
+{
+ for (; cond != NULL; cond = cond->next)
+ {
+ int sqlerrstate = cond->sqlerrstate;
+
+ /*
+ * OTHERS matches everything *except* query-canceled and
+ * assert-failure. If you're foolish enough, you can match those
+ * explicitly.
+ */
+ if (sqlerrstate == 0)
+ {
+ if (edata->sqlerrcode != ERRCODE_QUERY_CANCELED &&
+ edata->sqlerrcode != ERRCODE_ASSERT_FAILURE)
+ return true;
+ }
+ /* Exact match? */
+ else if (edata->sqlerrcode == sqlerrstate)
+ return true;
+ /* Category match? */
+ else if (ERRCODE_IS_CATEGORY(sqlerrstate) &&
+ ERRCODE_TO_CATEGORY(edata->sqlerrcode) == sqlerrstate)
+ return true;
+ }
+ return false;
+}
+
+
+/* ----------
+ * exec_toplevel_block Execute the toplevel block
+ *
+ * This is intentionally equivalent to executing exec_stmts() with a
+ * list consisting of the one statement. One tiny difference is that
+ * we do not bother to save the entry value of estate->err_stmt;
+ * that's assumed to be NULL.
+ * ----------
+ */
+static int
+exec_toplevel_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
+{
+ int rc;
+
+ estate->err_stmt = (PLpgSQL_stmt *) block;
+
+ /* Let the plugin know that we are about to execute this statement */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
+ ((*plpgsql_plugin_ptr)->stmt_beg) (estate, (PLpgSQL_stmt *) block);
+
+ CHECK_FOR_INTERRUPTS();
+
+ rc = exec_stmt_block(estate, block);
+
+ /* Let the plugin know that we have finished executing this statement */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
+ ((*plpgsql_plugin_ptr)->stmt_end) (estate, (PLpgSQL_stmt *) block);
+
+ estate->err_stmt = NULL;
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_block Execute a block of statements
+ * ----------
+ */
+static int
+exec_stmt_block(PLpgSQL_execstate *estate, PLpgSQL_stmt_block *block)
+{
+ volatile int rc = -1;
+ int i;
+
+ /*
+ * First initialize all variables declared in this block
+ */
+ estate->err_text = gettext_noop("during statement block local variable initialization");
+
+ for (i = 0; i < block->n_initvars; i++)
+ {
+ int n = block->initvarnos[i];
+ PLpgSQL_datum *datum = estate->datums[n];
+
+ /*
+ * The set of dtypes handled here must match plpgsql_add_initdatums().
+ *
+ * Note that we currently don't support promise datums within blocks,
+ * only at a function's outermost scope, so we needn't handle those
+ * here.
+ *
+ * Since RECFIELD isn't a supported case either, it's okay to cast the
+ * PLpgSQL_datum to PLpgSQL_variable.
+ */
+ estate->err_var = (PLpgSQL_variable *) datum;
+
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+ /*
+ * Free any old value, in case re-entering block, and
+ * initialize to NULL
+ */
+ assign_simple_var(estate, var, (Datum) 0, true, false);
+
+ if (var->default_val == NULL)
+ {
+ /*
+ * If needed, give the datatype a chance to reject
+ * NULLs, by assigning a NULL to the variable. We
+ * claim the value is of type UNKNOWN, not the var's
+ * datatype, else coercion will be skipped.
+ */
+ if (var->datatype->typtype == TYPTYPE_DOMAIN)
+ exec_assign_value(estate,
+ (PLpgSQL_datum *) var,
+ (Datum) 0,
+ true,
+ UNKNOWNOID,
+ -1);
+
+ /* parser should have rejected NOT NULL */
+ Assert(!var->notnull);
+ }
+ else
+ {
+ exec_assign_expr(estate, (PLpgSQL_datum *) var,
+ var->default_val);
+ }
+ }
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ /*
+ * Deletion of any existing object will be handled during
+ * the assignments below, and in some cases it's more
+ * efficient for us not to get rid of it beforehand.
+ */
+ if (rec->default_val == NULL)
+ {
+ /*
+ * If needed, give the datatype a chance to reject
+ * NULLs, by assigning a NULL to the variable.
+ */
+ exec_move_row(estate, (PLpgSQL_variable *) rec,
+ NULL, NULL);
+
+ /* parser should have rejected NOT NULL */
+ Assert(!rec->notnull);
+ }
+ else
+ {
+ exec_assign_expr(estate, (PLpgSQL_datum *) rec,
+ rec->default_val);
+ }
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ }
+ }
+
+ estate->err_var = NULL;
+
+ if (block->exceptions)
+ {
+ /*
+ * Execute the statements in the block's body inside a sub-transaction
+ */
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ ExprContext *old_eval_econtext = estate->eval_econtext;
+ ErrorData *save_cur_error = estate->cur_error;
+ MemoryContext stmt_mcontext;
+
+ estate->err_text = gettext_noop("during statement block entry");
+
+ /*
+ * We will need a stmt_mcontext to hold the error data if an error
+ * occurs. It seems best to force it to exist before entering the
+ * subtransaction, so that we reduce the risk of out-of-memory during
+ * error recovery, and because this greatly simplifies restoring the
+ * stmt_mcontext stack to the correct state after an error. We can
+ * ameliorate the cost of this by allowing the called statements to
+ * use this mcontext too; so we don't push it down here.
+ */
+ stmt_mcontext = get_stmt_mcontext(estate);
+
+ BeginInternalSubTransaction(NULL);
+ /* Want to run statements inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ PG_TRY();
+ {
+ /*
+ * We need to run the block's statements with a new eval_econtext
+ * that belongs to the current subtransaction; if we try to use
+ * the outer econtext then ExprContext shutdown callbacks will be
+ * called at the wrong times.
+ */
+ plpgsql_create_econtext(estate);
+
+ estate->err_text = NULL;
+
+ /* Run the block's statements */
+ rc = exec_stmts(estate, block->body);
+
+ estate->err_text = gettext_noop("during statement block exit");
+
+ /*
+ * If the block ended with RETURN, we may need to copy the return
+ * value out of the subtransaction eval_context. We can avoid a
+ * physical copy if the value happens to be a R/W expanded object.
+ */
+ if (rc == PLPGSQL_RC_RETURN &&
+ !estate->retisset &&
+ !estate->retisnull)
+ {
+ int16 resTypLen;
+ bool resTypByVal;
+
+ get_typlenbyval(estate->rettype, &resTypLen, &resTypByVal);
+ estate->retval = datumTransfer(estate->retval,
+ resTypByVal, resTypLen);
+ }
+
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Assert that the stmt_mcontext stack is unchanged */
+ Assert(stmt_mcontext == estate->stmt_mcontext);
+
+ /*
+ * Revert to outer eval_econtext. (The inner one was
+ * automatically cleaned up during subxact exit.)
+ */
+ estate->eval_econtext = old_eval_econtext;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+ ListCell *e;
+
+ estate->err_text = gettext_noop("during exception cleanup");
+
+ /* Save error info in our stmt_mcontext */
+ MemoryContextSwitchTo(stmt_mcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /*
+ * Set up the stmt_mcontext stack as though we had restored our
+ * previous state and then done push_stmt_mcontext(). The push is
+ * needed so that statements in the exception handler won't
+ * clobber the error data that's in our stmt_mcontext.
+ */
+ estate->stmt_mcontext_parent = stmt_mcontext;
+ estate->stmt_mcontext = NULL;
+
+ /*
+ * Now we can delete any nested stmt_mcontexts that might have
+ * been created as children of ours. (Note: we do not immediately
+ * release any statement-lifespan data that might have been left
+ * behind in stmt_mcontext itself. We could attempt that by doing
+ * a MemoryContextReset on it before collecting the error data
+ * above, but it seems too risky to do any significant amount of
+ * work before collecting the error.)
+ */
+ MemoryContextDeleteChildren(stmt_mcontext);
+
+ /* Revert to outer eval_econtext */
+ estate->eval_econtext = old_eval_econtext;
+
+ /*
+ * Must clean up the econtext too. However, any tuple table made
+ * in the subxact will have been thrown away by SPI during subxact
+ * abort, so we don't need to (and mustn't try to) free the
+ * eval_tuptable.
+ */
+ estate->eval_tuptable = NULL;
+ exec_eval_cleanup(estate);
+
+ /* Look for a matching exception handler */
+ foreach(e, block->exceptions->exc_list)
+ {
+ PLpgSQL_exception *exception = (PLpgSQL_exception *) lfirst(e);
+
+ if (exception_matches_conditions(edata, exception->conditions))
+ {
+ /*
+ * Initialize the magic SQLSTATE and SQLERRM variables for
+ * the exception block; this also frees values from any
+ * prior use of the same exception. We needn't do this
+ * until we have found a matching exception.
+ */
+ PLpgSQL_var *state_var;
+ PLpgSQL_var *errm_var;
+
+ state_var = (PLpgSQL_var *)
+ estate->datums[block->exceptions->sqlstate_varno];
+ errm_var = (PLpgSQL_var *)
+ estate->datums[block->exceptions->sqlerrm_varno];
+
+ assign_text_var(estate, state_var,
+ unpack_sql_state(edata->sqlerrcode));
+ assign_text_var(estate, errm_var, edata->message);
+
+ /*
+ * Also set up cur_error so the error data is accessible
+ * inside the handler.
+ */
+ estate->cur_error = edata;
+
+ estate->err_text = NULL;
+
+ rc = exec_stmts(estate, exception->action);
+
+ break;
+ }
+ }
+
+ /*
+ * Restore previous state of cur_error, whether or not we executed
+ * a handler. This is needed in case an error got thrown from
+ * some inner block's exception handler.
+ */
+ estate->cur_error = save_cur_error;
+
+ /* If no match found, re-throw the error */
+ if (e == NULL)
+ ReThrowError(edata);
+
+ /* Restore stmt_mcontext stack and release the error data */
+ pop_stmt_mcontext(estate);
+ MemoryContextReset(stmt_mcontext);
+ }
+ PG_END_TRY();
+
+ Assert(save_cur_error == estate->cur_error);
+ }
+ else
+ {
+ /*
+ * Just execute the statements in the block's body
+ */
+ estate->err_text = NULL;
+
+ rc = exec_stmts(estate, block->body);
+ }
+
+ estate->err_text = NULL;
+
+ /*
+ * Handle the return code. This is intentionally different from
+ * LOOP_RC_PROCESSING(): CONTINUE never matches a block, and EXIT matches
+ * a block only if there is a label match.
+ */
+ switch (rc)
+ {
+ case PLPGSQL_RC_OK:
+ case PLPGSQL_RC_RETURN:
+ case PLPGSQL_RC_CONTINUE:
+ return rc;
+
+ case PLPGSQL_RC_EXIT:
+ if (estate->exitlabel == NULL)
+ return PLPGSQL_RC_EXIT;
+ if (block->label == NULL)
+ return PLPGSQL_RC_EXIT;
+ if (strcmp(block->label, estate->exitlabel) != 0)
+ return PLPGSQL_RC_EXIT;
+ estate->exitlabel = NULL;
+ return PLPGSQL_RC_OK;
+
+ default:
+ elog(ERROR, "unrecognized rc: %d", rc);
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmts Iterate over a list of statements
+ * as long as their return code is OK
+ * ----------
+ */
+static int
+exec_stmts(PLpgSQL_execstate *estate, List *stmts)
+{
+ PLpgSQL_stmt *save_estmt = estate->err_stmt;
+ ListCell *s;
+
+ if (stmts == NIL)
+ {
+ /*
+ * Ensure we do a CHECK_FOR_INTERRUPTS() even though there is no
+ * statement. This prevents hangup in a tight loop if, for instance,
+ * there is a LOOP construct with an empty body.
+ */
+ CHECK_FOR_INTERRUPTS();
+ return PLPGSQL_RC_OK;
+ }
+
+ foreach(s, stmts)
+ {
+ PLpgSQL_stmt *stmt = (PLpgSQL_stmt *) lfirst(s);
+ int rc;
+
+ estate->err_stmt = stmt;
+
+ /* Let the plugin know that we are about to execute this statement */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_beg)
+ ((*plpgsql_plugin_ptr)->stmt_beg) (estate, stmt);
+
+ CHECK_FOR_INTERRUPTS();
+
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_BLOCK:
+ rc = exec_stmt_block(estate, (PLpgSQL_stmt_block *) stmt);
+ break;
+
+ case PLPGSQL_STMT_ASSIGN:
+ rc = exec_stmt_assign(estate, (PLpgSQL_stmt_assign *) stmt);
+ break;
+
+ case PLPGSQL_STMT_PERFORM:
+ rc = exec_stmt_perform(estate, (PLpgSQL_stmt_perform *) stmt);
+ break;
+
+ case PLPGSQL_STMT_CALL:
+ rc = exec_stmt_call(estate, (PLpgSQL_stmt_call *) stmt);
+ break;
+
+ case PLPGSQL_STMT_GETDIAG:
+ rc = exec_stmt_getdiag(estate, (PLpgSQL_stmt_getdiag *) stmt);
+ break;
+
+ case PLPGSQL_STMT_IF:
+ rc = exec_stmt_if(estate, (PLpgSQL_stmt_if *) stmt);
+ break;
+
+ case PLPGSQL_STMT_CASE:
+ rc = exec_stmt_case(estate, (PLpgSQL_stmt_case *) stmt);
+ break;
+
+ case PLPGSQL_STMT_LOOP:
+ rc = exec_stmt_loop(estate, (PLpgSQL_stmt_loop *) stmt);
+ break;
+
+ case PLPGSQL_STMT_WHILE:
+ rc = exec_stmt_while(estate, (PLpgSQL_stmt_while *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FORI:
+ rc = exec_stmt_fori(estate, (PLpgSQL_stmt_fori *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FORS:
+ rc = exec_stmt_fors(estate, (PLpgSQL_stmt_fors *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FORC:
+ rc = exec_stmt_forc(estate, (PLpgSQL_stmt_forc *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FOREACH_A:
+ rc = exec_stmt_foreach_a(estate, (PLpgSQL_stmt_foreach_a *) stmt);
+ break;
+
+ case PLPGSQL_STMT_EXIT:
+ rc = exec_stmt_exit(estate, (PLpgSQL_stmt_exit *) stmt);
+ break;
+
+ case PLPGSQL_STMT_RETURN:
+ rc = exec_stmt_return(estate, (PLpgSQL_stmt_return *) stmt);
+ break;
+
+ case PLPGSQL_STMT_RETURN_NEXT:
+ rc = exec_stmt_return_next(estate, (PLpgSQL_stmt_return_next *) stmt);
+ break;
+
+ case PLPGSQL_STMT_RETURN_QUERY:
+ rc = exec_stmt_return_query(estate, (PLpgSQL_stmt_return_query *) stmt);
+ break;
+
+ case PLPGSQL_STMT_RAISE:
+ rc = exec_stmt_raise(estate, (PLpgSQL_stmt_raise *) stmt);
+ break;
+
+ case PLPGSQL_STMT_ASSERT:
+ rc = exec_stmt_assert(estate, (PLpgSQL_stmt_assert *) stmt);
+ break;
+
+ case PLPGSQL_STMT_EXECSQL:
+ rc = exec_stmt_execsql(estate, (PLpgSQL_stmt_execsql *) stmt);
+ break;
+
+ case PLPGSQL_STMT_DYNEXECUTE:
+ rc = exec_stmt_dynexecute(estate, (PLpgSQL_stmt_dynexecute *) stmt);
+ break;
+
+ case PLPGSQL_STMT_DYNFORS:
+ rc = exec_stmt_dynfors(estate, (PLpgSQL_stmt_dynfors *) stmt);
+ break;
+
+ case PLPGSQL_STMT_OPEN:
+ rc = exec_stmt_open(estate, (PLpgSQL_stmt_open *) stmt);
+ break;
+
+ case PLPGSQL_STMT_FETCH:
+ rc = exec_stmt_fetch(estate, (PLpgSQL_stmt_fetch *) stmt);
+ break;
+
+ case PLPGSQL_STMT_CLOSE:
+ rc = exec_stmt_close(estate, (PLpgSQL_stmt_close *) stmt);
+ break;
+
+ case PLPGSQL_STMT_COMMIT:
+ rc = exec_stmt_commit(estate, (PLpgSQL_stmt_commit *) stmt);
+ break;
+
+ case PLPGSQL_STMT_ROLLBACK:
+ rc = exec_stmt_rollback(estate, (PLpgSQL_stmt_rollback *) stmt);
+ break;
+
+ default:
+ /* point err_stmt to parent, since this one seems corrupt */
+ estate->err_stmt = save_estmt;
+ elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ rc = -1; /* keep compiler quiet */
+ }
+
+ /* Let the plugin know that we have finished executing this statement */
+ if (*plpgsql_plugin_ptr && (*plpgsql_plugin_ptr)->stmt_end)
+ ((*plpgsql_plugin_ptr)->stmt_end) (estate, stmt);
+
+ if (rc != PLPGSQL_RC_OK)
+ {
+ estate->err_stmt = save_estmt;
+ return rc;
+ }
+ } /* end of loop over statements */
+
+ estate->err_stmt = save_estmt;
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_assign Evaluate an expression and
+ * put the result into a variable.
+ * ----------
+ */
+static int
+exec_stmt_assign(PLpgSQL_execstate *estate, PLpgSQL_stmt_assign *stmt)
+{
+ Assert(stmt->varno >= 0);
+
+ exec_assign_expr(estate, estate->datums[stmt->varno], stmt->expr);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_stmt_perform Evaluate query and discard result (but set
+ * FOUND depending on whether at least one row
+ * was returned).
+ * ----------
+ */
+static int
+exec_stmt_perform(PLpgSQL_execstate *estate, PLpgSQL_stmt_perform *stmt)
+{
+ PLpgSQL_expr *expr = stmt->expr;
+
+ (void) exec_run_select(estate, expr, 0, NULL);
+ exec_set_found(estate, (estate->eval_processed != 0));
+ exec_eval_cleanup(estate);
+
+ return PLPGSQL_RC_OK;
+}
+
+/*
+ * exec_stmt_call
+ *
+ * NOTE: this is used for both CALL and DO statements.
+ */
+static int
+exec_stmt_call(PLpgSQL_execstate *estate, PLpgSQL_stmt_call *stmt)
+{
+ PLpgSQL_expr *expr = stmt->expr;
+ LocalTransactionId before_lxid;
+ LocalTransactionId after_lxid;
+ ParamListInfo paramLI;
+ SPIExecuteOptions options;
+ int rc;
+
+ /*
+ * Make a plan if we don't have one already.
+ */
+ if (expr->plan == NULL)
+ exec_prepare_plan(estate, expr, 0);
+
+ /*
+ * A CALL or DO can never be a simple expression.
+ */
+ Assert(!expr->expr_simple_expr);
+
+ /*
+ * Also construct a DTYPE_ROW datum representing the plpgsql variables
+ * associated with the procedure's output arguments. Then we can use
+ * exec_move_row() to do the assignments.
+ */
+ if (stmt->is_call && stmt->target == NULL)
+ stmt->target = make_callstmt_target(estate, expr);
+
+ paramLI = setup_param_list(estate, expr);
+
+ before_lxid = MyProc->lxid;
+
+ /*
+ * If we have a procedure-lifespan resowner, use that to hold the refcount
+ * for the plan. This avoids refcount leakage complaints if the called
+ * procedure ends the current transaction.
+ *
+ * Also, tell SPI to allow non-atomic execution.
+ */
+ memset(&options, 0, sizeof(options));
+ options.params = paramLI;
+ options.read_only = estate->readonly_func;
+ options.allow_nonatomic = true;
+ options.owner = estate->procedure_resowner;
+
+ rc = SPI_execute_plan_extended(expr->plan, &options);
+
+ if (rc < 0)
+ elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
+ expr->query, SPI_result_code_string(rc));
+
+ after_lxid = MyProc->lxid;
+
+ if (before_lxid != after_lxid)
+ {
+ /*
+ * If we are in a new transaction after the call, we need to build new
+ * simple-expression infrastructure.
+ */
+ estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
+ plpgsql_create_econtext(estate);
+ }
+
+ /*
+ * Check result rowcount; if there's one row, assign procedure's output
+ * values back to the appropriate variables.
+ */
+ if (SPI_processed == 1)
+ {
+ SPITupleTable *tuptab = SPI_tuptable;
+
+ if (!stmt->is_call)
+ elog(ERROR, "DO statement returned a row");
+
+ exec_move_row(estate, stmt->target, tuptab->vals[0], tuptab->tupdesc);
+ }
+ else if (SPI_processed > 1)
+ elog(ERROR, "procedure call returned more than one row");
+
+ exec_eval_cleanup(estate);
+ SPI_freetuptable(SPI_tuptable);
+
+ return PLPGSQL_RC_OK;
+}
+
+/*
+ * We construct a DTYPE_ROW datum representing the plpgsql variables
+ * associated with the procedure's output arguments. Then we can use
+ * exec_move_row() to do the assignments.
+ */
+static PLpgSQL_variable *
+make_callstmt_target(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
+{
+ List *plansources;
+ CachedPlanSource *plansource;
+ CallStmt *stmt;
+ FuncExpr *funcexpr;
+ HeapTuple func_tuple;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ int numargs;
+ MemoryContext oldcontext;
+ PLpgSQL_row *row;
+ int nfields;
+ int i;
+
+ /* Use eval_mcontext for any cruft accumulated here */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ /*
+ * Get the parsed CallStmt, and look up the called procedure
+ */
+ plansources = SPI_plan_get_plan_sources(expr->plan);
+ if (list_length(plansources) != 1)
+ elog(ERROR, "query for CALL statement is not a CallStmt");
+ plansource = (CachedPlanSource *) linitial(plansources);
+ if (list_length(plansource->query_list) != 1)
+ elog(ERROR, "query for CALL statement is not a CallStmt");
+ stmt = (CallStmt *) linitial_node(Query,
+ plansource->query_list)->utilityStmt;
+ if (stmt == NULL || !IsA(stmt, CallStmt))
+ elog(ERROR, "query for CALL statement is not a CallStmt");
+
+ funcexpr = stmt->funcexpr;
+
+ func_tuple = SearchSysCache1(PROCOID,
+ ObjectIdGetDatum(funcexpr->funcid));
+ if (!HeapTupleIsValid(func_tuple))
+ elog(ERROR, "cache lookup failed for function %u",
+ funcexpr->funcid);
+
+ /*
+ * Get the argument names and modes, so that we can deliver on-point error
+ * messages when something is wrong.
+ */
+ numargs = get_func_arg_info(func_tuple, &argtypes, &argnames, &argmodes);
+
+ ReleaseSysCache(func_tuple);
+
+ /*
+ * Begin constructing row Datum; keep it in fn_cxt so it's adequately
+ * long-lived.
+ */
+ MemoryContextSwitchTo(estate->func->fn_cxt);
+
+ row = (PLpgSQL_row *) palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = -1;
+ row->varnos = (int *) palloc(numargs * sizeof(int));
+
+ MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ /*
+ * Examine procedure's argument list. Each output arg position should be
+ * an unadorned plpgsql variable (Datum), which we can insert into the row
+ * Datum.
+ */
+ nfields = 0;
+ for (i = 0; i < numargs; i++)
+ {
+ if (argmodes &&
+ (argmodes[i] == PROARGMODE_INOUT ||
+ argmodes[i] == PROARGMODE_OUT))
+ {
+ Node *n = list_nth(stmt->outargs, nfields);
+
+ if (IsA(n, Param))
+ {
+ Param *param = (Param *) n;
+ int dno;
+
+ /* paramid is offset by 1 (see make_datum_param()) */
+ dno = param->paramid - 1;
+ /* must check assignability now, because grammar can't */
+ exec_check_assignable(estate, dno);
+ row->varnos[nfields++] = dno;
+ }
+ else
+ {
+ /* report error using parameter name, if available */
+ if (argnames && argnames[i] && argnames[i][0])
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("procedure parameter \"%s\" is an output parameter but corresponding argument is not writable",
+ argnames[i])));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("procedure parameter %d is an output parameter but corresponding argument is not writable",
+ i + 1)));
+ }
+ }
+ }
+
+ Assert(nfields == list_length(stmt->outargs));
+
+ row->nfields = nfields;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return (PLpgSQL_variable *) row;
+}
+
+/* ----------
+ * exec_stmt_getdiag Put internal PG information into
+ * specified variables.
+ * ----------
+ */
+static int
+exec_stmt_getdiag(PLpgSQL_execstate *estate, PLpgSQL_stmt_getdiag *stmt)
+{
+ ListCell *lc;
+
+ /*
+ * GET STACKED DIAGNOSTICS is only valid inside an exception handler.
+ *
+ * Note: we trust the grammar to have disallowed the relevant item kinds
+ * if not is_stacked, otherwise we'd dump core below.
+ */
+ if (stmt->is_stacked && estate->cur_error == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
+ errmsg("GET STACKED DIAGNOSTICS cannot be used outside an exception handler")));
+
+ foreach(lc, stmt->diag_items)
+ {
+ PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
+ PLpgSQL_datum *var = estate->datums[diag_item->target];
+
+ switch (diag_item->kind)
+ {
+ case PLPGSQL_GETDIAG_ROW_COUNT:
+ exec_assign_value(estate, var,
+ UInt64GetDatum(estate->eval_processed),
+ false, INT8OID, -1);
+ break;
+
+ case PLPGSQL_GETDIAG_ERROR_CONTEXT:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->context);
+ break;
+
+ case PLPGSQL_GETDIAG_ERROR_DETAIL:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->detail);
+ break;
+
+ case PLPGSQL_GETDIAG_ERROR_HINT:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->hint);
+ break;
+
+ case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
+ exec_assign_c_string(estate, var,
+ unpack_sql_state(estate->cur_error->sqlerrcode));
+ break;
+
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->column_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->constraint_name);
+ break;
+
+ case PLPGSQL_GETDIAG_DATATYPE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->datatype_name);
+ break;
+
+ case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->message);
+ break;
+
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->table_name);
+ break;
+
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ exec_assign_c_string(estate, var,
+ estate->cur_error->schema_name);
+ break;
+
+ case PLPGSQL_GETDIAG_CONTEXT:
+ {
+ char *contextstackstr;
+ MemoryContext oldcontext;
+
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ contextstackstr = GetErrorContextStack();
+ MemoryContextSwitchTo(oldcontext);
+
+ exec_assign_c_string(estate, var, contextstackstr);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized diagnostic item kind: %d",
+ diag_item->kind);
+ }
+ }
+
+ exec_eval_cleanup(estate);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_stmt_if Evaluate a bool expression and
+ * execute the true or false body
+ * conditionally.
+ * ----------
+ */
+static int
+exec_stmt_if(PLpgSQL_execstate *estate, PLpgSQL_stmt_if *stmt)
+{
+ bool value;
+ bool isnull;
+ ListCell *lc;
+
+ value = exec_eval_boolean(estate, stmt->cond, &isnull);
+ exec_eval_cleanup(estate);
+ if (!isnull && value)
+ return exec_stmts(estate, stmt->then_body);
+
+ foreach(lc, stmt->elsif_list)
+ {
+ PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(lc);
+
+ value = exec_eval_boolean(estate, elif->cond, &isnull);
+ exec_eval_cleanup(estate);
+ if (!isnull && value)
+ return exec_stmts(estate, elif->stmts);
+ }
+
+ return exec_stmts(estate, stmt->else_body);
+}
+
+
+/*-----------
+ * exec_stmt_case
+ *-----------
+ */
+static int
+exec_stmt_case(PLpgSQL_execstate *estate, PLpgSQL_stmt_case *stmt)
+{
+ PLpgSQL_var *t_var = NULL;
+ bool isnull;
+ ListCell *l;
+
+ if (stmt->t_expr != NULL)
+ {
+ /* simple case */
+ Datum t_val;
+ Oid t_typoid;
+ int32 t_typmod;
+
+ t_val = exec_eval_expr(estate, stmt->t_expr,
+ &isnull, &t_typoid, &t_typmod);
+
+ t_var = (PLpgSQL_var *) estate->datums[stmt->t_varno];
+
+ /*
+ * When expected datatype is different from real, change it. Note that
+ * what we're modifying here is an execution copy of the datum, so
+ * this doesn't affect the originally stored function parse tree. (In
+ * theory, if the expression datatype keeps changing during execution,
+ * this could cause a function-lifespan memory leak. Doesn't seem
+ * worth worrying about though.)
+ */
+ if (t_var->datatype->typoid != t_typoid ||
+ t_var->datatype->atttypmod != t_typmod)
+ t_var->datatype = plpgsql_build_datatype(t_typoid,
+ t_typmod,
+ estate->func->fn_input_collation,
+ NULL);
+
+ /* now we can assign to the variable */
+ exec_assign_value(estate,
+ (PLpgSQL_datum *) t_var,
+ t_val,
+ isnull,
+ t_typoid,
+ t_typmod);
+
+ exec_eval_cleanup(estate);
+ }
+
+ /* Now search for a successful WHEN clause */
+ foreach(l, stmt->case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ bool value;
+
+ value = exec_eval_boolean(estate, cwt->expr, &isnull);
+ exec_eval_cleanup(estate);
+ if (!isnull && value)
+ {
+ /* Found it */
+
+ /* We can now discard any value we had for the temp variable */
+ if (t_var != NULL)
+ assign_simple_var(estate, t_var, (Datum) 0, true, false);
+
+ /* Evaluate the statement(s), and we're done */
+ return exec_stmts(estate, cwt->stmts);
+ }
+ }
+
+ /* We can now discard any value we had for the temp variable */
+ if (t_var != NULL)
+ assign_simple_var(estate, t_var, (Datum) 0, true, false);
+
+ /* SQL2003 mandates this error if there was no ELSE clause */
+ if (!stmt->have_else)
+ ereport(ERROR,
+ (errcode(ERRCODE_CASE_NOT_FOUND),
+ errmsg("case not found"),
+ errhint("CASE statement is missing ELSE part.")));
+
+ /* Evaluate the ELSE statements, and we're done */
+ return exec_stmts(estate, stmt->else_stmts);
+}
+
+
+/* ----------
+ * exec_stmt_loop Loop over statements until
+ * an exit occurs.
+ * ----------
+ */
+static int
+exec_stmt_loop(PLpgSQL_execstate *estate, PLpgSQL_stmt_loop *stmt)
+{
+ int rc = PLPGSQL_RC_OK;
+
+ for (;;)
+ {
+ rc = exec_stmts(estate, stmt->body);
+
+ LOOP_RC_PROCESSING(stmt->label, break);
+ }
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_while Loop over statements as long
+ * as an expression evaluates to
+ * true or an exit occurs.
+ * ----------
+ */
+static int
+exec_stmt_while(PLpgSQL_execstate *estate, PLpgSQL_stmt_while *stmt)
+{
+ int rc = PLPGSQL_RC_OK;
+
+ for (;;)
+ {
+ bool value;
+ bool isnull;
+
+ value = exec_eval_boolean(estate, stmt->cond, &isnull);
+ exec_eval_cleanup(estate);
+
+ if (isnull || !value)
+ break;
+
+ rc = exec_stmts(estate, stmt->body);
+
+ LOOP_RC_PROCESSING(stmt->label, break);
+ }
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_fori Iterate an integer variable
+ * from a lower to an upper value
+ * incrementing or decrementing by the BY value
+ * ----------
+ */
+static int
+exec_stmt_fori(PLpgSQL_execstate *estate, PLpgSQL_stmt_fori *stmt)
+{
+ PLpgSQL_var *var;
+ Datum value;
+ bool isnull;
+ Oid valtype;
+ int32 valtypmod;
+ int32 loop_value;
+ int32 end_value;
+ int32 step_value;
+ bool found = false;
+ int rc = PLPGSQL_RC_OK;
+
+ var = (PLpgSQL_var *) (estate->datums[stmt->var->dno]);
+
+ /*
+ * Get the value of the lower bound
+ */
+ value = exec_eval_expr(estate, stmt->lower,
+ &isnull, &valtype, &valtypmod);
+ value = exec_cast_value(estate, value, &isnull,
+ valtype, valtypmod,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("lower bound of FOR loop cannot be null")));
+ loop_value = DatumGetInt32(value);
+ exec_eval_cleanup(estate);
+
+ /*
+ * Get the value of the upper bound
+ */
+ value = exec_eval_expr(estate, stmt->upper,
+ &isnull, &valtype, &valtypmod);
+ value = exec_cast_value(estate, value, &isnull,
+ valtype, valtypmod,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("upper bound of FOR loop cannot be null")));
+ end_value = DatumGetInt32(value);
+ exec_eval_cleanup(estate);
+
+ /*
+ * Get the step value
+ */
+ if (stmt->step)
+ {
+ value = exec_eval_expr(estate, stmt->step,
+ &isnull, &valtype, &valtypmod);
+ value = exec_cast_value(estate, value, &isnull,
+ valtype, valtypmod,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("BY value of FOR loop cannot be null")));
+ step_value = DatumGetInt32(value);
+ exec_eval_cleanup(estate);
+ if (step_value <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("BY value of FOR loop must be greater than zero")));
+ }
+ else
+ step_value = 1;
+
+ /*
+ * Now do the loop
+ */
+ for (;;)
+ {
+ /*
+ * Check against upper bound
+ */
+ if (stmt->reverse)
+ {
+ if (loop_value < end_value)
+ break;
+ }
+ else
+ {
+ if (loop_value > end_value)
+ break;
+ }
+
+ found = true; /* looped at least once */
+
+ /*
+ * Assign current value to loop var
+ */
+ assign_simple_var(estate, var, Int32GetDatum(loop_value), false, false);
+
+ /*
+ * Execute the statements
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ LOOP_RC_PROCESSING(stmt->label, break);
+
+ /*
+ * Increase/decrease loop value, unless it would overflow, in which
+ * case exit the loop.
+ */
+ if (stmt->reverse)
+ {
+ if (loop_value < (PG_INT32_MIN + step_value))
+ break;
+ loop_value -= step_value;
+ }
+ else
+ {
+ if (loop_value > (PG_INT32_MAX - step_value))
+ break;
+ loop_value += step_value;
+ }
+ }
+
+ /*
+ * Set the FOUND variable to indicate the result of executing the loop
+ * (namely, whether we looped one or more times). This must be set here so
+ * that it does not interfere with the value of the FOUND variable inside
+ * the loop processing itself.
+ */
+ exec_set_found(estate, found);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_fors Execute a query, assign each
+ * tuple to a record or row and
+ * execute a group of statements
+ * for it.
+ * ----------
+ */
+static int
+exec_stmt_fors(PLpgSQL_execstate *estate, PLpgSQL_stmt_fors *stmt)
+{
+ Portal portal;
+ int rc;
+
+ /*
+ * Open the implicit cursor for the statement using exec_run_select
+ */
+ exec_run_select(estate, stmt->query, 0, &portal);
+
+ /*
+ * Execute the loop
+ */
+ rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
+
+ /*
+ * Close the implicit cursor
+ */
+ SPI_cursor_close(portal);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_forc Execute a loop for each row from a cursor.
+ * ----------
+ */
+static int
+exec_stmt_forc(PLpgSQL_execstate *estate, PLpgSQL_stmt_forc *stmt)
+{
+ PLpgSQL_var *curvar;
+ MemoryContext stmt_mcontext = NULL;
+ char *curname = NULL;
+ PLpgSQL_expr *query;
+ ParamListInfo paramLI;
+ Portal portal;
+ int rc;
+
+ /* ----------
+ * Get the cursor variable and if it has an assigned name, check
+ * that it's not in use currently.
+ * ----------
+ */
+ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+ if (!curvar->isnull)
+ {
+ MemoryContext oldcontext;
+
+ /* We only need stmt_mcontext to hold the cursor name string */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+ curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SPI_cursor_find(curname) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_CURSOR),
+ errmsg("cursor \"%s\" already in use", curname)));
+ }
+
+ /* ----------
+ * Open the cursor just like an OPEN command
+ *
+ * Note: parser should already have checked that statement supplies
+ * args iff cursor needs them, but we check again to be safe.
+ * ----------
+ */
+ if (stmt->argquery != NULL)
+ {
+ /* ----------
+ * OPEN CURSOR with args. We fake a SELECT ... INTO ...
+ * statement to evaluate the args and put 'em into the
+ * internal row.
+ * ----------
+ */
+ PLpgSQL_stmt_execsql set_args;
+
+ if (curvar->cursor_explicit_argrow < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("arguments given for cursor without arguments")));
+
+ memset(&set_args, 0, sizeof(set_args));
+ set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
+ set_args.lineno = stmt->lineno;
+ set_args.sqlstmt = stmt->argquery;
+ set_args.into = true;
+ /* XXX historically this has not been STRICT */
+ set_args.target = (PLpgSQL_variable *)
+ (estate->datums[curvar->cursor_explicit_argrow]);
+
+ if (exec_stmt_execsql(estate, &set_args) != PLPGSQL_RC_OK)
+ elog(ERROR, "open cursor failed during argument processing");
+ }
+ else
+ {
+ if (curvar->cursor_explicit_argrow >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("arguments required for cursor")));
+ }
+
+ query = curvar->cursor_explicit_expr;
+ Assert(query);
+
+ if (query->plan == NULL)
+ exec_prepare_plan(estate, query, curvar->cursor_options);
+
+ /*
+ * Set up ParamListInfo for this query
+ */
+ paramLI = setup_param_list(estate, query);
+
+ /*
+ * Open the cursor (the paramlist will get copied into the portal)
+ */
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
+ if (portal == NULL)
+ elog(ERROR, "could not open cursor: %s",
+ SPI_result_code_string(SPI_result));
+
+ /*
+ * If cursor variable was NULL, store the generated portal name in it,
+ * after verifying it's okay to assign to.
+ */
+ if (curname == NULL)
+ {
+ exec_check_assignable(estate, stmt->curvar);
+ assign_text_var(estate, curvar, portal->name);
+ }
+
+ /*
+ * Clean up before entering exec_for_query
+ */
+ exec_eval_cleanup(estate);
+ if (stmt_mcontext)
+ MemoryContextReset(stmt_mcontext);
+
+ /*
+ * Execute the loop. We can't prefetch because the cursor is accessible
+ * to the user, for instance via UPDATE WHERE CURRENT OF within the loop.
+ */
+ rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, false);
+
+ /* ----------
+ * Close portal, and restore cursor variable if it was initially NULL.
+ * ----------
+ */
+ SPI_cursor_close(portal);
+
+ if (curname == NULL)
+ assign_simple_var(estate, curvar, (Datum) 0, true, false);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_foreach_a Loop over elements or slices of an array
+ *
+ * When looping over elements, the loop variable is the same type that the
+ * array stores (eg: integer), when looping through slices, the loop variable
+ * is an array of size and dimensions to match the size of the slice.
+ * ----------
+ */
+static int
+exec_stmt_foreach_a(PLpgSQL_execstate *estate, PLpgSQL_stmt_foreach_a *stmt)
+{
+ ArrayType *arr;
+ Oid arrtype;
+ int32 arrtypmod;
+ PLpgSQL_datum *loop_var;
+ Oid loop_var_elem_type;
+ bool found = false;
+ int rc = PLPGSQL_RC_OK;
+ MemoryContext stmt_mcontext;
+ MemoryContext oldcontext;
+ ArrayIterator array_iterator;
+ Oid iterator_result_type;
+ int32 iterator_result_typmod;
+ Datum value;
+ bool isnull;
+
+ /* get the value of the array expression */
+ value = exec_eval_expr(estate, stmt->expr, &isnull, &arrtype, &arrtypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("FOREACH expression must not be null")));
+
+ /*
+ * Do as much as possible of the code below in stmt_mcontext, to avoid any
+ * leaks from called subroutines. We need a private stmt_mcontext since
+ * we'll be calling arbitrary statement code.
+ */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ push_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+
+ /* check the type of the expression - must be an array */
+ if (!OidIsValid(get_element_type(arrtype)))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH expression must yield an array, not type %s",
+ format_type_be(arrtype))));
+
+ /*
+ * We must copy the array into stmt_mcontext, else it will disappear in
+ * exec_eval_cleanup. This is annoying, but cleanup will certainly happen
+ * while running the loop body, so we have little choice.
+ */
+ arr = DatumGetArrayTypePCopy(value);
+
+ /* Clean up any leftover temporary memory */
+ exec_eval_cleanup(estate);
+
+ /* Slice dimension must be less than or equal to array dimension */
+ if (stmt->slice < 0 || stmt->slice > ARR_NDIM(arr))
+ ereport(ERROR,
+ (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
+ errmsg("slice dimension (%d) is out of the valid range 0..%d",
+ stmt->slice, ARR_NDIM(arr))));
+
+ /* Set up the loop variable and see if it is of an array type */
+ loop_var = estate->datums[stmt->varno];
+ if (loop_var->dtype == PLPGSQL_DTYPE_REC ||
+ loop_var->dtype == PLPGSQL_DTYPE_ROW)
+ {
+ /*
+ * Record/row variable is certainly not of array type, and might not
+ * be initialized at all yet, so don't try to get its type
+ */
+ loop_var_elem_type = InvalidOid;
+ }
+ else
+ loop_var_elem_type = get_element_type(plpgsql_exec_get_datum_type(estate,
+ loop_var));
+
+ /*
+ * Sanity-check the loop variable type. We don't try very hard here, and
+ * should not be too picky since it's possible that exec_assign_value can
+ * coerce values of different types. But it seems worthwhile to complain
+ * if the array-ness of the loop variable is not right.
+ */
+ if (stmt->slice > 0 && loop_var_elem_type == InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH ... SLICE loop variable must be of an array type")));
+ if (stmt->slice == 0 && loop_var_elem_type != InvalidOid)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("FOREACH loop variable must not be of an array type")));
+
+ /* Create an iterator to step through the array */
+ array_iterator = array_create_iterator(arr, stmt->slice, NULL);
+
+ /* Identify iterator result type */
+ if (stmt->slice > 0)
+ {
+ /* When slicing, nominal type of result is same as array type */
+ iterator_result_type = arrtype;
+ iterator_result_typmod = arrtypmod;
+ }
+ else
+ {
+ /* Without slicing, results are individual array elements */
+ iterator_result_type = ARR_ELEMTYPE(arr);
+ iterator_result_typmod = arrtypmod;
+ }
+
+ /* Iterate over the array elements or slices */
+ while (array_iterate(array_iterator, &value, &isnull))
+ {
+ found = true; /* looped at least once */
+
+ /* exec_assign_value and exec_stmts must run in the main context */
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Assign current element/slice to the loop variable */
+ exec_assign_value(estate, loop_var, value, isnull,
+ iterator_result_type, iterator_result_typmod);
+
+ /* In slice case, value is temporary; must free it to avoid leakage */
+ if (stmt->slice > 0)
+ pfree(DatumGetPointer(value));
+
+ /*
+ * Execute the statements
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ LOOP_RC_PROCESSING(stmt->label, break);
+
+ MemoryContextSwitchTo(stmt_mcontext);
+ }
+
+ /* Restore memory context state */
+ MemoryContextSwitchTo(oldcontext);
+ pop_stmt_mcontext(estate);
+
+ /* Release temporary memory, including the array value */
+ MemoryContextReset(stmt_mcontext);
+
+ /*
+ * Set the FOUND variable to indicate the result of executing the loop
+ * (namely, whether we looped one or more times). This must be set here so
+ * that it does not interfere with the value of the FOUND variable inside
+ * the loop processing itself.
+ */
+ exec_set_found(estate, found);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_exit Implements EXIT and CONTINUE
+ *
+ * This begins the process of exiting / restarting a loop.
+ * ----------
+ */
+static int
+exec_stmt_exit(PLpgSQL_execstate *estate, PLpgSQL_stmt_exit *stmt)
+{
+ /*
+ * If the exit / continue has a condition, evaluate it
+ */
+ if (stmt->cond != NULL)
+ {
+ bool value;
+ bool isnull;
+
+ value = exec_eval_boolean(estate, stmt->cond, &isnull);
+ exec_eval_cleanup(estate);
+ if (isnull || value == false)
+ return PLPGSQL_RC_OK;
+ }
+
+ estate->exitlabel = stmt->label;
+ if (stmt->is_exit)
+ return PLPGSQL_RC_EXIT;
+ else
+ return PLPGSQL_RC_CONTINUE;
+}
+
+
+/* ----------
+ * exec_stmt_return Evaluate an expression and start
+ * returning from the function.
+ *
+ * Note: The result may be in the eval_mcontext. Therefore, we must not
+ * do exec_eval_cleanup while unwinding the control stack.
+ * ----------
+ */
+static int
+exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
+{
+ /*
+ * If processing a set-returning PL/pgSQL function, the final RETURN
+ * indicates that the function is finished producing tuples. The rest of
+ * the work will be done at the top level.
+ */
+ if (estate->retisset)
+ return PLPGSQL_RC_RETURN;
+
+ /* initialize for null result */
+ estate->retval = (Datum) 0;
+ estate->retisnull = true;
+ estate->rettype = InvalidOid;
+
+ /*
+ * Special case path when the RETURN expression is a simple variable
+ * reference; in particular, this path is always taken in functions with
+ * one or more OUT parameters.
+ *
+ * This special case is especially efficient for returning variables that
+ * have R/W expanded values: we can put the R/W pointer directly into
+ * estate->retval, leading to transferring the value to the caller's
+ * context cheaply. If we went through exec_eval_expr we'd end up with a
+ * R/O pointer. It's okay to skip MakeExpandedObjectReadOnly here since
+ * we know we won't need the variable's value within the function anymore.
+ */
+ if (stmt->retvarno >= 0)
+ {
+ PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
+
+ switch (retvar->dtype)
+ {
+ case PLPGSQL_DTYPE_PROMISE:
+ /* fulfill promise if needed, then handle like regular var */
+ plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
+
+ /* FALL THRU */
+
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+
+ estate->retval = var->value;
+ estate->retisnull = var->isnull;
+ estate->rettype = var->datatype->typoid;
+
+ /*
+ * A PLpgSQL_var could not be of composite type, so
+ * conversion must fail if retistuple. We throw a custom
+ * error mainly for consistency with historical behavior.
+ * For the same reason, we don't throw error if the result
+ * is NULL. (Note that plpgsql_exec_trigger assumes that
+ * any non-null result has been verified to be composite.)
+ */
+ if (estate->retistuple && !estate->retisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot return non-composite value from function returning composite type")));
+ }
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+
+ /* If record is empty, we return NULL not a row of nulls */
+ if (rec->erh && !ExpandedRecordIsEmpty(rec->erh))
+ {
+ estate->retval = ExpandedRecordGetDatum(rec->erh);
+ estate->retisnull = false;
+ estate->rettype = rec->rectypeid;
+ }
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+ int32 rettypmod;
+
+ /* We get here if there are multiple OUT parameters */
+ exec_eval_datum(estate,
+ (PLpgSQL_datum *) row,
+ &estate->rettype,
+ &rettypmod,
+ &estate->retval,
+ &estate->retisnull);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
+ }
+
+ return PLPGSQL_RC_RETURN;
+ }
+
+ if (stmt->expr != NULL)
+ {
+ int32 rettypmod;
+
+ estate->retval = exec_eval_expr(estate, stmt->expr,
+ &(estate->retisnull),
+ &(estate->rettype),
+ &rettypmod);
+
+ /*
+ * As in the DTYPE_VAR case above, throw a custom error if a non-null,
+ * non-composite value is returned in a function returning tuple.
+ */
+ if (estate->retistuple && !estate->retisnull &&
+ !type_is_rowtype(estate->rettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot return non-composite value from function returning composite type")));
+
+ return PLPGSQL_RC_RETURN;
+ }
+
+ /*
+ * Special hack for function returning VOID: instead of NULL, return a
+ * non-null VOID value. This is of dubious importance but is kept for
+ * backwards compatibility. We don't do it for procedures, though.
+ */
+ if (estate->fn_rettype == VOIDOID &&
+ estate->func->fn_prokind != PROKIND_PROCEDURE)
+ {
+ estate->retval = (Datum) 0;
+ estate->retisnull = false;
+ estate->rettype = VOIDOID;
+ }
+
+ return PLPGSQL_RC_RETURN;
+}
+
+/* ----------
+ * exec_stmt_return_next Evaluate an expression and add it to the
+ * list of tuples returned by the current
+ * SRF.
+ * ----------
+ */
+static int
+exec_stmt_return_next(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return_next *stmt)
+{
+ TupleDesc tupdesc;
+ int natts;
+ HeapTuple tuple;
+ MemoryContext oldcontext;
+
+ if (!estate->retisset)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use RETURN NEXT in a non-SETOF function")));
+
+ if (estate->tuple_store == NULL)
+ exec_init_tuple_store(estate);
+
+ /* tuple_store_desc will be filled by exec_init_tuple_store */
+ tupdesc = estate->tuple_store_desc;
+ natts = tupdesc->natts;
+
+ /*
+ * Special case path when the RETURN NEXT expression is a simple variable
+ * reference; in particular, this path is always taken in functions with
+ * one or more OUT parameters.
+ *
+ * Unlike exec_stmt_return, there's no special win here for R/W expanded
+ * values, since they'll have to get flattened to go into the tuplestore.
+ * Indeed, we'd better make them R/O to avoid any risk of the casting step
+ * changing them in-place.
+ */
+ if (stmt->retvarno >= 0)
+ {
+ PLpgSQL_datum *retvar = estate->datums[stmt->retvarno];
+
+ switch (retvar->dtype)
+ {
+ case PLPGSQL_DTYPE_PROMISE:
+ /* fulfill promise if needed, then handle like regular var */
+ plpgsql_fulfill_promise(estate, (PLpgSQL_var *) retvar);
+
+ /* FALL THRU */
+
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) retvar;
+ Datum retval = var->value;
+ bool isNull = var->isnull;
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, 0);
+
+ if (natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("wrong result type supplied in RETURN NEXT")));
+
+ /* let's be very paranoid about the cast step */
+ retval = MakeExpandedObjectReadOnly(retval,
+ isNull,
+ var->datatype->typlen);
+
+ /* coerce type if needed */
+ retval = exec_cast_value(estate,
+ retval,
+ &isNull,
+ var->datatype->typoid,
+ var->datatype->atttypmod,
+ attr->atttypid,
+ attr->atttypmod);
+
+ tuplestore_putvalues(estate->tuple_store, tupdesc,
+ &retval, &isNull);
+ }
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+ TupleDesc rec_tupdesc;
+ TupleConversionMap *tupmap;
+
+ /* If rec is null, try to convert it to a row of nulls */
+ if (rec->erh == NULL)
+ instantiate_empty_record_variable(estate, rec);
+ if (ExpandedRecordIsEmpty(rec->erh))
+ deconstruct_expanded_record(rec->erh);
+
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ rec_tupdesc = expanded_record_get_tupdesc(rec->erh);
+ tupmap = convert_tuples_by_position(rec_tupdesc,
+ tupdesc,
+ gettext_noop("wrong record type supplied in RETURN NEXT"));
+ tuple = expanded_record_get_tuple(rec->erh);
+ if (tupmap)
+ tuple = execute_attr_map_tuple(tuple, tupmap);
+ tuplestore_puttuple(estate->tuple_store, tuple);
+ MemoryContextSwitchTo(oldcontext);
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) retvar;
+
+ /* We get here if there are multiple OUT parameters */
+
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ tuple = make_tuple_from_row(estate, row, tupdesc);
+ if (tuple == NULL) /* should not happen */
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("wrong record type supplied in RETURN NEXT")));
+ tuplestore_puttuple(estate->tuple_store, tuple);
+ MemoryContextSwitchTo(oldcontext);
+ }
+ break;
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", retvar->dtype);
+ break;
+ }
+ }
+ else if (stmt->expr)
+ {
+ Datum retval;
+ bool isNull;
+ Oid rettype;
+ int32 rettypmod;
+
+ retval = exec_eval_expr(estate,
+ stmt->expr,
+ &isNull,
+ &rettype,
+ &rettypmod);
+
+ if (estate->retistuple)
+ {
+ /* Expression should be of RECORD or composite type */
+ if (!isNull)
+ {
+ HeapTupleData tmptup;
+ TupleDesc retvaldesc;
+ TupleConversionMap *tupmap;
+
+ if (!type_is_rowtype(rettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot return non-composite value from function returning composite type")));
+
+ /* Use eval_mcontext for tuple conversion work */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ retvaldesc = deconstruct_composite_datum(retval, &tmptup);
+ tuple = &tmptup;
+ tupmap = convert_tuples_by_position(retvaldesc, tupdesc,
+ gettext_noop("returned record type does not match expected record type"));
+ if (tupmap)
+ tuple = execute_attr_map_tuple(tuple, tupmap);
+ tuplestore_puttuple(estate->tuple_store, tuple);
+ ReleaseTupleDesc(retvaldesc);
+ MemoryContextSwitchTo(oldcontext);
+ }
+ else
+ {
+ /* Composite NULL --- store a row of nulls */
+ Datum *nulldatums;
+ bool *nullflags;
+
+ nulldatums = (Datum *)
+ eval_mcontext_alloc0(estate, natts * sizeof(Datum));
+ nullflags = (bool *)
+ eval_mcontext_alloc(estate, natts * sizeof(bool));
+ memset(nullflags, true, natts * sizeof(bool));
+ tuplestore_putvalues(estate->tuple_store, tupdesc,
+ nulldatums, nullflags);
+ }
+ }
+ else
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, 0);
+
+ /* Simple scalar result */
+ if (natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("wrong result type supplied in RETURN NEXT")));
+
+ /* coerce type if needed */
+ retval = exec_cast_value(estate,
+ retval,
+ &isNull,
+ rettype,
+ rettypmod,
+ attr->atttypid,
+ attr->atttypmod);
+
+ tuplestore_putvalues(estate->tuple_store, tupdesc,
+ &retval, &isNull);
+ }
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RETURN NEXT must have a parameter")));
+ }
+
+ exec_eval_cleanup(estate);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_stmt_return_query Evaluate a query and add it to the
+ * list of tuples returned by the current
+ * SRF.
+ * ----------
+ */
+static int
+exec_stmt_return_query(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_return_query *stmt)
+{
+ int64 tcount;
+ DestReceiver *treceiver;
+ int rc;
+ uint64 processed;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
+ MemoryContext oldcontext;
+
+ if (!estate->retisset)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot use RETURN QUERY in a non-SETOF function")));
+
+ if (estate->tuple_store == NULL)
+ exec_init_tuple_store(estate);
+ /* There might be some tuples in the tuplestore already */
+ tcount = tuplestore_tuple_count(estate->tuple_store);
+
+ /*
+ * Set up DestReceiver to transfer results directly to tuplestore,
+ * converting rowtype if necessary. DestReceiver lives in mcontext.
+ */
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+ treceiver = CreateDestReceiver(DestTuplestore);
+ SetTuplestoreDestReceiverParams(treceiver,
+ estate->tuple_store,
+ estate->tuple_store_cxt,
+ false,
+ estate->tuple_store_desc,
+ gettext_noop("structure of query does not match function result type"));
+ MemoryContextSwitchTo(oldcontext);
+
+ if (stmt->query != NULL)
+ {
+ /* static query */
+ PLpgSQL_expr *expr = stmt->query;
+ ParamListInfo paramLI;
+ SPIExecuteOptions options;
+
+ /*
+ * On the first call for this expression generate the plan.
+ */
+ if (expr->plan == NULL)
+ exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+
+ /*
+ * Set up ParamListInfo to pass to executor
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
+ * Execute the query
+ */
+ memset(&options, 0, sizeof(options));
+ options.params = paramLI;
+ options.read_only = estate->readonly_func;
+ options.must_return_tuples = true;
+ options.dest = treceiver;
+
+ rc = SPI_execute_plan_extended(expr->plan, &options);
+ if (rc < 0)
+ elog(ERROR, "SPI_execute_plan_extended failed executing query \"%s\": %s",
+ expr->query, SPI_result_code_string(rc));
+ }
+ else
+ {
+ /* RETURN QUERY EXECUTE */
+ Datum query;
+ bool isnull;
+ Oid restype;
+ int32 restypmod;
+ char *querystr;
+ SPIExecuteOptions options;
+
+ /*
+ * Evaluate the string expression after the EXECUTE keyword. Its
+ * result is the querystring we have to execute.
+ */
+ Assert(stmt->dynquery != NULL);
+ query = exec_eval_expr(estate, stmt->dynquery,
+ &isnull, &restype, &restypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("query string argument of EXECUTE is null")));
+
+ /* Get the C-String representation */
+ querystr = convert_value_to_string(estate, query, restype);
+
+ /* copy it into the stmt_mcontext before we clean up */
+ querystr = MemoryContextStrdup(stmt_mcontext, querystr);
+
+ exec_eval_cleanup(estate);
+
+ /* Execute query, passing params if necessary */
+ memset(&options, 0, sizeof(options));
+ options.params = exec_eval_using_params(estate,
+ stmt->params);
+ options.read_only = estate->readonly_func;
+ options.must_return_tuples = true;
+ options.dest = treceiver;
+
+ rc = SPI_execute_extended(querystr, &options);
+ if (rc < 0)
+ elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s",
+ querystr, SPI_result_code_string(rc));
+ }
+
+ /* Clean up */
+ treceiver->rDestroy(treceiver);
+ exec_eval_cleanup(estate);
+ MemoryContextReset(stmt_mcontext);
+
+ /* Count how many tuples we got */
+ processed = tuplestore_tuple_count(estate->tuple_store) - tcount;
+
+ estate->eval_processed = processed;
+ exec_set_found(estate, processed != 0);
+
+ return PLPGSQL_RC_OK;
+}
+
+static void
+exec_init_tuple_store(PLpgSQL_execstate *estate)
+{
+ ReturnSetInfo *rsi = estate->rsi;
+ MemoryContext oldcxt;
+ ResourceOwner oldowner;
+
+ /*
+ * Check caller can handle a set result in the way we want
+ */
+ if (!rsi || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsi->allowedModes & SFRM_Materialize) ||
+ rsi->expectedDesc == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ /*
+ * Switch to the right memory context and resource owner for storing the
+ * tuplestore for return set. If we're within a subtransaction opened for
+ * an exception-block, for example, we must still create the tuplestore in
+ * the resource owner that was active when this function was entered, and
+ * not in the subtransaction resource owner.
+ */
+ oldcxt = MemoryContextSwitchTo(estate->tuple_store_cxt);
+ oldowner = CurrentResourceOwner;
+ CurrentResourceOwner = estate->tuple_store_owner;
+
+ estate->tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ CurrentResourceOwner = oldowner;
+ MemoryContextSwitchTo(oldcxt);
+
+ estate->tuple_store_desc = rsi->expectedDesc;
+}
+
+#define SET_RAISE_OPTION_TEXT(opt, name) \
+do { \
+ if (opt) \
+ ereport(ERROR, \
+ (errcode(ERRCODE_SYNTAX_ERROR), \
+ errmsg("RAISE option already specified: %s", \
+ name))); \
+ opt = MemoryContextStrdup(stmt_mcontext, extval); \
+} while (0)
+
+/* ----------
+ * exec_stmt_raise Build a message and throw it with elog()
+ * ----------
+ */
+static int
+exec_stmt_raise(PLpgSQL_execstate *estate, PLpgSQL_stmt_raise *stmt)
+{
+ int err_code = 0;
+ char *condname = NULL;
+ char *err_message = NULL;
+ char *err_detail = NULL;
+ char *err_hint = NULL;
+ char *err_column = NULL;
+ char *err_constraint = NULL;
+ char *err_datatype = NULL;
+ char *err_table = NULL;
+ char *err_schema = NULL;
+ MemoryContext stmt_mcontext;
+ ListCell *lc;
+
+ /* RAISE with no parameters: re-throw current exception */
+ if (stmt->condname == NULL && stmt->message == NULL &&
+ stmt->options == NIL)
+ {
+ if (estate->cur_error != NULL)
+ ReThrowError(estate->cur_error);
+ /* oops, we're not inside a handler */
+ ereport(ERROR,
+ (errcode(ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER),
+ errmsg("RAISE without parameters cannot be used outside an exception handler")));
+ }
+
+ /* We'll need to accumulate the various strings in stmt_mcontext */
+ stmt_mcontext = get_stmt_mcontext(estate);
+
+ if (stmt->condname)
+ {
+ err_code = plpgsql_recognize_err_condition(stmt->condname, true);
+ condname = MemoryContextStrdup(stmt_mcontext, stmt->condname);
+ }
+
+ if (stmt->message)
+ {
+ StringInfoData ds;
+ ListCell *current_param;
+ char *cp;
+ MemoryContext oldcontext;
+
+ /* build string in stmt_mcontext */
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+ initStringInfo(&ds);
+ MemoryContextSwitchTo(oldcontext);
+
+ current_param = list_head(stmt->params);
+
+ for (cp = stmt->message; *cp; cp++)
+ {
+ /*
+ * Occurrences of a single % are replaced by the next parameter's
+ * external representation. Double %'s are converted to one %.
+ */
+ if (cp[0] == '%')
+ {
+ Oid paramtypeid;
+ int32 paramtypmod;
+ Datum paramvalue;
+ bool paramisnull;
+ char *extval;
+
+ if (cp[1] == '%')
+ {
+ appendStringInfoChar(&ds, '%');
+ cp++;
+ continue;
+ }
+
+ /* should have been checked at compile time */
+ if (current_param == NULL)
+ elog(ERROR, "unexpected RAISE parameter list length");
+
+ paramvalue = exec_eval_expr(estate,
+ (PLpgSQL_expr *) lfirst(current_param),
+ &paramisnull,
+ &paramtypeid,
+ &paramtypmod);
+
+ if (paramisnull)
+ extval = "<NULL>";
+ else
+ extval = convert_value_to_string(estate,
+ paramvalue,
+ paramtypeid);
+ appendStringInfoString(&ds, extval);
+ current_param = lnext(stmt->params, current_param);
+ exec_eval_cleanup(estate);
+ }
+ else
+ appendStringInfoChar(&ds, cp[0]);
+ }
+
+ /* should have been checked at compile time */
+ if (current_param != NULL)
+ elog(ERROR, "unexpected RAISE parameter list length");
+
+ err_message = ds.data;
+ }
+
+ foreach(lc, stmt->options)
+ {
+ PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+ Datum optionvalue;
+ bool optionisnull;
+ Oid optiontypeid;
+ int32 optiontypmod;
+ char *extval;
+
+ optionvalue = exec_eval_expr(estate, opt->expr,
+ &optionisnull,
+ &optiontypeid,
+ &optiontypmod);
+ if (optionisnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("RAISE statement option cannot be null")));
+
+ extval = convert_value_to_string(estate, optionvalue, optiontypeid);
+
+ switch (opt->opt_type)
+ {
+ case PLPGSQL_RAISEOPTION_ERRCODE:
+ if (err_code)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RAISE option already specified: %s",
+ "ERRCODE")));
+ err_code = plpgsql_recognize_err_condition(extval, true);
+ condname = MemoryContextStrdup(stmt_mcontext, extval);
+ break;
+ case PLPGSQL_RAISEOPTION_MESSAGE:
+ SET_RAISE_OPTION_TEXT(err_message, "MESSAGE");
+ break;
+ case PLPGSQL_RAISEOPTION_DETAIL:
+ SET_RAISE_OPTION_TEXT(err_detail, "DETAIL");
+ break;
+ case PLPGSQL_RAISEOPTION_HINT:
+ SET_RAISE_OPTION_TEXT(err_hint, "HINT");
+ break;
+ case PLPGSQL_RAISEOPTION_COLUMN:
+ SET_RAISE_OPTION_TEXT(err_column, "COLUMN");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT:
+ SET_RAISE_OPTION_TEXT(err_constraint, "CONSTRAINT");
+ break;
+ case PLPGSQL_RAISEOPTION_DATATYPE:
+ SET_RAISE_OPTION_TEXT(err_datatype, "DATATYPE");
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE:
+ SET_RAISE_OPTION_TEXT(err_table, "TABLE");
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA:
+ SET_RAISE_OPTION_TEXT(err_schema, "SCHEMA");
+ break;
+ default:
+ elog(ERROR, "unrecognized raise option: %d", opt->opt_type);
+ }
+
+ exec_eval_cleanup(estate);
+ }
+
+ /* Default code if nothing specified */
+ if (err_code == 0 && stmt->elog_level >= ERROR)
+ err_code = ERRCODE_RAISE_EXCEPTION;
+
+ /* Default error message if nothing specified */
+ if (err_message == NULL)
+ {
+ if (condname)
+ {
+ err_message = condname;
+ condname = NULL;
+ }
+ else
+ err_message = MemoryContextStrdup(stmt_mcontext,
+ unpack_sql_state(err_code));
+ }
+
+ /*
+ * Throw the error (may or may not come back)
+ */
+ ereport(stmt->elog_level,
+ (err_code ? errcode(err_code) : 0,
+ errmsg_internal("%s", err_message),
+ (err_detail != NULL) ? errdetail_internal("%s", err_detail) : 0,
+ (err_hint != NULL) ? errhint("%s", err_hint) : 0,
+ (err_column != NULL) ?
+ err_generic_string(PG_DIAG_COLUMN_NAME, err_column) : 0,
+ (err_constraint != NULL) ?
+ err_generic_string(PG_DIAG_CONSTRAINT_NAME, err_constraint) : 0,
+ (err_datatype != NULL) ?
+ err_generic_string(PG_DIAG_DATATYPE_NAME, err_datatype) : 0,
+ (err_table != NULL) ?
+ err_generic_string(PG_DIAG_TABLE_NAME, err_table) : 0,
+ (err_schema != NULL) ?
+ err_generic_string(PG_DIAG_SCHEMA_NAME, err_schema) : 0));
+
+ /* Clean up transient strings */
+ MemoryContextReset(stmt_mcontext);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_stmt_assert Assert statement
+ * ----------
+ */
+static int
+exec_stmt_assert(PLpgSQL_execstate *estate, PLpgSQL_stmt_assert *stmt)
+{
+ bool value;
+ bool isnull;
+
+ /* do nothing when asserts are not enabled */
+ if (!plpgsql_check_asserts)
+ return PLPGSQL_RC_OK;
+
+ value = exec_eval_boolean(estate, stmt->cond, &isnull);
+ exec_eval_cleanup(estate);
+
+ if (isnull || !value)
+ {
+ char *message = NULL;
+
+ if (stmt->message != NULL)
+ {
+ Datum val;
+ Oid typeid;
+ int32 typmod;
+
+ val = exec_eval_expr(estate, stmt->message,
+ &isnull, &typeid, &typmod);
+ if (!isnull)
+ message = convert_value_to_string(estate, val, typeid);
+ /* we mustn't do exec_eval_cleanup here */
+ }
+
+ ereport(ERROR,
+ (errcode(ERRCODE_ASSERT_FAILURE),
+ message ? errmsg_internal("%s", message) :
+ errmsg("assertion failed")));
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * Initialize a mostly empty execution state
+ * ----------
+ */
+static void
+plpgsql_estate_setup(PLpgSQL_execstate *estate,
+ PLpgSQL_function *func,
+ ReturnSetInfo *rsi,
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner)
+{
+ HASHCTL ctl;
+
+ /* this link will be restored at exit from plpgsql_call_handler */
+ func->cur_estate = estate;
+
+ estate->func = func;
+ estate->trigdata = NULL;
+ estate->evtrigdata = NULL;
+
+ estate->retval = (Datum) 0;
+ estate->retisnull = true;
+ estate->rettype = InvalidOid;
+
+ estate->fn_rettype = func->fn_rettype;
+ estate->retistuple = func->fn_retistuple;
+ estate->retisset = func->fn_retset;
+
+ estate->readonly_func = func->fn_readonly;
+ estate->atomic = true;
+
+ estate->exitlabel = NULL;
+ estate->cur_error = NULL;
+
+ estate->tuple_store = NULL;
+ estate->tuple_store_desc = NULL;
+ if (rsi)
+ {
+ estate->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+ estate->tuple_store_owner = CurrentResourceOwner;
+ }
+ else
+ {
+ estate->tuple_store_cxt = NULL;
+ estate->tuple_store_owner = NULL;
+ }
+ estate->rsi = rsi;
+
+ estate->found_varno = func->found_varno;
+ estate->ndatums = func->ndatums;
+ estate->datums = NULL;
+ /* the datums array will be filled by copy_plpgsql_datums() */
+ estate->datum_context = CurrentMemoryContext;
+
+ /* initialize our ParamListInfo with appropriate hook functions */
+ estate->paramLI = makeParamList(0);
+ estate->paramLI->paramFetch = plpgsql_param_fetch;
+ estate->paramLI->paramFetchArg = (void *) estate;
+ estate->paramLI->paramCompile = plpgsql_param_compile;
+ estate->paramLI->paramCompileArg = NULL; /* not needed */
+ estate->paramLI->parserSetup = (ParserSetupHook) plpgsql_parser_setup;
+ estate->paramLI->parserSetupArg = NULL; /* filled during use */
+ estate->paramLI->numParams = estate->ndatums;
+
+ /* Create the session-wide cast-expression hash if we didn't already */
+ if (cast_expr_hash == NULL)
+ {
+ ctl.keysize = sizeof(plpgsql_CastHashKey);
+ ctl.entrysize = sizeof(plpgsql_CastExprHashEntry);
+ cast_expr_hash = hash_create("PLpgSQL cast expressions",
+ 16, /* start small and extend */
+ &ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+
+ /* set up for use of appropriate simple-expression EState and cast hash */
+ if (simple_eval_estate)
+ {
+ estate->simple_eval_estate = simple_eval_estate;
+ /* Private cast hash just lives in function's main context */
+ ctl.keysize = sizeof(plpgsql_CastHashKey);
+ ctl.entrysize = sizeof(plpgsql_CastHashEntry);
+ ctl.hcxt = CurrentMemoryContext;
+ estate->cast_hash = hash_create("PLpgSQL private cast cache",
+ 16, /* start small and extend */
+ &ctl,
+ HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
+ }
+ else
+ {
+ estate->simple_eval_estate = shared_simple_eval_estate;
+ /* Create the session-wide cast-info hash table if we didn't already */
+ if (shared_cast_hash == NULL)
+ {
+ ctl.keysize = sizeof(plpgsql_CastHashKey);
+ ctl.entrysize = sizeof(plpgsql_CastHashEntry);
+ shared_cast_hash = hash_create("PLpgSQL cast cache",
+ 16, /* start small and extend */
+ &ctl,
+ HASH_ELEM | HASH_BLOBS);
+ }
+ estate->cast_hash = shared_cast_hash;
+ }
+ /* likewise for the simple-expression resource owner */
+ if (simple_eval_resowner)
+ estate->simple_eval_resowner = simple_eval_resowner;
+ else
+ estate->simple_eval_resowner = shared_simple_eval_resowner;
+
+ /* if there's a procedure resowner, it'll be filled in later */
+ estate->procedure_resowner = NULL;
+
+ /*
+ * We start with no stmt_mcontext; one will be created only if needed.
+ * That context will be a direct child of the function's main execution
+ * context. Additional stmt_mcontexts might be created as children of it.
+ */
+ estate->stmt_mcontext = NULL;
+ estate->stmt_mcontext_parent = CurrentMemoryContext;
+
+ estate->eval_tuptable = NULL;
+ estate->eval_processed = 0;
+ estate->eval_econtext = NULL;
+
+ estate->err_stmt = NULL;
+ estate->err_var = NULL;
+ estate->err_text = NULL;
+
+ estate->plugin_info = NULL;
+
+ /*
+ * Create an EState and ExprContext for evaluation of simple expressions.
+ */
+ plpgsql_create_econtext(estate);
+
+ /*
+ * Let the plugin, if any, see this function before we initialize local
+ * PL/pgSQL variables. Note that we also give the plugin a few function
+ * pointers, so it can call back into PL/pgSQL for doing things like
+ * variable assignments and stack traces.
+ */
+ if (*plpgsql_plugin_ptr)
+ {
+ (*plpgsql_plugin_ptr)->error_callback = plpgsql_exec_error_callback;
+ (*plpgsql_plugin_ptr)->assign_expr = exec_assign_expr;
+ (*plpgsql_plugin_ptr)->assign_value = exec_assign_value;
+ (*plpgsql_plugin_ptr)->eval_datum = exec_eval_datum;
+ (*plpgsql_plugin_ptr)->cast_value = exec_cast_value;
+
+ if ((*plpgsql_plugin_ptr)->func_setup)
+ ((*plpgsql_plugin_ptr)->func_setup) (estate, func);
+ }
+}
+
+/* ----------
+ * Release temporary memory used by expression/subselect evaluation
+ *
+ * NB: the result of the evaluation is no longer valid after this is done,
+ * unless it is a pass-by-value datatype.
+ * ----------
+ */
+static void
+exec_eval_cleanup(PLpgSQL_execstate *estate)
+{
+ /* Clear result of a full SPI_execute */
+ if (estate->eval_tuptable != NULL)
+ SPI_freetuptable(estate->eval_tuptable);
+ estate->eval_tuptable = NULL;
+
+ /*
+ * Clear result of exec_eval_simple_expr (but keep the econtext). This
+ * also clears any short-lived allocations done via get_eval_mcontext.
+ */
+ if (estate->eval_econtext != NULL)
+ ResetExprContext(estate->eval_econtext);
+}
+
+
+/* ----------
+ * Generate a prepared plan
+ *
+ * CAUTION: it is possible for this function to throw an error after it has
+ * built a SPIPlan and saved it in expr->plan. Therefore, be wary of doing
+ * additional things contingent on expr->plan being NULL. That is, given
+ * code like
+ *
+ * if (query->plan == NULL)
+ * {
+ * // okay to put setup code here
+ * exec_prepare_plan(estate, query, ...);
+ * // NOT okay to put more logic here
+ * }
+ *
+ * extra steps at the end are unsafe because they will not be executed when
+ * re-executing the calling statement, if exec_prepare_plan failed the first
+ * time. This is annoyingly error-prone, but the alternatives are worse.
+ * ----------
+ */
+static void
+exec_prepare_plan(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, int cursorOptions)
+{
+ SPIPlanPtr plan;
+ SPIPrepareOptions options;
+
+ /*
+ * The grammar can't conveniently set expr->func while building the parse
+ * tree, so make sure it's set before parser hooks need it.
+ */
+ expr->func = estate->func;
+
+ /*
+ * Generate and save the plan
+ */
+ memset(&options, 0, sizeof(options));
+ options.parserSetup = (ParserSetupHook) plpgsql_parser_setup;
+ options.parserSetupArg = (void *) expr;
+ options.parseMode = expr->parseMode;
+ options.cursorOptions = cursorOptions;
+ plan = SPI_prepare_extended(expr->query, &options);
+ if (plan == NULL)
+ elog(ERROR, "SPI_prepare_extended failed for \"%s\": %s",
+ expr->query, SPI_result_code_string(SPI_result));
+
+ SPI_keepplan(plan);
+ expr->plan = plan;
+
+ /* Check to see if it's a simple expression */
+ exec_simple_check_plan(estate, expr);
+}
+
+
+/* ----------
+ * exec_stmt_execsql Execute an SQL statement (possibly with INTO).
+ *
+ * Note: some callers rely on this not touching stmt_mcontext. If it ever
+ * needs to use that, fix those callers to push/pop stmt_mcontext.
+ * ----------
+ */
+static int
+exec_stmt_execsql(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_execsql *stmt)
+{
+ ParamListInfo paramLI;
+ long tcount;
+ int rc;
+ PLpgSQL_expr *expr = stmt->sqlstmt;
+ int too_many_rows_level = 0;
+
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_TOOMANYROWS)
+ too_many_rows_level = ERROR;
+ else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_TOOMANYROWS)
+ too_many_rows_level = WARNING;
+
+ /*
+ * On the first call for this statement generate the plan, and detect
+ * whether the statement is INSERT/UPDATE/DELETE/MERGE
+ */
+ if (expr->plan == NULL)
+ exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+
+ if (!stmt->mod_stmt_set)
+ {
+ ListCell *l;
+
+ stmt->mod_stmt = false;
+ foreach(l, SPI_plan_get_plan_sources(expr->plan))
+ {
+ CachedPlanSource *plansource = (CachedPlanSource *) lfirst(l);
+
+ /*
+ * We could look at the raw_parse_tree, but it seems simpler to
+ * check the command tag. Note we should *not* look at the Query
+ * tree(s), since those are the result of rewriting and could have
+ * been transmogrified into something else entirely.
+ */
+ if (plansource->commandTag == CMDTAG_INSERT ||
+ plansource->commandTag == CMDTAG_UPDATE ||
+ plansource->commandTag == CMDTAG_DELETE ||
+ plansource->commandTag == CMDTAG_MERGE)
+ {
+ stmt->mod_stmt = true;
+ break;
+ }
+ }
+ stmt->mod_stmt_set = true;
+ }
+
+ /*
+ * Set up ParamListInfo to pass to executor
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
+ * If we have INTO, then we only need one row back ... but if we have INTO
+ * STRICT or extra check too_many_rows, ask for two rows, so that we can
+ * verify the statement returns only one. INSERT/UPDATE/DELETE are always
+ * treated strictly. Without INTO, just run the statement to completion
+ * (tcount = 0).
+ *
+ * We could just ask for two rows always when using INTO, but there are
+ * some cases where demanding the extra row costs significant time, eg by
+ * forcing completion of a sequential scan. So don't do it unless we need
+ * to enforce strictness.
+ */
+ if (stmt->into)
+ {
+ if (stmt->strict || stmt->mod_stmt || too_many_rows_level)
+ tcount = 2;
+ else
+ tcount = 1;
+ }
+ else
+ tcount = 0;
+
+ /*
+ * Execute the plan
+ */
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, tcount);
+
+ /*
+ * Check for error, and set FOUND if appropriate (for historical reasons
+ * we set FOUND only for certain query types). Also Assert that we
+ * identified the statement type the same as SPI did.
+ */
+ switch (rc)
+ {
+ case SPI_OK_SELECT:
+ Assert(!stmt->mod_stmt);
+ exec_set_found(estate, (SPI_processed != 0));
+ break;
+
+ case SPI_OK_INSERT:
+ case SPI_OK_UPDATE:
+ case SPI_OK_DELETE:
+ case SPI_OK_INSERT_RETURNING:
+ case SPI_OK_UPDATE_RETURNING:
+ case SPI_OK_DELETE_RETURNING:
+ case SPI_OK_MERGE:
+ Assert(stmt->mod_stmt);
+ exec_set_found(estate, (SPI_processed != 0));
+ break;
+
+ case SPI_OK_SELINTO:
+ case SPI_OK_UTILITY:
+ Assert(!stmt->mod_stmt);
+ break;
+
+ case SPI_OK_REWRITTEN:
+
+ /*
+ * The command was rewritten into another kind of command. It's
+ * not clear what FOUND would mean in that case (and SPI doesn't
+ * return the row count either), so just set it to false. Note
+ * that we can't assert anything about mod_stmt here.
+ */
+ exec_set_found(estate, false);
+ break;
+
+ /* Some SPI errors deserve specific error messages */
+ case SPI_ERROR_COPY:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot COPY to/from client in PL/pgSQL")));
+ break;
+
+ case SPI_ERROR_TRANSACTION:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported transaction command in PL/pgSQL")));
+ break;
+
+ default:
+ elog(ERROR, "SPI_execute_plan_with_paramlist failed executing query \"%s\": %s",
+ expr->query, SPI_result_code_string(rc));
+ break;
+ }
+
+ /* All variants should save result info for GET DIAGNOSTICS */
+ estate->eval_processed = SPI_processed;
+
+ /* Process INTO if present */
+ if (stmt->into)
+ {
+ SPITupleTable *tuptab = SPI_tuptable;
+ uint64 n = SPI_processed;
+ PLpgSQL_variable *target;
+
+ /* If the statement did not return a tuple table, complain */
+ if (tuptab == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INTO used with a command that cannot return data")));
+
+ /* Fetch target's datum entry */
+ target = (PLpgSQL_variable *) estate->datums[stmt->target->dno];
+
+ /*
+ * If SELECT ... INTO specified STRICT, and the query didn't find
+ * exactly one row, throw an error. If STRICT was not specified, then
+ * allow the query to find any number of rows.
+ */
+ if (n == 0)
+ {
+ if (stmt->strict)
+ {
+ char *errdetail;
+
+ if (estate->func->print_strict_params)
+ errdetail = format_expr_params(estate, expr);
+ else
+ errdetail = NULL;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("query returned no rows"),
+ errdetail ? errdetail_internal("parameters: %s", errdetail) : 0));
+ }
+ /* set the target to NULL(s) */
+ exec_move_row(estate, target, NULL, tuptab->tupdesc);
+ }
+ else
+ {
+ if (n > 1 && (stmt->strict || stmt->mod_stmt || too_many_rows_level))
+ {
+ char *errdetail;
+ int errlevel;
+
+ if (estate->func->print_strict_params)
+ errdetail = format_expr_params(estate, expr);
+ else
+ errdetail = NULL;
+
+ errlevel = (stmt->strict || stmt->mod_stmt) ? ERROR : too_many_rows_level;
+
+ ereport(errlevel,
+ (errcode(ERRCODE_TOO_MANY_ROWS),
+ errmsg("query returned more than one row"),
+ errdetail ? errdetail_internal("parameters: %s", errdetail) : 0,
+ errhint("Make sure the query returns a single row, or use LIMIT 1.")));
+ }
+ /* Put the first result row into the target */
+ exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
+ }
+
+ /* Clean up */
+ exec_eval_cleanup(estate);
+ SPI_freetuptable(SPI_tuptable);
+ }
+ else
+ {
+ /* If the statement returned a tuple table, complain */
+ if (SPI_tuptable != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("query has no destination for result data"),
+ (rc == SPI_OK_SELECT) ? errhint("If you want to discard the results of a SELECT, use PERFORM instead.") : 0));
+ }
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_dynexecute Execute a dynamic SQL query
+ * (possibly with INTO).
+ * ----------
+ */
+static int
+exec_stmt_dynexecute(PLpgSQL_execstate *estate,
+ PLpgSQL_stmt_dynexecute *stmt)
+{
+ Datum query;
+ bool isnull;
+ Oid restype;
+ int32 restypmod;
+ char *querystr;
+ int exec_res;
+ ParamListInfo paramLI;
+ SPIExecuteOptions options;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
+
+ /*
+ * First we evaluate the string expression after the EXECUTE keyword. Its
+ * result is the querystring we have to execute.
+ */
+ query = exec_eval_expr(estate, stmt->query, &isnull, &restype, &restypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("query string argument of EXECUTE is null")));
+
+ /* Get the C-String representation */
+ querystr = convert_value_to_string(estate, query, restype);
+
+ /* copy it into the stmt_mcontext before we clean up */
+ querystr = MemoryContextStrdup(stmt_mcontext, querystr);
+
+ exec_eval_cleanup(estate);
+
+ /*
+ * Execute the query without preparing a saved plan.
+ */
+ paramLI = exec_eval_using_params(estate, stmt->params);
+
+ memset(&options, 0, sizeof(options));
+ options.params = paramLI;
+ options.read_only = estate->readonly_func;
+
+ exec_res = SPI_execute_extended(querystr, &options);
+
+ switch (exec_res)
+ {
+ case SPI_OK_SELECT:
+ case SPI_OK_INSERT:
+ case SPI_OK_UPDATE:
+ case SPI_OK_DELETE:
+ case SPI_OK_INSERT_RETURNING:
+ case SPI_OK_UPDATE_RETURNING:
+ case SPI_OK_DELETE_RETURNING:
+ case SPI_OK_MERGE:
+ case SPI_OK_UTILITY:
+ case SPI_OK_REWRITTEN:
+ break;
+
+ case 0:
+
+ /*
+ * Also allow a zero return, which implies the querystring
+ * contained no commands.
+ */
+ break;
+
+ case SPI_OK_SELINTO:
+
+ /*
+ * We want to disallow SELECT INTO for now, because its behavior
+ * is not consistent with SELECT INTO in a normal plpgsql context.
+ * (We need to reimplement EXECUTE to parse the string as a
+ * plpgsql command, not just feed it to SPI_execute.) This is not
+ * a functional limitation because CREATE TABLE AS is allowed.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("EXECUTE of SELECT ... INTO is not implemented"),
+ errhint("You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead.")));
+ break;
+
+ /* Some SPI errors deserve specific error messages */
+ case SPI_ERROR_COPY:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot COPY to/from client in PL/pgSQL")));
+ break;
+
+ case SPI_ERROR_TRANSACTION:
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("EXECUTE of transaction commands is not implemented")));
+ break;
+
+ default:
+ elog(ERROR, "SPI_execute_extended failed executing query \"%s\": %s",
+ querystr, SPI_result_code_string(exec_res));
+ break;
+ }
+
+ /* Save result info for GET DIAGNOSTICS */
+ estate->eval_processed = SPI_processed;
+
+ /* Process INTO if present */
+ if (stmt->into)
+ {
+ SPITupleTable *tuptab = SPI_tuptable;
+ uint64 n = SPI_processed;
+ PLpgSQL_variable *target;
+
+ /* If the statement did not return a tuple table, complain */
+ if (tuptab == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("INTO used with a command that cannot return data")));
+
+ /* Fetch target's datum entry */
+ target = (PLpgSQL_variable *) estate->datums[stmt->target->dno];
+
+ /*
+ * If SELECT ... INTO specified STRICT, and the query didn't find
+ * exactly one row, throw an error. If STRICT was not specified, then
+ * allow the query to find any number of rows.
+ */
+ if (n == 0)
+ {
+ if (stmt->strict)
+ {
+ char *errdetail;
+
+ if (estate->func->print_strict_params)
+ errdetail = format_preparedparamsdata(estate, paramLI);
+ else
+ errdetail = NULL;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_NO_DATA_FOUND),
+ errmsg("query returned no rows"),
+ errdetail ? errdetail_internal("parameters: %s", errdetail) : 0));
+ }
+ /* set the target to NULL(s) */
+ exec_move_row(estate, target, NULL, tuptab->tupdesc);
+ }
+ else
+ {
+ if (n > 1 && stmt->strict)
+ {
+ char *errdetail;
+
+ if (estate->func->print_strict_params)
+ errdetail = format_preparedparamsdata(estate, paramLI);
+ else
+ errdetail = NULL;
+
+ ereport(ERROR,
+ (errcode(ERRCODE_TOO_MANY_ROWS),
+ errmsg("query returned more than one row"),
+ errdetail ? errdetail_internal("parameters: %s", errdetail) : 0));
+ }
+
+ /* Put the first result row into the target */
+ exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
+ }
+ /* clean up after exec_move_row() */
+ exec_eval_cleanup(estate);
+ }
+ else
+ {
+ /*
+ * It might be a good idea to raise an error if the query returned
+ * tuples that are being ignored, but historically we have not done
+ * that.
+ */
+ }
+
+ /* Release any result from SPI_execute, as well as transient data */
+ SPI_freetuptable(SPI_tuptable);
+ MemoryContextReset(stmt_mcontext);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_dynfors Execute a dynamic query, assign each
+ * tuple to a record or row and
+ * execute a group of statements
+ * for it.
+ * ----------
+ */
+static int
+exec_stmt_dynfors(PLpgSQL_execstate *estate, PLpgSQL_stmt_dynfors *stmt)
+{
+ Portal portal;
+ int rc;
+
+ portal = exec_dynquery_with_params(estate, stmt->query, stmt->params,
+ NULL, CURSOR_OPT_NO_SCROLL);
+
+ /*
+ * Execute the loop
+ */
+ rc = exec_for_query(estate, (PLpgSQL_stmt_forq *) stmt, portal, true);
+
+ /*
+ * Close the implicit cursor
+ */
+ SPI_cursor_close(portal);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_stmt_open Execute an OPEN cursor statement
+ * ----------
+ */
+static int
+exec_stmt_open(PLpgSQL_execstate *estate, PLpgSQL_stmt_open *stmt)
+{
+ PLpgSQL_var *curvar;
+ MemoryContext stmt_mcontext = NULL;
+ char *curname = NULL;
+ PLpgSQL_expr *query;
+ Portal portal;
+ ParamListInfo paramLI;
+
+ /* ----------
+ * Get the cursor variable and if it has an assigned name, check
+ * that it's not in use currently.
+ * ----------
+ */
+ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+ if (!curvar->isnull)
+ {
+ MemoryContext oldcontext;
+
+ /* We only need stmt_mcontext to hold the cursor name string */
+ stmt_mcontext = get_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+ curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
+ if (SPI_cursor_find(curname) != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_CURSOR),
+ errmsg("cursor \"%s\" already in use", curname)));
+ }
+
+ /* ----------
+ * Process the OPEN according to it's type.
+ * ----------
+ */
+ if (stmt->query != NULL)
+ {
+ /* ----------
+ * This is an OPEN refcursor FOR SELECT ...
+ *
+ * We just make sure the query is planned. The real work is
+ * done downstairs.
+ * ----------
+ */
+ query = stmt->query;
+ if (query->plan == NULL)
+ exec_prepare_plan(estate, query, stmt->cursor_options);
+ }
+ else if (stmt->dynquery != NULL)
+ {
+ /* ----------
+ * This is an OPEN refcursor FOR EXECUTE ...
+ * ----------
+ */
+ portal = exec_dynquery_with_params(estate,
+ stmt->dynquery,
+ stmt->params,
+ curname,
+ stmt->cursor_options);
+
+ /*
+ * If cursor variable was NULL, store the generated portal name in it,
+ * after verifying it's okay to assign to.
+ *
+ * Note: exec_dynquery_with_params already reset the stmt_mcontext, so
+ * curname is a dangling pointer here; but testing it for nullness is
+ * OK.
+ */
+ if (curname == NULL)
+ {
+ exec_check_assignable(estate, stmt->curvar);
+ assign_text_var(estate, curvar, portal->name);
+ }
+
+ return PLPGSQL_RC_OK;
+ }
+ else
+ {
+ /* ----------
+ * This is an OPEN cursor
+ *
+ * Note: parser should already have checked that statement supplies
+ * args iff cursor needs them, but we check again to be safe.
+ * ----------
+ */
+ if (stmt->argquery != NULL)
+ {
+ /* ----------
+ * OPEN CURSOR with args. We fake a SELECT ... INTO ...
+ * statement to evaluate the args and put 'em into the
+ * internal row.
+ * ----------
+ */
+ PLpgSQL_stmt_execsql set_args;
+
+ if (curvar->cursor_explicit_argrow < 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("arguments given for cursor without arguments")));
+
+ memset(&set_args, 0, sizeof(set_args));
+ set_args.cmd_type = PLPGSQL_STMT_EXECSQL;
+ set_args.lineno = stmt->lineno;
+ set_args.sqlstmt = stmt->argquery;
+ set_args.into = true;
+ /* XXX historically this has not been STRICT */
+ set_args.target = (PLpgSQL_variable *)
+ (estate->datums[curvar->cursor_explicit_argrow]);
+
+ if (exec_stmt_execsql(estate, &set_args) != PLPGSQL_RC_OK)
+ elog(ERROR, "open cursor failed during argument processing");
+ }
+ else
+ {
+ if (curvar->cursor_explicit_argrow >= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("arguments required for cursor")));
+ }
+
+ query = curvar->cursor_explicit_expr;
+ if (query->plan == NULL)
+ exec_prepare_plan(estate, query, curvar->cursor_options);
+ }
+
+ /*
+ * Set up ParamListInfo for this query
+ */
+ paramLI = setup_param_list(estate, query);
+
+ /*
+ * Open the cursor (the paramlist will get copied into the portal)
+ */
+ portal = SPI_cursor_open_with_paramlist(curname, query->plan,
+ paramLI,
+ estate->readonly_func);
+ if (portal == NULL)
+ elog(ERROR, "could not open cursor: %s",
+ SPI_result_code_string(SPI_result));
+
+ /*
+ * If cursor variable was NULL, store the generated portal name in it,
+ * after verifying it's okay to assign to.
+ */
+ if (curname == NULL)
+ {
+ exec_check_assignable(estate, stmt->curvar);
+ assign_text_var(estate, curvar, portal->name);
+ }
+
+ /* If we had any transient data, clean it up */
+ exec_eval_cleanup(estate);
+ if (stmt_mcontext)
+ MemoryContextReset(stmt_mcontext);
+
+ return PLPGSQL_RC_OK;
+}
+
+
+/* ----------
+ * exec_stmt_fetch Fetch from a cursor into a target, or just
+ * move the current position of the cursor
+ * ----------
+ */
+static int
+exec_stmt_fetch(PLpgSQL_execstate *estate, PLpgSQL_stmt_fetch *stmt)
+{
+ PLpgSQL_var *curvar;
+ long how_many = stmt->how_many;
+ SPITupleTable *tuptab;
+ Portal portal;
+ char *curname;
+ uint64 n;
+ MemoryContext oldcontext;
+
+ /* ----------
+ * Get the portal of the cursor by name
+ * ----------
+ */
+ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+ if (curvar->isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
+ portal = SPI_cursor_find(curname);
+ if (portal == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_CURSOR),
+ errmsg("cursor \"%s\" does not exist", curname)));
+
+ /* Calculate position for FETCH_RELATIVE or FETCH_ABSOLUTE */
+ if (stmt->expr)
+ {
+ bool isnull;
+
+ /* XXX should be doing this in LONG not INT width */
+ how_many = exec_eval_integer(estate, stmt->expr, &isnull);
+
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("relative or absolute cursor position is null")));
+
+ exec_eval_cleanup(estate);
+ }
+
+ if (!stmt->is_move)
+ {
+ PLpgSQL_variable *target;
+
+ /* ----------
+ * Fetch 1 tuple from the cursor
+ * ----------
+ */
+ SPI_scroll_cursor_fetch(portal, stmt->direction, how_many);
+ tuptab = SPI_tuptable;
+ n = SPI_processed;
+
+ /* ----------
+ * Set the target appropriately.
+ * ----------
+ */
+ target = (PLpgSQL_variable *) estate->datums[stmt->target->dno];
+ if (n == 0)
+ exec_move_row(estate, target, NULL, tuptab->tupdesc);
+ else
+ exec_move_row(estate, target, tuptab->vals[0], tuptab->tupdesc);
+
+ exec_eval_cleanup(estate);
+ SPI_freetuptable(tuptab);
+ }
+ else
+ {
+ /* Move the cursor */
+ SPI_scroll_cursor_move(portal, stmt->direction, how_many);
+ n = SPI_processed;
+ }
+
+ /* Set the ROW_COUNT and the global FOUND variable appropriately. */
+ estate->eval_processed = n;
+ exec_set_found(estate, n != 0);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_stmt_close Close a cursor
+ * ----------
+ */
+static int
+exec_stmt_close(PLpgSQL_execstate *estate, PLpgSQL_stmt_close *stmt)
+{
+ PLpgSQL_var *curvar;
+ Portal portal;
+ char *curname;
+ MemoryContext oldcontext;
+
+ /* ----------
+ * Get the portal of the cursor by name
+ * ----------
+ */
+ curvar = (PLpgSQL_var *) (estate->datums[stmt->curvar]);
+ if (curvar->isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("cursor variable \"%s\" is null", curvar->refname)));
+
+ /* Use eval_mcontext for short-lived string */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ curname = TextDatumGetCString(curvar->value);
+ MemoryContextSwitchTo(oldcontext);
+
+ portal = SPI_cursor_find(curname);
+ if (portal == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_CURSOR),
+ errmsg("cursor \"%s\" does not exist", curname)));
+
+ /* ----------
+ * And close it.
+ * ----------
+ */
+ SPI_cursor_close(portal);
+
+ return PLPGSQL_RC_OK;
+}
+
+/*
+ * exec_stmt_commit
+ *
+ * Commit the transaction.
+ */
+static int
+exec_stmt_commit(PLpgSQL_execstate *estate, PLpgSQL_stmt_commit *stmt)
+{
+ if (stmt->chain)
+ SPI_commit_and_chain();
+ else
+ SPI_commit();
+
+ /*
+ * We need to build new simple-expression infrastructure, since the old
+ * data structures are gone.
+ */
+ estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
+ plpgsql_create_econtext(estate);
+
+ return PLPGSQL_RC_OK;
+}
+
+/*
+ * exec_stmt_rollback
+ *
+ * Abort the transaction.
+ */
+static int
+exec_stmt_rollback(PLpgSQL_execstate *estate, PLpgSQL_stmt_rollback *stmt)
+{
+ if (stmt->chain)
+ SPI_rollback_and_chain();
+ else
+ SPI_rollback();
+
+ /*
+ * We need to build new simple-expression infrastructure, since the old
+ * data structures are gone.
+ */
+ estate->simple_eval_estate = NULL;
+ estate->simple_eval_resowner = NULL;
+ plpgsql_create_econtext(estate);
+
+ return PLPGSQL_RC_OK;
+}
+
+/* ----------
+ * exec_assign_expr Put an expression's result into a variable.
+ * ----------
+ */
+static void
+exec_assign_expr(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
+ PLpgSQL_expr *expr)
+{
+ Datum value;
+ bool isnull;
+ Oid valtype;
+ int32 valtypmod;
+
+ /*
+ * If first time through, create a plan for this expression.
+ */
+ if (expr->plan == NULL)
+ {
+ /*
+ * Mark the expression as being an assignment source, if target is a
+ * simple variable. (This is a bit messy, but it seems cleaner than
+ * modifying the API of exec_prepare_plan for the purpose. We need to
+ * stash the target dno into the expr anyway, so that it will be
+ * available if we have to replan.)
+ */
+ if (target->dtype == PLPGSQL_DTYPE_VAR)
+ expr->target_param = target->dno;
+ else
+ expr->target_param = -1; /* should be that already */
+
+ exec_prepare_plan(estate, expr, 0);
+ }
+
+ value = exec_eval_expr(estate, expr, &isnull, &valtype, &valtypmod);
+ exec_assign_value(estate, target, value, isnull, valtype, valtypmod);
+ exec_eval_cleanup(estate);
+}
+
+
+/* ----------
+ * exec_assign_c_string Put a C string into a text variable.
+ *
+ * We take a NULL pointer as signifying empty string, not SQL null.
+ *
+ * As with the underlying exec_assign_value, caller is expected to do
+ * exec_eval_cleanup later.
+ * ----------
+ */
+static void
+exec_assign_c_string(PLpgSQL_execstate *estate, PLpgSQL_datum *target,
+ const char *str)
+{
+ text *value;
+ MemoryContext oldcontext;
+
+ /* Use eval_mcontext for short-lived text value */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ if (str != NULL)
+ value = cstring_to_text(str);
+ else
+ value = cstring_to_text("");
+ MemoryContextSwitchTo(oldcontext);
+
+ exec_assign_value(estate, target, PointerGetDatum(value), false,
+ TEXTOID, -1);
+}
+
+
+/* ----------
+ * exec_assign_value Put a value into a target datum
+ *
+ * Note: in some code paths, this will leak memory in the eval_mcontext;
+ * we assume that will be cleaned up later by exec_eval_cleanup. We cannot
+ * call exec_eval_cleanup here for fear of destroying the input Datum value.
+ * ----------
+ */
+static void
+exec_assign_value(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ Datum value, bool isNull,
+ Oid valtype, int32 valtypmod)
+{
+ switch (target->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ {
+ /*
+ * Target is a variable
+ */
+ PLpgSQL_var *var = (PLpgSQL_var *) target;
+ Datum newvalue;
+
+ newvalue = exec_cast_value(estate,
+ value,
+ &isNull,
+ valtype,
+ valtypmod,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+
+ if (isNull && var->notnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
+ var->refname)));
+
+ /*
+ * If type is by-reference, copy the new value (which is
+ * probably in the eval_mcontext) into the procedure's main
+ * memory context. But if it's a read/write reference to an
+ * expanded object, no physical copy needs to happen; at most
+ * we need to reparent the object's memory context.
+ *
+ * If it's an array, we force the value to be stored in R/W
+ * expanded form. This wins if the function later does, say,
+ * a lot of array subscripting operations on the variable, and
+ * otherwise might lose. We might need to use a different
+ * heuristic, but it's too soon to tell. Also, are there
+ * cases where it'd be useful to force non-array values into
+ * expanded form?
+ */
+ if (!var->datatype->typbyval && !isNull)
+ {
+ if (var->datatype->typisarray &&
+ !VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(newvalue)))
+ {
+ /* array and not already R/W, so apply expand_array */
+ newvalue = expand_array(newvalue,
+ estate->datum_context,
+ NULL);
+ }
+ else
+ {
+ /* else transfer value if R/W, else just datumCopy */
+ newvalue = datumTransfer(newvalue,
+ false,
+ var->datatype->typlen);
+ }
+ }
+
+ /*
+ * Now free the old value, if any, and assign the new one. But
+ * skip the assignment if old and new values are the same.
+ * Note that for expanded objects, this test is necessary and
+ * cannot reliably be made any earlier; we have to be looking
+ * at the object's standard R/W pointer to be sure pointer
+ * equality is meaningful.
+ *
+ * Also, if it's a promise variable, we should disarm the
+ * promise in any case --- otherwise, assigning null to an
+ * armed promise variable would fail to disarm the promise.
+ */
+ if (var->value != newvalue || var->isnull || isNull)
+ assign_simple_var(estate, var, newvalue, isNull,
+ (!var->datatype->typbyval && !isNull));
+ else
+ var->promise = PLPGSQL_PROMISE_NONE;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ /*
+ * Target is a row variable
+ */
+ PLpgSQL_row *row = (PLpgSQL_row *) target;
+
+ if (isNull)
+ {
+ /* If source is null, just assign nulls to the row */
+ exec_move_row(estate, (PLpgSQL_variable *) row,
+ NULL, NULL);
+ }
+ else
+ {
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a row variable")));
+ exec_move_row_from_datum(estate, (PLpgSQL_variable *) row,
+ value);
+ }
+ break;
+ }
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ /*
+ * Target is a record variable
+ */
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
+
+ if (isNull)
+ {
+ if (rec->notnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("null value cannot be assigned to variable \"%s\" declared NOT NULL",
+ rec->refname)));
+
+ /* Set variable to a simple NULL */
+ exec_move_row(estate, (PLpgSQL_variable *) rec,
+ NULL, NULL);
+ }
+ else
+ {
+ /* Source must be of RECORD or composite type */
+ if (!type_is_rowtype(valtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot assign non-composite value to a record variable")));
+ exec_move_row_from_datum(estate, (PLpgSQL_variable *) rec,
+ value);
+ }
+ break;
+ }
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ {
+ /*
+ * Target is a field of a record
+ */
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) target;
+ PLpgSQL_rec *rec;
+ ExpandedRecordHeader *erh;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+ erh = rec->erh;
+
+ /*
+ * If record variable is NULL, instantiate it if it has a
+ * named composite type, else complain. (This won't change
+ * the logical state of the record, but if we successfully
+ * assign below, the unassigned fields will all become NULLs.)
+ */
+ if (erh == NULL)
+ {
+ instantiate_empty_record_variable(estate, rec);
+ erh = rec->erh;
+ }
+
+ /*
+ * Look up the field's properties if we have not already, or
+ * if the tuple descriptor ID changed since last time.
+ */
+ if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id))
+ {
+ if (!expanded_record_lookup_field(erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ recfield->rectupledescid = erh->er_tupdesc_id;
+ }
+
+ /* We don't support assignments to system columns. */
+ if (recfield->finfo.fnumber <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot assign to system column \"%s\"",
+ recfield->fieldname)));
+
+ /* Cast the new value to the right type, if needed. */
+ value = exec_cast_value(estate,
+ value,
+ &isNull,
+ valtype,
+ valtypmod,
+ recfield->finfo.ftypeid,
+ recfield->finfo.ftypmod);
+
+ /* And assign it. */
+ expanded_record_set_field(erh, recfield->finfo.fnumber,
+ value, isNull, !estate->atomic);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", target->dtype);
+ }
+}
+
+/*
+ * exec_eval_datum Get current value of a PLpgSQL_datum
+ *
+ * The type oid, typmod, value in Datum format, and null flag are returned.
+ *
+ * At present this doesn't handle PLpgSQL_expr datums; that's not needed
+ * because we never pass references to such datums to SPI.
+ *
+ * NOTE: the returned Datum points right at the stored value in the case of
+ * pass-by-reference datatypes. Generally callers should take care not to
+ * modify the stored value. Some callers intentionally manipulate variables
+ * referenced by R/W expanded pointers, though; it is those callers'
+ * responsibility that the results are semantically OK.
+ *
+ * In some cases we have to palloc a return value, and in such cases we put
+ * it into the estate's eval_mcontext.
+ */
+static void
+exec_eval_datum(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum,
+ Oid *typeid,
+ int32 *typetypmod,
+ Datum *value,
+ bool *isnull)
+{
+ MemoryContext oldcontext;
+
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_PROMISE:
+ /* fulfill promise if needed, then handle like regular var */
+ plpgsql_fulfill_promise(estate, (PLpgSQL_var *) datum);
+
+ /* FALL THRU */
+
+ case PLPGSQL_DTYPE_VAR:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+ *typeid = var->datatype->typoid;
+ *typetypmod = var->datatype->atttypmod;
+ *value = var->value;
+ *isnull = var->isnull;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) datum;
+ HeapTuple tup;
+
+ /* We get here if there are multiple OUT parameters */
+ if (!row->rowtupdesc) /* should not happen */
+ elog(ERROR, "row variable has no tupdesc");
+ /* Make sure we have a valid type/typmod setting */
+ BlessTupleDesc(row->rowtupdesc);
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ tup = make_tuple_from_row(estate, row, row->rowtupdesc);
+ if (tup == NULL) /* should not happen */
+ elog(ERROR, "row not compatible with its own tupdesc");
+ *typeid = row->rowtupdesc->tdtypeid;
+ *typetypmod = row->rowtupdesc->tdtypmod;
+ *value = HeapTupleGetDatum(tup);
+ *isnull = false;
+ MemoryContextSwitchTo(oldcontext);
+ break;
+ }
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ if (rec->erh == NULL)
+ {
+ /* Treat uninstantiated record as a simple NULL */
+ *value = (Datum) 0;
+ *isnull = true;
+ /* Report variable's declared type */
+ *typeid = rec->rectypeid;
+ *typetypmod = -1;
+ }
+ else
+ {
+ if (ExpandedRecordIsEmpty(rec->erh))
+ {
+ /* Empty record is also a NULL */
+ *value = (Datum) 0;
+ *isnull = true;
+ }
+ else
+ {
+ *value = ExpandedRecordGetDatum(rec->erh);
+ *isnull = false;
+ }
+ if (rec->rectypeid != RECORDOID)
+ {
+ /* Report variable's declared type, if not RECORD */
+ *typeid = rec->rectypeid;
+ *typetypmod = -1;
+ }
+ else
+ {
+ /* Report record's actual type if declared RECORD */
+ *typeid = rec->erh->er_typeid;
+ *typetypmod = rec->erh->er_typmod;
+ }
+ }
+ break;
+ }
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ {
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+ ExpandedRecordHeader *erh;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+ erh = rec->erh;
+
+ /*
+ * If record variable is NULL, instantiate it if it has a
+ * named composite type, else complain. (This won't change
+ * the logical state of the record: it's still NULL.)
+ */
+ if (erh == NULL)
+ {
+ instantiate_empty_record_variable(estate, rec);
+ erh = rec->erh;
+ }
+
+ /*
+ * Look up the field's properties if we have not already, or
+ * if the tuple descriptor ID changed since last time.
+ */
+ if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id))
+ {
+ if (!expanded_record_lookup_field(erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ recfield->rectupledescid = erh->er_tupdesc_id;
+ }
+
+ /* Report type data. */
+ *typeid = recfield->finfo.ftypeid;
+ *typetypmod = recfield->finfo.ftypmod;
+
+ /* And fetch the field value. */
+ *value = expanded_record_get_field(erh,
+ recfield->finfo.fnumber,
+ isnull);
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ }
+}
+
+/*
+ * plpgsql_exec_get_datum_type Get datatype of a PLpgSQL_datum
+ *
+ * This is the same logic as in exec_eval_datum, but we skip acquiring
+ * the actual value of the variable. Also, needn't support DTYPE_ROW.
+ */
+Oid
+plpgsql_exec_get_datum_type(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum)
+{
+ Oid typeid;
+
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+ typeid = var->datatype->typoid;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ if (rec->erh == NULL || rec->rectypeid != RECORDOID)
+ {
+ /* Report variable's declared type */
+ typeid = rec->rectypeid;
+ }
+ else
+ {
+ /* Report record's actual type if declared RECORD */
+ typeid = rec->erh->er_typeid;
+ }
+ break;
+ }
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ {
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+
+ /*
+ * If record variable is NULL, instantiate it if it has a
+ * named composite type, else complain. (This won't change
+ * the logical state of the record: it's still NULL.)
+ */
+ if (rec->erh == NULL)
+ instantiate_empty_record_variable(estate, rec);
+
+ /*
+ * Look up the field's properties if we have not already, or
+ * if the tuple descriptor ID changed since last time.
+ */
+ if (unlikely(recfield->rectupledescid != rec->erh->er_tupdesc_id))
+ {
+ if (!expanded_record_lookup_field(rec->erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ recfield->rectupledescid = rec->erh->er_tupdesc_id;
+ }
+
+ typeid = recfield->finfo.ftypeid;
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ typeid = InvalidOid; /* keep compiler quiet */
+ break;
+ }
+
+ return typeid;
+}
+
+/*
+ * plpgsql_exec_get_datum_type_info Get datatype etc of a PLpgSQL_datum
+ *
+ * An extended version of plpgsql_exec_get_datum_type, which also retrieves the
+ * typmod and collation of the datum. Note however that we don't report the
+ * possibly-mutable typmod of RECORD values, but say -1 always.
+ */
+void
+plpgsql_exec_get_datum_type_info(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum,
+ Oid *typeId, int32 *typMod, Oid *collation)
+{
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) datum;
+
+ *typeId = var->datatype->typoid;
+ *typMod = var->datatype->atttypmod;
+ *collation = var->datatype->collation;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) datum;
+
+ if (rec->erh == NULL || rec->rectypeid != RECORDOID)
+ {
+ /* Report variable's declared type */
+ *typeId = rec->rectypeid;
+ *typMod = -1;
+ }
+ else
+ {
+ /* Report record's actual type if declared RECORD */
+ *typeId = rec->erh->er_typeid;
+ /* do NOT return the mutable typmod of a RECORD variable */
+ *typMod = -1;
+ }
+ /* composite types are never collatable */
+ *collation = InvalidOid;
+ break;
+ }
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ {
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+
+ /*
+ * If record variable is NULL, instantiate it if it has a
+ * named composite type, else complain. (This won't change
+ * the logical state of the record: it's still NULL.)
+ */
+ if (rec->erh == NULL)
+ instantiate_empty_record_variable(estate, rec);
+
+ /*
+ * Look up the field's properties if we have not already, or
+ * if the tuple descriptor ID changed since last time.
+ */
+ if (unlikely(recfield->rectupledescid != rec->erh->er_tupdesc_id))
+ {
+ if (!expanded_record_lookup_field(rec->erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ recfield->rectupledescid = rec->erh->er_tupdesc_id;
+ }
+
+ *typeId = recfield->finfo.ftypeid;
+ *typMod = recfield->finfo.ftypmod;
+ *collation = recfield->finfo.fcollation;
+ break;
+ }
+
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ *typeId = InvalidOid; /* keep compiler quiet */
+ *typMod = -1;
+ *collation = InvalidOid;
+ break;
+ }
+}
+
+/* ----------
+ * exec_eval_integer Evaluate an expression, coerce result to int4
+ *
+ * Note we do not do exec_eval_cleanup here; the caller must do it at
+ * some later point. (We do this because the caller may be holding the
+ * results of other, pass-by-reference, expression evaluations, such as
+ * an array value to be subscripted.)
+ * ----------
+ */
+static int
+exec_eval_integer(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull)
+{
+ Datum exprdatum;
+ Oid exprtypeid;
+ int32 exprtypmod;
+
+ exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
+ exprdatum = exec_cast_value(estate, exprdatum, isNull,
+ exprtypeid, exprtypmod,
+ INT4OID, -1);
+ return DatumGetInt32(exprdatum);
+}
+
+/* ----------
+ * exec_eval_boolean Evaluate an expression, coerce result to bool
+ *
+ * Note we do not do exec_eval_cleanup here; the caller must do it at
+ * some later point.
+ * ----------
+ */
+static bool
+exec_eval_boolean(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull)
+{
+ Datum exprdatum;
+ Oid exprtypeid;
+ int32 exprtypmod;
+
+ exprdatum = exec_eval_expr(estate, expr, isNull, &exprtypeid, &exprtypmod);
+ exprdatum = exec_cast_value(estate, exprdatum, isNull,
+ exprtypeid, exprtypmod,
+ BOOLOID, -1);
+ return DatumGetBool(exprdatum);
+}
+
+/* ----------
+ * exec_eval_expr Evaluate an expression and return
+ * the result Datum, along with data type/typmod.
+ *
+ * NOTE: caller must do exec_eval_cleanup when done with the Datum.
+ * ----------
+ */
+static Datum
+exec_eval_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ bool *isNull,
+ Oid *rettype,
+ int32 *rettypmod)
+{
+ Datum result = 0;
+ int rc;
+ Form_pg_attribute attr;
+
+ /*
+ * If first time through, create a plan for this expression.
+ */
+ if (expr->plan == NULL)
+ exec_prepare_plan(estate, expr, CURSOR_OPT_PARALLEL_OK);
+
+ /*
+ * If this is a simple expression, bypass SPI and use the executor
+ * directly
+ */
+ if (exec_eval_simple_expr(estate, expr,
+ &result, isNull, rettype, rettypmod))
+ return result;
+
+ /*
+ * Else do it the hard way via exec_run_select
+ */
+ rc = exec_run_select(estate, expr, 2, NULL);
+ if (rc != SPI_OK_SELECT)
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("query did not return data"),
+ errcontext("query: %s", expr->query)));
+
+ /*
+ * Check that the expression returns exactly one column...
+ */
+ if (estate->eval_tuptable->tupdesc->natts != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg_plural("query returned %d column",
+ "query returned %d columns",
+ estate->eval_tuptable->tupdesc->natts,
+ estate->eval_tuptable->tupdesc->natts),
+ errcontext("query: %s", expr->query)));
+
+ /*
+ * ... and get the column's datatype.
+ */
+ attr = TupleDescAttr(estate->eval_tuptable->tupdesc, 0);
+ *rettype = attr->atttypid;
+ *rettypmod = attr->atttypmod;
+
+ /*
+ * If there are no rows selected, the result is a NULL of that type.
+ */
+ if (estate->eval_processed == 0)
+ {
+ *isNull = true;
+ return (Datum) 0;
+ }
+
+ /*
+ * Check that the expression returned no more than one row.
+ */
+ if (estate->eval_processed != 1)
+ ereport(ERROR,
+ (errcode(ERRCODE_CARDINALITY_VIOLATION),
+ errmsg("query returned more than one row"),
+ errcontext("query: %s", expr->query)));
+
+ /*
+ * Return the single result Datum.
+ */
+ return SPI_getbinval(estate->eval_tuptable->vals[0],
+ estate->eval_tuptable->tupdesc, 1, isNull);
+}
+
+
+/* ----------
+ * exec_run_select Execute a select query
+ * ----------
+ */
+static int
+exec_run_select(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr, long maxtuples, Portal *portalP)
+{
+ ParamListInfo paramLI;
+ int rc;
+
+ /*
+ * On the first call for this expression generate the plan.
+ *
+ * If we don't need to return a portal, then we're just going to execute
+ * the query immediately, which means it's OK to use a parallel plan, even
+ * if the number of rows being fetched is limited. If we do need to
+ * return a portal (i.e., this is for a FOR loop), the user's code might
+ * invoke additional operations inside the FOR loop, making parallel query
+ * unsafe. In any case, we don't expect any cursor operations to be done,
+ * so specify NO_SCROLL for efficiency and semantic safety.
+ */
+ if (expr->plan == NULL)
+ {
+ int cursorOptions = CURSOR_OPT_NO_SCROLL;
+
+ if (portalP == NULL)
+ cursorOptions |= CURSOR_OPT_PARALLEL_OK;
+ exec_prepare_plan(estate, expr, cursorOptions);
+ }
+
+ /*
+ * Set up ParamListInfo to pass to executor
+ */
+ paramLI = setup_param_list(estate, expr);
+
+ /*
+ * If a portal was requested, put the query and paramlist into the portal
+ */
+ if (portalP != NULL)
+ {
+ *portalP = SPI_cursor_open_with_paramlist(NULL, expr->plan,
+ paramLI,
+ estate->readonly_func);
+ if (*portalP == NULL)
+ elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
+ expr->query, SPI_result_code_string(SPI_result));
+ exec_eval_cleanup(estate);
+ return SPI_OK_CURSOR;
+ }
+
+ /*
+ * Execute the query
+ */
+ rc = SPI_execute_plan_with_paramlist(expr->plan, paramLI,
+ estate->readonly_func, maxtuples);
+ if (rc != SPI_OK_SELECT)
+ {
+ /*
+ * SELECT INTO deserves a special error message, because "query is not
+ * a SELECT" is not very helpful in that case.
+ */
+ if (rc == SPI_OK_SELINTO)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("query is SELECT INTO, but it should be plain SELECT"),
+ errcontext("query: %s", expr->query)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("query is not a SELECT"),
+ errcontext("query: %s", expr->query)));
+ }
+
+ /* Save query results for eventual cleanup */
+ Assert(estate->eval_tuptable == NULL);
+ estate->eval_tuptable = SPI_tuptable;
+ estate->eval_processed = SPI_processed;
+
+ return rc;
+}
+
+
+/*
+ * exec_for_query --- execute body of FOR loop for each row from a portal
+ *
+ * Used by exec_stmt_fors, exec_stmt_forc and exec_stmt_dynfors
+ */
+static int
+exec_for_query(PLpgSQL_execstate *estate, PLpgSQL_stmt_forq *stmt,
+ Portal portal, bool prefetch_ok)
+{
+ PLpgSQL_variable *var;
+ SPITupleTable *tuptab;
+ bool found = false;
+ int rc = PLPGSQL_RC_OK;
+ uint64 previous_id = INVALID_TUPLEDESC_IDENTIFIER;
+ bool tupdescs_match = true;
+ uint64 n;
+
+ /* Fetch loop variable's datum entry */
+ var = (PLpgSQL_variable *) estate->datums[stmt->var->dno];
+
+ /*
+ * Make sure the portal doesn't get closed by the user statements we
+ * execute.
+ */
+ PinPortal(portal);
+
+ /*
+ * In a non-atomic context, we dare not prefetch, even if it would
+ * otherwise be safe. Aside from any semantic hazards that that might
+ * create, if we prefetch toasted data and then the user commits the
+ * transaction, the toast references could turn into dangling pointers.
+ * (Rows we haven't yet fetched from the cursor are safe, because the
+ * PersistHoldablePortal mechanism handles this scenario.)
+ */
+ if (!estate->atomic)
+ prefetch_ok = false;
+
+ /*
+ * Fetch the initial tuple(s). If prefetching is allowed then we grab a
+ * few more rows to avoid multiple trips through executor startup
+ * overhead.
+ */
+ SPI_cursor_fetch(portal, true, prefetch_ok ? 10 : 1);
+ tuptab = SPI_tuptable;
+ n = SPI_processed;
+
+ /*
+ * If the query didn't return any rows, set the target to NULL and fall
+ * through with found = false.
+ */
+ if (n == 0)
+ {
+ exec_move_row(estate, var, NULL, tuptab->tupdesc);
+ exec_eval_cleanup(estate);
+ }
+ else
+ found = true; /* processed at least one tuple */
+
+ /*
+ * Now do the loop
+ */
+ while (n > 0)
+ {
+ uint64 i;
+
+ for (i = 0; i < n; i++)
+ {
+ /*
+ * Assign the tuple to the target. Here, because we know that all
+ * loop iterations should be assigning the same tupdesc, we can
+ * optimize away repeated creations of expanded records with
+ * identical tupdescs. Testing for changes of er_tupdesc_id is
+ * reliable even if the loop body contains assignments that
+ * replace the target's value entirely, because it's assigned from
+ * a process-global counter. The case where the tupdescs don't
+ * match could possibly be handled more efficiently than this
+ * coding does, but it's not clear extra effort is worthwhile.
+ */
+ if (var->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) var;
+
+ if (rec->erh &&
+ rec->erh->er_tupdesc_id == previous_id &&
+ tupdescs_match)
+ {
+ /* Only need to assign a new tuple value */
+ expanded_record_set_tuple(rec->erh, tuptab->vals[i],
+ true, !estate->atomic);
+ }
+ else
+ {
+ /*
+ * First time through, or var's tupdesc changed in loop,
+ * or we have to do it the hard way because type coercion
+ * is needed.
+ */
+ exec_move_row(estate, var,
+ tuptab->vals[i], tuptab->tupdesc);
+
+ /*
+ * Check to see if physical assignment is OK next time.
+ * Once the tupdesc comparison has failed once, we don't
+ * bother rechecking in subsequent loop iterations.
+ */
+ if (tupdescs_match)
+ {
+ tupdescs_match =
+ (rec->rectypeid == RECORDOID ||
+ rec->rectypeid == tuptab->tupdesc->tdtypeid ||
+ compatible_tupdescs(tuptab->tupdesc,
+ expanded_record_get_tupdesc(rec->erh)));
+ }
+ previous_id = rec->erh->er_tupdesc_id;
+ }
+ }
+ else
+ exec_move_row(estate, var, tuptab->vals[i], tuptab->tupdesc);
+
+ exec_eval_cleanup(estate);
+
+ /*
+ * Execute the statements
+ */
+ rc = exec_stmts(estate, stmt->body);
+
+ LOOP_RC_PROCESSING(stmt->label, goto loop_exit);
+ }
+
+ SPI_freetuptable(tuptab);
+
+ /*
+ * Fetch more tuples. If prefetching is allowed, grab 50 at a time.
+ */
+ SPI_cursor_fetch(portal, true, prefetch_ok ? 50 : 1);
+ tuptab = SPI_tuptable;
+ n = SPI_processed;
+ }
+
+loop_exit:
+
+ /*
+ * Release last group of tuples (if any)
+ */
+ SPI_freetuptable(tuptab);
+
+ UnpinPortal(portal);
+
+ /*
+ * Set the FOUND variable to indicate the result of executing the loop
+ * (namely, whether we looped one or more times). This must be set last so
+ * that it does not interfere with the value of the FOUND variable inside
+ * the loop processing itself.
+ */
+ exec_set_found(estate, found);
+
+ return rc;
+}
+
+
+/* ----------
+ * exec_eval_simple_expr - Evaluate a simple expression returning
+ * a Datum by directly calling ExecEvalExpr().
+ *
+ * If successful, store results into *result, *isNull, *rettype, *rettypmod
+ * and return true. If the expression cannot be handled by simple evaluation,
+ * return false.
+ *
+ * Because we only store one execution tree for a simple expression, we
+ * can't handle recursion cases. So, if we see the tree is already busy
+ * with an evaluation in the current xact, we just return false and let the
+ * caller run the expression the hard way. (Other alternatives such as
+ * creating a new tree for a recursive call either introduce memory leaks,
+ * or add enough bookkeeping to be doubtful wins anyway.) Another case that
+ * is covered by the expr_simple_in_use test is where a previous execution
+ * of the tree was aborted by an error: the tree may contain bogus state
+ * so we dare not re-use it.
+ *
+ * It is possible that we'd need to replan a simple expression; for example,
+ * someone might redefine a SQL function that had been inlined into the simple
+ * expression. That cannot cause a simple expression to become non-simple (or
+ * vice versa), but we do have to handle replacing the expression tree.
+ *
+ * Note: if pass-by-reference, the result is in the eval_mcontext.
+ * It will be freed when exec_eval_cleanup is done.
+ * ----------
+ */
+static bool
+exec_eval_simple_expr(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *expr,
+ Datum *result,
+ bool *isNull,
+ Oid *rettype,
+ int32 *rettypmod)
+{
+ ExprContext *econtext = estate->eval_econtext;
+ LocalTransactionId curlxid = MyProc->lxid;
+ ParamListInfo paramLI;
+ void *save_setup_arg;
+ bool need_snapshot;
+ MemoryContext oldcontext;
+
+ /*
+ * Forget it if expression wasn't simple before.
+ */
+ if (expr->expr_simple_expr == NULL)
+ return false;
+
+ /*
+ * If expression is in use in current xact, don't touch it.
+ */
+ if (unlikely(expr->expr_simple_in_use) &&
+ expr->expr_simple_lxid == curlxid)
+ return false;
+
+ /*
+ * Ensure that there's a portal-level snapshot, in case this simple
+ * expression is the first thing evaluated after a COMMIT or ROLLBACK.
+ * We'd have to do this anyway before executing the expression, so we
+ * might as well do it now to ensure that any possible replanning doesn't
+ * need to take a new snapshot.
+ */
+ EnsurePortalSnapshotExists();
+
+ /*
+ * Check to see if the cached plan has been invalidated. If not, and this
+ * is the first use in the current transaction, save a plan refcount in
+ * the simple-expression resowner.
+ */
+ if (likely(CachedPlanIsSimplyValid(expr->expr_simple_plansource,
+ expr->expr_simple_plan,
+ (expr->expr_simple_plan_lxid != curlxid ?
+ estate->simple_eval_resowner : NULL))))
+ {
+ /*
+ * It's still good, so just remember that we have a refcount on the
+ * plan in the current transaction. (If we already had one, this
+ * assignment is a no-op.)
+ */
+ expr->expr_simple_plan_lxid = curlxid;
+ }
+ else
+ {
+ /* Need to replan */
+ CachedPlan *cplan;
+
+ /*
+ * If we have a valid refcount on some previous version of the plan,
+ * release it, so we don't leak plans intra-transaction.
+ */
+ if (expr->expr_simple_plan_lxid == curlxid)
+ {
+ ReleaseCachedPlan(expr->expr_simple_plan,
+ estate->simple_eval_resowner);
+ expr->expr_simple_plan = NULL;
+ expr->expr_simple_plan_lxid = InvalidLocalTransactionId;
+ }
+
+ /* Do the replanning work in the eval_mcontext */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ cplan = SPI_plan_get_cached_plan(expr->plan);
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * We can't get a failure here, because the number of
+ * CachedPlanSources in the SPI plan can't change from what
+ * exec_simple_check_plan saw; it's a property of the raw parsetree
+ * generated from the query text.
+ */
+ Assert(cplan != NULL);
+
+ /*
+ * This test probably can't fail either, but if it does, cope by
+ * declaring the plan to be non-simple. On success, we'll acquire a
+ * refcount on the new plan, stored in simple_eval_resowner.
+ */
+ if (CachedPlanAllowsSimpleValidityCheck(expr->expr_simple_plansource,
+ cplan,
+ estate->simple_eval_resowner))
+ {
+ /* Remember that we have the refcount */
+ expr->expr_simple_plan = cplan;
+ expr->expr_simple_plan_lxid = curlxid;
+ }
+ else
+ {
+ /* Release SPI_plan_get_cached_plan's refcount */
+ ReleaseCachedPlan(cplan, CurrentResourceOwner);
+ /* Mark expression as non-simple, and fail */
+ expr->expr_simple_expr = NULL;
+ expr->expr_rw_param = NULL;
+ return false;
+ }
+
+ /*
+ * SPI_plan_get_cached_plan acquired a plan refcount stored in the
+ * active resowner. We don't need that anymore, so release it.
+ */
+ ReleaseCachedPlan(cplan, CurrentResourceOwner);
+
+ /* Extract desired scalar expression from cached plan */
+ exec_save_simple_expr(expr, cplan);
+ }
+
+ /*
+ * Pass back previously-determined result type.
+ */
+ *rettype = expr->expr_simple_type;
+ *rettypmod = expr->expr_simple_typmod;
+
+ /*
+ * Set up ParamListInfo to pass to executor. For safety, save and restore
+ * estate->paramLI->parserSetupArg around our use of the param list.
+ */
+ paramLI = estate->paramLI;
+ save_setup_arg = paramLI->parserSetupArg;
+
+ /*
+ * We can skip using setup_param_list() in favor of just doing this
+ * unconditionally, because there's no need for the optimization of
+ * possibly setting ecxt_param_list_info to NULL; we've already forced use
+ * of a generic plan.
+ */
+ paramLI->parserSetupArg = (void *) expr;
+ econtext->ecxt_param_list_info = paramLI;
+
+ /*
+ * Prepare the expression for execution, if it's not been done already in
+ * the current transaction. (This will be forced to happen if we called
+ * exec_save_simple_expr above.)
+ */
+ if (unlikely(expr->expr_simple_lxid != curlxid))
+ {
+ oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
+ expr->expr_simple_state =
+ ExecInitExprWithParams(expr->expr_simple_expr,
+ econtext->ecxt_param_list_info);
+ expr->expr_simple_in_use = false;
+ expr->expr_simple_lxid = curlxid;
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /*
+ * We have to do some of the things SPI_execute_plan would do, in
+ * particular push a new snapshot so that stable functions within the
+ * expression can see updates made so far by our own function. However,
+ * we can skip doing that (and just invoke the expression with the same
+ * snapshot passed to our function) in some cases, which is useful because
+ * it's quite expensive relative to the cost of a simple expression. We
+ * can skip it if the expression contains no stable or volatile functions;
+ * immutable functions shouldn't need to see our updates. Also, if this
+ * is a read-only function, we haven't made any updates so again it's okay
+ * to skip.
+ */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ need_snapshot = (expr->expr_simple_mutable && !estate->readonly_func);
+ if (need_snapshot)
+ {
+ CommandCounterIncrement();
+ PushActiveSnapshot(GetTransactionSnapshot());
+ }
+
+ /*
+ * Mark expression as busy for the duration of the ExecEvalExpr call.
+ */
+ expr->expr_simple_in_use = true;
+
+ /*
+ * Finally we can call the executor to evaluate the expression
+ */
+ *result = ExecEvalExpr(expr->expr_simple_state,
+ econtext,
+ isNull);
+
+ /* Assorted cleanup */
+ expr->expr_simple_in_use = false;
+
+ econtext->ecxt_param_list_info = NULL;
+
+ paramLI->parserSetupArg = save_setup_arg;
+
+ if (need_snapshot)
+ PopActiveSnapshot();
+
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * That's it.
+ */
+ return true;
+}
+
+
+/*
+ * Create a ParamListInfo to pass to SPI
+ *
+ * We use a single ParamListInfo struct for all SPI calls made to evaluate
+ * PLpgSQL_exprs in this estate. It contains no per-param data, just hook
+ * functions, so it's effectively read-only for SPI.
+ *
+ * An exception from pure read-only-ness is that the parserSetupArg points
+ * to the specific PLpgSQL_expr being evaluated. This is not an issue for
+ * statement-level callers, but lower-level callers must save and restore
+ * estate->paramLI->parserSetupArg just in case there's an active evaluation
+ * at an outer call level. (A plausible alternative design would be to
+ * create a ParamListInfo struct for each PLpgSQL_expr, but for the moment
+ * that seems like a waste of memory.)
+ */
+static ParamListInfo
+setup_param_list(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
+{
+ ParamListInfo paramLI;
+
+ /*
+ * We must have created the SPIPlan already (hence, query text has been
+ * parsed/analyzed at least once); else we cannot rely on expr->paramnos.
+ */
+ Assert(expr->plan != NULL);
+
+ /*
+ * We only need a ParamListInfo if the expression has parameters. In
+ * principle we should test with bms_is_empty(), but we use a not-null
+ * test because it's faster. In current usage bits are never removed from
+ * expr->paramnos, only added, so this test is correct anyway.
+ */
+ if (expr->paramnos)
+ {
+ /* Use the common ParamListInfo */
+ paramLI = estate->paramLI;
+
+ /*
+ * Set up link to active expr where the hook functions can find it.
+ * Callers must save and restore parserSetupArg if there is any chance
+ * that they are interrupting an active use of parameters.
+ */
+ paramLI->parserSetupArg = (void *) expr;
+
+ /*
+ * Also make sure this is set before parser hooks need it. There is
+ * no need to save and restore, since the value is always correct once
+ * set. (Should be set already, but let's be sure.)
+ */
+ expr->func = estate->func;
+ }
+ else
+ {
+ /*
+ * Expression requires no parameters. Be sure we represent this case
+ * as a NULL ParamListInfo, so that plancache.c knows there is no
+ * point in a custom plan.
+ */
+ paramLI = NULL;
+ }
+ return paramLI;
+}
+
+/*
+ * plpgsql_param_fetch paramFetch callback for dynamic parameter fetch
+ *
+ * We always use the caller's workspace to construct the returned struct.
+ *
+ * Note: this is no longer used during query execution. It is used during
+ * planning (with speculative == true) and when the ParamListInfo we supply
+ * to the executor is copied into a cursor portal or transferred to a
+ * parallel child process.
+ */
+static ParamExternData *
+plpgsql_param_fetch(ParamListInfo params,
+ int paramid, bool speculative,
+ ParamExternData *prm)
+{
+ int dno;
+ PLpgSQL_execstate *estate;
+ PLpgSQL_expr *expr;
+ PLpgSQL_datum *datum;
+ bool ok = true;
+ int32 prmtypmod;
+
+ /* paramid's are 1-based, but dnos are 0-based */
+ dno = paramid - 1;
+ Assert(dno >= 0 && dno < params->numParams);
+
+ /* fetch back the hook data */
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ expr = (PLpgSQL_expr *) params->parserSetupArg;
+ Assert(params->numParams == estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+
+ /*
+ * Since copyParamList() or SerializeParamList() will try to materialize
+ * every single parameter slot, it's important to return a dummy param
+ * when asked for a datum that's not supposed to be used by this SQL
+ * expression. Otherwise we risk failures in exec_eval_datum(), or
+ * copying a lot more data than necessary.
+ */
+ if (!bms_is_member(dno, expr->paramnos))
+ ok = false;
+
+ /*
+ * If the access is speculative, we prefer to return no data rather than
+ * to fail in exec_eval_datum(). Check the likely failure cases.
+ */
+ else if (speculative)
+ {
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ /* always safe */
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
+ /* should be safe in all interesting cases */
+ break;
+
+ case PLPGSQL_DTYPE_REC:
+ /* always safe (might return NULL, that's fine) */
+ break;
+
+ case PLPGSQL_DTYPE_RECFIELD:
+ {
+ PLpgSQL_recfield *recfield = (PLpgSQL_recfield *) datum;
+ PLpgSQL_rec *rec;
+
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+
+ /*
+ * If record variable is NULL, don't risk anything.
+ */
+ if (rec->erh == NULL)
+ ok = false;
+
+ /*
+ * Look up the field's properties if we have not already,
+ * or if the tuple descriptor ID changed since last time.
+ */
+ else if (unlikely(recfield->rectupledescid != rec->erh->er_tupdesc_id))
+ {
+ if (expanded_record_lookup_field(rec->erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ recfield->rectupledescid = rec->erh->er_tupdesc_id;
+ else
+ ok = false;
+ }
+ break;
+ }
+
+ default:
+ ok = false;
+ break;
+ }
+ }
+
+ /* Return "no such parameter" if not ok */
+ if (!ok)
+ {
+ prm->value = (Datum) 0;
+ prm->isnull = true;
+ prm->pflags = 0;
+ prm->ptype = InvalidOid;
+ return prm;
+ }
+
+ /* OK, evaluate the value and store into the return struct */
+ exec_eval_datum(estate, datum,
+ &prm->ptype, &prmtypmod,
+ &prm->value, &prm->isnull);
+ /* We can always mark params as "const" for executor's purposes */
+ prm->pflags = PARAM_FLAG_CONST;
+
+ /*
+ * If it's a read/write expanded datum, convert reference to read-only.
+ * (There's little point in trying to optimize read/write parameters,
+ * given the cases in which this function is used.)
+ */
+ if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ prm->value = MakeExpandedObjectReadOnly(prm->value,
+ prm->isnull,
+ ((PLpgSQL_var *) datum)->datatype->typlen);
+ else if (datum->dtype == PLPGSQL_DTYPE_REC)
+ prm->value = MakeExpandedObjectReadOnly(prm->value,
+ prm->isnull,
+ -1);
+
+ return prm;
+}
+
+/*
+ * plpgsql_param_compile paramCompile callback for plpgsql parameters
+ */
+static void
+plpgsql_param_compile(ParamListInfo params, Param *param,
+ ExprState *state,
+ Datum *resv, bool *resnull)
+{
+ PLpgSQL_execstate *estate;
+ PLpgSQL_expr *expr;
+ int dno;
+ PLpgSQL_datum *datum;
+ ExprEvalStep scratch;
+
+ /* fetch back the hook data */
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ expr = (PLpgSQL_expr *) params->parserSetupArg;
+
+ /* paramid's are 1-based, but dnos are 0-based */
+ dno = param->paramid - 1;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+
+ scratch.opcode = EEOP_PARAM_CALLBACK;
+ scratch.resvalue = resv;
+ scratch.resnull = resnull;
+
+ /*
+ * Select appropriate eval function. It seems worth special-casing
+ * DTYPE_VAR and DTYPE_RECFIELD for performance. Also, we can determine
+ * in advance whether MakeExpandedObjectReadOnly() will be required.
+ * Currently, only VAR/PROMISE and REC datums could contain read/write
+ * expanded objects.
+ */
+ if (datum->dtype == PLPGSQL_DTYPE_VAR)
+ {
+ if (param != expr->expr_rw_param &&
+ ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_var_ro;
+ else
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_var;
+ }
+ else if (datum->dtype == PLPGSQL_DTYPE_RECFIELD)
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_recfield;
+ else if (datum->dtype == PLPGSQL_DTYPE_PROMISE)
+ {
+ if (param != expr->expr_rw_param &&
+ ((PLpgSQL_var *) datum)->datatype->typlen == -1)
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
+ else
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
+ }
+ else if (datum->dtype == PLPGSQL_DTYPE_REC &&
+ param != expr->expr_rw_param)
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_generic_ro;
+ else
+ scratch.d.cparam.paramfunc = plpgsql_param_eval_generic;
+
+ /*
+ * Note: it's tempting to use paramarg to store the estate pointer and
+ * thereby save an indirection or two in the eval functions. But that
+ * doesn't work because the compiled expression might be used with
+ * different estates for the same PL/pgSQL function.
+ */
+ scratch.d.cparam.paramarg = NULL;
+ scratch.d.cparam.paramid = param->paramid;
+ scratch.d.cparam.paramtype = param->paramtype;
+ ExprEvalPushStep(state, &scratch);
+}
+
+/*
+ * plpgsql_param_eval_var evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This is specialized to the case of DTYPE_VAR variables for which
+ * we do not need to invoke MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_var(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_var *var;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ var = (PLpgSQL_var *) estate->datums[dno];
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+
+ /* inlined version of exec_eval_datum() */
+ *op->resvalue = var->value;
+ *op->resnull = var->isnull;
+
+ /* safety check -- an assertion should be sufficient */
+ Assert(var->datatype->typoid == op->d.cparam.paramtype);
+}
+
+/*
+ * plpgsql_param_eval_var_ro evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This is specialized to the case of DTYPE_VAR variables for which
+ * we need to invoke MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_var_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_var *var;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ var = (PLpgSQL_var *) estate->datums[dno];
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR);
+
+ /*
+ * Inlined version of exec_eval_datum() ... and while we're at it, force
+ * expanded datums to read-only.
+ */
+ *op->resvalue = MakeExpandedObjectReadOnly(var->value,
+ var->isnull,
+ -1);
+ *op->resnull = var->isnull;
+
+ /* safety check -- an assertion should be sufficient */
+ Assert(var->datatype->typoid == op->d.cparam.paramtype);
+}
+
+/*
+ * plpgsql_param_eval_recfield evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This is specialized to the case of DTYPE_RECFIELD variables, for which
+ * we never need to invoke MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_recfield(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_recfield *recfield;
+ PLpgSQL_rec *rec;
+ ExpandedRecordHeader *erh;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ recfield = (PLpgSQL_recfield *) estate->datums[dno];
+ Assert(recfield->dtype == PLPGSQL_DTYPE_RECFIELD);
+
+ /* inline the relevant part of exec_eval_datum */
+ rec = (PLpgSQL_rec *) (estate->datums[recfield->recparentno]);
+ erh = rec->erh;
+
+ /*
+ * If record variable is NULL, instantiate it if it has a named composite
+ * type, else complain. (This won't change the logical state of the
+ * record: it's still NULL.)
+ */
+ if (erh == NULL)
+ {
+ instantiate_empty_record_variable(estate, rec);
+ erh = rec->erh;
+ }
+
+ /*
+ * Look up the field's properties if we have not already, or if the tuple
+ * descriptor ID changed since last time.
+ */
+ if (unlikely(recfield->rectupledescid != erh->er_tupdesc_id))
+ {
+ if (!expanded_record_lookup_field(erh,
+ recfield->fieldname,
+ &recfield->finfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("record \"%s\" has no field \"%s\"",
+ rec->refname, recfield->fieldname)));
+ recfield->rectupledescid = erh->er_tupdesc_id;
+ }
+
+ /* OK to fetch the field value. */
+ *op->resvalue = expanded_record_get_field(erh,
+ recfield->finfo.fnumber,
+ op->resnull);
+
+ /* safety check -- needed for, eg, record fields */
+ if (unlikely(recfield->finfo.ftypeid != op->d.cparam.paramtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ op->d.cparam.paramid,
+ format_type_be(recfield->finfo.ftypeid),
+ format_type_be(op->d.cparam.paramtype))));
+}
+
+/*
+ * plpgsql_param_eval_generic evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This handles all variable types, but assumes we do not need to invoke
+ * MakeExpandedObjectReadOnly.
+ */
+static void
+plpgsql_param_eval_generic(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_datum *datum;
+ Oid datumtype;
+ int32 datumtypmod;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+
+ /* fetch datum's value */
+ exec_eval_datum(estate, datum,
+ &datumtype, &datumtypmod,
+ op->resvalue, op->resnull);
+
+ /* safety check -- needed for, eg, record fields */
+ if (unlikely(datumtype != op->d.cparam.paramtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ op->d.cparam.paramid,
+ format_type_be(datumtype),
+ format_type_be(op->d.cparam.paramtype))));
+}
+
+/*
+ * plpgsql_param_eval_generic_ro evaluation of EEOP_PARAM_CALLBACK step
+ *
+ * This handles all variable types, but assumes we need to invoke
+ * MakeExpandedObjectReadOnly (hence, variable must be of a varlena type).
+ */
+static void
+plpgsql_param_eval_generic_ro(ExprState *state, ExprEvalStep *op,
+ ExprContext *econtext)
+{
+ ParamListInfo params;
+ PLpgSQL_execstate *estate;
+ int dno = op->d.cparam.paramid - 1;
+ PLpgSQL_datum *datum;
+ Oid datumtype;
+ int32 datumtypmod;
+
+ /* fetch back the hook data */
+ params = econtext->ecxt_param_list_info;
+ estate = (PLpgSQL_execstate *) params->paramFetchArg;
+ Assert(dno >= 0 && dno < estate->ndatums);
+
+ /* now we can access the target datum */
+ datum = estate->datums[dno];
+
+ /* fetch datum's value */
+ exec_eval_datum(estate, datum,
+ &datumtype, &datumtypmod,
+ op->resvalue, op->resnull);
+
+ /* safety check -- needed for, eg, record fields */
+ if (unlikely(datumtype != op->d.cparam.paramtype))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("type of parameter %d (%s) does not match that when preparing the plan (%s)",
+ op->d.cparam.paramid,
+ format_type_be(datumtype),
+ format_type_be(op->d.cparam.paramtype))));
+
+ /* force the value to read-only */
+ *op->resvalue = MakeExpandedObjectReadOnly(*op->resvalue,
+ *op->resnull,
+ -1);
+}
+
+
+/*
+ * exec_move_row Move one tuple's values into a record or row
+ *
+ * tup and tupdesc may both be NULL if we're just assigning an indeterminate
+ * composite NULL to the target. Alternatively, can have tup be NULL and
+ * tupdesc not NULL, in which case we assign a row of NULLs to the target.
+ *
+ * Since this uses the mcontext for workspace, caller should eventually call
+ * exec_eval_cleanup to prevent long-term memory leaks.
+ */
+static void
+exec_move_row(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ HeapTuple tup, TupleDesc tupdesc)
+{
+ ExpandedRecordHeader *newerh = NULL;
+
+ /*
+ * If target is RECORD, we may be able to avoid field-by-field processing.
+ */
+ if (target->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
+
+ /*
+ * If we have no source tupdesc, just set the record variable to NULL.
+ * (If we have a source tupdesc but not a tuple, we'll set the
+ * variable to a row of nulls, instead. This is odd perhaps, but
+ * backwards compatible.)
+ */
+ if (tupdesc == NULL)
+ {
+ if (rec->datatype &&
+ rec->datatype->typtype == TYPTYPE_DOMAIN)
+ {
+ /*
+ * If it's a composite domain, NULL might not be a legal
+ * value, so we instead need to make an empty expanded record
+ * and ensure that domain type checking gets done. If there
+ * is already an expanded record, piggyback on its lookups.
+ */
+ newerh = make_expanded_record_for_rec(estate, rec,
+ NULL, rec->erh);
+ expanded_record_set_tuple(newerh, NULL, false, false);
+ assign_record_var(estate, rec, newerh);
+ }
+ else
+ {
+ /* Just clear it to NULL */
+ if (rec->erh)
+ DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
+ rec->erh = NULL;
+ }
+ return;
+ }
+
+ /*
+ * Build a new expanded record with appropriate tupdesc.
+ */
+ newerh = make_expanded_record_for_rec(estate, rec, tupdesc, NULL);
+
+ /*
+ * If the rowtypes match, or if we have no tuple anyway, we can
+ * complete the assignment without field-by-field processing.
+ *
+ * The tests here are ordered more or less in order of cheapness. We
+ * can easily detect it will work if the target is declared RECORD or
+ * has the same typeid as the source. But when assigning from a query
+ * result, it's common to have a source tupdesc that's labeled RECORD
+ * but is actually physically compatible with a named-composite-type
+ * target, so it's worth spending extra cycles to check for that.
+ */
+ if (rec->rectypeid == RECORDOID ||
+ rec->rectypeid == tupdesc->tdtypeid ||
+ !HeapTupleIsValid(tup) ||
+ compatible_tupdescs(tupdesc, expanded_record_get_tupdesc(newerh)))
+ {
+ if (!HeapTupleIsValid(tup))
+ {
+ /* No data, so force the record into all-nulls state */
+ deconstruct_expanded_record(newerh);
+ }
+ else
+ {
+ /* No coercion is needed, so just assign the row value */
+ expanded_record_set_tuple(newerh, tup, true, !estate->atomic);
+ }
+
+ /* Complete the assignment */
+ assign_record_var(estate, rec, newerh);
+
+ return;
+ }
+ }
+
+ /*
+ * Otherwise, deconstruct the tuple and do field-by-field assignment,
+ * using exec_move_row_from_fields.
+ */
+ if (tupdesc && HeapTupleIsValid(tup))
+ {
+ int td_natts = tupdesc->natts;
+ Datum *values;
+ bool *nulls;
+ Datum values_local[64];
+ bool nulls_local[64];
+
+ /*
+ * Need workspace arrays. If td_natts is small enough, use local
+ * arrays to save doing a palloc. Even if it's not small, we can
+ * allocate both the Datum and isnull arrays in one palloc chunk.
+ */
+ if (td_natts <= lengthof(values_local))
+ {
+ values = values_local;
+ nulls = nulls_local;
+ }
+ else
+ {
+ char *chunk;
+
+ chunk = eval_mcontext_alloc(estate,
+ td_natts * (sizeof(Datum) + sizeof(bool)));
+ values = (Datum *) chunk;
+ nulls = (bool *) (chunk + td_natts * sizeof(Datum));
+ }
+
+ heap_deform_tuple(tup, tupdesc, values, nulls);
+
+ exec_move_row_from_fields(estate, target, newerh,
+ values, nulls, tupdesc);
+ }
+ else
+ {
+ /*
+ * Assign all-nulls.
+ */
+ exec_move_row_from_fields(estate, target, newerh,
+ NULL, NULL, NULL);
+ }
+}
+
+/*
+ * Verify that a PLpgSQL_rec's rectypeid is up-to-date.
+ */
+static void
+revalidate_rectypeid(PLpgSQL_rec *rec)
+{
+ PLpgSQL_type *typ = rec->datatype;
+ TypeCacheEntry *typentry;
+
+ if (rec->rectypeid == RECORDOID)
+ return; /* it's RECORD, so nothing to do */
+ Assert(typ != NULL);
+ if (typ->tcache &&
+ typ->tcache->tupDesc_identifier == typ->tupdesc_id)
+ {
+ /*
+ * Although *typ is known up-to-date, it's possible that rectypeid
+ * isn't, because *rec is cloned during each function startup from a
+ * copy that we don't have a good way to update. Hence, forcibly fix
+ * rectypeid before returning.
+ */
+ rec->rectypeid = typ->typoid;
+ return;
+ }
+
+ /*
+ * typcache entry has suffered invalidation, so re-look-up the type name
+ * if possible, and then recheck the type OID. If we don't have a
+ * TypeName, then we just have to soldier on with the OID we've got.
+ */
+ if (typ->origtypname != NULL)
+ {
+ /* this bit should match parse_datatype() in pl_gram.y */
+ typenameTypeIdAndMod(NULL, typ->origtypname,
+ &typ->typoid,
+ &typ->atttypmod);
+ }
+
+ /* this bit should match build_datatype() in pl_comp.c */
+ typentry = lookup_type_cache(typ->typoid,
+ TYPECACHE_TUPDESC |
+ TYPECACHE_DOMAIN_BASE_INFO);
+ if (typentry->typtype == TYPTYPE_DOMAIN)
+ typentry = lookup_type_cache(typentry->domainBaseType,
+ TYPECACHE_TUPDESC);
+ if (typentry->tupDesc == NULL)
+ {
+ /*
+ * If we get here, user tried to replace a composite type with a
+ * non-composite one. We're not gonna support that.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+ errmsg("type %s is not composite",
+ format_type_be(typ->typoid))));
+ }
+
+ /*
+ * Update tcache and tupdesc_id. Since we don't support changing to a
+ * non-composite type, none of the rest of *typ needs to change.
+ */
+ typ->tcache = typentry;
+ typ->tupdesc_id = typentry->tupDesc_identifier;
+
+ /*
+ * Update *rec, too. (We'll deal with subsidiary RECFIELDs as needed.)
+ */
+ rec->rectypeid = typ->typoid;
+}
+
+/*
+ * Build an expanded record object suitable for assignment to "rec".
+ *
+ * Caller must supply either a source tuple descriptor or a source expanded
+ * record (not both). If the record variable has declared type RECORD,
+ * it'll adopt the source's rowtype. Even if it doesn't, we may be able to
+ * piggyback on a source expanded record to save a typcache lookup.
+ *
+ * Caller must fill the object with data, then do assign_record_var().
+ *
+ * The new record is initially put into the mcontext, so it will be cleaned up
+ * if we fail before reaching assign_record_var().
+ */
+static ExpandedRecordHeader *
+make_expanded_record_for_rec(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec,
+ TupleDesc srctupdesc,
+ ExpandedRecordHeader *srcerh)
+{
+ ExpandedRecordHeader *newerh;
+ MemoryContext mcontext = get_eval_mcontext(estate);
+
+ if (rec->rectypeid != RECORDOID)
+ {
+ /*
+ * Make sure rec->rectypeid is up-to-date before using it.
+ */
+ revalidate_rectypeid(rec);
+
+ /*
+ * New record must be of desired type, but maybe srcerh has already
+ * done all the same lookups.
+ */
+ if (srcerh && rec->rectypeid == srcerh->er_decltypeid)
+ newerh = make_expanded_record_from_exprecord(srcerh,
+ mcontext);
+ else
+ newerh = make_expanded_record_from_typeid(rec->rectypeid, -1,
+ mcontext);
+ }
+ else
+ {
+ /*
+ * We'll adopt the input tupdesc. We can still use
+ * make_expanded_record_from_exprecord, if srcerh isn't a composite
+ * domain. (If it is, we effectively adopt its base type.)
+ */
+ if (srcerh && !ExpandedRecordIsDomain(srcerh))
+ newerh = make_expanded_record_from_exprecord(srcerh,
+ mcontext);
+ else
+ {
+ if (!srctupdesc)
+ srctupdesc = expanded_record_get_tupdesc(srcerh);
+ newerh = make_expanded_record_from_tupdesc(srctupdesc,
+ mcontext);
+ }
+ }
+
+ return newerh;
+}
+
+/*
+ * exec_move_row_from_fields Move arrays of field values into a record or row
+ *
+ * When assigning to a record, the caller must have already created a suitable
+ * new expanded record object, newerh. Pass NULL when assigning to a row.
+ *
+ * tupdesc describes the input row, which might have different column
+ * types and/or different dropped-column positions than the target.
+ * values/nulls/tupdesc can all be NULL if we just want to assign nulls to
+ * all fields of the record or row.
+ *
+ * Since this uses the mcontext for workspace, caller should eventually call
+ * exec_eval_cleanup to prevent long-term memory leaks.
+ */
+static void
+exec_move_row_from_fields(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ ExpandedRecordHeader *newerh,
+ Datum *values, bool *nulls,
+ TupleDesc tupdesc)
+{
+ int td_natts = tupdesc ? tupdesc->natts : 0;
+ int fnum;
+ int anum;
+ int strict_multiassignment_level = 0;
+
+ /*
+ * The extra check strict strict_multi_assignment can be active, only when
+ * input tupdesc is specified.
+ */
+ if (tupdesc != NULL)
+ {
+ if (plpgsql_extra_errors & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
+ strict_multiassignment_level = ERROR;
+ else if (plpgsql_extra_warnings & PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT)
+ strict_multiassignment_level = WARNING;
+ }
+
+ /* Handle RECORD-target case */
+ if (target->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
+ TupleDesc var_tupdesc;
+ Datum newvalues_local[64];
+ bool newnulls_local[64];
+
+ Assert(newerh != NULL); /* caller must have built new object */
+
+ var_tupdesc = expanded_record_get_tupdesc(newerh);
+
+ /*
+ * Coerce field values if needed. This might involve dealing with
+ * different sets of dropped columns and/or coercing individual column
+ * types. That's sort of a pain, but historically plpgsql has allowed
+ * it, so we preserve the behavior. However, it's worth a quick check
+ * to see if the tupdescs are identical. (Since expandedrecord.c
+ * prefers to use refcounted tupdescs from the typcache, expanded
+ * records with the same rowtype will have pointer-equal tupdescs.)
+ */
+ if (var_tupdesc != tupdesc)
+ {
+ int vtd_natts = var_tupdesc->natts;
+ Datum *newvalues;
+ bool *newnulls;
+
+ /*
+ * Need workspace arrays. If vtd_natts is small enough, use local
+ * arrays to save doing a palloc. Even if it's not small, we can
+ * allocate both the Datum and isnull arrays in one palloc chunk.
+ */
+ if (vtd_natts <= lengthof(newvalues_local))
+ {
+ newvalues = newvalues_local;
+ newnulls = newnulls_local;
+ }
+ else
+ {
+ char *chunk;
+
+ chunk = eval_mcontext_alloc(estate,
+ vtd_natts * (sizeof(Datum) + sizeof(bool)));
+ newvalues = (Datum *) chunk;
+ newnulls = (bool *) (chunk + vtd_natts * sizeof(Datum));
+ }
+
+ /* Walk over destination columns */
+ anum = 0;
+ for (fnum = 0; fnum < vtd_natts; fnum++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(var_tupdesc, fnum);
+ Datum value;
+ bool isnull;
+ Oid valtype;
+ int32 valtypmod;
+
+ if (attr->attisdropped)
+ {
+ /* expanded_record_set_fields should ignore this column */
+ continue; /* skip dropped column in record */
+ }
+
+ while (anum < td_natts &&
+ TupleDescAttr(tupdesc, anum)->attisdropped)
+ anum++; /* skip dropped column in tuple */
+
+ if (anum < td_natts)
+ {
+ value = values[anum];
+ isnull = nulls[anum];
+ valtype = TupleDescAttr(tupdesc, anum)->atttypid;
+ valtypmod = TupleDescAttr(tupdesc, anum)->atttypmod;
+ anum++;
+ }
+ else
+ {
+ /* no source for destination column */
+ value = (Datum) 0;
+ isnull = true;
+ valtype = UNKNOWNOID;
+ valtypmod = -1;
+
+ /* When source value is missing */
+ if (strict_multiassignment_level)
+ ereport(strict_multiassignment_level,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of source and target fields in assignment does not match"),
+ /* translator: %s represents a name of an extra check */
+ errdetail("%s check of %s is active.",
+ "strict_multi_assignment",
+ strict_multiassignment_level == ERROR ? "extra_errors" :
+ "extra_warnings"),
+ errhint("Make sure the query returns the exact list of columns.")));
+ }
+
+ /* Cast the new value to the right type, if needed. */
+ newvalues[fnum] = exec_cast_value(estate,
+ value,
+ &isnull,
+ valtype,
+ valtypmod,
+ attr->atttypid,
+ attr->atttypmod);
+ newnulls[fnum] = isnull;
+ }
+
+ /*
+ * When strict_multiassignment extra check is active, then ensure
+ * there are no unassigned source attributes.
+ */
+ if (strict_multiassignment_level && anum < td_natts)
+ {
+ /* skip dropped columns in the source descriptor */
+ while (anum < td_natts &&
+ TupleDescAttr(tupdesc, anum)->attisdropped)
+ anum++;
+
+ if (anum < td_natts)
+ ereport(strict_multiassignment_level,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of source and target fields in assignment does not match"),
+ /* translator: %s represents a name of an extra check */
+ errdetail("%s check of %s is active.",
+ "strict_multi_assignment",
+ strict_multiassignment_level == ERROR ? "extra_errors" :
+ "extra_warnings"),
+ errhint("Make sure the query returns the exact list of columns.")));
+ }
+
+ values = newvalues;
+ nulls = newnulls;
+ }
+
+ /* Insert the coerced field values into the new expanded record */
+ expanded_record_set_fields(newerh, values, nulls, !estate->atomic);
+
+ /* Complete the assignment */
+ assign_record_var(estate, rec, newerh);
+
+ return;
+ }
+
+ /* newerh should not have been passed in non-RECORD cases */
+ Assert(newerh == NULL);
+
+ /*
+ * For a row, we assign the individual field values to the variables the
+ * row points to.
+ *
+ * NOTE: both this code and the record code above silently ignore extra
+ * columns in the source and assume NULL for missing columns. This is
+ * pretty dubious but it's the historical behavior.
+ *
+ * If we have no input data at all, we'll assign NULL to all columns of
+ * the row variable.
+ */
+ if (target->dtype == PLPGSQL_DTYPE_ROW)
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) target;
+
+ anum = 0;
+ for (fnum = 0; fnum < row->nfields; fnum++)
+ {
+ PLpgSQL_var *var;
+ Datum value;
+ bool isnull;
+ Oid valtype;
+ int32 valtypmod;
+
+ var = (PLpgSQL_var *) (estate->datums[row->varnos[fnum]]);
+
+ while (anum < td_natts &&
+ TupleDescAttr(tupdesc, anum)->attisdropped)
+ anum++; /* skip dropped column in tuple */
+
+ if (anum < td_natts)
+ {
+ value = values[anum];
+ isnull = nulls[anum];
+ valtype = TupleDescAttr(tupdesc, anum)->atttypid;
+ valtypmod = TupleDescAttr(tupdesc, anum)->atttypmod;
+ anum++;
+ }
+ else
+ {
+ /* no source for destination column */
+ value = (Datum) 0;
+ isnull = true;
+ valtype = UNKNOWNOID;
+ valtypmod = -1;
+
+ if (strict_multiassignment_level)
+ ereport(strict_multiassignment_level,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of source and target fields in assignment does not match"),
+ /* translator: %s represents a name of an extra check */
+ errdetail("%s check of %s is active.",
+ "strict_multi_assignment",
+ strict_multiassignment_level == ERROR ? "extra_errors" :
+ "extra_warnings"),
+ errhint("Make sure the query returns the exact list of columns.")));
+ }
+
+ exec_assign_value(estate, (PLpgSQL_datum *) var,
+ value, isnull, valtype, valtypmod);
+ }
+
+ /*
+ * When strict_multiassignment extra check is active, ensure there are
+ * no unassigned source attributes.
+ */
+ if (strict_multiassignment_level && anum < td_natts)
+ {
+ while (anum < td_natts &&
+ TupleDescAttr(tupdesc, anum)->attisdropped)
+ anum++; /* skip dropped column in tuple */
+
+ if (anum < td_natts)
+ ereport(strict_multiassignment_level,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("number of source and target fields in assignment does not match"),
+ /* translator: %s represents a name of an extra check */
+ errdetail("%s check of %s is active.",
+ "strict_multi_assignment",
+ strict_multiassignment_level == ERROR ? "extra_errors" :
+ "extra_warnings"),
+ errhint("Make sure the query returns the exact list of columns.")));
+ }
+
+ return;
+ }
+
+ elog(ERROR, "unsupported target type: %d", target->dtype);
+}
+
+/*
+ * compatible_tupdescs: detect whether two tupdescs are physically compatible
+ *
+ * TRUE indicates that a tuple satisfying src_tupdesc can be used directly as
+ * a value for a composite variable using dst_tupdesc.
+ */
+static bool
+compatible_tupdescs(TupleDesc src_tupdesc, TupleDesc dst_tupdesc)
+{
+ int i;
+
+ /* Possibly we could allow src_tupdesc to have extra columns? */
+ if (dst_tupdesc->natts != src_tupdesc->natts)
+ return false;
+
+ for (i = 0; i < dst_tupdesc->natts; i++)
+ {
+ Form_pg_attribute dattr = TupleDescAttr(dst_tupdesc, i);
+ Form_pg_attribute sattr = TupleDescAttr(src_tupdesc, i);
+
+ if (dattr->attisdropped != sattr->attisdropped)
+ return false;
+ if (!dattr->attisdropped)
+ {
+ /* Normal columns must match by type and typmod */
+ if (dattr->atttypid != sattr->atttypid ||
+ (dattr->atttypmod >= 0 &&
+ dattr->atttypmod != sattr->atttypmod))
+ return false;
+ }
+ else
+ {
+ /* Dropped columns are OK as long as length/alignment match */
+ if (dattr->attlen != sattr->attlen ||
+ dattr->attalign != sattr->attalign)
+ return false;
+ }
+ }
+ return true;
+}
+
+/* ----------
+ * make_tuple_from_row Make a tuple from the values of a row object
+ *
+ * A NULL return indicates rowtype mismatch; caller must raise suitable error
+ *
+ * The result tuple is freshly palloc'd in caller's context. Some junk
+ * may be left behind in eval_mcontext, too.
+ * ----------
+ */
+static HeapTuple
+make_tuple_from_row(PLpgSQL_execstate *estate,
+ PLpgSQL_row *row,
+ TupleDesc tupdesc)
+{
+ int natts = tupdesc->natts;
+ HeapTuple tuple;
+ Datum *dvalues;
+ bool *nulls;
+ int i;
+
+ if (natts != row->nfields)
+ return NULL;
+
+ dvalues = (Datum *) eval_mcontext_alloc0(estate, natts * sizeof(Datum));
+ nulls = (bool *) eval_mcontext_alloc(estate, natts * sizeof(bool));
+
+ for (i = 0; i < natts; i++)
+ {
+ Oid fieldtypeid;
+ int32 fieldtypmod;
+
+ if (TupleDescAttr(tupdesc, i)->attisdropped)
+ {
+ nulls[i] = true; /* leave the column as null */
+ continue;
+ }
+
+ exec_eval_datum(estate, estate->datums[row->varnos[i]],
+ &fieldtypeid, &fieldtypmod,
+ &dvalues[i], &nulls[i]);
+ if (fieldtypeid != TupleDescAttr(tupdesc, i)->atttypid)
+ return NULL;
+ /* XXX should we insist on typmod match, too? */
+ }
+
+ tuple = heap_form_tuple(tupdesc, dvalues, nulls);
+
+ return tuple;
+}
+
+/*
+ * deconstruct_composite_datum extract tuple+tupdesc from composite Datum
+ *
+ * The caller must supply a HeapTupleData variable, in which we set up a
+ * tuple header pointing to the composite datum's body. To make the tuple
+ * value outlive that variable, caller would need to apply heap_copytuple...
+ * but current callers only need a short-lived tuple value anyway.
+ *
+ * Returns a pointer to the TupleDesc of the datum's rowtype.
+ * Caller is responsible for calling ReleaseTupleDesc when done with it.
+ *
+ * Note: it's caller's responsibility to be sure value is of composite type.
+ * Also, best to call this in a short-lived context, as it might leak memory.
+ */
+static TupleDesc
+deconstruct_composite_datum(Datum value, HeapTupleData *tmptup)
+{
+ HeapTupleHeader td;
+ Oid tupType;
+ int32 tupTypmod;
+
+ /* Get tuple body (note this could involve detoasting) */
+ td = DatumGetHeapTupleHeader(value);
+
+ /* Build a temporary HeapTuple control structure */
+ tmptup->t_len = HeapTupleHeaderGetDatumLength(td);
+ ItemPointerSetInvalid(&(tmptup->t_self));
+ tmptup->t_tableOid = InvalidOid;
+ tmptup->t_data = td;
+
+ /* Extract rowtype info and find a tupdesc */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+ return lookup_rowtype_tupdesc(tupType, tupTypmod);
+}
+
+/*
+ * exec_move_row_from_datum Move a composite Datum into a record or row
+ *
+ * This is equivalent to deconstruct_composite_datum() followed by
+ * exec_move_row(), but we can optimize things if the Datum is an
+ * expanded-record reference.
+ *
+ * Note: it's caller's responsibility to be sure value is of composite type.
+ */
+static void
+exec_move_row_from_datum(PLpgSQL_execstate *estate,
+ PLpgSQL_variable *target,
+ Datum value)
+{
+ /* Check to see if source is an expanded record */
+ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value)))
+ {
+ ExpandedRecordHeader *erh = (ExpandedRecordHeader *) DatumGetEOHP(value);
+ ExpandedRecordHeader *newerh = NULL;
+
+ Assert(erh->er_magic == ER_MAGIC);
+
+ /* These cases apply if the target is record not row... */
+ if (target->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
+
+ /*
+ * If it's the same record already stored in the variable, do
+ * nothing. This would happen only in silly cases like "r := r",
+ * but we need some check to avoid possibly freeing the variable's
+ * live value below. Note that this applies even if what we have
+ * is a R/O pointer.
+ */
+ if (erh == rec->erh)
+ return;
+
+ /*
+ * Make sure rec->rectypeid is up-to-date before using it.
+ */
+ revalidate_rectypeid(rec);
+
+ /*
+ * If we have a R/W pointer, we're allowed to just commandeer
+ * ownership of the expanded record. If it's of the right type to
+ * put into the record variable, do that. (Note we don't accept
+ * an expanded record of a composite-domain type as a RECORD
+ * value. We'll treat it as the base composite type instead;
+ * compare logic in make_expanded_record_for_rec.)
+ */
+ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(value)) &&
+ (rec->rectypeid == erh->er_decltypeid ||
+ (rec->rectypeid == RECORDOID &&
+ !ExpandedRecordIsDomain(erh))))
+ {
+ assign_record_var(estate, rec, erh);
+ return;
+ }
+
+ /*
+ * If we already have an expanded record object in the target
+ * variable, and the source record contains a valid tuple
+ * representation with the right rowtype, then we can skip making
+ * a new expanded record and just assign the tuple with
+ * expanded_record_set_tuple. (We can't do the equivalent if we
+ * have to do field-by-field assignment, since that wouldn't be
+ * atomic if there's an error.) We consider that there's a
+ * rowtype match only if it's the same named composite type or
+ * same registered rowtype; checking for matches of anonymous
+ * rowtypes would be more expensive than this is worth.
+ */
+ if (rec->erh &&
+ (erh->flags & ER_FLAG_FVALUE_VALID) &&
+ erh->er_typeid == rec->erh->er_typeid &&
+ (erh->er_typeid != RECORDOID ||
+ (erh->er_typmod == rec->erh->er_typmod &&
+ erh->er_typmod >= 0)))
+ {
+ expanded_record_set_tuple(rec->erh, erh->fvalue,
+ true, !estate->atomic);
+ return;
+ }
+
+ /*
+ * Otherwise we're gonna need a new expanded record object. Make
+ * it here in hopes of piggybacking on the source object's
+ * previous typcache lookup.
+ */
+ newerh = make_expanded_record_for_rec(estate, rec, NULL, erh);
+
+ /*
+ * If the expanded record contains a valid tuple representation,
+ * and we don't need rowtype conversion, then just copying the
+ * tuple is probably faster than field-by-field processing. (This
+ * isn't duplicative of the previous check, since here we will
+ * catch the case where the record variable was previously empty.)
+ */
+ if ((erh->flags & ER_FLAG_FVALUE_VALID) &&
+ (rec->rectypeid == RECORDOID ||
+ rec->rectypeid == erh->er_typeid))
+ {
+ expanded_record_set_tuple(newerh, erh->fvalue,
+ true, !estate->atomic);
+ assign_record_var(estate, rec, newerh);
+ return;
+ }
+
+ /*
+ * Need to special-case empty source record, else code below would
+ * leak newerh.
+ */
+ if (ExpandedRecordIsEmpty(erh))
+ {
+ /* Set newerh to a row of NULLs */
+ deconstruct_expanded_record(newerh);
+ assign_record_var(estate, rec, newerh);
+ return;
+ }
+ } /* end of record-target-only cases */
+
+ /*
+ * If the source expanded record is empty, we should treat that like a
+ * NULL tuple value. (We're unlikely to see such a case, but we must
+ * check this; deconstruct_expanded_record would cause a change of
+ * logical state, which is not OK.)
+ */
+ if (ExpandedRecordIsEmpty(erh))
+ {
+ exec_move_row(estate, target, NULL,
+ expanded_record_get_tupdesc(erh));
+ return;
+ }
+
+ /*
+ * Otherwise, ensure that the source record is deconstructed, and
+ * assign from its field values.
+ */
+ deconstruct_expanded_record(erh);
+ exec_move_row_from_fields(estate, target, newerh,
+ erh->dvalues, erh->dnulls,
+ expanded_record_get_tupdesc(erh));
+ }
+ else
+ {
+ /*
+ * Nope, we've got a plain composite Datum. Deconstruct it; but we
+ * don't use deconstruct_composite_datum(), because we may be able to
+ * skip calling lookup_rowtype_tupdesc().
+ */
+ HeapTupleHeader td;
+ HeapTupleData tmptup;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ MemoryContext oldcontext;
+
+ /* Ensure that any detoasted data winds up in the eval_mcontext */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ /* Get tuple body (note this could involve detoasting) */
+ td = DatumGetHeapTupleHeader(value);
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Build a temporary HeapTuple control structure */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ ItemPointerSetInvalid(&(tmptup.t_self));
+ tmptup.t_tableOid = InvalidOid;
+ tmptup.t_data = td;
+
+ /* Extract rowtype info */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+
+ /* Now, if the target is record not row, maybe we can optimize ... */
+ if (target->dtype == PLPGSQL_DTYPE_REC)
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) target;
+
+ /*
+ * If we already have an expanded record object in the target
+ * variable, and the source datum has a matching rowtype, then we
+ * can skip making a new expanded record and just assign the tuple
+ * with expanded_record_set_tuple. We consider that there's a
+ * rowtype match only if it's the same named composite type or
+ * same registered rowtype. (Checking to reject an anonymous
+ * rowtype here should be redundant, but let's be safe.)
+ */
+ if (rec->erh &&
+ tupType == rec->erh->er_typeid &&
+ (tupType != RECORDOID ||
+ (tupTypmod == rec->erh->er_typmod &&
+ tupTypmod >= 0)))
+ {
+ expanded_record_set_tuple(rec->erh, &tmptup,
+ true, !estate->atomic);
+ return;
+ }
+
+ /*
+ * If the source datum has a rowtype compatible with the target
+ * variable, just build a new expanded record and assign the tuple
+ * into it. Using make_expanded_record_from_typeid() here saves
+ * one typcache lookup compared to the code below.
+ */
+ if (rec->rectypeid == RECORDOID || rec->rectypeid == tupType)
+ {
+ ExpandedRecordHeader *newerh;
+ MemoryContext mcontext = get_eval_mcontext(estate);
+
+ newerh = make_expanded_record_from_typeid(tupType, tupTypmod,
+ mcontext);
+ expanded_record_set_tuple(newerh, &tmptup,
+ true, !estate->atomic);
+ assign_record_var(estate, rec, newerh);
+ return;
+ }
+
+ /*
+ * Otherwise, we're going to need conversion, so fall through to
+ * do it the hard way.
+ */
+ }
+
+ /*
+ * ROW target, or unoptimizable RECORD target, so we have to expend a
+ * lookup to obtain the source datum's tupdesc.
+ */
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ /* Do the move */
+ exec_move_row(estate, target, &tmptup, tupdesc);
+
+ /* Release tupdesc usage count */
+ ReleaseTupleDesc(tupdesc);
+ }
+}
+
+/*
+ * If we have not created an expanded record to hold the record variable's
+ * value, do so. The expanded record will be "empty", so this does not
+ * change the logical state of the record variable: it's still NULL.
+ * However, now we'll have a tupdesc with which we can e.g. look up fields.
+ */
+static void
+instantiate_empty_record_variable(PLpgSQL_execstate *estate, PLpgSQL_rec *rec)
+{
+ Assert(rec->erh == NULL); /* else caller error */
+
+ /* If declared type is RECORD, we can't instantiate */
+ if (rec->rectypeid == RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("record \"%s\" is not assigned yet", rec->refname),
+ errdetail("The tuple structure of a not-yet-assigned record is indeterminate.")));
+
+ /* Make sure rec->rectypeid is up-to-date before using it */
+ revalidate_rectypeid(rec);
+
+ /* OK, do it */
+ rec->erh = make_expanded_record_from_typeid(rec->rectypeid, -1,
+ estate->datum_context);
+}
+
+/* ----------
+ * convert_value_to_string Convert a non-null Datum to C string
+ *
+ * Note: the result is in the estate's eval_mcontext, and will be cleared
+ * by the next exec_eval_cleanup() call. The invoked output function might
+ * leave additional cruft there as well, so just pfree'ing the result string
+ * would not be enough to avoid memory leaks if we did not do it like this.
+ * In most usages the Datum being passed in is also in that context (if
+ * pass-by-reference) and so an exec_eval_cleanup() call is needed anyway.
+ *
+ * Note: not caching the conversion function lookup is bad for performance.
+ * However, this function isn't currently used in any places where an extra
+ * catalog lookup or two seems like a big deal.
+ * ----------
+ */
+static char *
+convert_value_to_string(PLpgSQL_execstate *estate, Datum value, Oid valtype)
+{
+ char *result;
+ MemoryContext oldcontext;
+ Oid typoutput;
+ bool typIsVarlena;
+
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ getTypeOutputInfo(valtype, &typoutput, &typIsVarlena);
+ result = OidOutputFunctionCall(typoutput, value);
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
+}
+
+/* ----------
+ * exec_cast_value Cast a value if required
+ *
+ * Note that *isnull is an input and also an output parameter. While it's
+ * unlikely that a cast operation would produce null from non-null or vice
+ * versa, that could happen in principle.
+ *
+ * Note: the estate's eval_mcontext is used for temporary storage, and may
+ * also contain the result Datum if we have to do a conversion to a pass-
+ * by-reference data type. Be sure to do an exec_eval_cleanup() call when
+ * done with the result.
+ * ----------
+ */
+static inline Datum
+exec_cast_value(PLpgSQL_execstate *estate,
+ Datum value, bool *isnull,
+ Oid valtype, int32 valtypmod,
+ Oid reqtype, int32 reqtypmod)
+{
+ /*
+ * If the type of the given value isn't what's requested, convert it.
+ */
+ if (valtype != reqtype ||
+ (valtypmod != reqtypmod && reqtypmod != -1))
+ {
+ /* We keep the slow path out-of-line. */
+ value = do_cast_value(estate, value, isnull, valtype, valtypmod,
+ reqtype, reqtypmod);
+ }
+
+ return value;
+}
+
+/* ----------
+ * do_cast_value Slow path for exec_cast_value.
+ * ----------
+ */
+static Datum
+do_cast_value(PLpgSQL_execstate *estate,
+ Datum value, bool *isnull,
+ Oid valtype, int32 valtypmod,
+ Oid reqtype, int32 reqtypmod)
+{
+ plpgsql_CastHashEntry *cast_entry;
+
+ cast_entry = get_cast_hashentry(estate,
+ valtype, valtypmod,
+ reqtype, reqtypmod);
+ if (cast_entry)
+ {
+ ExprContext *econtext = estate->eval_econtext;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ econtext->caseValue_datum = value;
+ econtext->caseValue_isNull = *isnull;
+
+ cast_entry->cast_in_use = true;
+
+ value = ExecEvalExpr(cast_entry->cast_exprstate, econtext,
+ isnull);
+
+ cast_entry->cast_in_use = false;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ return value;
+}
+
+/* ----------
+ * get_cast_hashentry Look up how to perform a type cast
+ *
+ * Returns a plpgsql_CastHashEntry if an expression has to be evaluated,
+ * or NULL if the cast is a mere no-op relabeling. If there's work to be
+ * done, the cast_exprstate field contains an expression evaluation tree
+ * based on a CaseTestExpr input, and the cast_in_use field should be set
+ * true while executing it.
+ * ----------
+ */
+static plpgsql_CastHashEntry *
+get_cast_hashentry(PLpgSQL_execstate *estate,
+ Oid srctype, int32 srctypmod,
+ Oid dsttype, int32 dsttypmod)
+{
+ plpgsql_CastHashKey cast_key;
+ plpgsql_CastHashEntry *cast_entry;
+ plpgsql_CastExprHashEntry *expr_entry;
+ bool found;
+ LocalTransactionId curlxid;
+ MemoryContext oldcontext;
+
+ /* Look for existing entry */
+ cast_key.srctype = srctype;
+ cast_key.dsttype = dsttype;
+ cast_key.srctypmod = srctypmod;
+ cast_key.dsttypmod = dsttypmod;
+ cast_entry = (plpgsql_CastHashEntry *) hash_search(estate->cast_hash,
+ (void *) &cast_key,
+ HASH_ENTER, &found);
+ if (!found) /* initialize if new entry */
+ {
+ /* We need a second lookup to see if a cast_expr_hash entry exists */
+ expr_entry = (plpgsql_CastExprHashEntry *) hash_search(cast_expr_hash,
+ &cast_key,
+ HASH_ENTER,
+ &found);
+ if (!found) /* initialize if new expr entry */
+ expr_entry->cast_cexpr = NULL;
+
+ cast_entry->cast_centry = expr_entry;
+ cast_entry->cast_exprstate = NULL;
+ cast_entry->cast_in_use = false;
+ cast_entry->cast_lxid = InvalidLocalTransactionId;
+ }
+ else
+ {
+ /* Use always-valid link to avoid a second hash lookup */
+ expr_entry = cast_entry->cast_centry;
+ }
+
+ if (expr_entry->cast_cexpr == NULL ||
+ !expr_entry->cast_cexpr->is_valid)
+ {
+ /*
+ * We've not looked up this coercion before, or we have but the cached
+ * expression has been invalidated.
+ */
+ Node *cast_expr;
+ CachedExpression *cast_cexpr;
+ CaseTestExpr *placeholder;
+
+ /*
+ * Drop old cached expression if there is one.
+ */
+ if (expr_entry->cast_cexpr)
+ {
+ FreeCachedExpression(expr_entry->cast_cexpr);
+ expr_entry->cast_cexpr = NULL;
+ }
+
+ /*
+ * Since we could easily fail (no such coercion), construct a
+ * temporary coercion expression tree in the short-lived
+ * eval_mcontext, then if successful save it as a CachedExpression.
+ */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ /*
+ * We use a CaseTestExpr as the base of the coercion tree, since it's
+ * very cheap to insert the source value for that.
+ */
+ placeholder = makeNode(CaseTestExpr);
+ placeholder->typeId = srctype;
+ placeholder->typeMod = srctypmod;
+ placeholder->collation = get_typcollation(srctype);
+
+ /*
+ * Apply coercion. We use the special coercion context
+ * COERCION_PLPGSQL to match plpgsql's historical behavior, namely
+ * that any cast not available at ASSIGNMENT level will be implemented
+ * as an I/O coercion. (It's somewhat dubious that we prefer I/O
+ * coercion over cast pathways that exist at EXPLICIT level. Changing
+ * that would cause assorted minor behavioral differences though, and
+ * a user who wants the explicit-cast behavior can always write an
+ * explicit cast.)
+ *
+ * If source type is UNKNOWN, coerce_to_target_type will fail (it only
+ * expects to see that for Const input nodes), so don't call it; we'll
+ * apply CoerceViaIO instead. Likewise, it doesn't currently work for
+ * coercing RECORD to some other type, so skip for that too.
+ */
+ if (srctype == UNKNOWNOID || srctype == RECORDOID)
+ cast_expr = NULL;
+ else
+ cast_expr = coerce_to_target_type(NULL,
+ (Node *) placeholder, srctype,
+ dsttype, dsttypmod,
+ COERCION_PLPGSQL,
+ COERCE_IMPLICIT_CAST,
+ -1);
+
+ /*
+ * If there's no cast path according to the parser, fall back to using
+ * an I/O coercion; this is semantically dubious but matches plpgsql's
+ * historical behavior. We would need something of the sort for
+ * UNKNOWN literals in any case. (This is probably now only reachable
+ * in the case where srctype is UNKNOWN/RECORD.)
+ */
+ if (cast_expr == NULL)
+ {
+ CoerceViaIO *iocoerce = makeNode(CoerceViaIO);
+
+ iocoerce->arg = (Expr *) placeholder;
+ iocoerce->resulttype = dsttype;
+ iocoerce->resultcollid = InvalidOid;
+ iocoerce->coerceformat = COERCE_IMPLICIT_CAST;
+ iocoerce->location = -1;
+ cast_expr = (Node *) iocoerce;
+ if (dsttypmod != -1)
+ cast_expr = coerce_to_target_type(NULL,
+ cast_expr, dsttype,
+ dsttype, dsttypmod,
+ COERCION_ASSIGNMENT,
+ COERCE_IMPLICIT_CAST,
+ -1);
+ }
+
+ /* Note: we don't bother labeling the expression tree with collation */
+
+ /* Plan the expression and build a CachedExpression */
+ cast_cexpr = GetCachedExpression(cast_expr);
+ cast_expr = cast_cexpr->expr;
+
+ /* Detect whether we have a no-op (RelabelType) coercion */
+ if (IsA(cast_expr, RelabelType) &&
+ ((RelabelType *) cast_expr)->arg == (Expr *) placeholder)
+ cast_expr = NULL;
+
+ /* Now we can fill in the expression hashtable entry. */
+ expr_entry->cast_cexpr = cast_cexpr;
+ expr_entry->cast_expr = (Expr *) cast_expr;
+
+ /* Be sure to reset the exprstate hashtable entry, too. */
+ cast_entry->cast_exprstate = NULL;
+ cast_entry->cast_in_use = false;
+ cast_entry->cast_lxid = InvalidLocalTransactionId;
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ /* Done if we have determined that this is a no-op cast. */
+ if (expr_entry->cast_expr == NULL)
+ return NULL;
+
+ /*
+ * Prepare the expression for execution, if it's not been done already in
+ * the current transaction; also, if it's marked busy in the current
+ * transaction, abandon that expression tree and build a new one, so as to
+ * avoid potential problems with recursive cast expressions and failed
+ * executions. (We will leak some memory intra-transaction if that
+ * happens a lot, but we don't expect it to.) It's okay to update the
+ * hash table with the new tree because all plpgsql functions within a
+ * given transaction share the same simple_eval_estate. (Well, regular
+ * functions do; DO blocks have private simple_eval_estates, and private
+ * cast hash tables to go with them.)
+ */
+ curlxid = MyProc->lxid;
+ if (cast_entry->cast_lxid != curlxid || cast_entry->cast_in_use)
+ {
+ oldcontext = MemoryContextSwitchTo(estate->simple_eval_estate->es_query_cxt);
+ cast_entry->cast_exprstate = ExecInitExpr(expr_entry->cast_expr, NULL);
+ cast_entry->cast_in_use = false;
+ cast_entry->cast_lxid = curlxid;
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ return cast_entry;
+}
+
+
+/* ----------
+ * exec_simple_check_plan - Check if a plan is simple enough to
+ * be evaluated by ExecEvalExpr() instead
+ * of SPI.
+ *
+ * Note: the refcount manipulations in this function assume that expr->plan
+ * is a "saved" SPI plan. That's a bit annoying from the caller's standpoint,
+ * but it's otherwise difficult to avoid leaking the plan on failure.
+ * ----------
+ */
+static void
+exec_simple_check_plan(PLpgSQL_execstate *estate, PLpgSQL_expr *expr)
+{
+ List *plansources;
+ CachedPlanSource *plansource;
+ Query *query;
+ CachedPlan *cplan;
+ MemoryContext oldcontext;
+
+ /*
+ * Initialize to "not simple".
+ */
+ expr->expr_simple_expr = NULL;
+ expr->expr_rw_param = NULL;
+
+ /*
+ * Check the analyzed-and-rewritten form of the query to see if we will be
+ * able to treat it as a simple expression. Since this function is only
+ * called immediately after creating the CachedPlanSource, we need not
+ * worry about the query being stale.
+ */
+
+ /*
+ * We can only test queries that resulted in exactly one CachedPlanSource
+ */
+ plansources = SPI_plan_get_plan_sources(expr->plan);
+ if (list_length(plansources) != 1)
+ return;
+ plansource = (CachedPlanSource *) linitial(plansources);
+
+ /*
+ * 1. There must be one single querytree.
+ */
+ if (list_length(plansource->query_list) != 1)
+ return;
+ query = (Query *) linitial(plansource->query_list);
+
+ /*
+ * 2. It must be a plain SELECT query without any input tables
+ */
+ if (!IsA(query, Query))
+ return;
+ if (query->commandType != CMD_SELECT)
+ return;
+ if (query->rtable != NIL)
+ return;
+
+ /*
+ * 3. Can't have any subplans, aggregates, qual clauses either. (These
+ * tests should generally match what inline_function() checks before
+ * inlining a SQL function; otherwise, inlining could change our
+ * conclusion about whether an expression is simple, which we don't want.)
+ */
+ if (query->hasAggs ||
+ query->hasWindowFuncs ||
+ query->hasTargetSRFs ||
+ query->hasSubLinks ||
+ query->cteList ||
+ query->jointree->fromlist ||
+ query->jointree->quals ||
+ query->groupClause ||
+ query->groupingSets ||
+ query->havingQual ||
+ query->windowClause ||
+ query->distinctClause ||
+ query->sortClause ||
+ query->limitOffset ||
+ query->limitCount ||
+ query->setOperations)
+ return;
+
+ /*
+ * 4. The query must have a single attribute as result
+ */
+ if (list_length(query->targetList) != 1)
+ return;
+
+ /*
+ * OK, we can treat it as a simple plan.
+ *
+ * Get the generic plan for the query. If replanning is needed, do that
+ * work in the eval_mcontext. (Note that replanning could throw an error,
+ * in which case the expr is left marked "not simple", which is fine.)
+ */
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ cplan = SPI_plan_get_cached_plan(expr->plan);
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Can't fail, because we checked for a single CachedPlanSource above */
+ Assert(cplan != NULL);
+
+ /*
+ * Verify that plancache.c thinks the plan is simple enough to use
+ * CachedPlanIsSimplyValid. Given the restrictions above, it's unlikely
+ * that this could fail, but if it does, just treat plan as not simple. On
+ * success, save a refcount on the plan in the simple-expression resowner.
+ */
+ if (CachedPlanAllowsSimpleValidityCheck(plansource, cplan,
+ estate->simple_eval_resowner))
+ {
+ /* Remember that we have the refcount */
+ expr->expr_simple_plansource = plansource;
+ expr->expr_simple_plan = cplan;
+ expr->expr_simple_plan_lxid = MyProc->lxid;
+
+ /* Share the remaining work with the replan code path */
+ exec_save_simple_expr(expr, cplan);
+ }
+
+ /*
+ * Release the plan refcount obtained by SPI_plan_get_cached_plan. (This
+ * refcount is held by the wrong resowner, so we can't just repurpose it.)
+ */
+ ReleaseCachedPlan(cplan, CurrentResourceOwner);
+}
+
+/*
+ * exec_save_simple_expr --- extract simple expression from CachedPlan
+ */
+static void
+exec_save_simple_expr(PLpgSQL_expr *expr, CachedPlan *cplan)
+{
+ PlannedStmt *stmt;
+ Plan *plan;
+ Expr *tle_expr;
+
+ /*
+ * Given the checks that exec_simple_check_plan did, none of the Asserts
+ * here should ever fail.
+ */
+
+ /* Extract the single PlannedStmt */
+ Assert(list_length(cplan->stmt_list) == 1);
+ stmt = linitial_node(PlannedStmt, cplan->stmt_list);
+ Assert(stmt->commandType == CMD_SELECT);
+
+ /*
+ * Ordinarily, the plan node should be a simple Result. However, if
+ * force_parallel_mode is on, the planner might've stuck a Gather node
+ * atop that. The simplest way to deal with this is to look through the
+ * Gather node. The Gather node's tlist would normally contain a Var
+ * referencing the child node's output, but it could also be a Param, or
+ * it could be a Const that setrefs.c copied as-is.
+ */
+ plan = stmt->planTree;
+ for (;;)
+ {
+ /* Extract the single tlist expression */
+ Assert(list_length(plan->targetlist) == 1);
+ tle_expr = linitial_node(TargetEntry, plan->targetlist)->expr;
+
+ if (IsA(plan, Result))
+ {
+ Assert(plan->lefttree == NULL &&
+ plan->righttree == NULL &&
+ plan->initPlan == NULL &&
+ plan->qual == NULL &&
+ ((Result *) plan)->resconstantqual == NULL);
+ break;
+ }
+ else if (IsA(plan, Gather))
+ {
+ Assert(plan->lefttree != NULL &&
+ plan->righttree == NULL &&
+ plan->initPlan == NULL &&
+ plan->qual == NULL);
+ /* If setrefs.c copied up a Const, no need to look further */
+ if (IsA(tle_expr, Const))
+ break;
+ /* Otherwise, it had better be a Param or an outer Var */
+ Assert(IsA(tle_expr, Param) || (IsA(tle_expr, Var) &&
+ ((Var *) tle_expr)->varno == OUTER_VAR));
+ /* Descend to the child node */
+ plan = plan->lefttree;
+ }
+ else
+ elog(ERROR, "unexpected plan node type: %d",
+ (int) nodeTag(plan));
+ }
+
+ /*
+ * Save the simple expression, and initialize state to "not valid in
+ * current transaction".
+ */
+ expr->expr_simple_expr = tle_expr;
+ expr->expr_simple_state = NULL;
+ expr->expr_simple_in_use = false;
+ expr->expr_simple_lxid = InvalidLocalTransactionId;
+ /* Also stash away the expression result type */
+ expr->expr_simple_type = exprType((Node *) tle_expr);
+ expr->expr_simple_typmod = exprTypmod((Node *) tle_expr);
+ /* We also want to remember if it is immutable or not */
+ expr->expr_simple_mutable = contain_mutable_functions((Node *) tle_expr);
+
+ /*
+ * Lastly, check to see if there's a possibility of optimizing a
+ * read/write parameter.
+ */
+ exec_check_rw_parameter(expr);
+}
+
+/*
+ * exec_check_rw_parameter --- can we pass expanded object as read/write param?
+ *
+ * If we have an assignment like "x := array_append(x, foo)" in which the
+ * top-level function is trusted not to corrupt its argument in case of an
+ * error, then when x has an expanded object as value, it is safe to pass the
+ * value as a read/write pointer and let the function modify the value
+ * in-place.
+ *
+ * This function checks for a safe expression, and sets expr->expr_rw_param
+ * to the address of any Param within the expression that can be passed as
+ * read/write (there can be only one); or to NULL when there is no safe Param.
+ *
+ * Note that this mechanism intentionally applies the safety labeling to just
+ * one Param; the expression could contain other Params referencing the target
+ * variable, but those must still be treated as read-only.
+ *
+ * Also note that we only apply this optimization within simple expressions.
+ * There's no point in it for non-simple expressions, because the
+ * exec_run_select code path will flatten any expanded result anyway.
+ * Also, it's safe to assume that an expr_simple_expr tree won't get copied
+ * somewhere before it gets compiled, so that looking for pointer equality
+ * to expr_rw_param will work for matching the target Param. That'd be much
+ * shakier in the general case.
+ */
+static void
+exec_check_rw_parameter(PLpgSQL_expr *expr)
+{
+ int target_dno;
+ Oid funcid;
+ List *fargs;
+ ListCell *lc;
+
+ /* Assume unsafe */
+ expr->expr_rw_param = NULL;
+
+ /* Done if expression isn't an assignment source */
+ target_dno = expr->target_param;
+ if (target_dno < 0)
+ return;
+
+ /*
+ * If target variable isn't referenced by expression, no need to look
+ * further.
+ */
+ if (!bms_is_member(target_dno, expr->paramnos))
+ return;
+
+ /* Shouldn't be here for non-simple expression */
+ Assert(expr->expr_simple_expr != NULL);
+
+ /*
+ * Top level of expression must be a simple FuncExpr, OpExpr, or
+ * SubscriptingRef, else we can't optimize.
+ */
+ if (IsA(expr->expr_simple_expr, FuncExpr))
+ {
+ FuncExpr *fexpr = (FuncExpr *) expr->expr_simple_expr;
+
+ funcid = fexpr->funcid;
+ fargs = fexpr->args;
+ }
+ else if (IsA(expr->expr_simple_expr, OpExpr))
+ {
+ OpExpr *opexpr = (OpExpr *) expr->expr_simple_expr;
+
+ funcid = opexpr->opfuncid;
+ fargs = opexpr->args;
+ }
+ else if (IsA(expr->expr_simple_expr, SubscriptingRef))
+ {
+ SubscriptingRef *sbsref = (SubscriptingRef *) expr->expr_simple_expr;
+
+ /* We only trust standard varlena arrays to be safe */
+ if (get_typsubscript(sbsref->refcontainertype, NULL) !=
+ F_ARRAY_SUBSCRIPT_HANDLER)
+ return;
+
+ /* We can optimize the refexpr if it's the target, otherwise not */
+ if (sbsref->refexpr && IsA(sbsref->refexpr, Param))
+ {
+ Param *param = (Param *) sbsref->refexpr;
+
+ if (param->paramkind == PARAM_EXTERN &&
+ param->paramid == target_dno + 1)
+ {
+ /* Found the Param we want to pass as read/write */
+ expr->expr_rw_param = param;
+ return;
+ }
+ }
+
+ return;
+ }
+ else
+ return;
+
+ /*
+ * The top-level function must be one that we trust to be "safe".
+ * Currently we hard-wire the list, but it would be very desirable to
+ * allow extensions to mark their functions as safe ...
+ */
+ if (!(funcid == F_ARRAY_APPEND ||
+ funcid == F_ARRAY_PREPEND))
+ return;
+
+ /*
+ * The target variable (in the form of a Param) must appear as a direct
+ * argument of the top-level function. References further down in the
+ * tree can't be optimized; but on the other hand, they don't invalidate
+ * optimizing the top-level call, since that will be executed last.
+ */
+ foreach(lc, fargs)
+ {
+ Node *arg = (Node *) lfirst(lc);
+
+ if (arg && IsA(arg, Param))
+ {
+ Param *param = (Param *) arg;
+
+ if (param->paramkind == PARAM_EXTERN &&
+ param->paramid == target_dno + 1)
+ {
+ /* Found the Param we want to pass as read/write */
+ expr->expr_rw_param = param;
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * exec_check_assignable --- is it OK to assign to the indicated datum?
+ *
+ * This should match pl_gram.y's check_assignable().
+ */
+static void
+exec_check_assignable(PLpgSQL_execstate *estate, int dno)
+{
+ PLpgSQL_datum *datum;
+
+ Assert(dno >= 0 && dno < estate->ndatums);
+ datum = estate->datums[dno];
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ case PLPGSQL_DTYPE_REC:
+ if (((PLpgSQL_variable *) datum)->isconst)
+ ereport(ERROR,
+ (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+ errmsg("variable \"%s\" is declared CONSTANT",
+ ((PLpgSQL_variable *) datum)->refname)));
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ /* always assignable; member vars were checked at compile time */
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ /* assignable if parent record is */
+ exec_check_assignable(estate,
+ ((PLpgSQL_recfield *) datum)->recparentno);
+ break;
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ break;
+ }
+}
+
+/* ----------
+ * exec_set_found Set the global found variable to true/false
+ * ----------
+ */
+static void
+exec_set_found(PLpgSQL_execstate *estate, bool state)
+{
+ PLpgSQL_var *var;
+
+ var = (PLpgSQL_var *) (estate->datums[estate->found_varno]);
+ assign_simple_var(estate, var, BoolGetDatum(state), false, false);
+}
+
+/*
+ * plpgsql_create_econtext --- create an eval_econtext for the current function
+ *
+ * We may need to create a new shared_simple_eval_estate too, if there's not
+ * one already for the current transaction. The EState will be cleaned up at
+ * transaction end. Ditto for shared_simple_eval_resowner.
+ */
+static void
+plpgsql_create_econtext(PLpgSQL_execstate *estate)
+{
+ SimpleEcontextStackEntry *entry;
+
+ /*
+ * Create an EState for evaluation of simple expressions, if there's not
+ * one already in the current transaction. The EState is made a child of
+ * TopTransactionContext so it will have the right lifespan.
+ *
+ * Note that this path is never taken when beginning a DO block; the
+ * required EState was already made by plpgsql_inline_handler. However,
+ * if the DO block executes COMMIT or ROLLBACK, then we'll come here and
+ * make a shared EState to use for the rest of the DO block. That's OK;
+ * see the comments for shared_simple_eval_estate. (Note also that a DO
+ * block will continue to use its private cast hash table for the rest of
+ * the block. That's okay for now, but it might cause problems someday.)
+ */
+ if (estate->simple_eval_estate == NULL)
+ {
+ MemoryContext oldcontext;
+
+ if (shared_simple_eval_estate == NULL)
+ {
+ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+ shared_simple_eval_estate = CreateExecutorState();
+ MemoryContextSwitchTo(oldcontext);
+ }
+ estate->simple_eval_estate = shared_simple_eval_estate;
+ }
+
+ /*
+ * Likewise for the simple-expression resource owner.
+ */
+ if (estate->simple_eval_resowner == NULL)
+ {
+ if (shared_simple_eval_resowner == NULL)
+ shared_simple_eval_resowner =
+ ResourceOwnerCreate(TopTransactionResourceOwner,
+ "PL/pgSQL simple expressions");
+ estate->simple_eval_resowner = shared_simple_eval_resowner;
+ }
+
+ /*
+ * Create a child econtext for the current function.
+ */
+ estate->eval_econtext = CreateExprContext(estate->simple_eval_estate);
+
+ /*
+ * Make a stack entry so we can clean up the econtext at subxact end.
+ * Stack entries are kept in TopTransactionContext for simplicity.
+ */
+ entry = (SimpleEcontextStackEntry *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(SimpleEcontextStackEntry));
+
+ entry->stack_econtext = estate->eval_econtext;
+ entry->xact_subxid = GetCurrentSubTransactionId();
+
+ entry->next = simple_econtext_stack;
+ simple_econtext_stack = entry;
+}
+
+/*
+ * plpgsql_destroy_econtext --- destroy function's econtext
+ *
+ * We check that it matches the top stack entry, and destroy the stack
+ * entry along with the context.
+ */
+static void
+plpgsql_destroy_econtext(PLpgSQL_execstate *estate)
+{
+ SimpleEcontextStackEntry *next;
+
+ Assert(simple_econtext_stack != NULL);
+ Assert(simple_econtext_stack->stack_econtext == estate->eval_econtext);
+
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
+
+ FreeExprContext(estate->eval_econtext, true);
+ estate->eval_econtext = NULL;
+}
+
+/*
+ * plpgsql_xact_cb --- post-transaction-commit-or-abort cleanup
+ *
+ * If a simple-expression EState was created in the current transaction,
+ * it has to be cleaned up. The same for the simple-expression resowner.
+ */
+void
+plpgsql_xact_cb(XactEvent event, void *arg)
+{
+ /*
+ * If we are doing a clean transaction shutdown, free the EState and tell
+ * the resowner to release whatever plancache references it has, so that
+ * all remaining resources will be released correctly. (We don't need to
+ * actually delete the resowner here; deletion of the
+ * TopTransactionResourceOwner will take care of that.)
+ *
+ * In an abort, we expect the regular abort recovery procedures to release
+ * everything of interest, so just clear our pointers.
+ */
+ if (event == XACT_EVENT_COMMIT ||
+ event == XACT_EVENT_PARALLEL_COMMIT ||
+ event == XACT_EVENT_PREPARE)
+ {
+ simple_econtext_stack = NULL;
+
+ if (shared_simple_eval_estate)
+ FreeExecutorState(shared_simple_eval_estate);
+ shared_simple_eval_estate = NULL;
+ if (shared_simple_eval_resowner)
+ ResourceOwnerReleaseAllPlanCacheRefs(shared_simple_eval_resowner);
+ shared_simple_eval_resowner = NULL;
+ }
+ else if (event == XACT_EVENT_ABORT ||
+ event == XACT_EVENT_PARALLEL_ABORT)
+ {
+ simple_econtext_stack = NULL;
+ shared_simple_eval_estate = NULL;
+ shared_simple_eval_resowner = NULL;
+ }
+}
+
+/*
+ * plpgsql_subxact_cb --- post-subtransaction-commit-or-abort cleanup
+ *
+ * Make sure any simple-expression econtexts created in the current
+ * subtransaction get cleaned up. We have to do this explicitly because
+ * no other code knows which econtexts belong to which level of subxact.
+ */
+void
+plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
+ SubTransactionId parentSubid, void *arg)
+{
+ if (event == SUBXACT_EVENT_COMMIT_SUB || event == SUBXACT_EVENT_ABORT_SUB)
+ {
+ while (simple_econtext_stack != NULL &&
+ simple_econtext_stack->xact_subxid == mySubid)
+ {
+ SimpleEcontextStackEntry *next;
+
+ FreeExprContext(simple_econtext_stack->stack_econtext,
+ (event == SUBXACT_EVENT_COMMIT_SUB));
+ next = simple_econtext_stack->next;
+ pfree(simple_econtext_stack);
+ simple_econtext_stack = next;
+ }
+ }
+}
+
+/*
+ * assign_simple_var --- assign a new value to any VAR datum.
+ *
+ * This should be the only mechanism for assignment to simple variables,
+ * lest we do the release of the old value incorrectly (not to mention
+ * the detoasting business).
+ */
+static void
+assign_simple_var(PLpgSQL_execstate *estate, PLpgSQL_var *var,
+ Datum newvalue, bool isnull, bool freeable)
+{
+ Assert(var->dtype == PLPGSQL_DTYPE_VAR ||
+ var->dtype == PLPGSQL_DTYPE_PROMISE);
+
+ /*
+ * In non-atomic contexts, we do not want to store TOAST pointers in
+ * variables, because such pointers might become stale after a commit.
+ * Forcibly detoast in such cases. We don't want to detoast (flatten)
+ * expanded objects, however; those should be OK across a transaction
+ * boundary since they're just memory-resident objects. (Elsewhere in
+ * this module, operations on expanded records likewise need to request
+ * detoasting of record fields when !estate->atomic. Expanded arrays are
+ * not a problem since all array entries are always detoasted.)
+ */
+ if (!estate->atomic && !isnull && var->datatype->typlen == -1 &&
+ VARATT_IS_EXTERNAL_NON_EXPANDED(DatumGetPointer(newvalue)))
+ {
+ MemoryContext oldcxt;
+ Datum detoasted;
+
+ /*
+ * Do the detoasting in the eval_mcontext to avoid long-term leakage
+ * of whatever memory toast fetching might leak. Then we have to copy
+ * the detoasted datum to the function's main context, which is a
+ * pain, but there's little choice.
+ */
+ oldcxt = MemoryContextSwitchTo(get_eval_mcontext(estate));
+ detoasted = PointerGetDatum(detoast_external_attr((struct varlena *) DatumGetPointer(newvalue)));
+ MemoryContextSwitchTo(oldcxt);
+ /* Now's a good time to not leak the input value if it's freeable */
+ if (freeable)
+ pfree(DatumGetPointer(newvalue));
+ /* Once we copy the value, it's definitely freeable */
+ newvalue = datumCopy(detoasted, false, -1);
+ freeable = true;
+ /* Can't clean up eval_mcontext here, but it'll happen before long */
+ }
+
+ /* Free the old value if needed */
+ if (var->freeval)
+ {
+ if (DatumIsReadWriteExpandedObject(var->value,
+ var->isnull,
+ var->datatype->typlen))
+ DeleteExpandedObject(var->value);
+ else
+ pfree(DatumGetPointer(var->value));
+ }
+ /* Assign new value to datum */
+ var->value = newvalue;
+ var->isnull = isnull;
+ var->freeval = freeable;
+
+ /*
+ * If it's a promise variable, then either we just assigned the promised
+ * value, or the user explicitly assigned an overriding value. Either
+ * way, cancel the promise.
+ */
+ var->promise = PLPGSQL_PROMISE_NONE;
+}
+
+/*
+ * free old value of a text variable and assign new value from C string
+ */
+static void
+assign_text_var(PLpgSQL_execstate *estate, PLpgSQL_var *var, const char *str)
+{
+ assign_simple_var(estate, var, CStringGetTextDatum(str), false, true);
+}
+
+/*
+ * assign_record_var --- assign a new value to any REC datum.
+ */
+static void
+assign_record_var(PLpgSQL_execstate *estate, PLpgSQL_rec *rec,
+ ExpandedRecordHeader *erh)
+{
+ Assert(rec->dtype == PLPGSQL_DTYPE_REC);
+
+ /* Transfer new record object into datum_context */
+ TransferExpandedRecord(erh, estate->datum_context);
+
+ /* Free the old value ... */
+ if (rec->erh)
+ DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
+
+ /* ... and install the new */
+ rec->erh = erh;
+}
+
+/*
+ * exec_eval_using_params --- evaluate params of USING clause
+ *
+ * The result data structure is created in the stmt_mcontext, and should
+ * be freed by resetting that context.
+ */
+static ParamListInfo
+exec_eval_using_params(PLpgSQL_execstate *estate, List *params)
+{
+ ParamListInfo paramLI;
+ int nargs;
+ MemoryContext stmt_mcontext;
+ MemoryContext oldcontext;
+ int i;
+ ListCell *lc;
+
+ /* Fast path for no parameters: we can just return NULL */
+ if (params == NIL)
+ return NULL;
+
+ nargs = list_length(params);
+ stmt_mcontext = get_stmt_mcontext(estate);
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+ paramLI = makeParamList(nargs);
+ MemoryContextSwitchTo(oldcontext);
+
+ i = 0;
+ foreach(lc, params)
+ {
+ PLpgSQL_expr *param = (PLpgSQL_expr *) lfirst(lc);
+ ParamExternData *prm = &paramLI->params[i];
+ int32 ppdtypmod;
+
+ /*
+ * Always mark params as const, since we only use the result with
+ * one-shot plans.
+ */
+ prm->pflags = PARAM_FLAG_CONST;
+
+ prm->value = exec_eval_expr(estate, param,
+ &prm->isnull,
+ &prm->ptype,
+ &ppdtypmod);
+
+ oldcontext = MemoryContextSwitchTo(stmt_mcontext);
+
+ if (prm->ptype == UNKNOWNOID)
+ {
+ /*
+ * Treat 'unknown' parameters as text, since that's what most
+ * people would expect. The SPI functions can coerce unknown
+ * constants in a more intelligent way, but not unknown Params.
+ * This code also takes care of copying into the right context.
+ * Note we assume 'unknown' has the representation of C-string.
+ */
+ prm->ptype = TEXTOID;
+ if (!prm->isnull)
+ prm->value = CStringGetTextDatum(DatumGetCString(prm->value));
+ }
+ /* pass-by-ref non null values must be copied into stmt_mcontext */
+ else if (!prm->isnull)
+ {
+ int16 typLen;
+ bool typByVal;
+
+ get_typlenbyval(prm->ptype, &typLen, &typByVal);
+ if (!typByVal)
+ prm->value = datumCopy(prm->value, typByVal, typLen);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ exec_eval_cleanup(estate);
+
+ i++;
+ }
+
+ return paramLI;
+}
+
+/*
+ * Open portal for dynamic query
+ *
+ * Caution: this resets the stmt_mcontext at exit. We might eventually need
+ * to move that responsibility to the callers, but currently no caller needs
+ * to have statement-lifetime temp data that survives past this, so it's
+ * simpler to do it here.
+ */
+static Portal
+exec_dynquery_with_params(PLpgSQL_execstate *estate,
+ PLpgSQL_expr *dynquery,
+ List *params,
+ const char *portalname,
+ int cursorOptions)
+{
+ Portal portal;
+ Datum query;
+ bool isnull;
+ Oid restype;
+ int32 restypmod;
+ char *querystr;
+ SPIParseOpenOptions options;
+ MemoryContext stmt_mcontext = get_stmt_mcontext(estate);
+
+ /*
+ * Evaluate the string expression after the EXECUTE keyword. Its result is
+ * the querystring we have to execute.
+ */
+ query = exec_eval_expr(estate, dynquery, &isnull, &restype, &restypmod);
+ if (isnull)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("query string argument of EXECUTE is null")));
+
+ /* Get the C-String representation */
+ querystr = convert_value_to_string(estate, query, restype);
+
+ /* copy it into the stmt_mcontext before we clean up */
+ querystr = MemoryContextStrdup(stmt_mcontext, querystr);
+
+ exec_eval_cleanup(estate);
+
+ /*
+ * Open an implicit cursor for the query. We use SPI_cursor_parse_open
+ * even when there are no params, because this avoids making and freeing
+ * one copy of the plan.
+ */
+ memset(&options, 0, sizeof(options));
+ options.params = exec_eval_using_params(estate, params);
+ options.cursorOptions = cursorOptions;
+ options.read_only = estate->readonly_func;
+
+ portal = SPI_cursor_parse_open(portalname, querystr, &options);
+
+ if (portal == NULL)
+ elog(ERROR, "could not open implicit cursor for query \"%s\": %s",
+ querystr, SPI_result_code_string(SPI_result));
+
+ /* Release transient data */
+ MemoryContextReset(stmt_mcontext);
+
+ return portal;
+}
+
+/*
+ * Return a formatted string with information about an expression's parameters,
+ * or NULL if the expression does not take any parameters.
+ * The result is in the eval_mcontext.
+ */
+static char *
+format_expr_params(PLpgSQL_execstate *estate,
+ const PLpgSQL_expr *expr)
+{
+ int paramno;
+ int dno;
+ StringInfoData paramstr;
+ MemoryContext oldcontext;
+
+ if (!expr->paramnos)
+ return NULL;
+
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ initStringInfo(&paramstr);
+ paramno = 0;
+ dno = -1;
+ while ((dno = bms_next_member(expr->paramnos, dno)) >= 0)
+ {
+ Datum paramdatum;
+ Oid paramtypeid;
+ bool paramisnull;
+ int32 paramtypmod;
+ PLpgSQL_var *curvar;
+
+ curvar = (PLpgSQL_var *) estate->datums[dno];
+
+ exec_eval_datum(estate, (PLpgSQL_datum *) curvar,
+ &paramtypeid, &paramtypmod,
+ &paramdatum, &paramisnull);
+
+ appendStringInfo(&paramstr, "%s%s = ",
+ paramno > 0 ? ", " : "",
+ curvar->refname);
+
+ if (paramisnull)
+ appendStringInfoString(&paramstr, "NULL");
+ else
+ appendStringInfoStringQuoted(&paramstr,
+ convert_value_to_string(estate,
+ paramdatum,
+ paramtypeid),
+ -1);
+
+ paramno++;
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return paramstr.data;
+}
+
+/*
+ * Return a formatted string with information about the parameter values,
+ * or NULL if there are no parameters.
+ * The result is in the eval_mcontext.
+ */
+static char *
+format_preparedparamsdata(PLpgSQL_execstate *estate,
+ ParamListInfo paramLI)
+{
+ int paramno;
+ StringInfoData paramstr;
+ MemoryContext oldcontext;
+
+ if (!paramLI)
+ return NULL;
+
+ oldcontext = MemoryContextSwitchTo(get_eval_mcontext(estate));
+
+ initStringInfo(&paramstr);
+ for (paramno = 0; paramno < paramLI->numParams; paramno++)
+ {
+ ParamExternData *prm = &paramLI->params[paramno];
+
+ /*
+ * Note: for now, this is only used on ParamListInfos produced by
+ * exec_eval_using_params(), so we don't worry about invoking the
+ * paramFetch hook or skipping unused parameters.
+ */
+ appendStringInfo(&paramstr, "%s$%d = ",
+ paramno > 0 ? ", " : "",
+ paramno + 1);
+
+ if (prm->isnull)
+ appendStringInfoString(&paramstr, "NULL");
+ else
+ appendStringInfoStringQuoted(&paramstr,
+ convert_value_to_string(estate,
+ prm->value,
+ prm->ptype),
+ -1);
+ }
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return paramstr.data;
+}
diff --git a/src/pl/plpgsql/src/pl_funcs.c b/src/pl/plpgsql/src/pl_funcs.c
new file mode 100644
index 0000000..93d9cef
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_funcs.c
@@ -0,0 +1,1692 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_funcs.c - Misc functions for the PL/pgSQL
+ * procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_funcs.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "plpgsql.h"
+#include "utils/memutils.h"
+
+/* ----------
+ * Local variables for namespace handling
+ *
+ * The namespace structure actually forms a tree, of which only one linear
+ * list or "chain" (from the youngest item to the root) is accessible from
+ * any one plpgsql statement. During initial parsing of a function, ns_top
+ * points to the youngest item accessible from the block currently being
+ * parsed. We store the entire tree, however, since at runtime we will need
+ * to access the chain that's relevant to any one statement.
+ *
+ * Block boundaries in the namespace chain are marked by PLPGSQL_NSTYPE_LABEL
+ * items.
+ * ----------
+ */
+static PLpgSQL_nsitem *ns_top = NULL;
+
+
+/* ----------
+ * plpgsql_ns_init Initialize namespace processing for a new function
+ * ----------
+ */
+void
+plpgsql_ns_init(void)
+{
+ ns_top = NULL;
+}
+
+
+/* ----------
+ * plpgsql_ns_push Create a new namespace level
+ * ----------
+ */
+void
+plpgsql_ns_push(const char *label, PLpgSQL_label_type label_type)
+{
+ if (label == NULL)
+ label = "";
+ plpgsql_ns_additem(PLPGSQL_NSTYPE_LABEL, (int) label_type, label);
+}
+
+
+/* ----------
+ * plpgsql_ns_pop Pop entries back to (and including) the last label
+ * ----------
+ */
+void
+plpgsql_ns_pop(void)
+{
+ Assert(ns_top != NULL);
+ while (ns_top->itemtype != PLPGSQL_NSTYPE_LABEL)
+ ns_top = ns_top->prev;
+ ns_top = ns_top->prev;
+}
+
+
+/* ----------
+ * plpgsql_ns_top Fetch the current namespace chain end
+ * ----------
+ */
+PLpgSQL_nsitem *
+plpgsql_ns_top(void)
+{
+ return ns_top;
+}
+
+
+/* ----------
+ * plpgsql_ns_additem Add an item to the current namespace chain
+ * ----------
+ */
+void
+plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name)
+{
+ PLpgSQL_nsitem *nse;
+
+ Assert(name != NULL);
+ /* first item added must be a label */
+ Assert(ns_top != NULL || itemtype == PLPGSQL_NSTYPE_LABEL);
+
+ nse = palloc(offsetof(PLpgSQL_nsitem, name) + strlen(name) + 1);
+ nse->itemtype = itemtype;
+ nse->itemno = itemno;
+ nse->prev = ns_top;
+ strcpy(nse->name, name);
+ ns_top = nse;
+}
+
+
+/* ----------
+ * plpgsql_ns_lookup Lookup an identifier in the given namespace chain
+ *
+ * Note that this only searches for variables, not labels.
+ *
+ * If localmode is true, only the topmost block level is searched.
+ *
+ * name1 must be non-NULL. Pass NULL for name2 and/or name3 if parsing a name
+ * with fewer than three components.
+ *
+ * If names_used isn't NULL, *names_used receives the number of names
+ * matched: 0 if no match, 1 if name1 matched an unqualified variable name,
+ * 2 if name1 and name2 matched a block label + variable name.
+ *
+ * Note that name3 is never directly matched to anything. However, if it
+ * isn't NULL, we will disregard qualified matches to scalar variables.
+ * Similarly, if name2 isn't NULL, we disregard unqualified matches to
+ * scalar variables.
+ * ----------
+ */
+PLpgSQL_nsitem *
+plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
+ const char *name1, const char *name2, const char *name3,
+ int *names_used)
+{
+ /* Outer loop iterates once per block level in the namespace chain */
+ while (ns_cur != NULL)
+ {
+ PLpgSQL_nsitem *nsitem;
+
+ /* Check this level for unqualified match to variable name */
+ for (nsitem = ns_cur;
+ nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
+ nsitem = nsitem->prev)
+ {
+ if (strcmp(nsitem->name, name1) == 0)
+ {
+ if (name2 == NULL ||
+ nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
+ {
+ if (names_used)
+ *names_used = 1;
+ return nsitem;
+ }
+ }
+ }
+
+ /* Check this level for qualified match to variable name */
+ if (name2 != NULL &&
+ strcmp(nsitem->name, name1) == 0)
+ {
+ for (nsitem = ns_cur;
+ nsitem->itemtype != PLPGSQL_NSTYPE_LABEL;
+ nsitem = nsitem->prev)
+ {
+ if (strcmp(nsitem->name, name2) == 0)
+ {
+ if (name3 == NULL ||
+ nsitem->itemtype != PLPGSQL_NSTYPE_VAR)
+ {
+ if (names_used)
+ *names_used = 2;
+ return nsitem;
+ }
+ }
+ }
+ }
+
+ if (localmode)
+ break; /* do not look into upper levels */
+
+ ns_cur = nsitem->prev;
+ }
+
+ /* This is just to suppress possibly-uninitialized-variable warnings */
+ if (names_used)
+ *names_used = 0;
+ return NULL; /* No match found */
+}
+
+
+/* ----------
+ * plpgsql_ns_lookup_label Lookup a label in the given namespace chain
+ * ----------
+ */
+PLpgSQL_nsitem *
+plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur, const char *name)
+{
+ while (ns_cur != NULL)
+ {
+ if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
+ strcmp(ns_cur->name, name) == 0)
+ return ns_cur;
+ ns_cur = ns_cur->prev;
+ }
+
+ return NULL; /* label not found */
+}
+
+
+/* ----------
+ * plpgsql_ns_find_nearest_loop Find innermost loop label in namespace chain
+ * ----------
+ */
+PLpgSQL_nsitem *
+plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur)
+{
+ while (ns_cur != NULL)
+ {
+ if (ns_cur->itemtype == PLPGSQL_NSTYPE_LABEL &&
+ ns_cur->itemno == PLPGSQL_LABEL_LOOP)
+ return ns_cur;
+ ns_cur = ns_cur->prev;
+ }
+
+ return NULL; /* no loop found */
+}
+
+
+/*
+ * Statement type as a string, for use in error messages etc.
+ */
+const char *
+plpgsql_stmt_typename(PLpgSQL_stmt *stmt)
+{
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_BLOCK:
+ return _("statement block");
+ case PLPGSQL_STMT_ASSIGN:
+ return _("assignment");
+ case PLPGSQL_STMT_IF:
+ return "IF";
+ case PLPGSQL_STMT_CASE:
+ return "CASE";
+ case PLPGSQL_STMT_LOOP:
+ return "LOOP";
+ case PLPGSQL_STMT_WHILE:
+ return "WHILE";
+ case PLPGSQL_STMT_FORI:
+ return _("FOR with integer loop variable");
+ case PLPGSQL_STMT_FORS:
+ return _("FOR over SELECT rows");
+ case PLPGSQL_STMT_FORC:
+ return _("FOR over cursor");
+ case PLPGSQL_STMT_FOREACH_A:
+ return _("FOREACH over array");
+ case PLPGSQL_STMT_EXIT:
+ return ((PLpgSQL_stmt_exit *) stmt)->is_exit ? "EXIT" : "CONTINUE";
+ case PLPGSQL_STMT_RETURN:
+ return "RETURN";
+ case PLPGSQL_STMT_RETURN_NEXT:
+ return "RETURN NEXT";
+ case PLPGSQL_STMT_RETURN_QUERY:
+ return "RETURN QUERY";
+ case PLPGSQL_STMT_RAISE:
+ return "RAISE";
+ case PLPGSQL_STMT_ASSERT:
+ return "ASSERT";
+ case PLPGSQL_STMT_EXECSQL:
+ return _("SQL statement");
+ case PLPGSQL_STMT_DYNEXECUTE:
+ return "EXECUTE";
+ case PLPGSQL_STMT_DYNFORS:
+ return _("FOR over EXECUTE statement");
+ case PLPGSQL_STMT_GETDIAG:
+ return ((PLpgSQL_stmt_getdiag *) stmt)->is_stacked ?
+ "GET STACKED DIAGNOSTICS" : "GET DIAGNOSTICS";
+ case PLPGSQL_STMT_OPEN:
+ return "OPEN";
+ case PLPGSQL_STMT_FETCH:
+ return ((PLpgSQL_stmt_fetch *) stmt)->is_move ? "MOVE" : "FETCH";
+ case PLPGSQL_STMT_CLOSE:
+ return "CLOSE";
+ case PLPGSQL_STMT_PERFORM:
+ return "PERFORM";
+ case PLPGSQL_STMT_CALL:
+ return ((PLpgSQL_stmt_call *) stmt)->is_call ? "CALL" : "DO";
+ case PLPGSQL_STMT_COMMIT:
+ return "COMMIT";
+ case PLPGSQL_STMT_ROLLBACK:
+ return "ROLLBACK";
+ }
+
+ return "unknown";
+}
+
+/*
+ * GET DIAGNOSTICS item name as a string, for use in error messages etc.
+ */
+const char *
+plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind)
+{
+ switch (kind)
+ {
+ case PLPGSQL_GETDIAG_ROW_COUNT:
+ return "ROW_COUNT";
+ case PLPGSQL_GETDIAG_CONTEXT:
+ return "PG_CONTEXT";
+ case PLPGSQL_GETDIAG_ERROR_CONTEXT:
+ return "PG_EXCEPTION_CONTEXT";
+ case PLPGSQL_GETDIAG_ERROR_DETAIL:
+ return "PG_EXCEPTION_DETAIL";
+ case PLPGSQL_GETDIAG_ERROR_HINT:
+ return "PG_EXCEPTION_HINT";
+ case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
+ return "RETURNED_SQLSTATE";
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ return "COLUMN_NAME";
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ return "CONSTRAINT_NAME";
+ case PLPGSQL_GETDIAG_DATATYPE_NAME:
+ return "PG_DATATYPE_NAME";
+ case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ return "MESSAGE_TEXT";
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ return "TABLE_NAME";
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ return "SCHEMA_NAME";
+ }
+
+ return "unknown";
+}
+
+
+/**********************************************************************
+ * Release memory when a PL/pgSQL function is no longer needed
+ *
+ * The code for recursing through the function tree is really only
+ * needed to locate PLpgSQL_expr nodes, which may contain references
+ * to saved SPI Plans that must be freed. The function tree itself,
+ * along with subsidiary data, is freed in one swoop by freeing the
+ * function's permanent memory context.
+ **********************************************************************/
+static void free_stmt(PLpgSQL_stmt *stmt);
+static void free_block(PLpgSQL_stmt_block *block);
+static void free_assign(PLpgSQL_stmt_assign *stmt);
+static void free_if(PLpgSQL_stmt_if *stmt);
+static void free_case(PLpgSQL_stmt_case *stmt);
+static void free_loop(PLpgSQL_stmt_loop *stmt);
+static void free_while(PLpgSQL_stmt_while *stmt);
+static void free_fori(PLpgSQL_stmt_fori *stmt);
+static void free_fors(PLpgSQL_stmt_fors *stmt);
+static void free_forc(PLpgSQL_stmt_forc *stmt);
+static void free_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
+static void free_exit(PLpgSQL_stmt_exit *stmt);
+static void free_return(PLpgSQL_stmt_return *stmt);
+static void free_return_next(PLpgSQL_stmt_return_next *stmt);
+static void free_return_query(PLpgSQL_stmt_return_query *stmt);
+static void free_raise(PLpgSQL_stmt_raise *stmt);
+static void free_assert(PLpgSQL_stmt_assert *stmt);
+static void free_execsql(PLpgSQL_stmt_execsql *stmt);
+static void free_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
+static void free_dynfors(PLpgSQL_stmt_dynfors *stmt);
+static void free_getdiag(PLpgSQL_stmt_getdiag *stmt);
+static void free_open(PLpgSQL_stmt_open *stmt);
+static void free_fetch(PLpgSQL_stmt_fetch *stmt);
+static void free_close(PLpgSQL_stmt_close *stmt);
+static void free_perform(PLpgSQL_stmt_perform *stmt);
+static void free_call(PLpgSQL_stmt_call *stmt);
+static void free_commit(PLpgSQL_stmt_commit *stmt);
+static void free_rollback(PLpgSQL_stmt_rollback *stmt);
+static void free_expr(PLpgSQL_expr *expr);
+
+
+static void
+free_stmt(PLpgSQL_stmt *stmt)
+{
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_BLOCK:
+ free_block((PLpgSQL_stmt_block *) stmt);
+ break;
+ case PLPGSQL_STMT_ASSIGN:
+ free_assign((PLpgSQL_stmt_assign *) stmt);
+ break;
+ case PLPGSQL_STMT_IF:
+ free_if((PLpgSQL_stmt_if *) stmt);
+ break;
+ case PLPGSQL_STMT_CASE:
+ free_case((PLpgSQL_stmt_case *) stmt);
+ break;
+ case PLPGSQL_STMT_LOOP:
+ free_loop((PLpgSQL_stmt_loop *) stmt);
+ break;
+ case PLPGSQL_STMT_WHILE:
+ free_while((PLpgSQL_stmt_while *) stmt);
+ break;
+ case PLPGSQL_STMT_FORI:
+ free_fori((PLpgSQL_stmt_fori *) stmt);
+ break;
+ case PLPGSQL_STMT_FORS:
+ free_fors((PLpgSQL_stmt_fors *) stmt);
+ break;
+ case PLPGSQL_STMT_FORC:
+ free_forc((PLpgSQL_stmt_forc *) stmt);
+ break;
+ case PLPGSQL_STMT_FOREACH_A:
+ free_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ break;
+ case PLPGSQL_STMT_EXIT:
+ free_exit((PLpgSQL_stmt_exit *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN:
+ free_return((PLpgSQL_stmt_return *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN_NEXT:
+ free_return_next((PLpgSQL_stmt_return_next *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN_QUERY:
+ free_return_query((PLpgSQL_stmt_return_query *) stmt);
+ break;
+ case PLPGSQL_STMT_RAISE:
+ free_raise((PLpgSQL_stmt_raise *) stmt);
+ break;
+ case PLPGSQL_STMT_ASSERT:
+ free_assert((PLpgSQL_stmt_assert *) stmt);
+ break;
+ case PLPGSQL_STMT_EXECSQL:
+ free_execsql((PLpgSQL_stmt_execsql *) stmt);
+ break;
+ case PLPGSQL_STMT_DYNEXECUTE:
+ free_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
+ break;
+ case PLPGSQL_STMT_DYNFORS:
+ free_dynfors((PLpgSQL_stmt_dynfors *) stmt);
+ break;
+ case PLPGSQL_STMT_GETDIAG:
+ free_getdiag((PLpgSQL_stmt_getdiag *) stmt);
+ break;
+ case PLPGSQL_STMT_OPEN:
+ free_open((PLpgSQL_stmt_open *) stmt);
+ break;
+ case PLPGSQL_STMT_FETCH:
+ free_fetch((PLpgSQL_stmt_fetch *) stmt);
+ break;
+ case PLPGSQL_STMT_CLOSE:
+ free_close((PLpgSQL_stmt_close *) stmt);
+ break;
+ case PLPGSQL_STMT_PERFORM:
+ free_perform((PLpgSQL_stmt_perform *) stmt);
+ break;
+ case PLPGSQL_STMT_CALL:
+ free_call((PLpgSQL_stmt_call *) stmt);
+ break;
+ case PLPGSQL_STMT_COMMIT:
+ free_commit((PLpgSQL_stmt_commit *) stmt);
+ break;
+ case PLPGSQL_STMT_ROLLBACK:
+ free_rollback((PLpgSQL_stmt_rollback *) stmt);
+ break;
+ default:
+ elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ break;
+ }
+}
+
+static void
+free_stmts(List *stmts)
+{
+ ListCell *s;
+
+ foreach(s, stmts)
+ {
+ free_stmt((PLpgSQL_stmt *) lfirst(s));
+ }
+}
+
+static void
+free_block(PLpgSQL_stmt_block *block)
+{
+ free_stmts(block->body);
+ if (block->exceptions)
+ {
+ ListCell *e;
+
+ foreach(e, block->exceptions->exc_list)
+ {
+ PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
+
+ free_stmts(exc->action);
+ }
+ }
+}
+
+static void
+free_assign(PLpgSQL_stmt_assign *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_if(PLpgSQL_stmt_if *stmt)
+{
+ ListCell *l;
+
+ free_expr(stmt->cond);
+ free_stmts(stmt->then_body);
+ foreach(l, stmt->elsif_list)
+ {
+ PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+
+ free_expr(elif->cond);
+ free_stmts(elif->stmts);
+ }
+ free_stmts(stmt->else_body);
+}
+
+static void
+free_case(PLpgSQL_stmt_case *stmt)
+{
+ ListCell *l;
+
+ free_expr(stmt->t_expr);
+ foreach(l, stmt->case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+ free_expr(cwt->expr);
+ free_stmts(cwt->stmts);
+ }
+ free_stmts(stmt->else_stmts);
+}
+
+static void
+free_loop(PLpgSQL_stmt_loop *stmt)
+{
+ free_stmts(stmt->body);
+}
+
+static void
+free_while(PLpgSQL_stmt_while *stmt)
+{
+ free_expr(stmt->cond);
+ free_stmts(stmt->body);
+}
+
+static void
+free_fori(PLpgSQL_stmt_fori *stmt)
+{
+ free_expr(stmt->lower);
+ free_expr(stmt->upper);
+ free_expr(stmt->step);
+ free_stmts(stmt->body);
+}
+
+static void
+free_fors(PLpgSQL_stmt_fors *stmt)
+{
+ free_stmts(stmt->body);
+ free_expr(stmt->query);
+}
+
+static void
+free_forc(PLpgSQL_stmt_forc *stmt)
+{
+ free_stmts(stmt->body);
+ free_expr(stmt->argquery);
+}
+
+static void
+free_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+{
+ free_expr(stmt->expr);
+ free_stmts(stmt->body);
+}
+
+static void
+free_open(PLpgSQL_stmt_open *stmt)
+{
+ ListCell *lc;
+
+ free_expr(stmt->argquery);
+ free_expr(stmt->query);
+ free_expr(stmt->dynquery);
+ foreach(lc, stmt->params)
+ {
+ free_expr((PLpgSQL_expr *) lfirst(lc));
+ }
+}
+
+static void
+free_fetch(PLpgSQL_stmt_fetch *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_close(PLpgSQL_stmt_close *stmt)
+{
+}
+
+static void
+free_perform(PLpgSQL_stmt_perform *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_call(PLpgSQL_stmt_call *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_commit(PLpgSQL_stmt_commit *stmt)
+{
+}
+
+static void
+free_rollback(PLpgSQL_stmt_rollback *stmt)
+{
+}
+
+static void
+free_exit(PLpgSQL_stmt_exit *stmt)
+{
+ free_expr(stmt->cond);
+}
+
+static void
+free_return(PLpgSQL_stmt_return *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_return_next(PLpgSQL_stmt_return_next *stmt)
+{
+ free_expr(stmt->expr);
+}
+
+static void
+free_return_query(PLpgSQL_stmt_return_query *stmt)
+{
+ ListCell *lc;
+
+ free_expr(stmt->query);
+ free_expr(stmt->dynquery);
+ foreach(lc, stmt->params)
+ {
+ free_expr((PLpgSQL_expr *) lfirst(lc));
+ }
+}
+
+static void
+free_raise(PLpgSQL_stmt_raise *stmt)
+{
+ ListCell *lc;
+
+ foreach(lc, stmt->params)
+ {
+ free_expr((PLpgSQL_expr *) lfirst(lc));
+ }
+ foreach(lc, stmt->options)
+ {
+ PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+
+ free_expr(opt->expr);
+ }
+}
+
+static void
+free_assert(PLpgSQL_stmt_assert *stmt)
+{
+ free_expr(stmt->cond);
+ free_expr(stmt->message);
+}
+
+static void
+free_execsql(PLpgSQL_stmt_execsql *stmt)
+{
+ free_expr(stmt->sqlstmt);
+}
+
+static void
+free_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
+{
+ ListCell *lc;
+
+ free_expr(stmt->query);
+ foreach(lc, stmt->params)
+ {
+ free_expr((PLpgSQL_expr *) lfirst(lc));
+ }
+}
+
+static void
+free_dynfors(PLpgSQL_stmt_dynfors *stmt)
+{
+ ListCell *lc;
+
+ free_stmts(stmt->body);
+ free_expr(stmt->query);
+ foreach(lc, stmt->params)
+ {
+ free_expr((PLpgSQL_expr *) lfirst(lc));
+ }
+}
+
+static void
+free_getdiag(PLpgSQL_stmt_getdiag *stmt)
+{
+}
+
+static void
+free_expr(PLpgSQL_expr *expr)
+{
+ if (expr && expr->plan)
+ {
+ SPI_freeplan(expr->plan);
+ expr->plan = NULL;
+ }
+}
+
+void
+plpgsql_free_function_memory(PLpgSQL_function *func)
+{
+ int i;
+
+ /* Better not call this on an in-use function */
+ Assert(func->use_count == 0);
+
+ /* Release plans associated with variable declarations */
+ for (i = 0; i < func->ndatums; i++)
+ {
+ PLpgSQL_datum *d = func->datums[i];
+
+ switch (d->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) d;
+
+ free_expr(var->default_val);
+ free_expr(var->cursor_explicit_expr);
+ }
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ break;
+ case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) d;
+
+ free_expr(rec->default_val);
+ }
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ break;
+ default:
+ elog(ERROR, "unrecognized data type: %d", d->dtype);
+ }
+ }
+ func->ndatums = 0;
+
+ /* Release plans in statement tree */
+ if (func->action)
+ free_block(func->action);
+ func->action = NULL;
+
+ /*
+ * And finally, release all memory except the PLpgSQL_function struct
+ * itself (which has to be kept around because there may be multiple
+ * fn_extra pointers to it).
+ */
+ if (func->fn_cxt)
+ MemoryContextDelete(func->fn_cxt);
+ func->fn_cxt = NULL;
+}
+
+
+/**********************************************************************
+ * Debug functions for analyzing the compiled code
+ **********************************************************************/
+static int dump_indent;
+
+static void dump_ind(void);
+static void dump_stmt(PLpgSQL_stmt *stmt);
+static void dump_block(PLpgSQL_stmt_block *block);
+static void dump_assign(PLpgSQL_stmt_assign *stmt);
+static void dump_if(PLpgSQL_stmt_if *stmt);
+static void dump_case(PLpgSQL_stmt_case *stmt);
+static void dump_loop(PLpgSQL_stmt_loop *stmt);
+static void dump_while(PLpgSQL_stmt_while *stmt);
+static void dump_fori(PLpgSQL_stmt_fori *stmt);
+static void dump_fors(PLpgSQL_stmt_fors *stmt);
+static void dump_forc(PLpgSQL_stmt_forc *stmt);
+static void dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt);
+static void dump_exit(PLpgSQL_stmt_exit *stmt);
+static void dump_return(PLpgSQL_stmt_return *stmt);
+static void dump_return_next(PLpgSQL_stmt_return_next *stmt);
+static void dump_return_query(PLpgSQL_stmt_return_query *stmt);
+static void dump_raise(PLpgSQL_stmt_raise *stmt);
+static void dump_assert(PLpgSQL_stmt_assert *stmt);
+static void dump_execsql(PLpgSQL_stmt_execsql *stmt);
+static void dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt);
+static void dump_dynfors(PLpgSQL_stmt_dynfors *stmt);
+static void dump_getdiag(PLpgSQL_stmt_getdiag *stmt);
+static void dump_open(PLpgSQL_stmt_open *stmt);
+static void dump_fetch(PLpgSQL_stmt_fetch *stmt);
+static void dump_cursor_direction(PLpgSQL_stmt_fetch *stmt);
+static void dump_close(PLpgSQL_stmt_close *stmt);
+static void dump_perform(PLpgSQL_stmt_perform *stmt);
+static void dump_call(PLpgSQL_stmt_call *stmt);
+static void dump_commit(PLpgSQL_stmt_commit *stmt);
+static void dump_rollback(PLpgSQL_stmt_rollback *stmt);
+static void dump_expr(PLpgSQL_expr *expr);
+
+
+static void
+dump_ind(void)
+{
+ int i;
+
+ for (i = 0; i < dump_indent; i++)
+ printf(" ");
+}
+
+static void
+dump_stmt(PLpgSQL_stmt *stmt)
+{
+ printf("%3d:", stmt->lineno);
+ switch (stmt->cmd_type)
+ {
+ case PLPGSQL_STMT_BLOCK:
+ dump_block((PLpgSQL_stmt_block *) stmt);
+ break;
+ case PLPGSQL_STMT_ASSIGN:
+ dump_assign((PLpgSQL_stmt_assign *) stmt);
+ break;
+ case PLPGSQL_STMT_IF:
+ dump_if((PLpgSQL_stmt_if *) stmt);
+ break;
+ case PLPGSQL_STMT_CASE:
+ dump_case((PLpgSQL_stmt_case *) stmt);
+ break;
+ case PLPGSQL_STMT_LOOP:
+ dump_loop((PLpgSQL_stmt_loop *) stmt);
+ break;
+ case PLPGSQL_STMT_WHILE:
+ dump_while((PLpgSQL_stmt_while *) stmt);
+ break;
+ case PLPGSQL_STMT_FORI:
+ dump_fori((PLpgSQL_stmt_fori *) stmt);
+ break;
+ case PLPGSQL_STMT_FORS:
+ dump_fors((PLpgSQL_stmt_fors *) stmt);
+ break;
+ case PLPGSQL_STMT_FORC:
+ dump_forc((PLpgSQL_stmt_forc *) stmt);
+ break;
+ case PLPGSQL_STMT_FOREACH_A:
+ dump_foreach_a((PLpgSQL_stmt_foreach_a *) stmt);
+ break;
+ case PLPGSQL_STMT_EXIT:
+ dump_exit((PLpgSQL_stmt_exit *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN:
+ dump_return((PLpgSQL_stmt_return *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN_NEXT:
+ dump_return_next((PLpgSQL_stmt_return_next *) stmt);
+ break;
+ case PLPGSQL_STMT_RETURN_QUERY:
+ dump_return_query((PLpgSQL_stmt_return_query *) stmt);
+ break;
+ case PLPGSQL_STMT_RAISE:
+ dump_raise((PLpgSQL_stmt_raise *) stmt);
+ break;
+ case PLPGSQL_STMT_ASSERT:
+ dump_assert((PLpgSQL_stmt_assert *) stmt);
+ break;
+ case PLPGSQL_STMT_EXECSQL:
+ dump_execsql((PLpgSQL_stmt_execsql *) stmt);
+ break;
+ case PLPGSQL_STMT_DYNEXECUTE:
+ dump_dynexecute((PLpgSQL_stmt_dynexecute *) stmt);
+ break;
+ case PLPGSQL_STMT_DYNFORS:
+ dump_dynfors((PLpgSQL_stmt_dynfors *) stmt);
+ break;
+ case PLPGSQL_STMT_GETDIAG:
+ dump_getdiag((PLpgSQL_stmt_getdiag *) stmt);
+ break;
+ case PLPGSQL_STMT_OPEN:
+ dump_open((PLpgSQL_stmt_open *) stmt);
+ break;
+ case PLPGSQL_STMT_FETCH:
+ dump_fetch((PLpgSQL_stmt_fetch *) stmt);
+ break;
+ case PLPGSQL_STMT_CLOSE:
+ dump_close((PLpgSQL_stmt_close *) stmt);
+ break;
+ case PLPGSQL_STMT_PERFORM:
+ dump_perform((PLpgSQL_stmt_perform *) stmt);
+ break;
+ case PLPGSQL_STMT_CALL:
+ dump_call((PLpgSQL_stmt_call *) stmt);
+ break;
+ case PLPGSQL_STMT_COMMIT:
+ dump_commit((PLpgSQL_stmt_commit *) stmt);
+ break;
+ case PLPGSQL_STMT_ROLLBACK:
+ dump_rollback((PLpgSQL_stmt_rollback *) stmt);
+ break;
+ default:
+ elog(ERROR, "unrecognized cmd_type: %d", stmt->cmd_type);
+ break;
+ }
+}
+
+static void
+dump_stmts(List *stmts)
+{
+ ListCell *s;
+
+ dump_indent += 2;
+ foreach(s, stmts)
+ dump_stmt((PLpgSQL_stmt *) lfirst(s));
+ dump_indent -= 2;
+}
+
+static void
+dump_block(PLpgSQL_stmt_block *block)
+{
+ char *name;
+
+ if (block->label == NULL)
+ name = "*unnamed*";
+ else
+ name = block->label;
+
+ dump_ind();
+ printf("BLOCK <<%s>>\n", name);
+
+ dump_stmts(block->body);
+
+ if (block->exceptions)
+ {
+ ListCell *e;
+
+ foreach(e, block->exceptions->exc_list)
+ {
+ PLpgSQL_exception *exc = (PLpgSQL_exception *) lfirst(e);
+ PLpgSQL_condition *cond;
+
+ dump_ind();
+ printf(" EXCEPTION WHEN ");
+ for (cond = exc->conditions; cond; cond = cond->next)
+ {
+ if (cond != exc->conditions)
+ printf(" OR ");
+ printf("%s", cond->condname);
+ }
+ printf(" THEN\n");
+ dump_stmts(exc->action);
+ }
+ }
+
+ dump_ind();
+ printf(" END -- %s\n", name);
+}
+
+static void
+dump_assign(PLpgSQL_stmt_assign *stmt)
+{
+ dump_ind();
+ printf("ASSIGN var %d := ", stmt->varno);
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
+static void
+dump_if(PLpgSQL_stmt_if *stmt)
+{
+ ListCell *l;
+
+ dump_ind();
+ printf("IF ");
+ dump_expr(stmt->cond);
+ printf(" THEN\n");
+ dump_stmts(stmt->then_body);
+ foreach(l, stmt->elsif_list)
+ {
+ PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *) lfirst(l);
+
+ dump_ind();
+ printf(" ELSIF ");
+ dump_expr(elif->cond);
+ printf(" THEN\n");
+ dump_stmts(elif->stmts);
+ }
+ if (stmt->else_body != NIL)
+ {
+ dump_ind();
+ printf(" ELSE\n");
+ dump_stmts(stmt->else_body);
+ }
+ dump_ind();
+ printf(" ENDIF\n");
+}
+
+static void
+dump_case(PLpgSQL_stmt_case *stmt)
+{
+ ListCell *l;
+
+ dump_ind();
+ printf("CASE %d ", stmt->t_varno);
+ if (stmt->t_expr)
+ dump_expr(stmt->t_expr);
+ printf("\n");
+ dump_indent += 6;
+ foreach(l, stmt->case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+
+ dump_ind();
+ printf("WHEN ");
+ dump_expr(cwt->expr);
+ printf("\n");
+ dump_ind();
+ printf("THEN\n");
+ dump_indent += 2;
+ dump_stmts(cwt->stmts);
+ dump_indent -= 2;
+ }
+ if (stmt->have_else)
+ {
+ dump_ind();
+ printf("ELSE\n");
+ dump_indent += 2;
+ dump_stmts(stmt->else_stmts);
+ dump_indent -= 2;
+ }
+ dump_indent -= 6;
+ dump_ind();
+ printf(" ENDCASE\n");
+}
+
+static void
+dump_loop(PLpgSQL_stmt_loop *stmt)
+{
+ dump_ind();
+ printf("LOOP\n");
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDLOOP\n");
+}
+
+static void
+dump_while(PLpgSQL_stmt_while *stmt)
+{
+ dump_ind();
+ printf("WHILE ");
+ dump_expr(stmt->cond);
+ printf("\n");
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDWHILE\n");
+}
+
+static void
+dump_fori(PLpgSQL_stmt_fori *stmt)
+{
+ dump_ind();
+ printf("FORI %s %s\n", stmt->var->refname, (stmt->reverse) ? "REVERSE" : "NORMAL");
+
+ dump_indent += 2;
+ dump_ind();
+ printf(" lower = ");
+ dump_expr(stmt->lower);
+ printf("\n");
+ dump_ind();
+ printf(" upper = ");
+ dump_expr(stmt->upper);
+ printf("\n");
+ if (stmt->step)
+ {
+ dump_ind();
+ printf(" step = ");
+ dump_expr(stmt->step);
+ printf("\n");
+ }
+ dump_indent -= 2;
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDFORI\n");
+}
+
+static void
+dump_fors(PLpgSQL_stmt_fors *stmt)
+{
+ dump_ind();
+ printf("FORS %s ", stmt->var->refname);
+ dump_expr(stmt->query);
+ printf("\n");
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDFORS\n");
+}
+
+static void
+dump_forc(PLpgSQL_stmt_forc *stmt)
+{
+ dump_ind();
+ printf("FORC %s ", stmt->var->refname);
+ printf("curvar=%d\n", stmt->curvar);
+
+ dump_indent += 2;
+ if (stmt->argquery != NULL)
+ {
+ dump_ind();
+ printf(" arguments = ");
+ dump_expr(stmt->argquery);
+ printf("\n");
+ }
+ dump_indent -= 2;
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDFORC\n");
+}
+
+static void
+dump_foreach_a(PLpgSQL_stmt_foreach_a *stmt)
+{
+ dump_ind();
+ printf("FOREACHA var %d ", stmt->varno);
+ if (stmt->slice != 0)
+ printf("SLICE %d ", stmt->slice);
+ printf("IN ");
+ dump_expr(stmt->expr);
+ printf("\n");
+
+ dump_stmts(stmt->body);
+
+ dump_ind();
+ printf(" ENDFOREACHA");
+}
+
+static void
+dump_open(PLpgSQL_stmt_open *stmt)
+{
+ dump_ind();
+ printf("OPEN curvar=%d\n", stmt->curvar);
+
+ dump_indent += 2;
+ if (stmt->argquery != NULL)
+ {
+ dump_ind();
+ printf(" arguments = '");
+ dump_expr(stmt->argquery);
+ printf("'\n");
+ }
+ if (stmt->query != NULL)
+ {
+ dump_ind();
+ printf(" query = '");
+ dump_expr(stmt->query);
+ printf("'\n");
+ }
+ if (stmt->dynquery != NULL)
+ {
+ dump_ind();
+ printf(" execute = '");
+ dump_expr(stmt->dynquery);
+ printf("'\n");
+
+ if (stmt->params != NIL)
+ {
+ ListCell *lc;
+ int i;
+
+ dump_indent += 2;
+ dump_ind();
+ printf(" USING\n");
+ dump_indent += 2;
+ i = 1;
+ foreach(lc, stmt->params)
+ {
+ dump_ind();
+ printf(" parameter $%d: ", i++);
+ dump_expr((PLpgSQL_expr *) lfirst(lc));
+ printf("\n");
+ }
+ dump_indent -= 4;
+ }
+ }
+ dump_indent -= 2;
+}
+
+static void
+dump_fetch(PLpgSQL_stmt_fetch *stmt)
+{
+ dump_ind();
+
+ if (!stmt->is_move)
+ {
+ printf("FETCH curvar=%d\n", stmt->curvar);
+ dump_cursor_direction(stmt);
+
+ dump_indent += 2;
+ if (stmt->target != NULL)
+ {
+ dump_ind();
+ printf(" target = %d %s\n",
+ stmt->target->dno, stmt->target->refname);
+ }
+ dump_indent -= 2;
+ }
+ else
+ {
+ printf("MOVE curvar=%d\n", stmt->curvar);
+ dump_cursor_direction(stmt);
+ }
+}
+
+static void
+dump_cursor_direction(PLpgSQL_stmt_fetch *stmt)
+{
+ dump_indent += 2;
+ dump_ind();
+ switch (stmt->direction)
+ {
+ case FETCH_FORWARD:
+ printf(" FORWARD ");
+ break;
+ case FETCH_BACKWARD:
+ printf(" BACKWARD ");
+ break;
+ case FETCH_ABSOLUTE:
+ printf(" ABSOLUTE ");
+ break;
+ case FETCH_RELATIVE:
+ printf(" RELATIVE ");
+ break;
+ default:
+ printf("??? unknown cursor direction %d", stmt->direction);
+ }
+
+ if (stmt->expr)
+ {
+ dump_expr(stmt->expr);
+ printf("\n");
+ }
+ else
+ printf("%ld\n", stmt->how_many);
+
+ dump_indent -= 2;
+}
+
+static void
+dump_close(PLpgSQL_stmt_close *stmt)
+{
+ dump_ind();
+ printf("CLOSE curvar=%d\n", stmt->curvar);
+}
+
+static void
+dump_perform(PLpgSQL_stmt_perform *stmt)
+{
+ dump_ind();
+ printf("PERFORM expr = ");
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
+static void
+dump_call(PLpgSQL_stmt_call *stmt)
+{
+ dump_ind();
+ printf("%s expr = ", stmt->is_call ? "CALL" : "DO");
+ dump_expr(stmt->expr);
+ printf("\n");
+}
+
+static void
+dump_commit(PLpgSQL_stmt_commit *stmt)
+{
+ dump_ind();
+ if (stmt->chain)
+ printf("COMMIT AND CHAIN\n");
+ else
+ printf("COMMIT\n");
+}
+
+static void
+dump_rollback(PLpgSQL_stmt_rollback *stmt)
+{
+ dump_ind();
+ if (stmt->chain)
+ printf("ROLLBACK AND CHAIN\n");
+ else
+ printf("ROLLBACK\n");
+}
+
+static void
+dump_exit(PLpgSQL_stmt_exit *stmt)
+{
+ dump_ind();
+ printf("%s", stmt->is_exit ? "EXIT" : "CONTINUE");
+ if (stmt->label != NULL)
+ printf(" label='%s'", stmt->label);
+ if (stmt->cond != NULL)
+ {
+ printf(" WHEN ");
+ dump_expr(stmt->cond);
+ }
+ printf("\n");
+}
+
+static void
+dump_return(PLpgSQL_stmt_return *stmt)
+{
+ dump_ind();
+ printf("RETURN ");
+ if (stmt->retvarno >= 0)
+ printf("variable %d", stmt->retvarno);
+ else if (stmt->expr != NULL)
+ dump_expr(stmt->expr);
+ else
+ printf("NULL");
+ printf("\n");
+}
+
+static void
+dump_return_next(PLpgSQL_stmt_return_next *stmt)
+{
+ dump_ind();
+ printf("RETURN NEXT ");
+ if (stmt->retvarno >= 0)
+ printf("variable %d", stmt->retvarno);
+ else if (stmt->expr != NULL)
+ dump_expr(stmt->expr);
+ else
+ printf("NULL");
+ printf("\n");
+}
+
+static void
+dump_return_query(PLpgSQL_stmt_return_query *stmt)
+{
+ dump_ind();
+ if (stmt->query)
+ {
+ printf("RETURN QUERY ");
+ dump_expr(stmt->query);
+ printf("\n");
+ }
+ else
+ {
+ printf("RETURN QUERY EXECUTE ");
+ dump_expr(stmt->dynquery);
+ printf("\n");
+ if (stmt->params != NIL)
+ {
+ ListCell *lc;
+ int i;
+
+ dump_indent += 2;
+ dump_ind();
+ printf(" USING\n");
+ dump_indent += 2;
+ i = 1;
+ foreach(lc, stmt->params)
+ {
+ dump_ind();
+ printf(" parameter $%d: ", i++);
+ dump_expr((PLpgSQL_expr *) lfirst(lc));
+ printf("\n");
+ }
+ dump_indent -= 4;
+ }
+ }
+}
+
+static void
+dump_raise(PLpgSQL_stmt_raise *stmt)
+{
+ ListCell *lc;
+ int i = 0;
+
+ dump_ind();
+ printf("RAISE level=%d", stmt->elog_level);
+ if (stmt->condname)
+ printf(" condname='%s'", stmt->condname);
+ if (stmt->message)
+ printf(" message='%s'", stmt->message);
+ printf("\n");
+ dump_indent += 2;
+ foreach(lc, stmt->params)
+ {
+ dump_ind();
+ printf(" parameter %d: ", i++);
+ dump_expr((PLpgSQL_expr *) lfirst(lc));
+ printf("\n");
+ }
+ if (stmt->options)
+ {
+ dump_ind();
+ printf(" USING\n");
+ dump_indent += 2;
+ foreach(lc, stmt->options)
+ {
+ PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *) lfirst(lc);
+
+ dump_ind();
+ switch (opt->opt_type)
+ {
+ case PLPGSQL_RAISEOPTION_ERRCODE:
+ printf(" ERRCODE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_MESSAGE:
+ printf(" MESSAGE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_DETAIL:
+ printf(" DETAIL = ");
+ break;
+ case PLPGSQL_RAISEOPTION_HINT:
+ printf(" HINT = ");
+ break;
+ case PLPGSQL_RAISEOPTION_COLUMN:
+ printf(" COLUMN = ");
+ break;
+ case PLPGSQL_RAISEOPTION_CONSTRAINT:
+ printf(" CONSTRAINT = ");
+ break;
+ case PLPGSQL_RAISEOPTION_DATATYPE:
+ printf(" DATATYPE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_TABLE:
+ printf(" TABLE = ");
+ break;
+ case PLPGSQL_RAISEOPTION_SCHEMA:
+ printf(" SCHEMA = ");
+ break;
+ }
+ dump_expr(opt->expr);
+ printf("\n");
+ }
+ dump_indent -= 2;
+ }
+ dump_indent -= 2;
+}
+
+static void
+dump_assert(PLpgSQL_stmt_assert *stmt)
+{
+ dump_ind();
+ printf("ASSERT ");
+ dump_expr(stmt->cond);
+ printf("\n");
+
+ dump_indent += 2;
+ if (stmt->message != NULL)
+ {
+ dump_ind();
+ printf(" MESSAGE = ");
+ dump_expr(stmt->message);
+ printf("\n");
+ }
+ dump_indent -= 2;
+}
+
+static void
+dump_execsql(PLpgSQL_stmt_execsql *stmt)
+{
+ dump_ind();
+ printf("EXECSQL ");
+ dump_expr(stmt->sqlstmt);
+ printf("\n");
+
+ dump_indent += 2;
+ if (stmt->target != NULL)
+ {
+ dump_ind();
+ printf(" INTO%s target = %d %s\n",
+ stmt->strict ? " STRICT" : "",
+ stmt->target->dno, stmt->target->refname);
+ }
+ dump_indent -= 2;
+}
+
+static void
+dump_dynexecute(PLpgSQL_stmt_dynexecute *stmt)
+{
+ dump_ind();
+ printf("EXECUTE ");
+ dump_expr(stmt->query);
+ printf("\n");
+
+ dump_indent += 2;
+ if (stmt->target != NULL)
+ {
+ dump_ind();
+ printf(" INTO%s target = %d %s\n",
+ stmt->strict ? " STRICT" : "",
+ stmt->target->dno, stmt->target->refname);
+ }
+ if (stmt->params != NIL)
+ {
+ ListCell *lc;
+ int i;
+
+ dump_ind();
+ printf(" USING\n");
+ dump_indent += 2;
+ i = 1;
+ foreach(lc, stmt->params)
+ {
+ dump_ind();
+ printf(" parameter %d: ", i++);
+ dump_expr((PLpgSQL_expr *) lfirst(lc));
+ printf("\n");
+ }
+ dump_indent -= 2;
+ }
+ dump_indent -= 2;
+}
+
+static void
+dump_dynfors(PLpgSQL_stmt_dynfors *stmt)
+{
+ dump_ind();
+ printf("FORS %s EXECUTE ", stmt->var->refname);
+ dump_expr(stmt->query);
+ printf("\n");
+ if (stmt->params != NIL)
+ {
+ ListCell *lc;
+ int i;
+
+ dump_indent += 2;
+ dump_ind();
+ printf(" USING\n");
+ dump_indent += 2;
+ i = 1;
+ foreach(lc, stmt->params)
+ {
+ dump_ind();
+ printf(" parameter $%d: ", i++);
+ dump_expr((PLpgSQL_expr *) lfirst(lc));
+ printf("\n");
+ }
+ dump_indent -= 4;
+ }
+ dump_stmts(stmt->body);
+ dump_ind();
+ printf(" ENDFORS\n");
+}
+
+static void
+dump_getdiag(PLpgSQL_stmt_getdiag *stmt)
+{
+ ListCell *lc;
+
+ dump_ind();
+ printf("GET %s DIAGNOSTICS ", stmt->is_stacked ? "STACKED" : "CURRENT");
+ foreach(lc, stmt->diag_items)
+ {
+ PLpgSQL_diag_item *diag_item = (PLpgSQL_diag_item *) lfirst(lc);
+
+ if (lc != list_head(stmt->diag_items))
+ printf(", ");
+
+ printf("{var %d} = %s", diag_item->target,
+ plpgsql_getdiag_kindname(diag_item->kind));
+ }
+ printf("\n");
+}
+
+static void
+dump_expr(PLpgSQL_expr *expr)
+{
+ printf("'%s'", expr->query);
+}
+
+void
+plpgsql_dumptree(PLpgSQL_function *func)
+{
+ int i;
+ PLpgSQL_datum *d;
+
+ printf("\nExecution tree of successfully compiled PL/pgSQL function %s:\n",
+ func->fn_signature);
+
+ printf("\nFunction's data area:\n");
+ for (i = 0; i < func->ndatums; i++)
+ {
+ d = func->datums[i];
+
+ printf(" entry %d: ", i);
+ switch (d->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ {
+ PLpgSQL_var *var = (PLpgSQL_var *) d;
+
+ printf("VAR %-16s type %s (typoid %u) atttypmod %d\n",
+ var->refname, var->datatype->typname,
+ var->datatype->typoid,
+ var->datatype->atttypmod);
+ if (var->isconst)
+ printf(" CONSTANT\n");
+ if (var->notnull)
+ printf(" NOT NULL\n");
+ if (var->default_val != NULL)
+ {
+ printf(" DEFAULT ");
+ dump_expr(var->default_val);
+ printf("\n");
+ }
+ if (var->cursor_explicit_expr != NULL)
+ {
+ if (var->cursor_explicit_argrow >= 0)
+ printf(" CURSOR argument row %d\n", var->cursor_explicit_argrow);
+
+ printf(" CURSOR IS ");
+ dump_expr(var->cursor_explicit_expr);
+ printf("\n");
+ }
+ if (var->promise != PLPGSQL_PROMISE_NONE)
+ printf(" PROMISE %d\n",
+ (int) var->promise);
+ }
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ {
+ PLpgSQL_row *row = (PLpgSQL_row *) d;
+ int i;
+
+ printf("ROW %-16s fields", row->refname);
+ for (i = 0; i < row->nfields; i++)
+ {
+ printf(" %s=var %d", row->fieldnames[i],
+ row->varnos[i]);
+ }
+ printf("\n");
+ }
+ break;
+ case PLPGSQL_DTYPE_REC:
+ printf("REC %-16s typoid %u\n",
+ ((PLpgSQL_rec *) d)->refname,
+ ((PLpgSQL_rec *) d)->rectypeid);
+ if (((PLpgSQL_rec *) d)->isconst)
+ printf(" CONSTANT\n");
+ if (((PLpgSQL_rec *) d)->notnull)
+ printf(" NOT NULL\n");
+ if (((PLpgSQL_rec *) d)->default_val != NULL)
+ {
+ printf(" DEFAULT ");
+ dump_expr(((PLpgSQL_rec *) d)->default_val);
+ printf("\n");
+ }
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ printf("RECFIELD %-16s of REC %d\n",
+ ((PLpgSQL_recfield *) d)->fieldname,
+ ((PLpgSQL_recfield *) d)->recparentno);
+ break;
+ default:
+ printf("??? unknown data type %d\n", d->dtype);
+ }
+ }
+ printf("\nFunction's statements:\n");
+
+ dump_indent = 0;
+ printf("%3d:", func->action->lineno);
+ dump_block(func->action);
+ printf("\nEnd of execution tree of function %s\n\n", func->fn_signature);
+ fflush(stdout);
+}
diff --git a/src/pl/plpgsql/src/pl_gram.c b/src/pl/plpgsql/src/pl_gram.c
new file mode 100644
index 0000000..2d3e1c2
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_gram.c
@@ -0,0 +1,6316 @@
+/* A Bison parser, made by GNU Bison 3.7.5. */
+
+/* Bison implementation for Yacc-like parsers in C
+
+ Copyright (C) 1984, 1989-1990, 2000-2015, 2018-2021 Free Software Foundation,
+ Inc.
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* C LALR(1) parser skeleton written by Richard Stallman, by
+ simplifying the original so-called "semantic" parser. */
+
+/* DO NOT RELY ON FEATURES THAT ARE NOT DOCUMENTED in the manual,
+ especially those whose name start with YY_ or yy_. They are
+ private implementation details that can be changed or removed. */
+
+/* All symbols defined below should begin with yy or YY, to avoid
+ infringing on user name space. This should be done even for local
+ variables, as they might otherwise be expanded by user macros.
+ There are some unavoidable exceptions within include files to
+ define necessary library symbols; they are noted "INFRINGES ON
+ USER NAME SPACE" below. */
+
+/* Identify Bison output, and Bison version. */
+#define YYBISON 30705
+
+/* Bison version string. */
+#define YYBISON_VERSION "3.7.5"
+
+/* Skeleton name. */
+#define YYSKELETON_NAME "yacc.c"
+
+/* Pure parsers. */
+#define YYPURE 0
+
+/* Push parsers. */
+#define YYPUSH 0
+
+/* Pull parsers. */
+#define YYPULL 1
+
+
+/* Substitute the variable and function names. */
+#define yyparse plpgsql_yyparse
+#define yylex plpgsql_yylex
+#define yyerror plpgsql_yyerror
+#define yydebug plpgsql_yydebug
+#define yynerrs plpgsql_yynerrs
+#define yylval plpgsql_yylval
+#define yychar plpgsql_yychar
+#define yylloc plpgsql_yylloc
+
+/* First part of user prologue. */
+#line 1 "pl_gram.y"
+
+/*-------------------------------------------------------------------------
+ *
+ * pl_gram.y - Parser for the PL/pgSQL procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "parser/parser.h"
+#include "parser/parse_type.h"
+#include "parser/scanner.h"
+#include "parser/scansup.h"
+#include "utils/builtins.h"
+
+#include "plpgsql.h"
+
+
+/* Location tracking support --- simpler than bison's default */
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do { \
+ if (N) \
+ (Current) = (Rhs)[1]; \
+ else \
+ (Current) = (Rhs)[0]; \
+ } while (0)
+
+/*
+ * 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. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+
+typedef struct
+{
+ int location;
+} sql_error_callback_arg;
+
+#define parser_errposition(pos) plpgsql_scanner_errposition(pos)
+
+union YYSTYPE; /* need forward reference for tok_is_keyword */
+
+static bool tok_is_keyword(int token, union YYSTYPE *lval,
+ int kw_token, const char *kw_str);
+static void word_is_not_variable(PLword *word, int location);
+static void cword_is_not_variable(PLcword *cword, int location);
+static void current_token_is_not_variable(int tok);
+static PLpgSQL_expr *read_sql_construct(int until,
+ int until2,
+ int until3,
+ const char *expected,
+ RawParseMode parsemode,
+ bool isexpression,
+ bool valid_sql,
+ bool trim,
+ int *startloc,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_expression(int until,
+ const char *expected);
+static PLpgSQL_expr *read_sql_expression2(int until, int until2,
+ const char *expected,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_stmt(void);
+static PLpgSQL_type *read_datatype(int tok);
+static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
+static PLpgSQL_stmt_fetch *read_fetch_direction(void);
+static void complete_direction(PLpgSQL_stmt_fetch *fetch,
+ bool *check_FROM);
+static PLpgSQL_stmt *make_return_stmt(int location);
+static PLpgSQL_stmt *make_return_next_stmt(int location);
+static PLpgSQL_stmt *make_return_query_stmt(int location);
+static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts);
+static char *NameOfDatum(PLwdatum *wdatum);
+static void check_assignable(PLpgSQL_datum *datum, int location);
+static void read_into_target(PLpgSQL_variable **target,
+ bool *strict);
+static PLpgSQL_row *read_into_scalar_list(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int initial_location);
+static PLpgSQL_row *make_scalar_list1(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int lineno, int location);
+static void check_sql_expr(const char *stmt,
+ RawParseMode parseMode, int location);
+static void plpgsql_sql_error_callback(void *arg);
+static PLpgSQL_type *parse_datatype(const char *string, int location);
+static void check_labels(const char *start_label,
+ const char *end_label,
+ int end_location);
+static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
+ int until);
+static List *read_raise_options(void);
+static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+
+
+#line 193 "pl_gram.c"
+
+# ifndef YY_CAST
+# ifdef __cplusplus
+# define YY_CAST(Type, Val) static_cast<Type> (Val)
+# define YY_REINTERPRET_CAST(Type, Val) reinterpret_cast<Type> (Val)
+# else
+# define YY_CAST(Type, Val) ((Type) (Val))
+# define YY_REINTERPRET_CAST(Type, Val) ((Type) (Val))
+# endif
+# endif
+# ifndef YY_NULLPTR
+# if defined __cplusplus
+# if 201103L <= __cplusplus
+# define YY_NULLPTR nullptr
+# else
+# define YY_NULLPTR 0
+# endif
+# else
+# define YY_NULLPTR ((void*)0)
+# endif
+# endif
+
+#include "pl_gram.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_IDENT = 3, /* IDENT */
+ YYSYMBOL_UIDENT = 4, /* UIDENT */
+ YYSYMBOL_FCONST = 5, /* FCONST */
+ YYSYMBOL_SCONST = 6, /* SCONST */
+ YYSYMBOL_USCONST = 7, /* USCONST */
+ YYSYMBOL_BCONST = 8, /* BCONST */
+ YYSYMBOL_XCONST = 9, /* XCONST */
+ YYSYMBOL_Op = 10, /* Op */
+ YYSYMBOL_ICONST = 11, /* ICONST */
+ YYSYMBOL_PARAM = 12, /* PARAM */
+ YYSYMBOL_TYPECAST = 13, /* TYPECAST */
+ YYSYMBOL_DOT_DOT = 14, /* DOT_DOT */
+ YYSYMBOL_COLON_EQUALS = 15, /* COLON_EQUALS */
+ YYSYMBOL_EQUALS_GREATER = 16, /* EQUALS_GREATER */
+ YYSYMBOL_LESS_EQUALS = 17, /* LESS_EQUALS */
+ YYSYMBOL_GREATER_EQUALS = 18, /* GREATER_EQUALS */
+ YYSYMBOL_NOT_EQUALS = 19, /* NOT_EQUALS */
+ YYSYMBOL_T_WORD = 20, /* T_WORD */
+ YYSYMBOL_T_CWORD = 21, /* T_CWORD */
+ YYSYMBOL_T_DATUM = 22, /* T_DATUM */
+ YYSYMBOL_LESS_LESS = 23, /* LESS_LESS */
+ YYSYMBOL_GREATER_GREATER = 24, /* GREATER_GREATER */
+ YYSYMBOL_K_ABSOLUTE = 25, /* K_ABSOLUTE */
+ YYSYMBOL_K_ALIAS = 26, /* K_ALIAS */
+ YYSYMBOL_K_ALL = 27, /* K_ALL */
+ YYSYMBOL_K_AND = 28, /* K_AND */
+ YYSYMBOL_K_ARRAY = 29, /* K_ARRAY */
+ YYSYMBOL_K_ASSERT = 30, /* K_ASSERT */
+ YYSYMBOL_K_BACKWARD = 31, /* K_BACKWARD */
+ YYSYMBOL_K_BEGIN = 32, /* K_BEGIN */
+ YYSYMBOL_K_BY = 33, /* K_BY */
+ YYSYMBOL_K_CALL = 34, /* K_CALL */
+ YYSYMBOL_K_CASE = 35, /* K_CASE */
+ YYSYMBOL_K_CHAIN = 36, /* K_CHAIN */
+ YYSYMBOL_K_CLOSE = 37, /* K_CLOSE */
+ YYSYMBOL_K_COLLATE = 38, /* K_COLLATE */
+ YYSYMBOL_K_COLUMN = 39, /* K_COLUMN */
+ YYSYMBOL_K_COLUMN_NAME = 40, /* K_COLUMN_NAME */
+ YYSYMBOL_K_COMMIT = 41, /* K_COMMIT */
+ YYSYMBOL_K_CONSTANT = 42, /* K_CONSTANT */
+ YYSYMBOL_K_CONSTRAINT = 43, /* K_CONSTRAINT */
+ YYSYMBOL_K_CONSTRAINT_NAME = 44, /* K_CONSTRAINT_NAME */
+ YYSYMBOL_K_CONTINUE = 45, /* K_CONTINUE */
+ YYSYMBOL_K_CURRENT = 46, /* K_CURRENT */
+ YYSYMBOL_K_CURSOR = 47, /* K_CURSOR */
+ YYSYMBOL_K_DATATYPE = 48, /* K_DATATYPE */
+ YYSYMBOL_K_DEBUG = 49, /* K_DEBUG */
+ YYSYMBOL_K_DECLARE = 50, /* K_DECLARE */
+ YYSYMBOL_K_DEFAULT = 51, /* K_DEFAULT */
+ YYSYMBOL_K_DETAIL = 52, /* K_DETAIL */
+ YYSYMBOL_K_DIAGNOSTICS = 53, /* K_DIAGNOSTICS */
+ YYSYMBOL_K_DO = 54, /* K_DO */
+ YYSYMBOL_K_DUMP = 55, /* K_DUMP */
+ YYSYMBOL_K_ELSE = 56, /* K_ELSE */
+ YYSYMBOL_K_ELSIF = 57, /* K_ELSIF */
+ YYSYMBOL_K_END = 58, /* K_END */
+ YYSYMBOL_K_ERRCODE = 59, /* K_ERRCODE */
+ YYSYMBOL_K_ERROR = 60, /* K_ERROR */
+ YYSYMBOL_K_EXCEPTION = 61, /* K_EXCEPTION */
+ YYSYMBOL_K_EXECUTE = 62, /* K_EXECUTE */
+ YYSYMBOL_K_EXIT = 63, /* K_EXIT */
+ YYSYMBOL_K_FETCH = 64, /* K_FETCH */
+ YYSYMBOL_K_FIRST = 65, /* K_FIRST */
+ YYSYMBOL_K_FOR = 66, /* K_FOR */
+ YYSYMBOL_K_FOREACH = 67, /* K_FOREACH */
+ YYSYMBOL_K_FORWARD = 68, /* K_FORWARD */
+ YYSYMBOL_K_FROM = 69, /* K_FROM */
+ YYSYMBOL_K_GET = 70, /* K_GET */
+ YYSYMBOL_K_HINT = 71, /* K_HINT */
+ YYSYMBOL_K_IF = 72, /* K_IF */
+ YYSYMBOL_K_IMPORT = 73, /* K_IMPORT */
+ YYSYMBOL_K_IN = 74, /* K_IN */
+ YYSYMBOL_K_INFO = 75, /* K_INFO */
+ YYSYMBOL_K_INSERT = 76, /* K_INSERT */
+ YYSYMBOL_K_INTO = 77, /* K_INTO */
+ YYSYMBOL_K_IS = 78, /* K_IS */
+ YYSYMBOL_K_LAST = 79, /* K_LAST */
+ YYSYMBOL_K_LOG = 80, /* K_LOG */
+ YYSYMBOL_K_LOOP = 81, /* K_LOOP */
+ YYSYMBOL_K_MERGE = 82, /* K_MERGE */
+ YYSYMBOL_K_MESSAGE = 83, /* K_MESSAGE */
+ YYSYMBOL_K_MESSAGE_TEXT = 84, /* K_MESSAGE_TEXT */
+ YYSYMBOL_K_MOVE = 85, /* K_MOVE */
+ YYSYMBOL_K_NEXT = 86, /* K_NEXT */
+ YYSYMBOL_K_NO = 87, /* K_NO */
+ YYSYMBOL_K_NOT = 88, /* K_NOT */
+ YYSYMBOL_K_NOTICE = 89, /* K_NOTICE */
+ YYSYMBOL_K_NULL = 90, /* K_NULL */
+ YYSYMBOL_K_OPEN = 91, /* K_OPEN */
+ YYSYMBOL_K_OPTION = 92, /* K_OPTION */
+ YYSYMBOL_K_OR = 93, /* K_OR */
+ YYSYMBOL_K_PERFORM = 94, /* K_PERFORM */
+ YYSYMBOL_K_PG_CONTEXT = 95, /* K_PG_CONTEXT */
+ YYSYMBOL_K_PG_DATATYPE_NAME = 96, /* K_PG_DATATYPE_NAME */
+ YYSYMBOL_K_PG_EXCEPTION_CONTEXT = 97, /* K_PG_EXCEPTION_CONTEXT */
+ YYSYMBOL_K_PG_EXCEPTION_DETAIL = 98, /* K_PG_EXCEPTION_DETAIL */
+ YYSYMBOL_K_PG_EXCEPTION_HINT = 99, /* K_PG_EXCEPTION_HINT */
+ YYSYMBOL_K_PRINT_STRICT_PARAMS = 100, /* K_PRINT_STRICT_PARAMS */
+ YYSYMBOL_K_PRIOR = 101, /* K_PRIOR */
+ YYSYMBOL_K_QUERY = 102, /* K_QUERY */
+ YYSYMBOL_K_RAISE = 103, /* K_RAISE */
+ YYSYMBOL_K_RELATIVE = 104, /* K_RELATIVE */
+ YYSYMBOL_K_RETURN = 105, /* K_RETURN */
+ YYSYMBOL_K_RETURNED_SQLSTATE = 106, /* K_RETURNED_SQLSTATE */
+ YYSYMBOL_K_REVERSE = 107, /* K_REVERSE */
+ YYSYMBOL_K_ROLLBACK = 108, /* K_ROLLBACK */
+ YYSYMBOL_K_ROW_COUNT = 109, /* K_ROW_COUNT */
+ YYSYMBOL_K_ROWTYPE = 110, /* K_ROWTYPE */
+ YYSYMBOL_K_SCHEMA = 111, /* K_SCHEMA */
+ YYSYMBOL_K_SCHEMA_NAME = 112, /* K_SCHEMA_NAME */
+ YYSYMBOL_K_SCROLL = 113, /* K_SCROLL */
+ YYSYMBOL_K_SLICE = 114, /* K_SLICE */
+ YYSYMBOL_K_SQLSTATE = 115, /* K_SQLSTATE */
+ YYSYMBOL_K_STACKED = 116, /* K_STACKED */
+ YYSYMBOL_K_STRICT = 117, /* K_STRICT */
+ YYSYMBOL_K_TABLE = 118, /* K_TABLE */
+ YYSYMBOL_K_TABLE_NAME = 119, /* K_TABLE_NAME */
+ YYSYMBOL_K_THEN = 120, /* K_THEN */
+ YYSYMBOL_K_TO = 121, /* K_TO */
+ YYSYMBOL_K_TYPE = 122, /* K_TYPE */
+ YYSYMBOL_K_USE_COLUMN = 123, /* K_USE_COLUMN */
+ YYSYMBOL_K_USE_VARIABLE = 124, /* K_USE_VARIABLE */
+ YYSYMBOL_K_USING = 125, /* K_USING */
+ YYSYMBOL_K_VARIABLE_CONFLICT = 126, /* K_VARIABLE_CONFLICT */
+ YYSYMBOL_K_WARNING = 127, /* K_WARNING */
+ YYSYMBOL_K_WHEN = 128, /* K_WHEN */
+ YYSYMBOL_K_WHILE = 129, /* K_WHILE */
+ YYSYMBOL_130_ = 130, /* '#' */
+ YYSYMBOL_131_ = 131, /* ';' */
+ YYSYMBOL_132_ = 132, /* '(' */
+ YYSYMBOL_133_ = 133, /* ')' */
+ YYSYMBOL_134_ = 134, /* ',' */
+ YYSYMBOL_135_ = 135, /* '=' */
+ YYSYMBOL_YYACCEPT = 136, /* $accept */
+ YYSYMBOL_pl_function = 137, /* pl_function */
+ YYSYMBOL_comp_options = 138, /* comp_options */
+ YYSYMBOL_comp_option = 139, /* comp_option */
+ YYSYMBOL_option_value = 140, /* option_value */
+ YYSYMBOL_opt_semi = 141, /* opt_semi */
+ YYSYMBOL_pl_block = 142, /* pl_block */
+ YYSYMBOL_decl_sect = 143, /* decl_sect */
+ YYSYMBOL_decl_start = 144, /* decl_start */
+ YYSYMBOL_decl_stmts = 145, /* decl_stmts */
+ YYSYMBOL_decl_stmt = 146, /* decl_stmt */
+ YYSYMBOL_decl_statement = 147, /* decl_statement */
+ YYSYMBOL_148_1 = 148, /* $@1 */
+ YYSYMBOL_opt_scrollable = 149, /* opt_scrollable */
+ YYSYMBOL_decl_cursor_query = 150, /* decl_cursor_query */
+ YYSYMBOL_decl_cursor_args = 151, /* decl_cursor_args */
+ YYSYMBOL_decl_cursor_arglist = 152, /* decl_cursor_arglist */
+ YYSYMBOL_decl_cursor_arg = 153, /* decl_cursor_arg */
+ YYSYMBOL_decl_is_for = 154, /* decl_is_for */
+ YYSYMBOL_decl_aliasitem = 155, /* decl_aliasitem */
+ YYSYMBOL_decl_varname = 156, /* decl_varname */
+ YYSYMBOL_decl_const = 157, /* decl_const */
+ YYSYMBOL_decl_datatype = 158, /* decl_datatype */
+ YYSYMBOL_decl_collate = 159, /* decl_collate */
+ YYSYMBOL_decl_notnull = 160, /* decl_notnull */
+ YYSYMBOL_decl_defval = 161, /* decl_defval */
+ YYSYMBOL_decl_defkey = 162, /* decl_defkey */
+ YYSYMBOL_assign_operator = 163, /* assign_operator */
+ YYSYMBOL_proc_sect = 164, /* proc_sect */
+ YYSYMBOL_proc_stmt = 165, /* proc_stmt */
+ YYSYMBOL_stmt_perform = 166, /* stmt_perform */
+ YYSYMBOL_stmt_call = 167, /* stmt_call */
+ YYSYMBOL_stmt_assign = 168, /* stmt_assign */
+ YYSYMBOL_stmt_getdiag = 169, /* stmt_getdiag */
+ YYSYMBOL_getdiag_area_opt = 170, /* getdiag_area_opt */
+ YYSYMBOL_getdiag_list = 171, /* getdiag_list */
+ YYSYMBOL_getdiag_list_item = 172, /* getdiag_list_item */
+ YYSYMBOL_getdiag_item = 173, /* getdiag_item */
+ YYSYMBOL_getdiag_target = 174, /* getdiag_target */
+ YYSYMBOL_stmt_if = 175, /* stmt_if */
+ YYSYMBOL_stmt_elsifs = 176, /* stmt_elsifs */
+ YYSYMBOL_stmt_else = 177, /* stmt_else */
+ YYSYMBOL_stmt_case = 178, /* stmt_case */
+ YYSYMBOL_opt_expr_until_when = 179, /* opt_expr_until_when */
+ YYSYMBOL_case_when_list = 180, /* case_when_list */
+ YYSYMBOL_case_when = 181, /* case_when */
+ YYSYMBOL_opt_case_else = 182, /* opt_case_else */
+ YYSYMBOL_stmt_loop = 183, /* stmt_loop */
+ YYSYMBOL_stmt_while = 184, /* stmt_while */
+ YYSYMBOL_stmt_for = 185, /* stmt_for */
+ YYSYMBOL_for_control = 186, /* for_control */
+ YYSYMBOL_for_variable = 187, /* for_variable */
+ YYSYMBOL_stmt_foreach_a = 188, /* stmt_foreach_a */
+ YYSYMBOL_foreach_slice = 189, /* foreach_slice */
+ YYSYMBOL_stmt_exit = 190, /* stmt_exit */
+ YYSYMBOL_exit_type = 191, /* exit_type */
+ YYSYMBOL_stmt_return = 192, /* stmt_return */
+ YYSYMBOL_stmt_raise = 193, /* stmt_raise */
+ YYSYMBOL_stmt_assert = 194, /* stmt_assert */
+ YYSYMBOL_loop_body = 195, /* loop_body */
+ YYSYMBOL_stmt_execsql = 196, /* stmt_execsql */
+ YYSYMBOL_stmt_dynexecute = 197, /* stmt_dynexecute */
+ YYSYMBOL_stmt_open = 198, /* stmt_open */
+ YYSYMBOL_stmt_fetch = 199, /* stmt_fetch */
+ YYSYMBOL_stmt_move = 200, /* stmt_move */
+ YYSYMBOL_opt_fetch_direction = 201, /* opt_fetch_direction */
+ YYSYMBOL_stmt_close = 202, /* stmt_close */
+ YYSYMBOL_stmt_null = 203, /* stmt_null */
+ YYSYMBOL_stmt_commit = 204, /* stmt_commit */
+ YYSYMBOL_stmt_rollback = 205, /* stmt_rollback */
+ YYSYMBOL_opt_transaction_chain = 206, /* opt_transaction_chain */
+ YYSYMBOL_cursor_variable = 207, /* cursor_variable */
+ YYSYMBOL_exception_sect = 208, /* exception_sect */
+ YYSYMBOL_209_2 = 209, /* @2 */
+ YYSYMBOL_proc_exceptions = 210, /* proc_exceptions */
+ YYSYMBOL_proc_exception = 211, /* proc_exception */
+ YYSYMBOL_proc_conditions = 212, /* proc_conditions */
+ YYSYMBOL_proc_condition = 213, /* proc_condition */
+ YYSYMBOL_expr_until_semi = 214, /* expr_until_semi */
+ YYSYMBOL_expr_until_then = 215, /* expr_until_then */
+ YYSYMBOL_expr_until_loop = 216, /* expr_until_loop */
+ YYSYMBOL_opt_block_label = 217, /* opt_block_label */
+ YYSYMBOL_opt_loop_label = 218, /* opt_loop_label */
+ YYSYMBOL_opt_label = 219, /* opt_label */
+ YYSYMBOL_opt_exitcond = 220, /* opt_exitcond */
+ YYSYMBOL_any_identifier = 221, /* any_identifier */
+ YYSYMBOL_unreserved_keyword = 222 /* unreserved_keyword */
+};
+typedef enum yysymbol_kind_t yysymbol_kind_t;
+
+
+
+
+#ifdef short
+# undef short
+#endif
+
+/* On compilers that do not define __PTRDIFF_MAX__ etc., make sure
+ <limits.h> and (if available) <stdint.h> are included
+ so that the code can choose integer types of a good width. */
+
+#ifndef __PTRDIFF_MAX__
+# include <limits.h> /* INFRINGES ON USER NAME SPACE */
+# if defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stdint.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_STDINT_H
+# endif
+#endif
+
+/* Narrow types that promote to a signed type and that can represent a
+ signed or unsigned integer of at least N bits. In tables they can
+ save space and decrease cache pressure. Promoting to a signed type
+ helps avoid bugs in integer arithmetic. */
+
+#ifdef __INT_LEAST8_MAX__
+typedef __INT_LEAST8_TYPE__ yytype_int8;
+#elif defined YY_STDINT_H
+typedef int_least8_t yytype_int8;
+#else
+typedef signed char yytype_int8;
+#endif
+
+#ifdef __INT_LEAST16_MAX__
+typedef __INT_LEAST16_TYPE__ yytype_int16;
+#elif defined YY_STDINT_H
+typedef int_least16_t yytype_int16;
+#else
+typedef short yytype_int16;
+#endif
+
+/* Work around bug in HP-UX 11.23, which defines these macros
+ incorrectly for preprocessor constants. This workaround can likely
+ be removed in 2023, as HPE has promised support for HP-UX 11.23
+ (aka HP-UX 11i v2) only through the end of 2022; see Table 2 of
+ <https://h20195.www2.hpe.com/V2/getpdf.aspx/4AA4-7673ENW.pdf>. */
+#ifdef __hpux
+# undef UINT_LEAST8_MAX
+# undef UINT_LEAST16_MAX
+# define UINT_LEAST8_MAX 255
+# define UINT_LEAST16_MAX 65535
+#endif
+
+#if defined __UINT_LEAST8_MAX__ && __UINT_LEAST8_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST8_TYPE__ yytype_uint8;
+#elif (!defined __UINT_LEAST8_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST8_MAX <= INT_MAX)
+typedef uint_least8_t yytype_uint8;
+#elif !defined __UINT_LEAST8_MAX__ && UCHAR_MAX <= INT_MAX
+typedef unsigned char yytype_uint8;
+#else
+typedef short yytype_uint8;
+#endif
+
+#if defined __UINT_LEAST16_MAX__ && __UINT_LEAST16_MAX__ <= __INT_MAX__
+typedef __UINT_LEAST16_TYPE__ yytype_uint16;
+#elif (!defined __UINT_LEAST16_MAX__ && defined YY_STDINT_H \
+ && UINT_LEAST16_MAX <= INT_MAX)
+typedef uint_least16_t yytype_uint16;
+#elif !defined __UINT_LEAST16_MAX__ && USHRT_MAX <= INT_MAX
+typedef unsigned short yytype_uint16;
+#else
+typedef int yytype_uint16;
+#endif
+
+#ifndef YYPTRDIFF_T
+# if defined __PTRDIFF_TYPE__ && defined __PTRDIFF_MAX__
+# define YYPTRDIFF_T __PTRDIFF_TYPE__
+# define YYPTRDIFF_MAXIMUM __PTRDIFF_MAX__
+# elif defined PTRDIFF_MAX
+# ifndef ptrdiff_t
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# endif
+# define YYPTRDIFF_T ptrdiff_t
+# define YYPTRDIFF_MAXIMUM PTRDIFF_MAX
+# else
+# define YYPTRDIFF_T long
+# define YYPTRDIFF_MAXIMUM LONG_MAX
+# endif
+#endif
+
+#ifndef YYSIZE_T
+# ifdef __SIZE_TYPE__
+# define YYSIZE_T __SIZE_TYPE__
+# elif defined size_t
+# define YYSIZE_T size_t
+# elif defined __STDC_VERSION__ && 199901 <= __STDC_VERSION__
+# include <stddef.h> /* INFRINGES ON USER NAME SPACE */
+# define YYSIZE_T size_t
+# else
+# define YYSIZE_T unsigned
+# endif
+#endif
+
+#define YYSIZE_MAXIMUM \
+ YY_CAST (YYPTRDIFF_T, \
+ (YYPTRDIFF_MAXIMUM < YY_CAST (YYSIZE_T, -1) \
+ ? YYPTRDIFF_MAXIMUM \
+ : YY_CAST (YYSIZE_T, -1)))
+
+#define YYSIZEOF(X) YY_CAST (YYPTRDIFF_T, sizeof (X))
+
+
+/* Stored state numbers (used for stacks). */
+typedef yytype_int16 yy_state_t;
+
+/* State numbers in computations. */
+typedef int yy_state_fast_t;
+
+#ifndef YY_
+# if defined YYENABLE_NLS && YYENABLE_NLS
+# if ENABLE_NLS
+# include <libintl.h> /* INFRINGES ON USER NAME SPACE */
+# define YY_(Msgid) dgettext ("bison-runtime", Msgid)
+# endif
+# endif
+# ifndef YY_
+# define YY_(Msgid) Msgid
+# endif
+#endif
+
+
+#ifndef YY_ATTRIBUTE_PURE
+# if defined __GNUC__ && 2 < __GNUC__ + (96 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_PURE __attribute__ ((__pure__))
+# else
+# define YY_ATTRIBUTE_PURE
+# endif
+#endif
+
+#ifndef YY_ATTRIBUTE_UNUSED
+# if defined __GNUC__ && 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
+# define YY_ATTRIBUTE_UNUSED __attribute__ ((__unused__))
+# else
+# define YY_ATTRIBUTE_UNUSED
+# endif
+#endif
+
+/* Suppress unused-variable warnings by "using" E. */
+#if ! defined lint || defined __GNUC__
+# define YY_USE(E) ((void) (E))
+#else
+# define YY_USE(E) /* empty */
+#endif
+
+#if defined __GNUC__ && ! defined __ICC && 407 <= __GNUC__ * 100 + __GNUC_MINOR__
+/* Suppress an incorrect diagnostic about yylval being uninitialized. */
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuninitialized\"") \
+ _Pragma ("GCC diagnostic ignored \"-Wmaybe-uninitialized\"")
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END \
+ _Pragma ("GCC diagnostic pop")
+#else
+# define YY_INITIAL_VALUE(Value) Value
+#endif
+#ifndef YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+# define YY_IGNORE_MAYBE_UNINITIALIZED_END
+#endif
+#ifndef YY_INITIAL_VALUE
+# define YY_INITIAL_VALUE(Value) /* Nothing. */
+#endif
+
+#if defined __cplusplus && defined __GNUC__ && ! defined __ICC && 6 <= __GNUC__
+# define YY_IGNORE_USELESS_CAST_BEGIN \
+ _Pragma ("GCC diagnostic push") \
+ _Pragma ("GCC diagnostic ignored \"-Wuseless-cast\"")
+# define YY_IGNORE_USELESS_CAST_END \
+ _Pragma ("GCC diagnostic pop")
+#endif
+#ifndef YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_BEGIN
+# define YY_IGNORE_USELESS_CAST_END
+#endif
+
+
+#define YY_ASSERT(E) ((void) (0 && (E)))
+
+#if !defined yyoverflow
+
+/* The parser invokes alloca or malloc; define the necessary symbols. */
+
+# ifdef YYSTACK_USE_ALLOCA
+# if YYSTACK_USE_ALLOCA
+# ifdef __GNUC__
+# define YYSTACK_ALLOC __builtin_alloca
+# elif defined __BUILTIN_VA_ARG_INCR
+# include <alloca.h> /* INFRINGES ON USER NAME SPACE */
+# elif defined _AIX
+# define YYSTACK_ALLOC __alloca
+# elif defined _MSC_VER
+# include <malloc.h> /* INFRINGES ON USER NAME SPACE */
+# define alloca _alloca
+# else
+# define YYSTACK_ALLOC alloca
+# if ! defined _ALLOCA_H && ! defined EXIT_SUCCESS
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+ /* Use EXIT_SUCCESS as a witness for stdlib.h. */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# ifdef YYSTACK_ALLOC
+ /* Pacify GCC's 'empty if-body' warning. */
+# define YYSTACK_FREE(Ptr) do { /* empty */; } while (0)
+# ifndef YYSTACK_ALLOC_MAXIMUM
+ /* The OS might guarantee only one guard page at the bottom of the stack,
+ and a page size can be as small as 4096 bytes. So we cannot safely
+ invoke alloca (N) if N exceeds 4096. Use a slightly smaller number
+ to allow for a few compiler-allocated temporary stack slots. */
+# define YYSTACK_ALLOC_MAXIMUM 4032 /* reasonable circa 2006 */
+# endif
+# else
+# define YYSTACK_ALLOC YYMALLOC
+# define YYSTACK_FREE YYFREE
+# ifndef YYSTACK_ALLOC_MAXIMUM
+# define YYSTACK_ALLOC_MAXIMUM YYSIZE_MAXIMUM
+# endif
+# if (defined __cplusplus && ! defined EXIT_SUCCESS \
+ && ! ((defined YYMALLOC || defined malloc) \
+ && (defined YYFREE || defined free)))
+# include <stdlib.h> /* INFRINGES ON USER NAME SPACE */
+# ifndef EXIT_SUCCESS
+# define EXIT_SUCCESS 0
+# endif
+# endif
+# ifndef YYMALLOC
+# define YYMALLOC malloc
+# if ! defined malloc && ! defined EXIT_SUCCESS
+void *malloc (YYSIZE_T); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# ifndef YYFREE
+# define YYFREE free
+# if ! defined free && ! defined EXIT_SUCCESS
+void free (void *); /* INFRINGES ON USER NAME SPACE */
+# endif
+# endif
+# endif
+#endif /* !defined yyoverflow */
+
+#if (! defined yyoverflow \
+ && (! defined __cplusplus \
+ || (defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL \
+ && 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;
+ YYLTYPE yyls_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) \
+ + YYSIZEOF (YYLTYPE)) \
+ + 2 * YYSTACK_GAP_MAXIMUM)
+
+# define YYCOPY_NEEDED 1
+
+/* Relocate STACK from its old location to the new one. The
+ local variables YYSIZE and YYSTACKSIZE give the old and new number of
+ elements in the stack, and YYPTR gives the new location of the
+ stack. Advance YYPTR to a properly aligned location for the next
+ stack. */
+# define YYSTACK_RELOCATE(Stack_alloc, Stack) \
+ do \
+ { \
+ YYPTRDIFF_T yynewbytes; \
+ YYCOPY (&yyptr->Stack_alloc, Stack, yysize); \
+ Stack = &yyptr->Stack_alloc; \
+ yynewbytes = yystacksize * YYSIZEOF (*Stack) + YYSTACK_GAP_MAXIMUM; \
+ yyptr += yynewbytes / YYSIZEOF (*yyptr); \
+ } \
+ while (0)
+
+#endif
+
+#if defined YYCOPY_NEEDED && YYCOPY_NEEDED
+/* Copy COUNT objects from SRC to DST. The source and destination do
+ not overlap. */
+# ifndef YYCOPY
+# if defined __GNUC__ && 1 < __GNUC__
+# define YYCOPY(Dst, Src, Count) \
+ __builtin_memcpy (Dst, Src, YY_CAST (YYSIZE_T, (Count)) * sizeof (*(Src)))
+# else
+# define YYCOPY(Dst, Src, Count) \
+ do \
+ { \
+ YYPTRDIFF_T yyi; \
+ for (yyi = 0; yyi < (Count); yyi++) \
+ (Dst)[yyi] = (Src)[yyi]; \
+ } \
+ while (0)
+# endif
+# endif
+#endif /* !YYCOPY_NEEDED */
+
+/* YYFINAL -- State number of the termination state. */
+#define YYFINAL 3
+/* YYLAST -- Last index in YYTABLE. */
+#define YYLAST 1382
+
+/* YYNTOKENS -- Number of terminals. */
+#define YYNTOKENS 136
+/* YYNNTS -- Number of nonterminals. */
+#define YYNNTS 87
+/* YYNRULES -- Number of rules. */
+#define YYNRULES 252
+/* YYNSTATES -- Number of states. */
+#define YYNSTATES 333
+
+/* YYMAXUTOK -- Last valid token kind. */
+#define YYMAXUTOK 384
+
+
+/* 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_uint8 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, 130, 2, 2, 2, 2,
+ 132, 133, 2, 2, 134, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 131,
+ 2, 135, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 2, 2, 2, 2, 2, 2, 1, 2, 3, 4,
+ 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34,
+ 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
+ 55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
+ 65, 66, 67, 68, 69, 70, 71, 72, 73, 74,
+ 75, 76, 77, 78, 79, 80, 81, 82, 83, 84,
+ 85, 86, 87, 88, 89, 90, 91, 92, 93, 94,
+ 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
+ 115, 116, 117, 118, 119, 120, 121, 122, 123, 124,
+ 125, 126, 127, 128, 129
+};
+
+#if YYDEBUG
+ /* YYRLINE[YYN] -- Source line where rule number YYN was defined. */
+static const yytype_int16 yyrline[] =
+{
+ 0, 361, 361, 367, 368, 371, 375, 384, 388, 392,
+ 398, 402, 407, 408, 411, 434, 442, 449, 458, 470,
+ 471, 474, 475, 479, 492, 530, 536, 535, 589, 592,
+ 596, 603, 609, 612, 643, 647, 653, 661, 662, 664,
+ 679, 694, 722, 750, 781, 782, 787, 798, 799, 804,
+ 809, 816, 817, 821, 823, 829, 830, 838, 839, 843,
+ 844, 854, 856, 858, 860, 862, 864, 866, 868, 870,
+ 872, 874, 876, 878, 880, 882, 884, 886, 888, 890,
+ 892, 894, 896, 898, 900, 904, 940, 958, 979, 1018,
+ 1081, 1084, 1088, 1094, 1098, 1104, 1117, 1161, 1179, 1184,
+ 1191, 1209, 1212, 1226, 1229, 1235, 1242, 1256, 1260, 1266,
+ 1278, 1281, 1296, 1314, 1333, 1367, 1626, 1652, 1666, 1673,
+ 1712, 1715, 1721, 1774, 1778, 1784, 1810, 1955, 1979, 1997,
+ 2001, 2005, 2009, 2020, 2033, 2097, 2175, 2205, 2218, 2223,
+ 2237, 2244, 2258, 2273, 2274, 2275, 2279, 2301, 2306, 2314,
+ 2316, 2315, 2357, 2361, 2367, 2380, 2389, 2395, 2432, 2436,
+ 2440, 2444, 2448, 2456, 2460, 2468, 2471, 2478, 2480, 2487,
+ 2491, 2495, 2504, 2505, 2506, 2507, 2508, 2509, 2510, 2511,
+ 2512, 2513, 2514, 2515, 2516, 2517, 2518, 2519, 2520, 2521,
+ 2522, 2523, 2524, 2525, 2526, 2527, 2528, 2529, 2530, 2531,
+ 2532, 2533, 2534, 2535, 2536, 2537, 2538, 2539, 2540, 2541,
+ 2542, 2543, 2544, 2545, 2546, 2547, 2548, 2549, 2550, 2551,
+ 2552, 2553, 2554, 2555, 2556, 2557, 2558, 2559, 2560, 2561,
+ 2562, 2563, 2564, 2565, 2566, 2567, 2568, 2569, 2570, 2571,
+ 2572, 2573, 2574, 2575, 2576, 2577, 2578, 2579, 2580, 2581,
+ 2582, 2583, 2584
+};
+#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\"", "IDENT", "UIDENT",
+ "FCONST", "SCONST", "USCONST", "BCONST", "XCONST", "Op", "ICONST",
+ "PARAM", "TYPECAST", "DOT_DOT", "COLON_EQUALS", "EQUALS_GREATER",
+ "LESS_EQUALS", "GREATER_EQUALS", "NOT_EQUALS", "T_WORD", "T_CWORD",
+ "T_DATUM", "LESS_LESS", "GREATER_GREATER", "K_ABSOLUTE", "K_ALIAS",
+ "K_ALL", "K_AND", "K_ARRAY", "K_ASSERT", "K_BACKWARD", "K_BEGIN", "K_BY",
+ "K_CALL", "K_CASE", "K_CHAIN", "K_CLOSE", "K_COLLATE", "K_COLUMN",
+ "K_COLUMN_NAME", "K_COMMIT", "K_CONSTANT", "K_CONSTRAINT",
+ "K_CONSTRAINT_NAME", "K_CONTINUE", "K_CURRENT", "K_CURSOR", "K_DATATYPE",
+ "K_DEBUG", "K_DECLARE", "K_DEFAULT", "K_DETAIL", "K_DIAGNOSTICS", "K_DO",
+ "K_DUMP", "K_ELSE", "K_ELSIF", "K_END", "K_ERRCODE", "K_ERROR",
+ "K_EXCEPTION", "K_EXECUTE", "K_EXIT", "K_FETCH", "K_FIRST", "K_FOR",
+ "K_FOREACH", "K_FORWARD", "K_FROM", "K_GET", "K_HINT", "K_IF",
+ "K_IMPORT", "K_IN", "K_INFO", "K_INSERT", "K_INTO", "K_IS", "K_LAST",
+ "K_LOG", "K_LOOP", "K_MERGE", "K_MESSAGE", "K_MESSAGE_TEXT", "K_MOVE",
+ "K_NEXT", "K_NO", "K_NOT", "K_NOTICE", "K_NULL", "K_OPEN", "K_OPTION",
+ "K_OR", "K_PERFORM", "K_PG_CONTEXT", "K_PG_DATATYPE_NAME",
+ "K_PG_EXCEPTION_CONTEXT", "K_PG_EXCEPTION_DETAIL", "K_PG_EXCEPTION_HINT",
+ "K_PRINT_STRICT_PARAMS", "K_PRIOR", "K_QUERY", "K_RAISE", "K_RELATIVE",
+ "K_RETURN", "K_RETURNED_SQLSTATE", "K_REVERSE", "K_ROLLBACK",
+ "K_ROW_COUNT", "K_ROWTYPE", "K_SCHEMA", "K_SCHEMA_NAME", "K_SCROLL",
+ "K_SLICE", "K_SQLSTATE", "K_STACKED", "K_STRICT", "K_TABLE",
+ "K_TABLE_NAME", "K_THEN", "K_TO", "K_TYPE", "K_USE_COLUMN",
+ "K_USE_VARIABLE", "K_USING", "K_VARIABLE_CONFLICT", "K_WARNING",
+ "K_WHEN", "K_WHILE", "'#'", "';'", "'('", "')'", "','", "'='", "$accept",
+ "pl_function", "comp_options", "comp_option", "option_value", "opt_semi",
+ "pl_block", "decl_sect", "decl_start", "decl_stmts", "decl_stmt",
+ "decl_statement", "$@1", "opt_scrollable", "decl_cursor_query",
+ "decl_cursor_args", "decl_cursor_arglist", "decl_cursor_arg",
+ "decl_is_for", "decl_aliasitem", "decl_varname", "decl_const",
+ "decl_datatype", "decl_collate", "decl_notnull", "decl_defval",
+ "decl_defkey", "assign_operator", "proc_sect", "proc_stmt",
+ "stmt_perform", "stmt_call", "stmt_assign", "stmt_getdiag",
+ "getdiag_area_opt", "getdiag_list", "getdiag_list_item", "getdiag_item",
+ "getdiag_target", "stmt_if", "stmt_elsifs", "stmt_else", "stmt_case",
+ "opt_expr_until_when", "case_when_list", "case_when", "opt_case_else",
+ "stmt_loop", "stmt_while", "stmt_for", "for_control", "for_variable",
+ "stmt_foreach_a", "foreach_slice", "stmt_exit", "exit_type",
+ "stmt_return", "stmt_raise", "stmt_assert", "loop_body", "stmt_execsql",
+ "stmt_dynexecute", "stmt_open", "stmt_fetch", "stmt_move",
+ "opt_fetch_direction", "stmt_close", "stmt_null", "stmt_commit",
+ "stmt_rollback", "opt_transaction_chain", "cursor_variable",
+ "exception_sect", "@2", "proc_exceptions", "proc_exception",
+ "proc_conditions", "proc_condition", "expr_until_semi",
+ "expr_until_then", "expr_until_loop", "opt_block_label",
+ "opt_loop_label", "opt_label", "opt_exitcond", "any_identifier",
+ "unreserved_keyword", YY_NULLPTR
+};
+
+static const char *
+yysymbol_name (yysymbol_kind_t yysymbol)
+{
+ return yytname[yysymbol];
+}
+#endif
+
+#ifdef YYPRINT
+/* YYTOKNUM[NUM] -- (External) token number corresponding to the
+ (internal) symbol number NUM (which must be that of a token). */
+static const yytype_int16 yytoknum[] =
+{
+ 0, 256, 257, 258, 259, 260, 261, 262, 263, 264,
+ 265, 266, 267, 268, 269, 270, 271, 272, 273, 274,
+ 275, 276, 277, 278, 279, 280, 281, 282, 283, 284,
+ 285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
+ 295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
+ 305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
+ 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
+ 325, 326, 327, 328, 329, 330, 331, 332, 333, 334,
+ 335, 336, 337, 338, 339, 340, 341, 342, 343, 344,
+ 345, 346, 347, 348, 349, 350, 351, 352, 353, 354,
+ 355, 356, 357, 358, 359, 360, 361, 362, 363, 364,
+ 365, 366, 367, 368, 369, 370, 371, 372, 373, 374,
+ 375, 376, 377, 378, 379, 380, 381, 382, 383, 384,
+ 35, 59, 40, 41, 44, 61
+};
+#endif
+
+#define YYPACT_NINF (-248)
+
+#define yypact_value_is_default(Yyn) \
+ ((Yyn) == YYPACT_NINF)
+
+#define YYTABLE_NINF (-163)
+
+#define yytable_value_is_error(Yyn) \
+ 0
+
+ /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
+ STATE-NUM. */
+static const yytype_int16 yypact[] =
+{
+ -248, 28, -18, -248, 355, -56, -248, -98, 3, -2,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, 22, -248, -5, 676, -35, -248, -248,
+ -248, -248, 247, -248, -248, -248, -248, -248, -248, -248,
+ -248, 1043, -248, 355, -248, 247, -248, -248, -20, -248,
+ -248, -248, -248, 355, -248, -248, -248, 56, 36, -248,
+ -248, -248, -248, -248, -248, -31, -248, -248, -248, -248,
+ -248, -75, 56, -248, -248, -248, 36, -50, -248, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ 355, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, -248, -248, 11, -26, 60, -248, 30, -248, -15,
+ -248, 52, -248, 77, -17, -248, -248, -248, -23, -19,
+ -21, -14, 56, -248, -248, 63, -248, 56, -248, -248,
+ -10, -248, -74, -248, 355, 85, 85, -248, -248, -248,
+ 463, -248, -248, 75, 10, -248, -42, -248, -248, -248,
+ 84, -248, 355, -14, -248, 50, 104, 889, -3, -248,
+ -248, -248, -248, -248, -248, -248, -248, -248, 55, 17,
+ 1120, -248, -248, -248, -248, -1, -248, 0, 571, 45,
+ -248, -248, -248, 76, -248, -62, -248, -248, -248, -248,
+ -248, -248, -248, -68, -248, -12, 16, -248, -248, -248,
+ -248, 124, 62, 57, -248, -248, 781, -29, -248, -248,
+ -248, 47, -13, -11, 1197, 105, 355, -248, -248, 104,
+ -248, -248, -248, -248, -248, 81, -248, 112, 355, -43,
+ -248, -248, -248, -248, -248, -248, -248, -248, -248, -248,
+ -248, 12, -248, 126, -248, -248, 1274, -248, 70, -248,
+ 13, -248, 781, -248, -248, -248, 966, 14, -248, -248,
+ -248, -248, -248
+};
+
+ /* 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_uint8 yydefact[] =
+{
+ 3, 0, 161, 1, 0, 0, 4, 12, 0, 15,
+ 169, 171, 172, 173, 174, 175, 176, 177, 178, 179,
+ 180, 181, 182, 183, 184, 185, 186, 187, 188, 189,
+ 190, 191, 192, 193, 194, 195, 196, 197, 198, 199,
+ 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
+ 210, 211, 212, 213, 214, 215, 216, 217, 218, 219,
+ 220, 221, 222, 223, 224, 225, 226, 227, 228, 229,
+ 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
+ 240, 241, 242, 243, 244, 245, 246, 247, 248, 249,
+ 250, 251, 252, 0, 170, 0, 0, 0, 13, 2,
+ 59, 18, 16, 162, 5, 10, 6, 11, 7, 9,
+ 8, 163, 42, 0, 22, 17, 20, 21, 44, 43,
+ 132, 133, 88, 0, 127, 86, 106, 0, 145, 124,
+ 87, 150, 134, 123, 138, 90, 159, 129, 130, 131,
+ 138, 0, 0, 85, 126, 125, 145, 0, 60, 75,
+ 76, 62, 77, 63, 64, 65, 66, 67, 68, 69,
+ 165, 70, 71, 72, 73, 74, 78, 79, 80, 81,
+ 82, 83, 84, 0, 0, 0, 19, 0, 45, 0,
+ 30, 0, 46, 0, 0, 147, 148, 146, 0, 0,
+ 0, 0, 0, 91, 92, 0, 59, 0, 140, 135,
+ 0, 61, 0, 166, 165, 0, 0, 59, 160, 23,
+ 0, 29, 26, 47, 164, 159, 110, 108, 139, 143,
+ 0, 141, 0, 151, 153, 0, 0, 163, 0, 142,
+ 158, 167, 122, 14, 117, 118, 116, 59, 0, 120,
+ 163, 112, 59, 39, 41, 0, 40, 32, 0, 51,
+ 59, 59, 107, 0, 144, 0, 156, 157, 152, 136,
+ 98, 99, 97, 0, 94, 0, 103, 137, 168, 114,
+ 115, 0, 0, 0, 113, 25, 0, 0, 48, 50,
+ 49, 0, 0, 163, 163, 0, 0, 59, 89, 0,
+ 58, 57, 96, 59, 159, 0, 121, 0, 165, 0,
+ 34, 46, 38, 37, 31, 52, 56, 53, 24, 54,
+ 55, 0, 155, 163, 93, 95, 163, 59, 0, 160,
+ 0, 33, 0, 36, 27, 105, 163, 0, 59, 128,
+ 35, 100, 119
+};
+
+ /* YYPGOTO[NTERM-NUM]. */
+static const yytype_int16 yypgoto[] =
+{
+ -248, -248, -248, -248, -248, -248, 148, -248, -248, -248,
+ 37, -248, -248, -248, -248, -248, -248, -171, -248, -248,
+ -247, -248, -144, -248, -248, -248, -248, -123, -96, -248,
+ -248, -248, -248, -248, -248, -248, -127, -248, -248, -248,
+ -248, -248, -248, -248, -248, -52, -248, -248, -248, -248,
+ -248, -41, -248, -248, -248, -248, -248, -248, -248, -224,
+ -248, -248, -248, -248, -248, 26, -248, -248, -248, -248,
+ 23, -110, -248, -248, -248, -55, -248, -116, -248, -199,
+ -147, -248, -248, -196, -248, -4, -95
+};
+
+ /* YYDEFGOTO[NTERM-NUM]. */
+static const yytype_int16 yydefgoto[] =
+{
+ 0, 1, 2, 6, 106, 99, 147, 8, 102, 115,
+ 116, 117, 247, 181, 324, 277, 299, 300, 304, 245,
+ 118, 182, 213, 249, 282, 308, 309, 292, 240, 148,
+ 149, 150, 151, 152, 195, 263, 264, 315, 265, 153,
+ 266, 295, 154, 184, 216, 217, 253, 155, 156, 157,
+ 237, 238, 158, 272, 159, 160, 161, 162, 163, 241,
+ 164, 165, 166, 167, 168, 192, 169, 170, 171, 172,
+ 190, 188, 173, 191, 223, 224, 255, 256, 268, 196,
+ 242, 9, 174, 202, 232, 203, 94
+};
+
+ /* 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_int16 yytable[] =
+{
+ 93, 107, 290, 290, 111, 4, 177, 119, 233, 120,
+ 121, 122, 123, 269, 251, 193, 250, 219, 274, 124,
+ 119, -161, 178, 125, 126, 108, 127, -28, 3, 301,
+ 128, 286, 199, 98, 129, 100, 95, 302, 306, -161,
+ 205, 206, -162, 130, 96, -109, 103, -109, 101, 303,
+ 104, 132, 133, 134, 230, 207, 198, 231, 287, 135,
+ -162, 136, 137, 288, 189, 138, 289, 179, 220, 204,
+ 97, 139, 293, 294, 140, 301, 185, 186, 187, 141,
+ 142, 201, 225, 143, 209, 194, 215, 228, 109, 110,
+ 321, 322, 144, 180, 145, 317, 210, 146, 211, 212,
+ 227, 214, 320, 208, 332, 234, 235, 236, 218, 175,
+ 221, 215, 5, 248, 222, 246, 226, -109, 307, 183,
+ 254, 229, 291, 291, 260, 261, 262, 259, 267, 270,
+ 275, 271, 276, 281, 285, 296, 297, 305, 298, 318,
+ 311, 319, 327, 325, 329, 331, 120, 121, 122, 123,
+ 7, 330, 176, 280, 283, 284, 124, 323, -161, 310,
+ 125, 126, 314, 127, 252, 239, 197, 128, 258, 200,
+ 312, 129, 328, 0, 0, 0, -161, 0, 0, 0,
+ 130, 119, 0, 0, -154, 0, 0, 0, 132, 133,
+ 134, 313, 0, 0, 0, 0, 135, 316, 136, 137,
+ 0, 0, 138, 0, 0, 0, 0, 0, 139, 0,
+ 0, 140, 0, 0, 0, 0, 141, 142, 257, 0,
+ 143, 326, 0, 0, 0, 0, 0, 119, 0, 144,
+ 0, 145, 0, 0, 146, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, -154, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 112, 0, 0,
+ 113, 0, 12, 13, 0, 14, 15, 16, 17, 0,
+ 0, 18, 257, 19, 20, 21, 22, 23, 24, 25,
+ 26, 27, 28, 29, 30, 31, 32, 114, 33, 34,
+ 35, 36, 37, 0, 38, 0, 39, 40, 41, 0,
+ 42, 43, 44, 0, 0, 45, 0, 46, 47, 0,
+ 48, 0, 49, 50, 0, 51, 52, 53, 0, 54,
+ 55, 56, 57, 58, 59, 0, 60, 0, 61, 62,
+ 0, 63, 64, 65, 66, 67, 68, 69, 70, 71,
+ 72, 73, 74, 75, 76, 77, 78, 79, 80, 81,
+ 82, 83, 84, 85, 0, 86, 87, 0, 0, 88,
+ 89, 90, 0, 91, 92, 10, 0, 11, 0, 0,
+ 12, 13, 0, 14, 15, 16, 17, 0, 0, 18,
+ 0, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+ 28, 29, 30, 31, 32, 0, 33, 34, 35, 36,
+ 37, 0, 38, 0, 39, 40, 41, 0, 42, 43,
+ 44, 0, 0, 45, 0, 46, 47, 0, 48, 0,
+ 49, 50, 0, 51, 52, 53, 0, 54, 55, 56,
+ 57, 58, 59, 0, 60, 0, 61, 62, 0, 63,
+ 64, 65, 66, 67, 68, 69, 70, 71, 72, 73,
+ 74, 75, 76, 77, 78, 79, 80, 81, 82, 83,
+ 84, 85, 0, 86, 87, 0, 0, 88, 89, 90,
+ 0, 91, 92, 243, 244, 0, 0, 0, 12, 13,
+ 0, 14, 15, 16, 17, 0, 0, 18, 0, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
+ 30, 31, 32, 0, 33, 34, 35, 36, 37, 0,
+ 38, 0, 39, 40, 41, 0, 42, 43, 44, 0,
+ 0, 45, 0, 46, 47, 0, 48, 0, 49, 50,
+ 0, 51, 52, 53, 0, 54, 55, 56, 57, 58,
+ 59, 0, 60, 0, 61, 62, 0, 63, 64, 65,
+ 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83, 84, 85,
+ 0, 86, 87, 0, 0, 88, 89, 90, 0, 91,
+ 92, 278, 279, 0, 0, 0, 12, 13, 0, 14,
+ 15, 16, 17, 0, 0, 18, 0, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 0, 33, 34, 35, 36, 37, 0, 38, 0,
+ 39, 40, 41, 0, 42, 43, 44, 0, 0, 45,
+ 0, 46, 47, 0, 48, 0, 49, 50, 0, 51,
+ 52, 53, 0, 54, 55, 56, 57, 58, 59, 0,
+ 60, 0, 61, 62, 0, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, 81, 82, 83, 84, 85, 0, 86,
+ 87, 0, 0, 88, 89, 90, 105, 91, 92, 0,
+ 0, 12, 13, 0, 14, 15, 16, 17, 0, 0,
+ 18, 0, 19, 20, 21, 22, 23, 24, 25, 26,
+ 27, 28, 29, 30, 31, 32, 0, 33, 34, 35,
+ 36, 37, 0, 38, 0, 39, 40, 41, 0, 42,
+ 43, 44, 0, 0, 45, 0, 46, 47, 0, 48,
+ 0, 49, 50, 0, 51, 52, 53, 0, 54, 55,
+ 56, 57, 58, 59, 0, 60, 0, 61, 62, 0,
+ 63, 64, 65, 66, 67, 68, 69, 70, 71, 72,
+ 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
+ 83, 84, 85, 0, 86, 87, 0, 0, 88, 89,
+ 90, 112, 91, 92, 0, 0, 12, 13, 0, 14,
+ 15, 16, 17, 0, 0, 18, 0, 19, 20, 21,
+ 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+ 32, 0, 33, 34, 35, 36, 37, 0, 38, 0,
+ 39, 40, 41, 0, 42, 43, 44, 0, 0, 45,
+ 0, 46, 47, 0, 48, 0, 49, 50, 0, 51,
+ 52, 53, 0, 54, 55, 56, 57, 58, 59, 0,
+ 60, 0, 61, 62, 0, 63, 64, 65, 66, 67,
+ 68, 69, 70, 71, 72, 73, 74, 75, 76, 77,
+ 78, 79, 80, 81, 82, 83, 84, 85, 0, 86,
+ 87, 0, 0, 88, 89, 90, 0, 91, 92, 120,
+ 121, 122, 123, 0, 0, 0, 0, 0, 0, 124,
+ 0, -161, 0, 125, 126, 0, 127, 0, 0, 0,
+ 128, 0, 0, 0, 129, 0, 0, 0, 0, -161,
+ 0, 0, 0, 130, 0, -101, -101, -101, 0, 0,
+ 0, 132, 133, 134, 0, 0, 0, 0, 0, 135,
+ 0, 136, 137, 0, 0, 138, 0, 0, 0, 0,
+ 0, 139, 0, 0, 140, 0, 0, 0, 0, 141,
+ 142, 0, 0, 143, 0, 0, 120, 121, 122, 123,
+ 0, 0, 144, 0, 145, 0, 124, 146, -161, 0,
+ 125, 126, 0, 127, 0, 0, 0, 128, 0, 0,
+ 0, 129, 0, 0, 0, 0, -161, 0, 0, 0,
+ 130, 0, -102, -102, -102, 0, 0, 0, 132, 133,
+ 134, 0, 0, 0, 0, 0, 135, 0, 136, 137,
+ 0, 0, 138, 0, 0, 0, 0, 0, 139, 0,
+ 0, 140, 0, 0, 0, 0, 141, 142, 0, 0,
+ 143, 0, 0, 120, 121, 122, 123, 0, 0, 144,
+ 0, 145, 0, 124, 146, -161, 0, 125, 126, 0,
+ 127, 0, 0, 0, 128, 0, 0, 0, 129, 0,
+ 0, 0, 0, -161, 0, 0, 0, 130, 0, 0,
+ 0, -149, 0, 0, 131, 132, 133, 134, 0, 0,
+ 0, 0, 0, 135, 0, 136, 137, 0, 0, 138,
+ 0, 0, 0, 0, 0, 139, 0, 0, 140, 0,
+ 0, 0, 0, 141, 142, 0, 0, 143, 0, 0,
+ 120, 121, 122, 123, 0, 0, 144, 0, 145, 0,
+ 124, 146, -161, 0, 125, 126, 0, 127, 0, 0,
+ 0, 128, 0, 0, 0, 129, 0, 0, 0, 0,
+ -161, 0, 0, 0, 130, 0, 0, 0, 273, 0,
+ 0, 0, 132, 133, 134, 0, 0, 0, 0, 0,
+ 135, 0, 136, 137, 0, 0, 138, 0, 0, 0,
+ 0, 0, 139, 0, 0, 140, 0, 0, 0, 0,
+ 141, 142, 0, 0, 143, 0, 0, 120, 121, 122,
+ 123, 0, 0, 144, 0, 145, 0, 124, 146, -161,
+ 0, 125, 126, 0, 127, 0, 0, 0, 128, 0,
+ 0, 0, 129, 0, 0, 0, 0, -161, 0, 0,
+ 0, 130, 0, 0, 0, -111, 0, 0, 0, 132,
+ 133, 134, 0, 0, 0, 0, 0, 135, 0, 136,
+ 137, 0, 0, 138, 0, 0, 0, 0, 0, 139,
+ 0, 0, 140, 0, 0, 0, 0, 141, 142, 0,
+ 0, 143, 0, 0, 120, 121, 122, 123, 0, 0,
+ 144, 0, 145, 0, 124, 146, -161, 0, 125, 126,
+ 0, 127, 0, 0, 0, 128, 0, 0, 0, 129,
+ 0, 0, 0, 0, -161, 0, 0, 0, 130, 0,
+ 0, 0, -104, 0, 0, 0, 132, 133, 134, 0,
+ 0, 0, 0, 0, 135, 0, 136, 137, 0, 0,
+ 138, 0, 0, 0, 0, 0, 139, 0, 0, 140,
+ 0, 0, 0, 0, 141, 142, 0, 0, 143, 0,
+ 0, 0, 0, 0, 0, 0, 0, 144, 0, 145,
+ 0, 0, 146
+};
+
+static const yytype_int16 yycheck[] =
+{
+ 4, 96, 15, 15, 100, 23, 26, 102, 204, 20,
+ 21, 22, 23, 237, 56, 46, 215, 36, 242, 30,
+ 115, 32, 42, 34, 35, 60, 37, 47, 0, 276,
+ 41, 93, 142, 131, 45, 32, 92, 66, 51, 50,
+ 66, 67, 32, 54, 100, 56, 24, 58, 50, 78,
+ 55, 62, 63, 64, 128, 81, 131, 131, 120, 70,
+ 50, 72, 73, 131, 28, 76, 134, 87, 87, 58,
+ 126, 82, 56, 57, 85, 322, 20, 21, 22, 90,
+ 91, 131, 192, 94, 24, 116, 128, 197, 123, 124,
+ 133, 134, 103, 113, 105, 294, 66, 108, 113, 47,
+ 196, 24, 298, 129, 328, 20, 21, 22, 131, 113,
+ 131, 128, 130, 38, 128, 210, 53, 128, 131, 123,
+ 36, 131, 135, 135, 20, 21, 22, 77, 131, 74,
+ 131, 114, 132, 88, 58, 11, 74, 90, 81, 58,
+ 35, 29, 72, 131, 131, 131, 20, 21, 22, 23,
+ 2, 322, 115, 248, 250, 251, 30, 301, 32, 282,
+ 34, 35, 289, 37, 216, 206, 140, 41, 223, 146,
+ 286, 45, 319, -1, -1, -1, 50, -1, -1, -1,
+ 54, 276, -1, -1, 58, -1, -1, -1, 62, 63,
+ 64, 287, -1, -1, -1, -1, 70, 293, 72, 73,
+ -1, -1, 76, -1, -1, -1, -1, -1, 82, -1,
+ -1, 85, -1, -1, -1, -1, 90, 91, 222, -1,
+ 94, 317, -1, -1, -1, -1, -1, 322, -1, 103,
+ -1, 105, -1, -1, 108, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 128, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 20, -1, -1,
+ 23, -1, 25, 26, -1, 28, 29, 30, 31, -1,
+ -1, 34, 286, 36, 37, 38, 39, 40, 41, 42,
+ 43, 44, 45, 46, 47, 48, 49, 50, 51, 52,
+ 53, 54, 55, -1, 57, -1, 59, 60, 61, -1,
+ 63, 64, 65, -1, -1, 68, -1, 70, 71, -1,
+ 73, -1, 75, 76, -1, 78, 79, 80, -1, 82,
+ 83, 84, 85, 86, 87, -1, 89, -1, 91, 92,
+ -1, 94, 95, 96, 97, 98, 99, 100, 101, 102,
+ 103, 104, 105, 106, 107, 108, 109, 110, 111, 112,
+ 113, 114, 115, 116, -1, 118, 119, -1, -1, 122,
+ 123, 124, -1, 126, 127, 20, -1, 22, -1, -1,
+ 25, 26, -1, 28, 29, 30, 31, -1, -1, 34,
+ -1, 36, 37, 38, 39, 40, 41, 42, 43, 44,
+ 45, 46, 47, 48, 49, -1, 51, 52, 53, 54,
+ 55, -1, 57, -1, 59, 60, 61, -1, 63, 64,
+ 65, -1, -1, 68, -1, 70, 71, -1, 73, -1,
+ 75, 76, -1, 78, 79, 80, -1, 82, 83, 84,
+ 85, 86, 87, -1, 89, -1, 91, 92, -1, 94,
+ 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
+ 105, 106, 107, 108, 109, 110, 111, 112, 113, 114,
+ 115, 116, -1, 118, 119, -1, -1, 122, 123, 124,
+ -1, 126, 127, 20, 21, -1, -1, -1, 25, 26,
+ -1, 28, 29, 30, 31, -1, -1, 34, -1, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
+ 47, 48, 49, -1, 51, 52, 53, 54, 55, -1,
+ 57, -1, 59, 60, 61, -1, 63, 64, 65, -1,
+ -1, 68, -1, 70, 71, -1, 73, -1, 75, 76,
+ -1, 78, 79, 80, -1, 82, 83, 84, 85, 86,
+ 87, -1, 89, -1, 91, 92, -1, 94, 95, 96,
+ 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
+ 107, 108, 109, 110, 111, 112, 113, 114, 115, 116,
+ -1, 118, 119, -1, -1, 122, 123, 124, -1, 126,
+ 127, 20, 21, -1, -1, -1, 25, 26, -1, 28,
+ 29, 30, 31, -1, -1, 34, -1, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, -1, 51, 52, 53, 54, 55, -1, 57, -1,
+ 59, 60, 61, -1, 63, 64, 65, -1, -1, 68,
+ -1, 70, 71, -1, 73, -1, 75, 76, -1, 78,
+ 79, 80, -1, 82, 83, 84, 85, 86, 87, -1,
+ 89, -1, 91, 92, -1, 94, 95, 96, 97, 98,
+ 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 112, 113, 114, 115, 116, -1, 118,
+ 119, -1, -1, 122, 123, 124, 20, 126, 127, -1,
+ -1, 25, 26, -1, 28, 29, 30, 31, -1, -1,
+ 34, -1, 36, 37, 38, 39, 40, 41, 42, 43,
+ 44, 45, 46, 47, 48, 49, -1, 51, 52, 53,
+ 54, 55, -1, 57, -1, 59, 60, 61, -1, 63,
+ 64, 65, -1, -1, 68, -1, 70, 71, -1, 73,
+ -1, 75, 76, -1, 78, 79, 80, -1, 82, 83,
+ 84, 85, 86, 87, -1, 89, -1, 91, 92, -1,
+ 94, 95, 96, 97, 98, 99, 100, 101, 102, 103,
+ 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, -1, 118, 119, -1, -1, 122, 123,
+ 124, 20, 126, 127, -1, -1, 25, 26, -1, 28,
+ 29, 30, 31, -1, -1, 34, -1, 36, 37, 38,
+ 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, -1, 51, 52, 53, 54, 55, -1, 57, -1,
+ 59, 60, 61, -1, 63, 64, 65, -1, -1, 68,
+ -1, 70, 71, -1, 73, -1, 75, 76, -1, 78,
+ 79, 80, -1, 82, 83, 84, 85, 86, 87, -1,
+ 89, -1, 91, 92, -1, 94, 95, 96, 97, 98,
+ 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
+ 109, 110, 111, 112, 113, 114, 115, 116, -1, 118,
+ 119, -1, -1, 122, 123, 124, -1, 126, 127, 20,
+ 21, 22, 23, -1, -1, -1, -1, -1, -1, 30,
+ -1, 32, -1, 34, 35, -1, 37, -1, -1, -1,
+ 41, -1, -1, -1, 45, -1, -1, -1, -1, 50,
+ -1, -1, -1, 54, -1, 56, 57, 58, -1, -1,
+ -1, 62, 63, 64, -1, -1, -1, -1, -1, 70,
+ -1, 72, 73, -1, -1, 76, -1, -1, -1, -1,
+ -1, 82, -1, -1, 85, -1, -1, -1, -1, 90,
+ 91, -1, -1, 94, -1, -1, 20, 21, 22, 23,
+ -1, -1, 103, -1, 105, -1, 30, 108, 32, -1,
+ 34, 35, -1, 37, -1, -1, -1, 41, -1, -1,
+ -1, 45, -1, -1, -1, -1, 50, -1, -1, -1,
+ 54, -1, 56, 57, 58, -1, -1, -1, 62, 63,
+ 64, -1, -1, -1, -1, -1, 70, -1, 72, 73,
+ -1, -1, 76, -1, -1, -1, -1, -1, 82, -1,
+ -1, 85, -1, -1, -1, -1, 90, 91, -1, -1,
+ 94, -1, -1, 20, 21, 22, 23, -1, -1, 103,
+ -1, 105, -1, 30, 108, 32, -1, 34, 35, -1,
+ 37, -1, -1, -1, 41, -1, -1, -1, 45, -1,
+ -1, -1, -1, 50, -1, -1, -1, 54, -1, -1,
+ -1, 58, -1, -1, 61, 62, 63, 64, -1, -1,
+ -1, -1, -1, 70, -1, 72, 73, -1, -1, 76,
+ -1, -1, -1, -1, -1, 82, -1, -1, 85, -1,
+ -1, -1, -1, 90, 91, -1, -1, 94, -1, -1,
+ 20, 21, 22, 23, -1, -1, 103, -1, 105, -1,
+ 30, 108, 32, -1, 34, 35, -1, 37, -1, -1,
+ -1, 41, -1, -1, -1, 45, -1, -1, -1, -1,
+ 50, -1, -1, -1, 54, -1, -1, -1, 58, -1,
+ -1, -1, 62, 63, 64, -1, -1, -1, -1, -1,
+ 70, -1, 72, 73, -1, -1, 76, -1, -1, -1,
+ -1, -1, 82, -1, -1, 85, -1, -1, -1, -1,
+ 90, 91, -1, -1, 94, -1, -1, 20, 21, 22,
+ 23, -1, -1, 103, -1, 105, -1, 30, 108, 32,
+ -1, 34, 35, -1, 37, -1, -1, -1, 41, -1,
+ -1, -1, 45, -1, -1, -1, -1, 50, -1, -1,
+ -1, 54, -1, -1, -1, 58, -1, -1, -1, 62,
+ 63, 64, -1, -1, -1, -1, -1, 70, -1, 72,
+ 73, -1, -1, 76, -1, -1, -1, -1, -1, 82,
+ -1, -1, 85, -1, -1, -1, -1, 90, 91, -1,
+ -1, 94, -1, -1, 20, 21, 22, 23, -1, -1,
+ 103, -1, 105, -1, 30, 108, 32, -1, 34, 35,
+ -1, 37, -1, -1, -1, 41, -1, -1, -1, 45,
+ -1, -1, -1, -1, 50, -1, -1, -1, 54, -1,
+ -1, -1, 58, -1, -1, -1, 62, 63, 64, -1,
+ -1, -1, -1, -1, 70, -1, 72, 73, -1, -1,
+ 76, -1, -1, -1, -1, -1, 82, -1, -1, 85,
+ -1, -1, -1, -1, 90, 91, -1, -1, 94, -1,
+ -1, -1, -1, -1, -1, -1, -1, 103, -1, 105,
+ -1, -1, 108
+};
+
+ /* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
+ symbol of state STATE-NUM. */
+static const yytype_uint8 yystos[] =
+{
+ 0, 137, 138, 0, 23, 130, 139, 142, 143, 217,
+ 20, 22, 25, 26, 28, 29, 30, 31, 34, 36,
+ 37, 38, 39, 40, 41, 42, 43, 44, 45, 46,
+ 47, 48, 49, 51, 52, 53, 54, 55, 57, 59,
+ 60, 61, 63, 64, 65, 68, 70, 71, 73, 75,
+ 76, 78, 79, 80, 82, 83, 84, 85, 86, 87,
+ 89, 91, 92, 94, 95, 96, 97, 98, 99, 100,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ 111, 112, 113, 114, 115, 116, 118, 119, 122, 123,
+ 124, 126, 127, 221, 222, 92, 100, 126, 131, 141,
+ 32, 50, 144, 24, 55, 20, 140, 222, 60, 123,
+ 124, 164, 20, 23, 50, 145, 146, 147, 156, 222,
+ 20, 21, 22, 23, 30, 34, 35, 37, 41, 45,
+ 54, 61, 62, 63, 64, 70, 72, 73, 76, 82,
+ 85, 90, 91, 94, 103, 105, 108, 142, 165, 166,
+ 167, 168, 169, 175, 178, 183, 184, 185, 188, 190,
+ 191, 192, 193, 194, 196, 197, 198, 199, 200, 202,
+ 203, 204, 205, 208, 218, 221, 146, 26, 42, 87,
+ 113, 149, 157, 221, 179, 20, 21, 22, 207, 28,
+ 206, 209, 201, 46, 116, 170, 215, 201, 131, 207,
+ 206, 131, 219, 221, 58, 66, 67, 81, 129, 24,
+ 66, 113, 47, 158, 24, 128, 180, 181, 131, 36,
+ 87, 131, 128, 210, 211, 207, 53, 164, 207, 131,
+ 128, 131, 220, 219, 20, 21, 22, 186, 187, 187,
+ 164, 195, 216, 20, 21, 155, 222, 148, 38, 159,
+ 215, 56, 181, 182, 36, 212, 213, 221, 211, 77,
+ 20, 21, 22, 171, 172, 174, 176, 131, 214, 195,
+ 74, 114, 189, 58, 195, 131, 132, 151, 20, 21,
+ 222, 88, 160, 164, 164, 58, 93, 120, 131, 134,
+ 15, 135, 163, 56, 57, 177, 11, 74, 81, 152,
+ 153, 156, 66, 78, 154, 90, 51, 131, 161, 162,
+ 163, 35, 213, 164, 172, 173, 164, 215, 58, 29,
+ 219, 133, 134, 158, 150, 131, 164, 72, 216, 131,
+ 153, 131, 195
+};
+
+ /* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
+static const yytype_uint8 yyr1[] =
+{
+ 0, 136, 137, 138, 138, 139, 139, 139, 139, 139,
+ 140, 140, 141, 141, 142, 143, 143, 143, 144, 145,
+ 145, 146, 146, 146, 147, 147, 148, 147, 149, 149,
+ 149, 150, 151, 151, 152, 152, 153, 154, 154, 155,
+ 155, 155, 156, 156, 157, 157, 158, 159, 159, 159,
+ 159, 160, 160, 161, 161, 162, 162, 163, 163, 164,
+ 164, 165, 165, 165, 165, 165, 165, 165, 165, 165,
+ 165, 165, 165, 165, 165, 165, 165, 165, 165, 165,
+ 165, 165, 165, 165, 165, 166, 167, 167, 168, 169,
+ 170, 170, 170, 171, 171, 172, 173, 174, 174, 174,
+ 175, 176, 176, 177, 177, 178, 179, 180, 180, 181,
+ 182, 182, 183, 184, 185, 186, 187, 187, 187, 188,
+ 189, 189, 190, 191, 191, 192, 193, 194, 195, 196,
+ 196, 196, 196, 196, 197, 198, 199, 200, 201, 202,
+ 203, 204, 205, 206, 206, 206, 207, 207, 207, 208,
+ 209, 208, 210, 210, 211, 212, 212, 213, 214, 215,
+ 216, 217, 217, 218, 218, 219, 219, 220, 220, 221,
+ 221, 221, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222, 222, 222, 222, 222, 222, 222, 222,
+ 222, 222, 222
+};
+
+ /* YYR2[YYN] -- Number of symbols on the right hand side of rule YYN. */
+static const yytype_int8 yyr2[] =
+{
+ 0, 2, 3, 0, 2, 3, 3, 3, 3, 3,
+ 1, 1, 0, 1, 6, 1, 2, 3, 1, 2,
+ 1, 1, 1, 3, 6, 5, 0, 7, 0, 2,
+ 1, 0, 0, 3, 1, 3, 2, 1, 1, 1,
+ 1, 1, 1, 1, 0, 1, 0, 0, 2, 2,
+ 2, 0, 2, 1, 1, 1, 1, 1, 1, 0,
+ 2, 2, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 5,
+ 0, 1, 1, 3, 1, 3, 0, 1, 1, 1,
+ 8, 0, 4, 0, 2, 7, 0, 2, 1, 3,
+ 0, 2, 3, 4, 4, 2, 1, 1, 1, 8,
+ 0, 2, 3, 1, 1, 1, 1, 1, 5, 1,
+ 1, 1, 1, 1, 1, 2, 4, 4, 0, 3,
+ 2, 3, 3, 2, 3, 0, 1, 1, 1, 0,
+ 0, 3, 2, 1, 4, 3, 1, 1, 0, 0,
+ 0, 0, 3, 0, 3, 0, 1, 1, 2, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+ 1, 1, 1
+};
+
+
+enum { YYENOMEM = -2 };
+
+#define yyerrok (yyerrstatus = 0)
+#define yyclearin (yychar = YYEMPTY)
+
+#define YYACCEPT goto yyacceptlab
+#define YYABORT goto yyabortlab
+#define YYERROR goto yyerrorlab
+
+
+#define YYRECOVERING() (!!yyerrstatus)
+
+#define YYBACKUP(Token, Value) \
+ do \
+ if (yychar == YYEMPTY) \
+ { \
+ yychar = (Token); \
+ yylval = (Value); \
+ YYPOPSTACK (yylen); \
+ yystate = *yyssp; \
+ goto yybackup; \
+ } \
+ else \
+ { \
+ yyerror (YY_("syntax error: cannot back up")); \
+ YYERROR; \
+ } \
+ while (0)
+
+/* Backward compatibility with an undocumented macro.
+ Use YYerror or YYUNDEF. */
+#define YYERRCODE YYUNDEF
+
+/* YYLLOC_DEFAULT -- Set CURRENT to span from RHS[1] to RHS[N].
+ If N is 0, then set CURRENT to the empty location which ends
+ the previous symbol: RHS[0] (always defined). */
+
+#ifndef YYLLOC_DEFAULT
+# define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do \
+ if (N) \
+ { \
+ (Current).first_line = YYRHSLOC (Rhs, 1).first_line; \
+ (Current).first_column = YYRHSLOC (Rhs, 1).first_column; \
+ (Current).last_line = YYRHSLOC (Rhs, N).last_line; \
+ (Current).last_column = YYRHSLOC (Rhs, N).last_column; \
+ } \
+ else \
+ { \
+ (Current).first_line = (Current).last_line = \
+ YYRHSLOC (Rhs, 0).last_line; \
+ (Current).first_column = (Current).last_column = \
+ YYRHSLOC (Rhs, 0).last_column; \
+ } \
+ while (0)
+#endif
+
+#define YYRHSLOC(Rhs, K) ((Rhs)[K])
+
+
+/* Enable debugging if requested. */
+#if YYDEBUG
+
+# ifndef YYFPRINTF
+# include <stdio.h> /* INFRINGES ON USER NAME SPACE */
+# define YYFPRINTF fprintf
+# endif
+
+# define YYDPRINTF(Args) \
+do { \
+ if (yydebug) \
+ YYFPRINTF Args; \
+} while (0)
+
+
+/* YY_LOCATION_PRINT -- Print the location on the stream.
+ This macro was not mandated originally: define only if we know
+ we won't break user code: when these are the locations we know. */
+
+# ifndef YY_LOCATION_PRINT
+# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL
+
+/* Print *YYLOCP on YYO. Private, do not rely on its existence. */
+
+YY_ATTRIBUTE_UNUSED
+static int
+yy_location_print_ (FILE *yyo, YYLTYPE const * const yylocp)
+{
+ int res = 0;
+ int end_col = 0 != yylocp->last_column ? yylocp->last_column - 1 : 0;
+ if (0 <= yylocp->first_line)
+ {
+ res += YYFPRINTF (yyo, "%d", yylocp->first_line);
+ if (0 <= yylocp->first_column)
+ res += YYFPRINTF (yyo, ".%d", yylocp->first_column);
+ }
+ if (0 <= yylocp->last_line)
+ {
+ if (yylocp->first_line < yylocp->last_line)
+ {
+ res += YYFPRINTF (yyo, "-%d", yylocp->last_line);
+ if (0 <= end_col)
+ res += YYFPRINTF (yyo, ".%d", end_col);
+ }
+ else if (0 <= end_col && yylocp->first_column < end_col)
+ res += YYFPRINTF (yyo, "-%d", end_col);
+ }
+ return res;
+ }
+
+# define YY_LOCATION_PRINT(File, Loc) \
+ yy_location_print_ (File, &(Loc))
+
+# else
+# define YY_LOCATION_PRINT(File, Loc) ((void) 0)
+# endif
+# endif /* !defined YY_LOCATION_PRINT */
+
+
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location) \
+do { \
+ if (yydebug) \
+ { \
+ YYFPRINTF (stderr, "%s ", Title); \
+ yy_symbol_print (stderr, \
+ Kind, Value, Location); \
+ 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, YYLTYPE const * const yylocationp)
+{
+ FILE *yyoutput = yyo;
+ YY_USE (yyoutput);
+ YY_USE (yylocationp);
+ 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, YYLTYPE const * const yylocationp)
+{
+ YYFPRINTF (yyo, "%s %s (",
+ yykind < YYNTOKENS ? "token" : "nterm", yysymbol_name (yykind));
+
+ YY_LOCATION_PRINT (yyo, *yylocationp);
+ YYFPRINTF (yyo, ": ");
+ yy_symbol_value_print (yyo, yykind, yyvaluep, yylocationp);
+ 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, YYLTYPE *yylsp,
+ int yyrule)
+{
+ int yylno = yyrline[yyrule];
+ int yynrhs = yyr2[yyrule];
+ int yyi;
+ YYFPRINTF (stderr, "Reducing stack by rule %d (line %d):\n",
+ yyrule - 1, yylno);
+ /* The symbols being reduced. */
+ for (yyi = 0; yyi < yynrhs; yyi++)
+ {
+ YYFPRINTF (stderr, " $%d = ", yyi + 1);
+ yy_symbol_print (stderr,
+ YY_ACCESSING_SYMBOL (+yyssp[yyi + 1 - yynrhs]),
+ &yyvsp[(yyi + 1) - (yynrhs)],
+ &(yylsp[(yyi + 1) - (yynrhs)]));
+ YYFPRINTF (stderr, "\n");
+ }
+}
+
+# define YY_REDUCE_PRINT(Rule) \
+do { \
+ if (yydebug) \
+ yy_reduce_print (yyssp, yyvsp, yylsp, Rule); \
+} while (0)
+
+/* Nonzero means print parse trace. It is left uninitialized so that
+ multiple parsers can coexist. */
+int yydebug;
+#else /* !YYDEBUG */
+# define YYDPRINTF(Args) ((void) 0)
+# define YY_SYMBOL_PRINT(Title, Kind, Value, Location)
+# define YY_STACK_PRINT(Bottom, Top)
+# define YY_REDUCE_PRINT(Rule)
+#endif /* !YYDEBUG */
+
+
+/* YYINITDEPTH -- initial size of the parser's stacks. */
+#ifndef YYINITDEPTH
+# define YYINITDEPTH 200
+#endif
+
+/* YYMAXDEPTH -- maximum size the stacks can grow to (effective only
+ if the built-in stack extension method is used).
+
+ Do not make this value too large; the results are undefined if
+ YYSTACK_ALLOC_MAXIMUM < YYSTACK_BYTES (YYMAXDEPTH)
+ evaluated with infinite-precision integer arithmetic. */
+
+#ifndef YYMAXDEPTH
+# define YYMAXDEPTH 10000
+#endif
+
+
+
+
+
+
+/*-----------------------------------------------.
+| Release the memory associated to this symbol. |
+`-----------------------------------------------*/
+
+static void
+yydestruct (const char *yymsg,
+ yysymbol_kind_t yykind, YYSTYPE *yyvaluep, YYLTYPE *yylocationp)
+{
+ YY_USE (yyvaluep);
+ YY_USE (yylocationp);
+ 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;
+/* Location data for the lookahead symbol. */
+YYLTYPE yylloc
+# if defined YYLTYPE_IS_TRIVIAL && YYLTYPE_IS_TRIVIAL
+ = { 1, 1, 1, 1 }
+# endif
+;
+/* Number of syntax errors so far. */
+int yynerrs;
+
+
+
+
+/*----------.
+| yyparse. |
+`----------*/
+
+int
+yyparse (void)
+{
+ yy_state_fast_t yystate = 0;
+ /* Number of tokens to shift before error messages enabled. */
+ int yyerrstatus = 0;
+
+ /* Refer to the stacks through separate pointers, to allow yyoverflow
+ to reallocate them elsewhere. */
+
+ /* Their size. */
+ YYPTRDIFF_T yystacksize = YYINITDEPTH;
+
+ /* The state stack: array, bottom, top. */
+ yy_state_t yyssa[YYINITDEPTH];
+ yy_state_t *yyss = yyssa;
+ yy_state_t *yyssp = yyss;
+
+ /* The semantic value stack: array, bottom, top. */
+ YYSTYPE yyvsa[YYINITDEPTH];
+ YYSTYPE *yyvs = yyvsa;
+ YYSTYPE *yyvsp = yyvs;
+
+ /* The location stack: array, bottom, top. */
+ YYLTYPE yylsa[YYINITDEPTH];
+ YYLTYPE *yyls = yylsa;
+ YYLTYPE *yylsp = yyls;
+
+ 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;
+ YYLTYPE yyloc;
+
+ /* The locations where the error started and ended. */
+ YYLTYPE yyerror_range[3];
+
+
+
+#define YYPOPSTACK(N) (yyvsp -= (N), yyssp -= (N), yylsp -= (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. */
+ yylsp[0] = yylloc;
+ 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;
+ YYLTYPE *yyls1 = yyls;
+
+ /* 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),
+ &yyls1, yysize * YYSIZEOF (*yylsp),
+ &yystacksize);
+ yyss = yyss1;
+ yyvs = yyvs1;
+ yyls = yyls1;
+ }
+# 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);
+ YYSTACK_RELOCATE (yyls_alloc, yyls);
+# undef YYSTACK_RELOCATE
+ if (yyss1 != yyssa)
+ YYSTACK_FREE (yyss1);
+ }
+# endif
+
+ yyssp = yyss + yysize - 1;
+ yyvsp = yyvs + yysize - 1;
+ yylsp = yyls + 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;
+ yyerror_range[1] = yylloc;
+ 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
+ *++yylsp = yylloc;
+
+ /* 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];
+
+ /* Default location. */
+ YYLLOC_DEFAULT (yyloc, (yylsp - yylen), yylen);
+ yyerror_range[1] = yyloc;
+ YY_REDUCE_PRINT (yyn);
+ switch (yyn)
+ {
+ case 2: /* pl_function: comp_options pl_block opt_semi */
+#line 362 "pl_gram.y"
+ {
+ plpgsql_parse_result = (PLpgSQL_stmt_block *) (yyvsp[-1].stmt);
+ }
+#line 2032 "pl_gram.c"
+ break;
+
+ case 5: /* comp_option: '#' K_OPTION K_DUMP */
+#line 372 "pl_gram.y"
+ {
+ plpgsql_DumpExecTree = true;
+ }
+#line 2040 "pl_gram.c"
+ break;
+
+ case 6: /* comp_option: '#' K_PRINT_STRICT_PARAMS option_value */
+#line 376 "pl_gram.y"
+ {
+ if (strcmp((yyvsp[0].str), "on") == 0)
+ plpgsql_curr_compile->print_strict_params = true;
+ else if (strcmp((yyvsp[0].str), "off") == 0)
+ plpgsql_curr_compile->print_strict_params = false;
+ else
+ elog(ERROR, "unrecognized print_strict_params option %s", (yyvsp[0].str));
+ }
+#line 2053 "pl_gram.c"
+ break;
+
+ case 7: /* comp_option: '#' K_VARIABLE_CONFLICT K_ERROR */
+#line 385 "pl_gram.y"
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
+ }
+#line 2061 "pl_gram.c"
+ break;
+
+ case 8: /* comp_option: '#' K_VARIABLE_CONFLICT K_USE_VARIABLE */
+#line 389 "pl_gram.y"
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE;
+ }
+#line 2069 "pl_gram.c"
+ break;
+
+ case 9: /* comp_option: '#' K_VARIABLE_CONFLICT K_USE_COLUMN */
+#line 393 "pl_gram.y"
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN;
+ }
+#line 2077 "pl_gram.c"
+ break;
+
+ case 10: /* option_value: T_WORD */
+#line 399 "pl_gram.y"
+ {
+ (yyval.str) = (yyvsp[0].word).ident;
+ }
+#line 2085 "pl_gram.c"
+ break;
+
+ case 11: /* option_value: unreserved_keyword */
+#line 403 "pl_gram.y"
+ {
+ (yyval.str) = pstrdup((yyvsp[0].keyword));
+ }
+#line 2093 "pl_gram.c"
+ break;
+
+ case 14: /* pl_block: decl_sect K_BEGIN proc_sect exception_sect K_END opt_label */
+#line 412 "pl_gram.y"
+ {
+ PLpgSQL_stmt_block *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_block));
+
+ new->cmd_type = PLPGSQL_STMT_BLOCK;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-4]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = (yyvsp[-5].declhdr).label;
+ new->n_initvars = (yyvsp[-5].declhdr).n_initvars;
+ new->initvarnos = (yyvsp[-5].declhdr).initvarnos;
+ new->body = (yyvsp[-3].list);
+ new->exceptions = (yyvsp[-2].exception_block);
+
+ check_labels((yyvsp[-5].declhdr).label, (yyvsp[0].str), (yylsp[0]));
+ plpgsql_ns_pop();
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 2117 "pl_gram.c"
+ break;
+
+ case 15: /* decl_sect: opt_block_label */
+#line 435 "pl_gram.y"
+ {
+ /* done with decls, so resume identifier lookup */
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ (yyval.declhdr).label = (yyvsp[0].str);
+ (yyval.declhdr).n_initvars = 0;
+ (yyval.declhdr).initvarnos = NULL;
+ }
+#line 2129 "pl_gram.c"
+ break;
+
+ case 16: /* decl_sect: opt_block_label decl_start */
+#line 443 "pl_gram.y"
+ {
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ (yyval.declhdr).label = (yyvsp[-1].str);
+ (yyval.declhdr).n_initvars = 0;
+ (yyval.declhdr).initvarnos = NULL;
+ }
+#line 2140 "pl_gram.c"
+ break;
+
+ case 17: /* decl_sect: opt_block_label decl_start decl_stmts */
+#line 450 "pl_gram.y"
+ {
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ (yyval.declhdr).label = (yyvsp[-2].str);
+ /* Remember variables declared in decl_stmts */
+ (yyval.declhdr).n_initvars = plpgsql_add_initdatums(&((yyval.declhdr).initvarnos));
+ }
+#line 2151 "pl_gram.c"
+ break;
+
+ case 18: /* decl_start: K_DECLARE */
+#line 459 "pl_gram.y"
+ {
+ /* Forget any variables created before block */
+ plpgsql_add_initdatums(NULL);
+ /*
+ * Disable scanner lookup of identifiers while
+ * we process the decl_stmts
+ */
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
+ }
+#line 2165 "pl_gram.c"
+ break;
+
+ case 22: /* decl_stmt: K_DECLARE */
+#line 476 "pl_gram.y"
+ {
+ /* We allow useless extra DECLAREs */
+ }
+#line 2173 "pl_gram.c"
+ break;
+
+ case 23: /* decl_stmt: LESS_LESS any_identifier GREATER_GREATER */
+#line 480 "pl_gram.y"
+ {
+ /*
+ * Throw a helpful error if user tries to put block
+ * label just before BEGIN, instead of before DECLARE.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("block label must be placed before DECLARE, not after"),
+ parser_errposition((yylsp[-2]))));
+ }
+#line 2188 "pl_gram.c"
+ break;
+
+ case 24: /* decl_statement: decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval */
+#line 493 "pl_gram.y"
+ {
+ PLpgSQL_variable *var;
+
+ /*
+ * If a collation is supplied, insert it into the
+ * datatype. We assume decl_datatype always returns
+ * a freshly built struct not shared with other
+ * variables.
+ */
+ if (OidIsValid((yyvsp[-2].oid)))
+ {
+ if (!OidIsValid((yyvsp[-3].dtype)->collation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be((yyvsp[-3].dtype)->typoid)),
+ parser_errposition((yylsp[-2]))));
+ (yyvsp[-3].dtype)->collation = (yyvsp[-2].oid);
+ }
+
+ var = plpgsql_build_variable((yyvsp[-5].varname).name, (yyvsp[-5].varname).lineno,
+ (yyvsp[-3].dtype), true);
+ var->isconst = (yyvsp[-4].boolean);
+ var->notnull = (yyvsp[-1].boolean);
+ var->default_val = (yyvsp[0].expr);
+
+ /*
+ * The combination of NOT NULL without an initializer
+ * can't work, so let's reject it at compile time.
+ */
+ if (var->notnull && var->default_val == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("variable \"%s\" must have a default value, since it's declared NOT NULL",
+ var->refname),
+ parser_errposition((yylsp[-1]))));
+ }
+#line 2230 "pl_gram.c"
+ break;
+
+ case 25: /* decl_statement: decl_varname K_ALIAS K_FOR decl_aliasitem ';' */
+#line 531 "pl_gram.y"
+ {
+ plpgsql_ns_additem((yyvsp[-1].nsitem)->itemtype,
+ (yyvsp[-1].nsitem)->itemno, (yyvsp[-4].varname).name);
+ }
+#line 2239 "pl_gram.c"
+ break;
+
+ case 26: /* $@1: %empty */
+#line 536 "pl_gram.y"
+ { plpgsql_ns_push((yyvsp[-2].varname).name, PLPGSQL_LABEL_OTHER); }
+#line 2245 "pl_gram.c"
+ break;
+
+ case 27: /* decl_statement: decl_varname opt_scrollable K_CURSOR $@1 decl_cursor_args decl_is_for decl_cursor_query */
+#line 538 "pl_gram.y"
+ {
+ PLpgSQL_var *new;
+ PLpgSQL_expr *curname_def;
+ char buf[NAMEDATALEN * 2 + 64];
+ char *cp1;
+ char *cp2;
+
+ /* pop local namespace for cursor args */
+ plpgsql_ns_pop();
+
+ new = (PLpgSQL_var *)
+ plpgsql_build_variable((yyvsp[-6].varname).name, (yyvsp[-6].varname).lineno,
+ plpgsql_build_datatype(REFCURSOROID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+
+ curname_def = palloc0(sizeof(PLpgSQL_expr));
+
+ /* Note: refname has been truncated to NAMEDATALEN */
+ cp1 = new->refname;
+ cp2 = buf;
+ /*
+ * Don't trust standard_conforming_strings here;
+ * it might change before we use the string.
+ */
+ if (strchr(cp1, '\\') != NULL)
+ *cp2++ = ESCAPE_STRING_SYNTAX;
+ *cp2++ = '\'';
+ while (*cp1)
+ {
+ if (SQL_STR_DOUBLE(*cp1, true))
+ *cp2++ = *cp1;
+ *cp2++ = *cp1++;
+ }
+ strcpy(cp2, "'::pg_catalog.refcursor");
+ curname_def->query = pstrdup(buf);
+ curname_def->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ new->default_val = curname_def;
+
+ new->cursor_explicit_expr = (yyvsp[0].expr);
+ if ((yyvsp[-2].datum) == NULL)
+ new->cursor_explicit_argrow = -1;
+ else
+ new->cursor_explicit_argrow = (yyvsp[-2].datum)->dno;
+ new->cursor_options = CURSOR_OPT_FAST_PLAN | (yyvsp[-5].ival);
+ }
+#line 2298 "pl_gram.c"
+ break;
+
+ case 28: /* opt_scrollable: %empty */
+#line 589 "pl_gram.y"
+ {
+ (yyval.ival) = 0;
+ }
+#line 2306 "pl_gram.c"
+ break;
+
+ case 29: /* opt_scrollable: K_NO K_SCROLL */
+#line 593 "pl_gram.y"
+ {
+ (yyval.ival) = CURSOR_OPT_NO_SCROLL;
+ }
+#line 2314 "pl_gram.c"
+ break;
+
+ case 30: /* opt_scrollable: K_SCROLL */
+#line 597 "pl_gram.y"
+ {
+ (yyval.ival) = CURSOR_OPT_SCROLL;
+ }
+#line 2322 "pl_gram.c"
+ break;
+
+ case 31: /* decl_cursor_query: %empty */
+#line 603 "pl_gram.y"
+ {
+ (yyval.expr) = read_sql_stmt();
+ }
+#line 2330 "pl_gram.c"
+ break;
+
+ case 32: /* decl_cursor_args: %empty */
+#line 609 "pl_gram.y"
+ {
+ (yyval.datum) = NULL;
+ }
+#line 2338 "pl_gram.c"
+ break;
+
+ case 33: /* decl_cursor_args: '(' decl_cursor_arglist ')' */
+#line 613 "pl_gram.y"
+ {
+ PLpgSQL_row *new;
+ int i;
+ ListCell *l;
+
+ new = palloc0(sizeof(PLpgSQL_row));
+ new->dtype = PLPGSQL_DTYPE_ROW;
+ new->refname = "(unnamed row)";
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->rowtupdesc = NULL;
+ new->nfields = list_length((yyvsp[-1].list));
+ new->fieldnames = palloc(new->nfields * sizeof(char *));
+ new->varnos = palloc(new->nfields * sizeof(int));
+
+ i = 0;
+ foreach (l, (yyvsp[-1].list))
+ {
+ PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
+ Assert(!arg->isconst);
+ new->fieldnames[i] = arg->refname;
+ new->varnos[i] = arg->dno;
+ i++;
+ }
+ list_free((yyvsp[-1].list));
+
+ plpgsql_adddatum((PLpgSQL_datum *) new);
+ (yyval.datum) = (PLpgSQL_datum *) new;
+ }
+#line 2371 "pl_gram.c"
+ break;
+
+ case 34: /* decl_cursor_arglist: decl_cursor_arg */
+#line 644 "pl_gram.y"
+ {
+ (yyval.list) = list_make1((yyvsp[0].datum));
+ }
+#line 2379 "pl_gram.c"
+ break;
+
+ case 35: /* decl_cursor_arglist: decl_cursor_arglist ',' decl_cursor_arg */
+#line 648 "pl_gram.y"
+ {
+ (yyval.list) = lappend((yyvsp[-2].list), (yyvsp[0].datum));
+ }
+#line 2387 "pl_gram.c"
+ break;
+
+ case 36: /* decl_cursor_arg: decl_varname decl_datatype */
+#line 654 "pl_gram.y"
+ {
+ (yyval.datum) = (PLpgSQL_datum *)
+ plpgsql_build_variable((yyvsp[-1].varname).name, (yyvsp[-1].varname).lineno,
+ (yyvsp[0].dtype), true);
+ }
+#line 2397 "pl_gram.c"
+ break;
+
+ case 39: /* decl_aliasitem: T_WORD */
+#line 665 "pl_gram.y"
+ {
+ PLpgSQL_nsitem *nsi;
+
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ (yyvsp[0].word).ident, NULL, NULL,
+ NULL);
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ (yyvsp[0].word).ident),
+ parser_errposition((yylsp[0]))));
+ (yyval.nsitem) = nsi;
+ }
+#line 2416 "pl_gram.c"
+ break;
+
+ case 40: /* decl_aliasitem: unreserved_keyword */
+#line 680 "pl_gram.y"
+ {
+ PLpgSQL_nsitem *nsi;
+
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ (yyvsp[0].keyword), NULL, NULL,
+ NULL);
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ (yyvsp[0].keyword)),
+ parser_errposition((yylsp[0]))));
+ (yyval.nsitem) = nsi;
+ }
+#line 2435 "pl_gram.c"
+ break;
+
+ case 41: /* decl_aliasitem: T_CWORD */
+#line 695 "pl_gram.y"
+ {
+ PLpgSQL_nsitem *nsi;
+
+ if (list_length((yyvsp[0].cword).idents) == 2)
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ strVal(linitial((yyvsp[0].cword).idents)),
+ strVal(lsecond((yyvsp[0].cword).idents)),
+ NULL,
+ NULL);
+ else if (list_length((yyvsp[0].cword).idents) == 3)
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ strVal(linitial((yyvsp[0].cword).idents)),
+ strVal(lsecond((yyvsp[0].cword).idents)),
+ strVal(lthird((yyvsp[0].cword).idents)),
+ NULL);
+ else
+ nsi = NULL;
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ NameListToString((yyvsp[0].cword).idents)),
+ parser_errposition((yylsp[0]))));
+ (yyval.nsitem) = nsi;
+ }
+#line 2465 "pl_gram.c"
+ break;
+
+ case 42: /* decl_varname: T_WORD */
+#line 723 "pl_gram.y"
+ {
+ (yyval.varname).name = (yyvsp[0].word).ident;
+ (yyval.varname).lineno = plpgsql_location_to_lineno((yylsp[0]));
+ /*
+ * Check to make sure name isn't already declared
+ * in the current block.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ (yyvsp[0].word).ident, NULL, NULL,
+ NULL) != NULL)
+ yyerror("duplicate declaration");
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
+ {
+ PLpgSQL_nsitem *nsi;
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ (yyvsp[0].word).ident, NULL, NULL, NULL);
+ if (nsi != NULL)
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("variable \"%s\" shadows a previously defined variable",
+ (yyvsp[0].word).ident),
+ parser_errposition((yylsp[0]))));
+ }
+
+ }
+#line 2497 "pl_gram.c"
+ break;
+
+ case 43: /* decl_varname: unreserved_keyword */
+#line 751 "pl_gram.y"
+ {
+ (yyval.varname).name = pstrdup((yyvsp[0].keyword));
+ (yyval.varname).lineno = plpgsql_location_to_lineno((yylsp[0]));
+ /*
+ * Check to make sure name isn't already declared
+ * in the current block.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ (yyvsp[0].keyword), NULL, NULL,
+ NULL) != NULL)
+ yyerror("duplicate declaration");
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
+ {
+ PLpgSQL_nsitem *nsi;
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ (yyvsp[0].keyword), NULL, NULL, NULL);
+ if (nsi != NULL)
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("variable \"%s\" shadows a previously defined variable",
+ (yyvsp[0].keyword)),
+ parser_errposition((yylsp[0]))));
+ }
+
+ }
+#line 2529 "pl_gram.c"
+ break;
+
+ case 44: /* decl_const: %empty */
+#line 781 "pl_gram.y"
+ { (yyval.boolean) = false; }
+#line 2535 "pl_gram.c"
+ break;
+
+ case 45: /* decl_const: K_CONSTANT */
+#line 783 "pl_gram.y"
+ { (yyval.boolean) = true; }
+#line 2541 "pl_gram.c"
+ break;
+
+ case 46: /* decl_datatype: %empty */
+#line 787 "pl_gram.y"
+ {
+ /*
+ * If there's a lookahead token, read_datatype
+ * should consume it.
+ */
+ (yyval.dtype) = read_datatype(yychar);
+ yyclearin;
+ }
+#line 2554 "pl_gram.c"
+ break;
+
+ case 47: /* decl_collate: %empty */
+#line 798 "pl_gram.y"
+ { (yyval.oid) = InvalidOid; }
+#line 2560 "pl_gram.c"
+ break;
+
+ case 48: /* decl_collate: K_COLLATE T_WORD */
+#line 800 "pl_gram.y"
+ {
+ (yyval.oid) = get_collation_oid(list_make1(makeString((yyvsp[0].word).ident)),
+ false);
+ }
+#line 2569 "pl_gram.c"
+ break;
+
+ case 49: /* decl_collate: K_COLLATE unreserved_keyword */
+#line 805 "pl_gram.y"
+ {
+ (yyval.oid) = get_collation_oid(list_make1(makeString(pstrdup((yyvsp[0].keyword)))),
+ false);
+ }
+#line 2578 "pl_gram.c"
+ break;
+
+ case 50: /* decl_collate: K_COLLATE T_CWORD */
+#line 810 "pl_gram.y"
+ {
+ (yyval.oid) = get_collation_oid((yyvsp[0].cword).idents, false);
+ }
+#line 2586 "pl_gram.c"
+ break;
+
+ case 51: /* decl_notnull: %empty */
+#line 816 "pl_gram.y"
+ { (yyval.boolean) = false; }
+#line 2592 "pl_gram.c"
+ break;
+
+ case 52: /* decl_notnull: K_NOT K_NULL */
+#line 818 "pl_gram.y"
+ { (yyval.boolean) = true; }
+#line 2598 "pl_gram.c"
+ break;
+
+ case 53: /* decl_defval: ';' */
+#line 822 "pl_gram.y"
+ { (yyval.expr) = NULL; }
+#line 2604 "pl_gram.c"
+ break;
+
+ case 54: /* decl_defval: decl_defkey */
+#line 824 "pl_gram.y"
+ {
+ (yyval.expr) = read_sql_expression(';', ";");
+ }
+#line 2612 "pl_gram.c"
+ break;
+
+ case 59: /* proc_sect: %empty */
+#line 843 "pl_gram.y"
+ { (yyval.list) = NIL; }
+#line 2618 "pl_gram.c"
+ break;
+
+ case 60: /* proc_sect: proc_sect proc_stmt */
+#line 845 "pl_gram.y"
+ {
+ /* don't bother linking null statements into list */
+ if ((yyvsp[0].stmt) == NULL)
+ (yyval.list) = (yyvsp[-1].list);
+ else
+ (yyval.list) = lappend((yyvsp[-1].list), (yyvsp[0].stmt));
+ }
+#line 2630 "pl_gram.c"
+ break;
+
+ case 61: /* proc_stmt: pl_block ';' */
+#line 855 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[-1].stmt); }
+#line 2636 "pl_gram.c"
+ break;
+
+ case 62: /* proc_stmt: stmt_assign */
+#line 857 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2642 "pl_gram.c"
+ break;
+
+ case 63: /* proc_stmt: stmt_if */
+#line 859 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2648 "pl_gram.c"
+ break;
+
+ case 64: /* proc_stmt: stmt_case */
+#line 861 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2654 "pl_gram.c"
+ break;
+
+ case 65: /* proc_stmt: stmt_loop */
+#line 863 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2660 "pl_gram.c"
+ break;
+
+ case 66: /* proc_stmt: stmt_while */
+#line 865 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2666 "pl_gram.c"
+ break;
+
+ case 67: /* proc_stmt: stmt_for */
+#line 867 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2672 "pl_gram.c"
+ break;
+
+ case 68: /* proc_stmt: stmt_foreach_a */
+#line 869 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2678 "pl_gram.c"
+ break;
+
+ case 69: /* proc_stmt: stmt_exit */
+#line 871 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2684 "pl_gram.c"
+ break;
+
+ case 70: /* proc_stmt: stmt_return */
+#line 873 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2690 "pl_gram.c"
+ break;
+
+ case 71: /* proc_stmt: stmt_raise */
+#line 875 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2696 "pl_gram.c"
+ break;
+
+ case 72: /* proc_stmt: stmt_assert */
+#line 877 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2702 "pl_gram.c"
+ break;
+
+ case 73: /* proc_stmt: stmt_execsql */
+#line 879 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2708 "pl_gram.c"
+ break;
+
+ case 74: /* proc_stmt: stmt_dynexecute */
+#line 881 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2714 "pl_gram.c"
+ break;
+
+ case 75: /* proc_stmt: stmt_perform */
+#line 883 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2720 "pl_gram.c"
+ break;
+
+ case 76: /* proc_stmt: stmt_call */
+#line 885 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2726 "pl_gram.c"
+ break;
+
+ case 77: /* proc_stmt: stmt_getdiag */
+#line 887 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2732 "pl_gram.c"
+ break;
+
+ case 78: /* proc_stmt: stmt_open */
+#line 889 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2738 "pl_gram.c"
+ break;
+
+ case 79: /* proc_stmt: stmt_fetch */
+#line 891 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2744 "pl_gram.c"
+ break;
+
+ case 80: /* proc_stmt: stmt_move */
+#line 893 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2750 "pl_gram.c"
+ break;
+
+ case 81: /* proc_stmt: stmt_close */
+#line 895 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2756 "pl_gram.c"
+ break;
+
+ case 82: /* proc_stmt: stmt_null */
+#line 897 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2762 "pl_gram.c"
+ break;
+
+ case 83: /* proc_stmt: stmt_commit */
+#line 899 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2768 "pl_gram.c"
+ break;
+
+ case 84: /* proc_stmt: stmt_rollback */
+#line 901 "pl_gram.y"
+ { (yyval.stmt) = (yyvsp[0].stmt); }
+#line 2774 "pl_gram.c"
+ break;
+
+ case 85: /* stmt_perform: K_PERFORM */
+#line 905 "pl_gram.y"
+ {
+ PLpgSQL_stmt_perform *new;
+ int startloc;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_perform));
+ new->cmd_type = PLPGSQL_STMT_PERFORM;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_PERFORM);
+
+ /*
+ * Since PERFORM isn't legal SQL, we have to cheat to
+ * the extent of substituting "SELECT" for "PERFORM"
+ * in the parsed text. It does not seem worth
+ * inventing a separate parse mode for this one case.
+ * We can't do syntax-checking until after we make the
+ * substitution.
+ */
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, false, true,
+ &startloc, NULL);
+ /* overwrite "perform" ... */
+ memcpy(new->expr->query, " SELECT", 7);
+ /* left-justify to get rid of the leading space */
+ memmove(new->expr->query, new->expr->query + 1,
+ strlen(new->expr->query));
+ /* offset syntax error position to account for that */
+ check_sql_expr(new->expr->query, new->expr->parseMode,
+ startloc + 1);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 2812 "pl_gram.c"
+ break;
+
+ case 86: /* stmt_call: K_CALL */
+#line 941 "pl_gram.y"
+ {
+ PLpgSQL_stmt_call *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_call));
+ new->cmd_type = PLPGSQL_STMT_CALL;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_CALL);
+ new->expr = read_sql_stmt();
+ new->is_call = true;
+
+ /* Remember we may need a procedure resource owner */
+ plpgsql_curr_compile->requires_procedure_resowner = true;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+
+ }
+#line 2834 "pl_gram.c"
+ break;
+
+ case 87: /* stmt_call: K_DO */
+#line 959 "pl_gram.y"
+ {
+ /* use the same structures as for CALL, for simplicity */
+ PLpgSQL_stmt_call *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_call));
+ new->cmd_type = PLPGSQL_STMT_CALL;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_DO);
+ new->expr = read_sql_stmt();
+ new->is_call = false;
+
+ /* Remember we may need a procedure resource owner */
+ plpgsql_curr_compile->requires_procedure_resowner = true;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+
+ }
+#line 2857 "pl_gram.c"
+ break;
+
+ case 88: /* stmt_assign: T_DATUM */
+#line 980 "pl_gram.y"
+ {
+ PLpgSQL_stmt_assign *new;
+ RawParseMode pmode;
+
+ /* see how many names identify the datum */
+ switch ((yyvsp[0].wdatum).ident ? 1 : list_length((yyvsp[0].wdatum).idents))
+ {
+ case 1:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ break;
+ case 2:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ break;
+ case 3:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ break;
+ default:
+ elog(ERROR, "unexpected number of names");
+ pmode = 0; /* keep compiler quiet */
+ }
+
+ check_assignable((yyvsp[0].wdatum).datum, (yylsp[0]));
+ new = palloc0(sizeof(PLpgSQL_stmt_assign));
+ new->cmd_type = PLPGSQL_STMT_ASSIGN;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->varno = (yyvsp[0].wdatum).datum->dno;
+ /* Push back the head name to include it in the stmt */
+ plpgsql_push_back_token(T_DATUM);
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ pmode,
+ false, true, true,
+ NULL, NULL);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 2898 "pl_gram.c"
+ break;
+
+ case 89: /* stmt_getdiag: K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';' */
+#line 1019 "pl_gram.y"
+ {
+ PLpgSQL_stmt_getdiag *new;
+ ListCell *lc;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
+ new->cmd_type = PLPGSQL_STMT_GETDIAG;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-4]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->is_stacked = (yyvsp[-3].boolean);
+ new->diag_items = (yyvsp[-1].list);
+
+ /*
+ * Check information items are valid for area option.
+ */
+ foreach(lc, new->diag_items)
+ {
+ PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc);
+
+ switch (ditem->kind)
+ {
+ /* these fields are disallowed in stacked case */
+ case PLPGSQL_GETDIAG_ROW_COUNT:
+ if (new->is_stacked)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS",
+ plpgsql_getdiag_kindname(ditem->kind)),
+ parser_errposition((yylsp[-4]))));
+ break;
+ /* these fields are disallowed in current case */
+ case PLPGSQL_GETDIAG_ERROR_CONTEXT:
+ case PLPGSQL_GETDIAG_ERROR_DETAIL:
+ case PLPGSQL_GETDIAG_ERROR_HINT:
+ case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ case PLPGSQL_GETDIAG_DATATYPE_NAME:
+ case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ if (!new->is_stacked)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
+ plpgsql_getdiag_kindname(ditem->kind)),
+ parser_errposition((yylsp[-4]))));
+ break;
+ /* these fields are allowed in either case */
+ case PLPGSQL_GETDIAG_CONTEXT:
+ break;
+ default:
+ elog(ERROR, "unrecognized diagnostic item kind: %d",
+ ditem->kind);
+ break;
+ }
+ }
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 2962 "pl_gram.c"
+ break;
+
+ case 90: /* getdiag_area_opt: %empty */
+#line 1081 "pl_gram.y"
+ {
+ (yyval.boolean) = false;
+ }
+#line 2970 "pl_gram.c"
+ break;
+
+ case 91: /* getdiag_area_opt: K_CURRENT */
+#line 1085 "pl_gram.y"
+ {
+ (yyval.boolean) = false;
+ }
+#line 2978 "pl_gram.c"
+ break;
+
+ case 92: /* getdiag_area_opt: K_STACKED */
+#line 1089 "pl_gram.y"
+ {
+ (yyval.boolean) = true;
+ }
+#line 2986 "pl_gram.c"
+ break;
+
+ case 93: /* getdiag_list: getdiag_list ',' getdiag_list_item */
+#line 1095 "pl_gram.y"
+ {
+ (yyval.list) = lappend((yyvsp[-2].list), (yyvsp[0].diagitem));
+ }
+#line 2994 "pl_gram.c"
+ break;
+
+ case 94: /* getdiag_list: getdiag_list_item */
+#line 1099 "pl_gram.y"
+ {
+ (yyval.list) = list_make1((yyvsp[0].diagitem));
+ }
+#line 3002 "pl_gram.c"
+ break;
+
+ case 95: /* getdiag_list_item: getdiag_target assign_operator getdiag_item */
+#line 1105 "pl_gram.y"
+ {
+ PLpgSQL_diag_item *new;
+
+ new = palloc(sizeof(PLpgSQL_diag_item));
+ new->target = (yyvsp[-2].datum)->dno;
+ new->kind = (yyvsp[0].ival);
+
+ (yyval.diagitem) = new;
+ }
+#line 3016 "pl_gram.c"
+ break;
+
+ case 96: /* getdiag_item: %empty */
+#line 1117 "pl_gram.y"
+ {
+ int tok = yylex();
+
+ if (tok_is_keyword(tok, &yylval,
+ K_ROW_COUNT, "row_count"))
+ (yyval.ival) = PLPGSQL_GETDIAG_ROW_COUNT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_CONTEXT, "pg_context"))
+ (yyval.ival) = PLPGSQL_GETDIAG_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
+ (yyval.ival) = PLPGSQL_GETDIAG_ERROR_DETAIL;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_HINT, "pg_exception_hint"))
+ (yyval.ival) = PLPGSQL_GETDIAG_ERROR_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
+ (yyval.ival) = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_COLUMN_NAME, "column_name"))
+ (yyval.ival) = PLPGSQL_GETDIAG_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_CONSTRAINT_NAME, "constraint_name"))
+ (yyval.ival) = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_DATATYPE_NAME, "pg_datatype_name"))
+ (yyval.ival) = PLPGSQL_GETDIAG_DATATYPE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_MESSAGE_TEXT, "message_text"))
+ (yyval.ival) = PLPGSQL_GETDIAG_MESSAGE_TEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_TABLE_NAME, "table_name"))
+ (yyval.ival) = PLPGSQL_GETDIAG_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCHEMA_NAME, "schema_name"))
+ (yyval.ival) = PLPGSQL_GETDIAG_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_RETURNED_SQLSTATE, "returned_sqlstate"))
+ (yyval.ival) = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
+ else
+ yyerror("unrecognized GET DIAGNOSTICS item");
+ }
+#line 3063 "pl_gram.c"
+ break;
+
+ case 97: /* getdiag_target: T_DATUM */
+#line 1162 "pl_gram.y"
+ {
+ /*
+ * In principle we should support a getdiag_target
+ * that is an array element, but for now we don't, so
+ * just throw an error if next token is '['.
+ */
+ if ((yyvsp[0].wdatum).datum->dtype == PLPGSQL_DTYPE_ROW ||
+ (yyvsp[0].wdatum).datum->dtype == PLPGSQL_DTYPE_REC ||
+ plpgsql_peek() == '[')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a scalar variable",
+ NameOfDatum(&((yyvsp[0].wdatum)))),
+ parser_errposition((yylsp[0]))));
+ check_assignable((yyvsp[0].wdatum).datum, (yylsp[0]));
+ (yyval.datum) = (yyvsp[0].wdatum).datum;
+ }
+#line 3085 "pl_gram.c"
+ break;
+
+ case 98: /* getdiag_target: T_WORD */
+#line 1180 "pl_gram.y"
+ {
+ /* just to give a better message than "syntax error" */
+ word_is_not_variable(&((yyvsp[0].word)), (yylsp[0]));
+ }
+#line 3094 "pl_gram.c"
+ break;
+
+ case 99: /* getdiag_target: T_CWORD */
+#line 1185 "pl_gram.y"
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&((yyvsp[0].cword)), (yylsp[0]));
+ }
+#line 3103 "pl_gram.c"
+ break;
+
+ case 100: /* stmt_if: K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';' */
+#line 1192 "pl_gram.y"
+ {
+ PLpgSQL_stmt_if *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_if));
+ new->cmd_type = PLPGSQL_STMT_IF;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-7]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->cond = (yyvsp[-6].expr);
+ new->then_body = (yyvsp[-5].list);
+ new->elsif_list = (yyvsp[-4].list);
+ new->else_body = (yyvsp[-3].list);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3122 "pl_gram.c"
+ break;
+
+ case 101: /* stmt_elsifs: %empty */
+#line 1209 "pl_gram.y"
+ {
+ (yyval.list) = NIL;
+ }
+#line 3130 "pl_gram.c"
+ break;
+
+ case 102: /* stmt_elsifs: stmt_elsifs K_ELSIF expr_until_then proc_sect */
+#line 1213 "pl_gram.y"
+ {
+ PLpgSQL_if_elsif *new;
+
+ new = palloc0(sizeof(PLpgSQL_if_elsif));
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->cond = (yyvsp[-1].expr);
+ new->stmts = (yyvsp[0].list);
+
+ (yyval.list) = lappend((yyvsp[-3].list), new);
+ }
+#line 3145 "pl_gram.c"
+ break;
+
+ case 103: /* stmt_else: %empty */
+#line 1226 "pl_gram.y"
+ {
+ (yyval.list) = NIL;
+ }
+#line 3153 "pl_gram.c"
+ break;
+
+ case 104: /* stmt_else: K_ELSE proc_sect */
+#line 1230 "pl_gram.y"
+ {
+ (yyval.list) = (yyvsp[0].list);
+ }
+#line 3161 "pl_gram.c"
+ break;
+
+ case 105: /* stmt_case: K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';' */
+#line 1236 "pl_gram.y"
+ {
+ (yyval.stmt) = make_case((yylsp[-6]), (yyvsp[-5].expr), (yyvsp[-4].list), (yyvsp[-3].list));
+ }
+#line 3169 "pl_gram.c"
+ break;
+
+ case 106: /* opt_expr_until_when: %empty */
+#line 1242 "pl_gram.y"
+ {
+ PLpgSQL_expr *expr = NULL;
+ int tok = yylex();
+
+ if (tok != K_WHEN)
+ {
+ plpgsql_push_back_token(tok);
+ expr = read_sql_expression(K_WHEN, "WHEN");
+ }
+ plpgsql_push_back_token(K_WHEN);
+ (yyval.expr) = expr;
+ }
+#line 3186 "pl_gram.c"
+ break;
+
+ case 107: /* case_when_list: case_when_list case_when */
+#line 1257 "pl_gram.y"
+ {
+ (yyval.list) = lappend((yyvsp[-1].list), (yyvsp[0].casewhen));
+ }
+#line 3194 "pl_gram.c"
+ break;
+
+ case 108: /* case_when_list: case_when */
+#line 1261 "pl_gram.y"
+ {
+ (yyval.list) = list_make1((yyvsp[0].casewhen));
+ }
+#line 3202 "pl_gram.c"
+ break;
+
+ case 109: /* case_when: K_WHEN expr_until_then proc_sect */
+#line 1267 "pl_gram.y"
+ {
+ PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
+
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->expr = (yyvsp[-1].expr);
+ new->stmts = (yyvsp[0].list);
+ (yyval.casewhen) = new;
+ }
+#line 3215 "pl_gram.c"
+ break;
+
+ case 110: /* opt_case_else: %empty */
+#line 1278 "pl_gram.y"
+ {
+ (yyval.list) = NIL;
+ }
+#line 3223 "pl_gram.c"
+ break;
+
+ case 111: /* opt_case_else: K_ELSE proc_sect */
+#line 1282 "pl_gram.y"
+ {
+ /*
+ * proc_sect could return an empty list, but we
+ * must distinguish that from not having ELSE at all.
+ * Simplest fix is to return a list with one NULL
+ * pointer, which make_case() must take care of.
+ */
+ if ((yyvsp[0].list) != NIL)
+ (yyval.list) = (yyvsp[0].list);
+ else
+ (yyval.list) = list_make1(NULL);
+ }
+#line 3240 "pl_gram.c"
+ break;
+
+ case 112: /* stmt_loop: opt_loop_label K_LOOP loop_body */
+#line 1297 "pl_gram.y"
+ {
+ PLpgSQL_stmt_loop *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_loop));
+ new->cmd_type = PLPGSQL_STMT_LOOP;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-1]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = (yyvsp[-2].str);
+ new->body = (yyvsp[0].loop_body).stmts;
+
+ check_labels((yyvsp[-2].str), (yyvsp[0].loop_body).end_label, (yyvsp[0].loop_body).end_label_location);
+ plpgsql_ns_pop();
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3260 "pl_gram.c"
+ break;
+
+ case 113: /* stmt_while: opt_loop_label K_WHILE expr_until_loop loop_body */
+#line 1315 "pl_gram.y"
+ {
+ PLpgSQL_stmt_while *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_while));
+ new->cmd_type = PLPGSQL_STMT_WHILE;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = (yyvsp[-3].str);
+ new->cond = (yyvsp[-1].expr);
+ new->body = (yyvsp[0].loop_body).stmts;
+
+ check_labels((yyvsp[-3].str), (yyvsp[0].loop_body).end_label, (yyvsp[0].loop_body).end_label_location);
+ plpgsql_ns_pop();
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3281 "pl_gram.c"
+ break;
+
+ case 114: /* stmt_for: opt_loop_label K_FOR for_control loop_body */
+#line 1334 "pl_gram.y"
+ {
+ /* This runs after we've scanned the loop body */
+ if ((yyvsp[-1].stmt)->cmd_type == PLPGSQL_STMT_FORI)
+ {
+ PLpgSQL_stmt_fori *new;
+
+ new = (PLpgSQL_stmt_fori *) (yyvsp[-1].stmt);
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->label = (yyvsp[-3].str);
+ new->body = (yyvsp[0].loop_body).stmts;
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ PLpgSQL_stmt_forq *new;
+
+ Assert((yyvsp[-1].stmt)->cmd_type == PLPGSQL_STMT_FORS ||
+ (yyvsp[-1].stmt)->cmd_type == PLPGSQL_STMT_FORC ||
+ (yyvsp[-1].stmt)->cmd_type == PLPGSQL_STMT_DYNFORS);
+ /* forq is the common supertype of all three */
+ new = (PLpgSQL_stmt_forq *) (yyvsp[-1].stmt);
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->label = (yyvsp[-3].str);
+ new->body = (yyvsp[0].loop_body).stmts;
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+
+ check_labels((yyvsp[-3].str), (yyvsp[0].loop_body).end_label, (yyvsp[0].loop_body).end_label_location);
+ /* close namespace started in opt_loop_label */
+ plpgsql_ns_pop();
+ }
+#line 3317 "pl_gram.c"
+ break;
+
+ case 115: /* for_control: for_variable K_IN */
+#line 1368 "pl_gram.y"
+ {
+ int tok = yylex();
+ int tokloc = yylloc;
+
+ if (tok == K_EXECUTE)
+ {
+ /* EXECUTE means it's a dynamic FOR loop */
+ PLpgSQL_stmt_dynfors *new;
+ PLpgSQL_expr *expr;
+ int term;
+
+ expr = read_sql_expression2(K_LOOP, K_USING,
+ "LOOP or USING",
+ &term);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
+ new->cmd_type = PLPGSQL_STMT_DYNFORS;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ if ((yyvsp[-1].forvariable).row)
+ {
+ new->var = (PLpgSQL_variable *) (yyvsp[-1].forvariable).row;
+ check_assignable((yyvsp[-1].forvariable).row, (yylsp[-1]));
+ }
+ else if ((yyvsp[-1].forvariable).scalar)
+ {
+ /* convert single scalar to list */
+ new->var = (PLpgSQL_variable *)
+ make_scalar_list1((yyvsp[-1].forvariable).name, (yyvsp[-1].forvariable).scalar,
+ (yyvsp[-1].forvariable).lineno, (yylsp[-1]));
+ /* make_scalar_list1 did check_assignable */
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
+ parser_errposition((yylsp[-1]))));
+ }
+ new->query = expr;
+
+ if (term == K_USING)
+ {
+ do
+ {
+ expr = read_sql_expression2(',', K_LOOP,
+ ", or LOOP",
+ &term);
+ new->params = lappend(new->params, expr);
+ } while (term == ',');
+ }
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+ else if (tok == T_DATUM &&
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR &&
+ ((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID)
+ {
+ /* It's FOR var IN cursor */
+ PLpgSQL_stmt_forc *new;
+ PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum;
+
+ new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
+ new->cmd_type = PLPGSQL_STMT_FORC;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = cursor->dno;
+
+ /* Should have had a single variable name */
+ if ((yyvsp[-1].forvariable).scalar && (yyvsp[-1].forvariable).row)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor FOR loop must have only one target variable"),
+ parser_errposition((yylsp[-1]))));
+
+ /* can't use an unbound cursor this way */
+ if (cursor->cursor_explicit_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor FOR loop must use a bound cursor variable"),
+ parser_errposition(tokloc)));
+
+ /* collect cursor's parameters if any */
+ new->argquery = read_cursor_args(cursor,
+ K_LOOP);
+
+ /* create loop's private RECORD variable */
+ new->var = (PLpgSQL_variable *)
+ plpgsql_build_record((yyvsp[-1].forvariable).name,
+ (yyvsp[-1].forvariable).lineno,
+ NULL,
+ RECORDOID,
+ true);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ PLpgSQL_expr *expr1;
+ int expr1loc;
+ bool reverse = false;
+
+ /*
+ * We have to distinguish between two
+ * alternatives: FOR var IN a .. b and FOR
+ * var IN query. Unfortunately this is
+ * tricky, since the query in the second
+ * form needn't start with a SELECT
+ * keyword. We use the ugly hack of
+ * looking for two periods after the first
+ * token. We also check for the REVERSE
+ * keyword, which means it must be an
+ * integer loop.
+ */
+ if (tok_is_keyword(tok, &yylval,
+ K_REVERSE, "reverse"))
+ reverse = true;
+ else
+ plpgsql_push_back_token(tok);
+
+ /*
+ * Read tokens until we see either a ".."
+ * or a LOOP. The text we read may be either
+ * an expression or a whole SQL statement, so
+ * we need to invoke read_sql_construct directly,
+ * and tell it not to check syntax yet.
+ */
+ expr1 = read_sql_construct(DOT_DOT,
+ K_LOOP,
+ 0,
+ "LOOP",
+ RAW_PARSE_DEFAULT,
+ true,
+ false,
+ true,
+ &expr1loc,
+ &tok);
+
+ if (tok == DOT_DOT)
+ {
+ /* Saw "..", so it must be an integer loop */
+ PLpgSQL_expr *expr2;
+ PLpgSQL_expr *expr_by;
+ PLpgSQL_var *fvar;
+ PLpgSQL_stmt_fori *new;
+
+ /*
+ * Relabel first expression as an expression;
+ * then we can check its syntax.
+ */
+ expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
+
+ /* Read and check the second one */
+ expr2 = read_sql_expression2(K_LOOP, K_BY,
+ "LOOP",
+ &tok);
+
+ /* Get the BY clause if any */
+ if (tok == K_BY)
+ expr_by = read_sql_expression(K_LOOP,
+ "LOOP");
+ else
+ expr_by = NULL;
+
+ /* Should have had a single variable name */
+ if ((yyvsp[-1].forvariable).scalar && (yyvsp[-1].forvariable).row)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("integer FOR loop must have only one target variable"),
+ parser_errposition((yylsp[-1]))));
+
+ /* create loop's private variable */
+ fvar = (PLpgSQL_var *)
+ plpgsql_build_variable((yyvsp[-1].forvariable).name,
+ (yyvsp[-1].forvariable).lineno,
+ plpgsql_build_datatype(INT4OID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_fori));
+ new->cmd_type = PLPGSQL_STMT_FORI;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->var = fvar;
+ new->reverse = reverse;
+ new->lower = expr1;
+ new->upper = expr2;
+ new->step = expr_by;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ /*
+ * No "..", so it must be a query loop.
+ */
+ PLpgSQL_stmt_fors *new;
+
+ if (reverse)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify REVERSE in query FOR loop"),
+ parser_errposition(tokloc)));
+
+ /* Check syntax as a regular query */
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_fors));
+ new->cmd_type = PLPGSQL_STMT_FORS;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ if ((yyvsp[-1].forvariable).row)
+ {
+ new->var = (PLpgSQL_variable *) (yyvsp[-1].forvariable).row;
+ check_assignable((yyvsp[-1].forvariable).row, (yylsp[-1]));
+ }
+ else if ((yyvsp[-1].forvariable).scalar)
+ {
+ /* convert single scalar to list */
+ new->var = (PLpgSQL_variable *)
+ make_scalar_list1((yyvsp[-1].forvariable).name, (yyvsp[-1].forvariable).scalar,
+ (yyvsp[-1].forvariable).lineno, (yylsp[-1]));
+ /* make_scalar_list1 did check_assignable */
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
+ parser_errposition((yylsp[-1]))));
+ }
+
+ new->query = expr1;
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+ }
+ }
+#line 3560 "pl_gram.c"
+ break;
+
+ case 116: /* for_variable: T_DATUM */
+#line 1627 "pl_gram.y"
+ {
+ (yyval.forvariable).name = NameOfDatum(&((yyvsp[0].wdatum)));
+ (yyval.forvariable).lineno = plpgsql_location_to_lineno((yylsp[0]));
+ if ((yyvsp[0].wdatum).datum->dtype == PLPGSQL_DTYPE_ROW ||
+ (yyvsp[0].wdatum).datum->dtype == PLPGSQL_DTYPE_REC)
+ {
+ (yyval.forvariable).scalar = NULL;
+ (yyval.forvariable).row = (yyvsp[0].wdatum).datum;
+ }
+ else
+ {
+ int tok;
+
+ (yyval.forvariable).scalar = (yyvsp[0].wdatum).datum;
+ (yyval.forvariable).row = NULL;
+ /* check for comma-separated list */
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == ',')
+ (yyval.forvariable).row = (PLpgSQL_datum *)
+ read_into_scalar_list((yyval.forvariable).name,
+ (yyval.forvariable).scalar,
+ (yylsp[0]));
+ }
+ }
+#line 3590 "pl_gram.c"
+ break;
+
+ case 117: /* for_variable: T_WORD */
+#line 1653 "pl_gram.y"
+ {
+ int tok;
+
+ (yyval.forvariable).name = (yyvsp[0].word).ident;
+ (yyval.forvariable).lineno = plpgsql_location_to_lineno((yylsp[0]));
+ (yyval.forvariable).scalar = NULL;
+ (yyval.forvariable).row = NULL;
+ /* check for comma-separated list */
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == ',')
+ word_is_not_variable(&((yyvsp[0].word)), (yylsp[0]));
+ }
+#line 3608 "pl_gram.c"
+ break;
+
+ case 118: /* for_variable: T_CWORD */
+#line 1667 "pl_gram.y"
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&((yyvsp[0].cword)), (yylsp[0]));
+ }
+#line 3617 "pl_gram.c"
+ break;
+
+ case 119: /* stmt_foreach_a: opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body */
+#line 1674 "pl_gram.y"
+ {
+ PLpgSQL_stmt_foreach_a *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_foreach_a));
+ new->cmd_type = PLPGSQL_STMT_FOREACH_A;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-6]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = (yyvsp[-7].str);
+ new->slice = (yyvsp[-4].ival);
+ new->expr = (yyvsp[-1].expr);
+ new->body = (yyvsp[0].loop_body).stmts;
+
+ if ((yyvsp[-5].forvariable).row)
+ {
+ new->varno = (yyvsp[-5].forvariable).row->dno;
+ check_assignable((yyvsp[-5].forvariable).row, (yylsp[-5]));
+ }
+ else if ((yyvsp[-5].forvariable).scalar)
+ {
+ new->varno = (yyvsp[-5].forvariable).scalar->dno;
+ check_assignable((yyvsp[-5].forvariable).scalar, (yylsp[-5]));
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("loop variable of FOREACH must be a known variable or list of variables"),
+ parser_errposition((yylsp[-5]))));
+ }
+
+ check_labels((yyvsp[-7].str), (yyvsp[0].loop_body).end_label, (yyvsp[0].loop_body).end_label_location);
+ plpgsql_ns_pop();
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3657 "pl_gram.c"
+ break;
+
+ case 120: /* foreach_slice: %empty */
+#line 1712 "pl_gram.y"
+ {
+ (yyval.ival) = 0;
+ }
+#line 3665 "pl_gram.c"
+ break;
+
+ case 121: /* foreach_slice: K_SLICE ICONST */
+#line 1716 "pl_gram.y"
+ {
+ (yyval.ival) = (yyvsp[0].ival);
+ }
+#line 3673 "pl_gram.c"
+ break;
+
+ case 122: /* stmt_exit: exit_type opt_label opt_exitcond */
+#line 1722 "pl_gram.y"
+ {
+ PLpgSQL_stmt_exit *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_exit));
+ new->cmd_type = PLPGSQL_STMT_EXIT;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->is_exit = (yyvsp[-2].boolean);
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->label = (yyvsp[-1].str);
+ new->cond = (yyvsp[0].expr);
+
+ if ((yyvsp[-1].str))
+ {
+ /* We have a label, so verify it exists */
+ PLpgSQL_nsitem *label;
+
+ label = plpgsql_ns_lookup_label(plpgsql_ns_top(), (yyvsp[-1].str));
+ if (label == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("there is no label \"%s\" "
+ "attached to any block or loop enclosing this statement",
+ (yyvsp[-1].str)),
+ parser_errposition((yylsp[-1]))));
+ /* CONTINUE only allows loop labels */
+ if (label->itemno != PLPGSQL_LABEL_LOOP && !new->is_exit)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("block label \"%s\" cannot be used in CONTINUE",
+ (yyvsp[-1].str)),
+ parser_errposition((yylsp[-1]))));
+ }
+ else
+ {
+ /*
+ * No label, so make sure there is some loop (an
+ * unlabeled EXIT does not match a block, so this
+ * is the same test for both EXIT and CONTINUE)
+ */
+ if (plpgsql_ns_find_nearest_loop(plpgsql_ns_top()) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ new->is_exit ?
+ errmsg("EXIT cannot be used outside a loop, unless it has a label") :
+ errmsg("CONTINUE cannot be used outside a loop"),
+ parser_errposition((yylsp[-2]))));
+ }
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3728 "pl_gram.c"
+ break;
+
+ case 123: /* exit_type: K_EXIT */
+#line 1775 "pl_gram.y"
+ {
+ (yyval.boolean) = true;
+ }
+#line 3736 "pl_gram.c"
+ break;
+
+ case 124: /* exit_type: K_CONTINUE */
+#line 1779 "pl_gram.y"
+ {
+ (yyval.boolean) = false;
+ }
+#line 3744 "pl_gram.c"
+ break;
+
+ case 125: /* stmt_return: K_RETURN */
+#line 1785 "pl_gram.y"
+ {
+ int tok;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
+ {
+ (yyval.stmt) = make_return_next_stmt((yylsp[0]));
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_QUERY, "query"))
+ {
+ (yyval.stmt) = make_return_query_stmt((yylsp[0]));
+ }
+ else
+ {
+ plpgsql_push_back_token(tok);
+ (yyval.stmt) = make_return_stmt((yylsp[0]));
+ }
+ }
+#line 3772 "pl_gram.c"
+ break;
+
+ case 126: /* stmt_raise: K_RAISE */
+#line 1811 "pl_gram.y"
+ {
+ PLpgSQL_stmt_raise *new;
+ int tok;
+
+ new = palloc(sizeof(PLpgSQL_stmt_raise));
+
+ new->cmd_type = PLPGSQL_STMT_RAISE;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->elog_level = ERROR; /* default */
+ new->condname = NULL;
+ new->message = NULL;
+ new->params = NIL;
+ new->options = NIL;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ /*
+ * We could have just RAISE, meaning to re-throw
+ * the current error.
+ */
+ if (tok != ';')
+ {
+ /*
+ * First is an optional elog severity level.
+ */
+ if (tok_is_keyword(tok, &yylval,
+ K_EXCEPTION, "exception"))
+ {
+ new->elog_level = ERROR;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_WARNING, "warning"))
+ {
+ new->elog_level = WARNING;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_NOTICE, "notice"))
+ {
+ new->elog_level = NOTICE;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_INFO, "info"))
+ {
+ new->elog_level = INFO;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_LOG, "log"))
+ {
+ new->elog_level = LOG;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_DEBUG, "debug"))
+ {
+ new->elog_level = DEBUG1;
+ tok = yylex();
+ }
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ /*
+ * Next we can have a condition name, or
+ * equivalently SQLSTATE 'xxxxx', or a string
+ * literal that is the old-style message format,
+ * or USING to start the option list immediately.
+ */
+ if (tok == SCONST)
+ {
+ /* old style message and parameters */
+ new->message = yylval.str;
+ /*
+ * We expect either a semi-colon, which
+ * indicates no parameters, or a comma that
+ * begins the list of parameter expressions,
+ * or USING to begin the options list.
+ */
+ tok = yylex();
+ if (tok != ',' && tok != ';' && tok != K_USING)
+ yyerror("syntax error");
+
+ while (tok == ',')
+ {
+ PLpgSQL_expr *expr;
+
+ expr = read_sql_construct(',', ';', K_USING,
+ ", or ; or USING",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &tok);
+ new->params = lappend(new->params, expr);
+ }
+ }
+ else if (tok != K_USING)
+ {
+ /* must be condition name or SQLSTATE */
+ if (tok_is_keyword(tok, &yylval,
+ K_SQLSTATE, "sqlstate"))
+ {
+ /* next token should be a string literal */
+ char *sqlstatestr;
+
+ if (yylex() != SCONST)
+ yyerror("syntax error");
+ sqlstatestr = yylval.str;
+
+ if (strlen(sqlstatestr) != 5)
+ yyerror("invalid SQLSTATE code");
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ yyerror("invalid SQLSTATE code");
+ new->condname = sqlstatestr;
+ }
+ else
+ {
+ if (tok == T_WORD)
+ new->condname = yylval.word.ident;
+ else if (plpgsql_token_is_unreserved_keyword(tok))
+ new->condname = pstrdup(yylval.keyword);
+ else
+ yyerror("syntax error");
+ plpgsql_recognize_err_condition(new->condname,
+ false);
+ }
+ tok = yylex();
+ if (tok != ';' && tok != K_USING)
+ yyerror("syntax error");
+ }
+
+ if (tok == K_USING)
+ new->options = read_raise_options();
+ }
+
+ check_raise_parameters(new);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3919 "pl_gram.c"
+ break;
+
+ case 127: /* stmt_assert: K_ASSERT */
+#line 1956 "pl_gram.y"
+ {
+ PLpgSQL_stmt_assert *new;
+ int tok;
+
+ new = palloc(sizeof(PLpgSQL_stmt_assert));
+
+ new->cmd_type = PLPGSQL_STMT_ASSERT;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ new->cond = read_sql_expression2(',', ';',
+ ", or ;",
+ &tok);
+
+ if (tok == ',')
+ new->message = read_sql_expression(';', ";");
+ else
+ new->message = NULL;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 3945 "pl_gram.c"
+ break;
+
+ case 128: /* loop_body: proc_sect K_END K_LOOP opt_label ';' */
+#line 1980 "pl_gram.y"
+ {
+ (yyval.loop_body).stmts = (yyvsp[-4].list);
+ (yyval.loop_body).end_label = (yyvsp[-1].str);
+ (yyval.loop_body).end_label_location = (yylsp[-1]);
+ }
+#line 3955 "pl_gram.c"
+ break;
+
+ case 129: /* stmt_execsql: K_IMPORT */
+#line 1998 "pl_gram.y"
+ {
+ (yyval.stmt) = make_execsql_stmt(K_IMPORT, (yylsp[0]));
+ }
+#line 3963 "pl_gram.c"
+ break;
+
+ case 130: /* stmt_execsql: K_INSERT */
+#line 2002 "pl_gram.y"
+ {
+ (yyval.stmt) = make_execsql_stmt(K_INSERT, (yylsp[0]));
+ }
+#line 3971 "pl_gram.c"
+ break;
+
+ case 131: /* stmt_execsql: K_MERGE */
+#line 2006 "pl_gram.y"
+ {
+ (yyval.stmt) = make_execsql_stmt(K_MERGE, (yylsp[0]));
+ }
+#line 3979 "pl_gram.c"
+ break;
+
+ case 132: /* stmt_execsql: T_WORD */
+#line 2010 "pl_gram.y"
+ {
+ int tok;
+
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
+ word_is_not_variable(&((yyvsp[0].word)), (yylsp[0]));
+ (yyval.stmt) = make_execsql_stmt(T_WORD, (yylsp[0]));
+ }
+#line 3994 "pl_gram.c"
+ break;
+
+ case 133: /* stmt_execsql: T_CWORD */
+#line 2021 "pl_gram.y"
+ {
+ int tok;
+
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
+ cword_is_not_variable(&((yyvsp[0].cword)), (yylsp[0]));
+ (yyval.stmt) = make_execsql_stmt(T_CWORD, (yylsp[0]));
+ }
+#line 4009 "pl_gram.c"
+ break;
+
+ case 134: /* stmt_dynexecute: K_EXECUTE */
+#line 2034 "pl_gram.y"
+ {
+ PLpgSQL_stmt_dynexecute *new;
+ PLpgSQL_expr *expr;
+ int endtoken;
+
+ expr = read_sql_construct(K_INTO, K_USING, ';',
+ "INTO or USING or ;",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &endtoken);
+
+ new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
+ new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
+ new->lineno = plpgsql_location_to_lineno((yylsp[0]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->query = expr;
+ new->into = false;
+ new->strict = false;
+ new->target = NULL;
+ new->params = NIL;
+
+ /*
+ * We loop to allow the INTO and USING clauses to
+ * appear in either order, since people easily get
+ * that wrong. This coding also prevents "INTO foo"
+ * from getting absorbed into a USING expression,
+ * which is *really* confusing.
+ */
+ for (;;)
+ {
+ if (endtoken == K_INTO)
+ {
+ if (new->into) /* multiple INTO */
+ yyerror("syntax error");
+ new->into = true;
+ read_into_target(&new->target, &new->strict);
+ endtoken = yylex();
+ }
+ else if (endtoken == K_USING)
+ {
+ if (new->params) /* multiple USING */
+ yyerror("syntax error");
+ do
+ {
+ expr = read_sql_construct(',', ';', K_INTO,
+ ", or ; or INTO",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &endtoken);
+ new->params = lappend(new->params, expr);
+ } while (endtoken == ',');
+ }
+ else if (endtoken == ';')
+ break;
+ else
+ yyerror("syntax error");
+ }
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 4074 "pl_gram.c"
+ break;
+
+ case 135: /* stmt_open: K_OPEN cursor_variable */
+#line 2098 "pl_gram.y"
+ {
+ PLpgSQL_stmt_open *new;
+ int tok;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_open));
+ new->cmd_type = PLPGSQL_STMT_OPEN;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-1]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = (yyvsp[0].var)->dno;
+ new->cursor_options = CURSOR_OPT_FAST_PLAN;
+
+ if ((yyvsp[0].var)->cursor_explicit_expr == NULL)
+ {
+ /* be nice if we could use opt_scrollable here */
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_NO, "no"))
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
+ {
+ new->cursor_options |= CURSOR_OPT_NO_SCROLL;
+ tok = yylex();
+ }
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
+ {
+ new->cursor_options |= CURSOR_OPT_SCROLL;
+ tok = yylex();
+ }
+
+ if (tok != K_FOR)
+ yyerror("syntax error, expected \"FOR\"");
+
+ tok = yylex();
+ if (tok == K_EXECUTE)
+ {
+ int endtoken;
+
+ new->dynquery =
+ read_sql_expression2(K_USING, ';',
+ "USING or ;",
+ &endtoken);
+
+ /* If we found "USING", collect argument(s) */
+ if (endtoken == K_USING)
+ {
+ PLpgSQL_expr *expr;
+
+ do
+ {
+ expr = read_sql_expression2(',', ';',
+ ", or ;",
+ &endtoken);
+ new->params = lappend(new->params,
+ expr);
+ } while (endtoken == ',');
+ }
+ }
+ else
+ {
+ plpgsql_push_back_token(tok);
+ new->query = read_sql_stmt();
+ }
+ }
+ else
+ {
+ /* predefined cursor query, so read args */
+ new->argquery = read_cursor_args((yyvsp[0].var), ';');
+ }
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 4154 "pl_gram.c"
+ break;
+
+ case 136: /* stmt_fetch: K_FETCH opt_fetch_direction cursor_variable K_INTO */
+#line 2176 "pl_gram.y"
+ {
+ PLpgSQL_stmt_fetch *fetch = (yyvsp[-2].fetch);
+ PLpgSQL_variable *target;
+
+ /* We have already parsed everything through the INTO keyword */
+ read_into_target(&target, NULL);
+
+ if (yylex() != ';')
+ yyerror("syntax error");
+
+ /*
+ * We don't allow multiple rows in PL/pgSQL's FETCH
+ * statement, only in MOVE.
+ */
+ if (fetch->returns_multiple_rows)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("FETCH statement cannot return multiple rows"),
+ parser_errposition((yylsp[-3]))));
+
+ fetch->lineno = plpgsql_location_to_lineno((yylsp[-3]));
+ fetch->target = target;
+ fetch->curvar = (yyvsp[-1].var)->dno;
+ fetch->is_move = false;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) fetch;
+ }
+#line 4186 "pl_gram.c"
+ break;
+
+ case 137: /* stmt_move: K_MOVE opt_fetch_direction cursor_variable ';' */
+#line 2206 "pl_gram.y"
+ {
+ PLpgSQL_stmt_fetch *fetch = (yyvsp[-2].fetch);
+
+ fetch->lineno = plpgsql_location_to_lineno((yylsp[-3]));
+ fetch->curvar = (yyvsp[-1].var)->dno;
+ fetch->is_move = true;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) fetch;
+ }
+#line 4200 "pl_gram.c"
+ break;
+
+ case 138: /* opt_fetch_direction: %empty */
+#line 2218 "pl_gram.y"
+ {
+ (yyval.fetch) = read_fetch_direction();
+ }
+#line 4208 "pl_gram.c"
+ break;
+
+ case 139: /* stmt_close: K_CLOSE cursor_variable ';' */
+#line 2224 "pl_gram.y"
+ {
+ PLpgSQL_stmt_close *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_close));
+ new->cmd_type = PLPGSQL_STMT_CLOSE;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = (yyvsp[-1].var)->dno;
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 4224 "pl_gram.c"
+ break;
+
+ case 140: /* stmt_null: K_NULL ';' */
+#line 2238 "pl_gram.y"
+ {
+ /* We do not bother building a node for NULL */
+ (yyval.stmt) = NULL;
+ }
+#line 4233 "pl_gram.c"
+ break;
+
+ case 141: /* stmt_commit: K_COMMIT opt_transaction_chain ';' */
+#line 2245 "pl_gram.y"
+ {
+ PLpgSQL_stmt_commit *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_commit));
+ new->cmd_type = PLPGSQL_STMT_COMMIT;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->chain = (yyvsp[-1].ival);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 4249 "pl_gram.c"
+ break;
+
+ case 142: /* stmt_rollback: K_ROLLBACK opt_transaction_chain ';' */
+#line 2259 "pl_gram.y"
+ {
+ PLpgSQL_stmt_rollback *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_rollback));
+ new->cmd_type = PLPGSQL_STMT_ROLLBACK;
+ new->lineno = plpgsql_location_to_lineno((yylsp[-2]));
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->chain = (yyvsp[-1].ival);
+
+ (yyval.stmt) = (PLpgSQL_stmt *) new;
+ }
+#line 4265 "pl_gram.c"
+ break;
+
+ case 143: /* opt_transaction_chain: K_AND K_CHAIN */
+#line 2273 "pl_gram.y"
+ { (yyval.ival) = true; }
+#line 4271 "pl_gram.c"
+ break;
+
+ case 144: /* opt_transaction_chain: K_AND K_NO K_CHAIN */
+#line 2274 "pl_gram.y"
+ { (yyval.ival) = false; }
+#line 4277 "pl_gram.c"
+ break;
+
+ case 145: /* opt_transaction_chain: %empty */
+#line 2275 "pl_gram.y"
+ { (yyval.ival) = false; }
+#line 4283 "pl_gram.c"
+ break;
+
+ case 146: /* cursor_variable: T_DATUM */
+#line 2280 "pl_gram.y"
+ {
+ /*
+ * In principle we should support a cursor_variable
+ * that is an array element, but for now we don't, so
+ * just throw an error if next token is '['.
+ */
+ if ((yyvsp[0].wdatum).datum->dtype != PLPGSQL_DTYPE_VAR ||
+ plpgsql_peek() == '[')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cursor variable must be a simple variable"),
+ parser_errposition((yylsp[0]))));
+
+ if (((PLpgSQL_var *) (yyvsp[0].wdatum).datum)->datatype->typoid != REFCURSOROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" must be of type cursor or refcursor",
+ ((PLpgSQL_var *) (yyvsp[0].wdatum).datum)->refname),
+ parser_errposition((yylsp[0]))));
+ (yyval.var) = (PLpgSQL_var *) (yyvsp[0].wdatum).datum;
+ }
+#line 4309 "pl_gram.c"
+ break;
+
+ case 147: /* cursor_variable: T_WORD */
+#line 2302 "pl_gram.y"
+ {
+ /* just to give a better message than "syntax error" */
+ word_is_not_variable(&((yyvsp[0].word)), (yylsp[0]));
+ }
+#line 4318 "pl_gram.c"
+ break;
+
+ case 148: /* cursor_variable: T_CWORD */
+#line 2307 "pl_gram.y"
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&((yyvsp[0].cword)), (yylsp[0]));
+ }
+#line 4327 "pl_gram.c"
+ break;
+
+ case 149: /* exception_sect: %empty */
+#line 2314 "pl_gram.y"
+ { (yyval.exception_block) = NULL; }
+#line 4333 "pl_gram.c"
+ break;
+
+ case 150: /* @2: %empty */
+#line 2316 "pl_gram.y"
+ {
+ /*
+ * We use a mid-rule action to add these
+ * special variables to the namespace before
+ * parsing the WHEN clauses themselves. The
+ * scope of the names extends to the end of the
+ * current block.
+ */
+ int lineno = plpgsql_location_to_lineno((yylsp[0]));
+ PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
+ PLpgSQL_variable *var;
+
+ var = plpgsql_build_variable("sqlstate", lineno,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ plpgsql_curr_compile->fn_input_collation,
+ NULL),
+ true);
+ var->isconst = true;
+ new->sqlstate_varno = var->dno;
+
+ var = plpgsql_build_variable("sqlerrm", lineno,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ plpgsql_curr_compile->fn_input_collation,
+ NULL),
+ true);
+ var->isconst = true;
+ new->sqlerrm_varno = var->dno;
+
+ (yyval.exception_block) = new;
+ }
+#line 4370 "pl_gram.c"
+ break;
+
+ case 151: /* exception_sect: K_EXCEPTION @2 proc_exceptions */
+#line 2349 "pl_gram.y"
+ {
+ PLpgSQL_exception_block *new = (yyvsp[-1].exception_block);
+ new->exc_list = (yyvsp[0].list);
+
+ (yyval.exception_block) = new;
+ }
+#line 4381 "pl_gram.c"
+ break;
+
+ case 152: /* proc_exceptions: proc_exceptions proc_exception */
+#line 2358 "pl_gram.y"
+ {
+ (yyval.list) = lappend((yyvsp[-1].list), (yyvsp[0].exception));
+ }
+#line 4389 "pl_gram.c"
+ break;
+
+ case 153: /* proc_exceptions: proc_exception */
+#line 2362 "pl_gram.y"
+ {
+ (yyval.list) = list_make1((yyvsp[0].exception));
+ }
+#line 4397 "pl_gram.c"
+ break;
+
+ case 154: /* proc_exception: K_WHEN proc_conditions K_THEN proc_sect */
+#line 2368 "pl_gram.y"
+ {
+ PLpgSQL_exception *new;
+
+ new = palloc0(sizeof(PLpgSQL_exception));
+ new->lineno = plpgsql_location_to_lineno((yylsp[-3]));
+ new->conditions = (yyvsp[-2].condition);
+ new->action = (yyvsp[0].list);
+
+ (yyval.exception) = new;
+ }
+#line 4412 "pl_gram.c"
+ break;
+
+ case 155: /* proc_conditions: proc_conditions K_OR proc_condition */
+#line 2381 "pl_gram.y"
+ {
+ PLpgSQL_condition *old;
+
+ for (old = (yyvsp[-2].condition); old->next != NULL; old = old->next)
+ /* skip */ ;
+ old->next = (yyvsp[0].condition);
+ (yyval.condition) = (yyvsp[-2].condition);
+ }
+#line 4425 "pl_gram.c"
+ break;
+
+ case 156: /* proc_conditions: proc_condition */
+#line 2390 "pl_gram.y"
+ {
+ (yyval.condition) = (yyvsp[0].condition);
+ }
+#line 4433 "pl_gram.c"
+ break;
+
+ case 157: /* proc_condition: any_identifier */
+#line 2396 "pl_gram.y"
+ {
+ if (strcmp((yyvsp[0].str), "sqlstate") != 0)
+ {
+ (yyval.condition) = plpgsql_parse_err_condition((yyvsp[0].str));
+ }
+ else
+ {
+ PLpgSQL_condition *new;
+ char *sqlstatestr;
+
+ /* next token should be a string literal */
+ if (yylex() != SCONST)
+ yyerror("syntax error");
+ sqlstatestr = yylval.str;
+
+ if (strlen(sqlstatestr) != 5)
+ yyerror("invalid SQLSTATE code");
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ yyerror("invalid SQLSTATE code");
+
+ new = palloc(sizeof(PLpgSQL_condition));
+ new->sqlerrstate =
+ MAKE_SQLSTATE(sqlstatestr[0],
+ sqlstatestr[1],
+ sqlstatestr[2],
+ sqlstatestr[3],
+ sqlstatestr[4]);
+ new->condname = sqlstatestr;
+ new->next = NULL;
+
+ (yyval.condition) = new;
+ }
+ }
+#line 4471 "pl_gram.c"
+ break;
+
+ case 158: /* expr_until_semi: %empty */
+#line 2432 "pl_gram.y"
+ { (yyval.expr) = read_sql_expression(';', ";"); }
+#line 4477 "pl_gram.c"
+ break;
+
+ case 159: /* expr_until_then: %empty */
+#line 2436 "pl_gram.y"
+ { (yyval.expr) = read_sql_expression(K_THEN, "THEN"); }
+#line 4483 "pl_gram.c"
+ break;
+
+ case 160: /* expr_until_loop: %empty */
+#line 2440 "pl_gram.y"
+ { (yyval.expr) = read_sql_expression(K_LOOP, "LOOP"); }
+#line 4489 "pl_gram.c"
+ break;
+
+ case 161: /* opt_block_label: %empty */
+#line 2444 "pl_gram.y"
+ {
+ plpgsql_ns_push(NULL, PLPGSQL_LABEL_BLOCK);
+ (yyval.str) = NULL;
+ }
+#line 4498 "pl_gram.c"
+ break;
+
+ case 162: /* opt_block_label: LESS_LESS any_identifier GREATER_GREATER */
+#line 2449 "pl_gram.y"
+ {
+ plpgsql_ns_push((yyvsp[-1].str), PLPGSQL_LABEL_BLOCK);
+ (yyval.str) = (yyvsp[-1].str);
+ }
+#line 4507 "pl_gram.c"
+ break;
+
+ case 163: /* opt_loop_label: %empty */
+#line 2456 "pl_gram.y"
+ {
+ plpgsql_ns_push(NULL, PLPGSQL_LABEL_LOOP);
+ (yyval.str) = NULL;
+ }
+#line 4516 "pl_gram.c"
+ break;
+
+ case 164: /* opt_loop_label: LESS_LESS any_identifier GREATER_GREATER */
+#line 2461 "pl_gram.y"
+ {
+ plpgsql_ns_push((yyvsp[-1].str), PLPGSQL_LABEL_LOOP);
+ (yyval.str) = (yyvsp[-1].str);
+ }
+#line 4525 "pl_gram.c"
+ break;
+
+ case 165: /* opt_label: %empty */
+#line 2468 "pl_gram.y"
+ {
+ (yyval.str) = NULL;
+ }
+#line 4533 "pl_gram.c"
+ break;
+
+ case 166: /* opt_label: any_identifier */
+#line 2472 "pl_gram.y"
+ {
+ /* label validity will be checked by outer production */
+ (yyval.str) = (yyvsp[0].str);
+ }
+#line 4542 "pl_gram.c"
+ break;
+
+ case 167: /* opt_exitcond: ';' */
+#line 2479 "pl_gram.y"
+ { (yyval.expr) = NULL; }
+#line 4548 "pl_gram.c"
+ break;
+
+ case 168: /* opt_exitcond: K_WHEN expr_until_semi */
+#line 2481 "pl_gram.y"
+ { (yyval.expr) = (yyvsp[0].expr); }
+#line 4554 "pl_gram.c"
+ break;
+
+ case 169: /* any_identifier: T_WORD */
+#line 2488 "pl_gram.y"
+ {
+ (yyval.str) = (yyvsp[0].word).ident;
+ }
+#line 4562 "pl_gram.c"
+ break;
+
+ case 170: /* any_identifier: unreserved_keyword */
+#line 2492 "pl_gram.y"
+ {
+ (yyval.str) = pstrdup((yyvsp[0].keyword));
+ }
+#line 4570 "pl_gram.c"
+ break;
+
+ case 171: /* any_identifier: T_DATUM */
+#line 2496 "pl_gram.y"
+ {
+ if ((yyvsp[0].wdatum).ident == NULL) /* composite name not OK */
+ yyerror("syntax error");
+ (yyval.str) = (yyvsp[0].wdatum).ident;
+ }
+#line 4580 "pl_gram.c"
+ break;
+
+
+#line 4584 "pl_gram.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;
+ *++yylsp = yyloc;
+
+ /* Now 'shift' the result of the reduction. Determine what state
+ that goes to, based on the state we popped back to and the rule
+ number reduced by. */
+ {
+ const int yylhs = yyr1[yyn] - YYNTOKENS;
+ const int yyi = yypgoto[yylhs] + *yyssp;
+ yystate = (0 <= yyi && yyi <= YYLAST && yycheck[yyi] == *yyssp
+ ? yytable[yyi]
+ : yydefgoto[yylhs]);
+ }
+
+ goto yynewstate;
+
+
+/*--------------------------------------.
+| yyerrlab -- here on detecting error. |
+`--------------------------------------*/
+yyerrlab:
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = yychar == YYEMPTY ? YYSYMBOL_YYEMPTY : YYTRANSLATE (yychar);
+ /* If not already recovering from an error, report this error. */
+ if (!yyerrstatus)
+ {
+ ++yynerrs;
+ yyerror (YY_("syntax error"));
+ }
+
+ yyerror_range[1] = yylloc;
+ 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, &yylloc);
+ 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;
+
+ yyerror_range[1] = *yylsp;
+ yydestruct ("Error: popping",
+ YY_ACCESSING_SYMBOL (yystate), yyvsp, yylsp);
+ YYPOPSTACK (1);
+ yystate = *yyssp;
+ YY_STACK_PRINT (yyss, yyssp);
+ }
+
+ YY_IGNORE_MAYBE_UNINITIALIZED_BEGIN
+ *++yyvsp = yylval;
+ YY_IGNORE_MAYBE_UNINITIALIZED_END
+
+ yyerror_range[2] = yylloc;
+ ++yylsp;
+ YYLLOC_DEFAULT (*yylsp, yyerror_range, 2);
+
+ /* Shift the error token. */
+ YY_SYMBOL_PRINT ("Shifting", YY_ACCESSING_SYMBOL (yyn), yyvsp, yylsp);
+
+ yystate = yyn;
+ goto yynewstate;
+
+
+/*-------------------------------------.
+| yyacceptlab -- YYACCEPT comes here. |
+`-------------------------------------*/
+yyacceptlab:
+ yyresult = 0;
+ goto yyreturn;
+
+
+/*-----------------------------------.
+| yyabortlab -- YYABORT comes here. |
+`-----------------------------------*/
+yyabortlab:
+ yyresult = 1;
+ goto yyreturn;
+
+
+#if !defined yyoverflow
+/*-------------------------------------------------.
+| yyexhaustedlab -- memory exhaustion comes here. |
+`-------------------------------------------------*/
+yyexhaustedlab:
+ yyerror (YY_("memory exhausted"));
+ yyresult = 2;
+ goto yyreturn;
+#endif
+
+
+/*-------------------------------------------------------.
+| yyreturn -- parsing is finished, clean up and return. |
+`-------------------------------------------------------*/
+yyreturn:
+ if (yychar != YYEMPTY)
+ {
+ /* Make sure we have latest lookahead translation. See comments at
+ user semantic actions for why this is necessary. */
+ yytoken = YYTRANSLATE (yychar);
+ yydestruct ("Cleanup: discarding lookahead",
+ yytoken, &yylval, &yylloc);
+ }
+ /* 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, yylsp);
+ YYPOPSTACK (1);
+ }
+#ifndef yyoverflow
+ if (yyss != yyssa)
+ YYSTACK_FREE (yyss);
+#endif
+
+ return yyresult;
+}
+
+#line 2587 "pl_gram.y"
+
+
+/*
+ * Check whether a token represents an "unreserved keyword".
+ * We have various places where we want to recognize a keyword in preference
+ * to a variable name, but not reserve that keyword in other contexts.
+ * Hence, this kluge.
+ */
+static bool
+tok_is_keyword(int token, union YYSTYPE *lval,
+ int kw_token, const char *kw_str)
+{
+ if (token == kw_token)
+ {
+ /* Normal case, was recognized by scanner (no conflicting variable) */
+ return true;
+ }
+ else if (token == T_DATUM)
+ {
+ /*
+ * It's a variable, so recheck the string name. Note we will not
+ * match composite names (hence an unreserved word followed by "."
+ * will not be recognized).
+ */
+ if (!lval->wdatum.quoted && lval->wdatum.ident != NULL &&
+ strcmp(lval->wdatum.ident, kw_str) == 0)
+ return true;
+ }
+ return false; /* not the keyword */
+}
+
+/*
+ * Convenience routine to complain when we expected T_DATUM and got T_WORD,
+ * ie, unrecognized variable.
+ */
+static void
+word_is_not_variable(PLword *word, int location)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a known variable",
+ word->ident),
+ parser_errposition(location)));
+}
+
+/* Same, for a CWORD */
+static void
+cword_is_not_variable(PLcword *cword, int location)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a known variable",
+ NameListToString(cword->idents)),
+ parser_errposition(location)));
+}
+
+/*
+ * Convenience routine to complain when we expected T_DATUM and got
+ * something else. "tok" must be the current token, since we also
+ * look at yylval and yylloc.
+ */
+static void
+current_token_is_not_variable(int tok)
+{
+ if (tok == T_WORD)
+ word_is_not_variable(&(yylval.word), yylloc);
+ else if (tok == T_CWORD)
+ cword_is_not_variable(&(yylval.cword), yylloc);
+ else
+ yyerror("syntax error");
+}
+
+/* Convenience routine to read an expression with one possible terminator */
+static PLpgSQL_expr *
+read_sql_expression(int until, const char *expected)
+{
+ return read_sql_construct(until, 0, 0, expected,
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, NULL);
+}
+
+/* Convenience routine to read an expression with two possible terminators */
+static PLpgSQL_expr *
+read_sql_expression2(int until, int until2, const char *expected,
+ int *endtoken)
+{
+ return read_sql_construct(until, until2, 0, expected,
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, endtoken);
+}
+
+/* Convenience routine to read a SQL statement that must end with ';' */
+static PLpgSQL_expr *
+read_sql_stmt(void)
+{
+ return read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, true, true, NULL, NULL);
+}
+
+/*
+ * Read a SQL construct and build a PLpgSQL_expr for it.
+ *
+ * until: token code for expected terminator
+ * until2: token code for alternate terminator (pass 0 if none)
+ * until3: token code for another alternate terminator (pass 0 if none)
+ * expected: text to use in complaining that terminator was not found
+ * parsemode: raw_parser() mode to use
+ * isexpression: whether to say we're reading an "expression" or a "statement"
+ * valid_sql: whether to check the syntax of the expr
+ * trim: trim trailing whitespace
+ * startloc: if not NULL, location of first token is stored at *startloc
+ * endtoken: if not NULL, ending token is stored at *endtoken
+ * (this is only interesting if until2 or until3 isn't zero)
+ */
+static PLpgSQL_expr *
+read_sql_construct(int until,
+ int until2,
+ int until3,
+ const char *expected,
+ RawParseMode parsemode,
+ bool isexpression,
+ bool valid_sql,
+ bool trim,
+ int *startloc,
+ int *endtoken)
+{
+ int tok;
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ int startlocation = -1;
+ int parenlevel = 0;
+ PLpgSQL_expr *expr;
+
+ initStringInfo(&ds);
+
+ /* special lookup mode for identifiers within the SQL text */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+
+ for (;;)
+ {
+ tok = yylex();
+ if (startlocation < 0) /* remember loc of first token */
+ startlocation = yylloc;
+ if (tok == until && parenlevel == 0)
+ break;
+ if (tok == until2 && parenlevel == 0)
+ break;
+ if (tok == until3 && parenlevel == 0)
+ break;
+ if (tok == '(' || tok == '[')
+ parenlevel++;
+ else if (tok == ')' || tok == ']')
+ {
+ parenlevel--;
+ if (parenlevel < 0)
+ yyerror("mismatched parentheses");
+ }
+ /*
+ * End of function definition is an error, and we don't expect to
+ * hit a semicolon either (unless it's the until symbol, in which
+ * case we should have fallen out above).
+ */
+ if (tok == 0 || tok == ';')
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ if (isexpression)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL expression",
+ expected),
+ parser_errposition(yylloc)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL statement",
+ expected),
+ parser_errposition(yylloc)));
+ }
+ }
+
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ if (startloc)
+ *startloc = startlocation;
+ if (endtoken)
+ *endtoken = tok;
+
+ /* give helpful complaint about empty input */
+ if (startlocation >= yylloc)
+ {
+ if (isexpression)
+ yyerror("missing expression");
+ else
+ yyerror("missing SQL statement");
+ }
+
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+
+ /* trim any trailing whitespace, for neatness */
+ if (trim)
+ {
+ while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+ ds.data[--ds.len] = '\0';
+ }
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = parsemode;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ if (valid_sql)
+ check_sql_expr(expr->query, expr->parseMode, startlocation);
+
+ return expr;
+}
+
+static PLpgSQL_type *
+read_datatype(int tok)
+{
+ StringInfoData ds;
+ char *type_name;
+ int startlocation;
+ PLpgSQL_type *result;
+ int parenlevel = 0;
+
+ /* Should only be called while parsing DECLARE sections */
+ Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);
+
+ /* Often there will be a lookahead token, but if not, get one */
+ if (tok == YYEMPTY)
+ tok = yylex();
+
+ startlocation = yylloc;
+
+ /*
+ * If we have a simple or composite identifier, check for %TYPE
+ * and %ROWTYPE constructs.
+ */
+ if (tok == T_WORD)
+ {
+ char *dtname = yylval.word.ident;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (plpgsql_token_is_unreserved_keyword(tok))
+ {
+ char *dtname = pstrdup(yylval.keyword);
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (tok == T_CWORD)
+ {
+ List *dtnames = yylval.cword.idents;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_cwordtype(dtnames);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_cwordrowtype(dtnames);
+ if (result)
+ return result;
+ }
+ }
+ }
+
+ while (tok != ';')
+ {
+ if (tok == 0)
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ else
+ yyerror("incomplete data type declaration");
+ }
+ /* Possible followers for datatype in a declaration */
+ if (tok == K_COLLATE || tok == K_NOT ||
+ tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT)
+ break;
+ /* Possible followers for datatype in a cursor_arg list */
+ if ((tok == ',' || tok == ')') && parenlevel == 0)
+ break;
+ if (tok == '(')
+ parenlevel++;
+ else if (tok == ')')
+ parenlevel--;
+
+ tok = yylex();
+ }
+
+ /* set up ds to contain complete typename text */
+ initStringInfo(&ds);
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+ type_name = ds.data;
+
+ if (type_name[0] == '\0')
+ yyerror("missing data type declaration");
+
+ result = parse_datatype(type_name, startlocation);
+
+ pfree(ds.data);
+
+ plpgsql_push_back_token(tok);
+
+ return result;
+}
+
+static PLpgSQL_stmt *
+make_execsql_stmt(int firsttoken, int location)
+{
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ PLpgSQL_stmt_execsql *execsql;
+ PLpgSQL_expr *expr;
+ PLpgSQL_variable *target = NULL;
+ int tok;
+ int prev_tok;
+ bool have_into = false;
+ bool have_strict = false;
+ int into_start_loc = -1;
+ int into_end_loc = -1;
+
+ initStringInfo(&ds);
+
+ /* special lookup mode for identifiers within the SQL text */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+
+ /*
+ * Scan to the end of the SQL command. Identify any INTO-variables
+ * clause lurking within it, and parse that via read_into_target().
+ *
+ * Because INTO is sometimes used in the main SQL grammar, we have to be
+ * careful not to take any such usage of INTO as a PL/pgSQL INTO clause.
+ * There are currently three such cases:
+ *
+ * 1. SELECT ... INTO. We don't care, we just override that with the
+ * PL/pgSQL definition.
+ *
+ * 2. INSERT INTO. This is relatively easy to recognize since the words
+ * must appear adjacently; but we can't assume INSERT starts the command,
+ * because it can appear in CREATE RULE or WITH. Unfortunately, INSERT is
+ * *not* fully reserved, so that means there is a chance of a false match;
+ * but it's not very likely.
+ *
+ * 3. IMPORT FOREIGN SCHEMA ... INTO. This is not allowed in CREATE RULE
+ * or WITH, so we just check for IMPORT as the command's first token.
+ * (If IMPORT FOREIGN SCHEMA returned data someone might wish to capture
+ * with an INTO-variables clause, we'd have to work much harder here.)
+ *
+ * Fortunately, INTO is a fully reserved word in the main grammar, so
+ * at least we need not worry about it appearing as an identifier.
+ *
+ * Any future additional uses of INTO in the main grammar will doubtless
+ * break this logic again ... beware!
+ */
+ tok = firsttoken;
+ for (;;)
+ {
+ prev_tok = tok;
+ tok = yylex();
+ if (have_into && into_end_loc < 0)
+ into_end_loc = yylloc; /* token after the INTO part */
+ if (tok == ';')
+ break;
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+ if (tok == K_INTO)
+ {
+ if (prev_tok == K_INSERT)
+ continue; /* INSERT INTO is not an INTO-target */
+ if (prev_tok == K_MERGE)
+ continue; /* MERGE INTO is not an INTO-target */
+ if (firsttoken == K_IMPORT)
+ continue; /* IMPORT ... INTO is not an INTO-target */
+ if (have_into)
+ yyerror("INTO specified more than once");
+ have_into = true;
+ into_start_loc = yylloc;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ read_into_target(&target, &have_strict);
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+ }
+ }
+
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ if (have_into)
+ {
+ /*
+ * Insert an appropriate number of spaces corresponding to the
+ * INTO text, so that locations within the redacted SQL statement
+ * still line up with those in the original source text.
+ */
+ plpgsql_append_source_text(&ds, location, into_start_loc);
+ appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
+ plpgsql_append_source_text(&ds, into_end_loc, yylloc);
+ }
+ else
+ plpgsql_append_source_text(&ds, location, yylloc);
+
+ /* trim any trailing whitespace, for neatness */
+ while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+ ds.data[--ds.len] = '\0';
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_DEFAULT;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ check_sql_expr(expr->query, expr->parseMode, location);
+
+ execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
+ execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
+ execsql->lineno = plpgsql_location_to_lineno(location);
+ execsql->stmtid = ++plpgsql_curr_compile->nstatements;
+ execsql->sqlstmt = expr;
+ execsql->into = have_into;
+ execsql->strict = have_strict;
+ execsql->target = target;
+
+ return (PLpgSQL_stmt *) execsql;
+}
+
+
+/*
+ * Read FETCH or MOVE direction clause (everything through FROM/IN).
+ */
+static PLpgSQL_stmt_fetch *
+read_fetch_direction(void)
+{
+ PLpgSQL_stmt_fetch *fetch;
+ int tok;
+ bool check_FROM = true;
+
+ /*
+ * We create the PLpgSQL_stmt_fetch struct here, but only fill in
+ * the fields arising from the optional direction clause
+ */
+ fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch));
+ fetch->cmd_type = PLPGSQL_STMT_FETCH;
+ fetch->stmtid = ++plpgsql_curr_compile->nstatements;
+ /* set direction defaults: */
+ fetch->direction = FETCH_FORWARD;
+ fetch->how_many = 1;
+ fetch->expr = NULL;
+ fetch->returns_multiple_rows = false;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
+ {
+ /* use defaults */
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_PRIOR, "prior"))
+ {
+ fetch->direction = FETCH_BACKWARD;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_FIRST, "first"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_LAST, "last"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ fetch->how_many = -1;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ABSOLUTE, "absolute"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ check_FROM = false;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_RELATIVE, "relative"))
+ {
+ fetch->direction = FETCH_RELATIVE;
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ check_FROM = false;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ALL, "all"))
+ {
+ fetch->how_many = FETCH_ALL;
+ fetch->returns_multiple_rows = true;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_FORWARD, "forward"))
+ {
+ complete_direction(fetch, &check_FROM);
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_BACKWARD, "backward"))
+ {
+ fetch->direction = FETCH_BACKWARD;
+ complete_direction(fetch, &check_FROM);
+ }
+ else if (tok == K_FROM || tok == K_IN)
+ {
+ /* empty direction */
+ check_FROM = false;
+ }
+ else if (tok == T_DATUM)
+ {
+ /* Assume there's no direction clause and tok is a cursor name */
+ plpgsql_push_back_token(tok);
+ check_FROM = false;
+ }
+ else
+ {
+ /*
+ * Assume it's a count expression with no preceding keyword.
+ * Note: we allow this syntax because core SQL does, but we don't
+ * document it because of the ambiguity with the omitted-direction
+ * case. For instance, "MOVE n IN c" will fail if n is a variable.
+ * Perhaps this can be improved someday, but it's hardly worth a
+ * lot of work.
+ */
+ plpgsql_push_back_token(tok);
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ fetch->returns_multiple_rows = true;
+ check_FROM = false;
+ }
+
+ /* check FROM or IN keyword after direction's specification */
+ if (check_FROM)
+ {
+ tok = yylex();
+ if (tok != K_FROM && tok != K_IN)
+ yyerror("expected FROM or IN");
+ }
+
+ return fetch;
+}
+
+/*
+ * Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD.
+ * Allows these cases:
+ * FORWARD expr, FORWARD ALL, FORWARD
+ * BACKWARD expr, BACKWARD ALL, BACKWARD
+ */
+static void
+complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM)
+{
+ int tok;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok == K_FROM || tok == K_IN)
+ {
+ *check_FROM = false;
+ return;
+ }
+
+ if (tok == K_ALL)
+ {
+ fetch->how_many = FETCH_ALL;
+ fetch->returns_multiple_rows = true;
+ *check_FROM = true;
+ return;
+ }
+
+ plpgsql_push_back_token(tok);
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ fetch->returns_multiple_rows = true;
+ *check_FROM = false;
+}
+
+
+static PLpgSQL_stmt *
+make_return_stmt(int location)
+{
+ PLpgSQL_stmt_return *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return));
+ new->cmd_type = PLPGSQL_STMT_RETURN;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->expr = NULL;
+ new->retvarno = -1;
+
+ if (plpgsql_curr_compile->fn_retset)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function returning set"),
+ errhint("Use RETURN NEXT or RETURN QUERY."),
+ parser_errposition(yylloc)));
+ }
+ else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
+ {
+ if (yylex() != ';')
+ {
+ if (plpgsql_curr_compile->fn_prokind == PROKIND_PROCEDURE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RETURN cannot have a parameter in a procedure"),
+ parser_errposition(yylloc)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function returning void"),
+ parser_errposition(yylloc)));
+ }
+ }
+ else if (plpgsql_curr_compile->out_param_varno >= 0)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function with OUT parameters"),
+ parser_errposition(yylloc)));
+ new->retvarno = plpgsql_curr_compile->out_param_varno;
+ }
+ else
+ {
+ /*
+ * We want to special-case simple variable references for efficiency.
+ * So peek ahead to see if that's what we have.
+ */
+ int tok = yylex();
+
+ if (tok == T_DATUM && plpgsql_peek() == ';' &&
+ (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
+ {
+ new->retvarno = yylval.wdatum.datum->dno;
+ /* eat the semicolon token that we only peeked at above */
+ tok = yylex();
+ Assert(tok == ';');
+ }
+ else
+ {
+ /*
+ * Not (just) a variable name, so treat as expression.
+ *
+ * Note that a well-formed expression is _required_ here;
+ * anything else is a compile-time error.
+ */
+ plpgsql_push_back_token(tok);
+ new->expr = read_sql_expression(';', ";");
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+static PLpgSQL_stmt *
+make_return_next_stmt(int location)
+{
+ PLpgSQL_stmt_return_next *new;
+
+ if (!plpgsql_curr_compile->fn_retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURN NEXT in a non-SETOF function"),
+ parser_errposition(location)));
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return_next));
+ new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->expr = NULL;
+ new->retvarno = -1;
+
+ if (plpgsql_curr_compile->out_param_varno >= 0)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
+ parser_errposition(yylloc)));
+ new->retvarno = plpgsql_curr_compile->out_param_varno;
+ }
+ else
+ {
+ /*
+ * We want to special-case simple variable references for efficiency.
+ * So peek ahead to see if that's what we have.
+ */
+ int tok = yylex();
+
+ if (tok == T_DATUM && plpgsql_peek() == ';' &&
+ (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
+ {
+ new->retvarno = yylval.wdatum.datum->dno;
+ /* eat the semicolon token that we only peeked at above */
+ tok = yylex();
+ Assert(tok == ';');
+ }
+ else
+ {
+ /*
+ * Not (just) a variable name, so treat as expression.
+ *
+ * Note that a well-formed expression is _required_ here;
+ * anything else is a compile-time error.
+ */
+ plpgsql_push_back_token(tok);
+ new->expr = read_sql_expression(';', ";");
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+static PLpgSQL_stmt *
+make_return_query_stmt(int location)
+{
+ PLpgSQL_stmt_return_query *new;
+ int tok;
+
+ if (!plpgsql_curr_compile->fn_retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURN QUERY in a non-SETOF function"),
+ parser_errposition(location)));
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return_query));
+ new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ /* check for RETURN QUERY EXECUTE */
+ if ((tok = yylex()) != K_EXECUTE)
+ {
+ /* ordinary static query */
+ plpgsql_push_back_token(tok);
+ new->query = read_sql_stmt();
+ }
+ else
+ {
+ /* dynamic SQL */
+ int term;
+
+ new->dynquery = read_sql_expression2(';', K_USING, "; or USING",
+ &term);
+ if (term == K_USING)
+ {
+ do
+ {
+ PLpgSQL_expr *expr;
+
+ expr = read_sql_expression2(',', ';', ", or ;", &term);
+ new->params = lappend(new->params, expr);
+ } while (term == ',');
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+/* convenience routine to fetch the name of a T_DATUM */
+static char *
+NameOfDatum(PLwdatum *wdatum)
+{
+ if (wdatum->ident)
+ return wdatum->ident;
+ Assert(wdatum->idents != NIL);
+ return NameListToString(wdatum->idents);
+}
+
+static void
+check_assignable(PLpgSQL_datum *datum, int location)
+{
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ case PLPGSQL_DTYPE_REC:
+ if (((PLpgSQL_variable *) datum)->isconst)
+ ereport(ERROR,
+ (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+ errmsg("variable \"%s\" is declared CONSTANT",
+ ((PLpgSQL_variable *) datum)->refname),
+ parser_errposition(location)));
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ /* always assignable; member vars were checked at compile time */
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ /* assignable if parent record is */
+ check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
+ location);
+ break;
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ break;
+ }
+}
+
+/*
+ * Read the argument of an INTO clause. On entry, we have just read the
+ * INTO keyword.
+ */
+static void
+read_into_target(PLpgSQL_variable **target, bool *strict)
+{
+ int tok;
+
+ /* Set default results */
+ *target = NULL;
+ if (strict)
+ *strict = false;
+
+ tok = yylex();
+ if (strict && tok == K_STRICT)
+ {
+ *strict = true;
+ tok = yylex();
+ }
+
+ /*
+ * Currently, a row or record variable can be the single INTO target,
+ * but not a member of a multi-target list. So we throw error if there
+ * is a comma after it, because that probably means the user tried to
+ * write a multi-target list. If this ever gets generalized, we should
+ * probably refactor read_into_scalar_list so it handles all cases.
+ */
+ switch (tok)
+ {
+ case T_DATUM:
+ if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+ {
+ check_assignable(yylval.wdatum.datum, yylloc);
+ *target = (PLpgSQL_variable *) yylval.wdatum.datum;
+
+ if ((tok = yylex()) == ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("record variable cannot be part of multiple-item INTO list"),
+ parser_errposition(yylloc)));
+ plpgsql_push_back_token(tok);
+ }
+ else
+ {
+ *target = (PLpgSQL_variable *)
+ read_into_scalar_list(NameOfDatum(&(yylval.wdatum)),
+ yylval.wdatum.datum, yylloc);
+ }
+ break;
+
+ default:
+ /* just to give a better message than "syntax error" */
+ current_token_is_not_variable(tok);
+ }
+}
+
+/*
+ * Given the first datum and name in the INTO list, continue to read
+ * comma-separated scalar variables until we run out. Then construct
+ * and return a fake "row" variable that represents the list of
+ * scalars.
+ */
+static PLpgSQL_row *
+read_into_scalar_list(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int initial_location)
+{
+ int nfields;
+ char *fieldnames[1024];
+ int varnos[1024];
+ PLpgSQL_row *row;
+ int tok;
+
+ check_assignable(initial_datum, initial_location);
+ fieldnames[0] = initial_name;
+ varnos[0] = initial_datum->dno;
+ nfields = 1;
+
+ while ((tok = yylex()) == ',')
+ {
+ /* Check for array overflow */
+ if (nfields >= 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many INTO variables specified"),
+ parser_errposition(yylloc)));
+
+ tok = yylex();
+ switch (tok)
+ {
+ case T_DATUM:
+ check_assignable(yylval.wdatum.datum, yylloc);
+ if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a scalar variable",
+ NameOfDatum(&(yylval.wdatum))),
+ parser_errposition(yylloc)));
+ fieldnames[nfields] = NameOfDatum(&(yylval.wdatum));
+ varnos[nfields++] = yylval.wdatum.datum->dno;
+ break;
+
+ default:
+ /* just to give a better message than "syntax error" */
+ current_token_is_not_variable(tok);
+ }
+ }
+
+ /*
+ * We read an extra, non-comma token from yylex(), so push it
+ * back onto the input stream
+ */
+ plpgsql_push_back_token(tok);
+
+ row = palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = plpgsql_location_to_lineno(initial_location);
+ row->rowtupdesc = NULL;
+ row->nfields = nfields;
+ row->fieldnames = palloc(sizeof(char *) * nfields);
+ row->varnos = palloc(sizeof(int) * nfields);
+ while (--nfields >= 0)
+ {
+ row->fieldnames[nfields] = fieldnames[nfields];
+ row->varnos[nfields] = varnos[nfields];
+ }
+
+ plpgsql_adddatum((PLpgSQL_datum *) row);
+
+ return row;
+}
+
+/*
+ * Convert a single scalar into a "row" list. This is exactly
+ * like read_into_scalar_list except we never consume any input.
+ *
+ * Note: lineno could be computed from location, but since callers
+ * have it at hand already, we may as well pass it in.
+ */
+static PLpgSQL_row *
+make_scalar_list1(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int lineno, int location)
+{
+ PLpgSQL_row *row;
+
+ check_assignable(initial_datum, location);
+
+ row = palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = lineno;
+ row->rowtupdesc = NULL;
+ row->nfields = 1;
+ row->fieldnames = palloc(sizeof(char *));
+ row->varnos = palloc(sizeof(int));
+ row->fieldnames[0] = initial_name;
+ row->varnos[0] = initial_datum->dno;
+
+ plpgsql_adddatum((PLpgSQL_datum *) row);
+
+ return row;
+}
+
+/*
+ * When the PL/pgSQL parser expects to see a SQL statement, it is very
+ * liberal in what it accepts; for example, we often assume an
+ * unrecognized keyword is the beginning of a SQL statement. This
+ * avoids the need to duplicate parts of the SQL grammar in the
+ * PL/pgSQL grammar, but it means we can accept wildly malformed
+ * input. To try and catch some of the more obviously invalid input,
+ * we run the strings we expect to be SQL statements through the main
+ * SQL parser.
+ *
+ * We only invoke the raw parser (not the analyzer); this doesn't do
+ * any database access and does not check any semantic rules, it just
+ * checks for basic syntactic correctness. We do this here, rather
+ * than after parsing has finished, because a malformed SQL statement
+ * may cause the PL/pgSQL parser to become confused about statement
+ * borders. So it is best to bail out as early as we can.
+ *
+ * It is assumed that "stmt" represents a copy of the function source text
+ * beginning at offset "location". We use this assumption to transpose
+ * any error cursor position back to the function source text.
+ * If no error cursor is provided, we'll just point at "location".
+ */
+static void
+check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
+{
+ sql_error_callback_arg cbarg;
+ ErrorContextCallback syntax_errcontext;
+ MemoryContext oldCxt;
+
+ if (!plpgsql_check_syntax)
+ return;
+
+ cbarg.location = location;
+
+ syntax_errcontext.callback = plpgsql_sql_error_callback;
+ syntax_errcontext.arg = &cbarg;
+ syntax_errcontext.previous = error_context_stack;
+ error_context_stack = &syntax_errcontext;
+
+ oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+ (void) raw_parser(stmt, parseMode);
+ MemoryContextSwitchTo(oldCxt);
+
+ /* Restore former ereport callback */
+ error_context_stack = syntax_errcontext.previous;
+}
+
+static void
+plpgsql_sql_error_callback(void *arg)
+{
+ sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg;
+ int errpos;
+
+ /*
+ * First, set up internalerrposition to point to the start of the
+ * statement text within the function text. Note this converts
+ * location (a byte offset) to a character number.
+ */
+ parser_errposition(cbarg->location);
+
+ /*
+ * If the core parser provided an error position, transpose it.
+ * Note we are dealing with 1-based character numbers at this point.
+ */
+ errpos = geterrposition();
+ if (errpos > 0)
+ {
+ int myerrpos = getinternalerrposition();
+
+ if (myerrpos > 0) /* safety check */
+ internalerrposition(myerrpos + errpos - 1);
+ }
+
+ /* In any case, flush errposition --- we want internalerrposition only */
+ errposition(0);
+}
+
+/*
+ * Parse a SQL datatype name and produce a PLpgSQL_type structure.
+ *
+ * The heavy lifting is done elsewhere. Here we are only concerned
+ * with setting up an errcontext link that will let us give an error
+ * cursor pointing into the plpgsql function source, if necessary.
+ * This is handled the same as in check_sql_expr(), and we likewise
+ * expect that the given string is a copy from the source text.
+ */
+static PLpgSQL_type *
+parse_datatype(const char *string, int location)
+{
+ TypeName *typeName;
+ Oid type_id;
+ int32 typmod;
+ sql_error_callback_arg cbarg;
+ ErrorContextCallback syntax_errcontext;
+
+ cbarg.location = location;
+
+ syntax_errcontext.callback = plpgsql_sql_error_callback;
+ syntax_errcontext.arg = &cbarg;
+ syntax_errcontext.previous = error_context_stack;
+ error_context_stack = &syntax_errcontext;
+
+ /* Let the main parser try to parse it under standard SQL rules */
+ typeName = typeStringToTypeName(string);
+ typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod);
+
+ /* Restore former ereport callback */
+ error_context_stack = syntax_errcontext.previous;
+
+ /* Okay, build a PLpgSQL_type data structure for it */
+ return plpgsql_build_datatype(type_id, typmod,
+ plpgsql_curr_compile->fn_input_collation,
+ typeName);
+}
+
+/*
+ * Check block starting and ending labels match.
+ */
+static void
+check_labels(const char *start_label, const char *end_label, int end_location)
+{
+ if (end_label)
+ {
+ if (!start_label)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("end label \"%s\" specified for unlabeled block",
+ end_label),
+ parser_errposition(end_location)));
+
+ if (strcmp(start_label, end_label) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("end label \"%s\" differs from block's label \"%s\"",
+ end_label, start_label),
+ parser_errposition(end_location)));
+ }
+}
+
+/*
+ * Read the arguments (if any) for a cursor, followed by the until token
+ *
+ * If cursor has no args, just swallow the until token and return NULL.
+ * If it does have args, we expect to see "( arg [, arg ...] )" followed
+ * by the until token, where arg may be a plain expression, or a named
+ * parameter assignment of the form argname := expr. Consume all that and
+ * return a SELECT query that evaluates the expression(s) (without the outer
+ * parens).
+ */
+static PLpgSQL_expr *
+read_cursor_args(PLpgSQL_var *cursor, int until)
+{
+ PLpgSQL_expr *expr;
+ PLpgSQL_row *row;
+ int tok;
+ int argc;
+ char **argv;
+ StringInfoData ds;
+ bool any_named = false;
+
+ tok = yylex();
+ if (cursor->cursor_explicit_argrow < 0)
+ {
+ /* No arguments expected */
+ if (tok == '(')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has no arguments",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ if (tok != until)
+ yyerror("syntax error");
+
+ return NULL;
+ }
+
+ /* Else better provide arguments */
+ if (tok != '(')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has arguments",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ /*
+ * Read the arguments, one by one.
+ */
+ row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow];
+ argv = (char **) palloc0(row->nfields * sizeof(char *));
+
+ for (argc = 0; argc < row->nfields; argc++)
+ {
+ PLpgSQL_expr *item;
+ int endtoken;
+ int argpos;
+ int tok1,
+ tok2;
+ int arglocation;
+
+ /* Check if it's a named parameter: "param := value" */
+ plpgsql_peek2(&tok1, &tok2, &arglocation, NULL);
+ if (tok1 == IDENT && tok2 == COLON_EQUALS)
+ {
+ char *argname;
+ IdentifierLookup save_IdentifierLookup;
+
+ /* Read the argument name, ignoring any matching variable */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
+ yylex();
+ argname = yylval.str;
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ /* Match argument name to cursor arguments */
+ for (argpos = 0; argpos < row->nfields; argpos++)
+ {
+ if (strcmp(row->fieldnames[argpos], argname) == 0)
+ break;
+ }
+ if (argpos == row->nfields)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has no argument named \"%s\"",
+ cursor->refname, argname),
+ parser_errposition(yylloc)));
+
+ /*
+ * Eat the ":=". We already peeked, so the error should never
+ * happen.
+ */
+ tok2 = yylex();
+ if (tok2 != COLON_EQUALS)
+ yyerror("syntax error");
+
+ any_named = true;
+ }
+ else
+ argpos = argc;
+
+ if (argv[argpos] != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once",
+ row->fieldnames[argpos], cursor->refname),
+ parser_errposition(arglocation)));
+
+ /*
+ * Read the value expression. To provide the user with meaningful
+ * parse error positions, we check the syntax immediately, instead of
+ * checking the final expression that may have the arguments
+ * reordered. Trailing whitespace must not be trimmed, because
+ * otherwise input of the form (param -- comment\n, param) would be
+ * translated into a form where the second parameter is commented
+ * out.
+ */
+ item = read_sql_construct(',', ')', 0,
+ ",\" or \")",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true,
+ false, /* do not trim */
+ NULL, &endtoken);
+
+ argv[argpos] = item->query;
+
+ if (endtoken == ')' && !(argc == row->nfields - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("not enough arguments for cursor \"%s\"",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ if (endtoken == ',' && (argc == row->nfields - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many arguments for cursor \"%s\"",
+ cursor->refname),
+ parser_errposition(yylloc)));
+ }
+
+ /* Make positional argument list */
+ initStringInfo(&ds);
+ for (argc = 0; argc < row->nfields; argc++)
+ {
+ Assert(argv[argc] != NULL);
+
+ /*
+ * Because named notation allows permutated argument lists, include
+ * the parameter name for meaningful runtime errors.
+ */
+ appendStringInfoString(&ds, argv[argc]);
+ if (any_named)
+ appendStringInfo(&ds, " AS %s",
+ quote_identifier(row->fieldnames[argc]));
+ if (argc < row->nfields - 1)
+ appendStringInfoString(&ds, ", ");
+ }
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ /* Next we'd better find the until token */
+ tok = yylex();
+ if (tok != until)
+ yyerror("syntax error");
+
+ return expr;
+}
+
+/*
+ * Parse RAISE ... USING options
+ */
+static List *
+read_raise_options(void)
+{
+ List *result = NIL;
+
+ for (;;)
+ {
+ PLpgSQL_raise_option *opt;
+ int tok;
+
+ if ((tok = yylex()) == 0)
+ yyerror("unexpected end of function definition");
+
+ opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
+
+ if (tok_is_keyword(tok, &yylval,
+ K_ERRCODE, "errcode"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_MESSAGE, "message"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DETAIL, "detail"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
+ else if (tok_is_keyword(tok, &yylval,
+ K_HINT, "hint"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_COLUMN, "column"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN;
+ else if (tok_is_keyword(tok, &yylval,
+ K_CONSTRAINT, "constraint"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DATATYPE, "datatype"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_TABLE, "table"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCHEMA, "schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA;
+ else
+ yyerror("unrecognized RAISE statement option");
+
+ tok = yylex();
+ if (tok != '=' && tok != COLON_EQUALS)
+ yyerror("syntax error, expected \"=\"");
+
+ opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
+
+ result = lappend(result, opt);
+
+ if (tok == ';')
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Check that the number of parameter placeholders in the message matches the
+ * number of parameters passed to it, if a message was given.
+ */
+static void
+check_raise_parameters(PLpgSQL_stmt_raise *stmt)
+{
+ char *cp;
+ int expected_nparams = 0;
+
+ if (stmt->message == NULL)
+ return;
+
+ for (cp = stmt->message; *cp; cp++)
+ {
+ if (cp[0] == '%')
+ {
+ /* ignore literal % characters */
+ if (cp[1] == '%')
+ cp++;
+ else
+ expected_nparams++;
+ }
+ }
+
+ if (expected_nparams < list_length(stmt->params))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many parameters specified for RAISE")));
+ if (expected_nparams > list_length(stmt->params))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too few parameters specified for RAISE")));
+}
+
+/*
+ * Fix up CASE statement
+ */
+static PLpgSQL_stmt *
+make_case(int location, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts)
+{
+ PLpgSQL_stmt_case *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_case));
+ new->cmd_type = PLPGSQL_STMT_CASE;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->t_expr = t_expr;
+ new->t_varno = 0;
+ new->case_when_list = case_when_list;
+ new->have_else = (else_stmts != NIL);
+ /* Get rid of list-with-NULL hack */
+ if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
+ new->else_stmts = NIL;
+ else
+ new->else_stmts = else_stmts;
+
+ /*
+ * When test expression is present, we create a var for it and then
+ * convert all the WHEN expressions to "VAR IN (original_expression)".
+ * This is a bit klugy, but okay since we haven't yet done more than
+ * read the expressions as text. (Note that previous parsing won't
+ * have complained if the WHEN ... THEN expression contained multiple
+ * comma-separated values.)
+ */
+ if (t_expr)
+ {
+ char varname[32];
+ PLpgSQL_var *t_var;
+ ListCell *l;
+
+ /* use a name unlikely to collide with any user names */
+ snprintf(varname, sizeof(varname), "__Case__Variable_%d__",
+ plpgsql_nDatums);
+
+ /*
+ * We don't yet know the result datatype of t_expr. Build the
+ * variable as if it were INT4; we'll fix this at runtime if needed.
+ */
+ t_var = (PLpgSQL_var *)
+ plpgsql_build_variable(varname, new->lineno,
+ plpgsql_build_datatype(INT4OID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ new->t_varno = t_var->dno;
+
+ foreach(l, case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ PLpgSQL_expr *expr = cwt->expr;
+ StringInfoData ds;
+
+ /* We expect to have expressions not statements */
+ Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+
+ /* Do the string hacking */
+ initStringInfo(&ds);
+
+ appendStringInfo(&ds, "\"%s\" IN (%s)",
+ varname, expr->query);
+
+ pfree(expr->query);
+ expr->query = pstrdup(ds.data);
+ /* Adjust expr's namespace to include the case variable */
+ expr->ns = plpgsql_ns_top();
+
+ pfree(ds.data);
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
diff --git a/src/pl/plpgsql/src/pl_gram.h b/src/pl/plpgsql/src/pl_gram.h
new file mode 100644
index 0000000..9fd14bd
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_gram.h
@@ -0,0 +1,270 @@
+/* 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 <http://www.gnu.org/licenses/>. */
+
+/* As a special exception, you may create a larger work that contains
+ part or all of the Bison parser skeleton and distribute that work
+ under terms of your choice, so long as that work isn't itself a
+ parser generator using the skeleton or a modified version thereof
+ as a parser skeleton. Alternatively, if you modify or redistribute
+ the parser skeleton itself, you may (at your option) remove this
+ special exception, which will cause the skeleton and the resulting
+ Bison output files to be licensed under the GNU General Public
+ License without this special exception.
+
+ This special exception was added by the Free Software Foundation in
+ version 2.2 of Bison. */
+
+/* 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_PLPGSQL_YY_PL_GRAM_H_INCLUDED
+# define YY_PLPGSQL_YY_PL_GRAM_H_INCLUDED
+/* Debug traces. */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int plpgsql_yydebug;
+#endif
+
+/* Token kinds. */
+#ifndef YYTOKENTYPE
+# define YYTOKENTYPE
+ enum yytokentype
+ {
+ YYEMPTY = -2,
+ YYEOF = 0, /* "end of file" */
+ YYerror = 256, /* error */
+ YYUNDEF = 257, /* "invalid token" */
+ IDENT = 258, /* IDENT */
+ UIDENT = 259, /* UIDENT */
+ FCONST = 260, /* FCONST */
+ SCONST = 261, /* SCONST */
+ USCONST = 262, /* USCONST */
+ BCONST = 263, /* BCONST */
+ XCONST = 264, /* XCONST */
+ Op = 265, /* Op */
+ ICONST = 266, /* ICONST */
+ PARAM = 267, /* PARAM */
+ TYPECAST = 268, /* TYPECAST */
+ DOT_DOT = 269, /* DOT_DOT */
+ COLON_EQUALS = 270, /* COLON_EQUALS */
+ EQUALS_GREATER = 271, /* EQUALS_GREATER */
+ LESS_EQUALS = 272, /* LESS_EQUALS */
+ GREATER_EQUALS = 273, /* GREATER_EQUALS */
+ NOT_EQUALS = 274, /* NOT_EQUALS */
+ T_WORD = 275, /* T_WORD */
+ T_CWORD = 276, /* T_CWORD */
+ T_DATUM = 277, /* T_DATUM */
+ LESS_LESS = 278, /* LESS_LESS */
+ GREATER_GREATER = 279, /* GREATER_GREATER */
+ K_ABSOLUTE = 280, /* K_ABSOLUTE */
+ K_ALIAS = 281, /* K_ALIAS */
+ K_ALL = 282, /* K_ALL */
+ K_AND = 283, /* K_AND */
+ K_ARRAY = 284, /* K_ARRAY */
+ K_ASSERT = 285, /* K_ASSERT */
+ K_BACKWARD = 286, /* K_BACKWARD */
+ K_BEGIN = 287, /* K_BEGIN */
+ K_BY = 288, /* K_BY */
+ K_CALL = 289, /* K_CALL */
+ K_CASE = 290, /* K_CASE */
+ K_CHAIN = 291, /* K_CHAIN */
+ K_CLOSE = 292, /* K_CLOSE */
+ K_COLLATE = 293, /* K_COLLATE */
+ K_COLUMN = 294, /* K_COLUMN */
+ K_COLUMN_NAME = 295, /* K_COLUMN_NAME */
+ K_COMMIT = 296, /* K_COMMIT */
+ K_CONSTANT = 297, /* K_CONSTANT */
+ K_CONSTRAINT = 298, /* K_CONSTRAINT */
+ K_CONSTRAINT_NAME = 299, /* K_CONSTRAINT_NAME */
+ K_CONTINUE = 300, /* K_CONTINUE */
+ K_CURRENT = 301, /* K_CURRENT */
+ K_CURSOR = 302, /* K_CURSOR */
+ K_DATATYPE = 303, /* K_DATATYPE */
+ K_DEBUG = 304, /* K_DEBUG */
+ K_DECLARE = 305, /* K_DECLARE */
+ K_DEFAULT = 306, /* K_DEFAULT */
+ K_DETAIL = 307, /* K_DETAIL */
+ K_DIAGNOSTICS = 308, /* K_DIAGNOSTICS */
+ K_DO = 309, /* K_DO */
+ K_DUMP = 310, /* K_DUMP */
+ K_ELSE = 311, /* K_ELSE */
+ K_ELSIF = 312, /* K_ELSIF */
+ K_END = 313, /* K_END */
+ K_ERRCODE = 314, /* K_ERRCODE */
+ K_ERROR = 315, /* K_ERROR */
+ K_EXCEPTION = 316, /* K_EXCEPTION */
+ K_EXECUTE = 317, /* K_EXECUTE */
+ K_EXIT = 318, /* K_EXIT */
+ K_FETCH = 319, /* K_FETCH */
+ K_FIRST = 320, /* K_FIRST */
+ K_FOR = 321, /* K_FOR */
+ K_FOREACH = 322, /* K_FOREACH */
+ K_FORWARD = 323, /* K_FORWARD */
+ K_FROM = 324, /* K_FROM */
+ K_GET = 325, /* K_GET */
+ K_HINT = 326, /* K_HINT */
+ K_IF = 327, /* K_IF */
+ K_IMPORT = 328, /* K_IMPORT */
+ K_IN = 329, /* K_IN */
+ K_INFO = 330, /* K_INFO */
+ K_INSERT = 331, /* K_INSERT */
+ K_INTO = 332, /* K_INTO */
+ K_IS = 333, /* K_IS */
+ K_LAST = 334, /* K_LAST */
+ K_LOG = 335, /* K_LOG */
+ K_LOOP = 336, /* K_LOOP */
+ K_MERGE = 337, /* K_MERGE */
+ K_MESSAGE = 338, /* K_MESSAGE */
+ K_MESSAGE_TEXT = 339, /* K_MESSAGE_TEXT */
+ K_MOVE = 340, /* K_MOVE */
+ K_NEXT = 341, /* K_NEXT */
+ K_NO = 342, /* K_NO */
+ K_NOT = 343, /* K_NOT */
+ K_NOTICE = 344, /* K_NOTICE */
+ K_NULL = 345, /* K_NULL */
+ K_OPEN = 346, /* K_OPEN */
+ K_OPTION = 347, /* K_OPTION */
+ K_OR = 348, /* K_OR */
+ K_PERFORM = 349, /* K_PERFORM */
+ K_PG_CONTEXT = 350, /* K_PG_CONTEXT */
+ K_PG_DATATYPE_NAME = 351, /* K_PG_DATATYPE_NAME */
+ K_PG_EXCEPTION_CONTEXT = 352, /* K_PG_EXCEPTION_CONTEXT */
+ K_PG_EXCEPTION_DETAIL = 353, /* K_PG_EXCEPTION_DETAIL */
+ K_PG_EXCEPTION_HINT = 354, /* K_PG_EXCEPTION_HINT */
+ K_PRINT_STRICT_PARAMS = 355, /* K_PRINT_STRICT_PARAMS */
+ K_PRIOR = 356, /* K_PRIOR */
+ K_QUERY = 357, /* K_QUERY */
+ K_RAISE = 358, /* K_RAISE */
+ K_RELATIVE = 359, /* K_RELATIVE */
+ K_RETURN = 360, /* K_RETURN */
+ K_RETURNED_SQLSTATE = 361, /* K_RETURNED_SQLSTATE */
+ K_REVERSE = 362, /* K_REVERSE */
+ K_ROLLBACK = 363, /* K_ROLLBACK */
+ K_ROW_COUNT = 364, /* K_ROW_COUNT */
+ K_ROWTYPE = 365, /* K_ROWTYPE */
+ K_SCHEMA = 366, /* K_SCHEMA */
+ K_SCHEMA_NAME = 367, /* K_SCHEMA_NAME */
+ K_SCROLL = 368, /* K_SCROLL */
+ K_SLICE = 369, /* K_SLICE */
+ K_SQLSTATE = 370, /* K_SQLSTATE */
+ K_STACKED = 371, /* K_STACKED */
+ K_STRICT = 372, /* K_STRICT */
+ K_TABLE = 373, /* K_TABLE */
+ K_TABLE_NAME = 374, /* K_TABLE_NAME */
+ K_THEN = 375, /* K_THEN */
+ K_TO = 376, /* K_TO */
+ K_TYPE = 377, /* K_TYPE */
+ K_USE_COLUMN = 378, /* K_USE_COLUMN */
+ K_USE_VARIABLE = 379, /* K_USE_VARIABLE */
+ K_USING = 380, /* K_USING */
+ K_VARIABLE_CONFLICT = 381, /* K_VARIABLE_CONFLICT */
+ K_WARNING = 382, /* K_WARNING */
+ K_WHEN = 383, /* K_WHEN */
+ K_WHILE = 384 /* K_WHILE */
+ };
+ typedef enum yytokentype yytoken_kind_t;
+#endif
+
+/* Value type. */
+#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
+union YYSTYPE
+{
+#line 120 "pl_gram.y"
+
+ core_YYSTYPE core_yystype;
+ /* these fields must match core_YYSTYPE: */
+ int ival;
+ char *str;
+ const char *keyword;
+
+ PLword word;
+ PLcword cword;
+ PLwdatum wdatum;
+ bool boolean;
+ Oid oid;
+ struct
+ {
+ char *name;
+ int lineno;
+ } varname;
+ struct
+ {
+ char *name;
+ int lineno;
+ PLpgSQL_datum *scalar;
+ PLpgSQL_datum *row;
+ } forvariable;
+ struct
+ {
+ char *label;
+ int n_initvars;
+ int *initvarnos;
+ } declhdr;
+ struct
+ {
+ List *stmts;
+ char *end_label;
+ int end_label_location;
+ } loop_body;
+ List *list;
+ PLpgSQL_type *dtype;
+ PLpgSQL_datum *datum;
+ PLpgSQL_var *var;
+ PLpgSQL_expr *expr;
+ PLpgSQL_stmt *stmt;
+ PLpgSQL_condition *condition;
+ PLpgSQL_exception *exception;
+ PLpgSQL_exception_block *exception_block;
+ PLpgSQL_nsitem *nsitem;
+ PLpgSQL_diag_item *diagitem;
+ PLpgSQL_stmt_fetch *fetch;
+ PLpgSQL_case_when *casewhen;
+
+#line 244 "pl_gram.h"
+
+};
+typedef union YYSTYPE YYSTYPE;
+# define YYSTYPE_IS_TRIVIAL 1
+# define YYSTYPE_IS_DECLARED 1
+#endif
+
+/* Location type. */
+#if ! defined YYLTYPE && ! defined YYLTYPE_IS_DECLARED
+typedef struct YYLTYPE YYLTYPE;
+struct YYLTYPE
+{
+ int first_line;
+ int first_column;
+ int last_line;
+ int last_column;
+};
+# define YYLTYPE_IS_DECLARED 1
+# define YYLTYPE_IS_TRIVIAL 1
+#endif
+
+
+extern YYSTYPE plpgsql_yylval;
+extern YYLTYPE plpgsql_yylloc;
+int plpgsql_yyparse (void);
+
+#endif /* !YY_PLPGSQL_YY_PL_GRAM_H_INCLUDED */
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
new file mode 100644
index 0000000..0b8aea9
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -0,0 +1,4120 @@
+%{
+/*-------------------------------------------------------------------------
+ *
+ * pl_gram.y - Parser for the PL/pgSQL procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_gram.y
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "catalog/namespace.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "parser/parser.h"
+#include "parser/parse_type.h"
+#include "parser/scanner.h"
+#include "parser/scansup.h"
+#include "utils/builtins.h"
+
+#include "plpgsql.h"
+
+
+/* Location tracking support --- simpler than bison's default */
+#define YYLLOC_DEFAULT(Current, Rhs, N) \
+ do { \
+ if (N) \
+ (Current) = (Rhs)[1]; \
+ else \
+ (Current) = (Rhs)[0]; \
+ } while (0)
+
+/*
+ * 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. Note this only works with
+ * bison >= 2.0. However, in bison 1.875 the default is to use alloca()
+ * if possible, so there's not really much problem anyhow, at least if
+ * you're building with gcc.
+ */
+#define YYMALLOC palloc
+#define YYFREE pfree
+
+
+typedef struct
+{
+ int location;
+} sql_error_callback_arg;
+
+#define parser_errposition(pos) plpgsql_scanner_errposition(pos)
+
+union YYSTYPE; /* need forward reference for tok_is_keyword */
+
+static bool tok_is_keyword(int token, union YYSTYPE *lval,
+ int kw_token, const char *kw_str);
+static void word_is_not_variable(PLword *word, int location);
+static void cword_is_not_variable(PLcword *cword, int location);
+static void current_token_is_not_variable(int tok);
+static PLpgSQL_expr *read_sql_construct(int until,
+ int until2,
+ int until3,
+ const char *expected,
+ RawParseMode parsemode,
+ bool isexpression,
+ bool valid_sql,
+ bool trim,
+ int *startloc,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_expression(int until,
+ const char *expected);
+static PLpgSQL_expr *read_sql_expression2(int until, int until2,
+ const char *expected,
+ int *endtoken);
+static PLpgSQL_expr *read_sql_stmt(void);
+static PLpgSQL_type *read_datatype(int tok);
+static PLpgSQL_stmt *make_execsql_stmt(int firsttoken, int location);
+static PLpgSQL_stmt_fetch *read_fetch_direction(void);
+static void complete_direction(PLpgSQL_stmt_fetch *fetch,
+ bool *check_FROM);
+static PLpgSQL_stmt *make_return_stmt(int location);
+static PLpgSQL_stmt *make_return_next_stmt(int location);
+static PLpgSQL_stmt *make_return_query_stmt(int location);
+static PLpgSQL_stmt *make_case(int location, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts);
+static char *NameOfDatum(PLwdatum *wdatum);
+static void check_assignable(PLpgSQL_datum *datum, int location);
+static void read_into_target(PLpgSQL_variable **target,
+ bool *strict);
+static PLpgSQL_row *read_into_scalar_list(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int initial_location);
+static PLpgSQL_row *make_scalar_list1(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int lineno, int location);
+static void check_sql_expr(const char *stmt,
+ RawParseMode parseMode, int location);
+static void plpgsql_sql_error_callback(void *arg);
+static PLpgSQL_type *parse_datatype(const char *string, int location);
+static void check_labels(const char *start_label,
+ const char *end_label,
+ int end_location);
+static PLpgSQL_expr *read_cursor_args(PLpgSQL_var *cursor,
+ int until);
+static List *read_raise_options(void);
+static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
+
+%}
+
+%expect 0
+%name-prefix="plpgsql_yy"
+%locations
+
+%union
+{
+ core_YYSTYPE core_yystype;
+ /* these fields must match core_YYSTYPE: */
+ int ival;
+ char *str;
+ const char *keyword;
+
+ PLword word;
+ PLcword cword;
+ PLwdatum wdatum;
+ bool boolean;
+ Oid oid;
+ struct
+ {
+ char *name;
+ int lineno;
+ } varname;
+ struct
+ {
+ char *name;
+ int lineno;
+ PLpgSQL_datum *scalar;
+ PLpgSQL_datum *row;
+ } forvariable;
+ struct
+ {
+ char *label;
+ int n_initvars;
+ int *initvarnos;
+ } declhdr;
+ struct
+ {
+ List *stmts;
+ char *end_label;
+ int end_label_location;
+ } loop_body;
+ List *list;
+ PLpgSQL_type *dtype;
+ PLpgSQL_datum *datum;
+ PLpgSQL_var *var;
+ PLpgSQL_expr *expr;
+ PLpgSQL_stmt *stmt;
+ PLpgSQL_condition *condition;
+ PLpgSQL_exception *exception;
+ PLpgSQL_exception_block *exception_block;
+ PLpgSQL_nsitem *nsitem;
+ PLpgSQL_diag_item *diagitem;
+ PLpgSQL_stmt_fetch *fetch;
+ PLpgSQL_case_when *casewhen;
+}
+
+%type <declhdr> decl_sect
+%type <varname> decl_varname
+%type <boolean> decl_const decl_notnull exit_type
+%type <expr> decl_defval decl_cursor_query
+%type <dtype> decl_datatype
+%type <oid> decl_collate
+%type <datum> decl_cursor_args
+%type <list> decl_cursor_arglist
+%type <nsitem> decl_aliasitem
+
+%type <expr> expr_until_semi
+%type <expr> expr_until_then expr_until_loop opt_expr_until_when
+%type <expr> opt_exitcond
+
+%type <var> cursor_variable
+%type <datum> decl_cursor_arg
+%type <forvariable> for_variable
+%type <ival> foreach_slice
+%type <stmt> for_control
+
+%type <str> any_identifier opt_block_label opt_loop_label opt_label
+%type <str> option_value
+
+%type <list> proc_sect stmt_elsifs stmt_else
+%type <loop_body> loop_body
+%type <stmt> proc_stmt pl_block
+%type <stmt> stmt_assign stmt_if stmt_loop stmt_while stmt_exit
+%type <stmt> stmt_return stmt_raise stmt_assert stmt_execsql
+%type <stmt> stmt_dynexecute stmt_for stmt_perform stmt_call stmt_getdiag
+%type <stmt> stmt_open stmt_fetch stmt_move stmt_close stmt_null
+%type <stmt> stmt_commit stmt_rollback
+%type <stmt> stmt_case stmt_foreach_a
+
+%type <list> proc_exceptions
+%type <exception_block> exception_sect
+%type <exception> proc_exception
+%type <condition> proc_conditions proc_condition
+
+%type <casewhen> case_when
+%type <list> case_when_list opt_case_else
+
+%type <boolean> getdiag_area_opt
+%type <list> getdiag_list
+%type <diagitem> getdiag_list_item
+%type <datum> getdiag_target
+%type <ival> getdiag_item
+
+%type <ival> opt_scrollable
+%type <fetch> opt_fetch_direction
+
+%type <ival> opt_transaction_chain
+
+%type <keyword> unreserved_keyword
+
+
+/*
+ * Basic non-keyword token types. These are hard-wired into the core lexer.
+ * They must be listed first so that their numeric codes do not depend on
+ * the set of keywords. Keep this list in sync with backend/parser/gram.y!
+ *
+ * Some of these are not directly referenced in this file, but they must be
+ * here anyway.
+ */
+%token <str> IDENT UIDENT FCONST SCONST USCONST BCONST XCONST Op
+%token <ival> ICONST PARAM
+%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+
+/*
+ * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).
+ */
+%token <word> T_WORD /* unrecognized simple identifier */
+%token <cword> T_CWORD /* unrecognized composite identifier */
+%token <wdatum> T_DATUM /* a VAR, ROW, REC, or RECFIELD variable */
+%token LESS_LESS
+%token GREATER_GREATER
+
+/*
+ * Keyword tokens. Some of these are reserved and some are not;
+ * see pl_scanner.c for info. Be sure unreserved keywords are listed
+ * in the "unreserved_keyword" production below.
+ */
+%token <keyword> K_ABSOLUTE
+%token <keyword> K_ALIAS
+%token <keyword> K_ALL
+%token <keyword> K_AND
+%token <keyword> K_ARRAY
+%token <keyword> K_ASSERT
+%token <keyword> K_BACKWARD
+%token <keyword> K_BEGIN
+%token <keyword> K_BY
+%token <keyword> K_CALL
+%token <keyword> K_CASE
+%token <keyword> K_CHAIN
+%token <keyword> K_CLOSE
+%token <keyword> K_COLLATE
+%token <keyword> K_COLUMN
+%token <keyword> K_COLUMN_NAME
+%token <keyword> K_COMMIT
+%token <keyword> K_CONSTANT
+%token <keyword> K_CONSTRAINT
+%token <keyword> K_CONSTRAINT_NAME
+%token <keyword> K_CONTINUE
+%token <keyword> K_CURRENT
+%token <keyword> K_CURSOR
+%token <keyword> K_DATATYPE
+%token <keyword> K_DEBUG
+%token <keyword> K_DECLARE
+%token <keyword> K_DEFAULT
+%token <keyword> K_DETAIL
+%token <keyword> K_DIAGNOSTICS
+%token <keyword> K_DO
+%token <keyword> K_DUMP
+%token <keyword> K_ELSE
+%token <keyword> K_ELSIF
+%token <keyword> K_END
+%token <keyword> K_ERRCODE
+%token <keyword> K_ERROR
+%token <keyword> K_EXCEPTION
+%token <keyword> K_EXECUTE
+%token <keyword> K_EXIT
+%token <keyword> K_FETCH
+%token <keyword> K_FIRST
+%token <keyword> K_FOR
+%token <keyword> K_FOREACH
+%token <keyword> K_FORWARD
+%token <keyword> K_FROM
+%token <keyword> K_GET
+%token <keyword> K_HINT
+%token <keyword> K_IF
+%token <keyword> K_IMPORT
+%token <keyword> K_IN
+%token <keyword> K_INFO
+%token <keyword> K_INSERT
+%token <keyword> K_INTO
+%token <keyword> K_IS
+%token <keyword> K_LAST
+%token <keyword> K_LOG
+%token <keyword> K_LOOP
+%token <keyword> K_MERGE
+%token <keyword> K_MESSAGE
+%token <keyword> K_MESSAGE_TEXT
+%token <keyword> K_MOVE
+%token <keyword> K_NEXT
+%token <keyword> K_NO
+%token <keyword> K_NOT
+%token <keyword> K_NOTICE
+%token <keyword> K_NULL
+%token <keyword> K_OPEN
+%token <keyword> K_OPTION
+%token <keyword> K_OR
+%token <keyword> K_PERFORM
+%token <keyword> K_PG_CONTEXT
+%token <keyword> K_PG_DATATYPE_NAME
+%token <keyword> K_PG_EXCEPTION_CONTEXT
+%token <keyword> K_PG_EXCEPTION_DETAIL
+%token <keyword> K_PG_EXCEPTION_HINT
+%token <keyword> K_PRINT_STRICT_PARAMS
+%token <keyword> K_PRIOR
+%token <keyword> K_QUERY
+%token <keyword> K_RAISE
+%token <keyword> K_RELATIVE
+%token <keyword> K_RETURN
+%token <keyword> K_RETURNED_SQLSTATE
+%token <keyword> K_REVERSE
+%token <keyword> K_ROLLBACK
+%token <keyword> K_ROW_COUNT
+%token <keyword> K_ROWTYPE
+%token <keyword> K_SCHEMA
+%token <keyword> K_SCHEMA_NAME
+%token <keyword> K_SCROLL
+%token <keyword> K_SLICE
+%token <keyword> K_SQLSTATE
+%token <keyword> K_STACKED
+%token <keyword> K_STRICT
+%token <keyword> K_TABLE
+%token <keyword> K_TABLE_NAME
+%token <keyword> K_THEN
+%token <keyword> K_TO
+%token <keyword> K_TYPE
+%token <keyword> K_USE_COLUMN
+%token <keyword> K_USE_VARIABLE
+%token <keyword> K_USING
+%token <keyword> K_VARIABLE_CONFLICT
+%token <keyword> K_WARNING
+%token <keyword> K_WHEN
+%token <keyword> K_WHILE
+
+%%
+
+pl_function : comp_options pl_block opt_semi
+ {
+ plpgsql_parse_result = (PLpgSQL_stmt_block *) $2;
+ }
+ ;
+
+comp_options :
+ | comp_options comp_option
+ ;
+
+comp_option : '#' K_OPTION K_DUMP
+ {
+ plpgsql_DumpExecTree = true;
+ }
+ | '#' K_PRINT_STRICT_PARAMS option_value
+ {
+ if (strcmp($3, "on") == 0)
+ plpgsql_curr_compile->print_strict_params = true;
+ else if (strcmp($3, "off") == 0)
+ plpgsql_curr_compile->print_strict_params = false;
+ else
+ elog(ERROR, "unrecognized print_strict_params option %s", $3);
+ }
+ | '#' K_VARIABLE_CONFLICT K_ERROR
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_ERROR;
+ }
+ | '#' K_VARIABLE_CONFLICT K_USE_VARIABLE
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_VARIABLE;
+ }
+ | '#' K_VARIABLE_CONFLICT K_USE_COLUMN
+ {
+ plpgsql_curr_compile->resolve_option = PLPGSQL_RESOLVE_COLUMN;
+ }
+ ;
+
+option_value : T_WORD
+ {
+ $$ = $1.ident;
+ }
+ | unreserved_keyword
+ {
+ $$ = pstrdup($1);
+ }
+
+opt_semi :
+ | ';'
+ ;
+
+pl_block : decl_sect K_BEGIN proc_sect exception_sect K_END opt_label
+ {
+ PLpgSQL_stmt_block *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_block));
+
+ new->cmd_type = PLPGSQL_STMT_BLOCK;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = $1.label;
+ new->n_initvars = $1.n_initvars;
+ new->initvarnos = $1.initvarnos;
+ new->body = $3;
+ new->exceptions = $4;
+
+ check_labels($1.label, $6, @6);
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+
+decl_sect : opt_block_label
+ {
+ /* done with decls, so resume identifier lookup */
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ $$.label = $1;
+ $$.n_initvars = 0;
+ $$.initvarnos = NULL;
+ }
+ | opt_block_label decl_start
+ {
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ $$.label = $1;
+ $$.n_initvars = 0;
+ $$.initvarnos = NULL;
+ }
+ | opt_block_label decl_start decl_stmts
+ {
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ $$.label = $1;
+ /* Remember variables declared in decl_stmts */
+ $$.n_initvars = plpgsql_add_initdatums(&($$.initvarnos));
+ }
+ ;
+
+decl_start : K_DECLARE
+ {
+ /* Forget any variables created before block */
+ plpgsql_add_initdatums(NULL);
+ /*
+ * Disable scanner lookup of identifiers while
+ * we process the decl_stmts
+ */
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
+ }
+ ;
+
+decl_stmts : decl_stmts decl_stmt
+ | decl_stmt
+ ;
+
+decl_stmt : decl_statement
+ | K_DECLARE
+ {
+ /* We allow useless extra DECLAREs */
+ }
+ | LESS_LESS any_identifier GREATER_GREATER
+ {
+ /*
+ * Throw a helpful error if user tries to put block
+ * label just before BEGIN, instead of before DECLARE.
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("block label must be placed before DECLARE, not after"),
+ parser_errposition(@1)));
+ }
+ ;
+
+decl_statement : decl_varname decl_const decl_datatype decl_collate decl_notnull decl_defval
+ {
+ PLpgSQL_variable *var;
+
+ /*
+ * If a collation is supplied, insert it into the
+ * datatype. We assume decl_datatype always returns
+ * a freshly built struct not shared with other
+ * variables.
+ */
+ if (OidIsValid($4))
+ {
+ if (!OidIsValid($3->collation))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("collations are not supported by type %s",
+ format_type_be($3->typoid)),
+ parser_errposition(@4)));
+ $3->collation = $4;
+ }
+
+ var = plpgsql_build_variable($1.name, $1.lineno,
+ $3, true);
+ var->isconst = $2;
+ var->notnull = $5;
+ var->default_val = $6;
+
+ /*
+ * The combination of NOT NULL without an initializer
+ * can't work, so let's reject it at compile time.
+ */
+ if (var->notnull && var->default_val == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+ errmsg("variable \"%s\" must have a default value, since it's declared NOT NULL",
+ var->refname),
+ parser_errposition(@5)));
+ }
+ | decl_varname K_ALIAS K_FOR decl_aliasitem ';'
+ {
+ plpgsql_ns_additem($4->itemtype,
+ $4->itemno, $1.name);
+ }
+ | decl_varname opt_scrollable K_CURSOR
+ { plpgsql_ns_push($1.name, PLPGSQL_LABEL_OTHER); }
+ decl_cursor_args decl_is_for decl_cursor_query
+ {
+ PLpgSQL_var *new;
+ PLpgSQL_expr *curname_def;
+ char buf[NAMEDATALEN * 2 + 64];
+ char *cp1;
+ char *cp2;
+
+ /* pop local namespace for cursor args */
+ plpgsql_ns_pop();
+
+ new = (PLpgSQL_var *)
+ plpgsql_build_variable($1.name, $1.lineno,
+ plpgsql_build_datatype(REFCURSOROID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+
+ curname_def = palloc0(sizeof(PLpgSQL_expr));
+
+ /* Note: refname has been truncated to NAMEDATALEN */
+ cp1 = new->refname;
+ cp2 = buf;
+ /*
+ * Don't trust standard_conforming_strings here;
+ * it might change before we use the string.
+ */
+ if (strchr(cp1, '\\') != NULL)
+ *cp2++ = ESCAPE_STRING_SYNTAX;
+ *cp2++ = '\'';
+ while (*cp1)
+ {
+ if (SQL_STR_DOUBLE(*cp1, true))
+ *cp2++ = *cp1;
+ *cp2++ = *cp1++;
+ }
+ strcpy(cp2, "'::pg_catalog.refcursor");
+ curname_def->query = pstrdup(buf);
+ curname_def->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ new->default_val = curname_def;
+
+ new->cursor_explicit_expr = $7;
+ if ($5 == NULL)
+ new->cursor_explicit_argrow = -1;
+ else
+ new->cursor_explicit_argrow = $5->dno;
+ new->cursor_options = CURSOR_OPT_FAST_PLAN | $2;
+ }
+ ;
+
+opt_scrollable :
+ {
+ $$ = 0;
+ }
+ | K_NO K_SCROLL
+ {
+ $$ = CURSOR_OPT_NO_SCROLL;
+ }
+ | K_SCROLL
+ {
+ $$ = CURSOR_OPT_SCROLL;
+ }
+ ;
+
+decl_cursor_query :
+ {
+ $$ = read_sql_stmt();
+ }
+ ;
+
+decl_cursor_args :
+ {
+ $$ = NULL;
+ }
+ | '(' decl_cursor_arglist ')'
+ {
+ PLpgSQL_row *new;
+ int i;
+ ListCell *l;
+
+ new = palloc0(sizeof(PLpgSQL_row));
+ new->dtype = PLPGSQL_DTYPE_ROW;
+ new->refname = "(unnamed row)";
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->rowtupdesc = NULL;
+ new->nfields = list_length($2);
+ new->fieldnames = palloc(new->nfields * sizeof(char *));
+ new->varnos = palloc(new->nfields * sizeof(int));
+
+ i = 0;
+ foreach (l, $2)
+ {
+ PLpgSQL_variable *arg = (PLpgSQL_variable *) lfirst(l);
+ Assert(!arg->isconst);
+ new->fieldnames[i] = arg->refname;
+ new->varnos[i] = arg->dno;
+ i++;
+ }
+ list_free($2);
+
+ plpgsql_adddatum((PLpgSQL_datum *) new);
+ $$ = (PLpgSQL_datum *) new;
+ }
+ ;
+
+decl_cursor_arglist : decl_cursor_arg
+ {
+ $$ = list_make1($1);
+ }
+ | decl_cursor_arglist ',' decl_cursor_arg
+ {
+ $$ = lappend($1, $3);
+ }
+ ;
+
+decl_cursor_arg : decl_varname decl_datatype
+ {
+ $$ = (PLpgSQL_datum *)
+ plpgsql_build_variable($1.name, $1.lineno,
+ $2, true);
+ }
+ ;
+
+decl_is_for : K_IS | /* Oracle */
+ K_FOR; /* SQL standard */
+
+decl_aliasitem : T_WORD
+ {
+ PLpgSQL_nsitem *nsi;
+
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ $1.ident, NULL, NULL,
+ NULL);
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ $1.ident),
+ parser_errposition(@1)));
+ $$ = nsi;
+ }
+ | unreserved_keyword
+ {
+ PLpgSQL_nsitem *nsi;
+
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ $1, NULL, NULL,
+ NULL);
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ $1),
+ parser_errposition(@1)));
+ $$ = nsi;
+ }
+ | T_CWORD
+ {
+ PLpgSQL_nsitem *nsi;
+
+ if (list_length($1.idents) == 2)
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ strVal(linitial($1.idents)),
+ strVal(lsecond($1.idents)),
+ NULL,
+ NULL);
+ else if (list_length($1.idents) == 3)
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ strVal(linitial($1.idents)),
+ strVal(lsecond($1.idents)),
+ strVal(lthird($1.idents)),
+ NULL);
+ else
+ nsi = NULL;
+ if (nsi == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("variable \"%s\" does not exist",
+ NameListToString($1.idents)),
+ parser_errposition(@1)));
+ $$ = nsi;
+ }
+ ;
+
+decl_varname : T_WORD
+ {
+ $$.name = $1.ident;
+ $$.lineno = plpgsql_location_to_lineno(@1);
+ /*
+ * Check to make sure name isn't already declared
+ * in the current block.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ $1.ident, NULL, NULL,
+ NULL) != NULL)
+ yyerror("duplicate declaration");
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
+ {
+ PLpgSQL_nsitem *nsi;
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ $1.ident, NULL, NULL, NULL);
+ if (nsi != NULL)
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("variable \"%s\" shadows a previously defined variable",
+ $1.ident),
+ parser_errposition(@1)));
+ }
+
+ }
+ | unreserved_keyword
+ {
+ $$.name = pstrdup($1);
+ $$.lineno = plpgsql_location_to_lineno(@1);
+ /*
+ * Check to make sure name isn't already declared
+ * in the current block.
+ */
+ if (plpgsql_ns_lookup(plpgsql_ns_top(), true,
+ $1, NULL, NULL,
+ NULL) != NULL)
+ yyerror("duplicate declaration");
+
+ if (plpgsql_curr_compile->extra_warnings & PLPGSQL_XCHECK_SHADOWVAR ||
+ plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR)
+ {
+ PLpgSQL_nsitem *nsi;
+ nsi = plpgsql_ns_lookup(plpgsql_ns_top(), false,
+ $1, NULL, NULL, NULL);
+ if (nsi != NULL)
+ ereport(plpgsql_curr_compile->extra_errors & PLPGSQL_XCHECK_SHADOWVAR ? ERROR : WARNING,
+ (errcode(ERRCODE_DUPLICATE_ALIAS),
+ errmsg("variable \"%s\" shadows a previously defined variable",
+ $1),
+ parser_errposition(@1)));
+ }
+
+ }
+ ;
+
+decl_const :
+ { $$ = false; }
+ | K_CONSTANT
+ { $$ = true; }
+ ;
+
+decl_datatype :
+ {
+ /*
+ * If there's a lookahead token, read_datatype
+ * should consume it.
+ */
+ $$ = read_datatype(yychar);
+ yyclearin;
+ }
+ ;
+
+decl_collate :
+ { $$ = InvalidOid; }
+ | K_COLLATE T_WORD
+ {
+ $$ = get_collation_oid(list_make1(makeString($2.ident)),
+ false);
+ }
+ | K_COLLATE unreserved_keyword
+ {
+ $$ = get_collation_oid(list_make1(makeString(pstrdup($2))),
+ false);
+ }
+ | K_COLLATE T_CWORD
+ {
+ $$ = get_collation_oid($2.idents, false);
+ }
+ ;
+
+decl_notnull :
+ { $$ = false; }
+ | K_NOT K_NULL
+ { $$ = true; }
+ ;
+
+decl_defval : ';'
+ { $$ = NULL; }
+ | decl_defkey
+ {
+ $$ = read_sql_expression(';', ";");
+ }
+ ;
+
+decl_defkey : assign_operator
+ | K_DEFAULT
+ ;
+
+/*
+ * Ada-based PL/SQL uses := for assignment and variable defaults, while
+ * the SQL standard uses equals for these cases and for GET
+ * DIAGNOSTICS, so we support both. FOR and OPEN only support :=.
+ */
+assign_operator : '='
+ | COLON_EQUALS
+ ;
+
+proc_sect :
+ { $$ = NIL; }
+ | proc_sect proc_stmt
+ {
+ /* don't bother linking null statements into list */
+ if ($2 == NULL)
+ $$ = $1;
+ else
+ $$ = lappend($1, $2);
+ }
+ ;
+
+proc_stmt : pl_block ';'
+ { $$ = $1; }
+ | stmt_assign
+ { $$ = $1; }
+ | stmt_if
+ { $$ = $1; }
+ | stmt_case
+ { $$ = $1; }
+ | stmt_loop
+ { $$ = $1; }
+ | stmt_while
+ { $$ = $1; }
+ | stmt_for
+ { $$ = $1; }
+ | stmt_foreach_a
+ { $$ = $1; }
+ | stmt_exit
+ { $$ = $1; }
+ | stmt_return
+ { $$ = $1; }
+ | stmt_raise
+ { $$ = $1; }
+ | stmt_assert
+ { $$ = $1; }
+ | stmt_execsql
+ { $$ = $1; }
+ | stmt_dynexecute
+ { $$ = $1; }
+ | stmt_perform
+ { $$ = $1; }
+ | stmt_call
+ { $$ = $1; }
+ | stmt_getdiag
+ { $$ = $1; }
+ | stmt_open
+ { $$ = $1; }
+ | stmt_fetch
+ { $$ = $1; }
+ | stmt_move
+ { $$ = $1; }
+ | stmt_close
+ { $$ = $1; }
+ | stmt_null
+ { $$ = $1; }
+ | stmt_commit
+ { $$ = $1; }
+ | stmt_rollback
+ { $$ = $1; }
+ ;
+
+stmt_perform : K_PERFORM
+ {
+ PLpgSQL_stmt_perform *new;
+ int startloc;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_perform));
+ new->cmd_type = PLPGSQL_STMT_PERFORM;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_PERFORM);
+
+ /*
+ * Since PERFORM isn't legal SQL, we have to cheat to
+ * the extent of substituting "SELECT" for "PERFORM"
+ * in the parsed text. It does not seem worth
+ * inventing a separate parse mode for this one case.
+ * We can't do syntax-checking until after we make the
+ * substitution.
+ */
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, false, true,
+ &startloc, NULL);
+ /* overwrite "perform" ... */
+ memcpy(new->expr->query, " SELECT", 7);
+ /* left-justify to get rid of the leading space */
+ memmove(new->expr->query, new->expr->query + 1,
+ strlen(new->expr->query));
+ /* offset syntax error position to account for that */
+ check_sql_expr(new->expr->query, new->expr->parseMode,
+ startloc + 1);
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_call : K_CALL
+ {
+ PLpgSQL_stmt_call *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_call));
+ new->cmd_type = PLPGSQL_STMT_CALL;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_CALL);
+ new->expr = read_sql_stmt();
+ new->is_call = true;
+
+ /* Remember we may need a procedure resource owner */
+ plpgsql_curr_compile->requires_procedure_resowner = true;
+
+ $$ = (PLpgSQL_stmt *) new;
+
+ }
+ | K_DO
+ {
+ /* use the same structures as for CALL, for simplicity */
+ PLpgSQL_stmt_call *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_call));
+ new->cmd_type = PLPGSQL_STMT_CALL;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ plpgsql_push_back_token(K_DO);
+ new->expr = read_sql_stmt();
+ new->is_call = false;
+
+ /* Remember we may need a procedure resource owner */
+ plpgsql_curr_compile->requires_procedure_resowner = true;
+
+ $$ = (PLpgSQL_stmt *) new;
+
+ }
+ ;
+
+stmt_assign : T_DATUM
+ {
+ PLpgSQL_stmt_assign *new;
+ RawParseMode pmode;
+
+ /* see how many names identify the datum */
+ switch ($1.ident ? 1 : list_length($1.idents))
+ {
+ case 1:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN1;
+ break;
+ case 2:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN2;
+ break;
+ case 3:
+ pmode = RAW_PARSE_PLPGSQL_ASSIGN3;
+ break;
+ default:
+ elog(ERROR, "unexpected number of names");
+ pmode = 0; /* keep compiler quiet */
+ }
+
+ check_assignable($1.datum, @1);
+ new = palloc0(sizeof(PLpgSQL_stmt_assign));
+ new->cmd_type = PLPGSQL_STMT_ASSIGN;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->varno = $1.datum->dno;
+ /* Push back the head name to include it in the stmt */
+ plpgsql_push_back_token(T_DATUM);
+ new->expr = read_sql_construct(';', 0, 0, ";",
+ pmode,
+ false, true, true,
+ NULL, NULL);
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_getdiag : K_GET getdiag_area_opt K_DIAGNOSTICS getdiag_list ';'
+ {
+ PLpgSQL_stmt_getdiag *new;
+ ListCell *lc;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_getdiag));
+ new->cmd_type = PLPGSQL_STMT_GETDIAG;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->is_stacked = $2;
+ new->diag_items = $4;
+
+ /*
+ * Check information items are valid for area option.
+ */
+ foreach(lc, new->diag_items)
+ {
+ PLpgSQL_diag_item *ditem = (PLpgSQL_diag_item *) lfirst(lc);
+
+ switch (ditem->kind)
+ {
+ /* these fields are disallowed in stacked case */
+ case PLPGSQL_GETDIAG_ROW_COUNT:
+ if (new->is_stacked)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS",
+ plpgsql_getdiag_kindname(ditem->kind)),
+ parser_errposition(@1)));
+ break;
+ /* these fields are disallowed in current case */
+ case PLPGSQL_GETDIAG_ERROR_CONTEXT:
+ case PLPGSQL_GETDIAG_ERROR_DETAIL:
+ case PLPGSQL_GETDIAG_ERROR_HINT:
+ case PLPGSQL_GETDIAG_RETURNED_SQLSTATE:
+ case PLPGSQL_GETDIAG_COLUMN_NAME:
+ case PLPGSQL_GETDIAG_CONSTRAINT_NAME:
+ case PLPGSQL_GETDIAG_DATATYPE_NAME:
+ case PLPGSQL_GETDIAG_MESSAGE_TEXT:
+ case PLPGSQL_GETDIAG_TABLE_NAME:
+ case PLPGSQL_GETDIAG_SCHEMA_NAME:
+ if (!new->is_stacked)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS",
+ plpgsql_getdiag_kindname(ditem->kind)),
+ parser_errposition(@1)));
+ break;
+ /* these fields are allowed in either case */
+ case PLPGSQL_GETDIAG_CONTEXT:
+ break;
+ default:
+ elog(ERROR, "unrecognized diagnostic item kind: %d",
+ ditem->kind);
+ break;
+ }
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+getdiag_area_opt :
+ {
+ $$ = false;
+ }
+ | K_CURRENT
+ {
+ $$ = false;
+ }
+ | K_STACKED
+ {
+ $$ = true;
+ }
+ ;
+
+getdiag_list : getdiag_list ',' getdiag_list_item
+ {
+ $$ = lappend($1, $3);
+ }
+ | getdiag_list_item
+ {
+ $$ = list_make1($1);
+ }
+ ;
+
+getdiag_list_item : getdiag_target assign_operator getdiag_item
+ {
+ PLpgSQL_diag_item *new;
+
+ new = palloc(sizeof(PLpgSQL_diag_item));
+ new->target = $1->dno;
+ new->kind = $3;
+
+ $$ = new;
+ }
+ ;
+
+getdiag_item :
+ {
+ int tok = yylex();
+
+ if (tok_is_keyword(tok, &yylval,
+ K_ROW_COUNT, "row_count"))
+ $$ = PLPGSQL_GETDIAG_ROW_COUNT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_CONTEXT, "pg_context"))
+ $$ = PLPGSQL_GETDIAG_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_DETAIL, "pg_exception_detail"))
+ $$ = PLPGSQL_GETDIAG_ERROR_DETAIL;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_HINT, "pg_exception_hint"))
+ $$ = PLPGSQL_GETDIAG_ERROR_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_EXCEPTION_CONTEXT, "pg_exception_context"))
+ $$ = PLPGSQL_GETDIAG_ERROR_CONTEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_COLUMN_NAME, "column_name"))
+ $$ = PLPGSQL_GETDIAG_COLUMN_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_CONSTRAINT_NAME, "constraint_name"))
+ $$ = PLPGSQL_GETDIAG_CONSTRAINT_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_PG_DATATYPE_NAME, "pg_datatype_name"))
+ $$ = PLPGSQL_GETDIAG_DATATYPE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_MESSAGE_TEXT, "message_text"))
+ $$ = PLPGSQL_GETDIAG_MESSAGE_TEXT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_TABLE_NAME, "table_name"))
+ $$ = PLPGSQL_GETDIAG_TABLE_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCHEMA_NAME, "schema_name"))
+ $$ = PLPGSQL_GETDIAG_SCHEMA_NAME;
+ else if (tok_is_keyword(tok, &yylval,
+ K_RETURNED_SQLSTATE, "returned_sqlstate"))
+ $$ = PLPGSQL_GETDIAG_RETURNED_SQLSTATE;
+ else
+ yyerror("unrecognized GET DIAGNOSTICS item");
+ }
+ ;
+
+getdiag_target : T_DATUM
+ {
+ /*
+ * In principle we should support a getdiag_target
+ * that is an array element, but for now we don't, so
+ * just throw an error if next token is '['.
+ */
+ if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ $1.datum->dtype == PLPGSQL_DTYPE_REC ||
+ plpgsql_peek() == '[')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a scalar variable",
+ NameOfDatum(&($1))),
+ parser_errposition(@1)));
+ check_assignable($1.datum, @1);
+ $$ = $1.datum;
+ }
+ | T_WORD
+ {
+ /* just to give a better message than "syntax error" */
+ word_is_not_variable(&($1), @1);
+ }
+ | T_CWORD
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&($1), @1);
+ }
+ ;
+
+stmt_if : K_IF expr_until_then proc_sect stmt_elsifs stmt_else K_END K_IF ';'
+ {
+ PLpgSQL_stmt_if *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_if));
+ new->cmd_type = PLPGSQL_STMT_IF;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->cond = $2;
+ new->then_body = $3;
+ new->elsif_list = $4;
+ new->else_body = $5;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_elsifs :
+ {
+ $$ = NIL;
+ }
+ | stmt_elsifs K_ELSIF expr_until_then proc_sect
+ {
+ PLpgSQL_if_elsif *new;
+
+ new = palloc0(sizeof(PLpgSQL_if_elsif));
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->cond = $3;
+ new->stmts = $4;
+
+ $$ = lappend($1, new);
+ }
+ ;
+
+stmt_else :
+ {
+ $$ = NIL;
+ }
+ | K_ELSE proc_sect
+ {
+ $$ = $2;
+ }
+ ;
+
+stmt_case : K_CASE opt_expr_until_when case_when_list opt_case_else K_END K_CASE ';'
+ {
+ $$ = make_case(@1, $2, $3, $4);
+ }
+ ;
+
+opt_expr_until_when :
+ {
+ PLpgSQL_expr *expr = NULL;
+ int tok = yylex();
+
+ if (tok != K_WHEN)
+ {
+ plpgsql_push_back_token(tok);
+ expr = read_sql_expression(K_WHEN, "WHEN");
+ }
+ plpgsql_push_back_token(K_WHEN);
+ $$ = expr;
+ }
+ ;
+
+case_when_list : case_when_list case_when
+ {
+ $$ = lappend($1, $2);
+ }
+ | case_when
+ {
+ $$ = list_make1($1);
+ }
+ ;
+
+case_when : K_WHEN expr_until_then proc_sect
+ {
+ PLpgSQL_case_when *new = palloc(sizeof(PLpgSQL_case_when));
+
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->expr = $2;
+ new->stmts = $3;
+ $$ = new;
+ }
+ ;
+
+opt_case_else :
+ {
+ $$ = NIL;
+ }
+ | K_ELSE proc_sect
+ {
+ /*
+ * proc_sect could return an empty list, but we
+ * must distinguish that from not having ELSE at all.
+ * Simplest fix is to return a list with one NULL
+ * pointer, which make_case() must take care of.
+ */
+ if ($2 != NIL)
+ $$ = $2;
+ else
+ $$ = list_make1(NULL);
+ }
+ ;
+
+stmt_loop : opt_loop_label K_LOOP loop_body
+ {
+ PLpgSQL_stmt_loop *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_loop));
+ new->cmd_type = PLPGSQL_STMT_LOOP;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = $1;
+ new->body = $3.stmts;
+
+ check_labels($1, $3.end_label, $3.end_label_location);
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_while : opt_loop_label K_WHILE expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_while *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_while));
+ new->cmd_type = PLPGSQL_STMT_WHILE;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = $1;
+ new->cond = $3;
+ new->body = $4.stmts;
+
+ check_labels($1, $4.end_label, $4.end_label_location);
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_for : opt_loop_label K_FOR for_control loop_body
+ {
+ /* This runs after we've scanned the loop body */
+ if ($3->cmd_type == PLPGSQL_STMT_FORI)
+ {
+ PLpgSQL_stmt_fori *new;
+
+ new = (PLpgSQL_stmt_fori *) $3;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->label = $1;
+ new->body = $4.stmts;
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ PLpgSQL_stmt_forq *new;
+
+ Assert($3->cmd_type == PLPGSQL_STMT_FORS ||
+ $3->cmd_type == PLPGSQL_STMT_FORC ||
+ $3->cmd_type == PLPGSQL_STMT_DYNFORS);
+ /* forq is the common supertype of all three */
+ new = (PLpgSQL_stmt_forq *) $3;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->label = $1;
+ new->body = $4.stmts;
+ $$ = (PLpgSQL_stmt *) new;
+ }
+
+ check_labels($1, $4.end_label, $4.end_label_location);
+ /* close namespace started in opt_loop_label */
+ plpgsql_ns_pop();
+ }
+ ;
+
+for_control : for_variable K_IN
+ {
+ int tok = yylex();
+ int tokloc = yylloc;
+
+ if (tok == K_EXECUTE)
+ {
+ /* EXECUTE means it's a dynamic FOR loop */
+ PLpgSQL_stmt_dynfors *new;
+ PLpgSQL_expr *expr;
+ int term;
+
+ expr = read_sql_expression2(K_LOOP, K_USING,
+ "LOOP or USING",
+ &term);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_dynfors));
+ new->cmd_type = PLPGSQL_STMT_DYNFORS;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ if ($1.row)
+ {
+ new->var = (PLpgSQL_variable *) $1.row;
+ check_assignable($1.row, @1);
+ }
+ else if ($1.scalar)
+ {
+ /* convert single scalar to list */
+ new->var = (PLpgSQL_variable *)
+ make_scalar_list1($1.name, $1.scalar,
+ $1.lineno, @1);
+ /* make_scalar_list1 did check_assignable */
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
+ parser_errposition(@1)));
+ }
+ new->query = expr;
+
+ if (term == K_USING)
+ {
+ do
+ {
+ expr = read_sql_expression2(',', K_LOOP,
+ ", or LOOP",
+ &term);
+ new->params = lappend(new->params, expr);
+ } while (term == ',');
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ else if (tok == T_DATUM &&
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR &&
+ ((PLpgSQL_var *) yylval.wdatum.datum)->datatype->typoid == REFCURSOROID)
+ {
+ /* It's FOR var IN cursor */
+ PLpgSQL_stmt_forc *new;
+ PLpgSQL_var *cursor = (PLpgSQL_var *) yylval.wdatum.datum;
+
+ new = (PLpgSQL_stmt_forc *) palloc0(sizeof(PLpgSQL_stmt_forc));
+ new->cmd_type = PLPGSQL_STMT_FORC;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = cursor->dno;
+
+ /* Should have had a single variable name */
+ if ($1.scalar && $1.row)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor FOR loop must have only one target variable"),
+ parser_errposition(@1)));
+
+ /* can't use an unbound cursor this way */
+ if (cursor->cursor_explicit_expr == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor FOR loop must use a bound cursor variable"),
+ parser_errposition(tokloc)));
+
+ /* collect cursor's parameters if any */
+ new->argquery = read_cursor_args(cursor,
+ K_LOOP);
+
+ /* create loop's private RECORD variable */
+ new->var = (PLpgSQL_variable *)
+ plpgsql_build_record($1.name,
+ $1.lineno,
+ NULL,
+ RECORDOID,
+ true);
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ PLpgSQL_expr *expr1;
+ int expr1loc;
+ bool reverse = false;
+
+ /*
+ * We have to distinguish between two
+ * alternatives: FOR var IN a .. b and FOR
+ * var IN query. Unfortunately this is
+ * tricky, since the query in the second
+ * form needn't start with a SELECT
+ * keyword. We use the ugly hack of
+ * looking for two periods after the first
+ * token. We also check for the REVERSE
+ * keyword, which means it must be an
+ * integer loop.
+ */
+ if (tok_is_keyword(tok, &yylval,
+ K_REVERSE, "reverse"))
+ reverse = true;
+ else
+ plpgsql_push_back_token(tok);
+
+ /*
+ * Read tokens until we see either a ".."
+ * or a LOOP. The text we read may be either
+ * an expression or a whole SQL statement, so
+ * we need to invoke read_sql_construct directly,
+ * and tell it not to check syntax yet.
+ */
+ expr1 = read_sql_construct(DOT_DOT,
+ K_LOOP,
+ 0,
+ "LOOP",
+ RAW_PARSE_DEFAULT,
+ true,
+ false,
+ true,
+ &expr1loc,
+ &tok);
+
+ if (tok == DOT_DOT)
+ {
+ /* Saw "..", so it must be an integer loop */
+ PLpgSQL_expr *expr2;
+ PLpgSQL_expr *expr_by;
+ PLpgSQL_var *fvar;
+ PLpgSQL_stmt_fori *new;
+
+ /*
+ * Relabel first expression as an expression;
+ * then we can check its syntax.
+ */
+ expr1->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
+
+ /* Read and check the second one */
+ expr2 = read_sql_expression2(K_LOOP, K_BY,
+ "LOOP",
+ &tok);
+
+ /* Get the BY clause if any */
+ if (tok == K_BY)
+ expr_by = read_sql_expression(K_LOOP,
+ "LOOP");
+ else
+ expr_by = NULL;
+
+ /* Should have had a single variable name */
+ if ($1.scalar && $1.row)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("integer FOR loop must have only one target variable"),
+ parser_errposition(@1)));
+
+ /* create loop's private variable */
+ fvar = (PLpgSQL_var *)
+ plpgsql_build_variable($1.name,
+ $1.lineno,
+ plpgsql_build_datatype(INT4OID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_fori));
+ new->cmd_type = PLPGSQL_STMT_FORI;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->var = fvar;
+ new->reverse = reverse;
+ new->lower = expr1;
+ new->upper = expr2;
+ new->step = expr_by;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ else
+ {
+ /*
+ * No "..", so it must be a query loop.
+ */
+ PLpgSQL_stmt_fors *new;
+
+ if (reverse)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cannot specify REVERSE in query FOR loop"),
+ parser_errposition(tokloc)));
+
+ /* Check syntax as a regular query */
+ check_sql_expr(expr1->query, expr1->parseMode,
+ expr1loc);
+
+ new = palloc0(sizeof(PLpgSQL_stmt_fors));
+ new->cmd_type = PLPGSQL_STMT_FORS;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ if ($1.row)
+ {
+ new->var = (PLpgSQL_variable *) $1.row;
+ check_assignable($1.row, @1);
+ }
+ else if ($1.scalar)
+ {
+ /* convert single scalar to list */
+ new->var = (PLpgSQL_variable *)
+ make_scalar_list1($1.name, $1.scalar,
+ $1.lineno, @1);
+ /* make_scalar_list1 did check_assignable */
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("loop variable of loop over rows must be a record variable or list of scalar variables"),
+ parser_errposition(@1)));
+ }
+
+ new->query = expr1;
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ }
+ }
+ ;
+
+/*
+ * Processing the for_variable is tricky because we don't yet know if the
+ * FOR is an integer FOR loop or a loop over query results. In the former
+ * case, the variable is just a name that we must instantiate as a loop
+ * local variable, regardless of any other definition it might have.
+ * Therefore, we always save the actual identifier into $$.name where it
+ * can be used for that case. We also save the outer-variable definition,
+ * if any, because that's what we need for the loop-over-query case. Note
+ * that we must NOT apply check_assignable() or any other semantic check
+ * until we know what's what.
+ *
+ * However, if we see a comma-separated list of names, we know that it
+ * can't be an integer FOR loop and so it's OK to check the variables
+ * immediately. In particular, for T_WORD followed by comma, we should
+ * complain that the name is not known rather than say it's a syntax error.
+ * Note that the non-error result of this case sets *both* $$.scalar and
+ * $$.row; see the for_control production.
+ */
+for_variable : T_DATUM
+ {
+ $$.name = NameOfDatum(&($1));
+ $$.lineno = plpgsql_location_to_lineno(@1);
+ if ($1.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ $1.datum->dtype == PLPGSQL_DTYPE_REC)
+ {
+ $$.scalar = NULL;
+ $$.row = $1.datum;
+ }
+ else
+ {
+ int tok;
+
+ $$.scalar = $1.datum;
+ $$.row = NULL;
+ /* check for comma-separated list */
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == ',')
+ $$.row = (PLpgSQL_datum *)
+ read_into_scalar_list($$.name,
+ $$.scalar,
+ @1);
+ }
+ }
+ | T_WORD
+ {
+ int tok;
+
+ $$.name = $1.ident;
+ $$.lineno = plpgsql_location_to_lineno(@1);
+ $$.scalar = NULL;
+ $$.row = NULL;
+ /* check for comma-separated list */
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == ',')
+ word_is_not_variable(&($1), @1);
+ }
+ | T_CWORD
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&($1), @1);
+ }
+ ;
+
+stmt_foreach_a : opt_loop_label K_FOREACH for_variable foreach_slice K_IN K_ARRAY expr_until_loop loop_body
+ {
+ PLpgSQL_stmt_foreach_a *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_foreach_a));
+ new->cmd_type = PLPGSQL_STMT_FOREACH_A;
+ new->lineno = plpgsql_location_to_lineno(@2);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->label = $1;
+ new->slice = $4;
+ new->expr = $7;
+ new->body = $8.stmts;
+
+ if ($3.row)
+ {
+ new->varno = $3.row->dno;
+ check_assignable($3.row, @3);
+ }
+ else if ($3.scalar)
+ {
+ new->varno = $3.scalar->dno;
+ check_assignable($3.scalar, @3);
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("loop variable of FOREACH must be a known variable or list of variables"),
+ parser_errposition(@3)));
+ }
+
+ check_labels($1, $8.end_label, $8.end_label_location);
+ plpgsql_ns_pop();
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+foreach_slice :
+ {
+ $$ = 0;
+ }
+ | K_SLICE ICONST
+ {
+ $$ = $2;
+ }
+ ;
+
+stmt_exit : exit_type opt_label opt_exitcond
+ {
+ PLpgSQL_stmt_exit *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_exit));
+ new->cmd_type = PLPGSQL_STMT_EXIT;
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->is_exit = $1;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->label = $2;
+ new->cond = $3;
+
+ if ($2)
+ {
+ /* We have a label, so verify it exists */
+ PLpgSQL_nsitem *label;
+
+ label = plpgsql_ns_lookup_label(plpgsql_ns_top(), $2);
+ if (label == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("there is no label \"%s\" "
+ "attached to any block or loop enclosing this statement",
+ $2),
+ parser_errposition(@2)));
+ /* CONTINUE only allows loop labels */
+ if (label->itemno != PLPGSQL_LABEL_LOOP && !new->is_exit)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("block label \"%s\" cannot be used in CONTINUE",
+ $2),
+ parser_errposition(@2)));
+ }
+ else
+ {
+ /*
+ * No label, so make sure there is some loop (an
+ * unlabeled EXIT does not match a block, so this
+ * is the same test for both EXIT and CONTINUE)
+ */
+ if (plpgsql_ns_find_nearest_loop(plpgsql_ns_top()) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ new->is_exit ?
+ errmsg("EXIT cannot be used outside a loop, unless it has a label") :
+ errmsg("CONTINUE cannot be used outside a loop"),
+ parser_errposition(@1)));
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+exit_type : K_EXIT
+ {
+ $$ = true;
+ }
+ | K_CONTINUE
+ {
+ $$ = false;
+ }
+ ;
+
+stmt_return : K_RETURN
+ {
+ int tok;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
+ {
+ $$ = make_return_next_stmt(@1);
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_QUERY, "query"))
+ {
+ $$ = make_return_query_stmt(@1);
+ }
+ else
+ {
+ plpgsql_push_back_token(tok);
+ $$ = make_return_stmt(@1);
+ }
+ }
+ ;
+
+stmt_raise : K_RAISE
+ {
+ PLpgSQL_stmt_raise *new;
+ int tok;
+
+ new = palloc(sizeof(PLpgSQL_stmt_raise));
+
+ new->cmd_type = PLPGSQL_STMT_RAISE;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->elog_level = ERROR; /* default */
+ new->condname = NULL;
+ new->message = NULL;
+ new->params = NIL;
+ new->options = NIL;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ /*
+ * We could have just RAISE, meaning to re-throw
+ * the current error.
+ */
+ if (tok != ';')
+ {
+ /*
+ * First is an optional elog severity level.
+ */
+ if (tok_is_keyword(tok, &yylval,
+ K_EXCEPTION, "exception"))
+ {
+ new->elog_level = ERROR;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_WARNING, "warning"))
+ {
+ new->elog_level = WARNING;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_NOTICE, "notice"))
+ {
+ new->elog_level = NOTICE;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_INFO, "info"))
+ {
+ new->elog_level = INFO;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_LOG, "log"))
+ {
+ new->elog_level = LOG;
+ tok = yylex();
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_DEBUG, "debug"))
+ {
+ new->elog_level = DEBUG1;
+ tok = yylex();
+ }
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ /*
+ * Next we can have a condition name, or
+ * equivalently SQLSTATE 'xxxxx', or a string
+ * literal that is the old-style message format,
+ * or USING to start the option list immediately.
+ */
+ if (tok == SCONST)
+ {
+ /* old style message and parameters */
+ new->message = yylval.str;
+ /*
+ * We expect either a semi-colon, which
+ * indicates no parameters, or a comma that
+ * begins the list of parameter expressions,
+ * or USING to begin the options list.
+ */
+ tok = yylex();
+ if (tok != ',' && tok != ';' && tok != K_USING)
+ yyerror("syntax error");
+
+ while (tok == ',')
+ {
+ PLpgSQL_expr *expr;
+
+ expr = read_sql_construct(',', ';', K_USING,
+ ", or ; or USING",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &tok);
+ new->params = lappend(new->params, expr);
+ }
+ }
+ else if (tok != K_USING)
+ {
+ /* must be condition name or SQLSTATE */
+ if (tok_is_keyword(tok, &yylval,
+ K_SQLSTATE, "sqlstate"))
+ {
+ /* next token should be a string literal */
+ char *sqlstatestr;
+
+ if (yylex() != SCONST)
+ yyerror("syntax error");
+ sqlstatestr = yylval.str;
+
+ if (strlen(sqlstatestr) != 5)
+ yyerror("invalid SQLSTATE code");
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ yyerror("invalid SQLSTATE code");
+ new->condname = sqlstatestr;
+ }
+ else
+ {
+ if (tok == T_WORD)
+ new->condname = yylval.word.ident;
+ else if (plpgsql_token_is_unreserved_keyword(tok))
+ new->condname = pstrdup(yylval.keyword);
+ else
+ yyerror("syntax error");
+ plpgsql_recognize_err_condition(new->condname,
+ false);
+ }
+ tok = yylex();
+ if (tok != ';' && tok != K_USING)
+ yyerror("syntax error");
+ }
+
+ if (tok == K_USING)
+ new->options = read_raise_options();
+ }
+
+ check_raise_parameters(new);
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_assert : K_ASSERT
+ {
+ PLpgSQL_stmt_assert *new;
+ int tok;
+
+ new = palloc(sizeof(PLpgSQL_stmt_assert));
+
+ new->cmd_type = PLPGSQL_STMT_ASSERT;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ new->cond = read_sql_expression2(',', ';',
+ ", or ;",
+ &tok);
+
+ if (tok == ',')
+ new->message = read_sql_expression(';', ";");
+ else
+ new->message = NULL;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+loop_body : proc_sect K_END K_LOOP opt_label ';'
+ {
+ $$.stmts = $1;
+ $$.end_label = $4;
+ $$.end_label_location = @4;
+ }
+ ;
+
+/*
+ * T_WORD+T_CWORD match any initial identifier that is not a known plpgsql
+ * variable. (The composite case is probably a syntax error, but we'll let
+ * the core parser decide that.) Normally, we should assume that such a
+ * word is a SQL statement keyword that isn't also a plpgsql keyword.
+ * However, if the next token is assignment or '[' or '.', it can't be a valid
+ * SQL statement, and what we're probably looking at is an intended variable
+ * assignment. Give an appropriate complaint for that, instead of letting
+ * the core parser throw an unhelpful "syntax error".
+ */
+stmt_execsql : K_IMPORT
+ {
+ $$ = make_execsql_stmt(K_IMPORT, @1);
+ }
+ | K_INSERT
+ {
+ $$ = make_execsql_stmt(K_INSERT, @1);
+ }
+ | K_MERGE
+ {
+ $$ = make_execsql_stmt(K_MERGE, @1);
+ }
+ | T_WORD
+ {
+ int tok;
+
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
+ word_is_not_variable(&($1), @1);
+ $$ = make_execsql_stmt(T_WORD, @1);
+ }
+ | T_CWORD
+ {
+ int tok;
+
+ tok = yylex();
+ plpgsql_push_back_token(tok);
+ if (tok == '=' || tok == COLON_EQUALS ||
+ tok == '[' || tok == '.')
+ cword_is_not_variable(&($1), @1);
+ $$ = make_execsql_stmt(T_CWORD, @1);
+ }
+ ;
+
+stmt_dynexecute : K_EXECUTE
+ {
+ PLpgSQL_stmt_dynexecute *new;
+ PLpgSQL_expr *expr;
+ int endtoken;
+
+ expr = read_sql_construct(K_INTO, K_USING, ';',
+ "INTO or USING or ;",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &endtoken);
+
+ new = palloc(sizeof(PLpgSQL_stmt_dynexecute));
+ new->cmd_type = PLPGSQL_STMT_DYNEXECUTE;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->query = expr;
+ new->into = false;
+ new->strict = false;
+ new->target = NULL;
+ new->params = NIL;
+
+ /*
+ * We loop to allow the INTO and USING clauses to
+ * appear in either order, since people easily get
+ * that wrong. This coding also prevents "INTO foo"
+ * from getting absorbed into a USING expression,
+ * which is *really* confusing.
+ */
+ for (;;)
+ {
+ if (endtoken == K_INTO)
+ {
+ if (new->into) /* multiple INTO */
+ yyerror("syntax error");
+ new->into = true;
+ read_into_target(&new->target, &new->strict);
+ endtoken = yylex();
+ }
+ else if (endtoken == K_USING)
+ {
+ if (new->params) /* multiple USING */
+ yyerror("syntax error");
+ do
+ {
+ expr = read_sql_construct(',', ';', K_INTO,
+ ", or ; or INTO",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true,
+ NULL, &endtoken);
+ new->params = lappend(new->params, expr);
+ } while (endtoken == ',');
+ }
+ else if (endtoken == ';')
+ break;
+ else
+ yyerror("syntax error");
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+
+stmt_open : K_OPEN cursor_variable
+ {
+ PLpgSQL_stmt_open *new;
+ int tok;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_open));
+ new->cmd_type = PLPGSQL_STMT_OPEN;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = $2->dno;
+ new->cursor_options = CURSOR_OPT_FAST_PLAN;
+
+ if ($2->cursor_explicit_expr == NULL)
+ {
+ /* be nice if we could use opt_scrollable here */
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_NO, "no"))
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
+ {
+ new->cursor_options |= CURSOR_OPT_NO_SCROLL;
+ tok = yylex();
+ }
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCROLL, "scroll"))
+ {
+ new->cursor_options |= CURSOR_OPT_SCROLL;
+ tok = yylex();
+ }
+
+ if (tok != K_FOR)
+ yyerror("syntax error, expected \"FOR\"");
+
+ tok = yylex();
+ if (tok == K_EXECUTE)
+ {
+ int endtoken;
+
+ new->dynquery =
+ read_sql_expression2(K_USING, ';',
+ "USING or ;",
+ &endtoken);
+
+ /* If we found "USING", collect argument(s) */
+ if (endtoken == K_USING)
+ {
+ PLpgSQL_expr *expr;
+
+ do
+ {
+ expr = read_sql_expression2(',', ';',
+ ", or ;",
+ &endtoken);
+ new->params = lappend(new->params,
+ expr);
+ } while (endtoken == ',');
+ }
+ }
+ else
+ {
+ plpgsql_push_back_token(tok);
+ new->query = read_sql_stmt();
+ }
+ }
+ else
+ {
+ /* predefined cursor query, so read args */
+ new->argquery = read_cursor_args($2, ';');
+ }
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_fetch : K_FETCH opt_fetch_direction cursor_variable K_INTO
+ {
+ PLpgSQL_stmt_fetch *fetch = $2;
+ PLpgSQL_variable *target;
+
+ /* We have already parsed everything through the INTO keyword */
+ read_into_target(&target, NULL);
+
+ if (yylex() != ';')
+ yyerror("syntax error");
+
+ /*
+ * We don't allow multiple rows in PL/pgSQL's FETCH
+ * statement, only in MOVE.
+ */
+ if (fetch->returns_multiple_rows)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("FETCH statement cannot return multiple rows"),
+ parser_errposition(@1)));
+
+ fetch->lineno = plpgsql_location_to_lineno(@1);
+ fetch->target = target;
+ fetch->curvar = $3->dno;
+ fetch->is_move = false;
+
+ $$ = (PLpgSQL_stmt *) fetch;
+ }
+ ;
+
+stmt_move : K_MOVE opt_fetch_direction cursor_variable ';'
+ {
+ PLpgSQL_stmt_fetch *fetch = $2;
+
+ fetch->lineno = plpgsql_location_to_lineno(@1);
+ fetch->curvar = $3->dno;
+ fetch->is_move = true;
+
+ $$ = (PLpgSQL_stmt *) fetch;
+ }
+ ;
+
+opt_fetch_direction :
+ {
+ $$ = read_fetch_direction();
+ }
+ ;
+
+stmt_close : K_CLOSE cursor_variable ';'
+ {
+ PLpgSQL_stmt_close *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_close));
+ new->cmd_type = PLPGSQL_STMT_CLOSE;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->curvar = $2->dno;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_null : K_NULL ';'
+ {
+ /* We do not bother building a node for NULL */
+ $$ = NULL;
+ }
+ ;
+
+stmt_commit : K_COMMIT opt_transaction_chain ';'
+ {
+ PLpgSQL_stmt_commit *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_commit));
+ new->cmd_type = PLPGSQL_STMT_COMMIT;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->chain = $2;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+stmt_rollback : K_ROLLBACK opt_transaction_chain ';'
+ {
+ PLpgSQL_stmt_rollback *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_rollback));
+ new->cmd_type = PLPGSQL_STMT_ROLLBACK;
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->chain = $2;
+
+ $$ = (PLpgSQL_stmt *) new;
+ }
+ ;
+
+opt_transaction_chain:
+ K_AND K_CHAIN { $$ = true; }
+ | K_AND K_NO K_CHAIN { $$ = false; }
+ | /* EMPTY */ { $$ = false; }
+ ;
+
+
+cursor_variable : T_DATUM
+ {
+ /*
+ * In principle we should support a cursor_variable
+ * that is an array element, but for now we don't, so
+ * just throw an error if next token is '['.
+ */
+ if ($1.datum->dtype != PLPGSQL_DTYPE_VAR ||
+ plpgsql_peek() == '[')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cursor variable must be a simple variable"),
+ parser_errposition(@1)));
+
+ if (((PLpgSQL_var *) $1.datum)->datatype->typoid != REFCURSOROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("variable \"%s\" must be of type cursor or refcursor",
+ ((PLpgSQL_var *) $1.datum)->refname),
+ parser_errposition(@1)));
+ $$ = (PLpgSQL_var *) $1.datum;
+ }
+ | T_WORD
+ {
+ /* just to give a better message than "syntax error" */
+ word_is_not_variable(&($1), @1);
+ }
+ | T_CWORD
+ {
+ /* just to give a better message than "syntax error" */
+ cword_is_not_variable(&($1), @1);
+ }
+ ;
+
+exception_sect :
+ { $$ = NULL; }
+ | K_EXCEPTION
+ {
+ /*
+ * We use a mid-rule action to add these
+ * special variables to the namespace before
+ * parsing the WHEN clauses themselves. The
+ * scope of the names extends to the end of the
+ * current block.
+ */
+ int lineno = plpgsql_location_to_lineno(@1);
+ PLpgSQL_exception_block *new = palloc(sizeof(PLpgSQL_exception_block));
+ PLpgSQL_variable *var;
+
+ var = plpgsql_build_variable("sqlstate", lineno,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ plpgsql_curr_compile->fn_input_collation,
+ NULL),
+ true);
+ var->isconst = true;
+ new->sqlstate_varno = var->dno;
+
+ var = plpgsql_build_variable("sqlerrm", lineno,
+ plpgsql_build_datatype(TEXTOID,
+ -1,
+ plpgsql_curr_compile->fn_input_collation,
+ NULL),
+ true);
+ var->isconst = true;
+ new->sqlerrm_varno = var->dno;
+
+ $<exception_block>$ = new;
+ }
+ proc_exceptions
+ {
+ PLpgSQL_exception_block *new = $<exception_block>2;
+ new->exc_list = $3;
+
+ $$ = new;
+ }
+ ;
+
+proc_exceptions : proc_exceptions proc_exception
+ {
+ $$ = lappend($1, $2);
+ }
+ | proc_exception
+ {
+ $$ = list_make1($1);
+ }
+ ;
+
+proc_exception : K_WHEN proc_conditions K_THEN proc_sect
+ {
+ PLpgSQL_exception *new;
+
+ new = palloc0(sizeof(PLpgSQL_exception));
+ new->lineno = plpgsql_location_to_lineno(@1);
+ new->conditions = $2;
+ new->action = $4;
+
+ $$ = new;
+ }
+ ;
+
+proc_conditions : proc_conditions K_OR proc_condition
+ {
+ PLpgSQL_condition *old;
+
+ for (old = $1; old->next != NULL; old = old->next)
+ /* skip */ ;
+ old->next = $3;
+ $$ = $1;
+ }
+ | proc_condition
+ {
+ $$ = $1;
+ }
+ ;
+
+proc_condition : any_identifier
+ {
+ if (strcmp($1, "sqlstate") != 0)
+ {
+ $$ = plpgsql_parse_err_condition($1);
+ }
+ else
+ {
+ PLpgSQL_condition *new;
+ char *sqlstatestr;
+
+ /* next token should be a string literal */
+ if (yylex() != SCONST)
+ yyerror("syntax error");
+ sqlstatestr = yylval.str;
+
+ if (strlen(sqlstatestr) != 5)
+ yyerror("invalid SQLSTATE code");
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ yyerror("invalid SQLSTATE code");
+
+ new = palloc(sizeof(PLpgSQL_condition));
+ new->sqlerrstate =
+ MAKE_SQLSTATE(sqlstatestr[0],
+ sqlstatestr[1],
+ sqlstatestr[2],
+ sqlstatestr[3],
+ sqlstatestr[4]);
+ new->condname = sqlstatestr;
+ new->next = NULL;
+
+ $$ = new;
+ }
+ }
+ ;
+
+expr_until_semi :
+ { $$ = read_sql_expression(';', ";"); }
+ ;
+
+expr_until_then :
+ { $$ = read_sql_expression(K_THEN, "THEN"); }
+ ;
+
+expr_until_loop :
+ { $$ = read_sql_expression(K_LOOP, "LOOP"); }
+ ;
+
+opt_block_label :
+ {
+ plpgsql_ns_push(NULL, PLPGSQL_LABEL_BLOCK);
+ $$ = NULL;
+ }
+ | LESS_LESS any_identifier GREATER_GREATER
+ {
+ plpgsql_ns_push($2, PLPGSQL_LABEL_BLOCK);
+ $$ = $2;
+ }
+ ;
+
+opt_loop_label :
+ {
+ plpgsql_ns_push(NULL, PLPGSQL_LABEL_LOOP);
+ $$ = NULL;
+ }
+ | LESS_LESS any_identifier GREATER_GREATER
+ {
+ plpgsql_ns_push($2, PLPGSQL_LABEL_LOOP);
+ $$ = $2;
+ }
+ ;
+
+opt_label :
+ {
+ $$ = NULL;
+ }
+ | any_identifier
+ {
+ /* label validity will be checked by outer production */
+ $$ = $1;
+ }
+ ;
+
+opt_exitcond : ';'
+ { $$ = NULL; }
+ | K_WHEN expr_until_semi
+ { $$ = $2; }
+ ;
+
+/*
+ * need to allow DATUM because scanner will have tried to resolve as variable
+ */
+any_identifier : T_WORD
+ {
+ $$ = $1.ident;
+ }
+ | unreserved_keyword
+ {
+ $$ = pstrdup($1);
+ }
+ | T_DATUM
+ {
+ if ($1.ident == NULL) /* composite name not OK */
+ yyerror("syntax error");
+ $$ = $1.ident;
+ }
+ ;
+
+unreserved_keyword :
+ K_ABSOLUTE
+ | K_ALIAS
+ | K_AND
+ | K_ARRAY
+ | K_ASSERT
+ | K_BACKWARD
+ | K_CALL
+ | K_CHAIN
+ | K_CLOSE
+ | K_COLLATE
+ | K_COLUMN
+ | K_COLUMN_NAME
+ | K_COMMIT
+ | K_CONSTANT
+ | K_CONSTRAINT
+ | K_CONSTRAINT_NAME
+ | K_CONTINUE
+ | K_CURRENT
+ | K_CURSOR
+ | K_DATATYPE
+ | K_DEBUG
+ | K_DEFAULT
+ | K_DETAIL
+ | K_DIAGNOSTICS
+ | K_DO
+ | K_DUMP
+ | K_ELSIF
+ | K_ERRCODE
+ | K_ERROR
+ | K_EXCEPTION
+ | K_EXIT
+ | K_FETCH
+ | K_FIRST
+ | K_FORWARD
+ | K_GET
+ | K_HINT
+ | K_IMPORT
+ | K_INFO
+ | K_INSERT
+ | K_IS
+ | K_LAST
+ | K_LOG
+ | K_MERGE
+ | K_MESSAGE
+ | K_MESSAGE_TEXT
+ | K_MOVE
+ | K_NEXT
+ | K_NO
+ | K_NOTICE
+ | K_OPEN
+ | K_OPTION
+ | K_PERFORM
+ | K_PG_CONTEXT
+ | K_PG_DATATYPE_NAME
+ | K_PG_EXCEPTION_CONTEXT
+ | K_PG_EXCEPTION_DETAIL
+ | K_PG_EXCEPTION_HINT
+ | K_PRINT_STRICT_PARAMS
+ | K_PRIOR
+ | K_QUERY
+ | K_RAISE
+ | K_RELATIVE
+ | K_RETURN
+ | K_RETURNED_SQLSTATE
+ | K_REVERSE
+ | K_ROLLBACK
+ | K_ROW_COUNT
+ | K_ROWTYPE
+ | K_SCHEMA
+ | K_SCHEMA_NAME
+ | K_SCROLL
+ | K_SLICE
+ | K_SQLSTATE
+ | K_STACKED
+ | K_TABLE
+ | K_TABLE_NAME
+ | K_TYPE
+ | K_USE_COLUMN
+ | K_USE_VARIABLE
+ | K_VARIABLE_CONFLICT
+ | K_WARNING
+ ;
+
+%%
+
+/*
+ * Check whether a token represents an "unreserved keyword".
+ * We have various places where we want to recognize a keyword in preference
+ * to a variable name, but not reserve that keyword in other contexts.
+ * Hence, this kluge.
+ */
+static bool
+tok_is_keyword(int token, union YYSTYPE *lval,
+ int kw_token, const char *kw_str)
+{
+ if (token == kw_token)
+ {
+ /* Normal case, was recognized by scanner (no conflicting variable) */
+ return true;
+ }
+ else if (token == T_DATUM)
+ {
+ /*
+ * It's a variable, so recheck the string name. Note we will not
+ * match composite names (hence an unreserved word followed by "."
+ * will not be recognized).
+ */
+ if (!lval->wdatum.quoted && lval->wdatum.ident != NULL &&
+ strcmp(lval->wdatum.ident, kw_str) == 0)
+ return true;
+ }
+ return false; /* not the keyword */
+}
+
+/*
+ * Convenience routine to complain when we expected T_DATUM and got T_WORD,
+ * ie, unrecognized variable.
+ */
+static void
+word_is_not_variable(PLword *word, int location)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a known variable",
+ word->ident),
+ parser_errposition(location)));
+}
+
+/* Same, for a CWORD */
+static void
+cword_is_not_variable(PLcword *cword, int location)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a known variable",
+ NameListToString(cword->idents)),
+ parser_errposition(location)));
+}
+
+/*
+ * Convenience routine to complain when we expected T_DATUM and got
+ * something else. "tok" must be the current token, since we also
+ * look at yylval and yylloc.
+ */
+static void
+current_token_is_not_variable(int tok)
+{
+ if (tok == T_WORD)
+ word_is_not_variable(&(yylval.word), yylloc);
+ else if (tok == T_CWORD)
+ cword_is_not_variable(&(yylval.cword), yylloc);
+ else
+ yyerror("syntax error");
+}
+
+/* Convenience routine to read an expression with one possible terminator */
+static PLpgSQL_expr *
+read_sql_expression(int until, const char *expected)
+{
+ return read_sql_construct(until, 0, 0, expected,
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, NULL);
+}
+
+/* Convenience routine to read an expression with two possible terminators */
+static PLpgSQL_expr *
+read_sql_expression2(int until, int until2, const char *expected,
+ int *endtoken)
+{
+ return read_sql_construct(until, until2, 0, expected,
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true, true, NULL, endtoken);
+}
+
+/* Convenience routine to read a SQL statement that must end with ';' */
+static PLpgSQL_expr *
+read_sql_stmt(void)
+{
+ return read_sql_construct(';', 0, 0, ";",
+ RAW_PARSE_DEFAULT,
+ false, true, true, NULL, NULL);
+}
+
+/*
+ * Read a SQL construct and build a PLpgSQL_expr for it.
+ *
+ * until: token code for expected terminator
+ * until2: token code for alternate terminator (pass 0 if none)
+ * until3: token code for another alternate terminator (pass 0 if none)
+ * expected: text to use in complaining that terminator was not found
+ * parsemode: raw_parser() mode to use
+ * isexpression: whether to say we're reading an "expression" or a "statement"
+ * valid_sql: whether to check the syntax of the expr
+ * trim: trim trailing whitespace
+ * startloc: if not NULL, location of first token is stored at *startloc
+ * endtoken: if not NULL, ending token is stored at *endtoken
+ * (this is only interesting if until2 or until3 isn't zero)
+ */
+static PLpgSQL_expr *
+read_sql_construct(int until,
+ int until2,
+ int until3,
+ const char *expected,
+ RawParseMode parsemode,
+ bool isexpression,
+ bool valid_sql,
+ bool trim,
+ int *startloc,
+ int *endtoken)
+{
+ int tok;
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ int startlocation = -1;
+ int parenlevel = 0;
+ PLpgSQL_expr *expr;
+
+ initStringInfo(&ds);
+
+ /* special lookup mode for identifiers within the SQL text */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+
+ for (;;)
+ {
+ tok = yylex();
+ if (startlocation < 0) /* remember loc of first token */
+ startlocation = yylloc;
+ if (tok == until && parenlevel == 0)
+ break;
+ if (tok == until2 && parenlevel == 0)
+ break;
+ if (tok == until3 && parenlevel == 0)
+ break;
+ if (tok == '(' || tok == '[')
+ parenlevel++;
+ else if (tok == ')' || tok == ']')
+ {
+ parenlevel--;
+ if (parenlevel < 0)
+ yyerror("mismatched parentheses");
+ }
+ /*
+ * End of function definition is an error, and we don't expect to
+ * hit a semicolon either (unless it's the until symbol, in which
+ * case we should have fallen out above).
+ */
+ if (tok == 0 || tok == ';')
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ if (isexpression)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL expression",
+ expected),
+ parser_errposition(yylloc)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("missing \"%s\" at end of SQL statement",
+ expected),
+ parser_errposition(yylloc)));
+ }
+ }
+
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ if (startloc)
+ *startloc = startlocation;
+ if (endtoken)
+ *endtoken = tok;
+
+ /* give helpful complaint about empty input */
+ if (startlocation >= yylloc)
+ {
+ if (isexpression)
+ yyerror("missing expression");
+ else
+ yyerror("missing SQL statement");
+ }
+
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+
+ /* trim any trailing whitespace, for neatness */
+ if (trim)
+ {
+ while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+ ds.data[--ds.len] = '\0';
+ }
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = parsemode;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ if (valid_sql)
+ check_sql_expr(expr->query, expr->parseMode, startlocation);
+
+ return expr;
+}
+
+static PLpgSQL_type *
+read_datatype(int tok)
+{
+ StringInfoData ds;
+ char *type_name;
+ int startlocation;
+ PLpgSQL_type *result;
+ int parenlevel = 0;
+
+ /* Should only be called while parsing DECLARE sections */
+ Assert(plpgsql_IdentifierLookup == IDENTIFIER_LOOKUP_DECLARE);
+
+ /* Often there will be a lookahead token, but if not, get one */
+ if (tok == YYEMPTY)
+ tok = yylex();
+
+ startlocation = yylloc;
+
+ /*
+ * If we have a simple or composite identifier, check for %TYPE
+ * and %ROWTYPE constructs.
+ */
+ if (tok == T_WORD)
+ {
+ char *dtname = yylval.word.ident;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (plpgsql_token_is_unreserved_keyword(tok))
+ {
+ char *dtname = pstrdup(yylval.keyword);
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_wordtype(dtname);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_wordrowtype(dtname);
+ if (result)
+ return result;
+ }
+ }
+ }
+ else if (tok == T_CWORD)
+ {
+ List *dtnames = yylval.cword.idents;
+
+ tok = yylex();
+ if (tok == '%')
+ {
+ tok = yylex();
+ if (tok_is_keyword(tok, &yylval,
+ K_TYPE, "type"))
+ {
+ result = plpgsql_parse_cwordtype(dtnames);
+ if (result)
+ return result;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ROWTYPE, "rowtype"))
+ {
+ result = plpgsql_parse_cwordrowtype(dtnames);
+ if (result)
+ return result;
+ }
+ }
+ }
+
+ while (tok != ';')
+ {
+ if (tok == 0)
+ {
+ if (parenlevel != 0)
+ yyerror("mismatched parentheses");
+ else
+ yyerror("incomplete data type declaration");
+ }
+ /* Possible followers for datatype in a declaration */
+ if (tok == K_COLLATE || tok == K_NOT ||
+ tok == '=' || tok == COLON_EQUALS || tok == K_DEFAULT)
+ break;
+ /* Possible followers for datatype in a cursor_arg list */
+ if ((tok == ',' || tok == ')') && parenlevel == 0)
+ break;
+ if (tok == '(')
+ parenlevel++;
+ else if (tok == ')')
+ parenlevel--;
+
+ tok = yylex();
+ }
+
+ /* set up ds to contain complete typename text */
+ initStringInfo(&ds);
+ plpgsql_append_source_text(&ds, startlocation, yylloc);
+ type_name = ds.data;
+
+ if (type_name[0] == '\0')
+ yyerror("missing data type declaration");
+
+ result = parse_datatype(type_name, startlocation);
+
+ pfree(ds.data);
+
+ plpgsql_push_back_token(tok);
+
+ return result;
+}
+
+static PLpgSQL_stmt *
+make_execsql_stmt(int firsttoken, int location)
+{
+ StringInfoData ds;
+ IdentifierLookup save_IdentifierLookup;
+ PLpgSQL_stmt_execsql *execsql;
+ PLpgSQL_expr *expr;
+ PLpgSQL_variable *target = NULL;
+ int tok;
+ int prev_tok;
+ bool have_into = false;
+ bool have_strict = false;
+ int into_start_loc = -1;
+ int into_end_loc = -1;
+
+ initStringInfo(&ds);
+
+ /* special lookup mode for identifiers within the SQL text */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+
+ /*
+ * Scan to the end of the SQL command. Identify any INTO-variables
+ * clause lurking within it, and parse that via read_into_target().
+ *
+ * Because INTO is sometimes used in the main SQL grammar, we have to be
+ * careful not to take any such usage of INTO as a PL/pgSQL INTO clause.
+ * There are currently three such cases:
+ *
+ * 1. SELECT ... INTO. We don't care, we just override that with the
+ * PL/pgSQL definition.
+ *
+ * 2. INSERT INTO. This is relatively easy to recognize since the words
+ * must appear adjacently; but we can't assume INSERT starts the command,
+ * because it can appear in CREATE RULE or WITH. Unfortunately, INSERT is
+ * *not* fully reserved, so that means there is a chance of a false match;
+ * but it's not very likely.
+ *
+ * 3. IMPORT FOREIGN SCHEMA ... INTO. This is not allowed in CREATE RULE
+ * or WITH, so we just check for IMPORT as the command's first token.
+ * (If IMPORT FOREIGN SCHEMA returned data someone might wish to capture
+ * with an INTO-variables clause, we'd have to work much harder here.)
+ *
+ * Fortunately, INTO is a fully reserved word in the main grammar, so
+ * at least we need not worry about it appearing as an identifier.
+ *
+ * Any future additional uses of INTO in the main grammar will doubtless
+ * break this logic again ... beware!
+ */
+ tok = firsttoken;
+ for (;;)
+ {
+ prev_tok = tok;
+ tok = yylex();
+ if (have_into && into_end_loc < 0)
+ into_end_loc = yylloc; /* token after the INTO part */
+ if (tok == ';')
+ break;
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+ if (tok == K_INTO)
+ {
+ if (prev_tok == K_INSERT)
+ continue; /* INSERT INTO is not an INTO-target */
+ if (prev_tok == K_MERGE)
+ continue; /* MERGE INTO is not an INTO-target */
+ if (firsttoken == K_IMPORT)
+ continue; /* IMPORT ... INTO is not an INTO-target */
+ if (have_into)
+ yyerror("INTO specified more than once");
+ have_into = true;
+ into_start_loc = yylloc;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ read_into_target(&target, &have_strict);
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_EXPR;
+ }
+ }
+
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ if (have_into)
+ {
+ /*
+ * Insert an appropriate number of spaces corresponding to the
+ * INTO text, so that locations within the redacted SQL statement
+ * still line up with those in the original source text.
+ */
+ plpgsql_append_source_text(&ds, location, into_start_loc);
+ appendStringInfoSpaces(&ds, into_end_loc - into_start_loc);
+ plpgsql_append_source_text(&ds, into_end_loc, yylloc);
+ }
+ else
+ plpgsql_append_source_text(&ds, location, yylloc);
+
+ /* trim any trailing whitespace, for neatness */
+ while (ds.len > 0 && scanner_isspace(ds.data[ds.len - 1]))
+ ds.data[--ds.len] = '\0';
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_DEFAULT;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ check_sql_expr(expr->query, expr->parseMode, location);
+
+ execsql = palloc0(sizeof(PLpgSQL_stmt_execsql));
+ execsql->cmd_type = PLPGSQL_STMT_EXECSQL;
+ execsql->lineno = plpgsql_location_to_lineno(location);
+ execsql->stmtid = ++plpgsql_curr_compile->nstatements;
+ execsql->sqlstmt = expr;
+ execsql->into = have_into;
+ execsql->strict = have_strict;
+ execsql->target = target;
+
+ return (PLpgSQL_stmt *) execsql;
+}
+
+
+/*
+ * Read FETCH or MOVE direction clause (everything through FROM/IN).
+ */
+static PLpgSQL_stmt_fetch *
+read_fetch_direction(void)
+{
+ PLpgSQL_stmt_fetch *fetch;
+ int tok;
+ bool check_FROM = true;
+
+ /*
+ * We create the PLpgSQL_stmt_fetch struct here, but only fill in
+ * the fields arising from the optional direction clause
+ */
+ fetch = (PLpgSQL_stmt_fetch *) palloc0(sizeof(PLpgSQL_stmt_fetch));
+ fetch->cmd_type = PLPGSQL_STMT_FETCH;
+ fetch->stmtid = ++plpgsql_curr_compile->nstatements;
+ /* set direction defaults: */
+ fetch->direction = FETCH_FORWARD;
+ fetch->how_many = 1;
+ fetch->expr = NULL;
+ fetch->returns_multiple_rows = false;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok_is_keyword(tok, &yylval,
+ K_NEXT, "next"))
+ {
+ /* use defaults */
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_PRIOR, "prior"))
+ {
+ fetch->direction = FETCH_BACKWARD;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_FIRST, "first"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_LAST, "last"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ fetch->how_many = -1;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ABSOLUTE, "absolute"))
+ {
+ fetch->direction = FETCH_ABSOLUTE;
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ check_FROM = false;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_RELATIVE, "relative"))
+ {
+ fetch->direction = FETCH_RELATIVE;
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ check_FROM = false;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_ALL, "all"))
+ {
+ fetch->how_many = FETCH_ALL;
+ fetch->returns_multiple_rows = true;
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_FORWARD, "forward"))
+ {
+ complete_direction(fetch, &check_FROM);
+ }
+ else if (tok_is_keyword(tok, &yylval,
+ K_BACKWARD, "backward"))
+ {
+ fetch->direction = FETCH_BACKWARD;
+ complete_direction(fetch, &check_FROM);
+ }
+ else if (tok == K_FROM || tok == K_IN)
+ {
+ /* empty direction */
+ check_FROM = false;
+ }
+ else if (tok == T_DATUM)
+ {
+ /* Assume there's no direction clause and tok is a cursor name */
+ plpgsql_push_back_token(tok);
+ check_FROM = false;
+ }
+ else
+ {
+ /*
+ * Assume it's a count expression with no preceding keyword.
+ * Note: we allow this syntax because core SQL does, but we don't
+ * document it because of the ambiguity with the omitted-direction
+ * case. For instance, "MOVE n IN c" will fail if n is a variable.
+ * Perhaps this can be improved someday, but it's hardly worth a
+ * lot of work.
+ */
+ plpgsql_push_back_token(tok);
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ fetch->returns_multiple_rows = true;
+ check_FROM = false;
+ }
+
+ /* check FROM or IN keyword after direction's specification */
+ if (check_FROM)
+ {
+ tok = yylex();
+ if (tok != K_FROM && tok != K_IN)
+ yyerror("expected FROM or IN");
+ }
+
+ return fetch;
+}
+
+/*
+ * Process remainder of FETCH/MOVE direction after FORWARD or BACKWARD.
+ * Allows these cases:
+ * FORWARD expr, FORWARD ALL, FORWARD
+ * BACKWARD expr, BACKWARD ALL, BACKWARD
+ */
+static void
+complete_direction(PLpgSQL_stmt_fetch *fetch, bool *check_FROM)
+{
+ int tok;
+
+ tok = yylex();
+ if (tok == 0)
+ yyerror("unexpected end of function definition");
+
+ if (tok == K_FROM || tok == K_IN)
+ {
+ *check_FROM = false;
+ return;
+ }
+
+ if (tok == K_ALL)
+ {
+ fetch->how_many = FETCH_ALL;
+ fetch->returns_multiple_rows = true;
+ *check_FROM = true;
+ return;
+ }
+
+ plpgsql_push_back_token(tok);
+ fetch->expr = read_sql_expression2(K_FROM, K_IN,
+ "FROM or IN",
+ NULL);
+ fetch->returns_multiple_rows = true;
+ *check_FROM = false;
+}
+
+
+static PLpgSQL_stmt *
+make_return_stmt(int location)
+{
+ PLpgSQL_stmt_return *new;
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return));
+ new->cmd_type = PLPGSQL_STMT_RETURN;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->expr = NULL;
+ new->retvarno = -1;
+
+ if (plpgsql_curr_compile->fn_retset)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function returning set"),
+ errhint("Use RETURN NEXT or RETURN QUERY."),
+ parser_errposition(yylloc)));
+ }
+ else if (plpgsql_curr_compile->fn_rettype == VOIDOID)
+ {
+ if (yylex() != ';')
+ {
+ if (plpgsql_curr_compile->fn_prokind == PROKIND_PROCEDURE)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("RETURN cannot have a parameter in a procedure"),
+ parser_errposition(yylloc)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function returning void"),
+ parser_errposition(yylloc)));
+ }
+ }
+ else if (plpgsql_curr_compile->out_param_varno >= 0)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN cannot have a parameter in function with OUT parameters"),
+ parser_errposition(yylloc)));
+ new->retvarno = plpgsql_curr_compile->out_param_varno;
+ }
+ else
+ {
+ /*
+ * We want to special-case simple variable references for efficiency.
+ * So peek ahead to see if that's what we have.
+ */
+ int tok = yylex();
+
+ if (tok == T_DATUM && plpgsql_peek() == ';' &&
+ (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
+ {
+ new->retvarno = yylval.wdatum.datum->dno;
+ /* eat the semicolon token that we only peeked at above */
+ tok = yylex();
+ Assert(tok == ';');
+ }
+ else
+ {
+ /*
+ * Not (just) a variable name, so treat as expression.
+ *
+ * Note that a well-formed expression is _required_ here;
+ * anything else is a compile-time error.
+ */
+ plpgsql_push_back_token(tok);
+ new->expr = read_sql_expression(';', ";");
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+static PLpgSQL_stmt *
+make_return_next_stmt(int location)
+{
+ PLpgSQL_stmt_return_next *new;
+
+ if (!plpgsql_curr_compile->fn_retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURN NEXT in a non-SETOF function"),
+ parser_errposition(location)));
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return_next));
+ new->cmd_type = PLPGSQL_STMT_RETURN_NEXT;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->expr = NULL;
+ new->retvarno = -1;
+
+ if (plpgsql_curr_compile->out_param_varno >= 0)
+ {
+ if (yylex() != ';')
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("RETURN NEXT cannot have a parameter in function with OUT parameters"),
+ parser_errposition(yylloc)));
+ new->retvarno = plpgsql_curr_compile->out_param_varno;
+ }
+ else
+ {
+ /*
+ * We want to special-case simple variable references for efficiency.
+ * So peek ahead to see if that's what we have.
+ */
+ int tok = yylex();
+
+ if (tok == T_DATUM && plpgsql_peek() == ';' &&
+ (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_VAR ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_PROMISE ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC))
+ {
+ new->retvarno = yylval.wdatum.datum->dno;
+ /* eat the semicolon token that we only peeked at above */
+ tok = yylex();
+ Assert(tok == ';');
+ }
+ else
+ {
+ /*
+ * Not (just) a variable name, so treat as expression.
+ *
+ * Note that a well-formed expression is _required_ here;
+ * anything else is a compile-time error.
+ */
+ plpgsql_push_back_token(tok);
+ new->expr = read_sql_expression(';', ";");
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+static PLpgSQL_stmt *
+make_return_query_stmt(int location)
+{
+ PLpgSQL_stmt_return_query *new;
+ int tok;
+
+ if (!plpgsql_curr_compile->fn_retset)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot use RETURN QUERY in a non-SETOF function"),
+ parser_errposition(location)));
+
+ new = palloc0(sizeof(PLpgSQL_stmt_return_query));
+ new->cmd_type = PLPGSQL_STMT_RETURN_QUERY;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+
+ /* check for RETURN QUERY EXECUTE */
+ if ((tok = yylex()) != K_EXECUTE)
+ {
+ /* ordinary static query */
+ plpgsql_push_back_token(tok);
+ new->query = read_sql_stmt();
+ }
+ else
+ {
+ /* dynamic SQL */
+ int term;
+
+ new->dynquery = read_sql_expression2(';', K_USING, "; or USING",
+ &term);
+ if (term == K_USING)
+ {
+ do
+ {
+ PLpgSQL_expr *expr;
+
+ expr = read_sql_expression2(',', ';', ", or ;", &term);
+ new->params = lappend(new->params, expr);
+ } while (term == ',');
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
+
+
+/* convenience routine to fetch the name of a T_DATUM */
+static char *
+NameOfDatum(PLwdatum *wdatum)
+{
+ if (wdatum->ident)
+ return wdatum->ident;
+ Assert(wdatum->idents != NIL);
+ return NameListToString(wdatum->idents);
+}
+
+static void
+check_assignable(PLpgSQL_datum *datum, int location)
+{
+ switch (datum->dtype)
+ {
+ case PLPGSQL_DTYPE_VAR:
+ case PLPGSQL_DTYPE_PROMISE:
+ case PLPGSQL_DTYPE_REC:
+ if (((PLpgSQL_variable *) datum)->isconst)
+ ereport(ERROR,
+ (errcode(ERRCODE_ERROR_IN_ASSIGNMENT),
+ errmsg("variable \"%s\" is declared CONSTANT",
+ ((PLpgSQL_variable *) datum)->refname),
+ parser_errposition(location)));
+ break;
+ case PLPGSQL_DTYPE_ROW:
+ /* always assignable; member vars were checked at compile time */
+ break;
+ case PLPGSQL_DTYPE_RECFIELD:
+ /* assignable if parent record is */
+ check_assignable(plpgsql_Datums[((PLpgSQL_recfield *) datum)->recparentno],
+ location);
+ break;
+ default:
+ elog(ERROR, "unrecognized dtype: %d", datum->dtype);
+ break;
+ }
+}
+
+/*
+ * Read the argument of an INTO clause. On entry, we have just read the
+ * INTO keyword.
+ */
+static void
+read_into_target(PLpgSQL_variable **target, bool *strict)
+{
+ int tok;
+
+ /* Set default results */
+ *target = NULL;
+ if (strict)
+ *strict = false;
+
+ tok = yylex();
+ if (strict && tok == K_STRICT)
+ {
+ *strict = true;
+ tok = yylex();
+ }
+
+ /*
+ * Currently, a row or record variable can be the single INTO target,
+ * but not a member of a multi-target list. So we throw error if there
+ * is a comma after it, because that probably means the user tried to
+ * write a multi-target list. If this ever gets generalized, we should
+ * probably refactor read_into_scalar_list so it handles all cases.
+ */
+ switch (tok)
+ {
+ case T_DATUM:
+ if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+ {
+ check_assignable(yylval.wdatum.datum, yylloc);
+ *target = (PLpgSQL_variable *) yylval.wdatum.datum;
+
+ if ((tok = yylex()) == ',')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("record variable cannot be part of multiple-item INTO list"),
+ parser_errposition(yylloc)));
+ plpgsql_push_back_token(tok);
+ }
+ else
+ {
+ *target = (PLpgSQL_variable *)
+ read_into_scalar_list(NameOfDatum(&(yylval.wdatum)),
+ yylval.wdatum.datum, yylloc);
+ }
+ break;
+
+ default:
+ /* just to give a better message than "syntax error" */
+ current_token_is_not_variable(tok);
+ }
+}
+
+/*
+ * Given the first datum and name in the INTO list, continue to read
+ * comma-separated scalar variables until we run out. Then construct
+ * and return a fake "row" variable that represents the list of
+ * scalars.
+ */
+static PLpgSQL_row *
+read_into_scalar_list(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int initial_location)
+{
+ int nfields;
+ char *fieldnames[1024];
+ int varnos[1024];
+ PLpgSQL_row *row;
+ int tok;
+
+ check_assignable(initial_datum, initial_location);
+ fieldnames[0] = initial_name;
+ varnos[0] = initial_datum->dno;
+ nfields = 1;
+
+ while ((tok = yylex()) == ',')
+ {
+ /* Check for array overflow */
+ if (nfields >= 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("too many INTO variables specified"),
+ parser_errposition(yylloc)));
+
+ tok = yylex();
+ switch (tok)
+ {
+ case T_DATUM:
+ check_assignable(yylval.wdatum.datum, yylloc);
+ if (yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_ROW ||
+ yylval.wdatum.datum->dtype == PLPGSQL_DTYPE_REC)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("\"%s\" is not a scalar variable",
+ NameOfDatum(&(yylval.wdatum))),
+ parser_errposition(yylloc)));
+ fieldnames[nfields] = NameOfDatum(&(yylval.wdatum));
+ varnos[nfields++] = yylval.wdatum.datum->dno;
+ break;
+
+ default:
+ /* just to give a better message than "syntax error" */
+ current_token_is_not_variable(tok);
+ }
+ }
+
+ /*
+ * We read an extra, non-comma token from yylex(), so push it
+ * back onto the input stream
+ */
+ plpgsql_push_back_token(tok);
+
+ row = palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = plpgsql_location_to_lineno(initial_location);
+ row->rowtupdesc = NULL;
+ row->nfields = nfields;
+ row->fieldnames = palloc(sizeof(char *) * nfields);
+ row->varnos = palloc(sizeof(int) * nfields);
+ while (--nfields >= 0)
+ {
+ row->fieldnames[nfields] = fieldnames[nfields];
+ row->varnos[nfields] = varnos[nfields];
+ }
+
+ plpgsql_adddatum((PLpgSQL_datum *) row);
+
+ return row;
+}
+
+/*
+ * Convert a single scalar into a "row" list. This is exactly
+ * like read_into_scalar_list except we never consume any input.
+ *
+ * Note: lineno could be computed from location, but since callers
+ * have it at hand already, we may as well pass it in.
+ */
+static PLpgSQL_row *
+make_scalar_list1(char *initial_name,
+ PLpgSQL_datum *initial_datum,
+ int lineno, int location)
+{
+ PLpgSQL_row *row;
+
+ check_assignable(initial_datum, location);
+
+ row = palloc0(sizeof(PLpgSQL_row));
+ row->dtype = PLPGSQL_DTYPE_ROW;
+ row->refname = "(unnamed row)";
+ row->lineno = lineno;
+ row->rowtupdesc = NULL;
+ row->nfields = 1;
+ row->fieldnames = palloc(sizeof(char *));
+ row->varnos = palloc(sizeof(int));
+ row->fieldnames[0] = initial_name;
+ row->varnos[0] = initial_datum->dno;
+
+ plpgsql_adddatum((PLpgSQL_datum *) row);
+
+ return row;
+}
+
+/*
+ * When the PL/pgSQL parser expects to see a SQL statement, it is very
+ * liberal in what it accepts; for example, we often assume an
+ * unrecognized keyword is the beginning of a SQL statement. This
+ * avoids the need to duplicate parts of the SQL grammar in the
+ * PL/pgSQL grammar, but it means we can accept wildly malformed
+ * input. To try and catch some of the more obviously invalid input,
+ * we run the strings we expect to be SQL statements through the main
+ * SQL parser.
+ *
+ * We only invoke the raw parser (not the analyzer); this doesn't do
+ * any database access and does not check any semantic rules, it just
+ * checks for basic syntactic correctness. We do this here, rather
+ * than after parsing has finished, because a malformed SQL statement
+ * may cause the PL/pgSQL parser to become confused about statement
+ * borders. So it is best to bail out as early as we can.
+ *
+ * It is assumed that "stmt" represents a copy of the function source text
+ * beginning at offset "location". We use this assumption to transpose
+ * any error cursor position back to the function source text.
+ * If no error cursor is provided, we'll just point at "location".
+ */
+static void
+check_sql_expr(const char *stmt, RawParseMode parseMode, int location)
+{
+ sql_error_callback_arg cbarg;
+ ErrorContextCallback syntax_errcontext;
+ MemoryContext oldCxt;
+
+ if (!plpgsql_check_syntax)
+ return;
+
+ cbarg.location = location;
+
+ syntax_errcontext.callback = plpgsql_sql_error_callback;
+ syntax_errcontext.arg = &cbarg;
+ syntax_errcontext.previous = error_context_stack;
+ error_context_stack = &syntax_errcontext;
+
+ oldCxt = MemoryContextSwitchTo(plpgsql_compile_tmp_cxt);
+ (void) raw_parser(stmt, parseMode);
+ MemoryContextSwitchTo(oldCxt);
+
+ /* Restore former ereport callback */
+ error_context_stack = syntax_errcontext.previous;
+}
+
+static void
+plpgsql_sql_error_callback(void *arg)
+{
+ sql_error_callback_arg *cbarg = (sql_error_callback_arg *) arg;
+ int errpos;
+
+ /*
+ * First, set up internalerrposition to point to the start of the
+ * statement text within the function text. Note this converts
+ * location (a byte offset) to a character number.
+ */
+ parser_errposition(cbarg->location);
+
+ /*
+ * If the core parser provided an error position, transpose it.
+ * Note we are dealing with 1-based character numbers at this point.
+ */
+ errpos = geterrposition();
+ if (errpos > 0)
+ {
+ int myerrpos = getinternalerrposition();
+
+ if (myerrpos > 0) /* safety check */
+ internalerrposition(myerrpos + errpos - 1);
+ }
+
+ /* In any case, flush errposition --- we want internalerrposition only */
+ errposition(0);
+}
+
+/*
+ * Parse a SQL datatype name and produce a PLpgSQL_type structure.
+ *
+ * The heavy lifting is done elsewhere. Here we are only concerned
+ * with setting up an errcontext link that will let us give an error
+ * cursor pointing into the plpgsql function source, if necessary.
+ * This is handled the same as in check_sql_expr(), and we likewise
+ * expect that the given string is a copy from the source text.
+ */
+static PLpgSQL_type *
+parse_datatype(const char *string, int location)
+{
+ TypeName *typeName;
+ Oid type_id;
+ int32 typmod;
+ sql_error_callback_arg cbarg;
+ ErrorContextCallback syntax_errcontext;
+
+ cbarg.location = location;
+
+ syntax_errcontext.callback = plpgsql_sql_error_callback;
+ syntax_errcontext.arg = &cbarg;
+ syntax_errcontext.previous = error_context_stack;
+ error_context_stack = &syntax_errcontext;
+
+ /* Let the main parser try to parse it under standard SQL rules */
+ typeName = typeStringToTypeName(string);
+ typenameTypeIdAndMod(NULL, typeName, &type_id, &typmod);
+
+ /* Restore former ereport callback */
+ error_context_stack = syntax_errcontext.previous;
+
+ /* Okay, build a PLpgSQL_type data structure for it */
+ return plpgsql_build_datatype(type_id, typmod,
+ plpgsql_curr_compile->fn_input_collation,
+ typeName);
+}
+
+/*
+ * Check block starting and ending labels match.
+ */
+static void
+check_labels(const char *start_label, const char *end_label, int end_location)
+{
+ if (end_label)
+ {
+ if (!start_label)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("end label \"%s\" specified for unlabeled block",
+ end_label),
+ parser_errposition(end_location)));
+
+ if (strcmp(start_label, end_label) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("end label \"%s\" differs from block's label \"%s\"",
+ end_label, start_label),
+ parser_errposition(end_location)));
+ }
+}
+
+/*
+ * Read the arguments (if any) for a cursor, followed by the until token
+ *
+ * If cursor has no args, just swallow the until token and return NULL.
+ * If it does have args, we expect to see "( arg [, arg ...] )" followed
+ * by the until token, where arg may be a plain expression, or a named
+ * parameter assignment of the form argname := expr. Consume all that and
+ * return a SELECT query that evaluates the expression(s) (without the outer
+ * parens).
+ */
+static PLpgSQL_expr *
+read_cursor_args(PLpgSQL_var *cursor, int until)
+{
+ PLpgSQL_expr *expr;
+ PLpgSQL_row *row;
+ int tok;
+ int argc;
+ char **argv;
+ StringInfoData ds;
+ bool any_named = false;
+
+ tok = yylex();
+ if (cursor->cursor_explicit_argrow < 0)
+ {
+ /* No arguments expected */
+ if (tok == '(')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has no arguments",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ if (tok != until)
+ yyerror("syntax error");
+
+ return NULL;
+ }
+
+ /* Else better provide arguments */
+ if (tok != '(')
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has arguments",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ /*
+ * Read the arguments, one by one.
+ */
+ row = (PLpgSQL_row *) plpgsql_Datums[cursor->cursor_explicit_argrow];
+ argv = (char **) palloc0(row->nfields * sizeof(char *));
+
+ for (argc = 0; argc < row->nfields; argc++)
+ {
+ PLpgSQL_expr *item;
+ int endtoken;
+ int argpos;
+ int tok1,
+ tok2;
+ int arglocation;
+
+ /* Check if it's a named parameter: "param := value" */
+ plpgsql_peek2(&tok1, &tok2, &arglocation, NULL);
+ if (tok1 == IDENT && tok2 == COLON_EQUALS)
+ {
+ char *argname;
+ IdentifierLookup save_IdentifierLookup;
+
+ /* Read the argument name, ignoring any matching variable */
+ save_IdentifierLookup = plpgsql_IdentifierLookup;
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_DECLARE;
+ yylex();
+ argname = yylval.str;
+ plpgsql_IdentifierLookup = save_IdentifierLookup;
+
+ /* Match argument name to cursor arguments */
+ for (argpos = 0; argpos < row->nfields; argpos++)
+ {
+ if (strcmp(row->fieldnames[argpos], argname) == 0)
+ break;
+ }
+ if (argpos == row->nfields)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("cursor \"%s\" has no argument named \"%s\"",
+ cursor->refname, argname),
+ parser_errposition(yylloc)));
+
+ /*
+ * Eat the ":=". We already peeked, so the error should never
+ * happen.
+ */
+ tok2 = yylex();
+ if (tok2 != COLON_EQUALS)
+ yyerror("syntax error");
+
+ any_named = true;
+ }
+ else
+ argpos = argc;
+
+ if (argv[argpos] != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("value for parameter \"%s\" of cursor \"%s\" specified more than once",
+ row->fieldnames[argpos], cursor->refname),
+ parser_errposition(arglocation)));
+
+ /*
+ * Read the value expression. To provide the user with meaningful
+ * parse error positions, we check the syntax immediately, instead of
+ * checking the final expression that may have the arguments
+ * reordered. Trailing whitespace must not be trimmed, because
+ * otherwise input of the form (param -- comment\n, param) would be
+ * translated into a form where the second parameter is commented
+ * out.
+ */
+ item = read_sql_construct(',', ')', 0,
+ ",\" or \")",
+ RAW_PARSE_PLPGSQL_EXPR,
+ true, true,
+ false, /* do not trim */
+ NULL, &endtoken);
+
+ argv[argpos] = item->query;
+
+ if (endtoken == ')' && !(argc == row->nfields - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("not enough arguments for cursor \"%s\"",
+ cursor->refname),
+ parser_errposition(yylloc)));
+
+ if (endtoken == ',' && (argc == row->nfields - 1))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many arguments for cursor \"%s\"",
+ cursor->refname),
+ parser_errposition(yylloc)));
+ }
+
+ /* Make positional argument list */
+ initStringInfo(&ds);
+ for (argc = 0; argc < row->nfields; argc++)
+ {
+ Assert(argv[argc] != NULL);
+
+ /*
+ * Because named notation allows permutated argument lists, include
+ * the parameter name for meaningful runtime errors.
+ */
+ appendStringInfoString(&ds, argv[argc]);
+ if (any_named)
+ appendStringInfo(&ds, " AS %s",
+ quote_identifier(row->fieldnames[argc]));
+ if (argc < row->nfields - 1)
+ appendStringInfoString(&ds, ", ");
+ }
+
+ expr = palloc0(sizeof(PLpgSQL_expr));
+ expr->query = pstrdup(ds.data);
+ expr->parseMode = RAW_PARSE_PLPGSQL_EXPR;
+ expr->plan = NULL;
+ expr->paramnos = NULL;
+ expr->target_param = -1;
+ expr->ns = plpgsql_ns_top();
+ pfree(ds.data);
+
+ /* Next we'd better find the until token */
+ tok = yylex();
+ if (tok != until)
+ yyerror("syntax error");
+
+ return expr;
+}
+
+/*
+ * Parse RAISE ... USING options
+ */
+static List *
+read_raise_options(void)
+{
+ List *result = NIL;
+
+ for (;;)
+ {
+ PLpgSQL_raise_option *opt;
+ int tok;
+
+ if ((tok = yylex()) == 0)
+ yyerror("unexpected end of function definition");
+
+ opt = (PLpgSQL_raise_option *) palloc(sizeof(PLpgSQL_raise_option));
+
+ if (tok_is_keyword(tok, &yylval,
+ K_ERRCODE, "errcode"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_ERRCODE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_MESSAGE, "message"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_MESSAGE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DETAIL, "detail"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_DETAIL;
+ else if (tok_is_keyword(tok, &yylval,
+ K_HINT, "hint"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_HINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_COLUMN, "column"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_COLUMN;
+ else if (tok_is_keyword(tok, &yylval,
+ K_CONSTRAINT, "constraint"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_CONSTRAINT;
+ else if (tok_is_keyword(tok, &yylval,
+ K_DATATYPE, "datatype"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_DATATYPE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_TABLE, "table"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_TABLE;
+ else if (tok_is_keyword(tok, &yylval,
+ K_SCHEMA, "schema"))
+ opt->opt_type = PLPGSQL_RAISEOPTION_SCHEMA;
+ else
+ yyerror("unrecognized RAISE statement option");
+
+ tok = yylex();
+ if (tok != '=' && tok != COLON_EQUALS)
+ yyerror("syntax error, expected \"=\"");
+
+ opt->expr = read_sql_expression2(',', ';', ", or ;", &tok);
+
+ result = lappend(result, opt);
+
+ if (tok == ';')
+ break;
+ }
+
+ return result;
+}
+
+/*
+ * Check that the number of parameter placeholders in the message matches the
+ * number of parameters passed to it, if a message was given.
+ */
+static void
+check_raise_parameters(PLpgSQL_stmt_raise *stmt)
+{
+ char *cp;
+ int expected_nparams = 0;
+
+ if (stmt->message == NULL)
+ return;
+
+ for (cp = stmt->message; *cp; cp++)
+ {
+ if (cp[0] == '%')
+ {
+ /* ignore literal % characters */
+ if (cp[1] == '%')
+ cp++;
+ else
+ expected_nparams++;
+ }
+ }
+
+ if (expected_nparams < list_length(stmt->params))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too many parameters specified for RAISE")));
+ if (expected_nparams > list_length(stmt->params))
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("too few parameters specified for RAISE")));
+}
+
+/*
+ * Fix up CASE statement
+ */
+static PLpgSQL_stmt *
+make_case(int location, PLpgSQL_expr *t_expr,
+ List *case_when_list, List *else_stmts)
+{
+ PLpgSQL_stmt_case *new;
+
+ new = palloc(sizeof(PLpgSQL_stmt_case));
+ new->cmd_type = PLPGSQL_STMT_CASE;
+ new->lineno = plpgsql_location_to_lineno(location);
+ new->stmtid = ++plpgsql_curr_compile->nstatements;
+ new->t_expr = t_expr;
+ new->t_varno = 0;
+ new->case_when_list = case_when_list;
+ new->have_else = (else_stmts != NIL);
+ /* Get rid of list-with-NULL hack */
+ if (list_length(else_stmts) == 1 && linitial(else_stmts) == NULL)
+ new->else_stmts = NIL;
+ else
+ new->else_stmts = else_stmts;
+
+ /*
+ * When test expression is present, we create a var for it and then
+ * convert all the WHEN expressions to "VAR IN (original_expression)".
+ * This is a bit klugy, but okay since we haven't yet done more than
+ * read the expressions as text. (Note that previous parsing won't
+ * have complained if the WHEN ... THEN expression contained multiple
+ * comma-separated values.)
+ */
+ if (t_expr)
+ {
+ char varname[32];
+ PLpgSQL_var *t_var;
+ ListCell *l;
+
+ /* use a name unlikely to collide with any user names */
+ snprintf(varname, sizeof(varname), "__Case__Variable_%d__",
+ plpgsql_nDatums);
+
+ /*
+ * We don't yet know the result datatype of t_expr. Build the
+ * variable as if it were INT4; we'll fix this at runtime if needed.
+ */
+ t_var = (PLpgSQL_var *)
+ plpgsql_build_variable(varname, new->lineno,
+ plpgsql_build_datatype(INT4OID,
+ -1,
+ InvalidOid,
+ NULL),
+ true);
+ new->t_varno = t_var->dno;
+
+ foreach(l, case_when_list)
+ {
+ PLpgSQL_case_when *cwt = (PLpgSQL_case_when *) lfirst(l);
+ PLpgSQL_expr *expr = cwt->expr;
+ StringInfoData ds;
+
+ /* We expect to have expressions not statements */
+ Assert(expr->parseMode == RAW_PARSE_PLPGSQL_EXPR);
+
+ /* Do the string hacking */
+ initStringInfo(&ds);
+
+ appendStringInfo(&ds, "\"%s\" IN (%s)",
+ varname, expr->query);
+
+ pfree(expr->query);
+ expr->query = pstrdup(ds.data);
+ /* Adjust expr's namespace to include the case variable */
+ expr->ns = plpgsql_ns_top();
+
+ pfree(ds.data);
+ }
+ }
+
+ return (PLpgSQL_stmt *) new;
+}
diff --git a/src/pl/plpgsql/src/pl_handler.c b/src/pl/plpgsql/src/pl_handler.c
new file mode 100644
index 0000000..190d286
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_handler.c
@@ -0,0 +1,551 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_handler.c - Handler for the PL/pgSQL
+ * procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_handler.c
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "miscadmin.h"
+#include "plpgsql.h"
+#include "utils/builtins.h"
+#include "utils/guc.h"
+#include "utils/lsyscache.h"
+#include "utils/syscache.h"
+#include "utils/varlena.h"
+
+static bool plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source);
+static void plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra);
+static void plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra);
+
+PG_MODULE_MAGIC;
+
+/* Custom GUC variable */
+static const struct config_enum_entry variable_conflict_options[] = {
+ {"error", PLPGSQL_RESOLVE_ERROR, false},
+ {"use_variable", PLPGSQL_RESOLVE_VARIABLE, false},
+ {"use_column", PLPGSQL_RESOLVE_COLUMN, false},
+ {NULL, 0, false}
+};
+
+int plpgsql_variable_conflict = PLPGSQL_RESOLVE_ERROR;
+
+bool plpgsql_print_strict_params = false;
+
+bool plpgsql_check_asserts = true;
+
+char *plpgsql_extra_warnings_string = NULL;
+char *plpgsql_extra_errors_string = NULL;
+int plpgsql_extra_warnings;
+int plpgsql_extra_errors;
+
+/* Hook for plugins */
+PLpgSQL_plugin **plpgsql_plugin_ptr = NULL;
+
+
+static bool
+plpgsql_extra_checks_check_hook(char **newvalue, void **extra, GucSource source)
+{
+ char *rawstring;
+ List *elemlist;
+ ListCell *l;
+ int extrachecks = 0;
+ int *myextra;
+
+ if (pg_strcasecmp(*newvalue, "all") == 0)
+ extrachecks = PLPGSQL_XCHECK_ALL;
+ else if (pg_strcasecmp(*newvalue, "none") == 0)
+ extrachecks = PLPGSQL_XCHECK_NONE;
+ else
+ {
+ /* Need a modifiable copy of string */
+ rawstring = pstrdup(*newvalue);
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ GUC_check_errdetail("List syntax is invalid.");
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+
+ if (pg_strcasecmp(tok, "shadowed_variables") == 0)
+ extrachecks |= PLPGSQL_XCHECK_SHADOWVAR;
+ else if (pg_strcasecmp(tok, "too_many_rows") == 0)
+ extrachecks |= PLPGSQL_XCHECK_TOOMANYROWS;
+ else if (pg_strcasecmp(tok, "strict_multi_assignment") == 0)
+ extrachecks |= PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT;
+ else if (pg_strcasecmp(tok, "all") == 0 || pg_strcasecmp(tok, "none") == 0)
+ {
+ GUC_check_errdetail("Key word \"%s\" cannot be combined with other key words.", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ else
+ {
+ GUC_check_errdetail("Unrecognized key word: \"%s\".", tok);
+ pfree(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+
+ pfree(rawstring);
+ list_free(elemlist);
+ }
+
+ myextra = (int *) malloc(sizeof(int));
+ if (!myextra)
+ return false;
+ *myextra = extrachecks;
+ *extra = (void *) myextra;
+
+ return true;
+}
+
+static void
+plpgsql_extra_warnings_assign_hook(const char *newvalue, void *extra)
+{
+ plpgsql_extra_warnings = *((int *) extra);
+}
+
+static void
+plpgsql_extra_errors_assign_hook(const char *newvalue, void *extra)
+{
+ plpgsql_extra_errors = *((int *) extra);
+}
+
+
+/*
+ * _PG_init() - library load-time initialization
+ *
+ * DO NOT make this static nor change its name!
+ */
+void
+_PG_init(void)
+{
+ /* Be sure we do initialization only once (should be redundant now) */
+ static bool inited = false;
+
+ if (inited)
+ return;
+
+ pg_bindtextdomain(TEXTDOMAIN);
+
+ DefineCustomEnumVariable("plpgsql.variable_conflict",
+ gettext_noop("Sets handling of conflicts between PL/pgSQL variable names and table column names."),
+ NULL,
+ &plpgsql_variable_conflict,
+ PLPGSQL_RESOLVE_ERROR,
+ variable_conflict_options,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+
+ DefineCustomBoolVariable("plpgsql.print_strict_params",
+ gettext_noop("Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."),
+ NULL,
+ &plpgsql_print_strict_params,
+ false,
+ PGC_USERSET, 0,
+ NULL, NULL, NULL);
+
+ DefineCustomBoolVariable("plpgsql.check_asserts",
+ gettext_noop("Perform checks given in ASSERT statements."),
+ NULL,
+ &plpgsql_check_asserts,
+ true,
+ PGC_USERSET, 0,
+ NULL, NULL, NULL);
+
+ DefineCustomStringVariable("plpgsql.extra_warnings",
+ gettext_noop("List of programming constructs that should produce a warning."),
+ NULL,
+ &plpgsql_extra_warnings_string,
+ "none",
+ PGC_USERSET, GUC_LIST_INPUT,
+ plpgsql_extra_checks_check_hook,
+ plpgsql_extra_warnings_assign_hook,
+ NULL);
+
+ DefineCustomStringVariable("plpgsql.extra_errors",
+ gettext_noop("List of programming constructs that should produce an error."),
+ NULL,
+ &plpgsql_extra_errors_string,
+ "none",
+ PGC_USERSET, GUC_LIST_INPUT,
+ plpgsql_extra_checks_check_hook,
+ plpgsql_extra_errors_assign_hook,
+ NULL);
+
+ MarkGUCPrefixReserved("plpgsql");
+
+ plpgsql_HashTableInit();
+ RegisterXactCallback(plpgsql_xact_cb, NULL);
+ RegisterSubXactCallback(plpgsql_subxact_cb, NULL);
+
+ /* Set up a rendezvous point with optional instrumentation plugin */
+ plpgsql_plugin_ptr = (PLpgSQL_plugin **) find_rendezvous_variable("PLpgSQL_plugin");
+
+ inited = true;
+}
+
+/* ----------
+ * plpgsql_call_handler
+ *
+ * The PostgreSQL function manager and trigger manager
+ * call this function for execution of PL/pgSQL procedures.
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_call_handler);
+
+Datum
+plpgsql_call_handler(PG_FUNCTION_ARGS)
+{
+ bool nonatomic;
+ PLpgSQL_function *func;
+ PLpgSQL_execstate *save_cur_estate;
+ ResourceOwner procedure_resowner;
+ volatile Datum retval = (Datum) 0;
+ int rc;
+
+ nonatomic = fcinfo->context &&
+ IsA(fcinfo->context, CallContext) &&
+ !castNode(CallContext, fcinfo->context)->atomic;
+
+ /*
+ * Connect to SPI manager
+ */
+ if ((rc = SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0)) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+
+ /* Find or compile the function */
+ func = plpgsql_compile(fcinfo, false);
+
+ /* Must save and restore prior value of cur_estate */
+ save_cur_estate = func->cur_estate;
+
+ /* Mark the function as busy, so it can't be deleted from under us */
+ func->use_count++;
+
+ /*
+ * If we'll need a procedure-lifespan resowner to execute any CALL or DO
+ * statements, create it now. Since this resowner is not tied to any
+ * parent, failing to free it would result in process-lifespan leaks.
+ * Therefore, be very wary of adding any code between here and the PG_TRY
+ * block.
+ */
+ procedure_resowner =
+ (nonatomic && func->requires_procedure_resowner) ?
+ ResourceOwnerCreate(NULL, "PL/pgSQL procedure resources") : NULL;
+
+ PG_TRY();
+ {
+ /*
+ * Determine if called as function or trigger and call appropriate
+ * subhandler
+ */
+ if (CALLED_AS_TRIGGER(fcinfo))
+ retval = PointerGetDatum(plpgsql_exec_trigger(func,
+ (TriggerData *) fcinfo->context));
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ plpgsql_exec_event_trigger(func,
+ (EventTriggerData *) fcinfo->context);
+ /* there's no return value in this case */
+ }
+ else
+ retval = plpgsql_exec_function(func, fcinfo,
+ NULL, NULL,
+ procedure_resowner,
+ !nonatomic);
+ }
+ PG_FINALLY();
+ {
+ /* Decrement use-count, restore cur_estate */
+ func->use_count--;
+ func->cur_estate = save_cur_estate;
+
+ /* Be sure to release the procedure resowner if any */
+ if (procedure_resowner)
+ {
+ ResourceOwnerReleaseAllPlanCacheRefs(procedure_resowner);
+ ResourceOwnerDelete(procedure_resowner);
+ }
+ }
+ PG_END_TRY();
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+
+ return retval;
+}
+
+/* ----------
+ * plpgsql_inline_handler
+ *
+ * Called by PostgreSQL to execute an anonymous code block
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_inline_handler);
+
+Datum
+plpgsql_inline_handler(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(fake_fcinfo, 0);
+ InlineCodeBlock *codeblock = castNode(InlineCodeBlock, DatumGetPointer(PG_GETARG_DATUM(0)));
+ PLpgSQL_function *func;
+ FmgrInfo flinfo;
+ EState *simple_eval_estate;
+ ResourceOwner simple_eval_resowner;
+ Datum retval;
+ int rc;
+
+ /*
+ * Connect to SPI manager
+ */
+ if ((rc = SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC)) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+
+ /* Compile the anonymous code block */
+ func = plpgsql_compile_inline(codeblock->source_text);
+
+ /* Mark the function as busy, just pro forma */
+ func->use_count++;
+
+ /*
+ * Set up a fake fcinfo with just enough info to satisfy
+ * plpgsql_exec_function(). In particular note that this sets things up
+ * with no arguments passed.
+ */
+ MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ fake_fcinfo->flinfo = &flinfo;
+ flinfo.fn_oid = InvalidOid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+
+ /*
+ * Create a private EState and resowner for simple-expression execution.
+ * Notice that these are NOT tied to transaction-level resources; they
+ * must survive any COMMIT/ROLLBACK the DO block executes, since we will
+ * unconditionally try to clean them up below. (Hence, be wary of adding
+ * anything that could fail between here and the PG_TRY block.) See the
+ * comments for shared_simple_eval_estate.
+ *
+ * Because this resowner isn't tied to the calling transaction, we can
+ * also use it as the "procedure" resowner for any CALL statements. That
+ * helps reduce the opportunities for failure here.
+ */
+ simple_eval_estate = CreateExecutorState();
+ simple_eval_resowner =
+ ResourceOwnerCreate(NULL, "PL/pgSQL DO block simple expressions");
+
+ /* And run the function */
+ PG_TRY();
+ {
+ retval = plpgsql_exec_function(func, fake_fcinfo,
+ simple_eval_estate,
+ simple_eval_resowner,
+ simple_eval_resowner, /* see above */
+ codeblock->atomic);
+ }
+ PG_CATCH();
+ {
+ /*
+ * We need to clean up what would otherwise be long-lived resources
+ * accumulated by the failed DO block, principally cached plans for
+ * statements (which can be flushed by plpgsql_free_function_memory),
+ * execution trees for simple expressions, which are in the private
+ * EState, and cached-plan refcounts held by the private resowner.
+ *
+ * Before releasing the private EState, we must clean up any
+ * simple_econtext_stack entries pointing into it, which we can do by
+ * invoking the subxact callback. (It will be called again later if
+ * some outer control level does a subtransaction abort, but no harm
+ * is done.) We cheat a bit knowing that plpgsql_subxact_cb does not
+ * pay attention to its parentSubid argument.
+ */
+ plpgsql_subxact_cb(SUBXACT_EVENT_ABORT_SUB,
+ GetCurrentSubTransactionId(),
+ 0, NULL);
+
+ /* Clean up the private EState and resowner */
+ FreeExecutorState(simple_eval_estate);
+ ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+ ResourceOwnerDelete(simple_eval_resowner);
+
+ /* Function should now have no remaining use-counts ... */
+ func->use_count--;
+ Assert(func->use_count == 0);
+
+ /* ... so we can free subsidiary storage */
+ plpgsql_free_function_memory(func);
+
+ /* And propagate the error */
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* Clean up the private EState and resowner */
+ FreeExecutorState(simple_eval_estate);
+ ResourceOwnerReleaseAllPlanCacheRefs(simple_eval_resowner);
+ ResourceOwnerDelete(simple_eval_resowner);
+
+ /* Function should now have no remaining use-counts ... */
+ func->use_count--;
+ Assert(func->use_count == 0);
+
+ /* ... so we can free subsidiary storage */
+ plpgsql_free_function_memory(func);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+
+ return retval;
+}
+
+/* ----------
+ * plpgsql_validator
+ *
+ * This function attempts to validate a PL/pgSQL function at
+ * CREATE FUNCTION time.
+ * ----------
+ */
+PG_FUNCTION_INFO_V1(plpgsql_validator);
+
+Datum
+plpgsql_validator(PG_FUNCTION_ARGS)
+{
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc proc;
+ char functyptype;
+ int numargs;
+ Oid *argtypes;
+ char **argnames;
+ char *argmodes;
+ bool is_dml_trigger = false;
+ bool is_event_trigger = false;
+ int i;
+
+ if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
+ PG_RETURN_VOID();
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcoid);
+ proc = (Form_pg_proc) GETSTRUCT(tuple);
+
+ functyptype = get_typtype(proc->prorettype);
+
+ /* Disallow pseudotype result */
+ /* except for TRIGGER, EVTTRIGGER, RECORD, VOID, or polymorphic */
+ if (functyptype == TYPTYPE_PSEUDO)
+ {
+ if (proc->prorettype == TRIGGEROID)
+ is_dml_trigger = true;
+ else if (proc->prorettype == EVENT_TRIGGEROID)
+ is_event_trigger = true;
+ else if (proc->prorettype != RECORDOID &&
+ proc->prorettype != VOIDOID &&
+ !IsPolymorphicType(proc->prorettype))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot return type %s",
+ format_type_be(proc->prorettype))));
+ }
+
+ /* Disallow pseudotypes in arguments (either IN or OUT) */
+ /* except for RECORD and polymorphic */
+ numargs = get_func_arg_info(tuple,
+ &argtypes, &argnames, &argmodes);
+ for (i = 0; i < numargs; i++)
+ {
+ if (get_typtype(argtypes[i]) == TYPTYPE_PSEUDO)
+ {
+ if (argtypes[i] != RECORDOID &&
+ !IsPolymorphicType(argtypes[i]))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/pgSQL functions cannot accept type %s",
+ format_type_be(argtypes[i]))));
+ }
+ }
+
+ /* Postpone body checks if !check_function_bodies */
+ if (check_function_bodies)
+ {
+ LOCAL_FCINFO(fake_fcinfo, 0);
+ FmgrInfo flinfo;
+ int rc;
+ TriggerData trigdata;
+ EventTriggerData etrigdata;
+
+ /*
+ * Connect to SPI manager (is this needed for compilation?)
+ */
+ if ((rc = SPI_connect()) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed: %s", SPI_result_code_string(rc));
+
+ /*
+ * Set up a fake fcinfo with just enough info to satisfy
+ * plpgsql_compile().
+ */
+ MemSet(fake_fcinfo, 0, SizeForFunctionCallInfo(0));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ fake_fcinfo->flinfo = &flinfo;
+ flinfo.fn_oid = funcoid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+ if (is_dml_trigger)
+ {
+ MemSet(&trigdata, 0, sizeof(trigdata));
+ trigdata.type = T_TriggerData;
+ fake_fcinfo->context = (Node *) &trigdata;
+ }
+ else if (is_event_trigger)
+ {
+ MemSet(&etrigdata, 0, sizeof(etrigdata));
+ etrigdata.type = T_EventTriggerData;
+ fake_fcinfo->context = (Node *) &etrigdata;
+ }
+
+ /* Test-compile the function */
+ plpgsql_compile(fake_fcinfo, true);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if ((rc = SPI_finish()) != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed: %s", SPI_result_code_string(rc));
+ }
+
+ ReleaseSysCache(tuple);
+
+ PG_RETURN_VOID();
+}
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist.h b/src/pl/plpgsql/src/pl_reserved_kwlist.h
new file mode 100644
index 0000000..9043fbd
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist.h
@@ -0,0 +1,52 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_reserved_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools. The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpgsql/src/pl_reserved_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef PL_RESERVED_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value) pairs.
+ *
+ * Be careful not to put the same word into pl_unreserved_kwlist.h.
+ *
+ * Note: gen_keywordlist.pl requires the entries to appear in ASCII order.
+ */
+
+/* name, value */
+PG_KEYWORD("all", K_ALL)
+PG_KEYWORD("begin", K_BEGIN)
+PG_KEYWORD("by", K_BY)
+PG_KEYWORD("case", K_CASE)
+PG_KEYWORD("declare", K_DECLARE)
+PG_KEYWORD("else", K_ELSE)
+PG_KEYWORD("end", K_END)
+PG_KEYWORD("execute", K_EXECUTE)
+PG_KEYWORD("for", K_FOR)
+PG_KEYWORD("foreach", K_FOREACH)
+PG_KEYWORD("from", K_FROM)
+PG_KEYWORD("if", K_IF)
+PG_KEYWORD("in", K_IN)
+PG_KEYWORD("into", K_INTO)
+PG_KEYWORD("loop", K_LOOP)
+PG_KEYWORD("not", K_NOT)
+PG_KEYWORD("null", K_NULL)
+PG_KEYWORD("or", K_OR)
+PG_KEYWORD("strict", K_STRICT)
+PG_KEYWORD("then", K_THEN)
+PG_KEYWORD("to", K_TO)
+PG_KEYWORD("using", K_USING)
+PG_KEYWORD("when", K_WHEN)
+PG_KEYWORD("while", K_WHILE)
diff --git a/src/pl/plpgsql/src/pl_reserved_kwlist_d.h b/src/pl/plpgsql/src/pl_reserved_kwlist_d.h
new file mode 100644
index 0000000..32689eb
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_reserved_kwlist_d.h
@@ -0,0 +1,114 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_reserved_kwlist_d.h
+ * List of keywords represented as a ScanKeywordList.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ * ******************************
+ * *** DO NOT EDIT THIS FILE! ***
+ * ******************************
+ *
+ * It has been GENERATED by src/tools/gen_keywordlist.pl
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PL_RESERVED_KWLIST_D_H
+#define PL_RESERVED_KWLIST_D_H
+
+#include "common/kwlookup.h"
+
+static const char ReservedPLKeywords_kw_string[] =
+ "all\0"
+ "begin\0"
+ "by\0"
+ "case\0"
+ "declare\0"
+ "else\0"
+ "end\0"
+ "execute\0"
+ "for\0"
+ "foreach\0"
+ "from\0"
+ "if\0"
+ "in\0"
+ "into\0"
+ "loop\0"
+ "not\0"
+ "null\0"
+ "or\0"
+ "strict\0"
+ "then\0"
+ "to\0"
+ "using\0"
+ "when\0"
+ "while";
+
+static const uint16 ReservedPLKeywords_kw_offsets[] = {
+ 0,
+ 4,
+ 10,
+ 13,
+ 18,
+ 26,
+ 31,
+ 35,
+ 43,
+ 47,
+ 55,
+ 60,
+ 63,
+ 66,
+ 71,
+ 76,
+ 80,
+ 85,
+ 88,
+ 95,
+ 100,
+ 103,
+ 109,
+ 114,
+};
+
+#define RESERVEDPLKEYWORDS_NUM_KEYWORDS 24
+
+static int
+ReservedPLKeywords_hash_func(const void *key, size_t keylen)
+{
+ static const int8 h[49] = {
+ 127, 7, 127, 127, -2, 127, 13, 127,
+ 127, 5, 0, 23, 0, 2, 127, 0,
+ 17, 0, 127, 19, 5, 127, 6, 2,
+ -3, 17, 0, 6, 127, 8, 18, 127,
+ -6, 3, -5, 0, 127, 0, 0, 11,
+ 15, 127, 127, 127, 13, 127, 0, 17,
+ 127
+ };
+
+ const unsigned char *k = (const unsigned char *) key;
+ uint32 a = 0;
+ uint32 b = 1;
+
+ while (keylen--)
+ {
+ unsigned char c = *k++ | 0x20;
+
+ a = a * 257 + c;
+ b = b * 8191 + c;
+ }
+ return h[a % 49] + h[b % 49];
+}
+
+static const ScanKeywordList ReservedPLKeywords = {
+ ReservedPLKeywords_kw_string,
+ ReservedPLKeywords_kw_offsets,
+ ReservedPLKeywords_hash_func,
+ RESERVEDPLKEYWORDS_NUM_KEYWORDS,
+ 7
+};
+
+#endif /* PL_RESERVED_KWLIST_D_H */
diff --git a/src/pl/plpgsql/src/pl_scanner.c b/src/pl/plpgsql/src/pl_scanner.c
new file mode 100644
index 0000000..aa1beb6
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_scanner.c
@@ -0,0 +1,620 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_scanner.c
+ * lexical scanning for PL/pgSQL
+ *
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/pl_scanner.c
+ *
+ *-------------------------------------------------------------------------
+ */
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "parser/scanner.h"
+
+#include "plpgsql.h"
+#include "pl_gram.h" /* must be after parser/scanner.h */
+
+
+/* Klugy flag to tell scanner how to look up identifiers */
+IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+
+/*
+ * A word about keywords:
+ *
+ * We keep reserved and unreserved keywords in separate headers. Be careful
+ * not to put the same word in both headers. Also be sure that pl_gram.y's
+ * unreserved_keyword production agrees with the unreserved header. The
+ * reserved keywords are passed to the core scanner, so they will be
+ * recognized before (and instead of) any variable name. Unreserved words
+ * are checked for separately, usually after determining that the identifier
+ * isn't a known variable name. If plpgsql_IdentifierLookup is DECLARE then
+ * no variable names will be recognized, so the unreserved words always work.
+ * (Note in particular that this helps us avoid reserving keywords that are
+ * only needed in DECLARE sections.)
+ *
+ * In certain contexts it is desirable to prefer recognizing an unreserved
+ * keyword over recognizing a variable name. In particular, at the start
+ * of a statement we should prefer unreserved keywords unless the statement
+ * looks like an assignment (i.e., first token is followed by ':=' or '[').
+ * This rule allows most statement-introducing keywords to be kept unreserved.
+ * (We still have to reserve initial keywords that might follow a block
+ * label, unfortunately, since the method used to determine if we are at
+ * start of statement doesn't recognize such cases. We'd also have to
+ * reserve any keyword that could legitimately be followed by ':=' or '['.)
+ * Some additional cases are handled in pl_gram.y using tok_is_keyword().
+ *
+ * We try to avoid reserving more keywords than we have to; but there's
+ * little point in not reserving a word if it's reserved in the core grammar.
+ * Currently, the following words are reserved here but not in the core:
+ * BEGIN BY DECLARE EXECUTE FOREACH IF LOOP STRICT WHILE
+ */
+
+/* ScanKeywordList lookup data for PL/pgSQL keywords */
+#include "pl_reserved_kwlist_d.h"
+#include "pl_unreserved_kwlist_d.h"
+
+/* Token codes for PL/pgSQL keywords */
+#define PG_KEYWORD(kwname, value) value,
+
+static const uint16 ReservedPLKeywordTokens[] = {
+#include "pl_reserved_kwlist.h"
+};
+
+static const uint16 UnreservedPLKeywordTokens[] = {
+#include "pl_unreserved_kwlist.h"
+};
+
+#undef PG_KEYWORD
+
+/*
+ * This macro must recognize all tokens that can immediately precede a
+ * PL/pgSQL executable statement (that is, proc_sect or proc_stmt in the
+ * grammar). Fortunately, there are not very many, so hard-coding in this
+ * fashion seems sufficient.
+ */
+#define AT_STMT_START(prev_token) \
+ ((prev_token) == ';' || \
+ (prev_token) == K_BEGIN || \
+ (prev_token) == K_THEN || \
+ (prev_token) == K_ELSE || \
+ (prev_token) == K_LOOP)
+
+
+/* Auxiliary data about a token (other than the token type) */
+typedef struct
+{
+ YYSTYPE lval; /* semantic information */
+ YYLTYPE lloc; /* offset in scanbuf */
+ int leng; /* length in bytes */
+} TokenAuxData;
+
+/*
+ * Scanner working state. At some point we might wish to fold all this
+ * into a YY_EXTRA struct. For the moment, there is no need for plpgsql's
+ * lexer to be re-entrant, and the notational burden of passing a yyscanner
+ * pointer around is great enough to not want to do it without need.
+ */
+
+/* The stuff the core lexer needs */
+static core_yyscan_t yyscanner = NULL;
+static core_yy_extra_type core_yy;
+
+/* The original input string */
+static const char *scanorig;
+
+/* Current token's length (corresponds to plpgsql_yylval and plpgsql_yylloc) */
+static int plpgsql_yyleng;
+
+/* Current token's code (corresponds to plpgsql_yylval and plpgsql_yylloc) */
+static int plpgsql_yytoken;
+
+/* Token pushback stack */
+#define MAX_PUSHBACKS 4
+
+static int num_pushbacks;
+static int pushback_token[MAX_PUSHBACKS];
+static TokenAuxData pushback_auxdata[MAX_PUSHBACKS];
+
+/* State for plpgsql_location_to_lineno() */
+static const char *cur_line_start;
+static const char *cur_line_end;
+static int cur_line_num;
+
+/* Internal functions */
+static int internal_yylex(TokenAuxData *auxdata);
+static void push_back_token(int token, TokenAuxData *auxdata);
+static void location_lineno_init(void);
+
+
+/*
+ * This is the yylex routine called from the PL/pgSQL grammar.
+ * It is a wrapper around the core lexer, with the ability to recognize
+ * PL/pgSQL variables and return them as special T_DATUM tokens. If a
+ * word or compound word does not match any variable name, or if matching
+ * is turned off by plpgsql_IdentifierLookup, it is returned as
+ * T_WORD or T_CWORD respectively, or as an unreserved keyword if it
+ * matches one of those.
+ */
+int
+plpgsql_yylex(void)
+{
+ int tok1;
+ TokenAuxData aux1;
+ int kwnum;
+
+ tok1 = internal_yylex(&aux1);
+ if (tok1 == IDENT || tok1 == PARAM)
+ {
+ int tok2;
+ TokenAuxData aux2;
+
+ tok2 = internal_yylex(&aux2);
+ if (tok2 == '.')
+ {
+ int tok3;
+ TokenAuxData aux3;
+
+ tok3 = internal_yylex(&aux3);
+ if (tok3 == IDENT)
+ {
+ int tok4;
+ TokenAuxData aux4;
+
+ tok4 = internal_yylex(&aux4);
+ if (tok4 == '.')
+ {
+ int tok5;
+ TokenAuxData aux5;
+
+ tok5 = internal_yylex(&aux5);
+ if (tok5 == IDENT)
+ {
+ if (plpgsql_parse_tripword(aux1.lval.str,
+ aux3.lval.str,
+ aux5.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ else
+ {
+ /* not A.B.C, so just process A.B */
+ push_back_token(tok5, &aux5);
+ push_back_token(tok4, &aux4);
+ if (plpgsql_parse_dblword(aux1.lval.str,
+ aux3.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ }
+ else
+ {
+ /* not A.B.C, so just process A.B */
+ push_back_token(tok4, &aux4);
+ if (plpgsql_parse_dblword(aux1.lval.str,
+ aux3.lval.str,
+ &aux1.lval.wdatum,
+ &aux1.lval.cword))
+ tok1 = T_DATUM;
+ else
+ tok1 = T_CWORD;
+ }
+ }
+ else
+ {
+ /* not A.B, so just process A */
+ push_back_token(tok3, &aux3);
+ push_back_token(tok2, &aux2);
+ if (plpgsql_parse_word(aux1.lval.str,
+ core_yy.scanbuf + aux1.lloc,
+ true,
+ &aux1.lval.wdatum,
+ &aux1.lval.word))
+ tok1 = T_DATUM;
+ else if (!aux1.lval.word.quoted &&
+ (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
+ &UnreservedPLKeywords)) >= 0)
+ {
+ aux1.lval.keyword = GetScanKeyword(kwnum,
+ &UnreservedPLKeywords);
+ tok1 = UnreservedPLKeywordTokens[kwnum];
+ }
+ else
+ tok1 = T_WORD;
+ }
+ }
+ else
+ {
+ /* not A.B, so just process A */
+ push_back_token(tok2, &aux2);
+
+ /*
+ * See if it matches a variable name, except in the context where
+ * we are at start of statement and the next token isn't
+ * assignment or '['. In that case, it couldn't validly be a
+ * variable name, and skipping the lookup allows variable names to
+ * be used that would conflict with plpgsql or core keywords that
+ * introduce statements (e.g., "comment"). Without this special
+ * logic, every statement-introducing keyword would effectively be
+ * reserved in PL/pgSQL, which would be unpleasant.
+ *
+ * If it isn't a variable name, try to match against unreserved
+ * plpgsql keywords. If not one of those either, it's T_WORD.
+ *
+ * Note: we must call plpgsql_parse_word even if we don't want to
+ * do variable lookup, because it sets up aux1.lval.word for the
+ * non-variable cases.
+ */
+ if (plpgsql_parse_word(aux1.lval.str,
+ core_yy.scanbuf + aux1.lloc,
+ (!AT_STMT_START(plpgsql_yytoken) ||
+ (tok2 == '=' || tok2 == COLON_EQUALS ||
+ tok2 == '[')),
+ &aux1.lval.wdatum,
+ &aux1.lval.word))
+ tok1 = T_DATUM;
+ else if (!aux1.lval.word.quoted &&
+ (kwnum = ScanKeywordLookup(aux1.lval.word.ident,
+ &UnreservedPLKeywords)) >= 0)
+ {
+ aux1.lval.keyword = GetScanKeyword(kwnum,
+ &UnreservedPLKeywords);
+ tok1 = UnreservedPLKeywordTokens[kwnum];
+ }
+ else
+ tok1 = T_WORD;
+ }
+ }
+ else
+ {
+ /*
+ * Not a potential plpgsql variable name, just return the data.
+ *
+ * Note that we also come through here if the grammar pushed back a
+ * T_DATUM, T_CWORD, T_WORD, or unreserved-keyword token returned by a
+ * previous lookup cycle; thus, pushbacks do not incur extra lookup
+ * work, since we'll never do the above code twice for the same token.
+ * This property also makes it safe to rely on the old value of
+ * plpgsql_yytoken in the is-this-start-of-statement test above.
+ */
+ }
+
+ plpgsql_yylval = aux1.lval;
+ plpgsql_yylloc = aux1.lloc;
+ plpgsql_yyleng = aux1.leng;
+ plpgsql_yytoken = tok1;
+ return tok1;
+}
+
+/*
+ * Internal yylex function. This wraps the core lexer and adds one feature:
+ * a token pushback stack. We also make a couple of trivial single-token
+ * translations from what the core lexer does to what we want, in particular
+ * interfacing from the core_YYSTYPE to YYSTYPE union.
+ */
+static int
+internal_yylex(TokenAuxData *auxdata)
+{
+ int token;
+ const char *yytext;
+
+ if (num_pushbacks > 0)
+ {
+ num_pushbacks--;
+ token = pushback_token[num_pushbacks];
+ *auxdata = pushback_auxdata[num_pushbacks];
+ }
+ else
+ {
+ token = core_yylex(&auxdata->lval.core_yystype,
+ &auxdata->lloc,
+ yyscanner);
+
+ /* remember the length of yytext before it gets changed */
+ yytext = core_yy.scanbuf + auxdata->lloc;
+ auxdata->leng = strlen(yytext);
+
+ /* Check for << >> and #, which the core considers operators */
+ if (token == Op)
+ {
+ if (strcmp(auxdata->lval.str, "<<") == 0)
+ token = LESS_LESS;
+ else if (strcmp(auxdata->lval.str, ">>") == 0)
+ token = GREATER_GREATER;
+ else if (strcmp(auxdata->lval.str, "#") == 0)
+ token = '#';
+ }
+
+ /* The core returns PARAM as ival, but we treat it like IDENT */
+ else if (token == PARAM)
+ {
+ auxdata->lval.str = pstrdup(yytext);
+ }
+ }
+
+ return token;
+}
+
+/*
+ * Push back a token to be re-read by next internal_yylex() call.
+ */
+static void
+push_back_token(int token, TokenAuxData *auxdata)
+{
+ if (num_pushbacks >= MAX_PUSHBACKS)
+ elog(ERROR, "too many tokens pushed back");
+ pushback_token[num_pushbacks] = token;
+ pushback_auxdata[num_pushbacks] = *auxdata;
+ num_pushbacks++;
+}
+
+/*
+ * Push back a single token to be re-read by next plpgsql_yylex() call.
+ *
+ * NOTE: this does not cause yylval or yylloc to "back up". Also, it
+ * is not a good idea to push back a token code other than what you read.
+ */
+void
+plpgsql_push_back_token(int token)
+{
+ TokenAuxData auxdata;
+
+ auxdata.lval = plpgsql_yylval;
+ auxdata.lloc = plpgsql_yylloc;
+ auxdata.leng = plpgsql_yyleng;
+ push_back_token(token, &auxdata);
+}
+
+/*
+ * Tell whether a token is an unreserved keyword.
+ *
+ * (If it is, its lowercased form was returned as the token value, so we
+ * do not need to offer that data here.)
+ */
+bool
+plpgsql_token_is_unreserved_keyword(int token)
+{
+ int i;
+
+ for (i = 0; i < lengthof(UnreservedPLKeywordTokens); i++)
+ {
+ if (UnreservedPLKeywordTokens[i] == token)
+ return true;
+ }
+ return false;
+}
+
+/*
+ * Append the function text starting at startlocation and extending to
+ * (not including) endlocation onto the existing contents of "buf".
+ */
+void
+plpgsql_append_source_text(StringInfo buf,
+ int startlocation, int endlocation)
+{
+ Assert(startlocation <= endlocation);
+ appendBinaryStringInfo(buf, scanorig + startlocation,
+ endlocation - startlocation);
+}
+
+/*
+ * Peek one token ahead in the input stream. Only the token code is
+ * made available, not any of the auxiliary info such as location.
+ *
+ * NB: no variable or unreserved keyword lookup is performed here, they will
+ * be returned as IDENT. Reserved keywords are resolved as usual.
+ */
+int
+plpgsql_peek(void)
+{
+ int tok1;
+ TokenAuxData aux1;
+
+ tok1 = internal_yylex(&aux1);
+ push_back_token(tok1, &aux1);
+ return tok1;
+}
+
+/*
+ * Peek two tokens ahead in the input stream. The first token and its
+ * location in the query are returned in *tok1_p and *tok1_loc, second token
+ * and its location in *tok2_p and *tok2_loc.
+ *
+ * NB: no variable or unreserved keyword lookup is performed here, they will
+ * be returned as IDENT. Reserved keywords are resolved as usual.
+ */
+void
+plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc, int *tok2_loc)
+{
+ int tok1,
+ tok2;
+ TokenAuxData aux1,
+ aux2;
+
+ tok1 = internal_yylex(&aux1);
+ tok2 = internal_yylex(&aux2);
+
+ *tok1_p = tok1;
+ if (tok1_loc)
+ *tok1_loc = aux1.lloc;
+ *tok2_p = tok2;
+ if (tok2_loc)
+ *tok2_loc = aux2.lloc;
+
+ push_back_token(tok2, &aux2);
+ push_back_token(tok1, &aux1);
+}
+
+/*
+ * plpgsql_scanner_errposition
+ * Report an error cursor position, if possible.
+ *
+ * This is expected to be used within an ereport() call. The return value
+ * is a dummy (always 0, in fact).
+ *
+ * Note that this can only be used for messages emitted during initial
+ * parsing of a plpgsql function, since it requires the scanorig string
+ * to still be available.
+ */
+int
+plpgsql_scanner_errposition(int location)
+{
+ int pos;
+
+ if (location < 0 || scanorig == NULL)
+ return 0; /* no-op if location is unknown */
+
+ /* Convert byte offset to character number */
+ pos = pg_mbstrlen_with_len(scanorig, location) + 1;
+ /* And pass it to the ereport mechanism */
+ (void) internalerrposition(pos);
+ /* Also pass the function body string */
+ return internalerrquery(scanorig);
+}
+
+/*
+ * plpgsql_yyerror
+ * Report a lexer or grammar error.
+ *
+ * The message's cursor position refers to the current token (the one
+ * last returned by plpgsql_yylex()).
+ * This is OK for syntax error messages from the Bison parser, because Bison
+ * parsers report error as soon as the first unparsable token is reached.
+ * Beware of using yyerror for other purposes, as the cursor position might
+ * be misleading!
+ */
+void
+plpgsql_yyerror(const char *message)
+{
+ char *yytext = core_yy.scanbuf + plpgsql_yylloc;
+
+ if (*yytext == '\0')
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: %s is typically the translation of "syntax error" */
+ errmsg("%s at end of input", _(message)),
+ plpgsql_scanner_errposition(plpgsql_yylloc)));
+ }
+ else
+ {
+ /*
+ * If we have done any lookahead then flex will have restored the
+ * character after the end-of-token. Zap it again so that we report
+ * only the single token here. This modifies scanbuf but we no longer
+ * care about that.
+ */
+ yytext[plpgsql_yyleng] = '\0';
+
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ /* translator: first %s is typically the translation of "syntax error" */
+ errmsg("%s at or near \"%s\"", _(message), yytext),
+ plpgsql_scanner_errposition(plpgsql_yylloc)));
+ }
+}
+
+/*
+ * Given a location (a byte offset in the function source text),
+ * return a line number.
+ *
+ * We expect that this is typically called for a sequence of increasing
+ * location values, so optimize accordingly by tracking the endpoints
+ * of the "current" line.
+ */
+int
+plpgsql_location_to_lineno(int location)
+{
+ const char *loc;
+
+ if (location < 0 || scanorig == NULL)
+ return 0; /* garbage in, garbage out */
+ loc = scanorig + location;
+
+ /* be correct, but not fast, if input location goes backwards */
+ if (loc < cur_line_start)
+ location_lineno_init();
+
+ while (cur_line_end != NULL && loc > cur_line_end)
+ {
+ cur_line_start = cur_line_end + 1;
+ cur_line_num++;
+ cur_line_end = strchr(cur_line_start, '\n');
+ }
+
+ return cur_line_num;
+}
+
+/* initialize or reset the state for plpgsql_location_to_lineno */
+static void
+location_lineno_init(void)
+{
+ cur_line_start = scanorig;
+ cur_line_num = 1;
+
+ cur_line_end = strchr(cur_line_start, '\n');
+}
+
+/* return the most recently computed lineno */
+int
+plpgsql_latest_lineno(void)
+{
+ return cur_line_num;
+}
+
+
+/*
+ * Called before any actual parsing is done
+ *
+ * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
+ * Although it is not fed directly to flex, we need the original string
+ * to cite in error messages.
+ */
+void
+plpgsql_scanner_init(const char *str)
+{
+ /* Start up the core scanner */
+ yyscanner = scanner_init(str, &core_yy,
+ &ReservedPLKeywords, ReservedPLKeywordTokens);
+
+ /*
+ * scanorig points to the original string, which unlike the scanner's
+ * scanbuf won't be modified on-the-fly by flex. Notice that although
+ * yytext points into scanbuf, we rely on being able to apply locations
+ * (offsets from string start) to scanorig as well.
+ */
+ scanorig = str;
+
+ /* Other setup */
+ plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
+ plpgsql_yytoken = 0;
+
+ num_pushbacks = 0;
+
+ location_lineno_init();
+}
+
+/*
+ * Called after parsing is done to clean up after plpgsql_scanner_init()
+ */
+void
+plpgsql_scanner_finish(void)
+{
+ /* release storage */
+ scanner_finish(yyscanner);
+ /* avoid leaving any dangling pointers */
+ yyscanner = NULL;
+ scanorig = NULL;
+}
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist.h b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
new file mode 100644
index 0000000..ee2be1b
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist.h
@@ -0,0 +1,111 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_unreserved_kwlist.h
+ *
+ * The keyword lists are kept in their own source files for use by
+ * automatic tools. The exact representation of a keyword is determined
+ * by the PG_KEYWORD macro, which is not defined in this file; it can
+ * be defined by the caller for special purposes.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpgsql/src/pl_unreserved_kwlist.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* There is deliberately not an #ifndef PL_UNRESERVED_KWLIST_H here. */
+
+/*
+ * List of (keyword-name, keyword-token-value) pairs.
+ *
+ * Be careful not to put the same word into pl_reserved_kwlist.h. Also be
+ * sure that pl_gram.y's unreserved_keyword production agrees with this list.
+ *
+ * Note: gen_keywordlist.pl requires the entries to appear in ASCII order.
+ */
+
+/* name, value */
+PG_KEYWORD("absolute", K_ABSOLUTE)
+PG_KEYWORD("alias", K_ALIAS)
+PG_KEYWORD("and", K_AND)
+PG_KEYWORD("array", K_ARRAY)
+PG_KEYWORD("assert", K_ASSERT)
+PG_KEYWORD("backward", K_BACKWARD)
+PG_KEYWORD("call", K_CALL)
+PG_KEYWORD("chain", K_CHAIN)
+PG_KEYWORD("close", K_CLOSE)
+PG_KEYWORD("collate", K_COLLATE)
+PG_KEYWORD("column", K_COLUMN)
+PG_KEYWORD("column_name", K_COLUMN_NAME)
+PG_KEYWORD("commit", K_COMMIT)
+PG_KEYWORD("constant", K_CONSTANT)
+PG_KEYWORD("constraint", K_CONSTRAINT)
+PG_KEYWORD("constraint_name", K_CONSTRAINT_NAME)
+PG_KEYWORD("continue", K_CONTINUE)
+PG_KEYWORD("current", K_CURRENT)
+PG_KEYWORD("cursor", K_CURSOR)
+PG_KEYWORD("datatype", K_DATATYPE)
+PG_KEYWORD("debug", K_DEBUG)
+PG_KEYWORD("default", K_DEFAULT)
+PG_KEYWORD("detail", K_DETAIL)
+PG_KEYWORD("diagnostics", K_DIAGNOSTICS)
+PG_KEYWORD("do", K_DO)
+PG_KEYWORD("dump", K_DUMP)
+PG_KEYWORD("elseif", K_ELSIF)
+PG_KEYWORD("elsif", K_ELSIF)
+PG_KEYWORD("errcode", K_ERRCODE)
+PG_KEYWORD("error", K_ERROR)
+PG_KEYWORD("exception", K_EXCEPTION)
+PG_KEYWORD("exit", K_EXIT)
+PG_KEYWORD("fetch", K_FETCH)
+PG_KEYWORD("first", K_FIRST)
+PG_KEYWORD("forward", K_FORWARD)
+PG_KEYWORD("get", K_GET)
+PG_KEYWORD("hint", K_HINT)
+PG_KEYWORD("import", K_IMPORT)
+PG_KEYWORD("info", K_INFO)
+PG_KEYWORD("insert", K_INSERT)
+PG_KEYWORD("is", K_IS)
+PG_KEYWORD("last", K_LAST)
+PG_KEYWORD("log", K_LOG)
+PG_KEYWORD("merge", K_MERGE)
+PG_KEYWORD("message", K_MESSAGE)
+PG_KEYWORD("message_text", K_MESSAGE_TEXT)
+PG_KEYWORD("move", K_MOVE)
+PG_KEYWORD("next", K_NEXT)
+PG_KEYWORD("no", K_NO)
+PG_KEYWORD("notice", K_NOTICE)
+PG_KEYWORD("open", K_OPEN)
+PG_KEYWORD("option", K_OPTION)
+PG_KEYWORD("perform", K_PERFORM)
+PG_KEYWORD("pg_context", K_PG_CONTEXT)
+PG_KEYWORD("pg_datatype_name", K_PG_DATATYPE_NAME)
+PG_KEYWORD("pg_exception_context", K_PG_EXCEPTION_CONTEXT)
+PG_KEYWORD("pg_exception_detail", K_PG_EXCEPTION_DETAIL)
+PG_KEYWORD("pg_exception_hint", K_PG_EXCEPTION_HINT)
+PG_KEYWORD("print_strict_params", K_PRINT_STRICT_PARAMS)
+PG_KEYWORD("prior", K_PRIOR)
+PG_KEYWORD("query", K_QUERY)
+PG_KEYWORD("raise", K_RAISE)
+PG_KEYWORD("relative", K_RELATIVE)
+PG_KEYWORD("return", K_RETURN)
+PG_KEYWORD("returned_sqlstate", K_RETURNED_SQLSTATE)
+PG_KEYWORD("reverse", K_REVERSE)
+PG_KEYWORD("rollback", K_ROLLBACK)
+PG_KEYWORD("row_count", K_ROW_COUNT)
+PG_KEYWORD("rowtype", K_ROWTYPE)
+PG_KEYWORD("schema", K_SCHEMA)
+PG_KEYWORD("schema_name", K_SCHEMA_NAME)
+PG_KEYWORD("scroll", K_SCROLL)
+PG_KEYWORD("slice", K_SLICE)
+PG_KEYWORD("sqlstate", K_SQLSTATE)
+PG_KEYWORD("stacked", K_STACKED)
+PG_KEYWORD("table", K_TABLE)
+PG_KEYWORD("table_name", K_TABLE_NAME)
+PG_KEYWORD("type", K_TYPE)
+PG_KEYWORD("use_column", K_USE_COLUMN)
+PG_KEYWORD("use_variable", K_USE_VARIABLE)
+PG_KEYWORD("variable_conflict", K_VARIABLE_CONFLICT)
+PG_KEYWORD("warning", K_WARNING)
diff --git a/src/pl/plpgsql/src/pl_unreserved_kwlist_d.h b/src/pl/plpgsql/src/pl_unreserved_kwlist_d.h
new file mode 100644
index 0000000..3ae9467
--- /dev/null
+++ b/src/pl/plpgsql/src/pl_unreserved_kwlist_d.h
@@ -0,0 +1,244 @@
+/*-------------------------------------------------------------------------
+ *
+ * pl_unreserved_kwlist_d.h
+ * List of keywords represented as a ScanKeywordList.
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * NOTES
+ * ******************************
+ * *** DO NOT EDIT THIS FILE! ***
+ * ******************************
+ *
+ * It has been GENERATED by src/tools/gen_keywordlist.pl
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PL_UNRESERVED_KWLIST_D_H
+#define PL_UNRESERVED_KWLIST_D_H
+
+#include "common/kwlookup.h"
+
+static const char UnreservedPLKeywords_kw_string[] =
+ "absolute\0"
+ "alias\0"
+ "and\0"
+ "array\0"
+ "assert\0"
+ "backward\0"
+ "call\0"
+ "chain\0"
+ "close\0"
+ "collate\0"
+ "column\0"
+ "column_name\0"
+ "commit\0"
+ "constant\0"
+ "constraint\0"
+ "constraint_name\0"
+ "continue\0"
+ "current\0"
+ "cursor\0"
+ "datatype\0"
+ "debug\0"
+ "default\0"
+ "detail\0"
+ "diagnostics\0"
+ "do\0"
+ "dump\0"
+ "elseif\0"
+ "elsif\0"
+ "errcode\0"
+ "error\0"
+ "exception\0"
+ "exit\0"
+ "fetch\0"
+ "first\0"
+ "forward\0"
+ "get\0"
+ "hint\0"
+ "import\0"
+ "info\0"
+ "insert\0"
+ "is\0"
+ "last\0"
+ "log\0"
+ "merge\0"
+ "message\0"
+ "message_text\0"
+ "move\0"
+ "next\0"
+ "no\0"
+ "notice\0"
+ "open\0"
+ "option\0"
+ "perform\0"
+ "pg_context\0"
+ "pg_datatype_name\0"
+ "pg_exception_context\0"
+ "pg_exception_detail\0"
+ "pg_exception_hint\0"
+ "print_strict_params\0"
+ "prior\0"
+ "query\0"
+ "raise\0"
+ "relative\0"
+ "return\0"
+ "returned_sqlstate\0"
+ "reverse\0"
+ "rollback\0"
+ "row_count\0"
+ "rowtype\0"
+ "schema\0"
+ "schema_name\0"
+ "scroll\0"
+ "slice\0"
+ "sqlstate\0"
+ "stacked\0"
+ "table\0"
+ "table_name\0"
+ "type\0"
+ "use_column\0"
+ "use_variable\0"
+ "variable_conflict\0"
+ "warning";
+
+static const uint16 UnreservedPLKeywords_kw_offsets[] = {
+ 0,
+ 9,
+ 15,
+ 19,
+ 25,
+ 32,
+ 41,
+ 46,
+ 52,
+ 58,
+ 66,
+ 73,
+ 85,
+ 92,
+ 101,
+ 112,
+ 128,
+ 137,
+ 145,
+ 152,
+ 161,
+ 167,
+ 175,
+ 182,
+ 194,
+ 197,
+ 202,
+ 209,
+ 215,
+ 223,
+ 229,
+ 239,
+ 244,
+ 250,
+ 256,
+ 264,
+ 268,
+ 273,
+ 280,
+ 285,
+ 292,
+ 295,
+ 300,
+ 304,
+ 310,
+ 318,
+ 331,
+ 336,
+ 341,
+ 344,
+ 351,
+ 356,
+ 363,
+ 371,
+ 382,
+ 399,
+ 420,
+ 440,
+ 458,
+ 478,
+ 484,
+ 490,
+ 496,
+ 505,
+ 512,
+ 530,
+ 538,
+ 547,
+ 557,
+ 565,
+ 572,
+ 584,
+ 591,
+ 597,
+ 606,
+ 614,
+ 620,
+ 631,
+ 636,
+ 647,
+ 660,
+ 678,
+};
+
+#define UNRESERVEDPLKEYWORDS_NUM_KEYWORDS 82
+
+static int
+UnreservedPLKeywords_hash_func(const void *key, size_t keylen)
+{
+ static const int16 h[165] = {
+ 58, 0, 26, 32767, 0, 0, 9, 32767,
+ 0, 32767, 37, 74, 32767, -7, 32767, 39,
+ 58, -5, 32767, 31, 32767, 32767, 75, -23,
+ 32767, 0, 32767, 32767, 32767, -14, 32767, 81,
+ 32767, 32767, 32767, -36, -9, 32767, 32767, 32767,
+ 40, 32767, 54, 10, 11, 43, 32767, 0,
+ 52, 105, -22, 15, 32767, -33, 49, -65,
+ 48, 32767, 32767, 32767, 25, 49, -47, 37,
+ 21, 32767, 32767, -15, 70, 32767, 32767, 64,
+ -10, 126, 32767, 51, 0, 36, 32767, -55,
+ -22, 32767, 32767, 32767, 32767, 32767, -26, -35,
+ 32767, 61, 32767, 32767, 32767, -23, 98, 48,
+ 23, 19, 32767, 7, 35, 5, -18, 71,
+ 28, 5, 32767, 32767, 32767, 74, 32767, 82,
+ 32767, 0, 32767, 32767, 66, 0, 0, 50,
+ 32767, 32767, 5, 2, 0, 32767, 55, 32767,
+ 32767, 45, 79, 32767, 32767, 73, 22, 0,
+ 103, 32767, -20, 72, 32767, 0, 29, 32767,
+ 0, 32767, 32767, 0, 50, 28, 32767, -40,
+ 32767, 32767, 34, 56, 32767, 32767, 32767, 17,
+ -36, 32767, 67, 32767, 0
+ };
+
+ const unsigned char *k = (const unsigned char *) key;
+ uint32 a = 0;
+ uint32 b = 0;
+
+ while (keylen--)
+ {
+ unsigned char c = *k++ | 0x20;
+
+ a = a * 257 + c;
+ b = b * 8191 + c;
+ }
+ return h[a % 165] + h[b % 165];
+}
+
+static const ScanKeywordList UnreservedPLKeywords = {
+ UnreservedPLKeywords_kw_string,
+ UnreservedPLKeywords_kw_offsets,
+ UnreservedPLKeywords_hash_func,
+ UNRESERVEDPLKEYWORDS_NUM_KEYWORDS,
+ 20
+};
+
+#endif /* PL_UNRESERVED_KWLIST_D_H */
diff --git a/src/pl/plpgsql/src/plerrcodes.h b/src/pl/plpgsql/src/plerrcodes.h
new file mode 100644
index 0000000..b17725b
--- /dev/null
+++ b/src/pl/plpgsql/src/plerrcodes.h
@@ -0,0 +1,998 @@
+/* autogenerated from src/backend/utils/errcodes.txt, do not edit */
+/* there is deliberately not an #ifndef PLERRCODES_H here */
+
+{
+ "sql_statement_not_yet_complete", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE
+},
+
+{
+ "connection_exception", ERRCODE_CONNECTION_EXCEPTION
+},
+
+{
+ "connection_does_not_exist", ERRCODE_CONNECTION_DOES_NOT_EXIST
+},
+
+{
+ "connection_failure", ERRCODE_CONNECTION_FAILURE
+},
+
+{
+ "sqlclient_unable_to_establish_sqlconnection", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION
+},
+
+{
+ "sqlserver_rejected_establishment_of_sqlconnection", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
+},
+
+{
+ "transaction_resolution_unknown", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN
+},
+
+{
+ "protocol_violation", ERRCODE_PROTOCOL_VIOLATION
+},
+
+{
+ "triggered_action_exception", ERRCODE_TRIGGERED_ACTION_EXCEPTION
+},
+
+{
+ "feature_not_supported", ERRCODE_FEATURE_NOT_SUPPORTED
+},
+
+{
+ "invalid_transaction_initiation", ERRCODE_INVALID_TRANSACTION_INITIATION
+},
+
+{
+ "locator_exception", ERRCODE_LOCATOR_EXCEPTION
+},
+
+{
+ "invalid_locator_specification", ERRCODE_L_E_INVALID_SPECIFICATION
+},
+
+{
+ "invalid_grantor", ERRCODE_INVALID_GRANTOR
+},
+
+{
+ "invalid_grant_operation", ERRCODE_INVALID_GRANT_OPERATION
+},
+
+{
+ "invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION
+},
+
+{
+ "diagnostics_exception", ERRCODE_DIAGNOSTICS_EXCEPTION
+},
+
+{
+ "stacked_diagnostics_accessed_without_active_handler", ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER
+},
+
+{
+ "case_not_found", ERRCODE_CASE_NOT_FOUND
+},
+
+{
+ "cardinality_violation", ERRCODE_CARDINALITY_VIOLATION
+},
+
+{
+ "data_exception", ERRCODE_DATA_EXCEPTION
+},
+
+{
+ "array_subscript_error", ERRCODE_ARRAY_SUBSCRIPT_ERROR
+},
+
+{
+ "character_not_in_repertoire", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE
+},
+
+{
+ "datetime_field_overflow", ERRCODE_DATETIME_FIELD_OVERFLOW
+},
+
+{
+ "division_by_zero", ERRCODE_DIVISION_BY_ZERO
+},
+
+{
+ "error_in_assignment", ERRCODE_ERROR_IN_ASSIGNMENT
+},
+
+{
+ "escape_character_conflict", ERRCODE_ESCAPE_CHARACTER_CONFLICT
+},
+
+{
+ "indicator_overflow", ERRCODE_INDICATOR_OVERFLOW
+},
+
+{
+ "interval_field_overflow", ERRCODE_INTERVAL_FIELD_OVERFLOW
+},
+
+{
+ "invalid_argument_for_logarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG
+},
+
+{
+ "invalid_argument_for_ntile_function", ERRCODE_INVALID_ARGUMENT_FOR_NTILE
+},
+
+{
+ "invalid_argument_for_nth_value_function", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE
+},
+
+{
+ "invalid_argument_for_power_function", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION
+},
+
+{
+ "invalid_argument_for_width_bucket_function", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION
+},
+
+{
+ "invalid_character_value_for_cast", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST
+},
+
+{
+ "invalid_datetime_format", ERRCODE_INVALID_DATETIME_FORMAT
+},
+
+{
+ "invalid_escape_character", ERRCODE_INVALID_ESCAPE_CHARACTER
+},
+
+{
+ "invalid_escape_octet", ERRCODE_INVALID_ESCAPE_OCTET
+},
+
+{
+ "invalid_escape_sequence", ERRCODE_INVALID_ESCAPE_SEQUENCE
+},
+
+{
+ "nonstandard_use_of_escape_character", ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "invalid_indicator_parameter_value", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE
+},
+
+{
+ "invalid_parameter_value", ERRCODE_INVALID_PARAMETER_VALUE
+},
+
+{
+ "invalid_preceding_or_following_size", ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE
+},
+
+{
+ "invalid_regular_expression", ERRCODE_INVALID_REGULAR_EXPRESSION
+},
+
+{
+ "invalid_row_count_in_limit_clause", ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE
+},
+
+{
+ "invalid_row_count_in_result_offset_clause", ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE
+},
+
+{
+ "invalid_tablesample_argument", ERRCODE_INVALID_TABLESAMPLE_ARGUMENT
+},
+
+{
+ "invalid_tablesample_repeat", ERRCODE_INVALID_TABLESAMPLE_REPEAT
+},
+
+{
+ "invalid_time_zone_displacement_value", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE
+},
+
+{
+ "invalid_use_of_escape_character", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "most_specific_type_mismatch", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH
+},
+
+{
+ "null_value_not_allowed", ERRCODE_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "null_value_no_indicator_parameter", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER
+},
+
+{
+ "numeric_value_out_of_range", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE
+},
+
+{
+ "sequence_generator_limit_exceeded", ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED
+},
+
+{
+ "string_data_length_mismatch", ERRCODE_STRING_DATA_LENGTH_MISMATCH
+},
+
+{
+ "string_data_right_truncation", ERRCODE_STRING_DATA_RIGHT_TRUNCATION
+},
+
+{
+ "substring_error", ERRCODE_SUBSTRING_ERROR
+},
+
+{
+ "trim_error", ERRCODE_TRIM_ERROR
+},
+
+{
+ "unterminated_c_string", ERRCODE_UNTERMINATED_C_STRING
+},
+
+{
+ "zero_length_character_string", ERRCODE_ZERO_LENGTH_CHARACTER_STRING
+},
+
+{
+ "floating_point_exception", ERRCODE_FLOATING_POINT_EXCEPTION
+},
+
+{
+ "invalid_text_representation", ERRCODE_INVALID_TEXT_REPRESENTATION
+},
+
+{
+ "invalid_binary_representation", ERRCODE_INVALID_BINARY_REPRESENTATION
+},
+
+{
+ "bad_copy_file_format", ERRCODE_BAD_COPY_FILE_FORMAT
+},
+
+{
+ "untranslatable_character", ERRCODE_UNTRANSLATABLE_CHARACTER
+},
+
+{
+ "not_an_xml_document", ERRCODE_NOT_AN_XML_DOCUMENT
+},
+
+{
+ "invalid_xml_document", ERRCODE_INVALID_XML_DOCUMENT
+},
+
+{
+ "invalid_xml_content", ERRCODE_INVALID_XML_CONTENT
+},
+
+{
+ "invalid_xml_comment", ERRCODE_INVALID_XML_COMMENT
+},
+
+{
+ "invalid_xml_processing_instruction", ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION
+},
+
+{
+ "duplicate_json_object_key_value", ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE
+},
+
+{
+ "invalid_argument_for_sql_json_datetime_function", ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION
+},
+
+{
+ "invalid_json_text", ERRCODE_INVALID_JSON_TEXT
+},
+
+{
+ "invalid_sql_json_subscript", ERRCODE_INVALID_SQL_JSON_SUBSCRIPT
+},
+
+{
+ "more_than_one_sql_json_item", ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM
+},
+
+{
+ "no_sql_json_item", ERRCODE_NO_SQL_JSON_ITEM
+},
+
+{
+ "non_numeric_sql_json_item", ERRCODE_NON_NUMERIC_SQL_JSON_ITEM
+},
+
+{
+ "non_unique_keys_in_a_json_object", ERRCODE_NON_UNIQUE_KEYS_IN_A_JSON_OBJECT
+},
+
+{
+ "singleton_sql_json_item_required", ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED
+},
+
+{
+ "sql_json_array_not_found", ERRCODE_SQL_JSON_ARRAY_NOT_FOUND
+},
+
+{
+ "sql_json_member_not_found", ERRCODE_SQL_JSON_MEMBER_NOT_FOUND
+},
+
+{
+ "sql_json_number_not_found", ERRCODE_SQL_JSON_NUMBER_NOT_FOUND
+},
+
+{
+ "sql_json_object_not_found", ERRCODE_SQL_JSON_OBJECT_NOT_FOUND
+},
+
+{
+ "too_many_json_array_elements", ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS
+},
+
+{
+ "too_many_json_object_members", ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS
+},
+
+{
+ "sql_json_scalar_required", ERRCODE_SQL_JSON_SCALAR_REQUIRED
+},
+
+{
+ "sql_json_item_cannot_be_cast_to_target_type", ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE
+},
+
+{
+ "integrity_constraint_violation", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "restrict_violation", ERRCODE_RESTRICT_VIOLATION
+},
+
+{
+ "not_null_violation", ERRCODE_NOT_NULL_VIOLATION
+},
+
+{
+ "foreign_key_violation", ERRCODE_FOREIGN_KEY_VIOLATION
+},
+
+{
+ "unique_violation", ERRCODE_UNIQUE_VIOLATION
+},
+
+{
+ "check_violation", ERRCODE_CHECK_VIOLATION
+},
+
+{
+ "exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+},
+
+{
+ "invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
+},
+
+{
+ "invalid_transaction_state", ERRCODE_INVALID_TRANSACTION_STATE
+},
+
+{
+ "active_sql_transaction", ERRCODE_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "branch_transaction_already_active", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE
+},
+
+{
+ "held_cursor_requires_same_isolation_level", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL
+},
+
+{
+ "inappropriate_access_mode_for_branch_transaction", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "inappropriate_isolation_level_for_branch_transaction", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "no_active_sql_transaction_for_branch_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "read_only_sql_transaction", ERRCODE_READ_ONLY_SQL_TRANSACTION
+},
+
+{
+ "schema_and_data_statement_mixing_not_supported", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED
+},
+
+{
+ "no_active_sql_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "in_failed_sql_transaction", ERRCODE_IN_FAILED_SQL_TRANSACTION
+},
+
+{
+ "idle_in_transaction_session_timeout", ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT
+},
+
+{
+ "invalid_sql_statement_name", ERRCODE_INVALID_SQL_STATEMENT_NAME
+},
+
+{
+ "triggered_data_change_violation", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION
+},
+
+{
+ "invalid_authorization_specification", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION
+},
+
+{
+ "invalid_password", ERRCODE_INVALID_PASSWORD
+},
+
+{
+ "dependent_privilege_descriptors_still_exist", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST
+},
+
+{
+ "dependent_objects_still_exist", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST
+},
+
+{
+ "invalid_transaction_termination", ERRCODE_INVALID_TRANSACTION_TERMINATION
+},
+
+{
+ "sql_routine_exception", ERRCODE_SQL_ROUTINE_EXCEPTION
+},
+
+{
+ "function_executed_no_return_statement", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT
+},
+
+{
+ "modifying_sql_data_not_permitted", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "prohibited_sql_statement_attempted", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "reading_sql_data_not_permitted", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "invalid_cursor_name", ERRCODE_INVALID_CURSOR_NAME
+},
+
+{
+ "external_routine_exception", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION
+},
+
+{
+ "containing_sql_not_permitted", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED
+},
+
+{
+ "modifying_sql_data_not_permitted", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "prohibited_sql_statement_attempted", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "reading_sql_data_not_permitted", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "external_routine_invocation_exception", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION
+},
+
+{
+ "invalid_sqlstate_returned", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED
+},
+
+{
+ "null_value_not_allowed", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "trigger_protocol_violated", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "srf_protocol_violated", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED
+},
+
+{
+ "event_trigger_protocol_violated", ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "savepoint_exception", ERRCODE_SAVEPOINT_EXCEPTION
+},
+
+{
+ "invalid_savepoint_specification", ERRCODE_S_E_INVALID_SPECIFICATION
+},
+
+{
+ "invalid_catalog_name", ERRCODE_INVALID_CATALOG_NAME
+},
+
+{
+ "invalid_schema_name", ERRCODE_INVALID_SCHEMA_NAME
+},
+
+{
+ "transaction_rollback", ERRCODE_TRANSACTION_ROLLBACK
+},
+
+{
+ "transaction_integrity_constraint_violation", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "serialization_failure", ERRCODE_T_R_SERIALIZATION_FAILURE
+},
+
+{
+ "statement_completion_unknown", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN
+},
+
+{
+ "deadlock_detected", ERRCODE_T_R_DEADLOCK_DETECTED
+},
+
+{
+ "syntax_error_or_access_rule_violation", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION
+},
+
+{
+ "syntax_error", ERRCODE_SYNTAX_ERROR
+},
+
+{
+ "insufficient_privilege", ERRCODE_INSUFFICIENT_PRIVILEGE
+},
+
+{
+ "cannot_coerce", ERRCODE_CANNOT_COERCE
+},
+
+{
+ "grouping_error", ERRCODE_GROUPING_ERROR
+},
+
+{
+ "windowing_error", ERRCODE_WINDOWING_ERROR
+},
+
+{
+ "invalid_recursion", ERRCODE_INVALID_RECURSION
+},
+
+{
+ "invalid_foreign_key", ERRCODE_INVALID_FOREIGN_KEY
+},
+
+{
+ "invalid_name", ERRCODE_INVALID_NAME
+},
+
+{
+ "name_too_long", ERRCODE_NAME_TOO_LONG
+},
+
+{
+ "reserved_name", ERRCODE_RESERVED_NAME
+},
+
+{
+ "datatype_mismatch", ERRCODE_DATATYPE_MISMATCH
+},
+
+{
+ "indeterminate_datatype", ERRCODE_INDETERMINATE_DATATYPE
+},
+
+{
+ "collation_mismatch", ERRCODE_COLLATION_MISMATCH
+},
+
+{
+ "indeterminate_collation", ERRCODE_INDETERMINATE_COLLATION
+},
+
+{
+ "wrong_object_type", ERRCODE_WRONG_OBJECT_TYPE
+},
+
+{
+ "generated_always", ERRCODE_GENERATED_ALWAYS
+},
+
+{
+ "undefined_column", ERRCODE_UNDEFINED_COLUMN
+},
+
+{
+ "undefined_function", ERRCODE_UNDEFINED_FUNCTION
+},
+
+{
+ "undefined_table", ERRCODE_UNDEFINED_TABLE
+},
+
+{
+ "undefined_parameter", ERRCODE_UNDEFINED_PARAMETER
+},
+
+{
+ "undefined_object", ERRCODE_UNDEFINED_OBJECT
+},
+
+{
+ "duplicate_column", ERRCODE_DUPLICATE_COLUMN
+},
+
+{
+ "duplicate_cursor", ERRCODE_DUPLICATE_CURSOR
+},
+
+{
+ "duplicate_database", ERRCODE_DUPLICATE_DATABASE
+},
+
+{
+ "duplicate_function", ERRCODE_DUPLICATE_FUNCTION
+},
+
+{
+ "duplicate_prepared_statement", ERRCODE_DUPLICATE_PSTATEMENT
+},
+
+{
+ "duplicate_schema", ERRCODE_DUPLICATE_SCHEMA
+},
+
+{
+ "duplicate_table", ERRCODE_DUPLICATE_TABLE
+},
+
+{
+ "duplicate_alias", ERRCODE_DUPLICATE_ALIAS
+},
+
+{
+ "duplicate_object", ERRCODE_DUPLICATE_OBJECT
+},
+
+{
+ "ambiguous_column", ERRCODE_AMBIGUOUS_COLUMN
+},
+
+{
+ "ambiguous_function", ERRCODE_AMBIGUOUS_FUNCTION
+},
+
+{
+ "ambiguous_parameter", ERRCODE_AMBIGUOUS_PARAMETER
+},
+
+{
+ "ambiguous_alias", ERRCODE_AMBIGUOUS_ALIAS
+},
+
+{
+ "invalid_column_reference", ERRCODE_INVALID_COLUMN_REFERENCE
+},
+
+{
+ "invalid_column_definition", ERRCODE_INVALID_COLUMN_DEFINITION
+},
+
+{
+ "invalid_cursor_definition", ERRCODE_INVALID_CURSOR_DEFINITION
+},
+
+{
+ "invalid_database_definition", ERRCODE_INVALID_DATABASE_DEFINITION
+},
+
+{
+ "invalid_function_definition", ERRCODE_INVALID_FUNCTION_DEFINITION
+},
+
+{
+ "invalid_prepared_statement_definition", ERRCODE_INVALID_PSTATEMENT_DEFINITION
+},
+
+{
+ "invalid_schema_definition", ERRCODE_INVALID_SCHEMA_DEFINITION
+},
+
+{
+ "invalid_table_definition", ERRCODE_INVALID_TABLE_DEFINITION
+},
+
+{
+ "invalid_object_definition", ERRCODE_INVALID_OBJECT_DEFINITION
+},
+
+{
+ "with_check_option_violation", ERRCODE_WITH_CHECK_OPTION_VIOLATION
+},
+
+{
+ "insufficient_resources", ERRCODE_INSUFFICIENT_RESOURCES
+},
+
+{
+ "disk_full", ERRCODE_DISK_FULL
+},
+
+{
+ "out_of_memory", ERRCODE_OUT_OF_MEMORY
+},
+
+{
+ "too_many_connections", ERRCODE_TOO_MANY_CONNECTIONS
+},
+
+{
+ "configuration_limit_exceeded", ERRCODE_CONFIGURATION_LIMIT_EXCEEDED
+},
+
+{
+ "program_limit_exceeded", ERRCODE_PROGRAM_LIMIT_EXCEEDED
+},
+
+{
+ "statement_too_complex", ERRCODE_STATEMENT_TOO_COMPLEX
+},
+
+{
+ "too_many_columns", ERRCODE_TOO_MANY_COLUMNS
+},
+
+{
+ "too_many_arguments", ERRCODE_TOO_MANY_ARGUMENTS
+},
+
+{
+ "object_not_in_prerequisite_state", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
+},
+
+{
+ "object_in_use", ERRCODE_OBJECT_IN_USE
+},
+
+{
+ "cant_change_runtime_param", ERRCODE_CANT_CHANGE_RUNTIME_PARAM
+},
+
+{
+ "lock_not_available", ERRCODE_LOCK_NOT_AVAILABLE
+},
+
+{
+ "unsafe_new_enum_value_usage", ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE
+},
+
+{
+ "operator_intervention", ERRCODE_OPERATOR_INTERVENTION
+},
+
+{
+ "query_canceled", ERRCODE_QUERY_CANCELED
+},
+
+{
+ "admin_shutdown", ERRCODE_ADMIN_SHUTDOWN
+},
+
+{
+ "crash_shutdown", ERRCODE_CRASH_SHUTDOWN
+},
+
+{
+ "cannot_connect_now", ERRCODE_CANNOT_CONNECT_NOW
+},
+
+{
+ "database_dropped", ERRCODE_DATABASE_DROPPED
+},
+
+{
+ "idle_session_timeout", ERRCODE_IDLE_SESSION_TIMEOUT
+},
+
+{
+ "system_error", ERRCODE_SYSTEM_ERROR
+},
+
+{
+ "io_error", ERRCODE_IO_ERROR
+},
+
+{
+ "undefined_file", ERRCODE_UNDEFINED_FILE
+},
+
+{
+ "duplicate_file", ERRCODE_DUPLICATE_FILE
+},
+
+{
+ "snapshot_too_old", ERRCODE_SNAPSHOT_TOO_OLD
+},
+
+{
+ "config_file_error", ERRCODE_CONFIG_FILE_ERROR
+},
+
+{
+ "lock_file_exists", ERRCODE_LOCK_FILE_EXISTS
+},
+
+{
+ "fdw_error", ERRCODE_FDW_ERROR
+},
+
+{
+ "fdw_column_name_not_found", ERRCODE_FDW_COLUMN_NAME_NOT_FOUND
+},
+
+{
+ "fdw_dynamic_parameter_value_needed", ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED
+},
+
+{
+ "fdw_function_sequence_error", ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR
+},
+
+{
+ "fdw_inconsistent_descriptor_information", ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION
+},
+
+{
+ "fdw_invalid_attribute_value", ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE
+},
+
+{
+ "fdw_invalid_column_name", ERRCODE_FDW_INVALID_COLUMN_NAME
+},
+
+{
+ "fdw_invalid_column_number", ERRCODE_FDW_INVALID_COLUMN_NUMBER
+},
+
+{
+ "fdw_invalid_data_type", ERRCODE_FDW_INVALID_DATA_TYPE
+},
+
+{
+ "fdw_invalid_data_type_descriptors", ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS
+},
+
+{
+ "fdw_invalid_descriptor_field_identifier", ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER
+},
+
+{
+ "fdw_invalid_handle", ERRCODE_FDW_INVALID_HANDLE
+},
+
+{
+ "fdw_invalid_option_index", ERRCODE_FDW_INVALID_OPTION_INDEX
+},
+
+{
+ "fdw_invalid_option_name", ERRCODE_FDW_INVALID_OPTION_NAME
+},
+
+{
+ "fdw_invalid_string_length_or_buffer_length", ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH
+},
+
+{
+ "fdw_invalid_string_format", ERRCODE_FDW_INVALID_STRING_FORMAT
+},
+
+{
+ "fdw_invalid_use_of_null_pointer", ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER
+},
+
+{
+ "fdw_too_many_handles", ERRCODE_FDW_TOO_MANY_HANDLES
+},
+
+{
+ "fdw_out_of_memory", ERRCODE_FDW_OUT_OF_MEMORY
+},
+
+{
+ "fdw_no_schemas", ERRCODE_FDW_NO_SCHEMAS
+},
+
+{
+ "fdw_option_name_not_found", ERRCODE_FDW_OPTION_NAME_NOT_FOUND
+},
+
+{
+ "fdw_reply_handle", ERRCODE_FDW_REPLY_HANDLE
+},
+
+{
+ "fdw_schema_not_found", ERRCODE_FDW_SCHEMA_NOT_FOUND
+},
+
+{
+ "fdw_table_not_found", ERRCODE_FDW_TABLE_NOT_FOUND
+},
+
+{
+ "fdw_unable_to_create_execution", ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION
+},
+
+{
+ "fdw_unable_to_create_reply", ERRCODE_FDW_UNABLE_TO_CREATE_REPLY
+},
+
+{
+ "fdw_unable_to_establish_connection", ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION
+},
+
+{
+ "plpgsql_error", ERRCODE_PLPGSQL_ERROR
+},
+
+{
+ "raise_exception", ERRCODE_RAISE_EXCEPTION
+},
+
+{
+ "no_data_found", ERRCODE_NO_DATA_FOUND
+},
+
+{
+ "too_many_rows", ERRCODE_TOO_MANY_ROWS
+},
+
+{
+ "assert_failure", ERRCODE_ASSERT_FAILURE
+},
+
+{
+ "internal_error", ERRCODE_INTERNAL_ERROR
+},
+
+{
+ "data_corrupted", ERRCODE_DATA_CORRUPTED
+},
+
+{
+ "index_corrupted", ERRCODE_INDEX_CORRUPTED
+},
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
new file mode 100644
index 0000000..6e5b990
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -0,0 +1,20 @@
+/* src/pl/plpgsql/src/plpgsql--1.0.sql */
+
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+ HANDLER plpgsql_call_handler
+ INLINE plpgsql_inline_handler
+ VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;
+
+COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
new file mode 100644
index 0000000..42e764b
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -0,0 +1,8 @@
+# plpgsql extension
+comment = 'PL/pgSQL procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/plpgsql'
+relocatable = false
+schema = pg_catalog
+superuser = true
+trusted = true
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
new file mode 100644
index 0000000..2b6cda6
--- /dev/null
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -0,0 +1,1343 @@
+/*-------------------------------------------------------------------------
+ *
+ * plpgsql.h - Definitions for the PL/pgSQL
+ * procedural language
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/pl/plpgsql/src/plpgsql.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef PLPGSQL_H
+#define PLPGSQL_H
+
+#include "access/xact.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "utils/expandedrecord.h"
+#include "utils/typcache.h"
+
+
+/**********************************************************************
+ * Definitions
+ **********************************************************************/
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("plpgsql")
+
+#undef _
+#define _(x) dgettext(TEXTDOMAIN, x)
+
+/*
+ * Compiler's namespace item types
+ */
+typedef enum PLpgSQL_nsitem_type
+{
+ PLPGSQL_NSTYPE_LABEL, /* block label */
+ PLPGSQL_NSTYPE_VAR, /* scalar variable */
+ PLPGSQL_NSTYPE_REC /* composite variable */
+} PLpgSQL_nsitem_type;
+
+/*
+ * A PLPGSQL_NSTYPE_LABEL stack entry must be one of these types
+ */
+typedef enum PLpgSQL_label_type
+{
+ PLPGSQL_LABEL_BLOCK, /* DECLARE/BEGIN block */
+ PLPGSQL_LABEL_LOOP, /* looping construct */
+ PLPGSQL_LABEL_OTHER /* anything else */
+} PLpgSQL_label_type;
+
+/*
+ * Datum array node types
+ */
+typedef enum PLpgSQL_datum_type
+{
+ PLPGSQL_DTYPE_VAR,
+ PLPGSQL_DTYPE_ROW,
+ PLPGSQL_DTYPE_REC,
+ PLPGSQL_DTYPE_RECFIELD,
+ PLPGSQL_DTYPE_PROMISE
+} PLpgSQL_datum_type;
+
+/*
+ * DTYPE_PROMISE datums have these possible ways of computing the promise
+ */
+typedef enum PLpgSQL_promise_type
+{
+ PLPGSQL_PROMISE_NONE = 0, /* not a promise, or promise satisfied */
+ PLPGSQL_PROMISE_TG_NAME,
+ PLPGSQL_PROMISE_TG_WHEN,
+ PLPGSQL_PROMISE_TG_LEVEL,
+ PLPGSQL_PROMISE_TG_OP,
+ PLPGSQL_PROMISE_TG_RELID,
+ PLPGSQL_PROMISE_TG_TABLE_NAME,
+ PLPGSQL_PROMISE_TG_TABLE_SCHEMA,
+ PLPGSQL_PROMISE_TG_NARGS,
+ PLPGSQL_PROMISE_TG_ARGV,
+ PLPGSQL_PROMISE_TG_EVENT,
+ PLPGSQL_PROMISE_TG_TAG
+} PLpgSQL_promise_type;
+
+/*
+ * Variants distinguished in PLpgSQL_type structs
+ */
+typedef enum PLpgSQL_type_type
+{
+ PLPGSQL_TTYPE_SCALAR, /* scalar types and domains */
+ PLPGSQL_TTYPE_REC, /* composite types, including RECORD */
+ PLPGSQL_TTYPE_PSEUDO /* pseudotypes */
+} PLpgSQL_type_type;
+
+/*
+ * Execution tree node types
+ */
+typedef enum PLpgSQL_stmt_type
+{
+ PLPGSQL_STMT_BLOCK,
+ PLPGSQL_STMT_ASSIGN,
+ PLPGSQL_STMT_IF,
+ PLPGSQL_STMT_CASE,
+ PLPGSQL_STMT_LOOP,
+ PLPGSQL_STMT_WHILE,
+ PLPGSQL_STMT_FORI,
+ PLPGSQL_STMT_FORS,
+ PLPGSQL_STMT_FORC,
+ PLPGSQL_STMT_FOREACH_A,
+ PLPGSQL_STMT_EXIT,
+ PLPGSQL_STMT_RETURN,
+ PLPGSQL_STMT_RETURN_NEXT,
+ PLPGSQL_STMT_RETURN_QUERY,
+ PLPGSQL_STMT_RAISE,
+ PLPGSQL_STMT_ASSERT,
+ PLPGSQL_STMT_EXECSQL,
+ PLPGSQL_STMT_DYNEXECUTE,
+ PLPGSQL_STMT_DYNFORS,
+ PLPGSQL_STMT_GETDIAG,
+ PLPGSQL_STMT_OPEN,
+ PLPGSQL_STMT_FETCH,
+ PLPGSQL_STMT_CLOSE,
+ PLPGSQL_STMT_PERFORM,
+ PLPGSQL_STMT_CALL,
+ PLPGSQL_STMT_COMMIT,
+ PLPGSQL_STMT_ROLLBACK
+} PLpgSQL_stmt_type;
+
+/*
+ * Execution node return codes
+ */
+enum
+{
+ PLPGSQL_RC_OK,
+ PLPGSQL_RC_EXIT,
+ PLPGSQL_RC_RETURN,
+ PLPGSQL_RC_CONTINUE
+};
+
+/*
+ * GET DIAGNOSTICS information items
+ */
+typedef enum PLpgSQL_getdiag_kind
+{
+ PLPGSQL_GETDIAG_ROW_COUNT,
+ PLPGSQL_GETDIAG_CONTEXT,
+ PLPGSQL_GETDIAG_ERROR_CONTEXT,
+ PLPGSQL_GETDIAG_ERROR_DETAIL,
+ PLPGSQL_GETDIAG_ERROR_HINT,
+ PLPGSQL_GETDIAG_RETURNED_SQLSTATE,
+ PLPGSQL_GETDIAG_COLUMN_NAME,
+ PLPGSQL_GETDIAG_CONSTRAINT_NAME,
+ PLPGSQL_GETDIAG_DATATYPE_NAME,
+ PLPGSQL_GETDIAG_MESSAGE_TEXT,
+ PLPGSQL_GETDIAG_TABLE_NAME,
+ PLPGSQL_GETDIAG_SCHEMA_NAME
+} PLpgSQL_getdiag_kind;
+
+/*
+ * RAISE statement options
+ */
+typedef enum PLpgSQL_raise_option_type
+{
+ PLPGSQL_RAISEOPTION_ERRCODE,
+ PLPGSQL_RAISEOPTION_MESSAGE,
+ PLPGSQL_RAISEOPTION_DETAIL,
+ PLPGSQL_RAISEOPTION_HINT,
+ PLPGSQL_RAISEOPTION_COLUMN,
+ PLPGSQL_RAISEOPTION_CONSTRAINT,
+ PLPGSQL_RAISEOPTION_DATATYPE,
+ PLPGSQL_RAISEOPTION_TABLE,
+ PLPGSQL_RAISEOPTION_SCHEMA
+} PLpgSQL_raise_option_type;
+
+/*
+ * Behavioral modes for plpgsql variable resolution
+ */
+typedef enum PLpgSQL_resolve_option
+{
+ PLPGSQL_RESOLVE_ERROR, /* throw error if ambiguous */
+ PLPGSQL_RESOLVE_VARIABLE, /* prefer plpgsql var to table column */
+ PLPGSQL_RESOLVE_COLUMN /* prefer table column to plpgsql var */
+} PLpgSQL_resolve_option;
+
+
+/**********************************************************************
+ * Node and structure definitions
+ **********************************************************************/
+
+/*
+ * Postgres data type
+ */
+typedef struct PLpgSQL_type
+{
+ char *typname; /* (simple) name of the type */
+ Oid typoid; /* OID of the data type */
+ PLpgSQL_type_type ttype; /* PLPGSQL_TTYPE_ code */
+ int16 typlen; /* stuff copied from its pg_type entry */
+ bool typbyval;
+ char typtype;
+ Oid collation; /* from pg_type, but can be overridden */
+ bool typisarray; /* is "true" array, or domain over one */
+ int32 atttypmod; /* typmod (taken from someplace else) */
+ /* Remaining fields are used only for named composite types (not RECORD) */
+ TypeName *origtypname; /* type name as written by user */
+ TypeCacheEntry *tcache; /* typcache entry for composite type */
+ uint64 tupdesc_id; /* last-seen tupdesc identifier */
+} PLpgSQL_type;
+
+/*
+ * SQL Query to plan and execute
+ */
+typedef struct PLpgSQL_expr
+{
+ char *query; /* query string, verbatim from function body */
+ RawParseMode parseMode; /* raw_parser() mode to use */
+ SPIPlanPtr plan; /* plan, or NULL if not made yet */
+ Bitmapset *paramnos; /* all dnos referenced by this query */
+
+ /* function containing this expr (not set until we first parse query) */
+ struct PLpgSQL_function *func;
+
+ /* namespace chain visible to this expr */
+ struct PLpgSQL_nsitem *ns;
+
+ /* fields for "simple expression" fast-path execution: */
+ Expr *expr_simple_expr; /* NULL means not a simple expr */
+ Oid expr_simple_type; /* result type Oid, if simple */
+ int32 expr_simple_typmod; /* result typmod, if simple */
+ bool expr_simple_mutable; /* true if simple expr is mutable */
+
+ /*
+ * These fields are used to optimize assignments to expanded-datum
+ * variables. If this expression is the source of an assignment to a
+ * simple variable, target_param holds that variable's dno; else it's -1.
+ * If we match a Param within expr_simple_expr to such a variable, that
+ * Param's address is stored in expr_rw_param; then expression code
+ * generation will allow the value for that Param to be passed read/write.
+ */
+ int target_param; /* dno of assign target, or -1 if none */
+ Param *expr_rw_param; /* read/write Param within expr, if any */
+
+ /*
+ * If the expression was ever determined to be simple, we remember its
+ * CachedPlanSource and CachedPlan here. If expr_simple_plan_lxid matches
+ * current LXID, then we hold a refcount on expr_simple_plan in the
+ * current transaction. Otherwise we need to get one before re-using it.
+ */
+ CachedPlanSource *expr_simple_plansource; /* extracted from "plan" */
+ CachedPlan *expr_simple_plan; /* extracted from "plan" */
+ LocalTransactionId expr_simple_plan_lxid;
+
+ /*
+ * if expr is simple AND prepared in current transaction,
+ * expr_simple_state and expr_simple_in_use are valid. Test validity by
+ * seeing if expr_simple_lxid matches current LXID. (If not,
+ * expr_simple_state probably points at garbage!)
+ */
+ ExprState *expr_simple_state; /* eval tree for expr_simple_expr */
+ bool expr_simple_in_use; /* true if eval tree is active */
+ LocalTransactionId expr_simple_lxid;
+} PLpgSQL_expr;
+
+/*
+ * Generic datum array item
+ *
+ * PLpgSQL_datum is the common supertype for PLpgSQL_var, PLpgSQL_row,
+ * PLpgSQL_rec, and PLpgSQL_recfield.
+ */
+typedef struct PLpgSQL_datum
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+} PLpgSQL_datum;
+
+/*
+ * Scalar or composite variable
+ *
+ * The variants PLpgSQL_var, PLpgSQL_row, and PLpgSQL_rec share these
+ * fields.
+ */
+typedef struct PLpgSQL_variable
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+ char *refname;
+ int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
+} PLpgSQL_variable;
+
+/*
+ * Scalar variable
+ *
+ * DTYPE_VAR and DTYPE_PROMISE datums both use this struct type.
+ * A PROMISE datum works exactly like a VAR datum for most purposes,
+ * but if it is read without having previously been assigned to, then
+ * a special "promised" value is computed and assigned to the datum
+ * before the read is performed. This technique avoids the overhead of
+ * computing the variable's value in cases where we expect that many
+ * functions will never read it.
+ */
+typedef struct PLpgSQL_var
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+ char *refname;
+ int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
+ /* end of PLpgSQL_variable fields */
+
+ PLpgSQL_type *datatype;
+
+ /*
+ * Variables declared as CURSOR FOR <query> are mostly like ordinary
+ * scalar variables of type refcursor, but they have these additional
+ * properties:
+ */
+ PLpgSQL_expr *cursor_explicit_expr;
+ int cursor_explicit_argrow;
+ int cursor_options;
+
+ /* Fields below here can change at runtime */
+
+ Datum value;
+ bool isnull;
+ bool freeval;
+
+ /*
+ * The promise field records which "promised" value to assign if the
+ * promise must be honored. If it's a normal variable, or the promise has
+ * been fulfilled, this is PLPGSQL_PROMISE_NONE.
+ */
+ PLpgSQL_promise_type promise;
+} PLpgSQL_var;
+
+/*
+ * Row variable - this represents one or more variables that are listed in an
+ * INTO clause, FOR-loop targetlist, cursor argument list, etc. We also use
+ * a row to represent a function's OUT parameters when there's more than one.
+ *
+ * Note that there's no way to name the row as such from PL/pgSQL code,
+ * so many functions don't need to support these.
+ *
+ * That also means that there's no real name for the row variable, so we
+ * conventionally set refname to "(unnamed row)". We could leave it NULL,
+ * but it's too convenient to be able to assume that refname is valid in
+ * all variants of PLpgSQL_variable.
+ *
+ * isconst, notnull, and default_val are unsupported (and hence
+ * always zero/null) for a row. The member variables of a row should have
+ * been checked to be writable at compile time, so isconst is correctly set
+ * to false. notnull and default_val aren't applicable.
+ */
+typedef struct PLpgSQL_row
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+ char *refname;
+ int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
+ /* end of PLpgSQL_variable fields */
+
+ /*
+ * rowtupdesc is only set up if we might need to convert the row into a
+ * composite datum, which currently only happens for OUT parameters.
+ * Otherwise it is NULL.
+ */
+ TupleDesc rowtupdesc;
+
+ int nfields;
+ char **fieldnames;
+ int *varnos;
+} PLpgSQL_row;
+
+/*
+ * Record variable (any composite type, including RECORD)
+ */
+typedef struct PLpgSQL_rec
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+ char *refname;
+ int lineno;
+ bool isconst;
+ bool notnull;
+ PLpgSQL_expr *default_val;
+ /* end of PLpgSQL_variable fields */
+
+ /*
+ * Note: for non-RECORD cases, we may from time to time re-look-up the
+ * composite type, using datatype->origtypname. That can result in
+ * changing rectypeid.
+ */
+
+ PLpgSQL_type *datatype; /* can be NULL, if rectypeid is RECORDOID */
+ Oid rectypeid; /* declared type of variable */
+ /* RECFIELDs for this record are chained together for easy access */
+ int firstfield; /* dno of first RECFIELD, or -1 if none */
+
+ /* Fields below here can change at runtime */
+
+ /* We always store record variables as "expanded" records */
+ ExpandedRecordHeader *erh;
+} PLpgSQL_rec;
+
+/*
+ * Field in record
+ */
+typedef struct PLpgSQL_recfield
+{
+ PLpgSQL_datum_type dtype;
+ int dno;
+ /* end of PLpgSQL_datum fields */
+
+ char *fieldname; /* name of field */
+ int recparentno; /* dno of parent record */
+ int nextfield; /* dno of next child, or -1 if none */
+ uint64 rectupledescid; /* record's tupledesc ID as of last lookup */
+ ExpandedRecordFieldInfo finfo; /* field's attnum and type info */
+ /* if rectupledescid == INVALID_TUPLEDESC_IDENTIFIER, finfo isn't valid */
+} PLpgSQL_recfield;
+
+/*
+ * Item in the compilers namespace tree
+ */
+typedef struct PLpgSQL_nsitem
+{
+ PLpgSQL_nsitem_type itemtype;
+
+ /*
+ * For labels, itemno is a value of enum PLpgSQL_label_type. For other
+ * itemtypes, itemno is the associated PLpgSQL_datum's dno.
+ */
+ int itemno;
+ struct PLpgSQL_nsitem *prev;
+ char name[FLEXIBLE_ARRAY_MEMBER]; /* nul-terminated string */
+} PLpgSQL_nsitem;
+
+/*
+ * Generic execution node
+ */
+typedef struct PLpgSQL_stmt
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+
+ /*
+ * Unique statement ID in this function (starting at 1; 0 is invalid/not
+ * set). This can be used by a profiler as the index for an array of
+ * per-statement metrics.
+ */
+ unsigned int stmtid;
+} PLpgSQL_stmt;
+
+/*
+ * One EXCEPTION condition name
+ */
+typedef struct PLpgSQL_condition
+{
+ int sqlerrstate; /* SQLSTATE code */
+ char *condname; /* condition name (for debugging) */
+ struct PLpgSQL_condition *next;
+} PLpgSQL_condition;
+
+/*
+ * EXCEPTION block
+ */
+typedef struct PLpgSQL_exception_block
+{
+ int sqlstate_varno;
+ int sqlerrm_varno;
+ List *exc_list; /* List of WHEN clauses */
+} PLpgSQL_exception_block;
+
+/*
+ * One EXCEPTION ... WHEN clause
+ */
+typedef struct PLpgSQL_exception
+{
+ int lineno;
+ PLpgSQL_condition *conditions;
+ List *action; /* List of statements */
+} PLpgSQL_exception;
+
+/*
+ * Block of statements
+ */
+typedef struct PLpgSQL_stmt_block
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ List *body; /* List of statements */
+ int n_initvars; /* Length of initvarnos[] */
+ int *initvarnos; /* dnos of variables declared in this block */
+ PLpgSQL_exception_block *exceptions;
+} PLpgSQL_stmt_block;
+
+/*
+ * Assign statement
+ */
+typedef struct PLpgSQL_stmt_assign
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ int varno;
+ PLpgSQL_expr *expr;
+} PLpgSQL_stmt_assign;
+
+/*
+ * PERFORM statement
+ */
+typedef struct PLpgSQL_stmt_perform
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+} PLpgSQL_stmt_perform;
+
+/*
+ * CALL statement
+ */
+typedef struct PLpgSQL_stmt_call
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+ bool is_call;
+ PLpgSQL_variable *target;
+} PLpgSQL_stmt_call;
+
+/*
+ * COMMIT statement
+ */
+typedef struct PLpgSQL_stmt_commit
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ bool chain;
+} PLpgSQL_stmt_commit;
+
+/*
+ * ROLLBACK statement
+ */
+typedef struct PLpgSQL_stmt_rollback
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ bool chain;
+} PLpgSQL_stmt_rollback;
+
+/*
+ * GET DIAGNOSTICS item
+ */
+typedef struct PLpgSQL_diag_item
+{
+ PLpgSQL_getdiag_kind kind; /* id for diagnostic value desired */
+ int target; /* where to assign it */
+} PLpgSQL_diag_item;
+
+/*
+ * GET DIAGNOSTICS statement
+ */
+typedef struct PLpgSQL_stmt_getdiag
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ bool is_stacked; /* STACKED or CURRENT diagnostics area? */
+ List *diag_items; /* List of PLpgSQL_diag_item */
+} PLpgSQL_stmt_getdiag;
+
+/*
+ * IF statement
+ */
+typedef struct PLpgSQL_stmt_if
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *cond; /* boolean expression for THEN */
+ List *then_body; /* List of statements */
+ List *elsif_list; /* List of PLpgSQL_if_elsif structs */
+ List *else_body; /* List of statements */
+} PLpgSQL_stmt_if;
+
+/*
+ * one ELSIF arm of IF statement
+ */
+typedef struct PLpgSQL_if_elsif
+{
+ int lineno;
+ PLpgSQL_expr *cond; /* boolean expression for this case */
+ List *stmts; /* List of statements */
+} PLpgSQL_if_elsif;
+
+/*
+ * CASE statement
+ */
+typedef struct PLpgSQL_stmt_case
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *t_expr; /* test expression, or NULL if none */
+ int t_varno; /* var to store test expression value into */
+ List *case_when_list; /* List of PLpgSQL_case_when structs */
+ bool have_else; /* flag needed because list could be empty */
+ List *else_stmts; /* List of statements */
+} PLpgSQL_stmt_case;
+
+/*
+ * one arm of CASE statement
+ */
+typedef struct PLpgSQL_case_when
+{
+ int lineno;
+ PLpgSQL_expr *expr; /* boolean expression for this case */
+ List *stmts; /* List of statements */
+} PLpgSQL_case_when;
+
+/*
+ * Unconditional LOOP statement
+ */
+typedef struct PLpgSQL_stmt_loop
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ List *body; /* List of statements */
+} PLpgSQL_stmt_loop;
+
+/*
+ * WHILE cond LOOP statement
+ */
+typedef struct PLpgSQL_stmt_while
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_expr *cond;
+ List *body; /* List of statements */
+} PLpgSQL_stmt_while;
+
+/*
+ * FOR statement with integer loopvar
+ */
+typedef struct PLpgSQL_stmt_fori
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_var *var;
+ PLpgSQL_expr *lower;
+ PLpgSQL_expr *upper;
+ PLpgSQL_expr *step; /* NULL means default (ie, BY 1) */
+ int reverse;
+ List *body; /* List of statements */
+} PLpgSQL_stmt_fori;
+
+/*
+ * PLpgSQL_stmt_forq represents a FOR statement running over a SQL query.
+ * It is the common supertype of PLpgSQL_stmt_fors, PLpgSQL_stmt_forc
+ * and PLpgSQL_stmt_dynfors.
+ */
+typedef struct PLpgSQL_stmt_forq
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_variable *var; /* Loop variable (record or row) */
+ List *body; /* List of statements */
+} PLpgSQL_stmt_forq;
+
+/*
+ * FOR statement running over SELECT
+ */
+typedef struct PLpgSQL_stmt_fors
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_variable *var; /* Loop variable (record or row) */
+ List *body; /* List of statements */
+ /* end of fields that must match PLpgSQL_stmt_forq */
+ PLpgSQL_expr *query;
+} PLpgSQL_stmt_fors;
+
+/*
+ * FOR statement running over cursor
+ */
+typedef struct PLpgSQL_stmt_forc
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_variable *var; /* Loop variable (record or row) */
+ List *body; /* List of statements */
+ /* end of fields that must match PLpgSQL_stmt_forq */
+ int curvar;
+ PLpgSQL_expr *argquery; /* cursor arguments if any */
+} PLpgSQL_stmt_forc;
+
+/*
+ * FOR statement running over EXECUTE
+ */
+typedef struct PLpgSQL_stmt_dynfors
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ PLpgSQL_variable *var; /* Loop variable (record or row) */
+ List *body; /* List of statements */
+ /* end of fields that must match PLpgSQL_stmt_forq */
+ PLpgSQL_expr *query;
+ List *params; /* USING expressions */
+} PLpgSQL_stmt_dynfors;
+
+/*
+ * FOREACH item in array loop
+ */
+typedef struct PLpgSQL_stmt_foreach_a
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ char *label;
+ int varno; /* loop target variable */
+ int slice; /* slice dimension, or 0 */
+ PLpgSQL_expr *expr; /* array expression */
+ List *body; /* List of statements */
+} PLpgSQL_stmt_foreach_a;
+
+/*
+ * OPEN a curvar
+ */
+typedef struct PLpgSQL_stmt_open
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ int curvar;
+ int cursor_options;
+ PLpgSQL_expr *argquery;
+ PLpgSQL_expr *query;
+ PLpgSQL_expr *dynquery;
+ List *params; /* USING expressions */
+} PLpgSQL_stmt_open;
+
+/*
+ * FETCH or MOVE statement
+ */
+typedef struct PLpgSQL_stmt_fetch
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_variable *target; /* target (record or row) */
+ int curvar; /* cursor variable to fetch from */
+ FetchDirection direction; /* fetch direction */
+ long how_many; /* count, if constant (expr is NULL) */
+ PLpgSQL_expr *expr; /* count, if expression */
+ bool is_move; /* is this a fetch or move? */
+ bool returns_multiple_rows; /* can return more than one row? */
+} PLpgSQL_stmt_fetch;
+
+/*
+ * CLOSE curvar
+ */
+typedef struct PLpgSQL_stmt_close
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ int curvar;
+} PLpgSQL_stmt_close;
+
+/*
+ * EXIT or CONTINUE statement
+ */
+typedef struct PLpgSQL_stmt_exit
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ bool is_exit; /* Is this an exit or a continue? */
+ char *label; /* NULL if it's an unlabeled EXIT/CONTINUE */
+ PLpgSQL_expr *cond;
+} PLpgSQL_stmt_exit;
+
+/*
+ * RETURN statement
+ */
+typedef struct PLpgSQL_stmt_return
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+ int retvarno;
+} PLpgSQL_stmt_return;
+
+/*
+ * RETURN NEXT statement
+ */
+typedef struct PLpgSQL_stmt_return_next
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *expr;
+ int retvarno;
+} PLpgSQL_stmt_return_next;
+
+/*
+ * RETURN QUERY statement
+ */
+typedef struct PLpgSQL_stmt_return_query
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *query; /* if static query */
+ PLpgSQL_expr *dynquery; /* if dynamic query (RETURN QUERY EXECUTE) */
+ List *params; /* USING arguments for dynamic query */
+} PLpgSQL_stmt_return_query;
+
+/*
+ * RAISE statement
+ */
+typedef struct PLpgSQL_stmt_raise
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ int elog_level;
+ char *condname; /* condition name, SQLSTATE, or NULL */
+ char *message; /* old-style message format literal, or NULL */
+ List *params; /* list of expressions for old-style message */
+ List *options; /* list of PLpgSQL_raise_option */
+} PLpgSQL_stmt_raise;
+
+/*
+ * RAISE statement option
+ */
+typedef struct PLpgSQL_raise_option
+{
+ PLpgSQL_raise_option_type opt_type;
+ PLpgSQL_expr *expr;
+} PLpgSQL_raise_option;
+
+/*
+ * ASSERT statement
+ */
+typedef struct PLpgSQL_stmt_assert
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *cond;
+ PLpgSQL_expr *message;
+} PLpgSQL_stmt_assert;
+
+/*
+ * Generic SQL statement to execute
+ */
+typedef struct PLpgSQL_stmt_execsql
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *sqlstmt;
+ bool mod_stmt; /* is the stmt INSERT/UPDATE/DELETE/MERGE? */
+ bool mod_stmt_set; /* is mod_stmt valid yet? */
+ bool into; /* INTO supplied? */
+ bool strict; /* INTO STRICT flag */
+ PLpgSQL_variable *target; /* INTO target (record or row) */
+} PLpgSQL_stmt_execsql;
+
+/*
+ * Dynamic SQL string to execute
+ */
+typedef struct PLpgSQL_stmt_dynexecute
+{
+ PLpgSQL_stmt_type cmd_type;
+ int lineno;
+ unsigned int stmtid;
+ PLpgSQL_expr *query; /* string expression */
+ bool into; /* INTO supplied? */
+ bool strict; /* INTO STRICT flag */
+ PLpgSQL_variable *target; /* INTO target (record or row) */
+ List *params; /* USING expressions */
+} PLpgSQL_stmt_dynexecute;
+
+/*
+ * Hash lookup key for functions
+ */
+typedef struct PLpgSQL_func_hashkey
+{
+ Oid funcOid;
+
+ bool isTrigger; /* true if called as a DML trigger */
+ bool isEventTrigger; /* true if called as an event trigger */
+
+ /* be careful that pad bytes in this struct get zeroed! */
+
+ /*
+ * For a trigger function, the OID of the trigger is part of the hash key
+ * --- we want to compile the trigger function separately for each trigger
+ * it is used with, in case the rowtype or transition table names are
+ * different. Zero if not called as a DML trigger.
+ */
+ Oid trigOid;
+
+ /*
+ * We must include the input collation as part of the hash key too,
+ * because we have to generate different plans (with different Param
+ * collations) for different collation settings.
+ */
+ Oid inputCollation;
+
+ /*
+ * We include actual argument types in the hash key to support polymorphic
+ * PLpgSQL functions. Be careful that extra positions are zeroed!
+ */
+ Oid argtypes[FUNC_MAX_ARGS];
+} PLpgSQL_func_hashkey;
+
+/*
+ * Trigger type
+ */
+typedef enum PLpgSQL_trigtype
+{
+ PLPGSQL_DML_TRIGGER,
+ PLPGSQL_EVENT_TRIGGER,
+ PLPGSQL_NOT_TRIGGER
+} PLpgSQL_trigtype;
+
+/*
+ * Complete compiled function
+ */
+typedef struct PLpgSQL_function
+{
+ char *fn_signature;
+ Oid fn_oid;
+ TransactionId fn_xmin;
+ ItemPointerData fn_tid;
+ PLpgSQL_trigtype fn_is_trigger;
+ Oid fn_input_collation;
+ PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
+ MemoryContext fn_cxt;
+
+ Oid fn_rettype;
+ int fn_rettyplen;
+ bool fn_retbyval;
+ bool fn_retistuple;
+ bool fn_retisdomain;
+ bool fn_retset;
+ bool fn_readonly;
+ char fn_prokind;
+
+ int fn_nargs;
+ int fn_argvarnos[FUNC_MAX_ARGS];
+ int out_param_varno;
+ int found_varno;
+ int new_varno;
+ int old_varno;
+
+ PLpgSQL_resolve_option resolve_option;
+
+ bool print_strict_params;
+
+ /* extra checks */
+ int extra_warnings;
+ int extra_errors;
+
+ /* the datums representing the function's local variables */
+ int ndatums;
+ PLpgSQL_datum **datums;
+ Size copiable_size; /* space for locally instantiated datums */
+
+ /* function body parsetree */
+ PLpgSQL_stmt_block *action;
+
+ /* data derived while parsing body */
+ unsigned int nstatements; /* counter for assigning stmtids */
+ bool requires_procedure_resowner; /* contains CALL or DO? */
+
+ /* these fields change when the function is used */
+ struct PLpgSQL_execstate *cur_estate;
+ unsigned long use_count;
+} PLpgSQL_function;
+
+/*
+ * Runtime execution data
+ */
+typedef struct PLpgSQL_execstate
+{
+ PLpgSQL_function *func; /* function being executed */
+
+ TriggerData *trigdata; /* if regular trigger, data about firing */
+ EventTriggerData *evtrigdata; /* if event trigger, data about firing */
+
+ Datum retval;
+ bool retisnull;
+ Oid rettype; /* type of current retval */
+
+ Oid fn_rettype; /* info about declared function rettype */
+ bool retistuple;
+ bool retisset;
+
+ bool readonly_func;
+ bool atomic;
+
+ char *exitlabel; /* the "target" label of the current EXIT or
+ * CONTINUE stmt, if any */
+ ErrorData *cur_error; /* current exception handler's error */
+
+ Tuplestorestate *tuple_store; /* SRFs accumulate results here */
+ TupleDesc tuple_store_desc; /* descriptor for tuples in tuple_store */
+ MemoryContext tuple_store_cxt;
+ ResourceOwner tuple_store_owner;
+ ReturnSetInfo *rsi;
+
+ int found_varno;
+
+ /*
+ * The datums representing the function's local variables. Some of these
+ * are local storage in this execstate, but some just point to the shared
+ * copy belonging to the PLpgSQL_function, depending on whether or not we
+ * need any per-execution state for the datum's dtype.
+ */
+ int ndatums;
+ PLpgSQL_datum **datums;
+ /* context containing variable values (same as func's SPI_proc context) */
+ MemoryContext datum_context;
+
+ /*
+ * paramLI is what we use to pass local variable values to the executor.
+ * It does not have a ParamExternData array; we just dynamically
+ * instantiate parameter data as needed. By convention, PARAM_EXTERN
+ * Params have paramid equal to the dno of the referenced local variable.
+ */
+ ParamListInfo paramLI;
+
+ /* EState and resowner to use for "simple" expression evaluation */
+ EState *simple_eval_estate;
+ ResourceOwner simple_eval_resowner;
+
+ /* if running nonatomic procedure or DO block, resowner to use for CALL */
+ ResourceOwner procedure_resowner;
+
+ /* lookup table to use for executing type casts */
+ HTAB *cast_hash;
+ MemoryContext cast_hash_context; /* not used; now always NULL */
+
+ /* memory context for statement-lifespan temporary values */
+ MemoryContext stmt_mcontext; /* current stmt context, or NULL if none */
+ MemoryContext stmt_mcontext_parent; /* parent of current context */
+
+ /* temporary state for results from evaluation of query or expr */
+ SPITupleTable *eval_tuptable;
+ uint64 eval_processed;
+ ExprContext *eval_econtext; /* for executing simple expressions */
+
+ /* status information for error context reporting */
+ PLpgSQL_stmt *err_stmt; /* current stmt */
+ PLpgSQL_variable *err_var; /* current variable, if in a DECLARE section */
+ const char *err_text; /* additional state info */
+
+ void *plugin_info; /* reserved for use by optional plugin */
+} PLpgSQL_execstate;
+
+/*
+ * A PLpgSQL_plugin structure represents an instrumentation plugin.
+ * To instrument PL/pgSQL, a plugin library must access the rendezvous
+ * variable "PLpgSQL_plugin" and set it to point to a PLpgSQL_plugin struct.
+ * Typically the struct could just be static data in the plugin library.
+ * We expect that a plugin would do this at library load time (_PG_init()).
+ *
+ * This structure is basically a collection of function pointers --- at
+ * various interesting points in pl_exec.c, we call these functions
+ * (if the pointers are non-NULL) to give the plugin a chance to watch
+ * what we are doing.
+ *
+ * func_setup is called when we start a function, before we've initialized
+ * the local variables defined by the function.
+ *
+ * func_beg is called when we start a function, after we've initialized
+ * the local variables.
+ *
+ * func_end is called at the end of a function.
+ *
+ * stmt_beg and stmt_end are called before and after (respectively) each
+ * statement.
+ *
+ * Also, immediately before any call to func_setup, PL/pgSQL fills in the
+ * remaining fields with pointers to some of its own functions, allowing the
+ * plugin to invoke those functions conveniently. The exposed functions are:
+ * plpgsql_exec_error_callback
+ * exec_assign_expr
+ * exec_assign_value
+ * exec_eval_datum
+ * exec_cast_value
+ * (plpgsql_exec_error_callback is not actually meant to be called by the
+ * plugin, but rather to allow it to identify PL/pgSQL error context stack
+ * frames. The others are useful for debugger-like plugins to examine and
+ * set variables.)
+ */
+typedef struct PLpgSQL_plugin
+{
+ /* Function pointers set up by the plugin */
+ void (*func_setup) (PLpgSQL_execstate *estate, PLpgSQL_function *func);
+ void (*func_beg) (PLpgSQL_execstate *estate, PLpgSQL_function *func);
+ void (*func_end) (PLpgSQL_execstate *estate, PLpgSQL_function *func);
+ void (*stmt_beg) (PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
+ void (*stmt_end) (PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt);
+
+ /* Function pointers set by PL/pgSQL itself */
+ void (*error_callback) (void *arg);
+ void (*assign_expr) (PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ PLpgSQL_expr *expr);
+ void (*assign_value) (PLpgSQL_execstate *estate,
+ PLpgSQL_datum *target,
+ Datum value, bool isNull,
+ Oid valtype, int32 valtypmod);
+ void (*eval_datum) (PLpgSQL_execstate *estate, PLpgSQL_datum *datum,
+ Oid *typeId, int32 *typetypmod,
+ Datum *value, bool *isnull);
+ Datum (*cast_value) (PLpgSQL_execstate *estate,
+ Datum value, bool *isnull,
+ Oid valtype, int32 valtypmod,
+ Oid reqtype, int32 reqtypmod);
+} PLpgSQL_plugin;
+
+/*
+ * Struct types used during parsing
+ */
+
+typedef struct PLword
+{
+ char *ident; /* palloc'd converted identifier */
+ bool quoted; /* Was it double-quoted? */
+} PLword;
+
+typedef struct PLcword
+{
+ List *idents; /* composite identifiers (list of String) */
+} PLcword;
+
+typedef struct PLwdatum
+{
+ PLpgSQL_datum *datum; /* referenced variable */
+ char *ident; /* valid if simple name */
+ bool quoted;
+ List *idents; /* valid if composite name */
+} PLwdatum;
+
+/**********************************************************************
+ * Global variable declarations
+ **********************************************************************/
+
+typedef enum
+{
+ IDENTIFIER_LOOKUP_NORMAL, /* normal processing of var names */
+ IDENTIFIER_LOOKUP_DECLARE, /* In DECLARE --- don't look up names */
+ IDENTIFIER_LOOKUP_EXPR /* In SQL expression --- special case */
+} IdentifierLookup;
+
+extern IdentifierLookup plpgsql_IdentifierLookup;
+
+extern int plpgsql_variable_conflict;
+
+extern bool plpgsql_print_strict_params;
+
+extern bool plpgsql_check_asserts;
+
+/* extra compile-time and run-time checks */
+#define PLPGSQL_XCHECK_NONE 0
+#define PLPGSQL_XCHECK_SHADOWVAR (1 << 1)
+#define PLPGSQL_XCHECK_TOOMANYROWS (1 << 2)
+#define PLPGSQL_XCHECK_STRICTMULTIASSIGNMENT (1 << 3)
+#define PLPGSQL_XCHECK_ALL ((int) ~0)
+
+extern int plpgsql_extra_warnings;
+extern int plpgsql_extra_errors;
+
+extern bool plpgsql_check_syntax;
+extern bool plpgsql_DumpExecTree;
+
+extern PLpgSQL_stmt_block *plpgsql_parse_result;
+
+extern int plpgsql_nDatums;
+extern PLpgSQL_datum **plpgsql_Datums;
+
+extern char *plpgsql_error_funcname;
+
+extern PLpgSQL_function *plpgsql_curr_compile;
+extern MemoryContext plpgsql_compile_tmp_cxt;
+
+extern PLpgSQL_plugin **plpgsql_plugin_ptr;
+
+/**********************************************************************
+ * Function declarations
+ **********************************************************************/
+
+/*
+ * Functions in pl_comp.c
+ */
+extern PLpgSQL_function *plpgsql_compile(FunctionCallInfo fcinfo,
+ bool forValidator);
+extern PLpgSQL_function *plpgsql_compile_inline(char *proc_source);
+extern void plpgsql_parser_setup(struct ParseState *pstate,
+ PLpgSQL_expr *expr);
+extern bool plpgsql_parse_word(char *word1, const char *yytxt, bool lookup,
+ PLwdatum *wdatum, PLword *word);
+extern bool plpgsql_parse_dblword(char *word1, char *word2,
+ PLwdatum *wdatum, PLcword *cword);
+extern bool plpgsql_parse_tripword(char *word1, char *word2, char *word3,
+ PLwdatum *wdatum, PLcword *cword);
+extern PLpgSQL_type *plpgsql_parse_wordtype(char *ident);
+extern PLpgSQL_type *plpgsql_parse_cwordtype(List *idents);
+extern PLpgSQL_type *plpgsql_parse_wordrowtype(char *ident);
+extern PLpgSQL_type *plpgsql_parse_cwordrowtype(List *idents);
+extern PLpgSQL_type *plpgsql_build_datatype(Oid typeOid, int32 typmod,
+ Oid collation,
+ TypeName *origtypname);
+extern PLpgSQL_variable *plpgsql_build_variable(const char *refname, int lineno,
+ PLpgSQL_type *dtype,
+ bool add2namespace);
+extern PLpgSQL_rec *plpgsql_build_record(const char *refname, int lineno,
+ PLpgSQL_type *dtype, Oid rectypeid,
+ bool add2namespace);
+extern PLpgSQL_recfield *plpgsql_build_recfield(PLpgSQL_rec *rec,
+ const char *fldname);
+extern int plpgsql_recognize_err_condition(const char *condname,
+ bool allow_sqlstate);
+extern PLpgSQL_condition *plpgsql_parse_err_condition(char *condname);
+extern void plpgsql_adddatum(PLpgSQL_datum *newdatum);
+extern int plpgsql_add_initdatums(int **varnos);
+extern void plpgsql_HashTableInit(void);
+
+/*
+ * Functions in pl_handler.c
+ */
+extern void _PG_init(void);
+
+/*
+ * Functions in pl_exec.c
+ */
+extern Datum plpgsql_exec_function(PLpgSQL_function *func,
+ FunctionCallInfo fcinfo,
+ EState *simple_eval_estate,
+ ResourceOwner simple_eval_resowner,
+ ResourceOwner procedure_resowner,
+ bool atomic);
+extern HeapTuple plpgsql_exec_trigger(PLpgSQL_function *func,
+ TriggerData *trigdata);
+extern void plpgsql_exec_event_trigger(PLpgSQL_function *func,
+ EventTriggerData *trigdata);
+extern void plpgsql_xact_cb(XactEvent event, void *arg);
+extern void plpgsql_subxact_cb(SubXactEvent event, SubTransactionId mySubid,
+ SubTransactionId parentSubid, void *arg);
+extern Oid plpgsql_exec_get_datum_type(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum);
+extern void plpgsql_exec_get_datum_type_info(PLpgSQL_execstate *estate,
+ PLpgSQL_datum *datum,
+ Oid *typeId, int32 *typMod,
+ Oid *collation);
+
+/*
+ * Functions for namespace handling in pl_funcs.c
+ */
+extern void plpgsql_ns_init(void);
+extern void plpgsql_ns_push(const char *label,
+ PLpgSQL_label_type label_type);
+extern void plpgsql_ns_pop(void);
+extern PLpgSQL_nsitem *plpgsql_ns_top(void);
+extern void plpgsql_ns_additem(PLpgSQL_nsitem_type itemtype, int itemno, const char *name);
+extern PLpgSQL_nsitem *plpgsql_ns_lookup(PLpgSQL_nsitem *ns_cur, bool localmode,
+ const char *name1, const char *name2,
+ const char *name3, int *names_used);
+extern PLpgSQL_nsitem *plpgsql_ns_lookup_label(PLpgSQL_nsitem *ns_cur,
+ const char *name);
+extern PLpgSQL_nsitem *plpgsql_ns_find_nearest_loop(PLpgSQL_nsitem *ns_cur);
+
+/*
+ * Other functions in pl_funcs.c
+ */
+extern const char *plpgsql_stmt_typename(PLpgSQL_stmt *stmt);
+extern const char *plpgsql_getdiag_kindname(PLpgSQL_getdiag_kind kind);
+extern void plpgsql_free_function_memory(PLpgSQL_function *func);
+extern void plpgsql_dumptree(PLpgSQL_function *func);
+
+/*
+ * Scanner functions in pl_scanner.c
+ */
+extern int plpgsql_base_yylex(void);
+extern int plpgsql_yylex(void);
+extern void plpgsql_push_back_token(int token);
+extern bool plpgsql_token_is_unreserved_keyword(int token);
+extern void plpgsql_append_source_text(StringInfo buf,
+ int startlocation, int endlocation);
+extern int plpgsql_peek(void);
+extern void plpgsql_peek2(int *tok1_p, int *tok2_p, int *tok1_loc,
+ int *tok2_loc);
+extern int plpgsql_scanner_errposition(int location);
+extern void plpgsql_yyerror(const char *message) pg_attribute_noreturn();
+extern int plpgsql_location_to_lineno(int location);
+extern int plpgsql_latest_lineno(void);
+extern void plpgsql_scanner_init(const char *str);
+extern void plpgsql_scanner_finish(void);
+
+/*
+ * Externs in gram.y
+ */
+extern int plpgsql_yyparse(void);
+
+#endif /* PLPGSQL_H */
diff --git a/src/pl/plpgsql/src/po/cs.po b/src/pl/plpgsql/src/po/cs.po
new file mode 100644
index 0000000..d70e16a
--- /dev/null
+++ b/src/pl/plpgsql/src/po/cs.po
@@ -0,0 +1,911 @@
+# Czech message translation file for plpgsql
+# Copyright (C) 2012 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Tomáš Vondra <tv@fuzzy.cz>, 2012, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql-cs (PostgreSQL 9.3)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2020-10-31 16:09+0000\n"
+"PO-Revision-Date: 2020-10-31 20:56+0100\n"
+"Last-Translator: Tomas Vondra <tv@fuzzy.cz>\n"
+"Language-Team: Czech <info@cspug.cx>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Poedit 2.4.1\n"
+
+#: pl_comp.c:436 pl_handler.c:471
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL funkce nemohou přijímat typ %s"
+
+#: pl_comp.c:526
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "nelze určit skutečný návratový typ polymorfní funkce \"%s\""
+
+#: pl_comp.c:556
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funkce pro obsluhu triggerů mohou být volané pouze prostřednictvím triggerů"
+
+#: pl_comp.c:560 pl_handler.c:455
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL funkce nemohou vracet typ %s"
+
+#: pl_comp.c:600
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "funkce pro obsluhu triggeru nesmí mít argumenty"
+
+#: pl_comp.c:601
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Parametry triggeru jsou přístupné prostřednictvím proměnných TG_NARGS a TG_ARGV."
+
+#: pl_comp.c:734
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "funkce pro obsluhu event triggeru nesmí mít argumenty"
+
+#: pl_comp.c:997
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "překlad PL/pgSQL funkce \"%s\" poblíž řádku %d"
+
+#: pl_comp.c:1020
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "identifikátor parametru \"%s\" není unikátní"
+
+#: pl_comp.c:1132
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "nejednoznačný odkaz na sloupec \"%s\""
+
+# přeložil bych spíš asi jako "Identifikátor může odkazovat na proměnnou PL/pgSQL nebo na sloupec v tabulce."
+# ok
+#: pl_comp.c:1134
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Identifikátor může odkazovat na proměnnou PL/pgSQL nebo na sloupec v tabulce."
+
+#: pl_comp.c:1317 pl_exec.c:5218 pl_exec.c:5583 pl_exec.c:5670 pl_exec.c:5761
+#: pl_exec.c:6749
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "záznam \"%s\" nemá položku \"%s\""
+
+#: pl_comp.c:1793
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "relace \"%s\" neexistuje"
+
+# asi spíš jako "proměnná \"%s\" má pseudo-typ \"%s\" (slovo "obsahuje" si vykládám spíš jako že je součástí)
+# podivej se do zdrojaku, hlasi, kdyz zkusis deklarovat promennou s pseudotype - takze jsem to prelozil
+# jeste trochu jinak
+#: pl_comp.c:1891
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "proměnná \"%s\" je deklarována jako pseudo-typ \"%s\""
+
+# spíš asi "je jenom obálka (shell)",
+# ok
+#: pl_comp.c:2080
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "typ \"%s\" je jen obálkou (shell)"
+
+#: pl_comp.c:2162 pl_exec.c:7050
+#, c-format
+msgid "type %s is not composite"
+msgstr "typ %s není kompozitní"
+
+#: pl_comp.c:2210 pl_comp.c:2263
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "nedefinovaná výjimka \"%s\""
+
+#: pl_comp.c:2484
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "nelze určit skutečný typ argumentu polymorfní funkce \"%s\""
+
+#: pl_exec.c:498 pl_exec.c:935 pl_exec.c:1173
+msgid "during initialization of execution state"
+msgstr "během inicializace proměnné execution state"
+
+#: pl_exec.c:504
+msgid "while storing call arguments into local variables"
+msgstr "během ukládání parametrů funkce do lokálních proměnných"
+
+#: pl_exec.c:592 pl_exec.c:1008
+msgid "during function entry"
+msgstr "během vstupu do funkce"
+
+#: pl_exec.c:617
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "funkce skončila, aniž by byl proveden příkaz RETURN"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "během konverze vracené hodnoty do návratového typu funkce"
+
+#: pl_exec.c:637 pl_exec.c:3653
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "funkce vracející tabulku (set-valued) byla zavolána z kontextu, který neumožňuje přijetí tabulky"
+
+#: pl_exec.c:763 pl_exec.c:1037 pl_exec.c:1198
+msgid "during function exit"
+msgstr "během ukončování funkce"
+
+#: pl_exec.c:818 pl_exec.c:882 pl_exec.c:3498
+msgid "returned record type does not match expected record type"
+msgstr "vracenou hodnotu typu record nelze konvertovat do očekávaného typu record"
+
+#: pl_exec.c:1033 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "funkce obsluhy triggeru skončila, aniž by byl proveden příkaz RETURN"
+
+#: pl_exec.c:1042
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "funkce obsluhy triggeru nemůže vrátit tabulku"
+
+#: pl_exec.c:1081 pl_exec.c:1109
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "struktura vrácené hodnoty neodpovídá struktuře tabulky svázané s triggerem"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1244
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL funkce %s řádek %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1255
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL funkce %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL funkce %s řádek %d na %s"
+
+#: pl_exec.c:1269
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL funkce %s"
+
+#: pl_exec.c:1607
+msgid "during statement block local variable initialization"
+msgstr "během inicializace lokálních proměnných bloku"
+
+#: pl_exec.c:1705
+msgid "during statement block entry"
+msgstr "během zahájení bloku"
+
+#: pl_exec.c:1737
+msgid "during statement block exit"
+msgstr "během ukončování bloku"
+
+#: pl_exec.c:1775
+msgid "during exception cleanup"
+msgstr "během čištění po zachycení výjimky"
+
+#: pl_exec.c:2304
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "parametr \"%s\" procedury je výstupní argument ale odpovídající argument není zapisovatelný"
+
+#: pl_exec.c:2309
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "parametr %d procedury je výstupní argument ale odpovídající argument není zapisovatelný"
+
+#: pl_exec.c:2437
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS nelze použít mimo obsluhu výjimky"
+
+#: pl_exec.c:2637
+#, c-format
+msgid "case not found"
+msgstr "varianta nenalezena"
+
+#: pl_exec.c:2638
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE příkazu chybí část ELSE."
+
+#: pl_exec.c:2731
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "spodní limit příkazu FOR nesmí být nullL"
+
+#: pl_exec.c:2747
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "horní limit příkazu FOR nesmí být null"
+
+#: pl_exec.c:2765
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "BY hodnota pro FOR cyklus nesmí být null"
+
+#: pl_exec.c:2771
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "BY hodnota pro FOR cyklus musí být větší než nula"
+
+#: pl_exec.c:2905 pl_exec.c:4632
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "kurzor \"%s\" se již používá"
+
+#: pl_exec.c:2928 pl_exec.c:4697
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "argumenty pro kurzor bez argumentů"
+
+#: pl_exec.c:2947 pl_exec.c:4716
+#, c-format
+msgid "arguments required for cursor"
+msgstr "kurzor vyžaduje argumenty"
+
+#: pl_exec.c:3034
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "výraz ve FOREACH nesmí být null"
+
+# výrazu/příkazu
+#: pl_exec.c:3049
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "výraz ve FOREACH musí vracet pole, nikoliv typ %s"
+
+#: pl_exec.c:3066
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "dimenze podpole (%d) je mimo validní rozsah 0..%d"
+
+#: pl_exec.c:3093
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE proměnná cyklu musí být typu pole"
+
+#: pl_exec.c:3097
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH proměnná cyklu nesmí být typu pole"
+
+#: pl_exec.c:3259 pl_exec.c:3316 pl_exec.c:3491
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "z funkce vracející kompozitní typ nelze vracet jednoduchý datový typ"
+
+#: pl_exec.c:3355 pl_gram.y:3309
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "RETURN NEXT nelze použít ve funkci, která nevrací tabulku"
+
+#: pl_exec.c:3396 pl_exec.c:3528
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "chybný návratový typ v RETURN NEXT"
+
+#: pl_exec.c:3434 pl_exec.c:3455
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "obsah parametru příkazu RETURN NEXT nelze převést na návratový typ funkce"
+
+#: pl_exec.c:3547
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT musí mít parametr"
+
+#: pl_exec.c:3573 pl_gram.y:3373
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "uvnitř funkce, která nevrací tabulku, nelze použít RETURN QUERY"
+
+#: pl_exec.c:3597
+msgid "structure of query does not match function result type"
+msgstr "struktura dotazu neodpovídá návratovému typu funkce"
+
+#: pl_exec.c:3681 pl_exec.c:3819
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE volba již zadána: %s"
+
+#: pl_exec.c:3715
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE bez parametrů nesmí být použito mimo obsluhu výjimky"
+
+#: pl_exec.c:3809
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "volba příkazu RAISE nesmí být null"
+
+#: pl_exec.c:3879
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3934
+#, c-format
+msgid "assertion failed"
+msgstr "assertion selhalo"
+
+#: pl_exec.c:4281 pl_exec.c:4471
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "v PL/pgSQL nelze použít COPY to/from klient"
+
+#: pl_exec.c:4287
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "nepodporovaný transakční příkaz v PL/pgSQL"
+
+# "nevrací" má trochu jiný význam než "nemůže vracet"
+#: pl_exec.c:4310 pl_exec.c:4500
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO je použito v příkazu, který nevrací data"
+
+#: pl_exec.c:4333 pl_exec.c:4523
+#, c-format
+msgid "query returned no rows"
+msgstr "dotaz nevrátil žádný řádek"
+
+#: pl_exec.c:4355 pl_exec.c:4542
+#, c-format
+msgid "query returned more than one row"
+msgstr "dotaz vrátil více než jeden řádek"
+
+#: pl_exec.c:4357
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Ujistěte se že dotaz vrací jediný řádek, nebo použijte LIMIT 1."
+
+#: pl_exec.c:4373
+#, c-format
+msgid "query has no destination for result data"
+msgstr "chybí cíl pro výsledek dotazu"
+
+#: pl_exec.c:4374
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Pokud nechcete použít výsledek SELECTu, použijte PERFORM."
+
+# generující? spíš asi "obsahující" nebo jenom "s dynamickým dotazem"
+# ok
+#: pl_exec.c:4407 pl_exec.c:8729
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "textový argument s dynamickým dotazem příkazu EXECUTE je null"
+
+#: pl_exec.c:4463
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE příkazu SELECT ... INTO není implementováno"
+
+#: pl_exec.c:4464
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Možná chcete použít EXECUTE ... INTO nebo EXECUTE CREATE TABLE ... AS."
+
+#: pl_exec.c:4477
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE transakčního příkazu není implementováno"
+
+# myslí se tím proměnná která se předává kurzoru nebo samotný kurzor? Pokud kurzor, tak asi spíš kurzorová proměnná.
+# ok, i kdyz v tom necitim rozdil
+#: pl_exec.c:4778 pl_exec.c:4866
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "kurzorová proměnná \"%s\" je null"
+
+#: pl_exec.c:4789 pl_exec.c:4877
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "kurzor \"%s\" neexistuje"
+
+#: pl_exec.c:4802
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "relativní nebo absolutní pozice kurzoru je null"
+
+#: pl_exec.c:5068 pl_exec.c:5163
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "proměnné \"%s\" deklarované jako NOT NULL nelze přiřadit null"
+
+# hodnotU
+#: pl_exec.c:5144
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "proměnné složeného typu nelze přiřadit jinou než složenou hodnot"
+
+#: pl_exec.c:5176
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "proměnné typu record nelze přiřadit jinou než slouženou hodnotu"
+
+#: pl_exec.c:5227
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "nelze přiřazovat do systémového sloupce \"%s\""
+
+#: pl_exec.c:5291
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "počet rozměrů pole (%d) překračuje povolené maximum (%d)"
+
+#: pl_exec.c:5323
+#, c-format
+msgid "subscripted object is not an array"
+msgstr "indexovaná proměnná není pole"
+
+#: pl_exec.c:5361
+#, c-format
+msgid "array subscript in assignment must not be null"
+msgstr "index pole v přířazovacím příkazu nesmí být null"
+
+#: pl_exec.c:5868
+#, c-format
+msgid "query \"%s\" did not return data"
+msgstr "dotaz \"%s\" nevrátil žádná data"
+
+#: pl_exec.c:5876
+#, c-format
+msgid "query \"%s\" returned %d column"
+msgid_plural "query \"%s\" returned %d columns"
+msgstr[0] "dotaz \"%s\" vrátil %d sloupec"
+msgstr[1] "dotaz \"%s\" vrátil %d sloupce"
+msgstr[2] "dotaz \"%s\" vrátil %d sloupců"
+
+#: pl_exec.c:5904
+#, c-format
+msgid "query \"%s\" returned more than one row"
+msgstr "dotaz \"%s\" vrátil více než jeden řádek"
+
+#: pl_exec.c:5967
+#, c-format
+msgid "query \"%s\" is not a SELECT"
+msgstr "dotaz \"%s\" není SELECT"
+
+#: pl_exec.c:6763 pl_exec.c:6803 pl_exec.c:6843
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "typ parametru %d (%s) neodpovídá typu při přípravě plánu (%s)"
+
+#: pl_exec.c:7254 pl_exec.c:7288 pl_exec.c:7362 pl_exec.c:7388
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "počet zdrojových a cílových položek v přiřazení neodpovídá"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7256 pl_exec.c:7290 pl_exec.c:7364 pl_exec.c:7390
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s kontrola %s je aktivní."
+
+#: pl_exec.c:7260 pl_exec.c:7294 pl_exec.c:7368 pl_exec.c:7394
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Ujistěte se že dotaz vrací přesný seznam sloupců."
+
+#: pl_exec.c:7781
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "proměnné \"%s\" typu record ještě nebyla přiřazena hodnota"
+
+# tečka na konci
+# ok
+#: pl_exec.c:7782
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Proměnná typu record, které ještě nebyla přiřazena hodnota, nemá definovanou strukturu."
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "blok"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "přiřazení"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR s celočíselnou řídící proměnnou"
+
+# možná spíš "FOR nad SELECT dotazem
+# zkusim jeste neco jineho"
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR nad SELECT(em)"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR nad kurzorem"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH nad polem"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL příkaz"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR nad dynamickým výběrem (FOR over EXECUTE)"
+
+#: pl_gram.y:489
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "návěstí bloku musí být umístěno před klíčové slovo DECLARE, nikoliv za"
+
+#: pl_gram.y:509
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "typ %s nepodporuje collations"
+
+#: pl_gram.y:528
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "proměnná \"%s\" musí mít implicitní hodnotu, protože je deklarována jako NOT NULL"
+
+#: pl_gram.y:675 pl_gram.y:690 pl_gram.y:716
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "proměnná \"%s\" neexistuje"
+
+#: pl_gram.y:734 pl_gram.y:762
+msgid "duplicate declaration"
+msgstr "duplicitní deklarace"
+
+#: pl_gram.y:745 pl_gram.y:773
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "proměnná \"%s\" zastiňuje dříve definovanou proměnnou"
+
+#: pl_gram.y:993
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "diagnostická položka %s není dostupná v příkazu GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1011
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "diagnostická položka %s není dostupná v příkazu GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1106
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "neznámá položka příkazu GET DIAGNOSTICS"
+
+#: pl_gram.y:1116 pl_gram.y:3553
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" není skalární proměnná"
+
+#: pl_gram.y:1370 pl_gram.y:1567
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "řídící proměnná cyklu nad řádky musí být typu record nebo seznam skalárních proměnných"
+
+# asi by tam mělo být i to FOR, neplatí to pro všechny cykly
+# ok
+#: pl_gram.y:1405
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "cyklus FOR nad kurzorem musí mít pouze jednu cílovou proměnnou"
+
+#: pl_gram.y:1412
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "cyklus FOR nad kurzorem musí použít vázanou proměnnou kurzoru"
+
+#: pl_gram.y:1499
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "celočiselný cyklus FOR musí mít pouze jednu cílovou proměnnou"
+
+#: pl_gram.y:1537
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "nelze zadat atribut REVERSE v případě cyklu FOR nad dotazem"
+
+#: pl_gram.y:1670
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "řídící proměnná(é) cyklu FOREACH musí být existující proměnná, případně seznam existujících proměnných"
+
+#: pl_gram.y:1712
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "žádné návěstí \"%s\" není přiřazeno k žádnému bloku nebo cyklu obsahujícímu tento příkaz"
+
+#: pl_gram.y:1720
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "návěstí bloku \"%s\" nelze použít v CONTINUE"
+
+#: pl_gram.y:1735
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT nemůže byt použito mimo tělo cyklu, pokud nemá návěstí"
+
+#: pl_gram.y:1736
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE nemůže byt použito mimo tělo cyklu"
+
+#: pl_gram.y:1760 pl_gram.y:1798 pl_gram.y:1846 pl_gram.y:2998 pl_gram.y:3083
+#: pl_gram.y:3194 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "neočekávaný konec definice funkce"
+
+#: pl_gram.y:1866 pl_gram.y:1890 pl_gram.y:1906 pl_gram.y:1912 pl_gram.y:2031
+#: pl_gram.y:2039 pl_gram.y:2053 pl_gram.y:2148 pl_gram.y:2399 pl_gram.y:2493
+#: pl_gram.y:2652 pl_gram.y:3799 pl_gram.y:3860 pl_gram.y:3938
+msgid "syntax error"
+msgstr "syntaktická chyba"
+
+#: pl_gram.y:1894 pl_gram.y:1896 pl_gram.y:2403 pl_gram.y:2405
+msgid "invalid SQLSTATE code"
+msgstr "nevalidní SQLSTATE kód"
+
+#: pl_gram.y:2096
+msgid "syntax error, expected \"FOR\""
+msgstr "syntaktická chyba, očekává se \"FOR\""
+
+#: pl_gram.y:2157
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "příkaz FETCH nesmí vracet více řádek"
+
+#: pl_gram.y:2281
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "proměnná kurzoru musí být skalární proměnná"
+
+# cursor bych asi nepřekládal, je to přímo název typu, navíc v refcursor to přeloženo není
+# kurzor (cursor) neni typ, a refcursor je fakticky varchar - vyhodil bych type
+# pripadne "promenna musi byt deklarovana jako kurzor nebo jako refcursor"
+#: pl_gram.y:2287
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "proměnná \"%s\" musí být kurzor nebo referencí na kurzor"
+
+#: pl_gram.y:2623 pl_gram.y:2634
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" není známou proměnnou"
+
+#: pl_gram.y:2738 pl_gram.y:2748 pl_gram.y:2903
+msgid "mismatched parentheses"
+msgstr "neodpovídající si závorky"
+
+#: pl_gram.y:2752
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "chybějící \"%s\" na konci SQL výrazu"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "chybějící \"%s\" na konci SQL příkazu"
+
+#: pl_gram.y:2775
+msgid "missing expression"
+msgstr "chybějící výraz"
+
+#: pl_gram.y:2777
+msgid "missing SQL statement"
+msgstr "chybějící SQL příkaz"
+
+#: pl_gram.y:2905
+msgid "incomplete data type declaration"
+msgstr "neúplná deklarace datového typu"
+
+#: pl_gram.y:2928
+msgid "missing data type declaration"
+msgstr "chybějící deklarace datového typu"
+
+#: pl_gram.y:3006
+msgid "INTO specified more than once"
+msgstr "opakované použití INTO"
+
+#: pl_gram.y:3175
+msgid "expected FROM or IN"
+msgstr "očekáváno FROM nebo IN"
+
+#: pl_gram.y:3236
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "uvnitř funkce, která vrací tabulku, RETURN nemá parametr"
+
+#: pl_gram.y:3237
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Použijte RETURN NEXT nebo RETURN QUERY."
+
+#: pl_gram.y:3247
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN v proceduře nemůže mít parametr"
+
+#: pl_gram.y:3252
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "uvnitř funkce s návratovou hodnotou typu void RETURN nemá parametr"
+
+#: pl_gram.y:3261
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "uvnitř funkce s OUT parametry RETURN nemá parametr"
+
+#: pl_gram.y:3324
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "uvnitř funkce s OUT parametry RETURN NEXT nemá paramet"
+
+#: pl_gram.y:3432
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "proměnná \"%s\" je deklarována jako konstanta (CONSTANT)"
+
+#: pl_gram.y:3495
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "v seznamu cílových proměnných klauzule INTO není dovoleno použítí proměnné typu record"
+
+#: pl_gram.y:3541
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "příliš mnoho cílových proměnných v klauzuli INTO"
+
+#: pl_gram.y:3752
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "koncové návěstí \"%s\" použito pro blok bez návěstí"
+
+#: pl_gram.y:3759
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "koncové návěstí \"%s\" nesouhlasí s návěstím bloku \"%s\""
+
+#: pl_gram.y:3794
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "kurzor \"%s\" je deklarován bez parametrů"
+
+#: pl_gram.y:3808
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "kurzor \"%s\" vyžaduje parametry"
+
+#: pl_gram.y:3850
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "kurzor \"%s\" nemá žádný argument s názvem \"%s\""
+
+#: pl_gram.y:3870
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "hodnota parametru \"%s\" kurzoru \"%s\" zadána více než jednou"
+
+#: pl_gram.y:3895
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "nedostatek argumentů pro kurzor \"%s\""
+
+#: pl_gram.y:3902
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "příliš mnoho argumentů pro kurzor \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "neznámý volitelný parametr příkazu RAISE"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "syntaktická chyba, očekáváno \"=\""
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "příliš mnoho parametrů příkazu RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "příliš málo parametrů příkazu RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Nastavuje způsob řešení konfliktu mezi názvy PL/pgSQL proměnných a názvy sloupců tabulek."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Vypíše informace o parametrech v DETAIL částo chybové zprávy generované selháním INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Vyková kontroly uvedené v ASSERT příkazech."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Seznam programovacích kontruktů které by měly vygenerovat varování."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Seznam programovacích konstruktů které by měly vygenerovat chybu."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "\"%s\" na konci vstupu"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s na nebo blízko \"%s\""
+
+#~ msgid "duplicate value for cursor \"%s\" parameter \"%s\""
+#~ msgstr "duplicitní hodnota pro kurzor \"%s\" parametr \"%s\""
+
+#~ msgid "RETURN NEXT must specify a record or row variable in function returning row"
+#~ msgstr "uvnitř funkce, která vrací složenou hodnotu, lze použít RETURN NEXT pouze s proměnnou typu record nebo složeného typu"
+
+#~ msgid "RETURN must specify a record or row variable in function returning row"
+#~ msgstr "uvnitř funkce, která vrací složenou hodnotu, lze použít RETURN pouze s proměnnou typu record nebo složeného typu"
+
+#~ msgid "label does not exist"
+#~ msgstr "návěstí neexistuje"
+
+#~ msgid "default value for row or record variable is not supported"
+#~ msgstr "nelze zadat defaultní hodnotu proměnným typu record nebo složeného typu"
+
+#~ msgid "row or record variable cannot be NOT NULL"
+#~ msgstr "proměnná typu record nebo složeného typu nemůže být označena jako NOT NULL"
+
+# překládat RECORD jako "proměnná složeného typu" mi přijde divný (resp. spousta lidí nebude vědět o co jde), ale "záznam" se asi často používá pro řádek tabulky ...
+# record neprekladam (je to typ), prekladam row, ktery odpovida castecne zaznamu tabulek, ale take odpovida kompozitnim typum
+# o zaznamu jsem take uvazoval, ale prislo mi divny, kdybych napsal "promenna typu record nebo zaznam" ponevadz jsou to pro
+# pro mnohe z nas synonyma
+#~ msgid "row or record variable cannot be CONSTANT"
+#~ msgstr "proměnná typu record nebo složeného typu nemůže být označena jako konstanta"
+
+#~ msgid "EXECUTE statement"
+#~ msgstr "EXECUTE příkaz"
+
+#~ msgid "Use a BEGIN block with an EXCEPTION clause instead."
+#~ msgstr "Použijte blok BEGIN .. END s klauzulí EXCEPTION."
+
+#~ msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+#~ msgstr "NULL nemůže být výchozí hodnotou proměnné \"%s\" deklarované jako NOT NULL"
+
+#~ msgid "relation \"%s\" is not a table"
+#~ msgstr "relace \"%s\" není tabulkou"
diff --git a/src/pl/plpgsql/src/po/de.po b/src/pl/plpgsql/src/po/de.po
new file mode 100644
index 0000000..16a0cfa
--- /dev/null
+++ b/src/pl/plpgsql/src/po/de.po
@@ -0,0 +1,850 @@
+# German message translation file for plpgsql
+# Copyright (C) 2009 - 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Use these quotes: »%s«
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-08 12:09+0000\n"
+"PO-Revision-Date: 2022-04-08 14:40+0200\n"
+"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n"
+"Language-Team: German <pgsql-translators@postgresql.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL-Funktionen können Typ %s nicht annehmen"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "konnte den tatsächlichen Rückgabetyp der polymorphischen Funktion »%s« nicht ermitteln"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggerfunktionen können nur als Trigger aufgerufen werden"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL-Funktionen können keinen Rückgabetyp %s haben"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "Triggerfunktionen können keine deklarierten Argumente haben"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Auf die Argumente des Triggers kann stattdessen über TG_NARGS und TG_ARGV zugegriffen werden."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "Ereignistriggerfunktionen können keine deklarierten Argumente haben"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "Kompilierung der PL/pgSQL-Funktion »%s« nahe Zeile %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "Parametername »%s« mehrmals angegeben"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "Spaltenverweis »%s« ist nicht eindeutig"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Er könnte sich entweder auf eine PL/pgSQL-Variable oder eine Tabellenspalte beziehen."
+
+#: pl_comp.c:1324 pl_exec.c:5216 pl_exec.c:5389 pl_exec.c:5476 pl_exec.c:5567
+#: pl_exec.c:6588
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "Record »%s« hat kein Feld »%s«"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "Relation »%s« existiert nicht"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "Relation »%s« hat keinen zusammengesetzten Typ"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "Variable »%s« hat Pseudotyp %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "Typ »%s« ist nur eine Hülle"
+
+#: pl_comp.c:2204 pl_exec.c:6889
+#, c-format
+msgid "type %s is not composite"
+msgstr "Typ %s ist kein zusammengesetzter Typ"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "unbekannte Ausnahmebedingung »%s«"
+
+#: pl_comp.c:2526
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "konnte den tatsächlichen Argumenttyp der polymorphischen Funktion »%s« nicht ermitteln"
+
+#: pl_exec.c:500 pl_exec.c:939 pl_exec.c:1174
+msgid "during initialization of execution state"
+msgstr "bei der Initialisierung des Ausführungszustandes"
+
+#: pl_exec.c:506
+msgid "while storing call arguments into local variables"
+msgstr "beim Abspeichern der Aufrufargumente in lokale Variablen"
+
+#: pl_exec.c:594 pl_exec.c:1012
+msgid "during function entry"
+msgstr "beim Eintritts in die Funktion"
+
+#: pl_exec.c:617
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "Kontrollfluss erreichte das Ende der Funktion ohne RETURN"
+
+#: pl_exec.c:623
+msgid "while casting return value to function's return type"
+msgstr "bei der Umwandlung des Rückgabewerts in den Rückgabetyp der Funktion"
+
+#: pl_exec.c:635 pl_exec.c:3656
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "Funktion mit Mengenergebnis in einem Zusammenhang aufgerufen, der keine Mengenergebnisse verarbeiten kann"
+
+#: pl_exec.c:640 pl_exec.c:3662
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "Materialisierungsmodus wird benötigt, ist aber in diesem Zusammenhang nicht erlaubt"
+
+#: pl_exec.c:767 pl_exec.c:1038 pl_exec.c:1196
+msgid "during function exit"
+msgstr "beim Verlassen der Funktion"
+
+#: pl_exec.c:822 pl_exec.c:886 pl_exec.c:3455
+msgid "returned record type does not match expected record type"
+msgstr "zurückgegebener Record-Typ stimmt nicht mit erwartetem Record-Typ überein"
+
+#: pl_exec.c:1035 pl_exec.c:1193
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "Kontrollfluss erreichte das Ende der Triggerprozedur ohne RETURN"
+
+#: pl_exec.c:1043
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "Triggerprozedur kann keine Ergebnismenge zurückgeben"
+
+#: pl_exec.c:1082 pl_exec.c:1110
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "zurückgegebene Zeilenstruktur stimmt nicht mit der Struktur der Tabelle, die den Trigger ausgelöst hat, überein"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1251
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL-Funktion %s Zeile %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL-Funktion %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1270
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL-Funktion %s Zeile %d bei %s"
+
+#: pl_exec.c:1276
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL-Funktion %s"
+
+#: pl_exec.c:1647
+msgid "during statement block local variable initialization"
+msgstr "bei der Initialisierung der lokalen Variablen des Anweisungsblocks"
+
+#: pl_exec.c:1752
+msgid "during statement block entry"
+msgstr "beim Eintreten in den Anweisungsblock"
+
+#: pl_exec.c:1784
+msgid "during statement block exit"
+msgstr "beim Verlassen des Anweisungsblocks"
+
+#: pl_exec.c:1822
+msgid "during exception cleanup"
+msgstr "beim Aufräumen der Ausnahme"
+
+#: pl_exec.c:2355
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "Prozedurparameter »%s« ist ein Ausgabeparameter, aber das entsprechende Argument ist nicht schreibbar"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "Prozedurparameter %d ist ein Ausgabeparameter, aber das entsprechende Argument ist nicht schreibbar"
+
+#: pl_exec.c:2394
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS kann nicht außerhalb einer Ausnahmebehandlung verwendet werden"
+
+#: pl_exec.c:2594
+#, c-format
+msgid "case not found"
+msgstr "Fall nicht gefunden"
+
+#: pl_exec.c:2595
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "Der CASE-Anweisung fehlt ein ELSE-Teil."
+
+#: pl_exec.c:2688
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "Untergrenze einer FOR-Schleife darf nicht NULL sein"
+
+#: pl_exec.c:2704
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "Obergrenze einer FOR-Schleife darf nicht NULL sein"
+
+#: pl_exec.c:2722
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "BY-Wert einer FOR-Schleife darf nicht NULL sein"
+
+#: pl_exec.c:2728
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "BY-Wert einer FOR-Schleife muss größer als null sein"
+
+#: pl_exec.c:2862 pl_exec.c:4658
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "Cursor »%s« ist bereits in Verwendung"
+
+#: pl_exec.c:2885 pl_exec.c:4723
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "einem Cursor ohne Argumente wurden Argumente übergeben"
+
+#: pl_exec.c:2904 pl_exec.c:4742
+#, c-format
+msgid "arguments required for cursor"
+msgstr "Cursor benötigt Argumente"
+
+#: pl_exec.c:2991
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH-Ausdruck darf nicht NULL sein"
+
+#: pl_exec.c:3006
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH-Ausdruck muss ein Array ergeben, nicht Typ %s"
+
+#: pl_exec.c:3023
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "Slice-Dimension (%d) ist außerhalb des gültigen Bereichs 0..%d"
+
+#: pl_exec.c:3050
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE Schleifenvariable muss einen Arraytyp haben"
+
+#: pl_exec.c:3054
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH-Schleifenvariable darf keinen Array-Typ haben"
+
+#: pl_exec.c:3216 pl_exec.c:3273 pl_exec.c:3448
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "kann keinen nicht zusammengesetzten Wert aus einer Funktion zurückgeben, die einen zusammengesetzten Typ zurückgibt"
+
+#: pl_exec.c:3312 pl_gram.y:3318
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "RETURN NEXT kann nur in einer Funktion mit SETOF-Rückgabetyp verwendet werden"
+
+#: pl_exec.c:3353 pl_exec.c:3485
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "falscher Ergebnistyp angegeben in RETURN NEXT"
+
+#: pl_exec.c:3391 pl_exec.c:3412
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "falscher Record-Typ angegeben in RETURN NEXT"
+
+#: pl_exec.c:3504
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT muss einen Parameter haben"
+
+#: pl_exec.c:3532 pl_gram.y:3382
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "RETURN QUERY kann nur in einer Funktion mit SETOF-Rückgabetyp verwendet werden"
+
+#: pl_exec.c:3550
+msgid "structure of query does not match function result type"
+msgstr "Struktur der Anfrage stimmt nicht mit Rückgabetyp der Funktion überein"
+
+#: pl_exec.c:3605 pl_exec.c:4435 pl_exec.c:8630
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "Anfrageargument von EXECUTE ist NULL"
+
+#: pl_exec.c:3690 pl_exec.c:3828
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE-Option bereits angegeben: %s"
+
+#: pl_exec.c:3724
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE ohne Parameter kann nicht außerhalb einer Ausnahmebehandlung verwendet werden"
+
+#: pl_exec.c:3818
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "Option einer RAISE-Anweisung darf nicht NULL sein"
+
+#: pl_exec.c:3888
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3943
+#, c-format
+msgid "assertion failed"
+msgstr "Assertion fehlgeschlagen"
+
+#: pl_exec.c:4308 pl_exec.c:4497
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "COPY vom/zum Client funktioniert in PL/pgSQL nicht"
+
+#: pl_exec.c:4314
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "nicht unterstützter Transaktionsbefehl in PL/pgSQL"
+
+#: pl_exec.c:4337 pl_exec.c:4526
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO mit einem Befehl verwendet, der keine Daten zurückgeben kann"
+
+#: pl_exec.c:4360 pl_exec.c:4549
+#, c-format
+msgid "query returned no rows"
+msgstr "Anfrage gab keine Zeilen zurück"
+
+#: pl_exec.c:4382 pl_exec.c:4568 pl_exec.c:5711
+#, c-format
+msgid "query returned more than one row"
+msgstr "Anfrage gab mehr als eine Zeile zurück"
+
+#: pl_exec.c:4384
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Stellen Sie sicher, dass die Anfrage eine einzige Zeile zurückgibt, oder verwenden Sie LIMIT 1."
+
+#: pl_exec.c:4400
+#, c-format
+msgid "query has no destination for result data"
+msgstr "Anfrage hat keinen Bestimmungsort für die Ergebnisdaten"
+
+#: pl_exec.c:4401
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Wenn Sie die Ergebnisse eines SELECT verwerfen wollen, verwenden Sie stattdessen PERFORM."
+
+#: pl_exec.c:4489
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE von SELECT ... INTO ist nicht implementiert"
+
+#: pl_exec.c:4490
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Sie könnten stattdessen EXECUTE ... INTO oder EXECUTE CREATE TABLE ... AS verwenden."
+
+#: pl_exec.c:4503
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE von Transaktionsbefehlen ist nicht implementiert"
+
+#: pl_exec.c:4804 pl_exec.c:4892
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "Cursor-Variable »%s« ist NULL"
+
+#: pl_exec.c:4815 pl_exec.c:4903
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "Cursor »%s« existiert nicht"
+
+#: pl_exec.c:4828
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "relative oder absolute Cursorposition ist NULL"
+
+#: pl_exec.c:5066 pl_exec.c:5161
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "NULL-Wert kann der Variable »%s« nicht zugewiesen werden, weil sie als NOT NULL deklariert ist"
+
+#: pl_exec.c:5142
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "nicht zusammengesetzter Wert kann nicht einer Zeilenvariable zugewiesen werden"
+
+#: pl_exec.c:5174
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "nicht zusammengesetzter Wert kann nicht einer Record-Variable zugewiesen werden"
+
+#: pl_exec.c:5225
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "kann Systemspalte »%s« keinen Wert zuweisen"
+
+#: pl_exec.c:5674
+#, c-format
+msgid "query did not return data"
+msgstr "Anfrage hat keine Daten zurückgegeben"
+
+#: pl_exec.c:5675 pl_exec.c:5687 pl_exec.c:5712 pl_exec.c:5788 pl_exec.c:5793
+#, c-format
+msgid "query: %s"
+msgstr "Anfrage: %s"
+
+#: pl_exec.c:5683
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "Anfrage hat %d Spalte zurückgegeben"
+msgstr[1] "Anfrage hat %d Spalten zurückgegeben"
+
+#: pl_exec.c:5787
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "Anfrage ist SELECT INTO, sollte aber ein normales SELECT sein"
+
+#: pl_exec.c:5792
+#, c-format
+msgid "query is not a SELECT"
+msgstr "Anfrage ist kein SELECT"
+
+#: pl_exec.c:6602 pl_exec.c:6642 pl_exec.c:6682
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "Typ von Parameter %d (%s) stimmt nicht mit dem überein, als der Plan vorbereitet worden ist (%s)"
+
+#: pl_exec.c:7093 pl_exec.c:7127 pl_exec.c:7201 pl_exec.c:7227
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "Anzahl der Quell- und Zielfelder in der Zuweisung stimmt nicht überein"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7095 pl_exec.c:7129 pl_exec.c:7203 pl_exec.c:7229
+#, c-format
+msgid "%s check of %s is active."
+msgstr "Check »%s« aus »%s« ist aktiv."
+
+#: pl_exec.c:7099 pl_exec.c:7133 pl_exec.c:7207 pl_exec.c:7233
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Stellen Sie sicher, dass die Anfrage die genaue Spaltenliste zurückgibt."
+
+#: pl_exec.c:7620
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "Record »%s« hat noch keinen Wert"
+
+#: pl_exec.c:7621
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Die Tupelstruktur eines Records ohne Wert ist unbestimmt."
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "Anweisungsblock"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "Zuweisung"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR mit ganzzahliger Schleifenvariable"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR über SELECT-Zeilen"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR über Cursor"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH über Array"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL-Anweisung"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR-über-EXECUTE-Anweisung"
+
+#: pl_gram.y:486
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "Blocklabel muss vor DECLARE stehen, nicht danach"
+
+#: pl_gram.y:506
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "Sortierfolgen werden von Typ %s nicht unterstützt"
+
+#: pl_gram.y:525
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "Variable »%s« muss einen Vorgabewert haben, da sie als NOT NULL deklariert ist"
+
+#: pl_gram.y:673 pl_gram.y:688 pl_gram.y:714
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "Variable »%s« existiert nicht"
+
+#: pl_gram.y:732 pl_gram.y:760
+msgid "duplicate declaration"
+msgstr "doppelte Deklaration"
+
+#: pl_gram.y:743 pl_gram.y:771
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "Variable »%s« verdeckt eine zuvor definierte Variable"
+
+#: pl_gram.y:1043
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "Diagnostikelement %s ist in GET STACKED DIAGNOSTICS nicht erlaubt"
+
+#: pl_gram.y:1061
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "Diagnostikelement %s ist in GET CURRENT DIAGNOSTICS nicht erlaubt"
+
+#: pl_gram.y:1156
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "unbekanntes Element in GET DIAGNOSTICS"
+
+#: pl_gram.y:1172 pl_gram.y:3557
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "»%s« ist keine skalare Variable"
+
+#: pl_gram.y:1402 pl_gram.y:1596
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "Schleifenvariable einer Schleife über Zeilen muss eine Record-Variable oder eine Liste von skalaren Variablen sein"
+
+#: pl_gram.y:1437
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "Cursor-FOR-Schleife darf nur eine Zielvariable haben"
+
+#: pl_gram.y:1444
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "Cursor-FOR-Schleife muss eine gebundene Cursor-Variable verwenden"
+
+#: pl_gram.y:1535
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "ganzzahlige FOR-Schleife darf nur eine Zielvariable haben"
+
+#: pl_gram.y:1569
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "REVERSE kann nicht in einer Anfrage-FOR-Schleife verwendet werden"
+
+#: pl_gram.y:1699
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "Schleifenvariable von FOREACH muss eine bekannte Variable oder Liste von Variablen sein"
+
+#: pl_gram.y:1741
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "diese Anweisung umschließt kein Block und keine Schleife mit Label »%s«"
+
+#: pl_gram.y:1749
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "Blocklabel »%s« kann nicht in CONTINUE verwendet werden"
+
+#: pl_gram.y:1764
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT kann nicht außerhalb einer Schleife verwendet werden, außer wenn es ein Label hat"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE kann nicht außerhalb einer Schleife verwendet werden"
+
+#: pl_gram.y:1789 pl_gram.y:1827 pl_gram.y:1875 pl_gram.y:3004 pl_gram.y:3092
+#: pl_gram.y:3203 pl_gram.y:3956
+msgid "unexpected end of function definition"
+msgstr "unerwartetes Ende der Funktionsdefinition"
+
+#: pl_gram.y:1895 pl_gram.y:1919 pl_gram.y:1935 pl_gram.y:1941 pl_gram.y:2066
+#: pl_gram.y:2074 pl_gram.y:2088 pl_gram.y:2183 pl_gram.y:2407 pl_gram.y:2497
+#: pl_gram.y:2655 pl_gram.y:3799 pl_gram.y:3860 pl_gram.y:3937
+msgid "syntax error"
+msgstr "Syntaxfehler"
+
+#: pl_gram.y:1923 pl_gram.y:1925 pl_gram.y:2411 pl_gram.y:2413
+msgid "invalid SQLSTATE code"
+msgstr "ungültiger SQLSTATE-Code"
+
+#: pl_gram.y:2131
+msgid "syntax error, expected \"FOR\""
+msgstr "Syntaxfehler, »FOR« erwartet"
+
+#: pl_gram.y:2192
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH-Anweisung kann nicht mehrere Zeilen zurückgeben"
+
+#: pl_gram.y:2289
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "Cursor-Variable muss eine einfache Variable sein"
+
+#: pl_gram.y:2295
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "Variable »%s« muss Typ cursor oder refcursor haben"
+
+#: pl_gram.y:2626 pl_gram.y:2637
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "»%s« ist keine bekannte Variable"
+
+#: pl_gram.y:2743 pl_gram.y:2753 pl_gram.y:2909
+msgid "mismatched parentheses"
+msgstr "Klammern passen nicht"
+
+#: pl_gram.y:2757
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "»%s« fehlt am Ende des SQL-Ausdrucks"
+
+#: pl_gram.y:2763
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "»%s« fehlt am Ende der SQL-Anweisung"
+
+#: pl_gram.y:2780
+msgid "missing expression"
+msgstr "Ausdruck fehlt"
+
+#: pl_gram.y:2782
+msgid "missing SQL statement"
+msgstr "SQL-Anweisung fehlt"
+
+#: pl_gram.y:2911
+msgid "incomplete data type declaration"
+msgstr "unvollständige Datentypdeklaration"
+
+#: pl_gram.y:2934
+msgid "missing data type declaration"
+msgstr "fehlende Datentypdeklaration"
+
+#: pl_gram.y:3014
+msgid "INTO specified more than once"
+msgstr "INTO mehr als einmal angegeben"
+
+#: pl_gram.y:3184
+msgid "expected FROM or IN"
+msgstr "FROM oder IN erwartet"
+
+#: pl_gram.y:3245
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN kann keinen Parameter haben in einer Funktion mit Mengenergebnis"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Verwenden Sie RETURN NEXT oder RETURN QUERY."
+
+#: pl_gram.y:3256
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN kann keinen Parameter haben in einer Prozedur"
+
+#: pl_gram.y:3261
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN kann keinen Parameter haben in einer Funktion, die »void« zurückgibt"
+
+#: pl_gram.y:3270
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN kann keinen Parameter haben in einer Funktion mit OUT-Parametern"
+
+#: pl_gram.y:3333
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT kann keinen Parameter haben in einer Funktion mit OUT-Parametern"
+
+#: pl_gram.y:3441
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "Variable »%s« ist als CONSTANT deklariert"
+
+#: pl_gram.y:3499
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "Record-Variable kann nicht Teil einer INTO-Liste mit mehreren Elementen sein"
+
+#: pl_gram.y:3545
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "zu viele INTO-Variablen angegeben"
+
+#: pl_gram.y:3753
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "Endlabel »%s« für ungelabelten Block angegeben"
+
+#: pl_gram.y:3760
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "Endlabel »%s« unterscheidet sich vom Label des Blocks »%s«"
+
+#: pl_gram.y:3794
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "Cursor »%s« hat keine Argumente"
+
+#: pl_gram.y:3808
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "Cursor »%s« hat Argumente"
+
+#: pl_gram.y:3850
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "Cursor »%s« hat kein Argument namens »%s«"
+
+#: pl_gram.y:3870
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "Wert für Parameter »%s« von Cursor »%s« mehrmals angegeben"
+
+#: pl_gram.y:3895
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "nicht genügend Argumente für Cursor »%s«"
+
+#: pl_gram.y:3902
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "zu viele Argumente für Cursor »%s«"
+
+#: pl_gram.y:3988
+msgid "unrecognized RAISE statement option"
+msgstr "unbekannte Option für RAISE-Anweisung"
+
+#: pl_gram.y:3992
+msgid "syntax error, expected \"=\""
+msgstr "Syntaxfehler, »=« erwartet"
+
+#: pl_gram.y:4033
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "zu viele Parameter für RAISE angegeben"
+
+#: pl_gram.y:4037
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "zu wenige Parameter für RAISE angegeben"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Bestimmt die Verarbeitung von Konflikten zwischen PL/pgSQL-Variablennamen und Tabellenspaltennamen."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Informationen über Parameter im DETAIL-Teil von Fehlermeldungen ausgeben, die durch Fehler in INTO ... STRICT erzeugt wurden."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Prüfungen in ASSERT-Anweisungen ausführen."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Zählt Programmierkonstrukte auf, die eine Warnung erzeugen sollen."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Zählt Programmierkonstrukte auf, die einen Fehler zeugen sollen."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s am Ende der Eingabe"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s bei »%s«"
diff --git a/src/pl/plpgsql/src/po/el.po b/src/pl/plpgsql/src/po/el.po
new file mode 100644
index 0000000..e89a0c4
--- /dev/null
+++ b/src/pl/plpgsql/src/po/el.po
@@ -0,0 +1,855 @@
+# Greek message translation file for plpgsql
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpgsql (PostgreSQL) package.
+# Georgios Kokolatos <gkokolatos@pm.me>, 2021.
+#
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-14 09:09+0000\n"
+"PO-Revision-Date: 2023-04-14 15:03+0200\n"
+"Last-Translator: Georgios Kokolatos <gkokolatos@pm.me>\n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "οι συναρτήσεις PL/pgSQL δεν μπορούν να δεχθούν τύπο %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "δεν ήταν δυνατός ο προσδιορισμός του πραγματικού τύπου επιστροφής για την πολυμορφική συνάρτηση «%s»"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "συναρτήσεις εναυσμάτων μπορούν να κληθούν μόνο ως εναύσματα"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "οι συναρτήσεις PL/pgSQL δεν μπορούν να επιστρέψουν τύπο %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "οι συναρτήσεις εναυσμάτων δεν μπορούν να έχουν δηλωμένες παραμέτρους"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Οι παράμετροι του εναύσματος μπορούν εναλλακτικά να προσπελαστούν μέσω TG_NARGS και TG_ARGV."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "οι συναρτήσεις εναυσμάτων συμβάντος δεν μπορούν να έχουν δηλωμένες παραμέτρους"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "μεταγλώτηση της συνάρτησης PL/pgSQL «%s» κοντά στη γραμμή %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "όνομα παραμέτρου «%s» χρησιμοποιείται περισσότερες από μία φορές"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "αναφορά στήλης «%s» είναι αμφίσημη"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Θα μπορούσε να αναφέρεται είτε σε μεταβλητή PL/pgSQL είτε σε στήλη πίνακα."
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "η εγγραφή «%s» δεν έχει πεδίο «%s»"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "η σχέση «%s» δεν υπάρχει"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "η σχέση «%s» δεν έχει συνθετικό τύπο"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "η μεταβλητή «%s» έχει ψευδο-τύπο %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "τύπος «%s» είναι μόνο ένα κέλυφος"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "τύπος %s δεν είναι συνθετικός"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "μη αναγνωρίσιμη συνθήκη εξαίρεσης «%s»"
+
+#: pl_comp.c:2534
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "δεν ήταν δυνατός ο προσδιορισμός του πραγματικού τύπου παραμέτρου για την πολυμορφική συνάρτηση «%s»"
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "κατά την αρχικοποίηση της κατάστασης εκτέλεσης"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "κατά την αποθήκευση παραμέτρων κλήσης σε τοπικές μεταβλητές"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "κατά τη διάρκεια εισόδου συνάρτησης"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "ο έλεγχος έφθασε στο τέλος συνάρτησης χωρίς RETURN"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "κατά τη διάρκεια cast της τιμής επιστροφής στον τύπο επιστροφής της συνάρτησης"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set-valued συνάρτηση καλείται σε περιεχόμενο που δεν μπορεί να δεχτεί ένα σύνολο"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "επιβάλλεται λειτουργία υλοποίησης, αλλά δεν επιτρέπεται σε αυτό το περιεχόμενο"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "κατά τη διάρκεια εξόδου συνάρτησης"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "ο επιστρεφόμενος τύπος εγγραφής δεν ταιριάζει με τον αναμενόμενο τύπο εγγραφής"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "ο έλεγχος έφτασε στο τέλος της διαδικασίας εναύσματος χωρίς RETURN"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "διεργασία εναύσματος δεν δύναται να επιστρέψει ένα σύνολο"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "η δομή της σειράς επιστροφής δεν ταιριάζει με τη δομή του πίνακα ενεργοποίησης"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "συνάρτηση PL/pgSQL %s γραμμή %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "συνάρτηση PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "συνάρτηση PL/pgSQL %s γραμμή %d στο %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "συνάρτηση PL/pgSQL «%s»"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "κατά τη διάρκεια μπλοκ δήλωσης τοπικής αρχικοποίησης μεταβλητής"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "κατά τη διάρκεια εισόδου μπλοκ δήλωσης"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "κατά τη διάρκεια εξόδου μπλοκ δήλωσης"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "κατά τη διάρκεια καθαρισμού εξαίρεσης"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "η παράμετρος διαδικασίας «%s» είναι παράμετρος εξόδου, αλλά το αντίστοιχο όρισμα δεν είναι εγγράψιμο"
+
+#: pl_exec.c:2365
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "η παράμετρος διαδικασίας %d είναι παράμετρος εξόδου, αλλά το αντίστοιχο όρισμα δεν είναι εγγράψιμο"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS δεν είναι δυνατό να χρησιμοποιηθεί εκτός ενός χειριστή εξαιρέσεων"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "υπόθεση δεν βρέθηκε"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "Η δήλωση CASE δεν περιέχει τμήμα ELSE."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "το κατώτατο όριο του βρόχου FOR δεν μπορεί να είναι null"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "το ανώνοτατο όριο του βρόχου FOR δεν μπορεί να είναι null"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "η τιμή BY του βρόχου FOR δεν μπορεί να είναι null"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "η τιμή BY του βρόχου FOR πρέπει να είναι μεγαλύτερη του μηδενός"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "ο δρομέας «%s» βρίσκεται ήδη σε χρήση"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "δίνονται ορίσματα σε δρομέα χωρίς ορίσματα"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "επιβάλλονται ορίσματα για τον δρομέα"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "η έκφραση FOREACH πρέπει να μην είναι null"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "η έκφραση FOREACH πρέπει αποδώσει μία συστοιχία, όχι ένα τύπο %s"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "η διάσταση (%d) του slice βρίσκεται εκτός του έγκυρου εύρους 0.. %d"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "η μεταβλητή βρόχου FOREACH ... SLICE πρέπει να είναι τύπου συστοιχίας"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "η μεταβλητή βρόχου FOREACH δεν πρέπει να είναι τύπου συστοιχίας"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "δεν είναι δυνατή η επιστροφή μη-σύνθετης τιμής από σύνθετο τύπο συνάρτησης που επιστρέφει σύνθετο τύπο"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "δεν είναι δυνατή η χρήση RETURN NEXT σε συνάρτηση non-SETOF"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "εσφαλμένος τύπος αποτελέσματος που παρέχεται στο RETURN NEXT"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "εσφαλμένος τύπος εγγραφής που παρέχεται στο RETURN NEXT"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "το RETURN NEXT πρέπει να έχει παράμετρο"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "δεν είναι δυνατή η χρήση RETURN QUERY σε συνάρτηση non-SETOF"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "η δομή του ερωτήματος δεν ταιριάζει με τον τύπο αποτελέσματος της συνάρτησης"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "η παράμετρος συμβολοσειράς ερωτήματος του EXECUTE είναι null"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "επιλογή RAISE που έχει ήδη καθοριστεί: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE χωρίς παραμέτρους δεν μπορεί να χρησιμοποιηθεί εκτός ενός δείκτη χειρισμού εξαιρέσεων"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "η επιλογή δήλωσης RAISE δεν μπορεί να είναι null"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "η επιβεβαίωση απέτυχε"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "δεν είναι δυνατό το COPY to/from τον πελάτη σε PL/pgSQL"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "μη υποστηριζόμενη συναλλαγή σε PL/pgSQL"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "το INTO χρησιμοποιείται με μια εντολή που δεν μπορεί να επιστρέψει δεδομένα"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "το ερώτημα επέστρεψε καθόλου γραμμές"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "ερώτημα επέστρεψε περισσότερες από μία γραμμές"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Βεβαιωθείτε ότι το ερώτημα επιστρέφει μία μόνο γραμμή ή χρησιμοποιήστε το LIMIT 1."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "το ερώτημα δεν έχει προορισμό για τα δεδομένα αποτελεσμάτων"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Εάν θέλετε να απορρίψετε τα αποτελέσματα ενός SELECT, χρησιμοποιήστε την επιλογή PERFORM."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE of SELECT ... INTO δεν έχει υλοποιηθεί"
+
+#: pl_exec.c:4499
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Ίσως θέλετε να χρησιμοποιήσετε το EXECUTE ... INTO ή EXECUTE CREATE TABLE ... AS αντ' αυτού."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE εντολών συναλλαγής δεν έχει εφαρμοσθεί"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "η μεταβλήτη «%s» του δρομέα είναι κενή"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "ο δρομέας «%s» δεν υπάρχει"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "η σχετική ή η απόλυτη θέση δρομέα είναι null"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "δεν είναι δυνατή η αντιστοίχιση τιμής null στη μεταβλητή «%s» δηλωμένης ως NOT NULL"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "δεν είναι δυνατή η αντιστοίχιση μη-σύνθετης τιμής σε μεταβλητή σειράς"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "δεν είναι δυνατή η αντιστοίχιση μη-σύνθετης τιμής σε μεταβλητή εγγραφής"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "δεν είναι δυνατή η ανάθεση στη στήλη συστήματος «%s»"
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "το ερώτημα δεν επέστρεψε δεδομένα"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "ερώτημα: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "το ερώτημα επέστρεψε %d στήλη"
+msgstr[1] "το ερώτημα επέστρεψε %d στήλες"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "το ερώτημα είναι SELECT INTO, αλλά θα έπρεπε να είναι απλό SELECT"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "ερώτημα δεν είναι SELECT"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "ο τύπος παραμέτρου %d (%s) δεν συμφωνεί με αυτόν κατά την προετοιμασία του σχεδίου (%s)"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "ο αριθμός των πεδίων προέλευσης και προορισμού στην ανάθεση δεν συμφωνεί"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s έλεγχος του %s είναι ενεργός."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Βεβαιωθείτε ότι το ερώτημα επιστρέφει την ακριβή λίστα στηλών."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "η εγγραφή «%s» δεν έχει ανατεθεί ακόμα"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Η δομή πλειάδας μίας εγγραφής που δεν έχει ακόμη ανατεθεί είναι απροσδιόριστη."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "η μεταβλητή «%s» είναι δηλωμένη ως CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "μπλοκ δήλωσης"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "ανάθεση"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR με ακέραια μεταβλητή βρόχου"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR σε SELECT rows"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR σε ένα δρομέα"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH σε συστοιχία"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL δήλωση"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR σε δήλωση EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "η ετικέτα μπλοκ πρέπει να τοποθετείται πριν από το DECLARE, όχι μετά"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "συρραφές δεν υποστηρίζονται από τον τύπο %s"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "η μεταβλητή «%s» πρέπει να έχει προεπιλεγμένη τιμή, καθώς έχει δηλωθεί NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "μεταβλητή «%s» δεν υπάρχει"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "διπλότυπη δήλωση"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "η μεταβλητή «%s» σκιάζει μια προηγουμένως ορισμένη μεταβλητή"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "δεν επιτρέπεται το διαγνωστικό στοιχείο %s σε GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "δεν επιτρέπεται το διαγνωστικό στοιχείο %s σε GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "μη αναγνωρίσιμο στοιχείο GET DIAGNOSTICS"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "«%s» δεν είναι scalar μεταβλητή"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "η μεταβλητή βρόχου ενός βρόχου πάνω από γραμμές πρέπει να είναι είτε μεταβλητή εγγραφής ή μια λίστα scalar μεταβλητών"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "ο δρομέας βρόχου FOR πρέπει να έχει μόνο μία μεταβλητή προορισμού"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "ο δρομέας βρόχου FOR πρέπει να χρησιμοποιήσει μια δεσμευμένη μεταβλητή δρομέα"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "ακέραιος βρόχος FOR πρέπει να έχει μόνο μία μεταβλητή προορισμού"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "δεν είναι δυνατός ο καθορισμός REVERSE σε ερώτημα βρόχου FOR"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "η μεταβλητή του βρόχου FOREACH πρέπει να είναι είτε γνωστή μεταβλητή ή λίστα μεταβλητών"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "δεν υπάρχει ετικέτα «%s» προσαρτημένη σε οποιοδήποτε μπλοκ ή βρόχο που περικλείει αυτήν τη δήλωση"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "η ετικέτα μπλοκ «%s» δεν μπορεί να χρησιμοποιηθεί σε CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "το EXIT δεν μπορεί να χρησιμοποιηθεί εκτός βρόχου, εκτός εάν έχει ετικέτα"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "το CONTINUE δεν μπορεί να χρησιμοποιηθεί εκτός βρόχου"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "μη αναμενόμενο τέλος ορισμού συνάρτησης"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "συντακτικό σφάλμα"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "μη έγκυρος κωδικός SQLSTATE"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "συντακτικό σφάλμα, αναμενόταν «FOR«"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "η δήλωση FETCH δεν είναι δυνατό να επιστρέψει πολλαπλές σειρές"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "η μεταβλητή δρομέα πρέπει να είναι απλή μεταβλητή"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "η μεταβλητή «%s» πρέπει να έχει τύπο δρομέα ή refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "«%s» δεν είναι γνωστή μεταβλητή"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "ασυμφωνία παρενθέσεων"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "λείπει «%s» στο τέλος SQL έκφρασης"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "λείπει «%s» στο τέλος SQL δήλωσης"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "λείπει έκφραση"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "λείπει SQL δήλωση"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "ελλιπής δήλωση τύπου δεδομένων"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "λείπει δήλωση τύπου δεδομένων"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO ορίστηκε περισσότερο από μία φορά"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "αναμενόταν FROM ή IN"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN δεν μπορεί να έχει μία παράμετρο σε συνάρτηση που επιστρέφει σύνολο"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Χρησιμοποίησε RETURN NEXT ή RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN δεν μπορεί να έχει μία παράμετρο σε μια διαδικασία"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN δεν μπορεί να έχει μία παράμετρο σε συνάρτηση που επιστρέφει κενό"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN δεν μπορεί να έχει μια παράμετρο σε συνάρτηση με παραμέτρους OUT"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT δεν μπορεί να έχει μια παράμετρο σε συνάρτηση με παραμέτρους OUT"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "η μεταβλητή εγγραφής δεν μπορεί να είναι μέρος λίστας INTO πολλαπλών στοιχείων"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "έχουν οριστεί πάρα πολλές παράμετροι ΙΝΤΟ"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "ετικέτα τέλους «%s» έχει καθοριστεί για μπλοκ χωρίς ετικέτα"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "η ετικέτα τέλους «%s» διαφέρει από την ετικέτα «%s» του μπλοκ"
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "o δρομέας «%s» δεν έχει παραμέτρους"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "o δρομέας «%s» έχει παραμέτρους"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "ο δρομέας «%s» δεν έχει παράμετρο με όνομα «%s»"
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "τιμή για την παράμετρο «%s» του δρομέα «%s» που καθορίζεται περισσότερες από μία φορές"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "λείπουν παράμετροι για τον δρομέα «%s»"
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "πάρα πολλές παράμετροι για δρομέα «%s»"
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "μη αναγνωρίσιμη επιλογή δήλωσης RAISE"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "συντακτικό σφάλμα, αναμενόταν «=«"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "έχουν οριστεί πάρα πολλές παράμετροι για RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "έχουν οριστεί πολύ λίγες παράμετροι για RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Ορίζει το χειρισμό των διενέξεων μεταξύ των ονομάτων μεταβλητών PL/pgSQL και των ονομάτων στηλών πίνακα."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Εκτύπωσε πληροφορίες σχετικά με παραμέτρους στο τμήμα DETAIL των μηνυμάτων σφάλματος που δημιουργούνται από αποτυχίες INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Εκτέλεσε ελέγχους που δίνονται σε δηλώσεις ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Λίστα προγραμματιστικών κατασκευών που θα πρέπει να παράγουν μια προειδοποίηση."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Λίστα προγραμματιστικών κατασκευών που θα πρέπει να παράγουν ένα σφάλμα."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "«%s» στο τέλος εισόδου"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s σε ή κοντά σε «%s»"
+
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "το ερώτημα «%s» επέστρεψε περισσότερες από μία γραμμές"
diff --git a/src/pl/plpgsql/src/po/es.po b/src/pl/plpgsql/src/po/es.po
new file mode 100644
index 0000000..18644d4
--- /dev/null
+++ b/src/pl/plpgsql/src/po/es.po
@@ -0,0 +1,855 @@
+# Spanish message translation file for plpgsql
+#
+# Copyright (c) 2008-2021, PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Álvaro Herrera <alvherre@alvh.no-ip.org> 2008-2013
+# Emanuel Calvo Franco <postgres.arg@gmail.com> 2008
+# Jaime Casanova <jcasanov@systemguards.com.ec> 2010
+# Carlos Chapi <carloswaldo@babelruins.org> 2013, 2021
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-07 16:39+0000\n"
+"PO-Revision-Date: 2022-10-20 09:06+0200\n"
+"Last-Translator: Carlos Chapi <carloswaldo@babelruins.org>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: BlackCAT 1.1\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "las funciones PL/pgSQL no pueden aceptar el tipo %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "no se pudo determinar el verdadero tipo de resultado para la función polimórfica «%s»"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "las funciones de disparador sólo pueden ser invocadas como disparadores"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "las funciones PL/pgSQL no pueden retornar el tipo %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "las funciones de disparador no pueden tener argumentos declarados"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Los argumentos del disparador pueden accederse usando TG_NARGS y TG_ARGV."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "las funciones de disparador por eventos no pueden tener argumentos declarados"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "compilación de la función PL/pgSQL «%s» cerca de la línea %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "el nombre de parámetro «%s» fue usado más de una vez"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "la referencia a la columna «%s» es ambigua"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Podría referirse tanto a una variable PL/pgSQL como a una columna de una tabla."
+
+#: pl_comp.c:1324 pl_exec.c:5249 pl_exec.c:5422 pl_exec.c:5509 pl_exec.c:5600
+#: pl_exec.c:6621
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "el registro «%s» no tiene un campo «%s»"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "no existe la relación «%s»"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "la relación «%s» no contiene un tipo compuesto"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "la variable «%s» tiene pseudotipo %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "el tipo «%s» está inconcluso"
+
+#: pl_comp.c:2204 pl_exec.c:6922
+#, c-format
+msgid "type %s is not composite"
+msgstr "el tipo %s no es compuesto"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "no se reconoce la condición de excepción «%s»"
+
+#: pl_comp.c:2534
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "no se pudo determinar el verdadero tipo de argumento para la función polimórfica «%s»"
+
+#: pl_exec.c:511 pl_exec.c:950 pl_exec.c:1185
+msgid "during initialization of execution state"
+msgstr "durante la inicialización del estado de ejecución"
+
+#: pl_exec.c:517
+msgid "while storing call arguments into local variables"
+msgstr "mientras se almacenaban los argumentos de invocación en variables locales"
+
+#: pl_exec.c:605 pl_exec.c:1023
+msgid "during function entry"
+msgstr "durante el ingreso a la función"
+
+#: pl_exec.c:628
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "la ejecución alcanzó el fin de la función sin encontrar RETURN"
+
+#: pl_exec.c:634
+msgid "while casting return value to function's return type"
+msgstr "mientras se hacía la conversión del valor de retorno al tipo de retorno de la función"
+
+#: pl_exec.c:646 pl_exec.c:3675
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "se llamó una función que retorna un conjunto en un contexto que no puede aceptarlo"
+
+#: pl_exec.c:651 pl_exec.c:3681
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "se requiere un nodo «materialize», pero no está permitido en este contexto"
+
+#: pl_exec.c:778 pl_exec.c:1049 pl_exec.c:1207
+msgid "during function exit"
+msgstr "durante la salida de la función"
+
+#: pl_exec.c:833 pl_exec.c:897 pl_exec.c:3474
+msgid "returned record type does not match expected record type"
+msgstr "el tipo de registro retornado no coincide con el tipo de registro esperado"
+
+#: pl_exec.c:1046 pl_exec.c:1204
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "la ejecución alcanzó el fin del procedimiento disparador sin encontrar RETURN"
+
+#: pl_exec.c:1054
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "los procedimientos disparadores no pueden retornar conjuntos"
+
+#: pl_exec.c:1093 pl_exec.c:1121
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "la estructura de fila retornada no coincide con la estructura de la tabla que generó el evento de disparador"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "función PL/pgSQL %s en la línea %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1273
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "función PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1281
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "función PL/pgSQL %s en la línea %d en %s"
+
+#: pl_exec.c:1287
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "función PL/pgSQL %s"
+
+#: pl_exec.c:1658
+msgid "during statement block local variable initialization"
+msgstr "durante inicialización de variables locales en el bloque de sentencias"
+
+#: pl_exec.c:1763
+msgid "during statement block entry"
+msgstr "durante la entrada al bloque de sentencias"
+
+#: pl_exec.c:1795
+msgid "during statement block exit"
+msgstr "durante la salida del bloque de sentencias"
+
+#: pl_exec.c:1833
+msgid "during exception cleanup"
+msgstr "durante la finalización por excepción"
+
+#: pl_exec.c:2370
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "el parámetro de procedimiento «%s» es un parámetro de salida pero el argumento correspondiente no es escribible"
+
+#: pl_exec.c:2375
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "el parámetro de procedimiento %d es un parámetro de salida pero el argumento correspondiente no es escribible"
+
+#: pl_exec.c:2409
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS no puede ser usado fuera de un manejador de excepción"
+
+#: pl_exec.c:2609
+#, c-format
+msgid "case not found"
+msgstr "caso no encontrado"
+
+#: pl_exec.c:2610
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "A la sentencia CASE le falta la parte ELSE."
+
+#: pl_exec.c:2703
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "el límite inferior de un ciclo FOR no puede ser null"
+
+#: pl_exec.c:2719
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "el límite superior de un ciclo FOR no puede ser null"
+
+#: pl_exec.c:2737
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "el valor BY de un ciclo FOR no puede ser null"
+
+#: pl_exec.c:2743
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "el valor BY de un ciclo FOR debe ser mayor que cero"
+
+#: pl_exec.c:2877 pl_exec.c:4682
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "el cursor «%s» ya está en uso"
+
+#: pl_exec.c:2900 pl_exec.c:4752
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "se dieron argumentos a un cursor sin argumentos"
+
+#: pl_exec.c:2919 pl_exec.c:4771
+#, c-format
+msgid "arguments required for cursor"
+msgstr "se requieren argumentos para el cursor"
+
+#: pl_exec.c:3010
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "la expresión FOREACH no debe ser nula"
+
+#: pl_exec.c:3025
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "una expresión FOREACH debe retornar un array, no tipo %s"
+
+#: pl_exec.c:3042
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "la dimensión del slice (%d) está fuera de rango 0..%d"
+
+#: pl_exec.c:3069
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "las variables de bucles FOREACH ... SLICE deben ser de un tipo array"
+
+#: pl_exec.c:3073
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "la variable de bucle FOREACH no debe ser de tipo array"
+
+#: pl_exec.c:3235 pl_exec.c:3292 pl_exec.c:3467
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "no se puede retornar un valor no-compuesto desde una función que retorne tipos compuestos"
+
+#: pl_exec.c:3331 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "no se puede usar RETURN NEXT en una función que no es SETOF"
+
+#: pl_exec.c:3372 pl_exec.c:3504
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "se pasó un tipo incorrecto de resultado a RETURN NEXT"
+
+#: pl_exec.c:3410 pl_exec.c:3431
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "se pasó un tipo de registro incorrecto a RETURN NEXT"
+
+#: pl_exec.c:3523
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT debe tener un parámetro"
+
+#: pl_exec.c:3551 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "no se puede usar RETURN QUERY en una función que no ha sido declarada SETOF"
+
+#: pl_exec.c:3569
+msgid "structure of query does not match function result type"
+msgstr "la estructura de la consulta no coincide con el tipo del resultado de la función"
+
+#: pl_exec.c:3624 pl_exec.c:4459 pl_exec.c:8721
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "el argumento de consulta a ejecutar en EXECUTE es null"
+
+#: pl_exec.c:3709 pl_exec.c:3847
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "la opción de RAISE ya se especificó: %s"
+
+#: pl_exec.c:3743
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE sin parámetros no puede ser usado fuera de un manejador de excepción"
+
+#: pl_exec.c:3837
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "la opción de sentencia en RAISE no puede ser null"
+
+#: pl_exec.c:3907
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3962
+#, c-format
+msgid "assertion failed"
+msgstr "aseveración falló"
+
+#: pl_exec.c:4332 pl_exec.c:4521
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "no se puede ejecutar COPY desde/a un cliente en PL/pgSQL"
+
+#: pl_exec.c:4338
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "orden de transacción no soportada en PL/pgSQL"
+
+#: pl_exec.c:4361 pl_exec.c:4550
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO es utilizado con una orden que no puede retornar datos"
+
+#: pl_exec.c:4384 pl_exec.c:4573
+#, c-format
+msgid "query returned no rows"
+msgstr "la consulta no regresó filas"
+
+#: pl_exec.c:4406 pl_exec.c:4592 pl_exec.c:5744
+#, c-format
+msgid "query returned more than one row"
+msgstr "la consulta regresó más de una fila"
+
+#: pl_exec.c:4408
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Asegúrese que la consulta retorne una única fila, o use LIMIT 1."
+
+#: pl_exec.c:4424
+#, c-format
+msgid "query has no destination for result data"
+msgstr "la consulta no tiene un destino para los datos de resultado"
+
+#: pl_exec.c:4425
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Si quiere descartar los resultados de un SELECT, utilice PERFORM."
+
+#: pl_exec.c:4513
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "no está implementado EXECUTE de un SELECT ... INTO"
+
+#: pl_exec.c:4514
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Puede desear usar EXECUTE ... INTO o EXECUTE CREATE TABLE ... AS en su lugar."
+
+#: pl_exec.c:4527
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "no está implementado EXECUTE de órdenes de transacción"
+
+#: pl_exec.c:4837 pl_exec.c:4925
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "variable cursor «%s» es null"
+
+#: pl_exec.c:4848 pl_exec.c:4936
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "no existe el cursor «%s»"
+
+#: pl_exec.c:4861
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "la posición relativa o absoluta del cursor es null"
+
+#: pl_exec.c:5099 pl_exec.c:5194
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "no puede asignarse un valor null a la variable «%s» que fue declarada NOT NULL"
+
+#: pl_exec.c:5175
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "no se puede asignar un valor no compuesto a una variable de tipo row"
+
+#: pl_exec.c:5207
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "no se puede asignar un valor no compuesto a una variable de tipo record"
+
+#: pl_exec.c:5258
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "no se puede asignar a la columna de sistema «%s»"
+
+#: pl_exec.c:5707
+#, c-format
+msgid "query did not return data"
+msgstr "la consulta no retornó datos"
+
+#: pl_exec.c:5708 pl_exec.c:5720 pl_exec.c:5745 pl_exec.c:5821 pl_exec.c:5826
+#, c-format
+msgid "query: %s"
+msgstr "consulta: %s"
+
+#: pl_exec.c:5716
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "la consulta retornó %d columna"
+msgstr[1] "la consulta retornó %d columnas"
+
+#: pl_exec.c:5820
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "la consulta es SELECT INTO, pero debería ser un SELECT simple"
+
+#: pl_exec.c:5825
+#, c-format
+msgid "query is not a SELECT"
+msgstr "la consulta no es un SELECT"
+
+#: pl_exec.c:6635 pl_exec.c:6675 pl_exec.c:6715
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "el tipo del parámetro %d (%s) no coincide aquel con que fue preparado el plan (%s)"
+
+#: pl_exec.c:7126 pl_exec.c:7160 pl_exec.c:7234 pl_exec.c:7260
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "no coincide el número de campos de origen y destino en la asignación"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7128 pl_exec.c:7162 pl_exec.c:7236 pl_exec.c:7262
+#, c-format
+msgid "%s check of %s is active."
+msgstr "El chequeo %s de %s está activo."
+
+#: pl_exec.c:7132 pl_exec.c:7166 pl_exec.c:7240 pl_exec.c:7266
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Asegúrese que la consulta retorna la lista exacta de columnas."
+
+#: pl_exec.c:7653
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "el registro «%s» no ha sido asignado aún"
+
+#: pl_exec.c:7654
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "La estructura de fila de un registro aún no asignado no está determinado."
+
+#: pl_exec.c:8319 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "la variable «%s» esta declarada como CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "bloque de sentencias"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "asignación"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "bucle FOR con variable entera"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "bucle FOR en torno a filas de un SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "bucle FOR en torno a un cursor"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH en torno a un array"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "sentencia SQL"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "bucle FOR en torno a una sentencia EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "etiqueta de bloque debe estar antes de DECLARE, no después"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "los ordenamientos (collation) no están soportados por el tipo %s"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "la variable «%s» debe tener valor por omisión, puesto que está declarado NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "no existe la variable «%s»"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "declaración duplicada"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "la variable «%s» oculta una variable definida anteriormente"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "elemento de diagnóstico %s no se permite en GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "elemento de diagnóstico %s no se permite en GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "elemento de GET DIAGNOSTICS no reconocido"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "«%s» no es una variable escalar"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "la variable de bucle de un bucle sobre filas debe ser una variable de tipo record o una lista de variables escalares"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "un bucle FOR de un cursor debe tener sólo una variable de destino"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "un bucle FOR en torno a un cursor debe usar un cursor enlazado (bound)"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "un bucle FOR de un número entero debe tener sólo una variable de destino"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "no se puede especificar REVERSE en un bucle FOR de una consulta"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "la variable de bucle de FOREACH debe ser una variable conocida o una lista de variables conocidas"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "ningún bloque o bucle que contenga esta sentencia tiene una etiqueta «%s»"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "la etiqueta de bloque «%s» no puede usarse en CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT no puede usarse fuera de un bucle, a menos que tenga una etiqueta"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE no puede usarse fuera de un bucle"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "fin inesperado de la definición de la función"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "error de sintaxis"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "código SQLSTATE no válido"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "error de sintaxis, se esperaba «FOR»"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "la sentencia FETCH no puede retornar múltiples filas"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "variable de cursor debe ser una variable simple"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "la variable «%s» debe ser de tipo cursor o refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "«%s» no es una variable conocida"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "no coinciden los paréntesis"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "falta «%s» al final de la expresión SQL"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "falta «%s» al final de la sentencia SQL"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "expresión faltante"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "sentencia SQL faltante"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "declaración de tipo de dato incompleta"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "declaración de tipo de dato faltante"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO fue especificado más de una vez"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "se espera FROM o IN"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN no puede tener un parámetro en una función que retorna un conjunto"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Use RETURN NEXT o RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN no puede tener un parámetro un procedimiento"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN no puede tener parámetro en una función que retorna void"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN no puede tener parámetros en una función con parámetros OUT"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT no puede tener parámetros en una función con parámetros OUT"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "una variable de tipo record no puede ser parte de una lista INTO de múltiples elementos"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "se especificaron demasiadas variables INTO"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "etiqueta de término «%s» especificada para un bloque sin etiqueta"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "etiqueta de término «%s» difiere de la etiqueta de bloque «%s»"
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "el cursor «%s» no tiene argumentos"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "el cursor «%s» tiene argumentos"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "el cursor «%s» no tiene un argumento llamado «%s»"
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "el valor para el parámetro «%s» del cursor «%s» fue especificado más de una vez"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "no hay suficientes argumentos para el cursor «%s»"
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "demasiados argumentos para el cursor «%s»"
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "no se reconoce la opción de sentencia RAISE"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "error de sintaxis, se esperaba «=»"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "se especificaron demasiados parámetros a RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "se especificaron muy pocos parámetros a RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Determina el manejo de conflictos entre nombres de variables PL/pgSQL y nombres de columnas de tablas."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Imprimir información de parámetros en la parte DETALLE de los mensajes de error generados por fallos en INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Ejecuta las verificaciones en sentencias ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Listado de estructuras de programación que deben dar una advertencia."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Listado de estructuras de programación que deben dar un error."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s al final de la entrada"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s en o cerca de «%s»"
diff --git a/src/pl/plpgsql/src/po/fr.po b/src/pl/plpgsql/src/po/fr.po
new file mode 100644
index 0000000..19676ff
--- /dev/null
+++ b/src/pl/plpgsql/src/po/fr.po
@@ -0,0 +1,1004 @@
+# LANGUAGE message translation file for plpgsql
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpgsql (PostgreSQL) package.
+#
+# Use these quotes: « %s »
+#
+# Guillaume Lelarge <guillaume@lelarge.info>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-12 05:16+0000\n"
+"PO-Revision-Date: 2022-04-12 17:29+0200\n"
+"Last-Translator: Guillaume Lelarge <guillaume@lelarge.info>\n"
+"Language-Team: French <guillaume@lelarge.info>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "les fonctions PL/pgSQL ne peuvent pas accepter le type %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "n'a pas pu déterminer le type de retour pour la fonction polymorphique « %s »"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "les fonctions trigger peuvent seulement être appelées par des triggers"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "les fonctions PL/pgSQL ne peuvent pas renvoyer le type %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "les fonctions triggers ne peuvent pas avoir d'arguments déclarés"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "À la place, on peut accéder aux arguments du trigger par TG_NARGS et TG_ARGV."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "les fonctions triggers sur événement ne peuvent pas avoir des arguments déclarés"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "compilation de la fonction PL/pgSQL « %s » près de la ligne %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "le nom du paramètre « %s » est utilisé plus d'une fois"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "la référence à la colonne « %s » est ambigüe"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Cela pourrait faire référence à une variable PL/pgSQL ou à la colonne d'une table."
+
+#: pl_comp.c:1324 pl_exec.c:5216 pl_exec.c:5389 pl_exec.c:5476 pl_exec.c:5567
+#: pl_exec.c:6588
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "l'enregistrement « %s » n'a pas de champs « %s »"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "la relation « %s » n'existe pas"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "la relation « %s » n'a pas un type composite"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "la variable « %s » a le pseudo-type %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "le type « %s » n'est qu'une coquille"
+
+#: pl_comp.c:2204 pl_exec.c:6889
+#, c-format
+msgid "type %s is not composite"
+msgstr "le type %s n'est pas un type composite"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "condition d'exception non reconnue « %s »"
+
+#: pl_comp.c:2526
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "n'a pas pu déterminer le type d'argument pour la fonction polymorphique « %s »"
+
+#: pl_exec.c:500 pl_exec.c:939 pl_exec.c:1174
+msgid "during initialization of execution state"
+msgstr "durant l'initialisation de l'état de la fonction"
+
+#: pl_exec.c:506
+msgid "while storing call arguments into local variables"
+msgstr "lors du stockage des arguments dans les variables locales"
+
+#: pl_exec.c:594 pl_exec.c:1012
+msgid "during function entry"
+msgstr "durant l'entrée d'une fonction"
+
+#: pl_exec.c:617
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "le contrôle a atteint la fin de la fonction sans RETURN"
+
+#: pl_exec.c:623
+msgid "while casting return value to function's return type"
+msgstr "lors de la conversion de la valeur de retour au type de retour de la fonction"
+
+#: pl_exec.c:635 pl_exec.c:3656
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "la fonction renvoyant un ensemble a été appelée dans un contexte qui n'accepte pas un ensemble"
+
+#: pl_exec.c:640 pl_exec.c:3662
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "mode matérialisé requis mais interdit dans ce contexte"
+
+#: pl_exec.c:767 pl_exec.c:1038 pl_exec.c:1196
+msgid "during function exit"
+msgstr "lors de la sortie de la fonction"
+
+#: pl_exec.c:822 pl_exec.c:886 pl_exec.c:3455
+msgid "returned record type does not match expected record type"
+msgstr "le type d'enregistrement renvoyé ne correspond pas au type d'enregistrement attendu"
+
+#: pl_exec.c:1035 pl_exec.c:1193
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "le contrôle a atteint la fin de la procédure trigger sans RETURN"
+
+#: pl_exec.c:1043
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "une procédure trigger ne peut pas renvoyer un ensemble"
+
+#: pl_exec.c:1082 pl_exec.c:1110
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "la structure de ligne renvoyée ne correspond pas à la structure de la table du trigger"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1251
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "fonction PL/pgSQL %s, ligne %d, %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "fonction PL/pgSQL %s, %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1270
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "fonction PL/pgSQL %s, ligne %d à %s"
+
+#: pl_exec.c:1276
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "fonction PL/pgSQL %s"
+
+#: pl_exec.c:1647
+msgid "during statement block local variable initialization"
+msgstr "lors de l'initialisation des variables locales du bloc d'instructions"
+
+#: pl_exec.c:1752
+msgid "during statement block entry"
+msgstr "lors de l'entrée dans le bloc d'instructions"
+
+#: pl_exec.c:1784
+msgid "during statement block exit"
+msgstr "lors de la sortie du bloc d'instructions"
+
+#: pl_exec.c:1822
+msgid "during exception cleanup"
+msgstr "lors du nettoyage de l'exception"
+
+#: pl_exec.c:2355
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "le paramètre de la procédure « %s » est un argument en sortie mais l'argument correspondant n'est pas modifiable"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "le paramètre de la procédure %d est un paramètre en sortie mais l'argument correspondant n'est pas modifiable"
+
+#: pl_exec.c:2394
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS ne peut pas être utilisé à l'extérieur d'un gestionnaire d'exception"
+
+# /* SQL2003 mandates this error if there was no ELSE clause */
+# if (!stmt->have_else)
+# ereport(ERROR,
+# (errcode(ERRCODE_CASE_NOT_FOUND),
+# errmsg("case not found"),
+# errhint("CASE statement is missing ELSE part.")));
+#: pl_exec.c:2594
+#, c-format
+msgid "case not found"
+msgstr "cas introuvable"
+
+#: pl_exec.c:2595
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "l'instruction CASE n'a pas de partie ELSE."
+
+#: pl_exec.c:2688
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "la limite inférieure d'une boucle FOR ne peut pas être NULL"
+
+#: pl_exec.c:2704
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "la limite supérieure de la boucle FOR ne peut pas être NULL"
+
+#: pl_exec.c:2722
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "la valeur BY d'une boucle FOR ne peut pas être NULL"
+
+#: pl_exec.c:2728
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "la valeur BY d'une boucle FOR doit être plus grande que zéro"
+
+#: pl_exec.c:2862 pl_exec.c:4658
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "curseur « %s » déjà en cours d'utilisation"
+
+#: pl_exec.c:2885 pl_exec.c:4723
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "arguments fournis pour un curseur sans argument"
+
+#: pl_exec.c:2904 pl_exec.c:4742
+#, c-format
+msgid "arguments required for cursor"
+msgstr "arguments requis pour le curseur"
+
+#: pl_exec.c:2991
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "l'expression FOREACH ne doit pas être NULL"
+
+#: pl_exec.c:3006
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "l'expression FOREACH doit renvoyer un tableau, pas un type %s"
+
+#: pl_exec.c:3023
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "la dimension de la partie (%d) est en dehors des valeurs valides (0..%d)"
+
+#: pl_exec.c:3050
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "la variable d'une boucle FOREACH ... SLICE doit être d'un type tableau"
+
+#: pl_exec.c:3054
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "la valeur d'une boucle FOREACH ne doit pas être de type tableau"
+
+#: pl_exec.c:3216 pl_exec.c:3273 pl_exec.c:3448
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "ne peut pas renvoyer de valeurs non composites à partir d'une fonction renvoyant un type composite"
+
+#: pl_exec.c:3312 pl_gram.y:3318
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "ne peut pas utiliser RETURN NEXT dans une fonction non SETOF"
+
+#: pl_exec.c:3353 pl_exec.c:3485
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "mauvais type de résultat fourni dans RETURN NEXT"
+
+#: pl_exec.c:3391 pl_exec.c:3412
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "mauvais type d'enregistrement fourni à RETURN NEXT"
+
+#: pl_exec.c:3504
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT doit avoir un paramètre"
+
+#: pl_exec.c:3532 pl_gram.y:3382
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "ne peut pas utiliser RETURN QUERY dans une fonction non SETOF"
+
+#: pl_exec.c:3550
+msgid "structure of query does not match function result type"
+msgstr "la structure de la requête ne correspond pas au type de résultat de la fonction"
+
+#: pl_exec.c:3605 pl_exec.c:4435 pl_exec.c:8630
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "l'argument de la requête d'EXECUTE est NULL"
+
+#: pl_exec.c:3690 pl_exec.c:3828
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "option RAISE déjà spécifiée : %s"
+
+#: pl_exec.c:3724
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE sans paramètre ne peut pas être utilisé sans un gestionnaire d'exceptions"
+
+#: pl_exec.c:3818
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "l'option de l'instruction RAISE ne peut pas être NULL"
+
+#: pl_exec.c:3888
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3943
+#, c-format
+msgid "assertion failed"
+msgstr "échec de l'assertion"
+
+#: pl_exec.c:4308 pl_exec.c:4497
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "ne peut pas utiliser COPY vers/depuis un client en PL/pgSQL"
+
+#: pl_exec.c:4314
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "commande de transaction non supportée dans PL/pgSQL"
+
+#: pl_exec.c:4337 pl_exec.c:4526
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO utilisé dans une commande qui ne peut pas envoyer de données"
+
+#: pl_exec.c:4360 pl_exec.c:4549
+#, c-format
+msgid "query returned no rows"
+msgstr "la requête n'a renvoyé aucune ligne"
+
+#: pl_exec.c:4382 pl_exec.c:4568 pl_exec.c:5711
+#, c-format
+msgid "query returned more than one row"
+msgstr "la requête a renvoyé plus d'une ligne"
+
+#: pl_exec.c:4384
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Assurez-vous que la requête ne renvoie qu'une seule ligne ou utilisez LIMIT 1."
+
+#: pl_exec.c:4400
+#, c-format
+msgid "query has no destination for result data"
+msgstr "la requête n'a pas de destination pour les données résultantes"
+
+#: pl_exec.c:4401
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Si vous voulez ignorer le résultat d'un SELECT, utilisez PERFORM à la place."
+
+#: pl_exec.c:4489
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE n'est pas implementé pour SELECT ... INTO"
+
+#: pl_exec.c:4490
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Vous pouvez aussi utiliser EXECUTE ... INTO ou EXECUTE CREATE TABLE ... AS à la place."
+
+#: pl_exec.c:4503
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE pour les commandes de transactions n'est pas implémenté"
+
+#: pl_exec.c:4804 pl_exec.c:4892
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "la variable du curseur « %s » est NULL"
+
+#: pl_exec.c:4815 pl_exec.c:4903
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "le curseur « %s » n'existe pas"
+
+#: pl_exec.c:4828
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "la position relative ou absolue du curseur est NULL"
+
+#: pl_exec.c:5066 pl_exec.c:5161
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "une valeur NULL ne peut pas être affectée à la variable « %s » déclarée non NULL"
+
+#: pl_exec.c:5142
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "ne peut pas affecter une valeur non composite à une variable de type ROW"
+
+#: pl_exec.c:5174
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "ne peut pas affecter une valeur non composite à une variable RECORD"
+
+#: pl_exec.c:5225
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "ne peut pas affecter à une colonne système « %s »"
+
+#: pl_exec.c:5674
+#, c-format
+msgid "query did not return data"
+msgstr "la requête n'a pas renvoyé de données"
+
+#: pl_exec.c:5675 pl_exec.c:5687 pl_exec.c:5712 pl_exec.c:5788 pl_exec.c:5793
+#, c-format
+msgid "query: %s"
+msgstr "requête : %s"
+
+#: pl_exec.c:5683
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "la requête a renvoyé %d colonne"
+msgstr[1] "la requête a renvoyé %d colonnes"
+
+#: pl_exec.c:5787
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "la requête est SELECT INTO, alors qu'elle devrait être un simple SELECT"
+
+#: pl_exec.c:5792
+#, c-format
+msgid "query is not a SELECT"
+msgstr "la requête n'est pas un SELECT"
+
+#: pl_exec.c:6602 pl_exec.c:6642 pl_exec.c:6682
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "le type de paramètre %d (%s) ne correspond pas à celui préparé dans le plan (%s)"
+
+#: pl_exec.c:7093 pl_exec.c:7127 pl_exec.c:7201 pl_exec.c:7227
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "le nombre de champs source et celui de champs cible dans l'affectation ne correspondent pas"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7095 pl_exec.c:7129 pl_exec.c:7203 pl_exec.c:7229
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s vérification de %s est active."
+
+#: pl_exec.c:7099 pl_exec.c:7133 pl_exec.c:7207 pl_exec.c:7233
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Assurez-vous que la requête renvoie la liste exacte des colonnes."
+
+#: pl_exec.c:7620
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "l'enregistrement « %s » n'est pas encore affecté"
+
+#: pl_exec.c:7621
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "La structure de ligne d'un enregistrement pas encore affecté est indéterminée."
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "bloc d'instructions"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "affectation"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "variable entière de boucle FOR"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR sur des lignes de SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR sur un curseur"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH sur un tableau"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "instruction SQL"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR sur une instruction EXECUTE"
+
+#: pl_gram.y:486
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "le label du bloc doit être placé avant DECLARE, et non pas après"
+
+#: pl_gram.y:506
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "les collationnements ne sont pas supportés par le type %s"
+
+#: pl_gram.y:525
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "la variable « %s » doit avoir une valeur par défaut car elle est déclarée NOT NULL"
+
+#: pl_gram.y:673 pl_gram.y:688 pl_gram.y:714
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "la variable « %s » n'existe pas"
+
+#: pl_gram.y:732 pl_gram.y:760
+msgid "duplicate declaration"
+msgstr "déclaration dupliquée"
+
+#: pl_gram.y:743 pl_gram.y:771
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "la variable « %s » cache une variable définie précédemment"
+
+#: pl_gram.y:1043
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "l'élément %s de diagnostique n'est pas autorisé dans GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1061
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "l'élément %s de diagnostique n'est pas autorisé dans GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1156
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "élément GET DIAGNOSTICS non reconnu"
+
+#: pl_gram.y:1172 pl_gram.y:3557
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "« %s » n'est pas une variable scalaire"
+
+#: pl_gram.y:1402 pl_gram.y:1596
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "la variable d'une boucle sur des lignes doit être une variable de type record ou une liste de variables scalaires"
+
+#: pl_gram.y:1437
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "le curseur de la boucle FOR ne doit avoir qu'une seule variable cible"
+
+#: pl_gram.y:1444
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "le curseur de la boucle FOR doit utiliser une variable d'un curseur lié"
+
+#: pl_gram.y:1535
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "une boucle FOR de type entier ne doit avoir qu'une seule variable cible"
+
+#: pl_gram.y:1569
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "ne peut pas spécifier REVERSE dans la requête d'une boucle FOR"
+
+#: pl_gram.y:1699
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "la variable d'une boucle FOREACH doit être une variable connue ou une liste de variables"
+
+#: pl_gram.y:1741
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "il n'existe pas de label « %s » attaché à un bloc ou à une boucle englobant cette instruction"
+
+#: pl_gram.y:1749
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "le label de bloc « %s » ne peut pas être utilisé avec CONTINUE"
+
+#: pl_gram.y:1764
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT ne peut pas être utilisé à l'extérieur d'une boucle, sauf s'il a un label"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE ne peut pas être utilisé à l'extérieur d'une boucle"
+
+#: pl_gram.y:1789 pl_gram.y:1827 pl_gram.y:1875 pl_gram.y:3004 pl_gram.y:3092
+#: pl_gram.y:3203 pl_gram.y:3956
+msgid "unexpected end of function definition"
+msgstr "fin inattendue de la définition de la fonction"
+
+#: pl_gram.y:1895 pl_gram.y:1919 pl_gram.y:1935 pl_gram.y:1941 pl_gram.y:2066
+#: pl_gram.y:2074 pl_gram.y:2088 pl_gram.y:2183 pl_gram.y:2407 pl_gram.y:2497
+#: pl_gram.y:2655 pl_gram.y:3799 pl_gram.y:3860 pl_gram.y:3937
+msgid "syntax error"
+msgstr "erreur de syntaxe"
+
+#: pl_gram.y:1923 pl_gram.y:1925 pl_gram.y:2411 pl_gram.y:2413
+msgid "invalid SQLSTATE code"
+msgstr "code SQLSTATE invalide"
+
+#: pl_gram.y:2131
+msgid "syntax error, expected \"FOR\""
+msgstr "erreur de syntaxe, « FOR » attendu"
+
+#: pl_gram.y:2192
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "l'instruction FETCH ne peut pas renvoyer plusieurs lignes"
+
+#: pl_gram.y:2289
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "la variable de curseur doit être une variable simple"
+
+#: pl_gram.y:2295
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "la variable « %s » doit être de type cursor ou refcursor"
+
+#: pl_gram.y:2626 pl_gram.y:2637
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "« %s » n'est pas une variable connue"
+
+#: pl_gram.y:2743 pl_gram.y:2753 pl_gram.y:2909
+msgid "mismatched parentheses"
+msgstr "parenthèses non correspondantes"
+
+#: pl_gram.y:2757
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "« %s » manquant à la fin de l'expression SQL"
+
+#: pl_gram.y:2763
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "« %s » manquant à la fin de l'instruction SQL"
+
+#: pl_gram.y:2780
+msgid "missing expression"
+msgstr "expression manquante"
+
+#: pl_gram.y:2782
+msgid "missing SQL statement"
+msgstr "instruction SQL manquante"
+
+#: pl_gram.y:2911
+msgid "incomplete data type declaration"
+msgstr "déclaration incomplète d'un type de données"
+
+#: pl_gram.y:2934
+msgid "missing data type declaration"
+msgstr "déclaration manquante d'un type de données"
+
+#: pl_gram.y:3014
+msgid "INTO specified more than once"
+msgstr "INTO spécifié plus d'une fois"
+
+#: pl_gram.y:3184
+msgid "expected FROM or IN"
+msgstr "attendait FROM ou IN"
+
+#: pl_gram.y:3245
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN ne peut pas avoir de paramètre dans une fonction renvoyant un ensemble"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Utilisez RETURN NEXT ou RETURN QUERY."
+
+#: pl_gram.y:3256
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN ne peut pas avoir de paramètre dans une procédure"
+
+#: pl_gram.y:3261
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN ne peut pas avoir de paramètre dans une fonction renvoyant void"
+
+#: pl_gram.y:3270
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN ne peut pas avoir de paramètre dans une fonction avec des paramètres OUT"
+
+#: pl_gram.y:3333
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT ne peut pas avoir de paramètre dans une fonction avec des paramètres OUT"
+
+#: pl_gram.y:3441
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "la variable « %s » est déclarée CONSTANT"
+
+#: pl_gram.y:3499
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "la variable de type record ne peut pas faire partie d'une liste INTO à plusieurs éléments"
+
+#: pl_gram.y:3545
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "trop de variables INTO indiquées"
+
+#: pl_gram.y:3753
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "label de fin « %s » spécifié pour un bloc sans label"
+
+#: pl_gram.y:3760
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "le label de fin « %s » est différent du label « %s » du bloc"
+
+#: pl_gram.y:3794
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "le curseur « %s » n'a pas d'argument"
+
+#: pl_gram.y:3808
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "le curseur « %s » a des arguments"
+
+#: pl_gram.y:3850
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "le curseur « %s » n'a pas d'argument nommé « %s »"
+
+#: pl_gram.y:3870
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "la valeur du paramètre « %s » pour le curseur « %s » est spécifiée plus d'une fois"
+
+#: pl_gram.y:3895
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "pas assez d'arguments pour le curseur « %s »"
+
+#: pl_gram.y:3902
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "trop d'arguments pour le curseur « %s »"
+
+#: pl_gram.y:3988
+msgid "unrecognized RAISE statement option"
+msgstr "option de l'instruction RAISE inconnue"
+
+#: pl_gram.y:3992
+msgid "syntax error, expected \"=\""
+msgstr "erreur de syntaxe, « = » attendu"
+
+#: pl_gram.y:4033
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "trop de paramètres spécifiés pour RAISE"
+
+#: pl_gram.y:4037
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "trop peu de paramètres pour RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Configure la gestion des conflits entre les noms de variables PL/pgSQL et les noms des colonnes des tables."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Affiche des informations sur les paramètres dans la partie DETAIL des messages d'erreur générés pour des échecs INTO .. STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Réalise les vérifications données dans les instructions ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Liste des constructions de programmation qui devraient produire un message d'avertissement."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Liste des constructions de programmation qui devraient produire une erreur."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s à la fin de l'entrée"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s sur ou près de « %s »"
+
+#~ msgid "EXECUTE statement"
+#~ msgstr "instruction EXECUTE"
+
+#~ msgid "Expected \"FOR\", to open a cursor for an unbound cursor variable."
+#~ msgstr "Attendait « FOR » pour ouvrir un curseur pour une variable sans limite."
+
+#~ msgid "Expected record variable, row variable, or list of scalar variables following INTO."
+#~ msgstr ""
+#~ "Attendait une variable RECORD, ROW ou une liste de variables scalaires\n"
+#~ "suivant INTO."
+
+#~ msgid "N/A (dropped column)"
+#~ msgstr "N/A (colonne supprimée)"
+
+#~ msgid "Number of returned columns (%d) does not match expected column count (%d)."
+#~ msgstr ""
+#~ "Le nombre de colonnes renvoyées (%d) ne correspond pas au nombre de colonnes\n"
+#~ "attendues (%d)."
+
+#~ msgid "RETURN NEXT must specify a record or row variable in function returning row"
+#~ msgstr ""
+#~ "RETURN NEXT doit indiquer une variable RECORD ou ROW dans une fonction\n"
+#~ "renvoyant une ligne"
+
+#~ msgid "RETURN cannot have a parameter in function returning set; use RETURN NEXT or RETURN QUERY"
+#~ msgstr ""
+#~ "RETURN ne peut pas avoir un paramètre dans une fonction renvoyant des\n"
+#~ "lignes ; utilisez RETURN NEXT ou RETURN QUERY"
+
+#~ msgid "RETURN must specify a record or row variable in function returning row"
+#~ msgstr ""
+#~ "RETURN ne peut pas indiquer une variable RECORD ou ROW dans une fonction\n"
+#~ "renvoyant une ligne"
+
+#~ msgid "Returned type %s does not match expected type %s in column \"%s\"."
+#~ msgstr "Le type %s renvoyé ne correspond pas au type %s attendu dans la colonne « %s »."
+
+#~ msgid "SQL statement in PL/PgSQL function \"%s\" near line %d"
+#~ msgstr "instruction SQL dans la fonction PL/pgsql « %s » près de la ligne %d"
+
+#~ msgid "Use a BEGIN block with an EXCEPTION clause instead."
+#~ msgstr "Utiliser un bloc BEGIN dans une clause EXCEPTION à la place."
+
+#~ msgid "array subscript in assignment must not be null"
+#~ msgstr "un indice de tableau dans une affectation ne peut pas être NULL"
+
+#~ msgid "cannot assign to tg_argv"
+#~ msgstr "ne peut pas affecter à tg_argv"
+
+#~ msgid "cursor \"%s\" closed unexpectedly"
+#~ msgstr "le curseur « %s » a été fermé de façon inattendu"
+
+#~ msgid "default value for row or record variable is not supported"
+#~ msgstr "la valeur par défaut de variable ROW ou RECORD n'est pas supportée"
+
+#~ msgid "expected \")\""
+#~ msgstr "« ) » attendu"
+
+#~ msgid "expected \"[\""
+#~ msgstr "« [ » attendu"
+
+#~ msgid "expected a cursor or refcursor variable"
+#~ msgstr "attendait une variable de type cursor ou refcursor"
+
+#~ msgid "expected an integer variable"
+#~ msgstr "attend une variable entière"
+
+#~ msgid "function has no parameter \"%s\""
+#~ msgstr "la fonction n'a pas de paramètre « %s »"
+
+#~ msgid "label does not exist"
+#~ msgstr "le label n'existe pas"
+
+#~ msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+#~ msgstr "le nombre de dimensions du tableau (%d) dépasse la maximum autorisé (%d)"
+
+#~ msgid "only positional parameters can be aliased"
+#~ msgstr "seuls les paramètres de position peuvent avoir un alias"
+
+#~ msgid "qualified identifier cannot be used here: %s"
+#~ msgstr "l'identifiant qualifié ne peut pas être utilisé ici : %s"
+
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "la requête « %s » a renvoyé plus d'une ligne"
+
+#~ msgid "relation \"%s\" is not a table"
+#~ msgstr "la relation « %s » n'est pas une table"
+
+#~ msgid "relation \"%s.%s\" does not exist"
+#~ msgstr "la relation « %s.%s » n'existe pas"
+
+#~ msgid "row \"%s\" has no field \"%s\""
+#~ msgstr "la ligne « %s » n'a aucun champ « %s »"
+
+#~ msgid "row \"%s.%s\" has no field \"%s\""
+#~ msgstr "la ligne « %s.%s » n'a aucun champ « %s »"
+
+#~ msgid "row or record variable cannot be CONSTANT"
+#~ msgstr "la variable ROW ou RECORD ne peut pas être CONSTANT"
+
+#~ msgid "row or record variable cannot be NOT NULL"
+#~ msgstr "la variable ROW ou RECORD ne peut pas être NOT NULL"
+
+#~ msgid "string literal in PL/PgSQL function \"%s\" near line %d"
+#~ msgstr "chaîne littérale dans la fonction PL/pgsql « %s » près de la ligne %d"
+
+#~ msgid "subscripted object is not an array"
+#~ msgstr "l'objet souscrit n'est pas un tableau"
+
+#~ msgid "syntax error at \"%s\""
+#~ msgstr "erreur de syntaxe à « %s »"
+
+#~ msgid "too many variables specified in SQL statement"
+#~ msgstr "trop de variables spécifiées dans l'instruction SQL"
+
+#~ msgid "type of \"%s\" does not match that when preparing the plan"
+#~ msgstr "le type de « %s » ne correspond pas à ce qui est préparé dans le plan"
+
+#~ msgid "type of \"%s.%s\" does not match that when preparing the plan"
+#~ msgstr "le type de « %s.%s » ne correspond pas à ce qui est préparé dans le plan"
+
+#~ msgid "type of tg_argv[%d] does not match that when preparing the plan"
+#~ msgstr "le type de tg_argv[%d] ne correspond pas à ce qui est préparé dans le plan"
+
+#~ msgid "unterminated \" in identifier: %s"
+#~ msgstr "\" non terminé dans l'identifiant : %s"
+
+#~ msgid "unterminated /* comment"
+#~ msgstr "commentaire /* non terminé"
+
+#~ msgid "unterminated dollar-quoted string"
+#~ msgstr "chaîne entre dollars non terminée"
+
+#~ msgid "unterminated quoted identifier"
+#~ msgstr "identifiant entre guillemets non terminé"
+
+#~ msgid "unterminated quoted string"
+#~ msgstr "chaîne entre guillemets non terminée"
+
+#~ msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+#~ msgstr "la variable « %s » déclarée NOT NULL ne peut pas valoir NULL par défaut"
+
+#~ msgid "variable \"%s\" does not exist in the current block"
+#~ msgstr "la variable « %s » n'existe pas dans le bloc actuel"
diff --git a/src/pl/plpgsql/src/po/it.po b/src/pl/plpgsql/src/po/it.po
new file mode 100644
index 0000000..a58d1cb
--- /dev/null
+++ b/src/pl/plpgsql/src/po/it.po
@@ -0,0 +1,874 @@
+#
+# plpgsql.po
+# Italian message translation file for plpgsql
+#
+# For development and bug report please use:
+# https://github.com/dvarrazzo/postgresql-it
+#
+# Copyright (C) 2012-2017 PostgreSQL Global Development Group
+# Copyright (C) 2010, Associazione Culturale ITPUG
+#
+# Daniele Varrazzo <daniele.varrazzo@gmail.com>, 2012-2017.
+# Diego Cinelli <diego.cinelli@itpug.org>
+#
+# This file is distributed under the same license as the PostgreSQL package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-26 08:09+0000\n"
+"PO-Revision-Date: 2022-09-26 15:08+0200\n"
+"Last-Translator: Daniele Varrazzo <daniele.varrazzo@gmail.com>\n"
+"Language-Team: https://github.com/dvarrazzo/postgresql-it\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Poedit-SourceCharset: utf-8\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "Le funzioni PL/pgSQL non accettano il tipo %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "determinazione del tipo di ritorno reale per la funzione polimorfa \"%s\" fallita"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "le funzioni trigger possono essere chiamate esclusivamente da trigger"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "Le funzioni PL/pgSQL non possono restituire un tipo %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "le funzioni trigger non possono avere argomenti dichiarati"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Gli argomenti del trigger possono essere acceduti tramite TG_NARGS e TG_ARGV invece."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "le funzioni trigger di evento non possono avere argomenti dichiarati"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "compilazione della funzione PL/pgSQL \"%s\" in prossimità della riga %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "il nome di parametro \"%s\" è usato più di una volta"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "il riferimento alla colonna \"%s\" è ambiguo"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Può riferirsi o ad una variabile PL/pgSQL o ad una colonna di tabella."
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "il record \"%s\" non ha un campo \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "la relazione \"%s\" non esiste"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "la relazione \"%s\" non ha un tipo composto"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "la variabile \"%s\" ha lo pseudo-tipo %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "il tipo \"%s\" non è completamente definito"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "il tipo %s non è composito"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "condizione di eccezione \"%s\" sconosciuta"
+
+#: pl_comp.c:2534
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "il tipo reale dell'argomento non è determinabile per la funzione polimorfa \"%s\""
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "durante l'inizializzazione dello stato di esecuzione"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "durante la memorizzazione degli argomenti di chiamata in variabili locali"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "durante l'ingresso nella funzione"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "il controllo ha raggiunto la fine di una funzione senza incontrare alcun RETURN"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "durante la conversione del valore da restituire nel tipo restituito della funzione"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "la funzione che restituisce insiemi è chiamata in un contesto che non può accettare un insieme"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "necessaria modalità materializzata, ma non ammessa in questo contesto"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "durante l'uscita della funzione"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "il tipo del record restituito non coincide con quello atteso"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "il controllo ha raggiunto la fine di una procedura trigger senza incontrare alcun RETURN"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "la procedura trigger non può restituire un insieme"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "la struttura della riga restituita non coincide con la struttura della tabella che ha generato il trigger"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "funzione PL/pgSQL %s riga %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "funzione PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "funzione PL/pgSQL %s riga %d a %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "funzione PL/pgSQL %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "durante l'inizializzazione di variabili locali del blocco di istruzioni"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "durante l'entrata nel blocco di istruzioni"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "durante l'uscita dal blocco di istruzioni"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "durante la pulizia delle eccezioni"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "il parametro di procedura \"%s\" è un parametro di output ma l'argomento corrispondente non è scrivibile"
+
+#: pl_exec.c:2365
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "il parametro di procedura %d è un parametro di output ma l'argomento corrispondente non è scrivibile"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS non può essere usato fuori da un gestore di eccezioni"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "caso non trovato"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "all'istruzione CASE manca la parte ELSE."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "il limite inferiore di un ciclo FOR non può essere nullo"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "il limite superiore di un ciclo FOR non può essere null"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "il valore BY di un ciclo FOR non può essere null"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "il valore BY di un ciclo FOR deve essere maggiore di zero"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "il cursore \"%s\" è già in uso"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "sono stati passati argomenti al cursore che non ne accetta"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "sono richiesti argomenti per il cursore"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "l'espressione FOREACH non può essere vuota"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "l'espressione FOREACH deve generare un array, non il tipo %s"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "la dimensione della sezione (%d) è fuori dell'intervallo valido 0..%d"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "la variabile del ciclo FOREACH ... SLICE dev'essere di tipo array"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "la variabile di ciclo FOREACH non può essere un tipo array"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "non è possibile restituire valori non compositi da una funzione che restituisce un tipo composito"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "non si può usare RETURN NEXT in una funzione non-SETOF"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "è stato fornito un risultato di tipo non corretto a RETURN NEXT"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "è stato fornito un record di tipo non corretto a RETURN NEXT"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT deve avere un parametro"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "non si può usare RETURN QUERY in una funzione non-SETOF"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "la struttura della query non coincide con il tipo del risultato della funzione"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "l'argomento della query di EXECUTE è nullo"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "opzione RAISE già specificata: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE senza parametri non può essere usato all'esterno di un gestore di eccezioni"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "l'opzione dell'istruzione RAISE non può essere nulla"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "asserzione fallita"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "non è possibile usare COPY verso/da un client in PL/pgSQL"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "comando di transazione non supportato in PL/pgSQL"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO usato con un comando che non restituisce alcun dato"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "la query non ha restituito alcuna riga"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "la query ha restituito più di una riga"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Assicurati che la query restituisca una singola riga o usa LIMIT 1."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "la query non ha una destinazione per i dati restituiti"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Se vuoi scartare i risultati di una SELECT, utilizza PERFORM."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE di SELECT ... INTO non è implementato"
+
+#: pl_exec.c:4499
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Potresti usare invece EXECUTE ... INTO oppure EXECUTE CREATE TABLE ... AS."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "l'EXECUTE dei comandi di transazione non è implementato"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "la variabile del cursore \"%s\" è nulla"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "il cursore \"%s\" non esiste"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "la posizione relativa o assoluta del cursore è nulla"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "il valore null non può essere assegnato alla variabile \"%s\" dichiarata NOT NULL"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "non è possibile assegnare un valore non composito ad una variabile di tipo row"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "non è possibile assegnare un valore non composito ad una variabile di tipo record"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "non è possibile assegnare alla colonna di sistema \"%s\""
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "la query non ha restituito dati"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "query: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "la query ha restituito %d colonna"
+msgstr[1] "la query ha restituito %d colonne"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "la query è SELECT INTO, ma dovrebbe essere semplice SELECT"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "la query non è una SELECT"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "il tipo del parametro %d (%s) non combacia con quello usato preparando il piano (%s)"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "il numero di campi di origine e di destinazione nell'assegnazione non corrisponde"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s il controllo di %s è attivo."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Assicurati che la query restituisca l'esatto elenco di colonne."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "il record \"%s\" non è stato ancora assegnato"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "La struttura della tupla di un record non ancora assegnato è indeterminata."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "la variabile \"%s\" è dichiarata CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "blocco di istruzioni"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "assegnazione"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "ciclo FOR con variabile di ciclo intera"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "ciclo FOR su righe SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "ciclo FOR su cursore"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH su array"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "istruzione SQL"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "ciclo FOR su una istruzione EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "l'etichetta del blocco dev'essere messa prima di DECLARE, non dopo"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "gli ordinamenti non sono supportati dal tipo %s"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "la variabile \"%s\" deve avere un valore di default, perché è dichiarata NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "la variabile \"%s\" non esiste"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "dichiarazione duplicata"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "la variabile \"%s\" nasconde una variabile definita precedentemente"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "l'elemento diagnostico %s non è consentito in GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "l'elemento diagnostico %s non è consentito in GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "elemento GET DIAGNOSTICS sconosciuto"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" non è una variabile scalare"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "la variabile di loop di un loop su righe deve essere una variabile record o una lista di variabili scalari"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "il cursore FOR nel ciclo deve avere solo una variabile di destinazione"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "il cursore FOR nel ciclo deve usare una variabile cursore vincolata"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "il valore integer del ciclo FOR deve avere solo una variabile di destinazione"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "non puoi specificare REVERSE nel ciclo FOR della query"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "la variabile del ciclo FOREACH dev'essere una variabile o lista di variabili conosciuta"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "non c'è un'etichetta \"%s\" collegata ad alcun blocco o loop contenente questa istruzione"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "l'etichetta di blocco \"%s\" non può essere usata con CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT non può essere usato fuori da un loop, a meno che non abbia un'etichetta"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE non può essere usato all'esterno di un ciclo"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "fine della definizione della funzione inaspettata"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "errore di sintassi"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "codice SQLSTATE non valido"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "errore di sintassi, atteso \"FOR\""
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "l'istruzione FETCH non può restituire più di una riga"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "la variabile cursore deve essere una variabile semplice"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "la variabile \"%s\" deve essere di tipo cursor o refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" non è una variabile conosciuta"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "le parentesi non corrispondono"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "manca \"%s\" alla fine della espressione SQL"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "manca \"%s\" alla fine dell'istruzione SQL"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "espressione mancante"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "istruzione SQL mancante"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "dichiarazione del tipo di dati incompleta"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "manca la dichiarazione del tipo di dati"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO specificato più di una volta"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "atteso FROM o IN"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN non può avere un parametro in una funzione che restituisce insiemi"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Usa RETURN NEXT o RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN non può avere un parametro in una procedura"
+
+# Il fatto che una funzione che restituisce void sia chiamato "procedura" è un visual-basic-ismo che si può dimenticare
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN non può avere un parametro in una funzione che restituisce void"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN non può avere un parametro in una funzione con parametri OUT"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT non può avere un parametro in una funzione con parametri OUT"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "una variabile record non può essere parte di una lista INTO con più di un elemento"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "troppe variabili INTO specificate"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "etichetta finale \"%s\" specificata per un blocco senza etichetta"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "l'etichetta finale \"%s\" differisce da quella del blocco \"%s\""
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "il cursore \"%s\" non ha argomenti"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "il cursore \"%s\" ha argomenti"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "il cursore \"%s\" non ha un argomento di nome \"%s\""
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "il valore per il parametro \"%s\" del cursore \"%s\" è stato specificato più di una volta"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "numero di argomenti non sufficiente per il cursore \"%s\""
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "troppi argomenti per il cursore \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "opzione dell'istruzione RAISE sconosciuta"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "errore di sintassi, atteso \"=\""
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "troppi parametri specificati per RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "numero di parametri non sufficiente specificati per RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Imposta la gestione dei conflitti tra nomi di variabili PL/pgSQL e nomi di colonne di tabella."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Stampa informazioni sui parametri nella parte DETAIL del messaggio di errore generato su errori in INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Effettua i controlli dati nelle istruzioni ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Elenco dei costrutti di programmazione che dovrebbero generare un avvertimento."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Elenco dei costrutti di programmazione che dovrebbero generare un errore."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s alla fine dell'input"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s a o presso \"%s\""
+
+#~ msgid "array subscript in assignment must not be null"
+#~ msgstr "l'indice di un array nell'assegnamento non può essere nullo"
+
+#~ msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+#~ msgstr "il numero di dimensioni dell'array (%d) eccede il massimo consentito (%d)"
+
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "la query \"%s\" ha restituito più di una riga"
+
+#~ msgid "subscripted object is not an array"
+#~ msgstr "l'oggetto del quale è stato richiesto un elemento non è un array"
diff --git a/src/pl/plpgsql/src/po/ja.po b/src/pl/plpgsql/src/po/ja.po
new file mode 100644
index 0000000..82da3a4
--- /dev/null
+++ b/src/pl/plpgsql/src/po/ja.po
@@ -0,0 +1,879 @@
+# Japanese message translation file for plpgsql
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_archivecleanup (PostgreSQL) package.
+# HOTTA Michihde <hotta@net-newbie.com>, 2013
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL 15)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-09 12:01+0900\n"
+"PO-Revision-Date: 2022-05-11 13:52+0900\n"
+"Last-Translator: Kyotaro Horiguchi <horikyota.ntt@gmail.com>\n"
+"Language-Team: jpug-doc <jpug-doc@ml.postgresql.jp>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.8.13\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL 関数では %s 型は指定できません"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "多相関数\"%s\"の実際の戻り値の型を特定できませんでした"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "トリガー関数はトリガーとしてのみコールできます"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL 関数は %s 型を返せません"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "トリガー関数には引数を宣言できません"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "その代わり、トリガーの引数には TG_NARGS と TG_ARGV を通してのみアクセスできます"
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "イベントトリガー関数では引数を宣言できません"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "PL/pgSQL 関数 \"%s\" の %d 行目付近でのコンパイル"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "パラメータ \"%s\" が複数指定されました"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "列参照 \"%s\" が一意に特定できません"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "PL/pgSQL 変数もしくはテーブルのカラム名のどちらかを参照していた可能性があります。"
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "レコード \"%s\" には項目 \"%s\" はありません"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "リレーション \"%s\" がありません"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "リレーション\"%s\"は複合型を持っていません"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "変数 \"%s\" の型は擬似タイプ %s です"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "型 \"%s\" はシェルでのみ使えます"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "型%sは複合型ではありません"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "例外条件 \"%s\" が認識できません"
+
+#: pl_comp.c:2526
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "多相関数\"%s\"の実際の引数の型を特定できませんでした"
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "実行状態の初期化の際"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "引数をローカル変数に格納する際"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "関数に入る際"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "RETURN が現れる前に、制御が関数の終わりに達しました"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "戻り値を関数の戻り値の型へキャストする際に"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "値の集合を受け付けないようなコンテキストで、集合値を返す関数が呼ばれました"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "マテリアライズモードが必要ですが、現在のコンテクストで禁止されています"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "関数を抜ける際"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "返されたレコードの型が期待するレコードの型と一致しません"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "RETURN が現れる前に、制御がトリガープロシージャの終わりに達しました"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "トリガー手続きは集合値を返すことができません"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "返された行の構造が、トリガーしているテーブルの構造とマッチしません"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL関数%sの%d行目 %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL関数%s - %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL関数%sの%d行目 - %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL関数 %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "ステートメントブロックでローカル変数を初期化中"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "ステートメントブロックに入る際に"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "ステートメントブロックを抜ける際に"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "例外をクリーンアップする際に"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "プロシージャのパラメータ\"%s\"は出力パラメータですが対応する引数が書き込み不可です"
+
+#: pl_exec.c:2365
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "プロシージャのパラメータ%dは出力パラメータですが対応する引数が書き込み不可です"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS は例外ハンドラの外では使えません"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "case が見つかりません"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE ステートメントに ELSE 部分がありません"
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "FOR ループの下限を NULL にすることはできません"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "FOR ループの上限を NULL にすることはできません"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "FOR ループにおける BY の値を NULL にすることはできません"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "FOR ループにおける BY の値はゼロより大きくなければなりません"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "カーソル \"%s\" はすでに使われています"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "引数なしのカーソルに引数が与えられました"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "カーソルには引数が必要です"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH 式は NULL であってはなりません"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH 式は %s 型ではなく配列を生成しなければなりません"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "配列の要素数 (%d) が有効範囲0から%dまでの間にありません"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE ループ変数は配列型でなければなりません"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH ループ変数は配列型であってはなりません"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "複合型を返す関数から複合型以外の値を返すことはできません"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "SETOF でない関数では RETURN NEXT は使えません"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "RETURN NEXT で指定されている結果の型が誤っています"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "RETURN NEXT で指定されているレコードの型が誤っています"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT にはパラメーターが必要です"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "SETOF でない関数では RETURN QUERY は使えません"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "問い合わせの構造が関数の結果の型と一致しません"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "EXECUTE の問い合わせ文字列の引数が NULL です"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE オプションは既に指定されています: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "引数の無い RAISE は、例外ハンドラの外では使えません"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE ステートメントのオプションには NULL は指定できません"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "アサーションに失敗"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "PL/pgSQL 内では COPY to/from クライアントは使えません"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "PL/pgSQL 内ではサポートされないトランザクションコマンド"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "データを返せないコマンドで INTO が使われました"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "問い合わせは行を返しませんでした"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "問い合わせが複数の行を返しました"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "問い合わせを1行返却するようにするか、LIMIT 1 をつけてください。"
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "問い合わせに結果データの返却先が指定されていません"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "SELECT の結果を破棄したい場合、代わりに PERFORM を使ってください"
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "SELECT ... INTO の EXECUTE は実装されていません"
+
+#: pl_exec.c:4499
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "代わりに EXECUTE ... INTO または EXECUTE CREATE TABLE ... AS が使えます。"
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "トランザクションコマンドのEXECUTEは実装されていません"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "カーソル変数 \"%s\" が NULL です"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "カーソル \"%s\" は存在しません"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "相対もしくは絶対カーソル位置が NULL です"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "NOT NULL として宣言された変数 \"%s\" には NULL を代入できません"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "複合型でない値を行変数に代入できません"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "複合型でない値をレコード変数に代入できません"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "システム列\"%s\"に代入できません"
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "問い合わせがデータを返しませんでした"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "問い合わせ: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "問い合わせが%d個の列を返しました"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "問い合わせはSELECT INTOですが、単純なSELECTでなければなりません"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "問い合わせがSELECTではありません"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "パラメータの型%d(%s)が実行計画(%s)を準備する時点と一致しません"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "代入のソースとターゲットのフィールド数が一致していません"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%2$sの%1$sチェックが有効です。"
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "問い合わせはカラムの正確なリストを返却するようにしてください。"
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "レコード \"%s\" にはまだ値が代入されていません"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "まだ代入されていないレコードのタプル構造は不定です"
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "変数\"%s\" はCONSTANTとして定義されています"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "ステートメントブロック"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "代入"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "整数のループ変数を使った FOR"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "SELECT 行を使った FOR"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "カーソルを使った FOR"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "配列を巡回する FOREACH"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL ステートメント"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "EXECUTE ステートメントを使った FOR"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "ブロックラベルは DECLARE の後ではなく前に置かなければなりません"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "%s 型では照合順序はサポートされていません"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "NOT NULL宣言されているため、変数\"%s\"はデフォルト値を持つ必要があります"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "変数 \"%s\" は存在しません"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "重複した宣言です。"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "変数 \"%s\" が事前に定義された変数を不可視にしています"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "GET STACKED DIAGNOSTICS では診断項目 %s は許可されていません"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "GET CURRENT DIAGNOSTICS では診断項目 %s は許可されていません"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "GET DIAGNOSTICS 項目が認識できません"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" はスカラー変数ではありません"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "行に対するループでのループ変数は、レコード変数またはスカラー変数のリストでなければなりません"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "カーソルを使った FOR ループには、ターゲット変数が1個だけ必要です"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "カーソルを使った FOR ループでは、それに関連付けられたカーソル変数を使用しなければなりません"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "整数を使った FOR ループには、ターゲット変数が1個だけ必要です"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "問い合わせを使った FOR ループの中では REVERSE は指定できません"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "FOREACH のループ変数は、既知の変数または変数のリストでなければなりません"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "このステートメントを囲むブロックやループに割り当てられた \"%s\" というラベルはありません。"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "ブロックラベル \"%s\" は CONTINUE の中では使えません。"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "ラベルのない EXIT は、ループの外では使えません"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE はループの外では使えません"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "予期しない関数定義の終端に達しました"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "構文エラー"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "無効な SQLSTATE コードです"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "構文エラー。\"FOR\" が現れるべきでした。"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH ステートメントは複数行を返せません"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "カーソル変数は単純変数でなければなりません"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "変数 \"%s\" は cursor 型または refcursor 型でなければなりません"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" は既知の変数ではありません"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "括弧が対応していません"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "SQL 表現式の終わりに \"%s\" がありません"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "SQL ステートメントの終わりに \"%s\" がありません"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "表現式がありません"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "SQL ステートメントがありません"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "データ型の定義が不完全です"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "データ型の定義がありません"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO が複数回指定されています"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "FROM もしくは IN が来るべきでした"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "集合を返す関数では、RETURN にパラメータを指定できません"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "RETURN NEXT もしくは RETURN QUERY を使用してください"
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "プロシージャないのRETURNはパラメータを取ることができません"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "void を返す関数では、RETURN にパラメータを指定できません"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "OUT パラメータのない関数では、RETURN にパラメータを指定できません"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "OUT パラメータ付きの関数では、RETURN NEXT にパラメータを指定できません"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "レコード変数は、複数項目を持つ INTO リストでは使えません"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "INTO 変数の指定が多すぎます"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "終端ラベル\"%s\"がラベルなしのブロックに対して指定されました"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "終端ラベル \"%s\" がブロックのラベル \"%s\" と異なります"
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "カーソル \"%s\" に引数がありません"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "カーソル \"%s\" に引数がついています"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "カーソル \"%s\" に \"%s\" という名前の引数がありません"
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "カーソル \"%2$s\" のパラメータ \"%1$s\" の値が複数個指定されました"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "カーソル \"%s\" の引数が不足しています"
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "カーソル \"%s\" に対する引数が多すぎます"
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "RAISE ステートメントのオプションを認識できません"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "構文エラー。\"=\" を期待していました"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "RAISE に指定されたパラメーターの数が多すぎます"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "RAISE に指定されたパラメーターの数が足りません"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "PL/pgSQL 変数名とテーブルのカラム名の間の衝突時処理を設定します。"
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "INTO ... STRICT 失敗時に生成されたエラーメッセージの DETAIL 部分のパラメーター情報を表示します。"
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "ASSERT ステートメントで指定されたチェックを実行します。"
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "生成されたプログラムの中で、警告を発生すべき部分の一覧です。"
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "生成されたプログラムの中で、エラーを発生すべき部分の一覧です。"
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "入力の最後で %s"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "\"%2$s\" もしくはその近辺で %1$s"
+
+#~ msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+#~ msgstr "配列の次元数(%d)が制限値(%d)を超えています"
+
+#~ msgid "subscripted object is not an array"
+#~ msgstr "添字つきオブジェクトは配列ではありません"
+
+#~ msgid "array subscript in assignment must not be null"
+#~ msgstr "代入における配列の添字が NULL であってはなりません"
+
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "問い合わせ \"%s\" が複数の行を返しました"
+
+#~ msgid "relation \"%s\" is not a table"
+#~ msgstr "リレーション \"%s\" はテーブルではありません"
+
+#~ msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+#~ msgstr "変数 \"%s\" は NOT NULL として宣言されているため、デフォルト値を NULL にすることはできません"
+
+#~ msgid "Use a BEGIN block with an EXCEPTION clause instead."
+#~ msgstr "代わりに EXCEPTION 句を伴う BEGIN ブロックを使用してください"
+
+#~ msgid "row or record variable cannot be CONSTANT"
+#~ msgstr "行またはレコード変数は CONSTANT にはできません"
+
+#~ msgid "row or record variable cannot be NOT NULL"
+#~ msgstr "行またはレコード変数を NOT NULL にはできません"
+
+#~ msgid "default value for row or record variable is not supported"
+#~ msgstr "行またはレコード変数のデフォルト値指定はサポートされていません"
diff --git a/src/pl/plpgsql/src/po/ka.po b/src/pl/plpgsql/src/po/ka.po
new file mode 100644
index 0000000..be83a3e
--- /dev/null
+++ b/src/pl/plpgsql/src/po/ka.po
@@ -0,0 +1,906 @@
+# Georgian message translation file for plpgsql
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpgsql (PostgreSQL) package.
+# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-07-02 04:39+0000\n"
+"PO-Revision-Date: 2022-10-15 20:11+0200\n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
+"Language-Team: Georgian <nothing>\n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL ფუნქციებს არ შეუძლიათ მიიღონ ტიპი %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "პოლიმორფული ფუნქციის დაბრუნების ტიპის გამოცნობა შეუძლებელია: %s"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "ტრიგერის ფუნქციების გამოძახება მხოლოდ ტრიგერებად შეიძლება"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL ფუნქციებს არ შეუძლიათ დააბრუნონ ტიპი %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "ტრიგერის ფუნქციებს გამოცხადებულ არგუმენტები ვერ ექნება"
+
+#: pl_comp.c:605
+#, c-format
+msgid ""
+"The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV "
+"instead."
+msgstr ""
+"ტრიგერს არგუმენტებთან წვდომა TG_NARGS და TG_ARGV instead გავლით შეგიძლიათ "
+"გქონდეთ.."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "მოვლენის ტრიგერის ფუნქციებს გამოცხადებული არგუმენტები ვერ ექნება"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "\"PL/pgSQL\" ფუნქციის \"%s\" შედგენა ხაზზე %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "პარამეტრის სახელი \"%s\" ერთზე მეტჯერ გამოიყენება"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "სვეტის მითითება \"%s\" ორაზროვანია"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "შეიძლება მიბმული იყოს PL/pgSQL ცვლადზე ან ცხრილის სვეტზე."
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "ჩანაწერს \"%s\" ველი \"%s\" არ გააჩნია"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "ურთიერთობა \"%s\" არ არსებობს"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "ურთიერთობას \"%s\" კომპოზიტური ტიპი არ გააჩნია"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "ცვლადის \"%s\" ტიპი ფსევდო ტიპია: %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "ტიპი \"%s\" მხოლოდ გარსია"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "ტიპი %s კომპოზიტური არაა"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "გამონაკლისის უცნობი პირობა \"%s\""
+
+#: pl_comp.c:2526
+#, c-format
+msgid ""
+"could not determine actual argument type for polymorphic function \"%s\""
+msgstr ""
+"პოლიმორფული ფუნქციისთვის (%s) მიმდინარე არგუმენტის ტიპის დადგენა შეუძლებელია"
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "შესრულების ინიციალიზაციისას"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "გამოძახების არგუმენტების ლოკალურ ცვლადებში დამახსოვრებისას"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "ფუნქციის შესვლისას"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "კონტროლმა ფუნქციის დასასრულს RETURN-ის გარეშე მიაღწია"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "დაბრუნებული მნიშვნელობის ფუნქციის დაბრუნების ტიპში კასტისას"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"ფუნქცია, რომელიც სეტს აბრუნებს, გამოძახებულია კონტექსტში, რომელიც სეტებს ვერ "
+"იღებს"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "საჭიროა მატერიალიზებული რეჟიმი, მაგრამ ამ კონტექსტში ეს დაუშვებელია"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "ფუნქციიდან გამოსვლისას"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "დაბრუნებული ჩანაწერის ტიპი მოსალოდნელი ჩანაწერის ტიპს არ ემთხვევა"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "კონტროლმა ტრიგერის პროცედურის დასასრულს RETURN-ის გარეშე მიაღწია"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "ტრიგერი პროცედურა სეტს ვერ დააბრუნებს"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid ""
+"returned row structure does not match the structure of the triggering table"
+msgstr "დაბრუნებული მწკრივის სტრუქტურა ტრიგერი ცხრილის სტრუქტურას არ ემთხვევა"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL ფუნქცია %s ხაზზე %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL ფუნქცია %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL ფუნქცია %s ხაზზე %d %s-სთან"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL ფუნქცია %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "ოპერატორის ბლოკის ლოკალური ცვლადის ინიციალიზაციისას"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "ოპერატორის ბლოკში შესვლისას"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "ოპერატორის ბლოკიდან გამოსვლისას"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "გამონაკლისის მოსუფთავებისას"
+
+#: pl_exec.c:2360
+#, c-format
+msgid ""
+"procedure parameter \"%s\" is an output parameter but corresponding argument "
+"is not writable"
+msgstr ""
+"პროცედურის პარამეტრი \"%s\" გამოტანის პარამეტრია, მაგრამ შესაბამისი "
+"არგუმენტი ჩაწერადი არაა"
+
+#: pl_exec.c:2365
+#, c-format
+msgid ""
+"procedure parameter %d is an output parameter but corresponding argument is "
+"not writable"
+msgstr ""
+"პროცედურის პარამეტრი %d გამოტანის პარამეტრია, მაგრამ შესაბამისი არგუმენტი "
+"ჩაწერადი არაა"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr ""
+"GET STACKED DIAGNOSTICS -ის გამოყენება გამონაკლისის დამმუშავებლის გარეთ "
+"შეუძლებელია"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "შემთხვევა ნაპოვნი არაა"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE ოპერატორს ELSE ნაწილი აკლია."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "\"FOR\" მარყუჟის ქვედა ზღვარი ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "\"FOR\" მარყუჟის ზედა ზღვარი ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "FOR მარყუჟის BY-ის მნიშვნელობა ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "FOR მარყუჟის BY-ის მნიშვნელობა ნულზე მეტი უნდა იყოს"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "კურსორი \"%s\" უკვე გამოიყენება"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "კურსორისთვის გადმოცემული არგუმენტები არგუმენტების გარეშეა"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "კურსორისთვის საჭირო არგუმენტები"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH გამოხატულება ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH გამოხატულების გამოტანა მასივი უნდა იყოს და არა %s ტიპის"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "ჭრილის განზომილება (%d) დასაშვებ დიაპაზონს (0.. %d) გარეთაა"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE მარყუჟის ცვლადი მასივის ტიპის უნდა იყოს"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH მარყუჟის ცვლადი მასივის ტიპის უნდა იყოს"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid ""
+"cannot return non-composite value from function returning composite type"
+msgstr ""
+"ფუნქცია, რომელიც კომპოზიტურ ტიპს აბრუნებს, არაკომპოზიტურ ტიპს ვერ დააბრუნებს"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "არა-SETOF ფუნქციაში RETURN NEXT-ს ვერ გამოიყენებთ"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "არასწორი შედეგის ტიპი, რომელიც მიეწოდება RETURN NEXT- ში"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "არასწორი ჩანაწერის ტიპი, რომელიც მიეწოდება RETURN NEXT- ში"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT უნდა ჰქონდეს პარამეტრი"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "არა-SETOF ფუნქციაში RETURN QUERY-ს ვერ გამოიყენებთ"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "მოთხოვნის სტრუქტურა ფუნქციის შედეგის ტიპს არ ემთხვევა"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "\"EXECUTE\"-ის მოთხოვნის სტრიქონის არგუმენტი ნულოვანია"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "პარამეტრი RAISE უკვე მითითებულია: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr ""
+"ოპერატორ RAISE -ს გამონაკლისის დამმუშავებლის გარეთ პარამეტრების გარეშე ვერ "
+"გამოიყენებთ"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE ოპერატორის პარამეტრი ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "მტკიცება ვერ მოხერხდა"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "pl/pgSQL-ში COPY კლიენტიდან/ში შეუძლებელია"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "\"PL/pgSQL\"-ში მხარდაუჭერელი ტრანზაქციის ბრძანება"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr ""
+"INTO-ს გამოყენება ბრძანებაში, რომელსაც მონაცემების დაბრუნება არ შეუძლია"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "მოთხოვნას მწკრივი არ დაუმატებია"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "მოთხოვნამ ერთზე მეტი მწკრივი დააბრუნა"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "დარწმუნდით, რომ მოთხოვნა ერთ მწკრივს აბრუნებს, ან LIMIT 1 გამოიყენეთ."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "მოთხოვნას არ აქვს დანიშნულების ადგილი შედეგის მონაცემებისთვის"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr ""
+"თუ გსურთ SELECT-ის შედეგების მოცილება, შეგიძლიათ მის ნაცვლად PERFORM "
+"გამოიყენოთ."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "ბრძანება EXECUTE \"SELECT ... INTO\" განხორციელებული არაა"
+
+#: pl_exec.c:4499
+#, c-format
+msgid ""
+"You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS "
+"instead."
+msgstr ""
+"შეიძლება სამაგიეროდ ამჯობინოთ EXECUTE … INTO ან EXECUTE CREATE TABLE .. AS -"
+"ის გამოყენება."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "ტრანზაქციების ბრძანებების EXECUTE განხორციელებული არაა"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "კურსორი ცვლადი \"%s\" ნულოვანია"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "კურსორი \"%s\" არ არსებობს"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "შედარებითი ან აბსოლუტური კურსორის პოზიცია ნულოვანია"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "ცვლადი \"%s\", აღწერილი, როგორც NOT NULL, ნულოვანი ვერ იქნება"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "მწკრივის ცვლადისთვის არაკომპოზიტური მნიშვნელობის მინიჭება შეუძლებელია"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "ჩანაწერის ცვლადისთვის არაკომპოზიტური მნიშვნელობის მინიჭება შეუძლებელია"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "სისტემური სვეტზე (%s) მინიჭება შეუძლებელია"
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "მოთხოვნამ მონაცემები არ დააბრუნა"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "მოთხოვნა: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "მოთხოვნამ %d სვეტი დააბრუნა"
+msgstr[1] "მოთხოვნამ %d სვეტი დააბრუნა"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "მოთხოვნა SELECT INTO-ა, როცა ის უბრალოდ SELECT-ი უნდა იყოს"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "მოთხოვნა SELECT-ი არაა"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid ""
+"type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr ""
+"პარამეტრის %d (%s) ტიპი არ ემთხვევა იმას, რომლითაც გეგმა მზადდებოდა (%s)"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "ტოლობაში საწყისი და სამიზნე ველების რაოდენობა არ ემთხვევა"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "აქტიურია %s შემოწმება %s -დან."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "დარწმუნდით, რომ მოთხოვნა სვეტების ზუსტ ჩამონათვალს აბრუნებს."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "ჩანაწერი %s ჯერ მიუნიჭებელია"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr ""
+"ჩანაწერის , რომელსაც მნიშვნელობა ჯერ მინიჭებული არ აქვს, კორტეჟის სტრუქტურა "
+"გაურკვეველია."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "ცვლადი \"%s\" გამოცხადდა, როგორც CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "ოპერატორის ბლოკი"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "მინიჭება"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR-ი მთელი რიცხვის მარყუჟის მნიშვნელობაზე"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR-ი SELECT-ის მწკრივებზე"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR კურსორზე"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH მასივზე"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL ოპერატორი"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR-ი EXECUTE ოპერატორზე"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "ბლოკის ჭდე DECLARE-მდე უნდა იყოს განთავსებული და არა შემდეგ"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "ტიპს \"%s\" კოლაციების მხარდაჭერა არ გააჩნია"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr ""
+"ცვლადისთვის \"%s\" ნაგულისხმები მნიშვნელობის ქონა აუცილებელია, რადგან ის "
+"აღწერილია, როგორც NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "ცვლადი \"%s\" არ არსებობს"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "დუბლირებული აღწერა"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "ცვლადი \"%s\" ადრე განსაზღვრულ ცვლადს ჩრდილავს"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "დიაგნოსტიკის ჩანაწერი %s GET STACKED DIAGNOSTICS- ში დაშვებული არაა"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "დიაგნოსტიკის ჩანაწერი %s GET CURRENT DIAGNOSTICS- ში დაშვებული არაა"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "\"GET DIAGNOSTICS\"-ის უცნობი ჩანაწერი"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" სკალარული ცვლადი არაა"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid ""
+"loop variable of loop over rows must be a record variable or list of scalar "
+"variables"
+msgstr ""
+"კორტეჟის მარყუჟი ცვლადი ჩანაწერის ცვლადის ან სკალარული ცვლადების სიის ტიპის "
+"უნდა იყოს"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "cursor FOR მარყუჟს მხოლოდ ერთი სამიზნე ცვლადი უნდა ჰქონდეს"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "cursor FOR მარყუჟმა მხოლოდ bound cursor ცვლადები უნდა გამოიყენოს"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "integer for loop მხოლოდ ერთი სამიზნე ცვლადი უნდა ჰქონდეს"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "მოთხოვნის FOR მარყუჟში REVERSE-ს ვერ მიუთითებთ"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr ""
+"\"FOREACH\"-ის მარყუჟი ცვლადი ცნობილი ცვლადს ან ცვლადების სიას უნდა "
+"წარმოადგენდეს"
+
+#: pl_gram.y:1742
+#, c-format
+msgid ""
+"there is no label \"%s\" attached to any block or loop enclosing this "
+"statement"
+msgstr ""
+"ბლოკში ან მარყუჟში, რომელიც ამ ოპერატორს შემოფარგლავს, ჭდე \"%s\" არ არსებობს"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "ბლოკის ჭდეს \"%s\" CONTINUE-ში ვერ გამოიყენებთ"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT არ შეიძლება გამოყენებულ იქნას მარყუჟის გარეთ, თუ მას არ აქვს ჭდე"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE-ის მარყუჟის გარეთ გამოყენება შეუძლებელია"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "ფუნქციის აღწერის მოულოდნელი დასასრული"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "სინტაქსური შეცდომა"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "არასწორი SQLSTATE კოდი"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "სინტაქსის შეცდომა. მოველოდი \"FOR\"-ს"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH ოპერატორს ბევრი მწკრივის დაბრუნება არ შეუძლია"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "cursor ტიპის ცვლადი მარტივი ცვლადი უნდა იყოს"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "ცვლადი %s -ის ტიპი cursor ან refcursor უნდა იყოს"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" უცნობი ცვლადია"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "მშობლობა არ ემთხვევა"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "\"SQL\" გამოსახულებას ბოლოში \"%s\" აკლია"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "\"SQL\" გამოსახულებას ბოლოში \"%s\" აკლია"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "აკლია გამოსახულება"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "აკლია SQL ოპერატორი"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "მონაცემის ტიპის არასრული აღწერა"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "მონაცემის ტიპის"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO ერთზე მეტჯერაა მითითებული"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "მოველოდი FROM -ს ან IN -ს"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN-ს სეტის დამბრუნებელ ფუნქციაში პარამეტრი ვერ ექნება"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "გამოიყენეთ RETURN NEXT ან RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN-ს პროცედურაში პარამეტრი ვერ ექნება"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN-ს არაფრის-დამბრუნებელ ფუნქციაში პარამეტრი ვერ ექნება"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN-ს OUT პარამეტრების მქონე ფუნქციაში პარამეტრები ვერ ექნება"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr ""
+"RETURN NEXT-ს პარამეტრი ფუნქციაში, რომელსაც OUT პარამეტრები აქვს, ვერ ექნება"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr ""
+"ჩანაწერის ცვლადი მრავალე-ლემენტიანი INTO ჩამონათვალის ნაწილი ვერ იქნება"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "მითითებულია მეტისმეტი INTO ცვლადი"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "უჭდეო ბლოკისთვის მითითებულია საბოლოო ჭდე \"%s\""
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "საბოლოო ჭდე \"%s\" განსხვავდება ბლოკის ჭდისგან \"%s\""
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "კურსორს %s არგუმენტები არ გააჩნია"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "კურსორს %s აქვს არგუმენტები"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "კურსორს %s არგუმენტი სახელად %s არ გააჩნია"
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr ""
+"კურსორის \"%2$s\" პარამეტრის \"%1$s\" მნიშვნელობა ერთზე მეტჯერაა მითითებული"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "არასაკმარისი არგუმენტები კურსორისთვის \"%s\""
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "ძალიან ბევრი არგუმენტი კურსორისთვის \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "\"RAISE\" ოპერატორის უცნობი პარამეტრი"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "სინტაქსის შეცდომა. მოველოდი \"=\"-ს"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "ძალიან ბევრი პარამეტრი მითითებულია RAISE- სთვის"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "ძალიან ცოტა პარამეტრი მითითებულია RAISE- სთვის"
+
+#: pl_handler.c:156
+msgid ""
+"Sets handling of conflicts between PL/pgSQL variable names and table column "
+"names."
+msgstr ""
+"PL/pgSQL-ის ცვლადების სახელებსა და ცხრილის სვეტის სახელებს შორის "
+"კონფლიქტების გადაწყვეტის რეჟიმი."
+
+#: pl_handler.c:165
+msgid ""
+"Print information about parameters in the DETAIL part of the error messages "
+"generated on INTO ... STRICT failures."
+msgstr ""
+"INTO … STRICT ავარიებზე გენერირებული შეცდომის შეტყობინებებში პარამეტრების "
+"შესახებ ინფორმაციის DETAIL ნაწილში დამატება."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "ASSERT ოპერატორებში მითითებული შემოწმებების შესრულება."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "პროგრამული კონსტრუქციების სია, რომლებიც გაფრთხილებებს გამოაგდებს."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "პროგრამული კონსტრუქციების სია, შეცდომას გაფრთხილებებს გამოაგდებს."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s შეყვანის ბოლოს"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s \"%s\"-სთან ან ახლოს"
diff --git a/src/pl/plpgsql/src/po/ko.po b/src/pl/plpgsql/src/po/ko.po
new file mode 100644
index 0000000..3b4b527
--- /dev/null
+++ b/src/pl/plpgsql/src/po/ko.po
@@ -0,0 +1,891 @@
+# Korean message translation file for plpgsql
+# Copyright (C) 2010 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Ioseph Kim <ioseph@uri.sarang.net>, 2010
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-12 00:39+0000\n"
+"PO-Revision-Date: 2023-04-06 10:03+0900\n"
+"Last-Translator: Ioseph Kim <ioseph@uri.sarang.net>\n"
+"Language-Team: Korean <pgsql-kr@postgresql.kr>\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL 함수에 %s 형식을 사용할 수 없음"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "다형적 함수 \"%s\"의 실제 반환 형식을 확인할 수 없음"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "트리거 함수는 트리거로만 호출될 수 있음"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL 함수는 %s 형식을 반환할 수 없음"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "트리거 함수는 선언된 인수를 포함할 수 없음"
+
+#: pl_comp.c:605
+#, c-format
+msgid ""
+"The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV "
+"instead."
+msgstr "대신 TG_NARGS 및 TG_ARGV를 통해 트리거의 인수에 액세스할 수 있습니다."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "이벤트 트리거 함수는 선언된 인자(declare argument)를 사용할 수 없음"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "PL/pgSQL 함수 \"%s\" 컴파일(%d번째 줄 근처)"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "\"%s\" 매개 변수가 여러 번 사용 됨"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "열 참조 \"%s\" 가 명확하지 않습니다."
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "PL/pgSQL 변수명도, 테이블 칼럼 이름도 아니여야 함"
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "\"%s\" 레코드에 \"%s\" 필드가 없음"
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "\"%s\" 이름의 릴레이션(relation)이 없습니다"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "\"%s\" 릴레이션에는 복합 자료형이 없습니다"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "\"%s\" 변수에 의사 형식 %s이(가) 있음"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "자료형 \"%s\" 는 오로지 shell 에만 있습니다. "
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "%s 자료형은 복합 자료형이 아님"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "인식할 수 없는 예외 조건 \"%s\""
+
+#: pl_comp.c:2534
+#, c-format
+msgid ""
+"could not determine actual argument type for polymorphic function \"%s\""
+msgstr "다형적 함수 \"%s\"의 실제 인수 형식을 확인할 수 없음"
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "실행 상태를 초기화하는 동안"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "호출 인수를 로컬 변수에 저장하는 동안"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "함수를 시작하는 동안"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "컨트롤이 RETURN 없이 함수 끝에 도달함"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "함수의 반환 형식으로 반환 값을 형변환하는 동안"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"set-values 함수(테이블 리턴 함수)가 set 정의 없이 사용되었습니다 (테이블과 해"
+"당 열 alias 지정하세요)"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialize 모드가 필요합니다만, 이 구문에서는 허용되지 않습니다"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "함수를 종료하는 동안"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "반환된 레코드 형식이 필요한 레코드 형식과 일치하지 않음"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "컨트롤이 RETURN 없이 트리거 프로시저 끝에 도달함"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "트리거 프로시저는 집합을 반환할 수 없음"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid ""
+"returned row structure does not match the structure of the triggering table"
+msgstr "반환된 행 구조가 트리거하는 테이블의 구조와 일치하지 않음"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL 함수 \"%s\" 의 %d번째 줄 %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL 함수 %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL 함수 \"%s\" 의 %d번째 %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL 함수 %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "문 블록 로컬 변수를 초기화하는 동안"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "문 블록을 시작하는 동안"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "문 블록을 종료하는 동안"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "예외를 정리하는 동안"
+
+#: pl_exec.c:2360
+#, c-format
+msgid ""
+"procedure parameter \"%s\" is an output parameter but corresponding argument "
+"is not writable"
+msgstr "\"%s\" 프로시져 인자는 출력 인자인데, 값 변경이 불가능 함"
+
+#: pl_exec.c:2365
+#, c-format
+msgid ""
+"procedure parameter %d is an output parameter but corresponding argument is "
+"not writable"
+msgstr "%d 프로시져 인자는 출력 인자인데, 값 변경이 불가능 함"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS 구문은 예외처리 헨들러 밖에서 사용할 수 없음"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "사례를 찾지 못함"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE 문에 ELSE 부분이 누락되었습니다."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "FOR 루프의 하한은 null일 수 없음"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "FOR 루프의 상한은 null일 수 없음"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "FOR 루프의 BY 값은 null일 수 없음"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "FOR 루프의 BY 값은 0보다 커야 함"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "\"%s\" 커서가 이미 사용 중임"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "인수가 없는 커서에 인수가 제공됨"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "커서에 인수 필요"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH 구문은 null 이 아니여야 함"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH 구문에서는 배열이 사용됩니다. 사용된 자료형 %s"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "slice dimension (%d) 값이 범위를 벗어남, 0..%d"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE 루프 변수는 배열 자료형이어야 함"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH 반복 변수는 배열형이 아니여야 함"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid ""
+"cannot return non-composite value from function returning composite type"
+msgstr ""
+"함수의 반환값이 복합 자료형인데, 복합 자료형아닌 자료형을 반환하려고 함"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "SETOF 함수가 아닌 함수에서 RETURN NEXT를 사용할 수 없음"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "RETURN NEXT에 잘못된 결과 형식이 제공됨"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "RETURN NEXT에 잘못된 레코드 형식이 제공됨"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT에 매개 변수 필요"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "SETOF 함수가 아닌 함수에서 RETURN QUERY를 사용할 수 없음"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "쿼리 구조가 함수 결과 형식과 일치하지 않음"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "EXECUTE의 쿼리 문자열 인수가 null임"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE 옵션이 이미 지정됨: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "매개 변수 없는 RAISE를 예외 처리기 외부에 사용할 수 없음"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE 문 옵션이 null일 수 없음"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "assertion 실패"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "PL/pgSQL의 클라이언트와 상호 복사할 수 없음"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "PL/pgSQL 안에서는 지원하지 않는 트랜잭션 명령"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "데이터를 반환할 수 없는 명령과 함께 INTO가 사용됨"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "쿼리에서 행을 반환하지 않음"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "쿼리에서 두 개 이상의 행을 반환"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "하나의 로우만 반환하도록 쿼리를 바꾸거나 LIMIT 1 옵션을 추가하세요."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "쿼리에 결과 데이터의 대상이 없음"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "SELECT의 결과를 취소하려면 대신 PERFORM을 사용하십시오."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "SELECT의 EXECUTE... INTO가 구현되지 않음"
+
+#: pl_exec.c:4499
+#, c-format
+msgid ""
+"You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS "
+"instead."
+msgstr "EXECUTE ... INTO 또는 EXECUTE CREATE TABLE ... AS 구문을 사용하세요."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "트랜잭션 명령들의 EXECUTE 기능은 구현되지 않았음"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "커서 변수 \"%s\"이(가) null임"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "\"%s\" 이름의 커서가 없음"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "상대 또는 절대 커서 위치가 null임"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "NOT NULL이 선언된 \"%s\" 변수에 null 값을 할당할 수 없음"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "행 변수에 비복합 값을 할당할 수 없음"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "레코드 변수에 비복합 값을 할당할 수 없음"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "시스템 열 \"%s\"에 할당할 수 없습니다."
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "쿼리 실행 결과 자료가 없음"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "쿼리: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "쿼리가 %d 개의 칼럼을 반환함"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "쿼리는 SELECT INTO 이지만, 일반 SELECT 구문이어야 함"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "SELECT 쿼리가 아님"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid ""
+"type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr ""
+"%d번째 매개 변수의 자료형(%s)이 미리 준비된 실행계획의 자료형(%s)과 다릅니다"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "원본과 대상 필드 수가 같지 않습니다."
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s 검사(해당 변수이름: %s)가 활성화 되어있습니다."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "쿼리 결과가 정확한 칼럼 목록을 반환하도록 수정하세요."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "\"%s\" 레코드가 아직 할당되지 않음"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "아직 할당되지 않은 레코드의 튜플 구조는 미정입니다."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "\"%s\" 변수는 CONSTANT로 선언됨"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "문 블록"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "할당"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "정수 루프 변수를 포함하는 FOR"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "SELECT 행을 제어하는 FOR"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "커서를 제어하는 FOR"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "배열 초과된 FOREACH"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL 문"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "EXECUTE 문을 제어하는 FOR"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "블록 라벨은 DECLARE 영역 앞에 있어야 함"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "%s 자료형은 collation 지원 안함"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "\"%s\" 변수는 NOT NULL 설정이 있어 기본값을 가져야 합니다"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "\"%s\" 변수가 없음"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "중복 선언"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "variable \"%s\" shadows a previously defined variable"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "GET STACKED DIAGNOSTICS 에서 %s 항목을 사용할 수 없음"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "GET CURRENT DIAGNOSTICS 에서 %s 항목을 사용할 수 없음"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "알 수 없는 GET DIAGNOSTICS 항목"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\"은(는) 스칼라 변수가 아님"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid ""
+"loop variable of loop over rows must be a record variable or list of scalar "
+"variables"
+msgstr ""
+"행에 있는 루프의 루프 변수는 레코드 변수이거나 스칼라 변수의 목록이어야 함"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "커서 FOR 루프에 대상 변수가 한 개만 있어야 함"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "커서 FOR 루프는 바인딩된 커서 변수를 한 개만 사용해야 함"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "정수 FOR 루프에 대상 변수가 한 개만 있어야 함"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "쿼리 FOR 루프에 REVERSE를 지정할 수 없음"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "FOREACH의 반복 변수는 알려진 변수이거나 변수의 목록이어야 함"
+
+#: pl_gram.y:1742
+#, c-format
+msgid ""
+"there is no label \"%s\" attached to any block or loop enclosing this "
+"statement"
+msgstr "임의 블록이나 루프 구문에 할당된 \"%s\" 라벨이 없음"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "CONTINUE 안에서 \"%s\" 블록 라벨을 사용할 수 없음"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "루프 외부에 라벨 지정 없이 EXIT 사용할 수 없음"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE를 루프 외부에 사용할 수 없음"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "예기치 않은 함수 정의의 끝"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "구문 오류"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "잘못된 SQLSTATE 코드"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "구문 오류, \"FOR\" 필요"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH 구문은 다중 로우를 반환할 수 없음"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "커서 변수는 단순 변수여야 함"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "\"%s\" 변수는 커서 또는 ref 커서 형식이어야 함"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" (은)는 알려진 변수가 아님"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "괄호의 짝이 맞지 않음"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "SQL 식 끝에 \"%s\" 누락"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "SQL 문 끝에 \"%s\" 누락"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "표현식 빠졌음"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "SQL 문이 빠졌음"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "불완전한 데이터 형식 선언"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "데이터 형식 선언 누락"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO가 여러 번 지정됨"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "FROM 또는 IN 필요"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "집합을 반환하는 함수에서 RETURN 구문에는 인자가 없음"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "RETURN NEXT 나 RETURN QUERY 구문을 사용하세요."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "프로시져에서는 RETURN 문의 매개 변수를 사용할 수 없음"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN은 void를 반환하는 함수에 매개 변수를 포함할 수 없음"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN은 OUT 매개 변수가 있는 함수에 매개 변수를 포함할 수 없음"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT는 OUT 매개 변수가 있는 함수에 매개 변수를 포함할 수 없음"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "다중 아이템 INTO 목록의 부분으로 record 변수가 사용될 수 없음"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "너무 많은 INTO 변수가 지정됨"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "레이블이 없는 블록에 끝 레이블 \"%s\"이(가) 지정됨"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "끝 레이블 \"%s\"이(가) 블록의 \"%s\" 레이블과 다름"
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "\"%s\" 커서에 인수가 없음"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "\"%s\" 커서에 인수가 있음"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "\"%s\" 커서는 \"%s\" 이름의 인자가 없음"
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "\"%s\" 이름의 인자가 \"%s\" 커서에서 중복됨"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "\"%s\" 커서를 위한 충분하지 않은 인자"
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "\"%s\" 커서를 위한 인자가 너무 많음"
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "인식할 수 없는 RAISE 문 옵션"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "구문 오류, \"=\" 필요"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "RAISE에 지정된 매개 변수가 너무 많음"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "RAISE에 지정된 매개 변수가 너무 적음"
+
+#: pl_handler.c:156
+msgid ""
+"Sets handling of conflicts between PL/pgSQL variable names and table column "
+"names."
+msgstr ""
+"PL/pgSQL 변수명과 테이블 칼럼명 사이 충돌이 일어날 경우에 대한 처리를 하세요."
+
+#: pl_handler.c:165
+msgid ""
+"Print information about parameters in the DETAIL part of the error messages "
+"generated on INTO ... STRICT failures."
+msgstr ""
+"INTO ... STRICT 실패에서 오류 메시지를 만들 때 그 DETAIL 부분에 들어갈 내용"
+"을 출력 하세요"
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "ASSERT 구문에서 주어진 검사를 수행하세요."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "경고로 처리할 프로그래밍 컨스트럭트 목록"
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "오류로 처리할 프로그래밍 컨스트럭트 목록"
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s, 입력 끝부분"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s, \"%s\" 부근"
+
+#, c-format
+#~ msgid "array subscript in assignment must not be null"
+#~ msgstr "배열 하위 스크립트로 지정하는 값으로 null 값을 사용할 수 없습니다"
+
+#, c-format
+#~ msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+#~ msgstr "지정한 배열 크기(%d)가 최대치(%d)를 초과했습니다"
+
+#, c-format
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "\"%s\" 쿼리에서 두 개 이상의 행을 반환함"
+
+#, c-format
+#~ msgid "subscripted object is not an array"
+#~ msgstr "하위 스크립트 개체는 배열이 아님"
diff --git a/src/pl/plpgsql/src/po/pl.po b/src/pl/plpgsql/src/po/pl.po
new file mode 100644
index 0000000..72407be
--- /dev/null
+++ b/src/pl/plpgsql/src/po/pl.po
@@ -0,0 +1,839 @@
+# plpgsql message translation file for plpgsql
+# Copyright (C) 2011 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Begina Felicysym <begina.felicysym@wp.eu>, 2011, 2012, 2013.
+# grzegorz <begina.felicysym@wp.eu>, 2014, 2015, 2016.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL 9.1)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2016-07-18 17:37+0000\n"
+"PO-Revision-Date: 2016-07-18 23:20+0200\n"
+"Last-Translator: grzegorz <begina.felicysym@wp.eu>\n"
+"Language-Team: begina.felicysym@wp.eu\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Virtaal 0.7.1\n"
+
+#: pl_comp.c:432 pl_handler.c:450
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "funkcje PL/pgSQL nie obsługują typu %s"
+
+#: pl_comp.c:513
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "nie można określić, jaki typ zwraca funkcja polimorficzna \"%s\""
+
+#: pl_comp.c:543
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "procedury wyzwalaczy mogą być wywoływane jedynie przez wyzwalacze"
+
+#: pl_comp.c:547 pl_handler.c:435
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "funkcje PL/pgSQL nie mogą zwracać wartości typu %s"
+
+#: pl_comp.c:588
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "funkcje wyzwalaczy nie mogą przyjmować żadnych argumentów"
+
+#: pl_comp.c:589
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Argumenty dla procedury wyzwalacza są umieszczane w zmiennych TG_NARGS oraz TG_ARGV."
+
+#: pl_comp.c:691
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "funkcje wyzwalaczy zdarzeń nie mogą przyjmować żadnych argumentów"
+
+#: pl_comp.c:944
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "kompilacja funkcji PL/pgSQL \"%s\", w okolicach linii %d"
+
+#: pl_comp.c:967
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "nazwa argumentu \"%s\" użyta więcej niż raz"
+
+#: pl_comp.c:1077
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "nazwa kolumny \"%s\" jest niejednoznaczna"
+
+#: pl_comp.c:1079
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Nazwa może odnosić się do zmiennej PL/pgSQL albo kolumny tabeli."
+
+#: pl_comp.c:1259 pl_comp.c:1287 pl_exec.c:4395 pl_exec.c:4744 pl_exec.c:4829
+#: pl_exec.c:4920
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "rekord \"%s\" nie posiada pola \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "relacja \"%s\" nie istnieje"
+
+#: pl_comp.c:1927
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "zmienna \"%s\" jest psuedo-typu %s"
+
+#: pl_comp.c:1994
+#, c-format
+msgid "relation \"%s\" is not a table"
+msgstr "relacja \"%s\" nie jest tabelą"
+
+#: pl_comp.c:2154
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "typ \"%s\" jest jedynie powłoką"
+
+#: pl_comp.c:2243 pl_comp.c:2296
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "nieznany warunek wyjątku \"%s\""
+
+#: pl_comp.c:2503
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "nie można określić typu argumentu dla funkcji polimorficznej \"%s\""
+
+#: pl_exec.c:324 pl_exec.c:612 pl_exec.c:872
+msgid "during initialization of execution state"
+msgstr "podczas inicjacji stanu wykonywania"
+
+#: pl_exec.c:331
+msgid "while storing call arguments into local variables"
+msgstr "podczas przepisywania argumentów wywołania do lokalnych zmiennych"
+
+#: pl_exec.c:416 pl_exec.c:760
+msgid "during function entry"
+msgstr "podczas wchodzenia do funkcji"
+
+#: pl_exec.c:441
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "osiągnięto koniec funkcji, brakuje instrukcji RETURN"
+
+#: pl_exec.c:448
+msgid "while casting return value to function's return type"
+msgstr "podczas rzutowania zwracanej wartości na typ wyniku funkcji"
+
+#: pl_exec.c:461 pl_exec.c:2938
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "funkcja zwracająca zbiór rekordów wywołana w kontekście, w którym nie jest to dopuszczalne"
+
+#: pl_exec.c:499 pl_exec.c:2779
+msgid "returned record type does not match expected record type"
+msgstr "został zwrócony rekord o niewłaściwym typie"
+
+#: pl_exec.c:554 pl_exec.c:789 pl_exec.c:907
+msgid "during function exit"
+msgstr "podczas wyjścia z funkcji"
+
+#: pl_exec.c:785 pl_exec.c:903
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "osiągnięto koniec funkcji wyzwalacza, brakuje instrukcji RETURN"
+
+#: pl_exec.c:794
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "procedura wyzwalacza nie może zwracać zbioru rekordów"
+
+#: pl_exec.c:816
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "struktura zwróconego rekordu nie odpowiada strukturze tabeli dla której wywołano wyzwalacz"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:954
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "funkcja PL/pgSQL %s, wiersz %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:965
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "funkcja PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:973
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "funkcja PL/pgSQL %s, wiersz %d w %s"
+
+#: pl_exec.c:979
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "funkcja PL/pgSQL %s"
+
+#: pl_exec.c:1089
+msgid "during statement block local variable initialization"
+msgstr "podczas inicjacji zmiennych lokalnych bloku instrukcji"
+
+#: pl_exec.c:1128
+#, c-format
+msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+msgstr "zmienna \"%s\" zadeklarowana jako NOT NULL nie może mieć wartości domyślnej NULL"
+
+#: pl_exec.c:1178
+msgid "during statement block entry"
+msgstr "podczas wchodzenia do bloku instrukcji"
+
+#: pl_exec.c:1199
+msgid "during statement block exit"
+msgstr "podczas opuszczania bloku instrukcji"
+
+#: pl_exec.c:1242
+msgid "during exception cleanup"
+msgstr "podczas kończenia obsługi wyjątków"
+
+#: pl_exec.c:1593
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS nie może być wykorzystane poza uchwytem wyjątku"
+
+#: pl_exec.c:1789
+#, c-format
+msgid "case not found"
+msgstr "etykieta instrukcji wyboru nie znaleziona"
+
+#: pl_exec.c:1790
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "w wyrażeniu CASE brakuje części ELSE."
+
+#: pl_exec.c:1944
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "początkowa wartość dla pętli FOR nie może być NULL"
+
+#: pl_exec.c:1960
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "końcowa wartość dla pętli FOR nie może być NULL"
+
+#: pl_exec.c:1978
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "wartość wyrażenia BY w pętli FOR nie może być NULL"
+
+#: pl_exec.c:1984
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "wartość wyrażenia BY w pętli FOR musi być większa od zera"
+
+#: pl_exec.c:2153 pl_exec.c:3912
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "kursor \"%s\" jest już używany"
+
+#: pl_exec.c:2176 pl_exec.c:3974
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "podano argumenty dla kursora nie przyjmującego żadnych argumentów"
+
+#: pl_exec.c:2195 pl_exec.c:3993
+#, c-format
+msgid "arguments required for cursor"
+msgstr "parametry wymagane dla kursora"
+
+#: pl_exec.c:2280
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "wyrażenie w instrukcji FOREACH nie może być NULL"
+
+#: pl_exec.c:2286
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "typem wyrażenie w instrukcji FOREACH musi być tablica, nie %s"
+
+#: pl_exec.c:2303
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "wymiar wycinka tablicy (%d) przekracza dopuszczalny zakres 0..%d"
+
+#: pl_exec.c:2330
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "zmienna użyta w pętli FOREACH ... SLICE musi być typu tablicowego"
+
+#: pl_exec.c:2334
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "zmienna użyta w pętli FOREACH nie może być typu tablicowego"
+
+#: pl_exec.c:2522 pl_exec.c:2604 pl_exec.c:2771
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "nie można zwracać wartości prostej z funkcji zwracającej typ złożony"
+
+#: pl_exec.c:2648 pl_gram.y:3190
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "instrukcja RETURN NEXT nie może zostać użyta w funkcjach nie zwracających zbioru rekordów"
+
+#: pl_exec.c:2682 pl_exec.c:2813
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "niewłaściwy typ wyniku w instrukcji RETURN NEXT"
+
+#: pl_exec.c:2711 pl_exec.c:4382 pl_exec.c:4711 pl_exec.c:4737 pl_exec.c:4803
+#: pl_exec.c:4822 pl_exec.c:4890 pl_exec.c:4913
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "struktura rekordu \"%s\" nie jest jeszcze znana"
+
+#: pl_exec.c:2713 pl_exec.c:4384 pl_exec.c:4713 pl_exec.c:4739 pl_exec.c:4805
+#: pl_exec.c:4824 pl_exec.c:4892 pl_exec.c:4915
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Struktura jest nieokreślona dla niezainicjowanego rekordu abstrakcyjnego."
+
+#: pl_exec.c:2717 pl_exec.c:2737
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "błędny typ rekordu w instrukcji RETURN NEXT"
+
+#: pl_exec.c:2832
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "po RETURN NEXT musi pojawić się parametr"
+
+#: pl_exec.c:2865 pl_gram.y:3252
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "nie można używać instrukcji RETURN QUERY w funkcjach nie zwracających zbioru rekordów"
+
+#: pl_exec.c:2886
+msgid "structure of query does not match function result type"
+msgstr "typ rekordu zwracany przez zapytanie nie odpowiada typowi zwracanemu przez funkcję"
+
+#: pl_exec.c:2966 pl_exec.c:3096
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "argument dla instrukcji RAISE został już podany: %s"
+
+#: pl_exec.c:2999
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE bez argumentów jest dopuszczalne tylko w bloku obsługi wyjątków"
+
+#: pl_exec.c:3086
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "argument dla wyrażenia RAISE nie może być NULL"
+
+#: pl_exec.c:3155
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3226
+#, c-format
+msgid "assertion failed"
+msgstr "niepowodzenie asercji"
+
+#: pl_exec.c:3418 pl_exec.c:3562 pl_exec.c:3751
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "instrukcja COPY nie jest obsługiwana w PL/pgSQL"
+
+#: pl_exec.c:3422 pl_exec.c:3566 pl_exec.c:3755
+#, c-format
+msgid "cannot begin/end transactions in PL/pgSQL"
+msgstr "nie można rozpocząć ani zakończyć transakcji w PL/pgSQL"
+
+#: pl_exec.c:3423 pl_exec.c:3567 pl_exec.c:3756
+#, c-format
+msgid "Use a BEGIN block with an EXCEPTION clause instead."
+msgstr "Zamiast tego użyj bloku BEGIN wraz z klauzulą EXCEPTION."
+
+#: pl_exec.c:3590 pl_exec.c:3780
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO zostało użyte z zapytaniem, które nie zwraca danych"
+
+#: pl_exec.c:3618 pl_exec.c:3808
+#, c-format
+msgid "query returned no rows"
+msgstr "zapytanie nie zwróciło żadnych wierszy"
+
+#: pl_exec.c:3637 pl_exec.c:3827
+#, c-format
+msgid "query returned more than one row"
+msgstr "zapytanie zwróciło więcej niż jeden wiersz"
+
+#: pl_exec.c:3654
+#, c-format
+msgid "query has no destination for result data"
+msgstr "nie wskazano gdzie mają zostać zapisane wyniki zapytania"
+
+#: pl_exec.c:3655
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Jeśli wyniki zapytania nie są istotne, używaj instrukcji PERFOM zamiast SELECT."
+
+#: pl_exec.c:3687 pl_exec.c:7128
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "treść zapytania dla instrukcji EXECUTE ma wartość NULL"
+
+#: pl_exec.c:3743
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "użycie SELECT ... INTO w instrukcji EXECUTE nie jest obsługiwane"
+
+#: pl_exec.c:3744
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Zamiast tego możesz użyć EXECUTE ... INTO lub EXECUTE CREATE TABLE ... AS."
+
+#: pl_exec.c:4056 pl_exec.c:4148
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "zmienna kursorowa \"%s\" ma wartość pustą"
+
+#: pl_exec.c:4063 pl_exec.c:4155
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "kursor \"%s\" nie istnieje"
+
+#: pl_exec.c:4077
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "względna lub bezwzględna pozycja kursora o wartości null"
+
+#: pl_exec.c:4257
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "zmienna \"%s\" została zadeklarowana jako NOT NULL, nie można przypisać wartości NULL"
+
+#: pl_exec.c:4326
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "nie można przypisać wartości skalarnej do zmiennej rekordowej"
+
+#: pl_exec.c:4350
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "nie można przypisać wartości skalarnej do zmiennej rekordowej"
+
+#: pl_exec.c:4493
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "liczba wymiarów tablicy (%d) przekracza maksimum (%d)"
+
+#: pl_exec.c:4525
+#, c-format
+msgid "subscripted object is not an array"
+msgstr "indeksowanie jest możliwe jedynie dla obiektu typu tablicowego"
+
+#: pl_exec.c:4562
+#, c-format
+msgid "array subscript in assignment must not be null"
+msgstr "w instrukcji przypisania do elementu tablicy indeksem elementu nie może być pusty"
+
+#: pl_exec.c:5029
+#, c-format
+msgid "query \"%s\" did not return data"
+msgstr "zapytanie \"%s\" nie zwróciło żadnych danych"
+
+#: pl_exec.c:5037
+#, c-format
+msgid "query \"%s\" returned %d column"
+msgid_plural "query \"%s\" returned %d columns"
+msgstr[0] "zapytanie \"%s\" zwróciło %d kolumnę"
+msgstr[1] "zapytanie \"%s\" zwróciło %d kolumny"
+msgstr[2] "zapytanie \"%s\" zwróciło %d kolumn"
+
+#: pl_exec.c:5064
+#, c-format
+msgid "query \"%s\" returned more than one row"
+msgstr "zapytanie \"%s\" zwróciło więcej niż jeden wiersz"
+
+#: pl_exec.c:5128
+#, c-format
+msgid "query \"%s\" is not a SELECT"
+msgstr "zapytanie \"%s\" nie jest kwerendą SELECT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "blok instrukcji"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "przypisanie"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "pętla FOR ze zmienną typu całkowitego"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "pętla FOR po rekordach z zapytania SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "pętla FOR względem kursora"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "pętla FOREACH po elementach tablicy"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "wyrażenie SQL"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "pętla FOR po wynikach instrukcji EXECUTE"
+
+#: pl_gram.y:474
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "etykieta bloku musi pojawić się przed częścią DECLARE, nie po niej"
+
+#: pl_gram.y:494
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "porównania nie jest dostępne dla typu %s"
+
+#: pl_gram.y:509
+#, c-format
+msgid "row or record variable cannot be CONSTANT"
+msgstr "rekord nie może być zadeklarowany jako CONSTANT"
+
+#: pl_gram.y:519
+#, c-format
+msgid "row or record variable cannot be NOT NULL"
+msgstr "rekord nie może być zadeklarowany jako NOT NULL"
+
+#: pl_gram.y:530
+#, c-format
+msgid "default value for row or record variable is not supported"
+msgstr "domyślna wartość dla rekordów (abstrakcyjnych oraz konkretnego typu) nie jest obsługiwana"
+
+#: pl_gram.y:675 pl_gram.y:690 pl_gram.y:716
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "zmienna \"%s\" nie istnieje"
+
+#: pl_gram.y:734 pl_gram.y:762
+msgid "duplicate declaration"
+msgstr "powtórzona deklaracja"
+
+#: pl_gram.y:745 pl_gram.y:773
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "zmienna \"%s\" przykrywa poprzednio zdefiniowaną zmienną"
+
+#: pl_gram.y:952
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "element diagnostyczny %s nie jest dozwolony w GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:970
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "element diagnostyczny %s nie jest dozwolony w GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1068
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "nieobsługiwany parametr dla instrukcji GET DIAGNOSTICS"
+
+#: pl_gram.y:1079 pl_gram.y:3439
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" nie jest zmienną skalarną"
+
+#: pl_gram.y:1331 pl_gram.y:1525
+#, c-format
+msgid "loop variable of loop over rows must be a record or row variable or list of scalar variables"
+msgstr "zmienna w pętli dla zapytań musi być rekordem (abstrakcyjnym lub konkretnego typu) albo listą zmiennych skalarnych"
+
+#: pl_gram.y:1365
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "w pętli FOR używającej kursorów dopuszczalna jest tylko jedna zmienna iteracyjna"
+
+#: pl_gram.y:1372
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "w pętli FOR można używać jedynie ograniczonych kursorów"
+
+#: pl_gram.y:1456
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "w pętli FOR dla liczb całkowitych można używać jednej zmiennej"
+
+#: pl_gram.y:1492
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "nie można używać REVERSE w pętli FOR dla zapytań"
+
+#: pl_gram.y:1639
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "zmienne używane w pętli FOREACH muszą zostać wcześniej zadeklarowana"
+
+#: pl_gram.y:1680
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr ""
+"nie ma etykiety \"%s\" dołączonej do dowolnego bloku lub pętli zawierającej to "
+"wyrażenie"
+
+#: pl_gram.y:1688
+#, c-format
+#| msgid "portal \"%s\" cannot be run"
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "etykieta bloku \"%s\" nie może być użyta w CONTINUE"
+
+#: pl_gram.y:1703
+#, c-format
+#| msgid "CONTINUE cannot be used outside a loop"
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "instrukcja EXIT nie może być użyta poza pętlą, chyba że ma etykietę"
+
+#: pl_gram.y:1704
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "instrukcja CONTINUE nie może być użyta poza pętlą"
+
+#: pl_gram.y:1728 pl_gram.y:1765 pl_gram.y:1813 pl_gram.y:2889 pl_gram.y:2974
+#: pl_gram.y:3085 pl_gram.y:3841
+msgid "unexpected end of function definition"
+msgstr "nieoczekiwany koniec definicji funkcji"
+
+#: pl_gram.y:1833 pl_gram.y:1857 pl_gram.y:1873 pl_gram.y:1879 pl_gram.y:1997
+#: pl_gram.y:2005 pl_gram.y:2019 pl_gram.y:2114 pl_gram.y:2295 pl_gram.y:2389
+#: pl_gram.y:2541 pl_gram.y:3682 pl_gram.y:3743 pl_gram.y:3822
+msgid "syntax error"
+msgstr "błąd składni"
+
+#: pl_gram.y:1861 pl_gram.y:1863 pl_gram.y:2299 pl_gram.y:2301
+msgid "invalid SQLSTATE code"
+msgstr "błędny kod SQLSTATE"
+
+#: pl_gram.y:2061
+msgid "syntax error, expected \"FOR\""
+msgstr "błąd składniowy, spodziewano się \"FOR\""
+
+#: pl_gram.y:2123
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "instrukcja FETCH nie może zwracać wielu wierszy"
+
+#: pl_gram.y:2179
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "zmienna kursorowa musi być zmienną skalarną"
+
+#: pl_gram.y:2185
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "zmienna \"%s\" musi być typu cursor lub refcursor"
+
+#: pl_gram.y:2512 pl_gram.y:2523
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" nie jest zmienną"
+
+#: pl_gram.y:2627 pl_gram.y:2637 pl_gram.y:2793
+msgid "mismatched parentheses"
+msgstr "niepasujące nawiasy"
+
+#: pl_gram.y:2641
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "brakuje \"%s\" na końcu wyrażenia SQL"
+
+#: pl_gram.y:2647
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "brakuje \"%s\" na końcu instrukcji SQL"
+
+#: pl_gram.y:2664
+msgid "missing expression"
+msgstr "brakuje wyrażenia"
+
+#: pl_gram.y:2666
+msgid "missing SQL statement"
+msgstr "brakuje instrukcji SQL"
+
+#: pl_gram.y:2795
+msgid "incomplete data type declaration"
+msgstr "deklaracja typu abstrakcyjnego"
+
+#: pl_gram.y:2818
+msgid "missing data type declaration"
+msgstr "brakująca deklaracja typu"
+
+#: pl_gram.y:2897
+msgid "INTO specified more than once"
+msgstr "INTO użyte więcej niż raz"
+
+#: pl_gram.y:3066
+msgid "expected FROM or IN"
+msgstr "spodziewano się FROM lub IN"
+
+#: pl_gram.y:3126
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "instrukcja RETURN nie może mieć parametru w funkcjach zwracających zbiory rekordów (SETOF ...)"
+
+#: pl_gram.y:3127
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Użyj RETURN NEXT lub RETURN QUERY."
+
+#: pl_gram.y:3135
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "instrukcja RETURN nie może mieć parametrów w funkcji posiadającej argumenty wyjściowe (OUT, INOUT)"
+
+#: pl_gram.y:3144
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "instrukcja RETURN nie może mieć parametru w funkcji, która nic nie zwraca"
+
+#: pl_gram.y:3204
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "instrukcja RETURN NEXT nie może mieć parametrów w funkcji posiadające argumenty wyjściowe (OUT, INOUT)"
+
+#: pl_gram.y:3308
+#, c-format
+msgid "\"%s\" is declared CONSTANT"
+msgstr "\"%s\" zadeklarowano jako CONSTANT"
+
+#: pl_gram.y:3370 pl_gram.y:3382
+#, c-format
+msgid "record or row variable cannot be part of multiple-item INTO list"
+msgstr "zmienna rekordowa nie może być celem w wyrażeniu INTO określonym dla więcej niż jednego argumentu"
+
+#: pl_gram.y:3427
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "po INTO podano za dużo zmiennych"
+
+#: pl_gram.y:3635
+#, c-format
+msgid "end label \"%s\" specified for unlabelled block"
+msgstr "etykieta \"%s\" podana na końcu bloku, który nie posiada etykiety"
+
+#: pl_gram.y:3642
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "etykieta końcowa \"%s\" jest inna niż etykieta bloku \"%s\""
+
+#: pl_gram.y:3677
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "kursor \"%s\" nie przyjmuje parametrów"
+
+#: pl_gram.y:3691
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "kursor \"%s\" przyjmuje parametry"
+
+#: pl_gram.y:3733
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "kursor \"%s\" nie przyjmuje parametru o nazwie \"%s\""
+
+#: pl_gram.y:3753
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "wartość parametru \"%s\" kursora \"%s\" wskazano więcej niż raz"
+
+#: pl_gram.y:3778
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "za mało argumentów dla kursora \"%s\""
+
+#: pl_gram.y:3785
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "zbyt wiele argumentów dla kursora \"%s\""
+
+#: pl_gram.y:3873
+msgid "unrecognized RAISE statement option"
+msgstr "nieznany parametr dla instrukcji RAISE"
+
+#: pl_gram.y:3877
+msgid "syntax error, expected \"=\""
+msgstr "błąd składniowy, spodziewano się \"=\""
+
+#: pl_gram.y:3918
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "za dużo argumentów dla instrukcji RAISE"
+
+#: pl_gram.y:3922
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "za mało argumentów dla instrukcji RAISE"
+
+#: pl_handler.c:151
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Ustawia sposób rozwiązywania niejednoznaczności nazw zmiennych PL/pgSQL i kolumn tabel."
+
+#: pl_handler.c:160
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Wydrukuj informacje o parametrach w części DETAIL komunikatów o błędach generowanych podczas niepowodzeń INTO ... STRICT."
+
+#: pl_handler.c:168
+msgid "Perform checks given in ASSERT statements."
+msgstr "Wykonanie sprawdzeń podanych w instrukcjach ASSERT."
+
+#: pl_handler.c:176
+msgid "List of programming constructs that should produce a warning."
+msgstr "Lista konstrukcji programowych, które powinny spowodować ostrzeżenie."
+
+#: pl_handler.c:186
+msgid "List of programming constructs that should produce an error."
+msgstr "Lista konstrukcji programowych, które powinny spowodować błąd."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:622
+#, c-format
+msgid "%s at end of input"
+msgstr "%s na końcu danych wejściowych"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:638
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s w lub pobliżu \"%s\""
+
+#~ msgid "RETURN must specify a record or row variable in function returning row"
+#~ msgstr "w funkcji zwracającej zbiory rekordów parametrem instrukcji RETURN musi być rekord (abstrakcyjny lub konkretnego typu)"
+
+#~ msgid "RETURN NEXT must specify a record or row variable in function returning row"
+#~ msgstr "w funkcji zwracającej rekord parametrem instrukcji RETURN NEXT musi również być rekord"
+
+#~ msgid "label does not exist"
+#~ msgstr "etykieta nie istnieje"
+
+#~ msgid "EXECUTE statement"
+#~ msgstr "instrukcja EXECUTE"
diff --git a/src/pl/plpgsql/src/po/pt_BR.po b/src/pl/plpgsql/src/po/pt_BR.po
new file mode 100644
index 0000000..0152c31
--- /dev/null
+++ b/src/pl/plpgsql/src/po/pt_BR.po
@@ -0,0 +1,851 @@
+# Brazilian Portuguese message translation file for plpgsql
+#
+# Copyright (C) 2010-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Euler Taveira <euler@eulerto.com>, 2010-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-27 13:15-0300\n"
+"PO-Revision-Date: 2010-07-08 17:13-0300\n"
+"Last-Translator: Euler Taveira <euler@eulerto.com>\n"
+"Language-Team: Brazilian Portuguese <pgsql-translators@postgresql.org>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n>1);\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "funções PL/pgSQL não podem aceitar tipo %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "não pôde determinar tipo de retorno atual para função polimófica \"%s\""
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funções de gatilho só podem ser chamadas como gatilhos"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "funções PL/pgSQL não podem retornar tipo %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "funções de gatilho não podem ter argumentos declarados"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Os argumentos de um gatilho podem ser acessados através de TG_NARGS e TG_ARGV."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "funções de gatilho de eventos não podem ter argumentos declarados"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "compilação da função PL/pgSQL \"%s\" próximo a linha %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "nome de parâmetro \"%s\" foi especificado mais de uma vez"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "referência à coluna \"%s\" é ambígua"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Ela poderia referenciar uma variável PL/pgSQL ou uma coluna de tabela."
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "registro \"%s\" não tem campo \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "relação \"%s\" não existe"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "relação \"%s\" não tem um tipo composto"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "variável \"%s\" tem pseudo-tipo %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "tipo \"%s\" é indefinido"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "tipo %s não é composto"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "condição de exceção \"%s\" é desconhecida"
+
+#: pl_comp.c:2534
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "não pôde determinar tipo do argumento atual para função polimórfica \"%s\""
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "durante inicialização de estado de execução"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "ao armazenar argumentos em variáveis locais"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "durante entrada da função"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "controle atingiu o fim da função sem RETURN"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "ao converter valor de retorno para tipo de retorno da função"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "função que tem argumento do tipo conjunto foi chamada em um contexto que não pode aceitar um conjunto"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "modo de materialização é requerido, mas ele não é permitido neste contexto"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "durante saída da função"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "tipo record retornado não corresponde ao tipo record esperado"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "controle atingiu o fim da função de gatilho sem RETURN"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "função de gatilho não pode retornar um conjunto"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "estrutura de registro retornada não corresponde a estrutura da tabela que disparou o evento"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "função PL/pgSQL %s linha %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "função PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "função PL/pgSQL %s linha %d em %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "função PL/pgSQL %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "durante inicialização de variável local em bloco de comandos"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "durante entrada em bloco de comandos"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "durante saída em bloco de comandos"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "durante término de exceção"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "parâmetro \"%s\" do procedimento é um parâmetro de saída mas o argumento correspondente não permite escrita"
+
+#: pl_exec.c:2365
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "parâmetro %d do procedimento é um parâmetro de saída mas o argumento correspondente não permite escrita"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS não pode ser utilizado fora de um manipulador de exceção"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "case não foi encontrado"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "comando CASE está faltando a parte ELSE."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "limite inferior do laço FOR não pode ser nulo"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "limite superior do laço FOR não pode ser nulo"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "valor BY do laço FOR não pode ser nulo"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "valor BY do laço FOR deve ser maior do que zero"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "cursor \"%s\" já está em uso"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "argumentos fornecidos a cursor sem argumentos"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "argumentos requeridos pelo cursor"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "expressão FOREACH não deve ser nula"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "expressão FOREACH deve produzir uma matriz, e não tipo %s"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "fatia da dimensão (%d) está fora do intervalo válido, 0..%d"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "variável do laço FOREACH ... SLICE deve ser de um tipo matriz"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "variável do laço FOREACH não deve ser de um tipo matriz"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "não pode retornar valor não-composto de função que retorna tipo composto"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "não pode utilizar RETURN NEXT em uma função que não foi declarada SETOF"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "tipo resultante incorreto foi fornecido em RETURN NEXT"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "tipo registro incorreto foi fornecido em RETURN NEXT"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT deve ter um parâmetro"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "não pode utilizar RETURN QUERY em uma função que não foi declarada SETOF"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "estrutura da consulta não corresponde ao tipo resultante da função"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "argumento da cadeia de caracteres do EXECUTE é nulo"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "opção RAISE já foi especificada: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE sem parâmetros não pode ser utilizado fora de um manipulador de exceção"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "opção do comando RAISE não pode ser nulo"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "asserção falhou"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "não pode executar COPY para/do cliente em PL/pgSQL"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "comando de controle de transação não é suportado em PL/pgSQL"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO utilizado com um comando que não pode retornar dados"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "consulta não retornou registros"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "consulta retornou mais de um registro"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Certifique-se que a consulta retorna um único registro, ou utilize LIMIT 1."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "consulta não tem destino para os dados resultantes"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Se você quer descartar os resultados de um SELECT, utilize PERFORM."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE de SELECT ... INTO não está implementado"
+
+#: pl_exec.c:4499
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Ao invés disso, você pode querer utilizar EXECUTE ... INTO ou EXECUTE CREATE TABLE ... AS."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE de comandos de controle de transação não está implementado"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "variável do cursor \"%s\" é nula"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "cursor \"%s\" não existe"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "posição relativa ou absoluta do cursor é nula"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "valor nulo não pode ser atribuído a variável \"%s\" declarada NOT NULL"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "não pode atribuir valor que não é composto a variável do tipo row"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "não pode atribuir valor que não é composto a variável do tipo record"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "não pode atribuir a coluna do sistema \"%s\""
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "consulta não retornou dados"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "consulta: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "consulta retornou %d coluna"
+msgstr[1] "consulta retornou %d colunas"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "consulta é SELECT INTO mas ela deveria ser um SELECT"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "consulta não é um SELECT"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "tipo de parâmetro %d (%s) não corresponde aquele ao preparar o plano (%s)"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "número de campos de fonte e alvo na atribuição não correspondem"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "verificação %s de %s está ativa."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Certifique-se que se a consulta retorna a lista exata de colunas."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "registro \"%s\" não foi atribuído ainda"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "A estrutura da tupla de um registro não atribuído é indeterminada."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "variável \"%s\" está declarada CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "bloco de comandos"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "atribuição"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR com variável de laço inteira"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR sobre registros de SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR sobre cursor"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH sobre matriz"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "comando SQL"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR sobre comando EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "rótulo de bloco deve estar localizado antes do DECLARE e não depois"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "ordenações não são suportadas pelo tipo %s"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "variável \"%s\" deve ter um valor padrão, pois ela foi declarada NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "variável \"%s\" não existe"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "declaração duplicada"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "variável \"%s\" esconde uma variável previamente definida"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "item de diagnóstico %s não é permitido em GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "item de diagnóstico %s não é permitido em GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "item de GET DIAGNOSTICS desconhecido"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" não é uma variável escalar"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "variável de laço sobre registros deve ser uma variável do tipo record ou row ou lista de variáveis escalares"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "cursor do laço FOR deve ter somente uma variável alvo"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "cursor do laço FOR deve utilizar uma variável cursor limitado"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "inteiro do laço FOR deve ter somente uma variável alvo"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "não pode especificar REVERSE na consulta do laço FOR"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "variável do laço FOEACH deve ser uma variável ou lista de variáveis conhecida"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "não há rótulo \"%s\" ligado a qualquer bloco ou laço que contém este comando"
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "rótulo de bloco \"%s\" não pode ser utilizado no CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT não pode ser utilizado fora de um laço, a menos que ele tenha um rótulo"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE não pode ser utilizado fora de um laço"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "fim de definição da função inesperado"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "erro de sintaxe"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "código SQLSTATE inválido"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "erro de sintaxe, \"FOR\" esperado"
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "comando FETCH não pode retornar múltiplos registros"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "variável do cursor deve ser uma variável simples"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "variável \"%s\" deve ser do tipo cursor ou refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" não é uma variável conhecida"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "parênteses não correspondem"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "faltando \"%s\" ao fim da expressão SQL"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "faltando \"%s\" ao fim do comando SQL"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "faltando expressão"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "faltando comando SQL"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "declaração de tipo de dado incompleta"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "faltando declaração de tipo de dado"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO especificado mais de uma vez"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "FROM ou IN esperado"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN não pode ter um parâmetro na função que retorna conjunto"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Utilize RETURN NEXT ou RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN não pode ter um parâmetro no procedimento"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN não pode ter um parâmetro na função que retorna void"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN não pode ter um parâmetro na função com parâmetros OUT"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT não pode ter um parâmetro na função com parâmetros OUT"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "variável do tipo record não pode ser parte de uma lista INTO de múltiplos itens"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "muitas variáveis INTO especificadas"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "rótulo de fim \"%s\" especificado para bloco sem rótulo"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "rótulo de fim \"%s\" difere de rótulo do bloco \"%s\""
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "cursor \"%s\" não tem argumentos"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "cursor \"%s\" tem argumentos"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "cursor \"%s\" não tem argumento chamado \"%s\""
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "valor para parâmetro \"%s\" do cursor \"%s\" foi especificado mais de uma vez"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "argumentos insuficientes para cursor \"%s\""
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "muitos argumentos para cursor \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "opção do comando RAISE desconhecida"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "erro de sintaxe, \"=\" esperado"
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "muitos parâmetros especificados para RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "poucos parâmetros especificados para RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Define resolução de conflitos entre nomes de variáveis PL/pgSQL e nomes de colunas de tabelas."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Mostra informação sobre parâmetros na parte DETALHE das mensagens de erro geradas nas falhas INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Realiza verificações informadas em comandos ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Lista de construções de programação que devem produzir um aviso."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Lista de construções de programação que devem produzir um erro."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s no fim da entrada"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s em ou próximo a \"%s\""
diff --git a/src/pl/plpgsql/src/po/ru.po b/src/pl/plpgsql/src/po/ru.po
new file mode 100644
index 0000000..5c93496
--- /dev/null
+++ b/src/pl/plpgsql/src/po/ru.po
@@ -0,0 +1,968 @@
+# Russian message translation file for plpgsql
+# Copyright (C) 2012-2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Alexander Lakhin <exclusion@gmail.com>, 2012-2017, 2018, 2019, 2020, 2021, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL current)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-03 05:56+0300\n"
+"PO-Revision-Date: 2022-09-05 13:38+0300\n"
+"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n"
+"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "функции PL/pgSQL не могут принимать тип %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr ""
+"не удалось определить фактический тип результата для полиморфной функции "
+"\"%s\""
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "триггерные функции могут вызываться только в триггерах"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "функции PL/pgSQL не могут возвращать тип %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "у триггерных функций не может быть объявленных аргументов"
+
+#: pl_comp.c:605
+#, c-format
+msgid ""
+"The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV "
+"instead."
+msgstr ""
+"При необходимости к аргументам триггера можно обращаться через переменные "
+"TG_NARGS и TG_ARGV."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "у функций событийных триггеров не может быть объявленных аргументов"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "компиляция функции PL/pgSQL \"%s\" в районе строки %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "имя параметра \"%s\" указано неоднократно"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "неоднозначная ссылка на столбец \"%s\""
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Подразумевается ссылка на переменную PL/pgSQL или столбец таблицы."
+
+#: pl_comp.c:1324 pl_exec.c:5249 pl_exec.c:5422 pl_exec.c:5509 pl_exec.c:5600
+#: pl_exec.c:6621
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "в записи \"%s\" нет поля \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "отношение \"%s\" не существует"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "отношение \"%s\" не имеет составного типа"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "переменная \"%s\" имеет псевдотип %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "тип \"%s\" является пустышкой"
+
+#: pl_comp.c:2204 pl_exec.c:6922
+#, c-format
+msgid "type %s is not composite"
+msgstr "тип %s не является составным"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "нераспознанное условие исключения \"%s\""
+
+#: pl_comp.c:2534
+#, c-format
+msgid ""
+"could not determine actual argument type for polymorphic function \"%s\""
+msgstr ""
+"не удалось определить фактический тип аргумента для полиморфной функции "
+"\"%s\""
+
+#: pl_exec.c:511 pl_exec.c:950 pl_exec.c:1185
+msgid "during initialization of execution state"
+msgstr "в процессе инициализации состояния выполнения"
+
+#: pl_exec.c:517
+msgid "while storing call arguments into local variables"
+msgstr "при сохранении аргументов вызова в локальных переменных"
+
+#: pl_exec.c:605 pl_exec.c:1023
+msgid "during function entry"
+msgstr "при входе в функцию"
+
+#: pl_exec.c:628
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "конец функции достигнут без RETURN"
+
+#: pl_exec.c:634
+msgid "while casting return value to function's return type"
+msgstr "при приведении возвращаемого значения к типу результата функции"
+
+#: pl_exec.c:646 pl_exec.c:3675
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"функция, возвращающая множество, вызвана в контексте, где ему нет места"
+
+#: pl_exec.c:651 pl_exec.c:3681
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "требуется режим материализации, но он недопустим в этом контексте"
+
+#: pl_exec.c:778 pl_exec.c:1049 pl_exec.c:1207
+msgid "during function exit"
+msgstr "при выходе из функции"
+
+#: pl_exec.c:833 pl_exec.c:897 pl_exec.c:3474
+msgid "returned record type does not match expected record type"
+msgstr "возвращаемый тип записи не соответствует ожидаемому"
+
+#: pl_exec.c:1046 pl_exec.c:1204
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "конец триггерной процедуры достигнут без RETURN"
+
+#: pl_exec.c:1054
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "триггерная процедура не может возвращать множество"
+
+#: pl_exec.c:1093 pl_exec.c:1121
+msgid ""
+"returned row structure does not match the structure of the triggering table"
+msgstr ""
+"структура возвращённой строки не соответствует структуре таблицы, вызвавшей "
+"триггер"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "функция PL/pgSQL %s, строка %d, %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1273
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "функция PL/pgSQL %s, %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1281
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "функция PL/pgSQL %s, строка %d, оператор %s"
+
+#: pl_exec.c:1287
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "функция PL/pgSQL %s"
+
+#: pl_exec.c:1658
+msgid "during statement block local variable initialization"
+msgstr "при инициализации локальной переменной в блоке операторов"
+
+#: pl_exec.c:1763
+msgid "during statement block entry"
+msgstr "при входе в блок операторов"
+
+#: pl_exec.c:1795
+msgid "during statement block exit"
+msgstr "при выходе из блока операторов"
+
+#: pl_exec.c:1833
+msgid "during exception cleanup"
+msgstr "при очистке после исключения"
+
+#: pl_exec.c:2370
+#, c-format
+msgid ""
+"procedure parameter \"%s\" is an output parameter but corresponding argument "
+"is not writable"
+msgstr ""
+"параметр процедуры \"%s\" является выходным, но соответствующий аргумент не "
+"допускает запись"
+
+#: pl_exec.c:2375
+#, c-format
+msgid ""
+"procedure parameter %d is an output parameter but corresponding argument is "
+"not writable"
+msgstr ""
+"параметр процедуры %d является выходным, но соответствующий аргумент не "
+"допускает запись"
+
+#: pl_exec.c:2409
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr ""
+"GET STACKED DIAGNOSTICS нельзя использовать вне блока обработчика исключения"
+
+#: pl_exec.c:2609
+#, c-format
+msgid "case not found"
+msgstr "неправильный CASE"
+
+#: pl_exec.c:2610
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "В операторе CASE не хватает части ELSE."
+
+#: pl_exec.c:2703
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "нижняя граница цикла FOR не может быть равна NULL"
+
+#: pl_exec.c:2719
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "верхняя граница цикла FOR не может быть равна NULL"
+
+#: pl_exec.c:2737
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "значение BY в цикле FOR не может быть равно NULL"
+
+#: pl_exec.c:2743
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "значение BY в цикле FOR должно быть больше нуля"
+
+#: pl_exec.c:2877 pl_exec.c:4682
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "курсор \"%s\" уже используется"
+
+#: pl_exec.c:2900 pl_exec.c:4752
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "курсору без аргументов были переданы аргументы"
+
+#: pl_exec.c:2919 pl_exec.c:4771
+#, c-format
+msgid "arguments required for cursor"
+msgstr "курсору требуются аргументы"
+
+#: pl_exec.c:3010
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "выражение FOREACH не может быть равно NULL"
+
+#: pl_exec.c:3025
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "выражение в FOREACH должно быть массивом, но не типом %s"
+
+#: pl_exec.c:3042
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "размерность среза (%d) вне допустимого диапазона 0..%d"
+
+#: pl_exec.c:3069
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "переменная цикла FOREACH ... SLICE должна быть массивом"
+
+#: pl_exec.c:3073
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "переменная цикла FOREACH не должна быть массивом"
+
+#: pl_exec.c:3235 pl_exec.c:3292 pl_exec.c:3467
+#, c-format
+msgid ""
+"cannot return non-composite value from function returning composite type"
+msgstr ""
+"функция, возвращающая составной тип, не может вернуть несоставное значение"
+
+#: pl_exec.c:3331 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr ""
+"RETURN NEXT можно использовать только в функциях, возвращающих множества"
+
+#: pl_exec.c:3372 pl_exec.c:3504
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "в RETURN NEXT передан неправильный тип результата"
+
+#: pl_exec.c:3410 pl_exec.c:3431
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "в RETURN NEXT передан неправильный тип записи"
+
+#: pl_exec.c:3523
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "у оператора RETURN NEXT должен быть параметр"
+
+#: pl_exec.c:3551 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr ""
+"RETURN QUERY можно использовать только в функциях, возвращающих множества"
+
+#: pl_exec.c:3569
+msgid "structure of query does not match function result type"
+msgstr "структура запроса не соответствует типу результата функции"
+
+#: pl_exec.c:3624 pl_exec.c:4459 pl_exec.c:8721
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "в качестве текста запроса в EXECUTE передан NULL"
+
+#: pl_exec.c:3709 pl_exec.c:3847
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "этот параметр RAISE уже указан: %s"
+
+#: pl_exec.c:3743
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr ""
+"RAISE без параметров нельзя использовать вне блока обработчика исключения"
+
+#: pl_exec.c:3837
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "параметром оператора RAISE не может быть NULL"
+
+#: pl_exec.c:3907
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3962
+#, c-format
+msgid "assertion failed"
+msgstr "нарушение истинности"
+
+#: pl_exec.c:4332 pl_exec.c:4521
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "в PL/pgSQL нельзя выполнить COPY с участием клиента"
+
+#: pl_exec.c:4338
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "неподдерживаемая транзакционная команда в PL/pgSQL"
+
+#: pl_exec.c:4361 pl_exec.c:4550
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO с командой не может возвращать данные"
+
+#: pl_exec.c:4384 pl_exec.c:4573
+#, c-format
+msgid "query returned no rows"
+msgstr "запрос не вернул строк"
+
+#: pl_exec.c:4406 pl_exec.c:4592 pl_exec.c:5744
+#, c-format
+msgid "query returned more than one row"
+msgstr "запрос вернул несколько строк"
+
+#: pl_exec.c:4408
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr ""
+"Измените запрос, чтобы он выбирал одну строку, или используйте LIMIT 1."
+
+#: pl_exec.c:4424
+#, c-format
+msgid "query has no destination for result data"
+msgstr "в запросе нет назначения для данных результата"
+
+#: pl_exec.c:4425
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Если вам нужно отбросить результаты SELECT, используйте PERFORM."
+
+#: pl_exec.c:4513
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "возможность выполнения SELECT ... INTO в EXECUTE не реализована"
+
+# skip-rule: space-before-ellipsis
+#: pl_exec.c:4514
+#, c-format
+msgid ""
+"You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS "
+"instead."
+msgstr ""
+"Альтернативой может стать EXECUTE ... INTO или EXECUTE CREATE TABLE ... "
+"AS ..."
+
+#: pl_exec.c:4527
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE с транзакционными командами не поддерживается"
+
+#: pl_exec.c:4837 pl_exec.c:4925
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "переменная курсора \"%s\" равна NULL"
+
+#: pl_exec.c:4848 pl_exec.c:4936
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "курсор \"%s\" не существует"
+
+#: pl_exec.c:4861
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "относительная или абсолютная позиция курсора равна NULL"
+
+#: pl_exec.c:5099 pl_exec.c:5194
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "значение NULL нельзя присвоить переменной \"%s\", объявленной NOT NULL"
+
+#: pl_exec.c:5175
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "переменной типа кортеж можно присвоить только составное значение"
+
+#: pl_exec.c:5207
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "переменной типа запись можно присвоить только составное значение"
+
+#: pl_exec.c:5258
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "присвоить значение системному столбцу \"%s\" нельзя"
+
+#: pl_exec.c:5707
+#, c-format
+msgid "query did not return data"
+msgstr "запрос не вернул данные"
+
+#: pl_exec.c:5708 pl_exec.c:5720 pl_exec.c:5745 pl_exec.c:5821 pl_exec.c:5826
+#, c-format
+msgid "query: %s"
+msgstr "запрос: %s"
+
+#: pl_exec.c:5716
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "запрос вернул %d столбец"
+msgstr[1] "запрос вернул %d столбца"
+msgstr[2] "запрос вернул %d столбцов"
+
+#: pl_exec.c:5820
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "запрос - не просто SELECT, а SELECT INTO"
+
+#: pl_exec.c:5825
+#, c-format
+msgid "query is not a SELECT"
+msgstr "запрос - не SELECT"
+
+#: pl_exec.c:6635 pl_exec.c:6675 pl_exec.c:6715
+#, c-format
+msgid ""
+"type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr ""
+"тип параметра %d (%s) не соответствует тому, с которым подготавливался план "
+"(%s)"
+
+#: pl_exec.c:7126 pl_exec.c:7160 pl_exec.c:7234 pl_exec.c:7260
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "в левой и правой части присваивания разное количество полей"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7128 pl_exec.c:7162 pl_exec.c:7236 pl_exec.c:7262
+#, c-format
+msgid "%s check of %s is active."
+msgstr "Включена проверка %s (с %s)."
+
+#: pl_exec.c:7132 pl_exec.c:7166 pl_exec.c:7240 pl_exec.c:7266
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr ""
+"Измените запрос, чтобы он возвращал в точности требуемый список столбцов."
+
+#: pl_exec.c:7653
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "записи \"%s\" не присвоено значение"
+
+#: pl_exec.c:7654
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr ""
+"Для записи, которой не присвоено значение, структура кортежа не определена."
+
+#: pl_exec.c:8319 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "переменная \"%s\" объявлена как CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "блок операторов"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "присваивание"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR с целочисленной переменной цикла"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR по результатам SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR по курсору"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH для массива"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL-оператор"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR по результатам EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "метка блока должна помещаться до DECLARE, а не после"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "тип %s не поддерживает сортировку (COLLATION)"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr ""
+"у переменной \"%s\" должно быть значение по умолчанию, так как она объявлена "
+"как NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "переменная \"%s\" не существует"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "повторяющееся объявление"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "переменная \"%s\" скрывает ранее определённую переменную"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "команда GET STACKED DIAGNOSTICS не принимает элемент %s"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "команда GET CURRENT DIAGNOSTICS не принимает элемент %s"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "нераспознанный элемент GET DIAGNOSTICS"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" - не скалярная переменная"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid ""
+"loop variable of loop over rows must be a record variable or list of scalar "
+"variables"
+msgstr ""
+"переменная цикла по кортежам должна быть переменной типа запись или списком "
+"скалярных переменных"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "в цикле FOR с курсором должна быть только одна переменная"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr ""
+"в цикле FOR с курсором должен использоваться курсор, привязанный к запросу"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "в целочисленном цикле FOR должна быть только одна переменная"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "в цикле FOR с запросом нельзя указать REVERSE"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr ""
+"переменной цикла FOREACH должна быть известная переменная или список "
+"переменных"
+
+#: pl_gram.y:1742
+#, c-format
+msgid ""
+"there is no label \"%s\" attached to any block or loop enclosing this "
+"statement"
+msgstr "в блоке или цикле, окружающем этот оператор, нет метки \"%s\""
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "метку блока \"%s\" нельзя использовать в CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT можно использовать вне цикла только с указанием метки"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE нельзя использовать вне цикла"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "неожиданный конец определения функции"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "ошибка синтаксиса"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "неверный код SQLSTATE"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "ошибка синтаксиса, ожидался \"FOR\""
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "оператор FETCH не может вернуть несколько строк"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "переменная-курсор должна быть простой переменной"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "переменная \"%s\" должна быть типа cursor или refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" - не известная переменная"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "непарные скобки"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "отсутствует \"%s\" в конце выражения SQL"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "отсутствует \"%s\" в конце оператора SQL"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "отсутствует выражение"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "отсутствует оператор SQL"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "неполное определение типа данных"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "отсутствует определение типа данных"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO указано неоднократно"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "ожидалось FROM или IN"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "в функции, возвращающей множество, RETURN должен быть без параметров"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Используйте RETURN NEXT или RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "в процедуре RETURN должен быть без параметров"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "в функции, не возвращающей ничего, RETURN не должен иметь параметров"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN должен быть без параметров в функции с параметрами OUT"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT должен быть без параметров в функции с параметрами OUT"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr ""
+"переменная типа запись не может быть частью списка INTO с несколькими "
+"элементами"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "указано слишком много переменных INTO"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "конечная метка \"%s\" указана для непомеченного блока"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "конечная метка \"%s\" отличается от метки блока \"%s\""
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "курсор \"%s\" не имеет аргументов"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "курсор \"%s\" имеет аргументы"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "курсор \"%s\" не имеет аргумента \"%s\""
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "значение параметра \"%s\" курсора \"%s\" указано неоднократно"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "недостаточно аргументов для курсора \"%s\""
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "слишком много аргументов для курсора \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "нераспознанный параметр оператора RAISE"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "ошибка синтаксиса, ожидалось \"=\""
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "слишком много параметров для RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "недостаточно параметров для RAISE"
+
+#: pl_handler.c:156
+msgid ""
+"Sets handling of conflicts between PL/pgSQL variable names and table column "
+"names."
+msgstr ""
+"Выбирает режим разрешения конфликтов между именами переменных PL/pgSQL и "
+"именами столбцов таблиц."
+
+#: pl_handler.c:165
+msgid ""
+"Print information about parameters in the DETAIL part of the error messages "
+"generated on INTO ... STRICT failures."
+msgstr ""
+"Добавляет информацию о параметрах в раздел DETAIL сообщений, выводимых при "
+"ошибках в INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Выполняет проверки, заданные в операторах ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr ""
+"Список программных конструкций, которые должны выдавать предупреждения."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Список программных конструкций, которые должны выдавать ошибку."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s в конце"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s (примерное положение: \"%s\")"
+
+#~ msgid "query \"%s\" returned more than one row"
+#~ msgstr "запрос \"%s\" вернул несколько строк"
+
+#~ msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+#~ msgstr "число размерностей массива (%d) превышает предел (%d)"
+
+#~ msgid "subscripted object is not an array"
+#~ msgstr "для объекта указан индекс, но этот объект - не массив"
+
+#~ msgid "array subscript in assignment must not be null"
+#~ msgstr "индекс элемента массива в присваивании не может быть NULL"
+
+#~ msgid "relation \"%s\" is not a table"
+#~ msgstr "отношение \"%s\" не является таблицей"
+
+#~ msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+#~ msgstr ""
+#~ "переменная \"%s\", объявленная NOT NULL, не может иметь значение по "
+#~ "умолчанию NULL"
+
+#~ msgid "Use a BEGIN block with an EXCEPTION clause instead."
+#~ msgstr "Используйте блок BEGIN с предложением EXCEPTION."
+
+#~ msgid "row or record variable cannot be CONSTANT"
+#~ msgstr "переменная типа кортеж или запись не может быть константой"
+
+#~ msgid "row or record variable cannot be NOT NULL"
+#~ msgstr "переменная типа кортеж или запись не может быть NULL"
+
+#~ msgid "default value for row or record variable is not supported"
+#~ msgstr ""
+#~ "переменная типа кортеж или запись не может иметь значения по умолчанию"
+
+#~ msgid "EXECUTE statement"
+#~ msgstr "оператор EXECUTE"
+
+#~ msgid "label does not exist"
+#~ msgstr "метка не существует"
+
+#~ msgid ""
+#~ "RETURN must specify a record or row variable in function returning row"
+#~ msgstr ""
+#~ "в функции, возвращающей кортеж, в RETURN должна указываться запись или "
+#~ "кортеж"
+
+#~ msgid ""
+#~ "RETURN NEXT must specify a record or row variable in function returning "
+#~ "row"
+#~ msgstr ""
+#~ "в функции, возвращающей кортеж, в RETURN NEXT должна указываться запись "
+#~ "или кортеж"
+
+#~ msgid "duplicate value for cursor \"%s\" parameter \"%s\""
+#~ msgstr "дублирующееся значение для \"%s\" (параметр \"%s\")"
+
+#~ msgid "relation \"%s.%s\" does not exist"
+#~ msgstr "отношение \"%s.%s\" не существует"
diff --git a/src/pl/plpgsql/src/po/sv.po b/src/pl/plpgsql/src/po/sv.po
new file mode 100644
index 0000000..4ba6138
--- /dev/null
+++ b/src/pl/plpgsql/src/po/sv.po
@@ -0,0 +1,849 @@
+# Swedish message translation file for plpgsql
+# Copyright (C) 2017 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Dennis Björklund <db@zigo.dhs.org>, 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-11 13:39+0000\n"
+"PO-Revision-Date: 2023-03-09 22:40+0100\n"
+"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n"
+"Language-Team: Swedish <pgsql-translators@postgresql.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL-funktioner kan inte acceptera typ %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "kunde inte bestämma aktuell returtyp för polymorfisk funktion \"%s\""
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "triggerfunktioner kan bara anropas som triggrar"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL-funktioner kan inte returnera typ %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "triggerfunktioner kan inte ha deklarerade argument"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Argumenten till triggern kan accessas via TG_NARGS och TG_ARGV istället."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "händelsetriggerfunktioner kan inte ha deklarerade argument"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "kompilering av PL/pgSQL-funktion \"%s\" nära rad %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "parameternamn \"%s\" angivet mer än en gång"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "kolumnreferens \"%s\" är tvetydig"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Det kan referera till antingen en PL/pgSQL-variabel eller en tabellkolumn."
+
+#: pl_comp.c:1324 pl_exec.c:5216 pl_exec.c:5389 pl_exec.c:5476 pl_exec.c:5567
+#: pl_exec.c:6588
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "post \"%s\" saknar fält \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "relationen \"%s\" existerar inte"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "relationen \"%s\" har inte en composite-typ"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "variabel \"%s\" har pseudotyp %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "typ \"%s\" är bara ett skal"
+
+#: pl_comp.c:2204 pl_exec.c:6889
+#, c-format
+msgid "type %s is not composite"
+msgstr "typen %s är inte composite"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "okänt avbrottsvillkor \"%s\""
+
+#: pl_comp.c:2526
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "kunde inte bestämma argumenttyp för polymorfisk funktion function \"%s\""
+
+#: pl_exec.c:500 pl_exec.c:939 pl_exec.c:1174
+msgid "during initialization of execution state"
+msgstr "unde initiering av körtillstånd"
+
+#: pl_exec.c:506
+msgid "while storing call arguments into local variables"
+msgstr "under sparande av anropsargument till lokala variabler"
+
+#: pl_exec.c:594 pl_exec.c:1012
+msgid "during function entry"
+msgstr "under funktionsingången"
+
+#: pl_exec.c:617
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "kontrollen nådde slutet av funktionen utan RETURN"
+
+#: pl_exec.c:623
+msgid "while casting return value to function's return type"
+msgstr "under typomvandling av returvärde till funktionens returtyp"
+
+#: pl_exec.c:635 pl_exec.c:3656
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "en funktion som returnerar en mängd anropades i kontext som inte godtar en mängd"
+
+#: pl_exec.c:640 pl_exec.c:3662
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialiserat läge krävs, men stöds inte i detta kontext"
+
+#: pl_exec.c:767 pl_exec.c:1038 pl_exec.c:1196
+msgid "during function exit"
+msgstr "under funktionsavslutning"
+
+#: pl_exec.c:822 pl_exec.c:886 pl_exec.c:3455
+msgid "returned record type does not match expected record type"
+msgstr "returnerad posttyp matchar inte förväntad posttyp"
+
+#: pl_exec.c:1035 pl_exec.c:1193
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "programflödet nådde slutet på triggerprocedur utan RETURN"
+
+#: pl_exec.c:1043
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "triggerprocedur kan inte returnera en mängd"
+
+#: pl_exec.c:1082 pl_exec.c:1110
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "returnerad radstruktur matchar inte strukturen på triggad tabell"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1251
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL-funktion %s rad %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL-funktion %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1270
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "PL/pgSQL-funktion %s rad %d vid %s"
+
+#: pl_exec.c:1276
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL-funktion %s"
+
+#: pl_exec.c:1647
+msgid "during statement block local variable initialization"
+msgstr "under initiering av lokala variabler i satsblock"
+
+#: pl_exec.c:1752
+msgid "during statement block entry"
+msgstr "under ingång till satsblock"
+
+#: pl_exec.c:1784
+msgid "during statement block exit"
+msgstr "under satsblockavslutning"
+
+#: pl_exec.c:1822
+msgid "during exception cleanup"
+msgstr "under avbrottsuppstädning"
+
+#: pl_exec.c:2355
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "procedurparameter \"%s\" är en utdataparameter men motsvarande argument är inte skrivbar"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "procedurparameter %d är en utdataparameter men motsvarande argument är inte skrivbar"
+
+#: pl_exec.c:2394
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS kan inte användas utanför en avbrottshanterare"
+
+#: pl_exec.c:2594
+#, c-format
+msgid "case not found"
+msgstr "hittade inte alternativ"
+
+#: pl_exec.c:2595
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE-sats saknar ELSE-del."
+
+#: pl_exec.c:2688
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "lägre gräns i FOR-loop kan inte vara null"
+
+#: pl_exec.c:2704
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "övre gräns i FOR-loop kan inte vara null"
+
+#: pl_exec.c:2722
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "BY-värde i FOR-loop kan inte vara null"
+
+#: pl_exec.c:2728
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "BY-värde i FOR-loop måste vara större än noll"
+
+#: pl_exec.c:2862 pl_exec.c:4658
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "markören \"%s\" används redan"
+
+#: pl_exec.c:2885 pl_exec.c:4723
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "argument angivna till markör utan argumnet"
+
+#: pl_exec.c:2904 pl_exec.c:4742
+#, c-format
+msgid "arguments required for cursor"
+msgstr "argument krävs för markör"
+
+#: pl_exec.c:2991
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH-uttryck får inte vara null"
+
+#: pl_exec.c:3006
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH-uttryck måste ge en array, inte typ %s"
+
+#: pl_exec.c:3023
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "slice-storlek (%d) är utanför giltigt intervall 0..%d"
+
+#: pl_exec.c:3050
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE-loop-variabel måste ha typen array"
+
+#: pl_exec.c:3054
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH-loop-variable får inte ha typen array"
+
+#: pl_exec.c:3216 pl_exec.c:3273 pl_exec.c:3448
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "kan inte returnera icke-composit-värde från funktion med returtyp composit"
+
+#: pl_exec.c:3312 pl_gram.y:3318
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "kan inte använda RETURN NEXT i en icke-SETOF-funktion"
+
+#: pl_exec.c:3353 pl_exec.c:3485
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "fel resultattyp given i RETURN NEXT"
+
+#: pl_exec.c:3391 pl_exec.c:3412
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "fel posttyp given i RETURN NEXT"
+
+#: pl_exec.c:3504
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT måste ha en parameter"
+
+#: pl_exec.c:3532 pl_gram.y:3382
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "kan inte använda RETURN QUERY i en icke-SETOF-funktion"
+
+#: pl_exec.c:3550
+msgid "structure of query does not match function result type"
+msgstr "strukturen på frågan matchar inte funktionens resultattyp"
+
+#: pl_exec.c:3605 pl_exec.c:4435 pl_exec.c:8630
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "frågesträngargumentet till EXECUTE är null"
+
+#: pl_exec.c:3690 pl_exec.c:3828
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE-flagga redan angiven: %s"
+
+#: pl_exec.c:3724
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE utan parametrar kan inte användas utanför en avbrottshanterare"
+
+#: pl_exec.c:3818
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE-satsens flagga får inte vare null"
+
+#: pl_exec.c:3888
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3943
+#, c-format
+msgid "assertion failed"
+msgstr "assert misslyckades"
+
+#: pl_exec.c:4308 pl_exec.c:4497
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "kan inte COPY till/från klient i PL/pgSQL"
+
+#: pl_exec.c:4314
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "transaktionskommando saknar stöd i PL/pgSQL"
+
+#: pl_exec.c:4337 pl_exec.c:4526
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO använd med ett kommando som inte returnerar data"
+
+#: pl_exec.c:4360 pl_exec.c:4549
+#, c-format
+msgid "query returned no rows"
+msgstr "frågan returnerade inga rader"
+
+#: pl_exec.c:4382 pl_exec.c:4568 pl_exec.c:5711
+#, c-format
+msgid "query returned more than one row"
+msgstr "frågan returnerade mer än en rad"
+
+#: pl_exec.c:4384
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Se till att frågan returerar exakt en rad eller använd LIMIT 1."
+
+#: pl_exec.c:4400
+#, c-format
+msgid "query has no destination for result data"
+msgstr "frågan har ingen destination för resultatdatan"
+
+#: pl_exec.c:4401
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Om du vill slänga resultatet av en SELECT, använd PERFORM istället."
+
+#: pl_exec.c:4489
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE för SELECT ... INTO är inte implementerad"
+
+#: pl_exec.c:4490
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Du vill nog använda EXECUTE ... INTO eller EXECUTE CREATE TABLE ... AS istället."
+
+#: pl_exec.c:4503
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE på transaktionskommanon är inte implementerat"
+
+#: pl_exec.c:4804 pl_exec.c:4892
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "markörvariabel \"%s\" är null"
+
+#: pl_exec.c:4815 pl_exec.c:4903
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "markör \"%s\" existerar inte"
+
+#: pl_exec.c:4828
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "relativ eller absolut markörposition är null"
+
+#: pl_exec.c:5066 pl_exec.c:5161
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "null-value kan inte tilldelas till variabel \"%s\" som deklarerats NOT NULL"
+
+#: pl_exec.c:5142
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "kan inte tilldela icke-composite-värde till radvariabel"
+
+#: pl_exec.c:5174
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "kan inte tilldela icke-composite-värde till en post-variabel"
+
+#: pl_exec.c:5225
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "kan inte skriva till systemkolumn \"%s\""
+
+#: pl_exec.c:5674
+#, c-format
+msgid "query did not return data"
+msgstr "frågan returnerade ingen data"
+
+#: pl_exec.c:5675 pl_exec.c:5687 pl_exec.c:5712 pl_exec.c:5788 pl_exec.c:5793
+#, c-format
+msgid "query: %s"
+msgstr "fråga: %s"
+
+#: pl_exec.c:5683
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "frågan returnerade %d kolumn"
+msgstr[1] "frågan returnerade %d kolumner"
+
+#: pl_exec.c:5787
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "frågan är SELECT INTO men skall vara en vanlig SELECT"
+
+#: pl_exec.c:5792
+#, c-format
+msgid "query is not a SELECT"
+msgstr "frågan är inte en SELECT"
+
+#: pl_exec.c:6602 pl_exec.c:6642 pl_exec.c:6682
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "typen av parameter %d (%s) matchar inte det som var vid preparerande av plan (%s)"
+
+#: pl_exec.c:7093 pl_exec.c:7127 pl_exec.c:7201 pl_exec.c:7227
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "antal käll- och mål-fält i tilldelningen matchar inte"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7095 pl_exec.c:7129 pl_exec.c:7203 pl_exec.c:7229
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s kontroll av %s är aktiv."
+
+#: pl_exec.c:7099 pl_exec.c:7133 pl_exec.c:7207 pl_exec.c:7233
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Se till att frågan returerar exakt rätt lista med kolumner."
+
+#: pl_exec.c:7620
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "posten \"%s\" är inte tilldelad än"
+
+#: pl_exec.c:7621
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Tuple-strukturen av en ej-ännu-tilldelad post är obestämd."
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "satsblock"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "tilldelning"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR med helatalsloopvariabel"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR över SELECT-rader"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR över markör"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH över array"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL-sats"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR över EXECUTE-sats"
+
+#: pl_gram.y:486
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "blocketikett måste anges före DECLARE, inte efter"
+
+#: pl_gram.y:506
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "jämförelser stöds inte för typ %s"
+
+#: pl_gram.y:525
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "variabel \"%s\" måste ha ett default-värde då det inte deklarerats som NOT NULL"
+
+#: pl_gram.y:673 pl_gram.y:688 pl_gram.y:714
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "variabel \"%s\" finns inte"
+
+#: pl_gram.y:732 pl_gram.y:760
+msgid "duplicate declaration"
+msgstr "duplicerad deklaration"
+
+#: pl_gram.y:743 pl_gram.y:771
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "variabeln \"%s\" döljer en tidigare definierad variabel"
+
+#: pl_gram.y:1043
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "diagnostikdel %s tillåts inte i GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1061
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "diagnostikdel %s tillåts inte i GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1156
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "okänd GET DIAGNOSTICS-del"
+
+#: pl_gram.y:1172 pl_gram.y:3557
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" är inte ett skalärt värde"
+
+#: pl_gram.y:1402 pl_gram.y:1596
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "loop-variabeln för loop över rader måste vara en postvariabel eller en lista av skalärvariabler"
+
+#: pl_gram.y:1437
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "markör-FOR-loop måste ha exakt en målvariabel"
+
+#: pl_gram.y:1444
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "markör-FOR-loop måste använda en bunden markörvariabel"
+
+#: pl_gram.y:1535
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "heltals-FOR-loop måste ha exakt en målvariabel"
+
+#: pl_gram.y:1569
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "kan inte ange REVERSE i fråge-FOR-loop"
+
+#: pl_gram.y:1699
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "loop-variabel för FOREACH måste vara en känd variabel eller lista av variabler"
+
+#: pl_gram.y:1741
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "det finns ingen etikett \"%s\" kopplad till något block eller loop-omslutning i denna sats"
+
+#: pl_gram.y:1749
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "blocketikett \"%s\" kan inte användas i CONTINUE"
+
+#: pl_gram.y:1764
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT kan inte användas utanför en loop, om den inte har en etikett"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE kan inte användas utanför en loop"
+
+#: pl_gram.y:1789 pl_gram.y:1827 pl_gram.y:1875 pl_gram.y:3004 pl_gram.y:3092
+#: pl_gram.y:3203 pl_gram.y:3956
+msgid "unexpected end of function definition"
+msgstr "oväntat slut på funktionsdefinitionen"
+
+#: pl_gram.y:1895 pl_gram.y:1919 pl_gram.y:1935 pl_gram.y:1941 pl_gram.y:2066
+#: pl_gram.y:2074 pl_gram.y:2088 pl_gram.y:2183 pl_gram.y:2407 pl_gram.y:2497
+#: pl_gram.y:2655 pl_gram.y:3799 pl_gram.y:3860 pl_gram.y:3937
+msgid "syntax error"
+msgstr "syntaxfel"
+
+#: pl_gram.y:1923 pl_gram.y:1925 pl_gram.y:2411 pl_gram.y:2413
+msgid "invalid SQLSTATE code"
+msgstr "ogiltig SQLSTATE-kod"
+
+#: pl_gram.y:2131
+msgid "syntax error, expected \"FOR\""
+msgstr "syntaxfel, förväntade \"FOR\""
+
+#: pl_gram.y:2192
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH-sats kan inte returnera multipla rader"
+
+#: pl_gram.y:2289
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "markörvariabel måste vara en enkel variabel"
+
+#: pl_gram.y:2295
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "variabel \"%s\" måste ha typen cursor eller refcursor"
+
+#: pl_gram.y:2626 pl_gram.y:2637
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" är inte en känd variabel"
+
+#: pl_gram.y:2743 pl_gram.y:2753 pl_gram.y:2909
+msgid "mismatched parentheses"
+msgstr "missmatchade parenteser"
+
+#: pl_gram.y:2757
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "saknar \"%s\" vid slutet av SQL-uttryck"
+
+#: pl_gram.y:2763
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "saknar \"%s\" vid slutet av SQL-sats"
+
+#: pl_gram.y:2780
+msgid "missing expression"
+msgstr "saknar uttryck"
+
+#: pl_gram.y:2782
+msgid "missing SQL statement"
+msgstr "saknars SQL-sats"
+
+#: pl_gram.y:2911
+msgid "incomplete data type declaration"
+msgstr "inkomplett datatypdeklaration"
+
+#: pl_gram.y:2934
+msgid "missing data type declaration"
+msgstr "saknar datatypdeklaration"
+
+#: pl_gram.y:3014
+msgid "INTO specified more than once"
+msgstr "INTO angiven mer än en gång"
+
+#: pl_gram.y:3184
+msgid "expected FROM or IN"
+msgstr "förväntade FROM eller IN"
+
+#: pl_gram.y:3245
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN kan inte ha en parameter i funktion som returnerar en mängd"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Använd RETURN NEXT eller RETURN QUERY."
+
+#: pl_gram.y:3256
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN kan inte ha en parameter i en procedur"
+
+#: pl_gram.y:3261
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN kan inte ha en parameter i funktion som returnerar void"
+
+#: pl_gram.y:3270
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN kan inte ha en parameter i en funktion med OUT-parameterar"
+
+#: pl_gram.y:3333
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT kan inte ha en parameter i funktion med OUT-parametrar"
+
+#: pl_gram.y:3441
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "variabel \"%s\" är deklarerad CONSTANT"
+
+#: pl_gram.y:3499
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "postvariabel kan inte vara del av en multipel-INTO-lista"
+
+#: pl_gram.y:3545
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "för många INTO-variabler angivna"
+
+#: pl_gram.y:3753
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "slutetikett \"%s\" angiven för block utan etikett"
+
+#: pl_gram.y:3760
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "slutetikett \"%s\" stämmer inte med blockets etikett \"%s\""
+
+#: pl_gram.y:3794
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "markör \"%s\" har inga argument"
+
+#: pl_gram.y:3808
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "markör \"%s\" har argument"
+
+#: pl_gram.y:3850
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "markör \"%s\" har inga argument med namn \"%s\""
+
+#: pl_gram.y:3870
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "värdet för parameter \"%s\" i markör \"%s\" är angivet mer än en gång"
+
+#: pl_gram.y:3895
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "ej tillräckligt med argument för markör \"%s\""
+
+#: pl_gram.y:3902
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "fär många argument för markör \"%s\""
+
+#: pl_gram.y:3988
+msgid "unrecognized RAISE statement option"
+msgstr "okänd RAISE-sats-flagga"
+
+#: pl_gram.y:3992
+msgid "syntax error, expected \"=\""
+msgstr "syntaxfel, förväntade \"=\""
+
+#: pl_gram.y:4033
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "för många parametrar angivna för RAISE"
+
+#: pl_gram.y:4037
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "för få parametrar angivna för RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Sätter hantering av konflikter mellan PL/pgSQL-variabelnamn och tabellkolumnnamn."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Skriv information om parametrar i DETAIL-delen av felmeddelanden vid INTO ... STRICT-fel."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Utför kontroller angivna i ASSERT-satser."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Lista av programmeringskonstruktioner som skall ge en varning."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Lista av programmeringskonstruktioner som skall ge ett fel"
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s vid slutet av indatan"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s vid eller nära \"%s\""
diff --git a/src/pl/plpgsql/src/po/tr.po b/src/pl/plpgsql/src/po/tr.po
new file mode 100644
index 0000000..907f2c6
--- /dev/null
+++ b/src/pl/plpgsql/src/po/tr.po
@@ -0,0 +1,929 @@
+# LANGUAGE message translation file for plpgsql
+# Copyright (C) 2009 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2009.
+# Abdullah Gülner <agulner@gmail.com>, 2017, 2018, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 8.4\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-04-26 13:38+0000\n"
+"PO-Revision-Date: 2019-06-13 17:09+0300\n"
+"Last-Translator: Abdullah Gülner\n"
+"Language-Team: TR <ceviri@postgresql.org.tr>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.8.7.1\n"
+
+#: pl_comp.c:436 pl_handler.c:461
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL fonksiyonları %s veri tipini kabul etmezler"
+
+#: pl_comp.c:524
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "\"%s\" polimorfik fonksiyonunun asıl dönüşdeğeri belirlenemedi"
+
+#: pl_comp.c:554
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "trigger fonksiyonları sadece trigger olarak çağırılabilirler"
+
+#: pl_comp.c:558 pl_handler.c:445
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL fonksiyonları %s tipini döndüremezler"
+
+#: pl_comp.c:597
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "trigger fonksiyonları belirtilmiş (declared) argümanlara sahip olamaz"
+
+#: pl_comp.c:598
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Tetikleyici bağımsız değişkenlerine TG_NARGS ve TG_ARGV üzerinden erişilebilir."
+
+#: pl_comp.c:721
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "olay tetikleyici (trigger) fonksiyonları belirtilmiş (declared) argümanlara sahip olamaz"
+
+#: pl_comp.c:980
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "\"%s\" fonkiyonununun %d numaralı satırının civarlarında derlenmesi"
+
+#: pl_comp.c:1003
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "\"%s\" parametresi birden fazla kez kullanılmıştır"
+
+#: pl_comp.c:1115
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "\"%s\" sütun referansı iki anlamlı"
+
+#: pl_comp.c:1117
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Ya bir PL/pgSQL değişkenine ya da bir tablo sütununa atıfta bulunuyor olabilir."
+
+#: pl_comp.c:1300 pl_exec.c:5106 pl_exec.c:5471 pl_exec.c:5558 pl_exec.c:5649
+#: pl_exec.c:6566
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "\"%s\" kaydı \"%s\" alanını içermiyor"
+
+#: pl_comp.c:1765
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "\"%s\" nesnesi mevcut değil"
+
+#: pl_comp.c:1857
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "\"%s\" değişkeni %s pseudo tipine sahip"
+
+#: pl_comp.c:2037
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "\"%s\" tipi bir shelldir"
+
+#: pl_comp.c:2134 pl_comp.c:2187
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "tanımlanamayan exception durumu \"%s\""
+
+#: pl_comp.c:2401
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "\"%s\" polimorfik fonksiyonu için gerçek argüman tipi belirlenemedi"
+
+#: pl_exec.c:475 pl_exec.c:887 pl_exec.c:1125
+msgid "during initialization of execution state"
+msgstr "çalıştırma durumu ilklendirmesi sırasında"
+
+#: pl_exec.c:481
+msgid "while storing call arguments into local variables"
+msgstr "çağrı argümanlarını yerel değişkenlerde saklarken"
+
+#: pl_exec.c:569 pl_exec.c:960
+msgid "during function entry"
+msgstr "fonksiyon girişi sırasında"
+
+#: pl_exec.c:594
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "control fonksiyonun sonuna RETURNsüz ulaştı"
+
+#: pl_exec.c:601
+msgid "while casting return value to function's return type"
+msgstr "dönüş değerini fonksiyonun dönüş tipine dönüştürürken"
+
+#: pl_exec.c:614 pl_exec.c:3556
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set değerini kabul etmediği ortamda set değeri alan fonksiyon çağırılmış"
+
+#: pl_exec.c:740 pl_exec.c:989 pl_exec.c:1150
+msgid "during function exit"
+msgstr "fonksiyon çıkışı sırasında"
+
+#: pl_exec.c:795 pl_exec.c:834 pl_exec.c:3401
+msgid "returned record type does not match expected record type"
+msgstr "dönen kayıt tipi beklenen kayıt tipine uymuyor"
+
+#: pl_exec.c:985 pl_exec.c:1146
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "trigger yordamı RETURN olmadan bitti"
+
+#: pl_exec.c:994
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "trigger yordamı bir küme döndüremez"
+
+#: pl_exec.c:1033 pl_exec.c:1061
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "dönen satır yapısı tetikleyen tablonun yapısına uymuyor"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1198
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL fonksiyonu %s satır %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1209
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL fonksiyonu %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1217
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "%s PL/pgSQL fonksiyonu, %d. satır, %s içinde"
+
+#: pl_exec.c:1223
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL fonksiyonu %s"
+
+#: pl_exec.c:1561
+msgid "during statement block local variable initialization"
+msgstr "ifade (statement) bloğu yerel değişken ilklendirmesi sırasında"
+
+#: pl_exec.c:1659
+msgid "during statement block entry"
+msgstr "ifade bloğu girişi sırasında"
+
+#: pl_exec.c:1691
+msgid "during statement block exit"
+msgstr "ifade bloğu çıkışı sırasında"
+
+#: pl_exec.c:1729
+msgid "during exception cleanup"
+msgstr "exception temizlemesi sırasında"
+
+#: pl_exec.c:2225
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "\"%s\" prosedür parametresi bir çıktı (output) parametresidir fakat karşılık gelen parametre yazılabilir değil"
+
+#: pl_exec.c:2230
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "%d prosedür parametresi bir çıktı (output) parametresidir fakat karşılık gelen parametre yazılabilir değil"
+
+#: pl_exec.c:2341
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS özel durum işleyici (exception handler) dışında kullanılamaz"
+
+#: pl_exec.c:2540
+#, c-format
+msgid "case not found"
+msgstr "case bulunamadı"
+
+#: pl_exec.c:2541
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "CASE ifadesindeki ELSE eksik."
+
+#: pl_exec.c:2634
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "FOR döngüsünün alt sınırı null olamaz"
+
+#: pl_exec.c:2650
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "For döngüsünün üst sınırı null olamaz"
+
+#: pl_exec.c:2668
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "FOR döngüsünün BY değeri null olamaz"
+
+#: pl_exec.c:2674
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "FOR döngüsünn BY değeri sıfırdan büyük olmalıdır"
+
+#: pl_exec.c:2808 pl_exec.c:4530
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "\"%s\" imleci kullanımda"
+
+#: pl_exec.c:2831 pl_exec.c:4595
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "argümansız imleç (cursor) için verilen argümanlar"
+
+#: pl_exec.c:2850 pl_exec.c:4614
+#, c-format
+msgid "arguments required for cursor"
+msgstr "imleç için gereken argümanlar"
+
+#: pl_exec.c:2937
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH ifadesi NULL olamaz"
+
+#: pl_exec.c:2952
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH ifadesi %s değil bir dizi (array) sağlamalı"
+
+#: pl_exec.c:2969
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "slice boyutu (%d) geçerli kapsamın dışındadır: 0..%d"
+
+#: pl_exec.c:2996
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE döngü değişkeni bir dizi (array) tipinde olmalı"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH döngü değişkeni dizgi tipinde olamaz"
+
+#: pl_exec.c:3162 pl_exec.c:3219 pl_exec.c:3394
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "bileşik tip dönen fonksiyondan bileşik olmayan değer döndürülemez"
+
+#: pl_exec.c:3258 pl_gram.y:3305
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "SETOF olmayan fonksiyonda RETURN NEXT kullanılamaz"
+
+#: pl_exec.c:3299 pl_exec.c:3431
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "RETURN NEXT içinde yanlış dönüş tipi verildi"
+
+#: pl_exec.c:3337 pl_exec.c:3358
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "RETURN NEXT içinde yanlış kayıt tipi verildi"
+
+#: pl_exec.c:3450
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT bir parameter içermeli"
+
+#: pl_exec.c:3476 pl_gram.y:3369
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "RETURN QUERY, SETOF olmayan bir fonksiyon içinde bulunamaz"
+
+#: pl_exec.c:3500
+msgid "structure of query does not match function result type"
+msgstr "sorgunun yapısı fonksiyonun sonuç tipine uymuyor"
+
+#: pl_exec.c:3584 pl_exec.c:3722
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "RAISE seçeneği zaten belirtilmiş: %s"
+
+#: pl_exec.c:3618
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "parametresi olmayan RAISE bir özel durum işleyici (exception handler) dışında kullanılamaz"
+
+#: pl_exec.c:3712
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE ifadesi seçeneği null olamaz"
+
+#: pl_exec.c:3782
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3837
+#, c-format
+msgid "assertion failed"
+msgstr "ısrar hatası"
+
+#: pl_exec.c:4179 pl_exec.c:4369
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "PL/pgSQL'de istemcide ya da istemciden COPY çalıştırılamaz"
+
+#: pl_exec.c:4185
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "PL/pgSQL'de desteklenmeyen işlem (transaction) komutu"
+
+#: pl_exec.c:4208 pl_exec.c:4398
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "Veri döndüremeyen bir komutta INTO kullanıldı"
+
+#: pl_exec.c:4231 pl_exec.c:4421
+#, c-format
+msgid "query returned no rows"
+msgstr "sorgu satır döndürmedi"
+
+#: pl_exec.c:4253 pl_exec.c:4440
+#, c-format
+msgid "query returned more than one row"
+msgstr "sorgu birden fazla satır döndürdü"
+
+#: pl_exec.c:4255
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Sorgunun tek bir satır döndürdüğünden emin olun, veya LIMIT 1 kullanın."
+
+#: pl_exec.c:4271
+#, c-format
+msgid "query has no destination for result data"
+msgstr "Sorgu sonuç verisi için bir hedef içermiyor"
+
+#: pl_exec.c:4272
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "SELECT'den gelen sonuçları gözardı etmek istiyorsanız SELECT yerine PERFORM kullanın."
+
+#: pl_exec.c:4305 pl_exec.c:8416
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "EXECUTE' un sorgu dizesi argümanı boştur (null)"
+
+#: pl_exec.c:4361
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE of SELECT ... INTO kodlanmadı"
+
+#: pl_exec.c:4362
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Bunun yerine, EXECUTE ... INTO ya da EXECUTE CREATE TABLE ... AS kullanmak isteyebilirsiniz."
+
+#: pl_exec.c:4375
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "işlem (transaction) komutlarının EXECUTE'u implement edilmemiştir"
+
+#: pl_exec.c:4676 pl_exec.c:4764
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "\"%s\" imleç değişkeni null'dır"
+
+#: pl_exec.c:4687 pl_exec.c:4775
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "\"%s\" imleci mevcut değil"
+
+#: pl_exec.c:4700
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "bağıl ya da mutlak imleç pozisyonu null"
+
+#: pl_exec.c:4956 pl_exec.c:5051
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "NOT NULL olarak belirtilen \"%s\" değişkenine null değer atanamaz"
+
+#: pl_exec.c:5032
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "bir satır değişkenine bileşik olmayan bir değer atanamaz"
+
+#: pl_exec.c:5064
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "bir kayıt değişkenine bileşik olmayan bir değer atanamaz"
+
+#: pl_exec.c:5115
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "\"%s\" sistem sütununa veri atanamıyor"
+
+#: pl_exec.c:5179
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "dizin boyut sayısı (%d), izin verilern en yüksek değerini (%d) aşmaktadır"
+
+#: pl_exec.c:5211
+#, c-format
+msgid "subscripted object is not an array"
+msgstr "subscript edilen nesne bir dizi (array) değil"
+
+#: pl_exec.c:5249
+#, c-format
+msgid "array subscript in assignment must not be null"
+msgstr "atamada array subscript null olamaz"
+
+#: pl_exec.c:5756
+#, c-format
+msgid "query \"%s\" did not return data"
+msgstr "\"%s\" sorgusu veri döndürmedi"
+
+#: pl_exec.c:5764
+#, c-format
+msgid "query \"%s\" returned %d column"
+msgid_plural "query \"%s\" returned %d columns"
+msgstr[0] "\"%s\" sorgusu %d kolon döndürdü"
+
+#: pl_exec.c:5792
+#, c-format
+msgid "query \"%s\" returned more than one row"
+msgstr "\"%s\" sorgusu birden fazla satır döndürdü"
+
+#: pl_exec.c:5855
+#, c-format
+msgid "query \"%s\" is not a SELECT"
+msgstr "\"%s\" sorgusu bir SELECT değil"
+
+#: pl_exec.c:6580 pl_exec.c:6620 pl_exec.c:6660
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "%d parametresinin tipi (%s) planın hazırlandığı andakiyle (%s) uyuşmuyor"
+
+#: pl_exec.c:6996 pl_exec.c:7030 pl_exec.c:7104 pl_exec.c:7130
+#, c-format
+msgid "number of source and target fields in assignment do not match"
+msgstr "atamadaki (assignment) kaynak ve hedef alanları eşleşmiyor"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:6998 pl_exec.c:7032 pl_exec.c:7106 pl_exec.c:7132
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%2$s'nin %1$s denetimi etkindir."
+
+#: pl_exec.c:7002 pl_exec.c:7036 pl_exec.c:7110 pl_exec.c:7136
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Sorgunun sütunların tam listesini döndürdüğünden emin olun."
+
+#: pl_exec.c:7518
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "\"%s\" kaydı henüz atanmamış"
+
+#: pl_exec.c:7519
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Henüz atanmamış kaydın satır yapısı belirsizdir."
+
+#: pl_funcs.c:239
+msgid "statement block"
+msgstr "ifade bloğu"
+
+#: pl_funcs.c:241
+msgid "assignment"
+msgstr "atama"
+
+#: pl_funcs.c:251
+msgid "FOR with integer loop variable"
+msgstr "tamsayı döngüsünde FOR"
+
+#: pl_funcs.c:253
+msgid "FOR over SELECT rows"
+msgstr "FOR over SELECT rows"
+
+#: pl_funcs.c:255
+msgid "FOR over cursor"
+msgstr "FOR over cursor"
+
+#: pl_funcs.c:257
+msgid "FOREACH over array"
+msgstr "FOREACH dizgi üstünde"
+
+#: pl_funcs.c:271
+msgid "SQL statement"
+msgstr "SQL ifadesi"
+
+#: pl_funcs.c:275
+msgid "FOR over EXECUTE statement"
+msgstr "EXECUTE ifadesinde FOR"
+
+#: pl_gram.y:489
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "blok etiketi DECLARE'den önce yerleştirilmelidir, sonra değil."
+
+#: pl_gram.y:509
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "%s veri tipinde collation desteklenmemektedir"
+
+#: pl_gram.y:528
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "\"%s\" değişkeni NOT NULL olarak tanımlandığı için varsayılan (default) bir değeri olmalı"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "\"%s\" değişkeni mevcut değil"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "tekrarlanmış veri tipi deklarasyonu"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "\"%s\" değişkeni daha önce tanımlanan bir değişkeni gölgeliyor"
+
+#: pl_gram.y:992
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "%s tanılayıcı elemanı GET STACKED DIAGNOSTICS içinde kullanılamaz"
+
+#: pl_gram.y:1010
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "%s tanılayıcı elemanı GET CURRENT DIAGNOSTICS içinde kullanılamaz"
+
+#: pl_gram.y:1105
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "tanımlanamayan GET DIAGNOSTICS öğesi"
+
+#: pl_gram.y:1115 pl_gram.y:3549
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" scalar bir değişken değil"
+
+#: pl_gram.y:1369 pl_gram.y:1565
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "döngü satırları üzerindeki döngü değişkeni, bir kayıt veya satır değişkeni veya sayısal (scalar) değişkenlerin listesi olmalıdır"
+
+#: pl_gram.y:1404
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "imleç (cursor) FOR döngüsünde sadece bir tane hedef değişken olabilir"
+
+#: pl_gram.y:1411
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "imleç (cursor) FOR döngüsü bir sınırlı (bound) imleç (cursor) değişkeni kullanmalı"
+
+#: pl_gram.y:1498
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "Tamsayı FOR döngüsünde sadece bir tane hedef değişken olabilir"
+
+#: pl_gram.y:1535
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "FOR döngü sorgusu içinde REVERSE belirtilemez"
+
+#: pl_gram.y:1668
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "FOREACH'in döngü değişkeni bilinen bir değişken ya da değişkenlerin listesi olmalıdır"
+
+#: pl_gram.y:1710
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "bu ifadeyi barındıran bir blok ya da döngüye ekli bir \"%s\" etiketi bulunmuyor"
+
+#: pl_gram.y:1718
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "\"%s\" blok etiketi CONTINUE içinde kullanılamaz"
+
+#: pl_gram.y:1733
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT, bir etiketi olmadıkça bir döngü dışında kullanılamaz"
+
+#: pl_gram.y:1734
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE bir döngü dışında kullanılamaz"
+
+#: pl_gram.y:1758 pl_gram.y:1796 pl_gram.y:1844 pl_gram.y:2994 pl_gram.y:3079
+#: pl_gram.y:3190 pl_gram.y:3950
+msgid "unexpected end of function definition"
+msgstr "fonksiyon tanımında beklenmeyen sonlanma"
+
+#: pl_gram.y:1864 pl_gram.y:1888 pl_gram.y:1904 pl_gram.y:1910 pl_gram.y:2029
+#: pl_gram.y:2037 pl_gram.y:2051 pl_gram.y:2146 pl_gram.y:2395 pl_gram.y:2489
+#: pl_gram.y:2648 pl_gram.y:3792 pl_gram.y:3853 pl_gram.y:3931
+msgid "syntax error"
+msgstr "söz dizim hatası "
+
+#: pl_gram.y:1892 pl_gram.y:1894 pl_gram.y:2399 pl_gram.y:2401
+msgid "invalid SQLSTATE code"
+msgstr "geçersiz SQLSTATE kodu"
+
+#: pl_gram.y:2094
+msgid "syntax error, expected \"FOR\""
+msgstr "sözdizimi hatası, \"FOR\" bekleniyordu"
+
+#: pl_gram.y:2155
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "RAISE ifadesi çoklu satır döndüremez"
+
+#: pl_gram.y:2279
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "imleç değişkeni basit bir değişken olmalıdır"
+
+#: pl_gram.y:2285
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "\"%s\" değişkeni cursor ya da refcursor tiplerinden birisi olmalıdır"
+
+#: pl_gram.y:2619 pl_gram.y:2630
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" bilinen bir değişken değil"
+
+#: pl_gram.y:2734 pl_gram.y:2744 pl_gram.y:2899
+msgid "mismatched parentheses"
+msgstr "eşlenmemiş parantezler"
+
+#: pl_gram.y:2748
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "SQL ifadesinin sonunda eksik \"%s\" "
+
+#: pl_gram.y:2754
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "SQL ifadesinin sonunda \"%s\" eksik"
+
+#: pl_gram.y:2771
+msgid "missing expression"
+msgstr "eksik ifade"
+
+#: pl_gram.y:2773
+msgid "missing SQL statement"
+msgstr "eksik SQL ifadesi"
+
+#: pl_gram.y:2901
+msgid "incomplete data type declaration"
+msgstr "eksik veri tipi deklarasyonu"
+
+#: pl_gram.y:2924
+msgid "missing data type declaration"
+msgstr "eksik veri tipi deklarasyonu"
+
+#: pl_gram.y:3002
+msgid "INTO specified more than once"
+msgstr "INTO birden fazla belirtildi"
+
+#: pl_gram.y:3171
+msgid "expected FROM or IN"
+msgstr "FROM ya da IN bekleniyordu"
+
+#: pl_gram.y:3232
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN, fonksiyon return set içinde parametre alamaz"
+
+#: pl_gram.y:3233
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "RETURN NEXT ya da RETURN QUERY kullanın."
+
+#: pl_gram.y:3243
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN, bir prosedür içinde parametre alamaz"
+
+#: pl_gram.y:3248
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN, void dönen bir fonksiyonda parametre alamaz"
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN, OUT parametreleri olan fonksiyonda parametre içeremez"
+
+#: pl_gram.y:3320
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT OUT parametreleri olan fonksiyonda parametre içeremez"
+
+#: pl_gram.y:3428
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "\"%s\" CONSTANT olarak deklare edilmiş"
+
+#: pl_gram.y:3491
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "kayıt veya satır değişkeni çok-ögeli INTO listesinin bir parçası olamaz"
+
+#: pl_gram.y:3537
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "çok fazla INTO değişkeni belirtilmiş"
+
+#: pl_gram.y:3745
+#, c-format
+msgid "end label \"%s\" specified for unlabelled block"
+msgstr "etiketlenmemiş blok için \"%s\" bitiş etiketi tanımlanmış"
+
+#: pl_gram.y:3752
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "\"%s\" bitiş etiketi bloğun etiketi \"%s\"den farklı"
+
+#: pl_gram.y:3787
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "\"%s\" imlecinin argümanı yok"
+
+#: pl_gram.y:3801
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "\"%s\" imlecinin argümanları var"
+
+#: pl_gram.y:3843
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "\"%s\" imlecinin \"%s\" adlı bir argümanı yok"
+
+#: pl_gram.y:3863
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "\"%2$s\" imlecinin (cursor) \"%1$s\" parametresinin değeri birden fazla kez belirtilmiştir"
+
+#: pl_gram.y:3888
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "\"%s\" imleci (cursor) için yetersiz sayıda argüman "
+
+#: pl_gram.y:3895
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "\"%s\" imleci (cursor) için çok fazla argüman"
+
+#: pl_gram.y:3982
+msgid "unrecognized RAISE statement option"
+msgstr "tanımsız RAISE ifadesi seçeneği"
+
+#: pl_gram.y:3986
+msgid "syntax error, expected \"=\""
+msgstr "sözdizimi hatası, \"=\" bekleniyordu"
+
+#: pl_gram.y:4027
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "RAISE için çok fazla parametre var"
+
+#: pl_gram.y:4031
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "RAISE için çok az parametre var"
+
+#: pl_handler.c:158
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "PL/pgSQL değişken adları ve tablo sütun adları arasındaki çatışmaların ele alınmasını ayarlar."
+
+#: pl_handler.c:167
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "INTO ... STRICT hatalarında oluşturulan hata mesajlarının DETAIL kısmındaki parametrelerle ilgili bilgileri yazdır."
+
+#: pl_handler.c:175
+msgid "Perform checks given in ASSERT statements."
+msgstr "ASSERT ifadelerinde verilen kontrolleri gerçekleştir."
+
+#: pl_handler.c:183
+msgid "List of programming constructs that should produce a warning."
+msgstr "Uyarı üretmesi gereken programlama construct'larının yapısı"
+
+#: pl_handler.c:193
+msgid "List of programming constructs that should produce an error."
+msgstr "Hata üretmesi gereken programlama construct'larının yapısı"
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "giriş sonuna %s"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "\"%2$s\" yerinde %1$s"
+
+#~ msgid "relation \"%s\" is not a table"
+#~ msgstr "\"%s\" bir tablo değil"
+
+#~ msgid "variable \"%s\" declared NOT NULL cannot default to NULL"
+#~ msgstr "NOT NULL olarak belirtilen \"%s\" değişkeni öntanımlı olarak NULL olamaz"
+
+#~ msgid "Use a BEGIN block with an EXCEPTION clause instead."
+#~ msgstr "Bunun yerine BEGIN bloğunu EXCEPTION yantümcesi ile kullanın."
+
+#~ msgid "row or record variable cannot be CONSTANT"
+#~ msgstr "Satır ya da kayıt değişkeni CONSTANT olamaz"
+
+#~ msgid "row or record variable cannot be NOT NULL"
+#~ msgstr "satır ya da kayıt değişkeni NOT NULL olamaz"
+
+#~ msgid "default value for row or record variable is not supported"
+#~ msgstr "satır ya da kayıt değişkenlerine öntanımlı değer atanması desteklenmiyor"
+
+#~ msgid "unterminated dollar-quoted string"
+#~ msgstr "sonuçlandırılmamış dolar işeretiyle sınırlandırılmış satır"
+
+#~ msgid "unterminated quoted string"
+#~ msgstr "sonuçlandırılmamış tırnakla sınırlandırılmış satır"
+
+#~ msgid "unterminated /* comment"
+#~ msgstr "/* açıklama sonlandırılmamış"
+
+#~ msgid "unterminated quoted identifier"
+#~ msgstr "sonuçlandırılmamış tırnakla sınırlandırılmış tanım"
+
+#~ msgid "unterminated \" in identifier: %s"
+#~ msgstr "belirteçte sonlandırılmamış *\" : %s"
+
+#~ msgid "variable \"%s\" does not exist in the current block"
+#~ msgstr "\"%s\" değişkeni mevcut bloğun içinde yok"
+
+#~ msgid "expected \")\""
+#~ msgstr "\")\" bekleniyordu"
+
+#~ msgid "cannot assign to tg_argv"
+#~ msgstr "tg_argv'ye atama yapılamadı"
+
+#~ msgid "too many variables specified in SQL statement"
+#~ msgstr "SQL ifadesinde çok fazla değişken belirtilmiş"
+
+#~ msgid "expected a cursor or refcursor variable"
+#~ msgstr "cursor ya da refcursonr değişkeni beklendi"
+
+#~ msgid "syntax error at \"%s\""
+#~ msgstr "\"%s\" içinde sözdizimi hatası"
+
+#~ msgid "expected an integer variable"
+#~ msgstr "tamsayı değişken bekleniyordu"
+
+#~ msgid "function has no parameter \"%s\""
+#~ msgstr "fonksiyonun \"%s\" parametresi var"
+
+#~ msgid "Number of returned columns (%d) does not match expected column count (%d)."
+#~ msgstr "Dönen kolonların sayısı (%d) beklenen kolon sayısı (%d) ile eşleşmiyor."
+
+#~ msgid "N/A (dropped column)"
+#~ msgstr "N/A (silinmiş kolon)"
+
+#~ msgid "row \"%s.%s\" has no field \"%s\""
+#~ msgstr "\"%s.%s\" satırında \"%s\" alanı yok."
+
+#~ msgid "row \"%s\" has no field \"%s\""
+#~ msgstr "\"%s\" satırının bir alanı yok \"%s\""
+
+#~ msgid "expected \"[\""
+#~ msgstr " \"[\" bekleniyordu"
+
+#~ msgid "relation \"%s.%s\" does not exist"
+#~ msgstr "\"%s.%s\" nesnesi mevcut değil"
+
+#~ msgid "EXECUTE statement"
+#~ msgstr "EXECUTE ifadesi"
+
+#~ msgid "RETURN NEXT must specify a record or row variable in function returning row"
+#~ msgstr "RETURN NEXT satır döndüren fonksiyonda kayıt ya da satır değişkeni belirtmelidir"
+
+#~ msgid "label does not exist"
+#~ msgstr "etiket bulunamadı"
diff --git a/src/pl/plpgsql/src/po/uk.po b/src/pl/plpgsql/src/po/uk.po
new file mode 100644
index 0000000..77510da
--- /dev/null
+++ b/src/pl/plpgsql/src/po/uk.po
@@ -0,0 +1,852 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: postgresql\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-12 10:39+0000\n"
+"PO-Revision-Date: 2022-09-13 11:52\n"
+"Last-Translator: \n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: postgresql\n"
+"X-Crowdin-Project-ID: 324573\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /REL_15_STABLE/plpgsql.pot\n"
+"X-Crowdin-File-ID: 894\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "Функції PL/pgSQL не можуть приймати тип %s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "не вдалося визначити фактичний тип результату поліморфної функції \"%s\""
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "тригер-функція може викликатися лише як тригер"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "Функції PL/pgSQL не можуть повертати тип %s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "тригер-функції не можуть мати задекларованих аргументи"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "Аргументи тригеру доступні через TG_NARGS та TG_ARGV замість цього."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "функції тригерів подій не можуть мати задекларовані аргументи"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "компіляція функції PL/pgSQL \"%s\" біля рядка %d"
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "ім'я параметру «%s» використано декілька разів"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "посилання на стовпець \"%s\" є неоднозначним"
+
+#: pl_comp.c:1141
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Це може відноситися до змінної PL/pgSQL або стовпця таблиці."
+
+#: pl_comp.c:1324 pl_exec.c:5234 pl_exec.c:5407 pl_exec.c:5494 pl_exec.c:5585
+#: pl_exec.c:6606
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "запис \"%s\" не має поля \"%s\""
+
+#: pl_comp.c:1818
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "відношення \"%s\" не існує"
+
+#: pl_comp.c:1825 pl_comp.c:1867
+#, c-format
+msgid "relation \"%s\" does not have a composite type"
+msgstr "відношення \"%s\" не має складеного типу"
+
+#: pl_comp.c:1933
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "змінна \"%s\" має псевдотип %s"
+
+#: pl_comp.c:2122
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "тип \"%s\" є лише оболонкою"
+
+#: pl_comp.c:2204 pl_exec.c:6907
+#, c-format
+msgid "type %s is not composite"
+msgstr "тип %s не є складеним"
+
+#: pl_comp.c:2252 pl_comp.c:2305
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "нерозпізнана умова виключення \"%s\""
+
+#: pl_comp.c:2526
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "не вдалося визначити фактичний тип аргумента поліморфної функції \"%s\""
+
+#: pl_exec.c:501 pl_exec.c:940 pl_exec.c:1175
+msgid "during initialization of execution state"
+msgstr "під час ініціалізації стану виконання"
+
+#: pl_exec.c:507
+msgid "while storing call arguments into local variables"
+msgstr "під час зберігання аргументів виклику до локальних змінних"
+
+#: pl_exec.c:595 pl_exec.c:1013
+msgid "during function entry"
+msgstr "під час входу до функції"
+
+#: pl_exec.c:618
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "досягнуто кінця функції без RETURN"
+
+#: pl_exec.c:624
+msgid "while casting return value to function's return type"
+msgstr "під час приведення значення, що повертається, до типу результата функції"
+
+#: pl_exec.c:636 pl_exec.c:3665
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "функція \"set-valued\" викликана в контексті, де йому немає місця"
+
+#: pl_exec.c:641 pl_exec.c:3671
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "необхідний режим матеріалізації (materialize mode), але він неприпустимий у цьому контексті"
+
+#: pl_exec.c:768 pl_exec.c:1039 pl_exec.c:1197
+msgid "during function exit"
+msgstr "під час виходу з функції"
+
+#: pl_exec.c:823 pl_exec.c:887 pl_exec.c:3464
+msgid "returned record type does not match expected record type"
+msgstr "тип запису, що повертається, не відповідає очікуваному типу"
+
+#: pl_exec.c:1036 pl_exec.c:1194
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "досягнуто кінця тригерної процедури без RETURN"
+
+#: pl_exec.c:1044
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "тригерна процедура не може повернути набір"
+
+#: pl_exec.c:1083 pl_exec.c:1111
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "структура рядка, що повертається, не відповідає структурі таблиці, яка викликала тригер"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1252
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "Функція PL/pgSQL %s рядок %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1263
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "Функція PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1271
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "Функція PL/pgSQL %s рядок %d в %s"
+
+#: pl_exec.c:1277
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "Функція PL/pgSQL %s"
+
+#: pl_exec.c:1648
+msgid "during statement block local variable initialization"
+msgstr "під час ініціалізації локальної змінної в блоці операторів"
+
+#: pl_exec.c:1753
+msgid "during statement block entry"
+msgstr "під час входу в блок операторів"
+
+#: pl_exec.c:1785
+msgid "during statement block exit"
+msgstr "під час виходу з блоку операторів"
+
+#: pl_exec.c:1823
+msgid "during exception cleanup"
+msgstr "під час очищення винятку"
+
+#: pl_exec.c:2360
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "параметр процедури \"%s\" є вихідним, але відповідний аргумент не допускає запис"
+
+#: pl_exec.c:2365
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "параметр процедури %d є вихідним, але відповідний аргумент не допускає запис"
+
+#: pl_exec.c:2399
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS не може використовуватись поза блоком обробника винятків"
+
+#: pl_exec.c:2599
+#, c-format
+msgid "case not found"
+msgstr "гілку не знайдено"
+
+#: pl_exec.c:2600
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "В операторі CASE пропущено частину ELSE."
+
+#: pl_exec.c:2693
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "нижня границя циклу FOR не може бути null"
+
+#: pl_exec.c:2709
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "верхня границя циклу FOR не може бути null"
+
+#: pl_exec.c:2727
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "Значення BY циклу FOR не може бути null"
+
+#: pl_exec.c:2733
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "Значення BY циклу FOR повинно бути більше нуля"
+
+#: pl_exec.c:2867 pl_exec.c:4667
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "курсор \"%s\" вже використовується"
+
+#: pl_exec.c:2890 pl_exec.c:4737
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "аргументи, надані курсору без аргументів"
+
+#: pl_exec.c:2909 pl_exec.c:4756
+#, c-format
+msgid "arguments required for cursor"
+msgstr "аргументи, необхідні для курсора"
+
+#: pl_exec.c:3000
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "Вираз FOREACH не може бути null"
+
+#: pl_exec.c:3015
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "Вираз в FOREACH повинен бути масивом, не типом %s"
+
+#: pl_exec.c:3032
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "розмір зрізу (%d) поза припустимим діапазоном 0..%d"
+
+#: pl_exec.c:3059
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "Змінна циклу FOREACH ... SLICE повинна бути масивом"
+
+#: pl_exec.c:3063
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "Змінна циклу FOREACH не повинна бути масивом"
+
+#: pl_exec.c:3225 pl_exec.c:3282 pl_exec.c:3457
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "функція, що повертає складений тип, не може повернути не складене значення"
+
+#: pl_exec.c:3321 pl_gram.y:3319
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "не можна використовувати RETURN NEXT в функціях, що не повертають набори даних"
+
+#: pl_exec.c:3362 pl_exec.c:3494
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "в RETURN NEXT вказано неправильний тип результату"
+
+#: pl_exec.c:3400 pl_exec.c:3421
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "в RETURN NEXT вказано неправильний тип запису"
+
+#: pl_exec.c:3513
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT повинен мати параметр"
+
+#: pl_exec.c:3541 pl_gram.y:3383
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "не можна використовувати RETURN QUERY в функціях, що не повертають набір"
+
+#: pl_exec.c:3559
+msgid "structure of query does not match function result type"
+msgstr "структура запиту не відповідає типу результата функції"
+
+#: pl_exec.c:3614 pl_exec.c:4444 pl_exec.c:8685
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "текстовий аргумент запиту EXECUTE є null"
+
+#: pl_exec.c:3699 pl_exec.c:3837
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "Параметр RAISE вже вказано: %s"
+
+#: pl_exec.c:3733
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "RAISE без параметрів не можна використовувати поза блоком обробника винятків"
+
+#: pl_exec.c:3827
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "Параметром оператора RAISE не може бути null"
+
+#: pl_exec.c:3897
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3952
+#, c-format
+msgid "assertion failed"
+msgstr "порушення істинності"
+
+#: pl_exec.c:4317 pl_exec.c:4506
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "в PL/pgSQL не можна виконати COPY за участю клієнта"
+
+#: pl_exec.c:4323
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "непідтримувана транзакційна команда в PL/pgSQL"
+
+#: pl_exec.c:4346 pl_exec.c:4535
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO використаний з командою, що не може повертати дані"
+
+#: pl_exec.c:4369 pl_exec.c:4558
+#, c-format
+msgid "query returned no rows"
+msgstr "запит не повернув рядки"
+
+#: pl_exec.c:4391 pl_exec.c:4577 pl_exec.c:5729
+#, c-format
+msgid "query returned more than one row"
+msgstr "запит повернув декілька рядків"
+
+#: pl_exec.c:4393
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "Переконайтеся, що запит повертає один рядок, або використовуйте LIMIT 1."
+
+#: pl_exec.c:4409
+#, c-format
+msgid "query has no destination for result data"
+msgstr "запит не має призначення для даних результату"
+
+#: pl_exec.c:4410
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Якщо ви хочете відкинути результати SELECT, використайте PERFORM."
+
+#: pl_exec.c:4498
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE виразу SELECT ... INTO не реалізовано"
+
+#: pl_exec.c:4499
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "Альтернативою може стати EXECUTE ... INTO або EXECUTE CREATE TABLE ... AS."
+
+#: pl_exec.c:4512
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "EXECUTE з транзакційними командами не реалізовано"
+
+#: pl_exec.c:4822 pl_exec.c:4910
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "змінна курсора \"%s\" дорівнює null"
+
+#: pl_exec.c:4833 pl_exec.c:4921
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "курсор \"%s\" не існує"
+
+#: pl_exec.c:4846
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "відносна або абсолютна позиція курсора дорівнює null"
+
+#: pl_exec.c:5084 pl_exec.c:5179
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "значення null не можна призначити змінній \"%s\", оголошеній NOT NULL"
+
+#: pl_exec.c:5160
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "змінній типу кортеж можна призначити лише складене значення"
+
+#: pl_exec.c:5192
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "змінній типу запис можна призначити лише складене значення"
+
+#: pl_exec.c:5243
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "призначити значення системному стовпцю \"%s\" не можна"
+
+#: pl_exec.c:5692
+#, c-format
+msgid "query did not return data"
+msgstr "запит не повернув даних"
+
+#: pl_exec.c:5693 pl_exec.c:5705 pl_exec.c:5730 pl_exec.c:5806 pl_exec.c:5811
+#, c-format
+msgid "query: %s"
+msgstr "запит: %s"
+
+#: pl_exec.c:5701
+#, c-format
+msgid "query returned %d column"
+msgid_plural "query returned %d columns"
+msgstr[0] "запит повернув %d колонку"
+msgstr[1] "запит повернув %d колонки"
+msgstr[2] "запит повернув %d колонок"
+msgstr[3] "запит повернув %d колонок"
+
+#: pl_exec.c:5805
+#, c-format
+msgid "query is SELECT INTO, but it should be plain SELECT"
+msgstr "запит є SELECT INTO, але має бути звичайним SELECT"
+
+#: pl_exec.c:5810
+#, c-format
+msgid "query is not a SELECT"
+msgstr "запит не є SELECT"
+
+#: pl_exec.c:6620 pl_exec.c:6660 pl_exec.c:6700
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "тип параметру %d (%s) не відповідає тому, з котрим тривала підготовка плану (%s)"
+
+#: pl_exec.c:7111 pl_exec.c:7145 pl_exec.c:7219 pl_exec.c:7245
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "кількість вихідних і цільових полів у присвоюванні не збігається"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7113 pl_exec.c:7147 pl_exec.c:7221 pl_exec.c:7247
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s перевірка %s активна."
+
+#: pl_exec.c:7117 pl_exec.c:7151 pl_exec.c:7225 pl_exec.c:7251
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "Переконайтеся, що запит повертає точний список стовпців."
+
+#: pl_exec.c:7638
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "запис \"%s\" ще не призначено"
+
+#: pl_exec.c:7639
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Для запису, котрому не призначене значення, структура кортежа не визначена."
+
+#: pl_exec.c:8283 pl_gram.y:3442
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "змінна \"%s\" оголошена як CONSTANT"
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "блок операторів"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "призначення"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "FOR з цілим числом змінної циклу"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "FOR за результатами SELECT"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "FOR за курсором"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "FOREACH за масивом"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL-оператор"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "FOR за результатами EXECUTE"
+
+#: pl_gram.y:487
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "мітка блоку повинна бути розміщена до DECLARE, а не після"
+
+#: pl_gram.y:507
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "тип %s не підтримує правила сортування"
+
+#: pl_gram.y:526
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "змінна \"%s\" повинна мати значення за замовчуванням після того, як вона оголошена як NOT NULL"
+
+#: pl_gram.y:674 pl_gram.y:689 pl_gram.y:715
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "змінної \"%s\" не існує"
+
+#: pl_gram.y:733 pl_gram.y:761
+msgid "duplicate declaration"
+msgstr "дублікат оголошення"
+
+#: pl_gram.y:744 pl_gram.y:772
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "змінна \"%s\" приховує раніше оголошену змінну"
+
+#: pl_gram.y:1044
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "команда GET STACKED DIAGNOSTICS не дозволяє елемент діагностування %s"
+
+#: pl_gram.y:1062
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "команда GET CURRENT DIAGNOSTICS не дозволяє елемент діагностування %s"
+
+#: pl_gram.y:1157
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "нерозпізнаний елемент GET DIAGNOSTICS"
+
+#: pl_gram.y:1173 pl_gram.y:3558
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" не є скалярною змінною"
+
+#: pl_gram.y:1403 pl_gram.y:1597
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "змінна циклу по кортежах повинна бути змінною типу запис або списком скалярних змінних"
+
+#: pl_gram.y:1438
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "курсор в циклі FOR повинен мати лише одну цільову змінну"
+
+#: pl_gram.y:1445
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "цикл курсора FOR повинен використовувати обмежуючу змінну курсора"
+
+#: pl_gram.y:1536
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "цілочисельний цикл FOR повинен мати лише одну цільову змінну"
+
+#: pl_gram.y:1570
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "в циклі FOR з запитом не можна вказати REVERSE"
+
+#: pl_gram.y:1700
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "змінній циклу FOREACH повинна бути відома змінна або список змінних"
+
+#: pl_gram.y:1742
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "в блоку або циклу, розділеному цим оператором, немає мітки \"%s\""
+
+#: pl_gram.y:1750
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "мітку блока \"%s\" не можна використовувати в CONTINUE"
+
+#: pl_gram.y:1765
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "EXIT можна використовувати поза циклом, тільки з зазначенням мітки"
+
+#: pl_gram.y:1766
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "CONTINUE не можна використовувати поза циклом"
+
+#: pl_gram.y:1790 pl_gram.y:1828 pl_gram.y:1876 pl_gram.y:3005 pl_gram.y:3093
+#: pl_gram.y:3204 pl_gram.y:3957
+msgid "unexpected end of function definition"
+msgstr "неочікуваний кінец визначення функції"
+
+#: pl_gram.y:1896 pl_gram.y:1920 pl_gram.y:1936 pl_gram.y:1942 pl_gram.y:2067
+#: pl_gram.y:2075 pl_gram.y:2089 pl_gram.y:2184 pl_gram.y:2408 pl_gram.y:2498
+#: pl_gram.y:2656 pl_gram.y:3800 pl_gram.y:3861 pl_gram.y:3938
+msgid "syntax error"
+msgstr "синтаксична помилка"
+
+#: pl_gram.y:1924 pl_gram.y:1926 pl_gram.y:2412 pl_gram.y:2414
+msgid "invalid SQLSTATE code"
+msgstr "неприпустимий код SQLSTATE"
+
+#: pl_gram.y:2132
+msgid "syntax error, expected \"FOR\""
+msgstr "помилка синтаксису, очікувався \"FOR\""
+
+#: pl_gram.y:2193
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "Оператор FETCH не може повернути декілька рядків"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "змінна-курсор повинна бути простою змінною"
+
+#: pl_gram.y:2296
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "змінна \"%s\" повинна бути типу cursor або refcursor"
+
+#: pl_gram.y:2627 pl_gram.y:2638
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" - невідома змінна"
+
+#: pl_gram.y:2744 pl_gram.y:2754 pl_gram.y:2910
+msgid "mismatched parentheses"
+msgstr "неузгоджені дужки"
+
+#: pl_gram.y:2758
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "пропущено \"%s\" в кінці виразу SQL"
+
+#: pl_gram.y:2764
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "пропущено \"%s\" в кінці оператора SQL"
+
+#: pl_gram.y:2781
+msgid "missing expression"
+msgstr "пропущено вираз"
+
+#: pl_gram.y:2783
+msgid "missing SQL statement"
+msgstr "пропущений оператор SQL"
+
+#: pl_gram.y:2912
+msgid "incomplete data type declaration"
+msgstr "неповне оголошення типу даних"
+
+#: pl_gram.y:2935
+msgid "missing data type declaration"
+msgstr "пропущено оголошення типу даних"
+
+#: pl_gram.y:3015
+msgid "INTO specified more than once"
+msgstr "INTO вказано неодноразово"
+
+#: pl_gram.y:3185
+msgid "expected FROM or IN"
+msgstr "очікувалось FROM або IN"
+
+#: pl_gram.y:3246
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "В функції, яка повертає набір, RETURN не може мати параметр"
+
+#: pl_gram.y:3247
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "Використайте RETURN NEXT або RETURN QUERY."
+
+#: pl_gram.y:3257
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "В процедурі RETURN не може мати параметр"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "В функції, яка не повертає нічого, RETURN не може мати параметр"
+
+#: pl_gram.y:3271
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "В функції з параметрами OUT, RETURN не може мати параметр"
+
+#: pl_gram.y:3334
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "В функції з параметрами OUT, RETURN NEXT не може мати параметр"
+
+#: pl_gram.y:3500
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "змінна типу запис не може бути частиною списка INTO з декількома елементами"
+
+#: pl_gram.y:3546
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "вказано занадто багато змінних INTO"
+
+#: pl_gram.y:3754
+#, c-format
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "кінцева мітка \"%s\" вказана для невідміченого блоку"
+
+#: pl_gram.y:3761
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "кінцева мітка \"%s\" відрізняється від мітки блоку \"%s\""
+
+#: pl_gram.y:3795
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "курсор \"%s\" не має аргументів"
+
+#: pl_gram.y:3809
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "курсор \"%s\" має аргументи"
+
+#: pl_gram.y:3851
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "курсор \"%s\" не має аргументу \"%s\""
+
+#: pl_gram.y:3871
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "значення параметра \"%s\" курсора \"%s\" вказано неодноразово"
+
+#: pl_gram.y:3896
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "недостатньо аргументів для курсора \"%s\""
+
+#: pl_gram.y:3903
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "занадто багато аргументів для курсора \"%s\""
+
+#: pl_gram.y:3989
+msgid "unrecognized RAISE statement option"
+msgstr "нерозпізнаний параметр оператора RAISE"
+
+#: pl_gram.y:3993
+msgid "syntax error, expected \"=\""
+msgstr "помилка синтаксису, очікувалось \"=\""
+
+#: pl_gram.y:4034
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "занадто багато параметрів вказано для RAISE"
+
+#: pl_gram.y:4038
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "занадто мало параметрів вказано для RAISE"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "Обирає режим вирішення конфліктів між іменами змінних PL/pgSQL та іменами стовпців таблиць."
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "Додає інформацію про параметри в частину DETAIL повідомлень, які виводяться під час помилок в INTO ... STRICT."
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "Виконує перевірки, задані в операторах ASSERT."
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "Список програмних конструкцій, які повинні видавати попередження."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "Список програмних конструкцій, які повинні видавати помилку."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s в кінці введення"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s в або поблизу \"%s\""
+
diff --git a/src/pl/plpgsql/src/po/vi.po b/src/pl/plpgsql/src/po/vi.po
new file mode 100644
index 0000000..6620dea
--- /dev/null
+++ b/src/pl/plpgsql/src/po/vi.po
@@ -0,0 +1,850 @@
+# LANGUAGE message translation file for plpgsql
+# Copyright (C) 2018 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpgsql (PostgreSQL) package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2018-04-22 12:08+0000\n"
+"PO-Revision-Date: 2018-05-13 15:28+0900\n"
+"Language-Team: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 2.0.6\n"
+"Last-Translator: \n"
+"Language: vi_VN\n"
+
+#: pl_comp.c:434 pl_handler.c:457
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "Các hàm PL/pgSQL không thể chấp nhận kiểu %s"
+
+#: pl_comp.c:522
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "không thể xác định kiểu thực tế trả về cho hàm đa hình \"%s\""
+
+#: pl_comp.c:552
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "hàm trigger chỉ có thể được gọi như một trigger"
+
+#: pl_comp.c:556 pl_handler.c:441
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "Các hàm PL/pgSQL không thể trả về kiểu %s"
+
+#: pl_comp.c:595
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "không thể khai báo đối số cho hàm trigger"
+
+#: pl_comp.c:596
+#, c-format
+msgid ""
+"The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV "
+"instead."
+msgstr ""
+"Thay vào đó các đối số của trigger có thể được truy cập thông qua TG_NARGS "
+"và TG_ARGV."
+
+#: pl_comp.c:719
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "không thể khai báo đối số cho hàm sự kiện trigger"
+
+#: pl_comp.c:976
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "biên dịch hàm PL/pgSQL \"%s\" gần dòng %d"
+
+#: pl_comp.c:999
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "tên thông số \"%s\" được sử dụng nhiều lần"
+
+#: pl_comp.c:1109
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "tham chiếu cột \"%s\" không rõ ràng"
+
+#: pl_comp.c:1111
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "Nó có thể tham chiếu đến một biến PL/pgSQL hoặc một cột bảng."
+
+#: pl_comp.c:1294 pl_exec.c:5031 pl_exec.c:5396 pl_exec.c:5483 pl_exec.c:5574
+#: pl_exec.c:6491
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "bản ghi \"%s\" không có trường \"%s\""
+
+#: pl_comp.c:1756
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "relation \"%s\" không tồn tại"
+
+#: pl_comp.c:1848
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "biến \"%s\" có pseudo-type %s"
+
+#: pl_comp.c:2026
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "kiểu \"%s\" chỉ là một shell(chưa được định nghĩa nội dung)"
+
+#: pl_comp.c:2123 pl_comp.c:2176
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "không thể nhận ra điều kiện của ngoại lệ \"%s\""
+
+#: pl_comp.c:2390
+#, c-format
+msgid ""
+"could not determine actual argument type for polymorphic function \"%s\""
+msgstr "không thể xác định loại đối số thực tế cho hàm đa hình \"%s\""
+
+#: pl_exec.c:473 pl_exec.c:885 pl_exec.c:1098
+msgid "during initialization of execution state"
+msgstr "trong khi khởi tạo trạng thái thực thi"
+
+#: pl_exec.c:479
+msgid "while storing call arguments into local variables"
+msgstr "trong khi lưu trữ các đối số gọi vào trong các biến cục bộ"
+
+#: pl_exec.c:567 pl_exec.c:933
+msgid "during function entry"
+msgstr "trong lúc đi vào hàm"
+
+#: pl_exec.c:592
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "Hàm đã kết thúc trước khi trả về RETURN"
+
+#: pl_exec.c:599
+msgid "while casting return value to function's return type"
+msgstr "trong khi ép kiểu giá trị trả về cho kiểu trả về của hàm"
+
+#: pl_exec.c:612 pl_exec.c:3484
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"hàm thiết lập giá trị được gọi trong ngữ cảnh không thể chấp nhận một tập hợp"
+
+#: pl_exec.c:738 pl_exec.c:962 pl_exec.c:1123
+msgid "during function exit"
+msgstr "trong lúc kết thúc hàm"
+
+#: pl_exec.c:793 pl_exec.c:832 pl_exec.c:3329
+msgid "returned record type does not match expected record type"
+msgstr "loại bản ghi đã trả về không khớp với kiểu được ghi kỳ vọng"
+
+#: pl_exec.c:958 pl_exec.c:1119
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "thủ tục trigger kết thúc trước khi tra về RETURN"
+
+#: pl_exec.c:967
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "thủ tục trigger không thể trả về một tập hợp"
+
+#: pl_exec.c:1006 pl_exec.c:1034
+msgid ""
+"returned row structure does not match the structure of the triggering table"
+msgstr "cấu trúc hàng trả về không khớp với cấu trúc của trigger bảng"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1171
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "Hàm PL/pgSQL %s dòng %d %s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1182
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "Hàm PL/pgSQL %s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1190
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "Hàm PL/pgSQL %s dòng %d tại %s"
+
+#: pl_exec.c:1196
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "Hàm PL/pgSQL %s"
+
+#: pl_exec.c:1534
+msgid "during statement block local variable initialization"
+msgstr "trong khi khởi tạo biến cục bộ trong khối lệnh"
+
+#: pl_exec.c:1632
+msgid "during statement block entry"
+msgstr "trong khi vào khối lệnh"
+
+#: pl_exec.c:1664
+msgid "during statement block exit"
+msgstr "trong khi kết thúc khối lệnh"
+
+#: pl_exec.c:1702
+msgid "during exception cleanup"
+msgstr "trong khi dọn dẹp ngoại lệ"
+
+#: pl_exec.c:2207 pl_exec.c:2221
+#, c-format
+msgid "argument %d is an output argument but is not writable"
+msgstr "đối số %d là đối số đầu ra nhưng không thể ghi"
+
+#: pl_exec.c:2263
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr ""
+"GET STACKED DIAGNOSTICS không thể được sử dụng bên ngoài bên ngoài một trình "
+"xử lý ngoại lệ"
+
+#: pl_exec.c:2468
+#, c-format
+msgid "case not found"
+msgstr "không tìm thấy trường hợp này"
+
+#: pl_exec.c:2469
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "Câu lệnh CASE thiếu phần ELSE."
+
+#: pl_exec.c:2562
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "giới hạn dưới của vòng lặp FOR không thể là null"
+
+#: pl_exec.c:2578
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "giới hạn trên của vòng lặp FOR không thể là null"
+
+#: pl_exec.c:2596
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "Giá trị BY của vòng lặp FOR không thể là null"
+
+#: pl_exec.c:2602
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "Giá trị BY của vòng lặp FOR phải lớn hơn 0"
+
+#: pl_exec.c:2736 pl_exec.c:4461
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "con trỏ \"%s\" đã đang được sử dụng"
+
+#: pl_exec.c:2759 pl_exec.c:4526
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "đối số được đưa ra cho con trỏ không có đối số"
+
+#: pl_exec.c:2778 pl_exec.c:4545
+#, c-format
+msgid "arguments required for cursor"
+msgstr "đối số cần thiết cho con trỏ"
+
+#: pl_exec.c:2865
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "Biểu thức FOREACH không được là null"
+
+#: pl_exec.c:2880
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "Biểu thức FOREACH phải tạo ra một mảng, không phải kiểu %s"
+
+#: pl_exec.c:2897
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "kích thước slice (%d) nằm ngoài phạm vi hợp lệ 0..%d"
+
+#: pl_exec.c:2924
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "Biến cho vòng lặp FOREACH ... SLICE phải là một kiểu mảng"
+
+#: pl_exec.c:2928
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "Biến vòng lặp FOREACH không được thuộc loại mảng"
+
+#: pl_exec.c:3090 pl_exec.c:3147 pl_exec.c:3322
+#, c-format
+msgid ""
+"cannot return non-composite value from function returning composite type"
+msgstr "không thể trả về giá trị không-phức hợp từ hàm trả về kiểu phức hợp"
+
+#: pl_exec.c:3186 pl_gram.y:3266
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "không thể sử dụng RETURN NEXT trong một hàm không phải-SETOF"
+
+#: pl_exec.c:3227 pl_exec.c:3359
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "kiểu kết quả trả về không đúng trong RETURN NEXT"
+
+#: pl_exec.c:3265 pl_exec.c:3286
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "kiểu bản ghi trả về không đúng trong RETURN NEXT"
+
+#: pl_exec.c:3378
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT phải có một tham số"
+
+#: pl_exec.c:3404 pl_gram.y:3329
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "không thể sử dụng RETURN QUERY trong một hàm không phải-SETOF"
+
+#: pl_exec.c:3428
+msgid "structure of query does not match function result type"
+msgstr "cấu trúc của truy vấn không khớp với kiểu kết quả hàm"
+
+#: pl_exec.c:3512 pl_exec.c:3650
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "Tùy chọn RAISE đã được chỉ định: %s"
+
+#: pl_exec.c:3546
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr ""
+"RAISE không có tham số không thể được sử dụng bên ngoài một trình xử lý "
+"ngoại lệ"
+
+#: pl_exec.c:3640
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "Tùy chọn lệnh RAISE không thể là null"
+
+#: pl_exec.c:3710
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3765
+#, c-format
+msgid "assertion failed"
+msgstr "lỗi assertion"
+
+#: pl_exec.c:3970 pl_exec.c:4117 pl_exec.c:4301
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "không thể COPY tới/từ client trong PL/pgSQL"
+
+#: pl_exec.c:3974 pl_exec.c:4121 pl_exec.c:4305
+#, c-format
+msgid "cannot begin/end transactions in PL/pgSQL"
+msgstr "không thể begin/end transactions trong PL/pgSQL"
+
+#: pl_exec.c:3975 pl_exec.c:4122 pl_exec.c:4306
+#, c-format
+msgid "Use a BEGIN block with an EXCEPTION clause instead."
+msgstr "Sử dụng một khối BEGIN với một cấu trúc EXCEPTION để thay thế."
+
+#: pl_exec.c:4144 pl_exec.c:4329
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "INTO được sử dụng với lệnh không thể trả về dữ liệu"
+
+#: pl_exec.c:4167 pl_exec.c:4352
+#, c-format
+msgid "query returned no rows"
+msgstr "truy vấn không trả lại dòng nào"
+
+#: pl_exec.c:4186 pl_exec.c:4371
+#, c-format
+msgid "query returned more than one row"
+msgstr "truy vấn trả về nhiều dòng"
+
+#: pl_exec.c:4203
+#, c-format
+msgid "query has no destination for result data"
+msgstr "truy vấn không có đích cho dữ liệu kết quả"
+
+#: pl_exec.c:4204
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "Nếu bạn không muốn sử dụng kết quả của SELECT, hãy sử dụng PERFORM."
+
+#: pl_exec.c:4237 pl_exec.c:8212
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "đối số chuỗi truy vấn của EXECUTE là null"
+
+#: pl_exec.c:4293
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "EXECUTE của SELECT ... INTO chưa được thực thi"
+
+#: pl_exec.c:4294
+#, c-format
+msgid ""
+"You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS "
+"instead."
+msgstr ""
+"Thay vào đó có thể bạn muốn sử dụng EXECUTE ... INTO hoặc EXECUTE CREATE "
+"TABLE ... AS."
+
+#: pl_exec.c:4607 pl_exec.c:4695
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "biến con trỏ \"%s\" là null"
+
+#: pl_exec.c:4618 pl_exec.c:4706
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "con trỏ \"%s\" không tồn tại"
+
+#: pl_exec.c:4631
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "vị trí con trỏ tương đối hoặc tuyệt đối là null"
+
+#: pl_exec.c:4881 pl_exec.c:4976
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "giá trị null không thể được gán cho biến \"%s\" được khai báo NOT NULL"
+
+#: pl_exec.c:4957
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "không thể gán giá trị không-phức hợp cho biến dòng"
+
+#: pl_exec.c:4989
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "không thể gán giá trị không phải-phức hợp cho biến bản ghi"
+
+#: pl_exec.c:5040
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "không thể gán cho cột hệ thống \"%s\""
+
+#: pl_exec.c:5104
+#, c-format
+msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgstr "số lượng chiều của mảng (%d) vượt quá số lượng tối đa cho phép (%d)"
+
+#: pl_exec.c:5136
+#, c-format
+msgid "subscripted object is not an array"
+msgstr "đối tượng chỉ số không phải là một mảng"
+
+#: pl_exec.c:5174
+#, c-format
+msgid "array subscript in assignment must not be null"
+msgstr "chỉ số mảng sử dụng trong gán không thể là null"
+
+#: pl_exec.c:5681
+#, c-format
+msgid "query \"%s\" did not return data"
+msgstr "truy vấn \"%s\" không trả về dữ liệu"
+
+#: pl_exec.c:5689
+#, c-format
+msgid "query \"%s\" returned %d column"
+msgid_plural "query \"%s\" returned %d columns"
+msgstr[0] "truy vấn \"%s\" trả lại %d cột"
+
+#: pl_exec.c:5717
+#, c-format
+msgid "query \"%s\" returned more than one row"
+msgstr "truy vấn \"%s\" đã trả lại nhiều hàng"
+
+#: pl_exec.c:5780
+#, c-format
+msgid "query \"%s\" is not a SELECT"
+msgstr "truy vấn \"%s\" không phải là một SELECT"
+
+#: pl_exec.c:6505 pl_exec.c:6545 pl_exec.c:6585
+#, c-format
+msgid ""
+"type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "kiểu tham số %d (%s) không khớp với thông số khi chuẩn bị plan (%s)"
+
+#: pl_exec.c:7356
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "bản ghi \"%s\" chưa được gán"
+
+#: pl_exec.c:7357
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "Cấu trúc tuple của một bản ghi chưa được gán là không xác định."
+
+#: pl_funcs.c:239
+msgid "statement block"
+msgstr "khối câu lệnh"
+
+#: pl_funcs.c:241
+msgid "assignment"
+msgstr "gán"
+
+#: pl_funcs.c:251
+msgid "FOR with integer loop variable"
+msgstr "FOR với biến số nguyên vòng lặp"
+
+#: pl_funcs.c:253
+msgid "FOR over SELECT rows"
+msgstr "FOR trên các dòng SELECT"
+
+#: pl_funcs.c:255
+msgid "FOR over cursor"
+msgstr "FOR trên con trỏ"
+
+#: pl_funcs.c:257
+msgid "FOREACH over array"
+msgstr "FOREACH trên mảng"
+
+#: pl_funcs.c:271
+msgid "SQL statement"
+msgstr "Câu lệnh SQL"
+
+#: pl_funcs.c:275
+msgid "FOR over EXECUTE statement"
+msgstr "FOR trên câu lệnh EXECUTE"
+
+#: pl_gram.y:485
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "nhãn khối phải được đặt trước DECLARE, không phải sau"
+
+#: pl_gram.y:505
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "collation không được hỗ trợ bởi kiểu %s"
+
+#: pl_gram.y:524
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "biến \"%s\" phải có giá trị mặc định, vì nó được khai báo là NOT NULL"
+
+#: pl_gram.y:669 pl_gram.y:684 pl_gram.y:710
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "biến \"%s\" không tồn tại"
+
+#: pl_gram.y:728 pl_gram.y:756
+msgid "duplicate declaration"
+msgstr "khai báo trùng lặp"
+
+#: pl_gram.y:739 pl_gram.y:767
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "biến \"%s\" làm cho một biến được định nghĩa trước đó không khả thị"
+
+#: pl_gram.y:983
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "diagnostics mục %s không được phép trong GET STACKED DIAGNOSTICS"
+
+#: pl_gram.y:1001
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "diagnostics mục %s không được cho phép trong GET CURRENT DIAGNOSTICS"
+
+#: pl_gram.y:1099
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "không nhận ra mục GET DIAGNOSTICS"
+
+#: pl_gram.y:1109 pl_gram.y:3508
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" không phải là biến vô hướng"
+
+#: pl_gram.y:1357 pl_gram.y:1550
+#, c-format
+msgid ""
+"loop variable of loop over rows must be a record variable or list of scalar "
+"variables"
+msgstr ""
+"vòng lặp của vòng lặp trên các dòng phải là một biến bản ghi hoặc danh sách "
+"các biến vô hướng"
+
+#: pl_gram.y:1391
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "vòng lặp FOR sử dụng con trỏ chỉ có một biến đích"
+
+#: pl_gram.y:1398
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "vòng lặp FOR sử dụng con trỏ phải sử dụng một biến con trỏ"
+
+#: pl_gram.y:1485
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "vòng lặp FOR sử dụng số nguyên chỉ được phép có một biến đích"
+
+#: pl_gram.y:1521
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "không thể chỉ định REVERSE trong vòng lặp truy vấn FOR"
+
+#: pl_gram.y:1652
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "biến vòng lặp của FOREACH phải là biến được biết hoặc danh sách biến"
+
+#: pl_gram.y:1693
+#, c-format
+msgid ""
+"there is no label \"%s\" attached to any block or loop enclosing this "
+"statement"
+msgstr ""
+"không có nhãn \"%s\" được đính kèm với bất kỳ khối hoặc vòng lặp nào kèm "
+"theo câu lệnh này"
+
+#: pl_gram.y:1701
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "không thể sử dụng nhãn khối \"%s\" trong CONTINUE"
+
+#: pl_gram.y:1716
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr ""
+"EXIT không thể được sử dụng bên ngoài một vòng lặp, trừ khi nó có một nhãn"
+
+#: pl_gram.y:1717
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "Không thể sử dụng CONTINUE bên ngoài vòng lặp"
+
+#: pl_gram.y:1741 pl_gram.y:1778 pl_gram.y:1826 pl_gram.y:2958 pl_gram.y:3041
+#: pl_gram.y:3152 pl_gram.y:3907
+msgid "unexpected end of function definition"
+msgstr "định nghĩa kết thúc hàm không mong đợi"
+
+#: pl_gram.y:1846 pl_gram.y:1870 pl_gram.y:1886 pl_gram.y:1892 pl_gram.y:2009
+#: pl_gram.y:2017 pl_gram.y:2031 pl_gram.y:2125 pl_gram.y:2360 pl_gram.y:2454
+#: pl_gram.y:2612 pl_gram.y:3749 pl_gram.y:3810 pl_gram.y:3888
+msgid "syntax error"
+msgstr "lỗi cú pháp"
+
+#: pl_gram.y:1874 pl_gram.y:1876 pl_gram.y:2364 pl_gram.y:2366
+msgid "invalid SQLSTATE code"
+msgstr "mã SQLSTATE không hợp lệ"
+
+#: pl_gram.y:2073
+msgid "syntax error, expected \"FOR\""
+msgstr "lỗi cú pháp, kỳ vọng \"FOR\""
+
+#: pl_gram.y:2134
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "Câu lệnh FETCH không thể trả về nhiều hàng"
+
+#: pl_gram.y:2244
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "biến con trỏ phải là một biến đơn giản"
+
+#: pl_gram.y:2250
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "biến \"%s\" phải là kiểu con trỏ hoặc refcursor"
+
+#: pl_gram.y:2583 pl_gram.y:2594
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" không phải là một biến"
+
+#: pl_gram.y:2698 pl_gram.y:2708 pl_gram.y:2863
+msgid "mismatched parentheses"
+msgstr "dấu ngoặc đơn không khớp"
+
+#: pl_gram.y:2712
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "thiếu \"%s\" ở cuối biểu thức SQL"
+
+#: pl_gram.y:2718
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "thiếu \"%s\" ở cuối câu lệnh SQL"
+
+#: pl_gram.y:2735
+msgid "missing expression"
+msgstr "thiếu biểu thức"
+
+#: pl_gram.y:2737
+msgid "missing SQL statement"
+msgstr "thiếu câu lệnh SQL"
+
+#: pl_gram.y:2865
+msgid "incomplete data type declaration"
+msgstr "khai báo kiểu dữ liệu không đầy đủ"
+
+#: pl_gram.y:2888
+msgid "missing data type declaration"
+msgstr "thiếu khai báo kiểu dữ liệu"
+
+#: pl_gram.y:2966
+msgid "INTO specified more than once"
+msgstr "INTO được chỉ định nhiều lần"
+
+#: pl_gram.y:3133
+msgid "expected FROM or IN"
+msgstr "kỳ vọng FROM hoặc IN"
+
+#: pl_gram.y:3193
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "RETURN không thể có tham số trong tập hợp giá trị trả về của hàm"
+
+#: pl_gram.y:3194
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "sử dụng RETURN NEXT hay RETURN QUERY."
+
+#: pl_gram.y:3204
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "RETURN không thể có tham số trong một thủ tục"
+
+#: pl_gram.y:3209
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "RETURN không thể có tham số trong hàm trả về void"
+
+#: pl_gram.y:3218
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "RETURN không thể có tham số trong hàm với tham số OUT"
+
+#: pl_gram.y:3280
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "RETURN NEXT không thể có tham số trong hàm với tham số OUT"
+
+#: pl_gram.y:3387
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "biến \"%s\" được khai báo CONSTANT"
+
+#: pl_gram.y:3450
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "biến bản ghi không thể là một phần của danh sách INTO nhiều mục"
+
+#: pl_gram.y:3496
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "quá nhiều biến INTO được chỉ định"
+
+#: pl_gram.y:3702
+#, c-format
+msgid "end label \"%s\" specified for unlabelled block"
+msgstr "nhãn kết thúc \"%s\" được chỉ định cho khối không được gắn nhãn"
+
+#: pl_gram.y:3709
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "nhãn kết thúc \"%s\" khác với nhãn của block \"%s\""
+
+#: pl_gram.y:3744
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "con trỏ \"%s\" không có đối số"
+
+#: pl_gram.y:3758
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "con trỏ \"%s\" có đối số"
+
+#: pl_gram.y:3800
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "con trỏ \"%s\" không có đối số tên là \"%s\""
+
+#: pl_gram.y:3820
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "giá trị cho tham số \"%s\" của con trỏ \"%s\" được chỉ định nhiều lần"
+
+#: pl_gram.y:3845
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "không đủ đối số cho con trỏ \"%s\""
+
+#: pl_gram.y:3852
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "quá nhiều đối số cho con trỏ \"%s\""
+
+#: pl_gram.y:3939
+msgid "unrecognized RAISE statement option"
+msgstr "không thể xác định tùy chọn cho lệnh RAISE"
+
+#: pl_gram.y:3943
+msgid "syntax error, expected \"=\""
+msgstr "lỗi cú pháp, kỳ vọng \"=\""
+
+#: pl_gram.y:3984
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "quá nhiều thông số được chỉ định cho RAISE"
+
+#: pl_gram.y:3988
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "quá ít thông số được chỉ định cho RAISE"
+
+#: pl_handler.c:154
+msgid ""
+"Sets handling of conflicts between PL/pgSQL variable names and table column "
+"names."
+msgstr "Đặt xử lý xung đột giữa tên biến PL/pgSQL và tên cột bảng."
+
+#: pl_handler.c:163
+msgid ""
+"Print information about parameters in the DETAIL part of the error messages "
+"generated on INTO ... STRICT failures."
+msgstr ""
+"Hiển thị thông tin về các tham số trong phần DETAIL của các thông báo lỗi "
+"được tạo ra khi INTO ... STRICT lỗi."
+
+#: pl_handler.c:171
+msgid "Perform checks given in ASSERT statements."
+msgstr "Thực hiện các kiểm tra được đưa ra trong các câu lệnh ASSERT."
+
+#: pl_handler.c:179
+msgid "List of programming constructs that should produce a warning."
+msgstr "Danh sách các cấu trúc lập trình sẽ tạo ra một cảnh báo."
+
+#: pl_handler.c:189
+msgid "List of programming constructs that should produce an error."
+msgstr "Danh sách các cấu trúc lập trình sẽ tạo ra lỗi."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:630
+#, c-format
+msgid "%s at end of input"
+msgstr "%s tại nơi kết thúc đầu vào"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:646
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s tại hoặc gần\"%s\""
diff --git a/src/pl/plpgsql/src/po/zh_CN.po b/src/pl/plpgsql/src/po/zh_CN.po
new file mode 100644
index 0000000..702d62d
--- /dev/null
+++ b/src/pl/plpgsql/src/po/zh_CN.po
@@ -0,0 +1,842 @@
+# LANGUAGE message translation file for plpgsql
+# Copyright (C) 2010 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpgsql (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-14 05:39+0000\n"
+"PO-Revision-Date: 2021-08-15 18:20+0800\n"
+"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n"
+"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.5.7\n"
+
+#: pl_comp.c:438 pl_handler.c:496
+#, c-format
+msgid "PL/pgSQL functions cannot accept type %s"
+msgstr "PL/pgSQL函数不使用类型%s"
+
+#: pl_comp.c:530
+#, c-format
+msgid "could not determine actual return type for polymorphic function \"%s\""
+msgstr "无法确定多态函数\"%s\"的实际返回类型"
+
+#: pl_comp.c:560
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "触发器函数只能以触发器的形式调用"
+
+#: pl_comp.c:564 pl_handler.c:480
+#, c-format
+msgid "PL/pgSQL functions cannot return type %s"
+msgstr "PL/pgSQL函数不能返回类型%s"
+
+#: pl_comp.c:604
+#, c-format
+msgid "trigger functions cannot have declared arguments"
+msgstr "触发器函数不能有已声明的参数"
+
+#: pl_comp.c:605
+#, c-format
+msgid "The arguments of the trigger can be accessed through TG_NARGS and TG_ARGV instead."
+msgstr "触发器的参数可以通过TG_NARGS和TG_ARGV进行访问."
+
+#: pl_comp.c:738
+#, c-format
+msgid "event trigger functions cannot have declared arguments"
+msgstr "事件触发器函数不能有已声明的参数"
+
+#: pl_comp.c:1002
+#, c-format
+msgid "compilation of PL/pgSQL function \"%s\" near line %d"
+msgstr "在第%2$d行附件编译PL/pgSQL函数\"%1$s\""
+
+#: pl_comp.c:1025
+#, c-format
+msgid "parameter name \"%s\" used more than once"
+msgstr "多次使用参数名称 \"%s\""
+
+#: pl_comp.c:1137
+#, c-format
+msgid "column reference \"%s\" is ambiguous"
+msgstr "字段关联 \"%s\" 是不明确的"
+
+#: pl_comp.c:1139
+#, c-format
+msgid "It could refer to either a PL/pgSQL variable or a table column."
+msgstr "可以指向一个PL/pgSQL变量或表中的列"
+
+#: pl_comp.c:1322 pl_exec.c:5189 pl_exec.c:5362 pl_exec.c:5449 pl_exec.c:5540
+#: pl_exec.c:6547
+#, c-format
+msgid "record \"%s\" has no field \"%s\""
+msgstr "记录\"%s\"没有字段\"%s\""
+
+#: pl_comp.c:1816
+#, c-format
+msgid "relation \"%s\" does not exist"
+msgstr "关系 \"%s\" 不存在"
+
+#: pl_comp.c:1823 pl_comp.c:1865
+#, fuzzy, c-format
+#| msgid "\"%s\" is not a composite type"
+msgid "relation \"%s\" does not have a composite type"
+msgstr "\"%s\" 不是组合类型"
+
+#: pl_comp.c:1931
+#, c-format
+msgid "variable \"%s\" has pseudo-type %s"
+msgstr "变量\"%s\"具有伪类型%s"
+
+#: pl_comp.c:2120
+#, c-format
+msgid "type \"%s\" is only a shell"
+msgstr "类型 \"%s\" 只是一个 shell"
+
+#: pl_comp.c:2202 pl_exec.c:6848
+#, c-format
+msgid "type %s is not composite"
+msgstr "类型 %s 不是复合类型"
+
+#: pl_comp.c:2250 pl_comp.c:2303
+#, c-format
+msgid "unrecognized exception condition \"%s\""
+msgstr "不可识别的异常条件\"%s\""
+
+#: pl_comp.c:2524
+#, c-format
+msgid "could not determine actual argument type for polymorphic function \"%s\""
+msgstr "无法确定多态函数\"%s\"的实际参数类型"
+
+#: pl_exec.c:500 pl_exec.c:934 pl_exec.c:1169
+msgid "during initialization of execution state"
+msgstr "在执行状态的初始化期间"
+
+#: pl_exec.c:506
+msgid "while storing call arguments into local variables"
+msgstr "在将调用的参数存储到本地变量时"
+
+#: pl_exec.c:594 pl_exec.c:1007
+msgid "during function entry"
+msgstr "在进入函数期间"
+
+#: pl_exec.c:617
+#, c-format
+msgid "control reached end of function without RETURN"
+msgstr "控制流程到达函数的结束部分,但是没有看到RETURN"
+
+#: pl_exec.c:623
+msgid "while casting return value to function's return type"
+msgstr "正在将返回值强行指派为函数的返回类型"
+
+#: pl_exec.c:636 pl_exec.c:3636
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "在不能接受使用集合的环境中调用set-valued函数"
+
+#: pl_exec.c:762 pl_exec.c:1033 pl_exec.c:1191
+msgid "during function exit"
+msgstr "在函数退出期间"
+
+#: pl_exec.c:817 pl_exec.c:881 pl_exec.c:3434
+msgid "returned record type does not match expected record type"
+msgstr "所返回的记录类型与所期待的记录类型不匹配"
+
+#: pl_exec.c:1030 pl_exec.c:1188
+#, c-format
+msgid "control reached end of trigger procedure without RETURN"
+msgstr "控制流程到达触发器/存储过程的结束部分,但是没有看到RETURN"
+
+#: pl_exec.c:1038
+#, c-format
+msgid "trigger procedure cannot return a set"
+msgstr "触发器存储过程无法返回集合"
+
+#: pl_exec.c:1077 pl_exec.c:1105
+msgid "returned row structure does not match the structure of the triggering table"
+msgstr "所返回的记录结构与触发表的结构不匹配"
+
+#. translator: last %s is a phrase such as "during statement block
+#. local variable initialization"
+#.
+#: pl_exec.c:1237
+#, c-format
+msgid "PL/pgSQL function %s line %d %s"
+msgstr "PL/pgSQL 函数%s在第%d行%s"
+
+#. translator: last %s is a phrase such as "while storing call
+#. arguments into local variables"
+#.
+#: pl_exec.c:1248
+#, c-format
+msgid "PL/pgSQL function %s %s"
+msgstr "PL/pgSQL 函数%s %s"
+
+#. translator: last %s is a plpgsql statement type name
+#: pl_exec.c:1256
+#, c-format
+msgid "PL/pgSQL function %s line %d at %s"
+msgstr "在%3$s的第%2$d行的PL/pgSQL函数%1$s"
+
+#: pl_exec.c:1262
+#, c-format
+msgid "PL/pgSQL function %s"
+msgstr "PL/pgSQL函数 %s"
+
+#: pl_exec.c:1633
+msgid "during statement block local variable initialization"
+msgstr "在初始化语句块的局部变量期间"
+
+#: pl_exec.c:1731
+msgid "during statement block entry"
+msgstr "在进入语句块期间"
+
+#: pl_exec.c:1763
+msgid "during statement block exit"
+msgstr "在退出语句块期间"
+
+#: pl_exec.c:1801
+msgid "during exception cleanup"
+msgstr "在清理异常期间"
+
+#: pl_exec.c:2334
+#, c-format
+msgid "procedure parameter \"%s\" is an output parameter but corresponding argument is not writable"
+msgstr "过程参数\"%s\"是输出参数,但相应的参数不可写"
+
+#: pl_exec.c:2339
+#, c-format
+msgid "procedure parameter %d is an output parameter but corresponding argument is not writable"
+msgstr "过程参数%d是输出参数,但相应的参数不可写"
+
+#: pl_exec.c:2373
+#, c-format
+msgid "GET STACKED DIAGNOSTICS cannot be used outside an exception handler"
+msgstr "GET STACKED DIAGNOSTICS不能用于异常处理之外"
+
+#: pl_exec.c:2573
+#, c-format
+msgid "case not found"
+msgstr "没有找到CASE"
+
+#: pl_exec.c:2574
+#, c-format
+msgid "CASE statement is missing ELSE part."
+msgstr "在CASE语句结构中丢失ELSE部分"
+
+#: pl_exec.c:2667
+#, c-format
+msgid "lower bound of FOR loop cannot be null"
+msgstr "FOR循环的低位边界不能为空"
+
+#: pl_exec.c:2683
+#, c-format
+msgid "upper bound of FOR loop cannot be null"
+msgstr "FOR循环的高位边界不能为空"
+
+#: pl_exec.c:2701
+#, c-format
+msgid "BY value of FOR loop cannot be null"
+msgstr "FOR循环的BY值不能为空"
+
+#: pl_exec.c:2707
+#, c-format
+msgid "BY value of FOR loop must be greater than zero"
+msgstr "FOR循环的BY值必须大于0"
+
+#: pl_exec.c:2841 pl_exec.c:4625
+#, c-format
+msgid "cursor \"%s\" already in use"
+msgstr "游标\"%s\"已经在使用"
+
+#: pl_exec.c:2864 pl_exec.c:4690
+#, c-format
+msgid "arguments given for cursor without arguments"
+msgstr "给不带有参数的游标指定参数"
+
+#: pl_exec.c:2883 pl_exec.c:4709
+#, c-format
+msgid "arguments required for cursor"
+msgstr "游标需要参数"
+
+#: pl_exec.c:2970
+#, c-format
+msgid "FOREACH expression must not be null"
+msgstr "FOREACH表达式不能为空"
+
+#: pl_exec.c:2985
+#, c-format
+msgid "FOREACH expression must yield an array, not type %s"
+msgstr "FOREACH表达式的结果必须是数组, 而不是类型 %s"
+
+#: pl_exec.c:3002
+#, c-format
+msgid "slice dimension (%d) is out of the valid range 0..%d"
+msgstr "索引维数(%d)超出有效范围: 0..%d"
+
+#: pl_exec.c:3029
+#, c-format
+msgid "FOREACH ... SLICE loop variable must be of an array type"
+msgstr "FOREACH ... SLICE循环变量必须属于数组类型"
+
+#: pl_exec.c:3033
+#, c-format
+msgid "FOREACH loop variable must not be of an array type"
+msgstr "FOREACH循环变量不能为数组类型"
+
+#: pl_exec.c:3195 pl_exec.c:3252 pl_exec.c:3427
+#, c-format
+msgid "cannot return non-composite value from function returning composite type"
+msgstr "返回值为组合类型的函数不能返回非组合型的值"
+
+#: pl_exec.c:3291 pl_gram.y:3310
+#, c-format
+msgid "cannot use RETURN NEXT in a non-SETOF function"
+msgstr "无法在非SETOF函数中使用RETURN NEXT"
+
+#: pl_exec.c:3332 pl_exec.c:3464
+#, c-format
+msgid "wrong result type supplied in RETURN NEXT"
+msgstr "在RETURN NEXT中提供了错误的结果类型"
+
+#: pl_exec.c:3370 pl_exec.c:3391
+#, c-format
+msgid "wrong record type supplied in RETURN NEXT"
+msgstr "在RETURN NEXT中提供了错误的记录类型"
+
+#: pl_exec.c:3483
+#, c-format
+msgid "RETURN NEXT must have a parameter"
+msgstr "RETURN NEXT必须有一个参数"
+
+#: pl_exec.c:3511 pl_gram.y:3374
+#, c-format
+msgid "cannot use RETURN QUERY in a non-SETOF function"
+msgstr "无法在非SETOF函数中使用RETURN QUERY"
+
+#: pl_exec.c:3529
+msgid "structure of query does not match function result type"
+msgstr "查询的结构与函数的返回类型不匹配"
+
+#: pl_exec.c:3562 pl_exec.c:5753
+#, c-format
+msgid "query \"%s\" is not a SELECT"
+msgstr "查询 \"%s\"不是SELECT语句"
+
+#: pl_exec.c:3584 pl_exec.c:4403 pl_exec.c:8589
+#, c-format
+msgid "query string argument of EXECUTE is null"
+msgstr "EXECUTE的查询字符串参数是空值"
+
+#: pl_exec.c:3664 pl_exec.c:3802
+#, c-format
+msgid "RAISE option already specified: %s"
+msgstr "已经指定了RAISE选项:%s"
+
+#: pl_exec.c:3698
+#, c-format
+msgid "RAISE without parameters cannot be used outside an exception handler"
+msgstr "不带有参数的RAISE不能在异常处理的外部使用"
+
+#: pl_exec.c:3792
+#, c-format
+msgid "RAISE statement option cannot be null"
+msgstr "RAISE语句选项不能为空"
+
+#: pl_exec.c:3862
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pl_exec.c:3917
+#, c-format
+msgid "assertion failed"
+msgstr "断言失败"
+
+#: pl_exec.c:4276 pl_exec.c:4464
+#, c-format
+msgid "cannot COPY to/from client in PL/pgSQL"
+msgstr "无法在PL/pgSQL中从客户端或向客户端使用COPY命令"
+
+#: pl_exec.c:4282
+#, c-format
+msgid "unsupported transaction command in PL/pgSQL"
+msgstr "PL/pgSQL中不支持的事务命令"
+
+#: pl_exec.c:4305 pl_exec.c:4493
+#, c-format
+msgid "INTO used with a command that cannot return data"
+msgstr "使用了带有无法返回数据的命令的INTO"
+
+#: pl_exec.c:4328 pl_exec.c:4516
+#, c-format
+msgid "query returned no rows"
+msgstr "查询没有返回记录"
+
+#: pl_exec.c:4350 pl_exec.c:4535
+#, c-format
+msgid "query returned more than one row"
+msgstr "查询返回多条记录"
+
+#: pl_exec.c:4352
+#, c-format
+msgid "Make sure the query returns a single row, or use LIMIT 1."
+msgstr "确保查询返回单行,或使用 LIMIT 1."
+
+#: pl_exec.c:4368
+#, c-format
+msgid "query has no destination for result data"
+msgstr "对于结果数据,查询没有目标"
+
+#: pl_exec.c:4369
+#, c-format
+msgid "If you want to discard the results of a SELECT, use PERFORM instead."
+msgstr "如果您想要放弃SELECT语句的结果,请使用PERFORM."
+
+#: pl_exec.c:4456
+#, c-format
+msgid "EXECUTE of SELECT ... INTO is not implemented"
+msgstr "没有执行EXECUTE of SELECT ... INTO "
+
+#: pl_exec.c:4457
+#, c-format
+msgid "You might want to use EXECUTE ... INTO or EXECUTE CREATE TABLE ... AS instead."
+msgstr "您可以使用EXECUTE ... INTO或者EXECUTE CREATE TABLE ... AS语句来替代"
+
+#: pl_exec.c:4470
+#, c-format
+msgid "EXECUTE of transaction commands is not implemented"
+msgstr "未执行事务命令"
+
+#: pl_exec.c:4771 pl_exec.c:4859
+#, c-format
+msgid "cursor variable \"%s\" is null"
+msgstr "游标变量\"%s\"是空的"
+
+#: pl_exec.c:4782 pl_exec.c:4870
+#, c-format
+msgid "cursor \"%s\" does not exist"
+msgstr "游标 \"%s\" 不存在"
+
+#: pl_exec.c:4795
+#, c-format
+msgid "relative or absolute cursor position is null"
+msgstr "游标的相对或绝对位置是空的"
+
+#: pl_exec.c:5039 pl_exec.c:5134
+#, c-format
+msgid "null value cannot be assigned to variable \"%s\" declared NOT NULL"
+msgstr "不能将声明为NOT NULL的变量\"%s\" 分配空值"
+
+#: pl_exec.c:5115
+#, c-format
+msgid "cannot assign non-composite value to a row variable"
+msgstr "无法将非组合值分配给记录变量"
+
+#: pl_exec.c:5147
+#, c-format
+msgid "cannot assign non-composite value to a record variable"
+msgstr "无法将非组合值分配给记录类型变量"
+
+#: pl_exec.c:5198
+#, c-format
+msgid "cannot assign to system column \"%s\""
+msgstr "不能指定系统字段名 \"%s\""
+
+#: pl_exec.c:5647
+#, c-format
+msgid "query \"%s\" did not return data"
+msgstr "查询\"%s\"没有返回数据"
+
+#: pl_exec.c:5655
+#, c-format
+msgid "query \"%s\" returned %d column"
+msgid_plural "query \"%s\" returned %d columns"
+msgstr[0] "查询\"%s\"返回%d列"
+
+#: pl_exec.c:5683
+#, c-format
+msgid "query \"%s\" returned more than one row"
+msgstr "查询\"%s\"返回多条数据"
+
+#: pl_exec.c:6561 pl_exec.c:6601 pl_exec.c:6641
+#, c-format
+msgid "type of parameter %d (%s) does not match that when preparing the plan (%s)"
+msgstr "第%d个参数(%s)的类型与正在执行计划(%s)中的不匹配"
+
+#: pl_exec.c:7052 pl_exec.c:7086 pl_exec.c:7160 pl_exec.c:7186
+#, c-format
+msgid "number of source and target fields in assignment does not match"
+msgstr "分配中的源字段和目标字段数不匹配"
+
+#. translator: %s represents a name of an extra check
+#: pl_exec.c:7054 pl_exec.c:7088 pl_exec.c:7162 pl_exec.c:7188
+#, c-format
+msgid "%s check of %s is active."
+msgstr "%s检查%s是否激活."
+
+#: pl_exec.c:7058 pl_exec.c:7092 pl_exec.c:7166 pl_exec.c:7192
+#, c-format
+msgid "Make sure the query returns the exact list of columns."
+msgstr "确保查询返回准确的列列表."
+
+#: pl_exec.c:7579
+#, c-format
+msgid "record \"%s\" is not assigned yet"
+msgstr "记录 \"%s\"还没有分配"
+
+#: pl_exec.c:7580
+#, c-format
+msgid "The tuple structure of a not-yet-assigned record is indeterminate."
+msgstr "未分配记录的元组结构未确定."
+
+#: pl_funcs.c:237
+msgid "statement block"
+msgstr "语句块"
+
+#: pl_funcs.c:239
+msgid "assignment"
+msgstr "赋值"
+
+#: pl_funcs.c:249
+msgid "FOR with integer loop variable"
+msgstr "带有整型循环变量的FOR语句"
+
+#: pl_funcs.c:251
+msgid "FOR over SELECT rows"
+msgstr "在SELECT记录上的FOR语句"
+
+#: pl_funcs.c:253
+msgid "FOR over cursor"
+msgstr "在游标上运行的FOR语句"
+
+#: pl_funcs.c:255
+msgid "FOREACH over array"
+msgstr "在数组上运行的FOREACH语句"
+
+#: pl_funcs.c:269
+msgid "SQL statement"
+msgstr "SQL语句"
+
+#: pl_funcs.c:273
+msgid "FOR over EXECUTE statement"
+msgstr "在EXECUTE语句上的FOR语句"
+
+#: pl_gram.y:485
+#, c-format
+msgid "block label must be placed before DECLARE, not after"
+msgstr "代码块标签必须放在DECLARE的前面,而不是后面"
+
+#: pl_gram.y:505
+#, c-format
+msgid "collations are not supported by type %s"
+msgstr "类型%s不能使用排序规则"
+
+#: pl_gram.y:524
+#, c-format
+msgid "variable \"%s\" must have a default value, since it's declared NOT NULL"
+msgstr "变量\"%s\"必须有默认值,因为它声明为非空"
+
+#: pl_gram.y:672 pl_gram.y:687 pl_gram.y:713
+#, c-format
+msgid "variable \"%s\" does not exist"
+msgstr "变量 \"%s\" 不存在"
+
+#: pl_gram.y:731 pl_gram.y:759
+msgid "duplicate declaration"
+msgstr "重复声明"
+
+#: pl_gram.y:742 pl_gram.y:770
+#, c-format
+msgid "variable \"%s\" shadows a previously defined variable"
+msgstr "变量\"%s\"隐藏了前一个已定义的变量"
+
+#: pl_gram.y:1042
+#, c-format
+msgid "diagnostics item %s is not allowed in GET STACKED DIAGNOSTICS"
+msgstr "诊断项 %s 不允许出现在GET STACKED DIAGNOSTICS的结果中"
+
+#: pl_gram.y:1060
+#, c-format
+msgid "diagnostics item %s is not allowed in GET CURRENT DIAGNOSTICS"
+msgstr "诊断项 %s 不允许出现在GET CURRENT DIAGNOSTICS的结果中"
+
+#: pl_gram.y:1155
+msgid "unrecognized GET DIAGNOSTICS item"
+msgstr "无法识别的项GET DIAGNOSTICS"
+
+#: pl_gram.y:1171 pl_gram.y:3549
+#, c-format
+msgid "\"%s\" is not a scalar variable"
+msgstr "\"%s\" 不是一个标量变量"
+
+#: pl_gram.y:1401 pl_gram.y:1595
+#, c-format
+msgid "loop variable of loop over rows must be a record variable or list of scalar variables"
+msgstr "在记录集上进行循环的循环变量必须是记录变量或标量变量列表"
+
+#: pl_gram.y:1436
+#, c-format
+msgid "cursor FOR loop must have only one target variable"
+msgstr "游标的FOR循环只能有一个目标变量"
+
+#: pl_gram.y:1443
+#, c-format
+msgid "cursor FOR loop must use a bound cursor variable"
+msgstr "游标的FOR循环必须使用有界游标变量"
+
+#: pl_gram.y:1534
+#, c-format
+msgid "integer FOR loop must have only one target variable"
+msgstr "整数FOR循环必须只能有一个目标变量"
+
+#: pl_gram.y:1568
+#, c-format
+msgid "cannot specify REVERSE in query FOR loop"
+msgstr "无法在查询FOR循环中指定REVERSE "
+
+#: pl_gram.y:1698
+#, c-format
+msgid "loop variable of FOREACH must be a known variable or list of variables"
+msgstr "FOREACH的循环变量必须是已知类型或者是变量列表"
+
+#: pl_gram.y:1740
+#, c-format
+msgid "there is no label \"%s\" attached to any block or loop enclosing this statement"
+msgstr "在任何包围这个语句的块或者循环上都没有附着标签\"%s\""
+
+#: pl_gram.y:1748
+#, c-format
+msgid "block label \"%s\" cannot be used in CONTINUE"
+msgstr "块标签 \"%s\" 不能被用在 CONTINUE 中"
+
+#: pl_gram.y:1763
+#, c-format
+msgid "EXIT cannot be used outside a loop, unless it has a label"
+msgstr "不能在循环外部使用EXIT,除非该循环有一个标签"
+
+#: pl_gram.y:1764
+#, c-format
+msgid "CONTINUE cannot be used outside a loop"
+msgstr "在循环的外部不能使用CONTINUE"
+
+#: pl_gram.y:1788 pl_gram.y:1826 pl_gram.y:1874 pl_gram.y:2998 pl_gram.y:3084
+#: pl_gram.y:3195 pl_gram.y:3948
+msgid "unexpected end of function definition"
+msgstr "在函数定义中意外出现的结束标志"
+
+#: pl_gram.y:1894 pl_gram.y:1918 pl_gram.y:1934 pl_gram.y:1940 pl_gram.y:2061
+#: pl_gram.y:2069 pl_gram.y:2083 pl_gram.y:2178 pl_gram.y:2402 pl_gram.y:2492
+#: pl_gram.y:2649 pl_gram.y:3791 pl_gram.y:3852 pl_gram.y:3929
+msgid "syntax error"
+msgstr "语法错误"
+
+#: pl_gram.y:1922 pl_gram.y:1924 pl_gram.y:2406 pl_gram.y:2408
+msgid "invalid SQLSTATE code"
+msgstr "无效的SQLSTATE代码"
+
+#: pl_gram.y:2126
+msgid "syntax error, expected \"FOR\""
+msgstr "语法错误,期望\"FOR\""
+
+#: pl_gram.y:2187
+#, c-format
+msgid "FETCH statement cannot return multiple rows"
+msgstr "FETCH语句无法返回多条记录"
+
+#: pl_gram.y:2284
+#, c-format
+msgid "cursor variable must be a simple variable"
+msgstr "游标变量必须是一个简单变量"
+
+#: pl_gram.y:2290
+#, c-format
+msgid "variable \"%s\" must be of type cursor or refcursor"
+msgstr "变量\"%s\" 必须属于游标类型或refcursor类型"
+
+#: pl_gram.y:2620 pl_gram.y:2631
+#, c-format
+msgid "\"%s\" is not a known variable"
+msgstr "\"%s\" 不是一个已知变量"
+
+#: pl_gram.y:2737 pl_gram.y:2747 pl_gram.y:2903
+msgid "mismatched parentheses"
+msgstr "括号不匹配"
+
+#: pl_gram.y:2751
+#, c-format
+msgid "missing \"%s\" at end of SQL expression"
+msgstr "在SQL表达式的结尾处丢失\"%s\""
+
+#: pl_gram.y:2757
+#, c-format
+msgid "missing \"%s\" at end of SQL statement"
+msgstr "在SQL语句的结尾处丢失\"%s\""
+
+#: pl_gram.y:2774
+msgid "missing expression"
+msgstr "缺少表达式"
+
+#: pl_gram.y:2776
+msgid "missing SQL statement"
+msgstr "缺少SQL语句"
+
+#: pl_gram.y:2905
+msgid "incomplete data type declaration"
+msgstr "未完成的数据类型声明"
+
+#: pl_gram.y:2928
+msgid "missing data type declaration"
+msgstr "丢失数据类型声明"
+
+#: pl_gram.y:3006
+msgid "INTO specified more than once"
+msgstr "多次指定INTO"
+
+#: pl_gram.y:3176
+msgid "expected FROM or IN"
+msgstr "期望关键字FROM或IN"
+
+#: pl_gram.y:3237
+#, c-format
+msgid "RETURN cannot have a parameter in function returning set"
+msgstr "在返回为集合的函数中RETURN不能带有参数"
+
+#: pl_gram.y:3238
+#, c-format
+msgid "Use RETURN NEXT or RETURN QUERY."
+msgstr "使用RETURN NEXT或RETURN QUERY."
+
+#: pl_gram.y:3248
+#, c-format
+msgid "RETURN cannot have a parameter in a procedure"
+msgstr "在过程中RETURN不能带有参数"
+
+#: pl_gram.y:3253
+#, c-format
+msgid "RETURN cannot have a parameter in function returning void"
+msgstr "在返回为空的函数中RETURN不能有参数"
+
+#: pl_gram.y:3262
+#, c-format
+msgid "RETURN cannot have a parameter in function with OUT parameters"
+msgstr "在带有输出参数的函数中RETURN不能有参数"
+
+#: pl_gram.y:3325
+#, c-format
+msgid "RETURN NEXT cannot have a parameter in function with OUT parameters"
+msgstr "在带有输出参数的函数中RETURN NEXT不能有参数"
+
+#: pl_gram.y:3433
+#, c-format
+msgid "variable \"%s\" is declared CONSTANT"
+msgstr "变量\"%s\"被声明为常量"
+
+#: pl_gram.y:3491
+#, c-format
+msgid "record variable cannot be part of multiple-item INTO list"
+msgstr "记录变量不能作为带有多个项的INTO列表中的一部分"
+
+#: pl_gram.y:3537
+#, c-format
+msgid "too many INTO variables specified"
+msgstr "在INTO后面指定了太多的变量"
+
+#: pl_gram.y:3745
+#, fuzzy, c-format
+#| msgid "end label \"%s\" specified for unlabelled block"
+msgid "end label \"%s\" specified for unlabeled block"
+msgstr "为没有标签的代码块指定结束标签\"%s\" "
+
+#: pl_gram.y:3752
+#, c-format
+msgid "end label \"%s\" differs from block's label \"%s\""
+msgstr "结束标签\"%s\" 与代码块标签\"%s\"不同"
+
+#: pl_gram.y:3786
+#, c-format
+msgid "cursor \"%s\" has no arguments"
+msgstr "游标\"%s\" 没有参数"
+
+#: pl_gram.y:3800
+#, c-format
+msgid "cursor \"%s\" has arguments"
+msgstr "游标\"%s\"有参数"
+
+#: pl_gram.y:3842
+#, c-format
+msgid "cursor \"%s\" has no argument named \"%s\""
+msgstr "游标\"%s\" 没有名为 \"%s\"的参数"
+
+#: pl_gram.y:3862
+#, c-format
+msgid "value for parameter \"%s\" of cursor \"%s\" specified more than once"
+msgstr "游标\"%2$s\"中的参数值\"%1$s\"指定了多次"
+
+#: pl_gram.y:3887
+#, c-format
+msgid "not enough arguments for cursor \"%s\""
+msgstr "游标 \"%s\"没有足够的参数"
+
+#: pl_gram.y:3894
+#, c-format
+msgid "too many arguments for cursor \"%s\""
+msgstr "游标 \"%s\"给定的参数太多"
+
+#: pl_gram.y:3980
+msgid "unrecognized RAISE statement option"
+msgstr "无法识别的RAISE语句选项"
+
+#: pl_gram.y:3984
+msgid "syntax error, expected \"=\""
+msgstr "语法错误,期望\"=\""
+
+#: pl_gram.y:4025
+#, c-format
+msgid "too many parameters specified for RAISE"
+msgstr "为RAISE子句指定参数过多"
+
+#: pl_gram.y:4029
+#, c-format
+msgid "too few parameters specified for RAISE"
+msgstr "为RAISE子句指定参数过少"
+
+#: pl_handler.c:156
+msgid "Sets handling of conflicts between PL/pgSQL variable names and table column names."
+msgstr "设置在PL/pgSQL变量名称和表中列名冲突时的处理原则"
+
+#: pl_handler.c:165
+msgid "Print information about parameters in the DETAIL part of the error messages generated on INTO ... STRICT failures."
+msgstr "打印产生于INTO...STRICT失败时的详细的错误消息里的参数信息"
+
+#: pl_handler.c:173
+msgid "Perform checks given in ASSERT statements."
+msgstr "执行在ASSERT语句中给定的检查。"
+
+#: pl_handler.c:181
+msgid "List of programming constructs that should produce a warning."
+msgstr "程序构造列表必须输出警告."
+
+#: pl_handler.c:191
+msgid "List of programming constructs that should produce an error."
+msgstr "程序构造列表必须输出一个错误信息提示."
+
+#. translator: %s is typically the translation of "syntax error"
+#: pl_scanner.c:508
+#, c-format
+msgid "%s at end of input"
+msgstr "%s 在输入的末尾"
+
+#. translator: first %s is typically the translation of "syntax error"
+#: pl_scanner.c:524
+#, c-format
+msgid "%s at or near \"%s\""
+msgstr "%s 在 \"%s\" 或附近的"
+
diff --git a/src/pl/plpgsql/src/sql/plpgsql_array.sql b/src/pl/plpgsql/src/sql/plpgsql_array.sql
new file mode 100644
index 0000000..4c3f26b
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_array.sql
@@ -0,0 +1,79 @@
+--
+-- Tests for PL/pgSQL handling of array variables
+--
+-- We also check arrays of composites here, so this has some overlap
+-- with the plpgsql_record tests.
+--
+
+create type complex as (r float8, i float8);
+create type quadarray as (c1 complex[], c2 complex);
+
+do $$ declare a int[];
+begin a := array[1,2]; a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[3] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1][4] := 4; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1] := 23::text; raise notice 'a = %', a; end$$; -- lax typing
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2:3] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := array[1,2]; a[2] := a[2] + 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := array[3,4]; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a[1:2] := 4; raise notice 'a = %', a; end$$; -- error
+
+do $$ declare a complex[];
+begin a[1] := (1,2); a[1].i := 11; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex[];
+begin a[1].i := 11; raise notice 'a = %, a[1].i = %', a, a[1].i; end$$;
+
+-- perhaps this ought to work, but for now it doesn't:
+do $$ declare a complex[];
+begin a[1:2].i := array[11,12]; raise notice 'a = %', a; end$$;
+
+do $$ declare a quadarray;
+begin a.c1[1].i := 11; raise notice 'a = %, a.c1[1].i = %', a, a.c1[1].i; end$$;
+
+do $$ declare a int[];
+begin a := array_agg(x) from (values(1),(2),(3)) v(x); raise notice 'a = %', a; end$$;
+
+create temp table onecol as select array[1,2] as f1;
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := * from onecol for update; raise notice 'a = %', a; end$$;
+
+-- error cases:
+
+do $$ declare a int[];
+begin a := from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1, f1 from onecol; raise notice 'a = %', a; end$$;
+
+insert into onecol values(array[11]);
+
+do $$ declare a int[];
+begin a := f1 from onecol; raise notice 'a = %', a; end$$;
+
+do $$ declare a int[];
+begin a := f1 from onecol limit 1; raise notice 'a = %', a; end$$;
+
+do $$ declare a real;
+begin a[1] := 2; raise notice 'a = %', a; end$$;
+
+do $$ declare a complex;
+begin a.r[1] := 2; raise notice 'a = %', a; end$$;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_cache.sql b/src/pl/plpgsql/src/sql/plpgsql_cache.sql
new file mode 100644
index 0000000..a48f9b2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_cache.sql
@@ -0,0 +1,50 @@
+--
+-- Cache-behavior-dependent test cases
+--
+-- These tests logically belong in plpgsql_record.sql, and perhaps someday
+-- can be merged back into it. For now, however, their results are different
+-- depending on debug_discard_caches, so we must have two expected-output
+-- files to cover both cases. To minimize the maintenance effort resulting
+-- from that, this file should contain only tests that do have different
+-- results under debug_discard_caches.
+--
+
+-- check behavior with changes of a named rowtype
+create table c_mutable(f1 int, f2 text);
+
+create function c_sillyaddone(int) returns int language plpgsql as
+$$ declare r c_mutable; begin r.f1 := $1; return r.f1 + 1; end $$;
+select c_sillyaddone(42);
+
+alter table c_mutable drop column f1;
+alter table c_mutable add column f1 float8;
+
+-- currently, this fails due to cached plan for "r.f1 + 1" expression
+-- (but if debug_discard_caches is on, it will succeed)
+select c_sillyaddone(42);
+
+-- but it's OK if we force plan rebuilding
+discard plans;
+select c_sillyaddone(42);
+
+-- check behavior with changes in a record rowtype
+create function show_result_type(text) returns text language plpgsql as
+$$
+ declare
+ r record;
+ t text;
+ begin
+ execute $1 into r;
+ select pg_typeof(r.a) into t;
+ return format('type %s value %s', t, r.a::text);
+ end;
+$$;
+
+select show_result_type('select 1 as a');
+-- currently this fails due to cached plan for pg_typeof expression
+-- (but if debug_discard_caches is on, it will succeed)
+select show_result_type('select 2.0 as a');
+
+-- but it's OK if we force plan rebuilding
+discard plans;
+select show_result_type('select 2.0 as a');
diff --git a/src/pl/plpgsql/src/sql/plpgsql_call.sql b/src/pl/plpgsql/src/sql/plpgsql_call.sql
new file mode 100644
index 0000000..8efc8e8
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_call.sql
@@ -0,0 +1,494 @@
+--
+-- Tests for procedures / CALL syntax
+--
+
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ NULL;
+END;
+$$;
+
+CALL test_proc1();
+
+
+-- error: can't return non-NULL
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RETURN 5;
+END;
+$$;
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ INSERT INTO test1 VALUES (x);
+END;
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+-- Check that plan revalidation doesn't prevent setting transaction properties
+-- (bug #18059). This test must include the first temp-object creation in
+-- this script, or it won't exercise the bug scenario. Hence we put it early.
+CREATE PROCEDURE test_proc3a()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ RAISE NOTICE 'done';
+END;
+$$;
+
+CALL test_proc3a();
+CREATE TEMP TABLE tt1(f1 int);
+CALL test_proc3a();
+
+
+-- nested CALL
+TRUNCATE TABLE test1;
+
+CREATE PROCEDURE test_proc4(y int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL test_proc3(y);
+ CALL test_proc3($1);
+END;
+$$;
+
+CALL test_proc4(66);
+
+SELECT * FROM test1;
+
+CALL test_proc4(66);
+
+SELECT * FROM test1;
+
+
+-- output arguments
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ a := a || '+' || a;
+END;
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ b := b * a;
+ c := c * a;
+END;
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+ CALL test_proc6(2, c => y, b => x);
+ RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x + 1, y); -- error
+ RAISE INFO 'x = %, y = %', x, y;
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x constant int := 3;
+ y int := 4;
+BEGIN
+ CALL test_proc6(2, x, y); -- error because x is constant
+END;
+$$;
+
+
+DO
+LANGUAGE plpgsql
+$$
+DECLARE
+ x int := 3;
+ y int := 4;
+BEGIN
+ FOR i IN 1..5 LOOP
+ CALL test_proc6(i, x, y);
+ RAISE INFO 'x = %, y = %', x, y;
+ END LOOP;
+END;
+$$;
+
+
+-- recursive with output arguments
+
+CREATE PROCEDURE test_proc7(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+IF x > 1 THEN
+ a := x / 10;
+ b := x / 2;
+ CALL test_proc7(b::int, a, b);
+END IF;
+END;
+$$;
+
+CALL test_proc7(100, -1, -1);
+
+-- inner COMMIT with output arguments
+
+CREATE PROCEDURE test_proc7c(x int, INOUT a int, INOUT b numeric)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ a := x / 10;
+ b := x / 2;
+ COMMIT;
+END;
+$$;
+
+CREATE PROCEDURE test_proc7cc(_x int)
+LANGUAGE plpgsql
+AS $$
+DECLARE _a int; _b numeric;
+BEGIN
+ CALL test_proc7c(_x, _a, _b);
+ RAISE NOTICE '_x: %,_a: %, _b: %', _x, _a, _b;
+END
+$$;
+
+CALL test_proc7cc(10);
+
+
+-- named parameters and defaults
+
+CREATE PROCEDURE test_proc8a(INOUT a int, INOUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := a * 10;
+ b := b + 10;
+END;
+$$;
+
+CALL test_proc8a(10, 20);
+CALL test_proc8a(b => 20, a => 10);
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc8a(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+ CALL test_proc8a(b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
+CREATE PROCEDURE test_proc8b(INOUT a int, INOUT b int, INOUT c int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := a * 10;
+ b := b + 10;
+ c := c * -10;
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8b(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ CALL test_proc8b(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+
+CREATE PROCEDURE test_proc8c(INOUT a int, INOUT b int, INOUT c int DEFAULT 11)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ a := a * 10;
+ b := b + 10;
+ c := c * -10;
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(c => _c, b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, _b); -- fail, no output argument for c
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 50;
+ CALL test_proc8c(_a, b => _b); -- fail, no output argument for c
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ b := a * 2;
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+CREATE PROCEDURE test_proc10(IN a int, OUT b int, IN c int DEFAULT 11)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %, c: %', a, b, c;
+ b := a - c;
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b, c => _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(a => _a, b => _b, c => _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, c => _c, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(_a, b => _b);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc10(b => _b, a => _a);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+-- OUT + VARIADIC
+
+CREATE PROCEDURE test_proc11(a OUT int, VARIADIC b int[])
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'a: %, b: %', a, b;
+ a := b[1] + b[2];
+END;
+$$;
+
+DO $$
+DECLARE _a int; _b int; _c int;
+BEGIN
+ _a := 10; _b := 30; _c := 7;
+ CALL test_proc11(_a, _b, _c);
+ RAISE NOTICE '_a: %, _b: %, _c: %', _a, _b, _c;
+END
+$$;
+
+
+-- transition variable assignment
+
+TRUNCATE test1;
+
+CREATE FUNCTION triggerfunc1() RETURNS trigger
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ z int := 0;
+BEGIN
+ CALL test_proc6(2, NEW.a, NEW.a);
+ RETURN NEW;
+END;
+$$;
+
+CREATE TRIGGER t1 BEFORE INSERT ON test1 EXECUTE PROCEDURE triggerfunc1();
+
+INSERT INTO test1 VALUES (1), (2), (3);
+
+UPDATE test1 SET a = 22 WHERE a = 2;
+
+SELECT * FROM test1 ORDER BY a;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc3;
+DROP PROCEDURE test_proc4;
+
+DROP TABLE test1;
+
+
+-- more checks for named-parameter handling
+
+CREATE PROCEDURE p1(v_cnt int, v_Text inout text = NULL)
+AS $$
+BEGIN
+ v_Text := 'v_cnt = ' || v_cnt;
+END
+$$ LANGUAGE plpgsql;
+
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer := 42;
+BEGIN
+ CALL p1(v_cnt := v_cnt); -- error, must supply something for v_Text
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer := 42;
+BEGIN
+ CALL p1(v_cnt := v_cnt, v_Text := v_Text);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+
+DO $$
+DECLARE
+ v_Text text;
+BEGIN
+ CALL p1(10, v_Text := v_Text);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+
+DO $$
+DECLARE
+ v_Text text;
+ v_cnt integer;
+BEGIN
+ CALL p1(v_Text := v_Text, v_cnt := v_cnt);
+ RAISE NOTICE '%', v_Text;
+END;
+$$;
+
+
+-- check that we detect change of dependencies in CALL
+-- atomic and non-atomic call sites used to do this differently, so check both
+
+CREATE PROCEDURE inner_p (f1 int)
+AS $$
+BEGIN
+ RAISE NOTICE 'inner_p(%)', f1;
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 1 $$ LANGUAGE sql;
+
+CREATE PROCEDURE outer_p (f1 int)
+AS $$
+BEGIN
+ RAISE NOTICE 'outer_p(%)', f1;
+ CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+
+CREATE FUNCTION outer_f (f1 int) RETURNS void
+AS $$
+BEGIN
+ RAISE NOTICE 'outer_f(%)', f1;
+ CALL inner_p(f(f1));
+END
+$$ LANGUAGE plpgsql;
+
+CALL outer_p(42);
+SELECT outer_f(42);
+
+DROP FUNCTION f(int);
+CREATE FUNCTION f(int) RETURNS int AS $$ SELECT $1 + 2 $$ LANGUAGE sql;
+
+CALL outer_p(42);
+SELECT outer_f(42);
diff --git a/src/pl/plpgsql/src/sql/plpgsql_control.sql b/src/pl/plpgsql/src/sql/plpgsql_control.sql
new file mode 100644
index 0000000..ed72311
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_control.sql
@@ -0,0 +1,488 @@
+--
+-- Tests for PL/pgSQL control structures
+--
+
+-- integer FOR loop
+
+do $$
+begin
+ -- basic case
+ for i in 1..3 loop
+ raise notice '1..3: i = %', i;
+ end loop;
+ -- with BY, end matches exactly
+ for i in 1..10 by 3 loop
+ raise notice '1..10 by 3: i = %', i;
+ end loop;
+ -- with BY, end does not match
+ for i in 1..11 by 3 loop
+ raise notice '1..11 by 3: i = %', i;
+ end loop;
+ -- zero iterations
+ for i in 1..0 by 3 loop
+ raise notice '1..0 by 3: i = %', i;
+ end loop;
+ -- REVERSE
+ for i in reverse 10..0 by 3 loop
+ raise notice 'reverse 10..0 by 3: i = %', i;
+ end loop;
+ -- potential overflow
+ for i in 2147483620..2147483647 by 10 loop
+ raise notice '2147483620..2147483647 by 10: i = %', i;
+ end loop;
+ -- potential overflow, reverse direction
+ for i in reverse -2147483620..-2147483647 by 10 loop
+ raise notice 'reverse -2147483620..-2147483647 by 10: i = %', i;
+ end loop;
+end$$;
+
+-- BY can't be zero or negative
+do $$
+begin
+ for i in 1..3 by 0 loop
+ raise notice '1..3 by 0: i = %', i;
+ end loop;
+end$$;
+
+do $$
+begin
+ for i in 1..3 by -1 loop
+ raise notice '1..3 by -1: i = %', i;
+ end loop;
+end$$;
+
+do $$
+begin
+ for i in reverse 1..3 by -1 loop
+ raise notice 'reverse 1..3 by -1: i = %', i;
+ end loop;
+end$$;
+
+
+-- CONTINUE statement
+
+create table conttesttbl(idx serial, v integer);
+insert into conttesttbl(v) values(10);
+insert into conttesttbl(v) values(20);
+insert into conttesttbl(v) values(30);
+insert into conttesttbl(v) values(40);
+
+create function continue_test1() returns void as $$
+declare _i integer = 0; _r record;
+begin
+ raise notice '---1---';
+ loop
+ _i := _i + 1;
+ raise notice '%', _i;
+ continue when _i < 10;
+ exit;
+ end loop;
+
+ raise notice '---2---';
+ <<lbl>>
+ loop
+ _i := _i - 1;
+ loop
+ raise notice '%', _i;
+ continue lbl when _i > 0;
+ exit lbl;
+ end loop;
+ end loop;
+
+ raise notice '---3---';
+ <<the_loop>>
+ while _i < 10 loop
+ _i := _i + 1;
+ continue the_loop when _i % 2 = 0;
+ raise notice '%', _i;
+ end loop;
+
+ raise notice '---4---';
+ for _i in 1..10 loop
+ begin
+ -- applies to outer loop, not the nested begin block
+ continue when _i < 5;
+ raise notice '%', _i;
+ end;
+ end loop;
+
+ raise notice '---5---';
+ for _r in select * from conttesttbl loop
+ continue when _r.v <= 20;
+ raise notice '%', _r.v;
+ end loop;
+
+ raise notice '---6---';
+ for _r in execute 'select * from conttesttbl' loop
+ continue when _r.v <= 20;
+ raise notice '%', _r.v;
+ end loop;
+
+ raise notice '---7---';
+ <<looplabel>>
+ for _i in 1..3 loop
+ continue looplabel when _i = 2;
+ raise notice '%', _i;
+ end loop;
+
+ raise notice '---8---';
+ _i := 1;
+ while _i <= 3 loop
+ raise notice '%', _i;
+ _i := _i + 1;
+ continue when _i = 3;
+ end loop;
+
+ raise notice '---9---';
+ for _r in select * from conttesttbl order by v limit 1 loop
+ raise notice '%', _r.v;
+ continue;
+ end loop;
+
+ raise notice '---10---';
+ for _r in execute 'select * from conttesttbl order by v limit 1' loop
+ raise notice '%', _r.v;
+ continue;
+ end loop;
+
+ raise notice '---11---';
+ <<outerlooplabel>>
+ for _i in 1..2 loop
+ raise notice 'outer %', _i;
+ <<innerlooplabel>>
+ for _j in 1..3 loop
+ continue outerlooplabel when _j = 2;
+ raise notice 'inner %', _j;
+ end loop;
+ end loop;
+end; $$ language plpgsql;
+
+select continue_test1();
+
+-- should fail: CONTINUE is only legal inside a loop
+create function continue_error1() returns void as $$
+begin
+ begin
+ continue;
+ end;
+end;
+$$ language plpgsql;
+
+-- should fail: unlabeled EXIT is only legal inside a loop
+create function exit_error1() returns void as $$
+begin
+ begin
+ exit;
+ end;
+end;
+$$ language plpgsql;
+
+-- should fail: no such label
+create function continue_error2() returns void as $$
+begin
+ begin
+ loop
+ continue no_such_label;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+
+-- should fail: no such label
+create function exit_error2() returns void as $$
+begin
+ begin
+ loop
+ exit no_such_label;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+
+-- should fail: CONTINUE can't reference the label of a named block
+create function continue_error3() returns void as $$
+begin
+ <<begin_block1>>
+ begin
+ loop
+ continue begin_block1;
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+
+-- On the other hand, EXIT *can* reference the label of a named block
+create function exit_block1() returns void as $$
+begin
+ <<begin_block1>>
+ begin
+ loop
+ exit begin_block1;
+ raise exception 'should not get here';
+ end loop;
+ end;
+end;
+$$ language plpgsql;
+
+select exit_block1();
+
+-- verbose end block and end loop
+create function end_label1() returns void as $$
+<<blbl>>
+begin
+ <<flbl1>>
+ for i in 1 .. 10 loop
+ raise notice 'i = %', i;
+ exit flbl1;
+ end loop flbl1;
+ <<flbl2>>
+ for j in 1 .. 10 loop
+ raise notice 'j = %', j;
+ exit flbl2;
+ end loop;
+end blbl;
+$$ language plpgsql;
+
+select end_label1();
+
+-- should fail: undefined end label
+create function end_label2() returns void as $$
+begin
+ for _i in 1 .. 10 loop
+ exit;
+ end loop flbl1;
+end;
+$$ language plpgsql;
+
+-- should fail: end label does not match start label
+create function end_label3() returns void as $$
+<<outer_label>>
+begin
+ <<inner_label>>
+ for _i in 1 .. 10 loop
+ exit;
+ end loop outer_label;
+end;
+$$ language plpgsql;
+
+-- should fail: end label on a block without a start label
+create function end_label4() returns void as $$
+<<outer_label>>
+begin
+ for _i in 1 .. 10 loop
+ exit;
+ end loop outer_label;
+end;
+$$ language plpgsql;
+
+-- unlabeled exit matches no blocks
+do $$
+begin
+for i in 1..10 loop
+ <<innerblock>>
+ begin
+ begin -- unlabeled block
+ exit;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+ end;
+ raise notice 'nor here';
+end loop;
+raise notice 'should get here';
+end$$;
+
+-- check exit out of an unlabeled block to a labeled one
+do $$
+<<outerblock>>
+begin
+ <<innerblock>>
+ begin
+ <<moreinnerblock>>
+ begin
+ begin -- unlabeled block
+ exit innerblock;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+ end;
+ raise notice 'nor here';
+ end;
+ raise notice 'should get here';
+end$$;
+
+-- check exit out of outermost block
+do $$
+<<outerblock>>
+begin
+ <<innerblock>>
+ begin
+ exit outerblock;
+ raise notice 'should not get here';
+ end;
+ raise notice 'should not get here, either';
+end$$;
+
+-- unlabeled exit does match a while loop
+do $$
+begin
+ <<outermostwhile>>
+ while 1 > 0 loop
+ <<outerwhile>>
+ while 1 > 0 loop
+ <<innerwhile>>
+ while 1 > 0 loop
+ exit;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should get here';
+ exit outermostwhile;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'nor here';
+ end loop;
+ raise notice 'should get here, too';
+end$$;
+
+-- check exit out of an unlabeled while to a labeled one
+do $$
+begin
+ <<outerwhile>>
+ while 1 > 0 loop
+ while 1 > 0 loop
+ exit outerwhile;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'should get here';
+end$$;
+
+-- continue to an outer while
+do $$
+declare i int := 0;
+begin
+ <<outermostwhile>>
+ while i < 2 loop
+ raise notice 'outermostwhile, i = %', i;
+ i := i + 1;
+ <<outerwhile>>
+ while 1 > 0 loop
+ <<innerwhile>>
+ while 1 > 0 loop
+ continue outermostwhile;
+ raise notice 'should not get here';
+ end loop;
+ raise notice 'should not get here, either';
+ end loop;
+ raise notice 'nor here';
+ end loop;
+ raise notice 'out of outermostwhile, i = %', i;
+end$$;
+
+-- return out of a while
+create function return_from_while() returns int language plpgsql as $$
+declare i int := 0;
+begin
+ while i < 10 loop
+ if i > 2 then
+ return i;
+ end if;
+ i := i + 1;
+ end loop;
+ return null;
+end$$;
+
+select return_from_while();
+
+-- using list of scalars in fori and fore stmts
+create function for_vect() returns void as $proc$
+<<lbl>>declare a integer; b varchar; c varchar; r record;
+begin
+ -- fori
+ for i in 1 .. 3 loop
+ raise notice '%', i;
+ end loop;
+ -- fore with record var
+ for r in select gs as aa, 'BB' as bb, 'CC' as cc from generate_series(1,4) gs loop
+ raise notice '% % %', r.aa, r.bb, r.cc;
+ end loop;
+ -- fore with single scalar
+ for a in select gs from generate_series(1,4) gs loop
+ raise notice '%', a;
+ end loop;
+ -- fore with multiple scalars
+ for a,b,c in select gs, 'BB','CC' from generate_series(1,4) gs loop
+ raise notice '% % %', a, b, c;
+ end loop;
+ -- using qualified names in fors, fore is enabled, disabled only for fori
+ for lbl.a, lbl.b, lbl.c in execute $$select gs, 'bb','cc' from generate_series(1,4) gs$$ loop
+ raise notice '% % %', a, b, c;
+ end loop;
+end;
+$proc$ language plpgsql;
+
+select for_vect();
+
+-- CASE statement
+
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+ b int = 1;
+begin
+ case $1
+ when 1 then
+ return 'one';
+ when 2 then
+ return 'two';
+ when 3,4,3+5 then
+ return 'three, four or eight';
+ when a then
+ return 'ten';
+ when a+b, a+b+1 then
+ return 'eleven, twelve';
+ end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(3);
+select case_test(4);
+select case_test(5); -- fails
+select case_test(8);
+select case_test(10);
+select case_test(11);
+select case_test(12);
+select case_test(13); -- fails
+
+create or replace function catch() returns void as $$
+begin
+ raise notice '%', case_test(6);
+exception
+ when case_not_found then
+ raise notice 'caught case_not_found % %', SQLSTATE, SQLERRM;
+end
+$$ language plpgsql;
+
+select catch();
+
+-- test the searched variant too, as well as ELSE
+create or replace function case_test(bigint) returns text as $$
+declare a int = 10;
+begin
+ case
+ when $1 = 1 then
+ return 'one';
+ when $1 = a + 2 then
+ return 'twelve';
+ else
+ return 'other';
+ end case;
+end;
+$$ language plpgsql immutable;
+
+select case_test(1);
+select case_test(2);
+select case_test(12);
+select case_test(13);
diff --git a/src/pl/plpgsql/src/sql/plpgsql_copy.sql b/src/pl/plpgsql/src/sql/plpgsql_copy.sql
new file mode 100644
index 0000000..37f1fa1
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_copy.sql
@@ -0,0 +1,58 @@
+-- directory paths are passed to us in environment variables
+\getenv abs_srcdir PG_ABS_SRCDIR
+\getenv abs_builddir PG_ABS_BUILDDIR
+
+-- set up file names to use
+\set srcfilename :abs_srcdir '/data/copy1.data'
+\set destfilename :abs_builddir '/results/copy1.data'
+
+CREATE TABLE copy1 (a int, b float);
+
+-- COPY TO/FROM not authorized from client.
+DO LANGUAGE plpgsql $$
+BEGIN
+ COPY copy1 TO stdout;
+END;
+$$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ COPY copy1 FROM stdin;
+END;
+$$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COPY copy1 TO stdout';
+END;
+$$;
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COPY copy1 FROM stdin';
+END;
+$$;
+
+-- Valid cases
+-- COPY FROM
+\set dobody 'BEGIN COPY copy1 FROM ' :'srcfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+SELECT * FROM copy1 ORDER BY 1;
+TRUNCATE copy1;
+\set cmd 'COPY copy1 FROM ' :'srcfilename'
+\set dobody 'BEGIN EXECUTE ' :'cmd' '; END'
+DO LANGUAGE plpgsql :'dobody';
+SELECT * FROM copy1 ORDER BY 1;
+
+-- COPY TO
+-- Copy the data externally once, then process it back to the table.
+\set dobody 'BEGIN COPY copy1 TO ' :'destfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+TRUNCATE copy1;
+\set dobody 'BEGIN COPY copy1 FROM ' :'destfilename' '; END'
+DO LANGUAGE plpgsql :'dobody';
+
+\set cmd 'COPY copy1 FROM ' :'destfilename'
+\set dobody 'BEGIN EXECUTE ' :'cmd' '; END'
+DO LANGUAGE plpgsql :'dobody';
+
+SELECT * FROM copy1 ORDER BY 1;
+
+DROP TABLE copy1;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_domain.sql b/src/pl/plpgsql/src/sql/plpgsql_domain.sql
new file mode 100644
index 0000000..8f99aae
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_domain.sql
@@ -0,0 +1,279 @@
+--
+-- Tests for PL/pgSQL's behavior with domain types
+--
+
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+
+CREATE FUNCTION test_argresult_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_booltrue(true, true);
+SELECT * FROM test_argresult_booltrue(false, true);
+SELECT * FROM test_argresult_booltrue(true, false);
+
+CREATE FUNCTION test_assign_booltrue(x bool, y bool) RETURNS booltrue AS $$
+declare v booltrue := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_booltrue(true, true);
+SELECT * FROM test_assign_booltrue(false, true);
+SELECT * FROM test_assign_booltrue(true, false);
+
+
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+
+CREATE FUNCTION test_argresult_uint2(x uint2, y int) RETURNS uint2 AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_uint2(100::uint2, 50);
+SELECT * FROM test_argresult_uint2(100::uint2, -50);
+SELECT * FROM test_argresult_uint2(null, 1);
+
+CREATE FUNCTION test_assign_uint2(x int, y int) RETURNS uint2 AS $$
+declare v uint2 := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_uint2(100, 50);
+SELECT * FROM test_assign_uint2(100, -50);
+SELECT * FROM test_assign_uint2(-100, 50);
+SELECT * FROM test_assign_uint2(null, 1);
+
+
+CREATE DOMAIN nnint AS int NOT NULL;
+
+CREATE FUNCTION test_argresult_nnint(x nnint, y int) RETURNS nnint AS $$
+begin
+return y;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_nnint(10, 20);
+SELECT * FROM test_argresult_nnint(null, 20);
+SELECT * FROM test_argresult_nnint(10, null);
+
+CREATE FUNCTION test_assign_nnint(x int, y int) RETURNS nnint AS $$
+declare v nnint := x;
+begin
+v := y;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint(10, 20);
+SELECT * FROM test_assign_nnint(null, 20);
+SELECT * FROM test_assign_nnint(10, null);
+
+
+--
+-- Domains over arrays
+--
+
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+
+CREATE FUNCTION test_argresult_array_domain(x ordered_pair_domain)
+ RETURNS ordered_pair_domain AS $$
+begin
+return x;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+SELECT * FROM test_argresult_array_domain(NULL::ordered_pair_domain);
+
+CREATE FUNCTION test_argresult_array_domain_check_violation()
+ RETURNS ordered_pair_domain AS $$
+begin
+return array[2,1];
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_argresult_array_domain_check_violation();
+
+CREATE FUNCTION test_assign_ordered_pair_domain(x int, y int, z int) RETURNS ordered_pair_domain AS $$
+declare v ordered_pair_domain := array[x, y];
+begin
+v[2] := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_pair_domain(1,2,3);
+SELECT * FROM test_assign_ordered_pair_domain(1,2,0);
+SELECT * FROM test_assign_ordered_pair_domain(2,1,3);
+
+
+--
+-- Arrays of domains
+--
+
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_read_uint2_array(array[1::uint2]);
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+begin
+return array[x, x];
+end
+$$ LANGUAGE plpgsql;
+
+select test_build_uint2_array(1::int2);
+select test_build_uint2_array(-1::int2); -- fail
+
+CREATE FUNCTION test_argresult_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+begin
+return array[x::ordered_pair_domain, x::ordered_pair_domain];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_domain_array(array[2,4]);
+select test_argresult_domain_array(array[4,2]); -- fail
+
+CREATE FUNCTION test_argresult_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_domain_array2(array[2,4]);
+select test_argresult_domain_array2(array[4,2]); -- fail
+
+CREATE FUNCTION test_argresult_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+begin
+return x[1];
+end
+$$ LANGUAGE plpgsql;
+
+select test_argresult_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+
+
+--
+-- Domains within composite
+--
+
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+
+CREATE FUNCTION test_result_nnint_container(x int, y int)
+ RETURNS nnint_container AS $$
+begin
+return row(x, y)::nnint_container;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT test_result_nnint_container(null, 3);
+SELECT test_result_nnint_container(3, null); -- fail
+
+CREATE FUNCTION test_assign_nnint_container(x int, y int, z int)
+ RETURNS nnint_container AS $$
+declare v nnint_container := row(x, y);
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint_container(1,2,3);
+SELECT * FROM test_assign_nnint_container(1,2,null);
+SELECT * FROM test_assign_nnint_container(1,null,3);
+
+-- Since core system allows this:
+SELECT null::nnint_container;
+-- so should PL/PgSQL
+
+CREATE FUNCTION test_assign_nnint_container2(x int, y int, z int)
+ RETURNS nnint_container AS $$
+declare v nnint_container;
+begin
+v.f2 := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_nnint_container2(1,2,3);
+SELECT * FROM test_assign_nnint_container2(1,2,null);
+
+
+--
+-- Domains of composite
+--
+
+CREATE TYPE named_pair AS (
+ i integer,
+ j integer
+);
+
+CREATE DOMAIN ordered_named_pair AS named_pair CHECK((VALUE).i <= (VALUE).j);
+
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+begin
+return p.i + p.j;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT read_ordered_named_pair(row(1, 2));
+SELECT read_ordered_named_pair(row(2, 1)); -- fail
+
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+begin
+return row(i, j);
+end
+$$ LANGUAGE plpgsql;
+
+SELECT build_ordered_named_pair(1,2);
+SELECT build_ordered_named_pair(2,1); -- fail
+
+CREATE FUNCTION test_assign_ordered_named_pair(x int, y int, z int)
+ RETURNS ordered_named_pair AS $$
+declare v ordered_named_pair := row(x, y);
+begin
+v.j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_named_pair(1,2,3);
+SELECT * FROM test_assign_ordered_named_pair(1,2,0);
+SELECT * FROM test_assign_ordered_named_pair(2,1,3);
+
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+begin
+return array[row(i, j), row(i, j+1)];
+end
+$$ LANGUAGE plpgsql;
+
+SELECT build_ordered_named_pairs(1,2);
+SELECT build_ordered_named_pairs(2,1); -- fail
+
+CREATE FUNCTION test_assign_ordered_named_pairs(x int, y int, z int)
+ RETURNS ordered_named_pair[] AS $$
+declare v ordered_named_pair[] := array[row(x, y)];
+begin
+-- ideally this would work, but it doesn't yet:
+-- v[1].j := z;
+return v;
+end
+$$ LANGUAGE plpgsql;
+
+SELECT * FROM test_assign_ordered_named_pairs(1,2,3);
+SELECT * FROM test_assign_ordered_named_pairs(2,1,3);
+SELECT * FROM test_assign_ordered_named_pairs(1,2,0); -- should fail someday
diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql
new file mode 100644
index 0000000..db65533
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql
@@ -0,0 +1,531 @@
+--
+-- Tests for PL/pgSQL handling of composite (record) variables
+--
+
+create type two_int4s as (f1 int4, f2 int4);
+create type more_int4s as (f0 text, f1 int4, f2 int4);
+create type two_int8s as (q1 int8, q2 int8);
+create type nested_int8s as (c1 two_int8s, c2 two_int8s);
+
+-- base-case return of a composite type
+create function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1,1)::two_int8s; end $$;
+select retc(42);
+
+-- ok to return a matching record type
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1::int8, 1::int8); end $$;
+select retc(42);
+
+-- we don't currently support implicit casting
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1,1); end $$;
+select retc(42);
+
+-- nor extra columns
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ begin return row($1::int8, 1::int8, 42); end $$;
+select retc(42);
+
+-- same cases with an intermediate "record" variable
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1::int8, 1::int8); return r; end $$;
+select retc(42);
+
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1,1); return r; end $$;
+select retc(42);
+
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1::int8, 1::int8, 42); return r; end $$;
+select retc(42);
+
+-- but, for mostly historical reasons, we do convert when assigning
+-- to a named-composite-type variable
+create or replace function retc(int) returns two_int8s language plpgsql as
+$$ declare r two_int8s; begin r := row($1::int8, 1::int8, 42); return r; end $$;
+select retc(42);
+
+do $$ declare c two_int8s;
+begin c := row(1,2); raise notice 'c = %', c; end$$;
+
+do $$ declare c two_int8s;
+begin for c in select 1,2 loop raise notice 'c = %', c; end loop; end$$;
+
+do $$ declare c4 two_int4s; c8 two_int8s;
+begin
+ c8 := row(1,2);
+ c4 := c8;
+ c8 := c4;
+ raise notice 'c4 = %', c4;
+ raise notice 'c8 = %', c8;
+end$$;
+
+do $$ declare c two_int8s; d nested_int8s;
+begin
+ c := row(1,2);
+ d := row(c, row(c.q1, c.q2+1));
+ raise notice 'c = %, d = %', c, d;
+ c.q1 := 10;
+ d.c1 := row(11,12);
+ d.c2.q2 := 42;
+ raise notice 'c = %, d = %', c, d;
+ raise notice 'c.q1 = %, d.c2 = %', c.q1, d.c2;
+ raise notice '(d).c2.q2 = %', (d).c2.q2; -- doesn't work without parens
+ raise notice '(d.c2).q2 = %', (d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- block-qualified naming
+do $$ <<b>> declare c two_int8s; d nested_int8s;
+begin
+ b.c := row(1,2);
+ b.d := row(b.c, row(b.c.q1, b.c.q2+1));
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ b.c.q1 := 10;
+ b.d.c1 := row(11,12);
+ b.d.c2.q2 := 42;
+ raise notice 'b.c = %, b.d = %', b.c, b.d;
+ raise notice 'b.c.q1 = %, b.d.c2 = %', b.c.q1, b.d.c2;
+ raise notice '(b.d).c2.q2 = %', (b.d).c2.q2; -- doesn't work without parens
+ raise notice '(b.d.c2).q2 = %', (b.d.c2).q2; -- doesn't work without parens
+end$$;
+
+-- error cases
+do $$ declare c two_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x = 1; end $$;
+do $$ declare c nested_int8s; begin c.x.q1 = 1; end $$;
+do $$ declare c nested_int8s; begin c.c2.x = 1; end $$;
+do $$ declare c nested_int8s; begin d.c2.x = 1; end $$;
+do $$ <<b>> declare c two_int8s; begin b.c.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.x.q1 = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.c.c2.x = 1; end $$;
+do $$ <<b>> declare c nested_int8s; begin b.d.c2.x = 1; end $$;
+
+-- check passing composite result to another function
+create function getq1(two_int8s) returns int8 language plpgsql as $$
+declare r two_int8s; begin r := $1; return r.q1; end $$;
+
+select getq1(retc(344));
+select getq1(row(1,2));
+
+do $$
+declare r1 two_int8s; r2 record; x int8;
+begin
+ r1 := retc(345);
+ perform getq1(r1);
+ x := getq1(r1);
+ raise notice 'x = %', x;
+ r2 := retc(346);
+ perform getq1(r2);
+ x := getq1(r2);
+ raise notice 'x = %', x;
+end$$;
+
+-- check assignments of composites
+do $$
+declare r1 two_int8s; r2 two_int8s; r3 record; r4 record;
+begin
+ r1 := row(1,2);
+ raise notice 'r1 = %', r1;
+ r1 := r1; -- shouldn't do anything
+ raise notice 'r1 = %', r1;
+ r2 := r1;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r2.q2 = r1.q1 + 3; -- check that r2 has distinct storage
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r1 := null;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r1 := row(7,11)::two_int8s;
+ r2 := r1;
+ raise notice 'r1 = %', r1;
+ raise notice 'r2 = %', r2;
+ r3 := row(1,2);
+ r4 := r3;
+ raise notice 'r3 = %', r3;
+ raise notice 'r4 = %', r4;
+ r4.f1 := r4.f1 + 3; -- check that r4 has distinct storage
+ raise notice 'r3 = %', r3;
+ raise notice 'r4 = %', r4;
+ r1 := r3;
+ raise notice 'r1 = %', r1;
+ r4 := r1;
+ raise notice 'r4 = %', r4;
+ r4.q2 := r4.q2 + 1; -- r4's field names have changed
+ raise notice 'r4 = %', r4;
+end$$;
+
+-- fields of named-type vars read as null if uninitialized
+do $$
+declare r1 two_int8s;
+begin
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.q1 = %', r1.q1;
+ raise notice 'r1.q2 = %', r1.q2;
+ raise notice 'r1 = %', r1;
+end$$;
+
+do $$
+declare r1 two_int8s;
+begin
+ raise notice 'r1.q1 = %', r1.q1;
+ raise notice 'r1.q2 = %', r1.q2;
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.nosuchfield = %', r1.nosuchfield;
+end$$;
+
+-- records, not so much
+do $$
+declare r1 record;
+begin
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.f1 = %', r1.f1;
+ raise notice 'r1.f2 = %', r1.f2;
+ raise notice 'r1 = %', r1;
+end$$;
+
+-- but OK if you assign first
+do $$
+declare r1 record;
+begin
+ raise notice 'r1 = %', r1;
+ r1 := row(1,2);
+ raise notice 'r1.f1 = %', r1.f1;
+ raise notice 'r1.f2 = %', r1.f2;
+ raise notice 'r1 = %', r1;
+ raise notice 'r1.nosuchfield = %', r1.nosuchfield;
+end$$;
+
+-- check repeated assignments to composite fields
+create table some_table (id int, data text);
+
+do $$
+declare r some_table;
+begin
+ r := (23, 'skidoo');
+ for i in 1 .. 10 loop
+ r.id := r.id + i;
+ r.data := r.data || ' ' || i;
+ end loop;
+ raise notice 'r = %', r;
+end$$;
+
+-- check behavior of function declared to return "record"
+
+create function returnsrecord(int) returns record language plpgsql as
+$$ begin return row($1,$1+1); end $$;
+
+select returnsrecord(42);
+select * from returnsrecord(42) as r(x int, y int);
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+
+-- same with an intermediate record variable
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ declare r record; begin r := row($1,$1+1); return r; end $$;
+
+select returnsrecord(42);
+select * from returnsrecord(42) as r(x int, y int);
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+
+-- should work the same with a missing column in the actual result value
+create table has_hole(f1 int, f2 int, f3 int);
+alter table has_hole drop column f2;
+
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ begin return row($1,$1+1)::has_hole; end $$;
+
+select returnsrecord(42);
+select * from returnsrecord(42) as r(x int, y int);
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+
+-- same with an intermediate record variable
+create or replace function returnsrecord(int) returns record language plpgsql as
+$$ declare r record; begin r := row($1,$1+1)::has_hole; return r; end $$;
+
+select returnsrecord(42);
+select * from returnsrecord(42) as r(x int, y int);
+select * from returnsrecord(42) as r(x int, y int, z int); -- fail
+select * from returnsrecord(42) as r(x int, y bigint); -- fail
+
+-- check access to a field of an argument declared "record"
+create function getf1(x record) returns int language plpgsql as
+$$ begin return x.f1; end $$;
+select getf1(1);
+select getf1(row(1,2));
+select getf1(row(1,2)::two_int4s);
+select getf1(row('foo',123,456)::more_int4s);
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select getf1(row(1,2)::two_int8s);
+\set SHOW_CONTEXT errors
+select getf1(row(1,2));
+
+-- this seemingly-equivalent case behaves a bit differently,
+-- because the core parser's handling of $N symbols is simplistic
+create function getf2(record) returns int language plpgsql as
+$$ begin return $1.f2; end $$;
+select getf2(row(1,2)); -- ideally would work, but does not
+select getf2(row(1,2)::two_int4s);
+select getf2(row('foo',123,456)::more_int4s);
+
+-- check behavior when assignment to FOR-loop variable requires coercion
+do $$
+declare r two_int8s;
+begin
+ for r in select i, i+1 from generate_series(1,4) i
+ loop
+ raise notice 'r = %', r;
+ end loop;
+end$$;
+
+-- check behavior when returning setof composite
+create function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+declare r record;
+ h has_hole;
+begin
+ return next h;
+ r := (1,2);
+ h := (3,4);
+ return next r;
+ return next h;
+ return next row(5,6);
+ return next row(7,8)::has_hole;
+end$$;
+select returnssetofholes();
+
+create or replace function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+declare r record;
+begin
+ return next r; -- fails, not assigned yet
+end$$;
+select returnssetofholes();
+
+create or replace function returnssetofholes() returns setof has_hole language plpgsql as
+$$
+begin
+ return next row(1,2,3); -- fails
+end$$;
+select returnssetofholes();
+
+-- check behavior with changes of a named rowtype
+create table mutable(f1 int, f2 text);
+
+create function sillyaddone(int) returns int language plpgsql as
+$$ declare r mutable; begin r.f1 := $1; return r.f1 + 1; end $$;
+select sillyaddone(42);
+
+-- test for change of type of column f1 should be here someday;
+-- for now see plpgsql_cache test
+
+alter table mutable drop column f1;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select sillyaddone(42); -- fail
+\set SHOW_CONTEXT errors
+
+create function getf3(x mutable) returns int language plpgsql as
+$$ begin return x.f3; end $$;
+select getf3(null::mutable); -- doesn't work yet
+alter table mutable add column f3 int;
+select getf3(null::mutable); -- now it works
+alter table mutable drop column f3;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select getf3(null::mutable); -- fails again
+\set SHOW_CONTEXT errors
+
+-- check behavior with creating/dropping a named rowtype
+set check_function_bodies = off; -- else reference to nonexistent type fails
+
+create function sillyaddtwo(int) returns int language plpgsql as
+$$ declare r mutable2; begin r.f1 := $1; return r.f1 + 2; end $$;
+
+reset check_function_bodies;
+
+select sillyaddtwo(42); -- fail
+create table mutable2(f1 int, f2 text);
+select sillyaddtwo(42);
+drop table mutable2;
+-- the context stack is different when debug_discard_caches
+-- is set, so suppress context output
+\set SHOW_CONTEXT never
+select sillyaddtwo(42); -- fail
+\set SHOW_CONTEXT errors
+create table mutable2(f0 text, f1 int, f2 text);
+select sillyaddtwo(42);
+select sillyaddtwo(43);
+
+-- check access to system columns in a record variable
+
+create function sillytrig() returns trigger language plpgsql as
+$$begin
+ raise notice 'old.ctid = %', old.ctid;
+ raise notice 'old.tableoid = %', old.tableoid::regclass;
+ return new;
+end$$;
+
+create trigger mutable_trig before update on mutable for each row
+execute procedure sillytrig();
+
+insert into mutable values ('foo'), ('bar');
+update mutable set f2 = f2 || ' baz';
+table mutable;
+
+-- check returning a composite datum from a trigger
+
+create or replace function sillytrig() returns trigger language plpgsql as
+$$begin
+ return row(new.*);
+end$$;
+
+update mutable set f2 = f2 || ' baz';
+table mutable;
+
+create or replace function sillytrig() returns trigger language plpgsql as
+$$declare r record;
+begin
+ r := row(new.*);
+ return r;
+end$$;
+
+update mutable set f2 = f2 || ' baz';
+table mutable;
+
+--
+-- Domains of composite
+--
+
+create domain ordered_int8s as two_int8s check((value).q1 <= (value).q2);
+
+create function read_ordered_int8s(p ordered_int8s) returns int8 as $$
+begin return p.q1 + p.q2; end
+$$ language plpgsql;
+
+select read_ordered_int8s(row(1, 2));
+select read_ordered_int8s(row(2, 1)); -- fail
+
+create function build_ordered_int8s(i int8, j int8) returns ordered_int8s as $$
+begin return row(i,j); end
+$$ language plpgsql;
+
+select build_ordered_int8s(1,2);
+select build_ordered_int8s(2,1); -- fail
+
+create function build_ordered_int8s_2(i int8, j int8) returns ordered_int8s as $$
+declare r record; begin r := row(i,j); return r; end
+$$ language plpgsql;
+
+select build_ordered_int8s_2(1,2);
+select build_ordered_int8s_2(2,1); -- fail
+
+create function build_ordered_int8s_3(i int8, j int8) returns ordered_int8s as $$
+declare r two_int8s; begin r := row(i,j); return r; end
+$$ language plpgsql;
+
+select build_ordered_int8s_3(1,2);
+select build_ordered_int8s_3(2,1); -- fail
+
+create function build_ordered_int8s_4(i int8, j int8) returns ordered_int8s as $$
+declare r ordered_int8s; begin r := row(i,j); return r; end
+$$ language plpgsql;
+
+select build_ordered_int8s_4(1,2);
+select build_ordered_int8s_4(2,1); -- fail
+
+create function build_ordered_int8s_a(i int8, j int8) returns ordered_int8s[] as $$
+begin return array[row(i,j), row(i,j+1)]; end
+$$ language plpgsql;
+
+select build_ordered_int8s_a(1,2);
+select build_ordered_int8s_a(2,1); -- fail
+
+-- check field assignment
+do $$
+declare r ordered_int8s;
+begin
+ r.q1 := null;
+ r.q2 := 43;
+ r.q1 := 42;
+ r.q2 := 41; -- fail
+end$$;
+
+-- check whole-row assignment
+do $$
+declare r ordered_int8s;
+begin
+ r := null;
+ r := row(null,null);
+ r := row(1,2);
+ r := row(2,1); -- fail
+end$$;
+
+-- check assignment in for-loop
+do $$
+declare r ordered_int8s;
+begin
+ for r in values (1,2),(3,4),(6,5) loop
+ raise notice 'r = %', r;
+ end loop;
+end$$;
+
+-- check behavior with toastable fields, too
+
+create type two_texts as (f1 text, f2 text);
+create domain ordered_texts as two_texts check((value).f1 <= (value).f2);
+
+create table sometable (id int, a text, b text);
+-- b should be compressed, but in-line
+insert into sometable values (1, 'a', repeat('ffoob',1000));
+-- this b should be out-of-line
+insert into sometable values (2, 'a', repeat('ffoob',100000));
+-- this pair should fail the domain check
+insert into sometable values (3, 'z', repeat('ffoob',100000));
+
+do $$
+declare d ordered_texts;
+begin
+ for d in select a, b from sometable loop
+ raise notice 'succeeded at "%"', d.f1;
+ end loop;
+end$$;
+
+do $$
+declare r record; d ordered_texts;
+begin
+ for r in select * from sometable loop
+ raise notice 'processing row %', r.id;
+ d := row(r.a, r.b);
+ end loop;
+end$$;
+
+do $$
+declare r record; d ordered_texts;
+begin
+ for r in select * from sometable loop
+ raise notice 'processing row %', r.id;
+ d := null;
+ d.f1 := r.a;
+ d.f2 := r.b;
+ end loop;
+end$$;
+
+-- check coercion of a record result to named-composite function output type
+create function compresult(int8) returns two_int8s language plpgsql as
+$$ declare r record; begin r := row($1,$1); return r; end $$;
+
+create table two_int8s_tab (f1 two_int8s);
+insert into two_int8s_tab values (compresult(42));
+-- reconnect so we lose any local knowledge of anonymous record types
+\c -
+table two_int8s_tab;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_simple.sql b/src/pl/plpgsql/src/sql/plpgsql_simple.sql
new file mode 100644
index 0000000..57020d2
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_simple.sql
@@ -0,0 +1,82 @@
+--
+-- Tests for plpgsql's handling of "simple" expressions
+--
+
+-- Check that changes to an inline-able function are handled correctly
+create function simplesql(int) returns int language sql
+as 'select $1';
+
+create function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simplesql(n);
+ if n = 5 then
+ create or replace function simplesql(int) returns int language sql
+ as 'select $1 + 100';
+ end if;
+ end loop;
+ return sum;
+end$$;
+
+select simplecaller();
+
+
+-- Check that changes in search path are dealt with correctly
+create schema simple1;
+
+create function simple1.simpletarget(int) returns int language plpgsql
+as $$begin return $1; end$$;
+
+create function simpletarget(int) returns int language plpgsql
+as $$begin return $1 + 100; end$$;
+
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare
+ sum int := 0;
+begin
+ for n in 1..10 loop
+ sum := sum + simpletarget(n);
+ if n = 5 then
+ set local search_path = 'simple1';
+ end if;
+ end loop;
+ return sum;
+end$$;
+
+select simplecaller();
+
+-- try it with non-volatile functions, too
+alter function simple1.simpletarget(int) immutable;
+alter function simpletarget(int) immutable;
+
+select simplecaller();
+
+-- make sure flushing local caches changes nothing
+\c -
+
+select simplecaller();
+
+
+-- Check case where first attempt to determine if it's simple fails
+
+create function simplesql() returns int language sql
+as $$select 1 / 0$$;
+
+create or replace function simplecaller() returns int language plpgsql
+as $$
+declare x int;
+begin
+ select simplesql() into x;
+ return x;
+end$$;
+
+select simplecaller(); -- division by zero occurs during simple-expr check
+
+create or replace function simplesql() returns int language sql
+as $$select 2 + 2$$;
+
+select simplecaller();
diff --git a/src/pl/plpgsql/src/sql/plpgsql_transaction.sql b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
new file mode 100644
index 0000000..8d76d00
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_transaction.sql
@@ -0,0 +1,637 @@
+CREATE TABLE test1 (a int, b text);
+
+
+CREATE PROCEDURE transaction_test1(x int, y text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ FOR i IN 0..x LOOP
+ INSERT INTO test1 (a, b) VALUES (i, y);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END
+$$;
+
+CALL transaction_test1(9, 'foo');
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+DO
+LANGUAGE plpgsql
+$$
+BEGIN
+ FOR i IN 0..9 LOOP
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END
+$$;
+
+SELECT * FROM test1;
+
+
+-- transaction commands not allowed when called in transaction block
+START TRANSACTION;
+CALL transaction_test1(9, 'error');
+COMMIT;
+
+START TRANSACTION;
+DO LANGUAGE plpgsql $$ BEGIN COMMIT; END $$;
+COMMIT;
+
+
+TRUNCATE test1;
+
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ FOR i IN 0..9 LOOP
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+ RETURN 1;
+END
+$$;
+
+SELECT transaction_test2();
+
+SELECT * FROM test1;
+
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL transaction_test1(9, 'error');
+ RETURN 1;
+END;
+$$;
+
+SELECT transaction_test3();
+
+SELECT * FROM test1;
+
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ EXECUTE 'DO LANGUAGE plpgsql $x$ BEGIN COMMIT; END $x$';
+ RETURN 1;
+END;
+$$;
+
+SELECT transaction_test4();
+
+
+-- proconfig settings currently disallow transaction statements
+CREATE PROCEDURE transaction_test5()
+LANGUAGE plpgsql
+SET work_mem = 555
+AS $$
+BEGIN
+ COMMIT;
+END;
+$$;
+
+CALL transaction_test5();
+
+
+-- SECURITY DEFINER currently disallow transaction statements
+CREATE PROCEDURE transaction_test5b()
+LANGUAGE plpgsql
+SECURITY DEFINER
+AS $$
+BEGIN
+ COMMIT;
+END;
+$$;
+
+CALL transaction_test5b();
+
+
+TRUNCATE test1;
+
+-- nested procedure calls
+CREATE PROCEDURE transaction_test6(c text)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ CALL transaction_test1(9, c);
+END;
+$$;
+
+CALL transaction_test6('bar');
+
+SELECT * FROM test1;
+
+TRUNCATE test1;
+
+CREATE PROCEDURE transaction_test7()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ DO 'BEGIN CALL transaction_test1(9, $x$baz$x$); END;';
+END;
+$$;
+
+CALL transaction_test7();
+
+SELECT * FROM test1;
+
+CREATE PROCEDURE transaction_test8()
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ EXECUTE 'CALL transaction_test1(10, $x$baz$x$)';
+END;
+$$;
+
+CALL transaction_test8();
+
+
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ COMMIT;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (12/(r.x-2));
+ COMMIT;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ ROLLBACK;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN SELECT * FROM test2 ORDER BY x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ IF r.x % 2 = 0 THEN
+ COMMIT;
+ ELSE
+ ROLLBACK;
+ END IF;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ r RECORD;
+BEGIN
+ FOR r IN UPDATE test2 SET x = x * 2 RETURNING x LOOP
+ INSERT INTO test1 (a) VALUES (r.x);
+ ROLLBACK;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+SELECT * FROM test2;
+
+SELECT * FROM pg_cursors;
+
+
+-- interaction of FOR UPDATE cursor with subsequent updates (bug #17050)
+TRUNCATE test1;
+
+INSERT INTO test1 VALUES (1,'one'), (2,'two'), (3,'three');
+
+DO LANGUAGE plpgsql $$
+DECLARE
+ l_cur CURSOR FOR SELECT a FROM test1 ORDER BY 1 FOR UPDATE;
+BEGIN
+ FOR r IN l_cur LOOP
+ UPDATE test1 SET b = b || ' ' || b WHERE a = r.a;
+ COMMIT;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- like bug #17050, but with implicit cursor
+TRUNCATE test1;
+
+INSERT INTO test1 VALUES (1,'one'), (2,'two'), (3,'three');
+
+DO LANGUAGE plpgsql $$
+DECLARE r RECORD;
+BEGIN
+ FOR r IN SELECT a FROM test1 FOR UPDATE LOOP
+ UPDATE test1 SET b = b || ' ' || b WHERE a = r.a;
+ COMMIT;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- commit inside block with exception handler
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ BEGIN
+ INSERT INTO test1 (a) VALUES (1);
+ COMMIT;
+ INSERT INTO test1 (a) VALUES (1/0);
+ COMMIT;
+ EXCEPTION
+ WHEN division_by_zero THEN
+ RAISE NOTICE 'caught division_by_zero';
+ END;
+END;
+$$;
+
+SELECT * FROM test1;
+
+
+-- rollback inside block with exception handler
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ BEGIN
+ INSERT INTO test1 (a) VALUES (1);
+ ROLLBACK;
+ INSERT INTO test1 (a) VALUES (1/0);
+ ROLLBACK;
+ EXCEPTION
+ WHEN division_by_zero THEN
+ RAISE NOTICE 'caught division_by_zero';
+ END;
+END;
+$$;
+
+SELECT * FROM test1;
+
+
+-- test commit/rollback inside exception handler, too
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ FOR i IN 1..10 LOOP
+ BEGIN
+ INSERT INTO test1 VALUES (i, 'good');
+ INSERT INTO test1 VALUES (i/0, 'bad');
+ EXCEPTION
+ WHEN division_by_zero THEN
+ INSERT INTO test1 VALUES (i, 'exception');
+ IF (i % 3) > 0 THEN COMMIT; ELSE ROLLBACK; END IF;
+ END;
+ END LOOP;
+END;
+$$;
+
+SELECT * FROM test1;
+
+
+-- detoast result of simple expression after commit
+CREATE TEMP TABLE test4(f1 text);
+ALTER TABLE test4 ALTER COLUMN f1 SET STORAGE EXTERNAL; -- disable compression
+INSERT INTO test4 SELECT repeat('xyzzy', 2000);
+
+-- immutable mark is a bit of a lie, but it serves to make call a simple expr
+-- that will return a still-toasted value
+CREATE FUNCTION data_source(i int) RETURNS TEXT LANGUAGE sql
+AS 'select f1 from test4' IMMUTABLE;
+
+DO $$
+declare x text;
+begin
+ for i in 1..3 loop
+ x := data_source(i);
+ commit;
+ end loop;
+ raise notice 'length(x) = %', length(x);
+end $$;
+
+
+-- operations on composite types vs. internal transactions
+DO LANGUAGE plpgsql $$
+declare
+ c test1 := row(42, 'hello');
+ r bool;
+begin
+ for i in 1..3 loop
+ r := c is not null;
+ raise notice 'r = %', r;
+ commit;
+ end loop;
+ for i in 1..3 loop
+ r := c is null;
+ raise notice 'r = %', r;
+ rollback;
+ end loop;
+end
+$$;
+
+
+-- COMMIT failures
+DO LANGUAGE plpgsql $$
+BEGIN
+ CREATE TABLE test3 (y int UNIQUE DEFERRABLE INITIALLY DEFERRED);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (1);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (1);
+ INSERT INTO test3 (y) VALUES (2);
+ COMMIT;
+ INSERT INTO test3 (y) VALUES (3); -- won't get here
+END;
+$$;
+
+SELECT * FROM test3;
+
+-- failure while trying to persist a cursor across a transaction (bug #15703)
+CREATE PROCEDURE cursor_fail_during_commit()
+ LANGUAGE plpgsql
+AS $$
+ DECLARE id int;
+ BEGIN
+ FOR id IN SELECT 1/(x-1000) FROM generate_series(1,1000) x LOOP
+ INSERT INTO test1 VALUES(id);
+ COMMIT;
+ END LOOP;
+ END;
+$$;
+
+TRUNCATE test1;
+
+CALL cursor_fail_during_commit();
+
+-- note that error occurs during first COMMIT, hence nothing is in test1
+SELECT count(*) FROM test1;
+
+CREATE PROCEDURE cursor_fail_during_rollback()
+ LANGUAGE plpgsql
+AS $$
+ DECLARE id int;
+ BEGIN
+ FOR id IN SELECT 1/(x-1000) FROM generate_series(1,1000) x LOOP
+ INSERT INTO test1 VALUES(id);
+ ROLLBACK;
+ END LOOP;
+ END;
+$$;
+
+TRUNCATE test1;
+
+CALL cursor_fail_during_rollback();
+
+SELECT count(*) FROM test1;
+
+
+-- SET TRANSACTION
+DO LANGUAGE plpgsql $$
+BEGIN
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ RESET TRANSACTION ISOLATION LEVEL;
+ PERFORM 1;
+ RAISE INFO '%', current_setting('transaction_isolation');
+ COMMIT;
+END;
+$$;
+
+-- error cases
+DO LANGUAGE plpgsql $$
+BEGIN
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+END;
+$$;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ SAVEPOINT foo;
+END;
+$$;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ EXECUTE 'COMMIT';
+END;
+$$;
+
+
+-- snapshot handling test
+TRUNCATE test2;
+
+CREATE PROCEDURE transaction_test9()
+LANGUAGE SQL
+AS $$
+INSERT INTO test2 VALUES (42);
+$$;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ ROLLBACK;
+ CALL transaction_test9();
+END
+$$;
+
+SELECT * FROM test2;
+
+
+-- another snapshot handling case: argument expressions of a CALL need
+-- to be evaluated with an up-to-date snapshot
+CREATE FUNCTION report_count() RETURNS int
+STABLE LANGUAGE sql
+AS $$ SELECT COUNT(*) FROM test2 $$;
+
+CREATE PROCEDURE transaction_test9b(cnt int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ RAISE NOTICE 'count = %', cnt;
+END
+$$;
+
+DO $$
+BEGIN
+ CALL transaction_test9b(report_count());
+ INSERT INTO test2 VALUES(43);
+ CALL transaction_test9b(report_count());
+END
+$$;
+
+
+-- Test transaction in procedure with output parameters. This uses a
+-- different portal strategy and different code paths in pquery.c.
+CREATE PROCEDURE transaction_test10a(INOUT x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ x := x + 1;
+ COMMIT;
+END;
+$$;
+
+CALL transaction_test10a(10);
+
+CREATE PROCEDURE transaction_test10b(INOUT x int)
+LANGUAGE plpgsql
+AS $$
+BEGIN
+ x := x - 1;
+ ROLLBACK;
+END;
+$$;
+
+CALL transaction_test10b(10);
+
+
+-- transaction timestamp vs. statement timestamp
+CREATE PROCEDURE transaction_test11()
+LANGUAGE plpgsql
+AS $$
+DECLARE
+ s1 timestamp with time zone;
+ s2 timestamp with time zone;
+ s3 timestamp with time zone;
+ t1 timestamp with time zone;
+ t2 timestamp with time zone;
+ t3 timestamp with time zone;
+BEGIN
+ s1 := statement_timestamp();
+ t1 := transaction_timestamp();
+ ASSERT s1 = t1;
+ PERFORM pg_sleep(0.001);
+ COMMIT;
+ s2 := statement_timestamp();
+ t2 := transaction_timestamp();
+ ASSERT s2 = s1;
+ ASSERT t2 > t1;
+ PERFORM pg_sleep(0.001);
+ ROLLBACK;
+ s3 := statement_timestamp();
+ t3 := transaction_timestamp();
+ ASSERT s3 = s1;
+ ASSERT t3 > t2;
+END;
+$$;
+
+CALL transaction_test11();
+
+
+-- transaction chain
+
+TRUNCATE test1;
+
+DO LANGUAGE plpgsql $$
+BEGIN
+ ROLLBACK;
+ SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
+ FOR i IN 0..3 LOOP
+ RAISE INFO 'transaction_isolation = %', current_setting('transaction_isolation');
+ INSERT INTO test1 (a) VALUES (i);
+ IF i % 2 = 0 THEN
+ COMMIT AND CHAIN;
+ ELSE
+ ROLLBACK AND CHAIN;
+ END IF;
+ END LOOP;
+END
+$$;
+
+SELECT * FROM test1;
+
+
+DROP TABLE test1;
+DROP TABLE test2;
+DROP TABLE test3;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_trap.sql b/src/pl/plpgsql/src/sql/plpgsql_trap.sql
new file mode 100644
index 0000000..c6c1ad8
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_trap.sql
@@ -0,0 +1,175 @@
+--
+-- Test error trapping
+--
+
+create function trap_zero_divide(int) returns int as $$
+declare x int;
+ sx smallint;
+begin
+ begin -- start a subtransaction
+ raise notice 'should see this';
+ x := 100 / $1;
+ raise notice 'should see this only if % <> 0', $1;
+ sx := $1;
+ raise notice 'should see this only if % fits in smallint', $1;
+ if $1 < 0 then
+ raise exception '% is less than zero', $1;
+ end if;
+ exception
+ when division_by_zero then
+ raise notice 'caught division_by_zero';
+ x := -1;
+ when NUMERIC_VALUE_OUT_OF_RANGE then
+ raise notice 'caught numeric_value_out_of_range';
+ x := -2;
+ end;
+ return x;
+end$$ language plpgsql;
+
+select trap_zero_divide(50);
+select trap_zero_divide(0);
+select trap_zero_divide(100000);
+select trap_zero_divide(-100);
+
+create table match_source as
+ select x as id, x*10 as data, x/10 as ten from generate_series(1,100) x;
+
+create function trap_matching_test(int) returns int as $$
+declare x int;
+ sx smallint;
+ y int;
+begin
+ begin -- start a subtransaction
+ x := 100 / $1;
+ sx := $1;
+ select into y data from match_source where id =
+ (select id from match_source b where ten = $1);
+ exception
+ when data_exception then -- category match
+ raise notice 'caught data_exception';
+ x := -1;
+ when NUMERIC_VALUE_OUT_OF_RANGE OR CARDINALITY_VIOLATION then
+ raise notice 'caught numeric_value_out_of_range or cardinality_violation';
+ x := -2;
+ end;
+ return x;
+end$$ language plpgsql;
+
+select trap_matching_test(50);
+select trap_matching_test(0);
+select trap_matching_test(100000);
+select trap_matching_test(1);
+
+create temp table foo (f1 int);
+
+create function subxact_rollback_semantics() returns int as $$
+declare x int;
+begin
+ x := 1;
+ insert into foo values(x);
+ begin
+ x := x + 1;
+ insert into foo values(x);
+ raise exception 'inner';
+ exception
+ when others then
+ x := x * 10;
+ end;
+ insert into foo values(x);
+ return x;
+end$$ language plpgsql;
+
+select subxact_rollback_semantics();
+select * from foo;
+drop table foo;
+
+create function trap_timeout() returns void as $$
+begin
+ declare x int;
+ begin
+ -- we assume this will take longer than 1 second:
+ select count(*) into x from generate_series(1, 1000000000000);
+ exception
+ when others then
+ raise notice 'caught others?';
+ when query_canceled then
+ raise notice 'nyeah nyeah, can''t stop me';
+ end;
+ -- Abort transaction to abandon the statement_timeout setting. Otherwise,
+ -- the next top-level statement would be vulnerable to the timeout.
+ raise exception 'end of function';
+end$$ language plpgsql;
+
+begin;
+set statement_timeout to 1000;
+select trap_timeout();
+rollback;
+
+-- Test for pass-by-ref values being stored in proper context
+create function test_variable_storage() returns text as $$
+declare x text;
+begin
+ x := '1234';
+ begin
+ x := x || '5678';
+ -- force error inside subtransaction SPI context
+ perform trap_zero_divide(-100);
+ exception
+ when others then
+ x := x || '9012';
+ end;
+ return x;
+end$$ language plpgsql;
+
+select test_variable_storage();
+
+--
+-- test foreign key error trapping
+--
+
+create temp table root(f1 int primary key);
+
+create temp table leaf(f1 int references root deferrable);
+
+insert into root values(1);
+insert into leaf values(1);
+insert into leaf values(2); -- fails
+
+create function trap_foreign_key(int) returns int as $$
+begin
+ begin -- start a subtransaction
+ insert into leaf values($1);
+ exception
+ when foreign_key_violation then
+ raise notice 'caught foreign_key_violation';
+ return 0;
+ end;
+ return 1;
+end$$ language plpgsql;
+
+create function trap_foreign_key_2() returns int as $$
+begin
+ begin -- start a subtransaction
+ set constraints all immediate;
+ exception
+ when foreign_key_violation then
+ raise notice 'caught foreign_key_violation';
+ return 0;
+ end;
+ return 1;
+end$$ language plpgsql;
+
+select trap_foreign_key(1);
+select trap_foreign_key(2); -- detects FK violation
+
+begin;
+ set constraints all deferred;
+ select trap_foreign_key(2); -- should not detect FK violation
+ savepoint x;
+ set constraints all immediate; -- fails
+ rollback to x;
+ select trap_foreign_key_2(); -- detects FK violation
+commit; -- still fails
+
+drop function trap_foreign_key(int);
+drop function trap_foreign_key_2();
diff --git a/src/pl/plpgsql/src/sql/plpgsql_trigger.sql b/src/pl/plpgsql/src/sql/plpgsql_trigger.sql
new file mode 100644
index 0000000..e04c273
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_trigger.sql
@@ -0,0 +1,24 @@
+-- Simple test to verify accessibility of the OLD and NEW trigger variables
+
+create table testtr (a int, b text);
+
+create function testtr_trigger() returns trigger language plpgsql as
+$$begin
+ raise notice 'tg_op = %', tg_op;
+ raise notice 'old(%) = %', old.a, row(old.*);
+ raise notice 'new(%) = %', new.a, row(new.*);
+ if (tg_op = 'DELETE') then
+ return old;
+ else
+ return new;
+ end if;
+end$$;
+
+create trigger testtr_trigger before insert or delete or update on testtr
+ for each row execute function testtr_trigger();
+
+insert into testtr values (1, 'one'), (2, 'two');
+
+update testtr set a = a + 1;
+
+delete from testtr;
diff --git a/src/pl/plpgsql/src/sql/plpgsql_varprops.sql b/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
new file mode 100644
index 0000000..778119d
--- /dev/null
+++ b/src/pl/plpgsql/src/sql/plpgsql_varprops.sql
@@ -0,0 +1,247 @@
+--
+-- Tests for PL/pgSQL variable properties: CONSTANT, NOT NULL, initializers
+--
+
+create type var_record as (f1 int4, f2 int4);
+create domain int_nn as int not null;
+create domain var_record_nn as var_record not null;
+create domain var_record_colnn as var_record check((value).f2 is not null);
+
+-- CONSTANT
+
+do $$
+declare x constant int := 42;
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x constant int;
+begin
+ x := 42; -- fail
+end$$;
+
+do $$
+declare x constant int; y int;
+begin
+ for x, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+
+do $$
+declare x constant int[];
+begin
+ x[1] := 42; -- fail
+end$$;
+
+do $$
+declare x constant int[]; y int;
+begin
+ for x[1], y in select 1, 2 loop -- fail (currently, unsupported syntax)
+ end loop;
+end$$;
+
+do $$
+declare x constant var_record;
+begin
+ x.f1 := 42; -- fail
+end$$;
+
+do $$
+declare x constant var_record; y int;
+begin
+ for x.f1, y in select 1, 2 loop -- fail
+ end loop;
+end$$;
+
+-- initializer expressions
+
+do $$
+declare x int := sin(0);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int := 1/0; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x bigint[] := array[1,3,5];
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record := row(1,2,3);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record := row(1,2);
+begin
+ raise notice 'x = %', x;
+end$$;
+
+-- NOT NULL
+
+do $$
+declare x int not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int not null := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x int not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x record not null := row(42);
+begin
+ raise notice 'x = %', x;
+ x := row(null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record not null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record not null := row(41,42);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record not null := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+-- Check that variables are reinitialized on block re-entry.
+
+do $$
+begin
+ for i in 1..3 loop
+ declare
+ x int;
+ y int := i;
+ r record;
+ c var_record;
+ begin
+ if i = 1 then
+ x := 42;
+ r := row(i, i+1);
+ c := row(i, i+1);
+ end if;
+ raise notice 'x = %', x;
+ raise notice 'y = %', y;
+ raise notice 'r = %', r;
+ raise notice 'c = %', c;
+ end;
+ end loop;
+end$$;
+
+-- Check enforcement of domain constraints during initialization
+
+do $$
+declare x int_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x int_nn := 42;
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_nn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_nn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_nn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- ok
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_colnn; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := null; -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,null); -- fail
+begin
+ raise notice 'x = %', x;
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := null; -- fail
+end$$;
+
+do $$
+declare x var_record_colnn := row(1,2);
+begin
+ raise notice 'x = %', x;
+ x := row(null,null); -- fail
+end$$;
diff --git a/src/pl/plpython/.gitignore b/src/pl/plpython/.gitignore
new file mode 100644
index 0000000..07bee6a
--- /dev/null
+++ b/src/pl/plpython/.gitignore
@@ -0,0 +1,5 @@
+/spiexceptions.h
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
new file mode 100644
index 0000000..6b1865c
--- /dev/null
+++ b/src/pl/plpython/Makefile
@@ -0,0 +1,156 @@
+# src/pl/plpython/Makefile
+
+subdir = src/pl/plpython
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+
+# On Windows we have to remove -lpython from the link since we are
+# building our own
+ifeq ($(PORTNAME), win32)
+override python_libspec =
+endif
+
+override CPPFLAGS := -I. -I$(srcdir) $(python_includespec) $(CPPFLAGS)
+
+rpathdir = $(python_libdir)
+
+PGFILEDESC = "PL/Python - procedural language"
+
+NAME = plpython$(python_majorversion)
+
+OBJS = \
+ $(WIN32RES) \
+ plpy_cursorobject.o \
+ plpy_elog.o \
+ plpy_exec.o \
+ plpy_main.o \
+ plpy_planobject.o \
+ plpy_plpymodule.o \
+ plpy_procedure.o \
+ plpy_resultobject.o \
+ plpy_spi.o \
+ plpy_subxactobject.o \
+ plpy_typeio.o \
+ plpy_util.o
+
+DATA = $(NAME)u.control $(NAME)u--1.0.sql
+
+# header files to install - it's not clear which of these might be needed
+# so install them all.
+INCS = plpython.h \
+ plpy_cursorobject.h \
+ plpy_elog.h \
+ plpy_exec.h \
+ plpy_main.h \
+ plpy_planobject.h \
+ plpy_plpymodule.h \
+ plpy_procedure.h \
+ plpy_resultobject.h \
+ plpy_spi.h \
+ plpy_subxactobject.h \
+ plpy_typeio.h \
+ plpy_util.h
+
+# Python on win32 ships with import libraries only for Microsoft Visual C++,
+# which are not compatible with mingw gcc. Therefore we need to build a
+# new import library to link with.
+ifeq ($(PORTNAME), win32)
+
+pytverstr=$(subst .,,${python_version})
+PYTHONDLL=$(subst \,/,$(WINDIR))/system32/python${pytverstr}.dll
+
+OBJS += libpython${pytverstr}.a
+
+libpython${pytverstr}.a: python${pytverstr}.def
+ dlltool --dllname python${pytverstr}.dll --def python${pytverstr}.def --output-lib libpython${pytverstr}.a
+
+python${pytverstr}.def:
+ gendef - $(PYTHONDLL) > $@
+
+endif # win32
+
+
+SHLIB_LINK = $(python_libspec) $(python_additional_libs) $(filter -lintl,$(LIBS))
+
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+
+REGRESS = \
+ plpython_schema \
+ plpython_populate \
+ plpython_test \
+ plpython_do \
+ plpython_global \
+ plpython_import \
+ plpython_spi \
+ plpython_newline \
+ plpython_void \
+ plpython_call \
+ plpython_params \
+ plpython_setof \
+ plpython_record \
+ plpython_trigger \
+ plpython_types \
+ plpython_error \
+ plpython_ereport \
+ plpython_unicode \
+ plpython_quote \
+ plpython_composite \
+ plpython_subtransaction \
+ plpython_transaction \
+ plpython_drop
+
+include $(top_srcdir)/src/Makefile.shlib
+
+all: all-lib
+
+# Ensure parallel safety if a build is started in this directory
+$(OBJS): | submake-generated-headers
+
+install: all install-lib install-data
+
+installdirs: installdirs-lib
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
+
+uninstall: uninstall-lib uninstall-data
+
+install-data: installdirs
+ $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(addprefix $(srcdir)/, $(INCS)) '$(DESTDIR)$(includedir_server)'
+
+uninstall-data:
+ rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plpython.h plpy_util.h)
+
+.PHONY: install-data uninstall-data
+
+
+check: submake-pg-regress
+ $(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
+
+installcheck: submake-pg-regress
+ $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS)
+
+
+.PHONY: submake-pg-regress
+submake-pg-regress: | submake-generated-headers
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
+ rm -rf $(pg_regress_clean_files)
+ifeq ($(PORTNAME), win32)
+ rm -f python${pytverstr}.def
+endif
+
+
+# Force this dependency to be known even without dependency info built:
+plpy_plpymodule.o: spiexceptions.h
+
+spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl
+ $(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@
+
+distprep: spiexceptions.h
+
+maintainer-clean: distclean
+ rm -f spiexceptions.h
diff --git a/src/pl/plpython/expected/README b/src/pl/plpython/expected/README
new file mode 100644
index 0000000..388c553
--- /dev/null
+++ b/src/pl/plpython/expected/README
@@ -0,0 +1,3 @@
+Guide to alternative expected files:
+
+plpython_error_5.out Python 3.5 and newer
diff --git a/src/pl/plpython/expected/plpython_call.out b/src/pl/plpython/expected/plpython_call.out
new file mode 100644
index 0000000..4c06900
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_call.out
@@ -0,0 +1,75 @@
+--
+-- Tests for procedures / CALL syntax
+--
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpython3u
+AS $$
+pass
+$$;
+CALL test_proc1();
+-- error: can't return non-None
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpython3u
+AS $$
+return 5
+$$;
+CALL test_proc2();
+ERROR: PL/Python procedure did not return None
+CONTEXT: PL/Python procedure "test_proc2"
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpython3u
+AS $$
+plpy.execute("INSERT INTO test1 VALUES (%s)" % x)
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+-- output arguments
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpython3u
+AS $$
+return [a + '+' + a]
+$$;
+CALL test_proc5('abc');
+ a
+---------
+ abc+abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpython3u
+AS $$
+return (b * a, c * a)
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c
+---+---
+ 6 | 8
+(1 row)
+
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpython3u
+AS $$
+plpy.notice("a: %s" % (a))
+return (a * 2,)
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE: a: 10
+NOTICE: _a: 10, _b: 20
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out
new file mode 100644
index 0000000..bb101e0
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_composite.out
@@ -0,0 +1,594 @@
+CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
+return (1, 2)
+$$ LANGUAGE plpython3u;
+SELECT multiout_simple();
+ multiout_simple
+-----------------
+ (1,2)
+(1 row)
+
+SELECT * FROM multiout_simple();
+ i | j
+---+---
+ 1 | 2
+(1 row)
+
+SELECT i, j + 2 FROM multiout_simple();
+ i | ?column?
+---+----------
+ 1 | 4
+(1 row)
+
+SELECT (multiout_simple()).j + 3;
+ ?column?
+----------
+ 5
+(1 row)
+
+CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$
+return [(1, 2)] * n
+$$ LANGUAGE plpython3u;
+SELECT multiout_simple_setof();
+ multiout_simple_setof
+-----------------------
+ (1,2)
+(1 row)
+
+SELECT * FROM multiout_simple_setof();
+ column1 | column2
+---------+---------
+ 1 | 2
+(1 row)
+
+SELECT * FROM multiout_simple_setof(3);
+ column1 | column2
+---------+---------
+ 1 | 2
+ 1 | 2
+ 1 | 2
+(3 rows)
+
+CREATE FUNCTION multiout_record_as(typ text,
+ first text, OUT first text,
+ second integer, OUT second integer,
+ retnull boolean) RETURNS record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+elif typ == 'str':
+ return "('%s',%r)" % (first, second)
+$$ LANGUAGE plpython3u;
+SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f');
+ first | second
+-------+--------
+ foo | 1
+(1 row)
+
+SELECT multiout_record_as('dict', 'foo', 1, 'f');
+ multiout_record_as
+--------------------
+ (foo,1)
+(1 row)
+
+SELECT * FROM multiout_record_as('dict', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('dict', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM multiout_record_as('dict', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM multiout_record_as('dict', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM multiout_record_as('dict', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('tuple', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('tuple', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM multiout_record_as('tuple', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM multiout_record_as('tuple', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM multiout_record_as('tuple', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('list', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('list', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM multiout_record_as('list', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM multiout_record_as('list', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM multiout_record_as('list', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM multiout_record_as('str', 'one', 1, false);
+ first | second
+-------+--------
+ 'one' | 1
+(1 row)
+
+SELECT * FROM multiout_record_as('str', 'one', 2, false);
+ first | second
+-------+--------
+ 'one' | 2
+(1 row)
+
+SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s);
+ f | s | snull
+-----+---+-------
+ xxx | | t
+(1 row)
+
+SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s);
+ f | s | fnull | snull
+---+---+-------+-------
+ | | t | t
+(1 row)
+
+SELECT * FROM multiout_record_as('obj', NULL, 10, 'f');
+ first | second
+-------+--------
+ | 10
+(1 row)
+
+CREATE FUNCTION multiout_setof(n integer,
+ OUT power_of_2 integer,
+ OUT length integer) RETURNS SETOF record AS $$
+for i in range(n):
+ power = 2 ** i
+ length = plpy.execute("select length('%d')" % power)[0]['length']
+ yield power, length
+$$ LANGUAGE plpython3u;
+SELECT * FROM multiout_setof(3);
+ power_of_2 | length
+------------+--------
+ 1 | 1
+ 2 | 1
+ 4 | 1
+(3 rows)
+
+SELECT multiout_setof(5);
+ multiout_setof
+----------------
+ (1,1)
+ (2,1)
+ (4,1)
+ (8,1)
+ (16,2)
+(5 rows)
+
+CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$
+return [{'x': 4, 'y' :'four'},
+ {'x': 7, 'y' :'seven'},
+ {'x': 0, 'y' :'zero'}]
+$$ LANGUAGE plpython3u;
+SELECT * FROM multiout_return_table();
+ x | y
+---+-------
+ 4 | four
+ 7 | seven
+ 0 | zero
+(3 rows)
+
+CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$
+yield [[1], 'a']
+yield [[1,2], 'b']
+yield [[1,2,3], None]
+$$ LANGUAGE plpython3u;
+SELECT * FROM multiout_array();
+ column1 | column2
+---------+---------
+ {1} | a
+ {1,2} | b
+ {1,2,3} |
+(3 rows)
+
+CREATE FUNCTION singleout_composite(OUT type_record) AS $$
+return {'first': 1, 'second': 2}
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$
+return [{'first': 1, 'second': 2},
+ {'first': 3, 'second': 4 }]
+$$ LANGUAGE plpython3u;
+SELECT * FROM singleout_composite();
+ first | second
+-------+--------
+ 1 | 2
+(1 row)
+
+SELECT * FROM multiout_composite();
+ first | second
+-------+--------
+ 1 | 2
+ 3 | 4
+(2 rows)
+
+-- composite OUT parameters in functions returning RECORD not supported yet
+CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$
+return (n, (n * 2, n * 3))
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$
+if returnnull:
+ d = None
+elif typ == 'dict':
+ d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'}
+elif typ == 'tuple':
+ d = (n * 2, n * 3)
+elif typ == 'list':
+ d = [ n * 2, n * 3 ]
+elif typ == 'obj':
+ class d: pass
+ d.first = n * 2
+ d.second = n * 3
+elif typ == 'str':
+ d = "(%r,%r)" % (n * 2, n * 3)
+for i in range(n):
+ yield (i, d)
+$$ LANGUAGE plpython3u;
+SELECT * FROM multiout_composite(2);
+ n | column2
+---+---------
+ 2 | (4,6)
+(1 row)
+
+SELECT * FROM multiout_table_type_setof('dict', 'f', 3);
+ n | column2
+---+---------
+ 0 | (6,9)
+ 1 | (6,9)
+ 2 | (6,9)
+(3 rows)
+
+SELECT * FROM multiout_table_type_setof('dict', 'f', 7);
+ n | column2
+---+---------
+ 0 | (14,21)
+ 1 | (14,21)
+ 2 | (14,21)
+ 3 | (14,21)
+ 4 | (14,21)
+ 5 | (14,21)
+ 6 | (14,21)
+(7 rows)
+
+SELECT * FROM multiout_table_type_setof('tuple', 'f', 2);
+ n | column2
+---+---------
+ 0 | (4,6)
+ 1 | (4,6)
+(2 rows)
+
+SELECT * FROM multiout_table_type_setof('tuple', 'f', 3);
+ n | column2
+---+---------
+ 0 | (6,9)
+ 1 | (6,9)
+ 2 | (6,9)
+(3 rows)
+
+SELECT * FROM multiout_table_type_setof('list', 'f', 2);
+ n | column2
+---+---------
+ 0 | (4,6)
+ 1 | (4,6)
+(2 rows)
+
+SELECT * FROM multiout_table_type_setof('list', 'f', 3);
+ n | column2
+---+---------
+ 0 | (6,9)
+ 1 | (6,9)
+ 2 | (6,9)
+(3 rows)
+
+SELECT * FROM multiout_table_type_setof('obj', 'f', 4);
+ n | column2
+---+---------
+ 0 | (8,12)
+ 1 | (8,12)
+ 2 | (8,12)
+ 3 | (8,12)
+(4 rows)
+
+SELECT * FROM multiout_table_type_setof('obj', 'f', 5);
+ n | column2
+---+---------
+ 0 | (10,15)
+ 1 | (10,15)
+ 2 | (10,15)
+ 3 | (10,15)
+ 4 | (10,15)
+(5 rows)
+
+SELECT * FROM multiout_table_type_setof('str', 'f', 6);
+ n | column2
+---+---------
+ 0 | (12,18)
+ 1 | (12,18)
+ 2 | (12,18)
+ 3 | (12,18)
+ 4 | (12,18)
+ 5 | (12,18)
+(6 rows)
+
+SELECT * FROM multiout_table_type_setof('str', 'f', 7);
+ n | column2
+---+---------
+ 0 | (14,21)
+ 1 | (14,21)
+ 2 | (14,21)
+ 3 | (14,21)
+ 4 | (14,21)
+ 5 | (14,21)
+ 6 | (14,21)
+(7 rows)
+
+SELECT * FROM multiout_table_type_setof('dict', 't', 3);
+ n | column2
+---+---------
+ 0 |
+ 1 |
+ 2 |
+(3 rows)
+
+-- check what happens if a type changes under us
+CREATE TABLE changing (
+ i integer,
+ j integer
+);
+CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$
+return [(1, {'i': 1, 'j': 2}),
+ (1, (3, 4))]
+$$ LANGUAGE plpython3u;
+SELECT * FROM changing_test();
+ n | column2
+---+---------
+ 1 | (1,2)
+ 1 | (3,4)
+(2 rows)
+
+ALTER TABLE changing DROP COLUMN j;
+SELECT * FROM changing_test();
+ERROR: length of returned sequence did not match number of columns in row
+CONTEXT: while creating return value
+PL/Python function "changing_test"
+SELECT * FROM changing_test();
+ERROR: length of returned sequence did not match number of columns in row
+CONTEXT: while creating return value
+PL/Python function "changing_test"
+ALTER TABLE changing ADD COLUMN j integer;
+SELECT * FROM changing_test();
+ n | column2
+---+---------
+ 1 | (1,2)
+ 1 | (3,4)
+(2 rows)
+
+-- tables of composite types
+CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_types_table();
+ tab | typ
+----------------------------+----------------------------
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+ {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"}
+(3 rows)
+
+-- check what happens if the output record descriptor changes
+CREATE FUNCTION return_record(t text) RETURNS record AS $$
+return {'t': t, 'val': 10}
+$$ LANGUAGE plpython3u;
+SELECT * FROM return_record('abc') AS r(t text, val integer);
+ t | val
+-----+-----
+ abc | 10
+(1 row)
+
+SELECT * FROM return_record('abc') AS r(t text, val bigint);
+ t | val
+-----+-----
+ abc | 10
+(1 row)
+
+SELECT * FROM return_record('abc') AS r(t text, val integer);
+ t | val
+-----+-----
+ abc | 10
+(1 row)
+
+SELECT * FROM return_record('abc') AS r(t varchar(30), val integer);
+ t | val
+-----+-----
+ abc | 10
+(1 row)
+
+SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);
+ t | val
+-----+-----
+ abc | 10
+(1 row)
+
+SELECT * FROM return_record('999') AS r(val text, t integer);
+ val | t
+-----+-----
+ 10 | 999
+(1 row)
+
+CREATE FUNCTION return_record_2(t text) RETURNS record AS $$
+return {'v1':1,'v2':2,t:3}
+$$ LANGUAGE plpython3u;
+SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int);
+ v3 | v2 | v1
+----+----+----
+ 3 | 2 | 1
+(1 row)
+
+SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int);
+ v2 | v3 | v1
+----+----+----
+ 2 | 3 | 1
+(1 row)
+
+SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
+ v1 | v4 | v2
+----+----+----
+ 1 | 3 | 2
+(1 row)
+
+SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
+ v1 | v4 | v2
+----+----+----
+ 1 | 3 | 2
+(1 row)
+
+-- error
+SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int);
+ERROR: key "v3" not found in mapping
+HINT: To return null in a column, add the value None to the mapping with the key named after the column.
+CONTEXT: while creating return value
+PL/Python function "return_record_2"
+-- works
+SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int);
+ v1 | v3 | v2
+----+----+----
+ 1 | 3 | 2
+(1 row)
+
+SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int);
+ v1 | v2 | v3
+----+----+----
+ 1 | 2 | 3
+(1 row)
+
+-- multi-dimensional array of composite types.
+CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$
+ return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_list();
+ composite_type_as_list
+------------------------------------------------------------------------------------
+ {{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}}
+(1 row)
+
+-- Starting with PostgreSQL 10, a composite type in an array cannot be
+-- represented as a Python list, because it's ambiguous with multi-dimensional
+-- arrays. So this throws an error now. The error should contain a useful hint
+-- on the issue.
+CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
+ return [['first', 1]];
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_list_broken();
+ERROR: malformed record literal: "first"
+DETAIL: Missing left parenthesis.
+HINT: To return a composite type in an array, return the composite type as a Python tuple, e.g., "[('foo',)]".
+CONTEXT: while creating return value
+PL/Python function "composite_type_as_list_broken"
diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out
new file mode 100644
index 0000000..d131a4c
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_do.out
@@ -0,0 +1,8 @@
+DO $$ plpy.notice("This is plpython3u.") $$ LANGUAGE plpython3u;
+NOTICE: This is plpython3u.
+DO $$ raise Exception("error test") $$ LANGUAGE plpython3u;
+ERROR: Exception: error test
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ raise Exception("error test")
+PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_drop.out b/src/pl/plpython/expected/plpython_drop.out
new file mode 100644
index 0000000..97bb54a
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_drop.out
@@ -0,0 +1,5 @@
+--
+-- For paranoia's sake, don't leave an untrusted language sitting around
+--
+SET client_min_messages = WARNING;
+DROP EXTENSION plpython3u CASCADE;
diff --git a/src/pl/plpython/expected/plpython_ereport.out b/src/pl/plpython/expected/plpython_ereport.out
new file mode 100644
index 0000000..74dcc41
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_ereport.out
@@ -0,0 +1,214 @@
+CREATE FUNCTION elog_test() RETURNS void
+AS $$
+plpy.debug('debug', detail='some detail')
+plpy.log('log', detail='some detail')
+plpy.info('info', detail='some detail')
+plpy.info()
+plpy.info('the question', detail=42);
+plpy.info('This is message text.',
+ detail='This is detail text',
+ hint='This is hint text.',
+ sqlstate='XX000',
+ schema_name='any info about schema',
+ table_name='any info about table',
+ column_name='any info about column',
+ datatype_name='any info about datatype',
+ constraint_name='any info about constraint')
+plpy.notice('notice', detail='some detail')
+plpy.warning('warning', detail='some detail')
+plpy.error('stop on error', detail='some detail', hint='some hint')
+$$ LANGUAGE plpython3u;
+SELECT elog_test();
+INFO: info
+DETAIL: some detail
+INFO: ()
+INFO: the question
+DETAIL: 42
+INFO: This is message text.
+DETAIL: This is detail text
+HINT: This is hint text.
+NOTICE: notice
+DETAIL: some detail
+WARNING: warning
+DETAIL: some detail
+ERROR: plpy.Error: stop on error
+DETAIL: some detail
+HINT: some hint
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "elog_test", line 18, in <module>
+ plpy.error('stop on error', detail='some detail', hint='some hint')
+PL/Python function "elog_test"
+DO $$ plpy.info('other types', detail=(10, 20)) $$ LANGUAGE plpython3u;
+INFO: other types
+DETAIL: (10, 20)
+DO $$
+import time;
+from datetime import date
+plpy.info('other types', detail=date(2016, 2, 26))
+$$ LANGUAGE plpython3u;
+INFO: other types
+DETAIL: 2016-02-26
+DO $$
+basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+plpy.info('other types', detail=basket)
+$$ LANGUAGE plpython3u;
+INFO: other types
+DETAIL: ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+-- should fail
+DO $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpython3u;
+ERROR: ValueError: invalid SQLSTATE code
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ plpy.info('wrong sqlstate', sqlstate='54444A')
+PL/Python anonymous code block
+DO $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpython3u;
+ERROR: TypeError: 'blabla' is an invalid keyword argument for this function
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ plpy.info('unsupported argument', blabla='fooboo')
+PL/Python anonymous code block
+DO $$ plpy.info('first message', message='second message') $$ LANGUAGE plpython3u;
+ERROR: TypeError: argument 'message' given by name and position
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ plpy.info('first message', message='second message')
+PL/Python anonymous code block
+DO $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpython3u;
+ERROR: TypeError: argument 'message' given by name and position
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 1, in <module>
+ plpy.info('first message', 'second message', message='third message')
+PL/Python anonymous code block
+-- raise exception in python, handle exception in plgsql
+CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
+ _sqlstate text DEFAULT NULL,
+ _schema_name text DEFAULT NULL,
+ _table_name text DEFAULT NULL,
+ _column_name text DEFAULT NULL,
+ _datatype_name text DEFAULT NULL,
+ _constraint_name text DEFAULT NULL)
+RETURNS void AS $$
+kwargs = {
+ "message": _message, "detail": _detail, "hint": _hint,
+ "sqlstate": _sqlstate, "schema_name": _schema_name, "table_name": _table_name,
+ "column_name": _column_name, "datatype_name": _datatype_name,
+ "constraint_name": _constraint_name
+}
+# ignore None values
+plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+$$ LANGUAGE plpython3u;
+SELECT raise_exception('hello', 'world');
+ERROR: plpy.Error: hello
+DETAIL: world
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "raise_exception", line 9, in <module>
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+PL/Python function "raise_exception"
+SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
+ERROR: plpy.Error: message text
+DETAIL: detail text
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "raise_exception", line 9, in <module>
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+PL/Python function "raise_exception"
+SELECT raise_exception(_message => 'message text',
+ _detail => 'detail text',
+ _hint => 'hint text',
+ _sqlstate => 'XX555',
+ _schema_name => 'schema text',
+ _table_name => 'table text',
+ _column_name => 'column text',
+ _datatype_name => 'datatype text',
+ _constraint_name => 'constraint text');
+ERROR: plpy.Error: message text
+DETAIL: detail text
+HINT: hint text
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "raise_exception", line 9, in <module>
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+PL/Python function "raise_exception"
+SELECT raise_exception(_message => 'message text',
+ _hint => 'hint text',
+ _schema_name => 'schema text',
+ _column_name => 'column text',
+ _constraint_name => 'constraint text');
+ERROR: plpy.Error: message text
+HINT: hint text
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "raise_exception", line 9, in <module>
+ plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+PL/Python function "raise_exception"
+DO $$
+DECLARE
+ __message text;
+ __detail text;
+ __hint text;
+ __sqlstate text;
+ __schema_name text;
+ __table_name text;
+ __column_name text;
+ __datatype_name text;
+ __constraint_name text;
+BEGIN
+ BEGIN
+ PERFORM raise_exception(_message => 'message text',
+ _detail => 'detail text',
+ _hint => 'hint text',
+ _sqlstate => 'XX555',
+ _schema_name => 'schema text',
+ _table_name => 'table text',
+ _column_name => 'column text',
+ _datatype_name => 'datatype text',
+ _constraint_name => 'constraint text');
+ EXCEPTION WHEN SQLSTATE 'XX555' THEN
+ GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
+ __detail = PG_EXCEPTION_DETAIL,
+ __hint = PG_EXCEPTION_HINT,
+ __sqlstate = RETURNED_SQLSTATE,
+ __schema_name = SCHEMA_NAME,
+ __table_name = TABLE_NAME,
+ __column_name = COLUMN_NAME,
+ __datatype_name = PG_DATATYPE_NAME,
+ __constraint_name = CONSTRAINT_NAME;
+ RAISE NOTICE 'handled exception'
+ USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
+ 'schema_name:(%s), table_name:(%s), column_name:(%s), datatype_name:(%s), constraint_name:(%s)',
+ __message, __detail, __hint, __sqlstate, __schema_name,
+ __table_name, __column_name, __datatype_name, __constraint_name);
+ END;
+END;
+$$;
+NOTICE: handled exception
+DETAIL: message:(plpy.Error: message text), detail:(detail text), hint: (hint text), sqlstate: (XX555), schema_name:(schema text), table_name:(table text), column_name:(column text), datatype_name:(datatype text), constraint_name:(constraint text)
+DO $$
+try:
+ plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')")
+except Exception as e:
+ plpy.info(e.spidata)
+ raise e
+$$ LANGUAGE plpython3u;
+INFO: (119577128, None, 'some hint', None, 0, None, 'users_tab', None, 'user_type', None)
+ERROR: plpy.SPIError: plpy.Error: my message
+HINT: some hint
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 6, in <module>
+ raise e
+ PL/Python anonymous code block, line 3, in __plpython_inline_block
+ plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')")
+PL/Python anonymous code block
+DO $$
+try:
+ plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type')
+except Exception as e:
+ plpy.info('sqlstate: %s, hint: %s, table_name: %s, datatype_name: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name))
+ raise e
+$$ LANGUAGE plpython3u;
+INFO: sqlstate: XX987, hint: some hint, table_name: users_tab, datatype_name: user_type
+ERROR: plpy.Error: my message
+HINT: some hint
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 6, in <module>
+ raise e
+ PL/Python anonymous code block, line 3, in __plpython_inline_block
+ plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type')
+PL/Python anonymous code block
diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out
new file mode 100644
index 0000000..68722b0
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_error.out
@@ -0,0 +1,460 @@
+-- test error handling, i forgot to restore Warn_restart in
+-- the trigger handler once. the errors and subsequent core dump were
+-- interesting.
+/* Flat out Python syntax error
+ */
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+/* With check_function_bodies = false the function should get defined
+ * and the error reported when called
+ */
+SET check_function_bodies = false;
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+SELECT python_syntax_error();
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+/* Run the function twice to check if the hashtable entry gets cleaned up */
+SELECT python_syntax_error();
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+RESET check_function_bodies;
+/* Flat out syntax error
+ */
+CREATE FUNCTION sql_syntax_error() RETURNS text
+ AS
+'plpy.execute("syntax error")'
+ LANGUAGE plpython3u;
+SELECT sql_syntax_error();
+ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax"
+LINE 1: syntax error
+ ^
+QUERY: syntax error
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_syntax_error", line 1, in <module>
+ plpy.execute("syntax error")
+PL/Python function "sql_syntax_error"
+/* check the handling of uncaught python exceptions
+ */
+CREATE FUNCTION exception_index_invalid(text) RETURNS text
+ AS
+'return args[1]'
+ LANGUAGE plpython3u;
+SELECT exception_index_invalid('test');
+ERROR: IndexError: list index out of range
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "exception_index_invalid", line 1, in <module>
+ return args[1]
+PL/Python function "exception_index_invalid"
+/* check handling of nested exceptions
+ */
+CREATE FUNCTION exception_index_invalid_nested() RETURNS text
+ AS
+'rv = plpy.execute("SELECT test5(''foo'')")
+return rv[0]'
+ LANGUAGE plpython3u;
+SELECT exception_index_invalid_nested();
+ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist
+LINE 1: SELECT test5('foo')
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+QUERY: SELECT test5('foo')
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "exception_index_invalid_nested", line 1, in <module>
+ rv = plpy.execute("SELECT test5('foo')")
+PL/Python function "exception_index_invalid_nested"
+/* a typo
+ */
+CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_uncaught('rick');
+ERROR: spiexceptions.UndefinedObject: type "test" does not exist
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "invalid_type_uncaught", line 3, in <module>
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+PL/Python function "invalid_type_uncaught"
+/* for what it's worth catch the exception generated by
+ * the typo, and return None
+ */
+CREATE FUNCTION invalid_type_caught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.notice(str(ex))
+ return None
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_caught('rick');
+NOTICE: type "test" does not exist
+ invalid_type_caught
+---------------------
+
+(1 row)
+
+/* for what it's worth catch the exception generated by
+ * the typo, and reraise it as a plain error
+ */
+CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.error(str(ex))
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_reraised('rick');
+ERROR: plpy.Error: type "test" does not exist
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "invalid_type_reraised", line 6, in <module>
+ plpy.error(str(ex))
+PL/Python function "invalid_type_reraised"
+/* no typo no messing about
+ */
+CREATE FUNCTION valid_type(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT valid_type('rick');
+ valid_type
+------------
+
+(1 row)
+
+/* error in nested functions to get a traceback
+*/
+CREATE FUNCTION nested_error() RETURNS text
+ AS
+'def fun1():
+ plpy.error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+SELECT nested_error();
+ERROR: plpy.Error: boom
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+PL/Python function "nested_error"
+/* raising plpy.Error is just like calling plpy.error
+*/
+CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+'def fun1():
+ raise plpy.Error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+SELECT nested_error_raise();
+ERROR: plpy.Error: boom
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+PL/Python function "nested_error_raise"
+/* using plpy.warning should not produce a traceback
+*/
+CREATE FUNCTION nested_warning() RETURNS text
+ AS
+'def fun1():
+ plpy.warning("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "you''ve been warned"
+'
+ LANGUAGE plpython3u;
+SELECT nested_warning();
+WARNING: boom
+ nested_warning
+--------------------
+ you've been warned
+(1 row)
+
+/* AttributeError at toplevel used to give segfaults with the traceback
+*/
+CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+$$
+plpy.nonexistent
+$$ LANGUAGE plpython3u;
+SELECT toplevel_attribute_error();
+ERROR: AttributeError: 'module' object has no attribute 'nonexistent'
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+PL/Python function "toplevel_attribute_error"
+/* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+def first():
+ second()
+
+def second():
+ third()
+
+def third():
+ plpy.execute("select sql_error()")
+
+first()
+$$ LANGUAGE plpython3u;
+CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+begin
+ select 1/0;
+end
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+begin
+ select python_traceback();
+end
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+plpy.execute("select sql_error()")
+$$ LANGUAGE plpython3u;
+SELECT python_traceback();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+PL/Python function "python_traceback"
+SELECT sql_error();
+ERROR: division by zero
+CONTEXT: SQL statement "select 1/0"
+PL/pgSQL function sql_error() line 3 at SQL statement
+SELECT python_from_sql_error();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+PL/Python function "python_traceback"
+SQL statement "select python_traceback()"
+PL/pgSQL function python_from_sql_error() line 3 at SQL statement
+SELECT sql_from_python_error();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_from_python_error", line 2, in <module>
+ plpy.execute("select sql_error()")
+PL/Python function "sql_from_python_error"
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation as e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation as e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpython3u;
+SELECT specific_exception(2);
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(NULL);
+NOTICE: Violated the NOT NULL constraint, sqlstate 23502
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(2);
+NOTICE: Violated the UNIQUE constraint, sqlstate 23505
+ specific_exception
+--------------------
+
+(1 row)
+
+/* SPI errors in PL/Python functions should preserve the SQLSTATE value
+ */
+CREATE FUNCTION python_unique_violation() RETURNS void AS $$
+plpy.execute("insert into specific values (1)")
+plpy.execute("insert into specific values (1)")
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
+begin
+ begin
+ perform python_unique_violation();
+ exception when unique_violation then
+ return 'ok';
+ end;
+ return 'not reached';
+end;
+$$ language plpgsql;
+SELECT catch_python_unique_violation();
+ catch_python_unique_violation
+-------------------------------
+ ok
+(1 row)
+
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpython3u;
+SELECT manual_subxact();
+ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "manual_subxact", line 2, in <module>
+ plpy.execute("savepoint save")
+PL/Python function "manual_subxact"
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpython3u;
+SELECT manual_subxact_prepared();
+ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "manual_subxact_prepared", line 4, in <module>
+ plpy.execute(save)
+PL/Python function "manual_subxact_prepared"
+/* raising plpy.spiexception.* from python code should preserve sqlstate
+ */
+CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
+raise plpy.spiexceptions.DivisionByZero()
+$$ LANGUAGE plpython3u;
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception();
+EXCEPTION WHEN division_by_zero THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+/* setting a custom sqlstate should be handled
+ */
+CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
+exc = plpy.spiexceptions.DivisionByZero()
+exc.sqlstate = 'SILLY'
+raise exc
+$$ LANGUAGE plpython3u;
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpython3u;
+\set SHOW_CONTEXT always
+SELECT notice_outerfunc();
+NOTICE: inside DO
+CONTEXT: PL/Python anonymous code block
+SQL statement "DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$"
+PL/Python function "notice_innerfunc"
+SQL statement "SELECT notice_innerfunc()"
+PL/Python function "notice_outerfunc"
+ notice_outerfunc
+------------------
+ 1
+(1 row)
+
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+ plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+ for row in plpy.cursor(plan):
+ yield row['d']
+$$ LANGUAGE plpython3u;
+SELECT python_error_detail();
+ERROR: error fetching next item from iterator
+DETAIL: spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD"
+CONTEXT: Traceback (most recent call last):
+PL/Python function "python_error_detail"
diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out
new file mode 100644
index 0000000..fd9cd73
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_error_5.out
@@ -0,0 +1,460 @@
+-- test error handling, i forgot to restore Warn_restart in
+-- the trigger handler once. the errors and subsequent core dump were
+-- interesting.
+/* Flat out Python syntax error
+ */
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+/* With check_function_bodies = false the function should get defined
+ * and the error reported when called
+ */
+SET check_function_bodies = false;
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+SELECT python_syntax_error();
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+/* Run the function twice to check if the hashtable entry gets cleaned up */
+SELECT python_syntax_error();
+ERROR: could not compile PL/Python function "python_syntax_error"
+DETAIL: SyntaxError: invalid syntax (<string>, line 2)
+RESET check_function_bodies;
+/* Flat out syntax error
+ */
+CREATE FUNCTION sql_syntax_error() RETURNS text
+ AS
+'plpy.execute("syntax error")'
+ LANGUAGE plpython3u;
+SELECT sql_syntax_error();
+ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax"
+LINE 1: syntax error
+ ^
+QUERY: syntax error
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_syntax_error", line 1, in <module>
+ plpy.execute("syntax error")
+PL/Python function "sql_syntax_error"
+/* check the handling of uncaught python exceptions
+ */
+CREATE FUNCTION exception_index_invalid(text) RETURNS text
+ AS
+'return args[1]'
+ LANGUAGE plpython3u;
+SELECT exception_index_invalid('test');
+ERROR: IndexError: list index out of range
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "exception_index_invalid", line 1, in <module>
+ return args[1]
+PL/Python function "exception_index_invalid"
+/* check handling of nested exceptions
+ */
+CREATE FUNCTION exception_index_invalid_nested() RETURNS text
+ AS
+'rv = plpy.execute("SELECT test5(''foo'')")
+return rv[0]'
+ LANGUAGE plpython3u;
+SELECT exception_index_invalid_nested();
+ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist
+LINE 1: SELECT test5('foo')
+ ^
+HINT: No function matches the given name and argument types. You might need to add explicit type casts.
+QUERY: SELECT test5('foo')
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "exception_index_invalid_nested", line 1, in <module>
+ rv = plpy.execute("SELECT test5('foo')")
+PL/Python function "exception_index_invalid_nested"
+/* a typo
+ */
+CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_uncaught('rick');
+ERROR: spiexceptions.UndefinedObject: type "test" does not exist
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "invalid_type_uncaught", line 3, in <module>
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+PL/Python function "invalid_type_uncaught"
+/* for what it's worth catch the exception generated by
+ * the typo, and return None
+ */
+CREATE FUNCTION invalid_type_caught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.notice(str(ex))
+ return None
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_caught('rick');
+NOTICE: type "test" does not exist
+ invalid_type_caught
+---------------------
+
+(1 row)
+
+/* for what it's worth catch the exception generated by
+ * the typo, and reraise it as a plain error
+ */
+CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.error(str(ex))
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT invalid_type_reraised('rick');
+ERROR: plpy.Error: type "test" does not exist
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "invalid_type_reraised", line 6, in <module>
+ plpy.error(str(ex))
+PL/Python function "invalid_type_reraised"
+/* no typo no messing about
+ */
+CREATE FUNCTION valid_type(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+SELECT valid_type('rick');
+ valid_type
+------------
+
+(1 row)
+
+/* error in nested functions to get a traceback
+*/
+CREATE FUNCTION nested_error() RETURNS text
+ AS
+'def fun1():
+ plpy.error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+SELECT nested_error();
+ERROR: plpy.Error: boom
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error", line 2, in fun1
+ plpy.error("boom")
+PL/Python function "nested_error"
+/* raising plpy.Error is just like calling plpy.error
+*/
+CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+'def fun1():
+ raise plpy.Error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+SELECT nested_error_raise();
+ERROR: plpy.Error: boom
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "nested_error_raise", line 10, in <module>
+ fun3()
+ PL/Python function "nested_error_raise", line 8, in fun3
+ fun2()
+ PL/Python function "nested_error_raise", line 5, in fun2
+ fun1()
+ PL/Python function "nested_error_raise", line 2, in fun1
+ raise plpy.Error("boom")
+PL/Python function "nested_error_raise"
+/* using plpy.warning should not produce a traceback
+*/
+CREATE FUNCTION nested_warning() RETURNS text
+ AS
+'def fun1():
+ plpy.warning("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "you''ve been warned"
+'
+ LANGUAGE plpython3u;
+SELECT nested_warning();
+WARNING: boom
+ nested_warning
+--------------------
+ you've been warned
+(1 row)
+
+/* AttributeError at toplevel used to give segfaults with the traceback
+*/
+CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+$$
+plpy.nonexistent
+$$ LANGUAGE plpython3u;
+SELECT toplevel_attribute_error();
+ERROR: AttributeError: module 'plpy' has no attribute 'nonexistent'
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "toplevel_attribute_error", line 2, in <module>
+ plpy.nonexistent
+PL/Python function "toplevel_attribute_error"
+/* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+def first():
+ second()
+
+def second():
+ third()
+
+def third():
+ plpy.execute("select sql_error()")
+
+first()
+$$ LANGUAGE plpython3u;
+CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+begin
+ select 1/0;
+end
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+begin
+ select python_traceback();
+end
+$$ LANGUAGE plpgsql;
+CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+plpy.execute("select sql_error()")
+$$ LANGUAGE plpython3u;
+SELECT python_traceback();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+PL/Python function "python_traceback"
+SELECT sql_error();
+ERROR: division by zero
+CONTEXT: SQL statement "select 1/0"
+PL/pgSQL function sql_error() line 3 at SQL statement
+SELECT python_from_sql_error();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "python_traceback", line 11, in <module>
+ first()
+ PL/Python function "python_traceback", line 3, in first
+ second()
+ PL/Python function "python_traceback", line 6, in second
+ third()
+ PL/Python function "python_traceback", line 9, in third
+ plpy.execute("select sql_error()")
+PL/Python function "python_traceback"
+SQL statement "select python_traceback()"
+PL/pgSQL function python_from_sql_error() line 3 at SQL statement
+SELECT sql_from_python_error();
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "sql_from_python_error", line 2, in <module>
+ plpy.execute("select sql_error()")
+PL/Python function "sql_from_python_error"
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation as e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation as e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpython3u;
+SELECT specific_exception(2);
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(NULL);
+NOTICE: Violated the NOT NULL constraint, sqlstate 23502
+ specific_exception
+--------------------
+
+(1 row)
+
+SELECT specific_exception(2);
+NOTICE: Violated the UNIQUE constraint, sqlstate 23505
+ specific_exception
+--------------------
+
+(1 row)
+
+/* SPI errors in PL/Python functions should preserve the SQLSTATE value
+ */
+CREATE FUNCTION python_unique_violation() RETURNS void AS $$
+plpy.execute("insert into specific values (1)")
+plpy.execute("insert into specific values (1)")
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
+begin
+ begin
+ perform python_unique_violation();
+ exception when unique_violation then
+ return 'ok';
+ end;
+ return 'not reached';
+end;
+$$ language plpgsql;
+SELECT catch_python_unique_violation();
+ catch_python_unique_violation
+-------------------------------
+ ok
+(1 row)
+
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpython3u;
+SELECT manual_subxact();
+ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "manual_subxact", line 2, in <module>
+ plpy.execute("savepoint save")
+PL/Python function "manual_subxact"
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpython3u;
+SELECT manual_subxact_prepared();
+ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "manual_subxact_prepared", line 4, in <module>
+ plpy.execute(save)
+PL/Python function "manual_subxact_prepared"
+/* raising plpy.spiexception.* from python code should preserve sqlstate
+ */
+CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
+raise plpy.spiexceptions.DivisionByZero()
+$$ LANGUAGE plpython3u;
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception();
+EXCEPTION WHEN division_by_zero THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+/* setting a custom sqlstate should be handled
+ */
+CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
+exc = plpy.spiexceptions.DivisionByZero()
+exc.sqlstate = 'SILLY'
+raise exc
+$$ LANGUAGE plpython3u;
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpython3u;
+\set SHOW_CONTEXT always
+SELECT notice_outerfunc();
+NOTICE: inside DO
+CONTEXT: PL/Python anonymous code block
+SQL statement "DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$"
+PL/Python function "notice_innerfunc"
+SQL statement "SELECT notice_innerfunc()"
+PL/Python function "notice_outerfunc"
+ notice_outerfunc
+------------------
+ 1
+(1 row)
+
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+ plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+ for row in plpy.cursor(plan):
+ yield row['d']
+$$ LANGUAGE plpython3u;
+SELECT python_error_detail();
+ERROR: error fetching next item from iterator
+DETAIL: spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD"
+CONTEXT: Traceback (most recent call last):
+PL/Python function "python_error_detail"
diff --git a/src/pl/plpython/expected/plpython_global.out b/src/pl/plpython/expected/plpython_global.out
new file mode 100644
index 0000000..a4cfb14
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_global.out
@@ -0,0 +1,52 @@
+--
+-- check static and global data (SD and GD)
+--
+CREATE FUNCTION global_test_one() returns text
+ AS
+'if "global_test" not in SD:
+ SD["global_test"] = "set by global_test_one"
+if "global_test" not in GD:
+ GD["global_test"] = "set by global_test_one"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+ LANGUAGE plpython3u;
+CREATE FUNCTION global_test_two() returns text
+ AS
+'if "global_test" not in SD:
+ SD["global_test"] = "set by global_test_two"
+if "global_test" not in GD:
+ GD["global_test"] = "set by global_test_two"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+ LANGUAGE plpython3u;
+CREATE FUNCTION static_test() returns int4
+ AS
+'if "call" in SD:
+ SD["call"] = SD["call"] + 1
+else:
+ SD["call"] = 1
+return SD["call"]
+'
+ LANGUAGE plpython3u;
+SELECT static_test();
+ static_test
+-------------
+ 1
+(1 row)
+
+SELECT static_test();
+ static_test
+-------------
+ 2
+(1 row)
+
+SELECT global_test_one();
+ global_test_one
+--------------------------------------------------------
+ SD: set by global_test_one, GD: set by global_test_one
+(1 row)
+
+SELECT global_test_two();
+ global_test_two
+--------------------------------------------------------
+ SD: set by global_test_two, GD: set by global_test_one
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_import.out b/src/pl/plpython/expected/plpython_import.out
new file mode 100644
index 0000000..854e989
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_import.out
@@ -0,0 +1,79 @@
+-- import python modules
+CREATE FUNCTION import_fail() returns text
+ AS
+'try:
+ import foosocket
+except ImportError:
+ return "failed as expected"
+return "succeeded, that wasn''t supposed to happen"'
+ LANGUAGE plpython3u;
+CREATE FUNCTION import_succeed() returns text
+ AS
+'try:
+ import array
+ import bisect
+ import calendar
+ import cmath
+ import errno
+ import math
+ import operator
+ import random
+ import re
+ import string
+ import time
+except Exception as ex:
+ plpy.notice("import failed -- %s" % str(ex))
+ return "failed, that wasn''t supposed to happen"
+return "succeeded, as expected"'
+ LANGUAGE plpython3u;
+CREATE FUNCTION import_test_one(p text) RETURNS text
+ AS
+'try:
+ import hashlib
+ digest = hashlib.sha1(p.encode("ascii"))
+except ImportError:
+ import sha
+ digest = sha.new(p)
+return digest.hexdigest()'
+ LANGUAGE plpython3u;
+CREATE FUNCTION import_test_two(u users) RETURNS text
+ AS
+'plain = u["fname"] + u["lname"]
+try:
+ import hashlib
+ digest = hashlib.sha1(plain.encode("ascii"))
+except ImportError:
+ import sha
+ digest = sha.new(plain);
+return "sha hash of " + plain + " is " + digest.hexdigest()'
+ LANGUAGE plpython3u;
+-- import python modules
+--
+SELECT import_fail();
+ import_fail
+--------------------
+ failed as expected
+(1 row)
+
+SELECT import_succeed();
+ import_succeed
+------------------------
+ succeeded, as expected
+(1 row)
+
+-- test import and simple argument handling
+--
+SELECT import_test_one('sha hash of this string');
+ import_test_one
+------------------------------------------
+ a04e23cb9b1a09cd1051a04a7c571aae0f90346c
+(1 row)
+
+-- test import and tuple argument handling
+--
+select import_test_two(users) from users where fname = 'willem';
+ import_test_two
+-------------------------------------------------------------------
+ sha hash of willemdoe is 3cde6b574953b0ca937b4d76ebc40d534d910759
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_newline.out b/src/pl/plpython/expected/plpython_newline.out
new file mode 100644
index 0000000..2bc1492
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_newline.out
@@ -0,0 +1,30 @@
+--
+-- Universal Newline Support
+--
+CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS
+E'x = 100\ny = 23\nreturn x + y\n'
+LANGUAGE plpython3u;
+CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS
+E'x = 100\ry = 23\rreturn x + y\r'
+LANGUAGE plpython3u;
+CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
+E'x = 100\r\ny = 23\r\nreturn x + y\r\n'
+LANGUAGE plpython3u;
+SELECT newline_lf();
+ newline_lf
+------------
+ 123
+(1 row)
+
+SELECT newline_cr();
+ newline_cr
+------------
+ 123
+(1 row)
+
+SELECT newline_crlf();
+ newline_crlf
+--------------
+ 123
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_params.out b/src/pl/plpython/expected/plpython_params.out
new file mode 100644
index 0000000..d1a36f3
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_params.out
@@ -0,0 +1,64 @@
+--
+-- Test named and nameless parameters
+--
+CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$
+return args[0] + args[1]
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
+assert a0 == args[0]
+assert a1 == args[1]
+return True
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
+assert u == args[0]
+if isinstance(u, dict):
+ # stringify dict the hard way because otherwise the order is implementation-dependent
+ u_keys = list(u.keys())
+ u_keys.sort()
+ s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}'
+else:
+ s = str(u)
+return s
+$$ LANGUAGE plpython3u;
+-- use deliberately wrong parameter names
+CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
+try:
+ assert a1 == args[0]
+ return False
+except NameError as e:
+ assert e.args[0].find("a1") > -1
+ return True
+$$ LANGUAGE plpython3u;
+SELECT test_param_names0(2,7);
+ test_param_names0
+-------------------
+ 9
+(1 row)
+
+SELECT test_param_names1(1,'text');
+ test_param_names1
+-------------------
+ t
+(1 row)
+
+SELECT test_param_names2(users) from users;
+ test_param_names2
+-----------------------------------------------------------------------
+ {'fname': 'jane', 'lname': 'doe', 'userid': 1, 'username': 'j_doe'}
+ {'fname': 'john', 'lname': 'doe', 'userid': 2, 'username': 'johnd'}
+ {'fname': 'willem', 'lname': 'doe', 'userid': 3, 'username': 'w_doe'}
+ {'fname': 'rick', 'lname': 'smith', 'userid': 4, 'username': 'slash'}
+(4 rows)
+
+SELECT test_param_names2(NULL);
+ test_param_names2
+-------------------
+ None
+(1 row)
+
+SELECT test_param_names3(1);
+ test_param_names3
+-------------------
+ t
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_populate.out b/src/pl/plpython/expected/plpython_populate.out
new file mode 100644
index 0000000..4db75b0
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_populate.out
@@ -0,0 +1,22 @@
+INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe');
+INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd');
+INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe');
+INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash');
+-- multi table tests
+--
+INSERT INTO taxonomy (name) VALUES ('HIV I') ;
+INSERT INTO taxonomy (name) VALUES ('HIV II') ;
+INSERT INTO taxonomy (name) VALUES ('HCV') ;
+INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ;
+INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ;
+INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ;
+INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ;
+INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ;
diff --git a/src/pl/plpython/expected/plpython_quote.out b/src/pl/plpython/expected/plpython_quote.out
new file mode 100644
index 0000000..1fbe93d
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_quote.out
@@ -0,0 +1,56 @@
+-- test quoting functions
+CREATE FUNCTION quote(t text, how text) RETURNS text AS $$
+ if how == "literal":
+ return plpy.quote_literal(t)
+ elif how == "nullable":
+ return plpy.quote_nullable(t)
+ elif how == "ident":
+ return plpy.quote_ident(t)
+ else:
+ raise plpy.Error("unrecognized quote type %s" % how)
+$$ LANGUAGE plpython3u;
+SELECT quote(t, 'literal') FROM (VALUES
+ ('abc'),
+ ('a''bc'),
+ ('''abc'''),
+ (''),
+ (''''),
+ ('xyzv')) AS v(t);
+ quote
+-----------
+ 'abc'
+ 'a''bc'
+ '''abc'''
+ ''
+ ''''
+ 'xyzv'
+(6 rows)
+
+SELECT quote(t, 'nullable') FROM (VALUES
+ ('abc'),
+ ('a''bc'),
+ ('''abc'''),
+ (''),
+ (''''),
+ (NULL)) AS v(t);
+ quote
+-----------
+ 'abc'
+ 'a''bc'
+ '''abc'''
+ ''
+ ''''
+ NULL
+(6 rows)
+
+SELECT quote(t, 'ident') FROM (VALUES
+ ('abc'),
+ ('a b c'),
+ ('a " ''abc''')) AS v(t);
+ quote
+--------------
+ abc
+ "a b c"
+ "a "" 'abc'"
+(3 rows)
+
diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out
new file mode 100644
index 0000000..31de198
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_record.out
@@ -0,0 +1,373 @@
+--
+-- Test returning tuples
+--
+CREATE TABLE table_record (
+ first text,
+ second int4
+ ) ;
+CREATE TYPE type_record AS (
+ first text,
+ second int4
+ ) ;
+CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+elif typ == 'str':
+ return "('%s',%r)" % (first, second)
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$
+return first + '_in_to_out';
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_in_out_params_multi(first in text,
+ second out text, third out text) AS $$
+return (first + '_record_in_to_out_1', first + '_record_in_to_out_2');
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_inout_params(first inout text) AS $$
+return first + '_inout';
+$$ LANGUAGE plpython3u;
+-- Test tuple returning functions
+SELECT * FROM test_table_record_as('dict', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('dict', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_table_record_as('dict', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_table_record_as('dict', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_table_record_as('dict', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('tuple', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('tuple', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_table_record_as('tuple', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_table_record_as('tuple', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('list', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('list', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_table_record_as('list', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_table_record_as('list', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_table_record_as('list', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('obj', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_table_record_as('obj', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_table_record_as('obj', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_table_record_as('obj', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_table_record_as('obj', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('dict', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('dict', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_type_record_as('dict', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_type_record_as('dict', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_type_record_as('dict', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('tuple', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('tuple', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_type_record_as('tuple', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_type_record_as('tuple', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('list', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('list', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_type_record_as('list', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_type_record_as('list', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_type_record_as('list', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('obj', null, null, false);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('obj', 'one', null, false);
+ first | second
+-------+--------
+ one |
+(1 row)
+
+SELECT * FROM test_type_record_as('obj', null, 2, false);
+ first | second
+-------+--------
+ | 2
+(1 row)
+
+SELECT * FROM test_type_record_as('obj', 'three', 3, false);
+ first | second
+-------+--------
+ three | 3
+(1 row)
+
+SELECT * FROM test_type_record_as('obj', null, null, true);
+ first | second
+-------+--------
+ |
+(1 row)
+
+SELECT * FROM test_type_record_as('str', 'one', 1, false);
+ first | second
+-------+--------
+ 'one' | 1
+(1 row)
+
+SELECT * FROM test_in_out_params('test_in');
+ second
+-------------------
+ test_in_in_to_out
+(1 row)
+
+SELECT * FROM test_in_out_params_multi('test_in');
+ second | third
+----------------------------+----------------------------
+ test_in_record_in_to_out_1 | test_in_record_in_to_out_2
+(1 row)
+
+SELECT * FROM test_inout_params('test_in');
+ first
+---------------
+ test_in_inout
+(1 row)
+
+-- try changing the return types and call functions again
+ALTER TABLE table_record DROP COLUMN first;
+ALTER TABLE table_record DROP COLUMN second;
+ALTER TABLE table_record ADD COLUMN first text;
+ALTER TABLE table_record ADD COLUMN second int4;
+SELECT * FROM test_table_record_as('obj', 'one', 1, false);
+ first | second
+-------+--------
+ one | 1
+(1 row)
+
+ALTER TYPE type_record DROP ATTRIBUTE first;
+ALTER TYPE type_record DROP ATTRIBUTE second;
+ALTER TYPE type_record ADD ATTRIBUTE first text;
+ALTER TYPE type_record ADD ATTRIBUTE second int4;
+SELECT * FROM test_type_record_as('obj', 'one', 1, false);
+ first | second
+-------+--------
+ one | 1
+(1 row)
+
+-- errors cases
+CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$
+ return { 'first': 'first' }
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_record_error1();
+ERROR: key "second" not found in mapping
+HINT: To return null in a column, add the value None to the mapping with the key named after the column.
+CONTEXT: while creating return value
+PL/Python function "test_type_record_error1"
+CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$
+ return [ 'first' ]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_record_error2();
+ERROR: length of returned sequence did not match number of columns in row
+CONTEXT: while creating return value
+PL/Python function "test_type_record_error2"
+CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
+ class type_record: pass
+ type_record.first = 'first'
+ return type_record
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_record_error3();
+ERROR: attribute "second" does not exist in Python object
+HINT: To return null in a column, let the returned object have an attribute named after column with value None.
+CONTEXT: while creating return value
+PL/Python function "test_type_record_error3"
+CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$
+ return 'foo'
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_record_error4();
+ERROR: malformed record literal: "foo"
+DETAIL: Missing left parenthesis.
+CONTEXT: while creating return value
+PL/Python function "test_type_record_error4"
diff --git a/src/pl/plpython/expected/plpython_schema.out b/src/pl/plpython/expected/plpython_schema.out
new file mode 100644
index 0000000..23d94f6
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_schema.out
@@ -0,0 +1,33 @@
+CREATE TABLE users (
+ fname text not null,
+ lname text not null,
+ username text,
+ userid serial,
+ PRIMARY KEY(lname, fname)
+ ) ;
+CREATE INDEX users_username_idx ON users(username);
+CREATE INDEX users_fname_idx ON users(fname);
+CREATE INDEX users_lname_idx ON users(lname);
+CREATE INDEX users_userid_idx ON users(userid);
+CREATE TABLE taxonomy (
+ id serial primary key,
+ name text unique
+ ) ;
+CREATE TABLE entry (
+ accession text not null primary key,
+ eid serial unique,
+ txid int2 not null references taxonomy(id)
+ ) ;
+CREATE TABLE sequences (
+ eid int4 not null references entry(eid),
+ pid serial primary key,
+ product text not null,
+ sequence text not null,
+ multipart bool default 'false'
+ ) ;
+CREATE INDEX sequences_product_idx ON sequences(product) ;
+CREATE TABLE xsequences (
+ pid int4 not null references sequences(pid),
+ sequence text not null
+ ) ;
+CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
diff --git a/src/pl/plpython/expected/plpython_setof.out b/src/pl/plpython/expected/plpython_setof.out
new file mode 100644
index 0000000..3940940
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_setof.out
@@ -0,0 +1,200 @@
+--
+-- Test returning SETOF
+--
+CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$
+return 37
+$$ LANGUAGE plpython3u;
+SELECT test_setof_error();
+ERROR: returned object cannot be iterated
+DETAIL: PL/Python set-returning functions must return an iterable object.
+CONTEXT: PL/Python function "test_setof_error"
+CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
+return [ content ]*count
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
+t = ()
+for i in range(count):
+ t += ( content, )
+return t
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
+class producer:
+ def __init__ (self, icount, icontent):
+ self.icontent = icontent
+ self.icount = icount
+ def __iter__ (self):
+ return self
+ def __next__ (self):
+ if self.icount == 0:
+ raise StopIteration
+ self.icount -= 1
+ return self.icontent
+return producer(count, content)
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS
+$$
+ for s in ('Hello', 'Brave', 'New', 'World'):
+ plpy.execute('select 1')
+ yield s
+ plpy.execute('select 2')
+$$
+LANGUAGE plpython3u;
+-- Test set returning functions
+SELECT test_setof_as_list(0, 'list');
+ test_setof_as_list
+--------------------
+(0 rows)
+
+SELECT test_setof_as_list(1, 'list');
+ test_setof_as_list
+--------------------
+ list
+(1 row)
+
+SELECT test_setof_as_list(2, 'list');
+ test_setof_as_list
+--------------------
+ list
+ list
+(2 rows)
+
+SELECT test_setof_as_list(2, null);
+ test_setof_as_list
+--------------------
+
+
+(2 rows)
+
+SELECT test_setof_as_tuple(0, 'tuple');
+ test_setof_as_tuple
+---------------------
+(0 rows)
+
+SELECT test_setof_as_tuple(1, 'tuple');
+ test_setof_as_tuple
+---------------------
+ tuple
+(1 row)
+
+SELECT test_setof_as_tuple(2, 'tuple');
+ test_setof_as_tuple
+---------------------
+ tuple
+ tuple
+(2 rows)
+
+SELECT test_setof_as_tuple(2, null);
+ test_setof_as_tuple
+---------------------
+
+
+(2 rows)
+
+SELECT test_setof_as_iterator(0, 'list');
+ test_setof_as_iterator
+------------------------
+(0 rows)
+
+SELECT test_setof_as_iterator(1, 'list');
+ test_setof_as_iterator
+------------------------
+ list
+(1 row)
+
+SELECT test_setof_as_iterator(2, 'list');
+ test_setof_as_iterator
+------------------------
+ list
+ list
+(2 rows)
+
+SELECT test_setof_as_iterator(2, null);
+ test_setof_as_iterator
+------------------------
+
+
+(2 rows)
+
+SELECT test_setof_spi_in_iterator();
+ test_setof_spi_in_iterator
+----------------------------
+ Hello
+ Brave
+ New
+ World
+(4 rows)
+
+-- set-returning function that modifies its parameters
+CREATE OR REPLACE FUNCTION ugly(x int, lim int) RETURNS SETOF int AS $$
+global x
+while x <= lim:
+ yield x
+ x = x + 1
+$$ LANGUAGE plpython3u;
+SELECT ugly(1, 5);
+ ugly
+------
+ 1
+ 2
+ 3
+ 4
+ 5
+(5 rows)
+
+-- interleaved execution of such a function
+SELECT ugly(1,3), ugly(7,8);
+ ugly | ugly
+------+------
+ 1 | 7
+ 2 | 8
+ 3 |
+(3 rows)
+
+-- returns set of named-composite-type tuples
+CREATE OR REPLACE FUNCTION get_user_records()
+RETURNS SETOF users
+AS $$
+ return plpy.execute("SELECT * FROM users ORDER BY username")
+$$ LANGUAGE plpython3u;
+SELECT get_user_records();
+ get_user_records
+----------------------
+ (jane,doe,j_doe,1)
+ (john,doe,johnd,2)
+ (rick,smith,slash,4)
+ (willem,doe,w_doe,3)
+(4 rows)
+
+SELECT * FROM get_user_records();
+ fname | lname | username | userid
+--------+-------+----------+--------
+ jane | doe | j_doe | 1
+ john | doe | johnd | 2
+ rick | smith | slash | 4
+ willem | doe | w_doe | 3
+(4 rows)
+
+-- same, but returning set of RECORD
+CREATE OR REPLACE FUNCTION get_user_records2()
+RETURNS TABLE(fname text, lname text, username text, userid int)
+AS $$
+ return plpy.execute("SELECT * FROM users ORDER BY username")
+$$ LANGUAGE plpython3u;
+SELECT get_user_records2();
+ get_user_records2
+----------------------
+ (jane,doe,j_doe,1)
+ (john,doe,johnd,2)
+ (rick,smith,slash,4)
+ (willem,doe,w_doe,3)
+(4 rows)
+
+SELECT * FROM get_user_records2();
+ fname | lname | username | userid
+--------+-------+----------+--------
+ jane | doe | j_doe | 1
+ john | doe | johnd | 2
+ rick | smith | slash | 4
+ willem | doe | w_doe | 3
+(4 rows)
+
diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out
new file mode 100644
index 0000000..8853e25
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_spi.out
@@ -0,0 +1,466 @@
+--
+-- nested calls
+--
+CREATE FUNCTION nested_call_one(a text) RETURNS text
+ AS
+'q = "SELECT nested_call_two(''%s'')" % a
+r = plpy.execute(q)
+return r[0]'
+ LANGUAGE plpython3u ;
+CREATE FUNCTION nested_call_two(a text) RETURNS text
+ AS
+'q = "SELECT nested_call_three(''%s'')" % a
+r = plpy.execute(q)
+return r[0]'
+ LANGUAGE plpython3u ;
+CREATE FUNCTION nested_call_three(a text) RETURNS text
+ AS
+'return a'
+ LANGUAGE plpython3u ;
+-- some spi stuff
+CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT count(*) FROM users WHERE lname = $1"
+ SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+ rv = plpy.execute(SD["myplan"], [a])
+ return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT count(*) FROM users WHERE lname = $1"
+ SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+ rv = SD["myplan"].execute([a])
+ return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
+ SD["myplan"] = plpy.prepare(q)
+try:
+ rv = plpy.execute(SD["myplan"])
+ if len(rv):
+ return rv[0]["count"]
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+CREATE FUNCTION join_sequences(s sequences) RETURNS text
+ AS
+'if not s["multipart"]:
+ return s["sequence"]
+q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
+rv = plpy.execute(q)
+seq = s["sequence"]
+for r in rv:
+ seq = seq + r["sequence"]
+return seq
+'
+ LANGUAGE plpython3u;
+CREATE FUNCTION spi_recursive_sum(a int) RETURNS int
+ AS
+'r = 0
+if a > 1:
+ r = plpy.execute("SELECT spi_recursive_sum(%d) as a" % (a-1))[0]["a"]
+return a + r
+'
+ LANGUAGE plpython3u;
+--
+-- spi and nested calls
+--
+select nested_call_one('pass this along');
+ nested_call_one
+-----------------------------------------------------------------
+ {'nested_call_two': "{'nested_call_three': 'pass this along'}"}
+(1 row)
+
+select spi_prepared_plan_test_one('doe');
+ spi_prepared_plan_test_one
+----------------------------
+ there are 3 does
+(1 row)
+
+select spi_prepared_plan_test_two('smith');
+ spi_prepared_plan_test_two
+----------------------------
+ there are 1 smiths
+(1 row)
+
+select spi_prepared_plan_test_nested('smith');
+ spi_prepared_plan_test_nested
+-------------------------------
+ there are 1 smiths
+(1 row)
+
+SELECT join_sequences(sequences) FROM sequences;
+ join_sequences
+----------------
+ ABCDEFGHIJKL
+ ABCDEF
+ ABCDEF
+ ABCDEF
+ ABCDEF
+ ABCDEF
+(6 rows)
+
+SELECT join_sequences(sequences) FROM sequences
+ WHERE join_sequences(sequences) ~* '^A';
+ join_sequences
+----------------
+ ABCDEFGHIJKL
+ ABCDEF
+ ABCDEF
+ ABCDEF
+ ABCDEF
+ ABCDEF
+(6 rows)
+
+SELECT join_sequences(sequences) FROM sequences
+ WHERE join_sequences(sequences) ~* '^B';
+ join_sequences
+----------------
+(0 rows)
+
+SELECT spi_recursive_sum(10);
+ spi_recursive_sum
+-------------------
+ 55
+(1 row)
+
+--
+-- plan and result objects
+--
+CREATE FUNCTION result_metadata_test(cmd text) RETURNS int
+AS $$
+plan = plpy.prepare(cmd)
+plpy.info(plan.status()) # not really documented or useful
+result = plpy.execute(plan)
+if result.status() > 0:
+ plpy.info(result.colnames())
+ plpy.info(result.coltypes())
+ plpy.info(result.coltypmods())
+ return result.nrows()
+else:
+ return None
+$$ LANGUAGE plpython3u;
+SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$);
+INFO: True
+INFO: ['foo', 'bar']
+INFO: [23, 25]
+INFO: [-1, -1]
+ result_metadata_test
+----------------------
+ 2
+(1 row)
+
+SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
+INFO: True
+ERROR: plpy.Error: command did not produce a result set
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "result_metadata_test", line 6, in <module>
+ plpy.info(result.colnames())
+PL/Python function "result_metadata_test"
+CREATE FUNCTION result_nrows_test(cmd text) RETURNS int
+AS $$
+result = plpy.execute(cmd)
+return result.nrows()
+$$ LANGUAGE plpython3u;
+SELECT result_nrows_test($$SELECT 1$$);
+ result_nrows_test
+-------------------
+ 1
+(1 row)
+
+SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$);
+ result_nrows_test
+-------------------
+ 0
+(1 row)
+
+SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$);
+ result_nrows_test
+-------------------
+ 2
+(1 row)
+
+SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$);
+ result_nrows_test
+-------------------
+ 1
+(1 row)
+
+CREATE FUNCTION result_len_test(cmd text) RETURNS int
+AS $$
+result = plpy.execute(cmd)
+return len(result)
+$$ LANGUAGE plpython3u;
+SELECT result_len_test($$SELECT 1$$);
+ result_len_test
+-----------------
+ 1
+(1 row)
+
+SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$);
+ result_len_test
+-----------------
+ 0
+(1 row)
+
+SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$);
+ result_len_test
+-----------------
+ 0
+(1 row)
+
+SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
+ result_len_test
+-----------------
+ 0
+(1 row)
+
+CREATE FUNCTION result_subscript_test() RETURNS void
+AS $$
+result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 "
+ "UNION ALL SELECT 3 UNION ALL SELECT 4")
+
+plpy.info(result[1]['c'])
+plpy.info(result[-1]['c'])
+
+plpy.info([item['c'] for item in result[1:3]])
+plpy.info([item['c'] for item in result[::2]])
+
+result[-1] = {'c': 1000}
+result[:2] = [{'c': 10}, {'c': 100}]
+plpy.info([item['c'] for item in result[:]])
+
+# raises TypeError, catch so further tests could be added
+try:
+ plpy.info(result['foo'])
+except TypeError:
+ pass
+else:
+ assert False, "TypeError not raised"
+
+$$ LANGUAGE plpython3u;
+SELECT result_subscript_test();
+INFO: 2
+INFO: 4
+INFO: [2, 3]
+INFO: [1, 3]
+INFO: [10, 100, 3, 1000]
+ result_subscript_test
+-----------------------
+
+(1 row)
+
+CREATE FUNCTION result_empty_test() RETURNS void
+AS $$
+result = plpy.execute("select 1 where false")
+
+plpy.info(result[:])
+
+$$ LANGUAGE plpython3u;
+SELECT result_empty_test();
+INFO: []
+ result_empty_test
+-------------------
+
+(1 row)
+
+CREATE FUNCTION result_str_test(cmd text) RETURNS text
+AS $$
+plan = plpy.prepare(cmd)
+result = plpy.execute(plan)
+return str(result)
+$$ LANGUAGE plpython3u;
+SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$);
+ result_str_test
+------------------------------------------------------------
+ <PLyResult status=5 nrows=2 rows=[{'foo': 1}, {'foo': 2}]>
+(1 row)
+
+SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
+ result_str_test
+--------------------------------------
+ <PLyResult status=4 nrows=0 rows=[]>
+(1 row)
+
+-- cursor objects
+CREATE FUNCTION simple_cursor_test() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+does = 0
+for row in res:
+ if row['lname'] == 'doe':
+ does += 1
+return does
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION double_cursor_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+res.close()
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_fetch() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+assert len(res.fetch(3)) == 3
+assert len(res.fetch(3)) == 1
+assert len(res.fetch(3)) == 0
+assert len(res.fetch(3)) == 0
+try:
+ # use next() or __next__(), the method name changed in
+ # http://www.python.org/dev/peps/pep-3114/
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except StopIteration:
+ pass
+else:
+ assert False, "StopIteration not raised"
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users order by fname")
+assert len(res.fetch(2)) == 2
+
+item = None
+try:
+ item = res.next()
+except AttributeError:
+ item = res.__next__()
+assert item['fname'] == 'rick'
+
+assert len(res.fetch(2)) == 1
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION fetch_after_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+try:
+ res.fetch(1)
+except ValueError:
+ pass
+else:
+ assert False, "ValueError not raised"
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION next_after_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+try:
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except ValueError:
+ pass
+else:
+ assert False, "ValueError not raised"
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users where false")
+assert len(res.fetch(1)) == 0
+try:
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except StopIteration:
+ pass
+else:
+ assert False, "StopIteration not raised"
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$
+plan = plpy.prepare(
+ "select fname, lname from users where fname like $1 || '%' order by fname",
+ ["text"])
+for row in plpy.cursor(plan, ["w"]):
+ yield row['fname']
+for row in plan.cursor(["j"]):
+ yield row['fname']
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$
+plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
+ ["text"])
+c = plpy.cursor(plan, ["a", "b"])
+$$ LANGUAGE plpython3u;
+CREATE TYPE test_composite_type AS (
+ a1 int,
+ a2 varchar
+);
+CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$
+plan = plpy.prepare("select $1 as c1", ["test_composite_type"])
+res = plpy.execute(plan, [{"a1": 3, "a2": "label"}])
+return res[0]["c1"]
+$$ LANGUAGE plpython3u;
+SELECT simple_cursor_test();
+ simple_cursor_test
+--------------------
+ 3
+(1 row)
+
+SELECT double_cursor_close();
+ double_cursor_close
+---------------------
+
+(1 row)
+
+SELECT cursor_fetch();
+ cursor_fetch
+--------------
+
+(1 row)
+
+SELECT cursor_mix_next_and_fetch();
+ cursor_mix_next_and_fetch
+---------------------------
+
+(1 row)
+
+SELECT fetch_after_close();
+ fetch_after_close
+-------------------
+
+(1 row)
+
+SELECT next_after_close();
+ next_after_close
+------------------
+
+(1 row)
+
+SELECT cursor_fetch_next_empty();
+ cursor_fetch_next_empty
+-------------------------
+
+(1 row)
+
+SELECT cursor_plan();
+ cursor_plan
+-------------
+ willem
+ jane
+ john
+(3 rows)
+
+SELECT cursor_plan_wrong_args();
+ERROR: TypeError: Expected sequence of 1 argument, got 2: ['a', 'b']
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_plan_wrong_args", line 4, in <module>
+ c = plpy.cursor(plan, ["a", "b"])
+PL/Python function "cursor_plan_wrong_args"
+SELECT plan_composite_args();
+ plan_composite_args
+---------------------
+ (3,label)
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_subtransaction.out b/src/pl/plpython/expected/plpython_subtransaction.out
new file mode 100644
index 0000000..43d9277
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_subtransaction.out
@@ -0,0 +1,401 @@
+--
+-- Test explicit subtransactions
+--
+-- Test table to see if transactions get properly rolled back
+CREATE TABLE subtransaction_tbl (
+ i integer
+);
+CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ if what_error == "SPI":
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
+ elif what_error == "Python":
+ raise Exception("Python exception")
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_ctx_test();
+ subtransaction_ctx_test
+-------------------------
+
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT subtransaction_ctx_test('SPI');
+ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for type integer: "oops"
+LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops')
+ ^
+QUERY: INSERT INTO subtransaction_tbl VALUES ('oops')
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_ctx_test", line 6, in <module>
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
+PL/Python function "subtransaction_ctx_test"
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT subtransaction_ctx_test('Python');
+ERROR: Exception: Python exception
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_ctx_test", line 8, in <module>
+ raise Exception("Python exception")
+PL/Python function "subtransaction_ctx_test"
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Nested subtransactions
+CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
+AS $$
+plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ try:
+ with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)")
+ plpy.execute("error")
+ except plpy.SPIError as e:
+ if not swallow:
+ raise
+ plpy.notice("Swallowed %s(%r)" % (e.__class__.__name__, e.args[0]))
+return "ok"
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_nested_test();
+ERROR: spiexceptions.SyntaxError: syntax error at or near "error"
+LINE 1: error
+ ^
+QUERY: error
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_nested_test", line 8, in <module>
+ plpy.execute("error")
+PL/Python function "subtransaction_nested_test"
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT subtransaction_nested_test('t');
+NOTICE: Swallowed SyntaxError('syntax error at or near "error"')
+ subtransaction_nested_test
+----------------------------
+ ok
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Nested subtransactions that recursively call code dealing with
+-- subtransactions
+CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text
+AS $$
+plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ plpy.execute("SELECT subtransaction_nested_test('t')")
+return "ok"
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_deeply_nested_test();
+NOTICE: Swallowed SyntaxError('syntax error at or near "error"')
+ subtransaction_deeply_nested_test
+-----------------------------------
+ ok
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+ 1
+ 2
+(4 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Error conditions from not opening/closing subtransactions
+CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void
+AS $$
+plpy.subtransaction().__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_exit_twice() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+plpy.subtransaction().__exit__(None, None, None)
+plpy.subtransaction().__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_enter_twice() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+plpy.subtransaction().__enter__()
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.__enter__()
+s.__exit__(None, None, None)
+s.__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.__enter__()
+s.__enter__()
+s.__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+-- No warnings here, as the subtransaction gets indeed closed
+CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void
+AS $$
+with plpy.subtransaction() as s:
+ s.__enter__()
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void
+AS $$
+try:
+ with plpy.subtransaction() as s:
+ s.__exit__(None, None, None)
+except ValueError as e:
+ raise ValueError(e)
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_exit_without_enter();
+ERROR: ValueError: this subtransaction has not been entered
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_exit_without_enter", line 2, in <module>
+ plpy.subtransaction().__exit__(None, None, None)
+PL/Python function "subtransaction_exit_without_enter"
+SELECT subtransaction_enter_without_exit();
+WARNING: forcibly aborting a subtransaction that has not been exited
+ subtransaction_enter_without_exit
+-----------------------------------
+
+(1 row)
+
+SELECT subtransaction_exit_twice();
+WARNING: forcibly aborting a subtransaction that has not been exited
+ERROR: ValueError: this subtransaction has not been entered
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_exit_twice", line 3, in <module>
+ plpy.subtransaction().__exit__(None, None, None)
+PL/Python function "subtransaction_exit_twice"
+SELECT subtransaction_enter_twice();
+WARNING: forcibly aborting a subtransaction that has not been exited
+WARNING: forcibly aborting a subtransaction that has not been exited
+ subtransaction_enter_twice
+----------------------------
+
+(1 row)
+
+SELECT subtransaction_exit_same_subtransaction_twice();
+ERROR: ValueError: this subtransaction has already been exited
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module>
+ s.__exit__(None, None, None)
+PL/Python function "subtransaction_exit_same_subtransaction_twice"
+SELECT subtransaction_enter_same_subtransaction_twice();
+WARNING: forcibly aborting a subtransaction that has not been exited
+ERROR: ValueError: this subtransaction has already been entered
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module>
+ s.__enter__()
+PL/Python function "subtransaction_enter_same_subtransaction_twice"
+SELECT subtransaction_enter_subtransaction_in_with();
+ERROR: ValueError: this subtransaction has already been entered
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_enter_subtransaction_in_with", line 3, in <module>
+ s.__enter__()
+PL/Python function "subtransaction_enter_subtransaction_in_with"
+SELECT subtransaction_exit_subtransaction_in_with();
+ERROR: ValueError: this subtransaction has already been exited
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "subtransaction_exit_subtransaction_in_with", line 6, in <module>
+ raise ValueError(e)
+PL/Python function "subtransaction_exit_subtransaction_in_with"
+-- Make sure we don't get a "current transaction is aborted" error
+SELECT 1 as test;
+ test
+------
+ 1
+(1 row)
+
+-- Mix explicit subtransactions and normal SPI calls
+CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void
+AS $$
+p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"])
+try:
+ with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute(p, [2])
+ plpy.execute(p, ["wrong"])
+except plpy.SPIError:
+ plpy.warning("Caught a SPI error from an explicit subtransaction")
+
+try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute(p, [2])
+ plpy.execute(p, ["wrong"])
+except plpy.SPIError:
+ plpy.warning("Caught a SPI error")
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_mix_explicit_and_implicit();
+WARNING: Caught a SPI error from an explicit subtransaction
+WARNING: Caught a SPI error
+ subtransaction_mix_explicit_and_implicit
+------------------------------------------
+
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Alternative method names for Python <2.6
+CREATE FUNCTION subtransaction_alternative_names() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.enter()
+s.exit(None, None, None)
+$$ LANGUAGE plpython3u;
+SELECT subtransaction_alternative_names();
+ subtransaction_alternative_names
+----------------------------------
+
+(1 row)
+
+-- try/catch inside a subtransaction block
+CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')")
+ except plpy.SPIError:
+ plpy.notice("caught")
+$$ LANGUAGE plpython3u;
+SELECT try_catch_inside_subtransaction();
+NOTICE: caught
+ try_catch_inside_subtransaction
+---------------------------------
+
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+(1 row)
+
+TRUNCATE subtransaction_tbl;
+ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i);
+CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ except plpy.SPIError:
+ plpy.notice("caught")
+$$ LANGUAGE plpython3u;
+SELECT pk_violation_inside_subtransaction();
+NOTICE: caught
+ pk_violation_inside_subtransaction
+------------------------------------
+
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+(1 row)
+
+DROP TABLE subtransaction_tbl;
+-- cursor/subtransactions interactions
+CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$
+with plpy.subtransaction():
+ cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)")
+ cur.fetch(10)
+fetched = cur.fetch(10);
+return int(fetched[5]["i"])
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$
+try:
+ with plpy.subtransaction():
+ cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)")
+ cur.fetch(10);
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ fetched = cur.fetch(10)
+ return int(fetched[5]["i"])
+return 0 # not reached
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$
+try:
+ with plpy.subtransaction():
+ plpy.execute('create temporary table tmp(i) '
+ 'as select generate_series(1, 10)')
+ plan = plpy.prepare("select i from tmp")
+ cur = plpy.cursor(plan)
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ fetched = cur.fetch(5)
+ return fetched[2]["i"]
+return 0 # not reached
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$
+try:
+ with plpy.subtransaction():
+ cur = plpy.cursor('select 1')
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ cur.close()
+ return True
+return False # not reached
+$$ LANGUAGE plpython3u;
+SELECT cursor_in_subxact();
+ cursor_in_subxact
+-------------------
+ 16
+(1 row)
+
+SELECT cursor_aborted_subxact();
+ERROR: ValueError: iterating a cursor in an aborted subtransaction
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_aborted_subxact", line 8, in <module>
+ fetched = cur.fetch(10)
+PL/Python function "cursor_aborted_subxact"
+SELECT cursor_plan_aborted_subxact();
+ERROR: ValueError: iterating a cursor in an aborted subtransaction
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_plan_aborted_subxact", line 10, in <module>
+ fetched = cur.fetch(5)
+PL/Python function "cursor_plan_aborted_subxact"
+SELECT cursor_close_aborted_subxact();
+ERROR: ValueError: closing a cursor in an aborted subtransaction
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "cursor_close_aborted_subxact", line 7, in <module>
+ cur.close()
+PL/Python function "cursor_close_aborted_subxact"
diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out
new file mode 100644
index 0000000..13c1411
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_test.out
@@ -0,0 +1,93 @@
+-- first some tests of basic functionality
+CREATE EXTENSION plpython3u;
+-- really stupid function just to get the module loaded
+CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u;
+select stupid();
+ stupid
+--------
+ zarkon
+(1 row)
+
+-- check 2/3 versioning
+CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u;
+select stupidn();
+ stupidn
+---------
+ zarkon
+(1 row)
+
+-- test multiple arguments and odd characters in function name
+CREATE FUNCTION "Argument test #1"(u users, a1 text, a2 text) RETURNS text
+ AS
+'keys = list(u.keys())
+keys.sort()
+out = []
+for key in keys:
+ out.append("%s: %s" % (key, u[key]))
+words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
+return words'
+ LANGUAGE plpython3u;
+select "Argument test #1"(users, fname, lname) from users where lname = 'doe' order by 1;
+ Argument test #1
+-----------------------------------------------------------------------
+ jane doe => {fname: jane, lname: doe, userid: 1, username: j_doe}
+ john doe => {fname: john, lname: doe, userid: 2, username: johnd}
+ willem doe => {fname: willem, lname: doe, userid: 3, username: w_doe}
+(3 rows)
+
+-- check module contents
+CREATE FUNCTION module_contents() RETURNS SETOF text AS
+$$
+contents = list(filter(lambda x: not x.startswith("__"), dir(plpy)))
+contents.sort()
+return contents
+$$ LANGUAGE plpython3u;
+select module_contents();
+ module_contents
+-----------------
+ Error
+ Fatal
+ SPIError
+ commit
+ cursor
+ debug
+ error
+ execute
+ fatal
+ info
+ log
+ notice
+ prepare
+ quote_ident
+ quote_literal
+ quote_nullable
+ rollback
+ spiexceptions
+ subtransaction
+ warning
+(20 rows)
+
+CREATE FUNCTION elog_test_basic() RETURNS void
+AS $$
+plpy.debug('debug')
+plpy.log('log')
+plpy.info('info')
+plpy.info(37)
+plpy.info()
+plpy.info('info', 37, [1, 2, 3])
+plpy.notice('notice')
+plpy.warning('warning')
+plpy.error('error')
+$$ LANGUAGE plpython3u;
+SELECT elog_test_basic();
+INFO: info
+INFO: 37
+INFO: ()
+INFO: ('info', 37, [1, 2, 3])
+NOTICE: notice
+WARNING: warning
+ERROR: plpy.Error: error
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "elog_test_basic", line 10, in <module>
+ plpy.error('error')
+PL/Python function "elog_test_basic"
diff --git a/src/pl/plpython/expected/plpython_transaction.out b/src/pl/plpython/expected/plpython_transaction.out
new file mode 100644
index 0000000..659ccef
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_transaction.out
@@ -0,0 +1,250 @@
+CREATE TABLE test1 (a int, b text);
+CREATE PROCEDURE transaction_test1()
+LANGUAGE plpython3u
+AS $$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+CALL transaction_test1();
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+TRUNCATE test1;
+DO
+LANGUAGE plpython3u
+$$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+TRUNCATE test1;
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plpython3u
+AS $$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+return 1
+$$;
+SELECT transaction_test2();
+ERROR: spiexceptions.InvalidTransactionTermination: invalid transaction termination
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "transaction_test2", line 5, in <module>
+ plpy.commit()
+PL/Python function "transaction_test2"
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plpy.execute("CALL transaction_test1()")
+return 1
+$$;
+SELECT transaction_test3();
+ERROR: spiexceptions.InvalidTransactionTermination: spiexceptions.InvalidTransactionTermination: invalid transaction termination
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "transaction_test3", line 2, in <module>
+ plpy.execute("CALL transaction_test1()")
+PL/Python function "transaction_test3"
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$")
+return 1
+$$;
+SELECT transaction_test4();
+ERROR: spiexceptions.InvalidTransactionTermination: spiexceptions.InvalidTransactionTermination: invalid transaction termination
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "transaction_test4", line 2, in <module>
+ plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$")
+PL/Python function "transaction_test4"
+-- commit inside subtransaction (prohibited)
+DO LANGUAGE plpython3u $$
+s = plpy.subtransaction()
+s.enter()
+plpy.commit()
+$$;
+WARNING: forcibly aborting a subtransaction that has not been exited
+ERROR: spiexceptions.InvalidTransactionTermination: cannot commit while a subtransaction is active
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 4, in <module>
+ plpy.commit()
+PL/Python anonymous code block
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+TRUNCATE test1;
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ plpy.commit()
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 1 |
+ 2 |
+ 3 |
+ 4 |
+(5 rows)
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x'])
+ plpy.commit()
+$$;
+ERROR: spiexceptions.DivisionByZero: division by zero
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 3, in <module>
+ plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x'])
+PL/Python anonymous code block
+SELECT * FROM test1;
+ a | b
+-----+---
+ -6 |
+ -12 |
+(2 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ plpy.rollback()
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ if row['x'] % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+(3 rows)
+
+SELECT * FROM pg_cursors;
+ name | statement | is_holdable | is_binary | is_scrollable | creation_time
+------+-----------+-------------+-----------+---------------+---------------
+(0 rows)
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+DO LANGUAGE plpython3u $$
+# this insert will fail during commit:
+plpy.execute("INSERT INTO testfk VALUES (0)")
+plpy.commit()
+plpy.warning('should not get here')
+$$;
+ERROR: spiexceptions.ForeignKeyViolation: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey"
+DETAIL: Key (f1)=(0) is not present in table "testpk".
+CONTEXT: Traceback (most recent call last):
+ PL/Python anonymous code block, line 4, in <module>
+ plpy.commit()
+PL/Python anonymous code block
+SELECT * FROM testpk;
+ id
+----
+(0 rows)
+
+SELECT * FROM testfk;
+ f1
+----
+(0 rows)
+
+DO LANGUAGE plpython3u $$
+# this insert will fail during commit:
+plpy.execute("INSERT INTO testfk VALUES (0)")
+try:
+ plpy.commit()
+except Exception as e:
+ plpy.info('sqlstate: %s' % (e.sqlstate))
+# these inserts should work:
+plpy.execute("INSERT INTO testpk VALUES (1)")
+plpy.execute("INSERT INTO testfk VALUES (1)")
+$$;
+INFO: sqlstate: 23503
+SELECT * FROM testpk;
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM testfk;
+ f1
+----
+ 1
+(1 row)
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out
new file mode 100644
index 0000000..dd1ca32
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_trigger.out
@@ -0,0 +1,620 @@
+-- these triggers are dedicated to HPHC of RI who
+-- decided that my kid's name was william not willem, and
+-- vigorously resisted all efforts at correction. they have
+-- since gone bankrupt...
+CREATE FUNCTION users_insert() returns trigger
+ AS
+'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
+ return "SKIP"
+if TD["new"]["username"] == None:
+ TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
+ rv = "MODIFY"
+else:
+ rv = None
+if TD["new"]["fname"] == "william":
+ TD["new"]["fname"] = TD["args"][0]
+ rv = "MODIFY"
+return rv'
+ LANGUAGE plpython3u;
+CREATE FUNCTION users_update() returns trigger
+ AS
+'if TD["event"] == "UPDATE":
+ if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
+ return "SKIP"
+return None'
+ LANGUAGE plpython3u;
+CREATE FUNCTION users_delete() RETURNS trigger
+ AS
+'if TD["old"]["fname"] == TD["args"][0]:
+ return "SKIP"
+return None'
+ LANGUAGE plpython3u;
+CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_insert ('willem');
+CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_update ('willem');
+CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_delete ('willem');
+-- quick peek at the table
+--
+SELECT * FROM users;
+ fname | lname | username | userid
+--------+-------+----------+--------
+ jane | doe | j_doe | 1
+ john | doe | johnd | 2
+ willem | doe | w_doe | 3
+ rick | smith | slash | 4
+(4 rows)
+
+-- should fail
+--
+UPDATE users SET fname = 'william' WHERE fname = 'willem';
+-- should modify william to willem and create username
+--
+INSERT INTO users (fname, lname) VALUES ('william', 'smith');
+INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
+SELECT * FROM users;
+ fname | lname | username | userid
+---------+--------+----------+--------
+ jane | doe | j_doe | 1
+ john | doe | johnd | 2
+ willem | doe | w_doe | 3
+ rick | smith | slash | 4
+ willem | smith | w_smith | 5
+ charles | darwin | beagle | 6
+(6 rows)
+
+-- dump trigger data
+CREATE TABLE trigger_test
+ (i int, v text );
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$
+
+if 'relid' in TD:
+ TD['relid'] = "bogus:12345"
+
+skeys = list(TD.keys())
+skeys.sort()
+for key in skeys:
+ val = TD[key]
+ if not isinstance(val, dict):
+ plpy.notice("TD[" + key + "] => " + str(val))
+ else:
+ # print dicts the hard way because otherwise the order is implementation-dependent
+ valkeys = list(val.keys())
+ valkeys.sort()
+ plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}')
+
+return None
+
+$$;
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+CREATE TRIGGER show_trigger_data_trig_stmt
+BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
+FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo');
+insert into trigger_test values(1,'insert');
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => STATEMENT
+NOTICE: TD[name] => show_trigger_data_trig_stmt
+NOTICE: TD[new] => None
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+update trigger_test set v = 'update' where i = 1;
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => STATEMENT
+NOTICE: TD[name] => show_trigger_data_trig_stmt
+NOTICE: TD[new] => None
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => {'i': 1, 'v': 'update'}
+NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => {'i': 1, 'v': 'update'}
+NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+delete from trigger_test;
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => STATEMENT
+NOTICE: TD[name] => show_trigger_data_trig_stmt
+NOTICE: TD[new] => None
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => None
+NOTICE: TD[old] => {'i': 1, 'v': 'update'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => None
+NOTICE: TD[old] => {'i': 1, 'v': 'update'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+truncate table trigger_test;
+NOTICE: TD[args] => ['23', 'skidoo']
+NOTICE: TD[event] => TRUNCATE
+NOTICE: TD[level] => STATEMENT
+NOTICE: TD[name] => show_trigger_data_trig_stmt
+NOTICE: TD[new] => None
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
+DROP TRIGGER show_trigger_data_trig_before on trigger_test;
+DROP TRIGGER show_trigger_data_trig_after on trigger_test;
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+insert into trigger_test_generated (i) values (1);
+NOTICE: TD[args] => None
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => {'i': 1}
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => None
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => {'i': 1, 'j': 2}
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+update trigger_test_generated set i = 11 where i = 1;
+NOTICE: TD[args] => None
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => {'i': 11}
+NOTICE: TD[old] => {'i': 1, 'j': 2}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => None
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => {'i': 11, 'j': 22}
+NOTICE: TD[old] => {'i': 1, 'j': 2}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+delete from trigger_test_generated;
+NOTICE: TD[args] => None
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_before
+NOTICE: TD[new] => None
+NOTICE: TD[old] => {'i': 11, 'j': 22}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => BEFORE
+NOTICE: TD[args] => None
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig_after
+NOTICE: TD[new] => None
+NOTICE: TD[old] => {'i': 11, 'j': 22}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_generated
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => AFTER
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+insert into trigger_test values(1,'insert');
+CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+CREATE TRIGGER show_trigger_data_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+insert into trigger_test_view values(2,'insert');
+NOTICE: TD[args] => ['24', 'skidoo view']
+NOTICE: TD[event] => INSERT
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig
+NOTICE: TD[new] => {'i': 2, 'v': 'insert'}
+NOTICE: TD[old] => None
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_view
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => INSTEAD OF
+update trigger_test_view set v = 'update' where i = 1;
+NOTICE: TD[args] => ['24', 'skidoo view']
+NOTICE: TD[event] => UPDATE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig
+NOTICE: TD[new] => {'i': 1, 'v': 'update'}
+NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_view
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => INSTEAD OF
+delete from trigger_test_view;
+NOTICE: TD[args] => ['24', 'skidoo view']
+NOTICE: TD[event] => DELETE
+NOTICE: TD[level] => ROW
+NOTICE: TD[name] => show_trigger_data_trig
+NOTICE: TD[new] => None
+NOTICE: TD[old] => {'i': 1, 'v': 'insert'}
+NOTICE: TD[relid] => bogus:12345
+NOTICE: TD[table_name] => trigger_test_view
+NOTICE: TD[table_schema] => public
+NOTICE: TD[when] => INSTEAD OF
+DROP FUNCTION trigger_data() CASCADE;
+NOTICE: drop cascades to trigger show_trigger_data_trig on view trigger_test_view
+DROP VIEW trigger_test_view;
+delete from trigger_test;
+--
+-- trigger error handling
+--
+INSERT INTO trigger_test VALUES (0, 'zero');
+-- returning non-string from trigger function
+CREATE FUNCTION stupid1() RETURNS trigger
+AS $$
+ return 37
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger1
+BEFORE INSERT ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid1();
+INSERT INTO trigger_test VALUES (1, 'one');
+ERROR: unexpected return value from trigger procedure
+DETAIL: Expected None or a string.
+CONTEXT: PL/Python function "stupid1"
+DROP TRIGGER stupid_trigger1 ON trigger_test;
+-- returning MODIFY from DELETE trigger
+CREATE FUNCTION stupid2() RETURNS trigger
+AS $$
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger2
+BEFORE DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid2();
+DELETE FROM trigger_test WHERE i = 0;
+WARNING: PL/Python trigger function returned "MODIFY" in a DELETE trigger -- ignored
+DROP TRIGGER stupid_trigger2 ON trigger_test;
+INSERT INTO trigger_test VALUES (0, 'zero');
+-- returning unrecognized string from trigger function
+CREATE FUNCTION stupid3() RETURNS trigger
+AS $$
+ return "foo"
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger3
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid3();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: unexpected return value from trigger procedure
+DETAIL: Expected None, "OK", "SKIP", or "MODIFY".
+CONTEXT: PL/Python function "stupid3"
+DROP TRIGGER stupid_trigger3 ON trigger_test;
+-- Unicode variant
+CREATE FUNCTION stupid3u() RETURNS trigger
+AS $$
+ return "foo"
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger3
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid3u();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: unexpected return value from trigger procedure
+DETAIL: Expected None, "OK", "SKIP", or "MODIFY".
+CONTEXT: PL/Python function "stupid3u"
+DROP TRIGGER stupid_trigger3 ON trigger_test;
+-- deleting the TD dictionary
+CREATE FUNCTION stupid4() RETURNS trigger
+AS $$
+ del TD["new"]
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger4
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid4();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: TD["new"] deleted, cannot modify row
+CONTEXT: while modifying trigger row
+PL/Python function "stupid4"
+DROP TRIGGER stupid_trigger4 ON trigger_test;
+-- TD not a dictionary
+CREATE FUNCTION stupid5() RETURNS trigger
+AS $$
+ TD["new"] = ['foo', 'bar']
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger5
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid5();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: TD["new"] is not a dictionary
+CONTEXT: while modifying trigger row
+PL/Python function "stupid5"
+DROP TRIGGER stupid_trigger5 ON trigger_test;
+-- TD not having string keys
+CREATE FUNCTION stupid6() RETURNS trigger
+AS $$
+ TD["new"] = {1: 'foo', 2: 'bar'}
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger6
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid6();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: TD["new"] dictionary key at ordinal position 0 is not a string
+CONTEXT: while modifying trigger row
+PL/Python function "stupid6"
+DROP TRIGGER stupid_trigger6 ON trigger_test;
+-- TD keys not corresponding to row columns
+CREATE FUNCTION stupid7() RETURNS trigger
+AS $$
+ TD["new"] = {'v': 'foo', 'a': 'bar'}
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger7
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid7();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row
+CONTEXT: while modifying trigger row
+PL/Python function "stupid7"
+DROP TRIGGER stupid_trigger7 ON trigger_test;
+-- Unicode variant
+CREATE FUNCTION stupid7u() RETURNS trigger
+AS $$
+ TD["new"] = {'v': 'foo', 'a': 'bar'}
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER stupid_trigger7
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid7u();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row
+CONTEXT: while modifying trigger row
+PL/Python function "stupid7u"
+DROP TRIGGER stupid_trigger7 ON trigger_test;
+-- calling a trigger function directly
+SELECT stupid7();
+ERROR: trigger functions can only be called as triggers
+--
+-- Null values
+--
+SELECT * FROM trigger_test;
+ i | v
+---+------
+ 0 | zero
+(1 row)
+
+CREATE FUNCTION test_null() RETURNS trigger
+AS $$
+ TD["new"]['v'] = None
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER test_null_trigger
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE test_null();
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+DROP TRIGGER test_null_trigger ON trigger_test;
+SELECT * FROM trigger_test;
+ i | v
+---+---
+ 0 |
+(1 row)
+
+--
+-- Test that triggers honor typmod when assigning to tuple fields,
+-- as per an early 9.0 bug report
+--
+SET DateStyle = 'ISO';
+CREATE FUNCTION set_modif_time() RETURNS trigger AS $$
+ TD['new']['modif_time'] = '2010-10-13 21:57:28.930486'
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE);
+CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb
+ FOR EACH ROW EXECUTE PROCEDURE set_modif_time();
+INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486');
+SELECT * FROM pb;
+ a | modif_time
+---+---------------------
+ a | 2010-10-09 21:57:34
+(1 row)
+
+UPDATE pb SET a = 'b';
+SELECT * FROM pb;
+ a | modif_time
+---+---------------------
+ b | 2010-10-13 21:57:29
+(1 row)
+
+-- triggers for tables with composite types
+CREATE TABLE comp1 (i integer, j boolean);
+CREATE TYPE comp2 AS (k integer, l boolean);
+CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
+CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
+ TD['new']['f1'] = (3, False)
+ TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
+INSERT INTO composite_trigger_test VALUES (NULL, NULL);
+SELECT * FROM composite_trigger_test;
+ f1 | f2
+-------+-------
+ (3,f) | (7,t)
+(1 row)
+
+-- triggers with composite type columns (bug #6559)
+CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
+CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
+INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL);
+INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL);
+INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f'));
+SELECT * FROM composite_trigger_noop_test;
+ f1 | f2
+-------+-------
+ |
+ (1,f) |
+ (,t) | (1,f)
+(3 rows)
+
+-- nested composite types
+CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer);
+CREATE TABLE composite_trigger_nested_test(c comp3);
+CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
+INSERT INTO composite_trigger_nested_test VALUES (NULL);
+INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
+INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
+SELECT * FROM composite_trigger_nested_test;
+ c
+-------------------
+
+ ("(1,f)",,3)
+ ("(,t)","(1,f)",)
+(3 rows)
+
+-- check that using a function as a trigger over two tables works correctly
+CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$
+ TD["new"]["data"] = '1234'
+ return 'MODIFY'
+$$;
+CREATE TABLE a(data text);
+CREATE TABLE b(data int); -- different type conversion
+CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234();
+CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234();
+INSERT INTO a DEFAULT VALUES;
+SELECT * FROM a;
+ data
+------
+ 1234
+(1 row)
+
+DROP TABLE a;
+INSERT INTO b DEFAULT VALUES;
+SELECT * FROM b;
+ data
+------
+ 1234
+(1 row)
+
+-- check that SQL run in trigger code can see transition tables
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpython3u AS
+$$
+ rv = plpy.execute("SELECT * FROM old_table")
+ assert(rv.nrows() == 1)
+ plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+ rv = plpy.execute("SELECT * FROM new_table")
+ assert(rv.nrows() == 1)
+ plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+ return None
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+INFO: old: 1 -> a
+INFO: new: 1 -> b
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
+-- dealing with generated columns
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE plpython3u
+AS $$
+TD['new']['j'] = 5 # not allowed
+return 'MODIFY'
+$$;
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+ERROR: cannot set generated column "j"
+CONTEXT: while modifying trigger row
+PL/Python function "generated_test_func1"
+SELECT * FROM trigger_test_generated;
+ i | j
+---+---
+(0 rows)
+
diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out
new file mode 100644
index 0000000..8a680e1
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_types.out
@@ -0,0 +1,1069 @@
+--
+-- Test data type behavior
+--
+--
+-- Base/common types
+--
+CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_bool(true);
+INFO: (True, <class 'bool'>)
+ test_type_conversion_bool
+---------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool(false);
+INFO: (False, <class 'bool'>)
+ test_type_conversion_bool
+---------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_bool
+---------------------------
+
+(1 row)
+
+-- test various other ways to express Booleans in Python
+CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$
+# numbers
+if n == 0:
+ ret = 0
+elif n == 1:
+ ret = 5
+# strings
+elif n == 2:
+ ret = ''
+elif n == 3:
+ ret = 'fa' # true in Python, false in PostgreSQL
+# containers
+elif n == 4:
+ ret = []
+elif n == 5:
+ ret = [0]
+plpy.info(ret, not not ret)
+return ret
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_bool_other(0);
+INFO: (0, False)
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(1);
+INFO: (5, True)
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(2);
+INFO: ('', False)
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(3);
+INFO: ('fa', True)
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(4);
+INFO: ([], False)
+ test_type_conversion_bool_other
+---------------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool_other(5);
+INFO: ([0], True)
+ test_type_conversion_bool_other
+---------------------------------
+ t
+(1 row)
+
+CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_char('a');
+INFO: ('a', <class 'str'>)
+ test_type_conversion_char
+---------------------------
+ a
+(1 row)
+
+SELECT * FROM test_type_conversion_char(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_char
+---------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_int2(100::int2);
+INFO: (100, <class 'int'>)
+ test_type_conversion_int2
+---------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_int2(-100::int2);
+INFO: (-100, <class 'int'>)
+ test_type_conversion_int2
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int2(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_int2
+---------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_int4(100);
+INFO: (100, <class 'int'>)
+ test_type_conversion_int4
+---------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4(-100);
+INFO: (-100, <class 'int'>)
+ test_type_conversion_int4
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_int4
+---------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_int8(100);
+INFO: (100, <class 'int'>)
+ test_type_conversion_int8
+---------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(-100);
+INFO: (-100, <class 'int'>)
+ test_type_conversion_int8
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(5000000000);
+INFO: (5000000000, <class 'int'>)
+ test_type_conversion_int8
+---------------------------
+ 5000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_int8
+---------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
+# print just the class name, not the type, to avoid differences
+# between decimal and cdecimal
+plpy.info(str(x), x.__class__.__name__)
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_numeric(100);
+INFO: ('100', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(-100);
+INFO: ('-100', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(100.0);
+INFO: ('100.0', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ 100.0
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(100.00);
+INFO: ('100.00', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ 100.00
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(5000000000.5);
+INFO: ('5000000000.5', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ 5000000000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
+INFO: ('1234567890.0987654321', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ 1234567890.0987654321
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
+INFO: ('-1234567890.0987654321', 'Decimal')
+ test_type_conversion_numeric
+------------------------------
+ -1234567890.0987654321
+(1 row)
+
+SELECT * FROM test_type_conversion_numeric(null);
+INFO: ('None', 'NoneType')
+ test_type_conversion_numeric
+------------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_float4(100);
+INFO: (100.0, <class 'float'>)
+ test_type_conversion_float4
+-----------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_float4(-100);
+INFO: (-100.0, <class 'float'>)
+ test_type_conversion_float4
+-----------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float4(5000.5);
+INFO: (5000.5, <class 'float'>)
+ test_type_conversion_float4
+-----------------------------
+ 5000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_float4(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_float4
+-----------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_float8(100);
+INFO: (100.0, <class 'float'>)
+ test_type_conversion_float8
+-----------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8(-100);
+INFO: (-100.0, <class 'float'>)
+ test_type_conversion_float8
+-----------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_float8(5000000000.5);
+INFO: (5000000000.5, <class 'float'>)
+ test_type_conversion_float8
+-----------------------------
+ 5000000000.5
+(1 row)
+
+SELECT * FROM test_type_conversion_float8(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_float8
+-----------------------------
+
+(1 row)
+
+SELECT * FROM test_type_conversion_float8(100100100.654321);
+INFO: (100100100.654321, <class 'float'>)
+ test_type_conversion_float8
+-----------------------------
+ 100100100.654321
+(1 row)
+
+CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_oid(100);
+INFO: (100, <class 'int'>)
+ test_type_conversion_oid
+--------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_oid(2147483649);
+INFO: (2147483649, <class 'int'>)
+ test_type_conversion_oid
+--------------------------
+ 2147483649
+(1 row)
+
+SELECT * FROM test_type_conversion_oid(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_oid
+--------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_text('hello world');
+INFO: ('hello world', <class 'str'>)
+ test_type_conversion_text
+---------------------------
+ hello world
+(1 row)
+
+SELECT * FROM test_type_conversion_text(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_text
+---------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_bytea('hello world');
+INFO: (b'hello world', <class 'bytes'>)
+ test_type_conversion_bytea
+----------------------------
+ \x68656c6c6f20776f726c64
+(1 row)
+
+SELECT * FROM test_type_conversion_bytea(E'null\\000byte');
+INFO: (b'null\x00byte', <class 'bytes'>)
+ test_type_conversion_bytea
+----------------------------
+ \x6e756c6c0062797465
+(1 row)
+
+SELECT * FROM test_type_conversion_bytea(null);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_bytea
+----------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$
+import marshal
+return marshal.dumps('hello world')
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$
+import marshal
+try:
+ return marshal.loads(x)
+except ValueError as e:
+ return 'FAILED: ' + str(e)
+$$ LANGUAGE plpython3u;
+SELECT test_type_unmarshal(x) FROM test_type_marshal() x;
+ test_type_unmarshal
+---------------------
+ hello world
+(1 row)
+
+--
+-- Domains
+--
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+return y
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_booltrue(true, true);
+ test_type_conversion_booltrue
+-------------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_booltrue(false, true);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+SELECT * FROM test_type_conversion_booltrue(true, false);
+ERROR: value for domain booltrue violates check constraint "booltrue_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_booltrue"
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return y
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_uint2(100::uint2, 50);
+INFO: (100, <class 'int'>)
+ test_type_conversion_uint2
+----------------------------
+ 50
+(1 row)
+
+SELECT * FROM test_type_conversion_uint2(100::uint2, -50);
+INFO: (100, <class 'int'>)
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_uint2"
+SELECT * FROM test_type_conversion_uint2(null, 1);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_uint2
+----------------------------
+ 1
+(1 row)
+
+CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL);
+CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$
+return y
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_nnint(10, 20);
+ test_type_conversion_nnint
+----------------------------
+ 20
+(1 row)
+
+SELECT * FROM test_type_conversion_nnint(null, 20);
+ERROR: value for domain nnint violates check constraint "nnint_check"
+SELECT * FROM test_type_conversion_nnint(10, null);
+ERROR: value for domain nnint violates check constraint "nnint_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_nnint"
+CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL);
+CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$
+plpy.info(x, type(x))
+return y
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold');
+INFO: (b'hello wold', <class 'bytes'>)
+ test_type_conversion_bytea10
+------------------------------
+ \x68656c6c6f20776f6c64
+(1 row)
+
+SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold');
+ERROR: value for domain bytea10 violates check constraint "bytea10_check"
+SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world');
+INFO: (b'hello word', <class 'bytes'>)
+ERROR: value for domain bytea10 violates check constraint "bytea10_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_bytea10"
+SELECT * FROM test_type_conversion_bytea10(null, 'hello word');
+ERROR: value for domain bytea10 violates check constraint "bytea10_check"
+SELECT * FROM test_type_conversion_bytea10('hello word', null);
+INFO: (b'hello word', <class 'bytes'>)
+ERROR: value for domain bytea10 violates check constraint "bytea10_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_bytea10"
+--
+-- Arrays
+--
+CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]);
+INFO: ([0, 100], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {0,100}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]);
+INFO: ([0, -100, 55], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {0,-100,55}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]);
+INFO: ([None, 1], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {NULL,1}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]);
+INFO: ([], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(NULL);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_array_int4
+---------------------------------
+
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]);
+INFO: ([[1, 2, 3], [4, 5, 6]], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {{1,2,3},{4,5,6}}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]);
+INFO: ([[[1, 2, None], [None, 5, 6]], [[None, 8, 9], [10, 11, 12]]], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------------------------
+ {{{1,2,NULL},{NULL,5,6}},{{NULL,8,9},{10,11,12}}}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_int4('[2:4]={1,2,3}');
+INFO: ([1, 2, 3], <class 'list'>)
+ test_type_conversion_array_int4
+---------------------------------
+ {1,2,3}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_int8(x int8[]) RETURNS int8[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_int8(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]::int8[]);
+INFO: ([[[1, 2, None], [None, 5, 6]], [[None, 8, 9], [10, 11, 12]]], <class 'list'>)
+ test_type_conversion_array_int8
+---------------------------------------------------
+ {{{1,2,NULL},{NULL,5,6}},{{NULL,8,9},{10,11,12}}}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_date(x date[]) RETURNS date[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_date(ARRAY[[['2016-09-21','2016-09-22',NULL],[NULL,'2016-10-21','2016-10-22']],
+ [[NULL,'2016-11-21','2016-10-21'],['2015-09-21','2015-09-22','2014-09-21']]]::date[]);
+INFO: ([[['09-21-2016', '09-22-2016', None], [None, '10-21-2016', '10-22-2016']], [[None, '11-21-2016', '10-21-2016'], ['09-21-2015', '09-22-2015', '09-21-2014']]], <class 'list'>)
+ test_type_conversion_array_date
+---------------------------------------------------------------------------------------------------------------------------------
+ {{{09-21-2016,09-22-2016,NULL},{NULL,10-21-2016,10-22-2016}},{{NULL,11-21-2016,10-21-2016},{09-21-2015,09-22-2015,09-21-2014}}}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_timestamp(x timestamp[]) RETURNS timestamp[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_timestamp(ARRAY[[['2016-09-21 15:34:24.078792-04','2016-10-22 11:34:24.078795-04',NULL],
+ [NULL,'2016-10-21 11:34:25.078792-04','2016-10-21 11:34:24.098792-04']],
+ [[NULL,'2016-01-21 11:34:24.078792-04','2016-11-21 11:34:24.108792-04'],
+ ['2015-09-21 11:34:24.079792-04','2014-09-21 11:34:24.078792-04','2013-09-21 11:34:24.078792-04']]]::timestamp[]);
+INFO: ([[['Wed Sep 21 15:34:24.078792 2016', 'Sat Oct 22 11:34:24.078795 2016', None], [None, 'Fri Oct 21 11:34:25.078792 2016', 'Fri Oct 21 11:34:24.098792 2016']], [[None, 'Thu Jan 21 11:34:24.078792 2016', 'Mon Nov 21 11:34:24.108792 2016'], ['Mon Sep 21 11:34:24.079792 2015', 'Sun Sep 21 11:34:24.078792 2014', 'Sat Sep 21 11:34:24.078792 2013']]], <class 'list'>)
+ test_type_conversion_array_timestamp
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {{{"Wed Sep 21 15:34:24.078792 2016","Sat Oct 22 11:34:24.078795 2016",NULL},{NULL,"Fri Oct 21 11:34:25.078792 2016","Fri Oct 21 11:34:24.098792 2016"}},{{NULL,"Thu Jan 21 11:34:24.078792 2016","Mon Nov 21 11:34:24.108792 2016"},{"Mon Sep 21 11:34:24.079792 2015","Sun Sep 21 11:34:24.078792 2014","Sat Sep 21 11:34:24.078792 2013"}}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemint4(h int4, i int4, j int4, k int4 ) RETURNS int4[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+select pyreturnmultidemint4(8,5,3,2);
+INFO: ([[[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]]], [[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]]]], <class 'list'>)
+ pyreturnmultidemint4
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {{{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}}},{{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}}}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemint8(h int4, i int4, j int4, k int4 ) RETURNS int8[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+select pyreturnmultidemint8(5,5,3,2);
+INFO: ([[[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]], [[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]]], <class 'list'>)
+ pyreturnmultidemint8
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {{{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}}},{{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}}}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemfloat4(h int4, i int4, j int4, k int4 ) RETURNS float4[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+select pyreturnmultidemfloat4(6,5,3,2);
+INFO: ([[[[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]]], [[[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]]]], <class 'list'>)
+ pyreturnmultidemfloat4
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {{{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}}},{{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}}}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemfloat8(h int4, i int4, j int4, k int4 ) RETURNS float8[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+select pyreturnmultidemfloat8(7,5,3,2);
+INFO: ([[[[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]]], [[[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]]]], <class 'list'>)
+ pyreturnmultidemfloat8
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ {{{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}}},{{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}}}}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']);
+INFO: (['foo', 'bar'], <class 'list'>)
+ test_type_conversion_array_text
+---------------------------------
+ {foo,bar}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_text(ARRAY[['foo', 'bar'],['foo2', 'bar2']]);
+INFO: ([['foo', 'bar'], ['foo2', 'bar2']], <class 'list'>)
+ test_type_conversion_array_text
+---------------------------------
+ {{foo,bar},{foo2,bar2}}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]);
+INFO: ([b'\xde\xad\xbe\xef', None], <class 'list'>)
+ test_type_conversion_array_bytea
+----------------------------------
+ {"\\xdeadbeef",NULL}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$
+return [123, 'abc']
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_mixed1();
+ test_type_conversion_array_mixed1
+-----------------------------------
+ {123,abc}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$
+return [123, 'abc']
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_mixed2();
+ERROR: invalid input syntax for type integer: "abc"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_array_mixed2"
+-- check output of multi-dimensional arrays
+CREATE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [['a'], ['b'], ['c']]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out();
+ test_type_conversion_md_array_out
+-----------------------------------
+ {{a},{b},{c}}
+(1 row)
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], []]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out();
+ test_type_conversion_md_array_out
+-----------------------------------
+ {}
+(1 row)
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], [1]]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_md_array_out"
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], 1]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_md_array_out"
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [1, []]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_md_array_out"
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[1], [[]]]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_md_array_out(); -- fail
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_md_array_out"
+CREATE FUNCTION test_type_conversion_mdarray_malformed() RETURNS int[] AS $$
+return [[1,2,3],[4,5]]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_mdarray_malformed();
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_mdarray_malformed"
+CREATE FUNCTION test_type_conversion_mdarray_malformed2() RETURNS text[] AS $$
+return [[1,2,3], "abc"]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_mdarray_malformed2();
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_mdarray_malformed2"
+CREATE FUNCTION test_type_conversion_mdarray_malformed3() RETURNS text[] AS $$
+return ["abc", [1,2,3]]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_mdarray_malformed3();
+ERROR: multidimensional arrays must have array expressions with matching dimensions
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_mdarray_malformed3"
+CREATE FUNCTION test_type_conversion_mdarray_toodeep() RETURNS int[] AS $$
+return [[[[[[[1]]]]]]]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_mdarray_toodeep();
+ERROR: number of array dimensions exceeds the maximum allowed (6)
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_mdarray_toodeep"
+CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
+return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_record();
+ test_type_conversion_array_record
+-----------------------------------
+ {"(one,42)","(two,11)"}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
+return 'abc'
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_string();
+ test_type_conversion_array_string
+-----------------------------------
+ {a,b,c}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$
+return ('abc', 'def')
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_tuple();
+ test_type_conversion_array_tuple
+----------------------------------
+ {abc,def}
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$
+return 5
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_error();
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_array_error"
+--
+-- Domains over arrays
+--
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+INFO: ([0, 100], <class 'list'>)
+ test_type_conversion_array_domain
+-----------------------------------
+ {0,100}
+(1 row)
+
+SELECT * FROM test_type_conversion_array_domain(NULL::ordered_pair_domain);
+INFO: (None, <class 'NoneType'>)
+ test_type_conversion_array_domain
+-----------------------------------
+
+(1 row)
+
+CREATE FUNCTION test_type_conversion_array_domain_check_violation() RETURNS ordered_pair_domain AS $$
+return [2,1]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_domain_check_violation();
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_array_domain_check_violation"
+--
+-- Arrays of domains
+--
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpython3u;
+select test_read_uint2_array(array[1::uint2]);
+INFO: ([1], <class 'list'>)
+ test_read_uint2_array
+-----------------------
+ 1
+(1 row)
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+return [x, x]
+$$ LANGUAGE plpython3u;
+select test_build_uint2_array(1::int2);
+ test_build_uint2_array
+------------------------
+ {1,1}
+(1 row)
+
+select test_build_uint2_array(-1::int2); -- fail
+ERROR: value for domain uint2 violates check constraint "uint2_check"
+CONTEXT: while creating return value
+PL/Python function "test_build_uint2_array"
+--
+-- ideally this would work, but for now it doesn't, because the return value
+-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+-- integer array, not an array of arrays.
+--
+CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+return [x, x]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_domain_array(array[2,4]);
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+select test_type_conversion_domain_array(array[4,2]); -- fail
+ERROR: return value of function with array return type is not a Python sequence
+CONTEXT: while creating return value
+PL/Python function "test_type_conversion_domain_array"
+CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+plpy.info(x, type(x))
+return x[1]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_domain_array2(array[2,4]);
+INFO: ([2, 4], <class 'list'>)
+ test_type_conversion_domain_array2
+------------------------------------
+ 4
+(1 row)
+
+select test_type_conversion_domain_array2(array[4,2]); -- fail
+ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check"
+CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpython3u;
+select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+INFO: ([[2, 4]], <class 'list'>)
+ test_type_conversion_array_domain_array
+-----------------------------------------
+ {2,4}
+(1 row)
+
+---
+--- Composite types
+---
+CREATE TABLE employee (
+ name text,
+ basesalary integer,
+ bonus integer
+);
+INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
+CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
+return e['basesalary'] + e['bonus']
+$$ LANGUAGE plpython3u;
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+ name | test_composite_table_input
+------+----------------------------
+ John | 110
+ Mary | 210
+(2 rows)
+
+ALTER TABLE employee DROP bonus;
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+ERROR: KeyError: 'bonus'
+CONTEXT: Traceback (most recent call last):
+ PL/Python function "test_composite_table_input", line 2, in <module>
+ return e['basesalary'] + e['bonus']
+PL/Python function "test_composite_table_input"
+ALTER TABLE employee ADD bonus integer;
+UPDATE employee SET bonus = 10;
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+ name | test_composite_table_input
+------+----------------------------
+ John | 110
+ Mary | 210
+(2 rows)
+
+CREATE TYPE named_pair AS (
+ i integer,
+ j integer
+);
+CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
+return sum(p.values())
+$$ LANGUAGE plpython3u;
+SELECT test_composite_type_input(row(1, 2));
+ test_composite_type_input
+---------------------------
+ 3
+(1 row)
+
+ALTER TYPE named_pair RENAME TO named_pair_2;
+SELECT test_composite_type_input(row(1, 2));
+ test_composite_type_input
+---------------------------
+ 3
+(1 row)
+
+--
+-- Domains within composite
+--
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
+return {'f1': x, 'f2': y}
+$$ LANGUAGE plpython3u;
+SELECT nnint_test(null, 3);
+ nnint_test
+------------
+ (,3)
+(1 row)
+
+SELECT nnint_test(3, null); -- fail
+ERROR: value for domain nnint violates check constraint "nnint_check"
+CONTEXT: while creating return value
+PL/Python function "nnint_test"
+--
+-- Domains of composite
+--
+CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+return p['i'] + p['j']
+$$ LANGUAGE plpython3u;
+SELECT read_ordered_named_pair(row(1, 2));
+ read_ordered_named_pair
+-------------------------
+ 3
+(1 row)
+
+SELECT read_ordered_named_pair(row(2, 1)); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+return {'i': i, 'j': j}
+$$ LANGUAGE plpython3u;
+SELECT build_ordered_named_pair(1,2);
+ build_ordered_named_pair
+--------------------------
+ (1,2)
+(1 row)
+
+SELECT build_ordered_named_pair(2,1); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: while creating return value
+PL/Python function "build_ordered_named_pair"
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
+$$ LANGUAGE plpython3u;
+SELECT build_ordered_named_pairs(1,2);
+ build_ordered_named_pairs
+---------------------------
+ {"(1,2)","(1,3)"}
+(1 row)
+
+SELECT build_ordered_named_pairs(2,1); -- fail
+ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check"
+CONTEXT: while creating return value
+PL/Python function "build_ordered_named_pairs"
+--
+-- Prepared statements
+--
+CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT CASE WHEN $1 THEN 1 ELSE 0 END AS val", ['boolean'])
+rv = plpy.execute(plan, ['fa'], 5) # 'fa' is true in Python
+return rv[0]['val']
+$$;
+SELECT test_prep_bool_input(); -- 1
+ test_prep_bool_input
+----------------------
+ 1
+(1 row)
+
+CREATE OR REPLACE FUNCTION test_prep_bool_output() RETURNS bool
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT $1 = 1 AS val", ['int'])
+rv = plpy.execute(plan, [0], 5)
+plpy.info(rv[0])
+return rv[0]['val']
+$$;
+SELECT test_prep_bool_output(); -- false
+INFO: {'val': False}
+ test_prep_bool_output
+-----------------------
+ f
+(1 row)
+
+CREATE OR REPLACE FUNCTION test_prep_bytea_input(bb bytea) RETURNS int
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT octet_length($1) AS val", ['bytea'])
+rv = plpy.execute(plan, [bb], 5)
+return rv[0]['val']
+$$;
+SELECT test_prep_bytea_input(E'a\\000b'); -- 3 (embedded null formerly truncated value)
+ test_prep_bytea_input
+-----------------------
+ 3
+(1 row)
+
+CREATE OR REPLACE FUNCTION test_prep_bytea_output() RETURNS bytea
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT decode('aa00bb', 'hex') AS val")
+rv = plpy.execute(plan, [], 5)
+plpy.info(rv[0])
+return rv[0]['val']
+$$;
+SELECT test_prep_bytea_output();
+INFO: {'val': b'\xaa\x00\xbb'}
+ test_prep_bytea_output
+------------------------
+ \xaa00bb
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_unicode.out b/src/pl/plpython/expected/plpython_unicode.out
new file mode 100644
index 0000000..fd54b0b
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_unicode.out
@@ -0,0 +1,56 @@
+--
+-- Unicode handling
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+--
+SET client_encoding TO UTF8;
+CREATE TABLE unicode_test (
+ testvalue text NOT NULL
+);
+CREATE FUNCTION unicode_return() RETURNS text AS E'
+return "\\xA0"
+' LANGUAGE plpython3u;
+CREATE FUNCTION unicode_trigger() RETURNS trigger AS E'
+TD["new"]["testvalue"] = "\\xA0"
+return "MODIFY"
+' LANGUAGE plpython3u;
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+ FOR EACH ROW EXECUTE PROCEDURE unicode_trigger();
+CREATE FUNCTION unicode_plan1() RETURNS text AS E'
+plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+rv = plpy.execute(plan, ["\\xA0"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpython3u;
+CREATE FUNCTION unicode_plan2() RETURNS text AS E'
+plan = plpy.prepare("SELECT $1 || $2 AS testvalue", ["text", "text"])
+rv = plpy.execute(plan, ["foo", "bar"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpython3u;
+SELECT unicode_return();
+ unicode_return
+----------------
+  
+(1 row)
+
+INSERT INTO unicode_test (testvalue) VALUES ('test');
+SELECT * FROM unicode_test;
+ testvalue
+-----------
+  
+(1 row)
+
+SELECT unicode_plan1();
+ unicode_plan1
+---------------
+  
+(1 row)
+
+SELECT unicode_plan2();
+ unicode_plan2
+---------------
+ foobar
+(1 row)
+
diff --git a/src/pl/plpython/expected/plpython_void.out b/src/pl/plpython/expected/plpython_void.out
new file mode 100644
index 0000000..07d0760
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_void.out
@@ -0,0 +1,30 @@
+--
+-- Tests for functions that return void
+--
+CREATE FUNCTION test_void_func1() RETURNS void AS $$
+x = 10
+$$ LANGUAGE plpython3u;
+-- illegal: can't return non-None value in void-returning func
+CREATE FUNCTION test_void_func2() RETURNS void AS $$
+return 10
+$$ LANGUAGE plpython3u;
+CREATE FUNCTION test_return_none() RETURNS int AS $$
+None
+$$ LANGUAGE plpython3u;
+-- Tests for functions returning void
+SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
+ test_void_func1 | is null
+-----------------+---------
+ | f
+(1 row)
+
+SELECT test_void_func2(); -- should fail
+ERROR: PL/Python function with return type "void" did not return None
+CONTEXT: while creating return value
+PL/Python function "test_void_func2"
+SELECT test_return_none(), test_return_none() IS NULL AS "is null";
+ test_return_none | is null
+------------------+---------
+ | t
+(1 row)
+
diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl
new file mode 100644
index 0000000..43289d2
--- /dev/null
+++ b/src/pl/plpython/generate-spiexceptions.pl
@@ -0,0 +1,44 @@
+#!/usr/bin/perl
+#
+# Generate the spiexceptions.h header from errcodes.txt
+# Copyright (c) 2000-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+print
+ "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+print "/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */\n";
+
+open my $errcodes, '<', $ARGV[0] or die;
+
+while (<$errcodes>)
+{
+ chomp;
+
+ # Skip comments
+ next if /^#/;
+ next if /^\s*$/;
+
+ # Skip section headers
+ next if /^Section:/;
+
+ die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+
+ (my $sqlstate, my $type, my $errcode_macro, my $condition_name) =
+ ($1, $2, $3, $4);
+
+ # Skip non-errors
+ next unless $type eq 'E';
+
+ # Skip lines without PL/pgSQL condition names
+ next unless defined($condition_name);
+
+ # Change some_error_condition to SomeErrorCondition
+ $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g;
+
+ print "\n{\n\t\"spiexceptions.$condition_name\", "
+ . "\"$condition_name\", $errcode_macro\n},\n";
+}
+
+close $errcodes;
diff --git a/src/pl/plpython/nls.mk b/src/pl/plpython/nls.mk
new file mode 100644
index 0000000..a7d3144
--- /dev/null
+++ b/src/pl/plpython/nls.mk
@@ -0,0 +1,11 @@
+# src/pl/plpython/nls.mk
+CATALOG_NAME = plpython
+AVAIL_LANGUAGES = cs de el es fr it ja ka ko pl pt_BR ru sv tr uk vi zh_CN
+GETTEXT_FILES = plpy_cursorobject.c plpy_elog.c plpy_exec.c plpy_main.c plpy_planobject.c plpy_plpymodule.c \
+ plpy_procedure.c plpy_resultobject.c plpy_spi.c plpy_subxactobject.c plpy_typeio.c plpy_util.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) PLy_elog:2 PLy_exception_set:2 PLy_exception_set_plural:2,3
+GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS) \
+ PLy_elog:2:c-format \
+ PLy_exception_set:2:c-format \
+ PLy_exception_set_plural:2:c-format \
+ PLy_exception_set_plural:3:c-format
diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c
new file mode 100644
index 0000000..6b6e743
--- /dev/null
+++ b/src/pl/plpython/plpy_cursorobject.c
@@ -0,0 +1,492 @@
+/*
+ * the PLyCursor class
+ *
+ * src/pl/plpython/plpy_cursorobject.c
+ */
+
+#include "postgres.h"
+
+#include <limits.h>
+
+#include "access/xact.h"
+#include "catalog/pg_type.h"
+#include "mb/pg_wchar.h"
+#include "plpy_cursorobject.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_planobject.h"
+#include "plpy_procedure.h"
+#include "plpy_resultobject.h"
+#include "plpy_spi.h"
+#include "plpython.h"
+#include "utils/memutils.h"
+
+static PyObject *PLy_cursor_query(const char *query);
+static void PLy_cursor_dealloc(PyObject *arg);
+static PyObject *PLy_cursor_iternext(PyObject *self);
+static PyObject *PLy_cursor_fetch(PyObject *self, PyObject *args);
+static PyObject *PLy_cursor_close(PyObject *self, PyObject *unused);
+
+static char PLy_cursor_doc[] = "Wrapper around a PostgreSQL cursor";
+
+static PyMethodDef PLy_cursor_methods[] = {
+ {"fetch", PLy_cursor_fetch, METH_VARARGS, NULL},
+ {"close", PLy_cursor_close, METH_NOARGS, NULL},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_CursorType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "PLyCursor",
+ .tp_basicsize = sizeof(PLyCursorObject),
+ .tp_dealloc = PLy_cursor_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = PLy_cursor_doc,
+ .tp_iter = PyObject_SelfIter,
+ .tp_iternext = PLy_cursor_iternext,
+ .tp_methods = PLy_cursor_methods,
+};
+
+void
+PLy_cursor_init_type(void)
+{
+ if (PyType_Ready(&PLy_CursorType) < 0)
+ elog(ERROR, "could not initialize PLy_CursorType");
+}
+
+PyObject *
+PLy_cursor(PyObject *self, PyObject *args)
+{
+ char *query;
+ PyObject *plan;
+ PyObject *planargs = NULL;
+
+ if (PyArg_ParseTuple(args, "s", &query))
+ return PLy_cursor_query(query);
+
+ PyErr_Clear();
+
+ if (PyArg_ParseTuple(args, "O|O", &plan, &planargs))
+ return PLy_cursor_plan(plan, planargs);
+
+ PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan");
+ return NULL;
+}
+
+
+static PyObject *
+PLy_cursor_query(const char *query)
+{
+ PLyCursorObject *cursor;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+
+ if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL)
+ return NULL;
+ cursor->portalname = NULL;
+ cursor->closed = false;
+ cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Python cursor context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&cursor->result, cursor->mcxt,
+ RECORDOID, -1,
+ exec_ctx->curr_proc);
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ SPIPlanPtr plan;
+ Portal portal;
+
+ pg_verifymbstr(query, strlen(query), false);
+
+ plan = SPI_prepare(query, 0, NULL);
+ if (plan == NULL)
+ elog(ERROR, "SPI_prepare failed: %s",
+ SPI_result_code_string(SPI_result));
+
+ portal = SPI_cursor_open(NULL, plan, NULL, NULL,
+ exec_ctx->curr_proc->fn_readonly);
+ SPI_freeplan(plan);
+
+ if (portal == NULL)
+ elog(ERROR, "SPI_cursor_open() failed: %s",
+ SPI_result_code_string(SPI_result));
+
+ cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
+
+ PinPortal(portal);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ Assert(cursor->portalname != NULL);
+ return (PyObject *) cursor;
+}
+
+PyObject *
+PLy_cursor_plan(PyObject *ob, PyObject *args)
+{
+ PLyCursorObject *cursor;
+ volatile int nargs;
+ int i;
+ PLyPlanObject *plan;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+
+ if (args)
+ {
+ if (!PySequence_Check(args) || PyUnicode_Check(args))
+ {
+ PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument");
+ return NULL;
+ }
+ nargs = PySequence_Length(args);
+ }
+ else
+ nargs = 0;
+
+ plan = (PLyPlanObject *) ob;
+
+ if (nargs != plan->nargs)
+ {
+ char *sv;
+ PyObject *so = PyObject_Str(args);
+
+ if (!so)
+ PLy_elog(ERROR, "could not execute plan");
+ sv = PLyUnicode_AsString(so);
+ PLy_exception_set_plural(PyExc_TypeError,
+ "Expected sequence of %d argument, got %d: %s",
+ "Expected sequence of %d arguments, got %d: %s",
+ plan->nargs,
+ plan->nargs, nargs, sv);
+ Py_DECREF(so);
+
+ return NULL;
+ }
+
+ if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL)
+ return NULL;
+ cursor->portalname = NULL;
+ cursor->closed = false;
+ cursor->mcxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Python cursor context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&cursor->result, cursor->mcxt,
+ RECORDOID, -1,
+ exec_ctx->curr_proc);
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ Portal portal;
+ char *volatile nulls;
+ volatile int j;
+
+ if (nargs > 0)
+ nulls = palloc(nargs * sizeof(char));
+ else
+ nulls = NULL;
+
+ for (j = 0; j < nargs; j++)
+ {
+ PLyObToDatum *arg = &plan->args[j];
+ PyObject *elem;
+
+ elem = PySequence_GetItem(args, j);
+ PG_TRY();
+ {
+ bool isnull;
+
+ plan->values[j] = PLy_output_convert(arg, elem, &isnull);
+ nulls[j] = isnull ? 'n' : ' ';
+ }
+ PG_FINALLY();
+ {
+ Py_DECREF(elem);
+ }
+ PG_END_TRY();
+ }
+
+ portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls,
+ exec_ctx->curr_proc->fn_readonly);
+ if (portal == NULL)
+ elog(ERROR, "SPI_cursor_open() failed: %s",
+ SPI_result_code_string(SPI_result));
+
+ cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name);
+
+ PinPortal(portal);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ int k;
+
+ /* cleanup plan->values array */
+ for (k = 0; k < nargs; k++)
+ {
+ if (!plan->args[k].typbyval &&
+ (plan->values[k] != PointerGetDatum(NULL)))
+ {
+ pfree(DatumGetPointer(plan->values[k]));
+ plan->values[k] = PointerGetDatum(NULL);
+ }
+ }
+
+ Py_DECREF(cursor);
+
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ for (i = 0; i < nargs; i++)
+ {
+ if (!plan->args[i].typbyval &&
+ (plan->values[i] != PointerGetDatum(NULL)))
+ {
+ pfree(DatumGetPointer(plan->values[i]));
+ plan->values[i] = PointerGetDatum(NULL);
+ }
+ }
+
+ Assert(cursor->portalname != NULL);
+ return (PyObject *) cursor;
+}
+
+static void
+PLy_cursor_dealloc(PyObject *arg)
+{
+ PLyCursorObject *cursor;
+ Portal portal;
+
+ cursor = (PLyCursorObject *) arg;
+
+ if (!cursor->closed)
+ {
+ portal = GetPortalByName(cursor->portalname);
+
+ if (PortalIsValid(portal))
+ {
+ UnpinPortal(portal);
+ SPI_cursor_close(portal);
+ }
+ cursor->closed = true;
+ }
+ if (cursor->mcxt)
+ {
+ MemoryContextDelete(cursor->mcxt);
+ cursor->mcxt = NULL;
+ }
+ arg->ob_type->tp_free(arg);
+}
+
+static PyObject *
+PLy_cursor_iternext(PyObject *self)
+{
+ PLyCursorObject *cursor;
+ PyObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+ Portal portal;
+
+ cursor = (PLyCursorObject *) self;
+
+ if (cursor->closed)
+ {
+ PLy_exception_set(PyExc_ValueError, "iterating a closed cursor");
+ return NULL;
+ }
+
+ portal = GetPortalByName(cursor->portalname);
+ if (!PortalIsValid(portal))
+ {
+ PLy_exception_set(PyExc_ValueError,
+ "iterating a cursor in an aborted subtransaction");
+ return NULL;
+ }
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ SPI_cursor_fetch(portal, true, 1);
+ if (SPI_processed == 0)
+ {
+ PyErr_SetNone(PyExc_StopIteration);
+ ret = NULL;
+ }
+ else
+ {
+ PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
+ exec_ctx->curr_proc);
+
+ ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0],
+ SPI_tuptable->tupdesc, true);
+ }
+
+ SPI_freetuptable(SPI_tuptable);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return ret;
+}
+
+static PyObject *
+PLy_cursor_fetch(PyObject *self, PyObject *args)
+{
+ PLyCursorObject *cursor;
+ int count;
+ PLyResultObject *ret;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+ Portal portal;
+
+ if (!PyArg_ParseTuple(args, "i:fetch", &count))
+ return NULL;
+
+ cursor = (PLyCursorObject *) self;
+
+ if (cursor->closed)
+ {
+ PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor");
+ return NULL;
+ }
+
+ portal = GetPortalByName(cursor->portalname);
+ if (!PortalIsValid(portal))
+ {
+ PLy_exception_set(PyExc_ValueError,
+ "iterating a cursor in an aborted subtransaction");
+ return NULL;
+ }
+
+ ret = (PLyResultObject *) PLy_result_new();
+ if (ret == NULL)
+ return NULL;
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ SPI_cursor_fetch(portal, true, count);
+
+ Py_DECREF(ret->status);
+ ret->status = PyLong_FromLong(SPI_OK_FETCH);
+
+ Py_DECREF(ret->nrows);
+ ret->nrows = PyLong_FromUnsignedLongLong(SPI_processed);
+
+ if (SPI_processed != 0)
+ {
+ uint64 i;
+
+ /*
+ * PyList_New() and PyList_SetItem() use Py_ssize_t for list size
+ * and list indices; so we cannot support a result larger than
+ * PY_SSIZE_T_MAX.
+ */
+ if (SPI_processed > (uint64) PY_SSIZE_T_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("query result has too many rows to fit in a Python list")));
+
+ Py_DECREF(ret->rows);
+ ret->rows = PyList_New(SPI_processed);
+ if (!ret->rows)
+ {
+ Py_DECREF(ret);
+ ret = NULL;
+ }
+ else
+ {
+ PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc,
+ exec_ctx->curr_proc);
+
+ for (i = 0; i < SPI_processed; i++)
+ {
+ PyObject *row = PLy_input_from_tuple(&cursor->result,
+ SPI_tuptable->vals[i],
+ SPI_tuptable->tupdesc,
+ true);
+
+ PyList_SetItem(ret->rows, i, row);
+ }
+ }
+ }
+
+ SPI_freetuptable(SPI_tuptable);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ return (PyObject *) ret;
+}
+
+static PyObject *
+PLy_cursor_close(PyObject *self, PyObject *unused)
+{
+ PLyCursorObject *cursor = (PLyCursorObject *) self;
+
+ if (!cursor->closed)
+ {
+ Portal portal = GetPortalByName(cursor->portalname);
+
+ if (!PortalIsValid(portal))
+ {
+ PLy_exception_set(PyExc_ValueError,
+ "closing a cursor in an aborted subtransaction");
+ return NULL;
+ }
+
+ UnpinPortal(portal);
+ SPI_cursor_close(portal);
+ cursor->closed = true;
+ }
+
+ Py_RETURN_NONE;
+}
diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h
new file mode 100644
index 0000000..e4d2c0e
--- /dev/null
+++ b/src/pl/plpython/plpy_cursorobject.h
@@ -0,0 +1,24 @@
+/*
+ * src/pl/plpython/plpy_cursorobject.h
+ */
+
+#ifndef PLPY_CURSOROBJECT_H
+#define PLPY_CURSOROBJECT_H
+
+#include "plpy_typeio.h"
+
+
+typedef struct PLyCursorObject
+{
+ PyObject_HEAD
+ char *portalname;
+ PLyDatumToOb result;
+ bool closed;
+ MemoryContext mcxt;
+} PLyCursorObject;
+
+extern void PLy_cursor_init_type(void);
+extern PyObject *PLy_cursor(PyObject *self, PyObject *args);
+extern PyObject *PLy_cursor_plan(PyObject *ob, PyObject *args);
+
+#endif /* PLPY_CURSOROBJECT_H */
diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c
new file mode 100644
index 0000000..70de5ba
--- /dev/null
+++ b/src/pl/plpython/plpy_elog.c
@@ -0,0 +1,599 @@
+/*
+ * reporting Python exceptions as PostgreSQL errors
+ *
+ * src/pl/plpython/plpy_elog.c
+ */
+
+#include "postgres.h"
+
+#include "lib/stringinfo.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_procedure.h"
+#include "plpython.h"
+
+PyObject *PLy_exc_error = NULL;
+PyObject *PLy_exc_fatal = NULL;
+PyObject *PLy_exc_spi_error = NULL;
+
+
+static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
+ char **xmsg, char **tbmsg, int *tb_depth);
+static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
+ char **hint, char **query, int *position,
+ char **schema_name, char **table_name, char **column_name,
+ char **datatype_name, char **constraint_name);
+static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail,
+ char **hint, char **schema_name, char **table_name, char **column_name,
+ char **datatype_name, char **constraint_name);
+static char *get_source_line(const char *src, int lineno);
+
+static void get_string_attr(PyObject *obj, char *attrname, char **str);
+static bool set_string_attr(PyObject *obj, char *attrname, char *str);
+
+/*
+ * Emit a PG error or notice, together with any available info about
+ * the current Python error, previously set by PLy_exception_set().
+ * This should be used to propagate Python errors into PG. If fmt is
+ * NULL, the Python error becomes the primary error message, otherwise
+ * it becomes the detail. If there is a Python traceback, it is put
+ * in the context.
+ */
+void
+PLy_elog_impl(int elevel, const char *fmt,...)
+{
+ int save_errno = errno;
+ char *xmsg;
+ char *tbmsg;
+ int tb_depth;
+ StringInfoData emsg;
+ PyObject *exc,
+ *val,
+ *tb;
+ const char *primary = NULL;
+ int sqlerrcode = 0;
+ char *detail = NULL;
+ char *hint = NULL;
+ char *query = NULL;
+ int position = 0;
+ char *schema_name = NULL;
+ char *table_name = NULL;
+ char *column_name = NULL;
+ char *datatype_name = NULL;
+ char *constraint_name = NULL;
+
+ PyErr_Fetch(&exc, &val, &tb);
+
+ if (exc != NULL)
+ {
+ PyErr_NormalizeException(&exc, &val, &tb);
+
+ if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error))
+ PLy_get_spi_error_data(val, &sqlerrcode,
+ &detail, &hint, &query, &position,
+ &schema_name, &table_name, &column_name,
+ &datatype_name, &constraint_name);
+ else if (PyErr_GivenExceptionMatches(val, PLy_exc_error))
+ PLy_get_error_data(val, &sqlerrcode, &detail, &hint,
+ &schema_name, &table_name, &column_name,
+ &datatype_name, &constraint_name);
+ else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal))
+ elevel = FATAL;
+ }
+
+ /* this releases our refcount on tb! */
+ PLy_traceback(exc, val, tb,
+ &xmsg, &tbmsg, &tb_depth);
+
+ if (fmt)
+ {
+ initStringInfo(&emsg);
+ for (;;)
+ {
+ va_list ap;
+ int needed;
+
+ errno = save_errno;
+ va_start(ap, fmt);
+ needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap);
+ va_end(ap);
+ if (needed == 0)
+ break;
+ enlargeStringInfo(&emsg, needed);
+ }
+ primary = emsg.data;
+
+ /* If there's an exception message, it goes in the detail. */
+ if (xmsg)
+ detail = xmsg;
+ }
+ else
+ {
+ if (xmsg)
+ primary = xmsg;
+ }
+
+ PG_TRY();
+ {
+ ereport(elevel,
+ (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg_internal("%s", primary ? primary : "no exception data"),
+ (detail) ? errdetail_internal("%s", detail) : 0,
+ (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0,
+ (hint) ? errhint("%s", hint) : 0,
+ (query) ? internalerrquery(query) : 0,
+ (position) ? internalerrposition(position) : 0,
+ (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME,
+ schema_name) : 0,
+ (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME,
+ table_name) : 0,
+ (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME,
+ column_name) : 0,
+ (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME,
+ datatype_name) : 0,
+ (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME,
+ constraint_name) : 0));
+ }
+ PG_FINALLY();
+ {
+ if (fmt)
+ pfree(emsg.data);
+ if (xmsg)
+ pfree(xmsg);
+ if (tbmsg)
+ pfree(tbmsg);
+ Py_XDECREF(exc);
+ Py_XDECREF(val);
+ }
+ PG_END_TRY();
+}
+
+/*
+ * Extract a Python traceback from the given exception data.
+ *
+ * The exception error message is returned in xmsg, the traceback in
+ * tbmsg (both as palloc'd strings) and the traceback depth in
+ * tb_depth.
+ *
+ * We release refcounts on all the Python objects in the traceback stack,
+ * but not on e or v.
+ */
+static void
+PLy_traceback(PyObject *e, PyObject *v, PyObject *tb,
+ char **xmsg, char **tbmsg, int *tb_depth)
+{
+ PyObject *e_type_o;
+ PyObject *e_module_o;
+ char *e_type_s = NULL;
+ char *e_module_s = NULL;
+ PyObject *vob = NULL;
+ char *vstr;
+ StringInfoData xstr;
+ StringInfoData tbstr;
+
+ /*
+ * if no exception, return nulls
+ */
+ if (e == NULL)
+ {
+ *xmsg = NULL;
+ *tbmsg = NULL;
+ *tb_depth = 0;
+
+ return;
+ }
+
+ /*
+ * Format the exception and its value and put it in xmsg.
+ */
+
+ e_type_o = PyObject_GetAttrString(e, "__name__");
+ e_module_o = PyObject_GetAttrString(e, "__module__");
+ if (e_type_o)
+ e_type_s = PLyUnicode_AsString(e_type_o);
+ if (e_type_s)
+ e_module_s = PLyUnicode_AsString(e_module_o);
+
+ if (v && ((vob = PyObject_Str(v)) != NULL))
+ vstr = PLyUnicode_AsString(vob);
+ else
+ vstr = "unknown";
+
+ initStringInfo(&xstr);
+ if (!e_type_s || !e_module_s)
+ {
+ /* shouldn't happen */
+ appendStringInfoString(&xstr, "unrecognized exception");
+ }
+ /* mimics behavior of traceback.format_exception_only */
+ else if (strcmp(e_module_s, "builtins") == 0
+ || strcmp(e_module_s, "__main__") == 0
+ || strcmp(e_module_s, "exceptions") == 0)
+ appendStringInfoString(&xstr, e_type_s);
+ else
+ appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s);
+ appendStringInfo(&xstr, ": %s", vstr);
+
+ *xmsg = xstr.data;
+
+ /*
+ * Now format the traceback and put it in tbmsg.
+ */
+
+ *tb_depth = 0;
+ initStringInfo(&tbstr);
+ /* Mimic Python traceback reporting as close as possible. */
+ appendStringInfoString(&tbstr, "Traceback (most recent call last):");
+ while (tb != NULL && tb != Py_None)
+ {
+ PyObject *volatile tb_prev = NULL;
+ PyObject *volatile frame = NULL;
+ PyObject *volatile code = NULL;
+ PyObject *volatile name = NULL;
+ PyObject *volatile lineno = NULL;
+ PyObject *volatile filename = NULL;
+
+ PG_TRY();
+ {
+ lineno = PyObject_GetAttrString(tb, "tb_lineno");
+ if (lineno == NULL)
+ elog(ERROR, "could not get line number from Python traceback");
+
+ frame = PyObject_GetAttrString(tb, "tb_frame");
+ if (frame == NULL)
+ elog(ERROR, "could not get frame from Python traceback");
+
+ code = PyObject_GetAttrString(frame, "f_code");
+ if (code == NULL)
+ elog(ERROR, "could not get code object from Python frame");
+
+ name = PyObject_GetAttrString(code, "co_name");
+ if (name == NULL)
+ elog(ERROR, "could not get function name from Python code object");
+
+ filename = PyObject_GetAttrString(code, "co_filename");
+ if (filename == NULL)
+ elog(ERROR, "could not get file name from Python code object");
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(frame);
+ Py_XDECREF(code);
+ Py_XDECREF(name);
+ Py_XDECREF(lineno);
+ Py_XDECREF(filename);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* The first frame always points at <module>, skip it. */
+ if (*tb_depth > 0)
+ {
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ char *proname;
+ char *fname;
+ char *line;
+ char *plain_filename;
+ long plain_lineno;
+
+ /*
+ * The second frame points at the internal function, but to mimic
+ * Python error reporting we want to say <module>.
+ */
+ if (*tb_depth == 1)
+ fname = "<module>";
+ else
+ fname = PLyUnicode_AsString(name);
+
+ proname = PLy_procedure_name(exec_ctx->curr_proc);
+ plain_filename = PLyUnicode_AsString(filename);
+ plain_lineno = PyLong_AsLong(lineno);
+
+ if (proname == NULL)
+ appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s",
+ plain_lineno - 1, fname);
+ else
+ appendStringInfo(&tbstr, "\n PL/Python function \"%s\", line %ld, in %s",
+ proname, plain_lineno - 1, fname);
+
+ /*
+ * function code object was compiled with "<string>" as the
+ * filename
+ */
+ if (exec_ctx->curr_proc && plain_filename != NULL &&
+ strcmp(plain_filename, "<string>") == 0)
+ {
+ /*
+ * If we know the current procedure, append the exact line
+ * from the source, again mimicking Python's traceback.py
+ * module behavior. We could store the already line-split
+ * source to avoid splitting it every time, but producing a
+ * traceback is not the most important scenario to optimize
+ * for. But we do not go as far as traceback.py in reading
+ * the source of imported modules.
+ */
+ line = get_source_line(exec_ctx->curr_proc->src, plain_lineno);
+ if (line)
+ {
+ appendStringInfo(&tbstr, "\n %s", line);
+ pfree(line);
+ }
+ }
+ }
+
+ Py_DECREF(frame);
+ Py_DECREF(code);
+ Py_DECREF(name);
+ Py_DECREF(lineno);
+ Py_DECREF(filename);
+
+ /* Release the current frame and go to the next one. */
+ tb_prev = tb;
+ tb = PyObject_GetAttrString(tb, "tb_next");
+ Assert(tb_prev != Py_None);
+ Py_DECREF(tb_prev);
+ if (tb == NULL)
+ elog(ERROR, "could not traverse Python traceback");
+ (*tb_depth)++;
+ }
+
+ /* Return the traceback. */
+ *tbmsg = tbstr.data;
+
+ Py_XDECREF(e_type_o);
+ Py_XDECREF(e_module_o);
+ Py_XDECREF(vob);
+}
+
+/*
+ * Extract error code from SPIError's sqlstate attribute.
+ */
+static void
+PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode)
+{
+ PyObject *sqlstate;
+ char *buffer;
+
+ sqlstate = PyObject_GetAttrString(exc, "sqlstate");
+ if (sqlstate == NULL)
+ return;
+
+ buffer = PLyUnicode_AsString(sqlstate);
+ if (strlen(buffer) == 5 &&
+ strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5)
+ {
+ *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2],
+ buffer[3], buffer[4]);
+ }
+
+ Py_DECREF(sqlstate);
+}
+
+/*
+ * Extract the error data from a SPIError
+ */
+static void
+PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail,
+ char **hint, char **query, int *position,
+ char **schema_name, char **table_name,
+ char **column_name,
+ char **datatype_name, char **constraint_name)
+{
+ PyObject *spidata;
+
+ spidata = PyObject_GetAttrString(exc, "spidata");
+
+ if (spidata != NULL)
+ {
+ PyArg_ParseTuple(spidata, "izzzizzzzz",
+ sqlerrcode, detail, hint, query, position,
+ schema_name, table_name, column_name,
+ datatype_name, constraint_name);
+ }
+ else
+ {
+ /*
+ * If there's no spidata, at least set the sqlerrcode. This can happen
+ * if someone explicitly raises a SPI exception from Python code.
+ */
+ PLy_get_sqlerrcode(exc, sqlerrcode);
+ }
+
+ Py_XDECREF(spidata);
+}
+
+/*
+ * Extract the error data from an Error.
+ *
+ * Note: position and query attributes are never set for Error so, unlike
+ * PLy_get_spi_error_data, this function doesn't return them.
+ */
+static void
+PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint,
+ char **schema_name, char **table_name, char **column_name,
+ char **datatype_name, char **constraint_name)
+{
+ PLy_get_sqlerrcode(exc, sqlerrcode);
+ get_string_attr(exc, "detail", detail);
+ get_string_attr(exc, "hint", hint);
+ get_string_attr(exc, "schema_name", schema_name);
+ get_string_attr(exc, "table_name", table_name);
+ get_string_attr(exc, "column_name", column_name);
+ get_string_attr(exc, "datatype_name", datatype_name);
+ get_string_attr(exc, "constraint_name", constraint_name);
+}
+
+/*
+ * Get the given source line as a palloc'd string
+ */
+static char *
+get_source_line(const char *src, int lineno)
+{
+ const char *s = NULL;
+ const char *next = src;
+ int current = 0;
+
+ /* sanity check */
+ if (lineno <= 0)
+ return NULL;
+
+ while (current < lineno)
+ {
+ s = next;
+ next = strchr(s + 1, '\n');
+ current++;
+ if (next == NULL)
+ break;
+ }
+
+ if (current != lineno)
+ return NULL;
+
+ while (*s && isspace((unsigned char) *s))
+ s++;
+
+ if (next == NULL)
+ return pstrdup(s);
+
+ /*
+ * Sanity check, next < s if the line was all-whitespace, which should
+ * never happen if Python reported a frame created on that line, but check
+ * anyway.
+ */
+ if (next < s)
+ return NULL;
+
+ return pnstrdup(s, next - s);
+}
+
+
+/* call PyErr_SetString with a vprint interface and translation support */
+void
+PLy_exception_set(PyObject *exc, const char *fmt,...)
+{
+ char buf[1024];
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap);
+ va_end(ap);
+
+ PyErr_SetString(exc, buf);
+}
+
+/* same, with pluralized message */
+void
+PLy_exception_set_plural(PyObject *exc,
+ const char *fmt_singular, const char *fmt_plural,
+ unsigned long n,...)
+{
+ char buf[1024];
+ va_list ap;
+
+ va_start(ap, n);
+ vsnprintf(buf, sizeof(buf),
+ dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n),
+ ap);
+ va_end(ap);
+
+ PyErr_SetString(exc, buf);
+}
+
+/* set attributes of the given exception to details from ErrorData */
+void
+PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata)
+{
+ PyObject *args = NULL;
+ PyObject *error = NULL;
+
+ args = Py_BuildValue("(s)", edata->message);
+ if (!args)
+ goto failure;
+
+ /* create a new exception with the error message as the parameter */
+ error = PyObject_CallObject(excclass, args);
+ if (!error)
+ goto failure;
+
+ if (!set_string_attr(error, "sqlstate",
+ unpack_sql_state(edata->sqlerrcode)))
+ goto failure;
+
+ if (!set_string_attr(error, "detail", edata->detail))
+ goto failure;
+
+ if (!set_string_attr(error, "hint", edata->hint))
+ goto failure;
+
+ if (!set_string_attr(error, "query", edata->internalquery))
+ goto failure;
+
+ if (!set_string_attr(error, "schema_name", edata->schema_name))
+ goto failure;
+
+ if (!set_string_attr(error, "table_name", edata->table_name))
+ goto failure;
+
+ if (!set_string_attr(error, "column_name", edata->column_name))
+ goto failure;
+
+ if (!set_string_attr(error, "datatype_name", edata->datatype_name))
+ goto failure;
+
+ if (!set_string_attr(error, "constraint_name", edata->constraint_name))
+ goto failure;
+
+ PyErr_SetObject(excclass, error);
+
+ Py_DECREF(args);
+ Py_DECREF(error);
+
+ return;
+
+failure:
+ Py_XDECREF(args);
+ Py_XDECREF(error);
+
+ elog(ERROR, "could not convert error to Python exception");
+}
+
+/* get string value of an object attribute */
+static void
+get_string_attr(PyObject *obj, char *attrname, char **str)
+{
+ PyObject *val;
+
+ val = PyObject_GetAttrString(obj, attrname);
+ if (val != NULL && val != Py_None)
+ {
+ *str = pstrdup(PLyUnicode_AsString(val));
+ }
+ Py_XDECREF(val);
+}
+
+/* set an object attribute to a string value, returns true when the set was
+ * successful
+ */
+static bool
+set_string_attr(PyObject *obj, char *attrname, char *str)
+{
+ int result;
+ PyObject *val;
+
+ if (str != NULL)
+ {
+ val = PLyUnicode_FromString(str);
+ if (!val)
+ return false;
+ }
+ else
+ {
+ val = Py_None;
+ Py_INCREF(Py_None);
+ }
+
+ result = PyObject_SetAttrString(obj, attrname, val);
+ Py_DECREF(val);
+
+ return result != -1;
+}
diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h
new file mode 100644
index 0000000..e02ef4f
--- /dev/null
+++ b/src/pl/plpython/plpy_elog.h
@@ -0,0 +1,46 @@
+/*
+ * src/pl/plpython/plpy_elog.h
+ */
+
+#ifndef PLPY_ELOG_H
+#define PLPY_ELOG_H
+
+#include "plpython.h"
+
+/* global exception classes */
+extern PyObject *PLy_exc_error;
+extern PyObject *PLy_exc_fatal;
+extern PyObject *PLy_exc_spi_error;
+
+/*
+ * PLy_elog()
+ *
+ * See comments at elog() about the compiler hinting.
+ */
+#ifdef HAVE__BUILTIN_CONSTANT_P
+#define PLy_elog(elevel, ...) \
+ do { \
+ PLy_elog_impl(elevel, __VA_ARGS__); \
+ if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \
+ pg_unreachable(); \
+ } while(0)
+#else /* !HAVE__BUILTIN_CONSTANT_P */
+#define PLy_elog(elevel, ...) \
+ do { \
+ const int elevel_ = (elevel); \
+ PLy_elog_impl(elevel_, __VA_ARGS__); \
+ if (elevel_ >= ERROR) \
+ pg_unreachable(); \
+ } while(0)
+#endif /* HAVE__BUILTIN_CONSTANT_P */
+
+extern void PLy_elog_impl(int elevel, const char *fmt,...) pg_attribute_printf(2, 3);
+
+extern void PLy_exception_set(PyObject *exc, const char *fmt,...) pg_attribute_printf(2, 3);
+
+extern void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural,
+ unsigned long n,...) pg_attribute_printf(2, 5) pg_attribute_printf(3, 5);
+
+extern void PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata);
+
+#endif /* PLPY_ELOG_H */
diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c
new file mode 100644
index 0000000..993a4e2
--- /dev/null
+++ b/src/pl/plpython/plpy_exec.c
@@ -0,0 +1,1100 @@
+/*
+ * executing Python code
+ *
+ * src/pl/plpython/plpy_exec.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "funcapi.h"
+#include "plpy_elog.h"
+#include "plpy_exec.h"
+#include "plpy_main.h"
+#include "plpy_procedure.h"
+#include "plpy_subxactobject.h"
+#include "plpython.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/rel.h"
+#include "utils/typcache.h"
+
+/* saved state for a set-returning function */
+typedef struct PLySRFState
+{
+ PyObject *iter; /* Python iterator producing results */
+ PLySavedArgs *savedargs; /* function argument values */
+ MemoryContextCallback callback; /* for releasing refcounts when done */
+} PLySRFState;
+
+static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc);
+static PLySavedArgs *PLy_function_save_args(PLyProcedure *proc);
+static void PLy_function_restore_args(PLyProcedure *proc, PLySavedArgs *savedargs);
+static void PLy_function_drop_args(PLySavedArgs *savedargs);
+static void PLy_global_args_push(PLyProcedure *proc);
+static void PLy_global_args_pop(PLyProcedure *proc);
+static void plpython_srf_cleanup_callback(void *arg);
+static void plpython_return_error_callback(void *arg);
+
+static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc,
+ HeapTuple *rv);
+static HeapTuple PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd,
+ TriggerData *tdata, HeapTuple otup);
+static void plpython_trigger_error_callback(void *arg);
+
+static PyObject *PLy_procedure_call(PLyProcedure *proc, const char *kargs, PyObject *vargs);
+static void PLy_abort_open_subtransactions(int save_subxact_level);
+
+
+/* function subhandler */
+Datum
+PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc)
+{
+ bool is_setof = proc->is_setof;
+ Datum rv;
+ PyObject *volatile plargs = NULL;
+ PyObject *volatile plrv = NULL;
+ FuncCallContext *volatile funcctx = NULL;
+ PLySRFState *volatile srfstate = NULL;
+ ErrorContextCallback plerrcontext;
+
+ /*
+ * If the function is called recursively, we must push outer-level
+ * arguments into the stack. This must be immediately before the PG_TRY
+ * to ensure that the corresponding pop happens.
+ */
+ PLy_global_args_push(proc);
+
+ PG_TRY();
+ {
+ if (is_setof)
+ {
+ /* First Call setup */
+ if (SRF_IS_FIRSTCALL())
+ {
+ funcctx = SRF_FIRSTCALL_INIT();
+ srfstate = (PLySRFState *)
+ MemoryContextAllocZero(funcctx->multi_call_memory_ctx,
+ sizeof(PLySRFState));
+ /* Immediately register cleanup callback */
+ srfstate->callback.func = plpython_srf_cleanup_callback;
+ srfstate->callback.arg = (void *) srfstate;
+ MemoryContextRegisterResetCallback(funcctx->multi_call_memory_ctx,
+ &srfstate->callback);
+ funcctx->user_fctx = (void *) srfstate;
+ }
+ /* Every call setup */
+ funcctx = SRF_PERCALL_SETUP();
+ Assert(funcctx != NULL);
+ srfstate = (PLySRFState *) funcctx->user_fctx;
+ Assert(srfstate != NULL);
+ }
+
+ if (srfstate == NULL || srfstate->iter == NULL)
+ {
+ /*
+ * Non-SETOF function or first time for SETOF function: build
+ * args, then actually execute the function.
+ */
+ plargs = PLy_function_build_args(fcinfo, proc);
+ plrv = PLy_procedure_call(proc, "args", plargs);
+ Assert(plrv != NULL);
+ }
+ else
+ {
+ /*
+ * Second or later call for a SETOF function: restore arguments in
+ * globals dict to what they were when we left off. We must do
+ * this in case multiple evaluations of the same SETOF function
+ * are interleaved. It's a bit annoying, since the iterator may
+ * not look at the arguments at all, but we have no way to know
+ * that. Fortunately this isn't terribly expensive.
+ */
+ if (srfstate->savedargs)
+ PLy_function_restore_args(proc, srfstate->savedargs);
+ srfstate->savedargs = NULL; /* deleted by restore_args */
+ }
+
+ /*
+ * If it returns a set, call the iterator to get the next return item.
+ * We stay in the SPI context while doing this, because PyIter_Next()
+ * calls back into Python code which might contain SPI calls.
+ */
+ if (is_setof)
+ {
+ if (srfstate->iter == NULL)
+ {
+ /* first time -- do checks and setup */
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo) ||
+ (rsi->allowedModes & SFRM_ValuePerCall) == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("unsupported set function return mode"),
+ errdetail("PL/Python set-returning functions only support returning one value per call.")));
+ }
+ rsi->returnMode = SFRM_ValuePerCall;
+
+ /* Make iterator out of returned object */
+ srfstate->iter = PyObject_GetIter(plrv);
+
+ Py_DECREF(plrv);
+ plrv = NULL;
+
+ if (srfstate->iter == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("returned object cannot be iterated"),
+ errdetail("PL/Python set-returning functions must return an iterable object.")));
+ }
+
+ /* Fetch next from iterator */
+ plrv = PyIter_Next(srfstate->iter);
+ if (plrv == NULL)
+ {
+ /* Iterator is exhausted or error happened */
+ bool has_error = (PyErr_Occurred() != NULL);
+
+ Py_DECREF(srfstate->iter);
+ srfstate->iter = NULL;
+
+ if (has_error)
+ PLy_elog(ERROR, "error fetching next item from iterator");
+
+ /* Pass a null through the data-returning steps below */
+ Py_INCREF(Py_None);
+ plrv = Py_None;
+ }
+ else
+ {
+ /*
+ * This won't be last call, so save argument values. We do
+ * this again each time in case the iterator is changing those
+ * values.
+ */
+ srfstate->savedargs = PLy_function_save_args(proc);
+ }
+ }
+
+ /*
+ * Disconnect from SPI manager and then create the return values datum
+ * (if the input function does a palloc for it this must not be
+ * allocated in the SPI memory context because SPI_finish would free
+ * it).
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ plerrcontext.callback = plpython_return_error_callback;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ /*
+ * For a procedure or function declared to return void, the Python
+ * return value must be None. For void-returning functions, we also
+ * treat a None return value as a special "void datum" rather than
+ * NULL (as is the case for non-void-returning functions).
+ */
+ if (proc->result.typoid == VOIDOID)
+ {
+ if (plrv != Py_None)
+ {
+ if (proc->is_procedure)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("PL/Python procedure did not return None")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("PL/Python function with return type \"void\" did not return None")));
+ }
+
+ fcinfo->isnull = false;
+ rv = (Datum) 0;
+ }
+ else if (plrv == Py_None &&
+ srfstate && srfstate->iter == NULL)
+ {
+ /*
+ * In a SETOF function, the iteration-ending null isn't a real
+ * value; don't pass it through the input function, which might
+ * complain.
+ */
+ fcinfo->isnull = true;
+ rv = (Datum) 0;
+ }
+ else
+ {
+ /* Normal conversion of result */
+ rv = PLy_output_convert(&proc->result, plrv,
+ &fcinfo->isnull);
+ }
+ }
+ PG_CATCH();
+ {
+ /* Pop old arguments from the stack if they were pushed above */
+ PLy_global_args_pop(proc);
+
+ Py_XDECREF(plargs);
+ Py_XDECREF(plrv);
+
+ /*
+ * If there was an error within a SRF, the iterator might not have
+ * been exhausted yet. Clear it so the next invocation of the
+ * function will start the iteration again. (This code is probably
+ * unnecessary now; plpython_srf_cleanup_callback should take care of
+ * cleanup. But it doesn't hurt anything to do it here.)
+ */
+ if (srfstate)
+ {
+ Py_XDECREF(srfstate->iter);
+ srfstate->iter = NULL;
+ /* And drop any saved args; we won't need them */
+ if (srfstate->savedargs)
+ PLy_function_drop_args(srfstate->savedargs);
+ srfstate->savedargs = NULL;
+ }
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ error_context_stack = plerrcontext.previous;
+
+ /* Pop old arguments from the stack if they were pushed above */
+ PLy_global_args_pop(proc);
+
+ Py_XDECREF(plargs);
+ Py_DECREF(plrv);
+
+ if (srfstate)
+ {
+ /* We're in a SRF, exit appropriately */
+ if (srfstate->iter == NULL)
+ {
+ /* Iterator exhausted, so we're done */
+ SRF_RETURN_DONE(funcctx);
+ }
+ else if (fcinfo->isnull)
+ SRF_RETURN_NEXT_NULL(funcctx);
+ else
+ SRF_RETURN_NEXT(funcctx, rv);
+ }
+
+ /* Plain function, just return the Datum value (possibly null) */
+ return rv;
+}
+
+/* trigger subhandler
+ *
+ * the python function is expected to return Py_None if the tuple is
+ * acceptable and unmodified. Otherwise it should return a PyUnicode
+ * object who's value is SKIP, or MODIFY. SKIP means don't perform
+ * this action. MODIFY means the tuple has been modified, so update
+ * tuple and perform action. SKIP and MODIFY assume the trigger fires
+ * BEFORE the event and is ROW level. postgres expects the function
+ * to take no arguments and return an argument of type trigger.
+ */
+HeapTuple
+PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc)
+{
+ HeapTuple rv = NULL;
+ PyObject *volatile plargs = NULL;
+ PyObject *volatile plrv = NULL;
+ TriggerData *tdata;
+ TupleDesc rel_descr;
+
+ Assert(CALLED_AS_TRIGGER(fcinfo));
+ tdata = (TriggerData *) fcinfo->context;
+
+ /*
+ * Input/output conversion for trigger tuples. We use the result and
+ * result_in fields to store the tuple conversion info. We do this over
+ * again on each call to cover the possibility that the relation's tupdesc
+ * changed since the trigger was last called. The PLy_xxx_setup_func
+ * calls should only happen once, but PLy_input_setup_tuple and
+ * PLy_output_setup_tuple are responsible for not doing repetitive work.
+ */
+ rel_descr = RelationGetDescr(tdata->tg_relation);
+ if (proc->result.typoid != rel_descr->tdtypeid)
+ PLy_output_setup_func(&proc->result, proc->mcxt,
+ rel_descr->tdtypeid,
+ rel_descr->tdtypmod,
+ proc);
+ if (proc->result_in.typoid != rel_descr->tdtypeid)
+ PLy_input_setup_func(&proc->result_in, proc->mcxt,
+ rel_descr->tdtypeid,
+ rel_descr->tdtypmod,
+ proc);
+ PLy_output_setup_tuple(&proc->result, rel_descr, proc);
+ PLy_input_setup_tuple(&proc->result_in, rel_descr, proc);
+
+ PG_TRY();
+ {
+ int rc PG_USED_FOR_ASSERTS_ONLY;
+
+ rc = SPI_register_trigger_data(tdata);
+ Assert(rc >= 0);
+
+ plargs = PLy_trigger_build_args(fcinfo, proc, &rv);
+ plrv = PLy_procedure_call(proc, "TD", plargs);
+
+ Assert(plrv != NULL);
+
+ /*
+ * Disconnect from SPI manager
+ */
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish failed");
+
+ /*
+ * return of None means we're happy with the tuple
+ */
+ if (plrv != Py_None)
+ {
+ char *srv;
+
+ if (PyUnicode_Check(plrv))
+ srv = PLyUnicode_AsString(plrv);
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("unexpected return value from trigger procedure"),
+ errdetail("Expected None or a string.")));
+ srv = NULL; /* keep compiler quiet */
+ }
+
+ if (pg_strcasecmp(srv, "SKIP") == 0)
+ rv = NULL;
+ else if (pg_strcasecmp(srv, "MODIFY") == 0)
+ {
+ TriggerData *tdata = (TriggerData *) fcinfo->context;
+
+ if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) ||
+ TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
+ rv = PLy_modify_tuple(proc, plargs, tdata, rv);
+ else
+ ereport(WARNING,
+ (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored")));
+ }
+ else if (pg_strcasecmp(srv, "OK") != 0)
+ {
+ /*
+ * accept "OK" as an alternative to None; otherwise, raise an
+ * error
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_DATA_EXCEPTION),
+ errmsg("unexpected return value from trigger procedure"),
+ errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\".")));
+ }
+ }
+ }
+ PG_FINALLY();
+ {
+ Py_XDECREF(plargs);
+ Py_XDECREF(plrv);
+ }
+ PG_END_TRY();
+
+ return rv;
+}
+
+/* helper functions for Python code execution */
+
+static PyObject *
+PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc)
+{
+ PyObject *volatile arg = NULL;
+ PyObject *args;
+ int i;
+
+ /*
+ * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+ * return NULL on failure. We can't return within the PG_TRY block, else
+ * we'd miss unwinding the exception stack.
+ */
+ args = PyList_New(proc->nargs);
+ if (!args)
+ return NULL;
+
+ PG_TRY();
+ {
+ for (i = 0; i < proc->nargs; i++)
+ {
+ PLyDatumToOb *arginfo = &proc->args[i];
+
+ if (fcinfo->args[i].isnull)
+ arg = NULL;
+ else
+ arg = PLy_input_convert(arginfo, fcinfo->args[i].value);
+
+ if (arg == NULL)
+ {
+ Py_INCREF(Py_None);
+ arg = Py_None;
+ }
+
+ if (PyList_SetItem(args, i, arg) == -1)
+ PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments");
+
+ if (proc->argnames && proc->argnames[i] &&
+ PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1)
+ PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments");
+ arg = NULL;
+ }
+
+ /* Set up output conversion for functions returning RECORD */
+ if (proc->result.typoid == RECORDOID)
+ {
+ TupleDesc desc;
+
+ if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("function returning record called in context "
+ "that cannot accept type record")));
+
+ /* cache the output conversion functions */
+ PLy_output_setup_record(&proc->result, desc, proc);
+ }
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(arg);
+ Py_XDECREF(args);
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return args;
+}
+
+/*
+ * Construct a PLySavedArgs struct representing the current values of the
+ * procedure's arguments in its globals dict. This can be used to restore
+ * those values when exiting a recursive call level or returning control to a
+ * set-returning function.
+ *
+ * This would not be necessary except for an ancient decision to make args
+ * available via the proc's globals :-( ... but we're stuck with that now.
+ */
+static PLySavedArgs *
+PLy_function_save_args(PLyProcedure *proc)
+{
+ PLySavedArgs *result;
+
+ /* saved args are always allocated in procedure's context */
+ result = (PLySavedArgs *)
+ MemoryContextAllocZero(proc->mcxt,
+ offsetof(PLySavedArgs, namedargs) +
+ proc->nargs * sizeof(PyObject *));
+ result->nargs = proc->nargs;
+
+ /* Fetch the "args" list */
+ result->args = PyDict_GetItemString(proc->globals, "args");
+ Py_XINCREF(result->args);
+
+ /* Fetch all the named arguments */
+ if (proc->argnames)
+ {
+ int i;
+
+ for (i = 0; i < result->nargs; i++)
+ {
+ if (proc->argnames[i])
+ {
+ result->namedargs[i] = PyDict_GetItemString(proc->globals,
+ proc->argnames[i]);
+ Py_XINCREF(result->namedargs[i]);
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * Restore procedure's arguments from a PLySavedArgs struct,
+ * then free the struct.
+ */
+static void
+PLy_function_restore_args(PLyProcedure *proc, PLySavedArgs *savedargs)
+{
+ /* Restore named arguments into their slots in the globals dict */
+ if (proc->argnames)
+ {
+ int i;
+
+ for (i = 0; i < savedargs->nargs; i++)
+ {
+ if (proc->argnames[i] && savedargs->namedargs[i])
+ {
+ PyDict_SetItemString(proc->globals, proc->argnames[i],
+ savedargs->namedargs[i]);
+ Py_DECREF(savedargs->namedargs[i]);
+ }
+ }
+ }
+
+ /* Restore the "args" object, too */
+ if (savedargs->args)
+ {
+ PyDict_SetItemString(proc->globals, "args", savedargs->args);
+ Py_DECREF(savedargs->args);
+ }
+
+ /* And free the PLySavedArgs struct */
+ pfree(savedargs);
+}
+
+/*
+ * Free a PLySavedArgs struct without restoring the values.
+ */
+static void
+PLy_function_drop_args(PLySavedArgs *savedargs)
+{
+ int i;
+
+ /* Drop references for named args */
+ for (i = 0; i < savedargs->nargs; i++)
+ {
+ Py_XDECREF(savedargs->namedargs[i]);
+ }
+
+ /* Drop ref to the "args" object, too */
+ Py_XDECREF(savedargs->args);
+
+ /* And free the PLySavedArgs struct */
+ pfree(savedargs);
+}
+
+/*
+ * Save away any existing arguments for the given procedure, so that we can
+ * install new values for a recursive call. This should be invoked before
+ * doing PLy_function_build_args().
+ *
+ * NB: caller must ensure that PLy_global_args_pop gets invoked once, and
+ * only once, per successful completion of PLy_global_args_push. Otherwise
+ * we'll end up out-of-sync between the actual call stack and the contents
+ * of proc->argstack.
+ */
+static void
+PLy_global_args_push(PLyProcedure *proc)
+{
+ /* We only need to push if we are already inside some active call */
+ if (proc->calldepth > 0)
+ {
+ PLySavedArgs *node;
+
+ /* Build a struct containing current argument values */
+ node = PLy_function_save_args(proc);
+
+ /*
+ * Push the saved argument values into the procedure's stack. Once we
+ * modify either proc->argstack or proc->calldepth, we had better
+ * return without the possibility of error.
+ */
+ node->next = proc->argstack;
+ proc->argstack = node;
+ }
+ proc->calldepth++;
+}
+
+/*
+ * Pop old arguments when exiting a recursive call.
+ *
+ * Note: the idea here is to adjust the proc's callstack state before doing
+ * anything that could possibly fail. In event of any error, we want the
+ * callstack to look like we've done the pop. Leaking a bit of memory is
+ * tolerable.
+ */
+static void
+PLy_global_args_pop(PLyProcedure *proc)
+{
+ Assert(proc->calldepth > 0);
+ /* We only need to pop if we were already inside some active call */
+ if (proc->calldepth > 1)
+ {
+ PLySavedArgs *ptr = proc->argstack;
+
+ /* Pop the callstack */
+ Assert(ptr != NULL);
+ proc->argstack = ptr->next;
+ proc->calldepth--;
+
+ /* Restore argument values, then free ptr */
+ PLy_function_restore_args(proc, ptr);
+ }
+ else
+ {
+ /* Exiting call depth 1 */
+ Assert(proc->argstack == NULL);
+ proc->calldepth--;
+
+ /*
+ * We used to delete the named arguments (but not "args") from the
+ * proc's globals dict when exiting the outermost call level for a
+ * function. This seems rather pointless though: nothing can see the
+ * dict until the function is called again, at which time we'll
+ * overwrite those dict entries. So don't bother with that.
+ */
+ }
+}
+
+/*
+ * Memory context deletion callback for cleaning up a PLySRFState.
+ * We need this in case execution of the SRF is terminated early,
+ * due to error or the caller simply not running it to completion.
+ */
+static void
+plpython_srf_cleanup_callback(void *arg)
+{
+ PLySRFState *srfstate = (PLySRFState *) arg;
+
+ /* Release refcount on the iter, if we still have one */
+ Py_XDECREF(srfstate->iter);
+ srfstate->iter = NULL;
+ /* And drop any saved args; we won't need them */
+ if (srfstate->savedargs)
+ PLy_function_drop_args(srfstate->savedargs);
+ srfstate->savedargs = NULL;
+}
+
+static void
+plpython_return_error_callback(void *arg)
+{
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+ if (exec_ctx->curr_proc &&
+ !exec_ctx->curr_proc->is_procedure)
+ errcontext("while creating return value");
+}
+
+static PyObject *
+PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv)
+{
+ TriggerData *tdata = (TriggerData *) fcinfo->context;
+ TupleDesc rel_descr = RelationGetDescr(tdata->tg_relation);
+ PyObject *pltname,
+ *pltevent,
+ *pltwhen,
+ *pltlevel,
+ *pltrelid,
+ *plttablename,
+ *plttableschema,
+ *pltargs = NULL,
+ *pytnew,
+ *pytold,
+ *pltdata;
+ char *stroid;
+
+ /*
+ * Make any Py*_New() calls before the PG_TRY block so that we can quickly
+ * return NULL on failure. We can't return within the PG_TRY block, else
+ * we'd miss unwinding the exception stack.
+ */
+ pltdata = PyDict_New();
+ if (!pltdata)
+ return NULL;
+
+ if (tdata->tg_trigger->tgnargs)
+ {
+ pltargs = PyList_New(tdata->tg_trigger->tgnargs);
+ if (!pltargs)
+ {
+ Py_DECREF(pltdata);
+ return NULL;
+ }
+ }
+
+ PG_TRY();
+ {
+ pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname);
+ PyDict_SetItemString(pltdata, "name", pltname);
+ Py_DECREF(pltname);
+
+ stroid = DatumGetCString(DirectFunctionCall1(oidout,
+ ObjectIdGetDatum(tdata->tg_relation->rd_id)));
+ pltrelid = PLyUnicode_FromString(stroid);
+ PyDict_SetItemString(pltdata, "relid", pltrelid);
+ Py_DECREF(pltrelid);
+ pfree(stroid);
+
+ stroid = SPI_getrelname(tdata->tg_relation);
+ plttablename = PLyUnicode_FromString(stroid);
+ PyDict_SetItemString(pltdata, "table_name", plttablename);
+ Py_DECREF(plttablename);
+ pfree(stroid);
+
+ stroid = SPI_getnspname(tdata->tg_relation);
+ plttableschema = PLyUnicode_FromString(stroid);
+ PyDict_SetItemString(pltdata, "table_schema", plttableschema);
+ Py_DECREF(plttableschema);
+ pfree(stroid);
+
+ if (TRIGGER_FIRED_BEFORE(tdata->tg_event))
+ pltwhen = PLyUnicode_FromString("BEFORE");
+ else if (TRIGGER_FIRED_AFTER(tdata->tg_event))
+ pltwhen = PLyUnicode_FromString("AFTER");
+ else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event))
+ pltwhen = PLyUnicode_FromString("INSTEAD OF");
+ else
+ {
+ elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event);
+ pltwhen = NULL; /* keep compiler quiet */
+ }
+ PyDict_SetItemString(pltdata, "when", pltwhen);
+ Py_DECREF(pltwhen);
+
+ if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event))
+ {
+ pltlevel = PLyUnicode_FromString("ROW");
+ PyDict_SetItemString(pltdata, "level", pltlevel);
+ Py_DECREF(pltlevel);
+
+ /*
+ * Note: In BEFORE trigger, stored generated columns are not
+ * computed yet, so don't make them accessible in NEW row.
+ */
+
+ if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
+ {
+ pltevent = PLyUnicode_FromString("INSERT");
+
+ PyDict_SetItemString(pltdata, "old", Py_None);
+ pytnew = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr,
+ !TRIGGER_FIRED_BEFORE(tdata->tg_event));
+ PyDict_SetItemString(pltdata, "new", pytnew);
+ Py_DECREF(pytnew);
+ *rv = tdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
+ {
+ pltevent = PLyUnicode_FromString("DELETE");
+
+ PyDict_SetItemString(pltdata, "new", Py_None);
+ pytold = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr,
+ true);
+ PyDict_SetItemString(pltdata, "old", pytold);
+ Py_DECREF(pytold);
+ *rv = tdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
+ {
+ pltevent = PLyUnicode_FromString("UPDATE");
+
+ pytnew = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_newtuple,
+ rel_descr,
+ !TRIGGER_FIRED_BEFORE(tdata->tg_event));
+ PyDict_SetItemString(pltdata, "new", pytnew);
+ Py_DECREF(pytnew);
+ pytold = PLy_input_from_tuple(&proc->result_in,
+ tdata->tg_trigtuple,
+ rel_descr,
+ true);
+ PyDict_SetItemString(pltdata, "old", pytold);
+ Py_DECREF(pytold);
+ *rv = tdata->tg_newtuple;
+ }
+ else
+ {
+ elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
+ pltevent = NULL; /* keep compiler quiet */
+ }
+
+ PyDict_SetItemString(pltdata, "event", pltevent);
+ Py_DECREF(pltevent);
+ }
+ else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event))
+ {
+ pltlevel = PLyUnicode_FromString("STATEMENT");
+ PyDict_SetItemString(pltdata, "level", pltlevel);
+ Py_DECREF(pltlevel);
+
+ PyDict_SetItemString(pltdata, "old", Py_None);
+ PyDict_SetItemString(pltdata, "new", Py_None);
+ *rv = NULL;
+
+ if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event))
+ pltevent = PLyUnicode_FromString("INSERT");
+ else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event))
+ pltevent = PLyUnicode_FromString("DELETE");
+ else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event))
+ pltevent = PLyUnicode_FromString("UPDATE");
+ else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event))
+ pltevent = PLyUnicode_FromString("TRUNCATE");
+ else
+ {
+ elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event);
+ pltevent = NULL; /* keep compiler quiet */
+ }
+
+ PyDict_SetItemString(pltdata, "event", pltevent);
+ Py_DECREF(pltevent);
+ }
+ else
+ elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event);
+
+ if (tdata->tg_trigger->tgnargs)
+ {
+ /*
+ * all strings...
+ */
+ int i;
+ PyObject *pltarg;
+
+ /* pltargs should have been allocated before the PG_TRY block. */
+ Assert(pltargs);
+
+ for (i = 0; i < tdata->tg_trigger->tgnargs; i++)
+ {
+ pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]);
+
+ /*
+ * stolen, don't Py_DECREF
+ */
+ PyList_SetItem(pltargs, i, pltarg);
+ }
+ }
+ else
+ {
+ Py_INCREF(Py_None);
+ pltargs = Py_None;
+ }
+ PyDict_SetItemString(pltdata, "args", pltargs);
+ Py_DECREF(pltargs);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(pltargs);
+ Py_XDECREF(pltdata);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return pltdata;
+}
+
+/*
+ * Apply changes requested by a MODIFY return from a trigger function.
+ */
+static HeapTuple
+PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata,
+ HeapTuple otup)
+{
+ HeapTuple rtup;
+ PyObject *volatile plntup;
+ PyObject *volatile plkeys;
+ PyObject *volatile plval;
+ Datum *volatile modvalues;
+ bool *volatile modnulls;
+ bool *volatile modrepls;
+ ErrorContextCallback plerrcontext;
+
+ plerrcontext.callback = plpython_trigger_error_callback;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ plntup = plkeys = plval = NULL;
+ modvalues = NULL;
+ modnulls = NULL;
+ modrepls = NULL;
+
+ PG_TRY();
+ {
+ TupleDesc tupdesc;
+ int nkeys,
+ i;
+
+ if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("TD[\"new\"] deleted, cannot modify row")));
+ Py_INCREF(plntup);
+ if (!PyDict_Check(plntup))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("TD[\"new\"] is not a dictionary")));
+
+ plkeys = PyDict_Keys(plntup);
+ nkeys = PyList_Size(plkeys);
+
+ tupdesc = RelationGetDescr(tdata->tg_relation);
+
+ modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
+ modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
+ modrepls = (bool *) palloc0(tupdesc->natts * sizeof(bool));
+
+ for (i = 0; i < nkeys; i++)
+ {
+ PyObject *platt;
+ char *plattstr;
+ int attn;
+ PLyObToDatum *att;
+
+ platt = PyList_GetItem(plkeys, i);
+ if (PyUnicode_Check(platt))
+ plattstr = PLyUnicode_AsString(platt);
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i)));
+ plattstr = NULL; /* keep compiler quiet */
+ }
+ attn = SPI_fnumber(tupdesc, plattstr);
+ if (attn == SPI_ERROR_NOATTRIBUTE)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row",
+ plattstr)));
+ if (attn <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set system attribute \"%s\"",
+ plattstr)));
+ if (TupleDescAttr(tupdesc, attn - 1)->attgenerated)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("cannot set generated column \"%s\"",
+ plattstr)));
+
+ plval = PyDict_GetItem(plntup, platt);
+ if (plval == NULL)
+ elog(FATAL, "Python interpreter is probably corrupted");
+
+ Py_INCREF(plval);
+
+ /* We assume proc->result is set up to convert tuples properly */
+ att = &proc->result.u.tuple.atts[attn - 1];
+
+ modvalues[attn - 1] = PLy_output_convert(att,
+ plval,
+ &modnulls[attn - 1]);
+ modrepls[attn - 1] = true;
+
+ Py_DECREF(plval);
+ plval = NULL;
+ }
+
+ rtup = heap_modify_tuple(otup, tupdesc, modvalues, modnulls, modrepls);
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(plntup);
+ Py_XDECREF(plkeys);
+ Py_XDECREF(plval);
+
+ if (modvalues)
+ pfree(modvalues);
+ if (modnulls)
+ pfree(modnulls);
+ if (modrepls)
+ pfree(modrepls);
+
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ Py_DECREF(plntup);
+ Py_DECREF(plkeys);
+
+ pfree(modvalues);
+ pfree(modnulls);
+ pfree(modrepls);
+
+ error_context_stack = plerrcontext.previous;
+
+ return rtup;
+}
+
+static void
+plpython_trigger_error_callback(void *arg)
+{
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+ if (exec_ctx->curr_proc)
+ errcontext("while modifying trigger row");
+}
+
+/* execute Python code, propagate Python errors to the backend */
+static PyObject *
+PLy_procedure_call(PLyProcedure *proc, const char *kargs, PyObject *vargs)
+{
+ PyObject *rv = NULL;
+ int volatile save_subxact_level = list_length(explicit_subtransactions);
+
+ PyDict_SetItemString(proc->globals, kargs, vargs);
+
+ PG_TRY();
+ {
+#if PY_VERSION_HEX >= 0x03020000
+ rv = PyEval_EvalCode(proc->code,
+ proc->globals, proc->globals);
+#else
+ rv = PyEval_EvalCode((PyCodeObject *) proc->code,
+ proc->globals, proc->globals);
+#endif
+
+ /*
+ * Since plpy will only let you close subtransactions that you
+ * started, you cannot *unnest* subtransactions, only *nest* them
+ * without closing.
+ */
+ Assert(list_length(explicit_subtransactions) >= save_subxact_level);
+ }
+ PG_FINALLY();
+ {
+ PLy_abort_open_subtransactions(save_subxact_level);
+ }
+ PG_END_TRY();
+
+ /* If the Python code returned an error, propagate it */
+ if (rv == NULL)
+ PLy_elog(ERROR, NULL);
+
+ return rv;
+}
+
+/*
+ * Abort lingering subtransactions that have been explicitly started
+ * by plpy.subtransaction().start() and not properly closed.
+ */
+static void
+PLy_abort_open_subtransactions(int save_subxact_level)
+{
+ Assert(save_subxact_level >= 0);
+
+ while (list_length(explicit_subtransactions) > save_subxact_level)
+ {
+ PLySubtransactionData *subtransactiondata;
+
+ Assert(explicit_subtransactions != NIL);
+
+ ereport(WARNING,
+ (errmsg("forcibly aborting a subtransaction that has not been exited")));
+
+ RollbackAndReleaseCurrentSubTransaction();
+
+ subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions);
+ explicit_subtransactions = list_delete_first(explicit_subtransactions);
+
+ MemoryContextSwitchTo(subtransactiondata->oldcontext);
+ CurrentResourceOwner = subtransactiondata->oldowner;
+ pfree(subtransactiondata);
+ }
+}
diff --git a/src/pl/plpython/plpy_exec.h b/src/pl/plpython/plpy_exec.h
new file mode 100644
index 0000000..68da1ff
--- /dev/null
+++ b/src/pl/plpython/plpy_exec.h
@@ -0,0 +1,13 @@
+/*
+ * src/pl/plpython/plpy_exec.h
+ */
+
+#ifndef PLPY_EXEC_H
+#define PLPY_EXEC_H
+
+#include "plpy_procedure.h"
+
+extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc);
+extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc);
+
+#endif /* PLPY_EXEC_H */
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
new file mode 100644
index 0000000..0bce106
--- /dev/null
+++ b/src/pl/plpython/plpy_main.c
@@ -0,0 +1,421 @@
+/*
+ * PL/Python main entry points
+ *
+ * src/pl/plpython/plpy_main.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "miscadmin.h"
+#include "plpy_elog.h"
+#include "plpy_exec.h"
+#include "plpy_main.h"
+#include "plpy_plpymodule.h"
+#include "plpy_procedure.h"
+#include "plpy_subxactobject.h"
+#include "plpython.h"
+#include "utils/guc.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+
+/*
+ * exported functions
+ */
+
+extern void _PG_init(void);
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plpython3_validator);
+PG_FUNCTION_INFO_V1(plpython3_call_handler);
+PG_FUNCTION_INFO_V1(plpython3_inline_handler);
+
+
+static bool PLy_procedure_is_trigger(Form_pg_proc procStruct);
+static void plpython_error_callback(void *arg);
+static void plpython_inline_error_callback(void *arg);
+static void PLy_init_interp(void);
+
+static PLyExecutionContext *PLy_push_execution_context(bool atomic_context);
+static void PLy_pop_execution_context(void);
+
+/* static state for Python library conflict detection */
+static int *plpython_version_bitmask_ptr = NULL;
+static int plpython_version_bitmask = 0;
+
+/* initialize global variables */
+PyObject *PLy_interp_globals = NULL;
+
+/* this doesn't need to be global; use PLy_current_execution_context() */
+static PLyExecutionContext *PLy_execution_contexts = NULL;
+
+
+void
+_PG_init(void)
+{
+ int **bitmask_ptr;
+
+ /*
+ * Set up a shared bitmask variable telling which Python version(s) are
+ * loaded into this process's address space. If there's more than one, we
+ * cannot call into libpython for fear of causing crashes. But postpone
+ * the actual failure for later, so that operations like pg_restore can
+ * load more than one plpython library so long as they don't try to do
+ * anything much with the language.
+ *
+ * While we only support Python 3 these days, somebody might create an
+ * out-of-tree version adding back support for Python 2. Conflicts with
+ * such an extension should be detected.
+ */
+ bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask");
+ if (!(*bitmask_ptr)) /* am I the first? */
+ *bitmask_ptr = &plpython_version_bitmask;
+ /* Retain pointer to the agreed-on shared variable ... */
+ plpython_version_bitmask_ptr = *bitmask_ptr;
+ /* ... and announce my presence */
+ *plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION);
+
+ /*
+ * This should be safe even in the presence of conflicting plpythons, and
+ * it's necessary to do it before possibly throwing a conflict error, or
+ * the error message won't get localized.
+ */
+ pg_bindtextdomain(TEXTDOMAIN);
+}
+
+/*
+ * Perform one-time setup of PL/Python, after checking for a conflict
+ * with other versions of Python.
+ */
+static void
+PLy_initialize(void)
+{
+ static bool inited = false;
+
+ /*
+ * Check for multiple Python libraries before actively doing anything with
+ * libpython. This must be repeated on each entry to PL/Python, in case a
+ * conflicting library got loaded since we last looked.
+ *
+ * It is attractive to weaken this error from FATAL to ERROR, but there
+ * would be corner cases, so it seems best to be conservative.
+ */
+ if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION))
+ ereport(FATAL,
+ (errmsg("multiple Python libraries are present in session"),
+ errdetail("Only one Python major version can be used in one session.")));
+
+ /* The rest should only be done once per session */
+ if (inited)
+ return;
+
+ PyImport_AppendInittab("plpy", PyInit_plpy);
+ Py_Initialize();
+ PyImport_ImportModule("plpy");
+ PLy_init_interp();
+ PLy_init_plpy();
+ if (PyErr_Occurred())
+ PLy_elog(FATAL, "untrapped error in initialization");
+
+ init_procedure_caches();
+
+ explicit_subtransactions = NIL;
+
+ PLy_execution_contexts = NULL;
+
+ inited = true;
+}
+
+/*
+ * This should be called only once, from PLy_initialize. Initialize the Python
+ * interpreter and global data.
+ */
+static void
+PLy_init_interp(void)
+{
+ static PyObject *PLy_interp_safe_globals = NULL;
+ PyObject *mainmod;
+
+ mainmod = PyImport_AddModule("__main__");
+ if (mainmod == NULL || PyErr_Occurred())
+ PLy_elog(ERROR, "could not import \"__main__\" module");
+ Py_INCREF(mainmod);
+ PLy_interp_globals = PyModule_GetDict(mainmod);
+ PLy_interp_safe_globals = PyDict_New();
+ if (PLy_interp_safe_globals == NULL)
+ PLy_elog(ERROR, NULL);
+ PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals);
+ Py_DECREF(mainmod);
+ if (PLy_interp_globals == NULL || PyErr_Occurred())
+ PLy_elog(ERROR, "could not initialize globals");
+}
+
+Datum
+plpython3_validator(PG_FUNCTION_ARGS)
+{
+ Oid funcoid = PG_GETARG_OID(0);
+ HeapTuple tuple;
+ Form_pg_proc procStruct;
+ bool is_trigger;
+
+ if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid))
+ PG_RETURN_VOID();
+
+ if (!check_function_bodies)
+ PG_RETURN_VOID();
+
+ /* Do this only after making sure we need to do something */
+ PLy_initialize();
+
+ /* Get the new function's pg_proc entry */
+ tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "cache lookup failed for function %u", funcoid);
+ procStruct = (Form_pg_proc) GETSTRUCT(tuple);
+
+ is_trigger = PLy_procedure_is_trigger(procStruct);
+
+ ReleaseSysCache(tuple);
+
+ /* We can't validate triggers against any particular table ... */
+ PLy_procedure_get(funcoid, InvalidOid, is_trigger);
+
+ PG_RETURN_VOID();
+}
+
+Datum
+plpython3_call_handler(PG_FUNCTION_ARGS)
+{
+ bool nonatomic;
+ Datum retval;
+ PLyExecutionContext *exec_ctx;
+ ErrorContextCallback plerrcontext;
+
+ PLy_initialize();
+
+ nonatomic = fcinfo->context &&
+ IsA(fcinfo->context, CallContext) &&
+ !castNode(CallContext, fcinfo->context)->atomic;
+
+ /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
+ if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ /*
+ * Push execution context onto stack. It is important that this get
+ * popped again, so avoid putting anything that could throw error between
+ * here and the PG_TRY.
+ */
+ exec_ctx = PLy_push_execution_context(!nonatomic);
+
+ PG_TRY();
+ {
+ Oid funcoid = fcinfo->flinfo->fn_oid;
+ PLyProcedure *proc;
+
+ /*
+ * Setup error traceback support for ereport(). Note that the PG_TRY
+ * structure pops this for us again at exit, so we needn't do that
+ * explicitly, nor do we risk the callback getting called after we've
+ * destroyed the exec_ctx.
+ */
+ plerrcontext.callback = plpython_error_callback;
+ plerrcontext.arg = exec_ctx;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ if (CALLED_AS_TRIGGER(fcinfo))
+ {
+ Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation;
+ HeapTuple trv;
+
+ proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true);
+ exec_ctx->curr_proc = proc;
+ trv = PLy_exec_trigger(fcinfo, proc);
+ retval = PointerGetDatum(trv);
+ }
+ else
+ {
+ proc = PLy_procedure_get(funcoid, InvalidOid, false);
+ exec_ctx->curr_proc = proc;
+ retval = PLy_exec_function(fcinfo, proc);
+ }
+ }
+ PG_CATCH();
+ {
+ PLy_pop_execution_context();
+ PyErr_Clear();
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* Destroy the execution context */
+ PLy_pop_execution_context();
+
+ return retval;
+}
+
+Datum
+plpython3_inline_handler(PG_FUNCTION_ARGS)
+{
+ LOCAL_FCINFO(fake_fcinfo, 0);
+ InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0));
+ FmgrInfo flinfo;
+ PLyProcedure proc;
+ PLyExecutionContext *exec_ctx;
+ ErrorContextCallback plerrcontext;
+
+ PLy_initialize();
+
+ /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */
+ if (SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT)
+ elog(ERROR, "SPI_connect failed");
+
+ MemSet(fcinfo, 0, SizeForFunctionCallInfo(0));
+ MemSet(&flinfo, 0, sizeof(flinfo));
+ fake_fcinfo->flinfo = &flinfo;
+ flinfo.fn_oid = InvalidOid;
+ flinfo.fn_mcxt = CurrentMemoryContext;
+
+ MemSet(&proc, 0, sizeof(PLyProcedure));
+ proc.mcxt = AllocSetContextCreate(TopMemoryContext,
+ "__plpython_inline_block",
+ ALLOCSET_DEFAULT_SIZES);
+ proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block");
+ proc.langid = codeblock->langOid;
+
+ /*
+ * This is currently sufficient to get PLy_exec_function to work, but
+ * someday we might need to be honest and use PLy_output_setup_func.
+ */
+ proc.result.typoid = VOIDOID;
+
+ /*
+ * Push execution context onto stack. It is important that this get
+ * popped again, so avoid putting anything that could throw error between
+ * here and the PG_TRY.
+ */
+ exec_ctx = PLy_push_execution_context(codeblock->atomic);
+
+ PG_TRY();
+ {
+ /*
+ * Setup error traceback support for ereport().
+ * plpython_inline_error_callback doesn't currently need exec_ctx, but
+ * for consistency with plpython_call_handler we do it the same way.
+ */
+ plerrcontext.callback = plpython_inline_error_callback;
+ plerrcontext.arg = exec_ctx;
+ plerrcontext.previous = error_context_stack;
+ error_context_stack = &plerrcontext;
+
+ PLy_procedure_compile(&proc, codeblock->source_text);
+ exec_ctx->curr_proc = &proc;
+ PLy_exec_function(fake_fcinfo, &proc);
+ }
+ PG_CATCH();
+ {
+ PLy_pop_execution_context();
+ PLy_procedure_delete(&proc);
+ PyErr_Clear();
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /* Destroy the execution context */
+ PLy_pop_execution_context();
+
+ /* Now clean up the transient procedure we made */
+ PLy_procedure_delete(&proc);
+
+ PG_RETURN_VOID();
+}
+
+static bool
+PLy_procedure_is_trigger(Form_pg_proc procStruct)
+{
+ return (procStruct->prorettype == TRIGGEROID);
+}
+
+static void
+plpython_error_callback(void *arg)
+{
+ PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg;
+
+ if (exec_ctx->curr_proc)
+ {
+ if (exec_ctx->curr_proc->is_procedure)
+ errcontext("PL/Python procedure \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ else
+ errcontext("PL/Python function \"%s\"",
+ PLy_procedure_name(exec_ctx->curr_proc));
+ }
+}
+
+static void
+plpython_inline_error_callback(void *arg)
+{
+ errcontext("PL/Python anonymous code block");
+}
+
+PLyExecutionContext *
+PLy_current_execution_context(void)
+{
+ if (PLy_execution_contexts == NULL)
+ elog(ERROR, "no Python function is currently executing");
+
+ return PLy_execution_contexts;
+}
+
+MemoryContext
+PLy_get_scratch_context(PLyExecutionContext *context)
+{
+ /*
+ * A scratch context might never be needed in a given plpython procedure,
+ * so allocate it on first request.
+ */
+ if (context->scratch_ctx == NULL)
+ context->scratch_ctx =
+ AllocSetContextCreate(TopTransactionContext,
+ "PL/Python scratch context",
+ ALLOCSET_DEFAULT_SIZES);
+ return context->scratch_ctx;
+}
+
+static PLyExecutionContext *
+PLy_push_execution_context(bool atomic_context)
+{
+ PLyExecutionContext *context;
+
+ /* Pick a memory context similar to what SPI uses. */
+ context = (PLyExecutionContext *)
+ MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext,
+ sizeof(PLyExecutionContext));
+ context->curr_proc = NULL;
+ context->scratch_ctx = NULL;
+ context->next = PLy_execution_contexts;
+ PLy_execution_contexts = context;
+ return context;
+}
+
+static void
+PLy_pop_execution_context(void)
+{
+ PLyExecutionContext *context = PLy_execution_contexts;
+
+ if (context == NULL)
+ elog(ERROR, "no Python function is currently executing");
+
+ PLy_execution_contexts = context->next;
+
+ if (context->scratch_ctx)
+ MemoryContextDelete(context->scratch_ctx);
+ pfree(context);
+}
diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h
new file mode 100644
index 0000000..e8c97c2
--- /dev/null
+++ b/src/pl/plpython/plpy_main.h
@@ -0,0 +1,31 @@
+/*
+ * src/pl/plpython/plpy_main.h
+ */
+
+#ifndef PLPY_MAIN_H
+#define PLPY_MAIN_H
+
+#include "plpy_procedure.h"
+
+/* the interpreter's globals dict */
+extern PyObject *PLy_interp_globals;
+
+/*
+ * A stack of PL/Python execution contexts. Each time user-defined Python code
+ * is called, an execution context is created and put on the stack. After the
+ * Python code returns, the context is destroyed.
+ */
+typedef struct PLyExecutionContext
+{
+ PLyProcedure *curr_proc; /* the currently executing procedure */
+ MemoryContext scratch_ctx; /* a context for things like type I/O */
+ struct PLyExecutionContext *next; /* previous stack level */
+} PLyExecutionContext;
+
+/* Get the current execution context */
+extern PLyExecutionContext *PLy_current_execution_context(void);
+
+/* Get the scratch memory context for specified execution context */
+extern MemoryContext PLy_get_scratch_context(PLyExecutionContext *context);
+
+#endif /* PLPY_MAIN_H */
diff --git a/src/pl/plpython/plpy_planobject.c b/src/pl/plpython/plpy_planobject.c
new file mode 100644
index 0000000..ec2439c
--- /dev/null
+++ b/src/pl/plpython/plpy_planobject.c
@@ -0,0 +1,125 @@
+/*
+ * the PLyPlan class
+ *
+ * src/pl/plpython/plpy_planobject.c
+ */
+
+#include "postgres.h"
+
+#include "plpy_cursorobject.h"
+#include "plpy_elog.h"
+#include "plpy_planobject.h"
+#include "plpy_spi.h"
+#include "plpython.h"
+#include "utils/memutils.h"
+
+static void PLy_plan_dealloc(PyObject *arg);
+static PyObject *PLy_plan_cursor(PyObject *self, PyObject *args);
+static PyObject *PLy_plan_execute(PyObject *self, PyObject *args);
+static PyObject *PLy_plan_status(PyObject *self, PyObject *args);
+
+static char PLy_plan_doc[] = "Store a PostgreSQL plan";
+
+static PyMethodDef PLy_plan_methods[] = {
+ {"cursor", PLy_plan_cursor, METH_VARARGS, NULL},
+ {"execute", PLy_plan_execute, METH_VARARGS, NULL},
+ {"status", PLy_plan_status, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_PlanType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "PLyPlan",
+ .tp_basicsize = sizeof(PLyPlanObject),
+ .tp_dealloc = PLy_plan_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = PLy_plan_doc,
+ .tp_methods = PLy_plan_methods,
+};
+
+void
+PLy_plan_init_type(void)
+{
+ if (PyType_Ready(&PLy_PlanType) < 0)
+ elog(ERROR, "could not initialize PLy_PlanType");
+}
+
+PyObject *
+PLy_plan_new(void)
+{
+ PLyPlanObject *ob;
+
+ if ((ob = PyObject_New(PLyPlanObject, &PLy_PlanType)) == NULL)
+ return NULL;
+
+ ob->plan = NULL;
+ ob->nargs = 0;
+ ob->types = NULL;
+ ob->values = NULL;
+ ob->args = NULL;
+ ob->mcxt = NULL;
+
+ return (PyObject *) ob;
+}
+
+bool
+is_PLyPlanObject(PyObject *ob)
+{
+ return ob->ob_type == &PLy_PlanType;
+}
+
+static void
+PLy_plan_dealloc(PyObject *arg)
+{
+ PLyPlanObject *ob = (PLyPlanObject *) arg;
+
+ if (ob->plan)
+ {
+ SPI_freeplan(ob->plan);
+ ob->plan = NULL;
+ }
+ if (ob->mcxt)
+ {
+ MemoryContextDelete(ob->mcxt);
+ ob->mcxt = NULL;
+ }
+ arg->ob_type->tp_free(arg);
+}
+
+
+static PyObject *
+PLy_plan_cursor(PyObject *self, PyObject *args)
+{
+ PyObject *planargs = NULL;
+
+ if (!PyArg_ParseTuple(args, "|O", &planargs))
+ return NULL;
+
+ return PLy_cursor_plan(self, planargs);
+}
+
+
+static PyObject *
+PLy_plan_execute(PyObject *self, PyObject *args)
+{
+ PyObject *list = NULL;
+ long limit = 0;
+
+ if (!PyArg_ParseTuple(args, "|Ol", &list, &limit))
+ return NULL;
+
+ return PLy_spi_execute_plan(self, list, limit);
+}
+
+
+static PyObject *
+PLy_plan_status(PyObject *self, PyObject *args)
+{
+ if (PyArg_ParseTuple(args, ":status"))
+ {
+ Py_INCREF(Py_True);
+ return Py_True;
+ /* return PyLong_FromLong(self->status); */
+ }
+ return NULL;
+}
diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h
new file mode 100644
index 0000000..729effb
--- /dev/null
+++ b/src/pl/plpython/plpy_planobject.h
@@ -0,0 +1,27 @@
+/*
+ * src/pl/plpython/plpy_planobject.h
+ */
+
+#ifndef PLPY_PLANOBJECT_H
+#define PLPY_PLANOBJECT_H
+
+#include "executor/spi.h"
+#include "plpy_typeio.h"
+
+
+typedef struct PLyPlanObject
+{
+ PyObject_HEAD
+ SPIPlanPtr plan;
+ int nargs;
+ Oid *types;
+ Datum *values;
+ PLyObToDatum *args;
+ MemoryContext mcxt;
+} PLyPlanObject;
+
+extern void PLy_plan_init_type(void);
+extern PyObject *PLy_plan_new(void);
+extern bool is_PLyPlanObject(PyObject *ob);
+
+#endif /* PLPY_PLANOBJECT_H */
diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c
new file mode 100644
index 0000000..fa08f0d
--- /dev/null
+++ b/src/pl/plpython/plpy_plpymodule.c
@@ -0,0 +1,561 @@
+/*
+ * the plpy module
+ *
+ * src/pl/plpython/plpy_plpymodule.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "mb/pg_wchar.h"
+#include "plpy_cursorobject.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_planobject.h"
+#include "plpy_plpymodule.h"
+#include "plpy_resultobject.h"
+#include "plpy_spi.h"
+#include "plpy_subxactobject.h"
+#include "plpython.h"
+#include "utils/builtins.h"
+#include "utils/snapmgr.h"
+
+HTAB *PLy_spi_exceptions = NULL;
+
+
+static void PLy_add_exceptions(PyObject *plpy);
+static PyObject *PLy_create_exception(char *name,
+ PyObject *base, PyObject *dict,
+ const char *modname, PyObject *mod);
+static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base);
+
+/* module functions */
+static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw);
+static PyObject *PLy_quote_literal(PyObject *self, PyObject *args);
+static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args);
+static PyObject *PLy_quote_ident(PyObject *self, PyObject *args);
+
+
+/* A list of all known exceptions, generated from backend/utils/errcodes.txt */
+typedef struct ExceptionMap
+{
+ char *name;
+ char *classname;
+ int sqlstate;
+} ExceptionMap;
+
+static const ExceptionMap exception_map[] = {
+#include "spiexceptions.h"
+ {NULL, NULL, 0}
+};
+
+static PyMethodDef PLy_methods[] = {
+ /*
+ * logging methods
+ */
+ {"debug", (PyCFunction) (pg_funcptr_t) PLy_debug, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"log", (PyCFunction) (pg_funcptr_t) PLy_log, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"info", (PyCFunction) (pg_funcptr_t) PLy_info, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"notice", (PyCFunction) (pg_funcptr_t) PLy_notice, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"warning", (PyCFunction) (pg_funcptr_t) PLy_warning, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"error", (PyCFunction) (pg_funcptr_t) PLy_error, METH_VARARGS | METH_KEYWORDS, NULL},
+ {"fatal", (PyCFunction) (pg_funcptr_t) PLy_fatal, METH_VARARGS | METH_KEYWORDS, NULL},
+
+ /*
+ * create a stored plan
+ */
+ {"prepare", PLy_spi_prepare, METH_VARARGS, NULL},
+
+ /*
+ * execute a plan or query
+ */
+ {"execute", PLy_spi_execute, METH_VARARGS, NULL},
+
+ /*
+ * escaping strings
+ */
+ {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL},
+ {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL},
+ {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL},
+
+ /*
+ * create the subtransaction context manager
+ */
+ {"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL},
+
+ /*
+ * create a cursor
+ */
+ {"cursor", PLy_cursor, METH_VARARGS, NULL},
+
+ /*
+ * transaction control
+ */
+ {"commit", PLy_commit, METH_NOARGS, NULL},
+ {"rollback", PLy_rollback, METH_NOARGS, NULL},
+
+ {NULL, NULL, 0, NULL}
+};
+
+static PyMethodDef PLy_exc_methods[] = {
+ {NULL, NULL, 0, NULL}
+};
+
+static PyModuleDef PLy_module = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "plpy",
+ .m_size = -1,
+ .m_methods = PLy_methods,
+};
+
+static PyModuleDef PLy_exc_module = {
+ PyModuleDef_HEAD_INIT,
+ .m_name = "spiexceptions",
+ .m_size = -1,
+ .m_methods = PLy_exc_methods,
+};
+
+/*
+ * Must have external linkage, because PyMODINIT_FUNC does dllexport on
+ * Windows-like platforms.
+ */
+PyMODINIT_FUNC
+PyInit_plpy(void)
+{
+ PyObject *m;
+
+ m = PyModule_Create(&PLy_module);
+ if (m == NULL)
+ return NULL;
+
+ PLy_add_exceptions(m);
+
+ return m;
+}
+
+void
+PLy_init_plpy(void)
+{
+ PyObject *main_mod,
+ *main_dict,
+ *plpy_mod;
+
+ /*
+ * initialize plpy module
+ */
+ PLy_plan_init_type();
+ PLy_result_init_type();
+ PLy_subtransaction_init_type();
+ PLy_cursor_init_type();
+
+ PyModule_Create(&PLy_module);
+
+ /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */
+
+ /*
+ * initialize main module, and add plpy
+ */
+ main_mod = PyImport_AddModule("__main__");
+ main_dict = PyModule_GetDict(main_mod);
+ plpy_mod = PyImport_AddModule("plpy");
+ if (plpy_mod == NULL)
+ PLy_elog(ERROR, "could not import \"plpy\" module");
+ PyDict_SetItemString(main_dict, "plpy", plpy_mod);
+ if (PyErr_Occurred())
+ PLy_elog(ERROR, "could not import \"plpy\" module");
+}
+
+static void
+PLy_add_exceptions(PyObject *plpy)
+{
+ PyObject *excmod;
+ HASHCTL hash_ctl;
+
+ excmod = PyModule_Create(&PLy_exc_module);
+ if (excmod == NULL)
+ PLy_elog(ERROR, "could not create the spiexceptions module");
+
+ /*
+ * PyModule_AddObject does not add a refcount to the object, for some odd
+ * reason; we must do that.
+ */
+ Py_INCREF(excmod);
+ if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0)
+ PLy_elog(ERROR, "could not add the spiexceptions module");
+
+ PLy_exc_error = PLy_create_exception("plpy.Error", NULL, NULL,
+ "Error", plpy);
+ PLy_exc_fatal = PLy_create_exception("plpy.Fatal", NULL, NULL,
+ "Fatal", plpy);
+ PLy_exc_spi_error = PLy_create_exception("plpy.SPIError", NULL, NULL,
+ "SPIError", plpy);
+
+ hash_ctl.keysize = sizeof(int);
+ hash_ctl.entrysize = sizeof(PLyExceptionEntry);
+ PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256,
+ &hash_ctl, HASH_ELEM | HASH_BLOBS);
+
+ PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error);
+}
+
+/*
+ * Create an exception object and add it to the module
+ */
+static PyObject *
+PLy_create_exception(char *name, PyObject *base, PyObject *dict,
+ const char *modname, PyObject *mod)
+{
+ PyObject *exc;
+
+ exc = PyErr_NewException(name, base, dict);
+ if (exc == NULL)
+ PLy_elog(ERROR, NULL);
+
+ /*
+ * PyModule_AddObject does not add a refcount to the object, for some odd
+ * reason; we must do that.
+ */
+ Py_INCREF(exc);
+ PyModule_AddObject(mod, modname, exc);
+
+ /*
+ * The caller will also store a pointer to the exception object in some
+ * permanent variable, so add another ref to account for that. This is
+ * probably excessively paranoid, but let's be sure.
+ */
+ Py_INCREF(exc);
+ return exc;
+}
+
+/*
+ * Add all the autogenerated exceptions as subclasses of SPIError
+ */
+static void
+PLy_generate_spi_exceptions(PyObject *mod, PyObject *base)
+{
+ int i;
+
+ for (i = 0; exception_map[i].name != NULL; i++)
+ {
+ bool found;
+ PyObject *exc;
+ PLyExceptionEntry *entry;
+ PyObject *sqlstate;
+ PyObject *dict = PyDict_New();
+
+ if (dict == NULL)
+ PLy_elog(ERROR, NULL);
+
+ sqlstate = PLyUnicode_FromString(unpack_sql_state(exception_map[i].sqlstate));
+ if (sqlstate == NULL)
+ PLy_elog(ERROR, "could not generate SPI exceptions");
+
+ PyDict_SetItemString(dict, "sqlstate", sqlstate);
+ Py_DECREF(sqlstate);
+
+ exc = PLy_create_exception(exception_map[i].name, base, dict,
+ exception_map[i].classname, mod);
+
+ entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate,
+ HASH_ENTER, &found);
+ Assert(!found);
+ entry->exc = exc;
+ }
+}
+
+
+/*
+ * the python interface to the elog function
+ * don't confuse these with PLy_elog
+ */
+static PyObject *PLy_output(volatile int level, PyObject *self,
+ PyObject *args, PyObject *kw);
+
+static PyObject *
+PLy_debug(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(DEBUG2, self, args, kw);
+}
+
+static PyObject *
+PLy_log(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(LOG, self, args, kw);
+}
+
+static PyObject *
+PLy_info(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(INFO, self, args, kw);
+}
+
+static PyObject *
+PLy_notice(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(NOTICE, self, args, kw);
+}
+
+static PyObject *
+PLy_warning(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(WARNING, self, args, kw);
+}
+
+static PyObject *
+PLy_error(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(ERROR, self, args, kw);
+}
+
+static PyObject *
+PLy_fatal(PyObject *self, PyObject *args, PyObject *kw)
+{
+ return PLy_output(FATAL, self, args, kw);
+}
+
+static PyObject *
+PLy_quote_literal(PyObject *self, PyObject *args)
+{
+ const char *str;
+ char *quoted;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "s:quote_literal", &str))
+ return NULL;
+
+ quoted = quote_literal_cstr(str);
+ ret = PLyUnicode_FromString(quoted);
+ pfree(quoted);
+
+ return ret;
+}
+
+static PyObject *
+PLy_quote_nullable(PyObject *self, PyObject *args)
+{
+ const char *str;
+ char *quoted;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "z:quote_nullable", &str))
+ return NULL;
+
+ if (str == NULL)
+ return PLyUnicode_FromString("NULL");
+
+ quoted = quote_literal_cstr(str);
+ ret = PLyUnicode_FromString(quoted);
+ pfree(quoted);
+
+ return ret;
+}
+
+static PyObject *
+PLy_quote_ident(PyObject *self, PyObject *args)
+{
+ const char *str;
+ const char *quoted;
+ PyObject *ret;
+
+ if (!PyArg_ParseTuple(args, "s:quote_ident", &str))
+ return NULL;
+
+ quoted = quote_identifier(str);
+ ret = PLyUnicode_FromString(quoted);
+
+ return ret;
+}
+
+/* enforce cast of object to string */
+static char *
+object_to_string(PyObject *obj)
+{
+ if (obj)
+ {
+ PyObject *so = PyObject_Str(obj);
+
+ if (so != NULL)
+ {
+ char *str;
+
+ str = pstrdup(PLyUnicode_AsString(so));
+ Py_DECREF(so);
+
+ return str;
+ }
+ }
+
+ return NULL;
+}
+
+static PyObject *
+PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw)
+{
+ int sqlstate = 0;
+ char *volatile sqlstatestr = NULL;
+ char *volatile message = NULL;
+ char *volatile detail = NULL;
+ char *volatile hint = NULL;
+ char *volatile column_name = NULL;
+ char *volatile constraint_name = NULL;
+ char *volatile datatype_name = NULL;
+ char *volatile table_name = NULL;
+ char *volatile schema_name = NULL;
+ volatile MemoryContext oldcontext;
+ PyObject *key,
+ *value;
+ PyObject *volatile so;
+ Py_ssize_t pos = 0;
+
+ if (PyTuple_Size(args) == 1)
+ {
+ /*
+ * Treat single argument specially to avoid undesirable ('tuple',)
+ * decoration.
+ */
+ PyObject *o;
+
+ if (!PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o))
+ PLy_elog(ERROR, "could not unpack arguments in plpy.elog");
+ so = PyObject_Str(o);
+ }
+ else
+ so = PyObject_Str(args);
+
+ if (so == NULL || ((message = PLyUnicode_AsString(so)) == NULL))
+ {
+ level = ERROR;
+ message = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog");
+ }
+ message = pstrdup(message);
+
+ Py_XDECREF(so);
+
+ if (kw != NULL)
+ {
+ while (PyDict_Next(kw, &pos, &key, &value))
+ {
+ char *keyword = PLyUnicode_AsString(key);
+
+ if (strcmp(keyword, "message") == 0)
+ {
+ /* the message should not be overwritten */
+ if (PyTuple_Size(args) != 0)
+ {
+ PLy_exception_set(PyExc_TypeError, "argument 'message' given by name and position");
+ return NULL;
+ }
+
+ if (message)
+ pfree(message);
+ message = object_to_string(value);
+ }
+ else if (strcmp(keyword, "detail") == 0)
+ detail = object_to_string(value);
+ else if (strcmp(keyword, "hint") == 0)
+ hint = object_to_string(value);
+ else if (strcmp(keyword, "sqlstate") == 0)
+ sqlstatestr = object_to_string(value);
+ else if (strcmp(keyword, "schema_name") == 0)
+ schema_name = object_to_string(value);
+ else if (strcmp(keyword, "table_name") == 0)
+ table_name = object_to_string(value);
+ else if (strcmp(keyword, "column_name") == 0)
+ column_name = object_to_string(value);
+ else if (strcmp(keyword, "datatype_name") == 0)
+ datatype_name = object_to_string(value);
+ else if (strcmp(keyword, "constraint_name") == 0)
+ constraint_name = object_to_string(value);
+ else
+ {
+ PLy_exception_set(PyExc_TypeError,
+ "'%s' is an invalid keyword argument for this function",
+ keyword);
+ return NULL;
+ }
+ }
+ }
+
+ if (sqlstatestr != NULL)
+ {
+ if (strlen(sqlstatestr) != 5)
+ {
+ PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code");
+ return NULL;
+ }
+
+ if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5)
+ {
+ PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code");
+ return NULL;
+ }
+
+ sqlstate = MAKE_SQLSTATE(sqlstatestr[0],
+ sqlstatestr[1],
+ sqlstatestr[2],
+ sqlstatestr[3],
+ sqlstatestr[4]);
+ }
+
+ oldcontext = CurrentMemoryContext;
+ PG_TRY();
+ {
+ if (message != NULL)
+ pg_verifymbstr(message, strlen(message), false);
+ if (detail != NULL)
+ pg_verifymbstr(detail, strlen(detail), false);
+ if (hint != NULL)
+ pg_verifymbstr(hint, strlen(hint), false);
+ if (schema_name != NULL)
+ pg_verifymbstr(schema_name, strlen(schema_name), false);
+ if (table_name != NULL)
+ pg_verifymbstr(table_name, strlen(table_name), false);
+ if (column_name != NULL)
+ pg_verifymbstr(column_name, strlen(column_name), false);
+ if (datatype_name != NULL)
+ pg_verifymbstr(datatype_name, strlen(datatype_name), false);
+ if (constraint_name != NULL)
+ pg_verifymbstr(constraint_name, strlen(constraint_name), false);
+
+ ereport(level,
+ ((sqlstate != 0) ? errcode(sqlstate) : 0,
+ (message != NULL) ? errmsg_internal("%s", message) : 0,
+ (detail != NULL) ? errdetail_internal("%s", detail) : 0,
+ (hint != NULL) ? errhint("%s", hint) : 0,
+ (column_name != NULL) ?
+ err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0,
+ (constraint_name != NULL) ?
+ err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0,
+ (datatype_name != NULL) ?
+ err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0,
+ (table_name != NULL) ?
+ err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0,
+ (schema_name != NULL) ?
+ err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0));
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ PLy_exception_set_with_details(PLy_exc_error, edata);
+ FreeErrorData(edata);
+
+ return NULL;
+ }
+ PG_END_TRY();
+
+ /*
+ * return a legal object so the interpreter will continue on its merry way
+ */
+ Py_RETURN_NONE;
+}
diff --git a/src/pl/plpython/plpy_plpymodule.h b/src/pl/plpython/plpy_plpymodule.h
new file mode 100644
index 0000000..1ca3823
--- /dev/null
+++ b/src/pl/plpython/plpy_plpymodule.h
@@ -0,0 +1,18 @@
+/*
+ * src/pl/plpython/plpy_plpymodule.h
+ */
+
+#ifndef PLPY_PLPYMODULE_H
+#define PLPY_PLPYMODULE_H
+
+#include "plpython.h"
+#include "utils/hsearch.h"
+
+/* A hash table mapping sqlstates to exceptions, for speedy lookup */
+extern HTAB *PLy_spi_exceptions;
+
+
+PyMODINIT_FUNC PyInit_plpy(void);
+extern void PLy_init_plpy(void);
+
+#endif /* PLPY_PLPYMODULE_H */
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
new file mode 100644
index 0000000..494f109
--- /dev/null
+++ b/src/pl/plpython/plpy_procedure.c
@@ -0,0 +1,472 @@
+/*
+ * Python procedure manipulation for plpython
+ *
+ * src/pl/plpython/plpy_procedure.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "access/transam.h"
+#include "funcapi.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_procedure.h"
+#include "plpython.h"
+#include "utils/builtins.h"
+#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+static HTAB *PLy_procedure_cache = NULL;
+
+static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
+static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
+static char *PLy_procedure_munge_source(const char *name, const char *src);
+
+
+void
+init_procedure_caches(void)
+{
+ HASHCTL hash_ctl;
+
+ hash_ctl.keysize = sizeof(PLyProcedureKey);
+ hash_ctl.entrysize = sizeof(PLyProcedureEntry);
+ PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+}
+
+/*
+ * PLy_procedure_name: get the name of the specified procedure.
+ *
+ * NB: this returns the SQL name, not the internal Python procedure name
+ */
+char *
+PLy_procedure_name(PLyProcedure *proc)
+{
+ if (proc == NULL)
+ return "<unknown procedure>";
+ return proc->proname;
+}
+
+/*
+ * PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and
+ * returns a new PLyProcedure.
+ *
+ * fn_oid is the OID of the function requested
+ * fn_rel is InvalidOid or the relation this function triggers on
+ * is_trigger denotes whether the function is a trigger function
+ *
+ * The reason that both fn_rel and is_trigger need to be passed is that when
+ * trigger functions get validated we don't know which relation(s) they'll
+ * be used with, so no sensible fn_rel can be passed.
+ */
+PLyProcedure *
+PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger)
+{
+ bool use_cache = !(is_trigger && fn_rel == InvalidOid);
+ HeapTuple procTup;
+ PLyProcedureKey key;
+ PLyProcedureEntry *volatile entry = NULL;
+ PLyProcedure *volatile proc = NULL;
+ bool found = false;
+
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", fn_oid);
+
+ /*
+ * Look for the function in the cache, unless we don't have the necessary
+ * information (e.g. during validation). In that case we just don't cache
+ * anything.
+ */
+ if (use_cache)
+ {
+ key.fn_oid = fn_oid;
+ key.fn_rel = fn_rel;
+ entry = hash_search(PLy_procedure_cache, &key, HASH_ENTER, &found);
+ proc = entry->proc;
+ }
+
+ PG_TRY();
+ {
+ if (!found)
+ {
+ /* Haven't found it, create a new procedure */
+ proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+ if (use_cache)
+ entry->proc = proc;
+ }
+ else if (!PLy_procedure_valid(proc, procTup))
+ {
+ /* Found it, but it's invalid, free and reuse the cache entry */
+ entry->proc = NULL;
+ if (proc)
+ PLy_procedure_delete(proc);
+ proc = PLy_procedure_create(procTup, fn_oid, is_trigger);
+ entry->proc = proc;
+ }
+ /* Found it and it's valid, it's fine to use it */
+ }
+ PG_CATCH();
+ {
+ /* Do not leave an uninitialized entry in the cache */
+ if (use_cache)
+ hash_search(PLy_procedure_cache, &key, HASH_REMOVE, NULL);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ ReleaseSysCache(procTup);
+
+ return proc;
+}
+
+/*
+ * Create a new PLyProcedure structure
+ */
+static PLyProcedure *
+PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
+{
+ char procName[NAMEDATALEN + 256];
+ Form_pg_proc procStruct;
+ PLyProcedure *volatile proc;
+ MemoryContext cxt;
+ MemoryContext oldcxt;
+ int rv;
+ char *ptr;
+
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+ rv = snprintf(procName, sizeof(procName),
+ "__plpython_procedure_%s_%u",
+ NameStr(procStruct->proname),
+ fn_oid);
+ if (rv >= sizeof(procName) || rv < 0)
+ elog(ERROR, "procedure name would overrun buffer");
+
+ /* Replace any not-legal-in-Python-names characters with '_' */
+ for (ptr = procName; *ptr; ptr++)
+ {
+ if (!((*ptr >= 'A' && *ptr <= 'Z') ||
+ (*ptr >= 'a' && *ptr <= 'z') ||
+ (*ptr >= '0' && *ptr <= '9')))
+ *ptr = '_';
+ }
+
+ /* Create long-lived context that all procedure info will live in */
+ cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Python function",
+ ALLOCSET_DEFAULT_SIZES);
+
+ oldcxt = MemoryContextSwitchTo(cxt);
+
+ proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure));
+ proc->mcxt = cxt;
+
+ PG_TRY();
+ {
+ Datum protrftypes_datum;
+ Datum prosrcdatum;
+ bool isnull;
+ char *procSource;
+ int i;
+
+ proc->proname = pstrdup(NameStr(procStruct->proname));
+ MemoryContextSetIdentifier(cxt, proc->proname);
+ proc->pyname = pstrdup(procName);
+ proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ proc->fn_tid = procTup->t_self;
+ proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE);
+ proc->is_setof = procStruct->proretset;
+ proc->is_procedure = (procStruct->prokind == PROKIND_PROCEDURE);
+ proc->src = NULL;
+ proc->argnames = NULL;
+ proc->args = NULL;
+ proc->nargs = 0;
+ proc->langid = procStruct->prolang;
+ protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes,
+ &isnull);
+ proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+ proc->code = NULL;
+ proc->statics = NULL;
+ proc->globals = NULL;
+ proc->calldepth = 0;
+ proc->argstack = NULL;
+
+ /*
+ * get information required for output conversion of the return value,
+ * but only if this isn't a trigger.
+ */
+ if (!is_trigger)
+ {
+ Oid rettype = procStruct->prorettype;
+ HeapTuple rvTypeTup;
+ Form_pg_type rvTypeStruct;
+
+ rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
+ if (!HeapTupleIsValid(rvTypeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettype);
+ rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup);
+
+ /* Disallow pseudotype result, except for void or record */
+ if (rvTypeStruct->typtype == TYPTYPE_PSEUDO)
+ {
+ if (rettype == VOIDOID ||
+ rettype == RECORDOID)
+ /* okay */ ;
+ else if (rettype == TRIGGEROID || rettype == EVENT_TRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Python functions cannot return type %s",
+ format_type_be(rettype))));
+ }
+
+ /* set up output function for procedure result */
+ PLy_output_setup_func(&proc->result, proc->mcxt,
+ rettype, -1, proc);
+
+ ReleaseSysCache(rvTypeTup);
+ }
+ else
+ {
+ /*
+ * In a trigger function, we use proc->result and proc->result_in
+ * for converting tuples, but we don't yet have enough info to set
+ * them up. PLy_exec_trigger will deal with it.
+ */
+ proc->result.typoid = InvalidOid;
+ proc->result_in.typoid = InvalidOid;
+ }
+
+ /*
+ * Now get information required for input conversion of the
+ * procedure's arguments. Note that we ignore output arguments here.
+ * If the function returns record, those I/O functions will be set up
+ * when the function is first called.
+ */
+ if (procStruct->pronargs)
+ {
+ Oid *types;
+ char **names,
+ *modes;
+ int pos,
+ total;
+
+ /* extract argument type info from the pg_proc tuple */
+ total = get_func_arg_info(procTup, &types, &names, &modes);
+
+ /* count number of in+inout args into proc->nargs */
+ if (modes == NULL)
+ proc->nargs = total;
+ else
+ {
+ /* proc->nargs was initialized to 0 above */
+ for (i = 0; i < total; i++)
+ {
+ if (modes[i] != PROARGMODE_OUT &&
+ modes[i] != PROARGMODE_TABLE)
+ (proc->nargs)++;
+ }
+ }
+
+ /* Allocate arrays for per-input-argument data */
+ proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs);
+ proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs);
+
+ for (i = pos = 0; i < total; i++)
+ {
+ HeapTuple argTypeTup;
+ Form_pg_type argTypeStruct;
+
+ if (modes &&
+ (modes[i] == PROARGMODE_OUT ||
+ modes[i] == PROARGMODE_TABLE))
+ continue; /* skip OUT arguments */
+
+ Assert(types[i] == procStruct->proargtypes.values[pos]);
+
+ argTypeTup = SearchSysCache1(TYPEOID,
+ ObjectIdGetDatum(types[i]));
+ if (!HeapTupleIsValid(argTypeTup))
+ elog(ERROR, "cache lookup failed for type %u", types[i]);
+ argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup);
+
+ /* disallow pseudotype arguments */
+ if (argTypeStruct->typtype == TYPTYPE_PSEUDO)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Python functions cannot accept type %s",
+ format_type_be(types[i]))));
+
+ /* set up I/O function info */
+ PLy_input_setup_func(&proc->args[pos], proc->mcxt,
+ types[i], -1, /* typmod not known */
+ proc);
+
+ /* get argument name */
+ proc->argnames[pos] = names ? pstrdup(names[i]) : NULL;
+
+ ReleaseSysCache(argTypeTup);
+
+ pos++;
+ }
+ }
+
+ /*
+ * get the text of the function.
+ */
+ prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+ procSource = TextDatumGetCString(prosrcdatum);
+
+ PLy_procedure_compile(proc, procSource);
+
+ pfree(procSource);
+ }
+ PG_CATCH();
+ {
+ MemoryContextSwitchTo(oldcxt);
+ PLy_procedure_delete(proc);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ MemoryContextSwitchTo(oldcxt);
+ return proc;
+}
+
+/*
+ * Insert the procedure into the Python interpreter
+ */
+void
+PLy_procedure_compile(PLyProcedure *proc, const char *src)
+{
+ PyObject *crv = NULL;
+ char *msrc;
+
+ proc->globals = PyDict_Copy(PLy_interp_globals);
+
+ /*
+ * SD is private preserved data between calls. GD is global data shared by
+ * all functions
+ */
+ proc->statics = PyDict_New();
+ if (!proc->statics)
+ PLy_elog(ERROR, NULL);
+ PyDict_SetItemString(proc->globals, "SD", proc->statics);
+
+ /*
+ * insert the function code into the interpreter
+ */
+ msrc = PLy_procedure_munge_source(proc->pyname, src);
+ /* Save the mangled source for later inclusion in tracebacks */
+ proc->src = MemoryContextStrdup(proc->mcxt, msrc);
+ crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL);
+ pfree(msrc);
+
+ if (crv != NULL)
+ {
+ int clen;
+ char call[NAMEDATALEN + 256];
+
+ Py_DECREF(crv);
+
+ /*
+ * compile a call to the function
+ */
+ clen = snprintf(call, sizeof(call), "%s()", proc->pyname);
+ if (clen < 0 || clen >= sizeof(call))
+ elog(ERROR, "string would overflow buffer");
+ proc->code = Py_CompileString(call, "<string>", Py_eval_input);
+ if (proc->code != NULL)
+ return;
+ }
+
+ if (proc->proname)
+ PLy_elog(ERROR, "could not compile PL/Python function \"%s\"",
+ proc->proname);
+ else
+ PLy_elog(ERROR, "could not compile anonymous PL/Python code block");
+}
+
+void
+PLy_procedure_delete(PLyProcedure *proc)
+{
+ Py_XDECREF(proc->code);
+ Py_XDECREF(proc->statics);
+ Py_XDECREF(proc->globals);
+ MemoryContextDelete(proc->mcxt);
+}
+
+/*
+ * Decide whether a cached PLyProcedure struct is still valid
+ */
+static bool
+PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup)
+{
+ if (proc == NULL)
+ return false;
+
+ /* If the pg_proc tuple has changed, it's not valid */
+ if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&proc->fn_tid, &procTup->t_self)))
+ return false;
+
+ return true;
+}
+
+static char *
+PLy_procedure_munge_source(const char *name, const char *src)
+{
+ char *mrc,
+ *mp;
+ const char *sp;
+ size_t mlen;
+ int plen;
+
+ /*
+ * room for function source and the def statement
+ */
+ mlen = (strlen(src) * 2) + strlen(name) + 16;
+
+ mrc = palloc(mlen);
+ plen = snprintf(mrc, mlen, "def %s():\n\t", name);
+ Assert(plen >= 0 && plen < mlen);
+
+ sp = src;
+ mp = mrc + plen;
+
+ while (*sp != '\0')
+ {
+ if (*sp == '\r' && *(sp + 1) == '\n')
+ sp++;
+
+ if (*sp == '\n' || *sp == '\r')
+ {
+ *mp++ = '\n';
+ *mp++ = '\t';
+ sp++;
+ }
+ else
+ *mp++ = *sp++;
+ }
+ *mp++ = '\n';
+ *mp++ = '\n';
+ *mp = '\0';
+
+ if (mp > (mrc + mlen))
+ elog(FATAL, "buffer overrun in PLy_procedure_munge_source");
+
+ return mrc;
+}
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
new file mode 100644
index 0000000..8968b5c
--- /dev/null
+++ b/src/pl/plpython/plpy_procedure.h
@@ -0,0 +1,70 @@
+/*
+ * src/pl/plpython/plpy_procedure.h
+ */
+
+#ifndef PLPY_PROCEDURE_H
+#define PLPY_PROCEDURE_H
+
+#include "plpy_typeio.h"
+
+
+extern void init_procedure_caches(void);
+
+
+/* saved arguments for outer recursion level or set-returning function */
+typedef struct PLySavedArgs
+{
+ struct PLySavedArgs *next; /* linked-list pointer */
+ PyObject *args; /* "args" element of globals dict */
+ int nargs; /* length of namedargs array */
+ PyObject *namedargs[FLEXIBLE_ARRAY_MEMBER]; /* named args */
+} PLySavedArgs;
+
+/* cached procedure data */
+typedef struct PLyProcedure
+{
+ MemoryContext mcxt; /* context holding this PLyProcedure and its
+ * subsidiary data */
+ char *proname; /* SQL name of procedure */
+ char *pyname; /* Python name of procedure */
+ TransactionId fn_xmin;
+ ItemPointerData fn_tid;
+ bool fn_readonly;
+ bool is_setof; /* true, if function returns result set */
+ bool is_procedure;
+ PLyObToDatum result; /* Function result output conversion info */
+ PLyDatumToOb result_in; /* For converting input tuples in a trigger */
+ char *src; /* textual procedure code, after mangling */
+ char **argnames; /* Argument names */
+ PLyDatumToOb *args; /* Argument input conversion info */
+ int nargs; /* Number of elements in above arrays */
+ Oid langid; /* OID of plpython pg_language entry */
+ List *trftypes; /* OID list of transform types */
+ PyObject *code; /* compiled procedure code */
+ PyObject *statics; /* data saved across calls, local scope */
+ PyObject *globals; /* data saved across calls, global scope */
+ long calldepth; /* depth of recursive calls of function */
+ PLySavedArgs *argstack; /* stack of outer-level call arguments */
+} PLyProcedure;
+
+/* the procedure cache key */
+typedef struct PLyProcedureKey
+{
+ Oid fn_oid; /* function OID */
+ Oid fn_rel; /* triggered-on relation or InvalidOid */
+} PLyProcedureKey;
+
+/* the procedure cache entry */
+typedef struct PLyProcedureEntry
+{
+ PLyProcedureKey key; /* hash key */
+ PLyProcedure *proc;
+} PLyProcedureEntry;
+
+/* PLyProcedure manipulation */
+extern char *PLy_procedure_name(PLyProcedure *proc);
+extern PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger);
+extern void PLy_procedure_compile(PLyProcedure *proc, const char *src);
+extern void PLy_procedure_delete(PLyProcedure *proc);
+
+#endif /* PLPY_PROCEDURE_H */
diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c
new file mode 100644
index 0000000..a8516b2
--- /dev/null
+++ b/src/pl/plpython/plpy_resultobject.c
@@ -0,0 +1,250 @@
+/*
+ * the PLyResult class
+ *
+ * src/pl/plpython/plpy_resultobject.c
+ */
+
+#include "postgres.h"
+
+#include "plpy_elog.h"
+#include "plpy_resultobject.h"
+#include "plpython.h"
+
+static void PLy_result_dealloc(PyObject *arg);
+static PyObject *PLy_result_colnames(PyObject *self, PyObject *unused);
+static PyObject *PLy_result_coltypes(PyObject *self, PyObject *unused);
+static PyObject *PLy_result_coltypmods(PyObject *self, PyObject *unused);
+static PyObject *PLy_result_nrows(PyObject *self, PyObject *args);
+static PyObject *PLy_result_status(PyObject *self, PyObject *args);
+static Py_ssize_t PLy_result_length(PyObject *arg);
+static PyObject *PLy_result_item(PyObject *arg, Py_ssize_t idx);
+static PyObject *PLy_result_str(PyObject *arg);
+static PyObject *PLy_result_subscript(PyObject *arg, PyObject *item);
+static int PLy_result_ass_subscript(PyObject *self, PyObject *item, PyObject *value);
+
+static char PLy_result_doc[] = "Results of a PostgreSQL query";
+
+static PySequenceMethods PLy_result_as_sequence = {
+ .sq_length = PLy_result_length,
+ .sq_item = PLy_result_item,
+};
+
+static PyMappingMethods PLy_result_as_mapping = {
+ .mp_length = PLy_result_length,
+ .mp_subscript = PLy_result_subscript,
+ .mp_ass_subscript = PLy_result_ass_subscript,
+};
+
+static PyMethodDef PLy_result_methods[] = {
+ {"colnames", PLy_result_colnames, METH_NOARGS, NULL},
+ {"coltypes", PLy_result_coltypes, METH_NOARGS, NULL},
+ {"coltypmods", PLy_result_coltypmods, METH_NOARGS, NULL},
+ {"nrows", PLy_result_nrows, METH_VARARGS, NULL},
+ {"status", PLy_result_status, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_ResultType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "PLyResult",
+ .tp_basicsize = sizeof(PLyResultObject),
+ .tp_dealloc = PLy_result_dealloc,
+ .tp_as_sequence = &PLy_result_as_sequence,
+ .tp_as_mapping = &PLy_result_as_mapping,
+ .tp_str = &PLy_result_str,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = PLy_result_doc,
+ .tp_methods = PLy_result_methods,
+};
+
+void
+PLy_result_init_type(void)
+{
+ if (PyType_Ready(&PLy_ResultType) < 0)
+ elog(ERROR, "could not initialize PLy_ResultType");
+}
+
+PyObject *
+PLy_result_new(void)
+{
+ PLyResultObject *ob;
+
+ if ((ob = PyObject_New(PLyResultObject, &PLy_ResultType)) == NULL)
+ return NULL;
+
+ /* ob->tuples = NULL; */
+
+ Py_INCREF(Py_None);
+ ob->status = Py_None;
+ ob->nrows = PyLong_FromLong(-1);
+ ob->rows = PyList_New(0);
+ ob->tupdesc = NULL;
+ if (!ob->rows)
+ {
+ Py_DECREF(ob);
+ return NULL;
+ }
+
+ return (PyObject *) ob;
+}
+
+static void
+PLy_result_dealloc(PyObject *arg)
+{
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ Py_XDECREF(ob->nrows);
+ Py_XDECREF(ob->rows);
+ Py_XDECREF(ob->status);
+ if (ob->tupdesc)
+ {
+ FreeTupleDesc(ob->tupdesc);
+ ob->tupdesc = NULL;
+ }
+
+ arg->ob_type->tp_free(arg);
+}
+
+static PyObject *
+PLy_result_colnames(PyObject *self, PyObject *unused)
+{
+ PLyResultObject *ob = (PLyResultObject *) self;
+ PyObject *list;
+ int i;
+
+ if (!ob->tupdesc)
+ {
+ PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+ return NULL;
+ }
+
+ list = PyList_New(ob->tupdesc->natts);
+ if (!list)
+ return NULL;
+ for (i = 0; i < ob->tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+ PyList_SET_ITEM(list, i, PLyUnicode_FromString(NameStr(attr->attname)));
+ }
+
+ return list;
+}
+
+static PyObject *
+PLy_result_coltypes(PyObject *self, PyObject *unused)
+{
+ PLyResultObject *ob = (PLyResultObject *) self;
+ PyObject *list;
+ int i;
+
+ if (!ob->tupdesc)
+ {
+ PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+ return NULL;
+ }
+
+ list = PyList_New(ob->tupdesc->natts);
+ if (!list)
+ return NULL;
+ for (i = 0; i < ob->tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+ PyList_SET_ITEM(list, i, PyLong_FromLong(attr->atttypid));
+ }
+
+ return list;
+}
+
+static PyObject *
+PLy_result_coltypmods(PyObject *self, PyObject *unused)
+{
+ PLyResultObject *ob = (PLyResultObject *) self;
+ PyObject *list;
+ int i;
+
+ if (!ob->tupdesc)
+ {
+ PLy_exception_set(PLy_exc_error, "command did not produce a result set");
+ return NULL;
+ }
+
+ list = PyList_New(ob->tupdesc->natts);
+ if (!list)
+ return NULL;
+ for (i = 0; i < ob->tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i);
+
+ PyList_SET_ITEM(list, i, PyLong_FromLong(attr->atttypmod));
+ }
+
+ return list;
+}
+
+static PyObject *
+PLy_result_nrows(PyObject *self, PyObject *args)
+{
+ PLyResultObject *ob = (PLyResultObject *) self;
+
+ Py_INCREF(ob->nrows);
+ return ob->nrows;
+}
+
+static PyObject *
+PLy_result_status(PyObject *self, PyObject *args)
+{
+ PLyResultObject *ob = (PLyResultObject *) self;
+
+ Py_INCREF(ob->status);
+ return ob->status;
+}
+
+static Py_ssize_t
+PLy_result_length(PyObject *arg)
+{
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ return PyList_Size(ob->rows);
+}
+
+static PyObject *
+PLy_result_item(PyObject *arg, Py_ssize_t idx)
+{
+ PyObject *rv;
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ rv = PyList_GetItem(ob->rows, idx);
+ if (rv != NULL)
+ Py_INCREF(rv);
+ return rv;
+}
+
+static PyObject *
+PLy_result_str(PyObject *arg)
+{
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ return PyUnicode_FromFormat("<%s status=%S nrows=%S rows=%S>",
+ Py_TYPE(ob)->tp_name,
+ ob->status,
+ ob->nrows,
+ ob->rows);
+}
+
+static PyObject *
+PLy_result_subscript(PyObject *arg, PyObject *item)
+{
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ return PyObject_GetItem(ob->rows, item);
+}
+
+static int
+PLy_result_ass_subscript(PyObject *arg, PyObject *item, PyObject *value)
+{
+ PLyResultObject *ob = (PLyResultObject *) arg;
+
+ return PyObject_SetItem(ob->rows, item, value);
+}
diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h
new file mode 100644
index 0000000..978c4cc
--- /dev/null
+++ b/src/pl/plpython/plpy_resultobject.h
@@ -0,0 +1,27 @@
+/*
+ * src/pl/plpython/plpy_resultobject.h
+ */
+
+#ifndef PLPY_RESULTOBJECT_H
+#define PLPY_RESULTOBJECT_H
+
+#include "access/tupdesc.h"
+
+#include "plpython.h"
+
+
+typedef struct PLyResultObject
+{
+ PyObject_HEAD
+ /* HeapTuple *tuples; */
+ PyObject *nrows; /* number of rows returned by query */
+ PyObject *rows; /* data rows, or empty list if no data
+ * returned */
+ PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */
+ TupleDesc tupdesc;
+} PLyResultObject;
+
+extern void PLy_result_init_type(void);
+extern PyObject *PLy_result_new(void);
+
+#endif /* PLPY_RESULTOBJECT_H */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
new file mode 100644
index 0000000..9a71a42
--- /dev/null
+++ b/src/pl/plpython/plpy_spi.c
@@ -0,0 +1,666 @@
+/*
+ * interface to SPI functions
+ *
+ * src/pl/plpython/plpy_spi.c
+ */
+
+#include "postgres.h"
+
+#include <limits.h>
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/pg_type.h"
+#include "executor/spi.h"
+#include "mb/pg_wchar.h"
+#include "parser/parse_type.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_planobject.h"
+#include "plpy_plpymodule.h"
+#include "plpy_procedure.h"
+#include "plpy_resultobject.h"
+#include "plpy_spi.h"
+#include "plpython.h"
+#include "utils/memutils.h"
+#include "utils/syscache.h"
+
+static PyObject *PLy_spi_execute_query(char *query, long limit);
+static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *tuptable,
+ uint64 rows, int status);
+static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata);
+
+
+/* prepare(query="select * from foo")
+ * prepare(query="select * from foo where bar = $1", params=["text"])
+ * prepare(query="select * from foo where bar = $1", params=["text"], limit=5)
+ */
+PyObject *
+PLy_spi_prepare(PyObject *self, PyObject *args)
+{
+ PLyPlanObject *plan;
+ PyObject *list = NULL;
+ PyObject *volatile optr = NULL;
+ char *query;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+ volatile int nargs;
+
+ if (!PyArg_ParseTuple(args, "s|O:prepare", &query, &list))
+ return NULL;
+
+ if (list && (!PySequence_Check(list)))
+ {
+ PLy_exception_set(PyExc_TypeError,
+ "second argument of plpy.prepare must be a sequence");
+ return NULL;
+ }
+
+ if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL)
+ return NULL;
+
+ plan->mcxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Python plan context",
+ ALLOCSET_DEFAULT_SIZES);
+ oldcontext = MemoryContextSwitchTo(plan->mcxt);
+
+ nargs = list ? PySequence_Length(list) : 0;
+
+ plan->nargs = nargs;
+ plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL;
+ plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL;
+ plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL;
+
+ MemoryContextSwitchTo(oldcontext);
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ int i;
+
+ for (i = 0; i < nargs; i++)
+ {
+ char *sptr;
+ Oid typeId;
+ int32 typmod;
+
+ optr = PySequence_GetItem(list, i);
+ if (PyUnicode_Check(optr))
+ sptr = PLyUnicode_AsString(optr);
+ else
+ {
+ ereport(ERROR,
+ (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i)));
+ sptr = NULL; /* keep compiler quiet */
+ }
+
+ /********************************************************
+ * Resolve argument type names and then look them up by
+ * oid in the system cache, and remember the required
+ *information for input conversion.
+ ********************************************************/
+
+ parseTypeString(sptr, &typeId, &typmod, false);
+
+ Py_DECREF(optr);
+
+ /*
+ * set optr to NULL, so we won't try to unref it again in case of
+ * an error
+ */
+ optr = NULL;
+
+ plan->types[i] = typeId;
+ PLy_output_setup_func(&plan->args[i], plan->mcxt,
+ typeId, typmod,
+ exec_ctx->curr_proc);
+ }
+
+ pg_verifymbstr(query, strlen(query), false);
+ plan->plan = SPI_prepare(query, plan->nargs, plan->types);
+ if (plan->plan == NULL)
+ elog(ERROR, "SPI_prepare failed: %s",
+ SPI_result_code_string(SPI_result));
+
+ /* transfer plan from procCxt to topCxt */
+ if (SPI_keepplan(plan->plan))
+ elog(ERROR, "SPI_keepplan failed");
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ Py_DECREF(plan);
+ Py_XDECREF(optr);
+
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ Assert(plan->plan != NULL);
+ return (PyObject *) plan;
+}
+
+/* execute(query="select * from foo", limit=5)
+ * execute(plan=plan, values=(foo, bar), limit=5)
+ */
+PyObject *
+PLy_spi_execute(PyObject *self, PyObject *args)
+{
+ char *query;
+ PyObject *plan;
+ PyObject *list = NULL;
+ long limit = 0;
+
+ if (PyArg_ParseTuple(args, "s|l", &query, &limit))
+ return PLy_spi_execute_query(query, limit);
+
+ PyErr_Clear();
+
+ if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) &&
+ is_PLyPlanObject(plan))
+ return PLy_spi_execute_plan(plan, list, limit);
+
+ PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan");
+ return NULL;
+}
+
+PyObject *
+PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit)
+{
+ volatile int nargs;
+ int i,
+ rv;
+ PLyPlanObject *plan;
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+ PyObject *ret;
+
+ if (list != NULL)
+ {
+ if (!PySequence_Check(list) || PyUnicode_Check(list))
+ {
+ PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument");
+ return NULL;
+ }
+ nargs = PySequence_Length(list);
+ }
+ else
+ nargs = 0;
+
+ plan = (PLyPlanObject *) ob;
+
+ if (nargs != plan->nargs)
+ {
+ char *sv;
+ PyObject *so = PyObject_Str(list);
+
+ if (!so)
+ PLy_elog(ERROR, "could not execute plan");
+ sv = PLyUnicode_AsString(so);
+ PLy_exception_set_plural(PyExc_TypeError,
+ "Expected sequence of %d argument, got %d: %s",
+ "Expected sequence of %d arguments, got %d: %s",
+ plan->nargs,
+ plan->nargs, nargs, sv);
+ Py_DECREF(so);
+
+ return NULL;
+ }
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ char *volatile nulls;
+ volatile int j;
+
+ if (nargs > 0)
+ nulls = palloc(nargs * sizeof(char));
+ else
+ nulls = NULL;
+
+ for (j = 0; j < nargs; j++)
+ {
+ PLyObToDatum *arg = &plan->args[j];
+ PyObject *elem;
+
+ elem = PySequence_GetItem(list, j);
+ PG_TRY();
+ {
+ bool isnull;
+
+ plan->values[j] = PLy_output_convert(arg, elem, &isnull);
+ nulls[j] = isnull ? 'n' : ' ';
+ }
+ PG_FINALLY();
+ {
+ Py_DECREF(elem);
+ }
+ PG_END_TRY();
+ }
+
+ rv = SPI_execute_plan(plan->plan, plan->values, nulls,
+ exec_ctx->curr_proc->fn_readonly, limit);
+ ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
+
+ if (nargs > 0)
+ pfree(nulls);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ int k;
+
+ /*
+ * cleanup plan->values array
+ */
+ for (k = 0; k < nargs; k++)
+ {
+ if (!plan->args[k].typbyval &&
+ (plan->values[k] != PointerGetDatum(NULL)))
+ {
+ pfree(DatumGetPointer(plan->values[k]));
+ plan->values[k] = PointerGetDatum(NULL);
+ }
+ }
+
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ for (i = 0; i < nargs; i++)
+ {
+ if (!plan->args[i].typbyval &&
+ (plan->values[i] != PointerGetDatum(NULL)))
+ {
+ pfree(DatumGetPointer(plan->values[i]));
+ plan->values[i] = PointerGetDatum(NULL);
+ }
+ }
+
+ if (rv < 0)
+ {
+ PLy_exception_set(PLy_exc_spi_error,
+ "SPI_execute_plan failed: %s",
+ SPI_result_code_string(rv));
+ return NULL;
+ }
+
+ return ret;
+}
+
+static PyObject *
+PLy_spi_execute_query(char *query, long limit)
+{
+ int rv;
+ volatile MemoryContext oldcontext;
+ volatile ResourceOwner oldowner;
+ PyObject *ret = NULL;
+
+ oldcontext = CurrentMemoryContext;
+ oldowner = CurrentResourceOwner;
+
+ PLy_spi_subtransaction_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+ pg_verifymbstr(query, strlen(query), false);
+ rv = SPI_execute(query, exec_ctx->curr_proc->fn_readonly, limit);
+ ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv);
+
+ PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ return NULL;
+ }
+ PG_END_TRY();
+
+ if (rv < 0)
+ {
+ Py_XDECREF(ret);
+ PLy_exception_set(PLy_exc_spi_error,
+ "SPI_execute failed: %s",
+ SPI_result_code_string(rv));
+ return NULL;
+ }
+
+ return ret;
+}
+
+static PyObject *
+PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status)
+{
+ PLyResultObject *result;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ volatile MemoryContext oldcontext;
+
+ result = (PLyResultObject *) PLy_result_new();
+ if (!result)
+ {
+ SPI_freetuptable(tuptable);
+ return NULL;
+ }
+ Py_DECREF(result->status);
+ result->status = PyLong_FromLong(status);
+
+ if (status > 0 && tuptable == NULL)
+ {
+ Py_DECREF(result->nrows);
+ result->nrows = PyLong_FromUnsignedLongLong(rows);
+ }
+ else if (status > 0 && tuptable != NULL)
+ {
+ PLyDatumToOb ininfo;
+ MemoryContext cxt;
+
+ Py_DECREF(result->nrows);
+ result->nrows = PyLong_FromUnsignedLongLong(rows);
+
+ cxt = AllocSetContextCreate(CurrentMemoryContext,
+ "PL/Python temp context",
+ ALLOCSET_DEFAULT_SIZES);
+
+ /* Initialize for converting result tuples to Python */
+ PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1,
+ exec_ctx->curr_proc);
+
+ oldcontext = CurrentMemoryContext;
+ PG_TRY();
+ {
+ MemoryContext oldcontext2;
+
+ if (rows)
+ {
+ uint64 i;
+
+ /*
+ * PyList_New() and PyList_SetItem() use Py_ssize_t for list
+ * size and list indices; so we cannot support a result larger
+ * than PY_SSIZE_T_MAX.
+ */
+ if (rows > (uint64) PY_SSIZE_T_MAX)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("query result has too many rows to fit in a Python list")));
+
+ Py_DECREF(result->rows);
+ result->rows = PyList_New(rows);
+ if (result->rows)
+ {
+ PLy_input_setup_tuple(&ininfo, tuptable->tupdesc,
+ exec_ctx->curr_proc);
+
+ for (i = 0; i < rows; i++)
+ {
+ PyObject *row = PLy_input_from_tuple(&ininfo,
+ tuptable->vals[i],
+ tuptable->tupdesc,
+ true);
+
+ PyList_SetItem(result->rows, i, row);
+ }
+ }
+ }
+
+ /*
+ * Save tuple descriptor for later use by result set metadata
+ * functions. Save it in TopMemoryContext so that it survives
+ * outside of an SPI context. We trust that PLy_result_dealloc()
+ * will clean it up when the time is right. (Do this as late as
+ * possible, to minimize the number of ways the tupdesc could get
+ * leaked due to errors.)
+ */
+ oldcontext2 = MemoryContextSwitchTo(TopMemoryContext);
+ result->tupdesc = CreateTupleDescCopy(tuptable->tupdesc);
+ MemoryContextSwitchTo(oldcontext2);
+ }
+ PG_CATCH();
+ {
+ MemoryContextSwitchTo(oldcontext);
+ MemoryContextDelete(cxt);
+ Py_DECREF(result);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ MemoryContextDelete(cxt);
+ SPI_freetuptable(tuptable);
+
+ /* in case PyList_New() failed above */
+ if (!result->rows)
+ {
+ Py_DECREF(result);
+ result = NULL;
+ }
+ }
+
+ return (PyObject *) result;
+}
+
+PyObject *
+PLy_commit(PyObject *self, PyObject *args)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+ PG_TRY();
+ {
+ SPI_commit();
+
+ /* was cleared at transaction end, reset pointer */
+ exec_ctx->scratch_ctx = NULL;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* was cleared at transaction end, reset pointer */
+ exec_ctx->scratch_ctx = NULL;
+
+ /* Look up the correct exception */
+ entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ HASH_FIND, NULL);
+
+ /*
+ * This could be a custom error code, if that's the case fallback to
+ * SPIError
+ */
+ exc = entry ? entry->exc : PLy_exc_spi_error;
+ /* Make Python raise the exception */
+ PLy_spi_exception_set(exc, edata);
+ FreeErrorData(edata);
+
+ return NULL;
+ }
+ PG_END_TRY();
+
+ Py_RETURN_NONE;
+}
+
+PyObject *
+PLy_rollback(PyObject *self, PyObject *args)
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+
+ PG_TRY();
+ {
+ SPI_rollback();
+
+ /* was cleared at transaction end, reset pointer */
+ exec_ctx->scratch_ctx = NULL;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* was cleared at transaction end, reset pointer */
+ exec_ctx->scratch_ctx = NULL;
+
+ /* Look up the correct exception */
+ entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ HASH_FIND, NULL);
+
+ /*
+ * This could be a custom error code, if that's the case fallback to
+ * SPIError
+ */
+ exc = entry ? entry->exc : PLy_exc_spi_error;
+ /* Make Python raise the exception */
+ PLy_spi_exception_set(exc, edata);
+ FreeErrorData(edata);
+
+ return NULL;
+ }
+ PG_END_TRY();
+
+ Py_RETURN_NONE;
+}
+
+/*
+ * Utilities for running SPI functions in subtransactions.
+ *
+ * Usage:
+ *
+ * MemoryContext oldcontext = CurrentMemoryContext;
+ * ResourceOwner oldowner = CurrentResourceOwner;
+ *
+ * PLy_spi_subtransaction_begin(oldcontext, oldowner);
+ * PG_TRY();
+ * {
+ * <call SPI functions>
+ * PLy_spi_subtransaction_commit(oldcontext, oldowner);
+ * }
+ * PG_CATCH();
+ * {
+ * <do cleanup>
+ * PLy_spi_subtransaction_abort(oldcontext, oldowner);
+ * return NULL;
+ * }
+ * PG_END_TRY();
+ *
+ * These utilities take care of restoring connection to the SPI manager and
+ * setting a Python exception in case of an abort.
+ */
+void
+PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ BeginInternalSubTransaction(NULL);
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+}
+
+void
+PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+}
+
+void
+PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ ErrorData *edata;
+ PLyExceptionEntry *entry;
+ PyObject *exc;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Look up the correct exception */
+ entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode),
+ HASH_FIND, NULL);
+
+ /*
+ * This could be a custom error code, if that's the case fallback to
+ * SPIError
+ */
+ exc = entry ? entry->exc : PLy_exc_spi_error;
+ /* Make Python raise the exception */
+ PLy_spi_exception_set(exc, edata);
+ FreeErrorData(edata);
+}
+
+/*
+ * Raise a SPIError, passing in it more error details, like the
+ * internal query and error position.
+ */
+static void
+PLy_spi_exception_set(PyObject *excclass, ErrorData *edata)
+{
+ PyObject *args = NULL;
+ PyObject *spierror = NULL;
+ PyObject *spidata = NULL;
+
+ args = Py_BuildValue("(s)", edata->message);
+ if (!args)
+ goto failure;
+
+ /* create a new SPI exception with the error message as the parameter */
+ spierror = PyObject_CallObject(excclass, args);
+ if (!spierror)
+ goto failure;
+
+ spidata = Py_BuildValue("(izzzizzzzz)", edata->sqlerrcode, edata->detail, edata->hint,
+ edata->internalquery, edata->internalpos,
+ edata->schema_name, edata->table_name, edata->column_name,
+ edata->datatype_name, edata->constraint_name);
+ if (!spidata)
+ goto failure;
+
+ if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1)
+ goto failure;
+
+ PyErr_SetObject(excclass, spierror);
+
+ Py_DECREF(args);
+ Py_DECREF(spierror);
+ Py_DECREF(spidata);
+ return;
+
+failure:
+ Py_XDECREF(args);
+ Py_XDECREF(spierror);
+ Py_XDECREF(spidata);
+ elog(ERROR, "could not convert SPI error to Python exception");
+}
diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h
new file mode 100644
index 0000000..98ccd21
--- /dev/null
+++ b/src/pl/plpython/plpy_spi.h
@@ -0,0 +1,29 @@
+/*
+ * src/pl/plpython/plpy_spi.h
+ */
+
+#ifndef PLPY_SPI_H
+#define PLPY_SPI_H
+
+#include "plpython.h"
+#include "utils/resowner.h"
+
+extern PyObject *PLy_spi_prepare(PyObject *self, PyObject *args);
+extern PyObject *PLy_spi_execute(PyObject *self, PyObject *args);
+extern PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit);
+
+extern PyObject *PLy_commit(PyObject *self, PyObject *args);
+extern PyObject *PLy_rollback(PyObject *self, PyObject *args);
+
+typedef struct PLyExceptionEntry
+{
+ int sqlstate; /* hash key, must be first */
+ PyObject *exc; /* corresponding exception */
+} PLyExceptionEntry;
+
+/* handling of SPI operations inside subtransactions */
+extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner);
+extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner);
+extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner);
+
+#endif /* PLPY_SPI_H */
diff --git a/src/pl/plpython/plpy_subxactobject.c b/src/pl/plpython/plpy_subxactobject.c
new file mode 100644
index 0000000..5c92a0e
--- /dev/null
+++ b/src/pl/plpython/plpy_subxactobject.c
@@ -0,0 +1,186 @@
+/*
+ * the PLySubtransaction class
+ *
+ * src/pl/plpython/plpy_subxactobject.c
+ */
+
+#include "postgres.h"
+
+#include "access/xact.h"
+#include "plpy_elog.h"
+#include "plpy_subxactobject.h"
+#include "plpython.h"
+#include "utils/memutils.h"
+
+List *explicit_subtransactions = NIL;
+
+
+static void PLy_subtransaction_dealloc(PyObject *subxact);
+static PyObject *PLy_subtransaction_enter(PyObject *self, PyObject *unused);
+static PyObject *PLy_subtransaction_exit(PyObject *self, PyObject *args);
+
+static char PLy_subtransaction_doc[] =
+"PostgreSQL subtransaction context manager";
+
+static PyMethodDef PLy_subtransaction_methods[] = {
+ {"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL},
+ {"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL},
+ /* user-friendly names for Python <2.6 */
+ {"enter", PLy_subtransaction_enter, METH_VARARGS, NULL},
+ {"exit", PLy_subtransaction_exit, METH_VARARGS, NULL},
+ {NULL, NULL, 0, NULL}
+};
+
+static PyTypeObject PLy_SubtransactionType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "PLySubtransaction",
+ .tp_basicsize = sizeof(PLySubtransactionObject),
+ .tp_dealloc = PLy_subtransaction_dealloc,
+ .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
+ .tp_doc = PLy_subtransaction_doc,
+ .tp_methods = PLy_subtransaction_methods,
+};
+
+
+void
+PLy_subtransaction_init_type(void)
+{
+ if (PyType_Ready(&PLy_SubtransactionType) < 0)
+ elog(ERROR, "could not initialize PLy_SubtransactionType");
+}
+
+/* s = plpy.subtransaction() */
+PyObject *
+PLy_subtransaction_new(PyObject *self, PyObject *unused)
+{
+ PLySubtransactionObject *ob;
+
+ ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType);
+
+ if (ob == NULL)
+ return NULL;
+
+ ob->started = false;
+ ob->exited = false;
+
+ return (PyObject *) ob;
+}
+
+/* Python requires a dealloc function to be defined */
+static void
+PLy_subtransaction_dealloc(PyObject *subxact)
+{
+}
+
+/*
+ * subxact.__enter__() or subxact.enter()
+ *
+ * Start an explicit subtransaction. SPI calls within an explicit
+ * subtransaction will not start another one, so you can atomically
+ * execute many SPI calls and still get a controllable exception if
+ * one of them fails.
+ */
+static PyObject *
+PLy_subtransaction_enter(PyObject *self, PyObject *unused)
+{
+ PLySubtransactionData *subxactdata;
+ MemoryContext oldcontext;
+ PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
+
+ if (subxact->started)
+ {
+ PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered");
+ return NULL;
+ }
+
+ if (subxact->exited)
+ {
+ PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
+ return NULL;
+ }
+
+ subxact->started = true;
+ oldcontext = CurrentMemoryContext;
+
+ subxactdata = (PLySubtransactionData *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(PLySubtransactionData));
+
+ subxactdata->oldcontext = oldcontext;
+ subxactdata->oldowner = CurrentResourceOwner;
+
+ BeginInternalSubTransaction(NULL);
+
+ /* Be sure that cells of explicit_subtransactions list are long-lived */
+ MemoryContextSwitchTo(TopTransactionContext);
+ explicit_subtransactions = lcons(subxactdata, explicit_subtransactions);
+
+ /* Caller wants to stay in original memory context */
+ MemoryContextSwitchTo(oldcontext);
+
+ Py_INCREF(self);
+ return self;
+}
+
+/*
+ * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb)
+ *
+ * Exit an explicit subtransaction. exc_type is an exception type, exc
+ * is the exception object, tb is the traceback. If exc_type is None,
+ * commit the subtransaction, if not abort it.
+ *
+ * The method signature is chosen to allow subtransaction objects to
+ * be used as context managers as described in
+ * <http://www.python.org/dev/peps/pep-0343/>.
+ */
+static PyObject *
+PLy_subtransaction_exit(PyObject *self, PyObject *args)
+{
+ PyObject *type;
+ PyObject *value;
+ PyObject *traceback;
+ PLySubtransactionData *subxactdata;
+ PLySubtransactionObject *subxact = (PLySubtransactionObject *) self;
+
+ if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback))
+ return NULL;
+
+ if (!subxact->started)
+ {
+ PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered");
+ return NULL;
+ }
+
+ if (subxact->exited)
+ {
+ PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited");
+ return NULL;
+ }
+
+ if (explicit_subtransactions == NIL)
+ {
+ PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from");
+ return NULL;
+ }
+
+ subxact->exited = true;
+
+ if (type != Py_None)
+ {
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ }
+ else
+ {
+ ReleaseCurrentSubTransaction();
+ }
+
+ subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions);
+ explicit_subtransactions = list_delete_first(explicit_subtransactions);
+
+ MemoryContextSwitchTo(subxactdata->oldcontext);
+ CurrentResourceOwner = subxactdata->oldowner;
+ pfree(subxactdata);
+
+ Py_RETURN_NONE;
+}
diff --git a/src/pl/plpython/plpy_subxactobject.h b/src/pl/plpython/plpy_subxactobject.h
new file mode 100644
index 0000000..5b6f863
--- /dev/null
+++ b/src/pl/plpython/plpy_subxactobject.h
@@ -0,0 +1,33 @@
+/*
+ * src/pl/plpython/plpy_subxactobject.h
+ */
+
+#ifndef PLPY_SUBXACTOBJECT
+#define PLPY_SUBXACTOBJECT
+
+#include "nodes/pg_list.h"
+#include "plpython.h"
+#include "utils/resowner.h"
+
+/* a list of nested explicit subtransactions */
+extern List *explicit_subtransactions;
+
+
+typedef struct PLySubtransactionObject
+{
+ PyObject_HEAD
+ bool started;
+ bool exited;
+} PLySubtransactionObject;
+
+/* explicit subtransaction data */
+typedef struct PLySubtransactionData
+{
+ MemoryContext oldcontext;
+ ResourceOwner oldowner;
+} PLySubtransactionData;
+
+extern void PLy_subtransaction_init_type(void);
+extern PyObject *PLy_subtransaction_new(PyObject *self, PyObject *unused);
+
+#endif /* PLPY_SUBXACTOBJECT */
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
new file mode 100644
index 0000000..db14c5f
--- /dev/null
+++ b/src/pl/plpython/plpy_typeio.c
@@ -0,0 +1,1557 @@
+/*
+ * transforming Datums to Python objects and vice versa
+ *
+ * src/pl/plpython/plpy_typeio.c
+ */
+
+#include "postgres.h"
+
+#include "access/htup_details.h"
+#include "catalog/pg_type.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "plpy_elog.h"
+#include "plpy_main.h"
+#include "plpy_typeio.h"
+#include "plpython.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/fmgroids.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+
+/* conversion from Datums to Python objects */
+static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInt16(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInt32(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyUnicode_FromScalar(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
+ char **dataptr_p, bits8 **bitmap_p, int *bitmask_p);
+static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated);
+
+/* conversion from Python objects to Datums */
+static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static Datum PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray);
+static void PLySequence_ToArray_recurse(PyObject *obj,
+ ArrayBuildState **astatep,
+ int *ndims, int *dims, int cur_depth,
+ PLyObToDatum *elm, Oid elmbasetype);
+
+/* conversion from Python objects to composite Datums */
+static Datum PLyUnicode_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray);
+static Datum PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping);
+static Datum PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence);
+static Datum PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray);
+
+
+/*
+ * Conversion functions. Remember output from Python is input to
+ * PostgreSQL, and vice versa.
+ */
+
+/*
+ * Perform input conversion, given correctly-set-up state information.
+ *
+ * This is the outer-level entry point for any input conversion. Internally,
+ * the conversion functions recurse directly to each other.
+ */
+PyObject *
+PLy_input_convert(PLyDatumToOb *arg, Datum val)
+{
+ PyObject *result;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
+ MemoryContext oldcontext;
+
+ /*
+ * Do the work in the scratch context to avoid leaking memory from the
+ * datatype output function calls. (The individual PLyDatumToObFunc
+ * functions can't reset the scratch context, because they recurse and an
+ * inner one might clobber data an outer one still needs. So we do it
+ * once at the outermost recursion level.)
+ *
+ * We reset the scratch context before, not after, each conversion cycle.
+ * This way we aren't on the hook to release a Python refcount on the
+ * result object in case MemoryContextReset throws an error.
+ */
+ MemoryContextReset(scratch_context);
+
+ oldcontext = MemoryContextSwitchTo(scratch_context);
+
+ result = arg->func(arg, val);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return result;
+}
+
+/*
+ * Perform output conversion, given correctly-set-up state information.
+ *
+ * This is the outer-level entry point for any output conversion. Internally,
+ * the conversion functions recurse directly to each other.
+ *
+ * The result, as well as any cruft generated along the way, are in the
+ * current memory context. Caller is responsible for cleanup.
+ */
+Datum
+PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull)
+{
+ /* at outer level, we are not considering an array element */
+ return arg->func(arg, val, isnull, false);
+}
+
+/*
+ * Transform a tuple into a Python dict object.
+ *
+ * Note: the tupdesc must match the one used to set up *arg. We could
+ * insist that this function lookup the tupdesc from what is in *arg,
+ * but in practice all callers have the right tupdesc available.
+ */
+PyObject *
+PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated)
+{
+ PyObject *dict;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
+ MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx);
+ MemoryContext oldcontext;
+
+ /*
+ * As in PLy_input_convert, do the work in the scratch context.
+ */
+ MemoryContextReset(scratch_context);
+
+ oldcontext = MemoryContextSwitchTo(scratch_context);
+
+ dict = PLyDict_FromTuple(arg, tuple, desc, include_generated);
+
+ MemoryContextSwitchTo(oldcontext);
+
+ return dict;
+}
+
+/*
+ * Initialize, or re-initialize, per-column input info for a composite type.
+ *
+ * This is separate from PLy_input_setup_func() because in cases involving
+ * anonymous record types, we need to be passed the tupdesc explicitly.
+ * It's caller's responsibility that the tupdesc has adequate lifespan
+ * in such cases. If the tupdesc is for a named composite or registered
+ * record type, it does not need to be long-lived.
+ */
+void
+PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, PLyProcedure *proc)
+{
+ int i;
+
+ /* We should be working on a previously-set-up struct */
+ Assert(arg->func == PLyDict_FromComposite);
+
+ /* Save pointer to tupdesc, but only if this is an anonymous record type */
+ if (arg->typoid == RECORDOID && arg->typmod < 0)
+ arg->u.tuple.recdesc = desc;
+
+ /* (Re)allocate atts array as needed */
+ if (arg->u.tuple.natts != desc->natts)
+ {
+ if (arg->u.tuple.atts)
+ pfree(arg->u.tuple.atts);
+ arg->u.tuple.natts = desc->natts;
+ arg->u.tuple.atts = (PLyDatumToOb *)
+ MemoryContextAllocZero(arg->mcxt,
+ desc->natts * sizeof(PLyDatumToOb));
+ }
+
+ /* Fill the atts entries, except for dropped columns */
+ for (i = 0; i < desc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyDatumToOb *att = &arg->u.tuple.atts[i];
+
+ if (attr->attisdropped)
+ continue;
+
+ if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
+ continue; /* already set up this entry */
+
+ PLy_input_setup_func(att, arg->mcxt,
+ attr->atttypid, attr->atttypmod,
+ proc);
+ }
+}
+
+/*
+ * Initialize, or re-initialize, per-column output info for a composite type.
+ *
+ * This is separate from PLy_output_setup_func() because in cases involving
+ * anonymous record types, we need to be passed the tupdesc explicitly.
+ * It's caller's responsibility that the tupdesc has adequate lifespan
+ * in such cases. If the tupdesc is for a named composite or registered
+ * record type, it does not need to be long-lived.
+ */
+void
+PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
+{
+ int i;
+
+ /* We should be working on a previously-set-up struct */
+ Assert(arg->func == PLyObject_ToComposite);
+
+ /* Save pointer to tupdesc, but only if this is an anonymous record type */
+ if (arg->typoid == RECORDOID && arg->typmod < 0)
+ arg->u.tuple.recdesc = desc;
+
+ /* (Re)allocate atts array as needed */
+ if (arg->u.tuple.natts != desc->natts)
+ {
+ if (arg->u.tuple.atts)
+ pfree(arg->u.tuple.atts);
+ arg->u.tuple.natts = desc->natts;
+ arg->u.tuple.atts = (PLyObToDatum *)
+ MemoryContextAllocZero(arg->mcxt,
+ desc->natts * sizeof(PLyObToDatum));
+ }
+
+ /* Fill the atts entries, except for dropped columns */
+ for (i = 0; i < desc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(desc, i);
+ PLyObToDatum *att = &arg->u.tuple.atts[i];
+
+ if (attr->attisdropped)
+ continue;
+
+ if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod)
+ continue; /* already set up this entry */
+
+ PLy_output_setup_func(att, arg->mcxt,
+ attr->atttypid, attr->atttypmod,
+ proc);
+ }
+}
+
+/*
+ * Set up output info for a PL/Python function returning record.
+ *
+ * Note: the given tupdesc is not necessarily long-lived.
+ */
+void
+PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc)
+{
+ /* Makes no sense unless RECORD */
+ Assert(arg->typoid == RECORDOID);
+ Assert(desc->tdtypeid == RECORDOID);
+
+ /*
+ * Bless the record type if not already done. We'd have to do this anyway
+ * to return a tuple, so we might as well force the issue so we can use
+ * the known-record-type code path.
+ */
+ BlessTupleDesc(desc);
+
+ /*
+ * Update arg->typmod, and clear the recdesc link if it's changed. The
+ * next call of PLyObject_ToComposite will look up a long-lived tupdesc
+ * for the record type.
+ */
+ arg->typmod = desc->tdtypmod;
+ if (arg->u.tuple.recdesc &&
+ arg->u.tuple.recdesc->tdtypmod != arg->typmod)
+ arg->u.tuple.recdesc = NULL;
+
+ /* Update derived data if necessary */
+ PLy_output_setup_tuple(arg, desc, proc);
+}
+
+/*
+ * Recursively initialize the PLyObToDatum structure(s) needed to construct
+ * a SQL value of the specified typeOid/typmod from a Python value.
+ * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
+ * record type.)
+ * proc is used to look up transform functions.
+ */
+void
+PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
+ Oid typeOid, int32 typmod,
+ PLyProcedure *proc)
+{
+ TypeCacheEntry *typentry;
+ char typtype;
+ Oid trfuncid;
+ Oid typinput;
+
+ /* Since this is recursive, it could theoretically be driven to overflow */
+ check_stack_depth();
+
+ arg->typoid = typeOid;
+ arg->typmod = typmod;
+ arg->mcxt = arg_mcxt;
+
+ /*
+ * Fetch typcache entry for the target type, asking for whatever info
+ * we'll need later. RECORD is a special case: just treat it as composite
+ * without bothering with the typcache entry.
+ */
+ if (typeOid != RECORDOID)
+ {
+ typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
+ typtype = typentry->typtype;
+ arg->typbyval = typentry->typbyval;
+ arg->typlen = typentry->typlen;
+ arg->typalign = typentry->typalign;
+ }
+ else
+ {
+ typentry = NULL;
+ typtype = TYPTYPE_COMPOSITE;
+ /* hard-wired knowledge about type RECORD: */
+ arg->typbyval = false;
+ arg->typlen = -1;
+ arg->typalign = TYPALIGN_DOUBLE;
+ }
+
+ /*
+ * Choose conversion method. Note that transform functions are checked
+ * for composite and scalar types, but not for arrays or domains. This is
+ * somewhat historical, but we'd have a problem allowing them on domains,
+ * since we drill down through all levels of a domain nest without looking
+ * at the intermediate levels at all.
+ */
+ if (typtype == TYPTYPE_DOMAIN)
+ {
+ /* Domain */
+ arg->func = PLyObject_ToDomain;
+ arg->u.domain.domain_info = NULL;
+ /* Recursively set up conversion info for the element type */
+ arg->u.domain.base = (PLyObToDatum *)
+ MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
+ PLy_output_setup_func(arg->u.domain.base, arg_mcxt,
+ typentry->domainBaseType,
+ typentry->domainBaseTypmod,
+ proc);
+ }
+ else if (typentry &&
+ IsTrueArrayType(typentry))
+ {
+ /* Standard array */
+ arg->func = PLySequence_ToArray;
+ /* Get base type OID to insert into constructed array */
+ /* (note this might not be the same as the immediate child type) */
+ arg->u.array.elmbasetype = getBaseType(typentry->typelem);
+ /* Recursively set up conversion info for the element type */
+ arg->u.array.elm = (PLyObToDatum *)
+ MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum));
+ PLy_output_setup_func(arg->u.array.elm, arg_mcxt,
+ typentry->typelem, typmod,
+ proc);
+ }
+ else if ((trfuncid = get_transform_tosql(typeOid,
+ proc->langid,
+ proc->trftypes)))
+ {
+ arg->func = PLyObject_ToTransform;
+ fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
+ }
+ else if (typtype == TYPTYPE_COMPOSITE)
+ {
+ /* Named composite type, or RECORD */
+ arg->func = PLyObject_ToComposite;
+ /* We'll set up the per-field data later */
+ arg->u.tuple.recdesc = NULL;
+ arg->u.tuple.typentry = typentry;
+ arg->u.tuple.tupdescid = INVALID_TUPLEDESC_IDENTIFIER;
+ arg->u.tuple.atts = NULL;
+ arg->u.tuple.natts = 0;
+ /* Mark this invalid till needed, too */
+ arg->u.tuple.recinfunc.fn_oid = InvalidOid;
+ }
+ else
+ {
+ /* Scalar type, but we have a couple of special cases */
+ switch (typeOid)
+ {
+ case BOOLOID:
+ arg->func = PLyObject_ToBool;
+ break;
+ case BYTEAOID:
+ arg->func = PLyObject_ToBytea;
+ break;
+ default:
+ arg->func = PLyObject_ToScalar;
+ getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam);
+ fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt);
+ break;
+ }
+ }
+}
+
+/*
+ * Recursively initialize the PLyDatumToOb structure(s) needed to construct
+ * a Python value from a SQL value of the specified typeOid/typmod.
+ * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate
+ * record type.)
+ * proc is used to look up transform functions.
+ */
+void
+PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
+ Oid typeOid, int32 typmod,
+ PLyProcedure *proc)
+{
+ TypeCacheEntry *typentry;
+ char typtype;
+ Oid trfuncid;
+ Oid typoutput;
+ bool typisvarlena;
+
+ /* Since this is recursive, it could theoretically be driven to overflow */
+ check_stack_depth();
+
+ arg->typoid = typeOid;
+ arg->typmod = typmod;
+ arg->mcxt = arg_mcxt;
+
+ /*
+ * Fetch typcache entry for the target type, asking for whatever info
+ * we'll need later. RECORD is a special case: just treat it as composite
+ * without bothering with the typcache entry.
+ */
+ if (typeOid != RECORDOID)
+ {
+ typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO);
+ typtype = typentry->typtype;
+ arg->typbyval = typentry->typbyval;
+ arg->typlen = typentry->typlen;
+ arg->typalign = typentry->typalign;
+ }
+ else
+ {
+ typentry = NULL;
+ typtype = TYPTYPE_COMPOSITE;
+ /* hard-wired knowledge about type RECORD: */
+ arg->typbyval = false;
+ arg->typlen = -1;
+ arg->typalign = TYPALIGN_DOUBLE;
+ }
+
+ /*
+ * Choose conversion method. Note that transform functions are checked
+ * for composite and scalar types, but not for arrays or domains. This is
+ * somewhat historical, but we'd have a problem allowing them on domains,
+ * since we drill down through all levels of a domain nest without looking
+ * at the intermediate levels at all.
+ */
+ if (typtype == TYPTYPE_DOMAIN)
+ {
+ /* Domain --- we don't care, just recurse down to the base type */
+ PLy_input_setup_func(arg, arg_mcxt,
+ typentry->domainBaseType,
+ typentry->domainBaseTypmod,
+ proc);
+ }
+ else if (typentry &&
+ IsTrueArrayType(typentry))
+ {
+ /* Standard array */
+ arg->func = PLyList_FromArray;
+ /* Recursively set up conversion info for the element type */
+ arg->u.array.elm = (PLyDatumToOb *)
+ MemoryContextAllocZero(arg_mcxt, sizeof(PLyDatumToOb));
+ PLy_input_setup_func(arg->u.array.elm, arg_mcxt,
+ typentry->typelem, typmod,
+ proc);
+ }
+ else if ((trfuncid = get_transform_fromsql(typeOid,
+ proc->langid,
+ proc->trftypes)))
+ {
+ arg->func = PLyObject_FromTransform;
+ fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt);
+ }
+ else if (typtype == TYPTYPE_COMPOSITE)
+ {
+ /* Named composite type, or RECORD */
+ arg->func = PLyDict_FromComposite;
+ /* We'll set up the per-field data later */
+ arg->u.tuple.recdesc = NULL;
+ arg->u.tuple.typentry = typentry;
+ arg->u.tuple.tupdescid = INVALID_TUPLEDESC_IDENTIFIER;
+ arg->u.tuple.atts = NULL;
+ arg->u.tuple.natts = 0;
+ }
+ else
+ {
+ /* Scalar type, but we have a couple of special cases */
+ switch (typeOid)
+ {
+ case BOOLOID:
+ arg->func = PLyBool_FromBool;
+ break;
+ case FLOAT4OID:
+ arg->func = PLyFloat_FromFloat4;
+ break;
+ case FLOAT8OID:
+ arg->func = PLyFloat_FromFloat8;
+ break;
+ case NUMERICOID:
+ arg->func = PLyDecimal_FromNumeric;
+ break;
+ case INT2OID:
+ arg->func = PLyLong_FromInt16;
+ break;
+ case INT4OID:
+ arg->func = PLyLong_FromInt32;
+ break;
+ case INT8OID:
+ arg->func = PLyLong_FromInt64;
+ break;
+ case OIDOID:
+ arg->func = PLyLong_FromOid;
+ break;
+ case BYTEAOID:
+ arg->func = PLyBytes_FromBytea;
+ break;
+ default:
+ arg->func = PLyUnicode_FromScalar;
+ getTypeOutputInfo(typeOid, &typoutput, &typisvarlena);
+ fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt);
+ break;
+ }
+ }
+}
+
+
+/*
+ * Special-purpose input converters.
+ */
+
+static PyObject *
+PLyBool_FromBool(PLyDatumToOb *arg, Datum d)
+{
+ if (DatumGetBool(d))
+ Py_RETURN_TRUE;
+ Py_RETURN_FALSE;
+}
+
+static PyObject *
+PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d)
+{
+ return PyFloat_FromDouble(DatumGetFloat4(d));
+}
+
+static PyObject *
+PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d)
+{
+ return PyFloat_FromDouble(DatumGetFloat8(d));
+}
+
+static PyObject *
+PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d)
+{
+ static PyObject *decimal_constructor;
+ char *str;
+ PyObject *pyvalue;
+
+ /* Try to import cdecimal. If it doesn't exist, fall back to decimal. */
+ if (!decimal_constructor)
+ {
+ PyObject *decimal_module;
+
+ decimal_module = PyImport_ImportModule("cdecimal");
+ if (!decimal_module)
+ {
+ PyErr_Clear();
+ decimal_module = PyImport_ImportModule("decimal");
+ }
+ if (!decimal_module)
+ PLy_elog(ERROR, "could not import a module for Decimal constructor");
+
+ decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal");
+ if (!decimal_constructor)
+ PLy_elog(ERROR, "no Decimal attribute in module");
+ }
+
+ str = DatumGetCString(DirectFunctionCall1(numeric_out, d));
+ pyvalue = PyObject_CallFunction(decimal_constructor, "s", str);
+ if (!pyvalue)
+ PLy_elog(ERROR, "conversion from numeric to Decimal failed");
+
+ return pyvalue;
+}
+
+static PyObject *
+PLyLong_FromInt16(PLyDatumToOb *arg, Datum d)
+{
+ return PyLong_FromLong(DatumGetInt16(d));
+}
+
+static PyObject *
+PLyLong_FromInt32(PLyDatumToOb *arg, Datum d)
+{
+ return PyLong_FromLong(DatumGetInt32(d));
+}
+
+static PyObject *
+PLyLong_FromInt64(PLyDatumToOb *arg, Datum d)
+{
+ return PyLong_FromLongLong(DatumGetInt64(d));
+}
+
+static PyObject *
+PLyLong_FromOid(PLyDatumToOb *arg, Datum d)
+{
+ return PyLong_FromUnsignedLong(DatumGetObjectId(d));
+}
+
+static PyObject *
+PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d)
+{
+ text *txt = DatumGetByteaPP(d);
+ char *str = VARDATA_ANY(txt);
+ size_t size = VARSIZE_ANY_EXHDR(txt);
+
+ return PyBytes_FromStringAndSize(str, size);
+}
+
+
+/*
+ * Generic input conversion using a SQL type's output function.
+ */
+static PyObject *
+PLyUnicode_FromScalar(PLyDatumToOb *arg, Datum d)
+{
+ char *x = OutputFunctionCall(&arg->u.scalar.typfunc, d);
+ PyObject *r = PLyUnicode_FromString(x);
+
+ pfree(x);
+ return r;
+}
+
+/*
+ * Convert using a from-SQL transform function.
+ */
+static PyObject *
+PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
+{
+ Datum t;
+
+ t = FunctionCall1(&arg->u.transform.typtransform, d);
+ return (PyObject *) DatumGetPointer(t);
+}
+
+/*
+ * Convert a SQL array to a Python list.
+ */
+static PyObject *
+PLyList_FromArray(PLyDatumToOb *arg, Datum d)
+{
+ ArrayType *array = DatumGetArrayTypeP(d);
+ PLyDatumToOb *elm = arg->u.array.elm;
+ int ndim;
+ int *dims;
+ char *dataptr;
+ bits8 *bitmap;
+ int bitmask;
+
+ if (ARR_NDIM(array) == 0)
+ return PyList_New(0);
+
+ /* Array dimensions and left bounds */
+ ndim = ARR_NDIM(array);
+ dims = ARR_DIMS(array);
+ Assert(ndim <= MAXDIM);
+
+ /*
+ * We iterate the SQL array in the physical order it's stored in the
+ * datum. For example, for a 3-dimensional array the order of iteration
+ * would be the following: [0,0,0] elements through [0,0,k], then [0,1,0]
+ * through [0,1,k] till [0,m,k], then [1,0,0] through [1,0,k] till
+ * [1,m,k], and so on.
+ *
+ * In Python, there are no multi-dimensional lists as such, but they are
+ * represented as a list of lists. So a 3-d array of [n,m,k] elements is a
+ * list of n m-element arrays, each element of which is k-element array.
+ * PLyList_FromArray_recurse() builds the Python list for a single
+ * dimension, and recurses for the next inner dimension.
+ */
+ dataptr = ARR_DATA_PTR(array);
+ bitmap = ARR_NULLBITMAP(array);
+ bitmask = 1;
+
+ return PLyList_FromArray_recurse(elm, dims, ndim, 0,
+ &dataptr, &bitmap, &bitmask);
+}
+
+static PyObject *
+PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim,
+ char **dataptr_p, bits8 **bitmap_p, int *bitmask_p)
+{
+ int i;
+ PyObject *list;
+
+ list = PyList_New(dims[dim]);
+ if (!list)
+ return NULL;
+
+ if (dim < ndim - 1)
+ {
+ /* Outer dimension. Recurse for each inner slice. */
+ for (i = 0; i < dims[dim]; i++)
+ {
+ PyObject *sublist;
+
+ sublist = PLyList_FromArray_recurse(elm, dims, ndim, dim + 1,
+ dataptr_p, bitmap_p, bitmask_p);
+ PyList_SET_ITEM(list, i, sublist);
+ }
+ }
+ else
+ {
+ /*
+ * Innermost dimension. Fill the list with the values from the array
+ * for this slice.
+ */
+ char *dataptr = *dataptr_p;
+ bits8 *bitmap = *bitmap_p;
+ int bitmask = *bitmask_p;
+
+ for (i = 0; i < dims[dim]; i++)
+ {
+ /* checking for NULL */
+ if (bitmap && (*bitmap & bitmask) == 0)
+ {
+ Py_INCREF(Py_None);
+ PyList_SET_ITEM(list, i, Py_None);
+ }
+ else
+ {
+ Datum itemvalue;
+
+ itemvalue = fetch_att(dataptr, elm->typbyval, elm->typlen);
+ PyList_SET_ITEM(list, i, elm->func(elm, itemvalue));
+ dataptr = att_addlength_pointer(dataptr, elm->typlen, dataptr);
+ dataptr = (char *) att_align_nominal(dataptr, elm->typalign);
+ }
+
+ /* advance bitmap pointer if any */
+ if (bitmap)
+ {
+ bitmask <<= 1;
+ if (bitmask == 0x100 /* (1<<8) */ )
+ {
+ bitmap++;
+ bitmask = 1;
+ }
+ }
+ }
+
+ *dataptr_p = dataptr;
+ *bitmap_p = bitmap;
+ *bitmask_p = bitmask;
+ }
+
+ return list;
+}
+
+/*
+ * Convert a composite SQL value to a Python dict.
+ */
+static PyObject *
+PLyDict_FromComposite(PLyDatumToOb *arg, Datum d)
+{
+ PyObject *dict;
+ HeapTupleHeader td;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ HeapTupleData tmptup;
+
+ td = DatumGetHeapTupleHeader(d);
+ /* Extract rowtype info and find a tupdesc */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+
+ /* Set up I/O funcs if not done yet */
+ PLy_input_setup_tuple(arg, tupdesc,
+ PLy_current_execution_context()->curr_proc);
+
+ /* Build a temporary HeapTuple control structure */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ tmptup.t_data = td;
+
+ dict = PLyDict_FromTuple(arg, &tmptup, tupdesc, true);
+
+ ReleaseTupleDesc(tupdesc);
+
+ return dict;
+}
+
+/*
+ * Transform a tuple into a Python dict object.
+ */
+static PyObject *
+PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated)
+{
+ PyObject *volatile dict;
+
+ /* Simple sanity check that desc matches */
+ Assert(desc->natts == arg->u.tuple.natts);
+
+ dict = PyDict_New();
+ if (dict == NULL)
+ return NULL;
+
+ PG_TRY();
+ {
+ int i;
+
+ for (i = 0; i < arg->u.tuple.natts; i++)
+ {
+ PLyDatumToOb *att = &arg->u.tuple.atts[i];
+ Form_pg_attribute attr = TupleDescAttr(desc, i);
+ char *key;
+ Datum vattr;
+ bool is_null;
+ PyObject *value;
+
+ if (attr->attisdropped)
+ continue;
+
+ if (attr->attgenerated)
+ {
+ /* don't include unless requested */
+ if (!include_generated)
+ continue;
+ }
+
+ key = NameStr(attr->attname);
+ vattr = heap_getattr(tuple, (i + 1), desc, &is_null);
+
+ if (is_null)
+ PyDict_SetItemString(dict, key, Py_None);
+ else
+ {
+ value = att->func(att, vattr);
+ PyDict_SetItemString(dict, key, value);
+ Py_DECREF(value);
+ }
+ }
+ }
+ PG_CATCH();
+ {
+ Py_DECREF(dict);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ return dict;
+}
+
+/*
+ * Convert a Python object to a PostgreSQL bool datum. This can't go
+ * through the generic conversion function, because Python attaches a
+ * Boolean value to everything, more things than the PostgreSQL bool
+ * type can parse.
+ */
+static Datum
+PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+ return BoolGetDatum(PyObject_IsTrue(plrv));
+}
+
+/*
+ * Convert a Python object to a PostgreSQL bytea datum. This doesn't
+ * go through the generic conversion function to circumvent problems
+ * with embedded nulls. And it's faster this way.
+ */
+static Datum
+PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ PyObject *volatile plrv_so = NULL;
+ Datum rv = (Datum) 0;
+
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+
+ plrv_so = PyObject_Bytes(plrv);
+ if (!plrv_so)
+ PLy_elog(ERROR, "could not create bytes representation of Python object");
+
+ PG_TRY();
+ {
+ char *plrv_sc = PyBytes_AsString(plrv_so);
+ size_t len = PyBytes_Size(plrv_so);
+ size_t size = len + VARHDRSZ;
+ bytea *result = palloc(size);
+
+ SET_VARSIZE(result, size);
+ memcpy(VARDATA(result), plrv_sc, len);
+ rv = PointerGetDatum(result);
+ }
+ PG_FINALLY();
+ {
+ Py_XDECREF(plrv_so);
+ }
+ PG_END_TRY();
+
+ return rv;
+}
+
+
+/*
+ * Convert a Python object to a composite type. First look up the type's
+ * description, then route the Python object through the conversion function
+ * for obtaining PostgreSQL tuples.
+ */
+static Datum
+PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ Datum rv;
+ TupleDesc desc;
+
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+
+ /*
+ * The string conversion case doesn't require a tupdesc, nor per-field
+ * conversion data, so just go for it if that's the case to use.
+ */
+ if (PyUnicode_Check(plrv))
+ return PLyUnicode_ToComposite(arg, plrv, inarray);
+
+ /*
+ * If we're dealing with a named composite type, we must look up the
+ * tupdesc every time, to protect against possible changes to the type.
+ * RECORD types can't change between calls; but we must still be willing
+ * to set up the info the first time, if nobody did yet.
+ */
+ if (arg->typoid != RECORDOID)
+ {
+ desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
+ /* We should have the descriptor of the type's typcache entry */
+ Assert(desc == arg->u.tuple.typentry->tupDesc);
+ /* Detect change of descriptor, update cache if needed */
+ if (arg->u.tuple.tupdescid != arg->u.tuple.typentry->tupDesc_identifier)
+ {
+ PLy_output_setup_tuple(arg, desc,
+ PLy_current_execution_context()->curr_proc);
+ arg->u.tuple.tupdescid = arg->u.tuple.typentry->tupDesc_identifier;
+ }
+ }
+ else
+ {
+ desc = arg->u.tuple.recdesc;
+ if (desc == NULL)
+ {
+ desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod);
+ arg->u.tuple.recdesc = desc;
+ }
+ else
+ {
+ /* Pin descriptor to match unpin below */
+ PinTupleDesc(desc);
+ }
+ }
+
+ /* Simple sanity check on our caching */
+ Assert(desc->natts == arg->u.tuple.natts);
+
+ /*
+ * Convert, using the appropriate method depending on the type of the
+ * supplied Python object.
+ */
+ if (PySequence_Check(plrv))
+ /* composite type as sequence (tuple, list etc) */
+ rv = PLySequence_ToComposite(arg, desc, plrv);
+ else if (PyMapping_Check(plrv))
+ /* composite type as mapping (currently only dict) */
+ rv = PLyMapping_ToComposite(arg, desc, plrv);
+ else
+ /* returned as smth, must provide method __getattr__(name) */
+ rv = PLyGenericObject_ToComposite(arg, desc, plrv, inarray);
+
+ ReleaseTupleDesc(desc);
+
+ return rv;
+}
+
+
+/*
+ * Convert Python object to C string in server encoding.
+ *
+ * Note: this is exported for use by add-on transform modules.
+ */
+char *
+PLyObject_AsString(PyObject *plrv)
+{
+ PyObject *plrv_bo;
+ char *plrv_sc;
+ size_t plen;
+ size_t slen;
+
+ if (PyUnicode_Check(plrv))
+ plrv_bo = PLyUnicode_Bytes(plrv);
+ else if (PyFloat_Check(plrv))
+ {
+ /* use repr() for floats, str() is lossy */
+ PyObject *s = PyObject_Repr(plrv);
+
+ plrv_bo = PLyUnicode_Bytes(s);
+ Py_XDECREF(s);
+ }
+ else
+ {
+ PyObject *s = PyObject_Str(plrv);
+
+ plrv_bo = PLyUnicode_Bytes(s);
+ Py_XDECREF(s);
+ }
+ if (!plrv_bo)
+ PLy_elog(ERROR, "could not create string representation of Python object");
+
+ plrv_sc = pstrdup(PyBytes_AsString(plrv_bo));
+ plen = PyBytes_Size(plrv_bo);
+ slen = strlen(plrv_sc);
+
+ Py_XDECREF(plrv_bo);
+
+ if (slen < plen)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
+ else if (slen > plen)
+ elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
+ pg_verifymbstr(plrv_sc, slen, false);
+
+ return plrv_sc;
+}
+
+
+/*
+ * Generic output conversion function: convert PyObject to cstring and
+ * cstring into PostgreSQL type.
+ */
+static Datum
+PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ char *str;
+
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+
+ str = PLyObject_AsString(plrv);
+
+ return InputFunctionCall(&arg->u.scalar.typfunc,
+ str,
+ arg->u.scalar.typioparam,
+ arg->typmod);
+}
+
+
+/*
+ * Convert to a domain type.
+ */
+static Datum
+PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ Datum result;
+ PLyObToDatum *base = arg->u.domain.base;
+
+ result = base->func(base, plrv, isnull, inarray);
+ domain_check(result, *isnull, arg->typoid,
+ &arg->u.domain.domain_info, arg->mcxt);
+ return result;
+}
+
+
+/*
+ * Convert using a to-SQL transform function.
+ */
+static Datum
+PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+ return FunctionCall1(&arg->u.transform.typtransform, PointerGetDatum(plrv));
+}
+
+
+/*
+ * Convert Python sequence (or list of lists) to SQL array.
+ */
+static Datum
+PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv,
+ bool *isnull, bool inarray)
+{
+ ArrayBuildState *astate = NULL;
+ int ndims = 1;
+ int dims[MAXDIM];
+ int lbs[MAXDIM];
+
+ if (plrv == Py_None)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ *isnull = false;
+
+ /*
+ * For historical reasons, we allow any sequence (not only a list) at the
+ * top level when converting a Python object to a SQL array. However, a
+ * multi-dimensional array is recognized only when the object contains
+ * true lists.
+ */
+ if (!PySequence_Check(plrv))
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("return value of function with array return type is not a Python sequence")));
+
+ /* Initialize dimensionality info with first-level dimension */
+ memset(dims, 0, sizeof(dims));
+ dims[0] = PySequence_Length(plrv);
+
+ /*
+ * Traverse the Python lists, in depth-first order, and collect all the
+ * elements at the bottom level into an ArrayBuildState.
+ */
+ PLySequence_ToArray_recurse(plrv, &astate,
+ &ndims, dims, 1,
+ arg->u.array.elm,
+ arg->u.array.elmbasetype);
+
+ /* ensure we get zero-D array for no inputs, as per PG convention */
+ if (astate == NULL)
+ return PointerGetDatum(construct_empty_array(arg->u.array.elmbasetype));
+
+ for (int i = 0; i < ndims; i++)
+ lbs[i] = 1;
+
+ return makeMdArrayResult(astate, ndims, dims, lbs,
+ CurrentMemoryContext, true);
+}
+
+/*
+ * Helper function for PLySequence_ToArray. Traverse a Python list of lists in
+ * depth-first order, storing the elements in *astatep.
+ *
+ * The ArrayBuildState is created only when we first find a scalar element;
+ * if we didn't do it like that, we'd need some other convention for knowing
+ * whether we'd already found any scalars (and thus the number of dimensions
+ * is frozen).
+ */
+static void
+PLySequence_ToArray_recurse(PyObject *obj, ArrayBuildState **astatep,
+ int *ndims, int *dims, int cur_depth,
+ PLyObToDatum *elm, Oid elmbasetype)
+{
+ int i;
+ int len = PySequence_Length(obj);
+
+ /* We should not get here with a non-sequence object */
+ if (len < 0)
+ PLy_elog(ERROR, "could not determine sequence length for function return value");
+
+ for (i = 0; i < len; i++)
+ {
+ /* fetch the array element */
+ PyObject *subobj = PySequence_GetItem(obj, i);
+
+ /* need PG_TRY to ensure we release the subobj's refcount */
+ PG_TRY();
+ {
+ /* multi-dimensional array? */
+ if (PyList_Check(subobj))
+ {
+ /* set size when at first element in this level, else compare */
+ if (i == 0 && *ndims == cur_depth)
+ {
+ /* array after some scalars at same level? */
+ if (*astatep != NULL)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+ /* too many dimensions? */
+ if (cur_depth >= MAXDIM)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("number of array dimensions exceeds the maximum allowed (%d)",
+ MAXDIM)));
+ /* OK, add a dimension */
+ dims[*ndims] = PySequence_Length(subobj);
+ (*ndims)++;
+ }
+ else if (cur_depth >= *ndims ||
+ PySequence_Length(subobj) != dims[cur_depth])
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+ /* recurse to fetch elements of this sub-array */
+ PLySequence_ToArray_recurse(subobj, astatep,
+ ndims, dims, cur_depth + 1,
+ elm, elmbasetype);
+ }
+ else
+ {
+ Datum dat;
+ bool isnull;
+
+ /* scalar after some sub-arrays at same level? */
+ if (*ndims != cur_depth)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("multidimensional arrays must have array expressions with matching dimensions")));
+
+ /* convert non-list object to Datum */
+ dat = elm->func(elm, subobj, &isnull, true);
+
+ /* create ArrayBuildState if we didn't already */
+ if (*astatep == NULL)
+ *astatep = initArrayResult(elmbasetype,
+ CurrentMemoryContext, true);
+
+ /* ... and save the element value in it */
+ (void) accumArrayResult(*astatep, dat, isnull,
+ elmbasetype, CurrentMemoryContext);
+ }
+ }
+ PG_FINALLY();
+ {
+ Py_XDECREF(subobj);
+ }
+ PG_END_TRY();
+ }
+}
+
+
+/*
+ * Convert a Python string to composite, using record_in.
+ */
+static Datum
+PLyUnicode_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray)
+{
+ char *str;
+
+ /*
+ * Set up call data for record_in, if we didn't already. (We can't just
+ * use DirectFunctionCall, because record_in needs a fn_extra field.)
+ */
+ if (!OidIsValid(arg->u.tuple.recinfunc.fn_oid))
+ fmgr_info_cxt(F_RECORD_IN, &arg->u.tuple.recinfunc, arg->mcxt);
+
+ str = PLyObject_AsString(string);
+
+ /*
+ * If we are parsing a composite type within an array, and the string
+ * isn't a valid record literal, there's a high chance that the function
+ * did something like:
+ *
+ * CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$
+ * LANGUAGE plpython;
+ *
+ * Before PostgreSQL 10, that was interpreted as a single-dimensional
+ * array, containing record ('foo', 'bar'). PostgreSQL 10 added support
+ * for multi-dimensional arrays, and it is now interpreted as a
+ * two-dimensional array, containing two records, 'foo', and 'bar'.
+ * record_in() will throw an error, because "foo" is not a valid record
+ * literal.
+ *
+ * To make that less confusing to users who are upgrading from older
+ * versions, try to give a hint in the typical instances of that. If we
+ * are parsing an array of composite types, and we see a string literal
+ * that is not a valid record literal, give a hint. We only want to give
+ * the hint in the narrow case of a malformed string literal, not any
+ * error from record_in(), so check for that case here specifically.
+ *
+ * This check better match the one in record_in(), so that we don't forbid
+ * literals that are actually valid!
+ */
+ if (inarray)
+ {
+ char *ptr = str;
+
+ /* Allow leading whitespace */
+ while (*ptr && isspace((unsigned char) *ptr))
+ ptr++;
+ if (*ptr++ != '(')
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION),
+ errmsg("malformed record literal: \"%s\"", str),
+ errdetail("Missing left parenthesis."),
+ errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\".")));
+ }
+
+ return InputFunctionCall(&arg->u.tuple.recinfunc,
+ str,
+ arg->typoid,
+ arg->typmod);
+}
+
+
+static Datum
+PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping)
+{
+ Datum result;
+ HeapTuple tuple;
+ Datum *values;
+ bool *nulls;
+ volatile int i;
+
+ Assert(PyMapping_Check(mapping));
+
+ /* Build tuple */
+ values = palloc(sizeof(Datum) * desc->natts);
+ nulls = palloc(sizeof(bool) * desc->natts);
+ for (i = 0; i < desc->natts; ++i)
+ {
+ char *key;
+ PyObject *volatile value;
+ PLyObToDatum *att;
+ Form_pg_attribute attr = TupleDescAttr(desc, i);
+
+ if (attr->attisdropped)
+ {
+ values[i] = (Datum) 0;
+ nulls[i] = true;
+ continue;
+ }
+
+ key = NameStr(attr->attname);
+ value = NULL;
+ att = &arg->u.tuple.atts[i];
+ PG_TRY();
+ {
+ value = PyMapping_GetItemString(mapping, key);
+ if (!value)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("key \"%s\" not found in mapping", key),
+ errhint("To return null in a column, "
+ "add the value None to the mapping with the key named after the column.")));
+
+ values[i] = att->func(att, value, &nulls[i], false);
+
+ Py_XDECREF(value);
+ value = NULL;
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(value);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ }
+
+ tuple = heap_form_tuple(desc, values, nulls);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+
+static Datum
+PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence)
+{
+ Datum result;
+ HeapTuple tuple;
+ Datum *values;
+ bool *nulls;
+ volatile int idx;
+ volatile int i;
+
+ Assert(PySequence_Check(sequence));
+
+ /*
+ * Check that sequence length is exactly same as PG tuple's. We actually
+ * can ignore exceeding items or assume missing ones as null but to avoid
+ * plpython developer's errors we are strict here
+ */
+ idx = 0;
+ for (i = 0; i < desc->natts; i++)
+ {
+ if (!TupleDescAttr(desc, i)->attisdropped)
+ idx++;
+ }
+ if (PySequence_Length(sequence) != idx)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("length of returned sequence did not match number of columns in row")));
+
+ /* Build tuple */
+ values = palloc(sizeof(Datum) * desc->natts);
+ nulls = palloc(sizeof(bool) * desc->natts);
+ idx = 0;
+ for (i = 0; i < desc->natts; ++i)
+ {
+ PyObject *volatile value;
+ PLyObToDatum *att;
+
+ if (TupleDescAttr(desc, i)->attisdropped)
+ {
+ values[i] = (Datum) 0;
+ nulls[i] = true;
+ continue;
+ }
+
+ value = NULL;
+ att = &arg->u.tuple.atts[i];
+ PG_TRY();
+ {
+ value = PySequence_GetItem(sequence, idx);
+ Assert(value);
+
+ values[i] = att->func(att, value, &nulls[i], false);
+
+ Py_XDECREF(value);
+ value = NULL;
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(value);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ idx++;
+ }
+
+ tuple = heap_form_tuple(desc, values, nulls);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
+
+
+static Datum
+PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray)
+{
+ Datum result;
+ HeapTuple tuple;
+ Datum *values;
+ bool *nulls;
+ volatile int i;
+
+ /* Build tuple */
+ values = palloc(sizeof(Datum) * desc->natts);
+ nulls = palloc(sizeof(bool) * desc->natts);
+ for (i = 0; i < desc->natts; ++i)
+ {
+ char *key;
+ PyObject *volatile value;
+ PLyObToDatum *att;
+ Form_pg_attribute attr = TupleDescAttr(desc, i);
+
+ if (attr->attisdropped)
+ {
+ values[i] = (Datum) 0;
+ nulls[i] = true;
+ continue;
+ }
+
+ key = NameStr(attr->attname);
+ value = NULL;
+ att = &arg->u.tuple.atts[i];
+ PG_TRY();
+ {
+ value = PyObject_GetAttrString(object, key);
+ if (!value)
+ {
+ /*
+ * No attribute for this column in the object.
+ *
+ * If we are parsing a composite type in an array, a likely
+ * cause is that the function contained something like "[[123,
+ * 'foo']]". Before PostgreSQL 10, that was interpreted as an
+ * array, with a composite type (123, 'foo') in it. But now
+ * it's interpreted as a two-dimensional array, and we try to
+ * interpret "123" as the composite type. See also similar
+ * heuristic in PLyObject_ToScalar().
+ */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("attribute \"%s\" does not exist in Python object", key),
+ inarray ?
+ errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\".") :
+ errhint("To return null in a column, let the returned object have an attribute named after column with value None.")));
+ }
+
+ values[i] = att->func(att, value, &nulls[i], false);
+
+ Py_XDECREF(value);
+ value = NULL;
+ }
+ PG_CATCH();
+ {
+ Py_XDECREF(value);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ }
+
+ tuple = heap_form_tuple(desc, values, nulls);
+ result = heap_copy_tuple_as_datum(tuple, desc);
+ heap_freetuple(tuple);
+
+ pfree(values);
+ pfree(nulls);
+
+ return result;
+}
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
new file mode 100644
index 0000000..d11e6ae
--- /dev/null
+++ b/src/pl/plpython/plpy_typeio.h
@@ -0,0 +1,175 @@
+/*
+ * src/pl/plpython/plpy_typeio.h
+ */
+
+#ifndef PLPY_TYPEIO_H
+#define PLPY_TYPEIO_H
+
+#include "access/htup.h"
+#include "fmgr.h"
+#include "plpython.h"
+#include "utils/typcache.h"
+
+struct PLyProcedure; /* avoid requiring plpy_procedure.h here */
+
+
+/*
+ * "Input" conversion from PostgreSQL Datum to a Python object.
+ *
+ * arg is the previously-set-up conversion data, val is the value to convert.
+ * val mustn't be NULL.
+ *
+ * Note: the conversion data structs should be regarded as private to
+ * plpy_typeio.c. We declare them here only so that other modules can
+ * define structs containing them.
+ */
+typedef struct PLyDatumToOb PLyDatumToOb; /* forward reference */
+
+typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val);
+
+typedef struct PLyScalarToOb
+{
+ FmgrInfo typfunc; /* lookup info for type's output function */
+} PLyScalarToOb;
+
+typedef struct PLyArrayToOb
+{
+ PLyDatumToOb *elm; /* conversion info for array's element type */
+} PLyArrayToOb;
+
+typedef struct PLyTupleToOb
+{
+ /* If we're dealing with a RECORD type, actual descriptor is here: */
+ TupleDesc recdesc;
+ /* If we're dealing with a named composite type, these fields are set: */
+ TypeCacheEntry *typentry; /* typcache entry for type */
+ uint64 tupdescid; /* last tupdesc identifier seen in typcache */
+ /* These fields are NULL/0 if not yet set: */
+ PLyDatumToOb *atts; /* array of per-column conversion info */
+ int natts; /* length of array */
+} PLyTupleToOb;
+
+typedef struct PLyTransformToOb
+{
+ FmgrInfo typtransform; /* lookup info for from-SQL transform func */
+} PLyTransformToOb;
+
+struct PLyDatumToOb
+{
+ PLyDatumToObFunc func; /* conversion control function */
+ Oid typoid; /* OID of the source type */
+ int32 typmod; /* typmod of the source type */
+ bool typbyval; /* its physical representation details */
+ int16 typlen;
+ char typalign;
+ MemoryContext mcxt; /* context this info is stored in */
+ union /* conversion-type-specific data */
+ {
+ PLyScalarToOb scalar;
+ PLyArrayToOb array;
+ PLyTupleToOb tuple;
+ PLyTransformToOb transform;
+ } u;
+};
+
+/*
+ * "Output" conversion from Python object to a PostgreSQL Datum.
+ *
+ * arg is the previously-set-up conversion data, val is the value to convert.
+ *
+ * *isnull is set to true if val is Py_None, false otherwise.
+ * (The conversion function *must* be called even for Py_None,
+ * so that domain constraints can be checked.)
+ *
+ * inarray is true if the converted value was in an array (Python list).
+ * It is used to give a better error message in some cases.
+ */
+typedef struct PLyObToDatum PLyObToDatum; /* forward reference */
+
+typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val,
+ bool *isnull,
+ bool inarray);
+
+typedef struct PLyObToScalar
+{
+ FmgrInfo typfunc; /* lookup info for type's input function */
+ Oid typioparam; /* argument to pass to it */
+} PLyObToScalar;
+
+typedef struct PLyObToArray
+{
+ PLyObToDatum *elm; /* conversion info for array's element type */
+ Oid elmbasetype; /* element base type */
+} PLyObToArray;
+
+typedef struct PLyObToTuple
+{
+ /* If we're dealing with a RECORD type, actual descriptor is here: */
+ TupleDesc recdesc;
+ /* If we're dealing with a named composite type, these fields are set: */
+ TypeCacheEntry *typentry; /* typcache entry for type */
+ uint64 tupdescid; /* last tupdesc identifier seen in typcache */
+ /* These fields are NULL/0 if not yet set: */
+ PLyObToDatum *atts; /* array of per-column conversion info */
+ int natts; /* length of array */
+ /* We might need to convert using record_in(); if so, cache info here */
+ FmgrInfo recinfunc; /* lookup info for record_in */
+} PLyObToTuple;
+
+typedef struct PLyObToDomain
+{
+ PLyObToDatum *base; /* conversion info for domain's base type */
+ void *domain_info; /* cache space for domain_check() */
+} PLyObToDomain;
+
+typedef struct PLyObToTransform
+{
+ FmgrInfo typtransform; /* lookup info for to-SQL transform function */
+} PLyObToTransform;
+
+struct PLyObToDatum
+{
+ PLyObToDatumFunc func; /* conversion control function */
+ Oid typoid; /* OID of the target type */
+ int32 typmod; /* typmod of the target type */
+ bool typbyval; /* its physical representation details */
+ int16 typlen;
+ char typalign;
+ MemoryContext mcxt; /* context this info is stored in */
+ union /* conversion-type-specific data */
+ {
+ PLyObToScalar scalar;
+ PLyObToArray array;
+ PLyObToTuple tuple;
+ PLyObToDomain domain;
+ PLyObToTransform transform;
+ } u;
+};
+
+
+extern PyObject *PLy_input_convert(PLyDatumToOb *arg, Datum val);
+extern Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val,
+ bool *isnull);
+
+extern PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple,
+ TupleDesc desc, bool include_generated);
+
+extern void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt,
+ Oid typeOid, int32 typmod,
+ struct PLyProcedure *proc);
+extern void PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt,
+ Oid typeOid, int32 typmod,
+ struct PLyProcedure *proc);
+
+extern void PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc,
+ struct PLyProcedure *proc);
+extern void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc,
+ struct PLyProcedure *proc);
+
+extern void PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc,
+ struct PLyProcedure *proc);
+
+/* conversion from Python objects to C strings --- exported for transforms */
+extern char *PLyObject_AsString(PyObject *plrv);
+
+#endif /* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c
new file mode 100644
index 0000000..22e2a59
--- /dev/null
+++ b/src/pl/plpython/plpy_util.c
@@ -0,0 +1,121 @@
+/*
+ * utility functions
+ *
+ * src/pl/plpython/plpy_util.c
+ */
+
+#include "postgres.h"
+
+#include "mb/pg_wchar.h"
+#include "plpy_elog.h"
+#include "plpy_util.h"
+#include "plpython.h"
+#include "utils/memutils.h"
+
+/*
+ * Convert a Python unicode object to a Python string/bytes object in
+ * PostgreSQL server encoding. Reference ownership is passed to the
+ * caller.
+ */
+PyObject *
+PLyUnicode_Bytes(PyObject *unicode)
+{
+ PyObject *bytes,
+ *rv;
+ char *utf8string,
+ *encoded;
+
+ /* First encode the Python unicode object with UTF-8. */
+ bytes = PyUnicode_AsUTF8String(unicode);
+ if (bytes == NULL)
+ PLy_elog(ERROR, "could not convert Python Unicode object to bytes");
+
+ utf8string = PyBytes_AsString(bytes);
+ if (utf8string == NULL)
+ {
+ Py_DECREF(bytes);
+ PLy_elog(ERROR, "could not extract bytes from encoded string");
+ }
+
+ /*
+ * Then convert to server encoding if necessary.
+ *
+ * PyUnicode_AsEncodedString could be used to encode the object directly
+ * in the server encoding, but Python doesn't support all the encodings
+ * that PostgreSQL does (EUC_TW and MULE_INTERNAL). UTF-8 is used as an
+ * intermediary in PLyUnicode_FromString as well.
+ */
+ if (GetDatabaseEncoding() != PG_UTF8)
+ {
+ PG_TRY();
+ {
+ encoded = pg_any_to_server(utf8string,
+ strlen(utf8string),
+ PG_UTF8);
+ }
+ PG_CATCH();
+ {
+ Py_DECREF(bytes);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+ }
+ else
+ encoded = utf8string;
+
+ /* finally, build a bytes object in the server encoding */
+ rv = PyBytes_FromStringAndSize(encoded, strlen(encoded));
+
+ /* if pg_any_to_server allocated memory, free it now */
+ if (utf8string != encoded)
+ pfree(encoded);
+
+ Py_DECREF(bytes);
+ return rv;
+}
+
+/*
+ * Convert a Python unicode object to a C string in PostgreSQL server
+ * encoding. No Python object reference is passed out of this
+ * function. The result is palloc'ed.
+ */
+char *
+PLyUnicode_AsString(PyObject *unicode)
+{
+ PyObject *o = PLyUnicode_Bytes(unicode);
+ char *rv = pstrdup(PyBytes_AsString(o));
+
+ Py_XDECREF(o);
+ return rv;
+}
+
+/*
+ * Convert a C string in the PostgreSQL server encoding to a Python
+ * unicode object. Reference ownership is passed to the caller.
+ */
+PyObject *
+PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size)
+{
+ char *utf8string;
+ PyObject *o;
+
+ utf8string = pg_server_to_any(s, size, PG_UTF8);
+
+ if (utf8string == s)
+ {
+ o = PyUnicode_FromStringAndSize(s, size);
+ }
+ else
+ {
+ o = PyUnicode_FromString(utf8string);
+ pfree(utf8string);
+ }
+
+ return o;
+}
+
+PyObject *
+PLyUnicode_FromString(const char *s)
+{
+ return PLyUnicode_FromStringAndSize(s, strlen(s));
+}
diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h
new file mode 100644
index 0000000..7c65779
--- /dev/null
+++ b/src/pl/plpython/plpy_util.h
@@ -0,0 +1,17 @@
+/*--------------------------
+ * common utility functions
+ *--------------------------
+ */
+
+#ifndef PLPY_UTIL_H
+#define PLPY_UTIL_H
+
+#include "plpython.h"
+
+extern PyObject *PLyUnicode_Bytes(PyObject *unicode);
+extern char *PLyUnicode_AsString(PyObject *unicode);
+
+extern PyObject *PLyUnicode_FromString(const char *s);
+extern PyObject *PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size);
+
+#endif /* PLPY_UTIL_H */
diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h
new file mode 100644
index 0000000..2a0c9bf
--- /dev/null
+++ b/src/pl/plpython/plpython.h
@@ -0,0 +1,106 @@
+/*-------------------------------------------------------------------------
+ *
+ * plpython.h - Python as a procedural language for PostgreSQL
+ *
+ * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/pl/plpython/plpython.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef PLPYTHON_H
+#define PLPYTHON_H
+
+/*
+ * Include order should be: postgres.h, other postgres headers, plpython.h,
+ * other plpython headers. (In practice, other plpython headers will also
+ * include this file, so that they can compile standalone.)
+ */
+#ifndef POSTGRES_H
+#error postgres.h must be included before plpython.h
+#endif
+
+/*
+ * Undefine some things that get (re)defined in the Python headers. They aren't
+ * used by the PL/Python code, and all PostgreSQL headers should be included
+ * earlier, so this should be pretty safe.
+ */
+#undef _POSIX_C_SOURCE
+#undef _XOPEN_SOURCE
+
+/*
+ * Sometimes python carefully scribbles on our *printf macros.
+ * So we undefine them here and redefine them after it's done its dirty deed.
+ */
+#undef vsnprintf
+#undef snprintf
+#undef vsprintf
+#undef sprintf
+#undef vfprintf
+#undef fprintf
+#undef vprintf
+#undef printf
+
+#if defined(_MSC_VER) && defined(_DEBUG)
+/* Python uses #pragma to bring in a non-default libpython on VC++ if
+ * _DEBUG is defined */
+#undef _DEBUG
+/* Also hide away errcode, since we load Python.h before postgres.h */
+#define errcode __msvc_errcode
+#include <Python.h>
+#undef errcode
+#define _DEBUG
+#elif defined (_MSC_VER)
+#define errcode __msvc_errcode
+#include <Python.h>
+#undef errcode
+#else
+#include <Python.h>
+#endif
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("plpython")
+
+/* put back our *printf macros ... this must match src/include/port.h */
+#ifdef vsnprintf
+#undef vsnprintf
+#endif
+#ifdef snprintf
+#undef snprintf
+#endif
+#ifdef vsprintf
+#undef vsprintf
+#endif
+#ifdef sprintf
+#undef sprintf
+#endif
+#ifdef vfprintf
+#undef vfprintf
+#endif
+#ifdef fprintf
+#undef fprintf
+#endif
+#ifdef vprintf
+#undef vprintf
+#endif
+#ifdef printf
+#undef printf
+#endif
+
+#define vsnprintf pg_vsnprintf
+#define snprintf pg_snprintf
+#define vsprintf pg_vsprintf
+#define sprintf pg_sprintf
+#define vfprintf pg_vfprintf
+#define fprintf pg_fprintf
+#define vprintf pg_vprintf
+#define printf(...) pg_printf(__VA_ARGS__)
+
+/*
+ * Used throughout, so it's easier to just include it everywhere.
+ */
+#include "plpy_util.h"
+
+#endif /* PLPYTHON_H */
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
new file mode 100644
index 0000000..ba2e6ac
--- /dev/null
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -0,0 +1,17 @@
+/* src/pl/plpython/plpython3u--1.0.sql */
+
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+ STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+ HANDLER plpython3_call_handler
+ INLINE plpython3_inline_handler
+ VALIDATOR plpython3_validator;
+
+COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u.control b/src/pl/plpython/plpython3u.control
new file mode 100644
index 0000000..01905ef
--- /dev/null
+++ b/src/pl/plpython/plpython3u.control
@@ -0,0 +1,7 @@
+# plpython3u extension
+comment = 'PL/Python3U untrusted procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/plpython3'
+relocatable = false
+schema = pg_catalog
+superuser = true
diff --git a/src/pl/plpython/po/cs.po b/src/pl/plpython/po/cs.po
new file mode 100644
index 0000000..61576ac
--- /dev/null
+++ b/src/pl/plpython/po/cs.po
@@ -0,0 +1,503 @@
+# Czech message translation file for plpython
+# Copyright (C) 2012 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Tomáš Vondra <tv@fuzzy.cz>, 2012, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython-cs (PostgreSQL 9.3)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-09-27 08:08+0000\n"
+"PO-Revision-Date: 2019-09-27 21:01+0200\n"
+"Last-Translator: Tomas Vondra <tv@fuzzy.cz>\n"
+"Language-Team: Czech <info@cspug.cx>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: Poedit 2.2.3\n"
+
+#: plpy_cursorobject.c:78
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor očekával dotaz nebo plán"
+
+#: plpy_cursorobject.c:161
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor jako druhý argument očekává sekvenci"
+
+#: plpy_cursorobject.c:177 plpy_spi.c:211
+#, c-format
+msgid "could not execute plan"
+msgstr "nelze spustit plán"
+
+#: plpy_cursorobject.c:180 plpy_spi.c:214
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Očekávána posloupnost %d argument, předáno %d: %s"
+msgstr[1] "Očekávána posloupnost %d argumentů, předáno %d: %s"
+msgstr[2] "Očekávána posloupnost %d argumentů, předáno %d: %s"
+
+#: plpy_cursorobject.c:329
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "iterování uzavřeného kurzoru"
+
+#: plpy_cursorobject.c:337 plpy_cursorobject.c:403
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iterování kurzoru ve zrušené subtransakci"
+
+#: plpy_cursorobject.c:395
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "fetch ze zavřeného kurzoru"
+
+#: plpy_cursorobject.c:438 plpy_spi.c:409
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "výsledek dotazu má příliš mnoho řádek a nevejde se to Python seznamu"
+
+#: plpy_cursorobject.c:490
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "uzavření kurzoru ve zrušené subtransakci"
+
+#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:143
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "nepodporovaný návratový mód funkce vracející tabulku"
+
+#: plpy_exec.c:144
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python funkce vracející tabulku podporují pouze vracení jedné hodnoty pro každé volání."
+
+#: plpy_exec.c:157
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "přes vrácený objekt nelze iterovat"
+
+#: plpy_exec.c:158
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python funkce vracející tabulku musí vracet iterovatelný objekt."
+
+#: plpy_exec.c:172
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "chyba při načítání další položky z iterátoru"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python procedura nevrátila hodnotu None"
+
+#: plpy_exec.c:219
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "PL/Python funkce s návratovým typem \"void\" nevrátila hodnotu None"
+
+#: plpy_exec.c:375 plpy_exec.c:401
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "neočekávaná návratová hodnota z trigger procedury"
+
+#: plpy_exec.c:376
+#, c-format
+msgid "Expected None or a string."
+msgstr "Očekáváno None nebo řetězec."
+
+#: plpy_exec.c:391
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "PL/Python trigger funkce vrátila \"MODIFY\" v DELETE triggeru -- ignorováno"
+
+#: plpy_exec.c:402
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Očekáváno None, \"OK\", \"SKIP\", nebo \"MODIFY\"."
+
+#: plpy_exec.c:452
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "volání PyList_SetItem() selhalo během vytváření argumentů"
+
+#: plpy_exec.c:456
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "volání PyDict_SetItemString() selhalo během přípravy argumentů"
+
+#: plpy_exec.c:468
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkce vracející záznam byla zavolána z kontextu, který neumožňuje přijetí záznamu"
+
+#: plpy_exec.c:685
+#, c-format
+msgid "while creating return value"
+msgstr "během vytváření návratové hodnoty"
+
+#: plpy_exec.c:919
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] smazáno, nelze modifikovat řádek"
+
+#: plpy_exec.c:924
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] není slovník"
+
+#: plpy_exec.c:951
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "TD[\"new\"] klíč slovníku na pozici %d není řetězec"
+
+#: plpy_exec.c:958
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "klíč \"%s\" nalezený v TD[\"new\"] neexistuje jako sloupec v triggering řádku"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nelze nastavit systémový atribut \"%s\""
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "nelze přiřazovat do generovaného sloupce \"%s\""
+
+#: plpy_exec.c:1026
+#, c-format
+msgid "while modifying trigger row"
+msgstr "během modifikace řádku triggeru"
+
+#: plpy_exec.c:1087
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "vynucené ukončení subtransakce která nebyla dokončena"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "v session je načteno několik Python knihoven"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Pouze jedna major verze Pythonu může být použita v jedné session."
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "nezachycená chyba v inicializaci"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "nepodařilo se naimportovat \"__main__\" modul"
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "nepodařilo se inicializovat globální proměnné (globals)"
+
+#: plpy_main.c:399
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python procedura \"%s\""
+
+#: plpy_main.c:402
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python funkce \"%s\""
+
+#: plpy_main.c:410
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python anonymní blok kódu"
+
+#: plpy_plpymodule.c:186 plpy_plpymodule.c:189
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "nepodařilo se naimportovat \"plpy\" modul"
+
+#: plpy_plpymodule.c:204
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "nepodařilo se vytvořit spiexceptions modul"
+
+#: plpy_plpymodule.c:212
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "nepodařilo se naimportovat \"__main__\" modul"
+
+#: plpy_plpymodule.c:280
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "nepodařilo se vygenerovat SPI výjimky"
+
+#: plpy_plpymodule.c:448
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "nepodařilo se rozbalit argumenty v plpy.elog"
+
+#: plpy_plpymodule.c:457
+msgid "could not parse error message in plpy.elog"
+msgstr "nepodařilo se naparsovat chybovou hlášku v plpy.elog"
+
+#: plpy_plpymodule.c:474
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "argument 'message' zadán jménem a pozicí"
+
+#: plpy_plpymodule.c:501
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' je neplatný keyword argument pro tuto funkci"
+
+#: plpy_plpymodule.c:512 plpy_plpymodule.c:518
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "chybný SQLSTATE kód"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funkce pro obsluhu triggerů mohou být volané pouze prostřednictvím triggerů"
+
+#: plpy_procedure.c:234
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python funkce nemohou vracet typ %s"
+
+#: plpy_procedure.c:312
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python funkce nepodporují typ %s"
+
+#: plpy_procedure.c:402
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "nelze zkompiloval PL/Python funkci \"%s\""
+
+#: plpy_procedure.c:405
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "nelze zkompiloval anonymní PL/Python blok"
+
+#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173
+#, c-format
+msgid "command did not produce a result set"
+msgstr "příkaz nevrátil žádný výsledek"
+
+#: plpy_spi.c:60
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "druhý argument pro plpy.prepare musí být posloupnost"
+
+#: plpy_spi.c:104
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: název typu na pozici %d není řetězec"
+
+#: plpy_spi.c:176
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute očekávala dotaz nebo plán"
+
+#: plpy_spi.c:195
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute jako druhý argument očekává posloupnost"
+
+#: plpy_spi.c:305
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "volání SPI_execute_plan selhalo: %s"
+
+#: plpy_spi.c:347
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "volání SPI_execute selhalo: %s"
+
+#: plpy_subxactobject.c:97
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "tato subtransakce již byla zahájena"
+
+#: plpy_subxactobject.c:103 plpy_subxactobject.c:161
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "tato subtransakce již byla dokončena"
+
+#: plpy_subxactobject.c:155
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "tato subtransakce nebyla zahájena"
+
+#: plpy_subxactobject.c:167
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "nenalezena subtransakce k ukončení"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "nepodařilo se naimportovat modul pro Decimal constructor"
+
+#: plpy_typeio.c:595
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "modul nemá žádný Decimal atribut"
+
+#: plpy_typeio.c:601
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "konverze z numeric na Decimal selhala"
+
+#: plpy_typeio.c:915
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "nepodařilo se vytvořit bytovou reprezentaci Python objektu"
+
+#: plpy_typeio.c:1063
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "nepodařilo se vytvořit řetězcovou reprezentaci Python objektu"
+
+#: plpy_typeio.c:1074
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "nepodařilo se převést Python objekt na cstring: zdá se že řetězcová reprezentace Python objektu obsahuje null byty"
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "počet dimenzí pole překračuje povolené maximum (%d)"
+
+#: plpy_typeio.c:1187
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "nelze určit délku posloupnosti pro návratovou hodnotu funkce"
+
+#: plpy_typeio.c:1190 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "velikost pole překračuje povolené maximum"
+
+#: plpy_typeio.c:1220
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "návratová hodnota funkce s návratovým typem pole není Python posloupnost (sequence)"
+
+#: plpy_typeio.c:1266
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "chybná délka vnitční sekvence: má délku %d, ale očekáváno bylo %d"
+
+#: plpy_typeio.c:1268
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "Pro vytvoření vícerozměrného pole musí mít včechny vnitřní sekvence stejnou délku."
+
+#: plpy_typeio.c:1347
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "chybný literál záznamu: \"%s\""
+
+#: plpy_typeio.c:1348
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Chybějící levá závorka."
+
+#: plpy_typeio.c:1349 plpy_typeio.c:1550
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Pro vrácení kompozitního typu v poli, vracejte kompozitní typ jako Python tuple, e.g., \"[('foo',)]\"."
+
+#: plpy_typeio.c:1396
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "klíč \"%s\" nenalezen v mapování"
+
+#: plpy_typeio.c:1397
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Pro vrácení hodnoty null ve sloupci, přidejte do mapování hodnotu None s klíčem pojmenovaným jako sloupec."
+
+#: plpy_typeio.c:1450
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "délka vrácené posloupnosti neodpovídala počtu sloupců v řádku"
+
+#: plpy_typeio.c:1548
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "atribut \"%s\" v Python objektu neexistuje"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Pro vrácení null ve sloupci, nechť vracený objekt má atribut pojmenovaný po sloupcis hodnotou None."
+
+#: plpy_util.c:35
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "nelze převést Python Unicode objekt na byty"
+
+#: plpy_util.c:41
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "nepodařilo se získat byty z kódovaného řetězce"
+
+#~ msgid "could not initialize plpy"
+#~ msgstr "nepodařilo se inicializovat plpy"
+
+#~ msgid "could not create new Python list"
+#~ msgstr "nelze vytvořit nový Python seznam"
+
+#~ msgid "PL/Python only supports one-dimensional arrays."
+#~ msgstr "PL/Python podporuje pouze jednorozměrná pole."
+
+#~ msgid "cannot convert multidimensional array to Python list"
+#~ msgstr "vícerozměrné pole nelze převést na Python seznam (list)"
+
+#~ msgid "PL/Python does not support conversion to arrays of row types."
+#~ msgstr "PL/Python nepodporuje konverzi na pole řádkových typů."
+
+#~ msgid "could not create new dictionary"
+#~ msgstr "nepodařilo se vytvořit nový slovník"
+
+#~ msgid "unrecognized error in PLy_spi_execute_fetch_result"
+#~ msgstr "nerozpoznaná chyba v PLy_spi_execute_fetch_result"
+
+#~ msgid "plpy.prepare does not support composite types"
+#~ msgstr "plpy.prepare nepodporuje složené typy"
+
+#~ msgid "could not create the base SPI exceptions"
+#~ msgstr "nepodařilo se vygenerovat základní SPI výjimky"
+
+#~ msgid "plan.status takes no arguments"
+#~ msgstr "plan.status nepřijímá žádné argumenty"
+
+#~ msgid "could not create globals"
+#~ msgstr "nepodařilo se vytvořit globals"
+
+#~ msgid "Start a new session to use a different Python major version."
+#~ msgstr "Spouští se nová session pro použití jiné hlavní verze Pythonu."
+
+#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d."
+#~ msgstr "Tato session již dříve používala Python s hlavní verzí %d, a nyní se pokouší použít Python s hlavní verzí %d."
+
+#~ msgid "could not create new dictionary while building trigger arguments"
+#~ msgstr "během vytváření argumentů triggeru nelze vytvářet nový slovník"
diff --git a/src/pl/plpython/po/de.po b/src/pl/plpython/po/de.po
new file mode 100644
index 0000000..3e386b7
--- /dev/null
+++ b/src/pl/plpython/po/de.po
@@ -0,0 +1,451 @@
+# German message translation file for plpython
+# Copyright (C) 2009 - 2019 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Peter Eisentraut <peter@eisentraut.org>, 2009 - 2019.
+#
+# Use these quotes: »%s«
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 12\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-05 06:26+0000\n"
+"PO-Revision-Date: 2023-05-05 10:54+0200\n"
+"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n"
+"Language-Team: German <pgsql-translators@postgresql.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor hat eine Anfrage oder einen Plan erwartet"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor nimmt eine Sequenz als zweites Argument"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "konnte Plan nicht ausführen"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Sequenz aus %d Argument erwartet, aber %d erhalten: %s"
+msgstr[1] "Sequenz aus %d Argumenten erwartet, aber %d erhalten: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "Iteration mit einem geschlossenen Cursor"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "Iteration mit einem Cursor in einer abgebrochenen Transaktionen"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "Lesen aus einem geschlossenen Cursor"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "Anfrageergebnis hat zu viele Zeilen, um in eine Python-Liste zu passen"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "Schließen eines Cursors in einer abgebrochenen Subtransaktion"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "nicht unterstützter Rückgabemodus für Funktion mit Mengenergebnis"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python unterstützt für Funktionen mit Mengenergebnis nur das Zurückgeben von einem Wert pro Aufruf."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "zurückgegebenes Objekt kann nicht iteriert werden"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python-Funktionen mit Mengenergebnis müssen ein iterierbares Objekt zurückgeben."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "Fehler beim Auslesen des nächsten Elements vom Iterator"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python-Prozedur hat nicht None zurückgegeben"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "PL/Python-Funktion mit Rückgabetyp »void« hat nicht None zurückgegeben"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "unerwarteter Rückgabewert von Triggerprozedur"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Erwartete None oder eine Zeichenkette."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "PL/Python-Funktion gab in einem DELETE-Trigger \"MODIFY\" zurück -- ignoriert"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Erwartete None, \"OK\", \"SKIP\" oder \"MODIFY\"."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() fehlgeschlagen, beim Einrichten der Argumente"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() fehlgeschlagen, beim Einrichten der Argumente"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "Funktion, die einen Record zurückgibt, in einem Zusammenhang aufgerufen, der Typ record nicht verarbeiten kann"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "beim Erzeugen des Rückgabewerts"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] wurde gelöscht, kann Zeile nicht ändern"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] ist kein Dictionary"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "Dictionary-Schlüssel auf Position %d in TD[\"new\"] ist keine Zeichenkette"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "in TD[\"new\"] gefundener Schlüssel »%s« existiert nicht als Spalte in der den Trigger auslösenden Zeile"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "Systemattribut »%s« kann nicht gesetzt werden"
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kann generierte Spalte »%s« nicht setzen"
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "beim Ändern der Triggerzeile"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "Abbruch einer Subtransaktion, die nicht beendet wurde, wird erzwungen"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "in dieser Sitzung sind mehrere Python-Bibliotheken präsent"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Nur eine Python-Hauptversion kann in einer Sitzung verwendet werden."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "nicht abgefangener Fehler bei der Initialisierung"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "konnte Modul »__main__« nicht importieren"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "konnte globale Objekte nicht initialisieren"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python-Prozedur »%s«"
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python-Funktion »%s«"
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "anonymer PL/Python-Codeblock"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "konnte Modul »plpy« nicht importieren"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "konnte das Modul »spiexceptions« nicht erzeugen"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "konnte das Modul »spiexceptions« nicht hinzufügen"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "konnte SPI-Ausnahmen nicht erzeugen"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "konnte Argumente in plpy.elog nicht entpacken"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "konnte Fehlermeldung in plpy.elog nicht parsen"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "Argument »message« wurde durch Namen und Position angegeben"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "»%s« ist ein ungültiges Schlüsselwortargument für diese Funktion"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "ungültiger SQLSTATE-Code"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggerfunktionen können nur als Trigger aufgerufen werden"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python-Funktionen können keinen Rückgabetyp %s haben"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python-Funktionen können Typ %s nicht annehmen"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "konnte PL/Python-Funktion »%s« nicht kompilieren"
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "konnte anonymen PL/Python-Codeblock nicht kompilieren"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "Befehl hat keine Ergebnismenge erzeugt"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "zweites Argument von plpy.prepare muss eine Sequenz sein"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: Typname auf Position %d ist keine Zeichenkette"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute hat eine Anfrage oder einen Plan erwartet"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute nimmt eine Sequenz als zweites Argument"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan fehlgeschlagen: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute fehlgeschlagen: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "diese Subtransaktion wurde bereits begonnen"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "diese Subtransaktion wurde bereits beendet"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "diese Subtransaktion wurde nicht begonnen"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "es gibt keine Subtransaktion zu beenden"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "konnte kein Modul für den »Decimal«-Konstruktor importieren"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "kein Attribut »Decimal« im Modul"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "Umwandlung von numeric in Decimal fehlgeschlagen"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "konnte Bytes-Darstellung eines Python-Objektes nicht erzeugen"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "konnte Zeichenkettendarstellung eines Python-Objektes nicht erzeugen"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "konnte Python-Objekt nicht in cstring umwandeln: Python-Zeichenkettendarstellung enthält anscheinend Null-Bytes"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "Rückgabewert von Funktion mit Array-Rückgabetyp ist keine Python-Sequenz"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "konnte Sequenzlänge für Funktionsrückgabewert nicht ermitteln"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "mehrdimensionale Arrays müssen Arraysausdrücke mit gleicher Anzahl Dimensionen haben"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "Anzahl der Arraydimensionen überschreitet erlaubtes Maximum (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "fehlerhafte Record-Konstante: »%s«"
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Linke Klammer fehlt."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Um einen zusammengesetzten Typ in einem Array zurückzugeben, geben Sie den zusammengesetzten Typ als ein Python-Tupel zurück, z.B. »[('foo',)]«."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "Schlüssel »%s« nicht in Mapping gefunden"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Um einen NULL-Wert in einer Spalte zurückzugeben, muss der Wert None mit einem nach der Spalte benannten Schlüssel in das Mapping eingefügt werden."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "Länge der zurückgegebenen Sequenz hat nicht mit der Anzahl der Spalten in der Zeile übereingestimmt"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "Attribut »%s« existiert nicht in Python-Objekt"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Um einen NULL-Wert in einer Spalte zurückzugeben, muss das zurückzugebende Objekt ein nach der Spalte benanntes Attribut mit dem Wert None haben."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "konnte Python-Unicode-Objekt nicht in Bytes umwandeln"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "konnte kodierte Zeichenkette nicht in Bytes umwandeln"
diff --git a/src/pl/plpython/po/el.po b/src/pl/plpython/po/el.po
new file mode 100644
index 0000000..cfe1123
--- /dev/null
+++ b/src/pl/plpython/po/el.po
@@ -0,0 +1,462 @@
+# Greek message translation file for plpython
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpython (PostgreSQL) package.
+# Georgios Kokolatos <gkokolatos@pm.me>, 2021.
+#
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-07-15 06:08+0000\n"
+"PO-Revision-Date: 2021-07-15 09:57+0200\n"
+"Last-Translator: Georgios Kokolatos <gkokolatos@pm.me>\n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Poedit 2.4.3\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor ανέμενε ένα ερώτημα ή ένα σχέδιο"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor λαμβάνει μια ακολουθία ως τη δεύτερη παράμετρο"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:207
+#, c-format
+msgid "could not execute plan"
+msgstr "δεν ήταν δυνατή η εκτέλεση του σχεδίου"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:210
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Ανέμενε ακολουθία %d παραμέτρου, έλαβε %d: %s"
+msgstr[1] "Ανέμενε ακολουθία %d παραμέτρων, έλαβε %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "επαναλαμβάνει έναν κλειστό δρομέα"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "επαναλαμβάνει ένα δρομέα σε μία ματαιωμένη υποσυναλλαγή"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "ανάκτηση από κλειστό δρομέα"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:403
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "το αποτέλεσμα του ερωτήματος έχει πάρα πολλές σειρές για να χωρέσει σε μια λίστα Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "κλείσιμο ενός δρομέα σε μία ματαιωμένη υποσυναλλαγή"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:548
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "μη υποστηριζόμενο είδος επιστροφής συνάρτησης συνόλου"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "Οι λειτουργίες επιστροφής συνόλου PL/Python υποστηρίζουν μόνο την επιστροφή μίας τιμής ανά κλήση."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "το επιστρεφόμενο αντικείμενο δεν μπορεί να επαναληφθεί"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "Οι συναρτήσεις επιστροφής συνόλου PL/Python επιβάλλεται να επιστρέφουν ένα επαναλαμβανόμενο αντικείμενο."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "σφάλμα κατά τη λήψη του επόμενου στοιχείου από το πρόγραμμα λήψης"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "διεργασία PL/Python δεν επέστρεψε None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "συνάρτηση PL/Python με τύπο επιστροφής «void» δεν επέστρεψε None"
+
+#: plpy_exec.c:371 plpy_exec.c:397
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "μη αναμενόμενη τιμή επιστροφής από διεργασία εναύσματος"
+
+#: plpy_exec.c:372
+#, c-format
+msgid "Expected None or a string."
+msgstr "Αναμενόταν None ή μία συμβολοσειρά."
+
+#: plpy_exec.c:387
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "συνάρτηση εναύσματος PL/Python επέστρεψε «MODIFY» σε ένα έναυσμα DELETE -- παραβλέφθηκε"
+
+#: plpy_exec.c:398
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Αναμενόταν None, «OK», «SKIP», ή «MODIFY»."
+
+#: plpy_exec.c:443
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() απέτυχε, κατά τη διάρκεια ετοιμασίας των παραμέτρων"
+
+#: plpy_exec.c:447
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() απέτυχε, κατά τη διάρκεια ετοιμασίας των παραμέτρων"
+
+#: plpy_exec.c:459
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "συνάρτηση που επιστρέφει εγγραφή καλείται σε περιεχόμενο που δεν δύναται να αποδεχτεί τύπο εγγραφής"
+
+#: plpy_exec.c:676
+#, c-format
+msgid "while creating return value"
+msgstr "κατά τη δημιουργία τιμής επιστροφής"
+
+#: plpy_exec.c:910
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[«new»] διαγράφηκε, δεν είναι δυνατή η μετατροπή σειράς"
+
+#: plpy_exec.c:915
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[«new»] δεν είναι ένα λεξικό"
+
+#: plpy_exec.c:942
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "TD[«new»] κλειδί λεξικού στη τακτή θέση %d δεν είναι μία συμβολοσειρά"
+
+#: plpy_exec.c:949
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "κλειδί «%s» που βρίσκεται στο TD[\"New\"] δεν υπάρχει ως στήλη στη γραμμή εναύσματος"
+
+#: plpy_exec.c:954
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός του χαρακτηριστικού συστήματος «%s»"
+
+#: plpy_exec.c:959
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός δημιουργημένης στήλης «%s»"
+
+#: plpy_exec.c:1017
+#, c-format
+msgid "while modifying trigger row"
+msgstr "κατά την τροποποίηση εναύσματος σειράς"
+
+#: plpy_exec.c:1075
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "βίαιη ματαίωση μιας υποσυναλλαγής που δεν έχει εξέλθει"
+
+#: plpy_main.c:121
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "υπάρχουν πολλαπλές βιβλιοθήκες Python στη συνεδρία"
+
+#: plpy_main.c:122
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Μόνο μία κύρια έκδοση Python μπορεί να χρησιμοποιηθεί σε μία συνεδρία."
+
+#: plpy_main.c:138
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "μη παγιδευμένο σφάλμα κατά την προετοιμασία"
+
+#: plpy_main.c:161
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "δεν ήταν δυνατή η εισαγωγή του αρθρώματος «__main__»"
+
+#: plpy_main.c:170
+#, c-format
+msgid "could not initialize globals"
+msgstr "δεν ήταν δυνατή η αρχικοποίηση καθολικών μεταβλητών"
+
+#: plpy_main.c:393
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "διεργασία PL/Python «%s»"
+
+#: plpy_main.c:396
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "συνάρτηση PL/Python «%s»"
+
+#: plpy_main.c:404
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "ονώνυμο μπλοκ κώδικα PL/Python"
+
+#: plpy_plpymodule.c:182 plpy_plpymodule.c:185
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "δεν ήταν δυνατή η εισαγωγή του αρθρώματος «plpy»"
+
+#: plpy_plpymodule.c:200
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "δεν ήταν δυνατή η δημιουργία του αρθρώματος spiexceptions"
+
+#: plpy_plpymodule.c:208
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "δεν ήταν δυνατή η πρόσθεση του αρθρώματος spiexceptions"
+
+#: plpy_plpymodule.c:275
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "δεν ήταν δυνατή η του αρθρώματος spiexceptions"
+
+#: plpy_plpymodule.c:443
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "δεν ήταν δυνατή η αποσυσκευασία παραμέτρων στο plpy.elog"
+
+#: plpy_plpymodule.c:452
+msgid "could not parse error message in plpy.elog"
+msgstr "δεν ήταν δυνατή η ανάλυση μηνυμάτος σφάλματος στο plpy.elog"
+
+#: plpy_plpymodule.c:469
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "παράμετρος «μήνυμα» που δόθηκε με όνομα και θέση"
+
+#: plpy_plpymodule.c:496
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "‘%s’ είναι μία άκυρη παράμετρος με λέξη-κλειδί για αυτή τη συνάρτηση"
+
+#: plpy_plpymodule.c:507 plpy_plpymodule.c:513
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "μη έγκυρος κωδικός SQLSTATE"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "συναρτήσεις εναυσμάτων μπορούν να κληθούν μόνο ως εναύσματα"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "οι συναρτήσεις PL/Python δεν μπορούν να επιστρέψουν τύπο %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "οι συναρτήσεις PL/Python δεν μπορούν να δεχθούν τύπο %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "δεν ήταν δυνατή η μεταγλώττιση της συνάρτησης PL/Python «%s»"
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "δεν ήταν δυνατή η μεταγλώττιση ανώνυμου μπλοκ κώδικα PL/Python"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "η εντολή δεν παρήγαγε ένα σύνολο αποτελάσματων"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "η δεύτερη παράμετρος του plpy.prepare επιβάλλεται να είναι μία ακολουθία"
+
+#: plpy_spi.c:100
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: το όνομα τύπου στη τακτή θέση %d δεν είναι συμβολοσειρά"
+
+#: plpy_spi.c:172
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute ανέμενε ένα ερώτημα ή ένα σχέδιο"
+
+#: plpy_spi.c:191
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute λαμβάνει μια ακολουθία ως δεύτερη παράμετρό του"
+
+#: plpy_spi.c:299
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan απέτυχε: %s"
+
+#: plpy_spi.c:341
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute απέτυχε: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "έχει εισέλθει ήδη σε αυτή τη υποσυναλλάγή"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "έχει εξέλθει ήδη από αυτή τη υποσυναλλάγή"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "δεν έχει εισέλθει σε αυτή τη υποσυναλλάγή"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "δεν υπάρχει υποσυναλλαγή από την οποία να εξέλθει"
+
+#: plpy_typeio.c:587
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "δεν ήταν δυνατή η εισαγωγή αρθρώματος για τον κατασκευαστή Decimal"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "καμία ιδιότητα Decimal στο άρθρωμα"
+
+#: plpy_typeio.c:597
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "μετατροπή από αριθμητικό σε Decimal απέτυχε"
+
+#: plpy_typeio.c:911
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "δεν ήταν δυνατή η δημιουργία bytes αναπαράστασης του αντικειμένου Python"
+
+#: plpy_typeio.c:1056
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "δεν ήταν δυνατή η δημιουργία string αναπαράστασης του αντικειμένου Python"
+
+#: plpy_typeio.c:1067
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "δεν ήταν δυνατή η μετατροπή του αντικειμένου Python σε cstring: Η αναπαράσταση συμβολοσειράς Python φαίνεται να περιέχει null bytes"
+
+#: plpy_typeio.c:1178
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "o αριθμός των διαστάσεων συστοιχίας υπερβαίνει το μέγιστο επιτρεπόμενο (%d)"
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "δεν ήταν δυνατός ο προσδιορισμός του μήκους της ακολουθίας για την τιμή επιστροφής της συνάρτησης"
+
+#: plpy_typeio.c:1188 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "το μέγεθος συστοιχίας υπερβαίνει το μέγιστο επιτρεπόμενο"
+
+#: plpy_typeio.c:1222
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "η τιμή επιστροφής της συνάρτησης με τύπο επιστροφής συστυχίας δεν είναι μία ακολουθία Python"
+
+#: plpy_typeio.c:1269
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "λάθος μήκος της εσωτερικής ακολουθίας: έχει μήκος %d , αλλά αναμενόταν %d"
+
+#: plpy_typeio.c:1271
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "Για να κατασκευαστεί μια πολυδιάστατη συστυχία, οι εσωτερικές ακολουθίες πρέπει να έχουν όλες το ίδιο μήκος."
+
+#: plpy_typeio.c:1350
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "κακοσχηματισμένο εγγραφή: «%s»"
+
+#: plpy_typeio.c:1351
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Λείπει δεξιά παρένθεση."
+
+#: plpy_typeio.c:1352 plpy_typeio.c:1553
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Για να επιστρέψετε έναν σύνθετο τύπο σε μία συστυχία, επιστρέψτε τον σύνθετο τύπο ως πλειάδα Python, π.χ. «[(‘foo’,)»."
+
+#: plpy_typeio.c:1399
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "κλειδί «%s» δεν βρέθηκε σε αντιστοίχιση"
+
+#: plpy_typeio.c:1400
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Για να επιστρέψετε null σε μια στήλη, προσθέστε την τιμή None στην αντιστοίχιση με το κλειδί που ονομάστηκε από τη στήλη."
+
+#: plpy_typeio.c:1453
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "το μήκος της ακολουθίας που επιστράφηκε δεν ταίριαζε με τον αριθμό των στηλών στη γραμμή"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "η ιδιότητα «%s» δεν υπάρχει στο αντικείμενο Python"
+
+#: plpy_typeio.c:1554
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Για να επιστρέψετε null σε μια στήλη, αφήστε το αντικείμενο που επιστράφηκε να έχει ένα χαρακτηριστικό που ονομάζεται από την στήλη με τιμή None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "δεν ήταν δυνατή η μετατροπή του αντικειμένου Python Unicode σε bytes"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "δεν ήταν δυνατή η εξόρυξη bytes από κωδικοποιημένη συμβολοσειρά"
diff --git a/src/pl/plpython/po/es.po b/src/pl/plpython/po/es.po
new file mode 100644
index 0000000..13bda28
--- /dev/null
+++ b/src/pl/plpython/po/es.po
@@ -0,0 +1,454 @@
+# Spanish message translation file for plpython
+#
+# Copyright (c) 2009-2021, PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Emanuel Calvo Franco <postgres.arg@gmail.com>, 2009.
+# Alvaro Herrera <alvherre@alvh.no-ip.org>, 2009-2012
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-07 16:39+0000\n"
+"PO-Revision-Date: 2022-10-20 09:06+0200\n"
+"Last-Translator: Carlos Chapi <carlos.chapi@2ndquadrant.com>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Poedit 2.0.2\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor espera una consulta o un plan"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor lleva una secuencia como segundo argumento"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "no se pudo ejecutar el plan"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Se esperaba una secuencia de %d argumento, se obtuvo %d: %s"
+msgstr[1] "Se esperaba una secuencia de %d argumentos, se obtuvo %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "iterando un cursor cerrado"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iterando un cursor en una subtransacción abortada"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "haciendo «fetch» en un cursor cerrado"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "el resultado de la consulta tiene demasiados registros y no entran en una lista de Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "cerrando un cursor en una subtransacción abortada"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "modo de retorno de conjunto de función no soportado"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "Las funciones PL/Python que retornan conjuntos sólo permiten retornar un valor por invocación."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "objeto retornado no puede ser iterado"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "Los funciones PL/Python que retornan conjuntos deben retornar un objeto iterable."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "error extrayendo el próximo elemento del iterador"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "procedimiento PL/Python no returnó None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "función PL/Python con tipo de retorno «void» no retorna None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "valor de retorno no esperado desde el procedimiento disparador"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Se esperaba None o una cadena."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "función de disparador de PL/Python retorno «MODIFY» en un disparador de tipo DELETE -- ignorado"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Se esperaba None, «OK», «SKIP» o «MODIFY»."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() falló, mientras se inicializaban los argumentos"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() falló, mientras se inicializaban los argumentos"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "se llamó una función que retorna un registro en un contexto que no puede aceptarlo"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "mientras se creaba el valor de retorno"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] borrado, no se puede modicar el registro"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] no es un diccionario"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "el nombre del atributo de TD[\"new\"] en la posición %d no es una cadena"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "la llave «%s» en TD[\"new\"] no existe como columna en la fila disparadora"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "no se puede definir el atributo de sistema «%s»"
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "no se puede definir el atributo generado «%s»"
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "mientras se modificaba la fila de disparador"
+
+# FIXME not very happy with this
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "abortando una subtransacción que no se ha cerrado"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "hay múltiples librerías de Python presentes en esta sesión"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Sólo se puede usar una versión mayor de Python en cada sesión."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "error no capturado en la inicialización"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "no se pudo importar el módulo «__main__»"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "no se pudo inicializar las globales"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "procedimiento PL/Python «%s»"
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "función PL/Python «%s»"
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "bloque de código anónimo de PL/Python"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "no se pudo importar el módulo «plpy»"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "no se pudo crear el módulo spiexceptions"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "no se pudo importar el módulo spiexceptions"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "no se pudo generar excepciones SPI"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "no se pudo desempaquetar los argumentos de plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "no se pudo analizar el mensaje de error de plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "el argumento 'message' fue pasado por nombre y posición"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "«%s» no es un argumento válido para esta función"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "código SQLSTATE no válido"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "las funciones disparadoras sólo pueden ser llamadas como disparadores"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "las funciones PL/Python no pueden retornar el tipo %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "la funciones PL/Python no pueden aceptar el tipo %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "no se pudo compilar la función PL/Python «%s»"
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "no se pudo compilar el bloque anónimo PL/Python"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "la orden no produjo un conjunto de resultados"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "el segundo argumento de plpy.prepare debe ser una secuencia"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: el nombre de tipo en la posición %d no es una cadena"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute espera una consulta o un plan"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute lleva una secuencia como segundo argumento"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "falló SPI_execute_plan: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "falló SPI_execute: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "ya se ha entrado en esta subtransacción"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "ya se ha salido de esta subtransacción"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "no se ha entrado en esta subtransacción"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "no hay una subtransacción de la cual salir"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "no se pudo importar un módulo para el constructor Decimal"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "no se encontró atributo Decimal en el módulo"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "falló la conversión de numeric a Decimal"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "no se pudo crear la representación de cadena de bytes de Python"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "no se pudo crear la representación de cadena de texto del objeto de Python"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "no se pudo convertir el objeto Python a un cstring: la representación de cadena Python parece tener bytes nulos (\\0)"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "el valor de retorno de la función con tipo de retorno array no es una secuencia Python"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "no se pudo determinar el largo de secuencia del retorno de valor de la función"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "los arrays multidimensionales deben tener expresiones de arrays con dimensiones coincidentes"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "el número de dimensiones del array excede el máximo permitido (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "literal de record no es válido: «%s»"
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Falta paréntesis izquierdo."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Para retornar un tipo compuesto en un array, retorne el tipo compuesto como una tupla de Python, e.g., «[('foo',)]»."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "la llave «%s» no fue encontrada en el mapa"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Para retornar null en una columna, agregue el valor None al mapa, con llave llamada igual que la columna."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "el tamaño de la secuencia retornada no concuerda con el número de columnas de la fila"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "el atributo «%s» no existe en el objeto Python"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Para retornar null en una columna, haga que el objeto retornado tenga un atributo llamado igual que la columna, con valor None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "no se pudo convertir el objeto Unicode de Python a bytes"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "no se pudo extraer bytes desde la cadena codificada"
diff --git a/src/pl/plpython/po/fr.po b/src/pl/plpython/po/fr.po
new file mode 100644
index 0000000..b002f9b
--- /dev/null
+++ b/src/pl/plpython/po/fr.po
@@ -0,0 +1,590 @@
+# LANGUAGE message translation file for plpython
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpython (PostgreSQL) package.
+#
+# Use these quotes: « %s »
+#
+# Guillaume Lelarge <guillaume@lelarge.info>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-05 17:08+0000\n"
+"PO-Revision-Date: 2022-04-12 17:29+0200\n"
+"Last-Translator: Guillaume Lelarge <guillaume@lelarge.info>\n"
+"Language-Team: French <guillaume@lelarge.info>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor attendait une requête ou un plan"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor prends une séquence dans son second argument"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "n'a pas pu exécuter le plan"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Séquence attendue de %d argument, %d obtenu : %s"
+msgstr[1] "Séquence attendue de %d arguments, %d obtenus : %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "itération d'un curseur fermé"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "itération d'un curseur dans une sous-transaction annulée"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "récupérer à partir d'un curseur fermé"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "le résultat de la requête contient trop de lignes pour être intégré dans une liste Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "fermeture d'un curseur dans une sous-transaction annulée"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "mode de retour non supporté pour la fonction SET"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr ""
+"les fonctions PL/python renvoyant des ensembles supportent seulement une\n"
+"valeur renvoyée par appel."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "l'objet renvoyé ne supporte pas les itérations"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr ""
+"les fonctions PL/python renvoyant des ensembles doivent renvoyer un objet\n"
+"itérable"
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "erreur lors de la récupération du prochain élément de l'itérateur"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "la procédure PL/python n'a pas renvoyé None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "la fonction PL/python avec un code de retour « void » ne renvoyait pas None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "valeur de retour inattendue de la procédure trigger"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Attendait None ou une chaîne de caractères."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr ""
+"la fonction trigger PL/python a renvoyé « MODIFY » dans un trigger DELETE\n"
+"-- ignoré"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Attendait None, « OK », « SKIP » ou « MODIFY »."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "échec de PyList_SetItem() lors de l'initialisation des arguments"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "échec de PyDict_SetItemString() lors de l'initialisation des arguments"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr ""
+"fonction renvoyant le type record appelée dans un contexte qui ne peut pas\n"
+"accepter le type record"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "lors de la création de la valeur de retour"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] supprimé, ne peut pas modifier la ligne"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] n'est pas un dictionnaire"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "la clé TD[\"new\"] à la position ordinale %d n'est pas une chaîne"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr ""
+"la clé « %s » trouvée dans TD[\"new\"] n'existe pas comme colonne\n"
+"de la ligne impactée par le trigger"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "ne peut pas initialiser l'attribut système « %s »"
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "ne peut pas initialiser la colonne générée « %s »"
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "lors de la modification de la ligne du trigger"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "annulation forcée d'une sous-transaction qui n'a jamais été quittée"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "plusieurs bibliothèques Python sont présentes dans la session"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Seule une version majeure de Python peut être utilisée dans une session."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "erreur non récupérée dans l'initialisation"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "n'a pas pu importer le module « __main__ »"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "n'a pas pu initialiser les variables globales"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "procédure PL/python « %s »"
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "fonction PL/python « %s »"
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "bloc de code PL/Python anonyme"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "n'a pas pu importer le module « plpy »"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "n'a pas pu créer le module « spiexceptions »"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "n'a pas pu ajouter le module « spiexceptions »"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "n'a pas pu générer les exceptions SPI"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "n'a pas pu déballer les arguments dans plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "n'a pas pu analyser le message d'erreur dans plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "argument 'message' donné par nom et position"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' est une argument mot-clé invalide pour cette fonction"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "code SQLSTATE invalide"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "les fonctions trigger peuvent seulement être appelées par des triggers"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "les fonctions PL/python ne peuvent pas renvoyer le type %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "les fonctions PL/python ne peuvent pas accepter le type %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "n'a pas pu compiler la fonction PL/python « %s »"
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "n'a pas pu compiler le bloc de code anonyme PL/python"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "la commande n'a pas fourni d'ensemble de résultats"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "le second argument de plpy.prepare doit être une séquence"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare : le nom du type sur la position ordinale %d n'est pas une chaîne"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.prepare attendait une requête ou un plan"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute prends une séquence dans son second argument"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "échec de SPI_execute_plan : %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "échec de SPI_execute : %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "cette sous-transaction est en cours d'utilisation"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "déjà sorti de cette sous-transaction"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "cette sous-transaction n'a jamais été utilisée"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "il n'y a pas de transaction à quitter"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "n'a pas pu importer un module pour le constructeur Decimal"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "pas d'attribut Decimal dans le module"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "échec de la conversion numeric vers Decimal"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "n'a pas pu créer une représentation octets de l'objet Python"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "n'a pas pu créer une représentation chaîne de caractères de l'objet Python"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "n'a pas pu convertir l'objet Python en csting : la représentation de la chaîne Python contient des octets nuls"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "la valeur de retour de la fonction de type tableau n'est pas une séquence Python"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "n'a pas pu déterminer la longueur de la séquence pour la valeur de retour de la fonction"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr ""
+"les tableaux multidimensionnels doivent avoir des expressions de tableaux\n"
+"avec les dimensions correspondantes"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "le nombre de dimensions du tableau dépasse le maximum autorisé (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "enregistrement litéral invalide : « %s »"
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Parenthèse gauche manquante."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Pour renvoyer un type composite dans un tableau, renvoyez le type composite sous la forme d'un tuple Python, c'est-à-dire \"[('foo',)]\"."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "la clé « %s » introuvable dans la correspondance"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr ""
+"Pour renvoyer NULL dans une colonne, ajoutez la valeur None à la\n"
+"correspondance de la clé nommée d'après la colonne."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr ""
+"la longueur de la séquence renvoyée ne correspondait pas au nombre de\n"
+"colonnes dans la ligne"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "l'attribut « %s » n'existe pas dans l'objet Python"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr ""
+"Pour renvoyer NULL dans une colonne, faites en sorte que l'objet renvoyé ait\n"
+"un attribut nommé suivant la colonne de valeur None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "n'a pas pu convertir l'objet Unicode Python en octets"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "n'a pas pu extraire les octets de la chaîne encodée"
+
+#~ msgid "PL/Python does not support conversion to arrays of row types."
+#~ msgstr "PL/Python ne supporte pas les conversions vers des tableaux de types row."
+
+#~ msgid "PL/Python function \"%s\" could not execute plan"
+#~ msgstr "la fonction PL/python « %s » n'a pas pu exécuter un plan"
+
+#~ msgid "PL/Python function \"%s\" failed"
+#~ msgstr "échec de la fonction PL/python « %s »"
+
+#~ msgid "PL/Python only supports one-dimensional arrays."
+#~ msgstr "PL/Python supporte seulement les tableaux uni-dimensionnels."
+
+#~ msgid "PL/Python: %s"
+#~ msgstr "PL/python : %s"
+
+#~ msgid "PyCObject_AsVoidPtr() failed"
+#~ msgstr "échec de PyCObject_AsVoidPtr()"
+
+#~ msgid "PyCObject_FromVoidPtr() failed"
+#~ msgstr "échec de PyCObject_FromVoidPtr()"
+
+#~ msgid "Python major version mismatch in session"
+#~ msgstr "Différence de version majeure de Python dans la session"
+
+#~ msgid "Start a new session to use a different Python major version."
+#~ msgstr ""
+#~ "Lancez une nouvelle session pour utiliser une version majeure différente de\n"
+#~ "Python."
+
+#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d."
+#~ msgstr ""
+#~ "Cette session a auparavant utilisé la version majeure %d de Python et elle\n"
+#~ "essaie maintenant d'utiliser la version majeure %d."
+
+#, c-format
+#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+#~ msgstr "Pour construire un tableau multidimensionnel, les séquences internes doivent toutes avoir la même longueur."
+
+#, c-format
+#~ msgid "array size exceeds the maximum allowed"
+#~ msgstr "la taille du tableau dépasse le maximum permis"
+
+#~ msgid "cannot convert multidimensional array to Python list"
+#~ msgstr "ne peut pas convertir un tableau multidimensionnel en liste Python"
+
+#~ msgid "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row"
+#~ msgstr ""
+#~ "n'a pas pu traiter la représentation de la chaîne d'un objet Python dans\n"
+#~ "la fonction PL/Python « %s » lors de la modification de la ligne du trigger"
+
+#~ msgid "could not create exception \"%s\""
+#~ msgstr "n'a pas pu créer l'exception « %s »"
+
+#~ msgid "could not create globals"
+#~ msgstr "n'a pas pu créer les globales"
+
+#~ msgid "could not create new Python list"
+#~ msgstr "n'a pas pu créer la nouvelle liste Python"
+
+#~ msgid "could not create new dictionary"
+#~ msgstr "n'a pas pu créer le nouveau dictionnaire"
+
+#~ msgid "could not create new dictionary while building trigger arguments"
+#~ msgstr ""
+#~ "n'a pas pu créer un nouveau dictionnaire lors de la construction des\n"
+#~ "arguments du trigger"
+
+#~ msgid "could not create procedure cache"
+#~ msgstr "n'a pas pu créer le cache de procédure"
+
+#~ msgid "could not create string representation of Python object in PL/Python function \"%s\" while creating return value"
+#~ msgstr ""
+#~ "n'a pas pu créer la représentation en chaîne de caractère de l'objet\n"
+#~ "Python dans la fonction PL/python « %s » lors de la création de la valeur\n"
+#~ "de retour"
+
+#~ msgid "could not create the base SPI exceptions"
+#~ msgstr "n'a pas pu créer les exceptions SPI de base"
+
+#~ msgid "invalid arguments for plpy.prepare"
+#~ msgstr "arguments invalides pour plpy.prepare"
+
+#~ msgid "multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length %d while expected %d"
+#~ msgstr ""
+#~ "les tableaux multidimensionnels doivent avoir des expressions de tableaux\n"
+#~ "avec des dimensions correspondantes. La valeur de retour de la fonction\n"
+#~ "PL/Python a une longueur de séquence %d alors que %d est attendue"
+
+#~ msgid "out of memory"
+#~ msgstr "mémoire épuisée"
+
+#~ msgid "plan.status takes no arguments"
+#~ msgstr "plan.status ne prends pas d'arguments"
+
+#~ msgid "plpy.prepare does not support composite types"
+#~ msgstr "plpy.prepare ne supporte pas les types composites"
+
+#~ msgid "the message is already specified"
+#~ msgstr "le message est déjà spécifié"
+
+#~ msgid "transaction aborted"
+#~ msgstr "transaction annulée"
+
+#~ msgid "unrecognized error in PLy_spi_execute_fetch_result"
+#~ msgstr "erreur inconnue dans PLy_spi_execute_fetch_result"
+
+#~ msgid "unrecognized error in PLy_spi_execute_plan"
+#~ msgstr "erreur inconnue dans PLy_spi_execute_plan"
+
+#~ msgid "unrecognized error in PLy_spi_execute_query"
+#~ msgstr "erreur inconnue dans PLy_spi_execute_query"
+
+#~ msgid "unrecognized error in PLy_spi_prepare"
+#~ msgstr "erreur inconnue dans PLy_spi_prepare"
+
+#, c-format
+#~ msgid "wrong length of inner sequence: has length %d, but %d was expected"
+#~ msgstr "mauvaise longueur de la séquence interne : a une longueur %d, mais %d était attendu"
diff --git a/src/pl/plpython/po/it.po b/src/pl/plpython/po/it.po
new file mode 100644
index 0000000..26ef9e0
--- /dev/null
+++ b/src/pl/plpython/po/it.po
@@ -0,0 +1,470 @@
+#
+# plpython.po
+# Italian message translation file for plpython
+#
+# For development and bug report please use:
+# https://github.com/dvarrazzo/postgresql-it
+#
+# Copyright (C) 2012-2017 PostgreSQL Global Development Group
+# Copyright (C) 2010, Associazione Culturale ITPUG
+#
+# Daniele Varrazzo <daniele.varrazzo@gmail.com>, 2012-2017.
+# Flavio Spada <f.spada@sbv.mi.it>
+#
+# This file is distributed under the same license as the PostgreSQL package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-30 12:08+0000\n"
+"PO-Revision-Date: 2022-09-30 15:00+0200\n"
+"Last-Translator: Domenico Sgarbossa <sgarbossa.domenico@gmail.com>\n"
+"Language-Team: https://github.com/dvarrazzo/postgresql-it\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Poedit 2.3\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor richiede una query o un piano"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor richiede una sequenza come secondo argomento"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "esecuzione del piano fallita"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Attesa sequenza di %d argomento, ricevuti %d: %s"
+msgstr[1] "Attesa sequenza di %d argomenti, ricevuti %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "iterazione di un cursore chiuso"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iterazione di un cursore in una sotto-transazione interrotta"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "lettura da un cursore chiuso"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "il risultato della query ha troppe righe per una lista Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "chiusura di un cursore in una sotto-transazione interrotta"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "modalità di ritorno della funzione set non supportata"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "Le funzioni PL/Python che restituiscono insiemi supportano la restituzione di un solo valore per chiamata."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "l'oggetto restituito non può essere iterato"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "Le funzioni PL/Python che restituiscono insiemi devono restituire un oggetto iterabile."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "errore nell'ottenere l'elemento successivo dall'iteratore"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "la procedura PL/Python non ha restituito None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "la funzione PL/Python che restituisce \"void\" non ha restituito None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "la prodedura trigger ha restituito un valore inatteso"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Atteso None o una stringa."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "la funzione trigger PL/Python ha restituito \"MODIFY\" in un trigger DELETE -- ignorato"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Atteso None, \"OK\", \"SKIP\", o \"MODIFY\"."
+
+#: plpy_exec.c:441
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() è fallita durante l'impostazione degli argomenti"
+
+#: plpy_exec.c:445
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() è fallita durante l'impostazione degli argomenti"
+
+#: plpy_exec.c:457
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "la funzione che restituisce un record è chiamata in un contesto che non può accettare il tipo record"
+
+#: plpy_exec.c:674
+#, c-format
+msgid "while creating return value"
+msgstr "durante la creazione del valore da restituire"
+
+#: plpy_exec.c:908
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] cancellato, non è possibile modificare la riga"
+
+#: plpy_exec.c:913
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] non è un dizionario"
+
+#: plpy_exec.c:938
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "la chiave di dizionario TD[\"new\"] alla posizione %d non è una stringa"
+
+#: plpy_exec.c:945
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "la chiave \"%s\" trovata in TD[\"new\"] non esiste come colonna nella riga del trigger"
+
+#: plpy_exec.c:950
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "non è possibile impostare l'attributo di sistema \"%s\""
+
+#: plpy_exec.c:955
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "impossibile impostare la colonna generata \"%s\""
+
+#: plpy_exec.c:1013
+#, c-format
+msgid "while modifying trigger row"
+msgstr "durante la modifica della riga trigger"
+
+#: plpy_exec.c:1071
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "interruzione forzata di una sotto-transazione che non è terminata"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "c'è più di una libreria Python presente nella sessione"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Solo una versione maggiore di Python può essere usata in una sessione."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "errore non catturato durante l'inizializzazione"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "importazione del modulo \"__main__\""
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "inizializzazione delle variabili globali fallita"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "procedura PL/Python \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "funzione PL/Python \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "blocco di codice anonimo in PL/Python"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "importazione del modulo \"plpy\" fallita"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "creazione del modulo spiexceptions fallita"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "aggiunta del modulo spiexceptions fallita"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "generazione delle eccezioni SPI fallita"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "non è stato possibile espandere gli argomenti in plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "non è stato possibile interpretare il messaggio di errore in plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "parametro 'message' dato con nome e posizione"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' è un nome di argomento non valido per questa funzione"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "codice SQLSTATE non valido"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "le funzioni trigger possono essere chiamate esclusivamente da trigger"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "le funzioni PL/Python non possono restituire il tipo %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "le funzioni PL/Python non possono accettare il tipo %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "compilazione della funzione PL/Python \"%s\" fallita"
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "compilazione del blocco di codice anonimo PL/Python fallita"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "il comando non ha prodotto risultati"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "il secondo argomento di plpy.prepare deve essere una sequenza"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: il nome del tipo nella posizione %d non è una stringa"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute si aspetta una query o un plan"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute richiede una sequenza come secondo argomento"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan ha fallito: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute ha fallito: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "si è già entrati in questa sotto-transazione"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "si è già usciti da questa sotto-transazione"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "non si è entrati in questa sotto-transazione"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "non c'è nessuna transazione da cui uscire"
+
+#: plpy_typeio.c:587
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "importazione di un modulo per il costrutto Decimal fallita"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "attributo Decimal non trovato nel modulo"
+
+#: plpy_typeio.c:597
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "conversione da numeric a Decimal fallita"
+
+#: plpy_typeio.c:911
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "creazione della rappresentazione in byte dell'oggetto Python fallita"
+
+#: plpy_typeio.c:1048
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "creazione della rappresentazione stringa dell'oggetto Python fallita"
+
+#: plpy_typeio.c:1059
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "conversione dell'oggetto Python in cstring fallita: la rappresentazione stringa Python sembra contenere byte null"
+
+#: plpy_typeio.c:1170
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "il numero di dimensioni dell'array supera il massimo consentito (%d)"
+
+#: plpy_typeio.c:1175
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "errore nel determinare la lunghezza della sequenza per il valore di ritorno della funzione"
+
+#: plpy_typeio.c:1180 plpy_typeio.c:1186
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "la dimensione dell'array supera il massimo consentito"
+
+#: plpy_typeio.c:1214
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "il valore restituito dalla funzione con tipo restituito array non è una sequenza Python"
+
+#: plpy_typeio.c:1261
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "lunghezza errata della sequenza interna: la lunghezza è %d ma era atteso %d"
+
+#: plpy_typeio.c:1263
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "Per costruire un array multidimensionale le sequenze interne devono avere tutte la stessa lunghezza."
+
+#: plpy_typeio.c:1342
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "letterale di record non corretto: \"%s\""
+
+#: plpy_typeio.c:1343
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Parentesi aperta mancante."
+
+#: plpy_typeio.c:1344 plpy_typeio.c:1545
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Per restutuire un tipo composito in un array, restituisci il tipo composito come tupla Python, per esempio \"[('foo',)]\" "
+
+#: plpy_typeio.c:1391
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "la chiave \"%s\" non è stata trovata nel dizionario"
+
+#: plpy_typeio.c:1392
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Per restituire null in una colonna, inserire nella mappa il valore None con una chiave chiamata come la colonna."
+
+#: plpy_typeio.c:1445
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "la lunghezza della sequenza ritornata non rispetta il numero di colonne presenti nella riga"
+
+#: plpy_typeio.c:1543
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "l'attributo \"%s\" non esiste nell'oggetto Python"
+
+#: plpy_typeio.c:1546
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Per restituire null in una colonna, l'oggetto restituito deve avere un attributo chiamato come la colonna con valore None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "conversione dell'oggetto Unicode Python in byte fallita"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "estrazione dei byte dalla stringa codificata fallita"
diff --git a/src/pl/plpython/po/ja.po b/src/pl/plpython/po/ja.po
new file mode 100644
index 0000000..5670478
--- /dev/null
+++ b/src/pl/plpython/po/ja.po
@@ -0,0 +1,497 @@
+# Japanese message translation file for plpython
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_archivecleanup (PostgreSQL) package.
+# Honda Shigehiro <honda@postgresql.jp>, 2012.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL 15)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-06-11 11:34+0900\n"
+"PO-Revision-Date: 2021-08-25 17:42+0900\n"
+"Last-Translator: Kyotaro Horiguchi <horikyota.ntt@gmail.com>\n"
+"Language-Team: Japan PostgreSQL Users Group <jpug-doc@ml.postgresql.jp>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.8.13\n"
+
+#: plpy_cursorobject.c:78
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor は問い合わせもしくは実行計画を期待していました"
+
+#: plpy_cursorobject.c:161
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor は第二引数としてシーケンスを取ります"
+
+#: plpy_cursorobject.c:177 plpy_spi.c:211
+#, c-format
+msgid "could not execute plan"
+msgstr "実行計画を実行できませんでした"
+
+#: plpy_cursorobject.c:180 plpy_spi.c:214
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "%d 個の引数のシーケンスを期待していましたが、個数は %d でした:%s"
+
+#: plpy_cursorobject.c:329
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "反復利用しようとしているカーソルは、すでにクローズされています"
+
+#: plpy_cursorobject.c:337 plpy_cursorobject.c:403
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr ""
+"中断されたサブトランザクションの中でカーソルを反復利用しようとしています"
+
+#: plpy_cursorobject.c:395
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "クローズされたカーソルからのフェッチ"
+
+#: plpy_cursorobject.c:438 plpy_spi.c:409
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "問い合わせの結果に含まれる行数が、Pythonのリストに対して多すぎます"
+
+#: plpy_cursorobject.c:490
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr ""
+"中断されたサブトランザクションの中でカーソルをクローズしようとしています"
+
+#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:143
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "非サポートの集合関数リターンモードです。"
+
+#: plpy_exec.c:144
+#, c-format
+msgid ""
+"PL/Python set-returning functions only support returning one value per call."
+msgstr ""
+"集合を返却するPL/Python関数では、1回の呼び出しに対して1つの値を返すことのみが"
+"サポートされています。"
+
+#: plpy_exec.c:157
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "返されたオブジェクトは反復利用できません"
+
+#: plpy_exec.c:158
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr ""
+"PL/Pythonの集合を返す関数は、反復処理可能なオブジェクトを返さなければなりませ"
+"ん。"
+
+#: plpy_exec.c:172
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "反復子から次の項目を取り出せませんでした"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python プロシージャが None を返しませんでした"
+
+#: plpy_exec.c:219
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "戻り値が\"void\"型である PL/Python関数がNoneを返しませんでした"
+
+#: plpy_exec.c:375 plpy_exec.c:401
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "トリガプロシージャから期待しない戻り値が返されました"
+
+#: plpy_exec.c:376
+#, c-format
+msgid "Expected None or a string."
+msgstr "None もしくは文字列を期待していました。"
+
+#: plpy_exec.c:391
+#, c-format
+msgid ""
+"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr ""
+"PL/Python トリガ関数が、DELETE トリガで \"MODIFY\" を返しました-- 無視します"
+
+#: plpy_exec.c:402
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "None, \"OK\", \"SKIP\", \"MODIFY\" のいずれかを期待していました。"
+
+#: plpy_exec.c:452
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "引数を設定する際に、PyList_SetItem() に失敗しました"
+
+#: plpy_exec.c:456
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "引数を設定する際に、PyDict_SetItemString() に失敗しました"
+
+#: plpy_exec.c:468
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"レコード型を受け付けられないコンテキストでレコードを返す関数が呼び出されまし"
+"た"
+
+#: plpy_exec.c:685
+#, c-format
+msgid "while creating return value"
+msgstr "戻り値を生成する際に"
+
+#: plpy_exec.c:919
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] は削除されました。行を変更できません。"
+
+#: plpy_exec.c:924
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] は辞書ではありません"
+
+#: plpy_exec.c:951
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "TD[\"new\"] 辞書の%d番目のキーが文字列ではありません"
+
+#: plpy_exec.c:958
+#, c-format
+msgid ""
+"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering "
+"row"
+msgstr ""
+"TD[\"new\"] で見つかったキー \"%s\" は、行レベルトリガにおけるカラムとしては"
+"存在しません"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "システム属性\"%s\"は設定できません"
+
+#: plpy_exec.c:968
+#, c-format
+#| msgid "cannot alter inherited column \"%s\""
+msgid "cannot set generated column \"%s\""
+msgstr "生成列\"%s\"は設定できません"
+
+#: plpy_exec.c:1026
+#, c-format
+msgid "while modifying trigger row"
+msgstr "トリガ行を変更する際に"
+
+#: plpy_exec.c:1087
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "終了していないサブトランザクションを強制的にアボートしています"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "セッションに複数の Python ライブラリが存在します"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "1個のセッション中で使えるPythonのメジャーバージョンは1種類だけです。"
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "初期化中に捕捉できないエラーがありました"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "\"__main__\" モジュールをインポートできませんでした"
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "グローバル変数(globals)を初期化できませんでした"
+
+#: plpy_main.c:399
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Pythonプロシージャ\"%s\""
+
+#: plpy_main.c:402
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python関数\"%s\""
+
+#: plpy_main.c:410
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Pythonの無名コードブロック"
+
+#: plpy_plpymodule.c:186 plpy_plpymodule.c:189
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "\"plpy\"モジュールをインポートできませんでした"
+
+#: plpy_plpymodule.c:204
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "spiexceptionsモジュールを生成できませんでした"
+
+#: plpy_plpymodule.c:212
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "spiexceptionsモジュールを追加できませんでした"
+
+#: plpy_plpymodule.c:280
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "SPI例外を生成できませんでした"
+
+#: plpy_plpymodule.c:448
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "plpy.elogで引数を展開できませんでした"
+
+#: plpy_plpymodule.c:457
+msgid "could not parse error message in plpy.elog"
+msgstr "plpy.elogでエラーメッセージをパースできませんでした"
+
+#: plpy_plpymodule.c:474
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "名前と位置で 'message' 引数が渡されました"
+
+#: plpy_plpymodule.c:501
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "この関数に対して'%s'は無効なキーワード引数です"
+
+#: plpy_plpymodule.c:512 plpy_plpymodule.c:518
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "無効なSQLSTATEコードです"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "トリガー関数はトリガーとしてのみ呼び出せます"
+
+#: plpy_procedure.c:234
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python関数は%s型を返せません"
+
+#: plpy_procedure.c:312
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python関数は%s型を受け付けられません"
+
+#: plpy_procedure.c:402
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "PL/Python関数\"%s\"をコンパイルできませんでした"
+
+#: plpy_procedure.c:405
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "PL/Python無名コードブロックをコンパイルできませんでした"
+
+#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173
+#, c-format
+msgid "command did not produce a result set"
+msgstr "コマンドは結果セットを生成しませんでした"
+
+#: plpy_spi.c:60
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "plpy.prepareの第二引数はシーケンスでなければなりません"
+
+#: plpy_spi.c:104
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: %d 番目の型名が文字列ではありません"
+
+#: plpy_spi.c:176
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute は問い合わせもしくは実行計画を期待していました"
+
+#: plpy_spi.c:195
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute は第二引数としてシーケンスを取ります"
+
+#: plpy_spi.c:305
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan が失敗しました: %s"
+
+#: plpy_spi.c:347
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute が失敗しました: %s"
+
+#: plpy_subxactobject.c:97
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "すでにこのサブトランザクションの中に入っています"
+
+#: plpy_subxactobject.c:103 plpy_subxactobject.c:161
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "このサブトランザクションからすでに抜けています"
+
+#: plpy_subxactobject.c:155
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "このサブトランザクションには入っていません"
+
+#: plpy_subxactobject.c:167
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "抜けるべきサブトランザクションがありません"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "Decimalコンストラクタのためのモジュールをインポートできませんでした"
+
+#: plpy_typeio.c:595
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "モジュールの中にDecimal属性が含まれていません"
+
+#: plpy_typeio.c:601
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "numericからDecimalへの変換に失敗しました"
+
+#: plpy_typeio.c:915
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "Pythonオブジェクトのバイト表現を生成できませんでした"
+
+#: plpy_typeio.c:1063
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "Pythonオブジェクトの文字列表現を生成できませんでした"
+
+#: plpy_typeio.c:1074
+#, c-format
+msgid ""
+"could not convert Python object into cstring: Python string representation "
+"appears to contain null bytes"
+msgstr ""
+"Pythonオブジェクトをcstringに変換できませんでした: Pythonの文字列表現にnullバ"
+"イトが含まれているようです"
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "配列の次元数が制限値(%d)を超えています"
+
+#: plpy_typeio.c:1187
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "関数の戻り値について、シーケンスの長さを決定できませんでした"
+
+#: plpy_typeio.c:1190 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "配列のサイズが制限値を超えています"
+
+#: plpy_typeio.c:1220
+#, c-format
+msgid ""
+"return value of function with array return type is not a Python sequence"
+msgstr "配列型を返す関数の戻り値がPythonのシーケンスではありません"
+
+#: plpy_typeio.c:1266
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "内部シーケンスで長さが異常です: 長さは%dですが、期待する値は%dでした"
+
+#: plpy_typeio.c:1268
+#, c-format
+msgid ""
+"To construct a multidimensional array, the inner sequences must all have the "
+"same length."
+msgstr ""
+"多次元配列を生成する場合、内部シーケンスはすべて同じ長さでなければなりませ"
+"ん。"
+
+#: plpy_typeio.c:1347
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "不正な形式のレコードリテラル: \"%s\""
+
+#: plpy_typeio.c:1348
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "左括弧がありません。"
+
+#: plpy_typeio.c:1349 plpy_typeio.c:1550
+#, c-format
+msgid ""
+"To return a composite type in an array, return the composite type as a "
+"Python tuple, e.g., \"[('foo',)]\"."
+msgstr ""
+"複合型を配列に入れて返したい場合、 \"[('foo',)]\" のように複合型を Pythonのタ"
+"プルとして返すようにしてください。"
+
+#: plpy_typeio.c:1396
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "マッピング上にキー\"%s\"が見つかりません"
+
+#: plpy_typeio.c:1397
+#, c-format
+msgid ""
+"To return null in a column, add the value None to the mapping with the key "
+"named after the column."
+msgstr ""
+"カラムにnullを入れて返す場合、カラム名をキーとして値がNoneのエントリをマッピ"
+"ングに追加してください。"
+
+#: plpy_typeio.c:1450
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "返されたシーケンスの長さが行のカラム数とマッチしませんでした"
+
+#: plpy_typeio.c:1548
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "属性\"%s\"がPythonオブジェクト中に存在しません"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid ""
+"To return null in a column, let the returned object have an attribute named "
+"after column with value None."
+msgstr ""
+"カラムにnullを入れて返す場合、カラム名をキーとして値がNoneである属性を持つオ"
+"ブジェクトを返すようにしてください。"
+
+#: plpy_util.c:35
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "PythonのUnicodeオブジェクトをバイト列に変換できませんでした"
+
+#: plpy_util.c:41
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "エンコードされた文字列からバイト列を抽出できませんでした"
diff --git a/src/pl/plpython/po/ka.po b/src/pl/plpython/po/ka.po
new file mode 100644
index 0000000..aeb662e
--- /dev/null
+++ b/src/pl/plpython/po/ka.po
@@ -0,0 +1,462 @@
+# Georgian message translation file for plpython
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpython (PostgreSQL) package.
+# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-08-23 22:55+0000\n"
+"PO-Revision-Date: 2022-09-25 19:18+0200\n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
+"Language-Team: Georgian <nothing>\n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor გეგმას ან მოთხოვნას მოელოდა"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor მეორე არგუმენტად მიმდევრობას იღებს"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "გეგმის შესრულება ვერ მოხერხდა"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "მოველოდი %d არგუმენტის მიმდევრობას. მივიღე %d: %s"
+msgstr[1] "მოველოდი %d არგუმენტის მიმდევრობას. მივიღე %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "დახურული კურსორის იტერაცია"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "კურსორის იტერაცია გაუქმებულ ტრანზაქციაში"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "დახურული კურსორიდან გამოთხოვა"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "მოთხოვნის პასუხს Python-ის სიაში ჩასატევად მეტისმეტად ბევრი მწკრივი გააჩნია"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "გაუქმებულ ქვეტრანზაქციაში კურსორის დახურვა"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "სეტების დამბრუნებელი ფუნქციის მხარდაუჭერელი რეჟიმი"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python -ის ფუნქციებს, რომლების სეტებს აბრუნებენ, თითოეულ გამოძახებაზე მხოლოდ ერთი მნიშვნელობის მხარდაჭერა გააჩნიათ."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "დაბრუნებული ობიექტის იტერაცია შეუძლებელია"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python-ის ფუნქციებმა, რომლებიც სეტებს აბრუნებენ, იტერირებადი ობიექტი უნდა დააბრუნონ."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "იტერატორიდან შემდეგი ჩანაწერის მოთხოვის შეცდომა"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python -ის პროცედურამ None არ დააბრუნა"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "PL/Python -ის ფუნქციამ, რომელიც აბრუნებს ტიპს \"void\", რაღაც დააბრუნა"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "ტრიგერის პროცედურის მოულოდნელი დაბრუნებული მნიშვნელობა"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "ველოდებოდი არაფერს ან სტრიქონს."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "PL/Python -ის ტრიგერმა ფუნქციამ DELETE ტრიგერში \"MODIFY\" დააბრუნა. -- იგნორირებულია"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "მოსალოდნელია არცერთი, \"OK\", \"SKIP\", ან \"MODIFY\"."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() -ის შეცდომა არგუმენტების მორგებისას"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() -ის შეცდომა არგუმენტების მორგებისას"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "ფუნქცია, რომელიც ჩანაწერს აბრუნებს, გამოძახებულია კონტექსტში, რომელსაც ჩანაწერის მიღება არ შეუძლია"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "დასაბრუნებელი მნიშვნელობის შექმნისას"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] წაშლილია, მწკრივის შეცვლა შეუძლებელია"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] ლექსიკონი არაა"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "TD[\"new\"] ლექსიკონის გასაღები ორდინალურ მდებარეობაზე %d სტრიქონს არ წარმოადგენს"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "\"TD[\"new\"]-ში ნაპოვნი გასაღები (%s) დამატრიგერებელი მწკრივის სვეტად არ არსებობს"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "სისტემური ატრიბუტის დაყენების შეცდომა: \"%s\""
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "გენერირებული სვეტის დაყენება შეუძლებელია: %s"
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "ტრიგერის მწკრივის შეცვლისას"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "ქვეტრანზაქცია, რომელიც ჯერ არ დასრულებულა, ძალით დასრულდება"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "სესია Python-ის ერთზე მეტ ბიბლიოთეკას შეიცავს"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "ერთ სესიაში Python-ის მხოლოდ ერთი ძირითადი ვერსია შეგიძლიათ გამოიყენოთ."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "დაუჭერელი შეცდომა ინიციალიზაციისას"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "\"__main__\" მოდულის შემოტანის შეცდომა"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "გლობალების ინიციალიზაციის შეცდომა"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python -ის პროცედურა \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python -ის ფუნქცია \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python -ის ანონიმური კოდის ბლოკი"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "\"plpy\" მოდულის შემოტანის შეცდომა"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "spiexceptions მოდულის შექმნის შეცდომა"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "spiexceptions მოდულის დამატების შეცდომა"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "\"SPI\" გამონაკლისების გენერაციის შეცდომა"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "plpy.elog-ში არგუმენტების გაშლის შეცდომა"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "plpy.elog-ში შეცდომის შეტყობინების დამუშავების შეცდომა"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "არგუმენტი 'message', მოცემული სახელითა მდებარეობით"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "%s ამ ფუნქციის არასწორი არგუმენტია"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "არასწორი SQLSTATE კოდი"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "ტრიგერის ფუნქციების გამოძახება მხოლოდ ტრიგერებად შეიძლება"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python ფუნქციებს ამ ტიპის დაბრუნება არ შეუძლიათ: %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python ფუნქციებს ამ ტიპის მიღება არ შეუძლიათ: %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "\"PL/Python\"-ის ფუნქციის კომპილაციის შეცდომა: \"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "\"PL/Python\"-ის კოდის ანონიმური ბლოკის კომპილაციის შეცდომა"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "ბრძანებამ შედეგი არ გამოიღო"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "plpy.prepare -ის მეორე არგუმენტი მიმდევრობა უნდა იყოს"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: ტიპის სახელი ორდინალურ მდგომარეობაში %d სტრიქონს არ წარმოადგენს"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute გეგმას ან მოთხოვნას მოელოდა"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute მეორე არგუმენტად მიმდევრობას იღებს"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan -ის შეცდომა: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute -ის შეცდომა: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "ეს ქვეტრანზაქცია უკვე დაიწყო"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "ეს ქვეტრანზაქცია უკვე დასრულდა"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "ეს ქვეტრანზაქცია არ დაწყებულა"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "გამოსასვლელი ქვეტრანზაქციები არ არსებობს"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "ათობითი კონსტრუქტორისთვის მოდულის შემოტანის პრობლემა"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "მოდულს ათობითს ატრიბუტი არ გააჩნია"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "რიცხვითიდან ათობითში გადაყვანის შეცდომა"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "\"Python\"-ის ობექტის ბაიტების რეპრეზენტაციის შექმნის შეცდომა"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "\"Python\"-ის ობექტის სტრიქონების რეპრეზენტაციის შექმნის შეცდომა"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "შეცდომა Python-ის ობიექტის cstring-ში გადაყვანისას: Python-ის სტრიქონის გამოხატულება ნულოვან ბაიტებს შეიცავს"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "ფუნქციის დაბრულების მნიშვნელობა მასივის დაბრუნების ტიპით Python-ის მიმდევრობა არაა"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "ფუნქციის დაბრუნებული მნიშვნელობის მიმდევრობის სიგრძის განსაზღვრა შეუძლებელია"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "მრავალგანზომილებიან მასივებს უნდა ჰქონდეთ მასივის გამოსახულებები შესაბამისი ზომებით"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "მასივის ზომების რაოდენობა მაქსიმუმ დასაშვებზე (%d) დიდია"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "ჩანაწერის არასწორი სტრიქონი: %s"
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "აკლია მარჯვენა ფრჩხილი."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "მასივში კომპოზიტური ტიპის დასაბრუნებლად კომპოზიტური ტიპი, როგორც Python-ის კოლაჟი, ისე დააბრუნეთ. მაგ: \"[('foo',)]\"."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "ბმაში გასაღები %s ნაპოვნი არაა"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "სვეტში ნულის დასაბრუნებლად ამ სვეტის სახელის მქონე გასაღების მიბმას მნიშვნელობა None დაამატეთ."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "დაბრუნებული მიმდევრობის სიგრძე მწკრივში სვეტების რაოდენობას არ ემთხვევა"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "ატრიბუტი \"%s\" Python -ის ობიექტში არ არსებობს"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "სვეტში ნულის დასაბრუნებლად დასაბრუნებელ ობიექტს მიანიჭეთ სვეტის სახელის მქონე ატრიბუტი , მნიშვნელობით \"None\"."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "\"Python Unicode\" ტიპის ობიექტის ბაიტებად გარდაქმნის შეცდომა"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "ბაიტების ამოღების შეცდომა კოდირებული სტრიქონიდან"
+
+#, c-format
+#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+#~ msgstr "მრავალგანზომილებიანი მასივის ასაშენებლად ყველა შიდა მიმდევრობის სიგრძე ტოლი უნდა იყოს."
+
+#, c-format
+#~ msgid "array size exceeds the maximum allowed"
+#~ msgstr "მასივის ზომა მაქსიმალურ დასაშვებს აჭარბებს"
+
+#, c-format
+#~ msgid "wrong length of inner sequence: has length %d, but %d was expected"
+#~ msgstr "შიდა მიმდევრობის არასწორი სიგრძე: სიგრძე: %d. უნდა იყოს: %d"
diff --git a/src/pl/plpython/po/ko.po b/src/pl/plpython/po/ko.po
new file mode 100644
index 0000000..e4d7a58
--- /dev/null
+++ b/src/pl/plpython/po/ko.po
@@ -0,0 +1,501 @@
+# LANGUAGE message translation file for plpython
+# Copyright (C) 2015 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Ioseph Kim <ioseph@uri.sarang.net>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 12\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2020-02-09 20:08+0000\n"
+"PO-Revision-Date: 2019-11-01 12:53+0900\n"
+"Last-Translator: Ioseph Kim <ioseph@uri.sarang.net>\n"
+"Language-Team: Korean <pgsql-kr@postgresql.kr>\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: plpy_cursorobject.c:78
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor 객체는 쿼리나 plpy.prepare 객체를 인자로 사용합니다"
+
+#: plpy_cursorobject.c:161
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr ""
+"plpy.cursor 객체의 인자로 plpy.prepare 객체를 사용한 경우 두번째 인자는 "
+"prepare 객체의 매개변수가 있어야 합니다."
+
+#: plpy_cursorobject.c:177 plpy_spi.c:211
+#, c-format
+msgid "could not execute plan"
+msgstr "plpy.prepare 객체를 실행할 수 없음"
+
+#: plpy_cursorobject.c:180 plpy_spi.c:214
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "%d 개의 인자가 필요한데, %d개의 인자를 지정했음: %s"
+
+#: plpy_cursorobject.c:329
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "이미 닫긴 커서에서 다음 자료를 요구하고 있음"
+
+#: plpy_cursorobject.c:337 plpy_cursorobject.c:403
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "중지된 서브 트랜잭션에 있는 커서에서 다음 자료를 요구하고 있음"
+
+#: plpy_cursorobject.c:395
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "닫긴 커서에서 fetch"
+
+#: plpy_cursorobject.c:438 plpy_spi.c:409
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "쿼리 결과가 Python 리스트로 담기에는 너무 많습니다"
+
+#: plpy_cursorobject.c:490
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "중지된 서브트랜잭션에서 커서를 닫고 있음"
+
+#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:143
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "지원하지 않는 집합 함수 리턴 모드"
+
+#: plpy_exec.c:144
+#, c-format
+msgid ""
+"PL/Python set-returning functions only support returning one value per call."
+msgstr ""
+"PL/Python 집합-반환 함수는 한번의 호출에 대해서 하나의 값만 반환할 수 있습니"
+"다."
+
+#: plpy_exec.c:157
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "반환하는 객체가 iterable 형이 아님"
+
+#: plpy_exec.c:158
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python 집합-반환 함수는 iterable 객체를 반환해야 합니다."
+
+#: plpy_exec.c:172
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "iterator에서 다음 아이템을 가져올 수 없음"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python 프로시져가 None을 반환하지 않았음"
+
+#: plpy_exec.c:219
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr ""
+"반환 자료형이 \"void\"인 PL/Python 함수가 return None으로 끝나지 않았음"
+
+#: plpy_exec.c:375 plpy_exec.c:401
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "트리거 프로시져가 예상치 못한 값을 반환했습니다"
+
+#: plpy_exec.c:376
+#, c-format
+msgid "Expected None or a string."
+msgstr "None 이나 문자열이 있어야합니다."
+
+#: plpy_exec.c:391
+#, c-format
+msgid ""
+"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr ""
+"PL/Python 트리거 함수가 DELETE 트리거에서 \"MODIFY\"를 반환했음 -- 무시함"
+
+#: plpy_exec.c:402
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "None, \"OK\", \"SKIP\", 또는 \"MODIFY\"를 사용해야 함."
+
+#: plpy_exec.c:452
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() 함수가 인자 설정하는 중 실패"
+
+#: plpy_exec.c:456
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() 함수가 인자 설정하는 중 실패"
+
+#: plpy_exec.c:468
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr "반환 자료형이 record인데 함수가 그 자료형으로 반환하지 않음"
+
+#: plpy_exec.c:685
+#, c-format
+msgid "while creating return value"
+msgstr "반환값을 만들고 있은 중"
+
+#: plpy_exec.c:919
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] 변수가 삭제되었음, 로우를 수정할 수 없음"
+
+#: plpy_exec.c:924
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] 변수가 딕션너리 형태가 아님"
+
+#: plpy_exec.c:951
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "%d 번째 TD[\"new\"] 딕션너리 키가 문자열이 아님"
+
+#: plpy_exec.c:958
+#, c-format
+msgid ""
+"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering "
+"row"
+msgstr ""
+"로우 트리거 작업에서 칼럼으로 사용되는 \"%s\" 키가 TD[\"new\"] 변수에 없음."
+
+#: plpy_exec.c:963
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" 시스템 속성을 지정할 수 없음"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "\"%s\" 계산된 칼럼을 지정할 수 없음"
+
+#: plpy_exec.c:1026
+#, c-format
+msgid "while modifying trigger row"
+msgstr "로우 변경 트리거 작업 도중"
+
+#: plpy_exec.c:1087
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "서브트랜잭션이 중지됨으로 강제로 중지됨"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "세션에서 여러 Python 라이브러리가 사용되고 있습니다"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "하나의 세션에서는 하나의 Python 메이져 버전만 사용할 수 있습니다."
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "plpy 모듈 초기화 실패"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "\"__main__\" 모듈은 임포트 할 수 없음"
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "전역변수들을 초기화 할 수 없음"
+
+#: plpy_main.c:399
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "\"%s\" PL/Python 프로시져"
+
+#: plpy_main.c:402
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "\"%s\" PL/Python 함수"
+
+#: plpy_main.c:410
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python 익명 코드 블럭"
+
+#: plpy_plpymodule.c:186 plpy_plpymodule.c:189
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "\"plpy\" 모듈을 임포트 할 수 없음"
+
+#: plpy_plpymodule.c:204
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "spiexceptions 모듈을 만들 수 없음"
+
+#: plpy_plpymodule.c:212
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "spiexceptions 모듈을 추가할 수 없음"
+
+#: plpy_plpymodule.c:280
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "SPI 예외처리를 생성할 수 없음"
+
+#: plpy_plpymodule.c:448
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "잘못된 인자로 구성된 plpy.elog"
+
+#: plpy_plpymodule.c:457
+msgid "could not parse error message in plpy.elog"
+msgstr "plpy.elog 에서 오류 메시지를 분석할 수 없음"
+
+#: plpy_plpymodule.c:474
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "'message' 인자는 이름과 위치가 있어야 함"
+
+#: plpy_plpymodule.c:501
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' 값은 이 함수에서 잘못된 예약어 인자입니다"
+
+#: plpy_plpymodule.c:512 plpy_plpymodule.c:518
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "잘못된 SQLSTATE 코드"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "트리거 함수는 트리거로만 호출될 수 있음"
+
+#: plpy_procedure.c:234
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python 함수는 %s 자료형을 반환할 수 없음"
+
+#: plpy_procedure.c:312
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python 함수는 %s 자료형을 사용할 수 없음"
+
+#: plpy_procedure.c:402
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "\"%s\" PL/Python 함수를 컴파일 할 수 없음"
+
+#: plpy_procedure.c:405
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "anonymous PL/Python 코드 블록을 컴파일 할 수 없음"
+
+#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173
+#, c-format
+msgid "command did not produce a result set"
+msgstr "명령의 결과값이 없음"
+
+#: plpy_spi.c:60
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "plpy.prepare 함수의 두번째 인자는 Python 시퀀스형이어야 함"
+
+#: plpy_spi.c:104
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: %d 번째 인자의 자료형이 문자열이 아님"
+
+#: plpy_spi.c:176
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute 함수의 인자는 쿼리문이나 plpy.prepare 객체여야 함"
+
+#: plpy_spi.c:195
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execut 함수의 두번째 인자는 python 시퀀스형이 와야함"
+
+#: plpy_spi.c:305
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan 실패: %s"
+
+#: plpy_spi.c:347
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute 실패: %s"
+
+#: plpy_subxactobject.c:97
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "이 서브트랜잭션은 이미 시작되었음"
+
+#: plpy_subxactobject.c:103 plpy_subxactobject.c:161
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "이 서브트랜잭션은 이미 끝났음"
+
+#: plpy_subxactobject.c:155
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "이 서브트랜잭션이 시작되지 않았음"
+
+#: plpy_subxactobject.c:167
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "종료할 서브트랜잭션이 없음, 위치:"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "Decimal 자료형 처리를 위해 모듈을 임포트 할 수 없음"
+
+#: plpy_typeio.c:595
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "모듈안에 Decimal 속성이 없음"
+
+#: plpy_typeio.c:601
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "numeric 형을 Decimal 형으로 변환할 수 없음"
+
+#: plpy_typeio.c:915
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "Python 객체를 bytea 자료형으로 변환할 수 없음"
+
+#: plpy_typeio.c:1063
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "Python 객체를 문자열 자료형으로 변환할 수 없음"
+
+#: plpy_typeio.c:1074
+#, c-format
+msgid ""
+"could not convert Python object into cstring: Python string representation "
+"appears to contain null bytes"
+msgstr ""
+"Python 객체를 cstring 형으로 변환할 수 없음: Python string 변수에 null문자열"
+"이 포함되어 있음"
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "배열 차원이 최대치 (%d)를 초과 했습니다."
+
+#: plpy_typeio.c:1187
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "함수 반환 값으로 시퀀스 길이를 결정할 수 없음"
+
+#: plpy_typeio.c:1190 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "배열 최대 크기를 초과함"
+
+#: plpy_typeio.c:1220
+#, c-format
+msgid ""
+"return value of function with array return type is not a Python sequence"
+msgstr "배열형으로 넘길 자료형이 Python 시퀀스형이 아님"
+
+#: plpy_typeio.c:1266
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "잘못된 내부 시퀀스 길이, 길이 %d, %d 초과했음"
+
+#: plpy_typeio.c:1268
+#, c-format
+msgid ""
+"To construct a multidimensional array, the inner sequences must all have the "
+"same length."
+msgstr "다차원 배열을 사용하려면, 그 하위 배열의 차원이 모두 같아야합니다."
+
+#: plpy_typeio.c:1347
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "잘못된 레코드 표현: \"%s\""
+
+#: plpy_typeio.c:1348
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "왼쪽 괄호가 없음."
+
+#: plpy_typeio.c:1349 plpy_typeio.c:1550
+#, c-format
+msgid ""
+"To return a composite type in an array, return the composite type as a "
+"Python tuple, e.g., \"[('foo',)]\"."
+msgstr ""
+"배열에서 복합 자료형을 반환하려면, Python 튜플 형을 사용하세요. 예: "
+"\"[('foo',)]\"."
+
+#: plpy_typeio.c:1396
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "맵 안에 \"%s\" 키가 없음"
+
+#: plpy_typeio.c:1397
+#, c-format
+msgid ""
+"To return null in a column, add the value None to the mapping with the key "
+"named after the column."
+msgstr ""
+"칼럼값으로 null을 반환하려면, 칼럼 다음에 해당 키 이름과 맵핑 되는 None값을 "
+"지정하세요"
+
+#: plpy_typeio.c:1450
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "반환되는 시퀀스형 변수의 길이가 로우의 칼럼수와 일치하지 않음"
+
+#: plpy_typeio.c:1548
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "Python 객체 가운데 \"%s\" 속성이 없음"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid ""
+"To return null in a column, let the returned object have an attribute named "
+"after column with value None."
+msgstr ""
+"칼럼 값으로 null 을 반환하려면, 값으로 None 값을 가지는 칼럼 뒤에, 속성 이름"
+"이 있는 객체를 반환하세요"
+
+#: plpy_util.c:35
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "Python 유니코드 객체를 UTF-8 문자열로 변환할 수 없음"
+
+#: plpy_util.c:41
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "해당 인코드 문자열을 Python에서 사용할 수 없음"
+
+#~ msgid "could not create new dictionary"
+#~ msgstr "새 디렉터리를 만들 수 없음"
+
+#~ msgid "could not create exception \"%s\""
+#~ msgstr "\"%s\" 예외처리를 생성할 수 없음"
+
+#~ msgid "could not create globals"
+#~ msgstr "전역변수들을 만들 수 없음"
+
+#~ msgid "could not create new dictionary while building trigger arguments"
+#~ msgstr "트리거 인자를 구성하는 중 새 딕션너리를 만들 수 없음"
diff --git a/src/pl/plpython/po/pl.po b/src/pl/plpython/po/pl.po
new file mode 100644
index 0000000..4abd6f5
--- /dev/null
+++ b/src/pl/plpython/po/pl.po
@@ -0,0 +1,507 @@
+# plpython message translation file for plpython
+# Copyright (C) 2011 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Begina Felicysym <begina.felicysym@wp.eu>, 2011, 2012.
+# grzegorz <begina.felicysym@wp.eu>, 2014, 2015, 2016, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL 9.1)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2017-04-09 21:07+0000\n"
+"PO-Revision-Date: 2017-04-11 23:15+0200\n"
+"Last-Translator: grzegorz <begina.felicysym@wp.eu>\n"
+"Language-Team: begina.felicysym@wp.eu\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Virtaal 0.7.1\n"
+
+#: plpy_cursorobject.c:100
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor oczekuje kwerendy lub planu"
+
+#: plpy_cursorobject.c:176
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor przyjmuje sekwencję jako drugi argument"
+
+#: plpy_cursorobject.c:192 plpy_spi.c:226
+#, c-format
+msgid "could not execute plan"
+msgstr "nie można wykonać planu"
+
+#: plpy_cursorobject.c:195 plpy_spi.c:229
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Oczekiwano sekwencji z %d argumentem, mamy %d: %s"
+msgstr[1] "Oczekiwano sekwencji z %d argumentami, mamy %d: %s"
+msgstr[2] "Oczekiwano sekwencji z %d argumentami, mamy %d: %s"
+
+#: plpy_cursorobject.c:350
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "iteracja zamkniętego kursora"
+
+#: plpy_cursorobject.c:358 plpy_cursorobject.c:423
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iteracja kursora w przerwanej podtransakcji"
+
+#: plpy_cursorobject.c:415
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "pobranie z zamkniętego kursora"
+
+#: plpy_cursorobject.c:463 plpy_spi.c:434
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "wynik zapytania ma za dużo wierszy by pomieścić w liście Python"
+
+#: plpy_cursorobject.c:504
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "zamknięcie kursora w przerwanej podtransakcji"
+
+#: plpy_elog.c:127 plpy_elog.c:128 plpy_plpymodule.c:548
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "nieobsługiwany tryb zwracania przez funkcję grupy"
+
+#: plpy_exec.c:141
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "funkcje zwracające grupę PL/Python obsługuje tylko zwracanie jednej wartości w wywołaniu."
+
+#: plpy_exec.c:154
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "zwrócony obiekt nie może być przeiterowany"
+
+#: plpy_exec.c:155
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "funkcje PL/Python zwracające grupę muszą zwracać iterowalny obiekt."
+
+#: plpy_exec.c:169
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "błąd pobierania następnego elementu z iteratora"
+
+#: plpy_exec.c:210
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "funkcja PL/Python zwracająca typ \"void\" nie zwróciła wartości None"
+
+#: plpy_exec.c:379 plpy_exec.c:405
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "nieoczekiwana wartość zwracana przez procedury wyzwalacza"
+
+#: plpy_exec.c:380
+#, c-format
+msgid "Expected None or a string."
+msgstr "Oczekiwano None lub ciąg znaków."
+
+#: plpy_exec.c:395
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "funkcja wyzwalacza PL/Python zwróciła \"MODIFY\" w wyzwalaczu DELETE -- zignorowano"
+
+#: plpy_exec.c:406
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Oczekiwano None, \"OK\", \"SKIP\", lub \"MODIFY\"."
+
+#: plpy_exec.c:487
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "nie powiodło się PyList_SetItem() podczas ustawiania argumentów"
+
+#: plpy_exec.c:491
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "nie powiodło się PyDict_SetItemString() podczas ustawiania argumentów"
+
+#: plpy_exec.c:503
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkcja zwracająca rekord w wywołaniu, które nie akceptuje typów złożonych"
+
+#: plpy_exec.c:719
+#, c-format
+msgid "while creating return value"
+msgstr "podczas tworzenia wartości zwracanej"
+
+#: plpy_exec.c:743
+#, c-format
+msgid "could not create new dictionary while building trigger arguments"
+msgstr "nie można utworzyć nowego słownika w czasie tworzenia argumentów wyzwalacza"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "usunięto TD[\"new\"], nie można zmienić wiersza"
+
+#: plpy_exec.c:936
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] nie jest słownikiem"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "klucz słownika TD[\"new\"] na pozycji porządkowej %d nie jest ciągiem znaków"
+
+#: plpy_exec.c:970
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "klucz \"%s\" znaleziony w TD[\"new\"] nie istnieje jako kolumna w wierszu obsługiwanym przez wyzwalacz"
+
+#: plpy_exec.c:975
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nie można ustawić atrybutu systemowego \"%s\""
+
+#: plpy_exec.c:1046
+#, c-format
+msgid "while modifying trigger row"
+msgstr "podczas modyfikowania wiersza wyzwalacza"
+
+#: plpy_exec.c:1107
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "wymuszone przerywanie podtransakcji, która nie została zakończona"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "kilka bibliotek Python jest dostępne w sesji"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Tylko jedna podstawowa wersja Python może być używana w sesji."
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "niewyłapany błąd w inicjacji"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "nie można zaimportować modułu \"__main__\""
+
+#: plpy_main.c:170
+#, c-format
+msgid "could not create globals"
+msgstr "nie można utworzyć zmiennych globalnych"
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "nie można zainicjować zmiennych globalnych"
+
+#: plpy_main.c:387
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "funkcja PL/Python \"%s\""
+
+#: plpy_main.c:394
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "anonimowy blok kodu PL/Python"
+
+#: plpy_plpymodule.c:181 plpy_plpymodule.c:184
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "nie można zaimportować modułu \"plpy\""
+
+#: plpy_plpymodule.c:199
+#, c-format
+#| msgid "could not add the spiexceptions module"
+msgid "could not create the spiexceptions module"
+msgstr "nie udało się utworzyć modułu spiexceptions"
+
+#: plpy_plpymodule.c:207
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "nie udało się dodać modułu spiexceptions"
+
+#: plpy_plpymodule.c:236
+#, c-format
+#| msgid "could not create directory \"%s\": %m"
+msgid "could not create exception \"%s\""
+msgstr "nie można utworzyć wyjątku \"%s\""
+
+#: plpy_plpymodule.c:271 plpy_plpymodule.c:275
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "nie można wygenerować wyjątków SPI"
+
+#: plpy_plpymodule.c:443
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "nie można rozpakować argumentów w plpy.elog"
+
+#: plpy_plpymodule.c:452
+msgid "could not parse error message in plpy.elog"
+msgstr "nie można przetworzyć komunikatu błędu w plpy.elog"
+
+#: plpy_plpymodule.c:469
+#, c-format
+msgid "Argument 'message' given by name and position"
+msgstr "Argument 'message' przekazany przez nazwę i pozycję"
+
+#: plpy_plpymodule.c:496
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' jest niepoprawnym słowem kluczowym argumentu dla tej funkcji"
+
+#: plpy_plpymodule.c:507 plpy_plpymodule.c:513
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "błędny kod SQLSTATE"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "procedury wyzwalaczy mogą być wywoływane jedynie przez wyzwalacze"
+
+#: plpy_procedure.c:235
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "funkcje PL/Python nie mogą zwracać wartości typu %s"
+
+#: plpy_procedure.c:316
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "funkcje PL/Python nie obsługują typu %s"
+
+#: plpy_procedure.c:412
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "nie powiodła się kompilacja funkcji PL/Python \"%s\""
+
+#: plpy_procedure.c:415
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "nie udało się skompilować anonimowego bloku kodu PL/Python"
+
+#: plpy_resultobject.c:145 plpy_resultobject.c:165 plpy_resultobject.c:185
+#, c-format
+msgid "command did not produce a result set"
+msgstr "polecenie nie utworzyło zbioru wynikowego"
+
+#: plpy_spi.c:59
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "drugi argument plpy.prepare musi być sekwencją"
+
+#: plpy_spi.c:115
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: nazwa typu na pozycji porządkowej %d nie jest ciągiem znaków"
+
+#: plpy_spi.c:191
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute oczekuje kwerendy lub planu"
+
+#: plpy_spi.c:210
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute przyjmuje sekwencję jako drugi argument"
+
+#: plpy_spi.c:335
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "nie powiódł się SPI_execute_plan: %s"
+
+#: plpy_spi.c:377
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "nie powiódł się SPI_execute: %s"
+
+#: plpy_subxactobject.c:122
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "ta podtransakcja już została wprowadzona"
+
+#: plpy_subxactobject.c:128 plpy_subxactobject.c:186
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "ta podtransakcja już została zakończona"
+
+#: plpy_subxactobject.c:180
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "ta podtransakcja nie została wprowadzona"
+
+#: plpy_subxactobject.c:192
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "brak podtransakcji by z niej wyjść"
+
+#: plpy_typeio.c:292
+#, c-format
+msgid "could not create new dictionary"
+msgstr "nie można utworzyć nowego słownika"
+
+#: plpy_typeio.c:560
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "nie można zaimportować modułu dla konstruktora Decimal"
+
+#: plpy_typeio.c:564
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "brak atrybutu Decimal w module"
+
+#: plpy_typeio.c:570
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "konwersja z numeric na Decimal nie powiodła się"
+
+#: plpy_typeio.c:772
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "nie można utworzyć reprezentacji bajtowej obiektu Python"
+
+#: plpy_typeio.c:881
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "nie można utworzyć reprezentacji znakowej obiektu Python"
+
+#: plpy_typeio.c:892
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "nie można zmienić obiektu Python na cstring: reprezentacja ciągu znaków Python wydaje się zawierać puste bajty"
+
+#: plpy_typeio.c:949
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "nieprawidłowy literał rekordu: \"%s\""
+
+#: plpy_typeio.c:950
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Brak lewego nawiasu."
+
+#: plpy_typeio.c:951 plpy_typeio.c:1389
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\""
+msgstr ""
+"By zwrócić typ złożony w tablicy, zwracaj typ złożony jako krotkę Pythona, "
+"np. \"[('foo')]\""
+
+#: plpy_typeio.c:1000
+#, c-format
+#| msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)"
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "liczba wymiarów tablicy przekracza dozwolone maksimum (%d)"
+
+#: plpy_typeio.c:1004
+#, c-format
+#| msgid "cannot determine OID of function lo_truncate\n"
+msgid "cannot determine sequence length for function return value"
+msgstr ""
+"nie można ustalić długości sekwencji dla zwracanej wartości przez funkcję"
+
+#: plpy_typeio.c:1007 plpy_typeio.c:1011
+#, c-format
+#| msgid "array size exceeds the maximum allowed (%d)"
+msgid "array size exceeds the maximum allowed"
+msgstr "rozmiar tablicy przekracza dozwolone maksimum"
+
+#: plpy_typeio.c:1037
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "wartość zwrócona przez funkcję zwracającą tablicę nie jest sekwencją Python"
+
+#: plpy_typeio.c:1090
+#, c-format
+#| msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgid "multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length %d while expected %d"
+msgstr ""
+"wielowymiarowe tablice muszą mieć wyrażenia tablicowe z pasującymi "
+"wymiarami. Wartość zwracana przez funkcję PL/Python ma długość sekwencji %d "
+"gdy oczekiwano %d"
+
+#: plpy_typeio.c:1212
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "nie odnaleziono klucza \"%s\" w mapowaniu"
+
+#: plpy_typeio.c:1213
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Aby zwrócić null w kolumnie, dodaj wartość None do mapowania z kluczem nazwanym wedle kolumny."
+
+#: plpy_typeio.c:1264
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "długość zwróconej sekwencji nie jest równa liczbie kolumn w wierszu"
+
+#: plpy_typeio.c:1387
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "atrybut \"%s\" nie istnieje w obiekcie Python"
+
+#: plpy_typeio.c:1390
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Aby zwrócić null w kolumnie, niech zwrócony obiekt posiada atrybut nazwany wedle kolumny z wartością None."
+
+#: plpy_util.c:36
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "nie można zmienić obiektu unikodowego Python na bajty"
+
+#: plpy_util.c:42
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "nie można wyciągnąć bajtów z kodowanego ciągu znaków"
+
+#~ msgid "Python major version mismatch in session"
+#~ msgstr "niezgodna wersja główna Python w sesji"
+
+#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d."
+#~ msgstr "Ta sesja używała poprzednio Python w głównej wersji %d, teraz próbuje użyć Python w głównej wersji %d."
+
+#~ msgid "Start a new session to use a different Python major version."
+#~ msgstr "Uruchom nową sesję aby użyć innej głównej wersji Python."
+
+#~ msgid "plpy.prepare does not support composite types"
+#~ msgstr "plpy.prepare nie obsługuje typów złożonych"
+
+#~ msgid "PL/Python does not support conversion to arrays of row types."
+#~ msgstr "PL/Python nie obsługuje konwersji typów wierszowych na tablice."
+
+#~ msgid "unrecognized error in PLy_spi_execute_fetch_result"
+#~ msgstr "nierozpoznany błąd w PLy_spi_execute_fetch_result"
+
+#~ msgid "could not create new Python list"
+#~ msgstr "nie można utworzyć nowej listy Python"
+
+#~ msgid "PL/Python only supports one-dimensional arrays."
+#~ msgstr "PL/Python obsługuje tylko jednowymiarowe tablice."
+
+#~ msgid "cannot convert multidimensional array to Python list"
+#~ msgstr "nie można skonwertować tablicy wielowymiarowej na listę Python"
+
+#~ msgid "could not create the base SPI exceptions"
+#~ msgstr "nie można stworzyć bazowych wyjątków SPI"
+
+#~ msgid "plan.status takes no arguments"
+#~ msgstr "plan.status nie przyjmuje żadnych argumentów"
diff --git a/src/pl/plpython/po/pt_BR.po b/src/pl/plpython/po/pt_BR.po
new file mode 100644
index 0000000..ddeae15
--- /dev/null
+++ b/src/pl/plpython/po/pt_BR.po
@@ -0,0 +1,461 @@
+# Brazilian Portuguese message translation file for plpython
+#
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Euler Taveira <euler@eulerto.com>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-27 13:15-0300\n"
+"PO-Revision-Date: 2009-05-10 01:15-0300\n"
+"Last-Translator: Euler Taveira <euler@eulerto.com>\n"
+"Language-Team: Brazilian Portuguese <pgsql-translators@postgresql.org>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n>1);\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor esperava uma consulta ou um plano"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor tem uma sequência como seu segundo argumento"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "não pôde executar plano"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Sequência esperada de %d argumento, recebeu %d: %s"
+msgstr[1] "Sequência esperada de %d argumentos, recebeu %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "iterando um cursor fechado"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iterando um cursor em uma subtransação abortada"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "busca em um cursor fechado"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "resultado da consulta tem muitos registros para caber em uma lista Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "fechando um cursor em uma subtransação abortada"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "modo de retorno da função que retorna conjunto não é suportado"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "funções PL/Python que retornam conjunto só suportam retornar um valor por chamada."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "objeto retornado não pode ser iterado"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "funções PL/Python que retornam conjunto devem retornar um objeto iterável."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "erro ao buscar próximo item do iterador"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "procedimento PL/Python não retornou None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "função PL/Python com tipo de retorno \"void\" não retornou None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "função de gatilho retornou valor inesperado"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "None ou uma cadeia de caracteres era esperado."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "função de gatilho PL/Python retornou \"MODIFY\" em um gatilho DELETE -- ignorado"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Era esperado None, \"OK\", \"SKIP\" ou \"MODIFY\"."
+
+#: plpy_exec.c:441
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() falhou ao definir argumentos"
+
+#: plpy_exec.c:445
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() falhou ao definir argumentos"
+
+#: plpy_exec.c:457
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "função que retorna record foi chamada em um contexto que não pode aceitar tipo record"
+
+#: plpy_exec.c:674
+#, c-format
+msgid "while creating return value"
+msgstr "ao criar valor de retorno"
+
+#: plpy_exec.c:908
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] removido, não pode modificar registro"
+
+#: plpy_exec.c:913
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] não é um dicionário"
+
+#: plpy_exec.c:938
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "chave do dicionário TD[\"new\"] na posição %d não é uma cadeia de caracteres"
+
+#: plpy_exec.c:945
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "chave \"%s\" encontrada em TD[\"new\"] não existe como uma coluna no registro do gatilho"
+
+#: plpy_exec.c:950
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "não pode definir atributo do sistema \"%s\""
+
+#: plpy_exec.c:955
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "não pode definir coluna gerada \"%s\""
+
+#: plpy_exec.c:1013
+#, c-format
+msgid "while modifying trigger row"
+msgstr "ao modificar registro de gatilho"
+
+#: plpy_exec.c:1071
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "forçado a abortar subtransação que não foi concluída"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "múltiplas bibliotecas do Python estão presentes na sessão"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Apenas uma versão do Python pode ser utilizada na sessão."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "erro não interceptado na inicialização"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "não pôde importar módulo \"__main__\""
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "não pôde inicializar globais"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "procedimento PL/Python \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "função PL/Python \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "bloco de código PL/Python anônimo"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "não pôde importar módulo \"plpy\""
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "não pôde criar o módulo spiexceptions"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "não pôde adicionar o módulo spiexceptions"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "não pôde gerar exceções da SPI"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "não pode desempacotar argumentos em plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "não pode analisar mensagem de erro em plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "argumento 'message' informado por nome e posição"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' é um argumento inválido para esta função"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "código SQLSTATE inválido"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funções de gatilho só podem ser chamadas como gatilhos"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "funções PL/Python não podem retornar tipo %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "funções PL/Python não podem aceitar tipo %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "não pôde compilar função PL/Python \"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "não pôde compilar bloco de código PL/Python anônimo"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "comando não produziu um conjunto de resultados"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "segundo argumento de plpy.prepare deve ser uma sequência"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: nome do tipo na posição %d não é uma cadeia de caracteres"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute espera uma consulta ou um plano"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute recebe uma sequência como segundo argumento"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan falhou: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute falhou: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "essa subtransação já foi iniciada"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "essa subtransação já foi concluída"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "essa subtransação não foi iniciada"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "não há uma subtransação a ser concluída"
+
+#: plpy_typeio.c:587
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "não pôde importar módulo para construtor Decimal"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "nenhum atributo Decimal no módulo"
+
+#: plpy_typeio.c:597
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "conversão de numeric para Decimal falhou"
+
+#: plpy_typeio.c:911
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "não pôde criar representação de bytes de um objeto Python"
+
+#: plpy_typeio.c:1048
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "não pôde criar representação de cadeia de caracteres de um objeto Python"
+
+#: plpy_typeio.c:1059
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "não pôde converter objeto Python em cstring: representação de cadeia de caracteres Python parece conter bytes nulos"
+
+#: plpy_typeio.c:1170
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "número de dimensões da matriz excede o máximo permitido (%d)"
+
+#: plpy_typeio.c:1175
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "não pôde determinar tamanho da sequência para valor de retorno da função"
+
+#: plpy_typeio.c:1180 plpy_typeio.c:1186
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "tamanho da matriz excede o máximo permitido"
+
+#: plpy_typeio.c:1214
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "valor de retorno da função do tipo matriz retorna tipo que não é uma sequência Python"
+
+#: plpy_typeio.c:1261
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "tamanho incorreto da sequência interna: tem tamanho %d, mas %d era esperado"
+
+#: plpy_typeio.c:1263
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "Para construir uma matriz multidimensional, todas as sequências internas devem ter o mesmo tamanho."
+
+#: plpy_typeio.c:1342
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "matriz mal formada: \"%s\""
+
+#: plpy_typeio.c:1343
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Faltando parêntese esquerdo."
+
+#: plpy_typeio.c:1344 plpy_typeio.c:1545
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Para retornar um tipo composto em uma matriz, retorne o tipo composto como uma tupla do Python, i.e., \"[('foo',)]\"."
+
+#: plpy_typeio.c:1391
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "chave \"%s\" não foi encontrada no mapeamento"
+
+#: plpy_typeio.c:1392
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Para retornar nulo em uma coluna, adicionar o valor None no mapeamento cuja chave é o nome da coluna."
+
+#: plpy_typeio.c:1445
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "tamanho da sequência retornada não combina com número de colunas no registro"
+
+#: plpy_typeio.c:1543
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "atributo \"%s\" não existe no objeto Python"
+
+#: plpy_typeio.c:1546
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Para retornar nulo na coluna, deixe o objeto retornado ter um atributo cuja chave é o nome do coluna e o valor é None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "não pôde converter objeto Unicode Python para bytes"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "não pôde extrair bytes de cadeia de caracteres codificada"
diff --git a/src/pl/plpython/po/ru.po b/src/pl/plpython/po/ru.po
new file mode 100644
index 0000000..b632dba
--- /dev/null
+++ b/src/pl/plpython/po/ru.po
@@ -0,0 +1,561 @@
+# Russian message translation file for plpython
+# Copyright (C) 2012-2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Alexander Lakhin <exclusion@gmail.com>, 2012-2017, 2018, 2019.
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL current)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-11-03 09:09+0300\n"
+"PO-Revision-Date: 2023-05-05 06:34+0300\n"
+"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n"
+"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor ожидает запрос или план"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor принимает в качестве второго аргумента последовательность"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "нельзя выполнить план"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Ожидалась последовательность из %d аргумента, получено %d: %s"
+msgstr[1] "Ожидалась последовательность из %d аргументов, получено %d: %s"
+msgstr[2] "Ожидалась последовательность из %d аргументов, получено %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "перемещение закрытого курсора"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "перемещение курсора в прерванной подтранзакции"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "выборка из закрытого курсора"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr ""
+"результат запроса содержит слишком много строк для передачи в списке Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "закрытие курсора в прерванной подтранзакции"
+
+#: plpy_elog.c:122 plpy_elog.c:123 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "неподдерживаемый режим возврата для функции с результатом-множеством"
+
+#: plpy_exec.c:140
+#, c-format
+msgid ""
+"PL/Python set-returning functions only support returning one value per call."
+msgstr ""
+"Функции PL/Python с результатом-множеством могут возвращать только одно "
+"значение за вызов."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "возвращаемый объект не поддерживает итерации"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr ""
+"Функции PL/Python с результатом-множеством должны возвращать объекты с "
+"возможностью итерации."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "ошибка получения следующего элемента из итератора"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "процедура PL/Python вернула не None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "функция PL/Python с типом результата \"void\" вернула не None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "триггерная процедура вернула недопустимое значение"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Ожидалось None или строка."
+
+#: plpy_exec.c:385
+#, c-format
+msgid ""
+"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr ""
+"триггерная функция PL/Python вернула \"MODIFY\" в триггере DELETE -- "
+"игнорируется"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Ожидалось None, \"OK\", \"SKIP\" или \"MODIFY\"."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "ошибка в PyList_SetItem() при настройке аргументов"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "ошибка в PyDict_SetItemString() при настройке аргументов"
+
+#: plpy_exec.c:462
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"функция, возвращающая запись, вызвана в контексте, не допускающем этот тип"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "при создании возвращаемого значения"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "элемент TD[\"new\"] удалён -- изменить строку нельзя"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] - не словарь"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "ключ словаря TD[\"new\"] с порядковым номером %d не является строкой"
+
+#: plpy_exec.c:963
+#, c-format
+msgid ""
+"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering "
+"row"
+msgstr ""
+"ключу \"%s\", найденному в TD[\"new\"], не соответствует столбец в строке, "
+"обрабатываемой триггером"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "присвоить значение системному атрибуту \"%s\" нельзя"
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "присвоить значение генерируемому столбцу \"%s\" нельзя"
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "при изменении строки в триггере"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "принудительное прерывание незавершённой подтранзакции"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "в сеансе представлено несколько библиотек Python"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "В одном сеансе нельзя использовать Python разных старших версий."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "необработанная ошибка при инициализации"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "не удалось импортировать модуль \"__main__\""
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "не удалось инициализировать глобальные данные"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "процедура PL/Python \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "функция PL/Python \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "анонимный блок кода PL/Python"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "не удалось импортировать модуль \"plpy\""
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "не удалось создать модуль spiexceptions"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "не удалось добавить модуль spiexceptions"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "не удалось сгенерировать исключения SPI"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "не удалось распаковать аргументы в plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "не удалось разобрать сообщение об ошибке в plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "аргумент 'message' задан и по имени, и по позиции"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' - недопустимое ключевое слово (аргумент) для этой функции"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "неверный код SQLSTATE"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "триггерные функции могут вызываться только в триггерах"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "функции PL/Python не могут возвращать тип %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "функции PL/Python не могут принимать тип %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "не удалось скомпилировать функцию PL/Python \"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "не удалось скомпилировать анонимный блок кода PL/Python"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "команда не выдала результирующий набор"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "вторым аргументом plpy.prepare должна быть последовательность"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: имя типа с порядковым номером %d не является строкой"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute ожидает запрос или план"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute принимает в качестве второго аргумента последовательность"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "ошибка в SPI_execute_plan: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "ошибка в SPI_execute: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "эта подтранзакция уже начата"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "эта подтранзакция уже закончена"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "эта подтранзакция ещё не начата"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "нет подтранзакции, которую нужно закончить"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "не удалось импортировать модуль для конструктора Decimal"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "в модуле нет атрибута Decimal"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "не удалось преобразовать numeric в Decimal"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "не удалось создать байтовое представление объекта Python"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "не удалось создать строковое представление объекта Python"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid ""
+"could not convert Python object into cstring: Python string representation "
+"appears to contain null bytes"
+msgstr ""
+"не удалось преобразовать объект Python в cstring: похоже, представление "
+"строки Python содержит нулевые байты"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid ""
+"return value of function with array return type is not a Python sequence"
+msgstr ""
+"возвращаемое значение функции с результатом-массивом не является "
+"последовательностью"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr ""
+"не удалось определить длину последовательности в возвращаемом функцией "
+"значении"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid ""
+"multidimensional arrays must have array expressions with matching dimensions"
+msgstr ""
+"для многомерных массивов должны задаваться выражения с соответствующими "
+"размерностями"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "число размерностей массива превышает предел (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "ошибка в литерале записи: \"%s\""
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Отсутствует левая скобка."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid ""
+"To return a composite type in an array, return the composite type as a "
+"Python tuple, e.g., \"[('foo',)]\"."
+msgstr ""
+"Чтобы возвратить составной тип в массиве, нужно возвратить составное "
+"значение в виде кортежа Python, например: \"[('foo',)]\"."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "ключ \"%s\" не найден в сопоставлении"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid ""
+"To return null in a column, add the value None to the mapping with the key "
+"named after the column."
+msgstr ""
+"Чтобы присвоить столбцу NULL, добавьте в сопоставление значение None с "
+"ключом-именем столбца."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "длина возвращённой последовательности не равна числу столбцов в строке"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "в объекте Python не существует атрибут \"%s\""
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid ""
+"To return null in a column, let the returned object have an attribute named "
+"after column with value None."
+msgstr ""
+"Чтобы присвоить столбцу NULL, присвойте возвращаемому значению атрибут с "
+"именем столбца и значением None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "не удалось преобразовать объект Python Unicode в байты"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "не удалось извлечь байты из кодированной строки"
+
+#, c-format
+#~ msgid "wrong length of inner sequence: has length %d, but %d was expected"
+#~ msgstr "неверная длина внутренней последовательности: %d (ожидалось: %d)"
+
+#, c-format
+#~ msgid ""
+#~ "To construct a multidimensional array, the inner sequences must all have "
+#~ "the same length."
+#~ msgstr ""
+#~ "Для образования многомерного массива внутренние последовательности должны "
+#~ "иметь одинаковую длину."
+
+#, c-format
+#~ msgid "array size exceeds the maximum allowed"
+#~ msgstr "размер массива превышает предел"
+
+#~ msgid "could not create new dictionary while building trigger arguments"
+#~ msgstr "не удалось создать словарь для передачи аргументов триггера"
+
+#~ msgid "could not create globals"
+#~ msgstr "не удалось создать глобальные данные"
+
+#~ msgid "could not create exception \"%s\""
+#~ msgstr "не удалось сгенерировать исключение \"%s\""
+
+#~ msgid "could not create new dictionary"
+#~ msgstr "не удалось создать словарь"
+
+#~ msgid "plan.status takes no arguments"
+#~ msgstr "plan.status не принимает аргументы"
+
+#~ msgid "cannot convert multidimensional array to Python list"
+#~ msgstr "преобразовать многомерный массив в список Python нельзя"
+
+#~ msgid "PL/Python only supports one-dimensional arrays."
+#~ msgstr "PL/Python поддерживает только одномерные массивы."
+
+#~ msgid "could not create new Python list"
+#~ msgstr "не удалось создать список Python"
+
+#~ msgid "could not create the base SPI exceptions"
+#~ msgstr "не удалось создать базовые объекты исключений SPI"
+
+#~ msgid "the message is already specified"
+#~ msgstr "сообщение уже указано"
+
+#~ msgid "Python major version mismatch in session"
+#~ msgstr "несовпадение базовой версии Python в сеансе"
+
+#~ msgid ""
+#~ "This session has previously used Python major version %d, and it is now "
+#~ "attempting to use Python major version %d."
+#~ msgstr ""
+#~ "В данном сеансе до этого использовался Python базовой версии %d, а сейчас "
+#~ "планируется использовать Python версии %d."
+
+#~ msgid "Start a new session to use a different Python major version."
+#~ msgstr ""
+#~ "Чтобы переключиться на другую базовую версию Python, начните новый сеанс."
+
+#~ msgid "plpy.prepare does not support composite types"
+#~ msgstr "plpy.prepare не поддерживает составные типы"
+
+#~ msgid "PL/Python does not support conversion to arrays of row types."
+#~ msgstr "PL/Python не поддерживает преобразование в массивы кортежей."
+
+#~ msgid "could not initialize plpy"
+#~ msgstr "не удалось инициализировать plpy"
+
+#~ msgid "unrecognized error in PLy_spi_execute_fetch_result"
+#~ msgstr "нераспознанная ошибка в PLy_spi_execute_fetch_result"
diff --git a/src/pl/plpython/po/sv.po b/src/pl/plpython/po/sv.po
new file mode 100644
index 0000000..1b0e83c
--- /dev/null
+++ b/src/pl/plpython/po/sv.po
@@ -0,0 +1,449 @@
+# Swedish message translation file for plpython
+# Copyright (C) 2017 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Dennis Björklund <db@zigo.dhs.org>, 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-08-07 20:24+0000\n"
+"PO-Revision-Date: 2023-08-02 12:03+0200\n"
+"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n"
+"Language-Team: Swedish <pgsql-translators@postgresql.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor förväntade sig en fråga eller en plan"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor tar en sekvens som sitt andra argument"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "kunde inte exekvera plan"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Förväntade sekvens med %d argument, fick %d: %s"
+msgstr[1] "Förväntade sekvens med %d argument, fick %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "itererar med en stängd markör"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "itererar med en markör i en avbruten subtransaktion"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "hämta från en stängd markör"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "frågeresultet har för många rader för att få plats i en Python-lista"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "stänger en markör i en avbruten subtransaktion"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "ej supportat returläge för mängdfunktion"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python mängdreturnerande funktioner stöder bara ett värde per anrop."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "returnerat objekt kan inte itereras"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python mängdreturnerande funktioner måste returnera ett itererbart objekt."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "fel vid hämtning av nästa del från iteratorn"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python-procedur returnerade inte None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "PL/Python-funktion med returtyp \"void\" returnerade inte None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "oväntat returvärde från triggerprocedur"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Förväntade None eller en sträng."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "PL/Python-triggerfunktion returnerade \"MODIFY\" i en DELETE-trigger -- ignorerad"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Förväntade None, \"OK\", \"SKIP\" eller \"MODIFY\"."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() misslyckades vid uppsättning av argument"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() misslyckades vid uppsättning av argument"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "en funktion med post som värde anropades i sammanhang där poster inte kan godtagas."
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "vid skapande av returvärde"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] raderad, kan inte modifiera rad"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] är inte en dictionary"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "TD[\"new\"] dictionary-nyckel vid numerisk position %d är inte en sträng"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "nyckel \"%s\" hittad i TD[\"new\"] finns inte som en kolumn i den triggande raden"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "kan inte sätta systemattribut \"%s\""
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kan inte sätta genererad kolumn \"%s\""
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "vid modifiering av triggerrad"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "tvingar avbrytande av subtransaktion som inte har avslutats"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "multipla Python-bibliotek är aktiva i sessionen"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Bara en major-version av Python kan användas i en session."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "ej fångar fel i initiering"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "kunde inte importera \"__main__\"-modul"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "kunde inte initierar globaler"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python-procedur \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python-funktion \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python anonymt kodblock"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "kunde inte importera \"plpy\"-modul"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "kunde inte skapa modulen spiexceptions"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "kunde inte lägga till modulen spiexceptions"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "kunde inte skapa SPI-undantag"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "kunde inte packa upp argument i plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "kunde inte parsa felmeddelande i plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "argumentet 'message' angivet med namn och position"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' är ett ogiltigt nyckelordsargument för denna funktion"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "ogiltig SQLSTATE-kod"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggningsfunktioner kan bara anropas vid triggning."
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python-funktioner kan inte returnera typ %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python-funktioner kan inte ta emot typ %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "kunde inte kompilera PL/Python-funktion \"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "kunde inte kompilera anonymt PL/Python-kodblock"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "kommandot producerade inte en resultatmängd"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "andra argumentet till plpy.prepare måste vara en sekvens"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: typnamn vid numerisk position %d är inte en sträng"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute förväntade en fråga eller en plan"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute tar en sekvens som sitt andra argument"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan misslyckades: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute misslyckades: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "denna subtransaktion har redan gåtts in i"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "denna subtransaktion har redan avslutat"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "denna subtransaktion har inte gåtts in i"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "det finns ingen subtransaktion att avsluta från"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "kunde inte importera en modul för Decimal-konstruktorn"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "inga Decimal-attribut i modulen"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "konvertering från numeric till Decimal misslyckades"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "kunde inte skapa byte-representation av Python-objekt"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "kunde inte skapa strängrepresentation av Python-objekt"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "kunde inte konvertera Python-objekt till cstring: Python-strängrepresentationen verkar innehålla noll-bytes"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "returvärde för funktion med array-returtyp är inte en Python-sekvens"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "kunde inte bestämma sekvenslängd för funktionens returvärde"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "flerdimensionella vektorer måste ha array-uttryck av passande dimensioner"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "antal array-dimensioner överskriver maximalt tillåtna (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "felaktig postliteral: \"%s\""
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Saknar vänster parentes"
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "För att returnera en composite-typ i en array, returnera composite-typen som en Python-tupel, t.ex. \"[('foo',)]\"."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "nyckeln \"%s\" hittades inte i mapping"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "För att returnera null i en kolumn så lägg till värdet None till mappningen med nyckelnamn taget från kolumnen."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "längden på den returnerade sekvensen matchade inte antal kolumner i raden"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "attributet \"%s\" finns inte i Python-objektet"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "För att returnera null i en kolumn så låt det returnerade objektet ha ett attribut med namn efter kolumnen och med värdet None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "kunde inte konvertera Python-unicode-objekt till bytes"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "kunde inte extrahera bytes från kodad sträng"
diff --git a/src/pl/plpython/po/tr.po b/src/pl/plpython/po/tr.po
new file mode 100644
index 0000000..330162a
--- /dev/null
+++ b/src/pl/plpython/po/tr.po
@@ -0,0 +1,537 @@
+# LANGUAGE message translation file for plpython
+# Copyright (C) 2009 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 8.4\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-04-26 13:38+0000\n"
+"PO-Revision-Date: 2019-06-13 17:10+0300\n"
+"Last-Translator: Abdullah G. Gülner <agulner@gmail.com>\n"
+"Language-Team: Turkish <devrim@gunduz.org>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.8.7.1\n"
+
+#: plpy_cursorobject.c:78
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor bir sorgu ya da bir plan bekledi"
+
+#: plpy_cursorobject.c:161
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor bir sequence'ı ikinci argüman olarak alır"
+
+#: plpy_cursorobject.c:177 plpy_spi.c:211
+#, c-format
+msgid "could not execute plan"
+msgstr "plan çalıştırılamadı"
+
+#: plpy_cursorobject.c:180 plpy_spi.c:214
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "%d argümanının sequence'ı beklendi; %d alındı: %s"
+
+#: plpy_cursorobject.c:329
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "kapalı bir imleç (cursor) yineleniyor"
+
+#: plpy_cursorobject.c:337 plpy_cursorobject.c:403
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "iptal edilen bir alt-işlemdeki (subtransaction) bir cursor yineleniyor"
+
+#: plpy_cursorobject.c:395
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "kapalı bir cursor'dan getir"
+
+#: plpy_cursorobject.c:438 plpy_spi.c:409
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "sorgu sonucundaki satır sayısı bir Python listesine sığabilecekten çok fazla "
+
+#: plpy_cursorobject.c:490
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "iptal edilen bir alt-işlemdeki (subtransaction) bir cursor kapatılıyor"
+
+#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:143
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "desteklenmeyen küme fonksiyonu dönüş modu"
+
+#: plpy_exec.c:144
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python küme dönen fonksiyonları sadece her çağrı içinde bir değer döndürmeyi desteklerler"
+
+#: plpy_exec.c:157
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "dönen nesne yinelenemez"
+
+#: plpy_exec.c:158
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python küme dönen fonksiyonları yinelenebilir bir nesne dönmelidir."
+
+#: plpy_exec.c:172
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "yineleticiden sonraki öğeyi alırken hata"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python prosedürü None döndürmedi"
+
+#: plpy_exec.c:219
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "dönüş tipi \"void\" olan PL/Python fonksiyonu None döndürmedi"
+
+#: plpy_exec.c:375 plpy_exec.c:401
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "trigger yordamından beklenmeyen dönüş değeri"
+
+#: plpy_exec.c:376
+#, c-format
+msgid "Expected None or a string."
+msgstr "None ya da string bekleniyordu."
+
+#: plpy_exec.c:391
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "PL/Python trigger fonksiyonu DELETE triggerında \"MODIFY\" döndürdü -- gözardı edildi"
+
+#: plpy_exec.c:402
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "None, \"OK\", \"SKIP\", ya da \"MODIFY\" bekleniyordu"
+
+#: plpy_exec.c:452
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "PyList_SetItem() bağımsız değişkenler ayarlanırken başarısız oldu"
+
+#: plpy_exec.c:456
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "PyDict_SetItemString() bağımsız değişkenler ayarlanırken başarısız oldu"
+
+#: plpy_exec.c:468
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "tip kaydı içermeyen alanda çağırılan ve kayıt döndüren fonksiyon"
+
+#: plpy_exec.c:685
+#, c-format
+msgid "while creating return value"
+msgstr "dönüş değeri yaratılırken"
+
+#: plpy_exec.c:919
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] silindi, satır düzenlenemiyor"
+
+#: plpy_exec.c:924
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] bir sözlük değil"
+
+#: plpy_exec.c:951
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "%d sıra pozisyonundaki TD[\"new\"] sözlük anahtarı dizi değil"
+
+#: plpy_exec.c:958
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "TD[\"new\"] içinde bulunan \"%s\" anahtarı tetikleyen satırda bir kolon olarak bulunmuyor"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" sistem niteliği ayarlanamıyor"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "oluşturulan \"%s\" sütunu ayarlanamıyor"
+
+#: plpy_exec.c:1026
+#, c-format
+msgid "while modifying trigger row"
+msgstr "tetikleyici satırını düzenlerken"
+
+#: plpy_exec.c:1087
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "çıkış yapılmamış bir alt-işlem (subtransaction) zorla iptal ediliyor"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "oturumda birden çok Python kütüphanesi mevcut"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Bir oturumda sadece bir Python ana sürümü kullanılabilir."
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "ilklendirme aşamasında yakalanamayan hata"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "\"__main__\" modülü alınamadı"
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "global değerler ilklendirilemedi"
+
+#: plpy_main.c:399
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "\"%s\" PL/Python prosedürü"
+
+#: plpy_main.c:402
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "\"%s\" PL/Python fonksiyonu"
+
+#: plpy_main.c:410
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python anonim kod bloğu"
+
+#: plpy_plpymodule.c:186 plpy_plpymodule.c:189
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "\"plpy\" modülü alınamadı"
+
+#: plpy_plpymodule.c:204
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "spiexceptions modülü oluşturulamadı"
+
+#: plpy_plpymodule.c:212
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "spiexceptions modülü eklenemedi"
+
+#: plpy_plpymodule.c:280
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "SPI istisnaları (exception) üretilemedi"
+
+#: plpy_plpymodule.c:448
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "plpy.elog dosyasındaki argümanlar unpack edilemedi"
+
+#: plpy_plpymodule.c:457
+msgid "could not parse error message in plpy.elog"
+msgstr "plpy.elog dosyasındaki hata mesajı ayrıştırılamadı"
+
+#: plpy_plpymodule.c:474
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "ad ve konum tarafından verilen argüman 'mesajı'"
+
+#: plpy_plpymodule.c:501
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' bu fonksiyon için geçersiz bir anahtar kelime argümanıdır"
+
+#: plpy_plpymodule.c:512 plpy_plpymodule.c:518
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "geçersiz SQLSTATE kodu"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "trigger fonksiyonları sadece trigger olarak çağırılabilirler."
+
+#: plpy_procedure.c:234
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python fonksiyonları %s tipini döndüremezler"
+
+#: plpy_procedure.c:312
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python fonksiyonlar %s tipini kabul etmezler"
+
+#: plpy_procedure.c:402
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "\"%s\" PL/Python fonksiyonu derlenemedi"
+
+#: plpy_procedure.c:405
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "anonim PL/Python kod bloğu derlenemedi"
+
+#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173
+#, c-format
+msgid "command did not produce a result set"
+msgstr "komut bir sonuç kümesi üretmedi"
+
+#: plpy_spi.c:60
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "plpy.prepare'in ikinci argümanı sequence olmalıdır"
+
+#: plpy_spi.c:104
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: %d sıra posizyonundaki veri tipi dizi değil"
+
+#: plpy_spi.c:176
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute bir sorgu ya da bir plan bekledi"
+
+#: plpy_spi.c:195
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute bir sequence'ı ikinci argüman olarak alır"
+
+#: plpy_spi.c:305
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan başarısız oldu: %s"
+
+#: plpy_spi.c:347
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute başarısız oldu: %s"
+
+#: plpy_subxactobject.c:97
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "bu alt-işleme (subtransaction) zaten girilmiş"
+
+#: plpy_subxactobject.c:103 plpy_subxactobject.c:161
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "bu alt-işlemden (subtransaction) zaten çıkılmış"
+
+#: plpy_subxactobject.c:155
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "bu alt-işleme (subtransaction) girilmemiş"
+
+#: plpy_subxactobject.c:167
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "çıkılacak bir alt-işlem (subtransaction) yok"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "Decimal constructor için bir modül alınamadı"
+
+#: plpy_typeio.c:595
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "modülde Decimal niteliği yok"
+
+#: plpy_typeio.c:601
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "numeric'ten Decimal'e dönüşüm başarısız oldu"
+
+#: plpy_typeio.c:915
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "Python nesnesinin bytes gösterimi yaratılamadı"
+
+#: plpy_typeio.c:1063
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "Python nesnesinin dizgi gösterimi yaratılamadı"
+
+#: plpy_typeio.c:1074
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "Python nesnesi cstring'e dönüştürülemedi: Python dizgi gösterimi null bayt içeriyor olabilir."
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "dizi (array) boyut sayısı izin verilen en yüksek değeri (%d) aşmaktadır"
+
+#: plpy_typeio.c:1187
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "fonksiyon dönüş değeri için sequence uzunluğu belirlenemedi"
+
+#: plpy_typeio.c:1190 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "dizi (array) boyutu izin verilen en yüksek değeri aşmaktadır"
+
+#: plpy_typeio.c:1220
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "dizi dönüp tipli dönüş değeri olan fonksiyon Python sequence'ı değildir"
+
+#: plpy_typeio.c:1266
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "iç sequence'in uzunluğu yanlış: %d uzunlukta, fakat %d bekleniyordu"
+
+#: plpy_typeio.c:1268
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "Çok boyutlu bir dizi oluşturmak için, iç sequence'lerin tamamı aynı uzunlukta olmalı."
+
+#: plpy_typeio.c:1347
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "hatalı değer: \"%s\""
+
+#: plpy_typeio.c:1348
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Sol parantez eksik."
+
+#: plpy_typeio.c:1349 plpy_typeio.c:1550
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Bir bileşik türü dizi (array) içinde döndürmek için, bileşik türü bir Python tuple, e.g., \"[('foo',)]\"."
+
+#: plpy_typeio.c:1396
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "\"%s\" anahtarı planlamada bulunnamadı"
+
+#: plpy_typeio.c:1397
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Bir kolondan Null döndürmek için, kolonun ismindeki eşleşmenin anahtarına, NONE değerini ekleyin"
+
+#: plpy_typeio.c:1450
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "Dönen sequence'in uzunluğu satırdaki kolonların sayısı ile eşleşmiyor."
+
+#: plpy_typeio.c:1548
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "\"%s\" niteliği Python nesnesinde bulunmaz"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr " Bir kolondan null döndürmek için, döndürdüğünüz nesnenin, kolonun adına sahip bir özelliğinin olmasını ve bu özelliğin değerinin NONE olmasını sağlamanız gerekir"
+
+#: plpy_util.c:35
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "Python unicode nesnesi baytlara dönüştürülemedi."
+
+#: plpy_util.c:41
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "kodlanmış string den baytlar çıkarılamadı"
+
+#~ msgid "could not create new dictionary while building trigger arguments"
+#~ msgstr "trigger argümanlarını oluştururken yeni sözlük yaratılamadı"
+
+#~ msgid "could not create globals"
+#~ msgstr "evrensel değerler (globals) oluşturulamadı"
+
+#~ msgid "could not create exception \"%s\""
+#~ msgstr "\"%s\" istisnası (exception) oluşturulamadı"
+
+#~ msgid "could not create new dictionary"
+#~ msgstr "Yeni sözlük yaratılamadı"
+
+#~ msgid "PL/Python function \"%s\" could not execute plan"
+#~ msgstr "\"%s\" PL/Python fonksiyonu planı çalıştıramadı"
+
+#~ msgid "PL/Python function \"%s\" failed"
+#~ msgstr "\"%s\" PL/Python fonksiyonu başarısız oldu"
+
+#~ msgid "could not create string representation of Python object in PL/Python function \"%s\" while creating return value"
+#~ msgstr "dönüş değeri yaratılırken \"%s\" Pl/Python fonksiyonunun içindeki Python ensnesinin dizi gösterimi yaratılamadı"
+
+#~ msgid "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row"
+#~ msgstr "tetikleyici satırı düzenlerken \"%s\" PL/Python fonksiyonunun içindeki Python nesnesinin dizi gösterimi hesaplanamadı"
+
+#~ msgid "out of memory"
+#~ msgstr "yetersiz bellek"
+
+#~ msgid "PL/Python: %s"
+#~ msgstr "PL/Python: %s"
+
+#~ msgid "could not create procedure cache"
+#~ msgstr "yordam önbelleği yaratılamadı"
+
+#~ msgid "Start a new session to use a different Python major version."
+#~ msgstr "Farklı bir Python ana sürümü kullanmak için yeni bir oturum açın."
+
+#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d."
+#~ msgstr "Bu oturum daha önceden %d Python ana sürümünü kullandı, ve şimdi %d ana sürümünü kullanmayı deniyor."
+
+#~ msgid "unrecognized error in PLy_spi_execute_fetch_result"
+#~ msgstr "PLy_spi_execute_fetch_result içinde tanımlanamayan hata"
+
+#~ msgid "unrecognized error in PLy_spi_execute_query"
+#~ msgstr "PLy_spi_execute_query içinde tanımlanamayan hata"
+
+#~ msgid "unrecognized error in PLy_spi_execute_plan"
+#~ msgstr "PLy_spi_execute_plan içinde beklenmeyen hata"
+
+#~ msgid "unrecognized error in PLy_spi_prepare"
+#~ msgstr "PLy_spi_prepare içinde tanımlanamayan hata"
+
+#~ msgid "plpy.prepare does not support composite types"
+#~ msgstr "plpy.prepare kompozit tipleri desteklemez"
+
+#~ msgid "invalid arguments for plpy.prepare"
+#~ msgstr "plpy.prepare için geçersiz argümanlar"
+
+#~ msgid "transaction aborted"
+#~ msgstr "transaction iptal edildi"
+
+#~ msgid "plan.status takes no arguments"
+#~ msgstr "plan.status bir argüman almaz"
+
+#~ msgid "PL/Python only supports one-dimensional arrays."
+#~ msgstr "PL/Python sadece bir boyutlu dizileri destekler."
+
+#~ msgid "cannot convert multidimensional array to Python list"
+#~ msgstr "çok boyutlu dizi, Python listesine dönüştürülemedi"
+
+#~ msgid "PL/Python does not support conversion to arrays of row types."
+#~ msgstr "PL/Python satır tiplerinin dizilere dönüşümünü desteklemez."
+
+#~ msgid "PyCObject_FromVoidPtr() failed"
+#~ msgstr "PyCObject_FromVoidPtr() başarısız oldu"
+
+#~ msgid "PyCObject_AsVoidPtr() failed"
+#~ msgstr "PyCObject_AsVoidPtr() başarısız oldu"
diff --git a/src/pl/plpython/po/uk.po b/src/pl/plpython/po/uk.po
new file mode 100644
index 0000000..4bff31a
--- /dev/null
+++ b/src/pl/plpython/po/uk.po
@@ -0,0 +1,452 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: postgresql\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-08-17 04:25+0000\n"
+"PO-Revision-Date: 2023-08-17 15:51\n"
+"Last-Translator: \n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: postgresql\n"
+"X-Crowdin-Project-ID: 324573\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /REL_15_STABLE/plpython.pot\n"
+"X-Crowdin-File-ID: 884\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor очікував запит або план"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.сursor приймає як другий аргумент послідовність"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:205
+#, c-format
+msgid "could not execute plan"
+msgstr "не вдалося виконати план"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:208
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Очікувалась послідовність %d аргументів, отримано %d: %s"
+msgstr[1] "Очікувалась послідовність %d аргументів, отримано %d: %s"
+msgstr[2] "Очікувалась послідовність %d аргументів, отримано %d: %s"
+msgstr[3] "Очікувалась послідовність %d аргумента, отримано %d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "ітерація закритого курсора"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "ітерація курсора в перерваній субтранзакції"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "витяг з закритого курсору"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:401
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "результат запиту має забагато рядків для передачі у список Python"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "закриття курсора в перерваній транзакції"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "режим не підтримується для функцій, що повертають набір"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "функції PL/Python підтримують лише одне значення на виклик, коли повертають набір."
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "повернутий об'єкт не підтримує ітерації"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "функції PL/Python повинні повертати об'єкт з підтримкою ітерації, коли повертають набір."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "помилка отримання наступного елемента від ітератора"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "процедура PL/Python не повернула None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "PL/Python функція з типом результату \"void\" не повернули None"
+
+#: plpy_exec.c:369 plpy_exec.c:395
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "неочікуване значення процедури тригера"
+
+#: plpy_exec.c:370
+#, c-format
+msgid "Expected None or a string."
+msgstr "Очікувалось None або рядок."
+
+#: plpy_exec.c:385
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "Тригерна функція PL/Python повернула \"MODIFY\" в тригері DELETE -- проігноровано"
+
+#: plpy_exec.c:396
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Очікувалось None, \"OK\", \"SKIP\" або \"MODIFY\"."
+
+#: plpy_exec.c:446
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "помилка PyList_SetItem() під час встановлення параметрів"
+
+#: plpy_exec.c:450
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "помилка PyDict_SetItemString() під час встановлення параметрів"
+
+#: plpy_exec.c:462
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "функція, що повертає набір, викликана у контексті, що не приймає тип запис"
+
+#: plpy_exec.c:679
+#, c-format
+msgid "while creating return value"
+msgstr "під час створення значення результату"
+
+#: plpy_exec.c:926
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] видалено, неможливо змінити рядок"
+
+#: plpy_exec.c:931
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] не є словником"
+
+#: plpy_exec.c:956
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "ключ словника TD[\"new\"] на порядковий позиції %d не є рядком"
+
+#: plpy_exec.c:963
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "ключ \"%s\" знайдений у TD[\"new\"] не існує як стовпець у рядку тригера"
+
+#: plpy_exec.c:968
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "не вдалося встановити системний атрибут \"%s\""
+
+#: plpy_exec.c:973
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "неможливо оновити згенерований стовпець \"%s\""
+
+#: plpy_exec.c:1031
+#, c-format
+msgid "while modifying trigger row"
+msgstr "під час зміни рядка тригера"
+
+#: plpy_exec.c:1089
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "примусове переривання субтранзакції, яка не вийшла"
+
+#: plpy_main.c:111
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "декілька бібліотек Python присутні у сесії"
+
+#: plpy_main.c:112
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "За один сеанс може використовуватися лише одна основна версія Python."
+
+#: plpy_main.c:124
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "неопрацьована помилка під час ініціалізації"
+
+#: plpy_main.c:147
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "не вдалося імпортувати \"__main__\" модуль"
+
+#: plpy_main.c:156
+#, c-format
+msgid "could not initialize globals"
+msgstr "не вдалося ініціалізувати globals"
+
+#: plpy_main.c:354
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python процедура \"%s\""
+
+#: plpy_main.c:357
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python функція \"%s\""
+
+#: plpy_main.c:365
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "анонімні коди блоку PL/Python"
+
+#: plpy_plpymodule.c:168 plpy_plpymodule.c:171
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "не вдалося імпортувати \"plpy\" модуль"
+
+#: plpy_plpymodule.c:182
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "не вдалося створити spiexceptions модуль"
+
+#: plpy_plpymodule.c:190
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "не вдалося додати spiexceptions модуль"
+
+#: plpy_plpymodule.c:257
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "не вдається створити винятки SPI"
+
+#: plpy_plpymodule.c:425
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "не вдалося розпакувати аргументи в plpy.elog"
+
+#: plpy_plpymodule.c:434
+msgid "could not parse error message in plpy.elog"
+msgstr "не вдалося проаналізувати повідомлення про помилку в plpy.elog"
+
+#: plpy_plpymodule.c:451
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "аргумент 'повідомлення' виданий за ім'ям та розташуванням"
+
+#: plpy_plpymodule.c:478
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' є неприпустимим ключовим словом-аргументом для цієї функції"
+
+#: plpy_plpymodule.c:489 plpy_plpymodule.c:495
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "неприпустимий код SQLSTATE"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "тригер-функція може викликатися лише як тригер"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python функції не можуть повернути тип %s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python функції не можуть прийняти тип %s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "не вдалося скомпілювати функцію PL/Python \"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "не вдалося скомпілювати анонімні коди блоку PL/Python"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "команда не створила набір результатів"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "другий аргумент plpy.prepare має бути послідовністю"
+
+#: plpy_spi.c:98
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: ім'я на порядковий позиції %d не є рядком"
+
+#: plpy_spi.c:170
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute очікував запит або план"
+
+#: plpy_spi.c:189
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute приймає як другий аргумент послідовність"
+
+#: plpy_spi.c:297
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan не спрацював: %s"
+
+#: plpy_spi.c:339
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute не спрацював: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "ця субтранзакція вже почалась"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "ця субтранзакція вже вийшла"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "ця субтранзакція ще не почалася"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "немає субтранзакції, щоб з неї вийти"
+
+#: plpy_typeio.c:588
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "не вдалося імпортувати модуль для конструктора Decimal"
+
+#: plpy_typeio.c:592
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "відсутній атрибут Decimal у модулі"
+
+#: plpy_typeio.c:598
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "не вдалося виконати перетворення з numeric на Decimal"
+
+#: plpy_typeio.c:912
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "не вдалося створити байтову репрезентацію об'єкта Python"
+
+#: plpy_typeio.c:1049
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "не вдалося створити рядкову репрезентацію об'єкта Python"
+
+#: plpy_typeio.c:1060
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "не вдалося перетворити об'єкт Python на cstring: репрезентація рядка Python містить значення null-байти"
+
+#: plpy_typeio.c:1157
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "значення функції з масивом в якості результату не є послідовністю Python"
+
+#: plpy_typeio.c:1202
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "не вдалося визначити довжину послідовності для значення функція"
+
+#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253
+#, c-format
+msgid "multidimensional arrays must have array expressions with matching dimensions"
+msgstr "для багатовимірних масивів повинні задаватись вирази з відповідними вимірами"
+
+#: plpy_typeio.c:1227
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "кількість вимірів масиву перевищує максимально дозволену (%d)"
+
+#: plpy_typeio.c:1329
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "невірно сформований літерал запису: \"%s\""
+
+#: plpy_typeio.c:1330
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Відсутня ліва дужка."
+
+#: plpy_typeio.c:1331 plpy_typeio.c:1532
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "Щоб повернути складений тип в масиві, треба повернути композитний тип як кортеж Python, наприклад, \"[('foo',)]\"."
+
+#: plpy_typeio.c:1378
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "ключ \"%s\" не знайдено в зіставленні"
+
+#: plpy_typeio.c:1379
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "Для повернення значення null в стовпці, додайте значення None з ключом, що дорівнює імені стовпця."
+
+#: plpy_typeio.c:1432
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "довжина повернутої послідовності не відповідає кількості стовпців у рядку"
+
+#: plpy_typeio.c:1530
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "атрибут \"%s\" не існує в об'єкті Python"
+
+#: plpy_typeio.c:1533
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "Щоб повернути null в стовпці, результуючий об'єкт має мати атрибут з іменем стовпця зі значенням None."
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "не вдалося конвертувати об'єкт Python Unicode в байти"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "не можливо отримати байт з закодованого рядка"
+
diff --git a/src/pl/plpython/po/vi.po b/src/pl/plpython/po/vi.po
new file mode 100644
index 0000000..58e0505
--- /dev/null
+++ b/src/pl/plpython/po/vi.po
@@ -0,0 +1,485 @@
+# LANGUAGE message translation file for plpython
+# Copyright (C) 2018 PostgreSQL Global Development Group
+# This file is distributed under the same license as the plpython (PostgreSQL) package.
+# FIRST AUTHOR <kakalot49@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2018-04-22 12:08+0000\n"
+"PO-Revision-Date: 2018-05-06 22:57+0900\n"
+"Language-Team: <pgvn_translators@postgresql.vn>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 2.0.6\n"
+"Last-Translator: Dang Minh Huong <kakalot49@gmail.com>\n"
+"Language: vi_VN\n"
+
+#: plpy_cursorobject.c:101
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor kỳ vọng một câu truy vấn hoặc một plan"
+
+#: plpy_cursorobject.c:184
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor lấy một chuỗi làm đối số thứ hai"
+
+#: plpy_cursorobject.c:200 plpy_spi.c:211
+#, c-format
+msgid "could not execute plan"
+msgstr "không thể chạy plan"
+
+#: plpy_cursorobject.c:203 plpy_spi.c:214
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "Kỳ vọng chuỗi của đối số %d, đã nhận %d: %s"
+
+#: plpy_cursorobject.c:352
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "lặp lại con trỏ đã đóng"
+
+#: plpy_cursorobject.c:360 plpy_cursorobject.c:426
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "lặp lại một con trỏ trong một subtransaction đã bị hủy bỏ"
+
+#: plpy_cursorobject.c:418
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "fetch từ một con trỏ đã bị đóng"
+
+#: plpy_cursorobject.c:461 plpy_spi.c:409
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "kết quả câu truy vấn có quá nhiều hàng để vừa với một danh sách Python"
+
+#: plpy_cursorobject.c:512
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "đóng một con trỏ trong một subtransaction bị hủy bỏ"
+
+#: plpy_elog.c:127 plpy_elog.c:128 plpy_plpymodule.c:559
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:142
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "không hỗ trợ thiết lập hàm trả về mode"
+
+#: plpy_exec.c:143
+#, c-format
+msgid ""
+"PL/Python set-returning functions only support returning one value per call."
+msgstr ""
+"PL/Python hàm thiết lập-trả về chỉ hỗ trợ trả về một giá trị cho một lần gọi."
+
+#: plpy_exec.c:156
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "đối tượng trả về không thể được lặp lại"
+
+#: plpy_exec.c:157
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python hàm thiết lập-trả về phải trả về một iterable object."
+
+#: plpy_exec.c:171
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "lỗi khi fetch item tiếp theo từ iterator"
+
+#: plpy_exec.c:214
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "Thủ tục PL/Python đã không trả về None"
+
+#: plpy_exec.c:218
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "Hàm PL/Python với kiểu trả về là \"void\" đã không trả về None"
+
+#: plpy_exec.c:374 plpy_exec.c:400
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "không mong đợi giá trị trả về từ thủ tục trigger"
+
+#: plpy_exec.c:375
+#, c-format
+msgid "Expected None or a string."
+msgstr "Kỳ vọng None hoặc một chuỗi."
+
+#: plpy_exec.c:390
+#, c-format
+msgid ""
+"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr ""
+"Hàm trigger PL/Python đã trả về \"MODIFY\" trong một DELETE trigger -- bỏ qua"
+
+#: plpy_exec.c:401
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "Kỳ vọng None, \"OK\", \"SKIP\", hoặc \"MODIFY\"."
+
+#: plpy_exec.c:451
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "Lỗi PyList_SetItem(), trong khi thiết lập đối số"
+
+#: plpy_exec.c:455
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "Lỗi PyDict_SetItemString(), trong khi thiết lập đối số"
+
+#: plpy_exec.c:467
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"hàm trả về bản ghi được gọi trong ngữ cảnh không thể chấp nhận kiểu bản ghi"
+
+#: plpy_exec.c:684
+#, c-format
+msgid "while creating return value"
+msgstr "trong khi tạo ra giá trị trả về"
+
+#: plpy_exec.c:909
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] đã bị xóa, không thể sửa đổi hàng"
+
+#: plpy_exec.c:914
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"] không phải là từ điển"
+
+#: plpy_exec.c:941
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "Khóa từ điển TD[\"new\"] ở vị trí thứ tự %d không phải là chuỗi"
+
+#: plpy_exec.c:948
+#, c-format
+msgid ""
+"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering "
+"row"
+msgstr ""
+"khóa \"%s\" được tìm thấy trong TD[\"new\"] không tồn tại như là trigger mức "
+"độ hàng"
+
+#: plpy_exec.c:953
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "không thể thiết lập thuộc tính hệ thống \"%s\""
+
+#: plpy_exec.c:1011
+#, c-format
+msgid "while modifying trigger row"
+msgstr "trong khi sửa đổi trigger mức độ hàng"
+
+#: plpy_exec.c:1072
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "buộc phải hủy bỏ một subtransaction chưa được thoát"
+
+#: plpy_main.c:125
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "có nhiều thư viện Python trong một phiên"
+
+#: plpy_main.c:126
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "Chỉ có thể sử dụng một phiên bản chính của Python trong một phiên."
+
+#: plpy_main.c:142
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "lỗi chưa được bẫy trong lúc khởi tạo"
+
+#: plpy_main.c:165
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "không thể nhập mô-đun \"__main__\""
+
+#: plpy_main.c:174
+#, c-format
+msgid "could not initialize globals"
+msgstr "không thể khởi tạo biến global"
+
+#: plpy_main.c:399
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "Thủ tục PL/Python \"%s\""
+
+#: plpy_main.c:402
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "Hàm PL/Python \"%s\""
+
+#: plpy_main.c:410
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "Khối mã ẩn danh PL/Python"
+
+#: plpy_plpymodule.c:192 plpy_plpymodule.c:195
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "không thể nhập mô-đun \"plpy\""
+
+#: plpy_plpymodule.c:210
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "không thể tạo mô-đun spiexceptions"
+
+#: plpy_plpymodule.c:218
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "không thể thêm mô-đun spiexceptions"
+
+#: plpy_plpymodule.c:286
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "không thể tạo exception SPI"
+
+#: plpy_plpymodule.c:454
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "không thể giải nén đối số trong plpy.elog"
+
+#: plpy_plpymodule.c:463
+msgid "could not parse error message in plpy.elog"
+msgstr "không thể phân tích cú pháp thông điệp lỗi trong plpy.elog"
+
+#: plpy_plpymodule.c:480
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "đối số 'message' được chỉ định theo tên và vị trí"
+
+#: plpy_plpymodule.c:507
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "'%s' là đối số từ khóa không hợp lệ cho hàm này"
+
+#: plpy_plpymodule.c:518 plpy_plpymodule.c:524
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "mã SQLSTATE không hợp lệ"
+
+#: plpy_procedure.c:230
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "hàm trigger chỉ có thể được gọi như trigger"
+
+#: plpy_procedure.c:234
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "Hàm PL/Python không thể trả về kiểu %s"
+
+#: plpy_procedure.c:312
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "Các hàm PL/Python không thể chấp nhận kiểu %s"
+
+#: plpy_procedure.c:402
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "không thể biên dịch hàm PL/Python \"%s\""
+
+#: plpy_procedure.c:405
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "không thể biên dịch khối mã ẩn danh PL/Python"
+
+#: plpy_resultobject.c:150 plpy_resultobject.c:176 plpy_resultobject.c:202
+#, c-format
+msgid "command did not produce a result set"
+msgstr "lệnh không tạo ra một tập hợp kết quả"
+
+#: plpy_spi.c:60
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "đối số thứ hai của plpy.prepare phải là một chuỗi"
+
+#: plpy_spi.c:104
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: gõ tên tại vị trí thứ tự %d không phải là một chuỗi"
+
+#: plpy_spi.c:176
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute kỳ vọng một truy vấn hoặc một plan"
+
+#: plpy_spi.c:195
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute lấy một chuỗi làm đối số thứ hai"
+
+#: plpy_spi.c:305
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "SPI_execute_plan lỗi: %s"
+
+#: plpy_spi.c:347
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute lỗi: %s"
+
+#: plpy_subxactobject.c:122
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "subtransaction này đã được nhập"
+
+#: plpy_subxactobject.c:128 plpy_subxactobject.c:186
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "subtransaction này đã được thoát"
+
+#: plpy_subxactobject.c:180
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "subtransaction này chưa được nhập"
+
+#: plpy_subxactobject.c:192
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "không có subtransaction để thoát khỏi"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "không thể nhập mô-đun cho Decimal constructor"
+
+#: plpy_typeio.c:595
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "không có thuộc tính thập phân trong mô-đun"
+
+#: plpy_typeio.c:601
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "chuyển đổi từ numeric sang thập phân không thành công"
+
+#: plpy_typeio.c:908
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "không thể tạo đại diện cho của đối tượng Python"
+
+#: plpy_typeio.c:1056
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "không thể tạo ra chuỗi đại diện cho đối tượng Python"
+
+#: plpy_typeio.c:1067
+#, c-format
+msgid ""
+"could not convert Python object into cstring: Python string representation "
+"appears to contain null bytes"
+msgstr ""
+"không thể chuyển đổi đối tượng Python thành cstring: đại diện chuỗi Python "
+"chứa byte null"
+
+#: plpy_typeio.c:1176
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "số lượng hướng của mảng vượt quá số lượng tối đa cho phép (%d)"
+
+#: plpy_typeio.c:1180
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "không thể xác định độ dài chuỗi cho giá trị trả về hàm"
+
+#: plpy_typeio.c:1183 plpy_typeio.c:1187
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "kích thước mảng vượt quá mức tối đa cho phép"
+
+#: plpy_typeio.c:1213
+#, c-format
+msgid ""
+"return value of function with array return type is not a Python sequence"
+msgstr ""
+"giá trị trả về của hàm với kiểu trả về là mảng không phải là một chuỗi Python"
+
+#: plpy_typeio.c:1259
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "sai độ dài của chuỗi bên trong: có độ dài %d, nhưng %d được mong đợi"
+
+#: plpy_typeio.c:1261
+#, c-format
+msgid ""
+"To construct a multidimensional array, the inner sequences must all have the "
+"same length."
+msgstr ""
+"Để xây dựng một mảng đa chiều, các chuỗi bên trong phải có cùng độ dài."
+
+#: plpy_typeio.c:1340
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "bản ghi literal không đúng định dạng: \"%s\""
+
+#: plpy_typeio.c:1341
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "Thiếu dấu ngoặc đơn trái."
+
+#: plpy_typeio.c:1342 plpy_typeio.c:1543
+#, c-format
+msgid ""
+"To return a composite type in an array, return the composite type as a "
+"Python tuple, e.g., \"[('foo',)]\"."
+msgstr ""
+"Để trả về kiểu phức hợp trong một mảng, hãy trả về kiểu phức hợp dưới dạng "
+"một hàng Python, ví dụ: \"[('foo',)]\"."
+
+#: plpy_typeio.c:1389
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "không tìm thấy khóa \"%s\" trong ánh xạ"
+
+#: plpy_typeio.c:1390
+#, c-format
+msgid ""
+"To return null in a column, add the value None to the mapping with the key "
+"named after the column."
+msgstr ""
+"Để trả về null trong một cột, thêm giá trị None vào ánh xạ với khóa được đặt "
+"tên sau cột."
+
+#: plpy_typeio.c:1443
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "độ dài của chuỗi được trả về không khớp với số cột trong hàng"
+
+#: plpy_typeio.c:1541
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "thuộc tính \"%s\" không tồn tại trong đối tượng Python"
+
+#: plpy_typeio.c:1544
+#, c-format
+msgid ""
+"To return null in a column, let the returned object have an attribute named "
+"after column with value None."
+msgstr ""
+"Để trả về null trong một cột, hãy để đối tượng trả về có một thuộc tính được "
+"đặt tên sau cột với giá trị None."
+
+#: plpy_util.c:35
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "không thể chuyển đổi đối tượng Python Unicode thành byte"
+
+#: plpy_util.c:41
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "không thể trích xuất byte từ chuỗi đã được mã hóa"
diff --git a/src/pl/plpython/po/zh_CN.po b/src/pl/plpython/po/zh_CN.po
new file mode 100644
index 0000000..264d4f5
--- /dev/null
+++ b/src/pl/plpython/po/zh_CN.po
@@ -0,0 +1,459 @@
+# LANGUAGE message translation file for plpython
+# Copyright (C) 2010 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2010.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: plpython (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-14 05:38+0000\n"
+"PO-Revision-Date: 2021-08-16 18:00+0800\n"
+"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n"
+"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.5.7\n"
+
+#: plpy_cursorobject.c:72
+#, c-format
+msgid "plpy.cursor expected a query or a plan"
+msgstr "plpy.cursor期望一个查询或一个计划"
+
+#: plpy_cursorobject.c:155
+#, c-format
+msgid "plpy.cursor takes a sequence as its second argument"
+msgstr "plpy.cursor将一个序列作为它的第二个参数"
+
+#: plpy_cursorobject.c:171 plpy_spi.c:207
+#, c-format
+msgid "could not execute plan"
+msgstr "无法执行计划"
+
+#: plpy_cursorobject.c:174 plpy_spi.c:210
+#, c-format
+msgid "Expected sequence of %d argument, got %d: %s"
+msgid_plural "Expected sequence of %d arguments, got %d: %s"
+msgstr[0] "期望%d序列参数,但是得到%d: %s"
+
+#: plpy_cursorobject.c:321
+#, c-format
+msgid "iterating a closed cursor"
+msgstr "遍历一个关闭的游标"
+
+#: plpy_cursorobject.c:329 plpy_cursorobject.c:395
+#, c-format
+msgid "iterating a cursor in an aborted subtransaction"
+msgstr "在终止的子事务里遍历一个游标"
+
+#: plpy_cursorobject.c:387
+#, c-format
+msgid "fetch from a closed cursor"
+msgstr "从关闭的游标里获取结果"
+
+#: plpy_cursorobject.c:430 plpy_spi.c:403
+#, c-format
+msgid "query result has too many rows to fit in a Python list"
+msgstr "查询结果中的行太多,无法放在一个Python列表中"
+
+#: plpy_cursorobject.c:482
+#, c-format
+msgid "closing a cursor in an aborted subtransaction"
+msgstr "在终止的子事务里关闭一个游标"
+
+#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:548
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: plpy_exec.c:139
+#, c-format
+msgid "unsupported set function return mode"
+msgstr "不支持集合函数返回模式"
+
+#: plpy_exec.c:140
+#, c-format
+msgid "PL/Python set-returning functions only support returning one value per call."
+msgstr "PL/Python集合返回函数只支持在每次调用时返回一个值。"
+
+#: plpy_exec.c:153
+#, c-format
+msgid "returned object cannot be iterated"
+msgstr "所返回的对象无法迭代"
+
+#: plpy_exec.c:154
+#, c-format
+msgid "PL/Python set-returning functions must return an iterable object."
+msgstr "PL/Python集合返回函数必须返回一个可迭代的对象."
+
+#: plpy_exec.c:168
+#, c-format
+msgid "error fetching next item from iterator"
+msgstr "当从迭代器中取回下一个成员时出现错误"
+
+#: plpy_exec.c:211
+#, c-format
+msgid "PL/Python procedure did not return None"
+msgstr "PL/Python过程没有返回None"
+
+#: plpy_exec.c:215
+#, c-format
+msgid "PL/Python function with return type \"void\" did not return None"
+msgstr "返回类型为\"void\"的PL/Python函数不返回None"
+
+#: plpy_exec.c:371 plpy_exec.c:397
+#, c-format
+msgid "unexpected return value from trigger procedure"
+msgstr "在触发器存储过程出现非期望的返回值"
+
+#: plpy_exec.c:372
+#, c-format
+msgid "Expected None or a string."
+msgstr "期望空值或一个字符串"
+
+#: plpy_exec.c:387
+#, c-format
+msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"
+msgstr "在DELETE触发器中的PL/Python 触发器函数返回 \"MODIFY\" -- 忽略"
+
+#: plpy_exec.c:398
+#, c-format
+msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."
+msgstr "期望None, \"OK\", \"SKIP\", 或\"MODIFY\""
+
+#: plpy_exec.c:443
+#, c-format
+msgid "PyList_SetItem() failed, while setting up arguments"
+msgstr "当设置参数的同时, 执行PyList_SetItem()失败"
+
+#: plpy_exec.c:447
+#, c-format
+msgid "PyDict_SetItemString() failed, while setting up arguments"
+msgstr "当设置参数的同时, 执行PyDict_SetItemString()失败"
+
+#: plpy_exec.c:459
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "返回值类型是记录的函数在不接受使用记录类型的环境中调用"
+
+#: plpy_exec.c:676
+#, c-format
+msgid "while creating return value"
+msgstr "同时在创建返回值"
+
+#: plpy_exec.c:910
+#, c-format
+msgid "TD[\"new\"] deleted, cannot modify row"
+msgstr "TD[\"new\"] 已删除,无法修改记录"
+
+#: plpy_exec.c:915
+#, c-format
+msgid "TD[\"new\"] is not a dictionary"
+msgstr "TD[\"new\"]不是一个字典"
+
+#: plpy_exec.c:942
+#, c-format
+msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string"
+msgstr "在顺序位置%d的TD[\"new\"]字典键值不是字符串"
+
+#: plpy_exec.c:949
+#, c-format
+msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row"
+msgstr "在 TD[\"new\"]中找到的键 \"%s\"在正在触发的记录中不是作为列而存在."
+
+#: plpy_exec.c:954
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "不能设置系统属性\"%s\""
+
+#: plpy_exec.c:959
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "无法设置生成的列 \"%s\""
+
+#: plpy_exec.c:1017
+#, c-format
+msgid "while modifying trigger row"
+msgstr "同时正在修改触发器记录"
+
+#: plpy_exec.c:1075
+#, c-format
+msgid "forcibly aborting a subtransaction that has not been exited"
+msgstr "强行终止一个还未退出的子事务"
+
+#: plpy_main.c:121
+#, c-format
+msgid "multiple Python libraries are present in session"
+msgstr "会话中存在多个Python库"
+
+#: plpy_main.c:122
+#, c-format
+msgid "Only one Python major version can be used in one session."
+msgstr "一个会话中只能使用一个Python主版本."
+
+#: plpy_main.c:138
+#, c-format
+msgid "untrapped error in initialization"
+msgstr "在初始化过程中出现无法捕获的错误"
+
+#: plpy_main.c:161
+#, c-format
+msgid "could not import \"__main__\" module"
+msgstr "无法导入模块\"__main__\" "
+
+#: plpy_main.c:170
+#, c-format
+msgid "could not initialize globals"
+msgstr "无法初始化全局变量"
+
+#: plpy_main.c:393
+#, c-format
+msgid "PL/Python procedure \"%s\""
+msgstr "PL/Python过程\"%s\""
+
+#: plpy_main.c:396
+#, c-format
+msgid "PL/Python function \"%s\""
+msgstr "PL/Python函数\"%s\""
+
+#: plpy_main.c:404
+#, c-format
+msgid "PL/Python anonymous code block"
+msgstr "PL/Python匿名代码块"
+
+#: plpy_plpymodule.c:182 plpy_plpymodule.c:185
+#, c-format
+msgid "could not import \"plpy\" module"
+msgstr "无法导入模块\"plpy\" "
+
+#: plpy_plpymodule.c:200
+#, c-format
+msgid "could not create the spiexceptions module"
+msgstr "无法创建spiexceptions模块"
+
+#: plpy_plpymodule.c:208
+#, c-format
+msgid "could not add the spiexceptions module"
+msgstr "无法添加spiexceptions模块 "
+
+#: plpy_plpymodule.c:275
+#, c-format
+msgid "could not generate SPI exceptions"
+msgstr "无法产生SPI异常"
+
+#: plpy_plpymodule.c:443
+#, c-format
+msgid "could not unpack arguments in plpy.elog"
+msgstr "无法解析plpy.elog中的参数"
+
+#: plpy_plpymodule.c:452
+msgid "could not parse error message in plpy.elog"
+msgstr "无法解析在plpy.elog中的错误消息"
+
+#: plpy_plpymodule.c:469
+#, c-format
+msgid "argument 'message' given by name and position"
+msgstr "由名称和位置提供的参数'message'"
+
+#: plpy_plpymodule.c:496
+#, c-format
+msgid "'%s' is an invalid keyword argument for this function"
+msgstr "对于这个函数,'%s'是一个无效的关键词参数"
+
+#: plpy_plpymodule.c:507 plpy_plpymodule.c:513
+#, c-format
+msgid "invalid SQLSTATE code"
+msgstr "无效的SQLSTATE代码"
+
+#: plpy_procedure.c:225
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "触发器函数只能以触发器的形式调用"
+
+#: plpy_procedure.c:229
+#, c-format
+msgid "PL/Python functions cannot return type %s"
+msgstr "PL/Python函数不能返回类型%s"
+
+#: plpy_procedure.c:307
+#, c-format
+msgid "PL/Python functions cannot accept type %s"
+msgstr "PL/Python函数不能接受类型%s"
+
+#: plpy_procedure.c:397
+#, c-format
+msgid "could not compile PL/Python function \"%s\""
+msgstr "无法编译PL/Python函数\"%s\""
+
+#: plpy_procedure.c:400
+#, c-format
+msgid "could not compile anonymous PL/Python code block"
+msgstr "无法编译PL/Python中的匿名代码块"
+
+#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169
+#, c-format
+msgid "command did not produce a result set"
+msgstr "命令没有产生结果集"
+
+#: plpy_spi.c:56
+#, c-format
+msgid "second argument of plpy.prepare must be a sequence"
+msgstr "plpy.prepare的第二个参数必须是一个序列"
+
+#: plpy_spi.c:100
+#, c-format
+msgid "plpy.prepare: type name at ordinal position %d is not a string"
+msgstr "plpy.prepare: 在顺序位置%d的类型名称不是string"
+
+#: plpy_spi.c:172
+#, c-format
+msgid "plpy.execute expected a query or a plan"
+msgstr "plpy.execute期望一个查询或一个计划"
+
+#: plpy_spi.c:191
+#, c-format
+msgid "plpy.execute takes a sequence as its second argument"
+msgstr "plpy.execute将一个序列作为它的第二个参数"
+
+#: plpy_spi.c:299
+#, c-format
+msgid "SPI_execute_plan failed: %s"
+msgstr "执行SPI_execute_plan失败: %s"
+
+#: plpy_spi.c:341
+#, c-format
+msgid "SPI_execute failed: %s"
+msgstr "SPI_execute执行失败: %s"
+
+#: plpy_subxactobject.c:92
+#, c-format
+msgid "this subtransaction has already been entered"
+msgstr "已经进入该子事务"
+
+#: plpy_subxactobject.c:98 plpy_subxactobject.c:156
+#, c-format
+msgid "this subtransaction has already been exited"
+msgstr "已经退出该子事务"
+
+#: plpy_subxactobject.c:150
+#, c-format
+msgid "this subtransaction has not been entered"
+msgstr "该子事务仍没有进入"
+
+#: plpy_subxactobject.c:162
+#, c-format
+msgid "there is no subtransaction to exit from"
+msgstr "没有子事务可以退出"
+
+#: plpy_typeio.c:587
+#, c-format
+msgid "could not import a module for Decimal constructor"
+msgstr "无法为十进制构造函数导入模块"
+
+#: plpy_typeio.c:591
+#, c-format
+msgid "no Decimal attribute in module"
+msgstr "模块中没有小数位属性"
+
+#: plpy_typeio.c:597
+#, c-format
+msgid "conversion from numeric to Decimal failed"
+msgstr "由numeric数值到Decimal小数转换失败"
+
+#: plpy_typeio.c:911
+#, c-format
+msgid "could not create bytes representation of Python object"
+msgstr "无法创建Python对象的字节表达式"
+
+#: plpy_typeio.c:1056
+#, c-format
+msgid "could not create string representation of Python object"
+msgstr "无法创建Python对象的字符串表达式"
+
+#: plpy_typeio.c:1067
+#, c-format
+msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes"
+msgstr "无法将Python对象转换为cstring: Python字符串表达式可能包含空字节"
+
+#: plpy_typeio.c:1178
+#, c-format
+msgid "number of array dimensions exceeds the maximum allowed (%d)"
+msgstr "数组的维数超过最大允许值(%d)"
+
+#: plpy_typeio.c:1183
+#, c-format
+msgid "could not determine sequence length for function return value"
+msgstr "无法确定函数返回值的序列长度"
+
+#: plpy_typeio.c:1188 plpy_typeio.c:1194
+#, c-format
+msgid "array size exceeds the maximum allowed"
+msgstr "数组的大小超过了最大允许值"
+
+#: plpy_typeio.c:1222
+#, c-format
+msgid "return value of function with array return type is not a Python sequence"
+msgstr "带有数组返回类型的函数返回值不是一个Python序列"
+
+#: plpy_typeio.c:1269
+#, c-format
+msgid "wrong length of inner sequence: has length %d, but %d was expected"
+msgstr "内部序列的长度错误:长度为%d,但应为%d"
+
+#: plpy_typeio.c:1271
+#, c-format
+msgid "To construct a multidimensional array, the inner sequences must all have the same length."
+msgstr "要构造多维数组,内部序列的长度必须相同."
+
+#: plpy_typeio.c:1350
+#, c-format
+msgid "malformed record literal: \"%s\""
+msgstr "有缺陷的记录常量: \"%s\""
+
+#: plpy_typeio.c:1351
+#, c-format
+msgid "Missing left parenthesis."
+msgstr "缺少一个左括弧"
+
+#: plpy_typeio.c:1352 plpy_typeio.c:1553
+#, c-format
+msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."
+msgstr "要返回数组中的复合类型,请将复合类型作为Python元组返回,例如 \"[('foo',)]\"."
+
+#: plpy_typeio.c:1399
+#, c-format
+msgid "key \"%s\" not found in mapping"
+msgstr "在映射中没有找到键\"%s\""
+
+#: plpy_typeio.c:1400
+#, c-format
+msgid "To return null in a column, add the value None to the mapping with the key named after the column."
+msgstr "为了在一列中返回空值, 需要在列的后面对带有已命名键的映射添加值None"
+
+#: plpy_typeio.c:1453
+#, c-format
+msgid "length of returned sequence did not match number of columns in row"
+msgstr "所返回序列的长度与在记录中列的数量不匹配"
+
+#: plpy_typeio.c:1551
+#, c-format
+msgid "attribute \"%s\" does not exist in Python object"
+msgstr "在Python对象中不存在属性\"%s\""
+
+#: plpy_typeio.c:1554
+#, c-format
+msgid "To return null in a column, let the returned object have an attribute named after column with value None."
+msgstr "为了在一列中返回空值, 需要让返回的对象在带有值None的列后面的带有已命名属性"
+
+#: plpy_util.c:31
+#, c-format
+msgid "could not convert Python Unicode object to bytes"
+msgstr "无法将Python中以Unicode编码的对象转换为PostgreSQL服务器字节码"
+
+#: plpy_util.c:37
+#, c-format
+msgid "could not extract bytes from encoded string"
+msgstr "无法从已编码字符串里提取相应字节码值"
diff --git a/src/pl/plpython/spiexceptions.h b/src/pl/plpython/spiexceptions.h
new file mode 100644
index 0000000..999e6bb
--- /dev/null
+++ b/src/pl/plpython/spiexceptions.h
@@ -0,0 +1,998 @@
+/* autogenerated from src/backend/utils/errcodes.txt, do not edit */
+/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */
+
+{
+ "spiexceptions.SqlStatementNotYetComplete", "SqlStatementNotYetComplete", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE
+},
+
+{
+ "spiexceptions.ConnectionException", "ConnectionException", ERRCODE_CONNECTION_EXCEPTION
+},
+
+{
+ "spiexceptions.ConnectionDoesNotExist", "ConnectionDoesNotExist", ERRCODE_CONNECTION_DOES_NOT_EXIST
+},
+
+{
+ "spiexceptions.ConnectionFailure", "ConnectionFailure", ERRCODE_CONNECTION_FAILURE
+},
+
+{
+ "spiexceptions.SqlclientUnableToEstablishSqlconnection", "SqlclientUnableToEstablishSqlconnection", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION
+},
+
+{
+ "spiexceptions.SqlserverRejectedEstablishmentOfSqlconnection", "SqlserverRejectedEstablishmentOfSqlconnection", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
+},
+
+{
+ "spiexceptions.TransactionResolutionUnknown", "TransactionResolutionUnknown", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN
+},
+
+{
+ "spiexceptions.ProtocolViolation", "ProtocolViolation", ERRCODE_PROTOCOL_VIOLATION
+},
+
+{
+ "spiexceptions.TriggeredActionException", "TriggeredActionException", ERRCODE_TRIGGERED_ACTION_EXCEPTION
+},
+
+{
+ "spiexceptions.FeatureNotSupported", "FeatureNotSupported", ERRCODE_FEATURE_NOT_SUPPORTED
+},
+
+{
+ "spiexceptions.InvalidTransactionInitiation", "InvalidTransactionInitiation", ERRCODE_INVALID_TRANSACTION_INITIATION
+},
+
+{
+ "spiexceptions.LocatorException", "LocatorException", ERRCODE_LOCATOR_EXCEPTION
+},
+
+{
+ "spiexceptions.InvalidLocatorSpecification", "InvalidLocatorSpecification", ERRCODE_L_E_INVALID_SPECIFICATION
+},
+
+{
+ "spiexceptions.InvalidGrantor", "InvalidGrantor", ERRCODE_INVALID_GRANTOR
+},
+
+{
+ "spiexceptions.InvalidGrantOperation", "InvalidGrantOperation", ERRCODE_INVALID_GRANT_OPERATION
+},
+
+{
+ "spiexceptions.InvalidRoleSpecification", "InvalidRoleSpecification", ERRCODE_INVALID_ROLE_SPECIFICATION
+},
+
+{
+ "spiexceptions.DiagnosticsException", "DiagnosticsException", ERRCODE_DIAGNOSTICS_EXCEPTION
+},
+
+{
+ "spiexceptions.StackedDiagnosticsAccessedWithoutActiveHandler", "StackedDiagnosticsAccessedWithoutActiveHandler", ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER
+},
+
+{
+ "spiexceptions.CaseNotFound", "CaseNotFound", ERRCODE_CASE_NOT_FOUND
+},
+
+{
+ "spiexceptions.CardinalityViolation", "CardinalityViolation", ERRCODE_CARDINALITY_VIOLATION
+},
+
+{
+ "spiexceptions.DataException", "DataException", ERRCODE_DATA_EXCEPTION
+},
+
+{
+ "spiexceptions.ArraySubscriptError", "ArraySubscriptError", ERRCODE_ARRAY_SUBSCRIPT_ERROR
+},
+
+{
+ "spiexceptions.CharacterNotInRepertoire", "CharacterNotInRepertoire", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE
+},
+
+{
+ "spiexceptions.DatetimeFieldOverflow", "DatetimeFieldOverflow", ERRCODE_DATETIME_FIELD_OVERFLOW
+},
+
+{
+ "spiexceptions.DivisionByZero", "DivisionByZero", ERRCODE_DIVISION_BY_ZERO
+},
+
+{
+ "spiexceptions.ErrorInAssignment", "ErrorInAssignment", ERRCODE_ERROR_IN_ASSIGNMENT
+},
+
+{
+ "spiexceptions.EscapeCharacterConflict", "EscapeCharacterConflict", ERRCODE_ESCAPE_CHARACTER_CONFLICT
+},
+
+{
+ "spiexceptions.IndicatorOverflow", "IndicatorOverflow", ERRCODE_INDICATOR_OVERFLOW
+},
+
+{
+ "spiexceptions.IntervalFieldOverflow", "IntervalFieldOverflow", ERRCODE_INTERVAL_FIELD_OVERFLOW
+},
+
+{
+ "spiexceptions.InvalidArgumentForLogarithm", "InvalidArgumentForLogarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG
+},
+
+{
+ "spiexceptions.InvalidArgumentForNtileFunction", "InvalidArgumentForNtileFunction", ERRCODE_INVALID_ARGUMENT_FOR_NTILE
+},
+
+{
+ "spiexceptions.InvalidArgumentForNthValueFunction", "InvalidArgumentForNthValueFunction", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE
+},
+
+{
+ "spiexceptions.InvalidArgumentForPowerFunction", "InvalidArgumentForPowerFunction", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION
+},
+
+{
+ "spiexceptions.InvalidArgumentForWidthBucketFunction", "InvalidArgumentForWidthBucketFunction", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION
+},
+
+{
+ "spiexceptions.InvalidCharacterValueForCast", "InvalidCharacterValueForCast", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST
+},
+
+{
+ "spiexceptions.InvalidDatetimeFormat", "InvalidDatetimeFormat", ERRCODE_INVALID_DATETIME_FORMAT
+},
+
+{
+ "spiexceptions.InvalidEscapeCharacter", "InvalidEscapeCharacter", ERRCODE_INVALID_ESCAPE_CHARACTER
+},
+
+{
+ "spiexceptions.InvalidEscapeOctet", "InvalidEscapeOctet", ERRCODE_INVALID_ESCAPE_OCTET
+},
+
+{
+ "spiexceptions.InvalidEscapeSequence", "InvalidEscapeSequence", ERRCODE_INVALID_ESCAPE_SEQUENCE
+},
+
+{
+ "spiexceptions.NonstandardUseOfEscapeCharacter", "NonstandardUseOfEscapeCharacter", ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "spiexceptions.InvalidIndicatorParameterValue", "InvalidIndicatorParameterValue", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE
+},
+
+{
+ "spiexceptions.InvalidParameterValue", "InvalidParameterValue", ERRCODE_INVALID_PARAMETER_VALUE
+},
+
+{
+ "spiexceptions.InvalidPrecedingOrFollowingSize", "InvalidPrecedingOrFollowingSize", ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE
+},
+
+{
+ "spiexceptions.InvalidRegularExpression", "InvalidRegularExpression", ERRCODE_INVALID_REGULAR_EXPRESSION
+},
+
+{
+ "spiexceptions.InvalidRowCountInLimitClause", "InvalidRowCountInLimitClause", ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE
+},
+
+{
+ "spiexceptions.InvalidRowCountInResultOffsetClause", "InvalidRowCountInResultOffsetClause", ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE
+},
+
+{
+ "spiexceptions.InvalidTablesampleArgument", "InvalidTablesampleArgument", ERRCODE_INVALID_TABLESAMPLE_ARGUMENT
+},
+
+{
+ "spiexceptions.InvalidTablesampleRepeat", "InvalidTablesampleRepeat", ERRCODE_INVALID_TABLESAMPLE_REPEAT
+},
+
+{
+ "spiexceptions.InvalidTimeZoneDisplacementValue", "InvalidTimeZoneDisplacementValue", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE
+},
+
+{
+ "spiexceptions.InvalidUseOfEscapeCharacter", "InvalidUseOfEscapeCharacter", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "spiexceptions.MostSpecificTypeMismatch", "MostSpecificTypeMismatch", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH
+},
+
+{
+ "spiexceptions.NullValueNotAllowed", "NullValueNotAllowed", ERRCODE_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "spiexceptions.NullValueNoIndicatorParameter", "NullValueNoIndicatorParameter", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER
+},
+
+{
+ "spiexceptions.NumericValueOutOfRange", "NumericValueOutOfRange", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE
+},
+
+{
+ "spiexceptions.SequenceGeneratorLimitExceeded", "SequenceGeneratorLimitExceeded", ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED
+},
+
+{
+ "spiexceptions.StringDataLengthMismatch", "StringDataLengthMismatch", ERRCODE_STRING_DATA_LENGTH_MISMATCH
+},
+
+{
+ "spiexceptions.StringDataRightTruncation", "StringDataRightTruncation", ERRCODE_STRING_DATA_RIGHT_TRUNCATION
+},
+
+{
+ "spiexceptions.SubstringError", "SubstringError", ERRCODE_SUBSTRING_ERROR
+},
+
+{
+ "spiexceptions.TrimError", "TrimError", ERRCODE_TRIM_ERROR
+},
+
+{
+ "spiexceptions.UnterminatedCString", "UnterminatedCString", ERRCODE_UNTERMINATED_C_STRING
+},
+
+{
+ "spiexceptions.ZeroLengthCharacterString", "ZeroLengthCharacterString", ERRCODE_ZERO_LENGTH_CHARACTER_STRING
+},
+
+{
+ "spiexceptions.FloatingPointException", "FloatingPointException", ERRCODE_FLOATING_POINT_EXCEPTION
+},
+
+{
+ "spiexceptions.InvalidTextRepresentation", "InvalidTextRepresentation", ERRCODE_INVALID_TEXT_REPRESENTATION
+},
+
+{
+ "spiexceptions.InvalidBinaryRepresentation", "InvalidBinaryRepresentation", ERRCODE_INVALID_BINARY_REPRESENTATION
+},
+
+{
+ "spiexceptions.BadCopyFileFormat", "BadCopyFileFormat", ERRCODE_BAD_COPY_FILE_FORMAT
+},
+
+{
+ "spiexceptions.UntranslatableCharacter", "UntranslatableCharacter", ERRCODE_UNTRANSLATABLE_CHARACTER
+},
+
+{
+ "spiexceptions.NotAnXmlDocument", "NotAnXmlDocument", ERRCODE_NOT_AN_XML_DOCUMENT
+},
+
+{
+ "spiexceptions.InvalidXmlDocument", "InvalidXmlDocument", ERRCODE_INVALID_XML_DOCUMENT
+},
+
+{
+ "spiexceptions.InvalidXmlContent", "InvalidXmlContent", ERRCODE_INVALID_XML_CONTENT
+},
+
+{
+ "spiexceptions.InvalidXmlComment", "InvalidXmlComment", ERRCODE_INVALID_XML_COMMENT
+},
+
+{
+ "spiexceptions.InvalidXmlProcessingInstruction", "InvalidXmlProcessingInstruction", ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION
+},
+
+{
+ "spiexceptions.DuplicateJsonObjectKeyValue", "DuplicateJsonObjectKeyValue", ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE
+},
+
+{
+ "spiexceptions.InvalidArgumentForSqlJsonDatetimeFunction", "InvalidArgumentForSqlJsonDatetimeFunction", ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION
+},
+
+{
+ "spiexceptions.InvalidJsonText", "InvalidJsonText", ERRCODE_INVALID_JSON_TEXT
+},
+
+{
+ "spiexceptions.InvalidSqlJsonSubscript", "InvalidSqlJsonSubscript", ERRCODE_INVALID_SQL_JSON_SUBSCRIPT
+},
+
+{
+ "spiexceptions.MoreThanOneSqlJsonItem", "MoreThanOneSqlJsonItem", ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM
+},
+
+{
+ "spiexceptions.NoSqlJsonItem", "NoSqlJsonItem", ERRCODE_NO_SQL_JSON_ITEM
+},
+
+{
+ "spiexceptions.NonNumericSqlJsonItem", "NonNumericSqlJsonItem", ERRCODE_NON_NUMERIC_SQL_JSON_ITEM
+},
+
+{
+ "spiexceptions.NonUniqueKeysInAJsonObject", "NonUniqueKeysInAJsonObject", ERRCODE_NON_UNIQUE_KEYS_IN_A_JSON_OBJECT
+},
+
+{
+ "spiexceptions.SingletonSqlJsonItemRequired", "SingletonSqlJsonItemRequired", ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED
+},
+
+{
+ "spiexceptions.SqlJsonArrayNotFound", "SqlJsonArrayNotFound", ERRCODE_SQL_JSON_ARRAY_NOT_FOUND
+},
+
+{
+ "spiexceptions.SqlJsonMemberNotFound", "SqlJsonMemberNotFound", ERRCODE_SQL_JSON_MEMBER_NOT_FOUND
+},
+
+{
+ "spiexceptions.SqlJsonNumberNotFound", "SqlJsonNumberNotFound", ERRCODE_SQL_JSON_NUMBER_NOT_FOUND
+},
+
+{
+ "spiexceptions.SqlJsonObjectNotFound", "SqlJsonObjectNotFound", ERRCODE_SQL_JSON_OBJECT_NOT_FOUND
+},
+
+{
+ "spiexceptions.TooManyJsonArrayElements", "TooManyJsonArrayElements", ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS
+},
+
+{
+ "spiexceptions.TooManyJsonObjectMembers", "TooManyJsonObjectMembers", ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS
+},
+
+{
+ "spiexceptions.SqlJsonScalarRequired", "SqlJsonScalarRequired", ERRCODE_SQL_JSON_SCALAR_REQUIRED
+},
+
+{
+ "spiexceptions.SqlJsonItemCannotBeCastToTargetType", "SqlJsonItemCannotBeCastToTargetType", ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE
+},
+
+{
+ "spiexceptions.IntegrityConstraintViolation", "IntegrityConstraintViolation", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "spiexceptions.RestrictViolation", "RestrictViolation", ERRCODE_RESTRICT_VIOLATION
+},
+
+{
+ "spiexceptions.NotNullViolation", "NotNullViolation", ERRCODE_NOT_NULL_VIOLATION
+},
+
+{
+ "spiexceptions.ForeignKeyViolation", "ForeignKeyViolation", ERRCODE_FOREIGN_KEY_VIOLATION
+},
+
+{
+ "spiexceptions.UniqueViolation", "UniqueViolation", ERRCODE_UNIQUE_VIOLATION
+},
+
+{
+ "spiexceptions.CheckViolation", "CheckViolation", ERRCODE_CHECK_VIOLATION
+},
+
+{
+ "spiexceptions.ExclusionViolation", "ExclusionViolation", ERRCODE_EXCLUSION_VIOLATION
+},
+
+{
+ "spiexceptions.InvalidCursorState", "InvalidCursorState", ERRCODE_INVALID_CURSOR_STATE
+},
+
+{
+ "spiexceptions.InvalidTransactionState", "InvalidTransactionState", ERRCODE_INVALID_TRANSACTION_STATE
+},
+
+{
+ "spiexceptions.ActiveSqlTransaction", "ActiveSqlTransaction", ERRCODE_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "spiexceptions.BranchTransactionAlreadyActive", "BranchTransactionAlreadyActive", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE
+},
+
+{
+ "spiexceptions.HeldCursorRequiresSameIsolationLevel", "HeldCursorRequiresSameIsolationLevel", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL
+},
+
+{
+ "spiexceptions.InappropriateAccessModeForBranchTransaction", "InappropriateAccessModeForBranchTransaction", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "spiexceptions.InappropriateIsolationLevelForBranchTransaction", "InappropriateIsolationLevelForBranchTransaction", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "spiexceptions.NoActiveSqlTransactionForBranchTransaction", "NoActiveSqlTransactionForBranchTransaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "spiexceptions.ReadOnlySqlTransaction", "ReadOnlySqlTransaction", ERRCODE_READ_ONLY_SQL_TRANSACTION
+},
+
+{
+ "spiexceptions.SchemaAndDataStatementMixingNotSupported", "SchemaAndDataStatementMixingNotSupported", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED
+},
+
+{
+ "spiexceptions.NoActiveSqlTransaction", "NoActiveSqlTransaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "spiexceptions.InFailedSqlTransaction", "InFailedSqlTransaction", ERRCODE_IN_FAILED_SQL_TRANSACTION
+},
+
+{
+ "spiexceptions.IdleInTransactionSessionTimeout", "IdleInTransactionSessionTimeout", ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT
+},
+
+{
+ "spiexceptions.InvalidSqlStatementName", "InvalidSqlStatementName", ERRCODE_INVALID_SQL_STATEMENT_NAME
+},
+
+{
+ "spiexceptions.TriggeredDataChangeViolation", "TriggeredDataChangeViolation", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION
+},
+
+{
+ "spiexceptions.InvalidAuthorizationSpecification", "InvalidAuthorizationSpecification", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION
+},
+
+{
+ "spiexceptions.InvalidPassword", "InvalidPassword", ERRCODE_INVALID_PASSWORD
+},
+
+{
+ "spiexceptions.DependentPrivilegeDescriptorsStillExist", "DependentPrivilegeDescriptorsStillExist", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST
+},
+
+{
+ "spiexceptions.DependentObjectsStillExist", "DependentObjectsStillExist", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST
+},
+
+{
+ "spiexceptions.InvalidTransactionTermination", "InvalidTransactionTermination", ERRCODE_INVALID_TRANSACTION_TERMINATION
+},
+
+{
+ "spiexceptions.SqlRoutineException", "SqlRoutineException", ERRCODE_SQL_ROUTINE_EXCEPTION
+},
+
+{
+ "spiexceptions.FunctionExecutedNoReturnStatement", "FunctionExecutedNoReturnStatement", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT
+},
+
+{
+ "spiexceptions.ModifyingSqlDataNotPermitted", "ModifyingSqlDataNotPermitted", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "spiexceptions.ProhibitedSqlStatementAttempted", "ProhibitedSqlStatementAttempted", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "spiexceptions.ReadingSqlDataNotPermitted", "ReadingSqlDataNotPermitted", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "spiexceptions.InvalidCursorName", "InvalidCursorName", ERRCODE_INVALID_CURSOR_NAME
+},
+
+{
+ "spiexceptions.ExternalRoutineException", "ExternalRoutineException", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION
+},
+
+{
+ "spiexceptions.ContainingSqlNotPermitted", "ContainingSqlNotPermitted", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED
+},
+
+{
+ "spiexceptions.ModifyingSqlDataNotPermitted", "ModifyingSqlDataNotPermitted", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "spiexceptions.ProhibitedSqlStatementAttempted", "ProhibitedSqlStatementAttempted", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "spiexceptions.ReadingSqlDataNotPermitted", "ReadingSqlDataNotPermitted", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "spiexceptions.ExternalRoutineInvocationException", "ExternalRoutineInvocationException", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION
+},
+
+{
+ "spiexceptions.InvalidSqlstateReturned", "InvalidSqlstateReturned", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED
+},
+
+{
+ "spiexceptions.NullValueNotAllowed", "NullValueNotAllowed", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "spiexceptions.TriggerProtocolViolated", "TriggerProtocolViolated", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "spiexceptions.SrfProtocolViolated", "SrfProtocolViolated", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED
+},
+
+{
+ "spiexceptions.EventTriggerProtocolViolated", "EventTriggerProtocolViolated", ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "spiexceptions.SavepointException", "SavepointException", ERRCODE_SAVEPOINT_EXCEPTION
+},
+
+{
+ "spiexceptions.InvalidSavepointSpecification", "InvalidSavepointSpecification", ERRCODE_S_E_INVALID_SPECIFICATION
+},
+
+{
+ "spiexceptions.InvalidCatalogName", "InvalidCatalogName", ERRCODE_INVALID_CATALOG_NAME
+},
+
+{
+ "spiexceptions.InvalidSchemaName", "InvalidSchemaName", ERRCODE_INVALID_SCHEMA_NAME
+},
+
+{
+ "spiexceptions.TransactionRollback", "TransactionRollback", ERRCODE_TRANSACTION_ROLLBACK
+},
+
+{
+ "spiexceptions.TransactionIntegrityConstraintViolation", "TransactionIntegrityConstraintViolation", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "spiexceptions.SerializationFailure", "SerializationFailure", ERRCODE_T_R_SERIALIZATION_FAILURE
+},
+
+{
+ "spiexceptions.StatementCompletionUnknown", "StatementCompletionUnknown", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN
+},
+
+{
+ "spiexceptions.DeadlockDetected", "DeadlockDetected", ERRCODE_T_R_DEADLOCK_DETECTED
+},
+
+{
+ "spiexceptions.SyntaxErrorOrAccessRuleViolation", "SyntaxErrorOrAccessRuleViolation", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION
+},
+
+{
+ "spiexceptions.SyntaxError", "SyntaxError", ERRCODE_SYNTAX_ERROR
+},
+
+{
+ "spiexceptions.InsufficientPrivilege", "InsufficientPrivilege", ERRCODE_INSUFFICIENT_PRIVILEGE
+},
+
+{
+ "spiexceptions.CannotCoerce", "CannotCoerce", ERRCODE_CANNOT_COERCE
+},
+
+{
+ "spiexceptions.GroupingError", "GroupingError", ERRCODE_GROUPING_ERROR
+},
+
+{
+ "spiexceptions.WindowingError", "WindowingError", ERRCODE_WINDOWING_ERROR
+},
+
+{
+ "spiexceptions.InvalidRecursion", "InvalidRecursion", ERRCODE_INVALID_RECURSION
+},
+
+{
+ "spiexceptions.InvalidForeignKey", "InvalidForeignKey", ERRCODE_INVALID_FOREIGN_KEY
+},
+
+{
+ "spiexceptions.InvalidName", "InvalidName", ERRCODE_INVALID_NAME
+},
+
+{
+ "spiexceptions.NameTooLong", "NameTooLong", ERRCODE_NAME_TOO_LONG
+},
+
+{
+ "spiexceptions.ReservedName", "ReservedName", ERRCODE_RESERVED_NAME
+},
+
+{
+ "spiexceptions.DatatypeMismatch", "DatatypeMismatch", ERRCODE_DATATYPE_MISMATCH
+},
+
+{
+ "spiexceptions.IndeterminateDatatype", "IndeterminateDatatype", ERRCODE_INDETERMINATE_DATATYPE
+},
+
+{
+ "spiexceptions.CollationMismatch", "CollationMismatch", ERRCODE_COLLATION_MISMATCH
+},
+
+{
+ "spiexceptions.IndeterminateCollation", "IndeterminateCollation", ERRCODE_INDETERMINATE_COLLATION
+},
+
+{
+ "spiexceptions.WrongObjectType", "WrongObjectType", ERRCODE_WRONG_OBJECT_TYPE
+},
+
+{
+ "spiexceptions.GeneratedAlways", "GeneratedAlways", ERRCODE_GENERATED_ALWAYS
+},
+
+{
+ "spiexceptions.UndefinedColumn", "UndefinedColumn", ERRCODE_UNDEFINED_COLUMN
+},
+
+{
+ "spiexceptions.UndefinedFunction", "UndefinedFunction", ERRCODE_UNDEFINED_FUNCTION
+},
+
+{
+ "spiexceptions.UndefinedTable", "UndefinedTable", ERRCODE_UNDEFINED_TABLE
+},
+
+{
+ "spiexceptions.UndefinedParameter", "UndefinedParameter", ERRCODE_UNDEFINED_PARAMETER
+},
+
+{
+ "spiexceptions.UndefinedObject", "UndefinedObject", ERRCODE_UNDEFINED_OBJECT
+},
+
+{
+ "spiexceptions.DuplicateColumn", "DuplicateColumn", ERRCODE_DUPLICATE_COLUMN
+},
+
+{
+ "spiexceptions.DuplicateCursor", "DuplicateCursor", ERRCODE_DUPLICATE_CURSOR
+},
+
+{
+ "spiexceptions.DuplicateDatabase", "DuplicateDatabase", ERRCODE_DUPLICATE_DATABASE
+},
+
+{
+ "spiexceptions.DuplicateFunction", "DuplicateFunction", ERRCODE_DUPLICATE_FUNCTION
+},
+
+{
+ "spiexceptions.DuplicatePreparedStatement", "DuplicatePreparedStatement", ERRCODE_DUPLICATE_PSTATEMENT
+},
+
+{
+ "spiexceptions.DuplicateSchema", "DuplicateSchema", ERRCODE_DUPLICATE_SCHEMA
+},
+
+{
+ "spiexceptions.DuplicateTable", "DuplicateTable", ERRCODE_DUPLICATE_TABLE
+},
+
+{
+ "spiexceptions.DuplicateAlias", "DuplicateAlias", ERRCODE_DUPLICATE_ALIAS
+},
+
+{
+ "spiexceptions.DuplicateObject", "DuplicateObject", ERRCODE_DUPLICATE_OBJECT
+},
+
+{
+ "spiexceptions.AmbiguousColumn", "AmbiguousColumn", ERRCODE_AMBIGUOUS_COLUMN
+},
+
+{
+ "spiexceptions.AmbiguousFunction", "AmbiguousFunction", ERRCODE_AMBIGUOUS_FUNCTION
+},
+
+{
+ "spiexceptions.AmbiguousParameter", "AmbiguousParameter", ERRCODE_AMBIGUOUS_PARAMETER
+},
+
+{
+ "spiexceptions.AmbiguousAlias", "AmbiguousAlias", ERRCODE_AMBIGUOUS_ALIAS
+},
+
+{
+ "spiexceptions.InvalidColumnReference", "InvalidColumnReference", ERRCODE_INVALID_COLUMN_REFERENCE
+},
+
+{
+ "spiexceptions.InvalidColumnDefinition", "InvalidColumnDefinition", ERRCODE_INVALID_COLUMN_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidCursorDefinition", "InvalidCursorDefinition", ERRCODE_INVALID_CURSOR_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidDatabaseDefinition", "InvalidDatabaseDefinition", ERRCODE_INVALID_DATABASE_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidFunctionDefinition", "InvalidFunctionDefinition", ERRCODE_INVALID_FUNCTION_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidPreparedStatementDefinition", "InvalidPreparedStatementDefinition", ERRCODE_INVALID_PSTATEMENT_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidSchemaDefinition", "InvalidSchemaDefinition", ERRCODE_INVALID_SCHEMA_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidTableDefinition", "InvalidTableDefinition", ERRCODE_INVALID_TABLE_DEFINITION
+},
+
+{
+ "spiexceptions.InvalidObjectDefinition", "InvalidObjectDefinition", ERRCODE_INVALID_OBJECT_DEFINITION
+},
+
+{
+ "spiexceptions.WithCheckOptionViolation", "WithCheckOptionViolation", ERRCODE_WITH_CHECK_OPTION_VIOLATION
+},
+
+{
+ "spiexceptions.InsufficientResources", "InsufficientResources", ERRCODE_INSUFFICIENT_RESOURCES
+},
+
+{
+ "spiexceptions.DiskFull", "DiskFull", ERRCODE_DISK_FULL
+},
+
+{
+ "spiexceptions.OutOfMemory", "OutOfMemory", ERRCODE_OUT_OF_MEMORY
+},
+
+{
+ "spiexceptions.TooManyConnections", "TooManyConnections", ERRCODE_TOO_MANY_CONNECTIONS
+},
+
+{
+ "spiexceptions.ConfigurationLimitExceeded", "ConfigurationLimitExceeded", ERRCODE_CONFIGURATION_LIMIT_EXCEEDED
+},
+
+{
+ "spiexceptions.ProgramLimitExceeded", "ProgramLimitExceeded", ERRCODE_PROGRAM_LIMIT_EXCEEDED
+},
+
+{
+ "spiexceptions.StatementTooComplex", "StatementTooComplex", ERRCODE_STATEMENT_TOO_COMPLEX
+},
+
+{
+ "spiexceptions.TooManyColumns", "TooManyColumns", ERRCODE_TOO_MANY_COLUMNS
+},
+
+{
+ "spiexceptions.TooManyArguments", "TooManyArguments", ERRCODE_TOO_MANY_ARGUMENTS
+},
+
+{
+ "spiexceptions.ObjectNotInPrerequisiteState", "ObjectNotInPrerequisiteState", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
+},
+
+{
+ "spiexceptions.ObjectInUse", "ObjectInUse", ERRCODE_OBJECT_IN_USE
+},
+
+{
+ "spiexceptions.CantChangeRuntimeParam", "CantChangeRuntimeParam", ERRCODE_CANT_CHANGE_RUNTIME_PARAM
+},
+
+{
+ "spiexceptions.LockNotAvailable", "LockNotAvailable", ERRCODE_LOCK_NOT_AVAILABLE
+},
+
+{
+ "spiexceptions.UnsafeNewEnumValueUsage", "UnsafeNewEnumValueUsage", ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE
+},
+
+{
+ "spiexceptions.OperatorIntervention", "OperatorIntervention", ERRCODE_OPERATOR_INTERVENTION
+},
+
+{
+ "spiexceptions.QueryCanceled", "QueryCanceled", ERRCODE_QUERY_CANCELED
+},
+
+{
+ "spiexceptions.AdminShutdown", "AdminShutdown", ERRCODE_ADMIN_SHUTDOWN
+},
+
+{
+ "spiexceptions.CrashShutdown", "CrashShutdown", ERRCODE_CRASH_SHUTDOWN
+},
+
+{
+ "spiexceptions.CannotConnectNow", "CannotConnectNow", ERRCODE_CANNOT_CONNECT_NOW
+},
+
+{
+ "spiexceptions.DatabaseDropped", "DatabaseDropped", ERRCODE_DATABASE_DROPPED
+},
+
+{
+ "spiexceptions.IdleSessionTimeout", "IdleSessionTimeout", ERRCODE_IDLE_SESSION_TIMEOUT
+},
+
+{
+ "spiexceptions.SystemError", "SystemError", ERRCODE_SYSTEM_ERROR
+},
+
+{
+ "spiexceptions.IoError", "IoError", ERRCODE_IO_ERROR
+},
+
+{
+ "spiexceptions.UndefinedFile", "UndefinedFile", ERRCODE_UNDEFINED_FILE
+},
+
+{
+ "spiexceptions.DuplicateFile", "DuplicateFile", ERRCODE_DUPLICATE_FILE
+},
+
+{
+ "spiexceptions.SnapshotTooOld", "SnapshotTooOld", ERRCODE_SNAPSHOT_TOO_OLD
+},
+
+{
+ "spiexceptions.ConfigFileError", "ConfigFileError", ERRCODE_CONFIG_FILE_ERROR
+},
+
+{
+ "spiexceptions.LockFileExists", "LockFileExists", ERRCODE_LOCK_FILE_EXISTS
+},
+
+{
+ "spiexceptions.FdwError", "FdwError", ERRCODE_FDW_ERROR
+},
+
+{
+ "spiexceptions.FdwColumnNameNotFound", "FdwColumnNameNotFound", ERRCODE_FDW_COLUMN_NAME_NOT_FOUND
+},
+
+{
+ "spiexceptions.FdwDynamicParameterValueNeeded", "FdwDynamicParameterValueNeeded", ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED
+},
+
+{
+ "spiexceptions.FdwFunctionSequenceError", "FdwFunctionSequenceError", ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR
+},
+
+{
+ "spiexceptions.FdwInconsistentDescriptorInformation", "FdwInconsistentDescriptorInformation", ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION
+},
+
+{
+ "spiexceptions.FdwInvalidAttributeValue", "FdwInvalidAttributeValue", ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE
+},
+
+{
+ "spiexceptions.FdwInvalidColumnName", "FdwInvalidColumnName", ERRCODE_FDW_INVALID_COLUMN_NAME
+},
+
+{
+ "spiexceptions.FdwInvalidColumnNumber", "FdwInvalidColumnNumber", ERRCODE_FDW_INVALID_COLUMN_NUMBER
+},
+
+{
+ "spiexceptions.FdwInvalidDataType", "FdwInvalidDataType", ERRCODE_FDW_INVALID_DATA_TYPE
+},
+
+{
+ "spiexceptions.FdwInvalidDataTypeDescriptors", "FdwInvalidDataTypeDescriptors", ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS
+},
+
+{
+ "spiexceptions.FdwInvalidDescriptorFieldIdentifier", "FdwInvalidDescriptorFieldIdentifier", ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER
+},
+
+{
+ "spiexceptions.FdwInvalidHandle", "FdwInvalidHandle", ERRCODE_FDW_INVALID_HANDLE
+},
+
+{
+ "spiexceptions.FdwInvalidOptionIndex", "FdwInvalidOptionIndex", ERRCODE_FDW_INVALID_OPTION_INDEX
+},
+
+{
+ "spiexceptions.FdwInvalidOptionName", "FdwInvalidOptionName", ERRCODE_FDW_INVALID_OPTION_NAME
+},
+
+{
+ "spiexceptions.FdwInvalidStringLengthOrBufferLength", "FdwInvalidStringLengthOrBufferLength", ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH
+},
+
+{
+ "spiexceptions.FdwInvalidStringFormat", "FdwInvalidStringFormat", ERRCODE_FDW_INVALID_STRING_FORMAT
+},
+
+{
+ "spiexceptions.FdwInvalidUseOfNullPointer", "FdwInvalidUseOfNullPointer", ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER
+},
+
+{
+ "spiexceptions.FdwTooManyHandles", "FdwTooManyHandles", ERRCODE_FDW_TOO_MANY_HANDLES
+},
+
+{
+ "spiexceptions.FdwOutOfMemory", "FdwOutOfMemory", ERRCODE_FDW_OUT_OF_MEMORY
+},
+
+{
+ "spiexceptions.FdwNoSchemas", "FdwNoSchemas", ERRCODE_FDW_NO_SCHEMAS
+},
+
+{
+ "spiexceptions.FdwOptionNameNotFound", "FdwOptionNameNotFound", ERRCODE_FDW_OPTION_NAME_NOT_FOUND
+},
+
+{
+ "spiexceptions.FdwReplyHandle", "FdwReplyHandle", ERRCODE_FDW_REPLY_HANDLE
+},
+
+{
+ "spiexceptions.FdwSchemaNotFound", "FdwSchemaNotFound", ERRCODE_FDW_SCHEMA_NOT_FOUND
+},
+
+{
+ "spiexceptions.FdwTableNotFound", "FdwTableNotFound", ERRCODE_FDW_TABLE_NOT_FOUND
+},
+
+{
+ "spiexceptions.FdwUnableToCreateExecution", "FdwUnableToCreateExecution", ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION
+},
+
+{
+ "spiexceptions.FdwUnableToCreateReply", "FdwUnableToCreateReply", ERRCODE_FDW_UNABLE_TO_CREATE_REPLY
+},
+
+{
+ "spiexceptions.FdwUnableToEstablishConnection", "FdwUnableToEstablishConnection", ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION
+},
+
+{
+ "spiexceptions.PlpgsqlError", "PlpgsqlError", ERRCODE_PLPGSQL_ERROR
+},
+
+{
+ "spiexceptions.RaiseException", "RaiseException", ERRCODE_RAISE_EXCEPTION
+},
+
+{
+ "spiexceptions.NoDataFound", "NoDataFound", ERRCODE_NO_DATA_FOUND
+},
+
+{
+ "spiexceptions.TooManyRows", "TooManyRows", ERRCODE_TOO_MANY_ROWS
+},
+
+{
+ "spiexceptions.AssertFailure", "AssertFailure", ERRCODE_ASSERT_FAILURE
+},
+
+{
+ "spiexceptions.InternalError", "InternalError", ERRCODE_INTERNAL_ERROR
+},
+
+{
+ "spiexceptions.DataCorrupted", "DataCorrupted", ERRCODE_DATA_CORRUPTED
+},
+
+{
+ "spiexceptions.IndexCorrupted", "IndexCorrupted", ERRCODE_INDEX_CORRUPTED
+},
diff --git a/src/pl/plpython/sql/plpython_call.sql b/src/pl/plpython/sql/plpython_call.sql
new file mode 100644
index 0000000..daa4bc3
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_call.sql
@@ -0,0 +1,80 @@
+--
+-- Tests for procedures / CALL syntax
+--
+
+CREATE PROCEDURE test_proc1()
+LANGUAGE plpython3u
+AS $$
+pass
+$$;
+
+CALL test_proc1();
+
+
+-- error: can't return non-None
+CREATE PROCEDURE test_proc2()
+LANGUAGE plpython3u
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE plpython3u
+AS $$
+plpy.execute("INSERT INTO test1 VALUES (%s)" % x)
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+-- output arguments
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE plpython3u
+AS $$
+return [a + '+' + a]
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE plpython3u
+AS $$
+return (b * a, c * a)
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE plpython3u
+AS $$
+plpy.notice("a: %s" % (a))
+return (a * 2,)
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql
new file mode 100644
index 0000000..2175770
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_composite.sql
@@ -0,0 +1,224 @@
+CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$
+return (1, 2)
+$$ LANGUAGE plpython3u;
+
+SELECT multiout_simple();
+SELECT * FROM multiout_simple();
+SELECT i, j + 2 FROM multiout_simple();
+SELECT (multiout_simple()).j + 3;
+
+CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$
+return [(1, 2)] * n
+$$ LANGUAGE plpython3u;
+
+SELECT multiout_simple_setof();
+SELECT * FROM multiout_simple_setof();
+SELECT * FROM multiout_simple_setof(3);
+
+CREATE FUNCTION multiout_record_as(typ text,
+ first text, OUT first text,
+ second integer, OUT second integer,
+ retnull boolean) RETURNS record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+elif typ == 'str':
+ return "('%s',%r)" % (first, second)
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f');
+SELECT multiout_record_as('dict', 'foo', 1, 'f');
+
+SELECT * FROM multiout_record_as('dict', null, null, false);
+SELECT * FROM multiout_record_as('dict', 'one', null, false);
+SELECT * FROM multiout_record_as('dict', null, 2, false);
+SELECT * FROM multiout_record_as('dict', 'three', 3, false);
+SELECT * FROM multiout_record_as('dict', null, null, true);
+
+SELECT * FROM multiout_record_as('tuple', null, null, false);
+SELECT * FROM multiout_record_as('tuple', 'one', null, false);
+SELECT * FROM multiout_record_as('tuple', null, 2, false);
+SELECT * FROM multiout_record_as('tuple', 'three', 3, false);
+SELECT * FROM multiout_record_as('tuple', null, null, true);
+
+SELECT * FROM multiout_record_as('list', null, null, false);
+SELECT * FROM multiout_record_as('list', 'one', null, false);
+SELECT * FROM multiout_record_as('list', null, 2, false);
+SELECT * FROM multiout_record_as('list', 'three', 3, false);
+SELECT * FROM multiout_record_as('list', null, null, true);
+
+SELECT * FROM multiout_record_as('obj', null, null, false);
+SELECT * FROM multiout_record_as('obj', 'one', null, false);
+SELECT * FROM multiout_record_as('obj', null, 2, false);
+SELECT * FROM multiout_record_as('obj', 'three', 3, false);
+SELECT * FROM multiout_record_as('obj', null, null, true);
+
+SELECT * FROM multiout_record_as('str', 'one', 1, false);
+SELECT * FROM multiout_record_as('str', 'one', 2, false);
+
+SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s);
+SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s);
+SELECT * FROM multiout_record_as('obj', NULL, 10, 'f');
+
+CREATE FUNCTION multiout_setof(n integer,
+ OUT power_of_2 integer,
+ OUT length integer) RETURNS SETOF record AS $$
+for i in range(n):
+ power = 2 ** i
+ length = plpy.execute("select length('%d')" % power)[0]['length']
+ yield power, length
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM multiout_setof(3);
+SELECT multiout_setof(5);
+
+CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$
+return [{'x': 4, 'y' :'four'},
+ {'x': 7, 'y' :'seven'},
+ {'x': 0, 'y' :'zero'}]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM multiout_return_table();
+
+CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$
+yield [[1], 'a']
+yield [[1,2], 'b']
+yield [[1,2,3], None]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM multiout_array();
+
+CREATE FUNCTION singleout_composite(OUT type_record) AS $$
+return {'first': 1, 'second': 2}
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$
+return [{'first': 1, 'second': 2},
+ {'first': 3, 'second': 4 }]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM singleout_composite();
+SELECT * FROM multiout_composite();
+
+-- composite OUT parameters in functions returning RECORD not supported yet
+CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$
+return (n, (n * 2, n * 3))
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$
+if returnnull:
+ d = None
+elif typ == 'dict':
+ d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'}
+elif typ == 'tuple':
+ d = (n * 2, n * 3)
+elif typ == 'list':
+ d = [ n * 2, n * 3 ]
+elif typ == 'obj':
+ class d: pass
+ d.first = n * 2
+ d.second = n * 3
+elif typ == 'str':
+ d = "(%r,%r)" % (n * 2, n * 3)
+for i in range(n):
+ yield (i, d)
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM multiout_composite(2);
+SELECT * FROM multiout_table_type_setof('dict', 'f', 3);
+SELECT * FROM multiout_table_type_setof('dict', 'f', 7);
+SELECT * FROM multiout_table_type_setof('tuple', 'f', 2);
+SELECT * FROM multiout_table_type_setof('tuple', 'f', 3);
+SELECT * FROM multiout_table_type_setof('list', 'f', 2);
+SELECT * FROM multiout_table_type_setof('list', 'f', 3);
+SELECT * FROM multiout_table_type_setof('obj', 'f', 4);
+SELECT * FROM multiout_table_type_setof('obj', 'f', 5);
+SELECT * FROM multiout_table_type_setof('str', 'f', 6);
+SELECT * FROM multiout_table_type_setof('str', 'f', 7);
+SELECT * FROM multiout_table_type_setof('dict', 't', 3);
+
+-- check what happens if a type changes under us
+
+CREATE TABLE changing (
+ i integer,
+ j integer
+);
+
+CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$
+return [(1, {'i': 1, 'j': 2}),
+ (1, (3, 4))]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM changing_test();
+ALTER TABLE changing DROP COLUMN j;
+SELECT * FROM changing_test();
+SELECT * FROM changing_test();
+ALTER TABLE changing ADD COLUMN j integer;
+SELECT * FROM changing_test();
+
+-- tables of composite types
+
+CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+yield {'tab': [('first', 1), ('second', 2)],
+ 'typ': [{'first': 'third', 'second': 3},
+ {'first': 'fourth', 'second': 4}]}
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM composite_types_table();
+
+-- check what happens if the output record descriptor changes
+CREATE FUNCTION return_record(t text) RETURNS record AS $$
+return {'t': t, 'val': 10}
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM return_record('abc') AS r(t text, val integer);
+SELECT * FROM return_record('abc') AS r(t text, val bigint);
+SELECT * FROM return_record('abc') AS r(t text, val integer);
+SELECT * FROM return_record('abc') AS r(t varchar(30), val integer);
+SELECT * FROM return_record('abc') AS r(t varchar(100), val integer);
+SELECT * FROM return_record('999') AS r(val text, t integer);
+
+CREATE FUNCTION return_record_2(t text) RETURNS record AS $$
+return {'v1':1,'v2':2,t:3}
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int);
+SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int);
+SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
+SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int);
+-- error
+SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int);
+-- works
+SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int);
+SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int);
+
+-- multi-dimensional array of composite types.
+CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$
+ return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]];
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_list();
+
+-- Starting with PostgreSQL 10, a composite type in an array cannot be
+-- represented as a Python list, because it's ambiguous with multi-dimensional
+-- arrays. So this throws an error now. The error should contain a useful hint
+-- on the issue.
+CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$
+ return [['first', 1]];
+$$ LANGUAGE plpython3u;
+SELECT * FROM composite_type_as_list_broken();
diff --git a/src/pl/plpython/sql/plpython_do.sql b/src/pl/plpython/sql/plpython_do.sql
new file mode 100644
index 0000000..d494132
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_do.sql
@@ -0,0 +1,3 @@
+DO $$ plpy.notice("This is plpython3u.") $$ LANGUAGE plpython3u;
+
+DO $$ raise Exception("error test") $$ LANGUAGE plpython3u;
diff --git a/src/pl/plpython/sql/plpython_drop.sql b/src/pl/plpython/sql/plpython_drop.sql
new file mode 100644
index 0000000..e4f373b
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_drop.sql
@@ -0,0 +1,6 @@
+--
+-- For paranoia's sake, don't leave an untrusted language sitting around
+--
+SET client_min_messages = WARNING;
+
+DROP EXTENSION plpython3u CASCADE;
diff --git a/src/pl/plpython/sql/plpython_ereport.sql b/src/pl/plpython/sql/plpython_ereport.sql
new file mode 100644
index 0000000..d4f6223
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_ereport.sql
@@ -0,0 +1,135 @@
+CREATE FUNCTION elog_test() RETURNS void
+AS $$
+plpy.debug('debug', detail='some detail')
+plpy.log('log', detail='some detail')
+plpy.info('info', detail='some detail')
+plpy.info()
+plpy.info('the question', detail=42);
+plpy.info('This is message text.',
+ detail='This is detail text',
+ hint='This is hint text.',
+ sqlstate='XX000',
+ schema_name='any info about schema',
+ table_name='any info about table',
+ column_name='any info about column',
+ datatype_name='any info about datatype',
+ constraint_name='any info about constraint')
+plpy.notice('notice', detail='some detail')
+plpy.warning('warning', detail='some detail')
+plpy.error('stop on error', detail='some detail', hint='some hint')
+$$ LANGUAGE plpython3u;
+
+SELECT elog_test();
+
+DO $$ plpy.info('other types', detail=(10, 20)) $$ LANGUAGE plpython3u;
+
+DO $$
+import time;
+from datetime import date
+plpy.info('other types', detail=date(2016, 2, 26))
+$$ LANGUAGE plpython3u;
+
+DO $$
+basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
+plpy.info('other types', detail=basket)
+$$ LANGUAGE plpython3u;
+
+-- should fail
+DO $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpython3u;
+DO $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpython3u;
+DO $$ plpy.info('first message', message='second message') $$ LANGUAGE plpython3u;
+DO $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpython3u;
+
+-- raise exception in python, handle exception in plgsql
+CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL,
+ _sqlstate text DEFAULT NULL,
+ _schema_name text DEFAULT NULL,
+ _table_name text DEFAULT NULL,
+ _column_name text DEFAULT NULL,
+ _datatype_name text DEFAULT NULL,
+ _constraint_name text DEFAULT NULL)
+RETURNS void AS $$
+kwargs = {
+ "message": _message, "detail": _detail, "hint": _hint,
+ "sqlstate": _sqlstate, "schema_name": _schema_name, "table_name": _table_name,
+ "column_name": _column_name, "datatype_name": _datatype_name,
+ "constraint_name": _constraint_name
+}
+# ignore None values
+plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v))
+$$ LANGUAGE plpython3u;
+
+SELECT raise_exception('hello', 'world');
+SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333');
+SELECT raise_exception(_message => 'message text',
+ _detail => 'detail text',
+ _hint => 'hint text',
+ _sqlstate => 'XX555',
+ _schema_name => 'schema text',
+ _table_name => 'table text',
+ _column_name => 'column text',
+ _datatype_name => 'datatype text',
+ _constraint_name => 'constraint text');
+
+SELECT raise_exception(_message => 'message text',
+ _hint => 'hint text',
+ _schema_name => 'schema text',
+ _column_name => 'column text',
+ _constraint_name => 'constraint text');
+
+DO $$
+DECLARE
+ __message text;
+ __detail text;
+ __hint text;
+ __sqlstate text;
+ __schema_name text;
+ __table_name text;
+ __column_name text;
+ __datatype_name text;
+ __constraint_name text;
+BEGIN
+ BEGIN
+ PERFORM raise_exception(_message => 'message text',
+ _detail => 'detail text',
+ _hint => 'hint text',
+ _sqlstate => 'XX555',
+ _schema_name => 'schema text',
+ _table_name => 'table text',
+ _column_name => 'column text',
+ _datatype_name => 'datatype text',
+ _constraint_name => 'constraint text');
+ EXCEPTION WHEN SQLSTATE 'XX555' THEN
+ GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT,
+ __detail = PG_EXCEPTION_DETAIL,
+ __hint = PG_EXCEPTION_HINT,
+ __sqlstate = RETURNED_SQLSTATE,
+ __schema_name = SCHEMA_NAME,
+ __table_name = TABLE_NAME,
+ __column_name = COLUMN_NAME,
+ __datatype_name = PG_DATATYPE_NAME,
+ __constraint_name = CONSTRAINT_NAME;
+ RAISE NOTICE 'handled exception'
+ USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), '
+ 'schema_name:(%s), table_name:(%s), column_name:(%s), datatype_name:(%s), constraint_name:(%s)',
+ __message, __detail, __hint, __sqlstate, __schema_name,
+ __table_name, __column_name, __datatype_name, __constraint_name);
+ END;
+END;
+$$;
+
+DO $$
+try:
+ plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')")
+except Exception as e:
+ plpy.info(e.spidata)
+ raise e
+$$ LANGUAGE plpython3u;
+
+DO $$
+try:
+ plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type')
+except Exception as e:
+ plpy.info('sqlstate: %s, hint: %s, table_name: %s, datatype_name: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name))
+ raise e
+$$ LANGUAGE plpython3u;
diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql
new file mode 100644
index 0000000..9bb1c0b
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_error.sql
@@ -0,0 +1,357 @@
+-- test error handling, i forgot to restore Warn_restart in
+-- the trigger handler once. the errors and subsequent core dump were
+-- interesting.
+
+/* Flat out Python syntax error
+ */
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+
+/* With check_function_bodies = false the function should get defined
+ * and the error reported when called
+ */
+SET check_function_bodies = false;
+
+CREATE FUNCTION python_syntax_error() RETURNS text
+ AS
+'.syntaxerror'
+ LANGUAGE plpython3u;
+
+SELECT python_syntax_error();
+/* Run the function twice to check if the hashtable entry gets cleaned up */
+SELECT python_syntax_error();
+
+RESET check_function_bodies;
+
+/* Flat out syntax error
+ */
+CREATE FUNCTION sql_syntax_error() RETURNS text
+ AS
+'plpy.execute("syntax error")'
+ LANGUAGE plpython3u;
+
+SELECT sql_syntax_error();
+
+
+/* check the handling of uncaught python exceptions
+ */
+CREATE FUNCTION exception_index_invalid(text) RETURNS text
+ AS
+'return args[1]'
+ LANGUAGE plpython3u;
+
+SELECT exception_index_invalid('test');
+
+
+/* check handling of nested exceptions
+ */
+CREATE FUNCTION exception_index_invalid_nested() RETURNS text
+ AS
+'rv = plpy.execute("SELECT test5(''foo'')")
+return rv[0]'
+ LANGUAGE plpython3u;
+
+SELECT exception_index_invalid_nested();
+
+
+/* a typo
+ */
+CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+
+SELECT invalid_type_uncaught('rick');
+
+
+/* for what it's worth catch the exception generated by
+ * the typo, and return None
+ */
+CREATE FUNCTION invalid_type_caught(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.notice(str(ex))
+ return None
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+
+SELECT invalid_type_caught('rick');
+
+
+/* for what it's worth catch the exception generated by
+ * the typo, and reraise it as a plain error
+ */
+CREATE FUNCTION invalid_type_reraised(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ q = "SELECT fname FROM users WHERE lname = $1"
+ try:
+ SD["plan"] = plpy.prepare(q, [ "test" ])
+ except plpy.SPIError as ex:
+ plpy.error(str(ex))
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+
+SELECT invalid_type_reraised('rick');
+
+
+/* no typo no messing about
+ */
+CREATE FUNCTION valid_type(a text) RETURNS text
+ AS
+'if "plan" not in SD:
+ SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ])
+rv = plpy.execute(SD["plan"], [ a ])
+if len(rv):
+ return rv[0]["fname"]
+return None
+'
+ LANGUAGE plpython3u;
+
+SELECT valid_type('rick');
+
+/* error in nested functions to get a traceback
+*/
+CREATE FUNCTION nested_error() RETURNS text
+ AS
+'def fun1():
+ plpy.error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+
+SELECT nested_error();
+
+/* raising plpy.Error is just like calling plpy.error
+*/
+CREATE FUNCTION nested_error_raise() RETURNS text
+ AS
+'def fun1():
+ raise plpy.Error("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "not reached"
+'
+ LANGUAGE plpython3u;
+
+SELECT nested_error_raise();
+
+/* using plpy.warning should not produce a traceback
+*/
+CREATE FUNCTION nested_warning() RETURNS text
+ AS
+'def fun1():
+ plpy.warning("boom")
+
+def fun2():
+ fun1()
+
+def fun3():
+ fun2()
+
+fun3()
+return "you''ve been warned"
+'
+ LANGUAGE plpython3u;
+
+SELECT nested_warning();
+
+/* AttributeError at toplevel used to give segfaults with the traceback
+*/
+CREATE FUNCTION toplevel_attribute_error() RETURNS void AS
+$$
+plpy.nonexistent
+$$ LANGUAGE plpython3u;
+
+SELECT toplevel_attribute_error();
+
+/* Calling PL/Python functions from SQL and vice versa should not lose context.
+ */
+CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$
+def first():
+ second()
+
+def second():
+ third()
+
+def third():
+ plpy.execute("select sql_error()")
+
+first()
+$$ LANGUAGE plpython3u;
+
+CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$
+begin
+ select 1/0;
+end
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$
+begin
+ select python_traceback();
+end
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$
+plpy.execute("select sql_error()")
+$$ LANGUAGE plpython3u;
+
+SELECT python_traceback();
+SELECT sql_error();
+SELECT python_from_sql_error();
+SELECT sql_from_python_error();
+
+/* check catching specific types of exceptions
+ */
+CREATE TABLE specific (
+ i integer PRIMARY KEY
+);
+
+CREATE FUNCTION specific_exception(i integer) RETURNS void AS
+$$
+from plpy import spiexceptions
+try:
+ plpy.execute("insert into specific values (%s)" % (i or "NULL"));
+except spiexceptions.NotNullViolation as e:
+ plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate)
+except spiexceptions.UniqueViolation as e:
+ plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate)
+$$ LANGUAGE plpython3u;
+
+SELECT specific_exception(2);
+SELECT specific_exception(NULL);
+SELECT specific_exception(2);
+
+/* SPI errors in PL/Python functions should preserve the SQLSTATE value
+ */
+CREATE FUNCTION python_unique_violation() RETURNS void AS $$
+plpy.execute("insert into specific values (1)")
+plpy.execute("insert into specific values (1)")
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$
+begin
+ begin
+ perform python_unique_violation();
+ exception when unique_violation then
+ return 'ok';
+ end;
+ return 'not reached';
+end;
+$$ language plpgsql;
+
+SELECT catch_python_unique_violation();
+
+/* manually starting subtransactions - a bad idea
+ */
+CREATE FUNCTION manual_subxact() RETURNS void AS $$
+plpy.execute("savepoint save")
+plpy.execute("create table foo(x integer)")
+plpy.execute("rollback to save")
+$$ LANGUAGE plpython3u;
+
+SELECT manual_subxact();
+
+/* same for prepared plans
+ */
+CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$
+save = plpy.prepare("savepoint save")
+rollback = plpy.prepare("rollback to save")
+plpy.execute(save)
+plpy.execute("create table foo(x integer)")
+plpy.execute(rollback)
+$$ LANGUAGE plpython3u;
+
+SELECT manual_subxact_prepared();
+
+/* raising plpy.spiexception.* from python code should preserve sqlstate
+ */
+CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$
+raise plpy.spiexceptions.DivisionByZero()
+$$ LANGUAGE plpython3u;
+
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception();
+EXCEPTION WHEN division_by_zero THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+
+/* setting a custom sqlstate should be handled
+ */
+CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$
+exc = plpy.spiexceptions.DivisionByZero()
+exc.sqlstate = 'SILLY'
+raise exc
+$$ LANGUAGE plpython3u;
+
+DO $$
+BEGIN
+ SELECT plpy_raise_spiexception_override();
+EXCEPTION WHEN SQLSTATE 'SILLY' THEN
+ -- NOOP
+END
+$$ LANGUAGE plpgsql;
+
+/* test the context stack trace for nested execution levels
+ */
+CREATE FUNCTION notice_innerfunc() RETURNS int AS $$
+plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$")
+return 1
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION notice_outerfunc() RETURNS int AS $$
+plpy.execute("SELECT notice_innerfunc()")
+return 1
+$$ LANGUAGE plpython3u;
+
+\set SHOW_CONTEXT always
+
+SELECT notice_outerfunc();
+
+/* test error logged with an underlying exception that includes a detail
+ * string (bug #18070).
+ */
+CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$
+ plan = plpy.prepare("SELECT to_date('xy', 'DD') d")
+ for row in plpy.cursor(plan):
+ yield row['d']
+$$ LANGUAGE plpython3u;
+
+SELECT python_error_detail();
diff --git a/src/pl/plpython/sql/plpython_global.sql b/src/pl/plpython/sql/plpython_global.sql
new file mode 100644
index 0000000..96d2049
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_global.sql
@@ -0,0 +1,38 @@
+--
+-- check static and global data (SD and GD)
+--
+
+CREATE FUNCTION global_test_one() returns text
+ AS
+'if "global_test" not in SD:
+ SD["global_test"] = "set by global_test_one"
+if "global_test" not in GD:
+ GD["global_test"] = "set by global_test_one"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION global_test_two() returns text
+ AS
+'if "global_test" not in SD:
+ SD["global_test"] = "set by global_test_two"
+if "global_test" not in GD:
+ GD["global_test"] = "set by global_test_two"
+return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]'
+ LANGUAGE plpython3u;
+
+
+CREATE FUNCTION static_test() returns int4
+ AS
+'if "call" in SD:
+ SD["call"] = SD["call"] + 1
+else:
+ SD["call"] = 1
+return SD["call"]
+'
+ LANGUAGE plpython3u;
+
+
+SELECT static_test();
+SELECT static_test();
+SELECT global_test_one();
+SELECT global_test_two();
diff --git a/src/pl/plpython/sql/plpython_import.sql b/src/pl/plpython/sql/plpython_import.sql
new file mode 100644
index 0000000..3031eef
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_import.sql
@@ -0,0 +1,68 @@
+-- import python modules
+
+CREATE FUNCTION import_fail() returns text
+ AS
+'try:
+ import foosocket
+except ImportError:
+ return "failed as expected"
+return "succeeded, that wasn''t supposed to happen"'
+ LANGUAGE plpython3u;
+
+
+CREATE FUNCTION import_succeed() returns text
+ AS
+'try:
+ import array
+ import bisect
+ import calendar
+ import cmath
+ import errno
+ import math
+ import operator
+ import random
+ import re
+ import string
+ import time
+except Exception as ex:
+ plpy.notice("import failed -- %s" % str(ex))
+ return "failed, that wasn''t supposed to happen"
+return "succeeded, as expected"'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION import_test_one(p text) RETURNS text
+ AS
+'try:
+ import hashlib
+ digest = hashlib.sha1(p.encode("ascii"))
+except ImportError:
+ import sha
+ digest = sha.new(p)
+return digest.hexdigest()'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION import_test_two(u users) RETURNS text
+ AS
+'plain = u["fname"] + u["lname"]
+try:
+ import hashlib
+ digest = hashlib.sha1(plain.encode("ascii"))
+except ImportError:
+ import sha
+ digest = sha.new(plain);
+return "sha hash of " + plain + " is " + digest.hexdigest()'
+ LANGUAGE plpython3u;
+
+
+-- import python modules
+--
+SELECT import_fail();
+SELECT import_succeed();
+
+-- test import and simple argument handling
+--
+SELECT import_test_one('sha hash of this string');
+
+-- test import and tuple argument handling
+--
+select import_test_two(users) from users where fname = 'willem';
diff --git a/src/pl/plpython/sql/plpython_newline.sql b/src/pl/plpython/sql/plpython_newline.sql
new file mode 100644
index 0000000..cb22ba9
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_newline.sql
@@ -0,0 +1,20 @@
+--
+-- Universal Newline Support
+--
+
+CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS
+E'x = 100\ny = 23\nreturn x + y\n'
+LANGUAGE plpython3u;
+
+CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS
+E'x = 100\ry = 23\rreturn x + y\r'
+LANGUAGE plpython3u;
+
+CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS
+E'x = 100\r\ny = 23\r\nreturn x + y\r\n'
+LANGUAGE plpython3u;
+
+
+SELECT newline_lf();
+SELECT newline_cr();
+SELECT newline_crlf();
diff --git a/src/pl/plpython/sql/plpython_params.sql b/src/pl/plpython/sql/plpython_params.sql
new file mode 100644
index 0000000..8bab488
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_params.sql
@@ -0,0 +1,42 @@
+--
+-- Test named and nameless parameters
+--
+
+CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$
+return args[0] + args[1]
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$
+assert a0 == args[0]
+assert a1 == args[1]
+return True
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$
+assert u == args[0]
+if isinstance(u, dict):
+ # stringify dict the hard way because otherwise the order is implementation-dependent
+ u_keys = list(u.keys())
+ u_keys.sort()
+ s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}'
+else:
+ s = str(u)
+return s
+$$ LANGUAGE plpython3u;
+
+-- use deliberately wrong parameter names
+CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$
+try:
+ assert a1 == args[0]
+ return False
+except NameError as e:
+ assert e.args[0].find("a1") > -1
+ return True
+$$ LANGUAGE plpython3u;
+
+
+SELECT test_param_names0(2,7);
+SELECT test_param_names1(1,'text');
+SELECT test_param_names2(users) from users;
+SELECT test_param_names2(NULL);
+SELECT test_param_names3(1);
diff --git a/src/pl/plpython/sql/plpython_populate.sql b/src/pl/plpython/sql/plpython_populate.sql
new file mode 100644
index 0000000..cc1e19b
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_populate.sql
@@ -0,0 +1,27 @@
+INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe');
+INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd');
+INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe');
+INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash');
+
+
+-- multi table tests
+--
+
+INSERT INTO taxonomy (name) VALUES ('HIV I') ;
+INSERT INTO taxonomy (name) VALUES ('HIV II') ;
+INSERT INTO taxonomy (name) VALUES ('HCV') ;
+
+INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ;
+INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ;
+INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ;
+INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ;
+
+INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ;
+INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ;
+INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ;
diff --git a/src/pl/plpython/sql/plpython_quote.sql b/src/pl/plpython/sql/plpython_quote.sql
new file mode 100644
index 0000000..a1133e7
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_quote.sql
@@ -0,0 +1,33 @@
+-- test quoting functions
+
+CREATE FUNCTION quote(t text, how text) RETURNS text AS $$
+ if how == "literal":
+ return plpy.quote_literal(t)
+ elif how == "nullable":
+ return plpy.quote_nullable(t)
+ elif how == "ident":
+ return plpy.quote_ident(t)
+ else:
+ raise plpy.Error("unrecognized quote type %s" % how)
+$$ LANGUAGE plpython3u;
+
+SELECT quote(t, 'literal') FROM (VALUES
+ ('abc'),
+ ('a''bc'),
+ ('''abc'''),
+ (''),
+ (''''),
+ ('xyzv')) AS v(t);
+
+SELECT quote(t, 'nullable') FROM (VALUES
+ ('abc'),
+ ('a''bc'),
+ ('''abc'''),
+ (''),
+ (''''),
+ (NULL)) AS v(t);
+
+SELECT quote(t, 'ident') FROM (VALUES
+ ('abc'),
+ ('a b c'),
+ ('a " ''abc''')) AS v(t);
diff --git a/src/pl/plpython/sql/plpython_record.sql b/src/pl/plpython/sql/plpython_record.sql
new file mode 100644
index 0000000..52bad8b
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_record.sql
@@ -0,0 +1,163 @@
+--
+-- Test returning tuples
+--
+
+CREATE TABLE table_record (
+ first text,
+ second int4
+ ) ;
+
+CREATE TYPE type_record AS (
+ first text,
+ second int4
+ ) ;
+
+
+CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$
+if retnull:
+ return None
+if typ == 'dict':
+ return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' }
+elif typ == 'tuple':
+ return ( first, second )
+elif typ == 'list':
+ return [ first, second ]
+elif typ == 'obj':
+ class type_record: pass
+ type_record.first = first
+ type_record.second = second
+ return type_record
+elif typ == 'str':
+ return "('%s',%r)" % (first, second)
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$
+return first + '_in_to_out';
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_in_out_params_multi(first in text,
+ second out text, third out text) AS $$
+return (first + '_record_in_to_out_1', first + '_record_in_to_out_2');
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_inout_params(first inout text) AS $$
+return first + '_inout';
+$$ LANGUAGE plpython3u;
+
+
+-- Test tuple returning functions
+SELECT * FROM test_table_record_as('dict', null, null, false);
+SELECT * FROM test_table_record_as('dict', 'one', null, false);
+SELECT * FROM test_table_record_as('dict', null, 2, false);
+SELECT * FROM test_table_record_as('dict', 'three', 3, false);
+SELECT * FROM test_table_record_as('dict', null, null, true);
+
+SELECT * FROM test_table_record_as('tuple', null, null, false);
+SELECT * FROM test_table_record_as('tuple', 'one', null, false);
+SELECT * FROM test_table_record_as('tuple', null, 2, false);
+SELECT * FROM test_table_record_as('tuple', 'three', 3, false);
+SELECT * FROM test_table_record_as('tuple', null, null, true);
+
+SELECT * FROM test_table_record_as('list', null, null, false);
+SELECT * FROM test_table_record_as('list', 'one', null, false);
+SELECT * FROM test_table_record_as('list', null, 2, false);
+SELECT * FROM test_table_record_as('list', 'three', 3, false);
+SELECT * FROM test_table_record_as('list', null, null, true);
+
+SELECT * FROM test_table_record_as('obj', null, null, false);
+SELECT * FROM test_table_record_as('obj', 'one', null, false);
+SELECT * FROM test_table_record_as('obj', null, 2, false);
+SELECT * FROM test_table_record_as('obj', 'three', 3, false);
+SELECT * FROM test_table_record_as('obj', null, null, true);
+
+SELECT * FROM test_type_record_as('dict', null, null, false);
+SELECT * FROM test_type_record_as('dict', 'one', null, false);
+SELECT * FROM test_type_record_as('dict', null, 2, false);
+SELECT * FROM test_type_record_as('dict', 'three', 3, false);
+SELECT * FROM test_type_record_as('dict', null, null, true);
+
+SELECT * FROM test_type_record_as('tuple', null, null, false);
+SELECT * FROM test_type_record_as('tuple', 'one', null, false);
+SELECT * FROM test_type_record_as('tuple', null, 2, false);
+SELECT * FROM test_type_record_as('tuple', 'three', 3, false);
+SELECT * FROM test_type_record_as('tuple', null, null, true);
+
+SELECT * FROM test_type_record_as('list', null, null, false);
+SELECT * FROM test_type_record_as('list', 'one', null, false);
+SELECT * FROM test_type_record_as('list', null, 2, false);
+SELECT * FROM test_type_record_as('list', 'three', 3, false);
+SELECT * FROM test_type_record_as('list', null, null, true);
+
+SELECT * FROM test_type_record_as('obj', null, null, false);
+SELECT * FROM test_type_record_as('obj', 'one', null, false);
+SELECT * FROM test_type_record_as('obj', null, 2, false);
+SELECT * FROM test_type_record_as('obj', 'three', 3, false);
+SELECT * FROM test_type_record_as('obj', null, null, true);
+
+SELECT * FROM test_type_record_as('str', 'one', 1, false);
+
+SELECT * FROM test_in_out_params('test_in');
+SELECT * FROM test_in_out_params_multi('test_in');
+SELECT * FROM test_inout_params('test_in');
+
+-- try changing the return types and call functions again
+
+ALTER TABLE table_record DROP COLUMN first;
+ALTER TABLE table_record DROP COLUMN second;
+ALTER TABLE table_record ADD COLUMN first text;
+ALTER TABLE table_record ADD COLUMN second int4;
+
+SELECT * FROM test_table_record_as('obj', 'one', 1, false);
+
+ALTER TYPE type_record DROP ATTRIBUTE first;
+ALTER TYPE type_record DROP ATTRIBUTE second;
+ALTER TYPE type_record ADD ATTRIBUTE first text;
+ALTER TYPE type_record ADD ATTRIBUTE second int4;
+
+SELECT * FROM test_type_record_as('obj', 'one', 1, false);
+
+-- errors cases
+
+CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$
+ return { 'first': 'first' }
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_record_error1();
+
+
+CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$
+ return [ 'first' ]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_record_error2();
+
+
+CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$
+ class type_record: pass
+ type_record.first = 'first'
+ return type_record
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_record_error3();
+
+CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$
+ return 'foo'
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_record_error4();
diff --git a/src/pl/plpython/sql/plpython_schema.sql b/src/pl/plpython/sql/plpython_schema.sql
new file mode 100644
index 0000000..a5bdbda
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_schema.sql
@@ -0,0 +1,39 @@
+CREATE TABLE users (
+ fname text not null,
+ lname text not null,
+ username text,
+ userid serial,
+ PRIMARY KEY(lname, fname)
+ ) ;
+
+CREATE INDEX users_username_idx ON users(username);
+CREATE INDEX users_fname_idx ON users(fname);
+CREATE INDEX users_lname_idx ON users(lname);
+CREATE INDEX users_userid_idx ON users(userid);
+
+
+CREATE TABLE taxonomy (
+ id serial primary key,
+ name text unique
+ ) ;
+
+CREATE TABLE entry (
+ accession text not null primary key,
+ eid serial unique,
+ txid int2 not null references taxonomy(id)
+ ) ;
+
+CREATE TABLE sequences (
+ eid int4 not null references entry(eid),
+ pid serial primary key,
+ product text not null,
+ sequence text not null,
+ multipart bool default 'false'
+ ) ;
+CREATE INDEX sequences_product_idx ON sequences(product) ;
+
+CREATE TABLE xsequences (
+ pid int4 not null references sequences(pid),
+ sequence text not null
+ ) ;
+CREATE INDEX xsequences_pid_idx ON xsequences(pid) ;
diff --git a/src/pl/plpython/sql/plpython_setof.sql b/src/pl/plpython/sql/plpython_setof.sql
new file mode 100644
index 0000000..4cfb101
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_setof.sql
@@ -0,0 +1,97 @@
+--
+-- Test returning SETOF
+--
+
+CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$
+return 37
+$$ LANGUAGE plpython3u;
+
+SELECT test_setof_error();
+
+
+CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$
+return [ content ]*count
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$
+t = ()
+for i in range(count):
+ t += ( content, )
+return t
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$
+class producer:
+ def __init__ (self, icount, icontent):
+ self.icontent = icontent
+ self.icount = icount
+ def __iter__ (self):
+ return self
+ def __next__ (self):
+ if self.icount == 0:
+ raise StopIteration
+ self.icount -= 1
+ return self.icontent
+return producer(count, content)
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS
+$$
+ for s in ('Hello', 'Brave', 'New', 'World'):
+ plpy.execute('select 1')
+ yield s
+ plpy.execute('select 2')
+$$
+LANGUAGE plpython3u;
+
+
+-- Test set returning functions
+SELECT test_setof_as_list(0, 'list');
+SELECT test_setof_as_list(1, 'list');
+SELECT test_setof_as_list(2, 'list');
+SELECT test_setof_as_list(2, null);
+
+SELECT test_setof_as_tuple(0, 'tuple');
+SELECT test_setof_as_tuple(1, 'tuple');
+SELECT test_setof_as_tuple(2, 'tuple');
+SELECT test_setof_as_tuple(2, null);
+
+SELECT test_setof_as_iterator(0, 'list');
+SELECT test_setof_as_iterator(1, 'list');
+SELECT test_setof_as_iterator(2, 'list');
+SELECT test_setof_as_iterator(2, null);
+
+SELECT test_setof_spi_in_iterator();
+
+-- set-returning function that modifies its parameters
+CREATE OR REPLACE FUNCTION ugly(x int, lim int) RETURNS SETOF int AS $$
+global x
+while x <= lim:
+ yield x
+ x = x + 1
+$$ LANGUAGE plpython3u;
+
+SELECT ugly(1, 5);
+
+-- interleaved execution of such a function
+SELECT ugly(1,3), ugly(7,8);
+
+-- returns set of named-composite-type tuples
+CREATE OR REPLACE FUNCTION get_user_records()
+RETURNS SETOF users
+AS $$
+ return plpy.execute("SELECT * FROM users ORDER BY username")
+$$ LANGUAGE plpython3u;
+
+SELECT get_user_records();
+SELECT * FROM get_user_records();
+
+-- same, but returning set of RECORD
+CREATE OR REPLACE FUNCTION get_user_records2()
+RETURNS TABLE(fname text, lname text, username text, userid int)
+AS $$
+ return plpy.execute("SELECT * FROM users ORDER BY username")
+$$ LANGUAGE plpython3u;
+
+SELECT get_user_records2();
+SELECT * FROM get_user_records2();
diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql
new file mode 100644
index 0000000..fcd113a
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_spi.sql
@@ -0,0 +1,322 @@
+--
+-- nested calls
+--
+
+CREATE FUNCTION nested_call_one(a text) RETURNS text
+ AS
+'q = "SELECT nested_call_two(''%s'')" % a
+r = plpy.execute(q)
+return r[0]'
+ LANGUAGE plpython3u ;
+
+CREATE FUNCTION nested_call_two(a text) RETURNS text
+ AS
+'q = "SELECT nested_call_three(''%s'')" % a
+r = plpy.execute(q)
+return r[0]'
+ LANGUAGE plpython3u ;
+
+CREATE FUNCTION nested_call_three(a text) RETURNS text
+ AS
+'return a'
+ LANGUAGE plpython3u ;
+
+-- some spi stuff
+
+CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT count(*) FROM users WHERE lname = $1"
+ SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+ rv = plpy.execute(SD["myplan"], [a])
+ return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT count(*) FROM users WHERE lname = $1"
+ SD["myplan"] = plpy.prepare(q, [ "text" ])
+try:
+ rv = SD["myplan"].execute([a])
+ return "there are " + str(rv[0]["count"]) + " " + str(a) + "s"
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text
+ AS
+'if "myplan" not in SD:
+ q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a
+ SD["myplan"] = plpy.prepare(q)
+try:
+ rv = plpy.execute(SD["myplan"])
+ if len(rv):
+ return rv[0]["count"]
+except Exception as ex:
+ plpy.error(str(ex))
+return None
+'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION join_sequences(s sequences) RETURNS text
+ AS
+'if not s["multipart"]:
+ return s["sequence"]
+q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"]
+rv = plpy.execute(q)
+seq = s["sequence"]
+for r in rv:
+ seq = seq + r["sequence"]
+return seq
+'
+ LANGUAGE plpython3u;
+
+CREATE FUNCTION spi_recursive_sum(a int) RETURNS int
+ AS
+'r = 0
+if a > 1:
+ r = plpy.execute("SELECT spi_recursive_sum(%d) as a" % (a-1))[0]["a"]
+return a + r
+'
+ LANGUAGE plpython3u;
+
+--
+-- spi and nested calls
+--
+select nested_call_one('pass this along');
+select spi_prepared_plan_test_one('doe');
+select spi_prepared_plan_test_two('smith');
+select spi_prepared_plan_test_nested('smith');
+
+SELECT join_sequences(sequences) FROM sequences;
+SELECT join_sequences(sequences) FROM sequences
+ WHERE join_sequences(sequences) ~* '^A';
+SELECT join_sequences(sequences) FROM sequences
+ WHERE join_sequences(sequences) ~* '^B';
+
+SELECT spi_recursive_sum(10);
+
+--
+-- plan and result objects
+--
+
+CREATE FUNCTION result_metadata_test(cmd text) RETURNS int
+AS $$
+plan = plpy.prepare(cmd)
+plpy.info(plan.status()) # not really documented or useful
+result = plpy.execute(plan)
+if result.status() > 0:
+ plpy.info(result.colnames())
+ plpy.info(result.coltypes())
+ plpy.info(result.coltypmods())
+ return result.nrows()
+else:
+ return None
+$$ LANGUAGE plpython3u;
+
+SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$);
+SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
+
+CREATE FUNCTION result_nrows_test(cmd text) RETURNS int
+AS $$
+result = plpy.execute(cmd)
+return result.nrows()
+$$ LANGUAGE plpython3u;
+
+SELECT result_nrows_test($$SELECT 1$$);
+SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$);
+SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$);
+SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$);
+
+CREATE FUNCTION result_len_test(cmd text) RETURNS int
+AS $$
+result = plpy.execute(cmd)
+return len(result)
+$$ LANGUAGE plpython3u;
+
+SELECT result_len_test($$SELECT 1$$);
+SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$);
+SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$);
+SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$);
+
+CREATE FUNCTION result_subscript_test() RETURNS void
+AS $$
+result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 "
+ "UNION ALL SELECT 3 UNION ALL SELECT 4")
+
+plpy.info(result[1]['c'])
+plpy.info(result[-1]['c'])
+
+plpy.info([item['c'] for item in result[1:3]])
+plpy.info([item['c'] for item in result[::2]])
+
+result[-1] = {'c': 1000}
+result[:2] = [{'c': 10}, {'c': 100}]
+plpy.info([item['c'] for item in result[:]])
+
+# raises TypeError, catch so further tests could be added
+try:
+ plpy.info(result['foo'])
+except TypeError:
+ pass
+else:
+ assert False, "TypeError not raised"
+
+$$ LANGUAGE plpython3u;
+
+SELECT result_subscript_test();
+
+CREATE FUNCTION result_empty_test() RETURNS void
+AS $$
+result = plpy.execute("select 1 where false")
+
+plpy.info(result[:])
+
+$$ LANGUAGE plpython3u;
+
+SELECT result_empty_test();
+
+CREATE FUNCTION result_str_test(cmd text) RETURNS text
+AS $$
+plan = plpy.prepare(cmd)
+result = plpy.execute(plan)
+return str(result)
+$$ LANGUAGE plpython3u;
+
+SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$);
+SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$);
+
+-- cursor objects
+
+CREATE FUNCTION simple_cursor_test() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+does = 0
+for row in res:
+ if row['lname'] == 'doe':
+ does += 1
+return does
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION double_cursor_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+res.close()
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_fetch() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+assert len(res.fetch(3)) == 3
+assert len(res.fetch(3)) == 1
+assert len(res.fetch(3)) == 0
+assert len(res.fetch(3)) == 0
+try:
+ # use next() or __next__(), the method name changed in
+ # http://www.python.org/dev/peps/pep-3114/
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except StopIteration:
+ pass
+else:
+ assert False, "StopIteration not raised"
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users order by fname")
+assert len(res.fetch(2)) == 2
+
+item = None
+try:
+ item = res.next()
+except AttributeError:
+ item = res.__next__()
+assert item['fname'] == 'rick'
+
+assert len(res.fetch(2)) == 1
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION fetch_after_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+try:
+ res.fetch(1)
+except ValueError:
+ pass
+else:
+ assert False, "ValueError not raised"
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION next_after_close() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users")
+res.close()
+try:
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except ValueError:
+ pass
+else:
+ assert False, "ValueError not raised"
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$
+res = plpy.cursor("select fname, lname from users where false")
+assert len(res.fetch(1)) == 0
+try:
+ try:
+ res.next()
+ except AttributeError:
+ res.__next__()
+except StopIteration:
+ pass
+else:
+ assert False, "StopIteration not raised"
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$
+plan = plpy.prepare(
+ "select fname, lname from users where fname like $1 || '%' order by fname",
+ ["text"])
+for row in plpy.cursor(plan, ["w"]):
+ yield row['fname']
+for row in plan.cursor(["j"]):
+ yield row['fname']
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$
+plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'",
+ ["text"])
+c = plpy.cursor(plan, ["a", "b"])
+$$ LANGUAGE plpython3u;
+
+CREATE TYPE test_composite_type AS (
+ a1 int,
+ a2 varchar
+);
+
+CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$
+plan = plpy.prepare("select $1 as c1", ["test_composite_type"])
+res = plpy.execute(plan, [{"a1": 3, "a2": "label"}])
+return res[0]["c1"]
+$$ LANGUAGE plpython3u;
+
+SELECT simple_cursor_test();
+SELECT double_cursor_close();
+SELECT cursor_fetch();
+SELECT cursor_mix_next_and_fetch();
+SELECT fetch_after_close();
+SELECT next_after_close();
+SELECT cursor_fetch_next_empty();
+SELECT cursor_plan();
+SELECT cursor_plan_wrong_args();
+SELECT plan_composite_args();
diff --git a/src/pl/plpython/sql/plpython_subtransaction.sql b/src/pl/plpython/sql/plpython_subtransaction.sql
new file mode 100644
index 0000000..c65c380
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_subtransaction.sql
@@ -0,0 +1,262 @@
+--
+-- Test explicit subtransactions
+--
+
+-- Test table to see if transactions get properly rolled back
+
+CREATE TABLE subtransaction_tbl (
+ i integer
+);
+
+CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ if what_error == "SPI":
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')")
+ elif what_error == "Python":
+ raise Exception("Python exception")
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_ctx_test();
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+SELECT subtransaction_ctx_test('SPI');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+SELECT subtransaction_ctx_test('Python');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Nested subtransactions
+
+CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
+AS $$
+plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ try:
+ with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)")
+ plpy.execute("error")
+ except plpy.SPIError as e:
+ if not swallow:
+ raise
+ plpy.notice("Swallowed %s(%r)" % (e.__class__.__name__, e.args[0]))
+return "ok"
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_nested_test();
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+SELECT subtransaction_nested_test('t');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Nested subtransactions that recursively call code dealing with
+-- subtransactions
+
+CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text
+AS $$
+plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)")
+ plpy.execute("SELECT subtransaction_nested_test('t')")
+return "ok"
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_deeply_nested_test();
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Error conditions from not opening/closing subtransactions
+
+CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void
+AS $$
+plpy.subtransaction().__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_exit_twice() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+plpy.subtransaction().__exit__(None, None, None)
+plpy.subtransaction().__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_enter_twice() RETURNS void
+AS $$
+plpy.subtransaction().__enter__()
+plpy.subtransaction().__enter__()
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.__enter__()
+s.__exit__(None, None, None)
+s.__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.__enter__()
+s.__enter__()
+s.__exit__(None, None, None)
+$$ LANGUAGE plpython3u;
+
+-- No warnings here, as the subtransaction gets indeed closed
+CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void
+AS $$
+with plpy.subtransaction() as s:
+ s.__enter__()
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void
+AS $$
+try:
+ with plpy.subtransaction() as s:
+ s.__exit__(None, None, None)
+except ValueError as e:
+ raise ValueError(e)
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_exit_without_enter();
+SELECT subtransaction_enter_without_exit();
+SELECT subtransaction_exit_twice();
+SELECT subtransaction_enter_twice();
+SELECT subtransaction_exit_same_subtransaction_twice();
+SELECT subtransaction_enter_same_subtransaction_twice();
+SELECT subtransaction_enter_subtransaction_in_with();
+SELECT subtransaction_exit_subtransaction_in_with();
+
+-- Make sure we don't get a "current transaction is aborted" error
+SELECT 1 as test;
+
+-- Mix explicit subtransactions and normal SPI calls
+
+CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void
+AS $$
+p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"])
+try:
+ with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute(p, [2])
+ plpy.execute(p, ["wrong"])
+except plpy.SPIError:
+ plpy.warning("Caught a SPI error from an explicit subtransaction")
+
+try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ plpy.execute(p, [2])
+ plpy.execute(p, ["wrong"])
+except plpy.SPIError:
+ plpy.warning("Caught a SPI error")
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_mix_explicit_and_implicit();
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Alternative method names for Python <2.6
+
+CREATE FUNCTION subtransaction_alternative_names() RETURNS void
+AS $$
+s = plpy.subtransaction()
+s.enter()
+s.exit(None, None, None)
+$$ LANGUAGE plpython3u;
+
+SELECT subtransaction_alternative_names();
+
+-- try/catch inside a subtransaction block
+
+CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')")
+ except plpy.SPIError:
+ plpy.notice("caught")
+$$ LANGUAGE plpython3u;
+
+SELECT try_catch_inside_subtransaction();
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i);
+
+CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void
+AS $$
+with plpy.subtransaction():
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ try:
+ plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)")
+ except plpy.SPIError:
+ plpy.notice("caught")
+$$ LANGUAGE plpython3u;
+
+SELECT pk_violation_inside_subtransaction();
+SELECT * FROM subtransaction_tbl;
+
+DROP TABLE subtransaction_tbl;
+
+-- cursor/subtransactions interactions
+
+CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$
+with plpy.subtransaction():
+ cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)")
+ cur.fetch(10)
+fetched = cur.fetch(10);
+return int(fetched[5]["i"])
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$
+try:
+ with plpy.subtransaction():
+ cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)")
+ cur.fetch(10);
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ fetched = cur.fetch(10)
+ return int(fetched[5]["i"])
+return 0 # not reached
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$
+try:
+ with plpy.subtransaction():
+ plpy.execute('create temporary table tmp(i) '
+ 'as select generate_series(1, 10)')
+ plan = plpy.prepare("select i from tmp")
+ cur = plpy.cursor(plan)
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ fetched = cur.fetch(5)
+ return fetched[2]["i"]
+return 0 # not reached
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$
+try:
+ with plpy.subtransaction():
+ cur = plpy.cursor('select 1')
+ plpy.execute("select no_such_function()")
+except plpy.SPIError:
+ cur.close()
+ return True
+return False # not reached
+$$ LANGUAGE plpython3u;
+
+SELECT cursor_in_subxact();
+SELECT cursor_aborted_subxact();
+SELECT cursor_plan_aborted_subxact();
+SELECT cursor_close_aborted_subxact();
diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql
new file mode 100644
index 0000000..aa22a27
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_test.sql
@@ -0,0 +1,52 @@
+-- first some tests of basic functionality
+CREATE EXTENSION plpython3u;
+
+-- really stupid function just to get the module loaded
+CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u;
+
+select stupid();
+
+-- check 2/3 versioning
+CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u;
+
+select stupidn();
+
+-- test multiple arguments and odd characters in function name
+CREATE FUNCTION "Argument test #1"(u users, a1 text, a2 text) RETURNS text
+ AS
+'keys = list(u.keys())
+keys.sort()
+out = []
+for key in keys:
+ out.append("%s: %s" % (key, u[key]))
+words = a1 + " " + a2 + " => {" + ", ".join(out) + "}"
+return words'
+ LANGUAGE plpython3u;
+
+select "Argument test #1"(users, fname, lname) from users where lname = 'doe' order by 1;
+
+
+-- check module contents
+CREATE FUNCTION module_contents() RETURNS SETOF text AS
+$$
+contents = list(filter(lambda x: not x.startswith("__"), dir(plpy)))
+contents.sort()
+return contents
+$$ LANGUAGE plpython3u;
+
+select module_contents();
+
+CREATE FUNCTION elog_test_basic() RETURNS void
+AS $$
+plpy.debug('debug')
+plpy.log('log')
+plpy.info('info')
+plpy.info(37)
+plpy.info()
+plpy.info('info', 37, [1, 2, 3])
+plpy.notice('notice')
+plpy.warning('warning')
+plpy.error('error')
+$$ LANGUAGE plpython3u;
+
+SELECT elog_test_basic();
diff --git a/src/pl/plpython/sql/plpython_transaction.sql b/src/pl/plpython/sql/plpython_transaction.sql
new file mode 100644
index 0000000..c939ba7
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_transaction.sql
@@ -0,0 +1,182 @@
+CREATE TABLE test1 (a int, b text);
+
+
+CREATE PROCEDURE transaction_test1()
+LANGUAGE plpython3u
+AS $$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+
+CALL transaction_test1();
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+DO
+LANGUAGE plpython3u
+$$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE plpython3u
+AS $$
+for i in range(0, 10):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i)
+ if i % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+return 1
+$$;
+
+SELECT transaction_test2();
+
+SELECT * FROM test1;
+
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plpy.execute("CALL transaction_test1()")
+return 1
+$$;
+
+SELECT transaction_test3();
+
+SELECT * FROM test1;
+
+
+-- DO block inside function
+CREATE FUNCTION transaction_test4() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$")
+return 1
+$$;
+
+SELECT transaction_test4();
+
+
+-- commit inside subtransaction (prohibited)
+DO LANGUAGE plpython3u $$
+s = plpy.subtransaction()
+s.enter()
+plpy.commit()
+$$;
+
+
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+
+TRUNCATE test1;
+
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ plpy.commit()
+$$;
+
+SELECT * FROM test1;
+
+-- check that this doesn't leak a holdable portal
+SELECT * FROM pg_cursors;
+
+
+-- error in cursor loop with commit
+TRUNCATE test1;
+
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x'])
+ plpy.commit()
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ plpy.rollback()
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- first commit then rollback inside cursor loop
+TRUNCATE test1;
+
+DO LANGUAGE plpython3u $$
+for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"):
+ plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x'])
+ if row['x'] % 2 == 0:
+ plpy.commit()
+ else:
+ plpy.rollback()
+$$;
+
+SELECT * FROM test1;
+
+SELECT * FROM pg_cursors;
+
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+
+DO LANGUAGE plpython3u $$
+# this insert will fail during commit:
+plpy.execute("INSERT INTO testfk VALUES (0)")
+plpy.commit()
+plpy.warning('should not get here')
+$$;
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+DO LANGUAGE plpython3u $$
+# this insert will fail during commit:
+plpy.execute("INSERT INTO testfk VALUES (0)")
+try:
+ plpy.commit()
+except Exception as e:
+ plpy.info('sqlstate: %s' % (e.sqlstate))
+# these inserts should work:
+plpy.execute("INSERT INTO testpk VALUES (1)")
+plpy.execute("INSERT INTO testfk VALUES (1)")
+$$;
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql
new file mode 100644
index 0000000..e5504b9
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_trigger.sql
@@ -0,0 +1,469 @@
+-- these triggers are dedicated to HPHC of RI who
+-- decided that my kid's name was william not willem, and
+-- vigorously resisted all efforts at correction. they have
+-- since gone bankrupt...
+
+CREATE FUNCTION users_insert() returns trigger
+ AS
+'if TD["new"]["fname"] == None or TD["new"]["lname"] == None:
+ return "SKIP"
+if TD["new"]["username"] == None:
+ TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"]
+ rv = "MODIFY"
+else:
+ rv = None
+if TD["new"]["fname"] == "william":
+ TD["new"]["fname"] = TD["args"][0]
+ rv = "MODIFY"
+return rv'
+ LANGUAGE plpython3u;
+
+
+CREATE FUNCTION users_update() returns trigger
+ AS
+'if TD["event"] == "UPDATE":
+ if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]:
+ return "SKIP"
+return None'
+ LANGUAGE plpython3u;
+
+
+CREATE FUNCTION users_delete() RETURNS trigger
+ AS
+'if TD["old"]["fname"] == TD["args"][0]:
+ return "SKIP"
+return None'
+ LANGUAGE plpython3u;
+
+
+CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_insert ('willem');
+
+CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_update ('willem');
+
+CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW
+ EXECUTE PROCEDURE users_delete ('willem');
+
+
+-- quick peek at the table
+--
+SELECT * FROM users;
+
+-- should fail
+--
+UPDATE users SET fname = 'william' WHERE fname = 'willem';
+
+-- should modify william to willem and create username
+--
+INSERT INTO users (fname, lname) VALUES ('william', 'smith');
+INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle');
+
+SELECT * FROM users;
+
+
+-- dump trigger data
+
+CREATE TABLE trigger_test
+ (i int, v text );
+
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+
+CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$
+
+if 'relid' in TD:
+ TD['relid'] = "bogus:12345"
+
+skeys = list(TD.keys())
+skeys.sort()
+for key in skeys:
+ val = TD[key]
+ if not isinstance(val, dict):
+ plpy.notice("TD[" + key + "] => " + str(val))
+ else:
+ # print dicts the hard way because otherwise the order is implementation-dependent
+ valkeys = list(val.keys())
+ valkeys.sort()
+ plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}')
+
+return None
+
+$$;
+
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+
+CREATE TRIGGER show_trigger_data_trig_stmt
+BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
+FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo');
+
+insert into trigger_test values(1,'insert');
+update trigger_test set v = 'update' where i = 1;
+delete from trigger_test;
+truncate table trigger_test;
+
+DROP TRIGGER show_trigger_data_trig_stmt on trigger_test;
+DROP TRIGGER show_trigger_data_trig_before on trigger_test;
+DROP TRIGGER show_trigger_data_trig_after on trigger_test;
+
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+
+insert into trigger_test_generated (i) values (1);
+update trigger_test_generated set i = 11 where i = 1;
+delete from trigger_test_generated;
+
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+
+insert into trigger_test values(1,'insert');
+CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test;
+
+CREATE TRIGGER show_trigger_data_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+
+insert into trigger_test_view values(2,'insert');
+update trigger_test_view set v = 'update' where i = 1;
+delete from trigger_test_view;
+
+DROP FUNCTION trigger_data() CASCADE;
+DROP VIEW trigger_test_view;
+delete from trigger_test;
+
+
+--
+-- trigger error handling
+--
+
+INSERT INTO trigger_test VALUES (0, 'zero');
+
+
+-- returning non-string from trigger function
+
+CREATE FUNCTION stupid1() RETURNS trigger
+AS $$
+ return 37
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger1
+BEFORE INSERT ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid1();
+
+INSERT INTO trigger_test VALUES (1, 'one');
+
+DROP TRIGGER stupid_trigger1 ON trigger_test;
+
+
+-- returning MODIFY from DELETE trigger
+
+CREATE FUNCTION stupid2() RETURNS trigger
+AS $$
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger2
+BEFORE DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid2();
+
+DELETE FROM trigger_test WHERE i = 0;
+
+DROP TRIGGER stupid_trigger2 ON trigger_test;
+
+INSERT INTO trigger_test VALUES (0, 'zero');
+
+
+-- returning unrecognized string from trigger function
+
+CREATE FUNCTION stupid3() RETURNS trigger
+AS $$
+ return "foo"
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger3
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid3();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger3 ON trigger_test;
+
+
+-- Unicode variant
+
+CREATE FUNCTION stupid3u() RETURNS trigger
+AS $$
+ return "foo"
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger3
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid3u();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger3 ON trigger_test;
+
+
+-- deleting the TD dictionary
+
+CREATE FUNCTION stupid4() RETURNS trigger
+AS $$
+ del TD["new"]
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger4
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid4();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger4 ON trigger_test;
+
+
+-- TD not a dictionary
+
+CREATE FUNCTION stupid5() RETURNS trigger
+AS $$
+ TD["new"] = ['foo', 'bar']
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger5
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid5();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger5 ON trigger_test;
+
+
+-- TD not having string keys
+
+CREATE FUNCTION stupid6() RETURNS trigger
+AS $$
+ TD["new"] = {1: 'foo', 2: 'bar'}
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger6
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid6();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger6 ON trigger_test;
+
+
+-- TD keys not corresponding to row columns
+
+CREATE FUNCTION stupid7() RETURNS trigger
+AS $$
+ TD["new"] = {'v': 'foo', 'a': 'bar'}
+ return "MODIFY";
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger7
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid7();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger7 ON trigger_test;
+
+
+-- Unicode variant
+
+CREATE FUNCTION stupid7u() RETURNS trigger
+AS $$
+ TD["new"] = {'v': 'foo', 'a': 'bar'}
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER stupid_trigger7
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE stupid7u();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER stupid_trigger7 ON trigger_test;
+
+
+-- calling a trigger function directly
+
+SELECT stupid7();
+
+
+--
+-- Null values
+--
+
+SELECT * FROM trigger_test;
+
+CREATE FUNCTION test_null() RETURNS trigger
+AS $$
+ TD["new"]['v'] = None
+ return "MODIFY"
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER test_null_trigger
+BEFORE UPDATE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE test_null();
+
+UPDATE trigger_test SET v = 'null' WHERE i = 0;
+
+DROP TRIGGER test_null_trigger ON trigger_test;
+
+SELECT * FROM trigger_test;
+
+
+--
+-- Test that triggers honor typmod when assigning to tuple fields,
+-- as per an early 9.0 bug report
+--
+
+SET DateStyle = 'ISO';
+
+CREATE FUNCTION set_modif_time() RETURNS trigger AS $$
+ TD['new']['modif_time'] = '2010-10-13 21:57:28.930486'
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+
+CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE);
+
+CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb
+ FOR EACH ROW EXECUTE PROCEDURE set_modif_time();
+
+INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486');
+SELECT * FROM pb;
+UPDATE pb SET a = 'b';
+SELECT * FROM pb;
+
+
+-- triggers for tables with composite types
+
+CREATE TABLE comp1 (i integer, j boolean);
+CREATE TYPE comp2 AS (k integer, l boolean);
+
+CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2);
+
+CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$
+ TD['new']['f1'] = (3, False)
+ TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10}
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f();
+
+INSERT INTO composite_trigger_test VALUES (NULL, NULL);
+SELECT * FROM composite_trigger_test;
+
+
+-- triggers with composite type columns (bug #6559)
+
+CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2);
+
+CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f();
+
+INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL);
+INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL);
+INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f'));
+SELECT * FROM composite_trigger_noop_test;
+
+
+-- nested composite types
+
+CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer);
+
+CREATE TABLE composite_trigger_nested_test(c comp3);
+
+CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$
+ return 'MODIFY'
+$$ LANGUAGE plpython3u;
+
+CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test
+ FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f();
+
+INSERT INTO composite_trigger_nested_test VALUES (NULL);
+INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3));
+INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL));
+SELECT * FROM composite_trigger_nested_test;
+
+-- check that using a function as a trigger over two tables works correctly
+CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$
+ TD["new"]["data"] = '1234'
+ return 'MODIFY'
+$$;
+
+CREATE TABLE a(data text);
+CREATE TABLE b(data int); -- different type conversion
+
+CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234();
+CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234();
+
+INSERT INTO a DEFAULT VALUES;
+SELECT * FROM a;
+DROP TABLE a;
+INSERT INTO b DEFAULT VALUES;
+SELECT * FROM b;
+
+-- check that SQL run in trigger code can see transition tables
+
+CREATE TABLE transition_table_test (id int, name text);
+INSERT INTO transition_table_test VALUES (1, 'a');
+
+CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpython3u AS
+$$
+ rv = plpy.execute("SELECT * FROM old_table")
+ assert(rv.nrows() == 1)
+ plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+ rv = plpy.execute("SELECT * FROM new_table")
+ assert(rv.nrows() == 1)
+ plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"])
+ return None
+$$;
+
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+UPDATE transition_table_test SET name = 'b';
+
+DROP TABLE transition_table_test;
+DROP FUNCTION transition_table_test_f();
+
+
+-- dealing with generated columns
+
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE plpython3u
+AS $$
+TD['new']['j'] = 5 # not allowed
+return 'MODIFY'
+$$;
+
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+SELECT * FROM trigger_test_generated;
diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql
new file mode 100644
index 0000000..0985a9c
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_types.sql
@@ -0,0 +1,622 @@
+--
+-- Test data type behavior
+--
+
+--
+-- Base/common types
+--
+
+CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_bool(true);
+SELECT * FROM test_type_conversion_bool(false);
+SELECT * FROM test_type_conversion_bool(null);
+
+
+-- test various other ways to express Booleans in Python
+CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$
+# numbers
+if n == 0:
+ ret = 0
+elif n == 1:
+ ret = 5
+# strings
+elif n == 2:
+ ret = ''
+elif n == 3:
+ ret = 'fa' # true in Python, false in PostgreSQL
+# containers
+elif n == 4:
+ ret = []
+elif n == 5:
+ ret = [0]
+plpy.info(ret, not not ret)
+return ret
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_bool_other(0);
+SELECT * FROM test_type_conversion_bool_other(1);
+SELECT * FROM test_type_conversion_bool_other(2);
+SELECT * FROM test_type_conversion_bool_other(3);
+SELECT * FROM test_type_conversion_bool_other(4);
+SELECT * FROM test_type_conversion_bool_other(5);
+
+
+CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_char('a');
+SELECT * FROM test_type_conversion_char(null);
+
+
+CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_int2(100::int2);
+SELECT * FROM test_type_conversion_int2(-100::int2);
+SELECT * FROM test_type_conversion_int2(null);
+
+
+CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_int4(100);
+SELECT * FROM test_type_conversion_int4(-100);
+SELECT * FROM test_type_conversion_int4(null);
+
+
+CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_int8(100);
+SELECT * FROM test_type_conversion_int8(-100);
+SELECT * FROM test_type_conversion_int8(5000000000);
+SELECT * FROM test_type_conversion_int8(null);
+
+
+CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$
+# print just the class name, not the type, to avoid differences
+# between decimal and cdecimal
+plpy.info(str(x), x.__class__.__name__)
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_numeric(100);
+SELECT * FROM test_type_conversion_numeric(-100);
+SELECT * FROM test_type_conversion_numeric(100.0);
+SELECT * FROM test_type_conversion_numeric(100.00);
+SELECT * FROM test_type_conversion_numeric(5000000000.5);
+SELECT * FROM test_type_conversion_numeric(1234567890.0987654321);
+SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321);
+SELECT * FROM test_type_conversion_numeric(null);
+
+
+CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_float4(100);
+SELECT * FROM test_type_conversion_float4(-100);
+SELECT * FROM test_type_conversion_float4(5000.5);
+SELECT * FROM test_type_conversion_float4(null);
+
+
+CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_float8(100);
+SELECT * FROM test_type_conversion_float8(-100);
+SELECT * FROM test_type_conversion_float8(5000000000.5);
+SELECT * FROM test_type_conversion_float8(null);
+SELECT * FROM test_type_conversion_float8(100100100.654321);
+
+
+CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_oid(100);
+SELECT * FROM test_type_conversion_oid(2147483649);
+SELECT * FROM test_type_conversion_oid(null);
+
+
+CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_text('hello world');
+SELECT * FROM test_type_conversion_text(null);
+
+
+CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_bytea('hello world');
+SELECT * FROM test_type_conversion_bytea(E'null\\000byte');
+SELECT * FROM test_type_conversion_bytea(null);
+
+
+CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$
+import marshal
+return marshal.dumps('hello world')
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$
+import marshal
+try:
+ return marshal.loads(x)
+except ValueError as e:
+ return 'FAILED: ' + str(e)
+$$ LANGUAGE plpython3u;
+
+SELECT test_type_unmarshal(x) FROM test_type_marshal() x;
+
+
+--
+-- Domains
+--
+
+CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL);
+
+CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$
+return y
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_booltrue(true, true);
+SELECT * FROM test_type_conversion_booltrue(false, true);
+SELECT * FROM test_type_conversion_booltrue(true, false);
+
+
+CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0);
+
+CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return y
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_uint2(100::uint2, 50);
+SELECT * FROM test_type_conversion_uint2(100::uint2, -50);
+SELECT * FROM test_type_conversion_uint2(null, 1);
+
+
+CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL);
+
+CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$
+return y
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_nnint(10, 20);
+SELECT * FROM test_type_conversion_nnint(null, 20);
+SELECT * FROM test_type_conversion_nnint(10, null);
+
+
+CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL);
+
+CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$
+plpy.info(x, type(x))
+return y
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold');
+SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold');
+SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world');
+SELECT * FROM test_type_conversion_bytea10(null, 'hello word');
+SELECT * FROM test_type_conversion_bytea10('hello word', null);
+
+
+--
+-- Arrays
+--
+
+CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]);
+SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]);
+SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]);
+SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]);
+SELECT * FROM test_type_conversion_array_int4(NULL);
+SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]);
+SELECT * FROM test_type_conversion_array_int4(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]);
+SELECT * FROM test_type_conversion_array_int4('[2:4]={1,2,3}');
+
+CREATE FUNCTION test_type_conversion_array_int8(x int8[]) RETURNS int8[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_int8(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]::int8[]);
+
+CREATE FUNCTION test_type_conversion_array_date(x date[]) RETURNS date[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_date(ARRAY[[['2016-09-21','2016-09-22',NULL],[NULL,'2016-10-21','2016-10-22']],
+ [[NULL,'2016-11-21','2016-10-21'],['2015-09-21','2015-09-22','2014-09-21']]]::date[]);
+
+CREATE FUNCTION test_type_conversion_array_timestamp(x timestamp[]) RETURNS timestamp[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_timestamp(ARRAY[[['2016-09-21 15:34:24.078792-04','2016-10-22 11:34:24.078795-04',NULL],
+ [NULL,'2016-10-21 11:34:25.078792-04','2016-10-21 11:34:24.098792-04']],
+ [[NULL,'2016-01-21 11:34:24.078792-04','2016-11-21 11:34:24.108792-04'],
+ ['2015-09-21 11:34:24.079792-04','2014-09-21 11:34:24.078792-04','2013-09-21 11:34:24.078792-04']]]::timestamp[]);
+
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemint4(h int4, i int4, j int4, k int4 ) RETURNS int4[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+
+select pyreturnmultidemint4(8,5,3,2);
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemint8(h int4, i int4, j int4, k int4 ) RETURNS int8[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+
+select pyreturnmultidemint8(5,5,3,2);
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemfloat4(h int4, i int4, j int4, k int4 ) RETURNS float4[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+
+select pyreturnmultidemfloat4(6,5,3,2);
+
+CREATE OR REPLACE FUNCTION pyreturnmultidemfloat8(h int4, i int4, j int4, k int4 ) RETURNS float8[] AS $BODY$
+m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)]
+plpy.info(m, type(m))
+return m
+$BODY$ LANGUAGE plpython3u;
+
+select pyreturnmultidemfloat8(7,5,3,2);
+
+CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']);
+SELECT * FROM test_type_conversion_array_text(ARRAY[['foo', 'bar'],['foo2', 'bar2']]);
+
+
+CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]);
+
+
+CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$
+return [123, 'abc']
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_mixed1();
+
+
+CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$
+return [123, 'abc']
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_mixed2();
+
+
+-- check output of multi-dimensional arrays
+CREATE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [['a'], ['b'], ['c']]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out();
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], []]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out();
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], [1]]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[], 1]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [1, []]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out(); -- fail
+
+CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$
+return [[1], [[]]]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_md_array_out(); -- fail
+
+
+CREATE FUNCTION test_type_conversion_mdarray_malformed() RETURNS int[] AS $$
+return [[1,2,3],[4,5]]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_mdarray_malformed();
+
+CREATE FUNCTION test_type_conversion_mdarray_malformed2() RETURNS text[] AS $$
+return [[1,2,3], "abc"]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_mdarray_malformed2();
+
+CREATE FUNCTION test_type_conversion_mdarray_malformed3() RETURNS text[] AS $$
+return ["abc", [1,2,3]]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_mdarray_malformed3();
+
+CREATE FUNCTION test_type_conversion_mdarray_toodeep() RETURNS int[] AS $$
+return [[[[[[[1]]]]]]]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_mdarray_toodeep();
+
+
+CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$
+return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}]
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_record();
+
+
+CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$
+return 'abc'
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_string();
+
+CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$
+return ('abc', 'def')
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_tuple();
+
+CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$
+return 5
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_error();
+
+
+--
+-- Domains over arrays
+--
+
+CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]);
+
+CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x
+$$ LANGUAGE plpython3u;
+
+SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain);
+SELECT * FROM test_type_conversion_array_domain(NULL::ordered_pair_domain);
+
+CREATE FUNCTION test_type_conversion_array_domain_check_violation() RETURNS ordered_pair_domain AS $$
+return [2,1]
+$$ LANGUAGE plpython3u;
+SELECT * FROM test_type_conversion_array_domain_check_violation();
+
+
+--
+-- Arrays of domains
+--
+
+CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpython3u;
+
+select test_read_uint2_array(array[1::uint2]);
+
+CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$
+return [x, x]
+$$ LANGUAGE plpython3u;
+
+select test_build_uint2_array(1::int2);
+select test_build_uint2_array(-1::int2); -- fail
+
+--
+-- ideally this would work, but for now it doesn't, because the return value
+-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D
+-- integer array, not an array of arrays.
+--
+CREATE FUNCTION test_type_conversion_domain_array(x integer[])
+ RETURNS ordered_pair_domain[] AS $$
+return [x, x]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_domain_array(array[2,4]);
+select test_type_conversion_domain_array(array[4,2]); -- fail
+
+CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain)
+ RETURNS integer AS $$
+plpy.info(x, type(x))
+return x[1]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_domain_array2(array[2,4]);
+select test_type_conversion_domain_array2(array[4,2]); -- fail
+
+CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[])
+ RETURNS ordered_pair_domain AS $$
+plpy.info(x, type(x))
+return x[0]
+$$ LANGUAGE plpython3u;
+
+select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]);
+
+
+---
+--- Composite types
+---
+
+CREATE TABLE employee (
+ name text,
+ basesalary integer,
+ bonus integer
+);
+
+INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10);
+
+CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$
+return e['basesalary'] + e['bonus']
+$$ LANGUAGE plpython3u;
+
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+
+ALTER TABLE employee DROP bonus;
+
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+
+ALTER TABLE employee ADD bonus integer;
+UPDATE employee SET bonus = 10;
+
+SELECT name, test_composite_table_input(employee.*) FROM employee;
+
+CREATE TYPE named_pair AS (
+ i integer,
+ j integer
+);
+
+CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$
+return sum(p.values())
+$$ LANGUAGE plpython3u;
+
+SELECT test_composite_type_input(row(1, 2));
+
+ALTER TYPE named_pair RENAME TO named_pair_2;
+
+SELECT test_composite_type_input(row(1, 2));
+
+
+--
+-- Domains within composite
+--
+
+CREATE TYPE nnint_container AS (f1 int, f2 nnint);
+
+CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$
+return {'f1': x, 'f2': y}
+$$ LANGUAGE plpython3u;
+
+SELECT nnint_test(null, 3);
+SELECT nnint_test(3, null); -- fail
+
+
+--
+-- Domains of composite
+--
+
+CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j);
+
+CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$
+return p['i'] + p['j']
+$$ LANGUAGE plpython3u;
+
+SELECT read_ordered_named_pair(row(1, 2));
+SELECT read_ordered_named_pair(row(2, 1)); -- fail
+
+CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$
+return {'i': i, 'j': j}
+$$ LANGUAGE plpython3u;
+
+SELECT build_ordered_named_pair(1,2);
+SELECT build_ordered_named_pair(2,1); -- fail
+
+CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$
+return [{'i': i, 'j': j}, {'i': i, 'j': j+1}]
+$$ LANGUAGE plpython3u;
+
+SELECT build_ordered_named_pairs(1,2);
+SELECT build_ordered_named_pairs(2,1); -- fail
+
+
+--
+-- Prepared statements
+--
+
+CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT CASE WHEN $1 THEN 1 ELSE 0 END AS val", ['boolean'])
+rv = plpy.execute(plan, ['fa'], 5) # 'fa' is true in Python
+return rv[0]['val']
+$$;
+
+SELECT test_prep_bool_input(); -- 1
+
+
+CREATE OR REPLACE FUNCTION test_prep_bool_output() RETURNS bool
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT $1 = 1 AS val", ['int'])
+rv = plpy.execute(plan, [0], 5)
+plpy.info(rv[0])
+return rv[0]['val']
+$$;
+
+SELECT test_prep_bool_output(); -- false
+
+
+CREATE OR REPLACE FUNCTION test_prep_bytea_input(bb bytea) RETURNS int
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT octet_length($1) AS val", ['bytea'])
+rv = plpy.execute(plan, [bb], 5)
+return rv[0]['val']
+$$;
+
+SELECT test_prep_bytea_input(E'a\\000b'); -- 3 (embedded null formerly truncated value)
+
+
+CREATE OR REPLACE FUNCTION test_prep_bytea_output() RETURNS bytea
+LANGUAGE plpython3u
+AS $$
+plan = plpy.prepare("SELECT decode('aa00bb', 'hex') AS val")
+rv = plpy.execute(plan, [], 5)
+plpy.info(rv[0])
+return rv[0]['val']
+$$;
+
+SELECT test_prep_bytea_output();
diff --git a/src/pl/plpython/sql/plpython_unicode.sql b/src/pl/plpython/sql/plpython_unicode.sql
new file mode 100644
index 0000000..14f7b4e
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_unicode.sql
@@ -0,0 +1,45 @@
+--
+-- Unicode handling
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+--
+
+SET client_encoding TO UTF8;
+
+CREATE TABLE unicode_test (
+ testvalue text NOT NULL
+);
+
+CREATE FUNCTION unicode_return() RETURNS text AS E'
+return "\\xA0"
+' LANGUAGE plpython3u;
+
+CREATE FUNCTION unicode_trigger() RETURNS trigger AS E'
+TD["new"]["testvalue"] = "\\xA0"
+return "MODIFY"
+' LANGUAGE plpython3u;
+
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+ FOR EACH ROW EXECUTE PROCEDURE unicode_trigger();
+
+CREATE FUNCTION unicode_plan1() RETURNS text AS E'
+plan = plpy.prepare("SELECT $1 AS testvalue", ["text"])
+rv = plpy.execute(plan, ["\\xA0"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpython3u;
+
+CREATE FUNCTION unicode_plan2() RETURNS text AS E'
+plan = plpy.prepare("SELECT $1 || $2 AS testvalue", ["text", "text"])
+rv = plpy.execute(plan, ["foo", "bar"], 1)
+return rv[0]["testvalue"]
+' LANGUAGE plpython3u;
+
+
+SELECT unicode_return();
+INSERT INTO unicode_test (testvalue) VALUES ('test');
+SELECT * FROM unicode_test;
+SELECT unicode_plan1();
+SELECT unicode_plan2();
diff --git a/src/pl/plpython/sql/plpython_void.sql b/src/pl/plpython/sql/plpython_void.sql
new file mode 100644
index 0000000..5a1a671
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_void.sql
@@ -0,0 +1,22 @@
+--
+-- Tests for functions that return void
+--
+
+CREATE FUNCTION test_void_func1() RETURNS void AS $$
+x = 10
+$$ LANGUAGE plpython3u;
+
+-- illegal: can't return non-None value in void-returning func
+CREATE FUNCTION test_void_func2() RETURNS void AS $$
+return 10
+$$ LANGUAGE plpython3u;
+
+CREATE FUNCTION test_return_none() RETURNS int AS $$
+None
+$$ LANGUAGE plpython3u;
+
+
+-- Tests for functions returning void
+SELECT test_void_func1(), test_void_func1() IS NULL AS "is null";
+SELECT test_void_func2(); -- should fail
+SELECT test_return_none(), test_return_none() IS NULL AS "is null";
diff --git a/src/pl/tcl/.gitignore b/src/pl/tcl/.gitignore
new file mode 100644
index 0000000..62b62eb
--- /dev/null
+++ b/src/pl/tcl/.gitignore
@@ -0,0 +1,6 @@
+/pltclerrcodes.h
+
+# Generated subdirectories
+/log/
+/results/
+/tmp_check/
diff --git a/src/pl/tcl/Makefile b/src/pl/tcl/Makefile
new file mode 100644
index 0000000..314f9b2
--- /dev/null
+++ b/src/pl/tcl/Makefile
@@ -0,0 +1,103 @@
+#-------------------------------------------------------------------------
+#
+# Makefile for the PL/Tcl procedural language
+#
+# src/pl/tcl/Makefile
+#
+#-------------------------------------------------------------------------
+
+subdir = src/pl/tcl
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+
+override CPPFLAGS := -I. -I$(srcdir) $(TCL_INCLUDE_SPEC) $(CPPFLAGS)
+
+# On Windows, we don't link directly with the Tcl library; see below
+ifneq ($(PORTNAME), win32)
+SHLIB_LINK = $(TCL_LIB_SPEC) $(TCL_LIBS)
+endif
+
+PGFILEDESC = "PL/Tcl - procedural language"
+
+NAME = pltcl
+
+OBJS = \
+ $(WIN32RES) \
+ pltcl.o
+
+DATA = pltcl.control pltcl--1.0.sql \
+ pltclu.control pltclu--1.0.sql
+
+REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=pltcl
+REGRESS = pltcl_setup pltcl_queries pltcl_trigger pltcl_call pltcl_start_proc pltcl_subxact pltcl_unicode pltcl_transaction
+
+# Tcl on win32 ships with import libraries only for Microsoft Visual C++,
+# which are not compatible with mingw gcc. Therefore we need to build a
+# new import library to link with.
+ifeq ($(PORTNAME), win32)
+
+tclwithver = $(subst -l,,$(filter -l%, $(TCL_LIB_SPEC)))
+TCLDLL = $(dir $(TCLSH))/$(tclwithver).dll
+
+OBJS += lib$(tclwithver).a
+
+lib$(tclwithver).a: $(tclwithver).def
+ dlltool --dllname $(tclwithver).dll --def $(tclwithver).def --output-lib lib$(tclwithver).a
+
+$(tclwithver).def: $(TCLDLL)
+ gendef - $^ > $@
+
+endif # win32
+
+
+include $(top_srcdir)/src/Makefile.shlib
+
+
+all: all-lib
+
+# Force this dependency to be known even without dependency info built:
+pltcl.o: pltclerrcodes.h
+
+# generate pltclerrcodes.h from src/backend/utils/errcodes.txt
+pltclerrcodes.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-pltclerrcodes.pl
+ $(PERL) $(srcdir)/generate-pltclerrcodes.pl $< > $@
+
+distprep: pltclerrcodes.h
+
+install: all install-lib install-data
+
+installdirs: installdirs-lib
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+
+uninstall: uninstall-lib uninstall-data
+
+install-data: installdirs
+ $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+
+uninstall-data:
+ rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+
+.PHONY: install-data uninstall-data
+
+
+check: submake
+ $(pg_regress_check) $(REGRESS_OPTS) $(REGRESS)
+
+installcheck: submake
+ $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS)
+
+.PHONY: submake
+submake:
+ $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X)
+
+# pltclerrcodes.h is in the distribution tarball, so don't clean it here.
+clean distclean: clean-lib
+ rm -f $(OBJS)
+ rm -rf $(pg_regress_clean_files)
+ifeq ($(PORTNAME), win32)
+ rm -f $(tclwithver).def
+endif
+
+maintainer-clean: distclean
+ rm -f pltclerrcodes.h
diff --git a/src/pl/tcl/expected/pltcl_call.out b/src/pl/tcl/expected/pltcl_call.out
new file mode 100644
index 0000000..e449837
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_call.out
@@ -0,0 +1,72 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE pltcl
+AS $$
+unset
+$$;
+CALL test_proc1();
+CREATE PROCEDURE test_proc2()
+LANGUAGE pltcl
+AS $$
+return 5
+$$;
+CALL test_proc2();
+CREATE TABLE test1 (a int);
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE pltcl
+AS $$
+spi_exec "INSERT INTO test1 VALUES ($1)"
+$$;
+CALL test_proc3(55);
+SELECT * FROM test1;
+ a
+----
+ 55
+(1 row)
+
+-- output arguments
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE pltcl
+AS $$
+set aa [concat $1 "+" $1]
+return [list a $aa]
+$$;
+CALL test_proc5('abc');
+ a
+-----------
+ abc + abc
+(1 row)
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE pltcl
+AS $$
+set bb [expr $2 * $1]
+set cc [expr $3 * $1]
+return [list b $bb c $cc]
+$$;
+CALL test_proc6(2, 3, 4);
+ b | c
+---+---
+ 6 | 8
+(1 row)
+
+-- OUT parameters
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE pltcl
+AS $$
+elog NOTICE "a: $1"
+return [list b [expr {$1 * 2}]]
+$$;
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+NOTICE: a: 10
+NOTICE: _a: 10, _b: 20
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+DROP TABLE test1;
diff --git a/src/pl/tcl/expected/pltcl_queries.out b/src/pl/tcl/expected/pltcl_queries.out
new file mode 100644
index 0000000..2d922c2
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_queries.out
@@ -0,0 +1,397 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+-- Test composite-type arguments
+select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
+ tcl_composite_arg_ref1
+------------------------
+ 42
+(1 row)
+
+select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
+ tcl_composite_arg_ref2
+------------------------
+ ref2
+(1 row)
+
+-- More tests for composite argument/result types
+create domain d_comp1 as T_comp1 check ((value).ref1 > 0);
+create function tcl_record_arg(record, fldname text) returns int as '
+ return $1($2)
+' language pltcl;
+select tcl_record_arg(row('tkey', 42, 'ref2')::T_comp1, 'ref1');
+ tcl_record_arg
+----------------
+ 42
+(1 row)
+
+select tcl_record_arg(row('tkey', 42, 'ref2')::d_comp1, 'ref1');
+ tcl_record_arg
+----------------
+ 42
+(1 row)
+
+select tcl_record_arg(row(2,4), 'f2');
+ tcl_record_arg
+----------------
+ 4
+(1 row)
+
+create function tcl_cdomain_arg(d_comp1) returns int as '
+ return $1(ref1)
+' language pltcl;
+select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
+ tcl_cdomain_arg
+-----------------
+ 42
+(1 row)
+
+select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_comp1);
+ tcl_cdomain_arg
+-----------------
+ 42
+(1 row)
+
+select tcl_cdomain_arg(row('tkey', -1, 'ref2')); -- fail
+ERROR: value for domain d_comp1 violates check constraint "d_comp1_check"
+-- Test argisnull primitive
+select tcl_argisnull('foo');
+ tcl_argisnull
+---------------
+ f
+(1 row)
+
+select tcl_argisnull('');
+ tcl_argisnull
+---------------
+ f
+(1 row)
+
+select tcl_argisnull(null);
+ tcl_argisnull
+---------------
+ t
+(1 row)
+
+-- test some error cases
+create function tcl_error(out a int, out b int) as $$return {$$ language pltcl;
+select tcl_error();
+ERROR: missing close-brace
+create function bad_record(out a text, out b text) as $$return [list a]$$ language pltcl;
+select bad_record();
+ERROR: column name/value list must have even number of elements
+create function bad_field(out a text, out b text) as $$return [list a 1 b 2 cow 3]$$ language pltcl;
+select bad_field();
+ERROR: column name/value list contains nonexistent column name "cow"
+-- test compound return
+select * from tcl_test_cube_squared(5);
+ squared | cubed
+---------+-------
+ 25 | 125
+(1 row)
+
+-- test SRF
+select * from tcl_test_squared_rows(0,5);
+ x | y
+---+----
+ 0 | 0
+ 1 | 1
+ 2 | 4
+ 3 | 9
+ 4 | 16
+(5 rows)
+
+select * from tcl_test_sequence(0,5) as a;
+ a
+---
+ 0
+ 1
+ 2
+ 3
+ 4
+(5 rows)
+
+select 1, tcl_test_sequence(0,5);
+ ?column? | tcl_test_sequence
+----------+-------------------
+ 1 | 0
+ 1 | 1
+ 1 | 2
+ 1 | 3
+ 1 | 4
+(5 rows)
+
+create function non_srf() returns int as $$return_next 1$$ language pltcl;
+select non_srf();
+ERROR: return_next cannot be used in non-set-returning functions
+create function bad_record_srf(out a text, out b text) returns setof record as $$
+return_next [list a]
+$$ language pltcl;
+select bad_record_srf();
+ERROR: column name/value list must have even number of elements
+create function bad_field_srf(out a text, out b text) returns setof record as $$
+return_next [list a 1 b 2 cow 3]
+$$ language pltcl;
+select bad_field_srf();
+ERROR: column name/value list contains nonexistent column name "cow"
+-- test composite and domain-over-composite results
+create function tcl_composite_result(int) returns T_comp1 as $$
+return [list tkey tkey1 ref1 $1 ref2 ref22]
+$$ language pltcl;
+select tcl_composite_result(1001);
+ tcl_composite_result
+--------------------------------------------
+ ("tkey1 ",1001,"ref22 ")
+(1 row)
+
+select * from tcl_composite_result(1002);
+ tkey | ref1 | ref2
+------------+------+----------------------
+ tkey1 | 1002 | ref22
+(1 row)
+
+create function tcl_dcomposite_result(int) returns d_comp1 as $$
+return [list tkey tkey2 ref1 $1 ref2 ref42]
+$$ language pltcl;
+select tcl_dcomposite_result(1001);
+ tcl_dcomposite_result
+--------------------------------------------
+ ("tkey2 ",1001,"ref42 ")
+(1 row)
+
+select * from tcl_dcomposite_result(1002);
+ tkey | ref1 | ref2
+------------+------+----------------------
+ tkey2 | 1002 | ref42
+(1 row)
+
+select * from tcl_dcomposite_result(-1); -- fail
+ERROR: value for domain d_comp1 violates check constraint "d_comp1_check"
+create function tcl_record_result(int) returns record as $$
+return [list q1 sometext q2 $1 q3 moretext]
+$$ language pltcl;
+select tcl_record_result(42); -- fail
+ERROR: function returning record called in context that cannot accept type record
+select * from tcl_record_result(42); -- fail
+ERROR: a column definition list is required for functions returning "record" at character 15
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
+ q1 | q2 | q3
+----------+----+----------
+ sometext | 42 | moretext
+(1 row)
+
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
+ q1 | q2 | q3 | q4
+----------+----+----------+----
+ sometext | 42 | moretext |
+(1 row)
+
+select * from tcl_record_result(42) as (q1 text, q2 int, q4 int); -- fail
+ERROR: column name/value list contains nonexistent column name "q3"
+-- test quote
+select tcl_eval('quote foo bar');
+ERROR: wrong # args: should be "quote string"
+select tcl_eval('quote [format %c 39]');
+ tcl_eval
+----------
+ ''
+(1 row)
+
+select tcl_eval('quote [format %c 92]');
+ tcl_eval
+----------
+ \\
+(1 row)
+
+-- Test argisnull
+select tcl_eval('argisnull');
+ERROR: wrong # args: should be "argisnull argno"
+select tcl_eval('argisnull 14');
+ERROR: argno out of range
+select tcl_eval('argisnull abc');
+ERROR: expected integer but got "abc"
+-- Test return_null
+select tcl_eval('return_null 14');
+ERROR: wrong # args: should be "return_null "
+-- Test spi_exec
+select tcl_eval('spi_exec');
+ERROR: wrong # args: should be "spi_exec ?-count n? ?-array name? query ?loop body?"
+select tcl_eval('spi_exec -count');
+ERROR: missing argument to -count or -array
+select tcl_eval('spi_exec -array');
+ERROR: missing argument to -count or -array
+select tcl_eval('spi_exec -count abc');
+ERROR: expected integer but got "abc"
+select tcl_eval('spi_exec query loop body toomuch');
+ERROR: wrong # args: should be "query ?loop body?"
+select tcl_eval('spi_exec "begin; rollback;"');
+ERROR: pltcl: SPI_execute failed: SPI_ERROR_TRANSACTION
+-- Test spi_execp
+select tcl_eval('spi_execp');
+ERROR: missing argument to -count or -array
+select tcl_eval('spi_execp -count');
+ERROR: missing argument to -array, -count or -nulls
+select tcl_eval('spi_execp -array');
+ERROR: missing argument to -array, -count or -nulls
+select tcl_eval('spi_execp -count abc');
+ERROR: expected integer but got "abc"
+select tcl_eval('spi_execp -nulls');
+ERROR: missing argument to -array, -count or -nulls
+select tcl_eval('spi_execp ""');
+ERROR: invalid queryid ''
+-- test spi_prepare
+select tcl_eval('spi_prepare');
+ERROR: wrong # args: should be "spi_prepare query argtypes"
+select tcl_eval('spi_prepare a b');
+ERROR: type "b" does not exist
+select tcl_eval('spi_prepare a "b {"');
+ERROR: unmatched open brace in list
+select tcl_error_handling_test($tcl$spi_prepare "select moo" []$tcl$);
+ tcl_error_handling_test
+--------------------------------------
+ SQLSTATE: 42703 +
+ condition: undefined_column +
+ cursor_position: 8 +
+ message: column "moo" does not exist+
+ statement: select moo
+(1 row)
+
+-- test full error text
+select tcl_error_handling_test($tcl$
+spi_exec "DO $$
+BEGIN
+RAISE 'my message'
+ USING HINT = 'my hint'
+ , DETAIL = 'my detail'
+ , SCHEMA = 'my schema'
+ , TABLE = 'my table'
+ , COLUMN = 'my column'
+ , CONSTRAINT = 'my constraint'
+ , DATATYPE = 'my datatype'
+;
+END$$;"
+$tcl$);
+ tcl_error_handling_test
+--------------------------------------------------------------
+ SQLSTATE: P0001 +
+ column: my column +
+ condition: raise_exception +
+ constraint: my constraint +
+ context: PL/pgSQL function inline_code_block line 3 at RAISE+
+ SQL statement "DO $$ +
+ BEGIN +
+ RAISE 'my message' +
+ USING HINT = 'my hint' +
+ , DETAIL = 'my detail' +
+ , SCHEMA = 'my schema' +
+ , TABLE = 'my table' +
+ , COLUMN = 'my column' +
+ , CONSTRAINT = 'my constraint' +
+ , DATATYPE = 'my datatype' +
+ ; +
+ END$$;" +
+ datatype: my datatype +
+ detail: my detail +
+ hint: my hint +
+ message: my message +
+ schema: my schema +
+ table: my table
+(1 row)
+
+-- verify tcl_error_handling_test() properly reports non-postgres errors
+select tcl_error_handling_test('moo');
+ tcl_error_handling_test
+----------------------------
+ invalid command name "moo"
+(1 row)
+
+-- test elog
+select tcl_eval('elog');
+ERROR: wrong # args: should be "elog level msg"
+select tcl_eval('elog foo bar');
+ERROR: bad priority "foo": must be DEBUG, LOG, INFO, NOTICE, WARNING, ERROR, or FATAL
+-- test forced error
+select tcl_eval('error "forced error"');
+ERROR: forced error
+-- test loop control in spi_exec[p]
+select tcl_spi_exec(true, 'break');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: break
+NOTICE: end of function
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+select tcl_spi_exec(true, 'continue');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: continue
+NOTICE: col1 3, col2 baz
+NOTICE: end of function
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+select tcl_spi_exec(true, 'error');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: error
+ERROR: error message
+select tcl_spi_exec(true, 'return');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: return
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+select tcl_spi_exec(false, 'break');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: break
+NOTICE: end of function
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+select tcl_spi_exec(false, 'continue');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: continue
+NOTICE: col1 3, col2 baz
+NOTICE: end of function
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+select tcl_spi_exec(false, 'error');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: error
+ERROR: error message
+select tcl_spi_exec(false, 'return');
+NOTICE: col1 1, col2 foo
+NOTICE: col1 2, col2 bar
+NOTICE: action: return
+ tcl_spi_exec
+--------------
+
+(1 row)
+
+-- forcibly run the Tcl event loop for awhile, to check that we have not
+-- messed things up too badly by disabling the Tcl notifier subsystem
+select tcl_eval($$
+ unset -nocomplain ::tcl_vwait
+ after 100 {set ::tcl_vwait 1}
+ vwait ::tcl_vwait
+ unset -nocomplain ::tcl_vwait$$);
+ tcl_eval
+----------
+
+(1 row)
+
diff --git a/src/pl/tcl/expected/pltcl_setup.out b/src/pl/tcl/expected/pltcl_setup.out
new file mode 100644
index 0000000..a8fdcf3
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_setup.out
@@ -0,0 +1,263 @@
+create table T_comp1 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+create function tcl_composite_arg_ref1(T_comp1) returns int as '
+ return $1(ref1)
+' language pltcl;
+create function tcl_composite_arg_ref2(T_comp1) returns text as '
+ return $1(ref2)
+' language pltcl;
+create function tcl_argisnull(text) returns bool as '
+ argisnull 1
+' language pltcl;
+create function tcl_int4add(int4,int4) returns int4 as '
+ return [expr $1 + $2]
+' language pltcl;
+-- We use split(n) as a quick-and-dirty way of parsing the input array
+-- value, which comes in as a string like '{1,2}'. There are better ways...
+create function tcl_int4_accum(int4[], int4) returns int4[] as '
+ set state [split $1 "{,}"]
+ set newsum [expr {[lindex $state 1] + $2}]
+ set newcnt [expr {[lindex $state 2] + 1}]
+ return "{$newsum,$newcnt}"
+' language pltcl;
+create function tcl_int4_avg(int4[]) returns int4 as '
+ set state [split $1 "{,}"]
+ if {[lindex $state 2] == 0} { return_null }
+ return [expr {[lindex $state 1] / [lindex $state 2]}]
+' language pltcl;
+create aggregate tcl_avg (
+ sfunc = tcl_int4_accum,
+ basetype = int4,
+ stype = int4[],
+ finalfunc = tcl_int4_avg,
+ initcond = '{0,0}'
+ );
+create aggregate tcl_sum (
+ sfunc = tcl_int4add,
+ basetype = int4,
+ stype = int4,
+ initcond1 = 0
+ );
+create function tcl_int4lt(int4,int4) returns bool as '
+ if {$1 < $2} {
+ return t
+ }
+ return f
+' language pltcl;
+create function tcl_int4le(int4,int4) returns bool as '
+ if {$1 <= $2} {
+ return t
+ }
+ return f
+' language pltcl;
+create function tcl_int4eq(int4,int4) returns bool as '
+ if {$1 == $2} {
+ return t
+ }
+ return f
+' language pltcl;
+create function tcl_int4ge(int4,int4) returns bool as '
+ if {$1 >= $2} {
+ return t
+ }
+ return f
+' language pltcl;
+create function tcl_int4gt(int4,int4) returns bool as '
+ if {$1 > $2} {
+ return t
+ }
+ return f
+' language pltcl;
+create operator @< (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4lt
+ );
+create operator @<= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4le
+ );
+create operator @= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4eq
+ );
+create operator @>= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4ge
+ );
+create operator @> (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4gt
+ );
+create function tcl_int4cmp(int4,int4) returns int4 as '
+ if {$1 < $2} {
+ return -1
+ }
+ if {$1 > $2} {
+ return 1
+ }
+ return 0
+' language pltcl;
+CREATE OPERATOR CLASS tcl_int4_ops
+ FOR TYPE int4 USING btree AS
+ OPERATOR 1 @<,
+ OPERATOR 2 @<=,
+ OPERATOR 3 @=,
+ OPERATOR 4 @>=,
+ OPERATOR 5 @>,
+ FUNCTION 1 tcl_int4cmp(int4,int4) ;
+--
+-- Test usage of Tcl's "clock" command. In recent Tcl versions this
+-- command fails without working "unknown" support, so it's a good canary
+-- for initialization problems.
+--
+create function tcl_date_week(int4,int4,int4) returns text as $$
+ return [clock format [clock scan "$2/$3/$1" -gmt 1] -format "%U" -gmt 1]
+$$ language pltcl immutable;
+select tcl_date_week(2010,1,26);
+ tcl_date_week
+---------------
+ 04
+(1 row)
+
+select tcl_date_week(2001,10,24);
+ tcl_date_week
+---------------
+ 42
+(1 row)
+
+-- test pltcl event triggers
+create function tclsnitch() returns event_trigger language pltcl as $$
+ elog NOTICE "tclsnitch: $TG_event $TG_tag"
+$$;
+create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
+create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
+create function foobar() returns int language sql as $$select 1;$$;
+NOTICE: tclsnitch: ddl_command_start CREATE FUNCTION
+NOTICE: tclsnitch: ddl_command_end CREATE FUNCTION
+alter function foobar() cost 77;
+NOTICE: tclsnitch: ddl_command_start ALTER FUNCTION
+NOTICE: tclsnitch: ddl_command_end ALTER FUNCTION
+drop function foobar();
+NOTICE: tclsnitch: ddl_command_start DROP FUNCTION
+NOTICE: tclsnitch: ddl_command_end DROP FUNCTION
+create table foo();
+NOTICE: tclsnitch: ddl_command_start CREATE TABLE
+NOTICE: tclsnitch: ddl_command_end CREATE TABLE
+drop table foo;
+NOTICE: tclsnitch: ddl_command_start DROP TABLE
+NOTICE: tclsnitch: ddl_command_end DROP TABLE
+drop event trigger tcl_a_snitch;
+drop event trigger tcl_b_snitch;
+create function tcl_test_cube_squared(in int, out squared int, out cubed int) as $$
+ return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
+$$ language pltcl;
+create function tcl_test_squared_rows(int,int) returns table (x int, y int) as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next [list y [expr {$i * $i}] x $i]
+ }
+$$ language pltcl;
+create function tcl_test_sequence(int,int) returns setof int as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next $i
+ }
+$$ language pltcl;
+create function tcl_eval(string text) returns text as $$
+ eval $1
+$$ language pltcl;
+-- test use of errorCode in error handling
+create function tcl_error_handling_test(text) returns text
+language pltcl
+as $function$
+ if {[catch $1 err]} {
+ # If not a Postgres error, just return the basic error message
+ if {[lindex $::errorCode 0] != "POSTGRES"} {
+ return $err
+ }
+
+ # Get rid of keys that can't be expected to remain constant
+ array set myArray $::errorCode
+ unset myArray(POSTGRES)
+ unset -nocomplain myArray(funcname)
+ unset -nocomplain myArray(filename)
+ unset -nocomplain myArray(lineno)
+
+ # Format into something nicer
+ set vals []
+ foreach {key} [lsort [array names myArray]] {
+ set value [string map {"\n" "\n\t"} $myArray($key)]
+ lappend vals "$key: $value"
+ }
+ return [join $vals "\n"]
+ } else {
+ return "no error"
+ }
+$function$;
+-- test spi_exec and spi_execp with -array
+create function tcl_spi_exec(
+ prepare boolean,
+ action text
+)
+returns void language pltcl AS $function$
+set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
+if {$1 == "t"} {
+ set prep [spi_prepare $query {}]
+ spi_execp -array A $prep {
+ elog NOTICE "col1 $A(col1), col2 $A(col2)"
+
+ switch $A(col1) {
+ 2 {
+ elog NOTICE "action: $2"
+ switch $2 {
+ break {
+ break
+ }
+ continue {
+ continue
+ }
+ return {
+ return
+ }
+ error {
+ error "error message"
+ }
+ }
+ error "should not get here"
+ }
+ }
+ }
+} else {
+ spi_exec -array A $query {
+ elog NOTICE "col1 $A(col1), col2 $A(col2)"
+
+ switch $A(col1) {
+ 2 {
+ elog NOTICE "action: $2"
+ switch $2 {
+ break {
+ break
+ }
+ continue {
+ continue
+ }
+ return {
+ return
+ }
+ error {
+ error "error message"
+ }
+ }
+ error "should not get here"
+ }
+ }
+ }
+}
+elog NOTICE "end of function"
+$function$;
diff --git a/src/pl/tcl/expected/pltcl_start_proc.out b/src/pl/tcl/expected/pltcl_start_proc.out
new file mode 100644
index 0000000..9946cd9
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_start_proc.out
@@ -0,0 +1,31 @@
+--
+-- Test start_proc execution
+--
+SET pltcl.start_proc = 'no_such_function';
+select tcl_int4add(1, 2);
+ERROR: function no_such_function() does not exist
+CONTEXT: processing pltcl.start_proc parameter
+select tcl_int4add(1, 2);
+ERROR: function no_such_function() does not exist
+CONTEXT: processing pltcl.start_proc parameter
+create function tcl_initialize() returns void as
+$$ elog NOTICE "in tcl_initialize" $$ language pltcl SECURITY DEFINER;
+SET pltcl.start_proc = 'public.tcl_initialize';
+select tcl_int4add(1, 2); -- fail
+ERROR: function "public.tcl_initialize" must not be SECURITY DEFINER
+CONTEXT: processing pltcl.start_proc parameter
+create or replace function tcl_initialize() returns void as
+$$ elog NOTICE "in tcl_initialize" $$ language pltcl;
+select tcl_int4add(1, 2);
+NOTICE: in tcl_initialize
+ tcl_int4add
+-------------
+ 3
+(1 row)
+
+select tcl_int4add(1, 2);
+ tcl_int4add
+-------------
+ 3
+(1 row)
+
diff --git a/src/pl/tcl/expected/pltcl_subxact.out b/src/pl/tcl/expected/pltcl_subxact.out
new file mode 100644
index 0000000..5e19bbb
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_subxact.out
@@ -0,0 +1,143 @@
+--
+-- Test explicit subtransactions
+--
+CREATE TABLE subtransaction_tbl (
+ i integer
+);
+--
+-- We use this wrapper to catch errors and return errormsg only,
+-- because values of $::errorinfo variable contain procedure name which
+-- includes OID, so it's not stable
+--
+CREATE FUNCTION pltcl_wrapper(statement text) RETURNS text
+AS $$
+ if [catch {spi_exec $1} msg] {
+ return "ERROR: $msg"
+ } else {
+ return "SUCCESS: $msg"
+ }
+$$ LANGUAGE pltcl;
+-- Test subtransaction successfully committed
+CREATE FUNCTION subtransaction_ctx_success() RETURNS void
+AS $$
+ spi_exec "INSERT INTO subtransaction_tbl VALUES(1)"
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES(2)"
+ }
+$$ LANGUAGE pltcl;
+BEGIN;
+INSERT INTO subtransaction_tbl VALUES(0);
+SELECT subtransaction_ctx_success();
+ subtransaction_ctx_success
+----------------------------
+
+(1 row)
+
+COMMIT;
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 0
+ 1
+ 2
+(3 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Test subtransaction rollback
+CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS void
+AS $$
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (1)"
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (2)"
+ if {$1 == "SPI"} {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES ('oops')"
+ } elseif { $1 == "Tcl"} {
+ elog ERROR "Tcl error"
+ }
+ }
+$$ LANGUAGE pltcl;
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test()');
+ pltcl_wrapper
+---------------
+ SUCCESS: 1
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test(''SPI'')');
+ pltcl_wrapper
+------------------------------------------------------
+ ERROR: invalid input syntax for type integer: "oops"
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test(''Tcl'')');
+ pltcl_wrapper
+------------------
+ ERROR: Tcl error
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+-- Nested subtransactions
+CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
+AS $$
+spi_exec "INSERT INTO subtransaction_tbl VALUES (1)"
+subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (2)"
+ if [catch {
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (3)"
+ spi_exec "error"
+ }
+ } errormsg] {
+ if {$1 != "t"} {
+ error $errormsg $::errorInfo $::errorCode
+ }
+ elog NOTICE "Swallowed $errormsg"
+ }
+}
+return "ok"
+$$ LANGUAGE pltcl;
+SELECT pltcl_wrapper('SELECT subtransaction_nested_test()');
+ pltcl_wrapper
+----------------------------------------
+ ERROR: syntax error at or near "error"
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+(0 rows)
+
+TRUNCATE subtransaction_tbl;
+SELECT pltcl_wrapper('SELECT subtransaction_nested_test(''t'')');
+NOTICE: Swallowed syntax error at or near "error"
+ pltcl_wrapper
+---------------
+ SUCCESS: 1
+(1 row)
+
+SELECT * FROM subtransaction_tbl;
+ i
+---
+ 1
+ 2
+(2 rows)
+
+TRUNCATE subtransaction_tbl;
diff --git a/src/pl/tcl/expected/pltcl_transaction.out b/src/pl/tcl/expected/pltcl_transaction.out
new file mode 100644
index 0000000..f557b79
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_transaction.out
@@ -0,0 +1,149 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+CREATE TABLE test1 (a int, b text);
+CREATE PROCEDURE transaction_test1()
+LANGUAGE pltcl
+AS $$
+for {set i 0} {$i < 10} {incr i} {
+ spi_exec "INSERT INTO test1 (a) VALUES ($i)"
+ if {$i % 2 == 0} {
+ commit
+ } else {
+ rollback
+ }
+}
+$$;
+CALL transaction_test1();
+SELECT * FROM test1;
+ a | b
+---+---
+ 0 |
+ 2 |
+ 4 |
+ 6 |
+ 8 |
+(5 rows)
+
+TRUNCATE test1;
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE pltcl
+AS $$
+for {set i 0} {$i < 10} {incr i} {
+ spi_exec "INSERT INTO test1 (a) VALUES ($i)"
+ if {$i % 2 == 0} {
+ commit
+ } else {
+ rollback
+ }
+}
+return 1
+$$;
+SELECT transaction_test2();
+ERROR: invalid transaction termination
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE pltcl
+AS $$
+spi_exec "CALL transaction_test1()"
+return 1
+$$;
+SELECT transaction_test3();
+ERROR: invalid transaction termination
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+TRUNCATE test1;
+CREATE PROCEDURE transaction_test4a()
+LANGUAGE pltcl
+AS $$
+spi_exec -array row "SELECT * FROM test2 ORDER BY x" {
+ spi_exec "INSERT INTO test1 (a) VALUES ($row(x))"
+ commit
+}
+$$;
+CALL transaction_test4a();
+ERROR: cannot commit while a subtransaction is active
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+CREATE PROCEDURE transaction_test4b()
+LANGUAGE pltcl
+AS $$
+spi_exec -array row "SELECT * FROM test2 ORDER BY x" {
+ spi_exec "INSERT INTO test1 (a) VALUES ($row(x))"
+ rollback
+}
+$$;
+CALL transaction_test4b();
+ERROR: cannot roll back while a subtransaction is active
+SELECT * FROM test1;
+ a | b
+---+---
+(0 rows)
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+CREATE PROCEDURE transaction_testfk()
+LANGUAGE pltcl
+AS $$
+# this insert will fail during commit:
+spi_exec "INSERT INTO testfk VALUES (0)"
+commit
+elog WARNING "should not get here"
+$$;
+CALL transaction_testfk();
+ERROR: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey"
+SELECT * FROM testpk;
+ id
+----
+(0 rows)
+
+SELECT * FROM testfk;
+ f1
+----
+(0 rows)
+
+CREATE OR REPLACE PROCEDURE transaction_testfk()
+LANGUAGE pltcl
+AS $$
+# this insert will fail during commit:
+spi_exec "INSERT INTO testfk VALUES (0)"
+if [catch {commit} msg] {
+ elog INFO $msg
+}
+# these inserts should work:
+spi_exec "INSERT INTO testpk VALUES (1)"
+spi_exec "INSERT INTO testfk VALUES (1)"
+$$;
+CALL transaction_testfk();
+INFO: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey"
+SELECT * FROM testpk;
+ id
+----
+ 1
+(1 row)
+
+SELECT * FROM testfk;
+ f1
+----
+ 1
+(1 row)
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/tcl/expected/pltcl_trigger.out b/src/pl/tcl/expected/pltcl_trigger.out
new file mode 100644
index 0000000..008ea19
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_trigger.out
@@ -0,0 +1,888 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+--
+-- Create the tables used in the test queries
+--
+-- T_pkey1 is the primary key table for T_dta1. Entries from T_pkey1
+-- Cannot be changed or deleted if they are referenced from T_dta1.
+--
+-- T_pkey2 is the primary key table for T_dta2. If the key values in
+-- T_pkey2 are changed, the references in T_dta2 follow. If entries
+-- are deleted, the referencing entries from T_dta2 are deleted too.
+-- The values for field key2 in T_pkey2 are silently converted to
+-- upper case on insert/update.
+--
+create table T_pkey1 (
+ key1 int4,
+ key2 char(20),
+ txt char(40)
+);
+create table T_pkey2 (
+ key1 int4,
+ key2 char(20),
+ txt char(40)
+);
+create table T_dta1 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+create table T_dta2 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+--
+-- Function to check key existence in T_pkey1
+--
+create function check_pkey1_exists(int4, bpchar) returns bool as E'
+ if {![info exists GD]} {
+ set GD(plan) [spi_prepare \\
+ "select 1 from T_pkey1 \\
+ where key1 = \\$1 and key2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
+
+ if {$n > 0} {
+ return "t"
+ }
+ return "f"
+' language pltcl;
+-- dump trigger data
+CREATE TABLE trigger_test (
+ i int,
+ v text,
+ dropme text,
+ test_skip boolean DEFAULT false,
+ test_return_null boolean DEFAULT false,
+ test_argisnull boolean DEFAULT false
+);
+-- Make certain dropped attributes are handled correctly
+ALTER TABLE trigger_test DROP dropme;
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
+CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
+ if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
+ # Special case tests
+ if {$NEW(test_return_null) eq "t" } {
+ return_null
+ }
+ if {$NEW(test_argisnull) eq "t" } {
+ set should_error [argisnull 1]
+ }
+ if {$NEW(test_skip) eq "t" } {
+ elog NOTICE "SKIPPING OPERATION $TG_op"
+ return SKIP
+ }
+ }
+
+ if { [info exists TG_relid] } {
+ set TG_relid "bogus:12345"
+ }
+
+ set dnames [info locals {[a-zA-Z]*} ]
+
+ foreach key [lsort $dnames] {
+
+ if { [array exists $key] } {
+ set str "{"
+ foreach akey [lsort [ array names $key ] ] {
+ if {[string length $str] > 1} { set str "$str, " }
+ set cmd "($akey)"
+ set cmd "set val \$$key$cmd"
+ eval $cmd
+ set str "$str$akey: $val"
+ }
+ set str "$str}"
+ elog NOTICE "$key: $str"
+ } else {
+ set val [eval list "\$$key" ]
+ elog NOTICE "$key: $val"
+ }
+ }
+
+
+ return OK
+
+$_$;
+CREATE TRIGGER show_trigger_data_trig
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+CREATE TRIGGER statement_trigger
+BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
+FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+CREATE TRIGGER show_trigger_data_view_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+--
+-- Trigger function on every change to T_pkey1
+--
+create function trig_pkey1_before() returns trigger as E'
+ #
+ # Create prepared plans on the first call
+ #
+ if {![info exists GD]} {
+ #
+ # Plan to check for duplicate key in T_pkey1
+ #
+ set GD(plan_pkey1) [spi_prepare \\
+ "select check_pkey1_exists(\\$1, \\$2) as ret" \\
+ {int4 bpchar}]
+ #
+ # Plan to check for references from T_dta1
+ #
+ set GD(plan_dta1) [spi_prepare \\
+ "select 1 from T_dta1 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Initialize flags
+ #
+ set check_old_ref 0
+ set check_new_dup 0
+
+ switch $TG_op {
+ INSERT {
+ #
+ # Must check for duplicate key on INSERT
+ #
+ set check_new_dup 1
+ }
+ UPDATE {
+ #
+ # Must check for duplicate key on UPDATE only if
+ # the key changes. In that case we must check for
+ # references to OLD values too.
+ #
+ if {[string compare $NEW(key1) $OLD(key1)] != 0} {
+ set check_old_ref 1
+ set check_new_dup 1
+ }
+ if {[string compare $NEW(key2) $OLD(key2)] != 0} {
+ set check_old_ref 1
+ set check_new_dup 1
+ }
+ }
+ DELETE {
+ #
+ # Must only check for references to OLD on DELETE
+ #
+ set check_old_ref 1
+ }
+ }
+
+ if {$check_new_dup} {
+ #
+ # Check for duplicate key
+ #
+ spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
+ if {$ret == "t"} {
+ elog ERROR \\
+ "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
+ }
+ }
+
+ if {$check_old_ref} {
+ #
+ # Check for references to OLD
+ #
+ set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
+ if {$n > 0} {
+ elog ERROR \\
+ "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
+ }
+ }
+
+ #
+ # Anything is fine - let operation pass through
+ #
+ return OK
+' language pltcl;
+create trigger pkey1_before before insert or update or delete on T_pkey1
+ for each row execute procedure
+ trig_pkey1_before();
+--
+-- Trigger function to check for duplicate keys in T_pkey2
+-- and to force key2 to be upper case only without leading whitespaces
+--
+create function trig_pkey2_before() returns trigger as E'
+ #
+ # Prepare plan on first call
+ #
+ if {![info exists GD]} {
+ set GD(plan_pkey2) [spi_prepare \\
+ "select 1 from T_pkey2 \\
+ where key1 = \\$1 and key2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Convert key2 value
+ #
+ set NEW(key2) [string toupper [string trim $NEW(key2)]]
+
+ #
+ # Check for duplicate key
+ #
+ set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
+ if {$n > 0} {
+ elog ERROR \\
+ "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
+ }
+
+ #
+ # Return modified tuple in NEW
+ #
+ return [array get NEW]
+' language pltcl;
+create trigger pkey2_before before insert or update on T_pkey2
+ for each row execute procedure
+ trig_pkey2_before();
+--
+-- Trigger function to force references from T_dta2 follow changes
+-- in T_pkey2 or be deleted too. This must be done AFTER the changes
+-- in T_pkey2 are done so the trigger for primkey check on T_dta2
+-- fired on our updates will see the new key values in T_pkey2.
+--
+create function trig_pkey2_after() returns trigger as E'
+ #
+ # Prepare plans on first call
+ #
+ if {![info exists GD]} {
+ #
+ # Plan to update references from T_dta2
+ #
+ set GD(plan_dta2_upd) [spi_prepare \\
+ "update T_dta2 set ref1 = \\$3, ref2 = \\$4 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar int4 bpchar}]
+ #
+ # Plan to delete references from T_dta2
+ #
+ set GD(plan_dta2_del) [spi_prepare \\
+ "delete from T_dta2 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Initialize flags
+ #
+ set old_ref_follow 0
+ set old_ref_delete 0
+
+ switch $TG_op {
+ UPDATE {
+ #
+ # On update we must let old references follow
+ #
+ set NEW(key2) [string toupper $NEW(key2)]
+
+ if {[string compare $NEW(key1) $OLD(key1)] != 0} {
+ set old_ref_follow 1
+ }
+ if {[string compare $NEW(key2) $OLD(key2)] != 0} {
+ set old_ref_follow 1
+ }
+ }
+ DELETE {
+ #
+ # On delete we must delete references too
+ #
+ set old_ref_delete 1
+ }
+ }
+
+ if {$old_ref_follow} {
+ #
+ # Let old references follow and fire NOTICE message if
+ # there where some
+ #
+ set n [spi_execp $GD(plan_dta2_upd) \\
+ [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
+ if {$n > 0} {
+ elog NOTICE \\
+ "updated $n entries in T_dta2 for new key in T_pkey2"
+ }
+ }
+
+ if {$old_ref_delete} {
+ #
+ # delete references and fire NOTICE message if
+ # there where some
+ #
+ set n [spi_execp $GD(plan_dta2_del) \\
+ [list $OLD(key1) $OLD(key2)]]
+ if {$n > 0} {
+ elog NOTICE \\
+ "deleted $n entries from T_dta2"
+ }
+ }
+
+ return OK
+' language pltcl;
+create trigger pkey2_after after update or delete on T_pkey2
+ for each row execute procedure
+ trig_pkey2_after();
+--
+-- Generic trigger function to check references in T_dta1 and T_dta2
+--
+create function check_primkey() returns trigger as E'
+ #
+ # For every trigger/relation pair we create
+ # a saved plan and hold them in GD
+ #
+ set plankey [list "plan" $TG_name $TG_relid]
+ set planrel [list "relname" $TG_relid]
+
+ #
+ # Extract the pkey relation name
+ #
+ set keyidx [expr [llength $args] / 2]
+ set keyrel [string tolower [lindex $args $keyidx]]
+
+ if {![info exists GD($plankey)]} {
+ #
+ # We must prepare a new plan. Build up a query string
+ # for the primary key check.
+ #
+ set keylist [lrange $args [expr $keyidx + 1] end]
+
+ set query "select 1 from $keyrel"
+ set qual " where"
+ set typlist ""
+ set idx 1
+ foreach key $keylist {
+ set key [string tolower $key]
+ #
+ # Add the qual part to the query string
+ #
+ append query "$qual $key = \\$$idx"
+ set qual " and"
+
+ #
+ # Lookup the fields type in pg_attribute
+ #
+ set n [spi_exec "select T.typname \\
+ from pg_catalog.pg_type T, pg_catalog.pg_attribute A, pg_catalog.pg_class C \\
+ where C.relname = ''[quote $keyrel]'' \\
+ and C.oid = A.attrelid \\
+ and A.attname = ''[quote $key]'' \\
+ and A.atttypid = T.oid"]
+ if {$n != 1} {
+ elog ERROR "table $keyrel doesn''t have a field named $key"
+ }
+
+ #
+ # Append the fields type to the argument type list
+ #
+ lappend typlist $typname
+ incr idx
+ }
+
+ #
+ # Prepare the plan
+ #
+ set GD($plankey) [spi_prepare $query $typlist]
+
+ #
+ # Lookup and remember the table name for later error messages
+ #
+ spi_exec "select relname from pg_catalog.pg_class \\
+ where oid = ''$TG_relid''::oid"
+ set GD($planrel) $relname
+ }
+
+ #
+ # Build the argument list from the NEW row
+ #
+ incr keyidx -1
+ set arglist ""
+ foreach arg [lrange $args 0 $keyidx] {
+ lappend arglist $NEW($arg)
+ }
+
+ #
+ # Check for the primary key
+ #
+ set n [spi_execp -count 1 $GD($plankey) $arglist]
+ if {$n <= 0} {
+ elog ERROR "key for $GD($planrel) not in $keyrel"
+ }
+
+ #
+ # Anything is fine
+ #
+ return OK
+' language pltcl;
+create trigger dta1_before before insert or update on T_dta1
+ for each row execute procedure
+ check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
+create trigger dta2_before before insert or update on T_dta2
+ for each row execute procedure
+ check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
+insert into T_pkey1 values (1, 'key1-1', 'test key');
+insert into T_pkey1 values (1, 'key1-2', 'test key');
+insert into T_pkey1 values (1, 'key1-3', 'test key');
+insert into T_pkey1 values (2, 'key2-1', 'test key');
+insert into T_pkey1 values (2, 'key2-2', 'test key');
+insert into T_pkey1 values (2, 'key2-3', 'test key');
+insert into T_pkey2 values (1, 'key1-1', 'test key');
+insert into T_pkey2 values (1, 'key1-2', 'test key');
+insert into T_pkey2 values (1, 'key1-3', 'test key');
+insert into T_pkey2 values (2, 'key2-1', 'test key');
+insert into T_pkey2 values (2, 'key2-2', 'test key');
+insert into T_pkey2 values (2, 'key2-3', 'test key');
+select * from T_pkey1;
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | key1-1 | test key
+ 1 | key1-2 | test key
+ 1 | key1-3 | test key
+ 2 | key2-1 | test key
+ 2 | key2-2 | test key
+ 2 | key2-3 | test key
+(6 rows)
+
+-- key2 in T_pkey2 should have upper case only
+select * from T_pkey2;
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | KEY1-1 | test key
+ 1 | KEY1-2 | test key
+ 1 | KEY1-3 | test key
+ 2 | KEY2-1 | test key
+ 2 | KEY2-2 | test key
+ 2 | KEY2-3 | test key
+(6 rows)
+
+insert into T_pkey1 values (1, 'KEY1-3', 'should work');
+-- Due to the upper case translation in trigger this must fail
+insert into T_pkey2 values (1, 'KEY1-3', 'should fail');
+ERROR: duplicate key '1', 'KEY1-3' for T_pkey2
+insert into T_dta1 values ('trec 1', 1, 'key1-1');
+insert into T_dta1 values ('trec 2', 1, 'key1-2');
+insert into T_dta1 values ('trec 3', 1, 'key1-3');
+-- Must fail due to unknown key in T_pkey1
+insert into T_dta1 values ('trec 4', 1, 'key1-4');
+ERROR: key for t_dta1 not in t_pkey1
+insert into T_dta2 values ('trec 1', 1, 'KEY1-1');
+insert into T_dta2 values ('trec 2', 1, 'KEY1-2');
+insert into T_dta2 values ('trec 3', 1, 'KEY1-3');
+-- Must fail due to unknown key in T_pkey2
+insert into T_dta2 values ('trec 4', 1, 'KEY1-4');
+ERROR: key for t_dta2 not in t_pkey2
+select * from T_dta1;
+ tkey | ref1 | ref2
+------------+------+----------------------
+ trec 1 | 1 | key1-1
+ trec 2 | 1 | key1-2
+ trec 3 | 1 | key1-3
+(3 rows)
+
+select * from T_dta2;
+ tkey | ref1 | ref2
+------------+------+----------------------
+ trec 1 | 1 | KEY1-1
+ trec 2 | 1 | KEY1-2
+ trec 3 | 1 | KEY1-3
+(3 rows)
+
+update T_pkey1 set key2 = 'key2-9' where key1 = 2 and key2 = 'key2-1';
+update T_pkey1 set key2 = 'key1-9' where key1 = 1 and key2 = 'key1-1';
+ERROR: key '1', 'key1-1 ' referenced by T_dta1
+delete from T_pkey1 where key1 = 2 and key2 = 'key2-2';
+delete from T_pkey1 where key1 = 1 and key2 = 'key1-2';
+ERROR: key '1', 'key1-2 ' referenced by T_dta1
+update T_pkey2 set key2 = 'KEY2-9' where key1 = 2 and key2 = 'KEY2-1';
+update T_pkey2 set key2 = 'KEY1-9' where key1 = 1 and key2 = 'KEY1-1';
+NOTICE: updated 1 entries in T_dta2 for new key in T_pkey2
+delete from T_pkey2 where key1 = 2 and key2 = 'KEY2-2';
+delete from T_pkey2 where key1 = 1 and key2 = 'KEY1-2';
+NOTICE: deleted 1 entries from T_dta2
+select * from T_pkey1;
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | key1-1 | test key
+ 1 | key1-2 | test key
+ 1 | key1-3 | test key
+ 2 | key2-3 | test key
+ 1 | KEY1-3 | should work
+ 2 | key2-9 | test key
+(6 rows)
+
+select * from T_pkey2;
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | KEY1-3 | test key
+ 2 | KEY2-3 | test key
+ 2 | KEY2-9 | test key
+ 1 | KEY1-9 | test key
+(4 rows)
+
+select * from T_dta1;
+ tkey | ref1 | ref2
+------------+------+----------------------
+ trec 1 | 1 | key1-1
+ trec 2 | 1 | key1-2
+ trec 3 | 1 | key1-3
+(3 rows)
+
+select * from T_dta2;
+ tkey | ref1 | ref2
+------------+------+----------------------
+ trec 3 | 1 | KEY1-3
+ trec 1 | 1 | KEY1-9
+(2 rows)
+
+select tcl_avg(key1) from T_pkey1;
+ tcl_avg
+---------
+ 1
+(1 row)
+
+select tcl_sum(key1) from T_pkey1;
+ tcl_sum
+---------
+ 8
+(1 row)
+
+select tcl_avg(key1) from T_pkey2;
+ tcl_avg
+---------
+ 1
+(1 row)
+
+select tcl_sum(key1) from T_pkey2;
+ tcl_sum
+---------
+ 6
+(1 row)
+
+-- The following should return NULL instead of 0
+select tcl_avg(key1) from T_pkey1 where key1 = 99;
+ tcl_avg
+---------
+
+(1 row)
+
+select tcl_sum(key1) from T_pkey1 where key1 = 99;
+ tcl_sum
+---------
+ 0
+(1 row)
+
+select 1 @< 2;
+ ?column?
+----------
+ t
+(1 row)
+
+select 100 @< 4;
+ ?column?
+----------
+ f
+(1 row)
+
+select * from T_pkey1 order by key1 using @<, key2 collate "C";
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | KEY1-3 | should work
+ 1 | key1-1 | test key
+ 1 | key1-2 | test key
+ 1 | key1-3 | test key
+ 2 | key2-3 | test key
+ 2 | key2-9 | test key
+(6 rows)
+
+select * from T_pkey2 order by key1 using @<, key2 collate "C";
+ key1 | key2 | txt
+------+----------------------+------------------------------------------
+ 1 | KEY1-3 | test key
+ 1 | KEY1-9 | test key
+ 2 | KEY2-3 | test key
+ 2 | KEY2-9 | test key
+(4 rows)
+
+-- show dump of trigger data
+insert into trigger_test values(1,'insert');
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+NOTICE: NEW: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: insert}
+NOTICE: OLD: {}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {23 skidoo}
+insert into trigger_test_generated (i) values (1);
+NOTICE: NEW: {i: 1}
+NOTICE: OLD: {}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_before
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {}
+NOTICE: NEW: {i: 1, j: 2}
+NOTICE: OLD: {}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_after
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: AFTER
+NOTICE: args: {}
+update trigger_test_generated set i = 11 where i = 1;
+NOTICE: NEW: {i: 11}
+NOTICE: OLD: {i: 1, j: 2}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_before
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {}
+NOTICE: NEW: {i: 11, j: 22}
+NOTICE: OLD: {i: 1, j: 2}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_after
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: AFTER
+NOTICE: args: {}
+delete from trigger_test_generated;
+NOTICE: NEW: {}
+NOTICE: OLD: {i: 11, j: 22}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_before
+NOTICE: TG_op: DELETE
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {}
+NOTICE: NEW: {}
+NOTICE: OLD: {i: 11, j: 22}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig_after
+NOTICE: TG_op: DELETE
+NOTICE: TG_relatts: {{} i j}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_generated
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: AFTER
+NOTICE: args: {}
+insert into trigger_test_view values(2,'insert');
+NOTICE: NEW: {i: 2, v: insert}
+NOTICE: OLD: {}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_view_trig
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i v}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_view
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: {INSTEAD OF}
+NOTICE: args: {24 {skidoo view}}
+update trigger_test_view set v = 'update' where i=1;
+NOTICE: NEW: {i: 1, v: update}
+NOTICE: OLD: {i: 1, v: insert}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_view_trig
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i v}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_view
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: {INSTEAD OF}
+NOTICE: args: {24 {skidoo view}}
+delete from trigger_test_view;
+NOTICE: NEW: {}
+NOTICE: OLD: {i: 1, v: insert}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_view_trig
+NOTICE: TG_op: DELETE
+NOTICE: TG_relatts: {{} i v}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test_view
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: {INSTEAD OF}
+NOTICE: args: {24 {skidoo view}}
+update trigger_test set v = 'update', test_skip=true where i = 1;
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+NOTICE: SKIPPING OPERATION UPDATE
+update trigger_test set v = 'update' where i = 1;
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+NOTICE: NEW: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: update}
+NOTICE: OLD: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: insert}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig
+NOTICE: TG_op: UPDATE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {23 skidoo}
+delete from trigger_test;
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: DELETE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+NOTICE: NEW: {}
+NOTICE: OLD: {i: 1, test_argisnull: f, test_return_null: f, test_skip: f, v: update}
+NOTICE: TG_level: ROW
+NOTICE: TG_name: show_trigger_data_trig
+NOTICE: TG_op: DELETE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {23 skidoo}
+truncate trigger_test;
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: TRUNCATE
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+-- should error
+insert into trigger_test(test_argisnull) values(true);
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+ERROR: argisnull cannot be used in triggers
+-- should error
+insert into trigger_test(test_return_null) values(true);
+NOTICE: NEW: {}
+NOTICE: OLD: {}
+NOTICE: TG_level: STATEMENT
+NOTICE: TG_name: statement_trigger
+NOTICE: TG_op: INSERT
+NOTICE: TG_relatts: {{} i v {} test_skip test_return_null test_argisnull}
+NOTICE: TG_relid: bogus:12345
+NOTICE: TG_table_name: trigger_test
+NOTICE: TG_table_schema: public
+NOTICE: TG_when: BEFORE
+NOTICE: args: {42 {statement trigger}}
+ERROR: return_null cannot be used in triggers
+-- test transition table visibility
+create table transition_table_test (id int, name text);
+insert into transition_table_test values (1, 'a');
+create function transition_table_test_f() returns trigger language pltcl as
+$$
+ spi_exec -array C "SELECT id, name FROM old_table" {
+ elog INFO "old: $C(id) -> $C(name)"
+ }
+ spi_exec -array C "SELECT id, name FROM new_table" {
+ elog INFO "new: $C(id) -> $C(name)"
+ }
+ return OK
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+update transition_table_test set name = 'b';
+INFO: old: 1 -> a
+INFO: new: 1 -> b
+drop table transition_table_test;
+drop function transition_table_test_f();
+-- dealing with generated columns
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE pltcl
+AS $$
+# not allowed
+set NEW(j) 5
+return [array get NEW]
+$$;
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+ERROR: cannot set generated column "j"
+SELECT * FROM trigger_test_generated;
+ i | j
+---+---
+(0 rows)
+
diff --git a/src/pl/tcl/expected/pltcl_unicode.out b/src/pl/tcl/expected/pltcl_unicode.out
new file mode 100644
index 0000000..eea7d70
--- /dev/null
+++ b/src/pl/tcl/expected/pltcl_unicode.out
@@ -0,0 +1,45 @@
+--
+-- Unicode handling
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+--
+SET client_encoding TO UTF8;
+CREATE TABLE unicode_test (
+ testvalue text NOT NULL
+);
+CREATE FUNCTION unicode_return() RETURNS text AS $$
+ return "\xA0"
+$$ LANGUAGE pltcl;
+CREATE FUNCTION unicode_trigger() RETURNS trigger AS $$
+ set NEW(testvalue) "\xA0"
+ return [array get NEW]
+$$ LANGUAGE pltcl;
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+ FOR EACH ROW EXECUTE PROCEDURE unicode_trigger();
+CREATE FUNCTION unicode_plan1() RETURNS text AS $$
+ set plan [ spi_prepare {SELECT $1 AS testvalue} [ list "text" ] ]
+ spi_execp $plan [ list "\xA0" ]
+ return $testvalue
+$$ LANGUAGE pltcl;
+SELECT unicode_return();
+ unicode_return
+----------------
+  
+(1 row)
+
+INSERT INTO unicode_test (testvalue) VALUES ('test');
+SELECT * FROM unicode_test;
+ testvalue
+-----------
+  
+(1 row)
+
+SELECT unicode_plan1();
+ unicode_plan1
+---------------
+  
+(1 row)
+
diff --git a/src/pl/tcl/generate-pltclerrcodes.pl b/src/pl/tcl/generate-pltclerrcodes.pl
new file mode 100644
index 0000000..e2aeefa
--- /dev/null
+++ b/src/pl/tcl/generate-pltclerrcodes.pl
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+#
+# Generate the pltclerrcodes.h header from errcodes.txt
+# Copyright (c) 2000-2022, PostgreSQL Global Development Group
+
+use strict;
+use warnings;
+
+print
+ "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n";
+print "/* there is deliberately not an #ifndef PLTCLERRCODES_H here */\n";
+
+open my $errcodes, '<', $ARGV[0] or die;
+
+while (<$errcodes>)
+{
+ chomp;
+
+ # Skip comments
+ next if /^#/;
+ next if /^\s*$/;
+
+ # Skip section headers
+ next if /^Section:/;
+
+ die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/;
+
+ (my $sqlstate, my $type, my $errcode_macro, my $condition_name) =
+ ($1, $2, $3, $4);
+
+ # Skip non-errors
+ next unless $type eq 'E';
+
+ # Skip lines without PL/pgSQL condition names
+ next unless defined($condition_name);
+
+ print "\n{\n\t\"$condition_name\", $errcode_macro\n},\n";
+}
+
+close $errcodes;
diff --git a/src/pl/tcl/nls.mk b/src/pl/tcl/nls.mk
new file mode 100644
index 0000000..5136d7f
--- /dev/null
+++ b/src/pl/tcl/nls.mk
@@ -0,0 +1,6 @@
+# src/pl/tcl/nls.mk
+CATALOG_NAME = pltcl
+AVAIL_LANGUAGES = cs de el es fr it ja ka ko pl pt_BR ru sv tr uk vi zh_CN
+GETTEXT_FILES = pltcl.c
+GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS)
+GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS)
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
new file mode 100644
index 0000000..2ed2b92
--- /dev/null
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -0,0 +1,12 @@
+/* src/pl/tcl/pltcl--1.0.sql */
+
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE pltcl
+ HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;
+
+COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c
new file mode 100644
index 0000000..11f1ff1
--- /dev/null
+++ b/src/pl/tcl/pltcl.c
@@ -0,0 +1,3291 @@
+/**********************************************************************
+ * pltcl.c - PostgreSQL support for Tcl as
+ * procedural language (PL)
+ *
+ * src/pl/tcl/pltcl.c
+ *
+ **********************************************************************/
+
+#include "postgres.h"
+
+#include <tcl.h>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "access/htup_details.h"
+#include "access/xact.h"
+#include "catalog/objectaccess.h"
+#include "catalog/pg_proc.h"
+#include "catalog/pg_type.h"
+#include "commands/event_trigger.h"
+#include "commands/trigger.h"
+#include "executor/spi.h"
+#include "fmgr.h"
+#include "funcapi.h"
+#include "mb/pg_wchar.h"
+#include "miscadmin.h"
+#include "nodes/makefuncs.h"
+#include "parser/parse_func.h"
+#include "parser/parse_type.h"
+#include "pgstat.h"
+#include "tcop/tcopprot.h"
+#include "utils/acl.h"
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/regproc.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+
+PG_MODULE_MAGIC;
+
+#define HAVE_TCL_VERSION(maj,min) \
+ ((TCL_MAJOR_VERSION > maj) || \
+ (TCL_MAJOR_VERSION == maj && TCL_MINOR_VERSION >= min))
+
+/* Insist on Tcl >= 8.4 */
+#if !HAVE_TCL_VERSION(8,4)
+#error PostgreSQL only supports Tcl 8.4 or later.
+#endif
+
+/* Hack to deal with Tcl 8.6 const-ification without losing compatibility */
+#ifndef CONST86
+#define CONST86
+#endif
+
+/* define our text domain for translations */
+#undef TEXTDOMAIN
+#define TEXTDOMAIN PG_TEXTDOMAIN("pltcl")
+
+
+/*
+ * Support for converting between UTF8 (which is what all strings going into
+ * or out of Tcl should be) and the database encoding.
+ *
+ * If you just use utf_u2e() or utf_e2u() directly, they will leak some
+ * palloc'd space when doing a conversion. This is not worth worrying about
+ * if it only happens, say, once per PL/Tcl function call. If it does seem
+ * worth worrying about, use the wrapper macros.
+ */
+
+static inline char *
+utf_u2e(const char *src)
+{
+ return pg_any_to_server(src, strlen(src), PG_UTF8);
+}
+
+static inline char *
+utf_e2u(const char *src)
+{
+ return pg_server_to_any(src, strlen(src), PG_UTF8);
+}
+
+#define UTF_BEGIN \
+ do { \
+ const char *_pltcl_utf_src = NULL; \
+ char *_pltcl_utf_dst = NULL
+
+#define UTF_END \
+ if (_pltcl_utf_src != (const char *) _pltcl_utf_dst) \
+ pfree(_pltcl_utf_dst); \
+ } while (0)
+
+#define UTF_U2E(x) \
+ (_pltcl_utf_dst = utf_u2e(_pltcl_utf_src = (x)))
+
+#define UTF_E2U(x) \
+ (_pltcl_utf_dst = utf_e2u(_pltcl_utf_src = (x)))
+
+
+/**********************************************************************
+ * Information associated with a Tcl interpreter. We have one interpreter
+ * that is used for all pltclu (untrusted) functions. For pltcl (trusted)
+ * functions, there is a separate interpreter for each effective SQL userid.
+ * (This is needed to ensure that an unprivileged user can't inject Tcl code
+ * that'll be executed with the privileges of some other SQL user.)
+ *
+ * The pltcl_interp_desc structs are kept in a Postgres hash table indexed
+ * by userid OID, with OID 0 used for the single untrusted interpreter.
+ **********************************************************************/
+typedef struct pltcl_interp_desc
+{
+ Oid user_id; /* Hash key (must be first!) */
+ Tcl_Interp *interp; /* The interpreter */
+ Tcl_HashTable query_hash; /* pltcl_query_desc structs */
+} pltcl_interp_desc;
+
+
+/**********************************************************************
+ * The information we cache about loaded procedures
+ *
+ * The pltcl_proc_desc struct itself, as well as all subsidiary data,
+ * is stored in the memory context identified by the fn_cxt field.
+ * We can reclaim all the data by deleting that context, and should do so
+ * when the fn_refcount goes to zero. (But note that we do not bother
+ * trying to clean up Tcl's copy of the procedure definition: it's Tcl's
+ * problem to manage its memory when we replace a proc definition. We do
+ * not clean up pltcl_proc_descs when a pg_proc row is deleted, only when
+ * it is updated, and the same policy applies to Tcl's copy as well.)
+ *
+ * Note that the data in this struct is shared across all active calls;
+ * nothing except the fn_refcount should be changed by a call instance.
+ **********************************************************************/
+typedef struct pltcl_proc_desc
+{
+ char *user_proname; /* user's name (from pg_proc.proname) */
+ char *internal_proname; /* Tcl name (based on function OID) */
+ MemoryContext fn_cxt; /* memory context for this procedure */
+ unsigned long fn_refcount; /* number of active references */
+ TransactionId fn_xmin; /* xmin of pg_proc row */
+ ItemPointerData fn_tid; /* TID of pg_proc row */
+ bool fn_readonly; /* is function readonly? */
+ bool lanpltrusted; /* is it pltcl (vs. pltclu)? */
+ pltcl_interp_desc *interp_desc; /* interpreter to use */
+ Oid result_typid; /* OID of fn's result type */
+ FmgrInfo result_in_func; /* input function for fn's result type */
+ Oid result_typioparam; /* param to pass to same */
+ bool fn_retisset; /* true if function returns a set */
+ bool fn_retistuple; /* true if function returns composite */
+ bool fn_retisdomain; /* true if function returns domain */
+ void *domain_info; /* opaque cache for domain checks */
+ int nargs; /* number of arguments */
+ /* these arrays have nargs entries: */
+ FmgrInfo *arg_out_func; /* output fns for arg types */
+ bool *arg_is_rowtype; /* is each arg composite? */
+} pltcl_proc_desc;
+
+
+/**********************************************************************
+ * The information we cache about prepared and saved plans
+ **********************************************************************/
+typedef struct pltcl_query_desc
+{
+ char qname[20];
+ SPIPlanPtr plan;
+ int nargs;
+ Oid *argtypes;
+ FmgrInfo *arginfuncs;
+ Oid *argtypioparams;
+} pltcl_query_desc;
+
+
+/**********************************************************************
+ * For speedy lookup, we maintain a hash table mapping from
+ * function OID + trigger flag + user OID to pltcl_proc_desc pointers.
+ * The reason the pltcl_proc_desc struct isn't directly part of the hash
+ * entry is to simplify recovery from errors during compile_pltcl_function.
+ *
+ * Note: if the same function is called by multiple userIDs within a session,
+ * there will be a separate pltcl_proc_desc entry for each userID in the case
+ * of pltcl functions, but only one entry for pltclu functions, because we
+ * set user_id = 0 for that case.
+ **********************************************************************/
+typedef struct pltcl_proc_key
+{
+ Oid proc_id; /* Function OID */
+
+ /*
+ * is_trigger is really a bool, but declare as Oid to ensure this struct
+ * contains no padding
+ */
+ Oid is_trigger; /* is it a trigger function? */
+ Oid user_id; /* User calling the function, or 0 */
+} pltcl_proc_key;
+
+typedef struct pltcl_proc_ptr
+{
+ pltcl_proc_key proc_key; /* Hash key (must be first!) */
+ pltcl_proc_desc *proc_ptr;
+} pltcl_proc_ptr;
+
+
+/**********************************************************************
+ * Per-call state
+ **********************************************************************/
+typedef struct pltcl_call_state
+{
+ /* Call info struct, or NULL in a trigger */
+ FunctionCallInfo fcinfo;
+
+ /* Trigger data, if we're in a normal (not event) trigger; else NULL */
+ TriggerData *trigdata;
+
+ /* Function we're executing (NULL if not yet identified) */
+ pltcl_proc_desc *prodesc;
+
+ /*
+ * Information for SRFs and functions returning composite types.
+ * ret_tupdesc and attinmeta are set up if either fn_retistuple or
+ * fn_retisset, since even a scalar-returning SRF needs a tuplestore.
+ */
+ TupleDesc ret_tupdesc; /* return rowtype, if retistuple or retisset */
+ AttInMetadata *attinmeta; /* metadata for building tuples of that type */
+
+ ReturnSetInfo *rsi; /* passed-in ReturnSetInfo, if any */
+ Tuplestorestate *tuple_store; /* SRFs accumulate result here */
+ MemoryContext tuple_store_cxt; /* context and resowner for tuplestore */
+ ResourceOwner tuple_store_owner;
+} pltcl_call_state;
+
+
+/**********************************************************************
+ * Global data
+ **********************************************************************/
+static char *pltcl_start_proc = NULL;
+static char *pltclu_start_proc = NULL;
+static bool pltcl_pm_init_done = false;
+static Tcl_Interp *pltcl_hold_interp = NULL;
+static HTAB *pltcl_interp_htab = NULL;
+static HTAB *pltcl_proc_htab = NULL;
+
+/* this is saved and restored by pltcl_handler */
+static pltcl_call_state *pltcl_current_call_state = NULL;
+
+/**********************************************************************
+ * Lookup table for SQLSTATE condition names
+ **********************************************************************/
+typedef struct
+{
+ const char *label;
+ int sqlerrstate;
+} TclExceptionNameMap;
+
+static const TclExceptionNameMap exception_name_map[] = {
+#include "pltclerrcodes.h" /* pgrminclude ignore */
+ {NULL, 0}
+};
+
+/**********************************************************************
+ * Forward declarations
+ **********************************************************************/
+void _PG_init(void);
+
+static void pltcl_init_interp(pltcl_interp_desc *interp_desc,
+ Oid prolang, bool pltrusted);
+static pltcl_interp_desc *pltcl_fetch_interp(Oid prolang, bool pltrusted);
+static void call_pltcl_start_proc(Oid prolang, bool pltrusted);
+static void start_proc_error_callback(void *arg);
+
+static Datum pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted);
+
+static Datum pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted);
+static HeapTuple pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted);
+static void pltcl_event_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted);
+
+static void throw_tcl_error(Tcl_Interp *interp, const char *proname);
+
+static pltcl_proc_desc *compile_pltcl_function(Oid fn_oid, Oid tgreloid,
+ bool is_event_trigger,
+ bool pltrusted);
+
+static int pltcl_elog(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static void pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata);
+static const char *pltcl_get_condition_name(int sqlstate);
+static int pltcl_quote(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_returnnext(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_process_SPI_result(Tcl_Interp *interp,
+ const char *arrayname,
+ Tcl_Obj *loop_body,
+ int spi_rc,
+ SPITupleTable *tuptable,
+ uint64 ntuples);
+static int pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_subtransaction(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_commit(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+static int pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[]);
+
+static void pltcl_subtrans_begin(MemoryContext oldcontext,
+ ResourceOwner oldowner);
+static void pltcl_subtrans_commit(MemoryContext oldcontext,
+ ResourceOwner oldowner);
+static void pltcl_subtrans_abort(Tcl_Interp *interp,
+ MemoryContext oldcontext,
+ ResourceOwner oldowner);
+
+static void pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
+ uint64 tupno, HeapTuple tuple, TupleDesc tupdesc);
+static Tcl_Obj *pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated);
+static HeapTuple pltcl_build_tuple_result(Tcl_Interp *interp,
+ Tcl_Obj **kvObjv, int kvObjc,
+ pltcl_call_state *call_state);
+static void pltcl_init_tuple_store(pltcl_call_state *call_state);
+
+
+/*
+ * Hack to override Tcl's builtin Notifier subsystem. This prevents the
+ * backend from becoming multithreaded, which breaks all sorts of things.
+ * That happens in the default version of Tcl_InitNotifier if the TCL library
+ * has been compiled with multithreading support (i.e. when TCL_THREADS is
+ * defined under Unix, and in all cases under Windows).
+ * It's okay to disable the notifier because we never enter the Tcl event loop
+ * from Postgres, so the notifier capabilities are initialized, but never
+ * used. Only InitNotifier and DeleteFileHandler ever seem to get called
+ * within Postgres, but we implement all the functions for completeness.
+ */
+static ClientData
+pltcl_InitNotifier(void)
+{
+ static int fakeThreadKey; /* To give valid address for ClientData */
+
+ return (ClientData) &(fakeThreadKey);
+}
+
+static void
+pltcl_FinalizeNotifier(ClientData clientData)
+{
+}
+
+static void
+pltcl_SetTimer(CONST86 Tcl_Time *timePtr)
+{
+}
+
+static void
+pltcl_AlertNotifier(ClientData clientData)
+{
+}
+
+static void
+pltcl_CreateFileHandler(int fd, int mask,
+ Tcl_FileProc *proc, ClientData clientData)
+{
+}
+
+static void
+pltcl_DeleteFileHandler(int fd)
+{
+}
+
+static void
+pltcl_ServiceModeHook(int mode)
+{
+}
+
+static int
+pltcl_WaitForEvent(CONST86 Tcl_Time *timePtr)
+{
+ return 0;
+}
+
+
+/*
+ * _PG_init() - library load-time initialization
+ *
+ * DO NOT make this static nor change its name!
+ *
+ * The work done here must be safe to do in the postmaster process,
+ * in case the pltcl library is preloaded in the postmaster.
+ */
+void
+_PG_init(void)
+{
+ Tcl_NotifierProcs notifier;
+ HASHCTL hash_ctl;
+
+ /* Be sure we do initialization only once (should be redundant now) */
+ if (pltcl_pm_init_done)
+ return;
+
+ pg_bindtextdomain(TEXTDOMAIN);
+
+#ifdef WIN32
+ /* Required on win32 to prevent error loading init.tcl */
+ Tcl_FindExecutable("");
+#endif
+
+ /*
+ * Override the functions in the Notifier subsystem. See comments above.
+ */
+ notifier.setTimerProc = pltcl_SetTimer;
+ notifier.waitForEventProc = pltcl_WaitForEvent;
+ notifier.createFileHandlerProc = pltcl_CreateFileHandler;
+ notifier.deleteFileHandlerProc = pltcl_DeleteFileHandler;
+ notifier.initNotifierProc = pltcl_InitNotifier;
+ notifier.finalizeNotifierProc = pltcl_FinalizeNotifier;
+ notifier.alertNotifierProc = pltcl_AlertNotifier;
+ notifier.serviceModeHookProc = pltcl_ServiceModeHook;
+ Tcl_SetNotifier(&notifier);
+
+ /************************************************************
+ * Create the dummy hold interpreter to prevent close of
+ * stdout and stderr on DeleteInterp
+ ************************************************************/
+ if ((pltcl_hold_interp = Tcl_CreateInterp()) == NULL)
+ elog(ERROR, "could not create dummy Tcl interpreter");
+ if (Tcl_Init(pltcl_hold_interp) == TCL_ERROR)
+ elog(ERROR, "could not initialize dummy Tcl interpreter");
+
+ /************************************************************
+ * Create the hash table for working interpreters
+ ************************************************************/
+ hash_ctl.keysize = sizeof(Oid);
+ hash_ctl.entrysize = sizeof(pltcl_interp_desc);
+ pltcl_interp_htab = hash_create("PL/Tcl interpreters",
+ 8,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+
+ /************************************************************
+ * Create the hash table for function lookup
+ ************************************************************/
+ hash_ctl.keysize = sizeof(pltcl_proc_key);
+ hash_ctl.entrysize = sizeof(pltcl_proc_ptr);
+ pltcl_proc_htab = hash_create("PL/Tcl functions",
+ 100,
+ &hash_ctl,
+ HASH_ELEM | HASH_BLOBS);
+
+ /************************************************************
+ * Define PL/Tcl's custom GUCs
+ ************************************************************/
+ DefineCustomStringVariable("pltcl.start_proc",
+ gettext_noop("PL/Tcl function to call once when pltcl is first used."),
+ NULL,
+ &pltcl_start_proc,
+ NULL,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+ DefineCustomStringVariable("pltclu.start_proc",
+ gettext_noop("PL/TclU function to call once when pltclu is first used."),
+ NULL,
+ &pltclu_start_proc,
+ NULL,
+ PGC_SUSET, 0,
+ NULL, NULL, NULL);
+
+ MarkGUCPrefixReserved("pltcl");
+ MarkGUCPrefixReserved("pltclu");
+
+ pltcl_pm_init_done = true;
+}
+
+/**********************************************************************
+ * pltcl_init_interp() - initialize a new Tcl interpreter
+ **********************************************************************/
+static void
+pltcl_init_interp(pltcl_interp_desc *interp_desc, Oid prolang, bool pltrusted)
+{
+ Tcl_Interp *interp;
+ char interpname[32];
+
+ /************************************************************
+ * Create the Tcl interpreter subsidiary to pltcl_hold_interp.
+ * Note: Tcl automatically does Tcl_Init in the untrusted case,
+ * and it's not wanted in the trusted case.
+ ************************************************************/
+ snprintf(interpname, sizeof(interpname), "subsidiary_%u", interp_desc->user_id);
+ if ((interp = Tcl_CreateSlave(pltcl_hold_interp, interpname,
+ pltrusted ? 1 : 0)) == NULL)
+ elog(ERROR, "could not create subsidiary Tcl interpreter");
+
+ /************************************************************
+ * Initialize the query hash table associated with interpreter
+ ************************************************************/
+ Tcl_InitHashTable(&interp_desc->query_hash, TCL_STRING_KEYS);
+
+ /************************************************************
+ * Install the commands for SPI support in the interpreter
+ ************************************************************/
+ Tcl_CreateObjCommand(interp, "elog",
+ pltcl_elog, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "quote",
+ pltcl_quote, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "argisnull",
+ pltcl_argisnull, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "return_null",
+ pltcl_returnnull, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "return_next",
+ pltcl_returnnext, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "spi_exec",
+ pltcl_SPI_execute, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "spi_prepare",
+ pltcl_SPI_prepare, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "spi_execp",
+ pltcl_SPI_execute_plan, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "subtransaction",
+ pltcl_subtransaction, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "commit",
+ pltcl_commit, NULL, NULL);
+ Tcl_CreateObjCommand(interp, "rollback",
+ pltcl_rollback, NULL, NULL);
+
+ /************************************************************
+ * Call the appropriate start_proc, if there is one.
+ *
+ * We must set interp_desc->interp before the call, else the start_proc
+ * won't find the interpreter it's supposed to use. But, if the
+ * start_proc fails, we want to abandon use of the interpreter.
+ ************************************************************/
+ PG_TRY();
+ {
+ interp_desc->interp = interp;
+ call_pltcl_start_proc(prolang, pltrusted);
+ }
+ PG_CATCH();
+ {
+ interp_desc->interp = NULL;
+ Tcl_DeleteInterp(interp);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+}
+
+/**********************************************************************
+ * pltcl_fetch_interp() - fetch the Tcl interpreter to use for a function
+ *
+ * This also takes care of any on-first-use initialization required.
+ **********************************************************************/
+static pltcl_interp_desc *
+pltcl_fetch_interp(Oid prolang, bool pltrusted)
+{
+ Oid user_id;
+ pltcl_interp_desc *interp_desc;
+ bool found;
+
+ /* Find or create the interpreter hashtable entry for this userid */
+ if (pltrusted)
+ user_id = GetUserId();
+ else
+ user_id = InvalidOid;
+
+ interp_desc = hash_search(pltcl_interp_htab, &user_id,
+ HASH_ENTER,
+ &found);
+ if (!found)
+ interp_desc->interp = NULL;
+
+ /* If we haven't yet successfully made an interpreter, try to do that */
+ if (!interp_desc->interp)
+ pltcl_init_interp(interp_desc, prolang, pltrusted);
+
+ return interp_desc;
+}
+
+
+/**********************************************************************
+ * call_pltcl_start_proc() - Call user-defined initialization proc, if any
+ **********************************************************************/
+static void
+call_pltcl_start_proc(Oid prolang, bool pltrusted)
+{
+ LOCAL_FCINFO(fcinfo, 0);
+ char *start_proc;
+ const char *gucname;
+ ErrorContextCallback errcallback;
+ List *namelist;
+ Oid procOid;
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ AclResult aclresult;
+ FmgrInfo finfo;
+ PgStat_FunctionCallUsage fcusage;
+
+ /* select appropriate GUC */
+ start_proc = pltrusted ? pltcl_start_proc : pltclu_start_proc;
+ gucname = pltrusted ? "pltcl.start_proc" : "pltclu.start_proc";
+
+ /* Nothing to do if it's empty or unset */
+ if (start_proc == NULL || start_proc[0] == '\0')
+ return;
+
+ /* Set up errcontext callback to make errors more helpful */
+ errcallback.callback = start_proc_error_callback;
+ errcallback.arg = unconstify(char *, gucname);
+ errcallback.previous = error_context_stack;
+ error_context_stack = &errcallback;
+
+ /* Parse possibly-qualified identifier and look up the function */
+ namelist = stringToQualifiedNameList(start_proc);
+ procOid = LookupFuncName(namelist, 0, NULL, false);
+
+ /* Current user must have permission to call function */
+ aclresult = pg_proc_aclcheck(procOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, OBJECT_FUNCTION, start_proc);
+
+ /* Get the function's pg_proc entry */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(procOid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", procOid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /* It must be same language as the function we're currently calling */
+ if (procStruct->prolang != prolang)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("function \"%s\" is in the wrong language",
+ start_proc)));
+
+ /*
+ * It must not be SECURITY DEFINER, either. This together with the
+ * language match check ensures that the function will execute in the same
+ * Tcl interpreter we just finished initializing.
+ */
+ if (procStruct->prosecdef)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("function \"%s\" must not be SECURITY DEFINER",
+ start_proc)));
+
+ /* A-OK */
+ ReleaseSysCache(procTup);
+
+ /*
+ * Call the function using the normal SQL function call mechanism. We
+ * could perhaps cheat and jump directly to pltcl_handler(), but it seems
+ * better to do it this way so that the call is exposed to, eg, call
+ * statistics collection.
+ */
+ InvokeFunctionExecuteHook(procOid);
+ fmgr_info(procOid, &finfo);
+ InitFunctionCallInfoData(*fcinfo, &finfo,
+ 0,
+ InvalidOid, NULL, NULL);
+ pgstat_init_function_usage(fcinfo, &fcusage);
+ (void) FunctionCallInvoke(fcinfo);
+ pgstat_end_function_usage(&fcusage, true);
+
+ /* Pop the error context stack */
+ error_context_stack = errcallback.previous;
+}
+
+/*
+ * Error context callback for errors occurring during start_proc processing.
+ */
+static void
+start_proc_error_callback(void *arg)
+{
+ const char *gucname = (const char *) arg;
+
+ /* translator: %s is "pltcl.start_proc" or "pltclu.start_proc" */
+ errcontext("processing %s parameter", gucname);
+}
+
+
+/**********************************************************************
+ * pltcl_call_handler - This is the only visible function
+ * of the PL interpreter. The PostgreSQL
+ * function manager and trigger manager
+ * call this function for execution of
+ * PL/Tcl procedures.
+ **********************************************************************/
+PG_FUNCTION_INFO_V1(pltcl_call_handler);
+
+/* keep non-static */
+Datum
+pltcl_call_handler(PG_FUNCTION_ARGS)
+{
+ return pltcl_handler(fcinfo, true);
+}
+
+/*
+ * Alternative handler for unsafe functions
+ */
+PG_FUNCTION_INFO_V1(pltclu_call_handler);
+
+/* keep non-static */
+Datum
+pltclu_call_handler(PG_FUNCTION_ARGS)
+{
+ return pltcl_handler(fcinfo, false);
+}
+
+
+/**********************************************************************
+ * pltcl_handler() - Handler for function and trigger calls, for
+ * both trusted and untrusted interpreters.
+ **********************************************************************/
+static Datum
+pltcl_handler(PG_FUNCTION_ARGS, bool pltrusted)
+{
+ Datum retval = (Datum) 0;
+ pltcl_call_state current_call_state;
+ pltcl_call_state *save_call_state;
+
+ /*
+ * Initialize current_call_state to nulls/zeroes; in particular, set its
+ * prodesc pointer to null. Anything that sets it non-null should
+ * increase the prodesc's fn_refcount at the same time. We'll decrease
+ * the refcount, and then delete the prodesc if it's no longer referenced,
+ * on the way out of this function. This ensures that prodescs live as
+ * long as needed even if somebody replaces the originating pg_proc row
+ * while they're executing.
+ */
+ memset(&current_call_state, 0, sizeof(current_call_state));
+
+ /*
+ * Ensure that static pointer is saved/restored properly
+ */
+ save_call_state = pltcl_current_call_state;
+ pltcl_current_call_state = &current_call_state;
+
+ PG_TRY();
+ {
+ /*
+ * Determine if called as function or trigger and call appropriate
+ * subhandler
+ */
+ if (CALLED_AS_TRIGGER(fcinfo))
+ {
+ /* invoke the trigger handler */
+ retval = PointerGetDatum(pltcl_trigger_handler(fcinfo,
+ &current_call_state,
+ pltrusted));
+ }
+ else if (CALLED_AS_EVENT_TRIGGER(fcinfo))
+ {
+ /* invoke the event trigger handler */
+ pltcl_event_trigger_handler(fcinfo, &current_call_state, pltrusted);
+ retval = (Datum) 0;
+ }
+ else
+ {
+ /* invoke the regular function handler */
+ current_call_state.fcinfo = fcinfo;
+ retval = pltcl_func_handler(fcinfo, &current_call_state, pltrusted);
+ }
+ }
+ PG_FINALLY();
+ {
+ /* Restore static pointer, then clean up the prodesc refcount if any */
+ /*
+ * (We're being paranoid in case an error is thrown in context
+ * deletion)
+ */
+ pltcl_current_call_state = save_call_state;
+ if (current_call_state.prodesc != NULL)
+ {
+ Assert(current_call_state.prodesc->fn_refcount > 0);
+ if (--current_call_state.prodesc->fn_refcount == 0)
+ MemoryContextDelete(current_call_state.prodesc->fn_cxt);
+ }
+ }
+ PG_END_TRY();
+
+ return retval;
+}
+
+
+/**********************************************************************
+ * pltcl_func_handler() - Handler for regular function calls
+ **********************************************************************/
+static Datum
+pltcl_func_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted)
+{
+ bool nonatomic;
+ pltcl_proc_desc *prodesc;
+ Tcl_Interp *volatile interp;
+ Tcl_Obj *tcl_cmd;
+ int i;
+ int tcl_rc;
+ Datum retval;
+
+ nonatomic = fcinfo->context &&
+ IsA(fcinfo->context, CallContext) &&
+ !castNode(CallContext, fcinfo->context)->atomic;
+
+ /* Connect to SPI manager */
+ if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Find or compile the function */
+ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid, InvalidOid,
+ false, pltrusted);
+
+ call_state->prodesc = prodesc;
+ prodesc->fn_refcount++;
+
+ interp = prodesc->interp_desc->interp;
+
+ /*
+ * If we're a SRF, check caller can handle materialize mode, and save
+ * relevant info into call_state. We must ensure that the returned
+ * tuplestore is owned by the caller's context, even if we first create it
+ * inside a subtransaction.
+ */
+ if (prodesc->fn_retisset)
+ {
+ ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo;
+
+ if (!rsi || !IsA(rsi, ReturnSetInfo))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("set-valued function called in context that cannot accept a set")));
+
+ if (!(rsi->allowedModes & SFRM_Materialize))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("materialize mode required, but it is not allowed in this context")));
+
+ call_state->rsi = rsi;
+ call_state->tuple_store_cxt = rsi->econtext->ecxt_per_query_memory;
+ call_state->tuple_store_owner = CurrentResourceOwner;
+ }
+
+ /************************************************************
+ * Create the tcl command to call the internal
+ * proc in the Tcl interpreter
+ ************************************************************/
+ tcl_cmd = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(prodesc->internal_proname, -1));
+
+ /* We hold a refcount on tcl_cmd just to be sure it stays around */
+ Tcl_IncrRefCount(tcl_cmd);
+
+ /************************************************************
+ * Add all call arguments to the command
+ ************************************************************/
+ PG_TRY();
+ {
+ for (i = 0; i < prodesc->nargs; i++)
+ {
+ if (prodesc->arg_is_rowtype[i])
+ {
+ /**************************************************
+ * For tuple values, add a list for 'array set ...'
+ **************************************************/
+ if (fcinfo->args[i].isnull)
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+ else
+ {
+ HeapTupleHeader td;
+ Oid tupType;
+ int32 tupTypmod;
+ TupleDesc tupdesc;
+ HeapTupleData tmptup;
+ Tcl_Obj *list_tmp;
+
+ td = DatumGetHeapTupleHeader(fcinfo->args[i].value);
+ /* Extract rowtype info and find a tupdesc */
+ tupType = HeapTupleHeaderGetTypeId(td);
+ tupTypmod = HeapTupleHeaderGetTypMod(td);
+ tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod);
+ /* Build a temporary HeapTuple control structure */
+ tmptup.t_len = HeapTupleHeaderGetDatumLength(td);
+ tmptup.t_data = td;
+
+ list_tmp = pltcl_build_tuple_argument(&tmptup, tupdesc, true);
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, list_tmp);
+
+ ReleaseTupleDesc(tupdesc);
+ }
+ }
+ else
+ {
+ /**************************************************
+ * Single values are added as string element
+ * of their external representation
+ **************************************************/
+ if (fcinfo->args[i].isnull)
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+ else
+ {
+ char *tmp;
+
+ tmp = OutputFunctionCall(&prodesc->arg_out_func[i],
+ fcinfo->args[i].value);
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(UTF_E2U(tmp), -1));
+ UTF_END;
+ pfree(tmp);
+ }
+ }
+ }
+ }
+ PG_CATCH();
+ {
+ /* Release refcount to free tcl_cmd */
+ Tcl_DecrRefCount(tcl_cmd);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /************************************************************
+ * Call the Tcl function
+ *
+ * We assume no PG error can be thrown directly from this call.
+ ************************************************************/
+ tcl_rc = Tcl_EvalObjEx(interp, tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));
+
+ /* Release refcount to free tcl_cmd (and all subsidiary objects) */
+ Tcl_DecrRefCount(tcl_cmd);
+
+ /************************************************************
+ * Check for errors reported by Tcl.
+ ************************************************************/
+ if (tcl_rc != TCL_OK)
+ throw_tcl_error(interp, prodesc->user_proname);
+
+ /************************************************************
+ * Disconnect from SPI manager and then create the return
+ * value datum (if the input function does a palloc for it
+ * this must not be allocated in the SPI memory context
+ * because SPI_finish would free it). But don't try to call
+ * the result_in_func if we've been told to return a NULL;
+ * the Tcl result may not be a valid value of the result type
+ * in that case.
+ ************************************************************/
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ if (prodesc->fn_retisset)
+ {
+ ReturnSetInfo *rsi = call_state->rsi;
+
+ /* We already checked this is OK */
+ rsi->returnMode = SFRM_Materialize;
+
+ /* If we produced any tuples, send back the result */
+ if (call_state->tuple_store)
+ {
+ rsi->setResult = call_state->tuple_store;
+ if (call_state->ret_tupdesc)
+ {
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(call_state->tuple_store_cxt);
+ rsi->setDesc = CreateTupleDescCopy(call_state->ret_tupdesc);
+ MemoryContextSwitchTo(oldcxt);
+ }
+ }
+ retval = (Datum) 0;
+ fcinfo->isnull = true;
+ }
+ else if (fcinfo->isnull)
+ {
+ retval = InputFunctionCall(&prodesc->result_in_func,
+ NULL,
+ prodesc->result_typioparam,
+ -1);
+ }
+ else if (prodesc->fn_retistuple)
+ {
+ TupleDesc td;
+ HeapTuple tup;
+ Tcl_Obj *resultObj;
+ Tcl_Obj **resultObjv;
+ int resultObjc;
+
+ /*
+ * Set up data about result type. XXX it's tempting to consider
+ * caching this in the prodesc, in the common case where the rowtype
+ * is determined by the function not the calling query. But we'd have
+ * to be able to deal with ADD/DROP/ALTER COLUMN events when the
+ * result type is a named composite type, so it's not exactly trivial.
+ * Maybe worth improving someday.
+ */
+ switch (get_call_result_type(fcinfo, NULL, &td))
+ {
+ case TYPEFUNC_COMPOSITE:
+ /* success */
+ break;
+ case TYPEFUNC_COMPOSITE_DOMAIN:
+ Assert(prodesc->fn_retisdomain);
+ 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;
+ }
+
+ Assert(!call_state->ret_tupdesc);
+ Assert(!call_state->attinmeta);
+ call_state->ret_tupdesc = td;
+ call_state->attinmeta = TupleDescGetAttInMetadata(td);
+
+ /* Convert function result to tuple */
+ resultObj = Tcl_GetObjResult(interp);
+ if (Tcl_ListObjGetElements(interp, resultObj, &resultObjc, &resultObjv) == TCL_ERROR)
+ throw_tcl_error(interp, prodesc->user_proname);
+
+ tup = pltcl_build_tuple_result(interp, resultObjv, resultObjc,
+ call_state);
+ retval = HeapTupleGetDatum(tup);
+ }
+ else
+ retval = InputFunctionCall(&prodesc->result_in_func,
+ utf_u2e(Tcl_GetStringResult(interp)),
+ prodesc->result_typioparam,
+ -1);
+
+ return retval;
+}
+
+
+/**********************************************************************
+ * pltcl_trigger_handler() - Handler for trigger calls
+ **********************************************************************/
+static HeapTuple
+pltcl_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted)
+{
+ pltcl_proc_desc *prodesc;
+ Tcl_Interp *volatile interp;
+ TriggerData *trigdata = (TriggerData *) fcinfo->context;
+ char *stroid;
+ TupleDesc tupdesc;
+ volatile HeapTuple rettup;
+ Tcl_Obj *tcl_cmd;
+ Tcl_Obj *tcl_trigtup;
+ int tcl_rc;
+ int i;
+ const char *result;
+ int result_Objc;
+ Tcl_Obj **result_Objv;
+ int rc PG_USED_FOR_ASSERTS_ONLY;
+
+ call_state->trigdata = trigdata;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Make transition tables visible to this SPI connection */
+ rc = SPI_register_trigger_data(trigdata);
+ Assert(rc >= 0);
+
+ /* Find or compile the function */
+ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
+ RelationGetRelid(trigdata->tg_relation),
+ false, /* not an event trigger */
+ pltrusted);
+
+ call_state->prodesc = prodesc;
+ prodesc->fn_refcount++;
+
+ interp = prodesc->interp_desc->interp;
+
+ tupdesc = RelationGetDescr(trigdata->tg_relation);
+
+ /************************************************************
+ * Create the tcl command to call the internal
+ * proc in the interpreter
+ ************************************************************/
+ tcl_cmd = Tcl_NewObj();
+ Tcl_IncrRefCount(tcl_cmd);
+
+ PG_TRY();
+ {
+ /* The procedure name (note this is all ASCII, so no utf_e2u) */
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(prodesc->internal_proname, -1));
+
+ /* The trigger name for argument TG_name */
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(trigdata->tg_trigger->tgname), -1));
+
+ /* The oid of the trigger relation for argument TG_relid */
+ /* Consider not converting to a string for more performance? */
+ stroid = DatumGetCString(DirectFunctionCall1(oidout,
+ ObjectIdGetDatum(trigdata->tg_relation->rd_id)));
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(stroid, -1));
+ pfree(stroid);
+
+ /* The name of the table the trigger is acting on: TG_table_name */
+ stroid = SPI_getrelname(trigdata->tg_relation);
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(stroid), -1));
+ pfree(stroid);
+
+ /* The schema of the table the trigger is acting on: TG_table_schema */
+ stroid = SPI_getnspname(trigdata->tg_relation);
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(stroid), -1));
+ pfree(stroid);
+
+ /* A list of attribute names for argument TG_relatts */
+ tcl_trigtup = Tcl_NewObj();
+ Tcl_ListObjAppendElement(NULL, tcl_trigtup, Tcl_NewObj());
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ if (att->attisdropped)
+ Tcl_ListObjAppendElement(NULL, tcl_trigtup, Tcl_NewObj());
+ else
+ Tcl_ListObjAppendElement(NULL, tcl_trigtup,
+ Tcl_NewStringObj(utf_e2u(NameStr(att->attname)), -1));
+ }
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, tcl_trigtup);
+
+ /* The when part of the event for TG_when */
+ if (TRIGGER_FIRED_BEFORE(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("BEFORE", -1));
+ else if (TRIGGER_FIRED_AFTER(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("AFTER", -1));
+ else if (TRIGGER_FIRED_INSTEAD(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("INSTEAD OF", -1));
+ else
+ elog(ERROR, "unrecognized WHEN tg_event: %u", trigdata->tg_event);
+
+ /* The level part of the event for TG_level */
+ if (TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
+ {
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("ROW", -1));
+
+ /*
+ * Now the command part of the event for TG_op and data for NEW
+ * and OLD
+ *
+ * Note: In BEFORE trigger, stored generated columns are not
+ * computed yet, so don't make them accessible in NEW row.
+ */
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ {
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("INSERT", -1));
+
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ pltcl_build_tuple_argument(trigdata->tg_trigtuple,
+ tupdesc,
+ !TRIGGER_FIRED_BEFORE(trigdata->tg_event)));
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+
+ rettup = trigdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ {
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("DELETE", -1));
+
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ pltcl_build_tuple_argument(trigdata->tg_trigtuple,
+ tupdesc,
+ true));
+
+ rettup = trigdata->tg_trigtuple;
+ }
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ {
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("UPDATE", -1));
+
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ pltcl_build_tuple_argument(trigdata->tg_newtuple,
+ tupdesc,
+ !TRIGGER_FIRED_BEFORE(trigdata->tg_event)));
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ pltcl_build_tuple_argument(trigdata->tg_trigtuple,
+ tupdesc,
+ true));
+
+ rettup = trigdata->tg_newtuple;
+ }
+ else
+ elog(ERROR, "unrecognized OP tg_event: %u", trigdata->tg_event);
+ }
+ else if (TRIGGER_FIRED_FOR_STATEMENT(trigdata->tg_event))
+ {
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("STATEMENT", -1));
+
+ if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("INSERT", -1));
+ else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("DELETE", -1));
+ else if (TRIGGER_FIRED_BY_UPDATE(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("UPDATE", -1));
+ else if (TRIGGER_FIRED_BY_TRUNCATE(trigdata->tg_event))
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj("TRUNCATE", -1));
+ else
+ elog(ERROR, "unrecognized OP tg_event: %u", trigdata->tg_event);
+
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+ Tcl_ListObjAppendElement(NULL, tcl_cmd, Tcl_NewObj());
+
+ rettup = (HeapTuple) NULL;
+ }
+ else
+ elog(ERROR, "unrecognized LEVEL tg_event: %u", trigdata->tg_event);
+
+ /* Finally append the arguments from CREATE TRIGGER */
+ for (i = 0; i < trigdata->tg_trigger->tgnargs; i++)
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(trigdata->tg_trigger->tgargs[i]), -1));
+ }
+ PG_CATCH();
+ {
+ Tcl_DecrRefCount(tcl_cmd);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /************************************************************
+ * Call the Tcl function
+ *
+ * We assume no PG error can be thrown directly from this call.
+ ************************************************************/
+ tcl_rc = Tcl_EvalObjEx(interp, tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));
+
+ /* Release refcount to free tcl_cmd (and all subsidiary objects) */
+ Tcl_DecrRefCount(tcl_cmd);
+
+ /************************************************************
+ * Check for errors reported by Tcl.
+ ************************************************************/
+ if (tcl_rc != TCL_OK)
+ throw_tcl_error(interp, prodesc->user_proname);
+
+ /************************************************************
+ * Exit SPI environment.
+ ************************************************************/
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+
+ /************************************************************
+ * The return value from the procedure might be one of
+ * the magic strings OK or SKIP, or a list from array get.
+ * We can check for OK or SKIP without worrying about encoding.
+ ************************************************************/
+ result = Tcl_GetStringResult(interp);
+
+ if (strcmp(result, "OK") == 0)
+ return rettup;
+ if (strcmp(result, "SKIP") == 0)
+ return (HeapTuple) NULL;
+
+ /************************************************************
+ * Otherwise, the return value should be a column name/value list
+ * specifying the modified tuple to return.
+ ************************************************************/
+ if (Tcl_ListObjGetElements(interp, Tcl_GetObjResult(interp),
+ &result_Objc, &result_Objv) != TCL_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("could not split return value from trigger: %s",
+ utf_u2e(Tcl_GetStringResult(interp)))));
+
+ /* Convert function result to tuple */
+ rettup = pltcl_build_tuple_result(interp, result_Objv, result_Objc,
+ call_state);
+
+ return rettup;
+}
+
+/**********************************************************************
+ * pltcl_event_trigger_handler() - Handler for event trigger calls
+ **********************************************************************/
+static void
+pltcl_event_trigger_handler(PG_FUNCTION_ARGS, pltcl_call_state *call_state,
+ bool pltrusted)
+{
+ pltcl_proc_desc *prodesc;
+ Tcl_Interp *volatile interp;
+ EventTriggerData *tdata = (EventTriggerData *) fcinfo->context;
+ Tcl_Obj *tcl_cmd;
+ int tcl_rc;
+
+ /* Connect to SPI manager */
+ if (SPI_connect() != SPI_OK_CONNECT)
+ elog(ERROR, "could not connect to SPI manager");
+
+ /* Find or compile the function */
+ prodesc = compile_pltcl_function(fcinfo->flinfo->fn_oid,
+ InvalidOid, true, pltrusted);
+
+ call_state->prodesc = prodesc;
+ prodesc->fn_refcount++;
+
+ interp = prodesc->interp_desc->interp;
+
+ /* Create the tcl command and call the internal proc */
+ tcl_cmd = Tcl_NewObj();
+ Tcl_IncrRefCount(tcl_cmd);
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(prodesc->internal_proname, -1));
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(tdata->event), -1));
+ Tcl_ListObjAppendElement(NULL, tcl_cmd,
+ Tcl_NewStringObj(utf_e2u(GetCommandTagName(tdata->tag)),
+ -1));
+
+ tcl_rc = Tcl_EvalObjEx(interp, tcl_cmd, (TCL_EVAL_DIRECT | TCL_EVAL_GLOBAL));
+
+ /* Release refcount to free tcl_cmd (and all subsidiary objects) */
+ Tcl_DecrRefCount(tcl_cmd);
+
+ /* Check for errors reported by Tcl. */
+ if (tcl_rc != TCL_OK)
+ throw_tcl_error(interp, prodesc->user_proname);
+
+ if (SPI_finish() != SPI_OK_FINISH)
+ elog(ERROR, "SPI_finish() failed");
+}
+
+
+/**********************************************************************
+ * throw_tcl_error - ereport an error returned from the Tcl interpreter
+ **********************************************************************/
+static void
+throw_tcl_error(Tcl_Interp *interp, const char *proname)
+{
+ /*
+ * Caution is needed here because Tcl_GetVar could overwrite the
+ * interpreter result (even though it's not really supposed to), and we
+ * can't control the order of evaluation of ereport arguments. Hence, make
+ * real sure we have our own copy of the result string before invoking
+ * Tcl_GetVar.
+ */
+ char *emsg;
+ char *econtext;
+
+ emsg = pstrdup(utf_u2e(Tcl_GetStringResult(interp)));
+ econtext = utf_u2e(Tcl_GetVar(interp, "errorInfo", TCL_GLOBAL_ONLY));
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", emsg),
+ errcontext("%s\nin PL/Tcl function \"%s\"",
+ econtext, proname)));
+}
+
+
+/**********************************************************************
+ * compile_pltcl_function - compile (or hopefully just look up) function
+ *
+ * tgreloid is the OID of the relation when compiling a trigger, or zero
+ * (InvalidOid) when compiling a plain function.
+ **********************************************************************/
+static pltcl_proc_desc *
+compile_pltcl_function(Oid fn_oid, Oid tgreloid,
+ bool is_event_trigger, bool pltrusted)
+{
+ HeapTuple procTup;
+ Form_pg_proc procStruct;
+ pltcl_proc_key proc_key;
+ pltcl_proc_ptr *proc_ptr;
+ bool found;
+ pltcl_proc_desc *prodesc;
+ pltcl_proc_desc *old_prodesc;
+ volatile MemoryContext proc_cxt = NULL;
+ Tcl_DString proc_internal_def;
+ Tcl_DString proc_internal_body;
+
+ /* We'll need the pg_proc tuple in any case... */
+ procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid));
+ if (!HeapTupleIsValid(procTup))
+ elog(ERROR, "cache lookup failed for function %u", fn_oid);
+ procStruct = (Form_pg_proc) GETSTRUCT(procTup);
+
+ /*
+ * Look up function in pltcl_proc_htab; if it's not there, create an entry
+ * and set the entry's proc_ptr to NULL.
+ */
+ proc_key.proc_id = fn_oid;
+ proc_key.is_trigger = OidIsValid(tgreloid);
+ proc_key.user_id = pltrusted ? GetUserId() : InvalidOid;
+
+ proc_ptr = hash_search(pltcl_proc_htab, &proc_key,
+ HASH_ENTER,
+ &found);
+ if (!found)
+ proc_ptr->proc_ptr = NULL;
+
+ prodesc = proc_ptr->proc_ptr;
+
+ /************************************************************
+ * If it's present, must check whether it's still up to date.
+ * This is needed because CREATE OR REPLACE FUNCTION can modify the
+ * function's pg_proc entry without changing its OID.
+ ************************************************************/
+ if (prodesc != NULL &&
+ prodesc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) &&
+ ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self))
+ {
+ /* It's still up-to-date, so we can use it */
+ ReleaseSysCache(procTup);
+ return prodesc;
+ }
+
+ /************************************************************
+ * If we haven't found it in the hashtable, we analyze
+ * the functions arguments and returntype and store
+ * the in-/out-functions in the prodesc block and create
+ * a new hashtable entry for it.
+ *
+ * Then we load the procedure into the Tcl interpreter.
+ ************************************************************/
+ Tcl_DStringInit(&proc_internal_def);
+ Tcl_DStringInit(&proc_internal_body);
+ PG_TRY();
+ {
+ bool is_trigger = OidIsValid(tgreloid);
+ char internal_proname[128];
+ HeapTuple typeTup;
+ Form_pg_type typeStruct;
+ char proc_internal_args[33 * FUNC_MAX_ARGS];
+ Datum prosrcdatum;
+ bool isnull;
+ char *proc_source;
+ char buf[48];
+ Tcl_Interp *interp;
+ int i;
+ int tcl_rc;
+ MemoryContext oldcontext;
+
+ /************************************************************
+ * Build our internal proc name from the function's Oid. Append
+ * "_trigger" when appropriate to ensure the normal and trigger
+ * cases are kept separate. Note name must be all-ASCII.
+ ************************************************************/
+ if (is_event_trigger)
+ snprintf(internal_proname, sizeof(internal_proname),
+ "__PLTcl_proc_%u_evttrigger", fn_oid);
+ else if (is_trigger)
+ snprintf(internal_proname, sizeof(internal_proname),
+ "__PLTcl_proc_%u_trigger", fn_oid);
+ else
+ snprintf(internal_proname, sizeof(internal_proname),
+ "__PLTcl_proc_%u", fn_oid);
+
+ /************************************************************
+ * Allocate a context that will hold all PG data for the procedure.
+ ************************************************************/
+ proc_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Tcl function",
+ ALLOCSET_SMALL_SIZES);
+
+ /************************************************************
+ * Allocate and fill a new procedure description block.
+ * struct prodesc and subsidiary data must all live in proc_cxt.
+ ************************************************************/
+ oldcontext = MemoryContextSwitchTo(proc_cxt);
+ prodesc = (pltcl_proc_desc *) palloc0(sizeof(pltcl_proc_desc));
+ prodesc->user_proname = pstrdup(NameStr(procStruct->proname));
+ MemoryContextSetIdentifier(proc_cxt, prodesc->user_proname);
+ prodesc->internal_proname = pstrdup(internal_proname);
+ prodesc->fn_cxt = proc_cxt;
+ prodesc->fn_refcount = 0;
+ prodesc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data);
+ prodesc->fn_tid = procTup->t_self;
+ prodesc->nargs = procStruct->pronargs;
+ prodesc->arg_out_func = (FmgrInfo *) palloc0(prodesc->nargs * sizeof(FmgrInfo));
+ prodesc->arg_is_rowtype = (bool *) palloc0(prodesc->nargs * sizeof(bool));
+ MemoryContextSwitchTo(oldcontext);
+
+ /* Remember if function is STABLE/IMMUTABLE */
+ prodesc->fn_readonly =
+ (procStruct->provolatile != PROVOLATILE_VOLATILE);
+ /* And whether it is trusted */
+ prodesc->lanpltrusted = pltrusted;
+
+ /************************************************************
+ * Identify the interpreter to use for the function
+ ************************************************************/
+ prodesc->interp_desc = pltcl_fetch_interp(procStruct->prolang,
+ prodesc->lanpltrusted);
+ interp = prodesc->interp_desc->interp;
+
+ /************************************************************
+ * Get the required information for input conversion of the
+ * return value.
+ ************************************************************/
+ if (!is_trigger && !is_event_trigger)
+ {
+ Oid rettype = procStruct->prorettype;
+
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", rettype);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ /* Disallow pseudotype result, except VOID and RECORD */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO)
+ {
+ if (rettype == VOIDOID ||
+ rettype == RECORDOID)
+ /* okay */ ;
+ else if (rettype == TRIGGEROID ||
+ rettype == EVENT_TRIGGEROID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("trigger functions can only be called as triggers")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Tcl functions cannot return type %s",
+ format_type_be(rettype))));
+ }
+
+ prodesc->result_typid = rettype;
+ fmgr_info_cxt(typeStruct->typinput,
+ &(prodesc->result_in_func),
+ proc_cxt);
+ prodesc->result_typioparam = getTypeIOParam(typeTup);
+
+ prodesc->fn_retisset = procStruct->proretset;
+ prodesc->fn_retistuple = type_is_rowtype(rettype);
+ prodesc->fn_retisdomain = (typeStruct->typtype == TYPTYPE_DOMAIN);
+ prodesc->domain_info = NULL;
+
+ ReleaseSysCache(typeTup);
+ }
+
+ /************************************************************
+ * Get the required information for output conversion
+ * of all procedure arguments, and set up argument naming info.
+ ************************************************************/
+ if (!is_trigger && !is_event_trigger)
+ {
+ proc_internal_args[0] = '\0';
+ for (i = 0; i < prodesc->nargs; i++)
+ {
+ Oid argtype = procStruct->proargtypes.values[i];
+
+ typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(argtype));
+ if (!HeapTupleIsValid(typeTup))
+ elog(ERROR, "cache lookup failed for type %u", argtype);
+ typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
+
+ /* Disallow pseudotype argument, except RECORD */
+ if (typeStruct->typtype == TYPTYPE_PSEUDO &&
+ argtype != RECORDOID)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("PL/Tcl functions cannot accept type %s",
+ format_type_be(argtype))));
+
+ if (type_is_rowtype(argtype))
+ {
+ prodesc->arg_is_rowtype[i] = true;
+ snprintf(buf, sizeof(buf), "__PLTcl_Tup_%d", i + 1);
+ }
+ else
+ {
+ prodesc->arg_is_rowtype[i] = false;
+ fmgr_info_cxt(typeStruct->typoutput,
+ &(prodesc->arg_out_func[i]),
+ proc_cxt);
+ snprintf(buf, sizeof(buf), "%d", i + 1);
+ }
+
+ if (i > 0)
+ strcat(proc_internal_args, " ");
+ strcat(proc_internal_args, buf);
+
+ ReleaseSysCache(typeTup);
+ }
+ }
+ else if (is_trigger)
+ {
+ /* trigger procedure has fixed args */
+ strcpy(proc_internal_args,
+ "TG_name TG_relid TG_table_name TG_table_schema TG_relatts TG_when TG_level TG_op __PLTcl_Tup_NEW __PLTcl_Tup_OLD args");
+ }
+ else if (is_event_trigger)
+ {
+ /* event trigger procedure has fixed args */
+ strcpy(proc_internal_args, "TG_event TG_tag");
+ }
+
+ /************************************************************
+ * Create the tcl command to define the internal
+ * procedure
+ *
+ * Leave this code as DString - performance is not critical here,
+ * and we don't want to duplicate the knowledge of the Tcl quoting
+ * rules that's embedded in Tcl_DStringAppendElement.
+ ************************************************************/
+ Tcl_DStringAppendElement(&proc_internal_def, "proc");
+ Tcl_DStringAppendElement(&proc_internal_def, internal_proname);
+ Tcl_DStringAppendElement(&proc_internal_def, proc_internal_args);
+
+ /************************************************************
+ * prefix procedure body with
+ * upvar #0 <internal_proname> GD
+ * and with appropriate setting of arguments
+ ************************************************************/
+ Tcl_DStringAppend(&proc_internal_body, "upvar #0 ", -1);
+ Tcl_DStringAppend(&proc_internal_body, internal_proname, -1);
+ Tcl_DStringAppend(&proc_internal_body, " GD\n", -1);
+ if (is_trigger)
+ {
+ Tcl_DStringAppend(&proc_internal_body,
+ "array set NEW $__PLTcl_Tup_NEW\n", -1);
+ Tcl_DStringAppend(&proc_internal_body,
+ "array set OLD $__PLTcl_Tup_OLD\n", -1);
+ Tcl_DStringAppend(&proc_internal_body,
+ "set i 0\n"
+ "set v 0\n"
+ "foreach v $args {\n"
+ " incr i\n"
+ " set $i $v\n"
+ "}\n"
+ "unset i v\n\n", -1);
+ }
+ else if (is_event_trigger)
+ {
+ /* no argument support for event triggers */
+ }
+ else
+ {
+ for (i = 0; i < prodesc->nargs; i++)
+ {
+ if (prodesc->arg_is_rowtype[i])
+ {
+ snprintf(buf, sizeof(buf),
+ "array set %d $__PLTcl_Tup_%d\n",
+ i + 1, i + 1);
+ Tcl_DStringAppend(&proc_internal_body, buf, -1);
+ }
+ }
+ }
+
+ /************************************************************
+ * Add user's function definition to proc body
+ ************************************************************/
+ prosrcdatum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_prosrc, &isnull);
+ if (isnull)
+ elog(ERROR, "null prosrc");
+ proc_source = TextDatumGetCString(prosrcdatum);
+ UTF_BEGIN;
+ Tcl_DStringAppend(&proc_internal_body, UTF_E2U(proc_source), -1);
+ UTF_END;
+ pfree(proc_source);
+ Tcl_DStringAppendElement(&proc_internal_def,
+ Tcl_DStringValue(&proc_internal_body));
+
+ /************************************************************
+ * Create the procedure in the interpreter
+ ************************************************************/
+ tcl_rc = Tcl_EvalEx(interp,
+ Tcl_DStringValue(&proc_internal_def),
+ Tcl_DStringLength(&proc_internal_def),
+ TCL_EVAL_GLOBAL);
+ if (tcl_rc != TCL_OK)
+ ereport(ERROR,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("could not create internal procedure \"%s\": %s",
+ internal_proname,
+ utf_u2e(Tcl_GetStringResult(interp)))));
+ }
+ PG_CATCH();
+ {
+ /*
+ * If we failed anywhere above, clean up whatever got allocated. It
+ * should all be in the proc_cxt, except for the DStrings.
+ */
+ if (proc_cxt)
+ MemoryContextDelete(proc_cxt);
+ Tcl_DStringFree(&proc_internal_def);
+ Tcl_DStringFree(&proc_internal_body);
+ PG_RE_THROW();
+ }
+ PG_END_TRY();
+
+ /*
+ * Install the new proc description block in the hashtable, incrementing
+ * its refcount (the hashtable link counts as a reference). Then, if
+ * there was a previous definition of the function, decrement that one's
+ * refcount, and delete it if no longer referenced. The order of
+ * operations here is important: if something goes wrong during the
+ * MemoryContextDelete, leaking some memory for the old definition is OK,
+ * but we don't want to corrupt the live hashtable entry. (Likewise,
+ * freeing the DStrings is pretty low priority if that happens.)
+ */
+ old_prodesc = proc_ptr->proc_ptr;
+
+ proc_ptr->proc_ptr = prodesc;
+ prodesc->fn_refcount++;
+
+ if (old_prodesc != NULL)
+ {
+ Assert(old_prodesc->fn_refcount > 0);
+ if (--old_prodesc->fn_refcount == 0)
+ MemoryContextDelete(old_prodesc->fn_cxt);
+ }
+
+ Tcl_DStringFree(&proc_internal_def);
+ Tcl_DStringFree(&proc_internal_body);
+
+ ReleaseSysCache(procTup);
+
+ return prodesc;
+}
+
+
+/**********************************************************************
+ * pltcl_elog() - elog() support for PLTcl
+ **********************************************************************/
+static int
+pltcl_elog(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ volatile int level;
+ MemoryContext oldcontext;
+ int priIndex;
+
+ static const char *logpriorities[] = {
+ "DEBUG", "LOG", "INFO", "NOTICE",
+ "WARNING", "ERROR", "FATAL", (const char *) NULL
+ };
+
+ static const int loglevels[] = {
+ DEBUG2, LOG, INFO, NOTICE,
+ WARNING, ERROR, FATAL
+ };
+
+ if (objc != 3)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "level msg");
+ return TCL_ERROR;
+ }
+
+ if (Tcl_GetIndexFromObj(interp, objv[1], logpriorities, "priority",
+ TCL_EXACT, &priIndex) != TCL_OK)
+ return TCL_ERROR;
+
+ level = loglevels[priIndex];
+
+ if (level == ERROR)
+ {
+ /*
+ * We just pass the error back to Tcl. If it's not caught, it'll
+ * eventually get converted to a PG error when we reach the call
+ * handler.
+ */
+ Tcl_SetObjResult(interp, objv[2]);
+ return TCL_ERROR;
+ }
+
+ /*
+ * For non-error messages, just pass 'em to ereport(). We do not expect
+ * that this will fail, but just on the off chance it does, report the
+ * error back to Tcl. Note we are assuming that ereport() can't have any
+ * internal failures that are so bad as to require a transaction abort.
+ *
+ * This path is also used for FATAL errors, which aren't going to come
+ * back to us at all.
+ */
+ oldcontext = CurrentMemoryContext;
+ PG_TRY();
+ {
+ UTF_BEGIN;
+ ereport(level,
+ (errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
+ errmsg("%s", UTF_U2E(Tcl_GetString(objv[2])))));
+ UTF_END;
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Must reset elog.c's state */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error data to Tcl */
+ pltcl_construct_errorCode(interp, edata);
+ UTF_BEGIN;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(UTF_E2U(edata->message), -1));
+ UTF_END;
+ FreeErrorData(edata);
+
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_construct_errorCode() - construct a Tcl errorCode
+ * list with detailed information from the PostgreSQL server
+ **********************************************************************/
+static void
+pltcl_construct_errorCode(Tcl_Interp *interp, ErrorData *edata)
+{
+ Tcl_Obj *obj = Tcl_NewObj();
+
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("POSTGRES", -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(PG_VERSION, -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("SQLSTATE", -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(unpack_sql_state(edata->sqlerrcode), -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("condition", -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(pltcl_get_condition_name(edata->sqlerrcode), -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("message", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->message), -1));
+ UTF_END;
+ if (edata->detail)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("detail", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->detail), -1));
+ UTF_END;
+ }
+ if (edata->hint)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("hint", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->hint), -1));
+ UTF_END;
+ }
+ if (edata->context)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("context", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->context), -1));
+ UTF_END;
+ }
+ if (edata->schema_name)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("schema", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->schema_name), -1));
+ UTF_END;
+ }
+ if (edata->table_name)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("table", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->table_name), -1));
+ UTF_END;
+ }
+ if (edata->column_name)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("column", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->column_name), -1));
+ UTF_END;
+ }
+ if (edata->datatype_name)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("datatype", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->datatype_name), -1));
+ UTF_END;
+ }
+ if (edata->constraint_name)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("constraint", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->constraint_name), -1));
+ UTF_END;
+ }
+ /* cursorpos is never interesting here; report internal query/pos */
+ if (edata->internalquery)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("statement", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->internalquery), -1));
+ UTF_END;
+ }
+ if (edata->internalpos > 0)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("cursor_position", -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewIntObj(edata->internalpos));
+ }
+ if (edata->filename)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("filename", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->filename), -1));
+ UTF_END;
+ }
+ if (edata->lineno > 0)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("lineno", -1));
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewIntObj(edata->lineno));
+ }
+ if (edata->funcname)
+ {
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj("funcname", -1));
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(interp, obj,
+ Tcl_NewStringObj(UTF_E2U(edata->funcname), -1));
+ UTF_END;
+ }
+
+ Tcl_SetObjErrorCode(interp, obj);
+}
+
+
+/**********************************************************************
+ * pltcl_get_condition_name() - find name for SQLSTATE
+ **********************************************************************/
+static const char *
+pltcl_get_condition_name(int sqlstate)
+{
+ int i;
+
+ for (i = 0; exception_name_map[i].label != NULL; i++)
+ {
+ if (exception_name_map[i].sqlerrstate == sqlstate)
+ return exception_name_map[i].label;
+ }
+ return "unrecognized_sqlstate";
+}
+
+
+/**********************************************************************
+ * pltcl_quote() - quote literal strings that are to
+ * be used in SPI_execute query strings
+ **********************************************************************/
+static int
+pltcl_quote(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ char *tmp;
+ const char *cp1;
+ char *cp2;
+ int length;
+
+ /************************************************************
+ * Check call syntax
+ ************************************************************/
+ if (objc != 2)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "string");
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Allocate space for the maximum the string can
+ * grow to and initialize pointers
+ ************************************************************/
+ cp1 = Tcl_GetStringFromObj(objv[1], &length);
+ tmp = palloc(length * 2 + 1);
+ cp2 = tmp;
+
+ /************************************************************
+ * Walk through string and double every quote and backslash
+ ************************************************************/
+ while (*cp1)
+ {
+ if (*cp1 == '\'')
+ *cp2++ = '\'';
+ else
+ {
+ if (*cp1 == '\\')
+ *cp2++ = '\\';
+ }
+ *cp2++ = *cp1++;
+ }
+
+ /************************************************************
+ * Terminate the string and set it as result
+ ************************************************************/
+ *cp2 = '\0';
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(tmp, -1));
+ pfree(tmp);
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_argisnull() - determine if a specific argument is NULL
+ **********************************************************************/
+static int
+pltcl_argisnull(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ int argno;
+ FunctionCallInfo fcinfo = pltcl_current_call_state->fcinfo;
+
+ /************************************************************
+ * Check call syntax
+ ************************************************************/
+ if (objc != 2)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "argno");
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Check that we're called as a normal function
+ ************************************************************/
+ if (fcinfo == NULL)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("argisnull cannot be used in triggers", -1));
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Get the argument number
+ ************************************************************/
+ if (Tcl_GetIntFromObj(interp, objv[1], &argno) != TCL_OK)
+ return TCL_ERROR;
+
+ /************************************************************
+ * Check that the argno is valid
+ ************************************************************/
+ argno--;
+ if (argno < 0 || argno >= fcinfo->nargs)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("argno out of range", -1));
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Get the requested NULL state
+ ************************************************************/
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(PG_ARGISNULL(argno)));
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_returnnull() - Cause a NULL return from the current function
+ **********************************************************************/
+static int
+pltcl_returnnull(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ FunctionCallInfo fcinfo = pltcl_current_call_state->fcinfo;
+
+ /************************************************************
+ * Check call syntax
+ ************************************************************/
+ if (objc != 1)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "");
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Check that we're called as a normal function
+ ************************************************************/
+ if (fcinfo == NULL)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("return_null cannot be used in triggers", -1));
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Set the NULL return flag and cause Tcl to return from the
+ * procedure.
+ ************************************************************/
+ fcinfo->isnull = true;
+
+ return TCL_RETURN;
+}
+
+
+/**********************************************************************
+ * pltcl_returnnext() - Add a row to the result tuplestore in a SRF.
+ **********************************************************************/
+static int
+pltcl_returnnext(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ pltcl_call_state *call_state = pltcl_current_call_state;
+ FunctionCallInfo fcinfo = call_state->fcinfo;
+ pltcl_proc_desc *prodesc = call_state->prodesc;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ volatile int result = TCL_OK;
+
+ /*
+ * Check that we're called as a set-returning function
+ */
+ if (fcinfo == NULL)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("return_next cannot be used in triggers", -1));
+ return TCL_ERROR;
+ }
+
+ if (!prodesc->fn_retisset)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("return_next cannot be used in non-set-returning functions", -1));
+ return TCL_ERROR;
+ }
+
+ /*
+ * Check call syntax
+ */
+ if (objc != 2)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "result");
+ return TCL_ERROR;
+ }
+
+ /*
+ * The rest might throw elog(ERROR), so must run in a subtransaction.
+ *
+ * A small advantage of using a subtransaction is that it provides a
+ * short-lived memory context for free, so we needn't worry about leaking
+ * memory here. To use that context, call BeginInternalSubTransaction
+ * directly instead of going through pltcl_subtrans_begin.
+ */
+ BeginInternalSubTransaction(NULL);
+ PG_TRY();
+ {
+ /* Set up tuple store if first output row */
+ if (call_state->tuple_store == NULL)
+ pltcl_init_tuple_store(call_state);
+
+ if (prodesc->fn_retistuple)
+ {
+ Tcl_Obj **rowObjv;
+ int rowObjc;
+
+ /* result should be a list, so break it down */
+ if (Tcl_ListObjGetElements(interp, objv[1], &rowObjc, &rowObjv) == TCL_ERROR)
+ result = TCL_ERROR;
+ else
+ {
+ HeapTuple tuple;
+
+ tuple = pltcl_build_tuple_result(interp, rowObjv, rowObjc,
+ call_state);
+ tuplestore_puttuple(call_state->tuple_store, tuple);
+ }
+ }
+ else
+ {
+ Datum retval;
+ bool isNull = false;
+
+ /* for paranoia's sake, check that tupdesc has exactly one column */
+ if (call_state->ret_tupdesc->natts != 1)
+ elog(ERROR, "wrong result type supplied in return_next");
+
+ retval = InputFunctionCall(&prodesc->result_in_func,
+ utf_u2e((char *) Tcl_GetString(objv[1])),
+ prodesc->result_typioparam,
+ -1);
+ tuplestore_putvalues(call_state->tuple_store, call_state->ret_tupdesc,
+ &retval, &isNull);
+ }
+
+ pltcl_subtrans_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ pltcl_subtrans_abort(interp, oldcontext, oldowner);
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return result;
+}
+
+
+/*----------
+ * Support for running SPI operations inside subtransactions
+ *
+ * Intended usage pattern is:
+ *
+ * MemoryContext oldcontext = CurrentMemoryContext;
+ * ResourceOwner oldowner = CurrentResourceOwner;
+ *
+ * ...
+ * pltcl_subtrans_begin(oldcontext, oldowner);
+ * PG_TRY();
+ * {
+ * do something risky;
+ * pltcl_subtrans_commit(oldcontext, oldowner);
+ * }
+ * PG_CATCH();
+ * {
+ * pltcl_subtrans_abort(interp, oldcontext, oldowner);
+ * return TCL_ERROR;
+ * }
+ * PG_END_TRY();
+ * return TCL_OK;
+ *----------
+ */
+static void
+pltcl_subtrans_begin(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ BeginInternalSubTransaction(NULL);
+
+ /* Want to run inside function's memory context */
+ MemoryContextSwitchTo(oldcontext);
+}
+
+static void
+pltcl_subtrans_commit(MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ /* Commit the inner transaction, return to outer xact context */
+ ReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+}
+
+static void
+pltcl_subtrans_abort(Tcl_Interp *interp,
+ MemoryContext oldcontext, ResourceOwner oldowner)
+{
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Abort the inner transaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ /* Pass the error data to Tcl */
+ pltcl_construct_errorCode(interp, edata);
+ UTF_BEGIN;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(UTF_E2U(edata->message), -1));
+ UTF_END;
+ FreeErrorData(edata);
+}
+
+
+/**********************************************************************
+ * pltcl_SPI_execute() - The builtin SPI_execute command
+ * for the Tcl interpreter
+ **********************************************************************/
+static int
+pltcl_SPI_execute(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ int my_rc;
+ int spi_rc;
+ int query_idx;
+ int i;
+ int optIndex;
+ int count = 0;
+ const char *volatile arrayname = NULL;
+ Tcl_Obj *volatile loop_body = NULL;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ enum options
+ {
+ OPT_ARRAY, OPT_COUNT
+ };
+
+ static const char *options[] = {
+ "-array", "-count", (const char *) NULL
+ };
+
+ /************************************************************
+ * Check the call syntax and get the options
+ ************************************************************/
+ if (objc < 2)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "?-count n? ?-array name? query ?loop body?");
+ return TCL_ERROR;
+ }
+
+ i = 1;
+ while (i < objc)
+ {
+ if (Tcl_GetIndexFromObj(NULL, objv[i], options, NULL,
+ TCL_EXACT, &optIndex) != TCL_OK)
+ break;
+
+ if (++i >= objc)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("missing argument to -count or -array", -1));
+ return TCL_ERROR;
+ }
+
+ switch ((enum options) optIndex)
+ {
+ case OPT_ARRAY:
+ arrayname = Tcl_GetString(objv[i++]);
+ break;
+
+ case OPT_COUNT:
+ if (Tcl_GetIntFromObj(interp, objv[i++], &count) != TCL_OK)
+ return TCL_ERROR;
+ break;
+ }
+ }
+
+ query_idx = i;
+ if (query_idx >= objc || query_idx + 2 < objc)
+ {
+ Tcl_WrongNumArgs(interp, query_idx - 1, objv, "query ?loop body?");
+ return TCL_ERROR;
+ }
+
+ if (query_idx + 1 < objc)
+ loop_body = objv[query_idx + 1];
+
+ /************************************************************
+ * Execute the query inside a sub-transaction, so we can cope with
+ * errors sanely
+ ************************************************************/
+
+ pltcl_subtrans_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ UTF_BEGIN;
+ spi_rc = SPI_execute(UTF_U2E(Tcl_GetString(objv[query_idx])),
+ pltcl_current_call_state->prodesc->fn_readonly, count);
+ UTF_END;
+
+ my_rc = pltcl_process_SPI_result(interp,
+ arrayname,
+ loop_body,
+ spi_rc,
+ SPI_tuptable,
+ SPI_processed);
+
+ pltcl_subtrans_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ pltcl_subtrans_abort(interp, oldcontext, oldowner);
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return my_rc;
+}
+
+/*
+ * Process the result from SPI_execute or SPI_execute_plan
+ *
+ * Shared code between pltcl_SPI_execute and pltcl_SPI_execute_plan
+ */
+static int
+pltcl_process_SPI_result(Tcl_Interp *interp,
+ const char *arrayname,
+ Tcl_Obj *loop_body,
+ int spi_rc,
+ SPITupleTable *tuptable,
+ uint64 ntuples)
+{
+ int my_rc = TCL_OK;
+ int loop_rc;
+ HeapTuple *tuples;
+ TupleDesc tupdesc;
+
+ switch (spi_rc)
+ {
+ case SPI_OK_SELINTO:
+ case SPI_OK_INSERT:
+ case SPI_OK_DELETE:
+ case SPI_OK_UPDATE:
+ case SPI_OK_MERGE:
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(ntuples));
+ break;
+
+ case SPI_OK_UTILITY:
+ case SPI_OK_REWRITTEN:
+ if (tuptable == NULL)
+ {
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(0));
+ break;
+ }
+ /* fall through for utility returning tuples */
+ /* FALLTHROUGH */
+
+ case SPI_OK_SELECT:
+ case SPI_OK_INSERT_RETURNING:
+ case SPI_OK_DELETE_RETURNING:
+ case SPI_OK_UPDATE_RETURNING:
+
+ /*
+ * Process the tuples we got
+ */
+ tuples = tuptable->vals;
+ tupdesc = tuptable->tupdesc;
+
+ if (loop_body == NULL)
+ {
+ /*
+ * If there is no loop body given, just set the variables from
+ * the first tuple (if any)
+ */
+ if (ntuples > 0)
+ pltcl_set_tuple_values(interp, arrayname, 0,
+ tuples[0], tupdesc);
+ }
+ else
+ {
+ /*
+ * There is a loop body - process all tuples and evaluate the
+ * body on each
+ */
+ uint64 i;
+
+ for (i = 0; i < ntuples; i++)
+ {
+ pltcl_set_tuple_values(interp, arrayname, i,
+ tuples[i], tupdesc);
+
+ loop_rc = Tcl_EvalObjEx(interp, loop_body, 0);
+
+ if (loop_rc == TCL_OK)
+ continue;
+ if (loop_rc == TCL_CONTINUE)
+ continue;
+ if (loop_rc == TCL_RETURN)
+ {
+ my_rc = TCL_RETURN;
+ break;
+ }
+ if (loop_rc == TCL_BREAK)
+ break;
+ my_rc = TCL_ERROR;
+ break;
+ }
+ }
+
+ if (my_rc == TCL_OK)
+ {
+ Tcl_SetObjResult(interp, Tcl_NewWideIntObj(ntuples));
+ }
+ break;
+
+ default:
+ Tcl_AppendResult(interp, "pltcl: SPI_execute failed: ",
+ SPI_result_code_string(spi_rc), NULL);
+ my_rc = TCL_ERROR;
+ break;
+ }
+
+ SPI_freetuptable(tuptable);
+
+ return my_rc;
+}
+
+
+/**********************************************************************
+ * pltcl_SPI_prepare() - Builtin support for prepared plans
+ * The Tcl command SPI_prepare
+ * always saves the plan using
+ * SPI_keepplan and returns a key for
+ * access. There is no chance to prepare
+ * and not save the plan currently.
+ **********************************************************************/
+static int
+pltcl_SPI_prepare(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ volatile MemoryContext plan_cxt = NULL;
+ int nargs;
+ Tcl_Obj **argsObj;
+ pltcl_query_desc *qdesc;
+ int i;
+ Tcl_HashEntry *hashent;
+ int hashnew;
+ Tcl_HashTable *query_hash;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+
+ /************************************************************
+ * Check the call syntax
+ ************************************************************/
+ if (objc != 3)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "query argtypes");
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Split the argument type list
+ ************************************************************/
+ if (Tcl_ListObjGetElements(interp, objv[2], &nargs, &argsObj) != TCL_OK)
+ return TCL_ERROR;
+
+ /************************************************************
+ * Allocate the new querydesc structure
+ *
+ * struct qdesc and subsidiary data all live in plan_cxt. Note that if the
+ * function is recompiled for whatever reason, permanent memory leaks
+ * occur. FIXME someday.
+ ************************************************************/
+ plan_cxt = AllocSetContextCreate(TopMemoryContext,
+ "PL/Tcl spi_prepare query",
+ ALLOCSET_SMALL_SIZES);
+ MemoryContextSwitchTo(plan_cxt);
+ qdesc = (pltcl_query_desc *) palloc0(sizeof(pltcl_query_desc));
+ snprintf(qdesc->qname, sizeof(qdesc->qname), "%p", qdesc);
+ qdesc->nargs = nargs;
+ qdesc->argtypes = (Oid *) palloc(nargs * sizeof(Oid));
+ qdesc->arginfuncs = (FmgrInfo *) palloc(nargs * sizeof(FmgrInfo));
+ qdesc->argtypioparams = (Oid *) palloc(nargs * sizeof(Oid));
+ MemoryContextSwitchTo(oldcontext);
+
+ /************************************************************
+ * Execute the prepare inside a sub-transaction, so we can cope with
+ * errors sanely
+ ************************************************************/
+
+ pltcl_subtrans_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ /************************************************************
+ * Resolve argument type names and then look them up by oid
+ * in the system cache, and remember the required information
+ * for input conversion.
+ ************************************************************/
+ for (i = 0; i < nargs; i++)
+ {
+ Oid typId,
+ typInput,
+ typIOParam;
+ int32 typmod;
+
+ parseTypeString(Tcl_GetString(argsObj[i]), &typId, &typmod, false);
+
+ getTypeInputInfo(typId, &typInput, &typIOParam);
+
+ qdesc->argtypes[i] = typId;
+ fmgr_info_cxt(typInput, &(qdesc->arginfuncs[i]), plan_cxt);
+ qdesc->argtypioparams[i] = typIOParam;
+ }
+
+ /************************************************************
+ * Prepare the plan and check for errors
+ ************************************************************/
+ UTF_BEGIN;
+ qdesc->plan = SPI_prepare(UTF_U2E(Tcl_GetString(objv[1])),
+ nargs, qdesc->argtypes);
+ UTF_END;
+
+ if (qdesc->plan == NULL)
+ elog(ERROR, "SPI_prepare() failed");
+
+ /************************************************************
+ * Save the plan into permanent memory (right now it's in the
+ * SPI procCxt, which will go away at function end).
+ ************************************************************/
+ if (SPI_keepplan(qdesc->plan))
+ elog(ERROR, "SPI_keepplan() failed");
+
+ pltcl_subtrans_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ pltcl_subtrans_abort(interp, oldcontext, oldowner);
+
+ MemoryContextDelete(plan_cxt);
+
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ /************************************************************
+ * Insert a hashtable entry for the plan and return
+ * the key to the caller
+ ************************************************************/
+ query_hash = &pltcl_current_call_state->prodesc->interp_desc->query_hash;
+
+ hashent = Tcl_CreateHashEntry(query_hash, qdesc->qname, &hashnew);
+ Tcl_SetHashValue(hashent, (ClientData) qdesc);
+
+ /* qname is ASCII, so no need for encoding conversion */
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(qdesc->qname, -1));
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_SPI_execute_plan() - Execute a prepared plan
+ **********************************************************************/
+static int
+pltcl_SPI_execute_plan(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ int my_rc;
+ int spi_rc;
+ int i;
+ int j;
+ int optIndex;
+ Tcl_HashEntry *hashent;
+ pltcl_query_desc *qdesc;
+ const char *nulls = NULL;
+ const char *arrayname = NULL;
+ Tcl_Obj *loop_body = NULL;
+ int count = 0;
+ int callObjc;
+ Tcl_Obj **callObjv = NULL;
+ Datum *argvalues;
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ Tcl_HashTable *query_hash;
+
+ enum options
+ {
+ OPT_ARRAY, OPT_COUNT, OPT_NULLS
+ };
+
+ static const char *options[] = {
+ "-array", "-count", "-nulls", (const char *) NULL
+ };
+
+ /************************************************************
+ * Get the options and check syntax
+ ************************************************************/
+ i = 1;
+ while (i < objc)
+ {
+ if (Tcl_GetIndexFromObj(NULL, objv[i], options, NULL,
+ TCL_EXACT, &optIndex) != TCL_OK)
+ break;
+
+ if (++i >= objc)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("missing argument to -array, -count or -nulls", -1));
+ return TCL_ERROR;
+ }
+
+ switch ((enum options) optIndex)
+ {
+ case OPT_ARRAY:
+ arrayname = Tcl_GetString(objv[i++]);
+ break;
+
+ case OPT_COUNT:
+ if (Tcl_GetIntFromObj(interp, objv[i++], &count) != TCL_OK)
+ return TCL_ERROR;
+ break;
+
+ case OPT_NULLS:
+ nulls = Tcl_GetString(objv[i++]);
+ break;
+ }
+ }
+
+ /************************************************************
+ * Get the prepared plan descriptor by its key
+ ************************************************************/
+ if (i >= objc)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("missing argument to -count or -array", -1));
+ return TCL_ERROR;
+ }
+
+ query_hash = &pltcl_current_call_state->prodesc->interp_desc->query_hash;
+
+ hashent = Tcl_FindHashEntry(query_hash, Tcl_GetString(objv[i]));
+ if (hashent == NULL)
+ {
+ Tcl_AppendResult(interp, "invalid queryid '", Tcl_GetString(objv[i]), "'", NULL);
+ return TCL_ERROR;
+ }
+ qdesc = (pltcl_query_desc *) Tcl_GetHashValue(hashent);
+ i++;
+
+ /************************************************************
+ * If a nulls string is given, check for correct length
+ ************************************************************/
+ if (nulls != NULL)
+ {
+ if (strlen(nulls) != qdesc->nargs)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("length of nulls string doesn't match number of arguments",
+ -1));
+ return TCL_ERROR;
+ }
+ }
+
+ /************************************************************
+ * If there was an argtype list on preparation, we need
+ * an argument value list now
+ ************************************************************/
+ if (qdesc->nargs > 0)
+ {
+ if (i >= objc)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("argument list length doesn't match number of arguments for query",
+ -1));
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Split the argument values
+ ************************************************************/
+ if (Tcl_ListObjGetElements(interp, objv[i++], &callObjc, &callObjv) != TCL_OK)
+ return TCL_ERROR;
+
+ /************************************************************
+ * Check that the number of arguments matches
+ ************************************************************/
+ if (callObjc != qdesc->nargs)
+ {
+ Tcl_SetObjResult(interp,
+ Tcl_NewStringObj("argument list length doesn't match number of arguments for query",
+ -1));
+ return TCL_ERROR;
+ }
+ }
+ else
+ callObjc = 0;
+
+ /************************************************************
+ * Get loop body if present
+ ************************************************************/
+ if (i < objc)
+ loop_body = objv[i++];
+
+ if (i != objc)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv,
+ "?-count n? ?-array name? ?-nulls string? "
+ "query ?args? ?loop body?");
+ return TCL_ERROR;
+ }
+
+ /************************************************************
+ * Execute the plan inside a sub-transaction, so we can cope with
+ * errors sanely
+ ************************************************************/
+
+ pltcl_subtrans_begin(oldcontext, oldowner);
+
+ PG_TRY();
+ {
+ /************************************************************
+ * Setup the value array for SPI_execute_plan() using
+ * the type specific input functions
+ ************************************************************/
+ argvalues = (Datum *) palloc(callObjc * sizeof(Datum));
+
+ for (j = 0; j < callObjc; j++)
+ {
+ if (nulls && nulls[j] == 'n')
+ {
+ argvalues[j] = InputFunctionCall(&qdesc->arginfuncs[j],
+ NULL,
+ qdesc->argtypioparams[j],
+ -1);
+ }
+ else
+ {
+ UTF_BEGIN;
+ argvalues[j] = InputFunctionCall(&qdesc->arginfuncs[j],
+ UTF_U2E(Tcl_GetString(callObjv[j])),
+ qdesc->argtypioparams[j],
+ -1);
+ UTF_END;
+ }
+ }
+
+ /************************************************************
+ * Execute the plan
+ ************************************************************/
+ spi_rc = SPI_execute_plan(qdesc->plan, argvalues, nulls,
+ pltcl_current_call_state->prodesc->fn_readonly,
+ count);
+
+ my_rc = pltcl_process_SPI_result(interp,
+ arrayname,
+ loop_body,
+ spi_rc,
+ SPI_tuptable,
+ SPI_processed);
+
+ pltcl_subtrans_commit(oldcontext, oldowner);
+ }
+ PG_CATCH();
+ {
+ pltcl_subtrans_abort(interp, oldcontext, oldowner);
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return my_rc;
+}
+
+
+/**********************************************************************
+ * pltcl_subtransaction() - Execute some Tcl code in a subtransaction
+ *
+ * The subtransaction is aborted if the Tcl code fragment returns TCL_ERROR,
+ * otherwise it's subcommitted.
+ **********************************************************************/
+static int
+pltcl_subtransaction(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+ ResourceOwner oldowner = CurrentResourceOwner;
+ int retcode;
+
+ if (objc != 2)
+ {
+ Tcl_WrongNumArgs(interp, 1, objv, "command");
+ return TCL_ERROR;
+ }
+
+ /*
+ * Note: we don't use pltcl_subtrans_begin and friends here because we
+ * don't want the error handling in pltcl_subtrans_abort. But otherwise
+ * the processing should be about the same as in those functions.
+ */
+ BeginInternalSubTransaction(NULL);
+ MemoryContextSwitchTo(oldcontext);
+
+ retcode = Tcl_EvalObjEx(interp, objv[1], 0);
+
+ if (retcode == TCL_ERROR)
+ {
+ /* Rollback the subtransaction */
+ RollbackAndReleaseCurrentSubTransaction();
+ }
+ else
+ {
+ /* Commit the subtransaction */
+ ReleaseCurrentSubTransaction();
+ }
+
+ /* In either case, restore previous memory context and resource owner */
+ MemoryContextSwitchTo(oldcontext);
+ CurrentResourceOwner = oldowner;
+
+ return retcode;
+}
+
+
+/**********************************************************************
+ * pltcl_commit()
+ *
+ * Commit the transaction and start a new one.
+ **********************************************************************/
+static int
+pltcl_commit(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ PG_TRY();
+ {
+ SPI_commit();
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error data to Tcl */
+ pltcl_construct_errorCode(interp, edata);
+ UTF_BEGIN;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(UTF_E2U(edata->message), -1));
+ UTF_END;
+ FreeErrorData(edata);
+
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_rollback()
+ *
+ * Abort the transaction and start a new one.
+ **********************************************************************/
+static int
+pltcl_rollback(ClientData cdata, Tcl_Interp *interp,
+ int objc, Tcl_Obj *const objv[])
+{
+ MemoryContext oldcontext = CurrentMemoryContext;
+
+ PG_TRY();
+ {
+ SPI_rollback();
+ }
+ PG_CATCH();
+ {
+ ErrorData *edata;
+
+ /* Save error info */
+ MemoryContextSwitchTo(oldcontext);
+ edata = CopyErrorData();
+ FlushErrorState();
+
+ /* Pass the error data to Tcl */
+ pltcl_construct_errorCode(interp, edata);
+ UTF_BEGIN;
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(UTF_E2U(edata->message), -1));
+ UTF_END;
+ FreeErrorData(edata);
+
+ return TCL_ERROR;
+ }
+ PG_END_TRY();
+
+ return TCL_OK;
+}
+
+
+/**********************************************************************
+ * pltcl_set_tuple_values() - Set variables for all attributes
+ * of a given tuple
+ *
+ * Note: arrayname is presumed to be UTF8; it usually came from Tcl
+ **********************************************************************/
+static void
+pltcl_set_tuple_values(Tcl_Interp *interp, const char *arrayname,
+ uint64 tupno, HeapTuple tuple, TupleDesc tupdesc)
+{
+ int i;
+ char *outputstr;
+ Datum attr;
+ bool isnull;
+ const char *attname;
+ Oid typoutput;
+ bool typisvarlena;
+ const char **arrptr;
+ const char **nameptr;
+ const char *nullname = NULL;
+
+ /************************************************************
+ * Prepare pointers for Tcl_SetVar2Ex() below
+ ************************************************************/
+ if (arrayname == NULL)
+ {
+ arrptr = &attname;
+ nameptr = &nullname;
+ }
+ else
+ {
+ arrptr = &arrayname;
+ nameptr = &attname;
+
+ /*
+ * When outputting to an array, fill the ".tupno" element with the
+ * current tuple number. This will be overridden below if ".tupno" is
+ * in use as an actual field name in the rowtype.
+ */
+ Tcl_SetVar2Ex(interp, arrayname, ".tupno", Tcl_NewWideIntObj(tupno), 0);
+ }
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ /* ignore dropped attributes */
+ if (att->attisdropped)
+ continue;
+
+ /************************************************************
+ * Get the attribute name
+ ************************************************************/
+ UTF_BEGIN;
+ attname = pstrdup(UTF_E2U(NameStr(att->attname)));
+ UTF_END;
+
+ /************************************************************
+ * Get the attributes value
+ ************************************************************/
+ attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
+
+ /************************************************************
+ * If there is a value, set the variable
+ * If not, unset it
+ *
+ * Hmmm - Null attributes will cause functions to
+ * crash if they don't expect them - need something
+ * smarter here.
+ ************************************************************/
+ if (!isnull)
+ {
+ getTypeOutputInfo(att->atttypid, &typoutput, &typisvarlena);
+ outputstr = OidOutputFunctionCall(typoutput, attr);
+ UTF_BEGIN;
+ Tcl_SetVar2Ex(interp, *arrptr, *nameptr,
+ Tcl_NewStringObj(UTF_E2U(outputstr), -1), 0);
+ UTF_END;
+ pfree(outputstr);
+ }
+ else
+ Tcl_UnsetVar2(interp, *arrptr, *nameptr, 0);
+
+ pfree(unconstify(char *, attname));
+ }
+}
+
+
+/**********************************************************************
+ * pltcl_build_tuple_argument() - Build a list object usable for 'array set'
+ * from all attributes of a given tuple
+ **********************************************************************/
+static Tcl_Obj *
+pltcl_build_tuple_argument(HeapTuple tuple, TupleDesc tupdesc, bool include_generated)
+{
+ Tcl_Obj *retobj = Tcl_NewObj();
+ int i;
+ char *outputstr;
+ Datum attr;
+ bool isnull;
+ char *attname;
+ Oid typoutput;
+ bool typisvarlena;
+
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupdesc, i);
+
+ /* ignore dropped attributes */
+ if (att->attisdropped)
+ continue;
+
+ if (att->attgenerated)
+ {
+ /* don't include unless requested */
+ if (!include_generated)
+ continue;
+ }
+
+ /************************************************************
+ * Get the attribute name
+ ************************************************************/
+ attname = NameStr(att->attname);
+
+ /************************************************************
+ * Get the attributes value
+ ************************************************************/
+ attr = heap_getattr(tuple, i + 1, tupdesc, &isnull);
+
+ /************************************************************
+ * If there is a value, append the attribute name and the
+ * value to the list
+ *
+ * Hmmm - Null attributes will cause functions to
+ * crash if they don't expect them - need something
+ * smarter here.
+ ************************************************************/
+ if (!isnull)
+ {
+ getTypeOutputInfo(att->atttypid,
+ &typoutput, &typisvarlena);
+ outputstr = OidOutputFunctionCall(typoutput, attr);
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(NULL, retobj,
+ Tcl_NewStringObj(UTF_E2U(attname), -1));
+ UTF_END;
+ UTF_BEGIN;
+ Tcl_ListObjAppendElement(NULL, retobj,
+ Tcl_NewStringObj(UTF_E2U(outputstr), -1));
+ UTF_END;
+ pfree(outputstr);
+ }
+ }
+
+ return retobj;
+}
+
+/**********************************************************************
+ * pltcl_build_tuple_result() - Build a tuple of function's result rowtype
+ * from a Tcl list of column names and values
+ *
+ * In a trigger function, we build a tuple of the trigger table's rowtype.
+ *
+ * Note: this function leaks memory. Even if we made it clean up its own
+ * mess, there's no way to prevent the datatype input functions it calls
+ * from leaking. Run it in a short-lived context, unless we're about to
+ * exit the procedure anyway.
+ **********************************************************************/
+static HeapTuple
+pltcl_build_tuple_result(Tcl_Interp *interp, Tcl_Obj **kvObjv, int kvObjc,
+ pltcl_call_state *call_state)
+{
+ HeapTuple tuple;
+ TupleDesc tupdesc;
+ AttInMetadata *attinmeta;
+ char **values;
+ int i;
+
+ if (call_state->ret_tupdesc)
+ {
+ tupdesc = call_state->ret_tupdesc;
+ attinmeta = call_state->attinmeta;
+ }
+ else if (call_state->trigdata)
+ {
+ tupdesc = RelationGetDescr(call_state->trigdata->tg_relation);
+ attinmeta = TupleDescGetAttInMetadata(tupdesc);
+ }
+ else
+ {
+ elog(ERROR, "PL/Tcl function does not return a tuple");
+ tupdesc = NULL; /* keep compiler quiet */
+ attinmeta = NULL;
+ }
+
+ values = (char **) palloc0(tupdesc->natts * sizeof(char *));
+
+ if (kvObjc % 2 != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("column name/value list must have even number of elements")));
+
+ for (i = 0; i < kvObjc; i += 2)
+ {
+ char *fieldName = utf_u2e(Tcl_GetString(kvObjv[i]));
+ int attn = SPI_fnumber(tupdesc, fieldName);
+
+ /*
+ * We silently ignore ".tupno", if it's present but doesn't match any
+ * actual output column. This allows direct use of a row returned by
+ * pltcl_set_tuple_values().
+ */
+ if (attn == SPI_ERROR_NOATTRIBUTE)
+ {
+ if (strcmp(fieldName, ".tupno") == 0)
+ continue;
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_COLUMN),
+ errmsg("column name/value list contains nonexistent column name \"%s\"",
+ fieldName)));
+ }
+
+ if (attn <= 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot set system attribute \"%s\"",
+ fieldName)));
+
+ if (TupleDescAttr(tupdesc, attn - 1)->attgenerated)
+ ereport(ERROR,
+ (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED),
+ errmsg("cannot set generated column \"%s\"",
+ fieldName)));
+
+ values[attn - 1] = utf_u2e(Tcl_GetString(kvObjv[i + 1]));
+ }
+
+ tuple = BuildTupleFromCStrings(attinmeta, values);
+
+ /* if result type is domain-over-composite, check domain constraints */
+ if (call_state->prodesc->fn_retisdomain)
+ domain_check(HeapTupleGetDatum(tuple), false,
+ call_state->prodesc->result_typid,
+ &call_state->prodesc->domain_info,
+ call_state->prodesc->fn_cxt);
+
+ return tuple;
+}
+
+/**********************************************************************
+ * pltcl_init_tuple_store() - Initialize the result tuplestore for a SRF
+ **********************************************************************/
+static void
+pltcl_init_tuple_store(pltcl_call_state *call_state)
+{
+ ReturnSetInfo *rsi = call_state->rsi;
+ MemoryContext oldcxt;
+ ResourceOwner oldowner;
+
+ /* Should be in a SRF */
+ Assert(rsi);
+ /* Should be first time through */
+ Assert(!call_state->tuple_store);
+ Assert(!call_state->attinmeta);
+
+ /* We expect caller to provide an appropriate result tupdesc */
+ Assert(rsi->expectedDesc);
+ call_state->ret_tupdesc = rsi->expectedDesc;
+
+ /*
+ * Switch to the right memory context and resource owner for storing the
+ * tuplestore. If we're within a subtransaction opened for an exception
+ * block, for example, we must still create the tuplestore in the resource
+ * owner that was active when this function was entered, and not in the
+ * subtransaction's resource owner.
+ */
+ oldcxt = MemoryContextSwitchTo(call_state->tuple_store_cxt);
+ oldowner = CurrentResourceOwner;
+ CurrentResourceOwner = call_state->tuple_store_owner;
+
+ call_state->tuple_store =
+ tuplestore_begin_heap(rsi->allowedModes & SFRM_Materialize_Random,
+ false, work_mem);
+
+ /* Build attinmeta in this context, too */
+ call_state->attinmeta = TupleDescGetAttInMetadata(call_state->ret_tupdesc);
+
+ CurrentResourceOwner = oldowner;
+ MemoryContextSwitchTo(oldcxt);
+}
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
new file mode 100644
index 0000000..1568c17
--- /dev/null
+++ b/src/pl/tcl/pltcl.control
@@ -0,0 +1,8 @@
+# pltcl extension
+comment = 'PL/Tcl procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/pltcl'
+relocatable = false
+schema = pg_catalog
+superuser = true
+trusted = true
diff --git a/src/pl/tcl/pltclerrcodes.h b/src/pl/tcl/pltclerrcodes.h
new file mode 100644
index 0000000..b3ca6df
--- /dev/null
+++ b/src/pl/tcl/pltclerrcodes.h
@@ -0,0 +1,998 @@
+/* autogenerated from src/backend/utils/errcodes.txt, do not edit */
+/* there is deliberately not an #ifndef PLTCLERRCODES_H here */
+
+{
+ "sql_statement_not_yet_complete", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE
+},
+
+{
+ "connection_exception", ERRCODE_CONNECTION_EXCEPTION
+},
+
+{
+ "connection_does_not_exist", ERRCODE_CONNECTION_DOES_NOT_EXIST
+},
+
+{
+ "connection_failure", ERRCODE_CONNECTION_FAILURE
+},
+
+{
+ "sqlclient_unable_to_establish_sqlconnection", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION
+},
+
+{
+ "sqlserver_rejected_establishment_of_sqlconnection", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION
+},
+
+{
+ "transaction_resolution_unknown", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN
+},
+
+{
+ "protocol_violation", ERRCODE_PROTOCOL_VIOLATION
+},
+
+{
+ "triggered_action_exception", ERRCODE_TRIGGERED_ACTION_EXCEPTION
+},
+
+{
+ "feature_not_supported", ERRCODE_FEATURE_NOT_SUPPORTED
+},
+
+{
+ "invalid_transaction_initiation", ERRCODE_INVALID_TRANSACTION_INITIATION
+},
+
+{
+ "locator_exception", ERRCODE_LOCATOR_EXCEPTION
+},
+
+{
+ "invalid_locator_specification", ERRCODE_L_E_INVALID_SPECIFICATION
+},
+
+{
+ "invalid_grantor", ERRCODE_INVALID_GRANTOR
+},
+
+{
+ "invalid_grant_operation", ERRCODE_INVALID_GRANT_OPERATION
+},
+
+{
+ "invalid_role_specification", ERRCODE_INVALID_ROLE_SPECIFICATION
+},
+
+{
+ "diagnostics_exception", ERRCODE_DIAGNOSTICS_EXCEPTION
+},
+
+{
+ "stacked_diagnostics_accessed_without_active_handler", ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER
+},
+
+{
+ "case_not_found", ERRCODE_CASE_NOT_FOUND
+},
+
+{
+ "cardinality_violation", ERRCODE_CARDINALITY_VIOLATION
+},
+
+{
+ "data_exception", ERRCODE_DATA_EXCEPTION
+},
+
+{
+ "array_subscript_error", ERRCODE_ARRAY_SUBSCRIPT_ERROR
+},
+
+{
+ "character_not_in_repertoire", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE
+},
+
+{
+ "datetime_field_overflow", ERRCODE_DATETIME_FIELD_OVERFLOW
+},
+
+{
+ "division_by_zero", ERRCODE_DIVISION_BY_ZERO
+},
+
+{
+ "error_in_assignment", ERRCODE_ERROR_IN_ASSIGNMENT
+},
+
+{
+ "escape_character_conflict", ERRCODE_ESCAPE_CHARACTER_CONFLICT
+},
+
+{
+ "indicator_overflow", ERRCODE_INDICATOR_OVERFLOW
+},
+
+{
+ "interval_field_overflow", ERRCODE_INTERVAL_FIELD_OVERFLOW
+},
+
+{
+ "invalid_argument_for_logarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG
+},
+
+{
+ "invalid_argument_for_ntile_function", ERRCODE_INVALID_ARGUMENT_FOR_NTILE
+},
+
+{
+ "invalid_argument_for_nth_value_function", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE
+},
+
+{
+ "invalid_argument_for_power_function", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION
+},
+
+{
+ "invalid_argument_for_width_bucket_function", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION
+},
+
+{
+ "invalid_character_value_for_cast", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST
+},
+
+{
+ "invalid_datetime_format", ERRCODE_INVALID_DATETIME_FORMAT
+},
+
+{
+ "invalid_escape_character", ERRCODE_INVALID_ESCAPE_CHARACTER
+},
+
+{
+ "invalid_escape_octet", ERRCODE_INVALID_ESCAPE_OCTET
+},
+
+{
+ "invalid_escape_sequence", ERRCODE_INVALID_ESCAPE_SEQUENCE
+},
+
+{
+ "nonstandard_use_of_escape_character", ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "invalid_indicator_parameter_value", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE
+},
+
+{
+ "invalid_parameter_value", ERRCODE_INVALID_PARAMETER_VALUE
+},
+
+{
+ "invalid_preceding_or_following_size", ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE
+},
+
+{
+ "invalid_regular_expression", ERRCODE_INVALID_REGULAR_EXPRESSION
+},
+
+{
+ "invalid_row_count_in_limit_clause", ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE
+},
+
+{
+ "invalid_row_count_in_result_offset_clause", ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE
+},
+
+{
+ "invalid_tablesample_argument", ERRCODE_INVALID_TABLESAMPLE_ARGUMENT
+},
+
+{
+ "invalid_tablesample_repeat", ERRCODE_INVALID_TABLESAMPLE_REPEAT
+},
+
+{
+ "invalid_time_zone_displacement_value", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE
+},
+
+{
+ "invalid_use_of_escape_character", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER
+},
+
+{
+ "most_specific_type_mismatch", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH
+},
+
+{
+ "null_value_not_allowed", ERRCODE_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "null_value_no_indicator_parameter", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER
+},
+
+{
+ "numeric_value_out_of_range", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE
+},
+
+{
+ "sequence_generator_limit_exceeded", ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED
+},
+
+{
+ "string_data_length_mismatch", ERRCODE_STRING_DATA_LENGTH_MISMATCH
+},
+
+{
+ "string_data_right_truncation", ERRCODE_STRING_DATA_RIGHT_TRUNCATION
+},
+
+{
+ "substring_error", ERRCODE_SUBSTRING_ERROR
+},
+
+{
+ "trim_error", ERRCODE_TRIM_ERROR
+},
+
+{
+ "unterminated_c_string", ERRCODE_UNTERMINATED_C_STRING
+},
+
+{
+ "zero_length_character_string", ERRCODE_ZERO_LENGTH_CHARACTER_STRING
+},
+
+{
+ "floating_point_exception", ERRCODE_FLOATING_POINT_EXCEPTION
+},
+
+{
+ "invalid_text_representation", ERRCODE_INVALID_TEXT_REPRESENTATION
+},
+
+{
+ "invalid_binary_representation", ERRCODE_INVALID_BINARY_REPRESENTATION
+},
+
+{
+ "bad_copy_file_format", ERRCODE_BAD_COPY_FILE_FORMAT
+},
+
+{
+ "untranslatable_character", ERRCODE_UNTRANSLATABLE_CHARACTER
+},
+
+{
+ "not_an_xml_document", ERRCODE_NOT_AN_XML_DOCUMENT
+},
+
+{
+ "invalid_xml_document", ERRCODE_INVALID_XML_DOCUMENT
+},
+
+{
+ "invalid_xml_content", ERRCODE_INVALID_XML_CONTENT
+},
+
+{
+ "invalid_xml_comment", ERRCODE_INVALID_XML_COMMENT
+},
+
+{
+ "invalid_xml_processing_instruction", ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION
+},
+
+{
+ "duplicate_json_object_key_value", ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE
+},
+
+{
+ "invalid_argument_for_sql_json_datetime_function", ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION
+},
+
+{
+ "invalid_json_text", ERRCODE_INVALID_JSON_TEXT
+},
+
+{
+ "invalid_sql_json_subscript", ERRCODE_INVALID_SQL_JSON_SUBSCRIPT
+},
+
+{
+ "more_than_one_sql_json_item", ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM
+},
+
+{
+ "no_sql_json_item", ERRCODE_NO_SQL_JSON_ITEM
+},
+
+{
+ "non_numeric_sql_json_item", ERRCODE_NON_NUMERIC_SQL_JSON_ITEM
+},
+
+{
+ "non_unique_keys_in_a_json_object", ERRCODE_NON_UNIQUE_KEYS_IN_A_JSON_OBJECT
+},
+
+{
+ "singleton_sql_json_item_required", ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED
+},
+
+{
+ "sql_json_array_not_found", ERRCODE_SQL_JSON_ARRAY_NOT_FOUND
+},
+
+{
+ "sql_json_member_not_found", ERRCODE_SQL_JSON_MEMBER_NOT_FOUND
+},
+
+{
+ "sql_json_number_not_found", ERRCODE_SQL_JSON_NUMBER_NOT_FOUND
+},
+
+{
+ "sql_json_object_not_found", ERRCODE_SQL_JSON_OBJECT_NOT_FOUND
+},
+
+{
+ "too_many_json_array_elements", ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS
+},
+
+{
+ "too_many_json_object_members", ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS
+},
+
+{
+ "sql_json_scalar_required", ERRCODE_SQL_JSON_SCALAR_REQUIRED
+},
+
+{
+ "sql_json_item_cannot_be_cast_to_target_type", ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE
+},
+
+{
+ "integrity_constraint_violation", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "restrict_violation", ERRCODE_RESTRICT_VIOLATION
+},
+
+{
+ "not_null_violation", ERRCODE_NOT_NULL_VIOLATION
+},
+
+{
+ "foreign_key_violation", ERRCODE_FOREIGN_KEY_VIOLATION
+},
+
+{
+ "unique_violation", ERRCODE_UNIQUE_VIOLATION
+},
+
+{
+ "check_violation", ERRCODE_CHECK_VIOLATION
+},
+
+{
+ "exclusion_violation", ERRCODE_EXCLUSION_VIOLATION
+},
+
+{
+ "invalid_cursor_state", ERRCODE_INVALID_CURSOR_STATE
+},
+
+{
+ "invalid_transaction_state", ERRCODE_INVALID_TRANSACTION_STATE
+},
+
+{
+ "active_sql_transaction", ERRCODE_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "branch_transaction_already_active", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE
+},
+
+{
+ "held_cursor_requires_same_isolation_level", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL
+},
+
+{
+ "inappropriate_access_mode_for_branch_transaction", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "inappropriate_isolation_level_for_branch_transaction", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "no_active_sql_transaction_for_branch_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION
+},
+
+{
+ "read_only_sql_transaction", ERRCODE_READ_ONLY_SQL_TRANSACTION
+},
+
+{
+ "schema_and_data_statement_mixing_not_supported", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED
+},
+
+{
+ "no_active_sql_transaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION
+},
+
+{
+ "in_failed_sql_transaction", ERRCODE_IN_FAILED_SQL_TRANSACTION
+},
+
+{
+ "idle_in_transaction_session_timeout", ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT
+},
+
+{
+ "invalid_sql_statement_name", ERRCODE_INVALID_SQL_STATEMENT_NAME
+},
+
+{
+ "triggered_data_change_violation", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION
+},
+
+{
+ "invalid_authorization_specification", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION
+},
+
+{
+ "invalid_password", ERRCODE_INVALID_PASSWORD
+},
+
+{
+ "dependent_privilege_descriptors_still_exist", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST
+},
+
+{
+ "dependent_objects_still_exist", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST
+},
+
+{
+ "invalid_transaction_termination", ERRCODE_INVALID_TRANSACTION_TERMINATION
+},
+
+{
+ "sql_routine_exception", ERRCODE_SQL_ROUTINE_EXCEPTION
+},
+
+{
+ "function_executed_no_return_statement", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT
+},
+
+{
+ "modifying_sql_data_not_permitted", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "prohibited_sql_statement_attempted", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "reading_sql_data_not_permitted", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "invalid_cursor_name", ERRCODE_INVALID_CURSOR_NAME
+},
+
+{
+ "external_routine_exception", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION
+},
+
+{
+ "containing_sql_not_permitted", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED
+},
+
+{
+ "modifying_sql_data_not_permitted", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "prohibited_sql_statement_attempted", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED
+},
+
+{
+ "reading_sql_data_not_permitted", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED
+},
+
+{
+ "external_routine_invocation_exception", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION
+},
+
+{
+ "invalid_sqlstate_returned", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED
+},
+
+{
+ "null_value_not_allowed", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED
+},
+
+{
+ "trigger_protocol_violated", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "srf_protocol_violated", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED
+},
+
+{
+ "event_trigger_protocol_violated", ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED
+},
+
+{
+ "savepoint_exception", ERRCODE_SAVEPOINT_EXCEPTION
+},
+
+{
+ "invalid_savepoint_specification", ERRCODE_S_E_INVALID_SPECIFICATION
+},
+
+{
+ "invalid_catalog_name", ERRCODE_INVALID_CATALOG_NAME
+},
+
+{
+ "invalid_schema_name", ERRCODE_INVALID_SCHEMA_NAME
+},
+
+{
+ "transaction_rollback", ERRCODE_TRANSACTION_ROLLBACK
+},
+
+{
+ "transaction_integrity_constraint_violation", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION
+},
+
+{
+ "serialization_failure", ERRCODE_T_R_SERIALIZATION_FAILURE
+},
+
+{
+ "statement_completion_unknown", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN
+},
+
+{
+ "deadlock_detected", ERRCODE_T_R_DEADLOCK_DETECTED
+},
+
+{
+ "syntax_error_or_access_rule_violation", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION
+},
+
+{
+ "syntax_error", ERRCODE_SYNTAX_ERROR
+},
+
+{
+ "insufficient_privilege", ERRCODE_INSUFFICIENT_PRIVILEGE
+},
+
+{
+ "cannot_coerce", ERRCODE_CANNOT_COERCE
+},
+
+{
+ "grouping_error", ERRCODE_GROUPING_ERROR
+},
+
+{
+ "windowing_error", ERRCODE_WINDOWING_ERROR
+},
+
+{
+ "invalid_recursion", ERRCODE_INVALID_RECURSION
+},
+
+{
+ "invalid_foreign_key", ERRCODE_INVALID_FOREIGN_KEY
+},
+
+{
+ "invalid_name", ERRCODE_INVALID_NAME
+},
+
+{
+ "name_too_long", ERRCODE_NAME_TOO_LONG
+},
+
+{
+ "reserved_name", ERRCODE_RESERVED_NAME
+},
+
+{
+ "datatype_mismatch", ERRCODE_DATATYPE_MISMATCH
+},
+
+{
+ "indeterminate_datatype", ERRCODE_INDETERMINATE_DATATYPE
+},
+
+{
+ "collation_mismatch", ERRCODE_COLLATION_MISMATCH
+},
+
+{
+ "indeterminate_collation", ERRCODE_INDETERMINATE_COLLATION
+},
+
+{
+ "wrong_object_type", ERRCODE_WRONG_OBJECT_TYPE
+},
+
+{
+ "generated_always", ERRCODE_GENERATED_ALWAYS
+},
+
+{
+ "undefined_column", ERRCODE_UNDEFINED_COLUMN
+},
+
+{
+ "undefined_function", ERRCODE_UNDEFINED_FUNCTION
+},
+
+{
+ "undefined_table", ERRCODE_UNDEFINED_TABLE
+},
+
+{
+ "undefined_parameter", ERRCODE_UNDEFINED_PARAMETER
+},
+
+{
+ "undefined_object", ERRCODE_UNDEFINED_OBJECT
+},
+
+{
+ "duplicate_column", ERRCODE_DUPLICATE_COLUMN
+},
+
+{
+ "duplicate_cursor", ERRCODE_DUPLICATE_CURSOR
+},
+
+{
+ "duplicate_database", ERRCODE_DUPLICATE_DATABASE
+},
+
+{
+ "duplicate_function", ERRCODE_DUPLICATE_FUNCTION
+},
+
+{
+ "duplicate_prepared_statement", ERRCODE_DUPLICATE_PSTATEMENT
+},
+
+{
+ "duplicate_schema", ERRCODE_DUPLICATE_SCHEMA
+},
+
+{
+ "duplicate_table", ERRCODE_DUPLICATE_TABLE
+},
+
+{
+ "duplicate_alias", ERRCODE_DUPLICATE_ALIAS
+},
+
+{
+ "duplicate_object", ERRCODE_DUPLICATE_OBJECT
+},
+
+{
+ "ambiguous_column", ERRCODE_AMBIGUOUS_COLUMN
+},
+
+{
+ "ambiguous_function", ERRCODE_AMBIGUOUS_FUNCTION
+},
+
+{
+ "ambiguous_parameter", ERRCODE_AMBIGUOUS_PARAMETER
+},
+
+{
+ "ambiguous_alias", ERRCODE_AMBIGUOUS_ALIAS
+},
+
+{
+ "invalid_column_reference", ERRCODE_INVALID_COLUMN_REFERENCE
+},
+
+{
+ "invalid_column_definition", ERRCODE_INVALID_COLUMN_DEFINITION
+},
+
+{
+ "invalid_cursor_definition", ERRCODE_INVALID_CURSOR_DEFINITION
+},
+
+{
+ "invalid_database_definition", ERRCODE_INVALID_DATABASE_DEFINITION
+},
+
+{
+ "invalid_function_definition", ERRCODE_INVALID_FUNCTION_DEFINITION
+},
+
+{
+ "invalid_prepared_statement_definition", ERRCODE_INVALID_PSTATEMENT_DEFINITION
+},
+
+{
+ "invalid_schema_definition", ERRCODE_INVALID_SCHEMA_DEFINITION
+},
+
+{
+ "invalid_table_definition", ERRCODE_INVALID_TABLE_DEFINITION
+},
+
+{
+ "invalid_object_definition", ERRCODE_INVALID_OBJECT_DEFINITION
+},
+
+{
+ "with_check_option_violation", ERRCODE_WITH_CHECK_OPTION_VIOLATION
+},
+
+{
+ "insufficient_resources", ERRCODE_INSUFFICIENT_RESOURCES
+},
+
+{
+ "disk_full", ERRCODE_DISK_FULL
+},
+
+{
+ "out_of_memory", ERRCODE_OUT_OF_MEMORY
+},
+
+{
+ "too_many_connections", ERRCODE_TOO_MANY_CONNECTIONS
+},
+
+{
+ "configuration_limit_exceeded", ERRCODE_CONFIGURATION_LIMIT_EXCEEDED
+},
+
+{
+ "program_limit_exceeded", ERRCODE_PROGRAM_LIMIT_EXCEEDED
+},
+
+{
+ "statement_too_complex", ERRCODE_STATEMENT_TOO_COMPLEX
+},
+
+{
+ "too_many_columns", ERRCODE_TOO_MANY_COLUMNS
+},
+
+{
+ "too_many_arguments", ERRCODE_TOO_MANY_ARGUMENTS
+},
+
+{
+ "object_not_in_prerequisite_state", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE
+},
+
+{
+ "object_in_use", ERRCODE_OBJECT_IN_USE
+},
+
+{
+ "cant_change_runtime_param", ERRCODE_CANT_CHANGE_RUNTIME_PARAM
+},
+
+{
+ "lock_not_available", ERRCODE_LOCK_NOT_AVAILABLE
+},
+
+{
+ "unsafe_new_enum_value_usage", ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE
+},
+
+{
+ "operator_intervention", ERRCODE_OPERATOR_INTERVENTION
+},
+
+{
+ "query_canceled", ERRCODE_QUERY_CANCELED
+},
+
+{
+ "admin_shutdown", ERRCODE_ADMIN_SHUTDOWN
+},
+
+{
+ "crash_shutdown", ERRCODE_CRASH_SHUTDOWN
+},
+
+{
+ "cannot_connect_now", ERRCODE_CANNOT_CONNECT_NOW
+},
+
+{
+ "database_dropped", ERRCODE_DATABASE_DROPPED
+},
+
+{
+ "idle_session_timeout", ERRCODE_IDLE_SESSION_TIMEOUT
+},
+
+{
+ "system_error", ERRCODE_SYSTEM_ERROR
+},
+
+{
+ "io_error", ERRCODE_IO_ERROR
+},
+
+{
+ "undefined_file", ERRCODE_UNDEFINED_FILE
+},
+
+{
+ "duplicate_file", ERRCODE_DUPLICATE_FILE
+},
+
+{
+ "snapshot_too_old", ERRCODE_SNAPSHOT_TOO_OLD
+},
+
+{
+ "config_file_error", ERRCODE_CONFIG_FILE_ERROR
+},
+
+{
+ "lock_file_exists", ERRCODE_LOCK_FILE_EXISTS
+},
+
+{
+ "fdw_error", ERRCODE_FDW_ERROR
+},
+
+{
+ "fdw_column_name_not_found", ERRCODE_FDW_COLUMN_NAME_NOT_FOUND
+},
+
+{
+ "fdw_dynamic_parameter_value_needed", ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED
+},
+
+{
+ "fdw_function_sequence_error", ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR
+},
+
+{
+ "fdw_inconsistent_descriptor_information", ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION
+},
+
+{
+ "fdw_invalid_attribute_value", ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE
+},
+
+{
+ "fdw_invalid_column_name", ERRCODE_FDW_INVALID_COLUMN_NAME
+},
+
+{
+ "fdw_invalid_column_number", ERRCODE_FDW_INVALID_COLUMN_NUMBER
+},
+
+{
+ "fdw_invalid_data_type", ERRCODE_FDW_INVALID_DATA_TYPE
+},
+
+{
+ "fdw_invalid_data_type_descriptors", ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS
+},
+
+{
+ "fdw_invalid_descriptor_field_identifier", ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER
+},
+
+{
+ "fdw_invalid_handle", ERRCODE_FDW_INVALID_HANDLE
+},
+
+{
+ "fdw_invalid_option_index", ERRCODE_FDW_INVALID_OPTION_INDEX
+},
+
+{
+ "fdw_invalid_option_name", ERRCODE_FDW_INVALID_OPTION_NAME
+},
+
+{
+ "fdw_invalid_string_length_or_buffer_length", ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH
+},
+
+{
+ "fdw_invalid_string_format", ERRCODE_FDW_INVALID_STRING_FORMAT
+},
+
+{
+ "fdw_invalid_use_of_null_pointer", ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER
+},
+
+{
+ "fdw_too_many_handles", ERRCODE_FDW_TOO_MANY_HANDLES
+},
+
+{
+ "fdw_out_of_memory", ERRCODE_FDW_OUT_OF_MEMORY
+},
+
+{
+ "fdw_no_schemas", ERRCODE_FDW_NO_SCHEMAS
+},
+
+{
+ "fdw_option_name_not_found", ERRCODE_FDW_OPTION_NAME_NOT_FOUND
+},
+
+{
+ "fdw_reply_handle", ERRCODE_FDW_REPLY_HANDLE
+},
+
+{
+ "fdw_schema_not_found", ERRCODE_FDW_SCHEMA_NOT_FOUND
+},
+
+{
+ "fdw_table_not_found", ERRCODE_FDW_TABLE_NOT_FOUND
+},
+
+{
+ "fdw_unable_to_create_execution", ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION
+},
+
+{
+ "fdw_unable_to_create_reply", ERRCODE_FDW_UNABLE_TO_CREATE_REPLY
+},
+
+{
+ "fdw_unable_to_establish_connection", ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION
+},
+
+{
+ "plpgsql_error", ERRCODE_PLPGSQL_ERROR
+},
+
+{
+ "raise_exception", ERRCODE_RAISE_EXCEPTION
+},
+
+{
+ "no_data_found", ERRCODE_NO_DATA_FOUND
+},
+
+{
+ "too_many_rows", ERRCODE_TOO_MANY_ROWS
+},
+
+{
+ "assert_failure", ERRCODE_ASSERT_FAILURE
+},
+
+{
+ "internal_error", ERRCODE_INTERNAL_ERROR
+},
+
+{
+ "data_corrupted", ERRCODE_DATA_CORRUPTED
+},
+
+{
+ "index_corrupted", ERRCODE_INDEX_CORRUPTED
+},
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
new file mode 100644
index 0000000..fca869f
--- /dev/null
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -0,0 +1,9 @@
+/* src/pl/tcl/pltclu--1.0.sql */
+
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+ LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE pltclu
+ HANDLER pltclu_call_handler;
+
+COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/src/pl/tcl/pltclu.control b/src/pl/tcl/pltclu.control
new file mode 100644
index 0000000..1418dc5
--- /dev/null
+++ b/src/pl/tcl/pltclu.control
@@ -0,0 +1,7 @@
+# pltclu extension
+comment = 'PL/TclU untrusted procedural language'
+default_version = '1.0'
+module_pathname = '$libdir/pltcl'
+relocatable = false
+schema = pg_catalog
+superuser = true
diff --git a/src/pl/tcl/po/cs.po b/src/pl/tcl/po/cs.po
new file mode 100644
index 0000000..e48dff2
--- /dev/null
+++ b/src/pl/tcl/po/cs.po
@@ -0,0 +1,117 @@
+# Czech message translation file for pltcl
+# Copyright (C) 2012 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Tomáš Vondra <tv@fuzzy.cz>, 2012, 2013.
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl-cs (PostgreSQL 9.3)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-09-27 08:07+0000\n"
+"PO-Revision-Date: 2019-09-27 21:02+0200\n"
+"Last-Translator: Tomas Vondra <tv@fuzzy.cz>\n"
+"Language-Team: Czech <info@cspug.cx>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.2.3\n"
+
+#: pltcl.c:464
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "PL/Tcl funkce kterou zavolat jednou při prvním použití pltcl."
+
+#: pltcl.c:471
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "PL/TclU funkce kterou zavolat jednou při prvním použití pltclu."
+
+#: pltcl.c:636
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "funkce \"%s\" je ve špatném jazyce"
+
+#: pltcl.c:647
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "funkce \"%s\" nesmí být SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:681
+#, c-format
+msgid "processing %s parameter"
+msgstr "zpracovávám %s parametr"
+
+#: pltcl.c:842
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set-valued funkce volána v kontextu který nemůže přijmout více řádek"
+
+#: pltcl.c:1015
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkce vracející záznam volána v kontextu který nemůže přijmout záznam"
+
+#: pltcl.c:1299
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "z triggeru nelze oddělit návratovou hodnotu: %s"
+
+#: pltcl.c:1379 pltcl.c:1809
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1380
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"v PL/Tcl funkci \"%s\""
+
+#: pltcl.c:1544
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funkce pro obsluhu triggerů mohou být volané pouze prostřednictvím triggerů"
+
+#: pltcl.c:1548
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl funkce nemohou vracet datový typ %s"
+
+#: pltcl.c:1587
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl funkce nemohou přijímat datový typ %s"
+
+#: pltcl.c:1701
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "nelze vytvořit interní proceduru \"%s\": %s"
+
+#: pltcl.c:3208
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "seznam názvů sloupců a hodnot musí mít sudý počet položek"
+
+#: pltcl.c:3226
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "seznam názvů sloupců a hodnot obsahuje neexistující název sloupce \"%s\""
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nelze nastavit systémový atribut \"%s\""
+
+#: pltcl.c:3239
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "nelze přiřazovat do generovaného sloupce \"%s\""
+
+#~ msgid "PL/Tcl functions cannot return composite types"
+#~ msgstr "PL/Tcl funkce nemohou vracet složené datové typy"
+
+#~ msgid "out of memory"
+#~ msgstr "paměť vyčerpána"
diff --git a/src/pl/tcl/po/de.po b/src/pl/tcl/po/de.po
new file mode 100644
index 0000000..e191a2b
--- /dev/null
+++ b/src/pl/tcl/po/de.po
@@ -0,0 +1,115 @@
+# German message translation file for PL/Tcl
+# Peter Eisentraut <peter@eisentraut.org>, 2009 - 2022.
+#
+# Use these quotes: »%s«
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-08 12:09+0000\n"
+"PO-Revision-Date: 2022-04-08 14:40+0200\n"
+"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n"
+"Language-Team: German <pgsql-translators@postgresql.org>\n"
+"Language: de\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "PL/Tcl-Funktion, die einmal aufgerufen wird, wenn pltcl zum ersten Mal benutzt wird."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "PL/Tcl-Funktion, die einmal aufgerufen wird, wenn pltclu zum ersten Mal benutzt wird."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "Funktion »%s« ist in der falschen Sprache"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "Funktion »%s« darf nicht SECURITY DEFINER sein"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "Verarbeiten von Parameter von %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "Funktion mit Mengenergebnis in einem Zusammenhang aufgerufen, der keine Mengenergebnisse verarbeiten kann"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "Materialisierungsmodus wird benötigt, ist aber in diesem Zusammenhang nicht erlaubt"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "Funktion, die einen Record zurückgibt, in einem Zusammenhang aufgerufen, der Typ record nicht verarbeiten kann"
+
+#: pltcl.c:1297
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "konnte Rückgabewert des Triggers nicht splitten: %s"
+
+#: pltcl.c:1378 pltcl.c:1808
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1379
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"in PL/Tcl-Funktion »%s«"
+
+#: pltcl.c:1543
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggerfunktionen können nur als Trigger aufgerufen werden"
+
+#: pltcl.c:1547
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl-Funktionen können keinen Rückgabetyp %s haben"
+
+#: pltcl.c:1586
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl-Funktionen können Typ %s nicht annehmen"
+
+#: pltcl.c:1700
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "konnte interne Prozedur »%s« nicht erzeugen: %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "Liste der Spaltennamen/-werte muss gerade Anzahl Elemente haben"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "Liste der Spaltennamen/-werte enthält nicht existierenden Spaltennamen »%s«"
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "Systemattribut »%s« kann nicht gesetzt werden"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kann generierte Spalte »%s« nicht setzen"
diff --git a/src/pl/tcl/po/el.po b/src/pl/tcl/po/el.po
new file mode 100644
index 0000000..6d0bd50
--- /dev/null
+++ b/src/pl/tcl/po/el.po
@@ -0,0 +1,118 @@
+# Greek message translation file for pltcl
+# Copyright (C) 2021 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pltcl (PostgreSQL) package.
+# Georgios Kokolatos <gkokolatos@pm.me>, 2021
+#
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-14 09:08+0000\n"
+"PO-Revision-Date: 2023-04-14 15:05+0200\n"
+"Last-Translator: Georgios Kokolatos <gkokolatos@pm.me>\n"
+"Language-Team: \n"
+"Language: el\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 3.2.2\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Συνάρτηση PL/Tcl για να καλέσει μία μόνο φορά όταν χρησιμοποιείται pltcl για πρώτη φορά."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Συνάρτηση PL/Tcl για να καλέσει μία μόνο φορά όταν χρησιμοποιείται pltclu για πρώτη φορά ."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "η συνάρτηση «%s» βρίσκεται σε λάθος γλώσσα"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "η συνάρτηση «%s» δεν πρέπει να είναι SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "επεξεργάζεται παράμετρο %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set-valued συνάρτηση καλείται σε περιεχόμενο που δεν μπορεί να δεχτεί ένα σύνολο"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "επιβάλλεται λειτουργία υλοποίησης, αλλά δεν επιτρέπεται σε αυτό το περιεχόμενο"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "συνάρτηση που επιστρέφει εγγραφή καλείται σε περιεχόμενο που δεν δύναται να αποδεχτεί τύπο εγγραφής"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "δεν ήταν δυνατός ο διαχωρισμός επιστρεφόμενης τιμής από έναυσμα: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"στη συνάρτηση PL/Tcl «%s»"
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "συναρτήσεις εναυσμάτων μπορούν να κληθούν μόνο ως εναύσματα"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "συναρτήσεις PL/Tcl δεν είναι δυνατό να επιστρέψουν τύπο %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "συναρτήσεις PL/Tcl δεν είναι δυνατό να δεχτούν τύπο %s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "δεν ήταν δυνατή η δημιουργία εσωτερικής διαδικασίας «%s»: %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "λίστα ονόματος/τιμής στήλης πρέπει να έχει άρτιο αριθμό στοιχείων"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "λίστα ονόματος/τιμής στήλης περιέχει ανύπαρκτο όνομα στήλης «%s»"
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός του χαρακτηριστικού συστήματος «%s»"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "δεν είναι δυνατός ο ορισμός δημιουργημένης στήλης «%s»"
diff --git a/src/pl/tcl/po/es.po b/src/pl/tcl/po/es.po
new file mode 100644
index 0000000..fe7b70a
--- /dev/null
+++ b/src/pl/tcl/po/es.po
@@ -0,0 +1,119 @@
+# Spanish translation file for pltcl
+#
+# Copyright (c) 2009-2021, PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Emanuel Calvo Franco <postgres.arg@gmail.com>, 2009.
+# Alvaro Herrera <alvherre@alvh.no-ip.org>, 2009-2012, 2015
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-07 16:38+0000\n"
+"PO-Revision-Date: 2022-10-20 09:06+0200\n"
+"Last-Translator: Carlos Chapi <carlos.chapi@2ndquadrant.com>\n"
+"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n"
+"Language: es\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.2\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "función PL/Tcl a ejecutar cuando se use pltcl por primera vez."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "función PL/TclU a ejecutar cuando se use pltclu por primera vez."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "la función «%s» está en el lenguaje equivocado"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "la función «%s» no debe ser SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "procesando el parámetro %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "se llamó una función que retorna un conjunto en un contexto que no puede aceptarlo"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "se requiere un nodo «materialize», pero no está permitido en este contexto"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "se llamó una función que retorna un registro en un contexto que no puede aceptarlo"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "no se pudo separar el valor de retorno del disparador: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"en función PL/Tcl \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "las funciones disparadoras sólo pueden ser invocadas como disparadores"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "las funciones PL/Tcl no pueden retornar tipo %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "las funciones PL/Tcl no pueden aceptar el tipog%s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "no se pudo crear procedimiento interno «%s»: %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "la lista de nombres de columnas y valores debe tener un número par de elementos"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "la lista de nombres de columnas y valores contiene el nombre de columna no existente «%s»"
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "no se puede definir el atributo de sistema «%s»"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "no se puede definir el atributo generado «%s»"
diff --git a/src/pl/tcl/po/fr.po b/src/pl/tcl/po/fr.po
new file mode 100644
index 0000000..2af48d8
--- /dev/null
+++ b/src/pl/tcl/po/fr.po
@@ -0,0 +1,140 @@
+# LANGUAGE message translation file for pltcl
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pltcl (PostgreSQL) package.
+#
+# Use these quotes: « %s »
+#
+# Guillaume Lelarge <guillaume@lelarge.info>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-12 05:16+0000\n"
+"PO-Revision-Date: 2022-04-12 17:29+0200\n"
+"Last-Translator: Guillaume Lelarge <guillaume@lelarge.info>\n"
+"Language-Team: French <guillaume@lelarge.info>\n"
+"Language: fr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"X-Generator: Poedit 3.0.1\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Fonction PL/Tcl à appeler une fois quand pltcl est utilisé pour la première fois."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Fonction PL/TclU à appeler une fois quand pltcl est utilisé pour la première fois."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "la fonction « %s » est dans le mauvais langage"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "la fonction « %s » doit être définie en SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "traitement du paramètre %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "la fonction renvoyant un ensemble a été appelée dans un contexte qui n'accepte pas un ensemble"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "mode matérialisé requis mais interdit dans ce contexte"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr ""
+"fonction renvoyant le type record appelée dans un contexte qui ne peut pas\n"
+"accepter le type record"
+
+#: pltcl.c:1297
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "n'a pas pu séparer la valeur de retour du trigger : %s"
+
+#: pltcl.c:1378 pltcl.c:1808
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1379
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"dans la fonction PL/Tcl « %s »"
+
+#: pltcl.c:1543
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "les fonctions trigger peuvent seulement être appelées par des triggers"
+
+#: pltcl.c:1547
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "les fonctions PL/Tcl ne peuvent pas renvoyer le type %s"
+
+#: pltcl.c:1586
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "les fonctions PL/Tcl ne peuvent pas accepter le type %s"
+
+#: pltcl.c:1700
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "n'a pas pu créer la procédure interne « %s » : %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "la liste de nom de colonne/valeur doit avoir un nombre pair d'éléments"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "la liste de nom de colonne/valeur contient des noms de colonne inexistantes (« %s »)"
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "ne peut pas initialiser l'attribut système « %s »"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "ne peut pas initialiser la colonne générée « %s »"
+
+#~ msgid "PL/Tcl functions cannot return composite types"
+#~ msgstr "les fonctions PL/Tcl ne peuvent pas renvoyer des types composites"
+
+#~ msgid "could not load module \"unknown\": %s"
+#~ msgstr "n'a pas pu charger le module « unknown » : %s"
+
+#~ msgid "module \"unknown\" not found in pltcl_modules"
+#~ msgstr "module « unkown » introuvable dans pltcl_modules"
+
+#~ msgid "out of memory"
+#~ msgstr "mémoire épuisée"
+
+#~ msgid "trigger's return list must have even number of elements"
+#~ msgstr "la liste de retour du trigger doit avoir un nombre pair d'éléments"
+
+#~ msgid "unrecognized attribute \"%s\""
+#~ msgstr "attribut « %s » non reconnu"
diff --git a/src/pl/tcl/po/it.po b/src/pl/tcl/po/it.po
new file mode 100644
index 0000000..01e64c6
--- /dev/null
+++ b/src/pl/tcl/po/it.po
@@ -0,0 +1,127 @@
+#
+# pltcl.po
+# Italian message translation file for pltcl
+#
+# For development and bug report please use:
+# https://github.com/dvarrazzo/postgresql-it
+#
+# Copyright (C) 2012-2017 PostgreSQL Global Development Group
+# Copyright (C) 2010, Associazione Culturale ITPUG
+#
+# Daniele Varrazzo <daniele.varrazzo@gmail.com>, 2012-2017.
+# Flavio Spada <f.spada@sbv.mi.it>
+#
+# This file is distributed under the same license as the PostgreSQL package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 10\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-26 08:08+0000\n"
+"PO-Revision-Date: 2022-09-26 15:04+0200\n"
+"Last-Translator: Daniele Varrazzo <daniele.varrazzo@gmail.com>\n"
+"Language-Team: https://github.com/dvarrazzo/postgresql-it\n"
+"Language: it\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+"X-Generator: Poedit 3.1.1\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Funzione PL/Tcl da richiamare una volta quando pltcl è usato la prima volta."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Funzione PL/TclU da richiamare una volta quando pltcl è usato la prima volta."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "la funzione \"%s\" è nel linguaggio sbagliato"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "la funzione \"%s\" non può essere SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "esecuzione del parametro %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "funzione che restituisce insiemi richiamata in un contesto che non può accettare un insieme"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "necessaria modalità materializzata, ma non ammessa in questo contesto"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funzione che restituisce record richiamata in un contesto che non può accettare record"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "divisione del valore di ritorno del trigger fallita: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"nella funzione PL/Tcl \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "le funzioni trigger possono essere chiamate esclusivamente da trigger"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "le funzioni PL/Tcl non possono restituire il tipo %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "le funzioni PL/Tcl non possono accettare il tipo %s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "creazione della procedura interna \"%s\" fallita: %s"
+
+#: pltcl.c:3201
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "la lista nome/valore di colonne deve avere un numero di elementi pari"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "la lista nome/valore di elementi contiene un nome di colonna inesistente \"%s\""
+
+#: pltcl.c:3226
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "non è possibile impostare l'attributo di sistema \"%s\""
+
+#: pltcl.c:3232
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "non è possibile modificare la colonna ereditata \"%s\""
diff --git a/src/pl/tcl/po/ja.po b/src/pl/tcl/po/ja.po
new file mode 100644
index 0000000..d31a248
--- /dev/null
+++ b/src/pl/tcl/po/ja.po
@@ -0,0 +1,117 @@
+# Japanese message translation file for pltcl
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pg_archivecleanup (PostgreSQL) package.
+# KOIZUMI Satoru <koizumistr@minos.ocn.ne.jp>, 2015.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL 15)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-09 12:01+0900\n"
+"PO-Revision-Date: 2019-06-11 17:26+0900\n"
+"Last-Translator: Kyotaro Horiguchi <horikyota.ntt@gmail.com>\n"
+"Language-Team: Japan PostgreSQL Users Group <jpug-doc@ml.postgresql.jp>\n"
+"Language: ja\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"X-Generator: Poedit 1.5.4\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "pltcl が最初に使用される際に一度だけ呼び出される PL/Tcl 関数。"
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "pltclu が最初に使用される際に一度だけ呼び出される PL/TclU 関数。"
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "関数\"%s\"は言語が異なります"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "関数\"%s\"はSECURITY DEFINERであってはなりません"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "%sパラメーターを処理しています"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "このコンテキストでは、集合値の関数は集合を受け付けられません"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "マテリアライズモードが必要ですが、現在のコンテクストで禁止されています"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "レコード型を受け付けられないコンテキストでレコードを返す関数が呼び出されました"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "トリガーからの戻り値を分割できませんでした: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"PL/Tcl 関数 \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "トリガー関数はトリガーとしてのみコールできます"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl 関数は%s型の戻り値を返せません"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl 関数は%s型を受け付けません"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "内部プロシージャ\"%s\"を作成できませんでした: %s"
+
+#: pltcl.c:3201
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "列名/値のリストの要素は偶数個でなければなりません"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "列名/値のリストの中に、存在しない列名\"%s\"が含まれています"
+
+#: pltcl.c:3226
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "システム属性\"%s\"は設定できません"
+
+#: pltcl.c:3232
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "生成列\"%s\"を変更できません"
diff --git a/src/pl/tcl/po/ka.po b/src/pl/tcl/po/ka.po
new file mode 100644
index 0000000..1554015
--- /dev/null
+++ b/src/pl/tcl/po/ka.po
@@ -0,0 +1,117 @@
+# Georgian message translation file for pltcl
+# Copyright (C) 2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pltcl (PostgreSQL) package.
+# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-07-06 23:08+0000\n"
+"PO-Revision-Date: 2022-07-07 06:12+0200\n"
+"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n"
+"Language-Team: Georgian <nothing>\n"
+"Language: ka\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: Poedit 3.1\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "PL/Tcl ფუნქცია, როლის გამოძახებაც ერთხელ, pltcl-ის პირველი გამოყენებისას ხდება."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "PL/TclU ფუნქცია, როლის გამოძახებაც ერთხელ, pltclu -ის პირველი გამოყენებისას ხდება."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "ფუნქცია \"%s\" არასწორ ენაზეა"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "ფუნქცია \"%s\" არ უნდა იყოს SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "პარამეტრის დამუშავება: %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "სეტ-ღირებული ფუნქცია, რომელსაც ეწოდება კონტექსტში, რომელსაც არ შეუძლია მიიღოს ნაკრები"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "საჭიროა მატერიალიზებული რეჟიმი, მაგრამ ამ კონტექსტში ეს დაუშვებელია"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "ფუნქცია, რომელიც ჩანაწერს აბრუნებს, გამოძახებულია კონტექსტში, რომელსაც ჩანაწერის მიღება არ შეუძლია"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "ტრიგერიდან დაბრუნებული მნიშვნელობის დაყოფა შეუძლებელია: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"PL/Tcl-ის ფუნქციაში\"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "ტრიგერის ფუნქციების გამოძახება მხოლოდ ტრიგერებად შეიძლება"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl ფუნქციას %s ტიპის დაბრუნება არ შეუძლია"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl ფუნქციას %s ტიპის მიღება არ შეუძლია"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "შიდა პროცედურის (\"%s\") შექმნის შეცდომა: %s"
+
+#: pltcl.c:3201
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "სვეტის სახელი/მნიშვნელობების სიას ელემენტების ლუწი რაოდენობა უნდა ჰქონდეს"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "სვეტის სახელი/მნიშვნელობების სია შეიცავს სვეტის არარსებულ სახელს „%s“"
+
+#: pltcl.c:3226
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "სისტემური ატრიბუტის დაყენების შეცდომა: \"%s\""
+
+#: pltcl.c:3232
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "გენერირებული სვეტის დაყენება შეუძლებელია: %s"
diff --git a/src/pl/tcl/po/ko.po b/src/pl/tcl/po/ko.po
new file mode 100644
index 0000000..ac9954b
--- /dev/null
+++ b/src/pl/tcl/po/ko.po
@@ -0,0 +1,118 @@
+# LANGUAGE message translation file for pltcl
+# Copyright (C) 2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Ioseph Kim <ioseph@uri.sarang.net>, 2016.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-04-12 00:38+0000\n"
+"PO-Revision-Date: 2023-04-06 10:02+0900\n"
+"Last-Translator: Ioseph Kim <ioseph@uri.sarang.net>\n"
+"Language-Team: Korean Team <pgsql-kr@postgresql.kr>\n"
+"Language: ko\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "pltcl 언어가 처음 사용될 때 한번 호출 될 PL/Tcl 함수"
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "pltclu 언어가 처음 사용될 때 한번 호출 될 PL/Tcl 함수"
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "\"%s\" 함수에 잘못된 언어가 있음"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "\"%s\" 함수는 SECURITY DEFINER 속성이 없어야 합니다"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "%s 매개 변수 처리 중"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "집합이 값이 함수가 집합을 사용할 수 없는 구문에서 호출 되었음"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialize 모드가 필요합니다만, 이 구문에서는 허용되지 않습니다"
+
+#: pltcl.c:1013
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"레코드를 반환하는 함수가 레코드 형을 사용할 수 없는 구문에서 호출 되었음"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "트리거에서 반환값을 분리할 수 없음: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"해당 PL/Tcl 함수: \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "트리거 함수는 트리거로만 호출될 수 있음"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl 함수는 %s 자료형을 반환할 수 없음"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl 함수는 %s 자료형을 사용할 수 없음"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "\"%s\" 내부 프로시져를 만들 수 없음: %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "칼럼 이름/값 목록은 그 요소의 개수가 짝수여야 함"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "칼럼 이름/값 목록에 \"%s\" 칼럼에 대한 값이 없음"
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" 시스템 속성을 지정할 수 없음"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "\"%s\" 계산된 칼럼을 지정할 수 없음"
diff --git a/src/pl/tcl/po/pl.po b/src/pl/tcl/po/pl.po
new file mode 100644
index 0000000..0a0b2bf
--- /dev/null
+++ b/src/pl/tcl/po/pl.po
@@ -0,0 +1,136 @@
+# pltcl message translation file for pltcl
+# Copyright (C) 2011 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Begina Felicysym <begina.felicysym@wp.eu>, 2011.
+# grzegorz <begina.felicysym@wp.eu>, 2015, 2017.
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL 9.5)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2017-04-09 21:07+0000\n"
+"PO-Revision-Date: 2017-05-02 13:57-0400\n"
+"Last-Translator: grzegorz <begina.felicysym@wp.eu>\n"
+"Language-Team: begina.felicysym@wp.eu\n"
+"Language: pl\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
+"|| n%100>=20) ? 1 : 2);\n"
+"X-Generator: Virtaal 0.7.1\n"
+
+#: pltcl.c:459
+#| msgid "Perl initialization code to execute once when plperl is first used."
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr ""
+"Funkcja PL/TCL do jednokrotnego wykonania gdy pltcl jest użyty po raz "
+"pierwszy."
+
+#: pltcl.c:466
+#| msgid "Perl initialization code to execute once when plperlu is first used."
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr ""
+"Funkcja PL/TCL do jednokrotnego wykonania gdy pltclu jest użyty po raz "
+"pierwszy."
+
+#: pltcl.c:629
+#, c-format
+#| msgid "function \"%s\" was not called by trigger manager"
+msgid "function \"%s\" is in the wrong language"
+msgstr "funkcja \"%s\" jest napisana nie w tym języku"
+
+#: pltcl.c:640
+#, c-format
+#| msgid "function \"%s\" must be fired for INSERT"
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "funkcja \"%s\" nie może być SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:674
+#, c-format
+#| msgid "processing %s\n"
+msgid "processing %s parameter"
+msgstr "przetwarzanie parametru %s"
+
+#: pltcl.c:830
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"funkcja zwracająca zbiór rekordów wywołana w kontekście, w którym nie jest "
+"to dopuszczalne"
+
+#: pltcl.c:994
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "funkcja zwracająca rekord w wywołaniu, które nie akceptuje typów złożonych"
+
+#: pltcl.c:1263
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "nie można podzielić wartości zwracanej przez wyzwalacz: %s"
+
+#: pltcl.c:1343 pltcl.c:1771
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1344
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"w funkcji PL/Tcl \"%s\""
+
+#: pltcl.c:1509
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "procedury wyzwalaczy mogą być wywoływane jedynie przez wyzwalacze"
+
+#: pltcl.c:1513
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "funkcje PL/Tcl nie mogą zwracać wartości typu %s"
+
+#: pltcl.c:1549
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "funkcje PL/Tcl nie akceptują typu %s"
+
+#: pltcl.c:1663
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "nie można utworzyć procedury wewnętrznej \"%s\": %s"
+
+#: pltcl.c:3100
+#, c-format
+#| msgid "argument list must have even number of elements"
+msgid "column name/value list must have even number of elements"
+msgstr "lista kolumn nazwa/wartość musi mieć parzystą liczbę elementów"
+
+#: pltcl.c:3118
+#, c-format
+#| msgid "Perl hash contains nonexistent column \"%s\""
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "lista kolumn nazwa/wartość zawiera nieistniejącą kolumnę \"%s\""
+
+#: pltcl.c:3125
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "nie można ustawić atrybutu systemowego \"%s\""
+
+#~ msgid "PL/Tcl functions cannot return composite types"
+#~ msgstr "funkcje PL/Tcl nie mogą zwracać wartości złożonych"
+
+#~ msgid "out of memory"
+#~ msgstr "brak pamięci"
+
+#~ msgid "unrecognized attribute \"%s\""
+#~ msgstr "nierozpoznany atrybut \"%s\""
+
+#~ msgid "could not load module \"unknown\": %s"
+#~ msgstr "nie można wczytać modułu \"unknown\": %s"
+
+#~ msgid "module \"unknown\" not found in pltcl_modules"
+#~ msgstr "nie znaleziono modułu \"unknown\" w pltcl_modules"
diff --git a/src/pl/tcl/po/pt_BR.po b/src/pl/tcl/po/pt_BR.po
new file mode 100644
index 0000000..39ee004
--- /dev/null
+++ b/src/pl/tcl/po/pt_BR.po
@@ -0,0 +1,117 @@
+# Brazilian Portuguese message translation file for pltcl
+#
+# Copyright (C) 2009-2022 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+#
+# Euler Taveira <euler@eulerto.com>, 2009-2022.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-09-27 13:15-0300\n"
+"PO-Revision-Date: 2009-05-06 18:00-0300\n"
+"Last-Translator: Euler Taveira <euler@eulerto.com>\n"
+"Language-Team: Brazilian Portuguese <pgsql-translators@postgresql.org>\n"
+"Language: pt_BR\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "função PL/Tcl executada quando pltcl for utilizado pela primeira vez."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "função PL/TclU executada quando pltclu for utilizado pela primeira vez."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "função \"%s\" está na linguagem incorreta"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "função \"%s\" não deve ser SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "processando parâmetro %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "função que tem argumento do tipo conjunto foi chamada em um contexto que não pode aceitar um conjunto"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "modo de materialização é requerido, mas ele não é permitido neste contexto"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "função que retorna record foi chamada em um contexto que não pode aceitar tipo record"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "não pôde dividir valor retornado do gatilho: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"na função PL/Tcl \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "funções de gatilho só podem ser chamadas como gatilhos"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "funções PL/Tcl não podem retornar tipo %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "funções PL/Tcl não podem aceitar tipo %s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "não pôde criar função interna \"%s\": %s"
+
+#: pltcl.c:3201
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "lista de nome/valor de colunas deve ter número par de elementos"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "lsta de nome/valor de colunas contém nome de coluna inexistente \"%s\""
+
+#: pltcl.c:3226
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "não pode definir atributo do sistema \"%s\""
+
+#: pltcl.c:3232
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "não pode definir coluna gerada \"%s\""
diff --git a/src/pl/tcl/po/ru.po b/src/pl/tcl/po/ru.po
new file mode 100644
index 0000000..876918d
--- /dev/null
+++ b/src/pl/tcl/po/ru.po
@@ -0,0 +1,135 @@
+# Russian message translation file for pltcl
+# Copyright (C) 2012-2016 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Alexander Lakhin <exclusion@gmail.com>, 2012-2017, 2019, 2022.
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL current)\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2023-05-03 05:56+0300\n"
+"PO-Revision-Date: 2022-09-05 13:38+0300\n"
+"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n"
+"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n"
+"Language: ru\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
+"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Функция на PL/Tcl, вызываемая при первом использовании pltcl."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Функция на PL/TclU, вызываемая при первом использовании pltclu."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "Функция \"%s\" объявлена на другом языке"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "функция \"%s\" не должна иметь характеристику SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "обработка параметра %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr ""
+"функция, возвращающая множество, вызвана в контексте, где ему нет места"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "требуется режим материализации, но он недопустим в этом контексте"
+
+#: pltcl.c:1013
+#, c-format
+msgid ""
+"function returning record called in context that cannot accept type record"
+msgstr ""
+"функция, возвращающая запись, вызвана в контексте, не допускающем этот тип"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "разложить возвращаемое из триггера значение не удалось: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"в функции PL/Tcl \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "триггерные функции могут вызываться только в триггерах"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "функции PL/Tcl не могут возвращать тип %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "функции PL/Tcl не могут принимать тип %s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "не удалось создать внутреннюю процедуру \"%s\": %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "в списке имён/значений столбцов должно быть чётное число элементов"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr ""
+"список имён/значений столбцов содержит имя несуществующего столбца \"%s\""
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "присвоить значение системному атрибуту \"%s\" нельзя"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "присвоить значение генерируемому столбцу \"%s\" нельзя"
+
+#~ msgid "module \"unknown\" not found in pltcl_modules"
+#~ msgstr "модуль \"unknown\" не найден в pltcl_modules"
+
+#~ msgid "could not load module \"unknown\": %s"
+#~ msgstr "загрузить модуль \"unknown\" не удалось: %s"
+
+#~ msgid "unrecognized attribute \"%s\""
+#~ msgstr "нераспознанный атрибут \"%s\""
+
+#~ msgid "out of memory"
+#~ msgstr "нехватка памяти"
+
+#~ msgid "PL/Tcl functions cannot return composite types"
+#~ msgstr "функции PL/Tcl не могут возвращать составные типы"
diff --git a/src/pl/tcl/po/sv.po b/src/pl/tcl/po/sv.po
new file mode 100644
index 0000000..5d14044
--- /dev/null
+++ b/src/pl/tcl/po/sv.po
@@ -0,0 +1,116 @@
+# Swedish message translation file for pltcl
+# Copyright (C) 2017 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# Dennis Björklund <db@zigo.dhs.org>, 2017, 2018, 2019, 2020, 2021, 2022, 2023.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 15\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-04-11 13:38+0000\n"
+"PO-Revision-Date: 2023-03-09 22:42+0100\n"
+"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n"
+"Language-Team: Swedish <pgsql-translators@postgresql.org>\n"
+"Language: sv\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=n != 1;\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "PL/Tcl-funktion att anropa en gång när pltcl först används."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "PL/TclU-funktion att anrop en gång när pltclu först används."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "funktionen \"%s\" är skriven i fel språk"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "funktionen \"%s\" får ej vara SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "processar parameter %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "en funktion som returnerar en mängd anropades i kontext som inte godtar en mängd"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "materialiserat läge krävs, men stöds inte i detta kontext"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "en funktion med post som värde anropades i sammanhang där poster inte kan godtagas."
+
+#: pltcl.c:1297
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "kunde inte dela på returvärde från trigger: %s"
+
+#: pltcl.c:1378 pltcl.c:1808
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1379
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"i PL/Tcl-funktion \"%s\""
+
+#: pltcl.c:1543
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "Triggningsfunktioner kan bara anropas vid triggning."
+
+#: pltcl.c:1547
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl-funktioner kan inte returnera typ %s"
+
+#: pltcl.c:1586
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl-funktioner kan inte ta emot typ %s"
+
+#: pltcl.c:1700
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "kunde inte skapa en intern procedur \"%s\": %s"
+
+#: pltcl.c:3202
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "kolumn-namn/-värde måste ha ett jämt antal element"
+
+#: pltcl.c:3220
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "listan med kolumn-namn/-värde innehåller det icke existerande kolumnnamnet \"%s\""
+
+#: pltcl.c:3227
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "kan inte sätta systemattribut \"%s\""
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "kan inte sätta genererad kolumn \"%s\""
diff --git a/src/pl/tcl/po/tr.po b/src/pl/tcl/po/tr.po
new file mode 100644
index 0000000..ee51dfd
--- /dev/null
+++ b/src/pl/tcl/po/tr.po
@@ -0,0 +1,117 @@
+# LANGUAGE message translation file for pltcl
+# Copyright (C) 2009 PostgreSQL Global Development Group
+# This file is distributed under the same license as the PostgreSQL package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2009.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PostgreSQL 8.4\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2019-04-26 13:38+0000\n"
+"PO-Revision-Date: 2019-06-13 17:11+0300\n"
+"Last-Translator: Devrim GÜNDÜZ <devrim@commandprompt.com>\n"
+"Language-Team: TR <devrim@commandprompt.com>\n"
+"Language: tr\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.8.7.1\n"
+
+#: pltcl.c:464
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "pltcl ilk sefer kullanıldığında bir kez çağrılacak PL/Tcl fonksiyonu"
+
+#: pltcl.c:471
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "pltclu ilk sefer kullanıldığında bir kez çağrılacak PL/Tclu fonksiyonu"
+
+#: pltcl.c:636
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "\"%s\" fonksiyonu yanlış dilde"
+
+#: pltcl.c:647
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "\"%s\" fonksiyonu SECURITY DEFINER olmamalı"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:681
+#, c-format
+msgid "processing %s parameter"
+msgstr "%s parametresi işleniyor"
+
+#: pltcl.c:842
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "set değerini kabul etmediği ortamda set değeri alan fonksiyon çağırılmış"
+
+#: pltcl.c:1015
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "tip kaydı içermeyen alanda çağırılan ve kayıt döndüren fonksiyon"
+
+#: pltcl.c:1299
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "sdönüş değeri tetikleyiciden (trigger) ayrılamadı: %s"
+
+#: pltcl.c:1379 pltcl.c:1809
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1380
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"Şu PL/Tcl fonksiyonunda: \"%s\""
+
+#: pltcl.c:1544
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "trigger fonksiyonları sadece trigger olarak çağırılabilirler"
+
+#: pltcl.c:1548
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl fonksiyonları %s tipini döndüremezler"
+
+#: pltcl.c:1587
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl fonksiyonları %s veri tipini kabul etmezler"
+
+#: pltcl.c:1701
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "\"%s\" dahili yordamı oluşturulamadı: %s"
+
+#: pltcl.c:3208
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "sütun adı/değer listesinin çift sayıda öğesi olmalı"
+
+#: pltcl.c:3226
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "sütun adı/değer listesi mevcut olmayan \"%s\" sütun adını içeriyor"
+
+#: pltcl.c:3233
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "\"%s\" sistem niteliği ayarlanamaz"
+
+#: pltcl.c:3239
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "oluşturulan \"%s\" sütunu ayarlanamıyor"
+
+#~ msgid "out of memory"
+#~ msgstr "yetersiz bellek"
+
+#~ msgid "PL/Tcl functions cannot return composite types"
+#~ msgstr "PL/Tcl fonksiyonları composit tip döndüremezler"
diff --git a/src/pl/tcl/po/uk.po b/src/pl/tcl/po/uk.po
new file mode 100644
index 0000000..ceb1e78
--- /dev/null
+++ b/src/pl/tcl/po/uk.po
@@ -0,0 +1,115 @@
+msgid ""
+msgstr ""
+"Project-Id-Version: postgresql\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2022-08-12 10:38+0000\n"
+"PO-Revision-Date: 2022-09-13 11:52\n"
+"Last-Translator: \n"
+"Language-Team: Ukrainian\n"
+"Language: uk_UA\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+"X-Crowdin-Project: postgresql\n"
+"X-Crowdin-Project-ID: 324573\n"
+"X-Crowdin-Language: uk\n"
+"X-Crowdin-File: /REL_15_STABLE/pltcl.pot\n"
+"X-Crowdin-File-ID: 914\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Функція PL/Tcl використовується для виклику коли pltcl вперше використаний."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Функція PL/TclU використовується для виклику коли pltclu вперше використаний."
+
+#: pltcl.c:637
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "функція «%s» написана неправильною мовою"
+
+#: pltcl.c:648
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "функція \"%s\" не має бути SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:682
+#, c-format
+msgid "processing %s parameter"
+msgstr "обробляється параметр %s"
+
+#: pltcl.c:835
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "функція \"set-valued\" викликана в контексті, де йому немає місця"
+
+#: pltcl.c:840
+#, c-format
+msgid "materialize mode required, but it is not allowed in this context"
+msgstr "необхідний режим матеріалізації (materialize mode), але він неприпустимий у цьому контексті"
+
+#: pltcl.c:1013
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "функція, що повертає набір, викликана у контексті, що не приймає тип запис"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "не вдалося розділити повернене значення з тригера: %s"
+
+#: pltcl.c:1377 pltcl.c:1807
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1378
+#, c-format
+msgid "%s\n"
+"in PL/Tcl function \"%s\""
+msgstr "%s\n"
+"у функції PL/Tcl \"%s\""
+
+#: pltcl.c:1542
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "тригер-функція може викликатися лише як тригер"
+
+#: pltcl.c:1546
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "Функції PL/Tcl не можуть повертати тип %s"
+
+#: pltcl.c:1585
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "Функції PL/Tcl не можуть приймати тип %s"
+
+#: pltcl.c:1699
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "не вдалося створити внутрішню процедуру \"%s\": %s"
+
+#: pltcl.c:3201
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "список імен і значень стовпців повинен мати парну кількість елементів"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "список імен і значень стовпців містить неіснуєче ім'я стовпця \"%s\""
+
+#: pltcl.c:3226
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "не вдалося встановити системний атрибут \"%s\""
+
+#: pltcl.c:3232
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "неможливо оновити згенерований стовпець \"%s\""
+
diff --git a/src/pl/tcl/po/vi.po b/src/pl/tcl/po/vi.po
new file mode 100644
index 0000000..7224bf1
--- /dev/null
+++ b/src/pl/tcl/po/vi.po
@@ -0,0 +1,107 @@
+# LANGUAGE message translation file for pltcl
+# Copyright (C) 2018 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pltcl (PostgreSQL) package.
+# FIRST AUTHOR <kakalot49@gmail.com>, 2018.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 11\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n"
+"POT-Creation-Date: 2018-04-22 12:08+0000\n"
+"PO-Revision-Date: 2018-04-29 22:56+0900\n"
+"Language-Team: <pgvn_translators@postgresql.vn>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 2.0.6\n"
+"Last-Translator: Dang Minh Huong <kakalot49@gmail.com>\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"Language: vi_VN\n"
+
+#: pltcl.c:466
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "Chỉ định hàm PL/Tcl được gọi một lần khi pltcl sử dụng lần đầu tiên."
+
+#: pltcl.c:473
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "Chỉ định hàm PL/TclU được gọi một lần khi pltclu sử dụng lần đầu tiên."
+
+#: pltcl.c:640
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "hàm \"%s\" không đúng ngôn ngữ"
+
+#: pltcl.c:651
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "hàm \"%s\" không được là SECURITY DEFINER"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:685
+#, c-format
+msgid "processing %s parameter"
+msgstr "xử lý tham số %s"
+
+#: pltcl.c:846
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "hàm thiết lập giá trị được gọi trong ngữ cảnh không thể chấp nhận một tập hợp"
+
+#: pltcl.c:1019
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "hàm trả về bản ghi được gọi trong ngữ cảnh không thể chấp nhận kiểu bản ghi"
+
+#: pltcl.c:1296
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "không thể tách giá trị trả về khỏi trigger: %s"
+
+#: pltcl.c:1376 pltcl.c:1806
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1377
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"trong hàm PL/Tcl \"%s\""
+
+#: pltcl.c:1541
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "hàm trigger chỉ có thể được goi như những triggers."
+
+#: pltcl.c:1545
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "Hàm PL/Tcl không thể trả về kiểu %s"
+
+#: pltcl.c:1584
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "Hàm PL/Tcl không thể chấp nhận kiểu %s"
+
+#: pltcl.c:1698
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "không thể tạo procedure nội bộ \"%s\": %s"
+
+#: pltcl.c:3219
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "danh sách cột tên/giá trị phải có giá trị chẵn cho số phần tử"
+
+#: pltcl.c:3237
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "danh sách cột tên/giá trị chứa tên cột không tồn tại \"%s\""
+
+#: pltcl.c:3244
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "không thể thiết lập attribute hệ thống \"%s\""
diff --git a/src/pl/tcl/po/zh_CN.po b/src/pl/tcl/po/zh_CN.po
new file mode 100644
index 0000000..d9e49a7
--- /dev/null
+++ b/src/pl/tcl/po/zh_CN.po
@@ -0,0 +1,111 @@
+# LANGUAGE message translation file for pltcl
+# Copyright (C) 2019 PostgreSQL Global Development Group
+# This file is distributed under the same license as the pltcl (PostgreSQL) package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, 2019.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: pltcl (PostgreSQL) 14\n"
+"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n"
+"POT-Creation-Date: 2021-08-14 05:38+0000\n"
+"PO-Revision-Date: 2021-08-16 18:00+0800\n"
+"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n"
+"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n"
+"Language: zh_CN\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: Poedit 1.5.7\n"
+
+#: pltcl.c:463
+msgid "PL/Tcl function to call once when pltcl is first used."
+msgstr "PL/Tcl函数在首次使用pltcl时调用一次."
+
+#: pltcl.c:470
+msgid "PL/TclU function to call once when pltclu is first used."
+msgstr "PL/TclU函数在首次使用pltcl时调用一次."
+
+#: pltcl.c:634
+#, c-format
+msgid "function \"%s\" is in the wrong language"
+msgstr "函数\"%s\"的语言错误"
+
+#: pltcl.c:645
+#, c-format
+msgid "function \"%s\" must not be SECURITY DEFINER"
+msgstr "函数\"%s\"不能是安全定义者"
+
+#. translator: %s is "pltcl.start_proc" or "pltclu.start_proc"
+#: pltcl.c:679
+#, c-format
+msgid "processing %s parameter"
+msgstr "正在处理%s参数"
+
+#: pltcl.c:833
+#, c-format
+msgid "set-valued function called in context that cannot accept a set"
+msgstr "在不能接受使用集合的环境中调用set-valued函数"
+
+#: pltcl.c:1006
+#, c-format
+msgid "function returning record called in context that cannot accept type record"
+msgstr "返回值类型是记录的函数在不接受使用记录类型的环境中调用"
+
+#: pltcl.c:1290
+#, c-format
+msgid "could not split return value from trigger: %s"
+msgstr "无法分离来自触发器的返回值:%s"
+
+#: pltcl.c:1371 pltcl.c:1801
+#, c-format
+msgid "%s"
+msgstr "%s"
+
+#: pltcl.c:1372
+#, c-format
+msgid ""
+"%s\n"
+"in PL/Tcl function \"%s\""
+msgstr ""
+"%s\n"
+"在PL/Tcl函数\"%s\"中"
+
+#: pltcl.c:1536
+#, c-format
+msgid "trigger functions can only be called as triggers"
+msgstr "触发器函数只能以触发器的形式调用"
+
+#: pltcl.c:1540
+#, c-format
+msgid "PL/Tcl functions cannot return type %s"
+msgstr "PL/Tcl函数不能返回类型%s"
+
+#: pltcl.c:1579
+#, c-format
+msgid "PL/Tcl functions cannot accept type %s"
+msgstr "PL/Tcl函数不能使用类型 %s"
+
+#: pltcl.c:1693
+#, c-format
+msgid "could not create internal procedure \"%s\": %s"
+msgstr "无法创建内部过程\"%s\":%s"
+
+#: pltcl.c:3197
+#, c-format
+msgid "column name/value list must have even number of elements"
+msgstr "列名/值列表必须具有偶数个元素"
+
+#: pltcl.c:3215
+#, c-format
+msgid "column name/value list contains nonexistent column name \"%s\""
+msgstr "列名/值列表包含不存在的列名\"%s\""
+
+#: pltcl.c:3222
+#, c-format
+msgid "cannot set system attribute \"%s\""
+msgstr "不能设置系统属性\"%s\""
+
+#: pltcl.c:3228
+#, c-format
+msgid "cannot set generated column \"%s\""
+msgstr "无法设置生成的列 \"%s\""
diff --git a/src/pl/tcl/sql/pltcl_call.sql b/src/pl/tcl/sql/pltcl_call.sql
new file mode 100644
index 0000000..37efbde
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_call.sql
@@ -0,0 +1,78 @@
+CREATE PROCEDURE test_proc1()
+LANGUAGE pltcl
+AS $$
+unset
+$$;
+
+CALL test_proc1();
+
+
+CREATE PROCEDURE test_proc2()
+LANGUAGE pltcl
+AS $$
+return 5
+$$;
+
+CALL test_proc2();
+
+
+CREATE TABLE test1 (a int);
+
+CREATE PROCEDURE test_proc3(x int)
+LANGUAGE pltcl
+AS $$
+spi_exec "INSERT INTO test1 VALUES ($1)"
+$$;
+
+CALL test_proc3(55);
+
+SELECT * FROM test1;
+
+
+-- output arguments
+
+CREATE PROCEDURE test_proc5(INOUT a text)
+LANGUAGE pltcl
+AS $$
+set aa [concat $1 "+" $1]
+return [list a $aa]
+$$;
+
+CALL test_proc5('abc');
+
+
+CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int)
+LANGUAGE pltcl
+AS $$
+set bb [expr $2 * $1]
+set cc [expr $3 * $1]
+return [list b $bb c $cc]
+$$;
+
+CALL test_proc6(2, 3, 4);
+
+
+-- OUT parameters
+
+CREATE PROCEDURE test_proc9(IN a int, OUT b int)
+LANGUAGE pltcl
+AS $$
+elog NOTICE "a: $1"
+return [list b [expr {$1 * 2}]]
+$$;
+
+DO $$
+DECLARE _a int; _b int;
+BEGIN
+ _a := 10; _b := 30;
+ CALL test_proc9(_a, _b);
+ RAISE NOTICE '_a: %, _b: %', _a, _b;
+END
+$$;
+
+
+DROP PROCEDURE test_proc1;
+DROP PROCEDURE test_proc2;
+DROP PROCEDURE test_proc3;
+
+DROP TABLE test1;
diff --git a/src/pl/tcl/sql/pltcl_queries.sql b/src/pl/tcl/sql/pltcl_queries.sql
new file mode 100644
index 0000000..bbd2d97
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_queries.sql
@@ -0,0 +1,166 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+
+-- Test composite-type arguments
+select tcl_composite_arg_ref1(row('tkey', 42, 'ref2'));
+select tcl_composite_arg_ref2(row('tkey', 42, 'ref2'));
+
+-- More tests for composite argument/result types
+
+create domain d_comp1 as T_comp1 check ((value).ref1 > 0);
+
+create function tcl_record_arg(record, fldname text) returns int as '
+ return $1($2)
+' language pltcl;
+
+select tcl_record_arg(row('tkey', 42, 'ref2')::T_comp1, 'ref1');
+select tcl_record_arg(row('tkey', 42, 'ref2')::d_comp1, 'ref1');
+select tcl_record_arg(row(2,4), 'f2');
+
+create function tcl_cdomain_arg(d_comp1) returns int as '
+ return $1(ref1)
+' language pltcl;
+
+select tcl_cdomain_arg(row('tkey', 42, 'ref2'));
+select tcl_cdomain_arg(row('tkey', 42, 'ref2')::T_comp1);
+select tcl_cdomain_arg(row('tkey', -1, 'ref2')); -- fail
+
+-- Test argisnull primitive
+select tcl_argisnull('foo');
+select tcl_argisnull('');
+select tcl_argisnull(null);
+
+-- test some error cases
+create function tcl_error(out a int, out b int) as $$return {$$ language pltcl;
+select tcl_error();
+
+create function bad_record(out a text, out b text) as $$return [list a]$$ language pltcl;
+select bad_record();
+
+create function bad_field(out a text, out b text) as $$return [list a 1 b 2 cow 3]$$ language pltcl;
+select bad_field();
+
+-- test compound return
+select * from tcl_test_cube_squared(5);
+
+-- test SRF
+select * from tcl_test_squared_rows(0,5);
+
+select * from tcl_test_sequence(0,5) as a;
+
+select 1, tcl_test_sequence(0,5);
+
+create function non_srf() returns int as $$return_next 1$$ language pltcl;
+select non_srf();
+
+create function bad_record_srf(out a text, out b text) returns setof record as $$
+return_next [list a]
+$$ language pltcl;
+select bad_record_srf();
+
+create function bad_field_srf(out a text, out b text) returns setof record as $$
+return_next [list a 1 b 2 cow 3]
+$$ language pltcl;
+select bad_field_srf();
+
+-- test composite and domain-over-composite results
+create function tcl_composite_result(int) returns T_comp1 as $$
+return [list tkey tkey1 ref1 $1 ref2 ref22]
+$$ language pltcl;
+select tcl_composite_result(1001);
+select * from tcl_composite_result(1002);
+
+create function tcl_dcomposite_result(int) returns d_comp1 as $$
+return [list tkey tkey2 ref1 $1 ref2 ref42]
+$$ language pltcl;
+select tcl_dcomposite_result(1001);
+select * from tcl_dcomposite_result(1002);
+select * from tcl_dcomposite_result(-1); -- fail
+
+create function tcl_record_result(int) returns record as $$
+return [list q1 sometext q2 $1 q3 moretext]
+$$ language pltcl;
+select tcl_record_result(42); -- fail
+select * from tcl_record_result(42); -- fail
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text);
+select * from tcl_record_result(42) as (q1 text, q2 int, q3 text, q4 int);
+select * from tcl_record_result(42) as (q1 text, q2 int, q4 int); -- fail
+
+-- test quote
+select tcl_eval('quote foo bar');
+select tcl_eval('quote [format %c 39]');
+select tcl_eval('quote [format %c 92]');
+
+-- Test argisnull
+select tcl_eval('argisnull');
+select tcl_eval('argisnull 14');
+select tcl_eval('argisnull abc');
+
+-- Test return_null
+select tcl_eval('return_null 14');
+
+-- Test spi_exec
+select tcl_eval('spi_exec');
+select tcl_eval('spi_exec -count');
+select tcl_eval('spi_exec -array');
+select tcl_eval('spi_exec -count abc');
+select tcl_eval('spi_exec query loop body toomuch');
+select tcl_eval('spi_exec "begin; rollback;"');
+
+-- Test spi_execp
+select tcl_eval('spi_execp');
+select tcl_eval('spi_execp -count');
+select tcl_eval('spi_execp -array');
+select tcl_eval('spi_execp -count abc');
+select tcl_eval('spi_execp -nulls');
+select tcl_eval('spi_execp ""');
+
+-- test spi_prepare
+select tcl_eval('spi_prepare');
+select tcl_eval('spi_prepare a b');
+select tcl_eval('spi_prepare a "b {"');
+select tcl_error_handling_test($tcl$spi_prepare "select moo" []$tcl$);
+
+-- test full error text
+select tcl_error_handling_test($tcl$
+spi_exec "DO $$
+BEGIN
+RAISE 'my message'
+ USING HINT = 'my hint'
+ , DETAIL = 'my detail'
+ , SCHEMA = 'my schema'
+ , TABLE = 'my table'
+ , COLUMN = 'my column'
+ , CONSTRAINT = 'my constraint'
+ , DATATYPE = 'my datatype'
+;
+END$$;"
+$tcl$);
+
+-- verify tcl_error_handling_test() properly reports non-postgres errors
+select tcl_error_handling_test('moo');
+
+-- test elog
+select tcl_eval('elog');
+select tcl_eval('elog foo bar');
+
+-- test forced error
+select tcl_eval('error "forced error"');
+
+-- test loop control in spi_exec[p]
+select tcl_spi_exec(true, 'break');
+select tcl_spi_exec(true, 'continue');
+select tcl_spi_exec(true, 'error');
+select tcl_spi_exec(true, 'return');
+select tcl_spi_exec(false, 'break');
+select tcl_spi_exec(false, 'continue');
+select tcl_spi_exec(false, 'error');
+select tcl_spi_exec(false, 'return');
+
+-- forcibly run the Tcl event loop for awhile, to check that we have not
+-- messed things up too badly by disabling the Tcl notifier subsystem
+select tcl_eval($$
+ unset -nocomplain ::tcl_vwait
+ after 100 {set ::tcl_vwait 1}
+ vwait ::tcl_vwait
+ unset -nocomplain ::tcl_vwait$$);
diff --git a/src/pl/tcl/sql/pltcl_setup.sql b/src/pl/tcl/sql/pltcl_setup.sql
new file mode 100644
index 0000000..b9892ea
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_setup.sql
@@ -0,0 +1,278 @@
+create table T_comp1 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+
+create function tcl_composite_arg_ref1(T_comp1) returns int as '
+ return $1(ref1)
+' language pltcl;
+
+create function tcl_composite_arg_ref2(T_comp1) returns text as '
+ return $1(ref2)
+' language pltcl;
+
+create function tcl_argisnull(text) returns bool as '
+ argisnull 1
+' language pltcl;
+
+
+create function tcl_int4add(int4,int4) returns int4 as '
+ return [expr $1 + $2]
+' language pltcl;
+
+-- We use split(n) as a quick-and-dirty way of parsing the input array
+-- value, which comes in as a string like '{1,2}'. There are better ways...
+
+create function tcl_int4_accum(int4[], int4) returns int4[] as '
+ set state [split $1 "{,}"]
+ set newsum [expr {[lindex $state 1] + $2}]
+ set newcnt [expr {[lindex $state 2] + 1}]
+ return "{$newsum,$newcnt}"
+' language pltcl;
+
+create function tcl_int4_avg(int4[]) returns int4 as '
+ set state [split $1 "{,}"]
+ if {[lindex $state 2] == 0} { return_null }
+ return [expr {[lindex $state 1] / [lindex $state 2]}]
+' language pltcl;
+
+create aggregate tcl_avg (
+ sfunc = tcl_int4_accum,
+ basetype = int4,
+ stype = int4[],
+ finalfunc = tcl_int4_avg,
+ initcond = '{0,0}'
+ );
+
+create aggregate tcl_sum (
+ sfunc = tcl_int4add,
+ basetype = int4,
+ stype = int4,
+ initcond1 = 0
+ );
+
+create function tcl_int4lt(int4,int4) returns bool as '
+ if {$1 < $2} {
+ return t
+ }
+ return f
+' language pltcl;
+
+create function tcl_int4le(int4,int4) returns bool as '
+ if {$1 <= $2} {
+ return t
+ }
+ return f
+' language pltcl;
+
+create function tcl_int4eq(int4,int4) returns bool as '
+ if {$1 == $2} {
+ return t
+ }
+ return f
+' language pltcl;
+
+create function tcl_int4ge(int4,int4) returns bool as '
+ if {$1 >= $2} {
+ return t
+ }
+ return f
+' language pltcl;
+
+create function tcl_int4gt(int4,int4) returns bool as '
+ if {$1 > $2} {
+ return t
+ }
+ return f
+' language pltcl;
+
+create operator @< (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4lt
+ );
+
+create operator @<= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4le
+ );
+
+create operator @= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4eq
+ );
+
+create operator @>= (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4ge
+ );
+
+create operator @> (
+ leftarg = int4,
+ rightarg = int4,
+ procedure = tcl_int4gt
+ );
+
+create function tcl_int4cmp(int4,int4) returns int4 as '
+ if {$1 < $2} {
+ return -1
+ }
+ if {$1 > $2} {
+ return 1
+ }
+ return 0
+' language pltcl;
+
+CREATE OPERATOR CLASS tcl_int4_ops
+ FOR TYPE int4 USING btree AS
+ OPERATOR 1 @<,
+ OPERATOR 2 @<=,
+ OPERATOR 3 @=,
+ OPERATOR 4 @>=,
+ OPERATOR 5 @>,
+ FUNCTION 1 tcl_int4cmp(int4,int4) ;
+
+--
+-- Test usage of Tcl's "clock" command. In recent Tcl versions this
+-- command fails without working "unknown" support, so it's a good canary
+-- for initialization problems.
+--
+create function tcl_date_week(int4,int4,int4) returns text as $$
+ return [clock format [clock scan "$2/$3/$1" -gmt 1] -format "%U" -gmt 1]
+$$ language pltcl immutable;
+
+select tcl_date_week(2010,1,26);
+select tcl_date_week(2001,10,24);
+
+-- test pltcl event triggers
+create function tclsnitch() returns event_trigger language pltcl as $$
+ elog NOTICE "tclsnitch: $TG_event $TG_tag"
+$$;
+
+create event trigger tcl_a_snitch on ddl_command_start execute procedure tclsnitch();
+create event trigger tcl_b_snitch on ddl_command_end execute procedure tclsnitch();
+
+create function foobar() returns int language sql as $$select 1;$$;
+alter function foobar() cost 77;
+drop function foobar();
+
+create table foo();
+drop table foo;
+
+drop event trigger tcl_a_snitch;
+drop event trigger tcl_b_snitch;
+
+create function tcl_test_cube_squared(in int, out squared int, out cubed int) as $$
+ return [list squared [expr {$1 * $1}] cubed [expr {$1 * $1 * $1}]]
+$$ language pltcl;
+
+create function tcl_test_squared_rows(int,int) returns table (x int, y int) as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next [list y [expr {$i * $i}] x $i]
+ }
+$$ language pltcl;
+
+create function tcl_test_sequence(int,int) returns setof int as $$
+ for {set i $1} {$i < $2} {incr i} {
+ return_next $i
+ }
+$$ language pltcl;
+
+create function tcl_eval(string text) returns text as $$
+ eval $1
+$$ language pltcl;
+
+-- test use of errorCode in error handling
+create function tcl_error_handling_test(text) returns text
+language pltcl
+as $function$
+ if {[catch $1 err]} {
+ # If not a Postgres error, just return the basic error message
+ if {[lindex $::errorCode 0] != "POSTGRES"} {
+ return $err
+ }
+
+ # Get rid of keys that can't be expected to remain constant
+ array set myArray $::errorCode
+ unset myArray(POSTGRES)
+ unset -nocomplain myArray(funcname)
+ unset -nocomplain myArray(filename)
+ unset -nocomplain myArray(lineno)
+
+ # Format into something nicer
+ set vals []
+ foreach {key} [lsort [array names myArray]] {
+ set value [string map {"\n" "\n\t"} $myArray($key)]
+ lappend vals "$key: $value"
+ }
+ return [join $vals "\n"]
+ } else {
+ return "no error"
+ }
+$function$;
+
+-- test spi_exec and spi_execp with -array
+create function tcl_spi_exec(
+ prepare boolean,
+ action text
+)
+returns void language pltcl AS $function$
+set query "select * from (values (1,'foo'),(2,'bar'),(3,'baz')) v(col1,col2)"
+if {$1 == "t"} {
+ set prep [spi_prepare $query {}]
+ spi_execp -array A $prep {
+ elog NOTICE "col1 $A(col1), col2 $A(col2)"
+
+ switch $A(col1) {
+ 2 {
+ elog NOTICE "action: $2"
+ switch $2 {
+ break {
+ break
+ }
+ continue {
+ continue
+ }
+ return {
+ return
+ }
+ error {
+ error "error message"
+ }
+ }
+ error "should not get here"
+ }
+ }
+ }
+} else {
+ spi_exec -array A $query {
+ elog NOTICE "col1 $A(col1), col2 $A(col2)"
+
+ switch $A(col1) {
+ 2 {
+ elog NOTICE "action: $2"
+ switch $2 {
+ break {
+ break
+ }
+ continue {
+ continue
+ }
+ return {
+ return
+ }
+ error {
+ error "error message"
+ }
+ }
+ error "should not get here"
+ }
+ }
+ }
+}
+elog NOTICE "end of function"
+$function$;
diff --git a/src/pl/tcl/sql/pltcl_start_proc.sql b/src/pl/tcl/sql/pltcl_start_proc.sql
new file mode 100644
index 0000000..7a8e68e
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_start_proc.sql
@@ -0,0 +1,21 @@
+--
+-- Test start_proc execution
+--
+
+SET pltcl.start_proc = 'no_such_function';
+
+select tcl_int4add(1, 2);
+select tcl_int4add(1, 2);
+
+create function tcl_initialize() returns void as
+$$ elog NOTICE "in tcl_initialize" $$ language pltcl SECURITY DEFINER;
+
+SET pltcl.start_proc = 'public.tcl_initialize';
+
+select tcl_int4add(1, 2); -- fail
+
+create or replace function tcl_initialize() returns void as
+$$ elog NOTICE "in tcl_initialize" $$ language pltcl;
+
+select tcl_int4add(1, 2);
+select tcl_int4add(1, 2);
diff --git a/src/pl/tcl/sql/pltcl_subxact.sql b/src/pl/tcl/sql/pltcl_subxact.sql
new file mode 100644
index 0000000..0625736
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_subxact.sql
@@ -0,0 +1,95 @@
+--
+-- Test explicit subtransactions
+--
+
+CREATE TABLE subtransaction_tbl (
+ i integer
+);
+
+--
+-- We use this wrapper to catch errors and return errormsg only,
+-- because values of $::errorinfo variable contain procedure name which
+-- includes OID, so it's not stable
+--
+CREATE FUNCTION pltcl_wrapper(statement text) RETURNS text
+AS $$
+ if [catch {spi_exec $1} msg] {
+ return "ERROR: $msg"
+ } else {
+ return "SUCCESS: $msg"
+ }
+$$ LANGUAGE pltcl;
+
+-- Test subtransaction successfully committed
+
+CREATE FUNCTION subtransaction_ctx_success() RETURNS void
+AS $$
+ spi_exec "INSERT INTO subtransaction_tbl VALUES(1)"
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES(2)"
+ }
+$$ LANGUAGE pltcl;
+
+BEGIN;
+INSERT INTO subtransaction_tbl VALUES(0);
+SELECT subtransaction_ctx_success();
+COMMIT;
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Test subtransaction rollback
+
+CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS void
+AS $$
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (1)"
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (2)"
+ if {$1 == "SPI"} {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES ('oops')"
+ } elseif { $1 == "Tcl"} {
+ elog ERROR "Tcl error"
+ }
+ }
+$$ LANGUAGE pltcl;
+
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test()');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test(''SPI'')');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+SELECT pltcl_wrapper('SELECT subtransaction_ctx_test(''Tcl'')');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+-- Nested subtransactions
+
+CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text
+AS $$
+spi_exec "INSERT INTO subtransaction_tbl VALUES (1)"
+subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (2)"
+ if [catch {
+ subtransaction {
+ spi_exec "INSERT INTO subtransaction_tbl VALUES (3)"
+ spi_exec "error"
+ }
+ } errormsg] {
+ if {$1 != "t"} {
+ error $errormsg $::errorInfo $::errorCode
+ }
+ elog NOTICE "Swallowed $errormsg"
+ }
+}
+return "ok"
+$$ LANGUAGE pltcl;
+
+SELECT pltcl_wrapper('SELECT subtransaction_nested_test()');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
+
+SELECT pltcl_wrapper('SELECT subtransaction_nested_test(''t'')');
+SELECT * FROM subtransaction_tbl;
+TRUNCATE subtransaction_tbl;
diff --git a/src/pl/tcl/sql/pltcl_transaction.sql b/src/pl/tcl/sql/pltcl_transaction.sql
new file mode 100644
index 0000000..bd75985
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_transaction.sql
@@ -0,0 +1,135 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+
+CREATE TABLE test1 (a int, b text);
+
+
+CREATE PROCEDURE transaction_test1()
+LANGUAGE pltcl
+AS $$
+for {set i 0} {$i < 10} {incr i} {
+ spi_exec "INSERT INTO test1 (a) VALUES ($i)"
+ if {$i % 2 == 0} {
+ commit
+ } else {
+ rollback
+ }
+}
+$$;
+
+CALL transaction_test1();
+
+SELECT * FROM test1;
+
+
+TRUNCATE test1;
+
+-- not allowed in a function
+CREATE FUNCTION transaction_test2() RETURNS int
+LANGUAGE pltcl
+AS $$
+for {set i 0} {$i < 10} {incr i} {
+ spi_exec "INSERT INTO test1 (a) VALUES ($i)"
+ if {$i % 2 == 0} {
+ commit
+ } else {
+ rollback
+ }
+}
+return 1
+$$;
+
+SELECT transaction_test2();
+
+SELECT * FROM test1;
+
+
+-- also not allowed if procedure is called from a function
+CREATE FUNCTION transaction_test3() RETURNS int
+LANGUAGE pltcl
+AS $$
+spi_exec "CALL transaction_test1()"
+return 1
+$$;
+
+SELECT transaction_test3();
+
+SELECT * FROM test1;
+
+
+-- commit inside cursor loop
+CREATE TABLE test2 (x int);
+INSERT INTO test2 VALUES (0), (1), (2), (3), (4);
+
+TRUNCATE test1;
+
+CREATE PROCEDURE transaction_test4a()
+LANGUAGE pltcl
+AS $$
+spi_exec -array row "SELECT * FROM test2 ORDER BY x" {
+ spi_exec "INSERT INTO test1 (a) VALUES ($row(x))"
+ commit
+}
+$$;
+
+CALL transaction_test4a();
+
+SELECT * FROM test1;
+
+
+-- rollback inside cursor loop
+TRUNCATE test1;
+
+CREATE PROCEDURE transaction_test4b()
+LANGUAGE pltcl
+AS $$
+spi_exec -array row "SELECT * FROM test2 ORDER BY x" {
+ spi_exec "INSERT INTO test1 (a) VALUES ($row(x))"
+ rollback
+}
+$$;
+
+CALL transaction_test4b();
+
+SELECT * FROM test1;
+
+
+-- check handling of an error during COMMIT
+CREATE TABLE testpk (id int PRIMARY KEY);
+CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED);
+
+CREATE PROCEDURE transaction_testfk()
+LANGUAGE pltcl
+AS $$
+# this insert will fail during commit:
+spi_exec "INSERT INTO testfk VALUES (0)"
+commit
+elog WARNING "should not get here"
+$$;
+
+CALL transaction_testfk();
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+CREATE OR REPLACE PROCEDURE transaction_testfk()
+LANGUAGE pltcl
+AS $$
+# this insert will fail during commit:
+spi_exec "INSERT INTO testfk VALUES (0)"
+if [catch {commit} msg] {
+ elog INFO $msg
+}
+# these inserts should work:
+spi_exec "INSERT INTO testpk VALUES (1)"
+spi_exec "INSERT INTO testfk VALUES (1)"
+$$;
+
+CALL transaction_testfk();
+
+SELECT * FROM testpk;
+SELECT * FROM testfk;
+
+
+DROP TABLE test1;
+DROP TABLE test2;
diff --git a/src/pl/tcl/sql/pltcl_trigger.sql b/src/pl/tcl/sql/pltcl_trigger.sql
new file mode 100644
index 0000000..2db75a3
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_trigger.sql
@@ -0,0 +1,603 @@
+-- suppress CONTEXT so that function OIDs aren't in output
+\set VERBOSITY terse
+
+--
+-- Create the tables used in the test queries
+--
+-- T_pkey1 is the primary key table for T_dta1. Entries from T_pkey1
+-- Cannot be changed or deleted if they are referenced from T_dta1.
+--
+-- T_pkey2 is the primary key table for T_dta2. If the key values in
+-- T_pkey2 are changed, the references in T_dta2 follow. If entries
+-- are deleted, the referencing entries from T_dta2 are deleted too.
+-- The values for field key2 in T_pkey2 are silently converted to
+-- upper case on insert/update.
+--
+create table T_pkey1 (
+ key1 int4,
+ key2 char(20),
+ txt char(40)
+);
+
+create table T_pkey2 (
+ key1 int4,
+ key2 char(20),
+ txt char(40)
+);
+
+create table T_dta1 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+
+create table T_dta2 (
+ tkey char(10),
+ ref1 int4,
+ ref2 char(20)
+);
+
+
+--
+-- Function to check key existence in T_pkey1
+--
+create function check_pkey1_exists(int4, bpchar) returns bool as E'
+ if {![info exists GD]} {
+ set GD(plan) [spi_prepare \\
+ "select 1 from T_pkey1 \\
+ where key1 = \\$1 and key2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ set n [spi_execp -count 1 $GD(plan) [list $1 $2]]
+
+ if {$n > 0} {
+ return "t"
+ }
+ return "f"
+' language pltcl;
+
+
+-- dump trigger data
+
+CREATE TABLE trigger_test (
+ i int,
+ v text,
+ dropme text,
+ test_skip boolean DEFAULT false,
+ test_return_null boolean DEFAULT false,
+ test_argisnull boolean DEFAULT false
+);
+-- Make certain dropped attributes are handled correctly
+ALTER TABLE trigger_test DROP dropme;
+
+CREATE TABLE trigger_test_generated (
+ i int,
+ j int GENERATED ALWAYS AS (i * 2) STORED
+);
+
+CREATE VIEW trigger_test_view AS SELECT i, v FROM trigger_test;
+
+CREATE FUNCTION trigger_data() returns trigger language pltcl as $_$
+ if {$TG_table_name eq "trigger_test" && $TG_level eq "ROW" && $TG_op ne "DELETE"} {
+ # Special case tests
+ if {$NEW(test_return_null) eq "t" } {
+ return_null
+ }
+ if {$NEW(test_argisnull) eq "t" } {
+ set should_error [argisnull 1]
+ }
+ if {$NEW(test_skip) eq "t" } {
+ elog NOTICE "SKIPPING OPERATION $TG_op"
+ return SKIP
+ }
+ }
+
+ if { [info exists TG_relid] } {
+ set TG_relid "bogus:12345"
+ }
+
+ set dnames [info locals {[a-zA-Z]*} ]
+
+ foreach key [lsort $dnames] {
+
+ if { [array exists $key] } {
+ set str "{"
+ foreach akey [lsort [ array names $key ] ] {
+ if {[string length $str] > 1} { set str "$str, " }
+ set cmd "($akey)"
+ set cmd "set val \$$key$cmd"
+ eval $cmd
+ set str "$str$akey: $val"
+ }
+ set str "$str}"
+ elog NOTICE "$key: $str"
+ } else {
+ set val [eval list "\$$key" ]
+ elog NOTICE "$key: $val"
+ }
+ }
+
+
+ return OK
+
+$_$;
+
+CREATE TRIGGER show_trigger_data_trig
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
+CREATE TRIGGER statement_trigger
+BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test
+FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(42,'statement trigger');
+
+CREATE TRIGGER show_trigger_data_trig_before
+BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+CREATE TRIGGER show_trigger_data_trig_after
+AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE trigger_data();
+
+CREATE TRIGGER show_trigger_data_view_trig
+INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view
+FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view');
+
+--
+-- Trigger function on every change to T_pkey1
+--
+create function trig_pkey1_before() returns trigger as E'
+ #
+ # Create prepared plans on the first call
+ #
+ if {![info exists GD]} {
+ #
+ # Plan to check for duplicate key in T_pkey1
+ #
+ set GD(plan_pkey1) [spi_prepare \\
+ "select check_pkey1_exists(\\$1, \\$2) as ret" \\
+ {int4 bpchar}]
+ #
+ # Plan to check for references from T_dta1
+ #
+ set GD(plan_dta1) [spi_prepare \\
+ "select 1 from T_dta1 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Initialize flags
+ #
+ set check_old_ref 0
+ set check_new_dup 0
+
+ switch $TG_op {
+ INSERT {
+ #
+ # Must check for duplicate key on INSERT
+ #
+ set check_new_dup 1
+ }
+ UPDATE {
+ #
+ # Must check for duplicate key on UPDATE only if
+ # the key changes. In that case we must check for
+ # references to OLD values too.
+ #
+ if {[string compare $NEW(key1) $OLD(key1)] != 0} {
+ set check_old_ref 1
+ set check_new_dup 1
+ }
+ if {[string compare $NEW(key2) $OLD(key2)] != 0} {
+ set check_old_ref 1
+ set check_new_dup 1
+ }
+ }
+ DELETE {
+ #
+ # Must only check for references to OLD on DELETE
+ #
+ set check_old_ref 1
+ }
+ }
+
+ if {$check_new_dup} {
+ #
+ # Check for duplicate key
+ #
+ spi_execp -count 1 $GD(plan_pkey1) [list $NEW(key1) $NEW(key2)]
+ if {$ret == "t"} {
+ elog ERROR \\
+ "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey1"
+ }
+ }
+
+ if {$check_old_ref} {
+ #
+ # Check for references to OLD
+ #
+ set n [spi_execp -count 1 $GD(plan_dta1) [list $OLD(key1) $OLD(key2)]]
+ if {$n > 0} {
+ elog ERROR \\
+ "key ''$OLD(key1)'', ''$OLD(key2)'' referenced by T_dta1"
+ }
+ }
+
+ #
+ # Anything is fine - let operation pass through
+ #
+ return OK
+' language pltcl;
+
+
+create trigger pkey1_before before insert or update or delete on T_pkey1
+ for each row execute procedure
+ trig_pkey1_before();
+
+
+--
+-- Trigger function to check for duplicate keys in T_pkey2
+-- and to force key2 to be upper case only without leading whitespaces
+--
+create function trig_pkey2_before() returns trigger as E'
+ #
+ # Prepare plan on first call
+ #
+ if {![info exists GD]} {
+ set GD(plan_pkey2) [spi_prepare \\
+ "select 1 from T_pkey2 \\
+ where key1 = \\$1 and key2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Convert key2 value
+ #
+ set NEW(key2) [string toupper [string trim $NEW(key2)]]
+
+ #
+ # Check for duplicate key
+ #
+ set n [spi_execp -count 1 $GD(plan_pkey2) [list $NEW(key1) $NEW(key2)]]
+ if {$n > 0} {
+ elog ERROR \\
+ "duplicate key ''$NEW(key1)'', ''$NEW(key2)'' for T_pkey2"
+ }
+
+ #
+ # Return modified tuple in NEW
+ #
+ return [array get NEW]
+' language pltcl;
+
+
+create trigger pkey2_before before insert or update on T_pkey2
+ for each row execute procedure
+ trig_pkey2_before();
+
+
+--
+-- Trigger function to force references from T_dta2 follow changes
+-- in T_pkey2 or be deleted too. This must be done AFTER the changes
+-- in T_pkey2 are done so the trigger for primkey check on T_dta2
+-- fired on our updates will see the new key values in T_pkey2.
+--
+create function trig_pkey2_after() returns trigger as E'
+ #
+ # Prepare plans on first call
+ #
+ if {![info exists GD]} {
+ #
+ # Plan to update references from T_dta2
+ #
+ set GD(plan_dta2_upd) [spi_prepare \\
+ "update T_dta2 set ref1 = \\$3, ref2 = \\$4 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar int4 bpchar}]
+ #
+ # Plan to delete references from T_dta2
+ #
+ set GD(plan_dta2_del) [spi_prepare \\
+ "delete from T_dta2 \\
+ where ref1 = \\$1 and ref2 = \\$2" \\
+ {int4 bpchar}]
+ }
+
+ #
+ # Initialize flags
+ #
+ set old_ref_follow 0
+ set old_ref_delete 0
+
+ switch $TG_op {
+ UPDATE {
+ #
+ # On update we must let old references follow
+ #
+ set NEW(key2) [string toupper $NEW(key2)]
+
+ if {[string compare $NEW(key1) $OLD(key1)] != 0} {
+ set old_ref_follow 1
+ }
+ if {[string compare $NEW(key2) $OLD(key2)] != 0} {
+ set old_ref_follow 1
+ }
+ }
+ DELETE {
+ #
+ # On delete we must delete references too
+ #
+ set old_ref_delete 1
+ }
+ }
+
+ if {$old_ref_follow} {
+ #
+ # Let old references follow and fire NOTICE message if
+ # there where some
+ #
+ set n [spi_execp $GD(plan_dta2_upd) \\
+ [list $OLD(key1) $OLD(key2) $NEW(key1) $NEW(key2)]]
+ if {$n > 0} {
+ elog NOTICE \\
+ "updated $n entries in T_dta2 for new key in T_pkey2"
+ }
+ }
+
+ if {$old_ref_delete} {
+ #
+ # delete references and fire NOTICE message if
+ # there where some
+ #
+ set n [spi_execp $GD(plan_dta2_del) \\
+ [list $OLD(key1) $OLD(key2)]]
+ if {$n > 0} {
+ elog NOTICE \\
+ "deleted $n entries from T_dta2"
+ }
+ }
+
+ return OK
+' language pltcl;
+
+
+create trigger pkey2_after after update or delete on T_pkey2
+ for each row execute procedure
+ trig_pkey2_after();
+
+
+--
+-- Generic trigger function to check references in T_dta1 and T_dta2
+--
+create function check_primkey() returns trigger as E'
+ #
+ # For every trigger/relation pair we create
+ # a saved plan and hold them in GD
+ #
+ set plankey [list "plan" $TG_name $TG_relid]
+ set planrel [list "relname" $TG_relid]
+
+ #
+ # Extract the pkey relation name
+ #
+ set keyidx [expr [llength $args] / 2]
+ set keyrel [string tolower [lindex $args $keyidx]]
+
+ if {![info exists GD($plankey)]} {
+ #
+ # We must prepare a new plan. Build up a query string
+ # for the primary key check.
+ #
+ set keylist [lrange $args [expr $keyidx + 1] end]
+
+ set query "select 1 from $keyrel"
+ set qual " where"
+ set typlist ""
+ set idx 1
+ foreach key $keylist {
+ set key [string tolower $key]
+ #
+ # Add the qual part to the query string
+ #
+ append query "$qual $key = \\$$idx"
+ set qual " and"
+
+ #
+ # Lookup the fields type in pg_attribute
+ #
+ set n [spi_exec "select T.typname \\
+ from pg_catalog.pg_type T, pg_catalog.pg_attribute A, pg_catalog.pg_class C \\
+ where C.relname = ''[quote $keyrel]'' \\
+ and C.oid = A.attrelid \\
+ and A.attname = ''[quote $key]'' \\
+ and A.atttypid = T.oid"]
+ if {$n != 1} {
+ elog ERROR "table $keyrel doesn''t have a field named $key"
+ }
+
+ #
+ # Append the fields type to the argument type list
+ #
+ lappend typlist $typname
+ incr idx
+ }
+
+ #
+ # Prepare the plan
+ #
+ set GD($plankey) [spi_prepare $query $typlist]
+
+ #
+ # Lookup and remember the table name for later error messages
+ #
+ spi_exec "select relname from pg_catalog.pg_class \\
+ where oid = ''$TG_relid''::oid"
+ set GD($planrel) $relname
+ }
+
+ #
+ # Build the argument list from the NEW row
+ #
+ incr keyidx -1
+ set arglist ""
+ foreach arg [lrange $args 0 $keyidx] {
+ lappend arglist $NEW($arg)
+ }
+
+ #
+ # Check for the primary key
+ #
+ set n [spi_execp -count 1 $GD($plankey) $arglist]
+ if {$n <= 0} {
+ elog ERROR "key for $GD($planrel) not in $keyrel"
+ }
+
+ #
+ # Anything is fine
+ #
+ return OK
+' language pltcl;
+
+
+create trigger dta1_before before insert or update on T_dta1
+ for each row execute procedure
+ check_primkey('ref1', 'ref2', 'T_pkey1', 'key1', 'key2');
+
+
+create trigger dta2_before before insert or update on T_dta2
+ for each row execute procedure
+ check_primkey('ref1', 'ref2', 'T_pkey2', 'key1', 'key2');
+
+
+insert into T_pkey1 values (1, 'key1-1', 'test key');
+insert into T_pkey1 values (1, 'key1-2', 'test key');
+insert into T_pkey1 values (1, 'key1-3', 'test key');
+insert into T_pkey1 values (2, 'key2-1', 'test key');
+insert into T_pkey1 values (2, 'key2-2', 'test key');
+insert into T_pkey1 values (2, 'key2-3', 'test key');
+
+insert into T_pkey2 values (1, 'key1-1', 'test key');
+insert into T_pkey2 values (1, 'key1-2', 'test key');
+insert into T_pkey2 values (1, 'key1-3', 'test key');
+insert into T_pkey2 values (2, 'key2-1', 'test key');
+insert into T_pkey2 values (2, 'key2-2', 'test key');
+insert into T_pkey2 values (2, 'key2-3', 'test key');
+
+select * from T_pkey1;
+
+-- key2 in T_pkey2 should have upper case only
+select * from T_pkey2;
+
+insert into T_pkey1 values (1, 'KEY1-3', 'should work');
+
+-- Due to the upper case translation in trigger this must fail
+insert into T_pkey2 values (1, 'KEY1-3', 'should fail');
+
+insert into T_dta1 values ('trec 1', 1, 'key1-1');
+insert into T_dta1 values ('trec 2', 1, 'key1-2');
+insert into T_dta1 values ('trec 3', 1, 'key1-3');
+
+-- Must fail due to unknown key in T_pkey1
+insert into T_dta1 values ('trec 4', 1, 'key1-4');
+
+insert into T_dta2 values ('trec 1', 1, 'KEY1-1');
+insert into T_dta2 values ('trec 2', 1, 'KEY1-2');
+insert into T_dta2 values ('trec 3', 1, 'KEY1-3');
+
+-- Must fail due to unknown key in T_pkey2
+insert into T_dta2 values ('trec 4', 1, 'KEY1-4');
+
+select * from T_dta1;
+
+select * from T_dta2;
+
+update T_pkey1 set key2 = 'key2-9' where key1 = 2 and key2 = 'key2-1';
+update T_pkey1 set key2 = 'key1-9' where key1 = 1 and key2 = 'key1-1';
+delete from T_pkey1 where key1 = 2 and key2 = 'key2-2';
+delete from T_pkey1 where key1 = 1 and key2 = 'key1-2';
+
+update T_pkey2 set key2 = 'KEY2-9' where key1 = 2 and key2 = 'KEY2-1';
+update T_pkey2 set key2 = 'KEY1-9' where key1 = 1 and key2 = 'KEY1-1';
+delete from T_pkey2 where key1 = 2 and key2 = 'KEY2-2';
+delete from T_pkey2 where key1 = 1 and key2 = 'KEY1-2';
+
+select * from T_pkey1;
+select * from T_pkey2;
+select * from T_dta1;
+select * from T_dta2;
+
+select tcl_avg(key1) from T_pkey1;
+select tcl_sum(key1) from T_pkey1;
+select tcl_avg(key1) from T_pkey2;
+select tcl_sum(key1) from T_pkey2;
+
+-- The following should return NULL instead of 0
+select tcl_avg(key1) from T_pkey1 where key1 = 99;
+select tcl_sum(key1) from T_pkey1 where key1 = 99;
+
+select 1 @< 2;
+select 100 @< 4;
+
+select * from T_pkey1 order by key1 using @<, key2 collate "C";
+select * from T_pkey2 order by key1 using @<, key2 collate "C";
+
+-- show dump of trigger data
+insert into trigger_test values(1,'insert');
+
+insert into trigger_test_generated (i) values (1);
+update trigger_test_generated set i = 11 where i = 1;
+delete from trigger_test_generated;
+
+insert into trigger_test_view values(2,'insert');
+update trigger_test_view set v = 'update' where i=1;
+delete from trigger_test_view;
+
+update trigger_test set v = 'update', test_skip=true where i = 1;
+update trigger_test set v = 'update' where i = 1;
+delete from trigger_test;
+truncate trigger_test;
+
+DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated;
+DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated;
+
+-- should error
+insert into trigger_test(test_argisnull) values(true);
+
+-- should error
+insert into trigger_test(test_return_null) values(true);
+
+-- test transition table visibility
+create table transition_table_test (id int, name text);
+insert into transition_table_test values (1, 'a');
+create function transition_table_test_f() returns trigger language pltcl as
+$$
+ spi_exec -array C "SELECT id, name FROM old_table" {
+ elog INFO "old: $C(id) -> $C(name)"
+ }
+ spi_exec -array C "SELECT id, name FROM new_table" {
+ elog INFO "new: $C(id) -> $C(name)"
+ }
+ return OK
+$$;
+CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test
+ REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
+ FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f();
+update transition_table_test set name = 'b';
+drop table transition_table_test;
+drop function transition_table_test_f();
+
+-- dealing with generated columns
+
+CREATE FUNCTION generated_test_func1() RETURNS trigger
+LANGUAGE pltcl
+AS $$
+# not allowed
+set NEW(j) 5
+return [array get NEW]
+$$;
+
+CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated
+FOR EACH ROW EXECUTE PROCEDURE generated_test_func1();
+
+TRUNCATE trigger_test_generated;
+INSERT INTO trigger_test_generated (i) VALUES (1);
+SELECT * FROM trigger_test_generated;
diff --git a/src/pl/tcl/sql/pltcl_unicode.sql b/src/pl/tcl/sql/pltcl_unicode.sql
new file mode 100644
index 0000000..f000604
--- /dev/null
+++ b/src/pl/tcl/sql/pltcl_unicode.sql
@@ -0,0 +1,38 @@
+--
+-- Unicode handling
+--
+-- Note: this test case is known to fail if the database encoding is
+-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to
+-- U+00A0 (no-break space) in those encodings. However, testing with
+-- plain ASCII data would be rather useless, so we must live with that.
+--
+
+SET client_encoding TO UTF8;
+
+CREATE TABLE unicode_test (
+ testvalue text NOT NULL
+);
+
+CREATE FUNCTION unicode_return() RETURNS text AS $$
+ return "\xA0"
+$$ LANGUAGE pltcl;
+
+CREATE FUNCTION unicode_trigger() RETURNS trigger AS $$
+ set NEW(testvalue) "\xA0"
+ return [array get NEW]
+$$ LANGUAGE pltcl;
+
+CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test
+ FOR EACH ROW EXECUTE PROCEDURE unicode_trigger();
+
+CREATE FUNCTION unicode_plan1() RETURNS text AS $$
+ set plan [ spi_prepare {SELECT $1 AS testvalue} [ list "text" ] ]
+ spi_execp $plan [ list "\xA0" ]
+ return $testvalue
+$$ LANGUAGE pltcl;
+
+
+SELECT unicode_return();
+INSERT INTO unicode_test (testvalue) VALUES ('test');
+SELECT * FROM unicode_test;
+SELECT unicode_plan1();