summaryrefslogtreecommitdiffstats
path: root/src/pl/plpgsql
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/plpgsql
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/plpgsql')
-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
64 files changed, 50514 insertions, 0 deletions
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$$;