diff options
Diffstat (limited to 'src/pl/plpython')
102 files changed, 26311 insertions, 0 deletions
diff --git a/src/pl/plpython/.gitignore b/src/pl/plpython/.gitignore new file mode 100644 index 0000000..07bee6a --- /dev/null +++ b/src/pl/plpython/.gitignore @@ -0,0 +1,5 @@ +/spiexceptions.h +# Generated subdirectories +/log/ +/results/ +/tmp_check/ diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile new file mode 100644 index 0000000..bb26426 --- /dev/null +++ b/src/pl/plpython/Makefile @@ -0,0 +1,157 @@ +# src/pl/plpython/Makefile + +subdir = src/pl/plpython +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + + +# On Windows we have to remove -lpython from the link since we are +# building our own +ifeq ($(PORTNAME), win32) +override python_libspec = +endif + +override CPPFLAGS := -I. -I$(srcdir) $(python_includespec) $(CPPFLAGS) + +rpathdir = $(python_libdir) + +PGFILEDESC = "PL/Python - procedural language" + +NAME = plpython$(python_majorversion) + +OBJS = \ + $(WIN32RES) \ + plpy_cursorobject.o \ + plpy_elog.o \ + plpy_exec.o \ + plpy_main.o \ + plpy_planobject.o \ + plpy_plpymodule.o \ + plpy_procedure.o \ + plpy_resultobject.o \ + plpy_spi.o \ + plpy_subxactobject.o \ + plpy_typeio.o \ + plpy_util.o + +DATA = $(NAME)u.control $(NAME)u--1.0.sql + +# header files to install - it's not clear which of these might be needed +# so install them all. +INCS = plpython.h \ + plpython_system.h \ + plpy_cursorobject.h \ + plpy_elog.h \ + plpy_exec.h \ + plpy_main.h \ + plpy_planobject.h \ + plpy_plpymodule.h \ + plpy_procedure.h \ + plpy_resultobject.h \ + plpy_spi.h \ + plpy_subxactobject.h \ + plpy_typeio.h \ + plpy_util.h + +# Python on win32 ships with import libraries only for Microsoft Visual C++, +# which are not compatible with mingw gcc. Therefore we need to build a +# new import library to link with. +ifeq ($(PORTNAME), win32) + +pytverstr=$(subst .,,${python_version}) +PYTHONDLL=$(subst \,/,$(WINDIR))/system32/python${pytverstr}.dll + +OBJS += libpython${pytverstr}.a + +libpython${pytverstr}.a: python${pytverstr}.def + dlltool --dllname python${pytverstr}.dll --def python${pytverstr}.def --output-lib libpython${pytverstr}.a + +python${pytverstr}.def: + gendef - $(PYTHONDLL) > $@ + +endif # win32 + + +SHLIB_LINK = $(python_libspec) $(python_additional_libs) $(filter -lintl,$(LIBS)) + +REGRESS_OPTS = --dbname=$(PL_TESTDB) + +REGRESS = \ + plpython_schema \ + plpython_populate \ + plpython_test \ + plpython_do \ + plpython_global \ + plpython_import \ + plpython_spi \ + plpython_newline \ + plpython_void \ + plpython_call \ + plpython_params \ + plpython_setof \ + plpython_record \ + plpython_trigger \ + plpython_types \ + plpython_error \ + plpython_ereport \ + plpython_unicode \ + plpython_quote \ + plpython_composite \ + plpython_subtransaction \ + plpython_transaction \ + plpython_drop + +include $(top_srcdir)/src/Makefile.shlib + +all: all-lib + +# Ensure parallel safety if a build is started in this directory +$(OBJS): | submake-generated-headers + +install: all install-lib install-data + +installdirs: installdirs-lib + $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)' + +uninstall: uninstall-lib uninstall-data + +install-data: installdirs + $(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/' + $(INSTALL_DATA) $(addprefix $(srcdir)/, $(INCS)) '$(DESTDIR)$(includedir_server)' + +uninstall-data: + rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA))) + rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, $(INCS)) + +.PHONY: install-data uninstall-data + + +check: submake-pg-regress + $(pg_regress_check) $(REGRESS_OPTS) $(REGRESS) + +installcheck: submake-pg-regress + $(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS) + + +.PHONY: submake-pg-regress +submake-pg-regress: | submake-generated-headers + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress$(X) + +clean distclean: clean-lib + rm -f $(OBJS) + rm -rf $(pg_regress_clean_files) +ifeq ($(PORTNAME), win32) + rm -f python${pytverstr}.def +endif + + +# Force this dependency to be known even without dependency info built: +plpy_plpymodule.o: spiexceptions.h + +spiexceptions.h: $(top_srcdir)/src/backend/utils/errcodes.txt generate-spiexceptions.pl + $(PERL) $(srcdir)/generate-spiexceptions.pl $< > $@ + +distprep: spiexceptions.h + +maintainer-clean: distclean + rm -f spiexceptions.h diff --git a/src/pl/plpython/expected/README b/src/pl/plpython/expected/README new file mode 100644 index 0000000..388c553 --- /dev/null +++ b/src/pl/plpython/expected/README @@ -0,0 +1,3 @@ +Guide to alternative expected files: + +plpython_error_5.out Python 3.5 and newer diff --git a/src/pl/plpython/expected/plpython_call.out b/src/pl/plpython/expected/plpython_call.out new file mode 100644 index 0000000..4c06900 --- /dev/null +++ b/src/pl/plpython/expected/plpython_call.out @@ -0,0 +1,75 @@ +-- +-- Tests for procedures / CALL syntax +-- +CREATE PROCEDURE test_proc1() +LANGUAGE plpython3u +AS $$ +pass +$$; +CALL test_proc1(); +-- error: can't return non-None +CREATE PROCEDURE test_proc2() +LANGUAGE plpython3u +AS $$ +return 5 +$$; +CALL test_proc2(); +ERROR: PL/Python procedure did not return None +CONTEXT: PL/Python procedure "test_proc2" +CREATE TABLE test1 (a int); +CREATE PROCEDURE test_proc3(x int) +LANGUAGE plpython3u +AS $$ +plpy.execute("INSERT INTO test1 VALUES (%s)" % x) +$$; +CALL test_proc3(55); +SELECT * FROM test1; + a +---- + 55 +(1 row) + +-- output arguments +CREATE PROCEDURE test_proc5(INOUT a text) +LANGUAGE plpython3u +AS $$ +return [a + '+' + a] +$$; +CALL test_proc5('abc'); + a +--------- + abc+abc +(1 row) + +CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int) +LANGUAGE plpython3u +AS $$ +return (b * a, c * a) +$$; +CALL test_proc6(2, 3, 4); + b | c +---+--- + 6 | 8 +(1 row) + +-- OUT parameters +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpython3u +AS $$ +plpy.notice("a: %s" % (a)) +return (a * 2,) +$$; +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; +NOTICE: a: 10 +NOTICE: _a: 10, _b: 20 +DROP PROCEDURE test_proc1; +DROP PROCEDURE test_proc2; +DROP PROCEDURE test_proc3; +DROP TABLE test1; diff --git a/src/pl/plpython/expected/plpython_composite.out b/src/pl/plpython/expected/plpython_composite.out new file mode 100644 index 0000000..bb101e0 --- /dev/null +++ b/src/pl/plpython/expected/plpython_composite.out @@ -0,0 +1,594 @@ +CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$ +return (1, 2) +$$ LANGUAGE plpython3u; +SELECT multiout_simple(); + multiout_simple +----------------- + (1,2) +(1 row) + +SELECT * FROM multiout_simple(); + i | j +---+--- + 1 | 2 +(1 row) + +SELECT i, j + 2 FROM multiout_simple(); + i | ?column? +---+---------- + 1 | 4 +(1 row) + +SELECT (multiout_simple()).j + 3; + ?column? +---------- + 5 +(1 row) + +CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$ +return [(1, 2)] * n +$$ LANGUAGE plpython3u; +SELECT multiout_simple_setof(); + multiout_simple_setof +----------------------- + (1,2) +(1 row) + +SELECT * FROM multiout_simple_setof(); + column1 | column2 +---------+--------- + 1 | 2 +(1 row) + +SELECT * FROM multiout_simple_setof(3); + column1 | column2 +---------+--------- + 1 | 2 + 1 | 2 + 1 | 2 +(3 rows) + +CREATE FUNCTION multiout_record_as(typ text, + first text, OUT first text, + second integer, OUT second integer, + retnull boolean) RETURNS record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); + first | second +-------+-------- + foo | 1 +(1 row) + +SELECT multiout_record_as('dict', 'foo', 1, 'f'); + multiout_record_as +-------------------- + (foo,1) +(1 row) + +SELECT * FROM multiout_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM multiout_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM multiout_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM multiout_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + +SELECT * FROM multiout_record_as('str', 'one', 2, false); + first | second +-------+-------- + 'one' | 2 +(1 row) + +SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); + f | s | snull +-----+---+------- + xxx | | t +(1 row) + +SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s); + f | s | fnull | snull +---+---+-------+------- + | | t | t +(1 row) + +SELECT * FROM multiout_record_as('obj', NULL, 10, 'f'); + first | second +-------+-------- + | 10 +(1 row) + +CREATE FUNCTION multiout_setof(n integer, + OUT power_of_2 integer, + OUT length integer) RETURNS SETOF record AS $$ +for i in range(n): + power = 2 ** i + length = plpy.execute("select length('%d')" % power)[0]['length'] + yield power, length +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_setof(3); + power_of_2 | length +------------+-------- + 1 | 1 + 2 | 1 + 4 | 1 +(3 rows) + +SELECT multiout_setof(5); + multiout_setof +---------------- + (1,1) + (2,1) + (4,1) + (8,1) + (16,2) +(5 rows) + +CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$ +return [{'x': 4, 'y' :'four'}, + {'x': 7, 'y' :'seven'}, + {'x': 0, 'y' :'zero'}] +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_return_table(); + x | y +---+------- + 4 | four + 7 | seven + 0 | zero +(3 rows) + +CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$ +yield [[1], 'a'] +yield [[1,2], 'b'] +yield [[1,2,3], None] +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_array(); + column1 | column2 +---------+--------- + {1} | a + {1,2} | b + {1,2,3} | +(3 rows) + +CREATE FUNCTION singleout_composite(OUT type_record) AS $$ +return {'first': 1, 'second': 2} +$$ LANGUAGE plpython3u; +CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$ +return [{'first': 1, 'second': 2}, + {'first': 3, 'second': 4 }] +$$ LANGUAGE plpython3u; +SELECT * FROM singleout_composite(); + first | second +-------+-------- + 1 | 2 +(1 row) + +SELECT * FROM multiout_composite(); + first | second +-------+-------- + 1 | 2 + 3 | 4 +(2 rows) + +-- composite OUT parameters in functions returning RECORD not supported yet +CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$ +return (n, (n * 2, n * 3)) +$$ LANGUAGE plpython3u; +CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$ +if returnnull: + d = None +elif typ == 'dict': + d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} +elif typ == 'tuple': + d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] +elif typ == 'obj': + class d: pass + d.first = n * 2 + d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) +for i in range(n): + yield (i, d) +$$ LANGUAGE plpython3u; +SELECT * FROM multiout_composite(2); + n | column2 +---+--------- + 2 | (4,6) +(1 row) + +SELECT * FROM multiout_table_type_setof('dict', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + +SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); + n | column2 +---+--------- + 0 | (4,6) + 1 | (4,6) +(2 rows) + +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 2); + n | column2 +---+--------- + 0 | (4,6) + 1 | (4,6) +(2 rows) + +SELECT * FROM multiout_table_type_setof('list', 'f', 3); + n | column2 +---+--------- + 0 | (6,9) + 1 | (6,9) + 2 | (6,9) +(3 rows) + +SELECT * FROM multiout_table_type_setof('obj', 'f', 4); + n | column2 +---+--------- + 0 | (8,12) + 1 | (8,12) + 2 | (8,12) + 3 | (8,12) +(4 rows) + +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); + n | column2 +---+--------- + 0 | (10,15) + 1 | (10,15) + 2 | (10,15) + 3 | (10,15) + 4 | (10,15) +(5 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 6); + n | column2 +---+--------- + 0 | (12,18) + 1 | (12,18) + 2 | (12,18) + 3 | (12,18) + 4 | (12,18) + 5 | (12,18) +(6 rows) + +SELECT * FROM multiout_table_type_setof('str', 'f', 7); + n | column2 +---+--------- + 0 | (14,21) + 1 | (14,21) + 2 | (14,21) + 3 | (14,21) + 4 | (14,21) + 5 | (14,21) + 6 | (14,21) +(7 rows) + +SELECT * FROM multiout_table_type_setof('dict', 't', 3); + n | column2 +---+--------- + 0 | + 1 | + 2 | +(3 rows) + +-- check what happens if a type changes under us +CREATE TABLE changing ( + i integer, + j integer +); +CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$ +return [(1, {'i': 1, 'j': 2}), + (1, (3, 4))] +$$ LANGUAGE plpython3u; +SELECT * FROM changing_test(); + n | column2 +---+--------- + 1 | (1,2) + 1 | (3,4) +(2 rows) + +ALTER TABLE changing DROP COLUMN j; +SELECT * FROM changing_test(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +PL/Python function "changing_test" +SELECT * FROM changing_test(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +PL/Python function "changing_test" +ALTER TABLE changing ADD COLUMN j integer; +SELECT * FROM changing_test(); + n | column2 +---+--------- + 1 | (1,2) + 1 | (3,4) +(2 rows) + +-- tables of composite types +CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$ +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +$$ LANGUAGE plpython3u; +SELECT * FROM composite_types_table(); + tab | typ +----------------------------+---------------------------- + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} + {"(first,1)","(second,2)"} | {"(third,3)","(fourth,4)"} +(3 rows) + +-- check what happens if the output record descriptor changes +CREATE FUNCTION return_record(t text) RETURNS record AS $$ +return {'t': t, 'val': 10} +$$ LANGUAGE plpython3u; +SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t text, val bigint); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t text, val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('abc') AS r(t varchar(100), val integer); + t | val +-----+----- + abc | 10 +(1 row) + +SELECT * FROM return_record('999') AS r(val text, t integer); + val | t +-----+----- + 10 | 999 +(1 row) + +CREATE FUNCTION return_record_2(t text) RETURNS record AS $$ +return {'v1':1,'v2':2,t:3} +$$ LANGUAGE plpython3u; +SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int); + v3 | v2 | v1 +----+----+---- + 3 | 2 | 1 +(1 row) + +SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int); + v2 | v3 | v1 +----+----+---- + 2 | 3 | 1 +(1 row) + +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); + v1 | v4 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); + v1 | v4 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +-- error +SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int); +ERROR: key "v3" not found in mapping +HINT: To return null in a column, add the value None to the mapping with the key named after the column. +CONTEXT: while creating return value +PL/Python function "return_record_2" +-- works +SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int); + v1 | v3 | v2 +----+----+---- + 1 | 3 | 2 +(1 row) + +SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int); + v1 | v2 | v3 +----+----+---- + 1 | 2 | 3 +(1 row) + +-- multi-dimensional array of composite types. +CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$ + return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]]; +$$ LANGUAGE plpython3u; +SELECT * FROM composite_type_as_list(); + composite_type_as_list +------------------------------------------------------------------------------------ + {{"(first,1)","(second,1)"},{"(first,2)","(second,2)"},{"(first,3)","(second,3)"}} +(1 row) + +-- Starting with PostgreSQL 10, a composite type in an array cannot be +-- represented as a Python list, because it's ambiguous with multi-dimensional +-- arrays. So this throws an error now. The error should contain a useful hint +-- on the issue. +CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$ + return [['first', 1]]; +$$ LANGUAGE plpython3u; +SELECT * FROM composite_type_as_list_broken(); +ERROR: malformed record literal: "first" +DETAIL: Missing left parenthesis. +HINT: To return a composite type in an array, return the composite type as a Python tuple, e.g., "[('foo',)]". +CONTEXT: while creating return value +PL/Python function "composite_type_as_list_broken" diff --git a/src/pl/plpython/expected/plpython_do.out b/src/pl/plpython/expected/plpython_do.out new file mode 100644 index 0000000..d131a4c --- /dev/null +++ b/src/pl/plpython/expected/plpython_do.out @@ -0,0 +1,8 @@ +DO $$ plpy.notice("This is plpython3u.") $$ LANGUAGE plpython3u; +NOTICE: This is plpython3u. +DO $$ raise Exception("error test") $$ LANGUAGE plpython3u; +ERROR: Exception: error test +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + raise Exception("error test") +PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_drop.out b/src/pl/plpython/expected/plpython_drop.out new file mode 100644 index 0000000..97bb54a --- /dev/null +++ b/src/pl/plpython/expected/plpython_drop.out @@ -0,0 +1,5 @@ +-- +-- For paranoia's sake, don't leave an untrusted language sitting around +-- +SET client_min_messages = WARNING; +DROP EXTENSION plpython3u CASCADE; diff --git a/src/pl/plpython/expected/plpython_ereport.out b/src/pl/plpython/expected/plpython_ereport.out new file mode 100644 index 0000000..0138d79 --- /dev/null +++ b/src/pl/plpython/expected/plpython_ereport.out @@ -0,0 +1,214 @@ +CREATE FUNCTION elog_test() RETURNS void +AS $$ +plpy.debug('debug', detail='some detail') +plpy.log('log', detail='some detail') +plpy.info('info', detail='some detail') +plpy.info() +plpy.info('the question', detail=42); +plpy.info('This is message text.', + detail='This is detail text', + hint='This is hint text.', + sqlstate='XX000', + schema_name='any info about schema', + table_name='any info about table', + column_name='any info about column', + datatype_name='any info about datatype', + constraint_name='any info about constraint') +plpy.notice('notice', detail='some detail') +plpy.warning('warning', detail='some detail') +plpy.error('stop on error', detail='some detail', hint='some hint') +$$ LANGUAGE plpython3u; +SELECT elog_test(); +INFO: info +DETAIL: some detail +INFO: () +INFO: the question +DETAIL: 42 +INFO: This is message text. +DETAIL: This is detail text +HINT: This is hint text. +NOTICE: notice +DETAIL: some detail +WARNING: warning +DETAIL: some detail +ERROR: plpy.Error: stop on error +DETAIL: some detail +HINT: some hint +CONTEXT: Traceback (most recent call last): + PL/Python function "elog_test", line 18, in <module> + plpy.error('stop on error', detail='some detail', hint='some hint') +PL/Python function "elog_test" +DO $$ plpy.info('other types', detail=(10, 20)) $$ LANGUAGE plpython3u; +INFO: other types +DETAIL: (10, 20) +DO $$ +import time; +from datetime import date +plpy.info('other types', detail=date(2016, 2, 26)) +$$ LANGUAGE plpython3u; +INFO: other types +DETAIL: 2016-02-26 +DO $$ +basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana'] +plpy.info('other types', detail=basket) +$$ LANGUAGE plpython3u; +INFO: other types +DETAIL: ['apple', 'orange', 'apple', 'pear', 'orange', 'banana'] +-- should fail +DO $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpython3u; +ERROR: ValueError: invalid SQLSTATE code +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + plpy.info('wrong sqlstate', sqlstate='54444A') +PL/Python anonymous code block +DO $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpython3u; +ERROR: TypeError: 'blabla' is an invalid keyword argument for this function +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + plpy.info('unsupported argument', blabla='fooboo') +PL/Python anonymous code block +DO $$ plpy.info('first message', message='second message') $$ LANGUAGE plpython3u; +ERROR: TypeError: argument 'message' given by name and position +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + plpy.info('first message', message='second message') +PL/Python anonymous code block +DO $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpython3u; +ERROR: TypeError: argument 'message' given by name and position +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 1, in <module> + plpy.info('first message', 'second message', message='third message') +PL/Python anonymous code block +-- raise exception in python, handle exception in plpgsql +CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL, + _sqlstate text DEFAULT NULL, + _schema_name text DEFAULT NULL, + _table_name text DEFAULT NULL, + _column_name text DEFAULT NULL, + _datatype_name text DEFAULT NULL, + _constraint_name text DEFAULT NULL) +RETURNS void AS $$ +kwargs = { + "message": _message, "detail": _detail, "hint": _hint, + "sqlstate": _sqlstate, "schema_name": _schema_name, "table_name": _table_name, + "column_name": _column_name, "datatype_name": _datatype_name, + "constraint_name": _constraint_name +} +# ignore None values +plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +$$ LANGUAGE plpython3u; +SELECT raise_exception('hello', 'world'); +ERROR: plpy.Error: hello +DETAIL: world +CONTEXT: Traceback (most recent call last): + PL/Python function "raise_exception", line 9, in <module> + plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +PL/Python function "raise_exception" +SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333'); +ERROR: plpy.Error: message text +DETAIL: detail text +CONTEXT: Traceback (most recent call last): + PL/Python function "raise_exception", line 9, in <module> + plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +PL/Python function "raise_exception" +SELECT raise_exception(_message => 'message text', + _detail => 'detail text', + _hint => 'hint text', + _sqlstate => 'XX555', + _schema_name => 'schema text', + _table_name => 'table text', + _column_name => 'column text', + _datatype_name => 'datatype text', + _constraint_name => 'constraint text'); +ERROR: plpy.Error: message text +DETAIL: detail text +HINT: hint text +CONTEXT: Traceback (most recent call last): + PL/Python function "raise_exception", line 9, in <module> + plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +PL/Python function "raise_exception" +SELECT raise_exception(_message => 'message text', + _hint => 'hint text', + _schema_name => 'schema text', + _column_name => 'column text', + _constraint_name => 'constraint text'); +ERROR: plpy.Error: message text +HINT: hint text +CONTEXT: Traceback (most recent call last): + PL/Python function "raise_exception", line 9, in <module> + plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +PL/Python function "raise_exception" +DO $$ +DECLARE + __message text; + __detail text; + __hint text; + __sqlstate text; + __schema_name text; + __table_name text; + __column_name text; + __datatype_name text; + __constraint_name text; +BEGIN + BEGIN + PERFORM raise_exception(_message => 'message text', + _detail => 'detail text', + _hint => 'hint text', + _sqlstate => 'XX555', + _schema_name => 'schema text', + _table_name => 'table text', + _column_name => 'column text', + _datatype_name => 'datatype text', + _constraint_name => 'constraint text'); + EXCEPTION WHEN SQLSTATE 'XX555' THEN + GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT, + __detail = PG_EXCEPTION_DETAIL, + __hint = PG_EXCEPTION_HINT, + __sqlstate = RETURNED_SQLSTATE, + __schema_name = SCHEMA_NAME, + __table_name = TABLE_NAME, + __column_name = COLUMN_NAME, + __datatype_name = PG_DATATYPE_NAME, + __constraint_name = CONSTRAINT_NAME; + RAISE NOTICE 'handled exception' + USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), ' + 'schema_name:(%s), table_name:(%s), column_name:(%s), datatype_name:(%s), constraint_name:(%s)', + __message, __detail, __hint, __sqlstate, __schema_name, + __table_name, __column_name, __datatype_name, __constraint_name); + END; +END; +$$; +NOTICE: handled exception +DETAIL: message:(plpy.Error: message text), detail:(detail text), hint: (hint text), sqlstate: (XX555), schema_name:(schema text), table_name:(table text), column_name:(column text), datatype_name:(datatype text), constraint_name:(constraint text) +DO $$ +try: + plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')") +except Exception as e: + plpy.info(e.spidata) + raise e +$$ LANGUAGE plpython3u; +INFO: (119577128, None, 'some hint', None, 0, None, 'users_tab', None, 'user_type', None) +ERROR: plpy.SPIError: plpy.Error: my message +HINT: some hint +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 6, in <module> + raise e + PL/Python anonymous code block, line 3, in __plpython_inline_block + plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')") +PL/Python anonymous code block +DO $$ +try: + plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type') +except Exception as e: + plpy.info('sqlstate: %s, hint: %s, table_name: %s, datatype_name: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name)) + raise e +$$ LANGUAGE plpython3u; +INFO: sqlstate: XX987, hint: some hint, table_name: users_tab, datatype_name: user_type +ERROR: plpy.Error: my message +HINT: some hint +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 6, in <module> + raise e + PL/Python anonymous code block, line 3, in __plpython_inline_block + plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type') +PL/Python anonymous code block diff --git a/src/pl/plpython/expected/plpython_error.out b/src/pl/plpython/expected/plpython_error.out new file mode 100644 index 0000000..68722b0 --- /dev/null +++ b/src/pl/plpython/expected/plpython_error.out @@ -0,0 +1,460 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +RESET check_function_bodies; +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpython3u; +SELECT sql_syntax_error(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" +LINE 1: syntax error + ^ +QUERY: syntax error +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in <module> + plpy.execute("syntax error") +PL/Python function "sql_syntax_error" +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpython3u; +SELECT exception_index_invalid('test'); +ERROR: IndexError: list index out of range +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in <module> + return args[1] +PL/Python function "exception_index_invalid" +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpython3u; +SELECT exception_index_invalid_nested(); +ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist +LINE 1: SELECT test5('foo') + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +QUERY: SELECT test5('foo') +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in <module> + rv = plpy.execute("SELECT test5('foo')") +PL/Python function "exception_index_invalid_nested" +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_uncaught('rick'); +ERROR: spiexceptions.UndefinedObject: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in <module> + SD["plan"] = plpy.prepare(q, [ "test" ]) +PL/Python function "invalid_type_uncaught" +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_caught('rick'); +NOTICE: type "test" does not exist + invalid_type_caught +--------------------- + +(1 row) + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_reraised('rick'); +ERROR: plpy.Error: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in <module> + plpy.error(str(ex)) +PL/Python function "invalid_type_reraised" +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT valid_type('rick'); + valid_type +------------ + +(1 row) + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in <module> + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") +PL/Python function "nested_error" +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error_raise(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in <module> + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") +PL/Python function "nested_error_raise" +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; +SELECT nested_warning(); +WARNING: boom + nested_warning +-------------------- + you've been warned +(1 row) + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; +SELECT toplevel_attribute_error(); +ERROR: AttributeError: 'module' object has no attribute 'nonexistent' +CONTEXT: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in <module> + plpy.nonexistent +PL/Python function "toplevel_attribute_error" +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; +SELECT python_traceback(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in <module> + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SELECT sql_error(); +ERROR: division by zero +CONTEXT: SQL statement "select 1/0" +PL/pgSQL function sql_error() line 3 at SQL statement +SELECT python_from_sql_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in <module> + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SQL statement "select python_traceback()" +PL/pgSQL function python_from_sql_error() line 3 at SQL statement +SELECT sql_from_python_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_from_python_error", line 2, in <module> + plpy.execute("select sql_error()") +PL/Python function "sql_from_python_error" +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; +SELECT specific_exception(2); + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(NULL); +NOTICE: Violated the NOT NULL constraint, sqlstate 23502 + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(2); +NOTICE: Violated the UNIQUE constraint, sqlstate 23505 + specific_exception +-------------------- + +(1 row) + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; +SELECT catch_python_unique_violation(); + catch_python_unique_violation +------------------------------- + ok +(1 row) + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; +SELECT manual_subxact(); +ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in <module> + plpy.execute("savepoint save") +PL/Python function "manual_subxact" +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; +SELECT manual_subxact_prepared(); +ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in <module> + plpy.execute(save) +PL/Python function "manual_subxact_prepared" +/* raising plpy.spiexception.* from python code should preserve sqlstate + */ +CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$ +raise plpy.spiexceptions.DivisionByZero() +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception(); +EXCEPTION WHEN division_by_zero THEN + -- NOOP +END +$$ LANGUAGE plpgsql; +/* setting a custom sqlstate should be handled + */ +CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$ +exc = plpy.spiexceptions.DivisionByZero() +exc.sqlstate = 'SILLY' +raise exc +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception_override(); +EXCEPTION WHEN SQLSTATE 'SILLY' THEN + -- NOOP +END +$$ LANGUAGE plpgsql; +/* test the context stack trace for nested execution levels + */ +CREATE FUNCTION notice_innerfunc() RETURNS int AS $$ +plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$") +return 1 +$$ LANGUAGE plpython3u; +CREATE FUNCTION notice_outerfunc() RETURNS int AS $$ +plpy.execute("SELECT notice_innerfunc()") +return 1 +$$ LANGUAGE plpython3u; +\set SHOW_CONTEXT always +SELECT notice_outerfunc(); +NOTICE: inside DO +CONTEXT: PL/Python anonymous code block +SQL statement "DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$" +PL/Python function "notice_innerfunc" +SQL statement "SELECT notice_innerfunc()" +PL/Python function "notice_outerfunc" + notice_outerfunc +------------------ + 1 +(1 row) + +/* test error logged with an underlying exception that includes a detail + * string (bug #18070). + */ +CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$ + plan = plpy.prepare("SELECT to_date('xy', 'DD') d") + for row in plpy.cursor(plan): + yield row['d'] +$$ LANGUAGE plpython3u; +SELECT python_error_detail(); +ERROR: error fetching next item from iterator +DETAIL: spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD" +CONTEXT: Traceback (most recent call last): +PL/Python function "python_error_detail" diff --git a/src/pl/plpython/expected/plpython_error_5.out b/src/pl/plpython/expected/plpython_error_5.out new file mode 100644 index 0000000..fd9cd73 --- /dev/null +++ b/src/pl/plpython/expected/plpython_error_5.out @@ -0,0 +1,460 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); +ERROR: could not compile PL/Python function "python_syntax_error" +DETAIL: SyntaxError: invalid syntax (<string>, line 2) +RESET check_function_bodies; +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpython3u; +SELECT sql_syntax_error(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "syntax" +LINE 1: syntax error + ^ +QUERY: syntax error +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_syntax_error", line 1, in <module> + plpy.execute("syntax error") +PL/Python function "sql_syntax_error" +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpython3u; +SELECT exception_index_invalid('test'); +ERROR: IndexError: list index out of range +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid", line 1, in <module> + return args[1] +PL/Python function "exception_index_invalid" +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpython3u; +SELECT exception_index_invalid_nested(); +ERROR: spiexceptions.UndefinedFunction: function test5(unknown) does not exist +LINE 1: SELECT test5('foo') + ^ +HINT: No function matches the given name and argument types. You might need to add explicit type casts. +QUERY: SELECT test5('foo') +CONTEXT: Traceback (most recent call last): + PL/Python function "exception_index_invalid_nested", line 1, in <module> + rv = plpy.execute("SELECT test5('foo')") +PL/Python function "exception_index_invalid_nested" +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_uncaught('rick'); +ERROR: spiexceptions.UndefinedObject: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_uncaught", line 3, in <module> + SD["plan"] = plpy.prepare(q, [ "test" ]) +PL/Python function "invalid_type_uncaught" +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_caught('rick'); +NOTICE: type "test" does not exist + invalid_type_caught +--------------------- + +(1 row) + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT invalid_type_reraised('rick'); +ERROR: plpy.Error: type "test" does not exist +CONTEXT: Traceback (most recent call last): + PL/Python function "invalid_type_reraised", line 6, in <module> + plpy.error(str(ex)) +PL/Python function "invalid_type_reraised" +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; +SELECT valid_type('rick'); + valid_type +------------ + +(1 row) + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error", line 10, in <module> + fun3() + PL/Python function "nested_error", line 8, in fun3 + fun2() + PL/Python function "nested_error", line 5, in fun2 + fun1() + PL/Python function "nested_error", line 2, in fun1 + plpy.error("boom") +PL/Python function "nested_error" +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; +SELECT nested_error_raise(); +ERROR: plpy.Error: boom +CONTEXT: Traceback (most recent call last): + PL/Python function "nested_error_raise", line 10, in <module> + fun3() + PL/Python function "nested_error_raise", line 8, in fun3 + fun2() + PL/Python function "nested_error_raise", line 5, in fun2 + fun1() + PL/Python function "nested_error_raise", line 2, in fun1 + raise plpy.Error("boom") +PL/Python function "nested_error_raise" +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; +SELECT nested_warning(); +WARNING: boom + nested_warning +-------------------- + you've been warned +(1 row) + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; +SELECT toplevel_attribute_error(); +ERROR: AttributeError: module 'plpy' has no attribute 'nonexistent' +CONTEXT: Traceback (most recent call last): + PL/Python function "toplevel_attribute_error", line 2, in <module> + plpy.nonexistent +PL/Python function "toplevel_attribute_error" +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; +SELECT python_traceback(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in <module> + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SELECT sql_error(); +ERROR: division by zero +CONTEXT: SQL statement "select 1/0" +PL/pgSQL function sql_error() line 3 at SQL statement +SELECT python_from_sql_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "python_traceback", line 11, in <module> + first() + PL/Python function "python_traceback", line 3, in first + second() + PL/Python function "python_traceback", line 6, in second + third() + PL/Python function "python_traceback", line 9, in third + plpy.execute("select sql_error()") +PL/Python function "python_traceback" +SQL statement "select python_traceback()" +PL/pgSQL function python_from_sql_error() line 3 at SQL statement +SELECT sql_from_python_error(); +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python function "sql_from_python_error", line 2, in <module> + plpy.execute("select sql_error()") +PL/Python function "sql_from_python_error" +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; +SELECT specific_exception(2); + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(NULL); +NOTICE: Violated the NOT NULL constraint, sqlstate 23502 + specific_exception +-------------------- + +(1 row) + +SELECT specific_exception(2); +NOTICE: Violated the UNIQUE constraint, sqlstate 23505 + specific_exception +-------------------- + +(1 row) + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; +SELECT catch_python_unique_violation(); + catch_python_unique_violation +------------------------------- + ok +(1 row) + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; +SELECT manual_subxact(); +ERROR: plpy.SPIError: SPI_execute failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact", line 2, in <module> + plpy.execute("savepoint save") +PL/Python function "manual_subxact" +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; +SELECT manual_subxact_prepared(); +ERROR: plpy.SPIError: SPI_execute_plan failed: SPI_ERROR_TRANSACTION +CONTEXT: Traceback (most recent call last): + PL/Python function "manual_subxact_prepared", line 4, in <module> + plpy.execute(save) +PL/Python function "manual_subxact_prepared" +/* raising plpy.spiexception.* from python code should preserve sqlstate + */ +CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$ +raise plpy.spiexceptions.DivisionByZero() +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception(); +EXCEPTION WHEN division_by_zero THEN + -- NOOP +END +$$ LANGUAGE plpgsql; +/* setting a custom sqlstate should be handled + */ +CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$ +exc = plpy.spiexceptions.DivisionByZero() +exc.sqlstate = 'SILLY' +raise exc +$$ LANGUAGE plpython3u; +DO $$ +BEGIN + SELECT plpy_raise_spiexception_override(); +EXCEPTION WHEN SQLSTATE 'SILLY' THEN + -- NOOP +END +$$ LANGUAGE plpgsql; +/* test the context stack trace for nested execution levels + */ +CREATE FUNCTION notice_innerfunc() RETURNS int AS $$ +plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$") +return 1 +$$ LANGUAGE plpython3u; +CREATE FUNCTION notice_outerfunc() RETURNS int AS $$ +plpy.execute("SELECT notice_innerfunc()") +return 1 +$$ LANGUAGE plpython3u; +\set SHOW_CONTEXT always +SELECT notice_outerfunc(); +NOTICE: inside DO +CONTEXT: PL/Python anonymous code block +SQL statement "DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$" +PL/Python function "notice_innerfunc" +SQL statement "SELECT notice_innerfunc()" +PL/Python function "notice_outerfunc" + notice_outerfunc +------------------ + 1 +(1 row) + +/* test error logged with an underlying exception that includes a detail + * string (bug #18070). + */ +CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$ + plan = plpy.prepare("SELECT to_date('xy', 'DD') d") + for row in plpy.cursor(plan): + yield row['d'] +$$ LANGUAGE plpython3u; +SELECT python_error_detail(); +ERROR: error fetching next item from iterator +DETAIL: spiexceptions.InvalidDatetimeFormat: invalid value "xy" for "DD" +CONTEXT: Traceback (most recent call last): +PL/Python function "python_error_detail" diff --git a/src/pl/plpython/expected/plpython_global.out b/src/pl/plpython/expected/plpython_global.out new file mode 100644 index 0000000..a4cfb14 --- /dev/null +++ b/src/pl/plpython/expected/plpython_global.out @@ -0,0 +1,52 @@ +-- +-- check static and global data (SD and GD) +-- +CREATE FUNCTION global_test_one() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_one" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_one" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; +CREATE FUNCTION global_test_two() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_two" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_two" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; +CREATE FUNCTION static_test() returns int4 + AS +'if "call" in SD: + SD["call"] = SD["call"] + 1 +else: + SD["call"] = 1 +return SD["call"] +' + LANGUAGE plpython3u; +SELECT static_test(); + static_test +------------- + 1 +(1 row) + +SELECT static_test(); + static_test +------------- + 2 +(1 row) + +SELECT global_test_one(); + global_test_one +-------------------------------------------------------- + SD: set by global_test_one, GD: set by global_test_one +(1 row) + +SELECT global_test_two(); + global_test_two +-------------------------------------------------------- + SD: set by global_test_two, GD: set by global_test_one +(1 row) + diff --git a/src/pl/plpython/expected/plpython_import.out b/src/pl/plpython/expected/plpython_import.out new file mode 100644 index 0000000..854e989 --- /dev/null +++ b/src/pl/plpython/expected/plpython_import.out @@ -0,0 +1,79 @@ +-- import python modules +CREATE FUNCTION import_fail() returns text + AS +'try: + import foosocket +except ImportError: + return "failed as expected" +return "succeeded, that wasn''t supposed to happen"' + LANGUAGE plpython3u; +CREATE FUNCTION import_succeed() returns text + AS +'try: + import array + import bisect + import calendar + import cmath + import errno + import math + import operator + import random + import re + import string + import time +except Exception as ex: + plpy.notice("import failed -- %s" % str(ex)) + return "failed, that wasn''t supposed to happen" +return "succeeded, as expected"' + LANGUAGE plpython3u; +CREATE FUNCTION import_test_one(p text) RETURNS text + AS +'try: + import hashlib + digest = hashlib.sha1(p.encode("ascii")) +except ImportError: + import sha + digest = sha.new(p) +return digest.hexdigest()' + LANGUAGE plpython3u; +CREATE FUNCTION import_test_two(u users) RETURNS text + AS +'plain = u["fname"] + u["lname"] +try: + import hashlib + digest = hashlib.sha1(plain.encode("ascii")) +except ImportError: + import sha + digest = sha.new(plain); +return "sha hash of " + plain + " is " + digest.hexdigest()' + LANGUAGE plpython3u; +-- import python modules +-- +SELECT import_fail(); + import_fail +-------------------- + failed as expected +(1 row) + +SELECT import_succeed(); + import_succeed +------------------------ + succeeded, as expected +(1 row) + +-- test import and simple argument handling +-- +SELECT import_test_one('sha hash of this string'); + import_test_one +------------------------------------------ + a04e23cb9b1a09cd1051a04a7c571aae0f90346c +(1 row) + +-- test import and tuple argument handling +-- +select import_test_two(users) from users where fname = 'willem'; + import_test_two +------------------------------------------------------------------- + sha hash of willemdoe is 3cde6b574953b0ca937b4d76ebc40d534d910759 +(1 row) + diff --git a/src/pl/plpython/expected/plpython_newline.out b/src/pl/plpython/expected/plpython_newline.out new file mode 100644 index 0000000..2bc1492 --- /dev/null +++ b/src/pl/plpython/expected/plpython_newline.out @@ -0,0 +1,30 @@ +-- +-- Universal Newline Support +-- +CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS +E'x = 100\ny = 23\nreturn x + y\n' +LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS +E'x = 100\ry = 23\rreturn x + y\r' +LANGUAGE plpython3u; +CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS +E'x = 100\r\ny = 23\r\nreturn x + y\r\n' +LANGUAGE plpython3u; +SELECT newline_lf(); + newline_lf +------------ + 123 +(1 row) + +SELECT newline_cr(); + newline_cr +------------ + 123 +(1 row) + +SELECT newline_crlf(); + newline_crlf +-------------- + 123 +(1 row) + diff --git a/src/pl/plpython/expected/plpython_params.out b/src/pl/plpython/expected/plpython_params.out new file mode 100644 index 0000000..d1a36f3 --- /dev/null +++ b/src/pl/plpython/expected/plpython_params.out @@ -0,0 +1,64 @@ +-- +-- Test named and nameless parameters +-- +CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$ +return args[0] + args[1] +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$ +assert a0 == args[0] +assert a1 == args[1] +return True +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$ +assert u == args[0] +if isinstance(u, dict): + # stringify dict the hard way because otherwise the order is implementation-dependent + u_keys = list(u.keys()) + u_keys.sort() + s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}' +else: + s = str(u) +return s +$$ LANGUAGE plpython3u; +-- use deliberately wrong parameter names +CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$ +try: + assert a1 == args[0] + return False +except NameError as e: + assert e.args[0].find("a1") > -1 + return True +$$ LANGUAGE plpython3u; +SELECT test_param_names0(2,7); + test_param_names0 +------------------- + 9 +(1 row) + +SELECT test_param_names1(1,'text'); + test_param_names1 +------------------- + t +(1 row) + +SELECT test_param_names2(users) from users; + test_param_names2 +----------------------------------------------------------------------- + {'fname': 'jane', 'lname': 'doe', 'userid': 1, 'username': 'j_doe'} + {'fname': 'john', 'lname': 'doe', 'userid': 2, 'username': 'johnd'} + {'fname': 'willem', 'lname': 'doe', 'userid': 3, 'username': 'w_doe'} + {'fname': 'rick', 'lname': 'smith', 'userid': 4, 'username': 'slash'} +(4 rows) + +SELECT test_param_names2(NULL); + test_param_names2 +------------------- + None +(1 row) + +SELECT test_param_names3(1); + test_param_names3 +------------------- + t +(1 row) + diff --git a/src/pl/plpython/expected/plpython_populate.out b/src/pl/plpython/expected/plpython_populate.out new file mode 100644 index 0000000..4db75b0 --- /dev/null +++ b/src/pl/plpython/expected/plpython_populate.out @@ -0,0 +1,22 @@ +INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe'); +INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd'); +INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe'); +INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash'); +-- multi table tests +-- +INSERT INTO taxonomy (name) VALUES ('HIV I') ; +INSERT INTO taxonomy (name) VALUES ('HIV II') ; +INSERT INTO taxonomy (name) VALUES ('HCV') ; +INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ; +INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ; +INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ; diff --git a/src/pl/plpython/expected/plpython_quote.out b/src/pl/plpython/expected/plpython_quote.out new file mode 100644 index 0000000..1fbe93d --- /dev/null +++ b/src/pl/plpython/expected/plpython_quote.out @@ -0,0 +1,56 @@ +-- test quoting functions +CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) +$$ LANGUAGE plpython3u; +SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + ('xyzv')) AS v(t); + quote +----------- + 'abc' + 'a''bc' + '''abc''' + '' + '''' + 'xyzv' +(6 rows) + +SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + quote +----------- + 'abc' + 'a''bc' + '''abc''' + '' + '''' + NULL +(6 rows) + +SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); + quote +-------------- + abc + "a b c" + "a "" 'abc'" +(3 rows) + diff --git a/src/pl/plpython/expected/plpython_record.out b/src/pl/plpython/expected/plpython_record.out new file mode 100644 index 0000000..31de198 --- /dev/null +++ b/src/pl/plpython/expected/plpython_record.out @@ -0,0 +1,373 @@ +-- +-- Test returning tuples +-- +CREATE TABLE table_record ( + first text, + second int4 + ) ; +CREATE TYPE type_record AS ( + first text, + second int4 + ) ; +CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ +return first + '_in_to_out'; +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_in_out_params_multi(first in text, + second out text, third out text) AS $$ +return (first + '_record_in_to_out_1', first + '_record_in_to_out_2'); +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_inout_params(first inout text) AS $$ +return first + '_inout'; +$$ LANGUAGE plpython3u; +-- Test tuple returning functions +SELECT * FROM test_table_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_table_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_table_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_table_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_table_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('dict', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('dict', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('dict', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('dict', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('dict', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('tuple', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('tuple', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('tuple', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('list', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('list', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('list', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('list', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('list', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('obj', null, null, false); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('obj', 'one', null, false); + first | second +-------+-------- + one | +(1 row) + +SELECT * FROM test_type_record_as('obj', null, 2, false); + first | second +-------+-------- + | 2 +(1 row) + +SELECT * FROM test_type_record_as('obj', 'three', 3, false); + first | second +-------+-------- + three | 3 +(1 row) + +SELECT * FROM test_type_record_as('obj', null, null, true); + first | second +-------+-------- + | +(1 row) + +SELECT * FROM test_type_record_as('str', 'one', 1, false); + first | second +-------+-------- + 'one' | 1 +(1 row) + +SELECT * FROM test_in_out_params('test_in'); + second +------------------- + test_in_in_to_out +(1 row) + +SELECT * FROM test_in_out_params_multi('test_in'); + second | third +----------------------------+---------------------------- + test_in_record_in_to_out_1 | test_in_record_in_to_out_2 +(1 row) + +SELECT * FROM test_inout_params('test_in'); + first +--------------- + test_in_inout +(1 row) + +-- try changing the return types and call functions again +ALTER TABLE table_record DROP COLUMN first; +ALTER TABLE table_record DROP COLUMN second; +ALTER TABLE table_record ADD COLUMN first text; +ALTER TABLE table_record ADD COLUMN second int4; +SELECT * FROM test_table_record_as('obj', 'one', 1, false); + first | second +-------+-------- + one | 1 +(1 row) + +ALTER TYPE type_record DROP ATTRIBUTE first; +ALTER TYPE type_record DROP ATTRIBUTE second; +ALTER TYPE type_record ADD ATTRIBUTE first text; +ALTER TYPE type_record ADD ATTRIBUTE second int4; +SELECT * FROM test_type_record_as('obj', 'one', 1, false); + first | second +-------+-------- + one | 1 +(1 row) + +-- errors cases +CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$ + return { 'first': 'first' } +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error1(); +ERROR: key "second" not found in mapping +HINT: To return null in a column, add the value None to the mapping with the key named after the column. +CONTEXT: while creating return value +PL/Python function "test_type_record_error1" +CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$ + return [ 'first' ] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error2(); +ERROR: length of returned sequence did not match number of columns in row +CONTEXT: while creating return value +PL/Python function "test_type_record_error2" +CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$ + class type_record: pass + type_record.first = 'first' + return type_record +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error3(); +ERROR: attribute "second" does not exist in Python object +HINT: To return null in a column, let the returned object have an attribute named after column with value None. +CONTEXT: while creating return value +PL/Python function "test_type_record_error3" +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_record_error4(); +ERROR: malformed record literal: "foo" +DETAIL: Missing left parenthesis. +CONTEXT: while creating return value +PL/Python function "test_type_record_error4" diff --git a/src/pl/plpython/expected/plpython_schema.out b/src/pl/plpython/expected/plpython_schema.out new file mode 100644 index 0000000..23d94f6 --- /dev/null +++ b/src/pl/plpython/expected/plpython_schema.out @@ -0,0 +1,33 @@ +CREATE TABLE users ( + fname text not null, + lname text not null, + username text, + userid serial, + PRIMARY KEY(lname, fname) + ) ; +CREATE INDEX users_username_idx ON users(username); +CREATE INDEX users_fname_idx ON users(fname); +CREATE INDEX users_lname_idx ON users(lname); +CREATE INDEX users_userid_idx ON users(userid); +CREATE TABLE taxonomy ( + id serial primary key, + name text unique + ) ; +CREATE TABLE entry ( + accession text not null primary key, + eid serial unique, + txid int2 not null references taxonomy(id) + ) ; +CREATE TABLE sequences ( + eid int4 not null references entry(eid), + pid serial primary key, + product text not null, + sequence text not null, + multipart bool default 'false' + ) ; +CREATE INDEX sequences_product_idx ON sequences(product) ; +CREATE TABLE xsequences ( + pid int4 not null references sequences(pid), + sequence text not null + ) ; +CREATE INDEX xsequences_pid_idx ON xsequences(pid) ; diff --git a/src/pl/plpython/expected/plpython_setof.out b/src/pl/plpython/expected/plpython_setof.out new file mode 100644 index 0000000..3940940 --- /dev/null +++ b/src/pl/plpython/expected/plpython_setof.out @@ -0,0 +1,200 @@ +-- +-- Test returning SETOF +-- +CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$ +return 37 +$$ LANGUAGE plpython3u; +SELECT test_setof_error(); +ERROR: returned object cannot be iterated +DETAIL: PL/Python set-returning functions must return an iterable object. +CONTEXT: PL/Python function "test_setof_error" +CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$ +return [ content ]*count +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$ +t = () +for i in range(count): + t += ( content, ) +return t +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$ +class producer: + def __init__ (self, icount, icontent): + self.icontent = icontent + self.icount = icount + def __iter__ (self): + return self + def __next__ (self): + if self.icount == 0: + raise StopIteration + self.icount -= 1 + return self.icontent +return producer(count, content) +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS +$$ + for s in ('Hello', 'Brave', 'New', 'World'): + plpy.execute('select 1') + yield s + plpy.execute('select 2') +$$ +LANGUAGE plpython3u; +-- Test set returning functions +SELECT test_setof_as_list(0, 'list'); + test_setof_as_list +-------------------- +(0 rows) + +SELECT test_setof_as_list(1, 'list'); + test_setof_as_list +-------------------- + list +(1 row) + +SELECT test_setof_as_list(2, 'list'); + test_setof_as_list +-------------------- + list + list +(2 rows) + +SELECT test_setof_as_list(2, null); + test_setof_as_list +-------------------- + + +(2 rows) + +SELECT test_setof_as_tuple(0, 'tuple'); + test_setof_as_tuple +--------------------- +(0 rows) + +SELECT test_setof_as_tuple(1, 'tuple'); + test_setof_as_tuple +--------------------- + tuple +(1 row) + +SELECT test_setof_as_tuple(2, 'tuple'); + test_setof_as_tuple +--------------------- + tuple + tuple +(2 rows) + +SELECT test_setof_as_tuple(2, null); + test_setof_as_tuple +--------------------- + + +(2 rows) + +SELECT test_setof_as_iterator(0, 'list'); + test_setof_as_iterator +------------------------ +(0 rows) + +SELECT test_setof_as_iterator(1, 'list'); + test_setof_as_iterator +------------------------ + list +(1 row) + +SELECT test_setof_as_iterator(2, 'list'); + test_setof_as_iterator +------------------------ + list + list +(2 rows) + +SELECT test_setof_as_iterator(2, null); + test_setof_as_iterator +------------------------ + + +(2 rows) + +SELECT test_setof_spi_in_iterator(); + test_setof_spi_in_iterator +---------------------------- + Hello + Brave + New + World +(4 rows) + +-- set-returning function that modifies its parameters +CREATE OR REPLACE FUNCTION ugly(x int, lim int) RETURNS SETOF int AS $$ +global x +while x <= lim: + yield x + x = x + 1 +$$ LANGUAGE plpython3u; +SELECT ugly(1, 5); + ugly +------ + 1 + 2 + 3 + 4 + 5 +(5 rows) + +-- interleaved execution of such a function +SELECT ugly(1,3), ugly(7,8); + ugly | ugly +------+------ + 1 | 7 + 2 | 8 + 3 | +(3 rows) + +-- returns set of named-composite-type tuples +CREATE OR REPLACE FUNCTION get_user_records() +RETURNS SETOF users +AS $$ + return plpy.execute("SELECT * FROM users ORDER BY username") +$$ LANGUAGE plpython3u; +SELECT get_user_records(); + get_user_records +---------------------- + (jane,doe,j_doe,1) + (john,doe,johnd,2) + (rick,smith,slash,4) + (willem,doe,w_doe,3) +(4 rows) + +SELECT * FROM get_user_records(); + fname | lname | username | userid +--------+-------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + rick | smith | slash | 4 + willem | doe | w_doe | 3 +(4 rows) + +-- same, but returning set of RECORD +CREATE OR REPLACE FUNCTION get_user_records2() +RETURNS TABLE(fname text, lname text, username text, userid int) +AS $$ + return plpy.execute("SELECT * FROM users ORDER BY username") +$$ LANGUAGE plpython3u; +SELECT get_user_records2(); + get_user_records2 +---------------------- + (jane,doe,j_doe,1) + (john,doe,johnd,2) + (rick,smith,slash,4) + (willem,doe,w_doe,3) +(4 rows) + +SELECT * FROM get_user_records2(); + fname | lname | username | userid +--------+-------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + rick | smith | slash | 4 + willem | doe | w_doe | 3 +(4 rows) + diff --git a/src/pl/plpython/expected/plpython_spi.out b/src/pl/plpython/expected/plpython_spi.out new file mode 100644 index 0000000..8853e25 --- /dev/null +++ b/src/pl/plpython/expected/plpython_spi.out @@ -0,0 +1,466 @@ +-- +-- nested calls +-- +CREATE FUNCTION nested_call_one(a text) RETURNS text + AS +'q = "SELECT nested_call_two(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; +CREATE FUNCTION nested_call_two(a text) RETURNS text + AS +'q = "SELECT nested_call_three(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; +CREATE FUNCTION nested_call_three(a text) RETURNS text + AS +'return a' + LANGUAGE plpython3u ; +-- some spi stuff +CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = plpy.execute(SD["myplan"], [a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = SD["myplan"].execute([a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a + SD["myplan"] = plpy.prepare(q) +try: + rv = plpy.execute(SD["myplan"]) + if len(rv): + return rv[0]["count"] +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; +CREATE FUNCTION join_sequences(s sequences) RETURNS text + AS +'if not s["multipart"]: + return s["sequence"] +q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"] +rv = plpy.execute(q) +seq = s["sequence"] +for r in rv: + seq = seq + r["sequence"] +return seq +' + LANGUAGE plpython3u; +CREATE FUNCTION spi_recursive_sum(a int) RETURNS int + AS +'r = 0 +if a > 1: + r = plpy.execute("SELECT spi_recursive_sum(%d) as a" % (a-1))[0]["a"] +return a + r +' + LANGUAGE plpython3u; +-- +-- spi and nested calls +-- +select nested_call_one('pass this along'); + nested_call_one +----------------------------------------------------------------- + {'nested_call_two': "{'nested_call_three': 'pass this along'}"} +(1 row) + +select spi_prepared_plan_test_one('doe'); + spi_prepared_plan_test_one +---------------------------- + there are 3 does +(1 row) + +select spi_prepared_plan_test_two('smith'); + spi_prepared_plan_test_two +---------------------------- + there are 1 smiths +(1 row) + +select spi_prepared_plan_test_nested('smith'); + spi_prepared_plan_test_nested +------------------------------- + there are 1 smiths +(1 row) + +SELECT join_sequences(sequences) FROM sequences; + join_sequences +---------------- + ABCDEFGHIJKL + ABCDEF + ABCDEF + ABCDEF + ABCDEF + ABCDEF +(6 rows) + +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^A'; + join_sequences +---------------- + ABCDEFGHIJKL + ABCDEF + ABCDEF + ABCDEF + ABCDEF + ABCDEF +(6 rows) + +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^B'; + join_sequences +---------------- +(0 rows) + +SELECT spi_recursive_sum(10); + spi_recursive_sum +------------------- + 55 +(1 row) + +-- +-- plan and result objects +-- +CREATE FUNCTION result_metadata_test(cmd text) RETURNS int +AS $$ +plan = plpy.prepare(cmd) +plpy.info(plan.status()) # not really documented or useful +result = plpy.execute(plan) +if result.status() > 0: + plpy.info(result.colnames()) + plpy.info(result.coltypes()) + plpy.info(result.coltypmods()) + return result.nrows() +else: + return None +$$ LANGUAGE plpython3u; +SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$); +INFO: True +INFO: ['foo', 'bar'] +INFO: [23, 25] +INFO: [-1, -1] + result_metadata_test +---------------------- + 2 +(1 row) + +SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); +INFO: True +ERROR: plpy.Error: command did not produce a result set +CONTEXT: Traceback (most recent call last): + PL/Python function "result_metadata_test", line 6, in <module> + plpy.info(result.colnames()) +PL/Python function "result_metadata_test" +CREATE FUNCTION result_nrows_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return result.nrows() +$$ LANGUAGE plpython3u; +SELECT result_nrows_test($$SELECT 1$$); + result_nrows_test +------------------- + 1 +(1 row) + +SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$); + result_nrows_test +------------------- + 0 +(1 row) + +SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$); + result_nrows_test +------------------- + 2 +(1 row) + +SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$); + result_nrows_test +------------------- + 1 +(1 row) + +CREATE FUNCTION result_len_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return len(result) +$$ LANGUAGE plpython3u; +SELECT result_len_test($$SELECT 1$$); + result_len_test +----------------- + 1 +(1 row) + +SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$); + result_len_test +----------------- + 0 +(1 row) + +SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$); + result_len_test +----------------- + 0 +(1 row) + +SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$); + result_len_test +----------------- + 0 +(1 row) + +CREATE FUNCTION result_subscript_test() RETURNS void +AS $$ +result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 " + "UNION ALL SELECT 3 UNION ALL SELECT 4") + +plpy.info(result[1]['c']) +plpy.info(result[-1]['c']) + +plpy.info([item['c'] for item in result[1:3]]) +plpy.info([item['c'] for item in result[::2]]) + +result[-1] = {'c': 1000} +result[:2] = [{'c': 10}, {'c': 100}] +plpy.info([item['c'] for item in result[:]]) + +# raises TypeError, catch so further tests could be added +try: + plpy.info(result['foo']) +except TypeError: + pass +else: + assert False, "TypeError not raised" + +$$ LANGUAGE plpython3u; +SELECT result_subscript_test(); +INFO: 2 +INFO: 4 +INFO: [2, 3] +INFO: [1, 3] +INFO: [10, 100, 3, 1000] + result_subscript_test +----------------------- + +(1 row) + +CREATE FUNCTION result_empty_test() RETURNS void +AS $$ +result = plpy.execute("select 1 where false") + +plpy.info(result[:]) + +$$ LANGUAGE plpython3u; +SELECT result_empty_test(); +INFO: [] + result_empty_test +------------------- + +(1 row) + +CREATE FUNCTION result_str_test(cmd text) RETURNS text +AS $$ +plan = plpy.prepare(cmd) +result = plpy.execute(plan) +return str(result) +$$ LANGUAGE plpython3u; +SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$); + result_str_test +------------------------------------------------------------ + <PLyResult status=5 nrows=2 rows=[{'foo': 1}, {'foo': 2}]> +(1 row) + +SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + result_str_test +-------------------------------------- + <PLyResult status=4 nrows=0 rows=[]> +(1 row) + +-- cursor objects +CREATE FUNCTION simple_cursor_test() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +does = 0 +for row in res: + if row['lname'] == 'doe': + does += 1 +return does +$$ LANGUAGE plpython3u; +CREATE FUNCTION double_cursor_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +res.close() +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +assert len(res.fetch(3)) == 3 +assert len(res.fetch(3)) == 1 +assert len(res.fetch(3)) == 0 +assert len(res.fetch(3)) == 0 +try: + # use next() or __next__(), the method name changed in + # http://www.python.org/dev/peps/pep-3114/ + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users order by fname") +assert len(res.fetch(2)) == 2 + +item = None +try: + item = res.next() +except AttributeError: + item = res.__next__() +assert item['fname'] == 'rick' + +assert len(res.fetch(2)) == 1 +$$ LANGUAGE plpython3u; +CREATE FUNCTION fetch_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + res.fetch(1) +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION next_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + try: + res.next() + except AttributeError: + res.__next__() +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users where false") +assert len(res.fetch(1)) == 0 +try: + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$ +plan = plpy.prepare( + "select fname, lname from users where fname like $1 || '%' order by fname", + ["text"]) +for row in plpy.cursor(plan, ["w"]): + yield row['fname'] +for row in plan.cursor(["j"]): + yield row['fname'] +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$ +plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", + ["text"]) +c = plpy.cursor(plan, ["a", "b"]) +$$ LANGUAGE plpython3u; +CREATE TYPE test_composite_type AS ( + a1 int, + a2 varchar +); +CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$ +plan = plpy.prepare("select $1 as c1", ["test_composite_type"]) +res = plpy.execute(plan, [{"a1": 3, "a2": "label"}]) +return res[0]["c1"] +$$ LANGUAGE plpython3u; +SELECT simple_cursor_test(); + simple_cursor_test +-------------------- + 3 +(1 row) + +SELECT double_cursor_close(); + double_cursor_close +--------------------- + +(1 row) + +SELECT cursor_fetch(); + cursor_fetch +-------------- + +(1 row) + +SELECT cursor_mix_next_and_fetch(); + cursor_mix_next_and_fetch +--------------------------- + +(1 row) + +SELECT fetch_after_close(); + fetch_after_close +------------------- + +(1 row) + +SELECT next_after_close(); + next_after_close +------------------ + +(1 row) + +SELECT cursor_fetch_next_empty(); + cursor_fetch_next_empty +------------------------- + +(1 row) + +SELECT cursor_plan(); + cursor_plan +------------- + willem + jane + john +(3 rows) + +SELECT cursor_plan_wrong_args(); +ERROR: TypeError: Expected sequence of 1 argument, got 2: ['a', 'b'] +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_plan_wrong_args", line 4, in <module> + c = plpy.cursor(plan, ["a", "b"]) +PL/Python function "cursor_plan_wrong_args" +SELECT plan_composite_args(); + plan_composite_args +--------------------- + (3,label) +(1 row) + diff --git a/src/pl/plpython/expected/plpython_subtransaction.out b/src/pl/plpython/expected/plpython_subtransaction.out new file mode 100644 index 0000000..43d9277 --- /dev/null +++ b/src/pl/plpython/expected/plpython_subtransaction.out @@ -0,0 +1,401 @@ +-- +-- Test explicit subtransactions +-- +-- Test table to see if transactions get properly rolled back +CREATE TABLE subtransaction_tbl ( + i integer +); +CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + raise Exception("Python exception") +$$ LANGUAGE plpython3u; +SELECT subtransaction_ctx_test(); + subtransaction_ctx_test +------------------------- + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('SPI'); +ERROR: spiexceptions.InvalidTextRepresentation: invalid input syntax for type integer: "oops" +LINE 1: INSERT INTO subtransaction_tbl VALUES ('oops') + ^ +QUERY: INSERT INTO subtransaction_tbl VALUES ('oops') +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_ctx_test", line 6, in <module> + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") +PL/Python function "subtransaction_ctx_test" +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('Python'); +ERROR: Exception: Python exception +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_ctx_test", line 8, in <module> + raise Exception("Python exception") +PL/Python function "subtransaction_ctx_test" +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +-- Nested subtransactions +CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)") + plpy.execute("error") + except plpy.SPIError as e: + if not swallow: + raise + plpy.notice("Swallowed %s(%r)" % (e.__class__.__name__, e.args[0])) +return "ok" +$$ LANGUAGE plpython3u; +SELECT subtransaction_nested_test(); +ERROR: spiexceptions.SyntaxError: syntax error at or near "error" +LINE 1: error + ^ +QUERY: error +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_nested_test", line 8, in <module> + plpy.execute("error") +PL/Python function "subtransaction_nested_test" +SELECT * FROM subtransaction_tbl; + i +--- +(0 rows) + +TRUNCATE subtransaction_tbl; +SELECT subtransaction_nested_test('t'); +NOTICE: Swallowed SyntaxError('syntax error at or near "error"') + subtransaction_nested_test +---------------------------- + ok +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +-- Nested subtransactions that recursively call code dealing with +-- subtransactions +CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + plpy.execute("SELECT subtransaction_nested_test('t')") +return "ok" +$$ LANGUAGE plpython3u; +SELECT subtransaction_deeply_nested_test(); +NOTICE: Swallowed SyntaxError('syntax error at or near "error"') + subtransaction_deeply_nested_test +----------------------------------- + ok +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 + 1 + 2 +(4 rows) + +TRUNCATE subtransaction_tbl; +-- Error conditions from not opening/closing subtransactions +CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void +AS $$ +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__exit__(None, None, None) +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__exit__(None, None, None) +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__enter__() +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; +-- No warnings here, as the subtransaction gets indeed closed +CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__enter__() +$$ LANGUAGE plpython3u; +CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void +AS $$ +try: + with plpy.subtransaction() as s: + s.__exit__(None, None, None) +except ValueError as e: + raise ValueError(e) +$$ LANGUAGE plpython3u; +SELECT subtransaction_exit_without_enter(); +ERROR: ValueError: this subtransaction has not been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_without_enter", line 2, in <module> + plpy.subtransaction().__exit__(None, None, None) +PL/Python function "subtransaction_exit_without_enter" +SELECT subtransaction_enter_without_exit(); +WARNING: forcibly aborting a subtransaction that has not been exited + subtransaction_enter_without_exit +----------------------------------- + +(1 row) + +SELECT subtransaction_exit_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +ERROR: ValueError: this subtransaction has not been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_twice", line 3, in <module> + plpy.subtransaction().__exit__(None, None, None) +PL/Python function "subtransaction_exit_twice" +SELECT subtransaction_enter_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +WARNING: forcibly aborting a subtransaction that has not been exited + subtransaction_enter_twice +---------------------------- + +(1 row) + +SELECT subtransaction_exit_same_subtransaction_twice(); +ERROR: ValueError: this subtransaction has already been exited +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_same_subtransaction_twice", line 5, in <module> + s.__exit__(None, None, None) +PL/Python function "subtransaction_exit_same_subtransaction_twice" +SELECT subtransaction_enter_same_subtransaction_twice(); +WARNING: forcibly aborting a subtransaction that has not been exited +ERROR: ValueError: this subtransaction has already been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_enter_same_subtransaction_twice", line 4, in <module> + s.__enter__() +PL/Python function "subtransaction_enter_same_subtransaction_twice" +SELECT subtransaction_enter_subtransaction_in_with(); +ERROR: ValueError: this subtransaction has already been entered +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_enter_subtransaction_in_with", line 3, in <module> + s.__enter__() +PL/Python function "subtransaction_enter_subtransaction_in_with" +SELECT subtransaction_exit_subtransaction_in_with(); +ERROR: ValueError: this subtransaction has already been exited +CONTEXT: Traceback (most recent call last): + PL/Python function "subtransaction_exit_subtransaction_in_with", line 6, in <module> + raise ValueError(e) +PL/Python function "subtransaction_exit_subtransaction_in_with" +-- Make sure we don't get a "current transaction is aborted" error +SELECT 1 as test; + test +------ + 1 +(1 row) + +-- Mix explicit subtransactions and normal SPI calls +CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void +AS $$ +p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"]) +try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error from an explicit subtransaction") + +try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error") +$$ LANGUAGE plpython3u; +SELECT subtransaction_mix_explicit_and_implicit(); +WARNING: Caught a SPI error from an explicit subtransaction +WARNING: Caught a SPI error + subtransaction_mix_explicit_and_implicit +------------------------------------------ + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 + 2 +(2 rows) + +TRUNCATE subtransaction_tbl; +-- Alternative method names for Python <2.6 +CREATE FUNCTION subtransaction_alternative_names() RETURNS void +AS $$ +s = plpy.subtransaction() +s.enter() +s.exit(None, None, None) +$$ LANGUAGE plpython3u; +SELECT subtransaction_alternative_names(); + subtransaction_alternative_names +---------------------------------- + +(1 row) + +-- try/catch inside a subtransaction block +CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; +SELECT try_catch_inside_subtransaction(); +NOTICE: caught + try_catch_inside_subtransaction +--------------------------------- + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 +(1 row) + +TRUNCATE subtransaction_tbl; +ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i); +CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; +SELECT pk_violation_inside_subtransaction(); +NOTICE: caught + pk_violation_inside_subtransaction +------------------------------------ + +(1 row) + +SELECT * FROM subtransaction_tbl; + i +--- + 1 +(1 row) + +DROP TABLE subtransaction_tbl; +-- cursor/subtransactions interactions +CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$ +with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10) +fetched = cur.fetch(10); +return int(fetched[5]["i"]) +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10); + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(10) + return int(fetched[5]["i"]) +return 0 # not reached +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + plpy.execute('create temporary table tmp(i) ' + 'as select generate_series(1, 10)') + plan = plpy.prepare("select i from tmp") + cur = plpy.cursor(plan) + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(5) + return fetched[2]["i"] +return 0 # not reached +$$ LANGUAGE plpython3u; +CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor('select 1') + plpy.execute("select no_such_function()") +except plpy.SPIError: + cur.close() + return True +return False # not reached +$$ LANGUAGE plpython3u; +SELECT cursor_in_subxact(); + cursor_in_subxact +------------------- + 16 +(1 row) + +SELECT cursor_aborted_subxact(); +ERROR: ValueError: iterating a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_aborted_subxact", line 8, in <module> + fetched = cur.fetch(10) +PL/Python function "cursor_aborted_subxact" +SELECT cursor_plan_aborted_subxact(); +ERROR: ValueError: iterating a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_plan_aborted_subxact", line 10, in <module> + fetched = cur.fetch(5) +PL/Python function "cursor_plan_aborted_subxact" +SELECT cursor_close_aborted_subxact(); +ERROR: ValueError: closing a cursor in an aborted subtransaction +CONTEXT: Traceback (most recent call last): + PL/Python function "cursor_close_aborted_subxact", line 7, in <module> + cur.close() +PL/Python function "cursor_close_aborted_subxact" diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out new file mode 100644 index 0000000..13c1411 --- /dev/null +++ b/src/pl/plpython/expected/plpython_test.out @@ -0,0 +1,93 @@ +-- first some tests of basic functionality +CREATE EXTENSION plpython3u; +-- really stupid function just to get the module loaded +CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; +select stupid(); + stupid +-------- + zarkon +(1 row) + +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; +select stupidn(); + stupidn +--------- + zarkon +(1 row) + +-- test multiple arguments and odd characters in function name +CREATE FUNCTION "Argument test #1"(u users, a1 text, a2 text) RETURNS text + AS +'keys = list(u.keys()) +keys.sort() +out = [] +for key in keys: + out.append("%s: %s" % (key, u[key])) +words = a1 + " " + a2 + " => {" + ", ".join(out) + "}" +return words' + LANGUAGE plpython3u; +select "Argument test #1"(users, fname, lname) from users where lname = 'doe' order by 1; + Argument test #1 +----------------------------------------------------------------------- + jane doe => {fname: jane, lname: doe, userid: 1, username: j_doe} + john doe => {fname: john, lname: doe, userid: 2, username: johnd} + willem doe => {fname: willem, lname: doe, userid: 3, username: w_doe} +(3 rows) + +-- check module contents +CREATE FUNCTION module_contents() RETURNS SETOF text AS +$$ +contents = list(filter(lambda x: not x.startswith("__"), dir(plpy))) +contents.sort() +return contents +$$ LANGUAGE plpython3u; +select module_contents(); + module_contents +----------------- + Error + Fatal + SPIError + commit + cursor + debug + error + execute + fatal + info + log + notice + prepare + quote_ident + quote_literal + quote_nullable + rollback + spiexceptions + subtransaction + warning +(20 rows) + +CREATE FUNCTION elog_test_basic() RETURNS void +AS $$ +plpy.debug('debug') +plpy.log('log') +plpy.info('info') +plpy.info(37) +plpy.info() +plpy.info('info', 37, [1, 2, 3]) +plpy.notice('notice') +plpy.warning('warning') +plpy.error('error') +$$ LANGUAGE plpython3u; +SELECT elog_test_basic(); +INFO: info +INFO: 37 +INFO: () +INFO: ('info', 37, [1, 2, 3]) +NOTICE: notice +WARNING: warning +ERROR: plpy.Error: error +CONTEXT: Traceback (most recent call last): + PL/Python function "elog_test_basic", line 10, in <module> + plpy.error('error') +PL/Python function "elog_test_basic" diff --git a/src/pl/plpython/expected/plpython_transaction.out b/src/pl/plpython/expected/plpython_transaction.out new file mode 100644 index 0000000..659ccef --- /dev/null +++ b/src/pl/plpython/expected/plpython_transaction.out @@ -0,0 +1,250 @@ +CREATE TABLE test1 (a int, b text); +CREATE PROCEDURE transaction_test1() +LANGUAGE plpython3u +AS $$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; +CALL transaction_test1(); +SELECT * FROM test1; + a | b +---+--- + 0 | + 2 | + 4 | + 6 | + 8 | +(5 rows) + +TRUNCATE test1; +DO +LANGUAGE plpython3u +$$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; +SELECT * FROM test1; + a | b +---+--- + 0 | + 2 | + 4 | + 6 | + 8 | +(5 rows) + +TRUNCATE test1; +-- not allowed in a function +CREATE FUNCTION transaction_test2() RETURNS int +LANGUAGE plpython3u +AS $$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +return 1 +$$; +SELECT transaction_test2(); +ERROR: spiexceptions.InvalidTransactionTermination: invalid transaction termination +CONTEXT: Traceback (most recent call last): + PL/Python function "transaction_test2", line 5, in <module> + plpy.commit() +PL/Python function "transaction_test2" +SELECT * FROM test1; + a | b +---+--- +(0 rows) + +-- also not allowed if procedure is called from a function +CREATE FUNCTION transaction_test3() RETURNS int +LANGUAGE plpython3u +AS $$ +plpy.execute("CALL transaction_test1()") +return 1 +$$; +SELECT transaction_test3(); +ERROR: spiexceptions.InvalidTransactionTermination: spiexceptions.InvalidTransactionTermination: invalid transaction termination +CONTEXT: Traceback (most recent call last): + PL/Python function "transaction_test3", line 2, in <module> + plpy.execute("CALL transaction_test1()") +PL/Python function "transaction_test3" +SELECT * FROM test1; + a | b +---+--- +(0 rows) + +-- DO block inside function +CREATE FUNCTION transaction_test4() RETURNS int +LANGUAGE plpython3u +AS $$ +plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$") +return 1 +$$; +SELECT transaction_test4(); +ERROR: spiexceptions.InvalidTransactionTermination: spiexceptions.InvalidTransactionTermination: invalid transaction termination +CONTEXT: Traceback (most recent call last): + PL/Python function "transaction_test4", line 2, in <module> + plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$") +PL/Python function "transaction_test4" +-- commit inside subtransaction (prohibited) +DO LANGUAGE plpython3u $$ +s = plpy.subtransaction() +s.enter() +plpy.commit() +$$; +WARNING: forcibly aborting a subtransaction that has not been exited +ERROR: spiexceptions.InvalidTransactionTermination: cannot commit while a subtransaction is active +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 4, in <module> + plpy.commit() +PL/Python anonymous code block +-- commit inside cursor loop +CREATE TABLE test2 (x int); +INSERT INTO test2 VALUES (0), (1), (2), (3), (4); +TRUNCATE test1; +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + plpy.commit() +$$; +SELECT * FROM test1; + a | b +---+--- + 0 | + 1 | + 2 | + 3 | + 4 | +(5 rows) + +-- check that this doesn't leak a holdable portal +SELECT * FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable | creation_time +------+-----------+-------------+-----------+---------------+--------------- +(0 rows) + +-- error in cursor loop with commit +TRUNCATE test1; +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x']) + plpy.commit() +$$; +ERROR: spiexceptions.DivisionByZero: division by zero +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 3, in <module> + plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x']) +PL/Python anonymous code block +SELECT * FROM test1; + a | b +-----+--- + -6 | + -12 | +(2 rows) + +SELECT * FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable | creation_time +------+-----------+-------------+-----------+---------------+--------------- +(0 rows) + +-- rollback inside cursor loop +TRUNCATE test1; +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + plpy.rollback() +$$; +SELECT * FROM test1; + a | b +---+--- +(0 rows) + +SELECT * FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable | creation_time +------+-----------+-------------+-----------+---------------+--------------- +(0 rows) + +-- first commit then rollback inside cursor loop +TRUNCATE test1; +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + if row['x'] % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; +SELECT * FROM test1; + a | b +---+--- + 0 | + 2 | + 4 | +(3 rows) + +SELECT * FROM pg_cursors; + name | statement | is_holdable | is_binary | is_scrollable | creation_time +------+-----------+-------------+-----------+---------------+--------------- +(0 rows) + +-- check handling of an error during COMMIT +CREATE TABLE testpk (id int PRIMARY KEY); +CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED); +DO LANGUAGE plpython3u $$ +# this insert will fail during commit: +plpy.execute("INSERT INTO testfk VALUES (0)") +plpy.commit() +plpy.warning('should not get here') +$$; +ERROR: spiexceptions.ForeignKeyViolation: insert or update on table "testfk" violates foreign key constraint "testfk_f1_fkey" +DETAIL: Key (f1)=(0) is not present in table "testpk". +CONTEXT: Traceback (most recent call last): + PL/Python anonymous code block, line 4, in <module> + plpy.commit() +PL/Python anonymous code block +SELECT * FROM testpk; + id +---- +(0 rows) + +SELECT * FROM testfk; + f1 +---- +(0 rows) + +DO LANGUAGE plpython3u $$ +# this insert will fail during commit: +plpy.execute("INSERT INTO testfk VALUES (0)") +try: + plpy.commit() +except Exception as e: + plpy.info('sqlstate: %s' % (e.sqlstate)) +# these inserts should work: +plpy.execute("INSERT INTO testpk VALUES (1)") +plpy.execute("INSERT INTO testfk VALUES (1)") +$$; +INFO: sqlstate: 23503 +SELECT * FROM testpk; + id +---- + 1 +(1 row) + +SELECT * FROM testfk; + f1 +---- + 1 +(1 row) + +DROP TABLE test1; +DROP TABLE test2; diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out new file mode 100644 index 0000000..dd1ca32 --- /dev/null +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -0,0 +1,620 @@ +-- these triggers are dedicated to HPHC of RI who +-- decided that my kid's name was william not willem, and +-- vigorously resisted all efforts at correction. they have +-- since gone bankrupt... +CREATE FUNCTION users_insert() returns trigger + AS +'if TD["new"]["fname"] == None or TD["new"]["lname"] == None: + return "SKIP" +if TD["new"]["username"] == None: + TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"] + rv = "MODIFY" +else: + rv = None +if TD["new"]["fname"] == "william": + TD["new"]["fname"] = TD["args"][0] + rv = "MODIFY" +return rv' + LANGUAGE plpython3u; +CREATE FUNCTION users_update() returns trigger + AS +'if TD["event"] == "UPDATE": + if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; +CREATE FUNCTION users_delete() RETURNS trigger + AS +'if TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; +CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW + EXECUTE PROCEDURE users_insert ('willem'); +CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW + EXECUTE PROCEDURE users_update ('willem'); +CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW + EXECUTE PROCEDURE users_delete ('willem'); +-- quick peek at the table +-- +SELECT * FROM users; + fname | lname | username | userid +--------+-------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + willem | doe | w_doe | 3 + rick | smith | slash | 4 +(4 rows) + +-- should fail +-- +UPDATE users SET fname = 'william' WHERE fname = 'willem'; +-- should modify william to willem and create username +-- +INSERT INTO users (fname, lname) VALUES ('william', 'smith'); +INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle'); +SELECT * FROM users; + fname | lname | username | userid +---------+--------+----------+-------- + jane | doe | j_doe | 1 + john | doe | johnd | 2 + willem | doe | w_doe | 3 + rick | smith | slash | 4 + willem | smith | w_smith | 5 + charles | darwin | beagle | 6 +(6 rows) + +-- dump trigger data +CREATE TABLE trigger_test + (i int, v text ); +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$ + +if 'relid' in TD: + TD['relid'] = "bogus:12345" + +skeys = list(TD.keys()) +skeys.sort() +for key in skeys: + val = TD[key] + if not isinstance(val, dict): + plpy.notice("TD[" + key + "] => " + str(val)) + else: + # print dicts the hard way because otherwise the order is implementation-dependent + valkeys = list(val.keys()) + valkeys.sort() + plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}') + +return None + +$$; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); +CREATE TRIGGER show_trigger_data_trig_stmt +BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo'); +insert into trigger_test values(1,'insert'); +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +update trigger_test set v = 'update' where i = 1; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +delete from trigger_test; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'update'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'update'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +truncate table trigger_test; +NOTICE: TD[args] => ['23', 'skidoo'] +NOTICE: TD[event] => TRUNCATE +NOTICE: TD[level] => STATEMENT +NOTICE: TD[name] => show_trigger_data_trig_stmt +NOTICE: TD[new] => None +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; +DROP TRIGGER show_trigger_data_trig_before on trigger_test; +DROP TRIGGER show_trigger_data_trig_after on trigger_test; +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); +insert into trigger_test_generated (i) values (1); +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 1} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 1, 'j': 2} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +update trigger_test_generated set i = 11 where i = 1; +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => {'i': 11} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => {'i': 11, 'j': 22} +NOTICE: TD[old] => {'i': 1, 'j': 2} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +delete from trigger_test_generated; +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_before +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => BEFORE +NOTICE: TD[args] => None +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig_after +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 11, 'j': 22} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_generated +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => AFTER +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; +insert into trigger_test values(1,'insert'); +CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; +CREATE TRIGGER show_trigger_data_trig +INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view +FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); +insert into trigger_test_view values(2,'insert'); +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => INSERT +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => {'i': 2, 'v': 'insert'} +NOTICE: TD[old] => None +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +update trigger_test_view set v = 'update' where i = 1; +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => UPDATE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => {'i': 1, 'v': 'update'} +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +delete from trigger_test_view; +NOTICE: TD[args] => ['24', 'skidoo view'] +NOTICE: TD[event] => DELETE +NOTICE: TD[level] => ROW +NOTICE: TD[name] => show_trigger_data_trig +NOTICE: TD[new] => None +NOTICE: TD[old] => {'i': 1, 'v': 'insert'} +NOTICE: TD[relid] => bogus:12345 +NOTICE: TD[table_name] => trigger_test_view +NOTICE: TD[table_schema] => public +NOTICE: TD[when] => INSTEAD OF +DROP FUNCTION trigger_data() CASCADE; +NOTICE: drop cascades to trigger show_trigger_data_trig on view trigger_test_view +DROP VIEW trigger_test_view; +delete from trigger_test; +-- +-- trigger error handling +-- +INSERT INTO trigger_test VALUES (0, 'zero'); +-- returning non-string from trigger function +CREATE FUNCTION stupid1() RETURNS trigger +AS $$ + return 37 +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger1 +BEFORE INSERT ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid1(); +INSERT INTO trigger_test VALUES (1, 'one'); +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None or a string. +CONTEXT: PL/Python function "stupid1" +DROP TRIGGER stupid_trigger1 ON trigger_test; +-- returning MODIFY from DELETE trigger +CREATE FUNCTION stupid2() RETURNS trigger +AS $$ + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger2 +BEFORE DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid2(); +DELETE FROM trigger_test WHERE i = 0; +WARNING: PL/Python trigger function returned "MODIFY" in a DELETE trigger -- ignored +DROP TRIGGER stupid_trigger2 ON trigger_test; +INSERT INTO trigger_test VALUES (0, 'zero'); +-- returning unrecognized string from trigger function +CREATE FUNCTION stupid3() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None, "OK", "SKIP", or "MODIFY". +CONTEXT: PL/Python function "stupid3" +DROP TRIGGER stupid_trigger3 ON trigger_test; +-- Unicode variant +CREATE FUNCTION stupid3u() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3u(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: unexpected return value from trigger procedure +DETAIL: Expected None, "OK", "SKIP", or "MODIFY". +CONTEXT: PL/Python function "stupid3u" +DROP TRIGGER stupid_trigger3 ON trigger_test; +-- deleting the TD dictionary +CREATE FUNCTION stupid4() RETURNS trigger +AS $$ + del TD["new"] + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger4 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid4(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] deleted, cannot modify row +CONTEXT: while modifying trigger row +PL/Python function "stupid4" +DROP TRIGGER stupid_trigger4 ON trigger_test; +-- TD not a dictionary +CREATE FUNCTION stupid5() RETURNS trigger +AS $$ + TD["new"] = ['foo', 'bar'] + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger5 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid5(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] is not a dictionary +CONTEXT: while modifying trigger row +PL/Python function "stupid5" +DROP TRIGGER stupid_trigger5 ON trigger_test; +-- TD not having string keys +CREATE FUNCTION stupid6() RETURNS trigger +AS $$ + TD["new"] = {1: 'foo', 2: 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger6 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid6(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: TD["new"] dictionary key at ordinal position 0 is not a string +CONTEXT: while modifying trigger row +PL/Python function "stupid6" +DROP TRIGGER stupid_trigger6 ON trigger_test; +-- TD keys not corresponding to row columns +CREATE FUNCTION stupid7() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row +CONTEXT: while modifying trigger row +PL/Python function "stupid7" +DROP TRIGGER stupid_trigger7 ON trigger_test; +-- Unicode variant +CREATE FUNCTION stupid7u() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7u(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +ERROR: key "a" found in TD["new"] does not exist as a column in the triggering row +CONTEXT: while modifying trigger row +PL/Python function "stupid7u" +DROP TRIGGER stupid_trigger7 ON trigger_test; +-- calling a trigger function directly +SELECT stupid7(); +ERROR: trigger functions can only be called as triggers +-- +-- Null values +-- +SELECT * FROM trigger_test; + i | v +---+------ + 0 | zero +(1 row) + +CREATE FUNCTION test_null() RETURNS trigger +AS $$ + TD["new"]['v'] = None + return "MODIFY" +$$ LANGUAGE plpython3u; +CREATE TRIGGER test_null_trigger +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE test_null(); +UPDATE trigger_test SET v = 'null' WHERE i = 0; +DROP TRIGGER test_null_trigger ON trigger_test; +SELECT * FROM trigger_test; + i | v +---+--- + 0 | +(1 row) + +-- +-- Test that triggers honor typmod when assigning to tuple fields, +-- as per an early 9.0 bug report +-- +SET DateStyle = 'ISO'; +CREATE FUNCTION set_modif_time() RETURNS trigger AS $$ + TD['new']['modif_time'] = '2010-10-13 21:57:28.930486' + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE); +CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb + FOR EACH ROW EXECUTE PROCEDURE set_modif_time(); +INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486'); +SELECT * FROM pb; + a | modif_time +---+--------------------- + a | 2010-10-09 21:57:34 +(1 row) + +UPDATE pb SET a = 'b'; +SELECT * FROM pb; + a | modif_time +---+--------------------- + b | 2010-10-13 21:57:29 +(1 row) + +-- triggers for tables with composite types +CREATE TABLE comp1 (i integer, j boolean); +CREATE TYPE comp2 AS (k integer, l boolean); +CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2); +CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$ + TD['new']['f1'] = (3, False) + TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10} + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f(); +INSERT INTO composite_trigger_test VALUES (NULL, NULL); +SELECT * FROM composite_trigger_test; + f1 | f2 +-------+------- + (3,f) | (7,t) +(1 row) + +-- triggers with composite type columns (bug #6559) +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + f1 | f2 +-------+------- + | + (1,f) | + (,t) | (1,f) +(3 rows) + +-- nested composite types +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); +CREATE TABLE composite_trigger_nested_test(c comp3); +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test; + c +------------------- + + ("(1,f)",,3) + ("(,t)","(1,f)",) +(3 rows) + +-- check that using a function as a trigger over two tables works correctly +CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$ + TD["new"]["data"] = '1234' + return 'MODIFY' +$$; +CREATE TABLE a(data text); +CREATE TABLE b(data int); -- different type conversion +CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234(); +CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234(); +INSERT INTO a DEFAULT VALUES; +SELECT * FROM a; + data +------ + 1234 +(1 row) + +DROP TABLE a; +INSERT INTO b DEFAULT VALUES; +SELECT * FROM b; + data +------ + 1234 +(1 row) + +-- check that SQL run in trigger code can see transition tables +CREATE TABLE transition_table_test (id int, name text); +INSERT INTO transition_table_test VALUES (1, 'a'); +CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpython3u AS +$$ + rv = plpy.execute("SELECT * FROM old_table") + assert(rv.nrows() == 1) + plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"]) + rv = plpy.execute("SELECT * FROM new_table") + assert(rv.nrows() == 1) + plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"]) + return None +$$; +CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test + REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f(); +UPDATE transition_table_test SET name = 'b'; +INFO: old: 1 -> a +INFO: new: 1 -> b +DROP TABLE transition_table_test; +DROP FUNCTION transition_table_test_f(); +-- dealing with generated columns +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpython3u +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +ERROR: cannot set generated column "j" +CONTEXT: while modifying trigger row +PL/Python function "generated_test_func1" +SELECT * FROM trigger_test_generated; + i | j +---+--- +(0 rows) + diff --git a/src/pl/plpython/expected/plpython_types.out b/src/pl/plpython/expected/plpython_types.out new file mode 100644 index 0000000..8a680e1 --- /dev/null +++ b/src/pl/plpython/expected/plpython_types.out @@ -0,0 +1,1069 @@ +-- +-- Test data type behavior +-- +-- +-- Base/common types +-- +CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bool(true); +INFO: (True, <class 'bool'>) + test_type_conversion_bool +--------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool(false); +INFO: (False, <class 'bool'>) + test_type_conversion_bool +--------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_bool +--------------------------- + +(1 row) + +-- test various other ways to express Booleans in Python +CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$ +# numbers +if n == 0: + ret = 0 +elif n == 1: + ret = 5 +# strings +elif n == 2: + ret = '' +elif n == 3: + ret = 'fa' # true in Python, false in PostgreSQL +# containers +elif n == 4: + ret = [] +elif n == 5: + ret = [0] +plpy.info(ret, not not ret) +return ret +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bool_other(0); +INFO: (0, False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(1); +INFO: (5, True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(2); +INFO: ('', False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(3); +INFO: ('fa', True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(4); +INFO: ([], False) + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(5); +INFO: ([0], True) + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_char('a'); +INFO: ('a', <class 'str'>) + test_type_conversion_char +--------------------------- + a +(1 row) + +SELECT * FROM test_type_conversion_char(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_char +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int2(100::int2); +INFO: (100, <class 'int'>) + test_type_conversion_int2 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int2(-100::int2); +INFO: (-100, <class 'int'>) + test_type_conversion_int2 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int2(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_int2 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int4(100); +INFO: (100, <class 'int'>) + test_type_conversion_int4 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int4(-100); +INFO: (-100, <class 'int'>) + test_type_conversion_int4 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int4(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_int4 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_int8(100); +INFO: (100, <class 'int'>) + test_type_conversion_int8 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int8(-100); +INFO: (-100, <class 'int'>) + test_type_conversion_int8 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int8(5000000000); +INFO: (5000000000, <class 'int'>) + test_type_conversion_int8 +--------------------------- + 5000000000 +(1 row) + +SELECT * FROM test_type_conversion_int8(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_int8 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ +# print just the class name, not the type, to avoid differences +# between decimal and cdecimal +plpy.info(str(x), x.__class__.__name__) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_numeric(100); +INFO: ('100', 'Decimal') + test_type_conversion_numeric +------------------------------ + 100 +(1 row) + +SELECT * FROM test_type_conversion_numeric(-100); +INFO: ('-100', 'Decimal') + test_type_conversion_numeric +------------------------------ + -100 +(1 row) + +SELECT * FROM test_type_conversion_numeric(100.0); +INFO: ('100.0', 'Decimal') + test_type_conversion_numeric +------------------------------ + 100.0 +(1 row) + +SELECT * FROM test_type_conversion_numeric(100.00); +INFO: ('100.00', 'Decimal') + test_type_conversion_numeric +------------------------------ + 100.00 +(1 row) + +SELECT * FROM test_type_conversion_numeric(5000000000.5); +INFO: ('5000000000.5', 'Decimal') + test_type_conversion_numeric +------------------------------ + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_numeric(1234567890.0987654321); +INFO: ('1234567890.0987654321', 'Decimal') + test_type_conversion_numeric +------------------------------ + 1234567890.0987654321 +(1 row) + +SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321); +INFO: ('-1234567890.0987654321', 'Decimal') + test_type_conversion_numeric +------------------------------ + -1234567890.0987654321 +(1 row) + +SELECT * FROM test_type_conversion_numeric(null); +INFO: ('None', 'NoneType') + test_type_conversion_numeric +------------------------------ + +(1 row) + +CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_float4(100); +INFO: (100.0, <class 'float'>) + test_type_conversion_float4 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float4(-100); +INFO: (-100.0, <class 'float'>) + test_type_conversion_float4 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float4(5000.5); +INFO: (5000.5, <class 'float'>) + test_type_conversion_float4 +----------------------------- + 5000.5 +(1 row) + +SELECT * FROM test_type_conversion_float4(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_float4 +----------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_float8(100); +INFO: (100.0, <class 'float'>) + test_type_conversion_float8 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float8(-100); +INFO: (-100.0, <class 'float'>) + test_type_conversion_float8 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float8(5000000000.5); +INFO: (5000000000.5, <class 'float'>) + test_type_conversion_float8 +----------------------------- + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_float8(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_float8 +----------------------------- + +(1 row) + +SELECT * FROM test_type_conversion_float8(100100100.654321); +INFO: (100100100.654321, <class 'float'>) + test_type_conversion_float8 +----------------------------- + 100100100.654321 +(1 row) + +CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_oid(100); +INFO: (100, <class 'int'>) + test_type_conversion_oid +-------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_oid(2147483649); +INFO: (2147483649, <class 'int'>) + test_type_conversion_oid +-------------------------- + 2147483649 +(1 row) + +SELECT * FROM test_type_conversion_oid(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_oid +-------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_text('hello world'); +INFO: ('hello world', <class 'str'>) + test_type_conversion_text +--------------------------- + hello world +(1 row) + +SELECT * FROM test_type_conversion_text(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_text +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bytea('hello world'); +INFO: (b'hello world', <class 'bytes'>) + test_type_conversion_bytea +---------------------------- + \x68656c6c6f20776f726c64 +(1 row) + +SELECT * FROM test_type_conversion_bytea(E'null\\000byte'); +INFO: (b'null\x00byte', <class 'bytes'>) + test_type_conversion_bytea +---------------------------- + \x6e756c6c0062797465 +(1 row) + +SELECT * FROM test_type_conversion_bytea(null); +INFO: (None, <class 'NoneType'>) + test_type_conversion_bytea +---------------------------- + +(1 row) + +CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$ +import marshal +return marshal.dumps('hello world') +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$ +import marshal +try: + return marshal.loads(x) +except ValueError as e: + return 'FAILED: ' + str(e) +$$ LANGUAGE plpython3u; +SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + test_type_unmarshal +--------------------- + hello world +(1 row) + +-- +-- Domains +-- +CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL); +CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$ +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_booltrue(true, true); + test_type_conversion_booltrue +------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_booltrue(false, true); +ERROR: value for domain booltrue violates check constraint "booltrue_check" +SELECT * FROM test_type_conversion_booltrue(true, false); +ERROR: value for domain booltrue violates check constraint "booltrue_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_booltrue" +CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); +CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_uint2(100::uint2, 50); +INFO: (100, <class 'int'>) + test_type_conversion_uint2 +---------------------------- + 50 +(1 row) + +SELECT * FROM test_type_conversion_uint2(100::uint2, -50); +INFO: (100, <class 'int'>) +ERROR: value for domain uint2 violates check constraint "uint2_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_uint2" +SELECT * FROM test_type_conversion_uint2(null, 1); +INFO: (None, <class 'NoneType'>) + test_type_conversion_uint2 +---------------------------- + 1 +(1 row) + +CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL); +CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$ +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_nnint(10, 20); + test_type_conversion_nnint +---------------------------- + 20 +(1 row) + +SELECT * FROM test_type_conversion_nnint(null, 20); +ERROR: value for domain nnint violates check constraint "nnint_check" +SELECT * FROM test_type_conversion_nnint(10, null); +ERROR: value for domain nnint violates check constraint "nnint_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_nnint" +CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); +CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); +INFO: (b'hello wold', <class 'bytes'>) + test_type_conversion_bytea10 +------------------------------ + \x68656c6c6f20776f6c64 +(1 row) + +SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold'); +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world'); +INFO: (b'hello word', <class 'bytes'>) +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_bytea10" +SELECT * FROM test_type_conversion_bytea10(null, 'hello word'); +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +SELECT * FROM test_type_conversion_bytea10('hello word', null); +INFO: (b'hello word', <class 'bytes'>) +ERROR: value for domain bytea10 violates check constraint "bytea10_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_bytea10" +-- +-- Arrays +-- +CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); +INFO: ([0, 100], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {0,100} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]); +INFO: ([0, -100, 55], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {0,-100,55} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]); +INFO: ([None, 1], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {NULL,1} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]); +INFO: ([], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(NULL); +INFO: (None, <class 'NoneType'>) + test_type_conversion_array_int4 +--------------------------------- + +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]); +INFO: ([[1, 2, 3], [4, 5, 6]], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {{1,2,3},{4,5,6}} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]); +INFO: ([[[1, 2, None], [None, 5, 6]], [[None, 8, 9], [10, 11, 12]]], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------------------------- + {{{1,2,NULL},{NULL,5,6}},{{NULL,8,9},{10,11,12}}} +(1 row) + +SELECT * FROM test_type_conversion_array_int4('[2:4]={1,2,3}'); +INFO: ([1, 2, 3], <class 'list'>) + test_type_conversion_array_int4 +--------------------------------- + {1,2,3} +(1 row) + +CREATE FUNCTION test_type_conversion_array_int8(x int8[]) RETURNS int8[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_int8(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]::int8[]); +INFO: ([[[1, 2, None], [None, 5, 6]], [[None, 8, 9], [10, 11, 12]]], <class 'list'>) + test_type_conversion_array_int8 +--------------------------------------------------- + {{{1,2,NULL},{NULL,5,6}},{{NULL,8,9},{10,11,12}}} +(1 row) + +CREATE FUNCTION test_type_conversion_array_date(x date[]) RETURNS date[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_date(ARRAY[[['2016-09-21','2016-09-22',NULL],[NULL,'2016-10-21','2016-10-22']], + [[NULL,'2016-11-21','2016-10-21'],['2015-09-21','2015-09-22','2014-09-21']]]::date[]); +INFO: ([[['09-21-2016', '09-22-2016', None], [None, '10-21-2016', '10-22-2016']], [[None, '11-21-2016', '10-21-2016'], ['09-21-2015', '09-22-2015', '09-21-2014']]], <class 'list'>) + test_type_conversion_array_date +--------------------------------------------------------------------------------------------------------------------------------- + {{{09-21-2016,09-22-2016,NULL},{NULL,10-21-2016,10-22-2016}},{{NULL,11-21-2016,10-21-2016},{09-21-2015,09-22-2015,09-21-2014}}} +(1 row) + +CREATE FUNCTION test_type_conversion_array_timestamp(x timestamp[]) RETURNS timestamp[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_timestamp(ARRAY[[['2016-09-21 15:34:24.078792-04','2016-10-22 11:34:24.078795-04',NULL], + [NULL,'2016-10-21 11:34:25.078792-04','2016-10-21 11:34:24.098792-04']], + [[NULL,'2016-01-21 11:34:24.078792-04','2016-11-21 11:34:24.108792-04'], + ['2015-09-21 11:34:24.079792-04','2014-09-21 11:34:24.078792-04','2013-09-21 11:34:24.078792-04']]]::timestamp[]); +INFO: ([[['Wed Sep 21 15:34:24.078792 2016', 'Sat Oct 22 11:34:24.078795 2016', None], [None, 'Fri Oct 21 11:34:25.078792 2016', 'Fri Oct 21 11:34:24.098792 2016']], [[None, 'Thu Jan 21 11:34:24.078792 2016', 'Mon Nov 21 11:34:24.108792 2016'], ['Mon Sep 21 11:34:24.079792 2015', 'Sun Sep 21 11:34:24.078792 2014', 'Sat Sep 21 11:34:24.078792 2013']]], <class 'list'>) + test_type_conversion_array_timestamp +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + {{{"Wed Sep 21 15:34:24.078792 2016","Sat Oct 22 11:34:24.078795 2016",NULL},{NULL,"Fri Oct 21 11:34:25.078792 2016","Fri Oct 21 11:34:24.098792 2016"}},{{NULL,"Thu Jan 21 11:34:24.078792 2016","Mon Nov 21 11:34:24.108792 2016"},{"Mon Sep 21 11:34:24.079792 2015","Sun Sep 21 11:34:24.078792 2014","Sat Sep 21 11:34:24.078792 2013"}}} +(1 row) + +CREATE OR REPLACE FUNCTION pyreturnmultidemint4(h int4, i int4, j int4, k int4 ) RETURNS int4[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; +select pyreturnmultidemint4(8,5,3,2); +INFO: ([[[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]]], [[[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]], [[0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7], [0, 1, 2, 3, 4, 5, 6, 7]]]], <class 'list'>) + pyreturnmultidemint4 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {{{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}}},{{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}},{{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7},{0,1,2,3,4,5,6,7}}}} +(1 row) + +CREATE OR REPLACE FUNCTION pyreturnmultidemint8(h int4, i int4, j int4, k int4 ) RETURNS int8[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; +select pyreturnmultidemint8(5,5,3,2); +INFO: ([[[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]], [[[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]], [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]]], <class 'list'>) + pyreturnmultidemint8 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {{{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}}},{{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}},{{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4},{0,1,2,3,4}}}} +(1 row) + +CREATE OR REPLACE FUNCTION pyreturnmultidemfloat4(h int4, i int4, j int4, k int4 ) RETURNS float4[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; +select pyreturnmultidemfloat4(6,5,3,2); +INFO: ([[[[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]]], [[[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]], [[0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5], [0, 1, 2, 3, 4, 5]]]], <class 'list'>) + pyreturnmultidemfloat4 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {{{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}}},{{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}},{{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5},{0,1,2,3,4,5}}}} +(1 row) + +CREATE OR REPLACE FUNCTION pyreturnmultidemfloat8(h int4, i int4, j int4, k int4 ) RETURNS float8[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; +select pyreturnmultidemfloat8(7,5,3,2); +INFO: ([[[[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]]], [[[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]], [[0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6], [0, 1, 2, 3, 4, 5, 6]]]], <class 'list'>) + pyreturnmultidemfloat8 +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + {{{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}}},{{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}},{{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6},{0,1,2,3,4,5,6}}}} +(1 row) + +CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); +INFO: (['foo', 'bar'], <class 'list'>) + test_type_conversion_array_text +--------------------------------- + {foo,bar} +(1 row) + +SELECT * FROM test_type_conversion_array_text(ARRAY[['foo', 'bar'],['foo2', 'bar2']]); +INFO: ([['foo', 'bar'], ['foo2', 'bar2']], <class 'list'>) + test_type_conversion_array_text +--------------------------------- + {{foo,bar},{foo2,bar2}} +(1 row) + +CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); +INFO: ([b'\xde\xad\xbe\xef', None], <class 'list'>) + test_type_conversion_array_bytea +---------------------------------- + {"\\xdeadbeef",NULL} +(1 row) + +CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_mixed1(); + test_type_conversion_array_mixed1 +----------------------------------- + {123,abc} +(1 row) + +CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_mixed2(); +ERROR: invalid input syntax for type integer: "abc" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_array_mixed2" +-- check output of multi-dimensional arrays +CREATE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [['a'], ['b'], ['c']] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); + test_type_conversion_md_array_out +----------------------------------- + {{a},{b},{c}} +(1 row) + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], []] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); + test_type_conversion_md_array_out +----------------------------------- + {} +(1 row) + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], [1]] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); -- fail +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_md_array_out" +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], 1] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); -- fail +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_md_array_out" +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [1, []] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); -- fail +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_md_array_out" +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[1], [[]]] +$$ LANGUAGE plpython3u; +select test_type_conversion_md_array_out(); -- fail +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_md_array_out" +CREATE FUNCTION test_type_conversion_mdarray_malformed() RETURNS int[] AS $$ +return [[1,2,3],[4,5]] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_mdarray_malformed(); +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_mdarray_malformed" +CREATE FUNCTION test_type_conversion_mdarray_malformed2() RETURNS text[] AS $$ +return [[1,2,3], "abc"] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_mdarray_malformed2(); +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_mdarray_malformed2" +CREATE FUNCTION test_type_conversion_mdarray_malformed3() RETURNS text[] AS $$ +return ["abc", [1,2,3]] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_mdarray_malformed3(); +ERROR: multidimensional arrays must have array expressions with matching dimensions +CONTEXT: while creating return value +PL/Python function "test_type_conversion_mdarray_malformed3" +CREATE FUNCTION test_type_conversion_mdarray_toodeep() RETURNS int[] AS $$ +return [[[[[[[1]]]]]]] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_mdarray_toodeep(); +ERROR: number of array dimensions exceeds the maximum allowed (6) +CONTEXT: while creating return value +PL/Python function "test_type_conversion_mdarray_toodeep" +CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ +return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_record(); + test_type_conversion_array_record +----------------------------------- + {"(one,42)","(two,11)"} +(1 row) + +CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ +return 'abc' +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_string(); + test_type_conversion_array_string +----------------------------------- + {a,b,c} +(1 row) + +CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$ +return ('abc', 'def') +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_tuple(); + test_type_conversion_array_tuple +---------------------------------- + {abc,def} +(1 row) + +CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$ +return 5 +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_error(); +ERROR: return value of function with array return type is not a Python sequence +CONTEXT: while creating return value +PL/Python function "test_type_conversion_array_error" +-- +-- Domains over arrays +-- +CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); +CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); +INFO: ([0, 100], <class 'list'>) + test_type_conversion_array_domain +----------------------------------- + {0,100} +(1 row) + +SELECT * FROM test_type_conversion_array_domain(NULL::ordered_pair_domain); +INFO: (None, <class 'NoneType'>) + test_type_conversion_array_domain +----------------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_array_domain_check_violation() RETURNS ordered_pair_domain AS $$ +return [2,1] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_domain_check_violation(); +ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_array_domain_check_violation" +-- +-- Arrays of domains +-- +CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return x[0] +$$ LANGUAGE plpython3u; +select test_read_uint2_array(array[1::uint2]); +INFO: ([1], <class 'list'>) + test_read_uint2_array +----------------------- + 1 +(1 row) + +CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$ +return [x, x] +$$ LANGUAGE plpython3u; +select test_build_uint2_array(1::int2); + test_build_uint2_array +------------------------ + {1,1} +(1 row) + +select test_build_uint2_array(-1::int2); -- fail +ERROR: value for domain uint2 violates check constraint "uint2_check" +CONTEXT: while creating return value +PL/Python function "test_build_uint2_array" +-- +-- ideally this would work, but for now it doesn't, because the return value +-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D +-- integer array, not an array of arrays. +-- +CREATE FUNCTION test_type_conversion_domain_array(x integer[]) + RETURNS ordered_pair_domain[] AS $$ +return [x, x] +$$ LANGUAGE plpython3u; +select test_type_conversion_domain_array(array[2,4]); +ERROR: return value of function with array return type is not a Python sequence +CONTEXT: while creating return value +PL/Python function "test_type_conversion_domain_array" +select test_type_conversion_domain_array(array[4,2]); -- fail +ERROR: return value of function with array return type is not a Python sequence +CONTEXT: while creating return value +PL/Python function "test_type_conversion_domain_array" +CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain) + RETURNS integer AS $$ +plpy.info(x, type(x)) +return x[1] +$$ LANGUAGE plpython3u; +select test_type_conversion_domain_array2(array[2,4]); +INFO: ([2, 4], <class 'list'>) + test_type_conversion_domain_array2 +------------------------------------ + 4 +(1 row) + +select test_type_conversion_domain_array2(array[4,2]); -- fail +ERROR: value for domain ordered_pair_domain violates check constraint "ordered_pair_domain_check" +CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[]) + RETURNS ordered_pair_domain AS $$ +plpy.info(x, type(x)) +return x[0] +$$ LANGUAGE plpython3u; +select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]); +INFO: ([[2, 4]], <class 'list'>) + test_type_conversion_array_domain_array +----------------------------------------- + {2,4} +(1 row) + +--- +--- Composite types +--- +CREATE TABLE employee ( + name text, + basesalary integer, + bonus integer +); +INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10); +CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$ +return e['basesalary'] + e['bonus'] +$$ LANGUAGE plpython3u; +SELECT name, test_composite_table_input(employee.*) FROM employee; + name | test_composite_table_input +------+---------------------------- + John | 110 + Mary | 210 +(2 rows) + +ALTER TABLE employee DROP bonus; +SELECT name, test_composite_table_input(employee.*) FROM employee; +ERROR: KeyError: 'bonus' +CONTEXT: Traceback (most recent call last): + PL/Python function "test_composite_table_input", line 2, in <module> + return e['basesalary'] + e['bonus'] +PL/Python function "test_composite_table_input" +ALTER TABLE employee ADD bonus integer; +UPDATE employee SET bonus = 10; +SELECT name, test_composite_table_input(employee.*) FROM employee; + name | test_composite_table_input +------+---------------------------- + John | 110 + Mary | 210 +(2 rows) + +CREATE TYPE named_pair AS ( + i integer, + j integer +); +CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$ +return sum(p.values()) +$$ LANGUAGE plpython3u; +SELECT test_composite_type_input(row(1, 2)); + test_composite_type_input +--------------------------- + 3 +(1 row) + +ALTER TYPE named_pair RENAME TO named_pair_2; +SELECT test_composite_type_input(row(1, 2)); + test_composite_type_input +--------------------------- + 3 +(1 row) + +-- +-- Domains within composite +-- +CREATE TYPE nnint_container AS (f1 int, f2 nnint); +CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$ +return {'f1': x, 'f2': y} +$$ LANGUAGE plpython3u; +SELECT nnint_test(null, 3); + nnint_test +------------ + (,3) +(1 row) + +SELECT nnint_test(3, null); -- fail +ERROR: value for domain nnint violates check constraint "nnint_check" +CONTEXT: while creating return value +PL/Python function "nnint_test" +-- +-- Domains of composite +-- +CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j); +CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$ +return p['i'] + p['j'] +$$ LANGUAGE plpython3u; +SELECT read_ordered_named_pair(row(1, 2)); + read_ordered_named_pair +------------------------- + 3 +(1 row) + +SELECT read_ordered_named_pair(row(2, 1)); -- fail +ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check" +CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$ +return {'i': i, 'j': j} +$$ LANGUAGE plpython3u; +SELECT build_ordered_named_pair(1,2); + build_ordered_named_pair +-------------------------- + (1,2) +(1 row) + +SELECT build_ordered_named_pair(2,1); -- fail +ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check" +CONTEXT: while creating return value +PL/Python function "build_ordered_named_pair" +CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$ +return [{'i': i, 'j': j}, {'i': i, 'j': j+1}] +$$ LANGUAGE plpython3u; +SELECT build_ordered_named_pairs(1,2); + build_ordered_named_pairs +--------------------------- + {"(1,2)","(1,3)"} +(1 row) + +SELECT build_ordered_named_pairs(2,1); -- fail +ERROR: value for domain ordered_named_pair violates check constraint "ordered_named_pair_check" +CONTEXT: while creating return value +PL/Python function "build_ordered_named_pairs" +-- +-- Prepared statements +-- +CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT CASE WHEN $1 THEN 1 ELSE 0 END AS val", ['boolean']) +rv = plpy.execute(plan, ['fa'], 5) # 'fa' is true in Python +return rv[0]['val'] +$$; +SELECT test_prep_bool_input(); -- 1 + test_prep_bool_input +---------------------- + 1 +(1 row) + +CREATE OR REPLACE FUNCTION test_prep_bool_output() RETURNS bool +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT $1 = 1 AS val", ['int']) +rv = plpy.execute(plan, [0], 5) +plpy.info(rv[0]) +return rv[0]['val'] +$$; +SELECT test_prep_bool_output(); -- false +INFO: {'val': False} + test_prep_bool_output +----------------------- + f +(1 row) + +CREATE OR REPLACE FUNCTION test_prep_bytea_input(bb bytea) RETURNS int +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT octet_length($1) AS val", ['bytea']) +rv = plpy.execute(plan, [bb], 5) +return rv[0]['val'] +$$; +SELECT test_prep_bytea_input(E'a\\000b'); -- 3 (embedded null formerly truncated value) + test_prep_bytea_input +----------------------- + 3 +(1 row) + +CREATE OR REPLACE FUNCTION test_prep_bytea_output() RETURNS bytea +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT decode('aa00bb', 'hex') AS val") +rv = plpy.execute(plan, [], 5) +plpy.info(rv[0]) +return rv[0]['val'] +$$; +SELECT test_prep_bytea_output(); +INFO: {'val': b'\xaa\x00\xbb'} + test_prep_bytea_output +------------------------ + \xaa00bb +(1 row) + diff --git a/src/pl/plpython/expected/plpython_unicode.out b/src/pl/plpython/expected/plpython_unicode.out new file mode 100644 index 0000000..fd54b0b --- /dev/null +++ b/src/pl/plpython/expected/plpython_unicode.out @@ -0,0 +1,56 @@ +-- +-- Unicode handling +-- +-- Note: this test case is known to fail if the database encoding is +-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to +-- U+00A0 (no-break space) in those encodings. However, testing with +-- plain ASCII data would be rather useless, so we must live with that. +-- +SET client_encoding TO UTF8; +CREATE TABLE unicode_test ( + testvalue text NOT NULL +); +CREATE FUNCTION unicode_return() RETURNS text AS E' +return "\\xA0" +' LANGUAGE plpython3u; +CREATE FUNCTION unicode_trigger() RETURNS trigger AS E' +TD["new"]["testvalue"] = "\\xA0" +return "MODIFY" +' LANGUAGE plpython3u; +CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test + FOR EACH ROW EXECUTE PROCEDURE unicode_trigger(); +CREATE FUNCTION unicode_plan1() RETURNS text AS E' +plan = plpy.prepare("SELECT $1 AS testvalue", ["text"]) +rv = plpy.execute(plan, ["\\xA0"], 1) +return rv[0]["testvalue"] +' LANGUAGE plpython3u; +CREATE FUNCTION unicode_plan2() RETURNS text AS E' +plan = plpy.prepare("SELECT $1 || $2 AS testvalue", ["text", "text"]) +rv = plpy.execute(plan, ["foo", "bar"], 1) +return rv[0]["testvalue"] +' LANGUAGE plpython3u; +SELECT unicode_return(); + unicode_return +---------------- + +(1 row) + +INSERT INTO unicode_test (testvalue) VALUES ('test'); +SELECT * FROM unicode_test; + testvalue +----------- + +(1 row) + +SELECT unicode_plan1(); + unicode_plan1 +--------------- + +(1 row) + +SELECT unicode_plan2(); + unicode_plan2 +--------------- + foobar +(1 row) + diff --git a/src/pl/plpython/expected/plpython_void.out b/src/pl/plpython/expected/plpython_void.out new file mode 100644 index 0000000..07d0760 --- /dev/null +++ b/src/pl/plpython/expected/plpython_void.out @@ -0,0 +1,30 @@ +-- +-- Tests for functions that return void +-- +CREATE FUNCTION test_void_func1() RETURNS void AS $$ +x = 10 +$$ LANGUAGE plpython3u; +-- illegal: can't return non-None value in void-returning func +CREATE FUNCTION test_void_func2() RETURNS void AS $$ +return 10 +$$ LANGUAGE plpython3u; +CREATE FUNCTION test_return_none() RETURNS int AS $$ +None +$$ LANGUAGE plpython3u; +-- Tests for functions returning void +SELECT test_void_func1(), test_void_func1() IS NULL AS "is null"; + test_void_func1 | is null +-----------------+--------- + | f +(1 row) + +SELECT test_void_func2(); -- should fail +ERROR: PL/Python function with return type "void" did not return None +CONTEXT: while creating return value +PL/Python function "test_void_func2" +SELECT test_return_none(), test_return_none() IS NULL AS "is null"; + test_return_none | is null +------------------+--------- + | t +(1 row) + diff --git a/src/pl/plpython/generate-spiexceptions.pl b/src/pl/plpython/generate-spiexceptions.pl new file mode 100644 index 0000000..61b37c3 --- /dev/null +++ b/src/pl/plpython/generate-spiexceptions.pl @@ -0,0 +1,44 @@ +#!/usr/bin/perl +# +# Generate the spiexceptions.h header from errcodes.txt +# Copyright (c) 2000-2023, PostgreSQL Global Development Group + +use strict; +use warnings; + +print + "/* autogenerated from src/backend/utils/errcodes.txt, do not edit */\n"; +print "/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */\n"; + +open my $errcodes, '<', $ARGV[0] or die; + +while (<$errcodes>) +{ + chomp; + + # Skip comments + next if /^#/; + next if /^\s*$/; + + # Skip section headers + next if /^Section:/; + + die unless /^([^\s]{5})\s+([EWS])\s+([^\s]+)(?:\s+)?([^\s]+)?/; + + (my $sqlstate, my $type, my $errcode_macro, my $condition_name) = + ($1, $2, $3, $4); + + # Skip non-errors + next unless $type eq 'E'; + + # Skip lines without PL/pgSQL condition names + next unless defined($condition_name); + + # Change some_error_condition to SomeErrorCondition + $condition_name =~ s/([a-z])([^_]*)(?:_|$)/\u$1$2/g; + + print "\n{\n\t\"spiexceptions.$condition_name\", " + . "\"$condition_name\", $errcode_macro\n},\n"; +} + +close $errcodes; diff --git a/src/pl/plpython/meson.build b/src/pl/plpython/meson.build new file mode 100644 index 0000000..98b7d7c --- /dev/null +++ b/src/pl/plpython/meson.build @@ -0,0 +1,109 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +if not python3_dep.found() + subdir_done() +endif + +plpython_sources = files( + 'plpy_cursorobject.c', + 'plpy_elog.c', + 'plpy_exec.c', + 'plpy_main.c', + 'plpy_planobject.c', + 'plpy_plpymodule.c', + 'plpy_procedure.c', + 'plpy_resultobject.c', + 'plpy_spi.c', + 'plpy_subxactobject.c', + 'plpy_typeio.c', + 'plpy_util.c', +) + +plpython_sources += custom_target('spiexceptions.h', + input: files('../../backend/utils/errcodes.txt'), + output: 'spiexceptions.h', + command: [perl, files('generate-spiexceptions.pl'), '@INPUT@'], + capture: true, +) + + +# FIXME: need to duplicate import library ugliness? +plpython_inc = include_directories('.') + +if host_system == 'windows' + plpython_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'plpython3', + '--FILEDESC', 'PL/Python - procedural language',]) +endif + +plpython = shared_module('plpython3', + plpython_sources, + c_pch: pch_postgres_h, + include_directories: [plpython_inc, postgres_inc], + kwargs: pg_mod_args + { + 'dependencies': [python3_dep, pg_mod_args['dependencies']], + }, +) +pl_targets += plpython + +# FIXME: Only install the relevant versions +install_data( + 'plpython3u.control', + 'plpython3u--1.0.sql', + install_dir: dir_data_extension, +) + +install_headers( + 'plpy_cursorobject.h', + 'plpy_elog.h', + 'plpy_exec.h', + 'plpy_main.h', + 'plpy_planobject.h', + 'plpy_plpymodule.h', + 'plpy_procedure.h', + 'plpy_resultobject.h', + 'plpy_spi.h', + 'plpy_subxactobject.h', + 'plpy_typeio.h', + 'plpy_util.h', + 'plpython.h', + 'plpython_system.h', + install_dir: dir_include_server, +) + +plpython_regress = [ + 'plpython_schema', + 'plpython_populate', + 'plpython_test', + 'plpython_do', + 'plpython_global', + 'plpython_import', + 'plpython_spi', + 'plpython_newline', + 'plpython_void', + 'plpython_call', + 'plpython_params', + 'plpython_setof', + 'plpython_record', + 'plpython_trigger', + 'plpython_types', + 'plpython_error', + 'plpython_ereport', + 'plpython_unicode', + 'plpython_quote', + 'plpython_composite', + 'plpython_subtransaction', + 'plpython_transaction', + 'plpython_drop', +] + +tests += { + 'name': 'plpython', + 'sd': meson.current_source_dir(), + 'bd': meson.current_build_dir(), + 'regress': { + 'sql': plpython_regress, + }, +} + +subdir('po', if_found: libintl) diff --git a/src/pl/plpython/nls.mk b/src/pl/plpython/nls.mk new file mode 100644 index 0000000..e7a25ca --- /dev/null +++ b/src/pl/plpython/nls.mk @@ -0,0 +1,20 @@ +# src/pl/plpython/nls.mk +CATALOG_NAME = plpython +GETTEXT_FILES = plpy_cursorobject.c \ + plpy_elog.c \ + plpy_exec.c \ + plpy_main.c \ + plpy_planobject.c \ + plpy_plpymodule.c \ + plpy_procedure.c \ + plpy_resultobject.c \ + plpy_spi.c \ + plpy_subxactobject.c \ + plpy_typeio.c \ + plpy_util.c +GETTEXT_TRIGGERS = $(BACKEND_COMMON_GETTEXT_TRIGGERS) PLy_elog:2 PLy_exception_set:2 PLy_exception_set_plural:2,3 +GETTEXT_FLAGS = $(BACKEND_COMMON_GETTEXT_FLAGS) \ + PLy_elog:2:c-format \ + PLy_exception_set:2:c-format \ + PLy_exception_set_plural:2:c-format \ + PLy_exception_set_plural:3:c-format diff --git a/src/pl/plpython/plpy_cursorobject.c b/src/pl/plpython/plpy_cursorobject.c new file mode 100644 index 0000000..57e8f8e --- /dev/null +++ b/src/pl/plpython/plpy_cursorobject.c @@ -0,0 +1,492 @@ +/* + * the PLyCursor class + * + * src/pl/plpython/plpy_cursorobject.c + */ + +#include "postgres.h" + +#include <limits.h> + +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "mb/pg_wchar.h" +#include "plpy_cursorobject.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_planobject.h" +#include "plpy_procedure.h" +#include "plpy_resultobject.h" +#include "plpy_spi.h" +#include "plpython.h" +#include "utils/memutils.h" + +static PyObject *PLy_cursor_query(const char *query); +static void PLy_cursor_dealloc(PyObject *arg); +static PyObject *PLy_cursor_iternext(PyObject *self); +static PyObject *PLy_cursor_fetch(PyObject *self, PyObject *args); +static PyObject *PLy_cursor_close(PyObject *self, PyObject *unused); + +static char PLy_cursor_doc[] = "Wrapper around a PostgreSQL cursor"; + +static PyMethodDef PLy_cursor_methods[] = { + {"fetch", PLy_cursor_fetch, METH_VARARGS, NULL}, + {"close", PLy_cursor_close, METH_NOARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_CursorType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "PLyCursor", + .tp_basicsize = sizeof(PLyCursorObject), + .tp_dealloc = PLy_cursor_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = PLy_cursor_doc, + .tp_iter = PyObject_SelfIter, + .tp_iternext = PLy_cursor_iternext, + .tp_methods = PLy_cursor_methods, +}; + +void +PLy_cursor_init_type(void) +{ + if (PyType_Ready(&PLy_CursorType) < 0) + elog(ERROR, "could not initialize PLy_CursorType"); +} + +PyObject * +PLy_cursor(PyObject *self, PyObject *args) +{ + char *query; + PyObject *plan; + PyObject *planargs = NULL; + + if (PyArg_ParseTuple(args, "s", &query)) + return PLy_cursor_query(query); + + PyErr_Clear(); + + if (PyArg_ParseTuple(args, "O|O", &plan, &planargs)) + return PLy_cursor_plan(plan, planargs); + + PLy_exception_set(PLy_exc_error, "plpy.cursor expected a query or a plan"); + return NULL; +} + + +static PyObject * +PLy_cursor_query(const char *query) +{ + PLyCursorObject *cursor; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + + if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) + return NULL; + cursor->portalname = NULL; + cursor->closed = false; + cursor->mcxt = AllocSetContextCreate(TopMemoryContext, + "PL/Python cursor context", + ALLOCSET_DEFAULT_SIZES); + + /* Initialize for converting result tuples to Python */ + PLy_input_setup_func(&cursor->result, cursor->mcxt, + RECORDOID, -1, + exec_ctx->curr_proc); + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPIPlanPtr plan; + Portal portal; + + pg_verifymbstr(query, strlen(query), false); + + plan = SPI_prepare(query, 0, NULL); + if (plan == NULL) + elog(ERROR, "SPI_prepare failed: %s", + SPI_result_code_string(SPI_result)); + + portal = SPI_cursor_open(NULL, plan, NULL, NULL, + exec_ctx->curr_proc->fn_readonly); + SPI_freeplan(plan); + + if (portal == NULL) + elog(ERROR, "SPI_cursor_open() failed: %s", + SPI_result_code_string(SPI_result)); + + cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); + + PinPortal(portal); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + Assert(cursor->portalname != NULL); + return (PyObject *) cursor; +} + +PyObject * +PLy_cursor_plan(PyObject *ob, PyObject *args) +{ + PLyCursorObject *cursor; + volatile int nargs; + int i; + PLyPlanObject *plan; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + + if (args) + { + if (!PySequence_Check(args) || PyUnicode_Check(args)) + { + PLy_exception_set(PyExc_TypeError, "plpy.cursor takes a sequence as its second argument"); + return NULL; + } + nargs = PySequence_Length(args); + } + else + nargs = 0; + + plan = (PLyPlanObject *) ob; + + if (nargs != plan->nargs) + { + char *sv; + PyObject *so = PyObject_Str(args); + + if (!so) + PLy_elog(ERROR, "could not execute plan"); + sv = PLyUnicode_AsString(so); + PLy_exception_set_plural(PyExc_TypeError, + "Expected sequence of %d argument, got %d: %s", + "Expected sequence of %d arguments, got %d: %s", + plan->nargs, + plan->nargs, nargs, sv); + Py_DECREF(so); + + return NULL; + } + + if ((cursor = PyObject_New(PLyCursorObject, &PLy_CursorType)) == NULL) + return NULL; + cursor->portalname = NULL; + cursor->closed = false; + cursor->mcxt = AllocSetContextCreate(TopMemoryContext, + "PL/Python cursor context", + ALLOCSET_DEFAULT_SIZES); + + /* Initialize for converting result tuples to Python */ + PLy_input_setup_func(&cursor->result, cursor->mcxt, + RECORDOID, -1, + exec_ctx->curr_proc); + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + Portal portal; + char *volatile nulls; + volatile int j; + + if (nargs > 0) + nulls = palloc(nargs * sizeof(char)); + else + nulls = NULL; + + for (j = 0; j < nargs; j++) + { + PLyObToDatum *arg = &plan->args[j]; + PyObject *elem; + + elem = PySequence_GetItem(args, j); + PG_TRY(2); + { + bool isnull; + + plan->values[j] = PLy_output_convert(arg, elem, &isnull); + nulls[j] = isnull ? 'n' : ' '; + } + PG_FINALLY(2); + { + Py_DECREF(elem); + } + PG_END_TRY(2); + } + + portal = SPI_cursor_open(NULL, plan->plan, plan->values, nulls, + exec_ctx->curr_proc->fn_readonly); + if (portal == NULL) + elog(ERROR, "SPI_cursor_open() failed: %s", + SPI_result_code_string(SPI_result)); + + cursor->portalname = MemoryContextStrdup(cursor->mcxt, portal->name); + + PinPortal(portal); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + int k; + + /* cleanup plan->values array */ + for (k = 0; k < nargs; k++) + { + if (!plan->args[k].typbyval && + (plan->values[k] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); + } + } + + Py_DECREF(cursor); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + for (i = 0; i < nargs; i++) + { + if (!plan->args[i].typbyval && + (plan->values[i] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[i])); + plan->values[i] = PointerGetDatum(NULL); + } + } + + Assert(cursor->portalname != NULL); + return (PyObject *) cursor; +} + +static void +PLy_cursor_dealloc(PyObject *arg) +{ + PLyCursorObject *cursor; + Portal portal; + + cursor = (PLyCursorObject *) arg; + + if (!cursor->closed) + { + portal = GetPortalByName(cursor->portalname); + + if (PortalIsValid(portal)) + { + UnpinPortal(portal); + SPI_cursor_close(portal); + } + cursor->closed = true; + } + if (cursor->mcxt) + { + MemoryContextDelete(cursor->mcxt); + cursor->mcxt = NULL; + } + arg->ob_type->tp_free(arg); +} + +static PyObject * +PLy_cursor_iternext(PyObject *self) +{ + PLyCursorObject *cursor; + PyObject *ret; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + Portal portal; + + cursor = (PLyCursorObject *) self; + + if (cursor->closed) + { + PLy_exception_set(PyExc_ValueError, "iterating a closed cursor"); + return NULL; + } + + portal = GetPortalByName(cursor->portalname); + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "iterating a cursor in an aborted subtransaction"); + return NULL; + } + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPI_cursor_fetch(portal, true, 1); + if (SPI_processed == 0) + { + PyErr_SetNone(PyExc_StopIteration); + ret = NULL; + } + else + { + PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc, + exec_ctx->curr_proc); + + ret = PLy_input_from_tuple(&cursor->result, SPI_tuptable->vals[0], + SPI_tuptable->tupdesc, true); + } + + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + return ret; +} + +static PyObject * +PLy_cursor_fetch(PyObject *self, PyObject *args) +{ + PLyCursorObject *cursor; + int count; + PLyResultObject *ret; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + Portal portal; + + if (!PyArg_ParseTuple(args, "i:fetch", &count)) + return NULL; + + cursor = (PLyCursorObject *) self; + + if (cursor->closed) + { + PLy_exception_set(PyExc_ValueError, "fetch from a closed cursor"); + return NULL; + } + + portal = GetPortalByName(cursor->portalname); + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "iterating a cursor in an aborted subtransaction"); + return NULL; + } + + ret = (PLyResultObject *) PLy_result_new(); + if (ret == NULL) + return NULL; + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + SPI_cursor_fetch(portal, true, count); + + Py_DECREF(ret->status); + ret->status = PyLong_FromLong(SPI_OK_FETCH); + + Py_DECREF(ret->nrows); + ret->nrows = PyLong_FromUnsignedLongLong(SPI_processed); + + if (SPI_processed != 0) + { + uint64 i; + + /* + * PyList_New() and PyList_SetItem() use Py_ssize_t for list size + * and list indices; so we cannot support a result larger than + * PY_SSIZE_T_MAX. + */ + if (SPI_processed > (uint64) PY_SSIZE_T_MAX) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("query result has too many rows to fit in a Python list"))); + + Py_DECREF(ret->rows); + ret->rows = PyList_New(SPI_processed); + if (!ret->rows) + { + Py_DECREF(ret); + ret = NULL; + } + else + { + PLy_input_setup_tuple(&cursor->result, SPI_tuptable->tupdesc, + exec_ctx->curr_proc); + + for (i = 0; i < SPI_processed; i++) + { + PyObject *row = PLy_input_from_tuple(&cursor->result, + SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, + true); + + PyList_SetItem(ret->rows, i, row); + } + } + } + + SPI_freetuptable(SPI_tuptable); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + return (PyObject *) ret; +} + +static PyObject * +PLy_cursor_close(PyObject *self, PyObject *unused) +{ + PLyCursorObject *cursor = (PLyCursorObject *) self; + + if (!cursor->closed) + { + Portal portal = GetPortalByName(cursor->portalname); + + if (!PortalIsValid(portal)) + { + PLy_exception_set(PyExc_ValueError, + "closing a cursor in an aborted subtransaction"); + return NULL; + } + + UnpinPortal(portal); + SPI_cursor_close(portal); + cursor->closed = true; + } + + Py_RETURN_NONE; +} diff --git a/src/pl/plpython/plpy_cursorobject.h b/src/pl/plpython/plpy_cursorobject.h new file mode 100644 index 0000000..e4d2c0e --- /dev/null +++ b/src/pl/plpython/plpy_cursorobject.h @@ -0,0 +1,24 @@ +/* + * src/pl/plpython/plpy_cursorobject.h + */ + +#ifndef PLPY_CURSOROBJECT_H +#define PLPY_CURSOROBJECT_H + +#include "plpy_typeio.h" + + +typedef struct PLyCursorObject +{ + PyObject_HEAD + char *portalname; + PLyDatumToOb result; + bool closed; + MemoryContext mcxt; +} PLyCursorObject; + +extern void PLy_cursor_init_type(void); +extern PyObject *PLy_cursor(PyObject *self, PyObject *args); +extern PyObject *PLy_cursor_plan(PyObject *ob, PyObject *args); + +#endif /* PLPY_CURSOROBJECT_H */ diff --git a/src/pl/plpython/plpy_elog.c b/src/pl/plpython/plpy_elog.c new file mode 100644 index 0000000..70de5ba --- /dev/null +++ b/src/pl/plpython/plpy_elog.c @@ -0,0 +1,599 @@ +/* + * reporting Python exceptions as PostgreSQL errors + * + * src/pl/plpython/plpy_elog.c + */ + +#include "postgres.h" + +#include "lib/stringinfo.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_procedure.h" +#include "plpython.h" + +PyObject *PLy_exc_error = NULL; +PyObject *PLy_exc_fatal = NULL; +PyObject *PLy_exc_spi_error = NULL; + + +static void PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, + char **xmsg, char **tbmsg, int *tb_depth); +static void PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, + char **hint, char **query, int *position, + char **schema_name, char **table_name, char **column_name, + char **datatype_name, char **constraint_name); +static void PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, + char **hint, char **schema_name, char **table_name, char **column_name, + char **datatype_name, char **constraint_name); +static char *get_source_line(const char *src, int lineno); + +static void get_string_attr(PyObject *obj, char *attrname, char **str); +static bool set_string_attr(PyObject *obj, char *attrname, char *str); + +/* + * Emit a PG error or notice, together with any available info about + * the current Python error, previously set by PLy_exception_set(). + * This should be used to propagate Python errors into PG. If fmt is + * NULL, the Python error becomes the primary error message, otherwise + * it becomes the detail. If there is a Python traceback, it is put + * in the context. + */ +void +PLy_elog_impl(int elevel, const char *fmt,...) +{ + int save_errno = errno; + char *xmsg; + char *tbmsg; + int tb_depth; + StringInfoData emsg; + PyObject *exc, + *val, + *tb; + const char *primary = NULL; + int sqlerrcode = 0; + char *detail = NULL; + char *hint = NULL; + char *query = NULL; + int position = 0; + char *schema_name = NULL; + char *table_name = NULL; + char *column_name = NULL; + char *datatype_name = NULL; + char *constraint_name = NULL; + + PyErr_Fetch(&exc, &val, &tb); + + if (exc != NULL) + { + PyErr_NormalizeException(&exc, &val, &tb); + + if (PyErr_GivenExceptionMatches(val, PLy_exc_spi_error)) + PLy_get_spi_error_data(val, &sqlerrcode, + &detail, &hint, &query, &position, + &schema_name, &table_name, &column_name, + &datatype_name, &constraint_name); + else if (PyErr_GivenExceptionMatches(val, PLy_exc_error)) + PLy_get_error_data(val, &sqlerrcode, &detail, &hint, + &schema_name, &table_name, &column_name, + &datatype_name, &constraint_name); + else if (PyErr_GivenExceptionMatches(val, PLy_exc_fatal)) + elevel = FATAL; + } + + /* this releases our refcount on tb! */ + PLy_traceback(exc, val, tb, + &xmsg, &tbmsg, &tb_depth); + + if (fmt) + { + initStringInfo(&emsg); + for (;;) + { + va_list ap; + int needed; + + errno = save_errno; + va_start(ap, fmt); + needed = appendStringInfoVA(&emsg, dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + if (needed == 0) + break; + enlargeStringInfo(&emsg, needed); + } + primary = emsg.data; + + /* If there's an exception message, it goes in the detail. */ + if (xmsg) + detail = xmsg; + } + else + { + if (xmsg) + primary = xmsg; + } + + PG_TRY(); + { + ereport(elevel, + (errcode(sqlerrcode ? sqlerrcode : ERRCODE_EXTERNAL_ROUTINE_EXCEPTION), + errmsg_internal("%s", primary ? primary : "no exception data"), + (detail) ? errdetail_internal("%s", detail) : 0, + (tb_depth > 0 && tbmsg) ? errcontext("%s", tbmsg) : 0, + (hint) ? errhint("%s", hint) : 0, + (query) ? internalerrquery(query) : 0, + (position) ? internalerrposition(position) : 0, + (schema_name) ? err_generic_string(PG_DIAG_SCHEMA_NAME, + schema_name) : 0, + (table_name) ? err_generic_string(PG_DIAG_TABLE_NAME, + table_name) : 0, + (column_name) ? err_generic_string(PG_DIAG_COLUMN_NAME, + column_name) : 0, + (datatype_name) ? err_generic_string(PG_DIAG_DATATYPE_NAME, + datatype_name) : 0, + (constraint_name) ? err_generic_string(PG_DIAG_CONSTRAINT_NAME, + constraint_name) : 0)); + } + PG_FINALLY(); + { + if (fmt) + pfree(emsg.data); + if (xmsg) + pfree(xmsg); + if (tbmsg) + pfree(tbmsg); + Py_XDECREF(exc); + Py_XDECREF(val); + } + PG_END_TRY(); +} + +/* + * Extract a Python traceback from the given exception data. + * + * The exception error message is returned in xmsg, the traceback in + * tbmsg (both as palloc'd strings) and the traceback depth in + * tb_depth. + * + * We release refcounts on all the Python objects in the traceback stack, + * but not on e or v. + */ +static void +PLy_traceback(PyObject *e, PyObject *v, PyObject *tb, + char **xmsg, char **tbmsg, int *tb_depth) +{ + PyObject *e_type_o; + PyObject *e_module_o; + char *e_type_s = NULL; + char *e_module_s = NULL; + PyObject *vob = NULL; + char *vstr; + StringInfoData xstr; + StringInfoData tbstr; + + /* + * if no exception, return nulls + */ + if (e == NULL) + { + *xmsg = NULL; + *tbmsg = NULL; + *tb_depth = 0; + + return; + } + + /* + * Format the exception and its value and put it in xmsg. + */ + + e_type_o = PyObject_GetAttrString(e, "__name__"); + e_module_o = PyObject_GetAttrString(e, "__module__"); + if (e_type_o) + e_type_s = PLyUnicode_AsString(e_type_o); + if (e_type_s) + e_module_s = PLyUnicode_AsString(e_module_o); + + if (v && ((vob = PyObject_Str(v)) != NULL)) + vstr = PLyUnicode_AsString(vob); + else + vstr = "unknown"; + + initStringInfo(&xstr); + if (!e_type_s || !e_module_s) + { + /* shouldn't happen */ + appendStringInfoString(&xstr, "unrecognized exception"); + } + /* mimics behavior of traceback.format_exception_only */ + else if (strcmp(e_module_s, "builtins") == 0 + || strcmp(e_module_s, "__main__") == 0 + || strcmp(e_module_s, "exceptions") == 0) + appendStringInfoString(&xstr, e_type_s); + else + appendStringInfo(&xstr, "%s.%s", e_module_s, e_type_s); + appendStringInfo(&xstr, ": %s", vstr); + + *xmsg = xstr.data; + + /* + * Now format the traceback and put it in tbmsg. + */ + + *tb_depth = 0; + initStringInfo(&tbstr); + /* Mimic Python traceback reporting as close as possible. */ + appendStringInfoString(&tbstr, "Traceback (most recent call last):"); + while (tb != NULL && tb != Py_None) + { + PyObject *volatile tb_prev = NULL; + PyObject *volatile frame = NULL; + PyObject *volatile code = NULL; + PyObject *volatile name = NULL; + PyObject *volatile lineno = NULL; + PyObject *volatile filename = NULL; + + PG_TRY(); + { + lineno = PyObject_GetAttrString(tb, "tb_lineno"); + if (lineno == NULL) + elog(ERROR, "could not get line number from Python traceback"); + + frame = PyObject_GetAttrString(tb, "tb_frame"); + if (frame == NULL) + elog(ERROR, "could not get frame from Python traceback"); + + code = PyObject_GetAttrString(frame, "f_code"); + if (code == NULL) + elog(ERROR, "could not get code object from Python frame"); + + name = PyObject_GetAttrString(code, "co_name"); + if (name == NULL) + elog(ERROR, "could not get function name from Python code object"); + + filename = PyObject_GetAttrString(code, "co_filename"); + if (filename == NULL) + elog(ERROR, "could not get file name from Python code object"); + } + PG_CATCH(); + { + Py_XDECREF(frame); + Py_XDECREF(code); + Py_XDECREF(name); + Py_XDECREF(lineno); + Py_XDECREF(filename); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* The first frame always points at <module>, skip it. */ + if (*tb_depth > 0) + { + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + char *proname; + char *fname; + char *line; + char *plain_filename; + long plain_lineno; + + /* + * The second frame points at the internal function, but to mimic + * Python error reporting we want to say <module>. + */ + if (*tb_depth == 1) + fname = "<module>"; + else + fname = PLyUnicode_AsString(name); + + proname = PLy_procedure_name(exec_ctx->curr_proc); + plain_filename = PLyUnicode_AsString(filename); + plain_lineno = PyLong_AsLong(lineno); + + if (proname == NULL) + appendStringInfo(&tbstr, "\n PL/Python anonymous code block, line %ld, in %s", + plain_lineno - 1, fname); + else + appendStringInfo(&tbstr, "\n PL/Python function \"%s\", line %ld, in %s", + proname, plain_lineno - 1, fname); + + /* + * function code object was compiled with "<string>" as the + * filename + */ + if (exec_ctx->curr_proc && plain_filename != NULL && + strcmp(plain_filename, "<string>") == 0) + { + /* + * If we know the current procedure, append the exact line + * from the source, again mimicking Python's traceback.py + * module behavior. We could store the already line-split + * source to avoid splitting it every time, but producing a + * traceback is not the most important scenario to optimize + * for. But we do not go as far as traceback.py in reading + * the source of imported modules. + */ + line = get_source_line(exec_ctx->curr_proc->src, plain_lineno); + if (line) + { + appendStringInfo(&tbstr, "\n %s", line); + pfree(line); + } + } + } + + Py_DECREF(frame); + Py_DECREF(code); + Py_DECREF(name); + Py_DECREF(lineno); + Py_DECREF(filename); + + /* Release the current frame and go to the next one. */ + tb_prev = tb; + tb = PyObject_GetAttrString(tb, "tb_next"); + Assert(tb_prev != Py_None); + Py_DECREF(tb_prev); + if (tb == NULL) + elog(ERROR, "could not traverse Python traceback"); + (*tb_depth)++; + } + + /* Return the traceback. */ + *tbmsg = tbstr.data; + + Py_XDECREF(e_type_o); + Py_XDECREF(e_module_o); + Py_XDECREF(vob); +} + +/* + * Extract error code from SPIError's sqlstate attribute. + */ +static void +PLy_get_sqlerrcode(PyObject *exc, int *sqlerrcode) +{ + PyObject *sqlstate; + char *buffer; + + sqlstate = PyObject_GetAttrString(exc, "sqlstate"); + if (sqlstate == NULL) + return; + + buffer = PLyUnicode_AsString(sqlstate); + if (strlen(buffer) == 5 && + strspn(buffer, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") == 5) + { + *sqlerrcode = MAKE_SQLSTATE(buffer[0], buffer[1], buffer[2], + buffer[3], buffer[4]); + } + + Py_DECREF(sqlstate); +} + +/* + * Extract the error data from a SPIError + */ +static void +PLy_get_spi_error_data(PyObject *exc, int *sqlerrcode, char **detail, + char **hint, char **query, int *position, + char **schema_name, char **table_name, + char **column_name, + char **datatype_name, char **constraint_name) +{ + PyObject *spidata; + + spidata = PyObject_GetAttrString(exc, "spidata"); + + if (spidata != NULL) + { + PyArg_ParseTuple(spidata, "izzzizzzzz", + sqlerrcode, detail, hint, query, position, + schema_name, table_name, column_name, + datatype_name, constraint_name); + } + else + { + /* + * If there's no spidata, at least set the sqlerrcode. This can happen + * if someone explicitly raises a SPI exception from Python code. + */ + PLy_get_sqlerrcode(exc, sqlerrcode); + } + + Py_XDECREF(spidata); +} + +/* + * Extract the error data from an Error. + * + * Note: position and query attributes are never set for Error so, unlike + * PLy_get_spi_error_data, this function doesn't return them. + */ +static void +PLy_get_error_data(PyObject *exc, int *sqlerrcode, char **detail, char **hint, + char **schema_name, char **table_name, char **column_name, + char **datatype_name, char **constraint_name) +{ + PLy_get_sqlerrcode(exc, sqlerrcode); + get_string_attr(exc, "detail", detail); + get_string_attr(exc, "hint", hint); + get_string_attr(exc, "schema_name", schema_name); + get_string_attr(exc, "table_name", table_name); + get_string_attr(exc, "column_name", column_name); + get_string_attr(exc, "datatype_name", datatype_name); + get_string_attr(exc, "constraint_name", constraint_name); +} + +/* + * Get the given source line as a palloc'd string + */ +static char * +get_source_line(const char *src, int lineno) +{ + const char *s = NULL; + const char *next = src; + int current = 0; + + /* sanity check */ + if (lineno <= 0) + return NULL; + + while (current < lineno) + { + s = next; + next = strchr(s + 1, '\n'); + current++; + if (next == NULL) + break; + } + + if (current != lineno) + return NULL; + + while (*s && isspace((unsigned char) *s)) + s++; + + if (next == NULL) + return pstrdup(s); + + /* + * Sanity check, next < s if the line was all-whitespace, which should + * never happen if Python reported a frame created on that line, but check + * anyway. + */ + if (next < s) + return NULL; + + return pnstrdup(s, next - s); +} + + +/* call PyErr_SetString with a vprint interface and translation support */ +void +PLy_exception_set(PyObject *exc, const char *fmt,...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), dgettext(TEXTDOMAIN, fmt), ap); + va_end(ap); + + PyErr_SetString(exc, buf); +} + +/* same, with pluralized message */ +void +PLy_exception_set_plural(PyObject *exc, + const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) +{ + char buf[1024]; + va_list ap; + + va_start(ap, n); + vsnprintf(buf, sizeof(buf), + dngettext(TEXTDOMAIN, fmt_singular, fmt_plural, n), + ap); + va_end(ap); + + PyErr_SetString(exc, buf); +} + +/* set attributes of the given exception to details from ErrorData */ +void +PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata) +{ + PyObject *args = NULL; + PyObject *error = NULL; + + args = Py_BuildValue("(s)", edata->message); + if (!args) + goto failure; + + /* create a new exception with the error message as the parameter */ + error = PyObject_CallObject(excclass, args); + if (!error) + goto failure; + + if (!set_string_attr(error, "sqlstate", + unpack_sql_state(edata->sqlerrcode))) + goto failure; + + if (!set_string_attr(error, "detail", edata->detail)) + goto failure; + + if (!set_string_attr(error, "hint", edata->hint)) + goto failure; + + if (!set_string_attr(error, "query", edata->internalquery)) + goto failure; + + if (!set_string_attr(error, "schema_name", edata->schema_name)) + goto failure; + + if (!set_string_attr(error, "table_name", edata->table_name)) + goto failure; + + if (!set_string_attr(error, "column_name", edata->column_name)) + goto failure; + + if (!set_string_attr(error, "datatype_name", edata->datatype_name)) + goto failure; + + if (!set_string_attr(error, "constraint_name", edata->constraint_name)) + goto failure; + + PyErr_SetObject(excclass, error); + + Py_DECREF(args); + Py_DECREF(error); + + return; + +failure: + Py_XDECREF(args); + Py_XDECREF(error); + + elog(ERROR, "could not convert error to Python exception"); +} + +/* get string value of an object attribute */ +static void +get_string_attr(PyObject *obj, char *attrname, char **str) +{ + PyObject *val; + + val = PyObject_GetAttrString(obj, attrname); + if (val != NULL && val != Py_None) + { + *str = pstrdup(PLyUnicode_AsString(val)); + } + Py_XDECREF(val); +} + +/* set an object attribute to a string value, returns true when the set was + * successful + */ +static bool +set_string_attr(PyObject *obj, char *attrname, char *str) +{ + int result; + PyObject *val; + + if (str != NULL) + { + val = PLyUnicode_FromString(str); + if (!val) + return false; + } + else + { + val = Py_None; + Py_INCREF(Py_None); + } + + result = PyObject_SetAttrString(obj, attrname, val); + Py_DECREF(val); + + return result != -1; +} diff --git a/src/pl/plpython/plpy_elog.h b/src/pl/plpython/plpy_elog.h new file mode 100644 index 0000000..dc65f2f --- /dev/null +++ b/src/pl/plpython/plpy_elog.h @@ -0,0 +1,46 @@ +/* + * src/pl/plpython/plpy_elog.h + */ + +#ifndef PLPY_ELOG_H +#define PLPY_ELOG_H + +#include "plpython.h" + +/* global exception classes */ +extern PyObject *PLy_exc_error; +extern PyObject *PLy_exc_fatal; +extern PyObject *PLy_exc_spi_error; + +/* + * PLy_elog() + * + * See comments at elog() about the compiler hinting. + */ +#ifdef HAVE__BUILTIN_CONSTANT_P +#define PLy_elog(elevel, ...) \ + do { \ + PLy_elog_impl(elevel, __VA_ARGS__); \ + if (__builtin_constant_p(elevel) && (elevel) >= ERROR) \ + pg_unreachable(); \ + } while(0) +#else /* !HAVE__BUILTIN_CONSTANT_P */ +#define PLy_elog(elevel, ...) \ + do { \ + const int elevel_ = (elevel); \ + PLy_elog_impl(elevel_, __VA_ARGS__); \ + if (elevel_ >= ERROR) \ + pg_unreachable(); \ + } while(0) +#endif /* HAVE__BUILTIN_CONSTANT_P */ + +extern PGDLLEXPORT void PLy_elog_impl(int elevel, const char *fmt,...) pg_attribute_printf(2, 3); + +extern PGDLLEXPORT void PLy_exception_set(PyObject *exc, const char *fmt,...) pg_attribute_printf(2, 3); + +extern PGDLLEXPORT void PLy_exception_set_plural(PyObject *exc, const char *fmt_singular, const char *fmt_plural, + unsigned long n,...) pg_attribute_printf(2, 5) pg_attribute_printf(3, 5); + +extern PGDLLEXPORT void PLy_exception_set_with_details(PyObject *excclass, ErrorData *edata); + +#endif /* PLPY_ELOG_H */ diff --git a/src/pl/plpython/plpy_exec.c b/src/pl/plpython/plpy_exec.c new file mode 100644 index 0000000..e06fde1 --- /dev/null +++ b/src/pl/plpython/plpy_exec.c @@ -0,0 +1,1098 @@ +/* + * executing Python code + * + * src/pl/plpython/plpy_exec.c + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "funcapi.h" +#include "plpy_elog.h" +#include "plpy_exec.h" +#include "plpy_main.h" +#include "plpy_procedure.h" +#include "plpy_subxactobject.h" +#include "plpython.h" +#include "utils/builtins.h" +#include "utils/lsyscache.h" +#include "utils/rel.h" +#include "utils/typcache.h" + +/* saved state for a set-returning function */ +typedef struct PLySRFState +{ + PyObject *iter; /* Python iterator producing results */ + PLySavedArgs *savedargs; /* function argument values */ + MemoryContextCallback callback; /* for releasing refcounts when done */ +} PLySRFState; + +static PyObject *PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc); +static PLySavedArgs *PLy_function_save_args(PLyProcedure *proc); +static void PLy_function_restore_args(PLyProcedure *proc, PLySavedArgs *savedargs); +static void PLy_function_drop_args(PLySavedArgs *savedargs); +static void PLy_global_args_push(PLyProcedure *proc); +static void PLy_global_args_pop(PLyProcedure *proc); +static void plpython_srf_cleanup_callback(void *arg); +static void plpython_return_error_callback(void *arg); + +static PyObject *PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, + HeapTuple *rv); +static HeapTuple PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, + TriggerData *tdata, HeapTuple otup); +static void plpython_trigger_error_callback(void *arg); + +static PyObject *PLy_procedure_call(PLyProcedure *proc, const char *kargs, PyObject *vargs); +static void PLy_abort_open_subtransactions(int save_subxact_level); + + +/* function subhandler */ +Datum +PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + bool is_setof = proc->is_setof; + Datum rv; + PyObject *volatile plargs = NULL; + PyObject *volatile plrv = NULL; + FuncCallContext *volatile funcctx = NULL; + PLySRFState *volatile srfstate = NULL; + ErrorContextCallback plerrcontext; + + /* + * If the function is called recursively, we must push outer-level + * arguments into the stack. This must be immediately before the PG_TRY + * to ensure that the corresponding pop happens. + */ + PLy_global_args_push(proc); + + PG_TRY(); + { + if (is_setof) + { + /* First Call setup */ + if (SRF_IS_FIRSTCALL()) + { + funcctx = SRF_FIRSTCALL_INIT(); + srfstate = (PLySRFState *) + MemoryContextAllocZero(funcctx->multi_call_memory_ctx, + sizeof(PLySRFState)); + /* Immediately register cleanup callback */ + srfstate->callback.func = plpython_srf_cleanup_callback; + srfstate->callback.arg = (void *) srfstate; + MemoryContextRegisterResetCallback(funcctx->multi_call_memory_ctx, + &srfstate->callback); + funcctx->user_fctx = (void *) srfstate; + } + /* Every call setup */ + funcctx = SRF_PERCALL_SETUP(); + Assert(funcctx != NULL); + srfstate = (PLySRFState *) funcctx->user_fctx; + Assert(srfstate != NULL); + } + + if (srfstate == NULL || srfstate->iter == NULL) + { + /* + * Non-SETOF function or first time for SETOF function: build + * args, then actually execute the function. + */ + plargs = PLy_function_build_args(fcinfo, proc); + plrv = PLy_procedure_call(proc, "args", plargs); + Assert(plrv != NULL); + } + else + { + /* + * Second or later call for a SETOF function: restore arguments in + * globals dict to what they were when we left off. We must do + * this in case multiple evaluations of the same SETOF function + * are interleaved. It's a bit annoying, since the iterator may + * not look at the arguments at all, but we have no way to know + * that. Fortunately this isn't terribly expensive. + */ + if (srfstate->savedargs) + PLy_function_restore_args(proc, srfstate->savedargs); + srfstate->savedargs = NULL; /* deleted by restore_args */ + } + + /* + * If it returns a set, call the iterator to get the next return item. + * We stay in the SPI context while doing this, because PyIter_Next() + * calls back into Python code which might contain SPI calls. + */ + if (is_setof) + { + if (srfstate->iter == NULL) + { + /* first time -- do checks and setup */ + ReturnSetInfo *rsi = (ReturnSetInfo *) fcinfo->resultinfo; + + if (!rsi || !IsA(rsi, ReturnSetInfo) || + (rsi->allowedModes & SFRM_ValuePerCall) == 0) + { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported set function return mode"), + errdetail("PL/Python set-returning functions only support returning one value per call."))); + } + rsi->returnMode = SFRM_ValuePerCall; + + /* Make iterator out of returned object */ + srfstate->iter = PyObject_GetIter(plrv); + + Py_DECREF(plrv); + plrv = NULL; + + if (srfstate->iter == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("returned object cannot be iterated"), + errdetail("PL/Python set-returning functions must return an iterable object."))); + } + + /* Fetch next from iterator */ + plrv = PyIter_Next(srfstate->iter); + if (plrv == NULL) + { + /* Iterator is exhausted or error happened */ + bool has_error = (PyErr_Occurred() != NULL); + + Py_DECREF(srfstate->iter); + srfstate->iter = NULL; + + if (has_error) + PLy_elog(ERROR, "error fetching next item from iterator"); + + /* Pass a null through the data-returning steps below */ + Py_INCREF(Py_None); + plrv = Py_None; + } + else + { + /* + * This won't be last call, so save argument values. We do + * this again each time in case the iterator is changing those + * values. + */ + srfstate->savedargs = PLy_function_save_args(proc); + } + } + + /* + * Disconnect from SPI manager and then create the return values datum + * (if the input function does a palloc for it this must not be + * allocated in the SPI memory context because SPI_finish would free + * it). + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + plerrcontext.callback = plpython_return_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + /* + * For a procedure or function declared to return void, the Python + * return value must be None. For void-returning functions, we also + * treat a None return value as a special "void datum" rather than + * NULL (as is the case for non-void-returning functions). + */ + if (proc->result.typoid == VOIDOID) + { + if (plrv != Py_None) + { + if (proc->is_procedure) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python procedure did not return None"))); + else + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("PL/Python function with return type \"void\" did not return None"))); + } + + fcinfo->isnull = false; + rv = (Datum) 0; + } + else if (plrv == Py_None && + srfstate && srfstate->iter == NULL) + { + /* + * In a SETOF function, the iteration-ending null isn't a real + * value; don't pass it through the input function, which might + * complain. + */ + fcinfo->isnull = true; + rv = (Datum) 0; + } + else + { + /* Normal conversion of result */ + rv = PLy_output_convert(&proc->result, plrv, + &fcinfo->isnull); + } + } + PG_CATCH(); + { + /* Pop old arguments from the stack if they were pushed above */ + PLy_global_args_pop(proc); + + Py_XDECREF(plargs); + Py_XDECREF(plrv); + + /* + * If there was an error within a SRF, the iterator might not have + * been exhausted yet. Clear it so the next invocation of the + * function will start the iteration again. (This code is probably + * unnecessary now; plpython_srf_cleanup_callback should take care of + * cleanup. But it doesn't hurt anything to do it here.) + */ + if (srfstate) + { + Py_XDECREF(srfstate->iter); + srfstate->iter = NULL; + /* And drop any saved args; we won't need them */ + if (srfstate->savedargs) + PLy_function_drop_args(srfstate->savedargs); + srfstate->savedargs = NULL; + } + + PG_RE_THROW(); + } + PG_END_TRY(); + + error_context_stack = plerrcontext.previous; + + /* Pop old arguments from the stack if they were pushed above */ + PLy_global_args_pop(proc); + + Py_XDECREF(plargs); + Py_DECREF(plrv); + + if (srfstate) + { + /* We're in a SRF, exit appropriately */ + if (srfstate->iter == NULL) + { + /* Iterator exhausted, so we're done */ + SRF_RETURN_DONE(funcctx); + } + else if (fcinfo->isnull) + SRF_RETURN_NEXT_NULL(funcctx); + else + SRF_RETURN_NEXT(funcctx, rv); + } + + /* Plain function, just return the Datum value (possibly null) */ + return rv; +} + +/* trigger subhandler + * + * the python function is expected to return Py_None if the tuple is + * acceptable and unmodified. Otherwise it should return a PyUnicode + * object who's value is SKIP, or MODIFY. SKIP means don't perform + * this action. MODIFY means the tuple has been modified, so update + * tuple and perform action. SKIP and MODIFY assume the trigger fires + * BEFORE the event and is ROW level. postgres expects the function + * to take no arguments and return an argument of type trigger. + */ +HeapTuple +PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + HeapTuple rv = NULL; + PyObject *volatile plargs = NULL; + PyObject *volatile plrv = NULL; + TriggerData *tdata; + TupleDesc rel_descr; + + Assert(CALLED_AS_TRIGGER(fcinfo)); + tdata = (TriggerData *) fcinfo->context; + + /* + * Input/output conversion for trigger tuples. We use the result and + * result_in fields to store the tuple conversion info. We do this over + * again on each call to cover the possibility that the relation's tupdesc + * changed since the trigger was last called. The PLy_xxx_setup_func + * calls should only happen once, but PLy_input_setup_tuple and + * PLy_output_setup_tuple are responsible for not doing repetitive work. + */ + rel_descr = RelationGetDescr(tdata->tg_relation); + if (proc->result.typoid != rel_descr->tdtypeid) + PLy_output_setup_func(&proc->result, proc->mcxt, + rel_descr->tdtypeid, + rel_descr->tdtypmod, + proc); + if (proc->result_in.typoid != rel_descr->tdtypeid) + PLy_input_setup_func(&proc->result_in, proc->mcxt, + rel_descr->tdtypeid, + rel_descr->tdtypmod, + proc); + PLy_output_setup_tuple(&proc->result, rel_descr, proc); + PLy_input_setup_tuple(&proc->result_in, rel_descr, proc); + + PG_TRY(); + { + int rc PG_USED_FOR_ASSERTS_ONLY; + + rc = SPI_register_trigger_data(tdata); + Assert(rc >= 0); + + plargs = PLy_trigger_build_args(fcinfo, proc, &rv); + plrv = PLy_procedure_call(proc, "TD", plargs); + + Assert(plrv != NULL); + + /* + * Disconnect from SPI manager + */ + if (SPI_finish() != SPI_OK_FINISH) + elog(ERROR, "SPI_finish failed"); + + /* + * return of None means we're happy with the tuple + */ + if (plrv != Py_None) + { + char *srv; + + if (PyUnicode_Check(plrv)) + srv = PLyUnicode_AsString(plrv); + else + { + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None or a string."))); + srv = NULL; /* keep compiler quiet */ + } + + if (pg_strcasecmp(srv, "SKIP") == 0) + rv = NULL; + else if (pg_strcasecmp(srv, "MODIFY") == 0) + { + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event) || + TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + rv = PLy_modify_tuple(proc, plargs, tdata, rv); + else + ereport(WARNING, + (errmsg("PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored"))); + } + else if (pg_strcasecmp(srv, "OK") != 0) + { + /* + * accept "OK" as an alternative to None; otherwise, raise an + * error + */ + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("unexpected return value from trigger procedure"), + errdetail("Expected None, \"OK\", \"SKIP\", or \"MODIFY\"."))); + } + } + } + PG_FINALLY(); + { + Py_XDECREF(plargs); + Py_XDECREF(plrv); + } + PG_END_TRY(); + + return rv; +} + +/* helper functions for Python code execution */ + +static PyObject * +PLy_function_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc) +{ + PyObject *volatile arg = NULL; + PyObject *args; + int i; + + /* + * Make any Py*_New() calls before the PG_TRY block so that we can quickly + * return NULL on failure. We can't return within the PG_TRY block, else + * we'd miss unwinding the exception stack. + */ + args = PyList_New(proc->nargs); + if (!args) + return NULL; + + PG_TRY(); + { + for (i = 0; i < proc->nargs; i++) + { + PLyDatumToOb *arginfo = &proc->args[i]; + + if (fcinfo->args[i].isnull) + arg = NULL; + else + arg = PLy_input_convert(arginfo, fcinfo->args[i].value); + + if (arg == NULL) + { + Py_INCREF(Py_None); + arg = Py_None; + } + + if (PyList_SetItem(args, i, arg) == -1) + PLy_elog(ERROR, "PyList_SetItem() failed, while setting up arguments"); + + if (proc->argnames && proc->argnames[i] && + PyDict_SetItemString(proc->globals, proc->argnames[i], arg) == -1) + PLy_elog(ERROR, "PyDict_SetItemString() failed, while setting up arguments"); + arg = NULL; + } + + /* Set up output conversion for functions returning RECORD */ + if (proc->result.typoid == RECORDOID) + { + TupleDesc desc; + + if (get_call_result_type(fcinfo, NULL, &desc) != TYPEFUNC_COMPOSITE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("function returning record called in context " + "that cannot accept type record"))); + + /* cache the output conversion functions */ + PLy_output_setup_record(&proc->result, desc, proc); + } + } + PG_CATCH(); + { + Py_XDECREF(arg); + Py_XDECREF(args); + + PG_RE_THROW(); + } + PG_END_TRY(); + + return args; +} + +/* + * Construct a PLySavedArgs struct representing the current values of the + * procedure's arguments in its globals dict. This can be used to restore + * those values when exiting a recursive call level or returning control to a + * set-returning function. + * + * This would not be necessary except for an ancient decision to make args + * available via the proc's globals :-( ... but we're stuck with that now. + */ +static PLySavedArgs * +PLy_function_save_args(PLyProcedure *proc) +{ + PLySavedArgs *result; + + /* saved args are always allocated in procedure's context */ + result = (PLySavedArgs *) + MemoryContextAllocZero(proc->mcxt, + offsetof(PLySavedArgs, namedargs) + + proc->nargs * sizeof(PyObject *)); + result->nargs = proc->nargs; + + /* Fetch the "args" list */ + result->args = PyDict_GetItemString(proc->globals, "args"); + Py_XINCREF(result->args); + + /* Fetch all the named arguments */ + if (proc->argnames) + { + int i; + + for (i = 0; i < result->nargs; i++) + { + if (proc->argnames[i]) + { + result->namedargs[i] = PyDict_GetItemString(proc->globals, + proc->argnames[i]); + Py_XINCREF(result->namedargs[i]); + } + } + } + + return result; +} + +/* + * Restore procedure's arguments from a PLySavedArgs struct, + * then free the struct. + */ +static void +PLy_function_restore_args(PLyProcedure *proc, PLySavedArgs *savedargs) +{ + /* Restore named arguments into their slots in the globals dict */ + if (proc->argnames) + { + int i; + + for (i = 0; i < savedargs->nargs; i++) + { + if (proc->argnames[i] && savedargs->namedargs[i]) + { + PyDict_SetItemString(proc->globals, proc->argnames[i], + savedargs->namedargs[i]); + Py_DECREF(savedargs->namedargs[i]); + } + } + } + + /* Restore the "args" object, too */ + if (savedargs->args) + { + PyDict_SetItemString(proc->globals, "args", savedargs->args); + Py_DECREF(savedargs->args); + } + + /* And free the PLySavedArgs struct */ + pfree(savedargs); +} + +/* + * Free a PLySavedArgs struct without restoring the values. + */ +static void +PLy_function_drop_args(PLySavedArgs *savedargs) +{ + int i; + + /* Drop references for named args */ + for (i = 0; i < savedargs->nargs; i++) + { + Py_XDECREF(savedargs->namedargs[i]); + } + + /* Drop ref to the "args" object, too */ + Py_XDECREF(savedargs->args); + + /* And free the PLySavedArgs struct */ + pfree(savedargs); +} + +/* + * Save away any existing arguments for the given procedure, so that we can + * install new values for a recursive call. This should be invoked before + * doing PLy_function_build_args(). + * + * NB: caller must ensure that PLy_global_args_pop gets invoked once, and + * only once, per successful completion of PLy_global_args_push. Otherwise + * we'll end up out-of-sync between the actual call stack and the contents + * of proc->argstack. + */ +static void +PLy_global_args_push(PLyProcedure *proc) +{ + /* We only need to push if we are already inside some active call */ + if (proc->calldepth > 0) + { + PLySavedArgs *node; + + /* Build a struct containing current argument values */ + node = PLy_function_save_args(proc); + + /* + * Push the saved argument values into the procedure's stack. Once we + * modify either proc->argstack or proc->calldepth, we had better + * return without the possibility of error. + */ + node->next = proc->argstack; + proc->argstack = node; + } + proc->calldepth++; +} + +/* + * Pop old arguments when exiting a recursive call. + * + * Note: the idea here is to adjust the proc's callstack state before doing + * anything that could possibly fail. In event of any error, we want the + * callstack to look like we've done the pop. Leaking a bit of memory is + * tolerable. + */ +static void +PLy_global_args_pop(PLyProcedure *proc) +{ + Assert(proc->calldepth > 0); + /* We only need to pop if we were already inside some active call */ + if (proc->calldepth > 1) + { + PLySavedArgs *ptr = proc->argstack; + + /* Pop the callstack */ + Assert(ptr != NULL); + proc->argstack = ptr->next; + proc->calldepth--; + + /* Restore argument values, then free ptr */ + PLy_function_restore_args(proc, ptr); + } + else + { + /* Exiting call depth 1 */ + Assert(proc->argstack == NULL); + proc->calldepth--; + + /* + * We used to delete the named arguments (but not "args") from the + * proc's globals dict when exiting the outermost call level for a + * function. This seems rather pointless though: nothing can see the + * dict until the function is called again, at which time we'll + * overwrite those dict entries. So don't bother with that. + */ + } +} + +/* + * Memory context deletion callback for cleaning up a PLySRFState. + * We need this in case execution of the SRF is terminated early, + * due to error or the caller simply not running it to completion. + */ +static void +plpython_srf_cleanup_callback(void *arg) +{ + PLySRFState *srfstate = (PLySRFState *) arg; + + /* Release refcount on the iter, if we still have one */ + Py_XDECREF(srfstate->iter); + srfstate->iter = NULL; + /* And drop any saved args; we won't need them */ + if (srfstate->savedargs) + PLy_function_drop_args(srfstate->savedargs); + srfstate->savedargs = NULL; +} + +static void +plpython_return_error_callback(void *arg) +{ + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + if (exec_ctx->curr_proc && + !exec_ctx->curr_proc->is_procedure) + errcontext("while creating return value"); +} + +static PyObject * +PLy_trigger_build_args(FunctionCallInfo fcinfo, PLyProcedure *proc, HeapTuple *rv) +{ + TriggerData *tdata = (TriggerData *) fcinfo->context; + TupleDesc rel_descr = RelationGetDescr(tdata->tg_relation); + PyObject *pltname, + *pltevent, + *pltwhen, + *pltlevel, + *pltrelid, + *plttablename, + *plttableschema, + *pltargs = NULL, + *pytnew, + *pytold, + *pltdata; + char *stroid; + + /* + * Make any Py*_New() calls before the PG_TRY block so that we can quickly + * return NULL on failure. We can't return within the PG_TRY block, else + * we'd miss unwinding the exception stack. + */ + pltdata = PyDict_New(); + if (!pltdata) + return NULL; + + if (tdata->tg_trigger->tgnargs) + { + pltargs = PyList_New(tdata->tg_trigger->tgnargs); + if (!pltargs) + { + Py_DECREF(pltdata); + return NULL; + } + } + + PG_TRY(); + { + pltname = PLyUnicode_FromString(tdata->tg_trigger->tgname); + PyDict_SetItemString(pltdata, "name", pltname); + Py_DECREF(pltname); + + stroid = DatumGetCString(DirectFunctionCall1(oidout, + ObjectIdGetDatum(tdata->tg_relation->rd_id))); + pltrelid = PLyUnicode_FromString(stroid); + PyDict_SetItemString(pltdata, "relid", pltrelid); + Py_DECREF(pltrelid); + pfree(stroid); + + stroid = SPI_getrelname(tdata->tg_relation); + plttablename = PLyUnicode_FromString(stroid); + PyDict_SetItemString(pltdata, "table_name", plttablename); + Py_DECREF(plttablename); + pfree(stroid); + + stroid = SPI_getnspname(tdata->tg_relation); + plttableschema = PLyUnicode_FromString(stroid); + PyDict_SetItemString(pltdata, "table_schema", plttableschema); + Py_DECREF(plttableschema); + pfree(stroid); + + if (TRIGGER_FIRED_BEFORE(tdata->tg_event)) + pltwhen = PLyUnicode_FromString("BEFORE"); + else if (TRIGGER_FIRED_AFTER(tdata->tg_event)) + pltwhen = PLyUnicode_FromString("AFTER"); + else if (TRIGGER_FIRED_INSTEAD(tdata->tg_event)) + pltwhen = PLyUnicode_FromString("INSTEAD OF"); + else + { + elog(ERROR, "unrecognized WHEN tg_event: %u", tdata->tg_event); + pltwhen = NULL; /* keep compiler quiet */ + } + PyDict_SetItemString(pltdata, "when", pltwhen); + Py_DECREF(pltwhen); + + if (TRIGGER_FIRED_FOR_ROW(tdata->tg_event)) + { + pltlevel = PLyUnicode_FromString("ROW"); + PyDict_SetItemString(pltdata, "level", pltlevel); + Py_DECREF(pltlevel); + + /* + * Note: In BEFORE trigger, stored generated columns are not + * computed yet, so don't make them accessible in NEW row. + */ + + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) + { + pltevent = PLyUnicode_FromString("INSERT"); + + PyDict_SetItemString(pltdata, "old", Py_None); + pytnew = PLy_input_from_tuple(&proc->result_in, + tdata->tg_trigtuple, + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); + PyDict_SetItemString(pltdata, "new", pytnew); + Py_DECREF(pytnew); + *rv = tdata->tg_trigtuple; + } + else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) + { + pltevent = PLyUnicode_FromString("DELETE"); + + PyDict_SetItemString(pltdata, "new", Py_None); + pytold = PLy_input_from_tuple(&proc->result_in, + tdata->tg_trigtuple, + rel_descr, + true); + PyDict_SetItemString(pltdata, "old", pytold); + Py_DECREF(pytold); + *rv = tdata->tg_trigtuple; + } + else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + { + pltevent = PLyUnicode_FromString("UPDATE"); + + pytnew = PLy_input_from_tuple(&proc->result_in, + tdata->tg_newtuple, + rel_descr, + !TRIGGER_FIRED_BEFORE(tdata->tg_event)); + PyDict_SetItemString(pltdata, "new", pytnew); + Py_DECREF(pytnew); + pytold = PLy_input_from_tuple(&proc->result_in, + tdata->tg_trigtuple, + rel_descr, + true); + PyDict_SetItemString(pltdata, "old", pytold); + Py_DECREF(pytold); + *rv = tdata->tg_newtuple; + } + else + { + elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); + pltevent = NULL; /* keep compiler quiet */ + } + + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + } + else if (TRIGGER_FIRED_FOR_STATEMENT(tdata->tg_event)) + { + pltlevel = PLyUnicode_FromString("STATEMENT"); + PyDict_SetItemString(pltdata, "level", pltlevel); + Py_DECREF(pltlevel); + + PyDict_SetItemString(pltdata, "old", Py_None); + PyDict_SetItemString(pltdata, "new", Py_None); + *rv = NULL; + + if (TRIGGER_FIRED_BY_INSERT(tdata->tg_event)) + pltevent = PLyUnicode_FromString("INSERT"); + else if (TRIGGER_FIRED_BY_DELETE(tdata->tg_event)) + pltevent = PLyUnicode_FromString("DELETE"); + else if (TRIGGER_FIRED_BY_UPDATE(tdata->tg_event)) + pltevent = PLyUnicode_FromString("UPDATE"); + else if (TRIGGER_FIRED_BY_TRUNCATE(tdata->tg_event)) + pltevent = PLyUnicode_FromString("TRUNCATE"); + else + { + elog(ERROR, "unrecognized OP tg_event: %u", tdata->tg_event); + pltevent = NULL; /* keep compiler quiet */ + } + + PyDict_SetItemString(pltdata, "event", pltevent); + Py_DECREF(pltevent); + } + else + elog(ERROR, "unrecognized LEVEL tg_event: %u", tdata->tg_event); + + if (tdata->tg_trigger->tgnargs) + { + /* + * all strings... + */ + int i; + PyObject *pltarg; + + /* pltargs should have been allocated before the PG_TRY block. */ + Assert(pltargs); + + for (i = 0; i < tdata->tg_trigger->tgnargs; i++) + { + pltarg = PLyUnicode_FromString(tdata->tg_trigger->tgargs[i]); + + /* + * stolen, don't Py_DECREF + */ + PyList_SetItem(pltargs, i, pltarg); + } + } + else + { + Py_INCREF(Py_None); + pltargs = Py_None; + } + PyDict_SetItemString(pltdata, "args", pltargs); + Py_DECREF(pltargs); + } + PG_CATCH(); + { + Py_XDECREF(pltargs); + Py_XDECREF(pltdata); + PG_RE_THROW(); + } + PG_END_TRY(); + + return pltdata; +} + +/* + * Apply changes requested by a MODIFY return from a trigger function. + */ +static HeapTuple +PLy_modify_tuple(PLyProcedure *proc, PyObject *pltd, TriggerData *tdata, + HeapTuple otup) +{ + HeapTuple rtup; + PyObject *volatile plntup; + PyObject *volatile plkeys; + PyObject *volatile plval; + Datum *volatile modvalues; + bool *volatile modnulls; + bool *volatile modrepls; + ErrorContextCallback plerrcontext; + + plerrcontext.callback = plpython_trigger_error_callback; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + plntup = plkeys = plval = NULL; + modvalues = NULL; + modnulls = NULL; + modrepls = NULL; + + PG_TRY(); + { + TupleDesc tupdesc; + int nkeys, + i; + + if ((plntup = PyDict_GetItemString(pltd, "new")) == NULL) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("TD[\"new\"] deleted, cannot modify row"))); + Py_INCREF(plntup); + if (!PyDict_Check(plntup)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("TD[\"new\"] is not a dictionary"))); + + plkeys = PyDict_Keys(plntup); + nkeys = PyList_Size(plkeys); + + tupdesc = RelationGetDescr(tdata->tg_relation); + + modvalues = (Datum *) palloc0(tupdesc->natts * sizeof(Datum)); + modnulls = (bool *) palloc0(tupdesc->natts * sizeof(bool)); + modrepls = (bool *) palloc0(tupdesc->natts * sizeof(bool)); + + for (i = 0; i < nkeys; i++) + { + PyObject *platt; + char *plattstr; + int attn; + PLyObToDatum *att; + + platt = PyList_GetItem(plkeys, i); + if (PyUnicode_Check(platt)) + plattstr = PLyUnicode_AsString(platt); + else + { + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("TD[\"new\"] dictionary key at ordinal position %d is not a string", i))); + plattstr = NULL; /* keep compiler quiet */ + } + attn = SPI_fnumber(tupdesc, plattstr); + if (attn == SPI_ERROR_NOATTRIBUTE) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row", + plattstr))); + if (attn <= 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot set system attribute \"%s\"", + plattstr))); + if (TupleDescAttr(tupdesc, attn - 1)->attgenerated) + ereport(ERROR, + (errcode(ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED), + errmsg("cannot set generated column \"%s\"", + plattstr))); + + plval = PyDict_GetItem(plntup, platt); + if (plval == NULL) + elog(FATAL, "Python interpreter is probably corrupted"); + + Py_INCREF(plval); + + /* We assume proc->result is set up to convert tuples properly */ + att = &proc->result.u.tuple.atts[attn - 1]; + + modvalues[attn - 1] = PLy_output_convert(att, + plval, + &modnulls[attn - 1]); + modrepls[attn - 1] = true; + + Py_DECREF(plval); + plval = NULL; + } + + rtup = heap_modify_tuple(otup, tupdesc, modvalues, modnulls, modrepls); + } + PG_CATCH(); + { + Py_XDECREF(plntup); + Py_XDECREF(plkeys); + Py_XDECREF(plval); + + if (modvalues) + pfree(modvalues); + if (modnulls) + pfree(modnulls); + if (modrepls) + pfree(modrepls); + + PG_RE_THROW(); + } + PG_END_TRY(); + + Py_DECREF(plntup); + Py_DECREF(plkeys); + + pfree(modvalues); + pfree(modnulls); + pfree(modrepls); + + error_context_stack = plerrcontext.previous; + + return rtup; +} + +static void +plpython_trigger_error_callback(void *arg) +{ + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + if (exec_ctx->curr_proc) + errcontext("while modifying trigger row"); +} + +/* execute Python code, propagate Python errors to the backend */ +static PyObject * +PLy_procedure_call(PLyProcedure *proc, const char *kargs, PyObject *vargs) +{ + PyObject *rv = NULL; + int volatile save_subxact_level = list_length(explicit_subtransactions); + + PyDict_SetItemString(proc->globals, kargs, vargs); + + PG_TRY(); + { +#if PY_VERSION_HEX >= 0x03020000 + rv = PyEval_EvalCode(proc->code, + proc->globals, proc->globals); +#else + rv = PyEval_EvalCode((PyCodeObject *) proc->code, + proc->globals, proc->globals); +#endif + + /* + * Since plpy will only let you close subtransactions that you + * started, you cannot *unnest* subtransactions, only *nest* them + * without closing. + */ + Assert(list_length(explicit_subtransactions) >= save_subxact_level); + } + PG_FINALLY(); + { + PLy_abort_open_subtransactions(save_subxact_level); + } + PG_END_TRY(); + + /* If the Python code returned an error, propagate it */ + if (rv == NULL) + PLy_elog(ERROR, NULL); + + return rv; +} + +/* + * Abort lingering subtransactions that have been explicitly started + * by plpy.subtransaction().start() and not properly closed. + */ +static void +PLy_abort_open_subtransactions(int save_subxact_level) +{ + Assert(save_subxact_level >= 0); + + while (list_length(explicit_subtransactions) > save_subxact_level) + { + PLySubtransactionData *subtransactiondata; + + Assert(explicit_subtransactions != NIL); + + ereport(WARNING, + (errmsg("forcibly aborting a subtransaction that has not been exited"))); + + RollbackAndReleaseCurrentSubTransaction(); + + subtransactiondata = (PLySubtransactionData *) linitial(explicit_subtransactions); + explicit_subtransactions = list_delete_first(explicit_subtransactions); + + MemoryContextSwitchTo(subtransactiondata->oldcontext); + CurrentResourceOwner = subtransactiondata->oldowner; + pfree(subtransactiondata); + } +} diff --git a/src/pl/plpython/plpy_exec.h b/src/pl/plpython/plpy_exec.h new file mode 100644 index 0000000..68da1ff --- /dev/null +++ b/src/pl/plpython/plpy_exec.h @@ -0,0 +1,13 @@ +/* + * src/pl/plpython/plpy_exec.h + */ + +#ifndef PLPY_EXEC_H +#define PLPY_EXEC_H + +#include "plpy_procedure.h" + +extern Datum PLy_exec_function(FunctionCallInfo fcinfo, PLyProcedure *proc); +extern HeapTuple PLy_exec_trigger(FunctionCallInfo fcinfo, PLyProcedure *proc); + +#endif /* PLPY_EXEC_H */ diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c new file mode 100644 index 0000000..010a973 --- /dev/null +++ b/src/pl/plpython/plpy_main.c @@ -0,0 +1,419 @@ +/* + * PL/Python main entry points + * + * src/pl/plpython/plpy_main.c + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "commands/trigger.h" +#include "executor/spi.h" +#include "miscadmin.h" +#include "plpy_elog.h" +#include "plpy_exec.h" +#include "plpy_main.h" +#include "plpy_plpymodule.h" +#include "plpy_procedure.h" +#include "plpy_subxactobject.h" +#include "plpython.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include "utils/rel.h" +#include "utils/syscache.h" + +/* + * exported functions + */ + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(plpython3_validator); +PG_FUNCTION_INFO_V1(plpython3_call_handler); +PG_FUNCTION_INFO_V1(plpython3_inline_handler); + + +static bool PLy_procedure_is_trigger(Form_pg_proc procStruct); +static void plpython_error_callback(void *arg); +static void plpython_inline_error_callback(void *arg); +static void PLy_init_interp(void); + +static PLyExecutionContext *PLy_push_execution_context(bool atomic_context); +static void PLy_pop_execution_context(void); + +/* static state for Python library conflict detection */ +static int *plpython_version_bitmask_ptr = NULL; +static int plpython_version_bitmask = 0; + +/* initialize global variables */ +PyObject *PLy_interp_globals = NULL; + +/* this doesn't need to be global; use PLy_current_execution_context() */ +static PLyExecutionContext *PLy_execution_contexts = NULL; + + +void +_PG_init(void) +{ + int **bitmask_ptr; + + /* + * Set up a shared bitmask variable telling which Python version(s) are + * loaded into this process's address space. If there's more than one, we + * cannot call into libpython for fear of causing crashes. But postpone + * the actual failure for later, so that operations like pg_restore can + * load more than one plpython library so long as they don't try to do + * anything much with the language. + * + * While we only support Python 3 these days, somebody might create an + * out-of-tree version adding back support for Python 2. Conflicts with + * such an extension should be detected. + */ + bitmask_ptr = (int **) find_rendezvous_variable("plpython_version_bitmask"); + if (!(*bitmask_ptr)) /* am I the first? */ + *bitmask_ptr = &plpython_version_bitmask; + /* Retain pointer to the agreed-on shared variable ... */ + plpython_version_bitmask_ptr = *bitmask_ptr; + /* ... and announce my presence */ + *plpython_version_bitmask_ptr |= (1 << PY_MAJOR_VERSION); + + /* + * This should be safe even in the presence of conflicting plpythons, and + * it's necessary to do it before possibly throwing a conflict error, or + * the error message won't get localized. + */ + pg_bindtextdomain(TEXTDOMAIN); +} + +/* + * Perform one-time setup of PL/Python, after checking for a conflict + * with other versions of Python. + */ +static void +PLy_initialize(void) +{ + static bool inited = false; + + /* + * Check for multiple Python libraries before actively doing anything with + * libpython. This must be repeated on each entry to PL/Python, in case a + * conflicting library got loaded since we last looked. + * + * It is attractive to weaken this error from FATAL to ERROR, but there + * would be corner cases, so it seems best to be conservative. + */ + if (*plpython_version_bitmask_ptr != (1 << PY_MAJOR_VERSION)) + ereport(FATAL, + (errmsg("multiple Python libraries are present in session"), + errdetail("Only one Python major version can be used in one session."))); + + /* The rest should only be done once per session */ + if (inited) + return; + + PyImport_AppendInittab("plpy", PyInit_plpy); + Py_Initialize(); + PyImport_ImportModule("plpy"); + PLy_init_interp(); + PLy_init_plpy(); + if (PyErr_Occurred()) + PLy_elog(FATAL, "untrapped error in initialization"); + + init_procedure_caches(); + + explicit_subtransactions = NIL; + + PLy_execution_contexts = NULL; + + inited = true; +} + +/* + * This should be called only once, from PLy_initialize. Initialize the Python + * interpreter and global data. + */ +static void +PLy_init_interp(void) +{ + static PyObject *PLy_interp_safe_globals = NULL; + PyObject *mainmod; + + mainmod = PyImport_AddModule("__main__"); + if (mainmod == NULL || PyErr_Occurred()) + PLy_elog(ERROR, "could not import \"__main__\" module"); + Py_INCREF(mainmod); + PLy_interp_globals = PyModule_GetDict(mainmod); + PLy_interp_safe_globals = PyDict_New(); + if (PLy_interp_safe_globals == NULL) + PLy_elog(ERROR, NULL); + PyDict_SetItemString(PLy_interp_globals, "GD", PLy_interp_safe_globals); + Py_DECREF(mainmod); + if (PLy_interp_globals == NULL || PyErr_Occurred()) + PLy_elog(ERROR, "could not initialize globals"); +} + +Datum +plpython3_validator(PG_FUNCTION_ARGS) +{ + Oid funcoid = PG_GETARG_OID(0); + HeapTuple tuple; + Form_pg_proc procStruct; + bool is_trigger; + + if (!CheckFunctionValidatorAccess(fcinfo->flinfo->fn_oid, funcoid)) + PG_RETURN_VOID(); + + if (!check_function_bodies) + PG_RETURN_VOID(); + + /* Do this only after making sure we need to do something */ + PLy_initialize(); + + /* Get the new function's pg_proc entry */ + tuple = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcoid)); + if (!HeapTupleIsValid(tuple)) + elog(ERROR, "cache lookup failed for function %u", funcoid); + procStruct = (Form_pg_proc) GETSTRUCT(tuple); + + is_trigger = PLy_procedure_is_trigger(procStruct); + + ReleaseSysCache(tuple); + + /* We can't validate triggers against any particular table ... */ + PLy_procedure_get(funcoid, InvalidOid, is_trigger); + + PG_RETURN_VOID(); +} + +Datum +plpython3_call_handler(PG_FUNCTION_ARGS) +{ + bool nonatomic; + Datum retval; + PLyExecutionContext *exec_ctx; + ErrorContextCallback plerrcontext; + + PLy_initialize(); + + nonatomic = fcinfo->context && + IsA(fcinfo->context, CallContext) && + !castNode(CallContext, fcinfo->context)->atomic; + + /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */ + if (SPI_connect_ext(nonatomic ? SPI_OPT_NONATOMIC : 0) != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + /* + * Push execution context onto stack. It is important that this get + * popped again, so avoid putting anything that could throw error between + * here and the PG_TRY. + */ + exec_ctx = PLy_push_execution_context(!nonatomic); + + PG_TRY(); + { + Oid funcoid = fcinfo->flinfo->fn_oid; + PLyProcedure *proc; + + /* + * Setup error traceback support for ereport(). Note that the PG_TRY + * structure pops this for us again at exit, so we needn't do that + * explicitly, nor do we risk the callback getting called after we've + * destroyed the exec_ctx. + */ + plerrcontext.callback = plpython_error_callback; + plerrcontext.arg = exec_ctx; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + if (CALLED_AS_TRIGGER(fcinfo)) + { + Relation tgrel = ((TriggerData *) fcinfo->context)->tg_relation; + HeapTuple trv; + + proc = PLy_procedure_get(funcoid, RelationGetRelid(tgrel), true); + exec_ctx->curr_proc = proc; + trv = PLy_exec_trigger(fcinfo, proc); + retval = PointerGetDatum(trv); + } + else + { + proc = PLy_procedure_get(funcoid, InvalidOid, false); + exec_ctx->curr_proc = proc; + retval = PLy_exec_function(fcinfo, proc); + } + } + PG_CATCH(); + { + PLy_pop_execution_context(); + PyErr_Clear(); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* Destroy the execution context */ + PLy_pop_execution_context(); + + return retval; +} + +Datum +plpython3_inline_handler(PG_FUNCTION_ARGS) +{ + LOCAL_FCINFO(fake_fcinfo, 0); + InlineCodeBlock *codeblock = (InlineCodeBlock *) DatumGetPointer(PG_GETARG_DATUM(0)); + FmgrInfo flinfo; + PLyProcedure proc; + PLyExecutionContext *exec_ctx; + ErrorContextCallback plerrcontext; + + PLy_initialize(); + + /* Note: SPI_finish() happens in plpy_exec.c, which is dubious design */ + if (SPI_connect_ext(codeblock->atomic ? 0 : SPI_OPT_NONATOMIC) != SPI_OK_CONNECT) + elog(ERROR, "SPI_connect failed"); + + MemSet(fcinfo, 0, SizeForFunctionCallInfo(0)); + MemSet(&flinfo, 0, sizeof(flinfo)); + fake_fcinfo->flinfo = &flinfo; + flinfo.fn_oid = InvalidOid; + flinfo.fn_mcxt = CurrentMemoryContext; + + MemSet(&proc, 0, sizeof(PLyProcedure)); + proc.mcxt = AllocSetContextCreate(TopMemoryContext, + "__plpython_inline_block", + ALLOCSET_DEFAULT_SIZES); + proc.pyname = MemoryContextStrdup(proc.mcxt, "__plpython_inline_block"); + proc.langid = codeblock->langOid; + + /* + * This is currently sufficient to get PLy_exec_function to work, but + * someday we might need to be honest and use PLy_output_setup_func. + */ + proc.result.typoid = VOIDOID; + + /* + * Push execution context onto stack. It is important that this get + * popped again, so avoid putting anything that could throw error between + * here and the PG_TRY. + */ + exec_ctx = PLy_push_execution_context(codeblock->atomic); + + PG_TRY(); + { + /* + * Setup error traceback support for ereport(). + * plpython_inline_error_callback doesn't currently need exec_ctx, but + * for consistency with plpython3_call_handler we do it the same way. + */ + plerrcontext.callback = plpython_inline_error_callback; + plerrcontext.arg = exec_ctx; + plerrcontext.previous = error_context_stack; + error_context_stack = &plerrcontext; + + PLy_procedure_compile(&proc, codeblock->source_text); + exec_ctx->curr_proc = &proc; + PLy_exec_function(fake_fcinfo, &proc); + } + PG_CATCH(); + { + PLy_pop_execution_context(); + PLy_procedure_delete(&proc); + PyErr_Clear(); + PG_RE_THROW(); + } + PG_END_TRY(); + + /* Destroy the execution context */ + PLy_pop_execution_context(); + + /* Now clean up the transient procedure we made */ + PLy_procedure_delete(&proc); + + PG_RETURN_VOID(); +} + +static bool +PLy_procedure_is_trigger(Form_pg_proc procStruct) +{ + return (procStruct->prorettype == TRIGGEROID); +} + +static void +plpython_error_callback(void *arg) +{ + PLyExecutionContext *exec_ctx = (PLyExecutionContext *) arg; + + if (exec_ctx->curr_proc) + { + if (exec_ctx->curr_proc->is_procedure) + errcontext("PL/Python procedure \"%s\"", + PLy_procedure_name(exec_ctx->curr_proc)); + else + errcontext("PL/Python function \"%s\"", + PLy_procedure_name(exec_ctx->curr_proc)); + } +} + +static void +plpython_inline_error_callback(void *arg) +{ + errcontext("PL/Python anonymous code block"); +} + +PLyExecutionContext * +PLy_current_execution_context(void) +{ + if (PLy_execution_contexts == NULL) + elog(ERROR, "no Python function is currently executing"); + + return PLy_execution_contexts; +} + +MemoryContext +PLy_get_scratch_context(PLyExecutionContext *context) +{ + /* + * A scratch context might never be needed in a given plpython procedure, + * so allocate it on first request. + */ + if (context->scratch_ctx == NULL) + context->scratch_ctx = + AllocSetContextCreate(TopTransactionContext, + "PL/Python scratch context", + ALLOCSET_DEFAULT_SIZES); + return context->scratch_ctx; +} + +static PLyExecutionContext * +PLy_push_execution_context(bool atomic_context) +{ + PLyExecutionContext *context; + + /* Pick a memory context similar to what SPI uses. */ + context = (PLyExecutionContext *) + MemoryContextAlloc(atomic_context ? TopTransactionContext : PortalContext, + sizeof(PLyExecutionContext)); + context->curr_proc = NULL; + context->scratch_ctx = NULL; + context->next = PLy_execution_contexts; + PLy_execution_contexts = context; + return context; +} + +static void +PLy_pop_execution_context(void) +{ + PLyExecutionContext *context = PLy_execution_contexts; + + if (context == NULL) + elog(ERROR, "no Python function is currently executing"); + + PLy_execution_contexts = context->next; + + if (context->scratch_ctx) + MemoryContextDelete(context->scratch_ctx); + pfree(context); +} diff --git a/src/pl/plpython/plpy_main.h b/src/pl/plpython/plpy_main.h new file mode 100644 index 0000000..e8c97c2 --- /dev/null +++ b/src/pl/plpython/plpy_main.h @@ -0,0 +1,31 @@ +/* + * src/pl/plpython/plpy_main.h + */ + +#ifndef PLPY_MAIN_H +#define PLPY_MAIN_H + +#include "plpy_procedure.h" + +/* the interpreter's globals dict */ +extern PyObject *PLy_interp_globals; + +/* + * A stack of PL/Python execution contexts. Each time user-defined Python code + * is called, an execution context is created and put on the stack. After the + * Python code returns, the context is destroyed. + */ +typedef struct PLyExecutionContext +{ + PLyProcedure *curr_proc; /* the currently executing procedure */ + MemoryContext scratch_ctx; /* a context for things like type I/O */ + struct PLyExecutionContext *next; /* previous stack level */ +} PLyExecutionContext; + +/* Get the current execution context */ +extern PLyExecutionContext *PLy_current_execution_context(void); + +/* Get the scratch memory context for specified execution context */ +extern MemoryContext PLy_get_scratch_context(PLyExecutionContext *context); + +#endif /* PLPY_MAIN_H */ diff --git a/src/pl/plpython/plpy_planobject.c b/src/pl/plpython/plpy_planobject.c new file mode 100644 index 0000000..ec2439c --- /dev/null +++ b/src/pl/plpython/plpy_planobject.c @@ -0,0 +1,125 @@ +/* + * the PLyPlan class + * + * src/pl/plpython/plpy_planobject.c + */ + +#include "postgres.h" + +#include "plpy_cursorobject.h" +#include "plpy_elog.h" +#include "plpy_planobject.h" +#include "plpy_spi.h" +#include "plpython.h" +#include "utils/memutils.h" + +static void PLy_plan_dealloc(PyObject *arg); +static PyObject *PLy_plan_cursor(PyObject *self, PyObject *args); +static PyObject *PLy_plan_execute(PyObject *self, PyObject *args); +static PyObject *PLy_plan_status(PyObject *self, PyObject *args); + +static char PLy_plan_doc[] = "Store a PostgreSQL plan"; + +static PyMethodDef PLy_plan_methods[] = { + {"cursor", PLy_plan_cursor, METH_VARARGS, NULL}, + {"execute", PLy_plan_execute, METH_VARARGS, NULL}, + {"status", PLy_plan_status, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_PlanType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "PLyPlan", + .tp_basicsize = sizeof(PLyPlanObject), + .tp_dealloc = PLy_plan_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = PLy_plan_doc, + .tp_methods = PLy_plan_methods, +}; + +void +PLy_plan_init_type(void) +{ + if (PyType_Ready(&PLy_PlanType) < 0) + elog(ERROR, "could not initialize PLy_PlanType"); +} + +PyObject * +PLy_plan_new(void) +{ + PLyPlanObject *ob; + + if ((ob = PyObject_New(PLyPlanObject, &PLy_PlanType)) == NULL) + return NULL; + + ob->plan = NULL; + ob->nargs = 0; + ob->types = NULL; + ob->values = NULL; + ob->args = NULL; + ob->mcxt = NULL; + + return (PyObject *) ob; +} + +bool +is_PLyPlanObject(PyObject *ob) +{ + return ob->ob_type == &PLy_PlanType; +} + +static void +PLy_plan_dealloc(PyObject *arg) +{ + PLyPlanObject *ob = (PLyPlanObject *) arg; + + if (ob->plan) + { + SPI_freeplan(ob->plan); + ob->plan = NULL; + } + if (ob->mcxt) + { + MemoryContextDelete(ob->mcxt); + ob->mcxt = NULL; + } + arg->ob_type->tp_free(arg); +} + + +static PyObject * +PLy_plan_cursor(PyObject *self, PyObject *args) +{ + PyObject *planargs = NULL; + + if (!PyArg_ParseTuple(args, "|O", &planargs)) + return NULL; + + return PLy_cursor_plan(self, planargs); +} + + +static PyObject * +PLy_plan_execute(PyObject *self, PyObject *args) +{ + PyObject *list = NULL; + long limit = 0; + + if (!PyArg_ParseTuple(args, "|Ol", &list, &limit)) + return NULL; + + return PLy_spi_execute_plan(self, list, limit); +} + + +static PyObject * +PLy_plan_status(PyObject *self, PyObject *args) +{ + if (PyArg_ParseTuple(args, ":status")) + { + Py_INCREF(Py_True); + return Py_True; + /* return PyLong_FromLong(self->status); */ + } + return NULL; +} diff --git a/src/pl/plpython/plpy_planobject.h b/src/pl/plpython/plpy_planobject.h new file mode 100644 index 0000000..729effb --- /dev/null +++ b/src/pl/plpython/plpy_planobject.h @@ -0,0 +1,27 @@ +/* + * src/pl/plpython/plpy_planobject.h + */ + +#ifndef PLPY_PLANOBJECT_H +#define PLPY_PLANOBJECT_H + +#include "executor/spi.h" +#include "plpy_typeio.h" + + +typedef struct PLyPlanObject +{ + PyObject_HEAD + SPIPlanPtr plan; + int nargs; + Oid *types; + Datum *values; + PLyObToDatum *args; + MemoryContext mcxt; +} PLyPlanObject; + +extern void PLy_plan_init_type(void); +extern PyObject *PLy_plan_new(void); +extern bool is_PLyPlanObject(PyObject *ob); + +#endif /* PLPY_PLANOBJECT_H */ diff --git a/src/pl/plpython/plpy_plpymodule.c b/src/pl/plpython/plpy_plpymodule.c new file mode 100644 index 0000000..fa08f0d --- /dev/null +++ b/src/pl/plpython/plpy_plpymodule.c @@ -0,0 +1,561 @@ +/* + * the plpy module + * + * src/pl/plpython/plpy_plpymodule.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "mb/pg_wchar.h" +#include "plpy_cursorobject.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_planobject.h" +#include "plpy_plpymodule.h" +#include "plpy_resultobject.h" +#include "plpy_spi.h" +#include "plpy_subxactobject.h" +#include "plpython.h" +#include "utils/builtins.h" +#include "utils/snapmgr.h" + +HTAB *PLy_spi_exceptions = NULL; + + +static void PLy_add_exceptions(PyObject *plpy); +static PyObject *PLy_create_exception(char *name, + PyObject *base, PyObject *dict, + const char *modname, PyObject *mod); +static void PLy_generate_spi_exceptions(PyObject *mod, PyObject *base); + +/* module functions */ +static PyObject *PLy_debug(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_log(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_info(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_notice(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_warning(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_error(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_fatal(PyObject *self, PyObject *args, PyObject *kw); +static PyObject *PLy_quote_literal(PyObject *self, PyObject *args); +static PyObject *PLy_quote_nullable(PyObject *self, PyObject *args); +static PyObject *PLy_quote_ident(PyObject *self, PyObject *args); + + +/* A list of all known exceptions, generated from backend/utils/errcodes.txt */ +typedef struct ExceptionMap +{ + char *name; + char *classname; + int sqlstate; +} ExceptionMap; + +static const ExceptionMap exception_map[] = { +#include "spiexceptions.h" + {NULL, NULL, 0} +}; + +static PyMethodDef PLy_methods[] = { + /* + * logging methods + */ + {"debug", (PyCFunction) (pg_funcptr_t) PLy_debug, METH_VARARGS | METH_KEYWORDS, NULL}, + {"log", (PyCFunction) (pg_funcptr_t) PLy_log, METH_VARARGS | METH_KEYWORDS, NULL}, + {"info", (PyCFunction) (pg_funcptr_t) PLy_info, METH_VARARGS | METH_KEYWORDS, NULL}, + {"notice", (PyCFunction) (pg_funcptr_t) PLy_notice, METH_VARARGS | METH_KEYWORDS, NULL}, + {"warning", (PyCFunction) (pg_funcptr_t) PLy_warning, METH_VARARGS | METH_KEYWORDS, NULL}, + {"error", (PyCFunction) (pg_funcptr_t) PLy_error, METH_VARARGS | METH_KEYWORDS, NULL}, + {"fatal", (PyCFunction) (pg_funcptr_t) PLy_fatal, METH_VARARGS | METH_KEYWORDS, NULL}, + + /* + * create a stored plan + */ + {"prepare", PLy_spi_prepare, METH_VARARGS, NULL}, + + /* + * execute a plan or query + */ + {"execute", PLy_spi_execute, METH_VARARGS, NULL}, + + /* + * escaping strings + */ + {"quote_literal", PLy_quote_literal, METH_VARARGS, NULL}, + {"quote_nullable", PLy_quote_nullable, METH_VARARGS, NULL}, + {"quote_ident", PLy_quote_ident, METH_VARARGS, NULL}, + + /* + * create the subtransaction context manager + */ + {"subtransaction", PLy_subtransaction_new, METH_NOARGS, NULL}, + + /* + * create a cursor + */ + {"cursor", PLy_cursor, METH_VARARGS, NULL}, + + /* + * transaction control + */ + {"commit", PLy_commit, METH_NOARGS, NULL}, + {"rollback", PLy_rollback, METH_NOARGS, NULL}, + + {NULL, NULL, 0, NULL} +}; + +static PyMethodDef PLy_exc_methods[] = { + {NULL, NULL, 0, NULL} +}; + +static PyModuleDef PLy_module = { + PyModuleDef_HEAD_INIT, + .m_name = "plpy", + .m_size = -1, + .m_methods = PLy_methods, +}; + +static PyModuleDef PLy_exc_module = { + PyModuleDef_HEAD_INIT, + .m_name = "spiexceptions", + .m_size = -1, + .m_methods = PLy_exc_methods, +}; + +/* + * Must have external linkage, because PyMODINIT_FUNC does dllexport on + * Windows-like platforms. + */ +PyMODINIT_FUNC +PyInit_plpy(void) +{ + PyObject *m; + + m = PyModule_Create(&PLy_module); + if (m == NULL) + return NULL; + + PLy_add_exceptions(m); + + return m; +} + +void +PLy_init_plpy(void) +{ + PyObject *main_mod, + *main_dict, + *plpy_mod; + + /* + * initialize plpy module + */ + PLy_plan_init_type(); + PLy_result_init_type(); + PLy_subtransaction_init_type(); + PLy_cursor_init_type(); + + PyModule_Create(&PLy_module); + + /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */ + + /* + * initialize main module, and add plpy + */ + main_mod = PyImport_AddModule("__main__"); + main_dict = PyModule_GetDict(main_mod); + plpy_mod = PyImport_AddModule("plpy"); + if (plpy_mod == NULL) + PLy_elog(ERROR, "could not import \"plpy\" module"); + PyDict_SetItemString(main_dict, "plpy", plpy_mod); + if (PyErr_Occurred()) + PLy_elog(ERROR, "could not import \"plpy\" module"); +} + +static void +PLy_add_exceptions(PyObject *plpy) +{ + PyObject *excmod; + HASHCTL hash_ctl; + + excmod = PyModule_Create(&PLy_exc_module); + if (excmod == NULL) + PLy_elog(ERROR, "could not create the spiexceptions module"); + + /* + * PyModule_AddObject does not add a refcount to the object, for some odd + * reason; we must do that. + */ + Py_INCREF(excmod); + if (PyModule_AddObject(plpy, "spiexceptions", excmod) < 0) + PLy_elog(ERROR, "could not add the spiexceptions module"); + + PLy_exc_error = PLy_create_exception("plpy.Error", NULL, NULL, + "Error", plpy); + PLy_exc_fatal = PLy_create_exception("plpy.Fatal", NULL, NULL, + "Fatal", plpy); + PLy_exc_spi_error = PLy_create_exception("plpy.SPIError", NULL, NULL, + "SPIError", plpy); + + hash_ctl.keysize = sizeof(int); + hash_ctl.entrysize = sizeof(PLyExceptionEntry); + PLy_spi_exceptions = hash_create("PL/Python SPI exceptions", 256, + &hash_ctl, HASH_ELEM | HASH_BLOBS); + + PLy_generate_spi_exceptions(excmod, PLy_exc_spi_error); +} + +/* + * Create an exception object and add it to the module + */ +static PyObject * +PLy_create_exception(char *name, PyObject *base, PyObject *dict, + const char *modname, PyObject *mod) +{ + PyObject *exc; + + exc = PyErr_NewException(name, base, dict); + if (exc == NULL) + PLy_elog(ERROR, NULL); + + /* + * PyModule_AddObject does not add a refcount to the object, for some odd + * reason; we must do that. + */ + Py_INCREF(exc); + PyModule_AddObject(mod, modname, exc); + + /* + * The caller will also store a pointer to the exception object in some + * permanent variable, so add another ref to account for that. This is + * probably excessively paranoid, but let's be sure. + */ + Py_INCREF(exc); + return exc; +} + +/* + * Add all the autogenerated exceptions as subclasses of SPIError + */ +static void +PLy_generate_spi_exceptions(PyObject *mod, PyObject *base) +{ + int i; + + for (i = 0; exception_map[i].name != NULL; i++) + { + bool found; + PyObject *exc; + PLyExceptionEntry *entry; + PyObject *sqlstate; + PyObject *dict = PyDict_New(); + + if (dict == NULL) + PLy_elog(ERROR, NULL); + + sqlstate = PLyUnicode_FromString(unpack_sql_state(exception_map[i].sqlstate)); + if (sqlstate == NULL) + PLy_elog(ERROR, "could not generate SPI exceptions"); + + PyDict_SetItemString(dict, "sqlstate", sqlstate); + Py_DECREF(sqlstate); + + exc = PLy_create_exception(exception_map[i].name, base, dict, + exception_map[i].classname, mod); + + entry = hash_search(PLy_spi_exceptions, &exception_map[i].sqlstate, + HASH_ENTER, &found); + Assert(!found); + entry->exc = exc; + } +} + + +/* + * the python interface to the elog function + * don't confuse these with PLy_elog + */ +static PyObject *PLy_output(volatile int level, PyObject *self, + PyObject *args, PyObject *kw); + +static PyObject * +PLy_debug(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(DEBUG2, self, args, kw); +} + +static PyObject * +PLy_log(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(LOG, self, args, kw); +} + +static PyObject * +PLy_info(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(INFO, self, args, kw); +} + +static PyObject * +PLy_notice(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(NOTICE, self, args, kw); +} + +static PyObject * +PLy_warning(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(WARNING, self, args, kw); +} + +static PyObject * +PLy_error(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(ERROR, self, args, kw); +} + +static PyObject * +PLy_fatal(PyObject *self, PyObject *args, PyObject *kw) +{ + return PLy_output(FATAL, self, args, kw); +} + +static PyObject * +PLy_quote_literal(PyObject *self, PyObject *args) +{ + const char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s:quote_literal", &str)) + return NULL; + + quoted = quote_literal_cstr(str); + ret = PLyUnicode_FromString(quoted); + pfree(quoted); + + return ret; +} + +static PyObject * +PLy_quote_nullable(PyObject *self, PyObject *args) +{ + const char *str; + char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "z:quote_nullable", &str)) + return NULL; + + if (str == NULL) + return PLyUnicode_FromString("NULL"); + + quoted = quote_literal_cstr(str); + ret = PLyUnicode_FromString(quoted); + pfree(quoted); + + return ret; +} + +static PyObject * +PLy_quote_ident(PyObject *self, PyObject *args) +{ + const char *str; + const char *quoted; + PyObject *ret; + + if (!PyArg_ParseTuple(args, "s:quote_ident", &str)) + return NULL; + + quoted = quote_identifier(str); + ret = PLyUnicode_FromString(quoted); + + return ret; +} + +/* enforce cast of object to string */ +static char * +object_to_string(PyObject *obj) +{ + if (obj) + { + PyObject *so = PyObject_Str(obj); + + if (so != NULL) + { + char *str; + + str = pstrdup(PLyUnicode_AsString(so)); + Py_DECREF(so); + + return str; + } + } + + return NULL; +} + +static PyObject * +PLy_output(volatile int level, PyObject *self, PyObject *args, PyObject *kw) +{ + int sqlstate = 0; + char *volatile sqlstatestr = NULL; + char *volatile message = NULL; + char *volatile detail = NULL; + char *volatile hint = NULL; + char *volatile column_name = NULL; + char *volatile constraint_name = NULL; + char *volatile datatype_name = NULL; + char *volatile table_name = NULL; + char *volatile schema_name = NULL; + volatile MemoryContext oldcontext; + PyObject *key, + *value; + PyObject *volatile so; + Py_ssize_t pos = 0; + + if (PyTuple_Size(args) == 1) + { + /* + * Treat single argument specially to avoid undesirable ('tuple',) + * decoration. + */ + PyObject *o; + + if (!PyArg_UnpackTuple(args, "plpy.elog", 1, 1, &o)) + PLy_elog(ERROR, "could not unpack arguments in plpy.elog"); + so = PyObject_Str(o); + } + else + so = PyObject_Str(args); + + if (so == NULL || ((message = PLyUnicode_AsString(so)) == NULL)) + { + level = ERROR; + message = dgettext(TEXTDOMAIN, "could not parse error message in plpy.elog"); + } + message = pstrdup(message); + + Py_XDECREF(so); + + if (kw != NULL) + { + while (PyDict_Next(kw, &pos, &key, &value)) + { + char *keyword = PLyUnicode_AsString(key); + + if (strcmp(keyword, "message") == 0) + { + /* the message should not be overwritten */ + if (PyTuple_Size(args) != 0) + { + PLy_exception_set(PyExc_TypeError, "argument 'message' given by name and position"); + return NULL; + } + + if (message) + pfree(message); + message = object_to_string(value); + } + else if (strcmp(keyword, "detail") == 0) + detail = object_to_string(value); + else if (strcmp(keyword, "hint") == 0) + hint = object_to_string(value); + else if (strcmp(keyword, "sqlstate") == 0) + sqlstatestr = object_to_string(value); + else if (strcmp(keyword, "schema_name") == 0) + schema_name = object_to_string(value); + else if (strcmp(keyword, "table_name") == 0) + table_name = object_to_string(value); + else if (strcmp(keyword, "column_name") == 0) + column_name = object_to_string(value); + else if (strcmp(keyword, "datatype_name") == 0) + datatype_name = object_to_string(value); + else if (strcmp(keyword, "constraint_name") == 0) + constraint_name = object_to_string(value); + else + { + PLy_exception_set(PyExc_TypeError, + "'%s' is an invalid keyword argument for this function", + keyword); + return NULL; + } + } + } + + if (sqlstatestr != NULL) + { + if (strlen(sqlstatestr) != 5) + { + PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code"); + return NULL; + } + + if (strspn(sqlstatestr, "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ") != 5) + { + PLy_exception_set(PyExc_ValueError, "invalid SQLSTATE code"); + return NULL; + } + + sqlstate = MAKE_SQLSTATE(sqlstatestr[0], + sqlstatestr[1], + sqlstatestr[2], + sqlstatestr[3], + sqlstatestr[4]); + } + + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + if (message != NULL) + pg_verifymbstr(message, strlen(message), false); + if (detail != NULL) + pg_verifymbstr(detail, strlen(detail), false); + if (hint != NULL) + pg_verifymbstr(hint, strlen(hint), false); + if (schema_name != NULL) + pg_verifymbstr(schema_name, strlen(schema_name), false); + if (table_name != NULL) + pg_verifymbstr(table_name, strlen(table_name), false); + if (column_name != NULL) + pg_verifymbstr(column_name, strlen(column_name), false); + if (datatype_name != NULL) + pg_verifymbstr(datatype_name, strlen(datatype_name), false); + if (constraint_name != NULL) + pg_verifymbstr(constraint_name, strlen(constraint_name), false); + + ereport(level, + ((sqlstate != 0) ? errcode(sqlstate) : 0, + (message != NULL) ? errmsg_internal("%s", message) : 0, + (detail != NULL) ? errdetail_internal("%s", detail) : 0, + (hint != NULL) ? errhint("%s", hint) : 0, + (column_name != NULL) ? + err_generic_string(PG_DIAG_COLUMN_NAME, column_name) : 0, + (constraint_name != NULL) ? + err_generic_string(PG_DIAG_CONSTRAINT_NAME, constraint_name) : 0, + (datatype_name != NULL) ? + err_generic_string(PG_DIAG_DATATYPE_NAME, datatype_name) : 0, + (table_name != NULL) ? + err_generic_string(PG_DIAG_TABLE_NAME, table_name) : 0, + (schema_name != NULL) ? + err_generic_string(PG_DIAG_SCHEMA_NAME, schema_name) : 0)); + } + PG_CATCH(); + { + ErrorData *edata; + + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + PLy_exception_set_with_details(PLy_exc_error, edata); + FreeErrorData(edata); + + return NULL; + } + PG_END_TRY(); + + /* + * return a legal object so the interpreter will continue on its merry way + */ + Py_RETURN_NONE; +} diff --git a/src/pl/plpython/plpy_plpymodule.h b/src/pl/plpython/plpy_plpymodule.h new file mode 100644 index 0000000..1ca3823 --- /dev/null +++ b/src/pl/plpython/plpy_plpymodule.h @@ -0,0 +1,18 @@ +/* + * src/pl/plpython/plpy_plpymodule.h + */ + +#ifndef PLPY_PLPYMODULE_H +#define PLPY_PLPYMODULE_H + +#include "plpython.h" +#include "utils/hsearch.h" + +/* A hash table mapping sqlstates to exceptions, for speedy lookup */ +extern HTAB *PLy_spi_exceptions; + + +PyMODINIT_FUNC PyInit_plpy(void); +extern void PLy_init_plpy(void); + +#endif /* PLPY_PLPYMODULE_H */ diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c new file mode 100644 index 0000000..79b6ef6 --- /dev/null +++ b/src/pl/plpython/plpy_procedure.c @@ -0,0 +1,470 @@ +/* + * Python procedure manipulation for plpython + * + * src/pl/plpython/plpy_procedure.c + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "access/transam.h" +#include "funcapi.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_type.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_procedure.h" +#include "plpython.h" +#include "utils/builtins.h" +#include "utils/hsearch.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/syscache.h" + +static HTAB *PLy_procedure_cache = NULL; + +static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger); +static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup); +static char *PLy_procedure_munge_source(const char *name, const char *src); + + +void +init_procedure_caches(void) +{ + HASHCTL hash_ctl; + + hash_ctl.keysize = sizeof(PLyProcedureKey); + hash_ctl.entrysize = sizeof(PLyProcedureEntry); + PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl, + HASH_ELEM | HASH_BLOBS); +} + +/* + * PLy_procedure_name: get the name of the specified procedure. + * + * NB: this returns the SQL name, not the internal Python procedure name + */ +char * +PLy_procedure_name(PLyProcedure *proc) +{ + if (proc == NULL) + return "<unknown procedure>"; + return proc->proname; +} + +/* + * PLy_procedure_get: returns a cached PLyProcedure, or creates, stores and + * returns a new PLyProcedure. + * + * fn_oid is the OID of the function requested + * fn_rel is InvalidOid or the relation this function triggers on + * is_trigger denotes whether the function is a trigger function + * + * The reason that both fn_rel and is_trigger need to be passed is that when + * trigger functions get validated we don't know which relation(s) they'll + * be used with, so no sensible fn_rel can be passed. + */ +PLyProcedure * +PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger) +{ + bool use_cache = !(is_trigger && fn_rel == InvalidOid); + HeapTuple procTup; + PLyProcedureKey key; + PLyProcedureEntry *volatile entry = NULL; + PLyProcedure *volatile proc = NULL; + bool found = false; + + procTup = SearchSysCache1(PROCOID, ObjectIdGetDatum(fn_oid)); + if (!HeapTupleIsValid(procTup)) + elog(ERROR, "cache lookup failed for function %u", fn_oid); + + /* + * Look for the function in the cache, unless we don't have the necessary + * information (e.g. during validation). In that case we just don't cache + * anything. + */ + if (use_cache) + { + key.fn_oid = fn_oid; + key.fn_rel = fn_rel; + entry = hash_search(PLy_procedure_cache, &key, HASH_ENTER, &found); + proc = entry->proc; + } + + PG_TRY(); + { + if (!found) + { + /* Haven't found it, create a new procedure */ + proc = PLy_procedure_create(procTup, fn_oid, is_trigger); + if (use_cache) + entry->proc = proc; + } + else if (!PLy_procedure_valid(proc, procTup)) + { + /* Found it, but it's invalid, free and reuse the cache entry */ + entry->proc = NULL; + if (proc) + PLy_procedure_delete(proc); + proc = PLy_procedure_create(procTup, fn_oid, is_trigger); + entry->proc = proc; + } + /* Found it and it's valid, it's fine to use it */ + } + PG_CATCH(); + { + /* Do not leave an uninitialized entry in the cache */ + if (use_cache) + hash_search(PLy_procedure_cache, &key, HASH_REMOVE, NULL); + PG_RE_THROW(); + } + PG_END_TRY(); + + ReleaseSysCache(procTup); + + return proc; +} + +/* + * Create a new PLyProcedure structure + */ +static PLyProcedure * +PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger) +{ + char procName[NAMEDATALEN + 256]; + Form_pg_proc procStruct; + PLyProcedure *volatile proc; + MemoryContext cxt; + MemoryContext oldcxt; + int rv; + char *ptr; + + procStruct = (Form_pg_proc) GETSTRUCT(procTup); + rv = snprintf(procName, sizeof(procName), + "__plpython_procedure_%s_%u", + NameStr(procStruct->proname), + fn_oid); + if (rv >= sizeof(procName) || rv < 0) + elog(ERROR, "procedure name would overrun buffer"); + + /* Replace any not-legal-in-Python-names characters with '_' */ + for (ptr = procName; *ptr; ptr++) + { + if (!((*ptr >= 'A' && *ptr <= 'Z') || + (*ptr >= 'a' && *ptr <= 'z') || + (*ptr >= '0' && *ptr <= '9'))) + *ptr = '_'; + } + + /* Create long-lived context that all procedure info will live in */ + cxt = AllocSetContextCreate(TopMemoryContext, + "PL/Python function", + ALLOCSET_DEFAULT_SIZES); + + oldcxt = MemoryContextSwitchTo(cxt); + + proc = (PLyProcedure *) palloc0(sizeof(PLyProcedure)); + proc->mcxt = cxt; + + PG_TRY(); + { + Datum protrftypes_datum; + Datum prosrcdatum; + bool isnull; + char *procSource; + int i; + + proc->proname = pstrdup(NameStr(procStruct->proname)); + MemoryContextSetIdentifier(cxt, proc->proname); + proc->pyname = pstrdup(procName); + proc->fn_xmin = HeapTupleHeaderGetRawXmin(procTup->t_data); + proc->fn_tid = procTup->t_self; + proc->fn_readonly = (procStruct->provolatile != PROVOLATILE_VOLATILE); + proc->is_setof = procStruct->proretset; + proc->is_procedure = (procStruct->prokind == PROKIND_PROCEDURE); + proc->src = NULL; + proc->argnames = NULL; + proc->args = NULL; + proc->nargs = 0; + proc->langid = procStruct->prolang; + protrftypes_datum = SysCacheGetAttr(PROCOID, procTup, + Anum_pg_proc_protrftypes, + &isnull); + proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum); + proc->code = NULL; + proc->statics = NULL; + proc->globals = NULL; + proc->calldepth = 0; + proc->argstack = NULL; + + /* + * get information required for output conversion of the return value, + * but only if this isn't a trigger. + */ + if (!is_trigger) + { + Oid rettype = procStruct->prorettype; + HeapTuple rvTypeTup; + Form_pg_type rvTypeStruct; + + rvTypeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(rettype)); + if (!HeapTupleIsValid(rvTypeTup)) + elog(ERROR, "cache lookup failed for type %u", rettype); + rvTypeStruct = (Form_pg_type) GETSTRUCT(rvTypeTup); + + /* Disallow pseudotype result, except for void or record */ + if (rvTypeStruct->typtype == TYPTYPE_PSEUDO) + { + if (rettype == VOIDOID || + rettype == RECORDOID) + /* okay */ ; + else if (rettype == TRIGGEROID || rettype == EVENT_TRIGGEROID) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("trigger functions can only be called as triggers"))); + else + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/Python functions cannot return type %s", + format_type_be(rettype)))); + } + + /* set up output function for procedure result */ + PLy_output_setup_func(&proc->result, proc->mcxt, + rettype, -1, proc); + + ReleaseSysCache(rvTypeTup); + } + else + { + /* + * In a trigger function, we use proc->result and proc->result_in + * for converting tuples, but we don't yet have enough info to set + * them up. PLy_exec_trigger will deal with it. + */ + proc->result.typoid = InvalidOid; + proc->result_in.typoid = InvalidOid; + } + + /* + * Now get information required for input conversion of the + * procedure's arguments. Note that we ignore output arguments here. + * If the function returns record, those I/O functions will be set up + * when the function is first called. + */ + if (procStruct->pronargs) + { + Oid *types; + char **names, + *modes; + int pos, + total; + + /* extract argument type info from the pg_proc tuple */ + total = get_func_arg_info(procTup, &types, &names, &modes); + + /* count number of in+inout args into proc->nargs */ + if (modes == NULL) + proc->nargs = total; + else + { + /* proc->nargs was initialized to 0 above */ + for (i = 0; i < total; i++) + { + if (modes[i] != PROARGMODE_OUT && + modes[i] != PROARGMODE_TABLE) + (proc->nargs)++; + } + } + + /* Allocate arrays for per-input-argument data */ + proc->argnames = (char **) palloc0(sizeof(char *) * proc->nargs); + proc->args = (PLyDatumToOb *) palloc0(sizeof(PLyDatumToOb) * proc->nargs); + + for (i = pos = 0; i < total; i++) + { + HeapTuple argTypeTup; + Form_pg_type argTypeStruct; + + if (modes && + (modes[i] == PROARGMODE_OUT || + modes[i] == PROARGMODE_TABLE)) + continue; /* skip OUT arguments */ + + Assert(types[i] == procStruct->proargtypes.values[pos]); + + argTypeTup = SearchSysCache1(TYPEOID, + ObjectIdGetDatum(types[i])); + if (!HeapTupleIsValid(argTypeTup)) + elog(ERROR, "cache lookup failed for type %u", types[i]); + argTypeStruct = (Form_pg_type) GETSTRUCT(argTypeTup); + + /* disallow pseudotype arguments */ + if (argTypeStruct->typtype == TYPTYPE_PSEUDO) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("PL/Python functions cannot accept type %s", + format_type_be(types[i])))); + + /* set up I/O function info */ + PLy_input_setup_func(&proc->args[pos], proc->mcxt, + types[i], -1, /* typmod not known */ + proc); + + /* get argument name */ + proc->argnames[pos] = names ? pstrdup(names[i]) : NULL; + + ReleaseSysCache(argTypeTup); + + pos++; + } + } + + /* + * get the text of the function. + */ + prosrcdatum = SysCacheGetAttrNotNull(PROCOID, procTup, + Anum_pg_proc_prosrc); + procSource = TextDatumGetCString(prosrcdatum); + + PLy_procedure_compile(proc, procSource); + + pfree(procSource); + } + PG_CATCH(); + { + MemoryContextSwitchTo(oldcxt); + PLy_procedure_delete(proc); + PG_RE_THROW(); + } + PG_END_TRY(); + + MemoryContextSwitchTo(oldcxt); + return proc; +} + +/* + * Insert the procedure into the Python interpreter + */ +void +PLy_procedure_compile(PLyProcedure *proc, const char *src) +{ + PyObject *crv = NULL; + char *msrc; + + proc->globals = PyDict_Copy(PLy_interp_globals); + + /* + * SD is private preserved data between calls. GD is global data shared by + * all functions + */ + proc->statics = PyDict_New(); + if (!proc->statics) + PLy_elog(ERROR, NULL); + PyDict_SetItemString(proc->globals, "SD", proc->statics); + + /* + * insert the function code into the interpreter + */ + msrc = PLy_procedure_munge_source(proc->pyname, src); + /* Save the mangled source for later inclusion in tracebacks */ + proc->src = MemoryContextStrdup(proc->mcxt, msrc); + crv = PyRun_String(msrc, Py_file_input, proc->globals, NULL); + pfree(msrc); + + if (crv != NULL) + { + int clen; + char call[NAMEDATALEN + 256]; + + Py_DECREF(crv); + + /* + * compile a call to the function + */ + clen = snprintf(call, sizeof(call), "%s()", proc->pyname); + if (clen < 0 || clen >= sizeof(call)) + elog(ERROR, "string would overflow buffer"); + proc->code = Py_CompileString(call, "<string>", Py_eval_input); + if (proc->code != NULL) + return; + } + + if (proc->proname) + PLy_elog(ERROR, "could not compile PL/Python function \"%s\"", + proc->proname); + else + PLy_elog(ERROR, "could not compile anonymous PL/Python code block"); +} + +void +PLy_procedure_delete(PLyProcedure *proc) +{ + Py_XDECREF(proc->code); + Py_XDECREF(proc->statics); + Py_XDECREF(proc->globals); + MemoryContextDelete(proc->mcxt); +} + +/* + * Decide whether a cached PLyProcedure struct is still valid + */ +static bool +PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup) +{ + if (proc == NULL) + return false; + + /* If the pg_proc tuple has changed, it's not valid */ + if (!(proc->fn_xmin == HeapTupleHeaderGetRawXmin(procTup->t_data) && + ItemPointerEquals(&proc->fn_tid, &procTup->t_self))) + return false; + + return true; +} + +static char * +PLy_procedure_munge_source(const char *name, const char *src) +{ + char *mrc, + *mp; + const char *sp; + size_t mlen; + int plen; + + /* + * room for function source and the def statement + */ + mlen = (strlen(src) * 2) + strlen(name) + 16; + + mrc = palloc(mlen); + plen = snprintf(mrc, mlen, "def %s():\n\t", name); + Assert(plen >= 0 && plen < mlen); + + sp = src; + mp = mrc + plen; + + while (*sp != '\0') + { + if (*sp == '\r' && *(sp + 1) == '\n') + sp++; + + if (*sp == '\n' || *sp == '\r') + { + *mp++ = '\n'; + *mp++ = '\t'; + sp++; + } + else + *mp++ = *sp++; + } + *mp++ = '\n'; + *mp++ = '\n'; + *mp = '\0'; + + if (mp > (mrc + mlen)) + elog(FATAL, "buffer overrun in PLy_procedure_munge_source"); + + return mrc; +} diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h new file mode 100644 index 0000000..8968b5c --- /dev/null +++ b/src/pl/plpython/plpy_procedure.h @@ -0,0 +1,70 @@ +/* + * src/pl/plpython/plpy_procedure.h + */ + +#ifndef PLPY_PROCEDURE_H +#define PLPY_PROCEDURE_H + +#include "plpy_typeio.h" + + +extern void init_procedure_caches(void); + + +/* saved arguments for outer recursion level or set-returning function */ +typedef struct PLySavedArgs +{ + struct PLySavedArgs *next; /* linked-list pointer */ + PyObject *args; /* "args" element of globals dict */ + int nargs; /* length of namedargs array */ + PyObject *namedargs[FLEXIBLE_ARRAY_MEMBER]; /* named args */ +} PLySavedArgs; + +/* cached procedure data */ +typedef struct PLyProcedure +{ + MemoryContext mcxt; /* context holding this PLyProcedure and its + * subsidiary data */ + char *proname; /* SQL name of procedure */ + char *pyname; /* Python name of procedure */ + TransactionId fn_xmin; + ItemPointerData fn_tid; + bool fn_readonly; + bool is_setof; /* true, if function returns result set */ + bool is_procedure; + PLyObToDatum result; /* Function result output conversion info */ + PLyDatumToOb result_in; /* For converting input tuples in a trigger */ + char *src; /* textual procedure code, after mangling */ + char **argnames; /* Argument names */ + PLyDatumToOb *args; /* Argument input conversion info */ + int nargs; /* Number of elements in above arrays */ + Oid langid; /* OID of plpython pg_language entry */ + List *trftypes; /* OID list of transform types */ + PyObject *code; /* compiled procedure code */ + PyObject *statics; /* data saved across calls, local scope */ + PyObject *globals; /* data saved across calls, global scope */ + long calldepth; /* depth of recursive calls of function */ + PLySavedArgs *argstack; /* stack of outer-level call arguments */ +} PLyProcedure; + +/* the procedure cache key */ +typedef struct PLyProcedureKey +{ + Oid fn_oid; /* function OID */ + Oid fn_rel; /* triggered-on relation or InvalidOid */ +} PLyProcedureKey; + +/* the procedure cache entry */ +typedef struct PLyProcedureEntry +{ + PLyProcedureKey key; /* hash key */ + PLyProcedure *proc; +} PLyProcedureEntry; + +/* PLyProcedure manipulation */ +extern char *PLy_procedure_name(PLyProcedure *proc); +extern PLyProcedure *PLy_procedure_get(Oid fn_oid, Oid fn_rel, bool is_trigger); +extern void PLy_procedure_compile(PLyProcedure *proc, const char *src); +extern void PLy_procedure_delete(PLyProcedure *proc); + +#endif /* PLPY_PROCEDURE_H */ diff --git a/src/pl/plpython/plpy_resultobject.c b/src/pl/plpython/plpy_resultobject.c new file mode 100644 index 0000000..95acce6 --- /dev/null +++ b/src/pl/plpython/plpy_resultobject.c @@ -0,0 +1,250 @@ +/* + * the PLyResult class + * + * src/pl/plpython/plpy_resultobject.c + */ + +#include "postgres.h" + +#include "plpy_elog.h" +#include "plpy_resultobject.h" +#include "plpython.h" + +static void PLy_result_dealloc(PyObject *arg); +static PyObject *PLy_result_colnames(PyObject *self, PyObject *unused); +static PyObject *PLy_result_coltypes(PyObject *self, PyObject *unused); +static PyObject *PLy_result_coltypmods(PyObject *self, PyObject *unused); +static PyObject *PLy_result_nrows(PyObject *self, PyObject *args); +static PyObject *PLy_result_status(PyObject *self, PyObject *args); +static Py_ssize_t PLy_result_length(PyObject *arg); +static PyObject *PLy_result_item(PyObject *arg, Py_ssize_t idx); +static PyObject *PLy_result_str(PyObject *arg); +static PyObject *PLy_result_subscript(PyObject *arg, PyObject *item); +static int PLy_result_ass_subscript(PyObject *arg, PyObject *item, PyObject *value); + +static char PLy_result_doc[] = "Results of a PostgreSQL query"; + +static PySequenceMethods PLy_result_as_sequence = { + .sq_length = PLy_result_length, + .sq_item = PLy_result_item, +}; + +static PyMappingMethods PLy_result_as_mapping = { + .mp_length = PLy_result_length, + .mp_subscript = PLy_result_subscript, + .mp_ass_subscript = PLy_result_ass_subscript, +}; + +static PyMethodDef PLy_result_methods[] = { + {"colnames", PLy_result_colnames, METH_NOARGS, NULL}, + {"coltypes", PLy_result_coltypes, METH_NOARGS, NULL}, + {"coltypmods", PLy_result_coltypmods, METH_NOARGS, NULL}, + {"nrows", PLy_result_nrows, METH_VARARGS, NULL}, + {"status", PLy_result_status, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_ResultType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "PLyResult", + .tp_basicsize = sizeof(PLyResultObject), + .tp_dealloc = PLy_result_dealloc, + .tp_as_sequence = &PLy_result_as_sequence, + .tp_as_mapping = &PLy_result_as_mapping, + .tp_str = &PLy_result_str, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = PLy_result_doc, + .tp_methods = PLy_result_methods, +}; + +void +PLy_result_init_type(void) +{ + if (PyType_Ready(&PLy_ResultType) < 0) + elog(ERROR, "could not initialize PLy_ResultType"); +} + +PyObject * +PLy_result_new(void) +{ + PLyResultObject *ob; + + if ((ob = PyObject_New(PLyResultObject, &PLy_ResultType)) == NULL) + return NULL; + + /* ob->tuples = NULL; */ + + Py_INCREF(Py_None); + ob->status = Py_None; + ob->nrows = PyLong_FromLong(-1); + ob->rows = PyList_New(0); + ob->tupdesc = NULL; + if (!ob->rows) + { + Py_DECREF(ob); + return NULL; + } + + return (PyObject *) ob; +} + +static void +PLy_result_dealloc(PyObject *arg) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + Py_XDECREF(ob->nrows); + Py_XDECREF(ob->rows); + Py_XDECREF(ob->status); + if (ob->tupdesc) + { + FreeTupleDesc(ob->tupdesc); + ob->tupdesc = NULL; + } + + arg->ob_type->tp_free(arg); +} + +static PyObject * +PLy_result_colnames(PyObject *self, PyObject *unused) +{ + PLyResultObject *ob = (PLyResultObject *) self; + PyObject *list; + int i; + + if (!ob->tupdesc) + { + PLy_exception_set(PLy_exc_error, "command did not produce a result set"); + return NULL; + } + + list = PyList_New(ob->tupdesc->natts); + if (!list) + return NULL; + for (i = 0; i < ob->tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i); + + PyList_SET_ITEM(list, i, PLyUnicode_FromString(NameStr(attr->attname))); + } + + return list; +} + +static PyObject * +PLy_result_coltypes(PyObject *self, PyObject *unused) +{ + PLyResultObject *ob = (PLyResultObject *) self; + PyObject *list; + int i; + + if (!ob->tupdesc) + { + PLy_exception_set(PLy_exc_error, "command did not produce a result set"); + return NULL; + } + + list = PyList_New(ob->tupdesc->natts); + if (!list) + return NULL; + for (i = 0; i < ob->tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i); + + PyList_SET_ITEM(list, i, PyLong_FromLong(attr->atttypid)); + } + + return list; +} + +static PyObject * +PLy_result_coltypmods(PyObject *self, PyObject *unused) +{ + PLyResultObject *ob = (PLyResultObject *) self; + PyObject *list; + int i; + + if (!ob->tupdesc) + { + PLy_exception_set(PLy_exc_error, "command did not produce a result set"); + return NULL; + } + + list = PyList_New(ob->tupdesc->natts); + if (!list) + return NULL; + for (i = 0; i < ob->tupdesc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(ob->tupdesc, i); + + PyList_SET_ITEM(list, i, PyLong_FromLong(attr->atttypmod)); + } + + return list; +} + +static PyObject * +PLy_result_nrows(PyObject *self, PyObject *args) +{ + PLyResultObject *ob = (PLyResultObject *) self; + + Py_INCREF(ob->nrows); + return ob->nrows; +} + +static PyObject * +PLy_result_status(PyObject *self, PyObject *args) +{ + PLyResultObject *ob = (PLyResultObject *) self; + + Py_INCREF(ob->status); + return ob->status; +} + +static Py_ssize_t +PLy_result_length(PyObject *arg) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyList_Size(ob->rows); +} + +static PyObject * +PLy_result_item(PyObject *arg, Py_ssize_t idx) +{ + PyObject *rv; + PLyResultObject *ob = (PLyResultObject *) arg; + + rv = PyList_GetItem(ob->rows, idx); + if (rv != NULL) + Py_INCREF(rv); + return rv; +} + +static PyObject * +PLy_result_str(PyObject *arg) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyUnicode_FromFormat("<%s status=%S nrows=%S rows=%S>", + Py_TYPE(ob)->tp_name, + ob->status, + ob->nrows, + ob->rows); +} + +static PyObject * +PLy_result_subscript(PyObject *arg, PyObject *item) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyObject_GetItem(ob->rows, item); +} + +static int +PLy_result_ass_subscript(PyObject *arg, PyObject *item, PyObject *value) +{ + PLyResultObject *ob = (PLyResultObject *) arg; + + return PyObject_SetItem(ob->rows, item, value); +} diff --git a/src/pl/plpython/plpy_resultobject.h b/src/pl/plpython/plpy_resultobject.h new file mode 100644 index 0000000..978c4cc --- /dev/null +++ b/src/pl/plpython/plpy_resultobject.h @@ -0,0 +1,27 @@ +/* + * src/pl/plpython/plpy_resultobject.h + */ + +#ifndef PLPY_RESULTOBJECT_H +#define PLPY_RESULTOBJECT_H + +#include "access/tupdesc.h" + +#include "plpython.h" + + +typedef struct PLyResultObject +{ + PyObject_HEAD + /* HeapTuple *tuples; */ + PyObject *nrows; /* number of rows returned by query */ + PyObject *rows; /* data rows, or empty list if no data + * returned */ + PyObject *status; /* query status, SPI_OK_*, or SPI_ERR_* */ + TupleDesc tupdesc; +} PLyResultObject; + +extern void PLy_result_init_type(void); +extern PyObject *PLy_result_new(void); + +#endif /* PLPY_RESULTOBJECT_H */ diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c new file mode 100644 index 0000000..ff87b27 --- /dev/null +++ b/src/pl/plpython/plpy_spi.c @@ -0,0 +1,666 @@ +/* + * interface to SPI functions + * + * src/pl/plpython/plpy_spi.c + */ + +#include "postgres.h" + +#include <limits.h> + +#include "access/htup_details.h" +#include "access/xact.h" +#include "catalog/pg_type.h" +#include "executor/spi.h" +#include "mb/pg_wchar.h" +#include "parser/parse_type.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_planobject.h" +#include "plpy_plpymodule.h" +#include "plpy_procedure.h" +#include "plpy_resultobject.h" +#include "plpy_spi.h" +#include "plpython.h" +#include "utils/memutils.h" +#include "utils/syscache.h" + +static PyObject *PLy_spi_execute_query(char *query, long limit); +static PyObject *PLy_spi_execute_fetch_result(SPITupleTable *tuptable, + uint64 rows, int status); +static void PLy_spi_exception_set(PyObject *excclass, ErrorData *edata); + + +/* prepare(query="select * from foo") + * prepare(query="select * from foo where bar = $1", params=["text"]) + * prepare(query="select * from foo where bar = $1", params=["text"], limit=5) + */ +PyObject * +PLy_spi_prepare(PyObject *self, PyObject *args) +{ + PLyPlanObject *plan; + PyObject *list = NULL; + PyObject *volatile optr = NULL; + char *query; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + volatile int nargs; + + if (!PyArg_ParseTuple(args, "s|O:prepare", &query, &list)) + return NULL; + + if (list && (!PySequence_Check(list))) + { + PLy_exception_set(PyExc_TypeError, + "second argument of plpy.prepare must be a sequence"); + return NULL; + } + + if ((plan = (PLyPlanObject *) PLy_plan_new()) == NULL) + return NULL; + + plan->mcxt = AllocSetContextCreate(TopMemoryContext, + "PL/Python plan context", + ALLOCSET_DEFAULT_SIZES); + oldcontext = MemoryContextSwitchTo(plan->mcxt); + + nargs = list ? PySequence_Length(list) : 0; + + plan->nargs = nargs; + plan->types = nargs ? palloc0(sizeof(Oid) * nargs) : NULL; + plan->values = nargs ? palloc0(sizeof(Datum) * nargs) : NULL; + plan->args = nargs ? palloc0(sizeof(PLyObToDatum) * nargs) : NULL; + + MemoryContextSwitchTo(oldcontext); + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + int i; + + for (i = 0; i < nargs; i++) + { + char *sptr; + Oid typeId; + int32 typmod; + + optr = PySequence_GetItem(list, i); + if (PyUnicode_Check(optr)) + sptr = PLyUnicode_AsString(optr); + else + { + ereport(ERROR, + (errmsg("plpy.prepare: type name at ordinal position %d is not a string", i))); + sptr = NULL; /* keep compiler quiet */ + } + + /******************************************************** + * Resolve argument type names and then look them up by + * oid in the system cache, and remember the required + *information for input conversion. + ********************************************************/ + + (void) parseTypeString(sptr, &typeId, &typmod, NULL); + + Py_DECREF(optr); + + /* + * set optr to NULL, so we won't try to unref it again in case of + * an error + */ + optr = NULL; + + plan->types[i] = typeId; + PLy_output_setup_func(&plan->args[i], plan->mcxt, + typeId, typmod, + exec_ctx->curr_proc); + } + + pg_verifymbstr(query, strlen(query), false); + plan->plan = SPI_prepare(query, plan->nargs, plan->types); + if (plan->plan == NULL) + elog(ERROR, "SPI_prepare failed: %s", + SPI_result_code_string(SPI_result)); + + /* transfer plan from procCxt to topCxt */ + if (SPI_keepplan(plan->plan)) + elog(ERROR, "SPI_keepplan failed"); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + Py_DECREF(plan); + Py_XDECREF(optr); + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + Assert(plan->plan != NULL); + return (PyObject *) plan; +} + +/* execute(query="select * from foo", limit=5) + * execute(plan=plan, values=(foo, bar), limit=5) + */ +PyObject * +PLy_spi_execute(PyObject *self, PyObject *args) +{ + char *query; + PyObject *plan; + PyObject *list = NULL; + long limit = 0; + + if (PyArg_ParseTuple(args, "s|l", &query, &limit)) + return PLy_spi_execute_query(query, limit); + + PyErr_Clear(); + + if (PyArg_ParseTuple(args, "O|Ol", &plan, &list, &limit) && + is_PLyPlanObject(plan)) + return PLy_spi_execute_plan(plan, list, limit); + + PLy_exception_set(PLy_exc_error, "plpy.execute expected a query or a plan"); + return NULL; +} + +PyObject * +PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit) +{ + volatile int nargs; + int i, + rv; + PLyPlanObject *plan; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + PyObject *ret; + + if (list != NULL) + { + if (!PySequence_Check(list) || PyUnicode_Check(list)) + { + PLy_exception_set(PyExc_TypeError, "plpy.execute takes a sequence as its second argument"); + return NULL; + } + nargs = PySequence_Length(list); + } + else + nargs = 0; + + plan = (PLyPlanObject *) ob; + + if (nargs != plan->nargs) + { + char *sv; + PyObject *so = PyObject_Str(list); + + if (!so) + PLy_elog(ERROR, "could not execute plan"); + sv = PLyUnicode_AsString(so); + PLy_exception_set_plural(PyExc_TypeError, + "Expected sequence of %d argument, got %d: %s", + "Expected sequence of %d arguments, got %d: %s", + plan->nargs, + plan->nargs, nargs, sv); + Py_DECREF(so); + + return NULL; + } + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + char *volatile nulls; + volatile int j; + + if (nargs > 0) + nulls = palloc(nargs * sizeof(char)); + else + nulls = NULL; + + for (j = 0; j < nargs; j++) + { + PLyObToDatum *arg = &plan->args[j]; + PyObject *elem; + + elem = PySequence_GetItem(list, j); + PG_TRY(2); + { + bool isnull; + + plan->values[j] = PLy_output_convert(arg, elem, &isnull); + nulls[j] = isnull ? 'n' : ' '; + } + PG_FINALLY(2); + { + Py_DECREF(elem); + } + PG_END_TRY(2); + } + + rv = SPI_execute_plan(plan->plan, plan->values, nulls, + exec_ctx->curr_proc->fn_readonly, limit); + ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); + + if (nargs > 0) + pfree(nulls); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + int k; + + /* + * cleanup plan->values array + */ + for (k = 0; k < nargs; k++) + { + if (!plan->args[k].typbyval && + (plan->values[k] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[k])); + plan->values[k] = PointerGetDatum(NULL); + } + } + + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + for (i = 0; i < nargs; i++) + { + if (!plan->args[i].typbyval && + (plan->values[i] != PointerGetDatum(NULL))) + { + pfree(DatumGetPointer(plan->values[i])); + plan->values[i] = PointerGetDatum(NULL); + } + } + + if (rv < 0) + { + PLy_exception_set(PLy_exc_spi_error, + "SPI_execute_plan failed: %s", + SPI_result_code_string(rv)); + return NULL; + } + + return ret; +} + +static PyObject * +PLy_spi_execute_query(char *query, long limit) +{ + int rv; + volatile MemoryContext oldcontext; + volatile ResourceOwner oldowner; + PyObject *ret = NULL; + + oldcontext = CurrentMemoryContext; + oldowner = CurrentResourceOwner; + + PLy_spi_subtransaction_begin(oldcontext, oldowner); + + PG_TRY(); + { + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + pg_verifymbstr(query, strlen(query), false); + rv = SPI_execute(query, exec_ctx->curr_proc->fn_readonly, limit); + ret = PLy_spi_execute_fetch_result(SPI_tuptable, SPI_processed, rv); + + PLy_spi_subtransaction_commit(oldcontext, oldowner); + } + PG_CATCH(); + { + PLy_spi_subtransaction_abort(oldcontext, oldowner); + return NULL; + } + PG_END_TRY(); + + if (rv < 0) + { + Py_XDECREF(ret); + PLy_exception_set(PLy_exc_spi_error, + "SPI_execute failed: %s", + SPI_result_code_string(rv)); + return NULL; + } + + return ret; +} + +static PyObject * +PLy_spi_execute_fetch_result(SPITupleTable *tuptable, uint64 rows, int status) +{ + PLyResultObject *result; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + volatile MemoryContext oldcontext; + + result = (PLyResultObject *) PLy_result_new(); + if (!result) + { + SPI_freetuptable(tuptable); + return NULL; + } + Py_DECREF(result->status); + result->status = PyLong_FromLong(status); + + if (status > 0 && tuptable == NULL) + { + Py_DECREF(result->nrows); + result->nrows = PyLong_FromUnsignedLongLong(rows); + } + else if (status > 0 && tuptable != NULL) + { + PLyDatumToOb ininfo; + MemoryContext cxt; + + Py_DECREF(result->nrows); + result->nrows = PyLong_FromUnsignedLongLong(rows); + + cxt = AllocSetContextCreate(CurrentMemoryContext, + "PL/Python temp context", + ALLOCSET_DEFAULT_SIZES); + + /* Initialize for converting result tuples to Python */ + PLy_input_setup_func(&ininfo, cxt, RECORDOID, -1, + exec_ctx->curr_proc); + + oldcontext = CurrentMemoryContext; + PG_TRY(); + { + MemoryContext oldcontext2; + + if (rows) + { + uint64 i; + + /* + * PyList_New() and PyList_SetItem() use Py_ssize_t for list + * size and list indices; so we cannot support a result larger + * than PY_SSIZE_T_MAX. + */ + if (rows > (uint64) PY_SSIZE_T_MAX) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("query result has too many rows to fit in a Python list"))); + + Py_DECREF(result->rows); + result->rows = PyList_New(rows); + if (result->rows) + { + PLy_input_setup_tuple(&ininfo, tuptable->tupdesc, + exec_ctx->curr_proc); + + for (i = 0; i < rows; i++) + { + PyObject *row = PLy_input_from_tuple(&ininfo, + tuptable->vals[i], + tuptable->tupdesc, + true); + + PyList_SetItem(result->rows, i, row); + } + } + } + + /* + * Save tuple descriptor for later use by result set metadata + * functions. Save it in TopMemoryContext so that it survives + * outside of an SPI context. We trust that PLy_result_dealloc() + * will clean it up when the time is right. (Do this as late as + * possible, to minimize the number of ways the tupdesc could get + * leaked due to errors.) + */ + oldcontext2 = MemoryContextSwitchTo(TopMemoryContext); + result->tupdesc = CreateTupleDescCopy(tuptable->tupdesc); + MemoryContextSwitchTo(oldcontext2); + } + PG_CATCH(); + { + MemoryContextSwitchTo(oldcontext); + MemoryContextDelete(cxt); + Py_DECREF(result); + PG_RE_THROW(); + } + PG_END_TRY(); + + MemoryContextDelete(cxt); + SPI_freetuptable(tuptable); + + /* in case PyList_New() failed above */ + if (!result->rows) + { + Py_DECREF(result); + result = NULL; + } + } + + return (PyObject *) result; +} + +PyObject * +PLy_commit(PyObject *self, PyObject *args) +{ + MemoryContext oldcontext = CurrentMemoryContext; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + PG_TRY(); + { + SPI_commit(); + + /* was cleared at transaction end, reset pointer */ + exec_ctx->scratch_ctx = NULL; + } + PG_CATCH(); + { + ErrorData *edata; + PLyExceptionEntry *entry; + PyObject *exc; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* was cleared at transaction end, reset pointer */ + exec_ctx->scratch_ctx = NULL; + + /* Look up the correct exception */ + entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode), + HASH_FIND, NULL); + + /* + * This could be a custom error code, if that's the case fallback to + * SPIError + */ + exc = entry ? entry->exc : PLy_exc_spi_error; + /* Make Python raise the exception */ + PLy_spi_exception_set(exc, edata); + FreeErrorData(edata); + + return NULL; + } + PG_END_TRY(); + + Py_RETURN_NONE; +} + +PyObject * +PLy_rollback(PyObject *self, PyObject *args) +{ + MemoryContext oldcontext = CurrentMemoryContext; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + + PG_TRY(); + { + SPI_rollback(); + + /* was cleared at transaction end, reset pointer */ + exec_ctx->scratch_ctx = NULL; + } + PG_CATCH(); + { + ErrorData *edata; + PLyExceptionEntry *entry; + PyObject *exc; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* was cleared at transaction end, reset pointer */ + exec_ctx->scratch_ctx = NULL; + + /* Look up the correct exception */ + entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode), + HASH_FIND, NULL); + + /* + * This could be a custom error code, if that's the case fallback to + * SPIError + */ + exc = entry ? entry->exc : PLy_exc_spi_error; + /* Make Python raise the exception */ + PLy_spi_exception_set(exc, edata); + FreeErrorData(edata); + + return NULL; + } + PG_END_TRY(); + + Py_RETURN_NONE; +} + +/* + * Utilities for running SPI functions in subtransactions. + * + * Usage: + * + * MemoryContext oldcontext = CurrentMemoryContext; + * ResourceOwner oldowner = CurrentResourceOwner; + * + * PLy_spi_subtransaction_begin(oldcontext, oldowner); + * PG_TRY(); + * { + * <call SPI functions> + * PLy_spi_subtransaction_commit(oldcontext, oldowner); + * } + * PG_CATCH(); + * { + * <do cleanup> + * PLy_spi_subtransaction_abort(oldcontext, oldowner); + * return NULL; + * } + * PG_END_TRY(); + * + * These utilities take care of restoring connection to the SPI manager and + * setting a Python exception in case of an abort. + */ +void +PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner) +{ + BeginInternalSubTransaction(NULL); + /* Want to run inside function's memory context */ + MemoryContextSwitchTo(oldcontext); +} + +void +PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner) +{ + /* Commit the inner transaction, return to outer xact context */ + ReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; +} + +void +PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner) +{ + ErrorData *edata; + PLyExceptionEntry *entry; + PyObject *exc; + + /* Save error info */ + MemoryContextSwitchTo(oldcontext); + edata = CopyErrorData(); + FlushErrorState(); + + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + MemoryContextSwitchTo(oldcontext); + CurrentResourceOwner = oldowner; + + /* Look up the correct exception */ + entry = hash_search(PLy_spi_exceptions, &(edata->sqlerrcode), + HASH_FIND, NULL); + + /* + * This could be a custom error code, if that's the case fallback to + * SPIError + */ + exc = entry ? entry->exc : PLy_exc_spi_error; + /* Make Python raise the exception */ + PLy_spi_exception_set(exc, edata); + FreeErrorData(edata); +} + +/* + * Raise a SPIError, passing in it more error details, like the + * internal query and error position. + */ +static void +PLy_spi_exception_set(PyObject *excclass, ErrorData *edata) +{ + PyObject *args = NULL; + PyObject *spierror = NULL; + PyObject *spidata = NULL; + + args = Py_BuildValue("(s)", edata->message); + if (!args) + goto failure; + + /* create a new SPI exception with the error message as the parameter */ + spierror = PyObject_CallObject(excclass, args); + if (!spierror) + goto failure; + + spidata = Py_BuildValue("(izzzizzzzz)", edata->sqlerrcode, edata->detail, edata->hint, + edata->internalquery, edata->internalpos, + edata->schema_name, edata->table_name, edata->column_name, + edata->datatype_name, edata->constraint_name); + if (!spidata) + goto failure; + + if (PyObject_SetAttrString(spierror, "spidata", spidata) == -1) + goto failure; + + PyErr_SetObject(excclass, spierror); + + Py_DECREF(args); + Py_DECREF(spierror); + Py_DECREF(spidata); + return; + +failure: + Py_XDECREF(args); + Py_XDECREF(spierror); + Py_XDECREF(spidata); + elog(ERROR, "could not convert SPI error to Python exception"); +} diff --git a/src/pl/plpython/plpy_spi.h b/src/pl/plpython/plpy_spi.h new file mode 100644 index 0000000..98ccd21 --- /dev/null +++ b/src/pl/plpython/plpy_spi.h @@ -0,0 +1,29 @@ +/* + * src/pl/plpython/plpy_spi.h + */ + +#ifndef PLPY_SPI_H +#define PLPY_SPI_H + +#include "plpython.h" +#include "utils/resowner.h" + +extern PyObject *PLy_spi_prepare(PyObject *self, PyObject *args); +extern PyObject *PLy_spi_execute(PyObject *self, PyObject *args); +extern PyObject *PLy_spi_execute_plan(PyObject *ob, PyObject *list, long limit); + +extern PyObject *PLy_commit(PyObject *self, PyObject *args); +extern PyObject *PLy_rollback(PyObject *self, PyObject *args); + +typedef struct PLyExceptionEntry +{ + int sqlstate; /* hash key, must be first */ + PyObject *exc; /* corresponding exception */ +} PLyExceptionEntry; + +/* handling of SPI operations inside subtransactions */ +extern void PLy_spi_subtransaction_begin(MemoryContext oldcontext, ResourceOwner oldowner); +extern void PLy_spi_subtransaction_commit(MemoryContext oldcontext, ResourceOwner oldowner); +extern void PLy_spi_subtransaction_abort(MemoryContext oldcontext, ResourceOwner oldowner); + +#endif /* PLPY_SPI_H */ diff --git a/src/pl/plpython/plpy_subxactobject.c b/src/pl/plpython/plpy_subxactobject.c new file mode 100644 index 0000000..5c92a0e --- /dev/null +++ b/src/pl/plpython/plpy_subxactobject.c @@ -0,0 +1,186 @@ +/* + * the PLySubtransaction class + * + * src/pl/plpython/plpy_subxactobject.c + */ + +#include "postgres.h" + +#include "access/xact.h" +#include "plpy_elog.h" +#include "plpy_subxactobject.h" +#include "plpython.h" +#include "utils/memutils.h" + +List *explicit_subtransactions = NIL; + + +static void PLy_subtransaction_dealloc(PyObject *subxact); +static PyObject *PLy_subtransaction_enter(PyObject *self, PyObject *unused); +static PyObject *PLy_subtransaction_exit(PyObject *self, PyObject *args); + +static char PLy_subtransaction_doc[] = +"PostgreSQL subtransaction context manager"; + +static PyMethodDef PLy_subtransaction_methods[] = { + {"__enter__", PLy_subtransaction_enter, METH_VARARGS, NULL}, + {"__exit__", PLy_subtransaction_exit, METH_VARARGS, NULL}, + /* user-friendly names for Python <2.6 */ + {"enter", PLy_subtransaction_enter, METH_VARARGS, NULL}, + {"exit", PLy_subtransaction_exit, METH_VARARGS, NULL}, + {NULL, NULL, 0, NULL} +}; + +static PyTypeObject PLy_SubtransactionType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "PLySubtransaction", + .tp_basicsize = sizeof(PLySubtransactionObject), + .tp_dealloc = PLy_subtransaction_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = PLy_subtransaction_doc, + .tp_methods = PLy_subtransaction_methods, +}; + + +void +PLy_subtransaction_init_type(void) +{ + if (PyType_Ready(&PLy_SubtransactionType) < 0) + elog(ERROR, "could not initialize PLy_SubtransactionType"); +} + +/* s = plpy.subtransaction() */ +PyObject * +PLy_subtransaction_new(PyObject *self, PyObject *unused) +{ + PLySubtransactionObject *ob; + + ob = PyObject_New(PLySubtransactionObject, &PLy_SubtransactionType); + + if (ob == NULL) + return NULL; + + ob->started = false; + ob->exited = false; + + return (PyObject *) ob; +} + +/* Python requires a dealloc function to be defined */ +static void +PLy_subtransaction_dealloc(PyObject *subxact) +{ +} + +/* + * subxact.__enter__() or subxact.enter() + * + * Start an explicit subtransaction. SPI calls within an explicit + * subtransaction will not start another one, so you can atomically + * execute many SPI calls and still get a controllable exception if + * one of them fails. + */ +static PyObject * +PLy_subtransaction_enter(PyObject *self, PyObject *unused) +{ + PLySubtransactionData *subxactdata; + MemoryContext oldcontext; + PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; + + if (subxact->started) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been entered"); + return NULL; + } + + if (subxact->exited) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); + return NULL; + } + + subxact->started = true; + oldcontext = CurrentMemoryContext; + + subxactdata = (PLySubtransactionData *) + MemoryContextAlloc(TopTransactionContext, + sizeof(PLySubtransactionData)); + + subxactdata->oldcontext = oldcontext; + subxactdata->oldowner = CurrentResourceOwner; + + BeginInternalSubTransaction(NULL); + + /* Be sure that cells of explicit_subtransactions list are long-lived */ + MemoryContextSwitchTo(TopTransactionContext); + explicit_subtransactions = lcons(subxactdata, explicit_subtransactions); + + /* Caller wants to stay in original memory context */ + MemoryContextSwitchTo(oldcontext); + + Py_INCREF(self); + return self; +} + +/* + * subxact.__exit__(exc_type, exc, tb) or subxact.exit(exc_type, exc, tb) + * + * Exit an explicit subtransaction. exc_type is an exception type, exc + * is the exception object, tb is the traceback. If exc_type is None, + * commit the subtransaction, if not abort it. + * + * The method signature is chosen to allow subtransaction objects to + * be used as context managers as described in + * <http://www.python.org/dev/peps/pep-0343/>. + */ +static PyObject * +PLy_subtransaction_exit(PyObject *self, PyObject *args) +{ + PyObject *type; + PyObject *value; + PyObject *traceback; + PLySubtransactionData *subxactdata; + PLySubtransactionObject *subxact = (PLySubtransactionObject *) self; + + if (!PyArg_ParseTuple(args, "OOO", &type, &value, &traceback)) + return NULL; + + if (!subxact->started) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has not been entered"); + return NULL; + } + + if (subxact->exited) + { + PLy_exception_set(PyExc_ValueError, "this subtransaction has already been exited"); + return NULL; + } + + if (explicit_subtransactions == NIL) + { + PLy_exception_set(PyExc_ValueError, "there is no subtransaction to exit from"); + return NULL; + } + + subxact->exited = true; + + if (type != Py_None) + { + /* Abort the inner transaction */ + RollbackAndReleaseCurrentSubTransaction(); + } + else + { + ReleaseCurrentSubTransaction(); + } + + subxactdata = (PLySubtransactionData *) linitial(explicit_subtransactions); + explicit_subtransactions = list_delete_first(explicit_subtransactions); + + MemoryContextSwitchTo(subxactdata->oldcontext); + CurrentResourceOwner = subxactdata->oldowner; + pfree(subxactdata); + + Py_RETURN_NONE; +} diff --git a/src/pl/plpython/plpy_subxactobject.h b/src/pl/plpython/plpy_subxactobject.h new file mode 100644 index 0000000..5b6f863 --- /dev/null +++ b/src/pl/plpython/plpy_subxactobject.h @@ -0,0 +1,33 @@ +/* + * src/pl/plpython/plpy_subxactobject.h + */ + +#ifndef PLPY_SUBXACTOBJECT +#define PLPY_SUBXACTOBJECT + +#include "nodes/pg_list.h" +#include "plpython.h" +#include "utils/resowner.h" + +/* a list of nested explicit subtransactions */ +extern List *explicit_subtransactions; + + +typedef struct PLySubtransactionObject +{ + PyObject_HEAD + bool started; + bool exited; +} PLySubtransactionObject; + +/* explicit subtransaction data */ +typedef struct PLySubtransactionData +{ + MemoryContext oldcontext; + ResourceOwner oldowner; +} PLySubtransactionData; + +extern void PLy_subtransaction_init_type(void); +extern PyObject *PLy_subtransaction_new(PyObject *self, PyObject *unused); + +#endif /* PLPY_SUBXACTOBJECT */ diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c new file mode 100644 index 0000000..db14c5f --- /dev/null +++ b/src/pl/plpython/plpy_typeio.c @@ -0,0 +1,1557 @@ +/* + * transforming Datums to Python objects and vice versa + * + * src/pl/plpython/plpy_typeio.c + */ + +#include "postgres.h" + +#include "access/htup_details.h" +#include "catalog/pg_type.h" +#include "funcapi.h" +#include "mb/pg_wchar.h" +#include "miscadmin.h" +#include "plpy_elog.h" +#include "plpy_main.h" +#include "plpy_typeio.h" +#include "plpython.h" +#include "utils/array.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" + +/* conversion from Datums to Python objects */ +static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d); +static PyObject *PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d); +static PyObject *PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d); +static PyObject *PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d); +static PyObject *PLyLong_FromInt16(PLyDatumToOb *arg, Datum d); +static PyObject *PLyLong_FromInt32(PLyDatumToOb *arg, Datum d); +static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d); +static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d); +static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d); +static PyObject *PLyUnicode_FromScalar(PLyDatumToOb *arg, Datum d); +static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d); +static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); +static PyObject *PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, + char **dataptr_p, bits8 **bitmap_p, int *bitmask_p); +static PyObject *PLyDict_FromComposite(PLyDatumToOb *arg, Datum d); +static PyObject *PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated); + +/* conversion from Python objects to Datums */ +static Datum PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static Datum PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray); +static void PLySequence_ToArray_recurse(PyObject *obj, + ArrayBuildState **astatep, + int *ndims, int *dims, int cur_depth, + PLyObToDatum *elm, Oid elmbasetype); + +/* conversion from Python objects to composite Datums */ +static Datum PLyUnicode_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray); +static Datum PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping); +static Datum PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence); +static Datum PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray); + + +/* + * Conversion functions. Remember output from Python is input to + * PostgreSQL, and vice versa. + */ + +/* + * Perform input conversion, given correctly-set-up state information. + * + * This is the outer-level entry point for any input conversion. Internally, + * the conversion functions recurse directly to each other. + */ +PyObject * +PLy_input_convert(PLyDatumToOb *arg, Datum val) +{ + PyObject *result; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx); + MemoryContext oldcontext; + + /* + * Do the work in the scratch context to avoid leaking memory from the + * datatype output function calls. (The individual PLyDatumToObFunc + * functions can't reset the scratch context, because they recurse and an + * inner one might clobber data an outer one still needs. So we do it + * once at the outermost recursion level.) + * + * We reset the scratch context before, not after, each conversion cycle. + * This way we aren't on the hook to release a Python refcount on the + * result object in case MemoryContextReset throws an error. + */ + MemoryContextReset(scratch_context); + + oldcontext = MemoryContextSwitchTo(scratch_context); + + result = arg->func(arg, val); + + MemoryContextSwitchTo(oldcontext); + + return result; +} + +/* + * Perform output conversion, given correctly-set-up state information. + * + * This is the outer-level entry point for any output conversion. Internally, + * the conversion functions recurse directly to each other. + * + * The result, as well as any cruft generated along the way, are in the + * current memory context. Caller is responsible for cleanup. + */ +Datum +PLy_output_convert(PLyObToDatum *arg, PyObject *val, bool *isnull) +{ + /* at outer level, we are not considering an array element */ + return arg->func(arg, val, isnull, false); +} + +/* + * Transform a tuple into a Python dict object. + * + * Note: the tupdesc must match the one used to set up *arg. We could + * insist that this function lookup the tupdesc from what is in *arg, + * but in practice all callers have the right tupdesc available. + */ +PyObject * +PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) +{ + PyObject *dict; + PLyExecutionContext *exec_ctx = PLy_current_execution_context(); + MemoryContext scratch_context = PLy_get_scratch_context(exec_ctx); + MemoryContext oldcontext; + + /* + * As in PLy_input_convert, do the work in the scratch context. + */ + MemoryContextReset(scratch_context); + + oldcontext = MemoryContextSwitchTo(scratch_context); + + dict = PLyDict_FromTuple(arg, tuple, desc, include_generated); + + MemoryContextSwitchTo(oldcontext); + + return dict; +} + +/* + * Initialize, or re-initialize, per-column input info for a composite type. + * + * This is separate from PLy_input_setup_func() because in cases involving + * anonymous record types, we need to be passed the tupdesc explicitly. + * It's caller's responsibility that the tupdesc has adequate lifespan + * in such cases. If the tupdesc is for a named composite or registered + * record type, it does not need to be long-lived. + */ +void +PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, PLyProcedure *proc) +{ + int i; + + /* We should be working on a previously-set-up struct */ + Assert(arg->func == PLyDict_FromComposite); + + /* Save pointer to tupdesc, but only if this is an anonymous record type */ + if (arg->typoid == RECORDOID && arg->typmod < 0) + arg->u.tuple.recdesc = desc; + + /* (Re)allocate atts array as needed */ + if (arg->u.tuple.natts != desc->natts) + { + if (arg->u.tuple.atts) + pfree(arg->u.tuple.atts); + arg->u.tuple.natts = desc->natts; + arg->u.tuple.atts = (PLyDatumToOb *) + MemoryContextAllocZero(arg->mcxt, + desc->natts * sizeof(PLyDatumToOb)); + } + + /* Fill the atts entries, except for dropped columns */ + for (i = 0; i < desc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(desc, i); + PLyDatumToOb *att = &arg->u.tuple.atts[i]; + + if (attr->attisdropped) + continue; + + if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod) + continue; /* already set up this entry */ + + PLy_input_setup_func(att, arg->mcxt, + attr->atttypid, attr->atttypmod, + proc); + } +} + +/* + * Initialize, or re-initialize, per-column output info for a composite type. + * + * This is separate from PLy_output_setup_func() because in cases involving + * anonymous record types, we need to be passed the tupdesc explicitly. + * It's caller's responsibility that the tupdesc has adequate lifespan + * in such cases. If the tupdesc is for a named composite or registered + * record type, it does not need to be long-lived. + */ +void +PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc) +{ + int i; + + /* We should be working on a previously-set-up struct */ + Assert(arg->func == PLyObject_ToComposite); + + /* Save pointer to tupdesc, but only if this is an anonymous record type */ + if (arg->typoid == RECORDOID && arg->typmod < 0) + arg->u.tuple.recdesc = desc; + + /* (Re)allocate atts array as needed */ + if (arg->u.tuple.natts != desc->natts) + { + if (arg->u.tuple.atts) + pfree(arg->u.tuple.atts); + arg->u.tuple.natts = desc->natts; + arg->u.tuple.atts = (PLyObToDatum *) + MemoryContextAllocZero(arg->mcxt, + desc->natts * sizeof(PLyObToDatum)); + } + + /* Fill the atts entries, except for dropped columns */ + for (i = 0; i < desc->natts; i++) + { + Form_pg_attribute attr = TupleDescAttr(desc, i); + PLyObToDatum *att = &arg->u.tuple.atts[i]; + + if (attr->attisdropped) + continue; + + if (att->typoid == attr->atttypid && att->typmod == attr->atttypmod) + continue; /* already set up this entry */ + + PLy_output_setup_func(att, arg->mcxt, + attr->atttypid, attr->atttypmod, + proc); + } +} + +/* + * Set up output info for a PL/Python function returning record. + * + * Note: the given tupdesc is not necessarily long-lived. + */ +void +PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, PLyProcedure *proc) +{ + /* Makes no sense unless RECORD */ + Assert(arg->typoid == RECORDOID); + Assert(desc->tdtypeid == RECORDOID); + + /* + * Bless the record type if not already done. We'd have to do this anyway + * to return a tuple, so we might as well force the issue so we can use + * the known-record-type code path. + */ + BlessTupleDesc(desc); + + /* + * Update arg->typmod, and clear the recdesc link if it's changed. The + * next call of PLyObject_ToComposite will look up a long-lived tupdesc + * for the record type. + */ + arg->typmod = desc->tdtypmod; + if (arg->u.tuple.recdesc && + arg->u.tuple.recdesc->tdtypmod != arg->typmod) + arg->u.tuple.recdesc = NULL; + + /* Update derived data if necessary */ + PLy_output_setup_tuple(arg, desc, proc); +} + +/* + * Recursively initialize the PLyObToDatum structure(s) needed to construct + * a SQL value of the specified typeOid/typmod from a Python value. + * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate + * record type.) + * proc is used to look up transform functions. + */ +void +PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt, + Oid typeOid, int32 typmod, + PLyProcedure *proc) +{ + TypeCacheEntry *typentry; + char typtype; + Oid trfuncid; + Oid typinput; + + /* Since this is recursive, it could theoretically be driven to overflow */ + check_stack_depth(); + + arg->typoid = typeOid; + arg->typmod = typmod; + arg->mcxt = arg_mcxt; + + /* + * Fetch typcache entry for the target type, asking for whatever info + * we'll need later. RECORD is a special case: just treat it as composite + * without bothering with the typcache entry. + */ + if (typeOid != RECORDOID) + { + typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO); + typtype = typentry->typtype; + arg->typbyval = typentry->typbyval; + arg->typlen = typentry->typlen; + arg->typalign = typentry->typalign; + } + else + { + typentry = NULL; + typtype = TYPTYPE_COMPOSITE; + /* hard-wired knowledge about type RECORD: */ + arg->typbyval = false; + arg->typlen = -1; + arg->typalign = TYPALIGN_DOUBLE; + } + + /* + * Choose conversion method. Note that transform functions are checked + * for composite and scalar types, but not for arrays or domains. This is + * somewhat historical, but we'd have a problem allowing them on domains, + * since we drill down through all levels of a domain nest without looking + * at the intermediate levels at all. + */ + if (typtype == TYPTYPE_DOMAIN) + { + /* Domain */ + arg->func = PLyObject_ToDomain; + arg->u.domain.domain_info = NULL; + /* Recursively set up conversion info for the element type */ + arg->u.domain.base = (PLyObToDatum *) + MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum)); + PLy_output_setup_func(arg->u.domain.base, arg_mcxt, + typentry->domainBaseType, + typentry->domainBaseTypmod, + proc); + } + else if (typentry && + IsTrueArrayType(typentry)) + { + /* Standard array */ + arg->func = PLySequence_ToArray; + /* Get base type OID to insert into constructed array */ + /* (note this might not be the same as the immediate child type) */ + arg->u.array.elmbasetype = getBaseType(typentry->typelem); + /* Recursively set up conversion info for the element type */ + arg->u.array.elm = (PLyObToDatum *) + MemoryContextAllocZero(arg_mcxt, sizeof(PLyObToDatum)); + PLy_output_setup_func(arg->u.array.elm, arg_mcxt, + typentry->typelem, typmod, + proc); + } + else if ((trfuncid = get_transform_tosql(typeOid, + proc->langid, + proc->trftypes))) + { + arg->func = PLyObject_ToTransform; + fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt); + } + else if (typtype == TYPTYPE_COMPOSITE) + { + /* Named composite type, or RECORD */ + arg->func = PLyObject_ToComposite; + /* We'll set up the per-field data later */ + arg->u.tuple.recdesc = NULL; + arg->u.tuple.typentry = typentry; + arg->u.tuple.tupdescid = INVALID_TUPLEDESC_IDENTIFIER; + arg->u.tuple.atts = NULL; + arg->u.tuple.natts = 0; + /* Mark this invalid till needed, too */ + arg->u.tuple.recinfunc.fn_oid = InvalidOid; + } + else + { + /* Scalar type, but we have a couple of special cases */ + switch (typeOid) + { + case BOOLOID: + arg->func = PLyObject_ToBool; + break; + case BYTEAOID: + arg->func = PLyObject_ToBytea; + break; + default: + arg->func = PLyObject_ToScalar; + getTypeInputInfo(typeOid, &typinput, &arg->u.scalar.typioparam); + fmgr_info_cxt(typinput, &arg->u.scalar.typfunc, arg_mcxt); + break; + } + } +} + +/* + * Recursively initialize the PLyDatumToOb structure(s) needed to construct + * a Python value from a SQL value of the specified typeOid/typmod. + * (But note that at this point we may have RECORDOID/-1, ie, an indeterminate + * record type.) + * proc is used to look up transform functions. + */ +void +PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt, + Oid typeOid, int32 typmod, + PLyProcedure *proc) +{ + TypeCacheEntry *typentry; + char typtype; + Oid trfuncid; + Oid typoutput; + bool typisvarlena; + + /* Since this is recursive, it could theoretically be driven to overflow */ + check_stack_depth(); + + arg->typoid = typeOid; + arg->typmod = typmod; + arg->mcxt = arg_mcxt; + + /* + * Fetch typcache entry for the target type, asking for whatever info + * we'll need later. RECORD is a special case: just treat it as composite + * without bothering with the typcache entry. + */ + if (typeOid != RECORDOID) + { + typentry = lookup_type_cache(typeOid, TYPECACHE_DOMAIN_BASE_INFO); + typtype = typentry->typtype; + arg->typbyval = typentry->typbyval; + arg->typlen = typentry->typlen; + arg->typalign = typentry->typalign; + } + else + { + typentry = NULL; + typtype = TYPTYPE_COMPOSITE; + /* hard-wired knowledge about type RECORD: */ + arg->typbyval = false; + arg->typlen = -1; + arg->typalign = TYPALIGN_DOUBLE; + } + + /* + * Choose conversion method. Note that transform functions are checked + * for composite and scalar types, but not for arrays or domains. This is + * somewhat historical, but we'd have a problem allowing them on domains, + * since we drill down through all levels of a domain nest without looking + * at the intermediate levels at all. + */ + if (typtype == TYPTYPE_DOMAIN) + { + /* Domain --- we don't care, just recurse down to the base type */ + PLy_input_setup_func(arg, arg_mcxt, + typentry->domainBaseType, + typentry->domainBaseTypmod, + proc); + } + else if (typentry && + IsTrueArrayType(typentry)) + { + /* Standard array */ + arg->func = PLyList_FromArray; + /* Recursively set up conversion info for the element type */ + arg->u.array.elm = (PLyDatumToOb *) + MemoryContextAllocZero(arg_mcxt, sizeof(PLyDatumToOb)); + PLy_input_setup_func(arg->u.array.elm, arg_mcxt, + typentry->typelem, typmod, + proc); + } + else if ((trfuncid = get_transform_fromsql(typeOid, + proc->langid, + proc->trftypes))) + { + arg->func = PLyObject_FromTransform; + fmgr_info_cxt(trfuncid, &arg->u.transform.typtransform, arg_mcxt); + } + else if (typtype == TYPTYPE_COMPOSITE) + { + /* Named composite type, or RECORD */ + arg->func = PLyDict_FromComposite; + /* We'll set up the per-field data later */ + arg->u.tuple.recdesc = NULL; + arg->u.tuple.typentry = typentry; + arg->u.tuple.tupdescid = INVALID_TUPLEDESC_IDENTIFIER; + arg->u.tuple.atts = NULL; + arg->u.tuple.natts = 0; + } + else + { + /* Scalar type, but we have a couple of special cases */ + switch (typeOid) + { + case BOOLOID: + arg->func = PLyBool_FromBool; + break; + case FLOAT4OID: + arg->func = PLyFloat_FromFloat4; + break; + case FLOAT8OID: + arg->func = PLyFloat_FromFloat8; + break; + case NUMERICOID: + arg->func = PLyDecimal_FromNumeric; + break; + case INT2OID: + arg->func = PLyLong_FromInt16; + break; + case INT4OID: + arg->func = PLyLong_FromInt32; + break; + case INT8OID: + arg->func = PLyLong_FromInt64; + break; + case OIDOID: + arg->func = PLyLong_FromOid; + break; + case BYTEAOID: + arg->func = PLyBytes_FromBytea; + break; + default: + arg->func = PLyUnicode_FromScalar; + getTypeOutputInfo(typeOid, &typoutput, &typisvarlena); + fmgr_info_cxt(typoutput, &arg->u.scalar.typfunc, arg_mcxt); + break; + } + } +} + + +/* + * Special-purpose input converters. + */ + +static PyObject * +PLyBool_FromBool(PLyDatumToOb *arg, Datum d) +{ + if (DatumGetBool(d)) + Py_RETURN_TRUE; + Py_RETURN_FALSE; +} + +static PyObject * +PLyFloat_FromFloat4(PLyDatumToOb *arg, Datum d) +{ + return PyFloat_FromDouble(DatumGetFloat4(d)); +} + +static PyObject * +PLyFloat_FromFloat8(PLyDatumToOb *arg, Datum d) +{ + return PyFloat_FromDouble(DatumGetFloat8(d)); +} + +static PyObject * +PLyDecimal_FromNumeric(PLyDatumToOb *arg, Datum d) +{ + static PyObject *decimal_constructor; + char *str; + PyObject *pyvalue; + + /* Try to import cdecimal. If it doesn't exist, fall back to decimal. */ + if (!decimal_constructor) + { + PyObject *decimal_module; + + decimal_module = PyImport_ImportModule("cdecimal"); + if (!decimal_module) + { + PyErr_Clear(); + decimal_module = PyImport_ImportModule("decimal"); + } + if (!decimal_module) + PLy_elog(ERROR, "could not import a module for Decimal constructor"); + + decimal_constructor = PyObject_GetAttrString(decimal_module, "Decimal"); + if (!decimal_constructor) + PLy_elog(ERROR, "no Decimal attribute in module"); + } + + str = DatumGetCString(DirectFunctionCall1(numeric_out, d)); + pyvalue = PyObject_CallFunction(decimal_constructor, "s", str); + if (!pyvalue) + PLy_elog(ERROR, "conversion from numeric to Decimal failed"); + + return pyvalue; +} + +static PyObject * +PLyLong_FromInt16(PLyDatumToOb *arg, Datum d) +{ + return PyLong_FromLong(DatumGetInt16(d)); +} + +static PyObject * +PLyLong_FromInt32(PLyDatumToOb *arg, Datum d) +{ + return PyLong_FromLong(DatumGetInt32(d)); +} + +static PyObject * +PLyLong_FromInt64(PLyDatumToOb *arg, Datum d) +{ + return PyLong_FromLongLong(DatumGetInt64(d)); +} + +static PyObject * +PLyLong_FromOid(PLyDatumToOb *arg, Datum d) +{ + return PyLong_FromUnsignedLong(DatumGetObjectId(d)); +} + +static PyObject * +PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d) +{ + text *txt = DatumGetByteaPP(d); + char *str = VARDATA_ANY(txt); + size_t size = VARSIZE_ANY_EXHDR(txt); + + return PyBytes_FromStringAndSize(str, size); +} + + +/* + * Generic input conversion using a SQL type's output function. + */ +static PyObject * +PLyUnicode_FromScalar(PLyDatumToOb *arg, Datum d) +{ + char *x = OutputFunctionCall(&arg->u.scalar.typfunc, d); + PyObject *r = PLyUnicode_FromString(x); + + pfree(x); + return r; +} + +/* + * Convert using a from-SQL transform function. + */ +static PyObject * +PLyObject_FromTransform(PLyDatumToOb *arg, Datum d) +{ + Datum t; + + t = FunctionCall1(&arg->u.transform.typtransform, d); + return (PyObject *) DatumGetPointer(t); +} + +/* + * Convert a SQL array to a Python list. + */ +static PyObject * +PLyList_FromArray(PLyDatumToOb *arg, Datum d) +{ + ArrayType *array = DatumGetArrayTypeP(d); + PLyDatumToOb *elm = arg->u.array.elm; + int ndim; + int *dims; + char *dataptr; + bits8 *bitmap; + int bitmask; + + if (ARR_NDIM(array) == 0) + return PyList_New(0); + + /* Array dimensions and left bounds */ + ndim = ARR_NDIM(array); + dims = ARR_DIMS(array); + Assert(ndim <= MAXDIM); + + /* + * We iterate the SQL array in the physical order it's stored in the + * datum. For example, for a 3-dimensional array the order of iteration + * would be the following: [0,0,0] elements through [0,0,k], then [0,1,0] + * through [0,1,k] till [0,m,k], then [1,0,0] through [1,0,k] till + * [1,m,k], and so on. + * + * In Python, there are no multi-dimensional lists as such, but they are + * represented as a list of lists. So a 3-d array of [n,m,k] elements is a + * list of n m-element arrays, each element of which is k-element array. + * PLyList_FromArray_recurse() builds the Python list for a single + * dimension, and recurses for the next inner dimension. + */ + dataptr = ARR_DATA_PTR(array); + bitmap = ARR_NULLBITMAP(array); + bitmask = 1; + + return PLyList_FromArray_recurse(elm, dims, ndim, 0, + &dataptr, &bitmap, &bitmask); +} + +static PyObject * +PLyList_FromArray_recurse(PLyDatumToOb *elm, int *dims, int ndim, int dim, + char **dataptr_p, bits8 **bitmap_p, int *bitmask_p) +{ + int i; + PyObject *list; + + list = PyList_New(dims[dim]); + if (!list) + return NULL; + + if (dim < ndim - 1) + { + /* Outer dimension. Recurse for each inner slice. */ + for (i = 0; i < dims[dim]; i++) + { + PyObject *sublist; + + sublist = PLyList_FromArray_recurse(elm, dims, ndim, dim + 1, + dataptr_p, bitmap_p, bitmask_p); + PyList_SET_ITEM(list, i, sublist); + } + } + else + { + /* + * Innermost dimension. Fill the list with the values from the array + * for this slice. + */ + char *dataptr = *dataptr_p; + bits8 *bitmap = *bitmap_p; + int bitmask = *bitmask_p; + + for (i = 0; i < dims[dim]; i++) + { + /* checking for NULL */ + if (bitmap && (*bitmap & bitmask) == 0) + { + Py_INCREF(Py_None); + PyList_SET_ITEM(list, i, Py_None); + } + else + { + Datum itemvalue; + + itemvalue = fetch_att(dataptr, elm->typbyval, elm->typlen); + PyList_SET_ITEM(list, i, elm->func(elm, itemvalue)); + dataptr = att_addlength_pointer(dataptr, elm->typlen, dataptr); + dataptr = (char *) att_align_nominal(dataptr, elm->typalign); + } + + /* advance bitmap pointer if any */ + if (bitmap) + { + bitmask <<= 1; + if (bitmask == 0x100 /* (1<<8) */ ) + { + bitmap++; + bitmask = 1; + } + } + } + + *dataptr_p = dataptr; + *bitmap_p = bitmap; + *bitmask_p = bitmask; + } + + return list; +} + +/* + * Convert a composite SQL value to a Python dict. + */ +static PyObject * +PLyDict_FromComposite(PLyDatumToOb *arg, Datum d) +{ + PyObject *dict; + HeapTupleHeader td; + Oid tupType; + int32 tupTypmod; + TupleDesc tupdesc; + HeapTupleData tmptup; + + td = DatumGetHeapTupleHeader(d); + /* Extract rowtype info and find a tupdesc */ + tupType = HeapTupleHeaderGetTypeId(td); + tupTypmod = HeapTupleHeaderGetTypMod(td); + tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); + + /* Set up I/O funcs if not done yet */ + PLy_input_setup_tuple(arg, tupdesc, + PLy_current_execution_context()->curr_proc); + + /* Build a temporary HeapTuple control structure */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(td); + tmptup.t_data = td; + + dict = PLyDict_FromTuple(arg, &tmptup, tupdesc, true); + + ReleaseTupleDesc(tupdesc); + + return dict; +} + +/* + * Transform a tuple into a Python dict object. + */ +static PyObject * +PLyDict_FromTuple(PLyDatumToOb *arg, HeapTuple tuple, TupleDesc desc, bool include_generated) +{ + PyObject *volatile dict; + + /* Simple sanity check that desc matches */ + Assert(desc->natts == arg->u.tuple.natts); + + dict = PyDict_New(); + if (dict == NULL) + return NULL; + + PG_TRY(); + { + int i; + + for (i = 0; i < arg->u.tuple.natts; i++) + { + PLyDatumToOb *att = &arg->u.tuple.atts[i]; + Form_pg_attribute attr = TupleDescAttr(desc, i); + char *key; + Datum vattr; + bool is_null; + PyObject *value; + + if (attr->attisdropped) + continue; + + if (attr->attgenerated) + { + /* don't include unless requested */ + if (!include_generated) + continue; + } + + key = NameStr(attr->attname); + vattr = heap_getattr(tuple, (i + 1), desc, &is_null); + + if (is_null) + PyDict_SetItemString(dict, key, Py_None); + else + { + value = att->func(att, vattr); + PyDict_SetItemString(dict, key, value); + Py_DECREF(value); + } + } + } + PG_CATCH(); + { + Py_DECREF(dict); + PG_RE_THROW(); + } + PG_END_TRY(); + + return dict; +} + +/* + * Convert a Python object to a PostgreSQL bool datum. This can't go + * through the generic conversion function, because Python attaches a + * Boolean value to everything, more things than the PostgreSQL bool + * type can parse. + */ +static Datum +PLyObject_ToBool(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + return BoolGetDatum(PyObject_IsTrue(plrv)); +} + +/* + * Convert a Python object to a PostgreSQL bytea datum. This doesn't + * go through the generic conversion function to circumvent problems + * with embedded nulls. And it's faster this way. + */ +static Datum +PLyObject_ToBytea(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + PyObject *volatile plrv_so = NULL; + Datum rv = (Datum) 0; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + + plrv_so = PyObject_Bytes(plrv); + if (!plrv_so) + PLy_elog(ERROR, "could not create bytes representation of Python object"); + + PG_TRY(); + { + char *plrv_sc = PyBytes_AsString(plrv_so); + size_t len = PyBytes_Size(plrv_so); + size_t size = len + VARHDRSZ; + bytea *result = palloc(size); + + SET_VARSIZE(result, size); + memcpy(VARDATA(result), plrv_sc, len); + rv = PointerGetDatum(result); + } + PG_FINALLY(); + { + Py_XDECREF(plrv_so); + } + PG_END_TRY(); + + return rv; +} + + +/* + * Convert a Python object to a composite type. First look up the type's + * description, then route the Python object through the conversion function + * for obtaining PostgreSQL tuples. + */ +static Datum +PLyObject_ToComposite(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + Datum rv; + TupleDesc desc; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + + /* + * The string conversion case doesn't require a tupdesc, nor per-field + * conversion data, so just go for it if that's the case to use. + */ + if (PyUnicode_Check(plrv)) + return PLyUnicode_ToComposite(arg, plrv, inarray); + + /* + * If we're dealing with a named composite type, we must look up the + * tupdesc every time, to protect against possible changes to the type. + * RECORD types can't change between calls; but we must still be willing + * to set up the info the first time, if nobody did yet. + */ + if (arg->typoid != RECORDOID) + { + desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); + /* We should have the descriptor of the type's typcache entry */ + Assert(desc == arg->u.tuple.typentry->tupDesc); + /* Detect change of descriptor, update cache if needed */ + if (arg->u.tuple.tupdescid != arg->u.tuple.typentry->tupDesc_identifier) + { + PLy_output_setup_tuple(arg, desc, + PLy_current_execution_context()->curr_proc); + arg->u.tuple.tupdescid = arg->u.tuple.typentry->tupDesc_identifier; + } + } + else + { + desc = arg->u.tuple.recdesc; + if (desc == NULL) + { + desc = lookup_rowtype_tupdesc(arg->typoid, arg->typmod); + arg->u.tuple.recdesc = desc; + } + else + { + /* Pin descriptor to match unpin below */ + PinTupleDesc(desc); + } + } + + /* Simple sanity check on our caching */ + Assert(desc->natts == arg->u.tuple.natts); + + /* + * Convert, using the appropriate method depending on the type of the + * supplied Python object. + */ + if (PySequence_Check(plrv)) + /* composite type as sequence (tuple, list etc) */ + rv = PLySequence_ToComposite(arg, desc, plrv); + else if (PyMapping_Check(plrv)) + /* composite type as mapping (currently only dict) */ + rv = PLyMapping_ToComposite(arg, desc, plrv); + else + /* returned as smth, must provide method __getattr__(name) */ + rv = PLyGenericObject_ToComposite(arg, desc, plrv, inarray); + + ReleaseTupleDesc(desc); + + return rv; +} + + +/* + * Convert Python object to C string in server encoding. + * + * Note: this is exported for use by add-on transform modules. + */ +char * +PLyObject_AsString(PyObject *plrv) +{ + PyObject *plrv_bo; + char *plrv_sc; + size_t plen; + size_t slen; + + if (PyUnicode_Check(plrv)) + plrv_bo = PLyUnicode_Bytes(plrv); + else if (PyFloat_Check(plrv)) + { + /* use repr() for floats, str() is lossy */ + PyObject *s = PyObject_Repr(plrv); + + plrv_bo = PLyUnicode_Bytes(s); + Py_XDECREF(s); + } + else + { + PyObject *s = PyObject_Str(plrv); + + plrv_bo = PLyUnicode_Bytes(s); + Py_XDECREF(s); + } + if (!plrv_bo) + PLy_elog(ERROR, "could not create string representation of Python object"); + + plrv_sc = pstrdup(PyBytes_AsString(plrv_bo)); + plen = PyBytes_Size(plrv_bo); + slen = strlen(plrv_sc); + + Py_XDECREF(plrv_bo); + + if (slen < plen) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes"))); + else if (slen > plen) + elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length"); + pg_verifymbstr(plrv_sc, slen, false); + + return plrv_sc; +} + + +/* + * Generic output conversion function: convert PyObject to cstring and + * cstring into PostgreSQL type. + */ +static Datum +PLyObject_ToScalar(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + char *str; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + + str = PLyObject_AsString(plrv); + + return InputFunctionCall(&arg->u.scalar.typfunc, + str, + arg->u.scalar.typioparam, + arg->typmod); +} + + +/* + * Convert to a domain type. + */ +static Datum +PLyObject_ToDomain(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + Datum result; + PLyObToDatum *base = arg->u.domain.base; + + result = base->func(base, plrv, isnull, inarray); + domain_check(result, *isnull, arg->typoid, + &arg->u.domain.domain_info, arg->mcxt); + return result; +} + + +/* + * Convert using a to-SQL transform function. + */ +static Datum +PLyObject_ToTransform(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + return FunctionCall1(&arg->u.transform.typtransform, PointerGetDatum(plrv)); +} + + +/* + * Convert Python sequence (or list of lists) to SQL array. + */ +static Datum +PLySequence_ToArray(PLyObToDatum *arg, PyObject *plrv, + bool *isnull, bool inarray) +{ + ArrayBuildState *astate = NULL; + int ndims = 1; + int dims[MAXDIM]; + int lbs[MAXDIM]; + + if (plrv == Py_None) + { + *isnull = true; + return (Datum) 0; + } + *isnull = false; + + /* + * For historical reasons, we allow any sequence (not only a list) at the + * top level when converting a Python object to a SQL array. However, a + * multi-dimensional array is recognized only when the object contains + * true lists. + */ + if (!PySequence_Check(plrv)) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("return value of function with array return type is not a Python sequence"))); + + /* Initialize dimensionality info with first-level dimension */ + memset(dims, 0, sizeof(dims)); + dims[0] = PySequence_Length(plrv); + + /* + * Traverse the Python lists, in depth-first order, and collect all the + * elements at the bottom level into an ArrayBuildState. + */ + PLySequence_ToArray_recurse(plrv, &astate, + &ndims, dims, 1, + arg->u.array.elm, + arg->u.array.elmbasetype); + + /* ensure we get zero-D array for no inputs, as per PG convention */ + if (astate == NULL) + return PointerGetDatum(construct_empty_array(arg->u.array.elmbasetype)); + + for (int i = 0; i < ndims; i++) + lbs[i] = 1; + + return makeMdArrayResult(astate, ndims, dims, lbs, + CurrentMemoryContext, true); +} + +/* + * Helper function for PLySequence_ToArray. Traverse a Python list of lists in + * depth-first order, storing the elements in *astatep. + * + * The ArrayBuildState is created only when we first find a scalar element; + * if we didn't do it like that, we'd need some other convention for knowing + * whether we'd already found any scalars (and thus the number of dimensions + * is frozen). + */ +static void +PLySequence_ToArray_recurse(PyObject *obj, ArrayBuildState **astatep, + int *ndims, int *dims, int cur_depth, + PLyObToDatum *elm, Oid elmbasetype) +{ + int i; + int len = PySequence_Length(obj); + + /* We should not get here with a non-sequence object */ + if (len < 0) + PLy_elog(ERROR, "could not determine sequence length for function return value"); + + for (i = 0; i < len; i++) + { + /* fetch the array element */ + PyObject *subobj = PySequence_GetItem(obj, i); + + /* need PG_TRY to ensure we release the subobj's refcount */ + PG_TRY(); + { + /* multi-dimensional array? */ + if (PyList_Check(subobj)) + { + /* set size when at first element in this level, else compare */ + if (i == 0 && *ndims == cur_depth) + { + /* array after some scalars at same level? */ + if (*astatep != NULL) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("multidimensional arrays must have array expressions with matching dimensions"))); + /* too many dimensions? */ + if (cur_depth >= MAXDIM) + ereport(ERROR, + (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), + errmsg("number of array dimensions exceeds the maximum allowed (%d)", + MAXDIM))); + /* OK, add a dimension */ + dims[*ndims] = PySequence_Length(subobj); + (*ndims)++; + } + else if (cur_depth >= *ndims || + PySequence_Length(subobj) != dims[cur_depth]) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("multidimensional arrays must have array expressions with matching dimensions"))); + + /* recurse to fetch elements of this sub-array */ + PLySequence_ToArray_recurse(subobj, astatep, + ndims, dims, cur_depth + 1, + elm, elmbasetype); + } + else + { + Datum dat; + bool isnull; + + /* scalar after some sub-arrays at same level? */ + if (*ndims != cur_depth) + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("multidimensional arrays must have array expressions with matching dimensions"))); + + /* convert non-list object to Datum */ + dat = elm->func(elm, subobj, &isnull, true); + + /* create ArrayBuildState if we didn't already */ + if (*astatep == NULL) + *astatep = initArrayResult(elmbasetype, + CurrentMemoryContext, true); + + /* ... and save the element value in it */ + (void) accumArrayResult(*astatep, dat, isnull, + elmbasetype, CurrentMemoryContext); + } + } + PG_FINALLY(); + { + Py_XDECREF(subobj); + } + PG_END_TRY(); + } +} + + +/* + * Convert a Python string to composite, using record_in. + */ +static Datum +PLyUnicode_ToComposite(PLyObToDatum *arg, PyObject *string, bool inarray) +{ + char *str; + + /* + * Set up call data for record_in, if we didn't already. (We can't just + * use DirectFunctionCall, because record_in needs a fn_extra field.) + */ + if (!OidIsValid(arg->u.tuple.recinfunc.fn_oid)) + fmgr_info_cxt(F_RECORD_IN, &arg->u.tuple.recinfunc, arg->mcxt); + + str = PLyObject_AsString(string); + + /* + * If we are parsing a composite type within an array, and the string + * isn't a valid record literal, there's a high chance that the function + * did something like: + * + * CREATE FUNCTION .. RETURNS comptype[] AS $$ return [['foo', 'bar']] $$ + * LANGUAGE plpython; + * + * Before PostgreSQL 10, that was interpreted as a single-dimensional + * array, containing record ('foo', 'bar'). PostgreSQL 10 added support + * for multi-dimensional arrays, and it is now interpreted as a + * two-dimensional array, containing two records, 'foo', and 'bar'. + * record_in() will throw an error, because "foo" is not a valid record + * literal. + * + * To make that less confusing to users who are upgrading from older + * versions, try to give a hint in the typical instances of that. If we + * are parsing an array of composite types, and we see a string literal + * that is not a valid record literal, give a hint. We only want to give + * the hint in the narrow case of a malformed string literal, not any + * error from record_in(), so check for that case here specifically. + * + * This check better match the one in record_in(), so that we don't forbid + * literals that are actually valid! + */ + if (inarray) + { + char *ptr = str; + + /* Allow leading whitespace */ + while (*ptr && isspace((unsigned char) *ptr)) + ptr++; + if (*ptr++ != '(') + ereport(ERROR, + (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), + errmsg("malformed record literal: \"%s\"", str), + errdetail("Missing left parenthesis."), + errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"."))); + } + + return InputFunctionCall(&arg->u.tuple.recinfunc, + str, + arg->typoid, + arg->typmod); +} + + +static Datum +PLyMapping_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *mapping) +{ + Datum result; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + Assert(PyMapping_Check(mapping)); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + char *key; + PyObject *volatile value; + PLyObToDatum *att; + Form_pg_attribute attr = TupleDescAttr(desc, i); + + if (attr->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + key = NameStr(attr->attname); + value = NULL; + att = &arg->u.tuple.atts[i]; + PG_TRY(); + { + value = PyMapping_GetItemString(mapping, key); + if (!value) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("key \"%s\" not found in mapping", key), + errhint("To return null in a column, " + "add the value None to the mapping with the key named after the column."))); + + values[i] = att->func(att, value, &nulls[i], false); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_form_tuple(desc, values, nulls); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + + pfree(values); + pfree(nulls); + + return result; +} + + +static Datum +PLySequence_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *sequence) +{ + Datum result; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int idx; + volatile int i; + + Assert(PySequence_Check(sequence)); + + /* + * Check that sequence length is exactly same as PG tuple's. We actually + * can ignore exceeding items or assume missing ones as null but to avoid + * plpython developer's errors we are strict here + */ + idx = 0; + for (i = 0; i < desc->natts; i++) + { + if (!TupleDescAttr(desc, i)->attisdropped) + idx++; + } + if (PySequence_Length(sequence) != idx) + ereport(ERROR, + (errcode(ERRCODE_DATATYPE_MISMATCH), + errmsg("length of returned sequence did not match number of columns in row"))); + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + idx = 0; + for (i = 0; i < desc->natts; ++i) + { + PyObject *volatile value; + PLyObToDatum *att; + + if (TupleDescAttr(desc, i)->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + value = NULL; + att = &arg->u.tuple.atts[i]; + PG_TRY(); + { + value = PySequence_GetItem(sequence, idx); + Assert(value); + + values[i] = att->func(att, value, &nulls[i], false); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + + idx++; + } + + tuple = heap_form_tuple(desc, values, nulls); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + + pfree(values); + pfree(nulls); + + return result; +} + + +static Datum +PLyGenericObject_ToComposite(PLyObToDatum *arg, TupleDesc desc, PyObject *object, bool inarray) +{ + Datum result; + HeapTuple tuple; + Datum *values; + bool *nulls; + volatile int i; + + /* Build tuple */ + values = palloc(sizeof(Datum) * desc->natts); + nulls = palloc(sizeof(bool) * desc->natts); + for (i = 0; i < desc->natts; ++i) + { + char *key; + PyObject *volatile value; + PLyObToDatum *att; + Form_pg_attribute attr = TupleDescAttr(desc, i); + + if (attr->attisdropped) + { + values[i] = (Datum) 0; + nulls[i] = true; + continue; + } + + key = NameStr(attr->attname); + value = NULL; + att = &arg->u.tuple.atts[i]; + PG_TRY(); + { + value = PyObject_GetAttrString(object, key); + if (!value) + { + /* + * No attribute for this column in the object. + * + * If we are parsing a composite type in an array, a likely + * cause is that the function contained something like "[[123, + * 'foo']]". Before PostgreSQL 10, that was interpreted as an + * array, with a composite type (123, 'foo') in it. But now + * it's interpreted as a two-dimensional array, and we try to + * interpret "123" as the composite type. See also similar + * heuristic in PLyObject_ToScalar(). + */ + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_COLUMN), + errmsg("attribute \"%s\" does not exist in Python object", key), + inarray ? + errhint("To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\".") : + errhint("To return null in a column, let the returned object have an attribute named after column with value None."))); + } + + values[i] = att->func(att, value, &nulls[i], false); + + Py_XDECREF(value); + value = NULL; + } + PG_CATCH(); + { + Py_XDECREF(value); + PG_RE_THROW(); + } + PG_END_TRY(); + } + + tuple = heap_form_tuple(desc, values, nulls); + result = heap_copy_tuple_as_datum(tuple, desc); + heap_freetuple(tuple); + + pfree(values); + pfree(nulls); + + return result; +} diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h new file mode 100644 index 0000000..5417f09 --- /dev/null +++ b/src/pl/plpython/plpy_typeio.h @@ -0,0 +1,175 @@ +/* + * src/pl/plpython/plpy_typeio.h + */ + +#ifndef PLPY_TYPEIO_H +#define PLPY_TYPEIO_H + +#include "access/htup.h" +#include "fmgr.h" +#include "plpython.h" +#include "utils/typcache.h" + +struct PLyProcedure; /* avoid requiring plpy_procedure.h here */ + + +/* + * "Input" conversion from PostgreSQL Datum to a Python object. + * + * arg is the previously-set-up conversion data, val is the value to convert. + * val mustn't be NULL. + * + * Note: the conversion data structs should be regarded as private to + * plpy_typeio.c. We declare them here only so that other modules can + * define structs containing them. + */ +typedef struct PLyDatumToOb PLyDatumToOb; /* forward reference */ + +typedef PyObject *(*PLyDatumToObFunc) (PLyDatumToOb *arg, Datum val); + +typedef struct PLyScalarToOb +{ + FmgrInfo typfunc; /* lookup info for type's output function */ +} PLyScalarToOb; + +typedef struct PLyArrayToOb +{ + PLyDatumToOb *elm; /* conversion info for array's element type */ +} PLyArrayToOb; + +typedef struct PLyTupleToOb +{ + /* If we're dealing with a RECORD type, actual descriptor is here: */ + TupleDesc recdesc; + /* If we're dealing with a named composite type, these fields are set: */ + TypeCacheEntry *typentry; /* typcache entry for type */ + uint64 tupdescid; /* last tupdesc identifier seen in typcache */ + /* These fields are NULL/0 if not yet set: */ + PLyDatumToOb *atts; /* array of per-column conversion info */ + int natts; /* length of array */ +} PLyTupleToOb; + +typedef struct PLyTransformToOb +{ + FmgrInfo typtransform; /* lookup info for from-SQL transform func */ +} PLyTransformToOb; + +struct PLyDatumToOb +{ + PLyDatumToObFunc func; /* conversion control function */ + Oid typoid; /* OID of the source type */ + int32 typmod; /* typmod of the source type */ + bool typbyval; /* its physical representation details */ + int16 typlen; + char typalign; + MemoryContext mcxt; /* context this info is stored in */ + union /* conversion-type-specific data */ + { + PLyScalarToOb scalar; + PLyArrayToOb array; + PLyTupleToOb tuple; + PLyTransformToOb transform; + } u; +}; + +/* + * "Output" conversion from Python object to a PostgreSQL Datum. + * + * arg is the previously-set-up conversion data, val is the value to convert. + * + * *isnull is set to true if val is Py_None, false otherwise. + * (The conversion function *must* be called even for Py_None, + * so that domain constraints can be checked.) + * + * inarray is true if the converted value was in an array (Python list). + * It is used to give a better error message in some cases. + */ +typedef struct PLyObToDatum PLyObToDatum; /* forward reference */ + +typedef Datum (*PLyObToDatumFunc) (PLyObToDatum *arg, PyObject *val, + bool *isnull, + bool inarray); + +typedef struct PLyObToScalar +{ + FmgrInfo typfunc; /* lookup info for type's input function */ + Oid typioparam; /* argument to pass to it */ +} PLyObToScalar; + +typedef struct PLyObToArray +{ + PLyObToDatum *elm; /* conversion info for array's element type */ + Oid elmbasetype; /* element base type */ +} PLyObToArray; + +typedef struct PLyObToTuple +{ + /* If we're dealing with a RECORD type, actual descriptor is here: */ + TupleDesc recdesc; + /* If we're dealing with a named composite type, these fields are set: */ + TypeCacheEntry *typentry; /* typcache entry for type */ + uint64 tupdescid; /* last tupdesc identifier seen in typcache */ + /* These fields are NULL/0 if not yet set: */ + PLyObToDatum *atts; /* array of per-column conversion info */ + int natts; /* length of array */ + /* We might need to convert using record_in(); if so, cache info here */ + FmgrInfo recinfunc; /* lookup info for record_in */ +} PLyObToTuple; + +typedef struct PLyObToDomain +{ + PLyObToDatum *base; /* conversion info for domain's base type */ + void *domain_info; /* cache space for domain_check() */ +} PLyObToDomain; + +typedef struct PLyObToTransform +{ + FmgrInfo typtransform; /* lookup info for to-SQL transform function */ +} PLyObToTransform; + +struct PLyObToDatum +{ + PLyObToDatumFunc func; /* conversion control function */ + Oid typoid; /* OID of the target type */ + int32 typmod; /* typmod of the target type */ + bool typbyval; /* its physical representation details */ + int16 typlen; + char typalign; + MemoryContext mcxt; /* context this info is stored in */ + union /* conversion-type-specific data */ + { + PLyObToScalar scalar; + PLyObToArray array; + PLyObToTuple tuple; + PLyObToDomain domain; + PLyObToTransform transform; + } u; +}; + + +extern PGDLLEXPORT PyObject *PLy_input_convert(PLyDatumToOb *arg, Datum val); +extern PGDLLEXPORT Datum PLy_output_convert(PLyObToDatum *arg, PyObject *val, + bool *isnull); + +extern PGDLLEXPORT PyObject *PLy_input_from_tuple(PLyDatumToOb *arg, HeapTuple tuple, + TupleDesc desc, bool include_generated); + +extern PGDLLEXPORT void PLy_input_setup_func(PLyDatumToOb *arg, MemoryContext arg_mcxt, + Oid typeOid, int32 typmod, + struct PLyProcedure *proc); +extern PGDLLEXPORT void PLy_output_setup_func(PLyObToDatum *arg, MemoryContext arg_mcxt, + Oid typeOid, int32 typmod, + struct PLyProcedure *proc); + +extern PGDLLEXPORT void PLy_input_setup_tuple(PLyDatumToOb *arg, TupleDesc desc, + struct PLyProcedure *proc); +extern PGDLLEXPORT void PLy_output_setup_tuple(PLyObToDatum *arg, TupleDesc desc, + struct PLyProcedure *proc); + +extern PGDLLEXPORT void PLy_output_setup_record(PLyObToDatum *arg, TupleDesc desc, + struct PLyProcedure *proc); + +/* conversion from Python objects to C strings --- exported for transforms */ +extern PGDLLEXPORT char *PLyObject_AsString(PyObject *plrv); + +#endif /* PLPY_TYPEIO_H */ diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c new file mode 100644 index 0000000..22e2a59 --- /dev/null +++ b/src/pl/plpython/plpy_util.c @@ -0,0 +1,121 @@ +/* + * utility functions + * + * src/pl/plpython/plpy_util.c + */ + +#include "postgres.h" + +#include "mb/pg_wchar.h" +#include "plpy_elog.h" +#include "plpy_util.h" +#include "plpython.h" +#include "utils/memutils.h" + +/* + * Convert a Python unicode object to a Python string/bytes object in + * PostgreSQL server encoding. Reference ownership is passed to the + * caller. + */ +PyObject * +PLyUnicode_Bytes(PyObject *unicode) +{ + PyObject *bytes, + *rv; + char *utf8string, + *encoded; + + /* First encode the Python unicode object with UTF-8. */ + bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) + PLy_elog(ERROR, "could not convert Python Unicode object to bytes"); + + utf8string = PyBytes_AsString(bytes); + if (utf8string == NULL) + { + Py_DECREF(bytes); + PLy_elog(ERROR, "could not extract bytes from encoded string"); + } + + /* + * Then convert to server encoding if necessary. + * + * PyUnicode_AsEncodedString could be used to encode the object directly + * in the server encoding, but Python doesn't support all the encodings + * that PostgreSQL does (EUC_TW and MULE_INTERNAL). UTF-8 is used as an + * intermediary in PLyUnicode_FromString as well. + */ + if (GetDatabaseEncoding() != PG_UTF8) + { + PG_TRY(); + { + encoded = pg_any_to_server(utf8string, + strlen(utf8string), + PG_UTF8); + } + PG_CATCH(); + { + Py_DECREF(bytes); + PG_RE_THROW(); + } + PG_END_TRY(); + } + else + encoded = utf8string; + + /* finally, build a bytes object in the server encoding */ + rv = PyBytes_FromStringAndSize(encoded, strlen(encoded)); + + /* if pg_any_to_server allocated memory, free it now */ + if (utf8string != encoded) + pfree(encoded); + + Py_DECREF(bytes); + return rv; +} + +/* + * Convert a Python unicode object to a C string in PostgreSQL server + * encoding. No Python object reference is passed out of this + * function. The result is palloc'ed. + */ +char * +PLyUnicode_AsString(PyObject *unicode) +{ + PyObject *o = PLyUnicode_Bytes(unicode); + char *rv = pstrdup(PyBytes_AsString(o)); + + Py_XDECREF(o); + return rv; +} + +/* + * Convert a C string in the PostgreSQL server encoding to a Python + * unicode object. Reference ownership is passed to the caller. + */ +PyObject * +PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size) +{ + char *utf8string; + PyObject *o; + + utf8string = pg_server_to_any(s, size, PG_UTF8); + + if (utf8string == s) + { + o = PyUnicode_FromStringAndSize(s, size); + } + else + { + o = PyUnicode_FromString(utf8string); + pfree(utf8string); + } + + return o; +} + +PyObject * +PLyUnicode_FromString(const char *s) +{ + return PLyUnicode_FromStringAndSize(s, strlen(s)); +} diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h new file mode 100644 index 0000000..6f491b0 --- /dev/null +++ b/src/pl/plpython/plpy_util.h @@ -0,0 +1,17 @@ +/*-------------------------- + * common utility functions + *-------------------------- + */ + +#ifndef PLPY_UTIL_H +#define PLPY_UTIL_H + +#include "plpython.h" + +extern PGDLLEXPORT PyObject *PLyUnicode_Bytes(PyObject *unicode); +extern PGDLLEXPORT char *PLyUnicode_AsString(PyObject *unicode); + +extern PGDLLEXPORT PyObject *PLyUnicode_FromString(const char *s); +extern PGDLLEXPORT PyObject *PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size); + +#endif /* PLPY_UTIL_H */ diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h new file mode 100644 index 0000000..f33f7b5 --- /dev/null +++ b/src/pl/plpython/plpython.h @@ -0,0 +1,37 @@ +/*------------------------------------------------------------------------- + * + * plpython.h - Python as a procedural language for PostgreSQL + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/pl/plpython/plpython.h + * + *------------------------------------------------------------------------- + */ +#ifndef PLPYTHON_H +#define PLPYTHON_H + +/* postgres.h needs to be included before Python.h, as usual */ +#if !defined(POSTGRES_H) +#error postgres.h must be included before plpython.h +#elif defined(Py_PYTHON_H) +#error Python.h must be included via plpython.h +#endif + +/* + * Pull in Python headers via a wrapper header, to control the scope of + * the system_header pragma therein. + */ +#include "plpython_system.h" + +/* define our text domain for translations */ +#undef TEXTDOMAIN +#define TEXTDOMAIN PG_TEXTDOMAIN("plpython") + +/* + * Used throughout, so it's easier to just include it everywhere. + */ +#include "plpy_util.h" + +#endif /* PLPYTHON_H */ diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql new file mode 100644 index 0000000..ba2e6ac --- /dev/null +++ b/src/pl/plpython/plpython3u--1.0.sql @@ -0,0 +1,17 @@ +/* src/pl/plpython/plpython3u--1.0.sql */ + +CREATE FUNCTION plpython3_call_handler() RETURNS language_handler + LANGUAGE c AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void + STRICT LANGUAGE c AS 'MODULE_PATHNAME'; + +CREATE FUNCTION plpython3_validator(oid) RETURNS void + STRICT LANGUAGE c AS 'MODULE_PATHNAME'; + +CREATE LANGUAGE plpython3u + HANDLER plpython3_call_handler + INLINE plpython3_inline_handler + VALIDATOR plpython3_validator; + +COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language'; diff --git a/src/pl/plpython/plpython3u.control b/src/pl/plpython/plpython3u.control new file mode 100644 index 0000000..01905ef --- /dev/null +++ b/src/pl/plpython/plpython3u.control @@ -0,0 +1,7 @@ +# plpython3u extension +comment = 'PL/Python3U untrusted procedural language' +default_version = '1.0' +module_pathname = '$libdir/plpython3' +relocatable = false +schema = pg_catalog +superuser = true diff --git a/src/pl/plpython/plpython_system.h b/src/pl/plpython/plpython_system.h new file mode 100644 index 0000000..9532789 --- /dev/null +++ b/src/pl/plpython/plpython_system.h @@ -0,0 +1,53 @@ +/*------------------------------------------------------------------------- + * + * plpython_system.h - pull in Python's system header files + * + * We break this out as a separate header file to precisely control + * the scope of the "system_header" pragma. No Postgres-specific + * declarations should be put here. However, we do include some stuff + * that is meant to prevent conflicts between our code and Python. + * + * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/pl/plpython/plpython_system.h + * + *------------------------------------------------------------------------- + */ +#ifndef PLPYTHON_SYSTEM_H +#define PLPYTHON_SYSTEM_H + +/* + * Newer versions of the Python headers trigger a lot of warnings with our + * preferred compiler flags (at least -Wdeclaration-after-statement is known + * to be problematic). The system_header pragma hides warnings from within + * the rest of this file, if supported. + */ +#ifdef HAVE_PRAGMA_GCC_SYSTEM_HEADER +#pragma GCC system_header +#endif + +/* + * Python versions <= 3.8 otherwise define a replacement, causing macro + * redefinition warnings. + */ +#define HAVE_SNPRINTF 1 + +#if defined(_MSC_VER) && defined(_DEBUG) +/* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ +#undef _DEBUG +/* Also hide away errcode, since we load Python.h before postgres.h */ +#define errcode __msvc_errcode +#include <Python.h> +#undef errcode +#define _DEBUG +#elif defined (_MSC_VER) +#define errcode __msvc_errcode +#include <Python.h> +#undef errcode +#else +#include <Python.h> +#endif + +#endif /* PLPYTHON_SYSTEM_H */ diff --git a/src/pl/plpython/po/LINGUAS b/src/pl/plpython/po/LINGUAS new file mode 100644 index 0000000..4cc8bed --- /dev/null +++ b/src/pl/plpython/po/LINGUAS @@ -0,0 +1 @@ +cs de el es fr it ja ka ko pl pt_BR ru sv tr uk vi zh_CN zh_TW diff --git a/src/pl/plpython/po/cs.po b/src/pl/plpython/po/cs.po new file mode 100644 index 0000000..61576ac --- /dev/null +++ b/src/pl/plpython/po/cs.po @@ -0,0 +1,503 @@ +# Czech message translation file for plpython +# Copyright (C) 2012 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# +# Tomáš Vondra <tv@fuzzy.cz>, 2012, 2013. +msgid "" +msgstr "" +"Project-Id-Version: plpython-cs (PostgreSQL 9.3)\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2019-09-27 08:08+0000\n" +"PO-Revision-Date: 2019-09-27 21:01+0200\n" +"Last-Translator: Tomas Vondra <tv@fuzzy.cz>\n" +"Language-Team: Czech <info@cspug.cx>\n" +"Language: cs\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n" +"X-Generator: Poedit 2.2.3\n" + +#: plpy_cursorobject.c:78 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor očekával dotaz nebo plán" + +#: plpy_cursorobject.c:161 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor jako druhý argument očekává sekvenci" + +#: plpy_cursorobject.c:177 plpy_spi.c:211 +#, c-format +msgid "could not execute plan" +msgstr "nelze spustit plán" + +#: plpy_cursorobject.c:180 plpy_spi.c:214 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Očekávána posloupnost %d argument, předáno %d: %s" +msgstr[1] "Očekávána posloupnost %d argumentů, předáno %d: %s" +msgstr[2] "Očekávána posloupnost %d argumentů, předáno %d: %s" + +#: plpy_cursorobject.c:329 +#, c-format +msgid "iterating a closed cursor" +msgstr "iterování uzavřeného kurzoru" + +#: plpy_cursorobject.c:337 plpy_cursorobject.c:403 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iterování kurzoru ve zrušené subtransakci" + +#: plpy_cursorobject.c:395 +#, c-format +msgid "fetch from a closed cursor" +msgstr "fetch ze zavřeného kurzoru" + +#: plpy_cursorobject.c:438 plpy_spi.c:409 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "výsledek dotazu má příliš mnoho řádek a nevejde se to Python seznamu" + +#: plpy_cursorobject.c:490 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "uzavření kurzoru ve zrušené subtransakci" + +#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:143 +#, c-format +msgid "unsupported set function return mode" +msgstr "nepodporovaný návratový mód funkce vracející tabulku" + +#: plpy_exec.c:144 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python funkce vracející tabulku podporují pouze vracení jedné hodnoty pro každé volání." + +#: plpy_exec.c:157 +#, c-format +msgid "returned object cannot be iterated" +msgstr "přes vrácený objekt nelze iterovat" + +#: plpy_exec.c:158 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python funkce vracející tabulku musí vracet iterovatelný objekt." + +#: plpy_exec.c:172 +#, c-format +msgid "error fetching next item from iterator" +msgstr "chyba při načítání další položky z iterátoru" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python procedura nevrátila hodnotu None" + +#: plpy_exec.c:219 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python funkce s návratovým typem \"void\" nevrátila hodnotu None" + +#: plpy_exec.c:375 plpy_exec.c:401 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "neočekávaná návratová hodnota z trigger procedury" + +#: plpy_exec.c:376 +#, c-format +msgid "Expected None or a string." +msgstr "Očekáváno None nebo řetězec." + +#: plpy_exec.c:391 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python trigger funkce vrátila \"MODIFY\" v DELETE triggeru -- ignorováno" + +#: plpy_exec.c:402 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Očekáváno None, \"OK\", \"SKIP\", nebo \"MODIFY\"." + +#: plpy_exec.c:452 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "volání PyList_SetItem() selhalo během vytváření argumentů" + +#: plpy_exec.c:456 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "volání PyDict_SetItemString() selhalo během přípravy argumentů" + +#: plpy_exec.c:468 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "funkce vracející záznam byla zavolána z kontextu, který neumožňuje přijetí záznamu" + +#: plpy_exec.c:685 +#, c-format +msgid "while creating return value" +msgstr "během vytváření návratové hodnoty" + +#: plpy_exec.c:919 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] smazáno, nelze modifikovat řádek" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] není slovník" + +#: plpy_exec.c:951 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "TD[\"new\"] klíč slovníku na pozici %d není řetězec" + +#: plpy_exec.c:958 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "klíč \"%s\" nalezený v TD[\"new\"] neexistuje jako sloupec v triggering řádku" + +#: plpy_exec.c:963 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "nelze nastavit systémový atribut \"%s\"" + +#: plpy_exec.c:968 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "nelze přiřazovat do generovaného sloupce \"%s\"" + +#: plpy_exec.c:1026 +#, c-format +msgid "while modifying trigger row" +msgstr "během modifikace řádku triggeru" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "vynucené ukončení subtransakce která nebyla dokončena" + +#: plpy_main.c:125 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "v session je načteno několik Python knihoven" + +#: plpy_main.c:126 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Pouze jedna major verze Pythonu může být použita v jedné session." + +#: plpy_main.c:142 +#, c-format +msgid "untrapped error in initialization" +msgstr "nezachycená chyba v inicializaci" + +#: plpy_main.c:165 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "nepodařilo se naimportovat \"__main__\" modul" + +#: plpy_main.c:174 +#, c-format +msgid "could not initialize globals" +msgstr "nepodařilo se inicializovat globální proměnné (globals)" + +#: plpy_main.c:399 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python procedura \"%s\"" + +#: plpy_main.c:402 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python funkce \"%s\"" + +#: plpy_main.c:410 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python anonymní blok kódu" + +#: plpy_plpymodule.c:186 plpy_plpymodule.c:189 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "nepodařilo se naimportovat \"plpy\" modul" + +#: plpy_plpymodule.c:204 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "nepodařilo se vytvořit spiexceptions modul" + +#: plpy_plpymodule.c:212 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "nepodařilo se naimportovat \"__main__\" modul" + +#: plpy_plpymodule.c:280 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "nepodařilo se vygenerovat SPI výjimky" + +#: plpy_plpymodule.c:448 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "nepodařilo se rozbalit argumenty v plpy.elog" + +#: plpy_plpymodule.c:457 +msgid "could not parse error message in plpy.elog" +msgstr "nepodařilo se naparsovat chybovou hlášku v plpy.elog" + +#: plpy_plpymodule.c:474 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "argument 'message' zadán jménem a pozicí" + +#: plpy_plpymodule.c:501 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' je neplatný keyword argument pro tuto funkci" + +#: plpy_plpymodule.c:512 plpy_plpymodule.c:518 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "chybný SQLSTATE kód" + +#: plpy_procedure.c:230 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "funkce pro obsluhu triggerů mohou být volané pouze prostřednictvím triggerů" + +#: plpy_procedure.c:234 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python funkce nemohou vracet typ %s" + +#: plpy_procedure.c:312 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python funkce nepodporují typ %s" + +#: plpy_procedure.c:402 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "nelze zkompiloval PL/Python funkci \"%s\"" + +#: plpy_procedure.c:405 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "nelze zkompiloval anonymní PL/Python blok" + +#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173 +#, c-format +msgid "command did not produce a result set" +msgstr "příkaz nevrátil žádný výsledek" + +#: plpy_spi.c:60 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "druhý argument pro plpy.prepare musí být posloupnost" + +#: plpy_spi.c:104 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: název typu na pozici %d není řetězec" + +#: plpy_spi.c:176 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute očekávala dotaz nebo plán" + +#: plpy_spi.c:195 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute jako druhý argument očekává posloupnost" + +#: plpy_spi.c:305 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "volání SPI_execute_plan selhalo: %s" + +#: plpy_spi.c:347 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "volání SPI_execute selhalo: %s" + +#: plpy_subxactobject.c:97 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "tato subtransakce již byla zahájena" + +#: plpy_subxactobject.c:103 plpy_subxactobject.c:161 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "tato subtransakce již byla dokončena" + +#: plpy_subxactobject.c:155 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "tato subtransakce nebyla zahájena" + +#: plpy_subxactobject.c:167 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "nenalezena subtransakce k ukončení" + +#: plpy_typeio.c:591 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "nepodařilo se naimportovat modul pro Decimal constructor" + +#: plpy_typeio.c:595 +#, c-format +msgid "no Decimal attribute in module" +msgstr "modul nemá žádný Decimal atribut" + +#: plpy_typeio.c:601 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "konverze z numeric na Decimal selhala" + +#: plpy_typeio.c:915 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "nepodařilo se vytvořit bytovou reprezentaci Python objektu" + +#: plpy_typeio.c:1063 +#, c-format +msgid "could not create string representation of Python object" +msgstr "nepodařilo se vytvořit řetězcovou reprezentaci Python objektu" + +#: plpy_typeio.c:1074 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "nepodařilo se převést Python objekt na cstring: zdá se že řetězcová reprezentace Python objektu obsahuje null byty" + +#: plpy_typeio.c:1183 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "počet dimenzí pole překračuje povolené maximum (%d)" + +#: plpy_typeio.c:1187 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "nelze určit délku posloupnosti pro návratovou hodnotu funkce" + +#: plpy_typeio.c:1190 plpy_typeio.c:1194 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "velikost pole překračuje povolené maximum" + +#: plpy_typeio.c:1220 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "návratová hodnota funkce s návratovým typem pole není Python posloupnost (sequence)" + +#: plpy_typeio.c:1266 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "chybná délka vnitční sekvence: má délku %d, ale očekáváno bylo %d" + +#: plpy_typeio.c:1268 +#, c-format +msgid "To construct a multidimensional array, the inner sequences must all have the same length." +msgstr "Pro vytvoření vícerozměrného pole musí mít včechny vnitřní sekvence stejnou délku." + +#: plpy_typeio.c:1347 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "chybný literál záznamu: \"%s\"" + +#: plpy_typeio.c:1348 +#, c-format +msgid "Missing left parenthesis." +msgstr "Chybějící levá závorka." + +#: plpy_typeio.c:1349 plpy_typeio.c:1550 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Pro vrácení kompozitního typu v poli, vracejte kompozitní typ jako Python tuple, e.g., \"[('foo',)]\"." + +#: plpy_typeio.c:1396 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "klíč \"%s\" nenalezen v mapování" + +#: plpy_typeio.c:1397 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Pro vrácení hodnoty null ve sloupci, přidejte do mapování hodnotu None s klíčem pojmenovaným jako sloupec." + +#: plpy_typeio.c:1450 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "délka vrácené posloupnosti neodpovídala počtu sloupců v řádku" + +#: plpy_typeio.c:1548 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "atribut \"%s\" v Python objektu neexistuje" + +#: plpy_typeio.c:1551 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Pro vrácení null ve sloupci, nechť vracený objekt má atribut pojmenovaný po sloupcis hodnotou None." + +#: plpy_util.c:35 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "nelze převést Python Unicode objekt na byty" + +#: plpy_util.c:41 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "nepodařilo se získat byty z kódovaného řetězce" + +#~ msgid "could not initialize plpy" +#~ msgstr "nepodařilo se inicializovat plpy" + +#~ msgid "could not create new Python list" +#~ msgstr "nelze vytvořit nový Python seznam" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python podporuje pouze jednorozměrná pole." + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "vícerozměrné pole nelze převést na Python seznam (list)" + +#~ msgid "PL/Python does not support conversion to arrays of row types." +#~ msgstr "PL/Python nepodporuje konverzi na pole řádkových typů." + +#~ msgid "could not create new dictionary" +#~ msgstr "nepodařilo se vytvořit nový slovník" + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "nerozpoznaná chyba v PLy_spi_execute_fetch_result" + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare nepodporuje složené typy" + +#~ msgid "could not create the base SPI exceptions" +#~ msgstr "nepodařilo se vygenerovat základní SPI výjimky" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status nepřijímá žádné argumenty" + +#~ msgid "could not create globals" +#~ msgstr "nepodařilo se vytvořit globals" + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "Spouští se nová session pro použití jiné hlavní verze Pythonu." + +#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +#~ msgstr "Tato session již dříve používala Python s hlavní verzí %d, a nyní se pokouší použít Python s hlavní verzí %d." + +#~ msgid "could not create new dictionary while building trigger arguments" +#~ msgstr "během vytváření argumentů triggeru nelze vytvářet nový slovník" diff --git a/src/pl/plpython/po/de.po b/src/pl/plpython/po/de.po new file mode 100644 index 0000000..29962f1 --- /dev/null +++ b/src/pl/plpython/po/de.po @@ -0,0 +1,451 @@ +# German message translation file for plpython +# Copyright (C) 2009 - 2019 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# Peter Eisentraut <peter@eisentraut.org>, 2009 - 2019. +# +# Use these quotes: »%s« +# +msgid "" +msgstr "" +"Project-Id-Version: PostgreSQL 12\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-05-04 23:38+0000\n" +"PO-Revision-Date: 2023-05-05 07:45+0200\n" +"Last-Translator: Peter Eisentraut <peter@eisentraut.org>\n" +"Language-Team: German <pgsql-translators@postgresql.org>\n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor hat eine Anfrage oder einen Plan erwartet" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor nimmt eine Sequenz als zweites Argument" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "konnte Plan nicht ausführen" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Sequenz aus %d Argument erwartet, aber %d erhalten: %s" +msgstr[1] "Sequenz aus %d Argumenten erwartet, aber %d erhalten: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "Iteration mit einem geschlossenen Cursor" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "Iteration mit einem Cursor in einer abgebrochenen Transaktionen" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "Lesen aus einem geschlossenen Cursor" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "Anfrageergebnis hat zu viele Zeilen, um in eine Python-Liste zu passen" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "Schließen eines Cursors in einer abgebrochenen Subtransaktion" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "nicht unterstützter Rückgabemodus für Funktion mit Mengenergebnis" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python unterstützt für Funktionen mit Mengenergebnis nur das Zurückgeben von einem Wert pro Aufruf." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "zurückgegebenes Objekt kann nicht iteriert werden" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python-Funktionen mit Mengenergebnis müssen ein iterierbares Objekt zurückgeben." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "Fehler beim Auslesen des nächsten Elements vom Iterator" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python-Prozedur hat nicht None zurückgegeben" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python-Funktion mit Rückgabetyp »void« hat nicht None zurückgegeben" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "unerwarteter Rückgabewert von Triggerprozedur" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Erwartete None oder eine Zeichenkette." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python-Funktion gab in einem DELETE-Trigger \"MODIFY\" zurück -- ignoriert" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Erwartete None, \"OK\", \"SKIP\" oder \"MODIFY\"." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() fehlgeschlagen, beim Einrichten der Argumente" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() fehlgeschlagen, beim Einrichten der Argumente" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "Funktion, die einen Record zurückgibt, in einem Zusammenhang aufgerufen, der Typ record nicht verarbeiten kann" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "beim Erzeugen des Rückgabewerts" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] wurde gelöscht, kann Zeile nicht ändern" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] ist kein Dictionary" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "Dictionary-Schlüssel auf Position %d in TD[\"new\"] ist keine Zeichenkette" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "in TD[\"new\"] gefundener Schlüssel »%s« existiert nicht als Spalte in der den Trigger auslösenden Zeile" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "Systemattribut »%s« kann nicht gesetzt werden" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "kann generierte Spalte »%s« nicht setzen" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "beim Ändern der Triggerzeile" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "Abbruch einer Subtransaktion, die nicht beendet wurde, wird erzwungen" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "in dieser Sitzung sind mehrere Python-Bibliotheken präsent" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Nur eine Python-Hauptversion kann in einer Sitzung verwendet werden." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "nicht abgefangener Fehler bei der Initialisierung" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "konnte Modul »__main__« nicht importieren" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "konnte globale Objekte nicht initialisieren" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python-Prozedur »%s«" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python-Funktion »%s«" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "anonymer PL/Python-Codeblock" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "konnte Modul »plpy« nicht importieren" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "konnte das Modul »spiexceptions« nicht erzeugen" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "konnte das Modul »spiexceptions« nicht hinzufügen" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "konnte SPI-Ausnahmen nicht erzeugen" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "konnte Argumente in plpy.elog nicht entpacken" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "konnte Fehlermeldung in plpy.elog nicht parsen" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "Argument »message« wurde durch Namen und Position angegeben" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "»%s« ist ein ungültiges Schlüsselwortargument für diese Funktion" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "ungültiger SQLSTATE-Code" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "Triggerfunktionen können nur als Trigger aufgerufen werden" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python-Funktionen können keinen Rückgabetyp %s haben" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python-Funktionen können Typ %s nicht annehmen" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "konnte PL/Python-Funktion »%s« nicht kompilieren" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "konnte anonymen PL/Python-Codeblock nicht kompilieren" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "Befehl hat keine Ergebnismenge erzeugt" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "zweites Argument von plpy.prepare muss eine Sequenz sein" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: Typname auf Position %d ist keine Zeichenkette" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute hat eine Anfrage oder einen Plan erwartet" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute nimmt eine Sequenz als zweites Argument" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan fehlgeschlagen: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute fehlgeschlagen: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "diese Subtransaktion wurde bereits begonnen" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "diese Subtransaktion wurde bereits beendet" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "diese Subtransaktion wurde nicht begonnen" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "es gibt keine Subtransaktion zu beenden" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "konnte kein Modul für den »Decimal«-Konstruktor importieren" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "kein Attribut »Decimal« im Modul" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "Umwandlung von numeric in Decimal fehlgeschlagen" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "konnte Bytes-Darstellung eines Python-Objektes nicht erzeugen" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "konnte Zeichenkettendarstellung eines Python-Objektes nicht erzeugen" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "konnte Python-Objekt nicht in cstring umwandeln: Python-Zeichenkettendarstellung enthält anscheinend Null-Bytes" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "Rückgabewert von Funktion mit Array-Rückgabetyp ist keine Python-Sequenz" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "konnte Sequenzlänge für Funktionsrückgabewert nicht ermitteln" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "mehrdimensionale Arrays müssen Arraysausdrücke mit gleicher Anzahl Dimensionen haben" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "Anzahl der Arraydimensionen überschreitet erlaubtes Maximum (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "fehlerhafte Record-Konstante: »%s«" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Linke Klammer fehlt." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Um einen zusammengesetzten Typ in einem Array zurückzugeben, geben Sie den zusammengesetzten Typ als ein Python-Tupel zurück, z.B. »[('foo',)]«." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "Schlüssel »%s« nicht in Mapping gefunden" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Um einen NULL-Wert in einer Spalte zurückzugeben, muss der Wert None mit einem nach der Spalte benannten Schlüssel in das Mapping eingefügt werden." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "Länge der zurückgegebenen Sequenz hat nicht mit der Anzahl der Spalten in der Zeile übereingestimmt" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "Attribut »%s« existiert nicht in Python-Objekt" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Um einen NULL-Wert in einer Spalte zurückzugeben, muss das zurückzugebende Objekt ein nach der Spalte benanntes Attribut mit dem Wert None haben." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "konnte Python-Unicode-Objekt nicht in Bytes umwandeln" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "konnte kodierte Zeichenkette nicht in Bytes umwandeln" diff --git a/src/pl/plpython/po/el.po b/src/pl/plpython/po/el.po new file mode 100644 index 0000000..a606ad1 --- /dev/null +++ b/src/pl/plpython/po/el.po @@ -0,0 +1,461 @@ +# Greek message translation file for plpython +# Copyright (C) 2021 PostgreSQL Global Development Group +# This file is distributed under the same license as the plpython (PostgreSQL) package. +# Georgios Kokolatos <gkokolatos@pm.me>, 2021. +# +# +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 14\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-08-15 13:38+0000\n" +"PO-Revision-Date: 2023-08-15 16:14+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.3.2\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor ανέμενε ένα ερώτημα ή ένα σχέδιο" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor λαμβάνει μια ακολουθία ως τη δεύτερη παράμετρο" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "δεν ήταν δυνατή η εκτέλεση του σχεδίου" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Ανέμενε ακολουθία %d παραμέτρου, έλαβε %d: %s" +msgstr[1] "Ανέμενε ακολουθία %d παραμέτρων, έλαβε %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "επαναλαμβάνει έναν κλειστό δρομέα" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "επαναλαμβάνει ένα δρομέα σε μία ματαιωμένη υποσυναλλαγή" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "ανάκτηση από κλειστό δρομέα" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "το αποτέλεσμα του ερωτήματος έχει πάρα πολλές σειρές για να χωρέσει σε μια λίστα Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "κλείσιμο ενός δρομέα σε μία ματαιωμένη υποσυναλλαγή" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "μη υποστηριζόμενο είδος επιστροφής συνάρτησης συνόλου" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "Οι λειτουργίες επιστροφής συνόλου PL/Python υποστηρίζουν μόνο την επιστροφή μίας τιμής ανά κλήση." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "το επιστρεφόμενο αντικείμενο δεν μπορεί να επαναληφθεί" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "Οι συναρτήσεις επιστροφής συνόλου PL/Python επιβάλλεται να επιστρέφουν ένα επαναλαμβανόμενο αντικείμενο." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "σφάλμα κατά τη λήψη του επόμενου στοιχείου από το πρόγραμμα λήψης" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "διεργασία PL/Python δεν επέστρεψε None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "συνάρτηση PL/Python με τύπο επιστροφής «void» δεν επέστρεψε None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "μη αναμενόμενη τιμή επιστροφής από διεργασία εναύσματος" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Αναμενόταν None ή μία συμβολοσειρά." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "συνάρτηση εναύσματος PL/Python επέστρεψε «MODIFY» σε ένα έναυσμα DELETE -- παραβλέφθηκε" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Αναμενόταν None, «OK», «SKIP», ή «MODIFY»." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() απέτυχε, κατά τη διάρκεια ετοιμασίας των παραμέτρων" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() απέτυχε, κατά τη διάρκεια ετοιμασίας των παραμέτρων" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "συνάρτηση που επιστρέφει εγγραφή καλείται σε περιεχόμενο που δεν δύναται να αποδεχτεί τύπο εγγραφής" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "κατά τη δημιουργία τιμής επιστροφής" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[«new»] διαγράφηκε, δεν είναι δυνατή η μετατροπή σειράς" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[«new»] δεν είναι ένα λεξικό" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "TD[«new»] κλειδί λεξικού στη τακτή θέση %d δεν είναι μία συμβολοσειρά" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "κλειδί «%s» που βρίσκεται στο TD[\"New\"] δεν υπάρχει ως στήλη στη γραμμή εναύσματος" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "δεν είναι δυνατός ο ορισμός του χαρακτηριστικού συστήματος «%s»" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "δεν είναι δυνατός ο ορισμός δημιουργημένης στήλης «%s»" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "κατά την τροποποίηση εναύσματος σειράς" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "βίαιη ματαίωση μιας υποσυναλλαγής που δεν έχει εξέλθει" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "υπάρχουν πολλαπλές βιβλιοθήκες Python στη συνεδρία" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Μόνο μία κύρια έκδοση Python μπορεί να χρησιμοποιηθεί σε μία συνεδρία." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "μη παγιδευμένο σφάλμα κατά την προετοιμασία" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "δεν ήταν δυνατή η εισαγωγή του αρθρώματος «__main__»" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "δεν ήταν δυνατή η αρχικοποίηση καθολικών μεταβλητών" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "διεργασία PL/Python «%s»" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "συνάρτηση PL/Python «%s»" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "ονώνυμο μπλοκ κώδικα PL/Python" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "δεν ήταν δυνατή η εισαγωγή του αρθρώματος «plpy»" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "δεν ήταν δυνατή η δημιουργία του αρθρώματος spiexceptions" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "δεν ήταν δυνατή η πρόσθεση του αρθρώματος spiexceptions" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "δεν ήταν δυνατή η του αρθρώματος spiexceptions" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "δεν ήταν δυνατή η αποσυσκευασία παραμέτρων στο plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "δεν ήταν δυνατή η ανάλυση μηνυμάτος σφάλματος στο plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "παράμετρος «μήνυμα» που δόθηκε με όνομα και θέση" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "‘%s’ είναι μία άκυρη παράμετρος με λέξη-κλειδί για αυτή τη συνάρτηση" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "μη έγκυρος κωδικός SQLSTATE" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "συναρτήσεις εναυσμάτων μπορούν να κληθούν μόνο ως εναύσματα" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "οι συναρτήσεις PL/Python δεν μπορούν να επιστρέψουν τύπο %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "οι συναρτήσεις PL/Python δεν μπορούν να δεχθούν τύπο %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "δεν ήταν δυνατή η μεταγλώττιση της συνάρτησης PL/Python «%s»" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "δεν ήταν δυνατή η μεταγλώττιση ανώνυμου μπλοκ κώδικα PL/Python" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "η εντολή δεν παρήγαγε ένα σύνολο αποτελάσματων" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "η δεύτερη παράμετρος του plpy.prepare επιβάλλεται να είναι μία ακολουθία" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: το όνομα τύπου στη τακτή θέση %d δεν είναι συμβολοσειρά" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute ανέμενε ένα ερώτημα ή ένα σχέδιο" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute λαμβάνει μια ακολουθία ως δεύτερη παράμετρό του" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan απέτυχε: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute απέτυχε: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "έχει εισέλθει ήδη σε αυτή τη υποσυναλλάγή" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "έχει εξέλθει ήδη από αυτή τη υποσυναλλάγή" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "δεν έχει εισέλθει σε αυτή τη υποσυναλλάγή" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "δεν υπάρχει υποσυναλλαγή από την οποία να εξέλθει" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "δεν ήταν δυνατή η εισαγωγή αρθρώματος για τον κατασκευαστή Decimal" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "καμία ιδιότητα Decimal στο άρθρωμα" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "μετατροπή από αριθμητικό σε Decimal απέτυχε" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "δεν ήταν δυνατή η δημιουργία bytes αναπαράστασης του αντικειμένου Python" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "δεν ήταν δυνατή η δημιουργία string αναπαράστασης του αντικειμένου Python" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "δεν ήταν δυνατή η μετατροπή του αντικειμένου Python σε cstring: Η αναπαράσταση συμβολοσειράς Python φαίνεται να περιέχει null bytes" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "η τιμή επιστροφής της συνάρτησης με τύπο επιστροφής συστυχίας δεν είναι μία ακολουθία Python" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "δεν ήταν δυνατός ο προσδιορισμός του μήκους της ακολουθίας για την τιμή επιστροφής της συνάρτησης" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "πολυδιάστατες συστυχίες πρέπει να περιέχουν εκφράσεις συστυχίας με αντίστοιχο αριθμό διαστάσεων" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "o αριθμός των διαστάσεων συστοιχίας υπερβαίνει το μέγιστο επιτρεπόμενο (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "κακοσχηματισμένο εγγραφή: «%s»" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Λείπει δεξιά παρένθεση." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Για να επιστρέψετε έναν σύνθετο τύπο σε μία συστυχία, επιστρέψτε τον σύνθετο τύπο ως πλειάδα Python, π.χ. «[(‘foo’,)»." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "κλειδί «%s» δεν βρέθηκε σε αντιστοίχιση" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Για να επιστρέψετε null σε μια στήλη, προσθέστε την τιμή None στην αντιστοίχιση με το κλειδί που ονομάστηκε από τη στήλη." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "το μήκος της ακολουθίας που επιστράφηκε δεν ταίριαζε με τον αριθμό των στηλών στη γραμμή" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "η ιδιότητα «%s» δεν υπάρχει στο αντικείμενο Python" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Για να επιστρέψετε null σε μια στήλη, αφήστε το αντικείμενο που επιστράφηκε να έχει ένα χαρακτηριστικό που ονομάζεται από την στήλη με τιμή None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "δεν ήταν δυνατή η μετατροπή του αντικειμένου Python Unicode σε bytes" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "δεν ήταν δυνατή η εξόρυξη bytes από κωδικοποιημένη συμβολοσειρά" + +#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length." +#~ msgstr "Για να κατασκευαστεί μια πολυδιάστατη συστυχία, οι εσωτερικές ακολουθίες πρέπει να έχουν όλες το ίδιο μήκος." + +#~ msgid "array size exceeds the maximum allowed" +#~ msgstr "το μέγεθος συστοιχίας υπερβαίνει το μέγιστο επιτρεπόμενο" + +#~ msgid "wrong length of inner sequence: has length %d, but %d was expected" +#~ msgstr "λάθος μήκος της εσωτερικής ακολουθίας: έχει μήκος %d , αλλά αναμενόταν %d" diff --git a/src/pl/plpython/po/es.po b/src/pl/plpython/po/es.po new file mode 100644 index 0000000..8186ef8 --- /dev/null +++ b/src/pl/plpython/po/es.po @@ -0,0 +1,454 @@ +# Spanish message translation file for plpython +# +# Copyright (c) 2009-2021, PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# +# Emanuel Calvo Franco <postgres.arg@gmail.com>, 2009. +# Alvaro Herrera <alvherre@alvh.no-ip.org>, 2009-2012 +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 16\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-05-22 07:08+0000\n" +"PO-Revision-Date: 2023-05-22 12:06+0200\n" +"Last-Translator: Carlos Chapi <carlos.chapi@2ndquadrant.com>\n" +"Language-Team: PgSQL-es-Ayuda <pgsql-es-ayuda@lists.postgresql.org>\n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Poedit 2.0.2\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor espera una consulta o un plan" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor lleva una secuencia como segundo argumento" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "no se pudo ejecutar el plan" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Se esperaba una secuencia de %d argumento, se obtuvo %d: %s" +msgstr[1] "Se esperaba una secuencia de %d argumentos, se obtuvo %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "iterando un cursor cerrado" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iterando un cursor en una subtransacción abortada" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "haciendo «fetch» en un cursor cerrado" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "el resultado de la consulta tiene demasiados registros y no entran en una lista de Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "cerrando un cursor en una subtransacción abortada" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "modo de retorno de conjunto de función no soportado" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "Las funciones PL/Python que retornan conjuntos sólo permiten retornar un valor por invocación." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "objeto retornado no puede ser iterado" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "Los funciones PL/Python que retornan conjuntos deben retornar un objeto iterable." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "error extrayendo el próximo elemento del iterador" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "procedimiento PL/Python no returnó None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "función PL/Python con tipo de retorno «void» no retorna None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "valor de retorno no esperado desde el procedimiento disparador" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Se esperaba None o una cadena." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "función de disparador de PL/Python retorno «MODIFY» en un disparador de tipo DELETE -- ignorado" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Se esperaba None, «OK», «SKIP» o «MODIFY»." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() falló, mientras se inicializaban los argumentos" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() falló, mientras se inicializaban los argumentos" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "se llamó una función que retorna un registro en un contexto que no puede aceptarlo" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "mientras se creaba el valor de retorno" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] borrado, no se puede modicar el registro" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] no es un diccionario" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "el nombre del atributo de TD[\"new\"] en la posición %d no es una cadena" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "la llave «%s» en TD[\"new\"] no existe como columna en la fila disparadora" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "no se puede definir el atributo de sistema «%s»" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "no se puede definir el atributo generado «%s»" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "mientras se modificaba la fila de disparador" + +# FIXME not very happy with this +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "abortando una subtransacción que no se ha cerrado" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "hay múltiples librerías de Python presentes en esta sesión" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Sólo se puede usar una versión mayor de Python en cada sesión." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "error no capturado en la inicialización" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "no se pudo importar el módulo «__main__»" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "no se pudo inicializar las globales" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "procedimiento PL/Python «%s»" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "función PL/Python «%s»" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "bloque de código anónimo de PL/Python" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "no se pudo importar el módulo «plpy»" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "no se pudo crear el módulo spiexceptions" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "no se pudo importar el módulo spiexceptions" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "no se pudo generar excepciones SPI" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "no se pudo desempaquetar los argumentos de plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "no se pudo analizar el mensaje de error de plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "el argumento 'message' fue pasado por nombre y posición" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "«%s» no es un argumento válido para esta función" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "código SQLSTATE no válido" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "las funciones disparadoras sólo pueden ser llamadas como disparadores" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "las funciones PL/Python no pueden retornar el tipo %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "la funciones PL/Python no pueden aceptar el tipo %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "no se pudo compilar la función PL/Python «%s»" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "no se pudo compilar el bloque anónimo PL/Python" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "la orden no produjo un conjunto de resultados" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "el segundo argumento de plpy.prepare debe ser una secuencia" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: el nombre de tipo en la posición %d no es una cadena" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute espera una consulta o un plan" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute lleva una secuencia como segundo argumento" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "falló SPI_execute_plan: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "falló SPI_execute: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "ya se ha entrado en esta subtransacción" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "ya se ha salido de esta subtransacción" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "no se ha entrado en esta subtransacción" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "no hay una subtransacción de la cual salir" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "no se pudo importar un módulo para el constructor Decimal" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "no se encontró atributo Decimal en el módulo" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "falló la conversión de numeric a Decimal" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "no se pudo crear la representación de cadena de bytes de Python" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "no se pudo crear la representación de cadena de texto del objeto de Python" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "no se pudo convertir el objeto Python a un cstring: la representación de cadena Python parece tener bytes nulos (\\0)" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "el valor de retorno de la función con tipo de retorno array no es una secuencia Python" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "no se pudo determinar el largo de secuencia del retorno de valor de la función" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "los arrays multidimensionales deben tener expresiones de arrays con dimensiones coincidentes" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "el número de dimensiones del array excede el máximo permitido (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "literal de record no es válido: «%s»" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Falta paréntesis izquierdo." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Para retornar un tipo compuesto en un array, retorne el tipo compuesto como una tupla de Python, e.g., «[('foo',)]»." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "la llave «%s» no fue encontrada en el mapa" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Para retornar null en una columna, agregue el valor None al mapa, con llave llamada igual que la columna." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "el tamaño de la secuencia retornada no concuerda con el número de columnas de la fila" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "el atributo «%s» no existe en el objeto Python" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Para retornar null en una columna, haga que el objeto retornado tenga un atributo llamado igual que la columna, con valor None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "no se pudo convertir el objeto Unicode de Python a bytes" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "no se pudo extraer bytes desde la cadena codificada" diff --git a/src/pl/plpython/po/fr.po b/src/pl/plpython/po/fr.po new file mode 100644 index 0000000..2922a28 --- /dev/null +++ b/src/pl/plpython/po/fr.po @@ -0,0 +1,590 @@ +# LANGUAGE message translation file for plpython +# Copyright (C) 2009-2022 PostgreSQL Global Development Group +# This file is distributed under the same license as the plpython (PostgreSQL) package. +# +# Use these quotes: « %s » +# +# Guillaume Lelarge <guillaume@lelarge.info>, 2009-2022. +# +msgid "" +msgstr "" +"Project-Id-Version: PostgreSQL 15\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-07-30 09:08+0000\n" +"PO-Revision-Date: 2022-04-12 17:29+0200\n" +"Last-Translator: Guillaume Lelarge <guillaume@lelarge.info>\n" +"Language-Team: French <guillaume@lelarge.info>\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"X-Generator: Poedit 3.0.1\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor attendait une requête ou un plan" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor prends une séquence dans son second argument" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "n'a pas pu exécuter le plan" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Séquence attendue de %d argument, %d obtenu : %s" +msgstr[1] "Séquence attendue de %d arguments, %d obtenus : %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "itération d'un curseur fermé" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "itération d'un curseur dans une sous-transaction annulée" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "récupérer à partir d'un curseur fermé" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "le résultat de la requête contient trop de lignes pour être intégré dans une liste Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "fermeture d'un curseur dans une sous-transaction annulée" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "mode de retour non supporté pour la fonction SET" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "" +"les fonctions PL/python renvoyant des ensembles supportent seulement une\n" +"valeur renvoyée par appel." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "l'objet renvoyé ne supporte pas les itérations" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "" +"les fonctions PL/python renvoyant des ensembles doivent renvoyer un objet\n" +"itérable" + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "erreur lors de la récupération du prochain élément de l'itérateur" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "la procédure PL/python n'a pas renvoyé None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "la fonction PL/python avec un code de retour « void » ne renvoyait pas None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "valeur de retour inattendue de la procédure trigger" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Attendait None ou une chaîne de caractères." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "" +"la fonction trigger PL/python a renvoyé « MODIFY » dans un trigger DELETE\n" +"-- ignoré" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Attendait None, « OK », « SKIP » ou « MODIFY »." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "échec de PyList_SetItem() lors de l'initialisation des arguments" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "échec de PyDict_SetItemString() lors de l'initialisation des arguments" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "" +"fonction renvoyant le type record appelée dans un contexte qui ne peut pas\n" +"accepter le type record" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "lors de la création de la valeur de retour" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] supprimé, ne peut pas modifier la ligne" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] n'est pas un dictionnaire" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "la clé TD[\"new\"] à la position ordinale %d n'est pas une chaîne" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "" +"la clé « %s » trouvée dans TD[\"new\"] n'existe pas comme colonne\n" +"de la ligne impactée par le trigger" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "ne peut pas initialiser l'attribut système « %s »" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "ne peut pas initialiser la colonne générée « %s »" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "lors de la modification de la ligne du trigger" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "annulation forcée d'une sous-transaction qui n'a jamais été quittée" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "plusieurs bibliothèques Python sont présentes dans la session" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Seule une version majeure de Python peut être utilisée dans une session." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "erreur non récupérée dans l'initialisation" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "n'a pas pu importer le module « __main__ »" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "n'a pas pu initialiser les variables globales" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "procédure PL/python « %s »" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "fonction PL/python « %s »" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "bloc de code PL/Python anonyme" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "n'a pas pu importer le module « plpy »" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "n'a pas pu créer le module « spiexceptions »" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "n'a pas pu ajouter le module « spiexceptions »" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "n'a pas pu générer les exceptions SPI" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "n'a pas pu déballer les arguments dans plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "n'a pas pu analyser le message d'erreur dans plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "argument 'message' donné par nom et position" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' est une argument mot-clé invalide pour cette fonction" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "code SQLSTATE invalide" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "les fonctions trigger peuvent seulement être appelées par des triggers" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "les fonctions PL/python ne peuvent pas renvoyer le type %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "les fonctions PL/python ne peuvent pas accepter le type %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "n'a pas pu compiler la fonction PL/python « %s »" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "n'a pas pu compiler le bloc de code anonyme PL/python" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "la commande n'a pas fourni d'ensemble de résultats" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "le second argument de plpy.prepare doit être une séquence" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare : le nom du type sur la position ordinale %d n'est pas une chaîne" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.prepare attendait une requête ou un plan" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute prends une séquence dans son second argument" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "échec de SPI_execute_plan : %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "échec de SPI_execute : %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "cette sous-transaction est en cours d'utilisation" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "déjà sorti de cette sous-transaction" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "cette sous-transaction n'a jamais été utilisée" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "il n'y a pas de transaction à quitter" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "n'a pas pu importer un module pour le constructeur Decimal" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "pas d'attribut Decimal dans le module" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "échec de la conversion numeric vers Decimal" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "n'a pas pu créer une représentation octets de l'objet Python" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "n'a pas pu créer une représentation chaîne de caractères de l'objet Python" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "n'a pas pu convertir l'objet Python en csting : la représentation de la chaîne Python contient des octets nuls" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "la valeur de retour de la fonction de type tableau n'est pas une séquence Python" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "n'a pas pu déterminer la longueur de la séquence pour la valeur de retour de la fonction" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "" +"les tableaux multidimensionnels doivent avoir des expressions de tableaux\n" +"avec les dimensions correspondantes" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "le nombre de dimensions du tableau dépasse le maximum autorisé (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "enregistrement litéral invalide : « %s »" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Parenthèse gauche manquante." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Pour renvoyer un type composite dans un tableau, renvoyez le type composite sous la forme d'un tuple Python, c'est-à-dire \"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "la clé « %s » introuvable dans la correspondance" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "" +"Pour renvoyer NULL dans une colonne, ajoutez la valeur None à la\n" +"correspondance de la clé nommée d'après la colonne." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "" +"la longueur de la séquence renvoyée ne correspondait pas au nombre de\n" +"colonnes dans la ligne" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "l'attribut « %s » n'existe pas dans l'objet Python" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "" +"Pour renvoyer NULL dans une colonne, faites en sorte que l'objet renvoyé ait\n" +"un attribut nommé suivant la colonne de valeur None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "n'a pas pu convertir l'objet Unicode Python en octets" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "n'a pas pu extraire les octets de la chaîne encodée" + +#~ msgid "PL/Python does not support conversion to arrays of row types." +#~ msgstr "PL/Python ne supporte pas les conversions vers des tableaux de types row." + +#~ msgid "PL/Python function \"%s\" could not execute plan" +#~ msgstr "la fonction PL/python « %s » n'a pas pu exécuter un plan" + +#~ msgid "PL/Python function \"%s\" failed" +#~ msgstr "échec de la fonction PL/python « %s »" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python supporte seulement les tableaux uni-dimensionnels." + +#~ msgid "PL/Python: %s" +#~ msgstr "PL/python : %s" + +#~ msgid "PyCObject_AsVoidPtr() failed" +#~ msgstr "échec de PyCObject_AsVoidPtr()" + +#~ msgid "PyCObject_FromVoidPtr() failed" +#~ msgstr "échec de PyCObject_FromVoidPtr()" + +#~ msgid "Python major version mismatch in session" +#~ msgstr "Différence de version majeure de Python dans la session" + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "" +#~ "Lancez une nouvelle session pour utiliser une version majeure différente de\n" +#~ "Python." + +#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +#~ msgstr "" +#~ "Cette session a auparavant utilisé la version majeure %d de Python et elle\n" +#~ "essaie maintenant d'utiliser la version majeure %d." + +#, c-format +#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length." +#~ msgstr "Pour construire un tableau multidimensionnel, les séquences internes doivent toutes avoir la même longueur." + +#, c-format +#~ msgid "array size exceeds the maximum allowed" +#~ msgstr "la taille du tableau dépasse le maximum permis" + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "ne peut pas convertir un tableau multidimensionnel en liste Python" + +#~ msgid "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row" +#~ msgstr "" +#~ "n'a pas pu traiter la représentation de la chaîne d'un objet Python dans\n" +#~ "la fonction PL/Python « %s » lors de la modification de la ligne du trigger" + +#~ msgid "could not create exception \"%s\"" +#~ msgstr "n'a pas pu créer l'exception « %s »" + +#~ msgid "could not create globals" +#~ msgstr "n'a pas pu créer les globales" + +#~ msgid "could not create new Python list" +#~ msgstr "n'a pas pu créer la nouvelle liste Python" + +#~ msgid "could not create new dictionary" +#~ msgstr "n'a pas pu créer le nouveau dictionnaire" + +#~ msgid "could not create new dictionary while building trigger arguments" +#~ msgstr "" +#~ "n'a pas pu créer un nouveau dictionnaire lors de la construction des\n" +#~ "arguments du trigger" + +#~ msgid "could not create procedure cache" +#~ msgstr "n'a pas pu créer le cache de procédure" + +#~ msgid "could not create string representation of Python object in PL/Python function \"%s\" while creating return value" +#~ msgstr "" +#~ "n'a pas pu créer la représentation en chaîne de caractère de l'objet\n" +#~ "Python dans la fonction PL/python « %s » lors de la création de la valeur\n" +#~ "de retour" + +#~ msgid "could not create the base SPI exceptions" +#~ msgstr "n'a pas pu créer les exceptions SPI de base" + +#~ msgid "invalid arguments for plpy.prepare" +#~ msgstr "arguments invalides pour plpy.prepare" + +#~ msgid "multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length %d while expected %d" +#~ msgstr "" +#~ "les tableaux multidimensionnels doivent avoir des expressions de tableaux\n" +#~ "avec des dimensions correspondantes. La valeur de retour de la fonction\n" +#~ "PL/Python a une longueur de séquence %d alors que %d est attendue" + +#~ msgid "out of memory" +#~ msgstr "mémoire épuisée" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status ne prends pas d'arguments" + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare ne supporte pas les types composites" + +#~ msgid "the message is already specified" +#~ msgstr "le message est déjà spécifié" + +#~ msgid "transaction aborted" +#~ msgstr "transaction annulée" + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "erreur inconnue dans PLy_spi_execute_fetch_result" + +#~ msgid "unrecognized error in PLy_spi_execute_plan" +#~ msgstr "erreur inconnue dans PLy_spi_execute_plan" + +#~ msgid "unrecognized error in PLy_spi_execute_query" +#~ msgstr "erreur inconnue dans PLy_spi_execute_query" + +#~ msgid "unrecognized error in PLy_spi_prepare" +#~ msgstr "erreur inconnue dans PLy_spi_prepare" + +#, c-format +#~ msgid "wrong length of inner sequence: has length %d, but %d was expected" +#~ msgstr "mauvaise longueur de la séquence interne : a une longueur %d, mais %d était attendu" diff --git a/src/pl/plpython/po/it.po b/src/pl/plpython/po/it.po new file mode 100644 index 0000000..26ef9e0 --- /dev/null +++ b/src/pl/plpython/po/it.po @@ -0,0 +1,470 @@ +# +# plpython.po +# Italian message translation file for plpython +# +# For development and bug report please use: +# https://github.com/dvarrazzo/postgresql-it +# +# Copyright (C) 2012-2017 PostgreSQL Global Development Group +# Copyright (C) 2010, Associazione Culturale ITPUG +# +# Daniele Varrazzo <daniele.varrazzo@gmail.com>, 2012-2017. +# Flavio Spada <f.spada@sbv.mi.it> +# +# This file is distributed under the same license as the PostgreSQL package. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 11\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2022-09-30 12:08+0000\n" +"PO-Revision-Date: 2022-09-30 15:00+0200\n" +"Last-Translator: Domenico Sgarbossa <sgarbossa.domenico@gmail.com>\n" +"Language-Team: https://github.com/dvarrazzo/postgresql-it\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Poedit 2.3\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor richiede una query o un piano" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor richiede una sequenza come secondo argomento" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "esecuzione del piano fallita" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Attesa sequenza di %d argomento, ricevuti %d: %s" +msgstr[1] "Attesa sequenza di %d argomenti, ricevuti %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "iterazione di un cursore chiuso" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iterazione di un cursore in una sotto-transazione interrotta" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "lettura da un cursore chiuso" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "il risultato della query ha troppe righe per una lista Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "chiusura di un cursore in una sotto-transazione interrotta" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "modalità di ritorno della funzione set non supportata" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "Le funzioni PL/Python che restituiscono insiemi supportano la restituzione di un solo valore per chiamata." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "l'oggetto restituito non può essere iterato" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "Le funzioni PL/Python che restituiscono insiemi devono restituire un oggetto iterabile." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "errore nell'ottenere l'elemento successivo dall'iteratore" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "la procedura PL/Python non ha restituito None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "la funzione PL/Python che restituisce \"void\" non ha restituito None" + +#: plpy_exec.c:369 plpy_exec.c:395 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "la prodedura trigger ha restituito un valore inatteso" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Atteso None o una stringa." + +#: plpy_exec.c:385 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "la funzione trigger PL/Python ha restituito \"MODIFY\" in un trigger DELETE -- ignorato" + +#: plpy_exec.c:396 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Atteso None, \"OK\", \"SKIP\", o \"MODIFY\"." + +#: plpy_exec.c:441 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() è fallita durante l'impostazione degli argomenti" + +#: plpy_exec.c:445 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() è fallita durante l'impostazione degli argomenti" + +#: plpy_exec.c:457 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "la funzione che restituisce un record è chiamata in un contesto che non può accettare il tipo record" + +#: plpy_exec.c:674 +#, c-format +msgid "while creating return value" +msgstr "durante la creazione del valore da restituire" + +#: plpy_exec.c:908 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] cancellato, non è possibile modificare la riga" + +#: plpy_exec.c:913 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] non è un dizionario" + +#: plpy_exec.c:938 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "la chiave di dizionario TD[\"new\"] alla posizione %d non è una stringa" + +#: plpy_exec.c:945 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "la chiave \"%s\" trovata in TD[\"new\"] non esiste come colonna nella riga del trigger" + +#: plpy_exec.c:950 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "non è possibile impostare l'attributo di sistema \"%s\"" + +#: plpy_exec.c:955 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "impossibile impostare la colonna generata \"%s\"" + +#: plpy_exec.c:1013 +#, c-format +msgid "while modifying trigger row" +msgstr "durante la modifica della riga trigger" + +#: plpy_exec.c:1071 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "interruzione forzata di una sotto-transazione che non è terminata" + +#: plpy_main.c:111 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "c'è più di una libreria Python presente nella sessione" + +#: plpy_main.c:112 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Solo una versione maggiore di Python può essere usata in una sessione." + +#: plpy_main.c:124 +#, c-format +msgid "untrapped error in initialization" +msgstr "errore non catturato durante l'inizializzazione" + +#: plpy_main.c:147 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "importazione del modulo \"__main__\"" + +#: plpy_main.c:156 +#, c-format +msgid "could not initialize globals" +msgstr "inizializzazione delle variabili globali fallita" + +#: plpy_main.c:354 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "procedura PL/Python \"%s\"" + +#: plpy_main.c:357 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "funzione PL/Python \"%s\"" + +#: plpy_main.c:365 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "blocco di codice anonimo in PL/Python" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "importazione del modulo \"plpy\" fallita" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "creazione del modulo spiexceptions fallita" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "aggiunta del modulo spiexceptions fallita" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "generazione delle eccezioni SPI fallita" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "non è stato possibile espandere gli argomenti in plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "non è stato possibile interpretare il messaggio di errore in plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "parametro 'message' dato con nome e posizione" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' è un nome di argomento non valido per questa funzione" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "codice SQLSTATE non valido" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "le funzioni trigger possono essere chiamate esclusivamente da trigger" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "le funzioni PL/Python non possono restituire il tipo %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "le funzioni PL/Python non possono accettare il tipo %s" + +#: plpy_procedure.c:397 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "compilazione della funzione PL/Python \"%s\" fallita" + +#: plpy_procedure.c:400 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "compilazione del blocco di codice anonimo PL/Python fallita" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "il comando non ha prodotto risultati" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "il secondo argomento di plpy.prepare deve essere una sequenza" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: il nome del tipo nella posizione %d non è una stringa" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute si aspetta una query o un plan" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute richiede una sequenza come secondo argomento" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan ha fallito: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute ha fallito: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "si è già entrati in questa sotto-transazione" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "si è già usciti da questa sotto-transazione" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "non si è entrati in questa sotto-transazione" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "non c'è nessuna transazione da cui uscire" + +#: plpy_typeio.c:587 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "importazione di un modulo per il costrutto Decimal fallita" + +#: plpy_typeio.c:591 +#, c-format +msgid "no Decimal attribute in module" +msgstr "attributo Decimal non trovato nel modulo" + +#: plpy_typeio.c:597 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "conversione da numeric a Decimal fallita" + +#: plpy_typeio.c:911 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "creazione della rappresentazione in byte dell'oggetto Python fallita" + +#: plpy_typeio.c:1048 +#, c-format +msgid "could not create string representation of Python object" +msgstr "creazione della rappresentazione stringa dell'oggetto Python fallita" + +#: plpy_typeio.c:1059 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "conversione dell'oggetto Python in cstring fallita: la rappresentazione stringa Python sembra contenere byte null" + +#: plpy_typeio.c:1170 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "il numero di dimensioni dell'array supera il massimo consentito (%d)" + +#: plpy_typeio.c:1175 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "errore nel determinare la lunghezza della sequenza per il valore di ritorno della funzione" + +#: plpy_typeio.c:1180 plpy_typeio.c:1186 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "la dimensione dell'array supera il massimo consentito" + +#: plpy_typeio.c:1214 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "il valore restituito dalla funzione con tipo restituito array non è una sequenza Python" + +#: plpy_typeio.c:1261 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "lunghezza errata della sequenza interna: la lunghezza è %d ma era atteso %d" + +#: plpy_typeio.c:1263 +#, c-format +msgid "To construct a multidimensional array, the inner sequences must all have the same length." +msgstr "Per costruire un array multidimensionale le sequenze interne devono avere tutte la stessa lunghezza." + +#: plpy_typeio.c:1342 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "letterale di record non corretto: \"%s\"" + +#: plpy_typeio.c:1343 +#, c-format +msgid "Missing left parenthesis." +msgstr "Parentesi aperta mancante." + +#: plpy_typeio.c:1344 plpy_typeio.c:1545 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Per restutuire un tipo composito in un array, restituisci il tipo composito come tupla Python, per esempio \"[('foo',)]\" " + +#: plpy_typeio.c:1391 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "la chiave \"%s\" non è stata trovata nel dizionario" + +#: plpy_typeio.c:1392 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Per restituire null in una colonna, inserire nella mappa il valore None con una chiave chiamata come la colonna." + +#: plpy_typeio.c:1445 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "la lunghezza della sequenza ritornata non rispetta il numero di colonne presenti nella riga" + +#: plpy_typeio.c:1543 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "l'attributo \"%s\" non esiste nell'oggetto Python" + +#: plpy_typeio.c:1546 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Per restituire null in una colonna, l'oggetto restituito deve avere un attributo chiamato come la colonna con valore None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "conversione dell'oggetto Unicode Python in byte fallita" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "estrazione dei byte dalla stringa codificata fallita" diff --git a/src/pl/plpython/po/ja.po b/src/pl/plpython/po/ja.po new file mode 100644 index 0000000..5670478 --- /dev/null +++ b/src/pl/plpython/po/ja.po @@ -0,0 +1,497 @@ +# Japanese message translation file for plpython +# Copyright (C) 2022 PostgreSQL Global Development Group +# This file is distributed under the same license as the pg_archivecleanup (PostgreSQL) package. +# Honda Shigehiro <honda@postgresql.jp>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL 15)\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2019-06-11 11:34+0900\n" +"PO-Revision-Date: 2021-08-25 17:42+0900\n" +"Last-Translator: Kyotaro Horiguchi <horikyota.ntt@gmail.com>\n" +"Language-Team: Japan PostgreSQL Users Group <jpug-doc@ml.postgresql.jp>\n" +"Language: ja\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 1.8.13\n" + +#: plpy_cursorobject.c:78 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor は問い合わせもしくは実行計画を期待していました" + +#: plpy_cursorobject.c:161 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor は第二引数としてシーケンスを取ります" + +#: plpy_cursorobject.c:177 plpy_spi.c:211 +#, c-format +msgid "could not execute plan" +msgstr "実行計画を実行できませんでした" + +#: plpy_cursorobject.c:180 plpy_spi.c:214 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "%d 個の引数のシーケンスを期待していましたが、個数は %d でした:%s" + +#: plpy_cursorobject.c:329 +#, c-format +msgid "iterating a closed cursor" +msgstr "反復利用しようとしているカーソルは、すでにクローズされています" + +#: plpy_cursorobject.c:337 plpy_cursorobject.c:403 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "" +"中断されたサブトランザクションの中でカーソルを反復利用しようとしています" + +#: plpy_cursorobject.c:395 +#, c-format +msgid "fetch from a closed cursor" +msgstr "クローズされたカーソルからのフェッチ" + +#: plpy_cursorobject.c:438 plpy_spi.c:409 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "問い合わせの結果に含まれる行数が、Pythonのリストに対して多すぎます" + +#: plpy_cursorobject.c:490 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "" +"中断されたサブトランザクションの中でカーソルをクローズしようとしています" + +#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:143 +#, c-format +msgid "unsupported set function return mode" +msgstr "非サポートの集合関数リターンモードです。" + +#: plpy_exec.c:144 +#, c-format +msgid "" +"PL/Python set-returning functions only support returning one value per call." +msgstr "" +"集合を返却するPL/Python関数では、1回の呼び出しに対して1つの値を返すことのみが" +"サポートされています。" + +#: plpy_exec.c:157 +#, c-format +msgid "returned object cannot be iterated" +msgstr "返されたオブジェクトは反復利用できません" + +#: plpy_exec.c:158 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "" +"PL/Pythonの集合を返す関数は、反復処理可能なオブジェクトを返さなければなりませ" +"ん。" + +#: plpy_exec.c:172 +#, c-format +msgid "error fetching next item from iterator" +msgstr "反復子から次の項目を取り出せませんでした" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python プロシージャが None を返しませんでした" + +#: plpy_exec.c:219 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "戻り値が\"void\"型である PL/Python関数がNoneを返しませんでした" + +#: plpy_exec.c:375 plpy_exec.c:401 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "トリガプロシージャから期待しない戻り値が返されました" + +#: plpy_exec.c:376 +#, c-format +msgid "Expected None or a string." +msgstr "None もしくは文字列を期待していました。" + +#: plpy_exec.c:391 +#, c-format +msgid "" +"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "" +"PL/Python トリガ関数が、DELETE トリガで \"MODIFY\" を返しました-- 無視します" + +#: plpy_exec.c:402 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "None, \"OK\", \"SKIP\", \"MODIFY\" のいずれかを期待していました。" + +#: plpy_exec.c:452 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "引数を設定する際に、PyList_SetItem() に失敗しました" + +#: plpy_exec.c:456 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "引数を設定する際に、PyDict_SetItemString() に失敗しました" + +#: plpy_exec.c:468 +#, c-format +msgid "" +"function returning record called in context that cannot accept type record" +msgstr "" +"レコード型を受け付けられないコンテキストでレコードを返す関数が呼び出されまし" +"た" + +#: plpy_exec.c:685 +#, c-format +msgid "while creating return value" +msgstr "戻り値を生成する際に" + +#: plpy_exec.c:919 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] は削除されました。行を変更できません。" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] は辞書ではありません" + +#: plpy_exec.c:951 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "TD[\"new\"] 辞書の%d番目のキーが文字列ではありません" + +#: plpy_exec.c:958 +#, c-format +msgid "" +"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering " +"row" +msgstr "" +"TD[\"new\"] で見つかったキー \"%s\" は、行レベルトリガにおけるカラムとしては" +"存在しません" + +#: plpy_exec.c:963 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "システム属性\"%s\"は設定できません" + +#: plpy_exec.c:968 +#, c-format +#| msgid "cannot alter inherited column \"%s\"" +msgid "cannot set generated column \"%s\"" +msgstr "生成列\"%s\"は設定できません" + +#: plpy_exec.c:1026 +#, c-format +msgid "while modifying trigger row" +msgstr "トリガ行を変更する際に" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "終了していないサブトランザクションを強制的にアボートしています" + +#: plpy_main.c:125 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "セッションに複数の Python ライブラリが存在します" + +#: plpy_main.c:126 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "1個のセッション中で使えるPythonのメジャーバージョンは1種類だけです。" + +#: plpy_main.c:142 +#, c-format +msgid "untrapped error in initialization" +msgstr "初期化中に捕捉できないエラーがありました" + +#: plpy_main.c:165 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "\"__main__\" モジュールをインポートできませんでした" + +#: plpy_main.c:174 +#, c-format +msgid "could not initialize globals" +msgstr "グローバル変数(globals)を初期化できませんでした" + +#: plpy_main.c:399 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Pythonプロシージャ\"%s\"" + +#: plpy_main.c:402 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python関数\"%s\"" + +#: plpy_main.c:410 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Pythonの無名コードブロック" + +#: plpy_plpymodule.c:186 plpy_plpymodule.c:189 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "\"plpy\"モジュールをインポートできませんでした" + +#: plpy_plpymodule.c:204 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "spiexceptionsモジュールを生成できませんでした" + +#: plpy_plpymodule.c:212 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "spiexceptionsモジュールを追加できませんでした" + +#: plpy_plpymodule.c:280 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "SPI例外を生成できませんでした" + +#: plpy_plpymodule.c:448 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "plpy.elogで引数を展開できませんでした" + +#: plpy_plpymodule.c:457 +msgid "could not parse error message in plpy.elog" +msgstr "plpy.elogでエラーメッセージをパースできませんでした" + +#: plpy_plpymodule.c:474 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "名前と位置で 'message' 引数が渡されました" + +#: plpy_plpymodule.c:501 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "この関数に対して'%s'は無効なキーワード引数です" + +#: plpy_plpymodule.c:512 plpy_plpymodule.c:518 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "無効なSQLSTATEコードです" + +#: plpy_procedure.c:230 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "トリガー関数はトリガーとしてのみ呼び出せます" + +#: plpy_procedure.c:234 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python関数は%s型を返せません" + +#: plpy_procedure.c:312 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python関数は%s型を受け付けられません" + +#: plpy_procedure.c:402 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "PL/Python関数\"%s\"をコンパイルできませんでした" + +#: plpy_procedure.c:405 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "PL/Python無名コードブロックをコンパイルできませんでした" + +#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173 +#, c-format +msgid "command did not produce a result set" +msgstr "コマンドは結果セットを生成しませんでした" + +#: plpy_spi.c:60 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepareの第二引数はシーケンスでなければなりません" + +#: plpy_spi.c:104 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: %d 番目の型名が文字列ではありません" + +#: plpy_spi.c:176 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute は問い合わせもしくは実行計画を期待していました" + +#: plpy_spi.c:195 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute は第二引数としてシーケンスを取ります" + +#: plpy_spi.c:305 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan が失敗しました: %s" + +#: plpy_spi.c:347 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute が失敗しました: %s" + +#: plpy_subxactobject.c:97 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "すでにこのサブトランザクションの中に入っています" + +#: plpy_subxactobject.c:103 plpy_subxactobject.c:161 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "このサブトランザクションからすでに抜けています" + +#: plpy_subxactobject.c:155 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "このサブトランザクションには入っていません" + +#: plpy_subxactobject.c:167 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "抜けるべきサブトランザクションがありません" + +#: plpy_typeio.c:591 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "Decimalコンストラクタのためのモジュールをインポートできませんでした" + +#: plpy_typeio.c:595 +#, c-format +msgid "no Decimal attribute in module" +msgstr "モジュールの中にDecimal属性が含まれていません" + +#: plpy_typeio.c:601 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "numericからDecimalへの変換に失敗しました" + +#: plpy_typeio.c:915 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "Pythonオブジェクトのバイト表現を生成できませんでした" + +#: plpy_typeio.c:1063 +#, c-format +msgid "could not create string representation of Python object" +msgstr "Pythonオブジェクトの文字列表現を生成できませんでした" + +#: plpy_typeio.c:1074 +#, c-format +msgid "" +"could not convert Python object into cstring: Python string representation " +"appears to contain null bytes" +msgstr "" +"Pythonオブジェクトをcstringに変換できませんでした: Pythonの文字列表現にnullバ" +"イトが含まれているようです" + +#: plpy_typeio.c:1183 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "配列の次元数が制限値(%d)を超えています" + +#: plpy_typeio.c:1187 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "関数の戻り値について、シーケンスの長さを決定できませんでした" + +#: plpy_typeio.c:1190 plpy_typeio.c:1194 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "配列のサイズが制限値を超えています" + +#: plpy_typeio.c:1220 +#, c-format +msgid "" +"return value of function with array return type is not a Python sequence" +msgstr "配列型を返す関数の戻り値がPythonのシーケンスではありません" + +#: plpy_typeio.c:1266 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "内部シーケンスで長さが異常です: 長さは%dですが、期待する値は%dでした" + +#: plpy_typeio.c:1268 +#, c-format +msgid "" +"To construct a multidimensional array, the inner sequences must all have the " +"same length." +msgstr "" +"多次元配列を生成する場合、内部シーケンスはすべて同じ長さでなければなりませ" +"ん。" + +#: plpy_typeio.c:1347 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "不正な形式のレコードリテラル: \"%s\"" + +#: plpy_typeio.c:1348 +#, c-format +msgid "Missing left parenthesis." +msgstr "左括弧がありません。" + +#: plpy_typeio.c:1349 plpy_typeio.c:1550 +#, c-format +msgid "" +"To return a composite type in an array, return the composite type as a " +"Python tuple, e.g., \"[('foo',)]\"." +msgstr "" +"複合型を配列に入れて返したい場合、 \"[('foo',)]\" のように複合型を Pythonのタ" +"プルとして返すようにしてください。" + +#: plpy_typeio.c:1396 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "マッピング上にキー\"%s\"が見つかりません" + +#: plpy_typeio.c:1397 +#, c-format +msgid "" +"To return null in a column, add the value None to the mapping with the key " +"named after the column." +msgstr "" +"カラムにnullを入れて返す場合、カラム名をキーとして値がNoneのエントリをマッピ" +"ングに追加してください。" + +#: plpy_typeio.c:1450 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "返されたシーケンスの長さが行のカラム数とマッチしませんでした" + +#: plpy_typeio.c:1548 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "属性\"%s\"がPythonオブジェクト中に存在しません" + +#: plpy_typeio.c:1551 +#, c-format +msgid "" +"To return null in a column, let the returned object have an attribute named " +"after column with value None." +msgstr "" +"カラムにnullを入れて返す場合、カラム名をキーとして値がNoneである属性を持つオ" +"ブジェクトを返すようにしてください。" + +#: plpy_util.c:35 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "PythonのUnicodeオブジェクトをバイト列に変換できませんでした" + +#: plpy_util.c:41 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "エンコードされた文字列からバイト列を抽出できませんでした" diff --git a/src/pl/plpython/po/ka.po b/src/pl/plpython/po/ka.po new file mode 100644 index 0000000..70a87f6 --- /dev/null +++ b/src/pl/plpython/po/ka.po @@ -0,0 +1,462 @@ +# Georgian message translation file for plpython +# Copyright (C) 2022 PostgreSQL Global Development Group +# This file is distributed under the same license as the plpython (PostgreSQL) package. +# Temuri Doghonadze <temuri.doghonadze@gmail.com>, 2022. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 15\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-05-17 01:38+0000\n" +"PO-Revision-Date: 2022-09-25 19:18+0200\n" +"Last-Translator: Temuri Doghonadze <temuri.doghonadze@gmail.com>\n" +"Language-Team: Georgian <nothing>\n" +"Language: ka\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 3.1.1\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor გეგმას ან მოთხოვნას მოელოდა" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor მეორე არგუმენტად მიმდევრობას იღებს" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "გეგმის შესრულება ვერ მოხერხდა" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "მოველოდი %d არგუმენტის მიმდევრობას. მივიღე %d: %s" +msgstr[1] "მოველოდი %d არგუმენტის მიმდევრობას. მივიღე %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "დახურული კურსორის იტერაცია" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "კურსორის იტერაცია გაუქმებულ ტრანზაქციაში" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "დახურული კურსორიდან გამოთხოვა" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "მოთხოვნის პასუხს Python-ის სიაში ჩასატევად მეტისმეტად ბევრი მწკრივი გააჩნია" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "გაუქმებულ ქვეტრანზაქციაში კურსორის დახურვა" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "სეტების დამბრუნებელი ფუნქციის მხარდაუჭერელი რეჟიმი" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python -ის ფუნქციებს, რომლების სეტებს აბრუნებენ, თითოეულ გამოძახებაზე მხოლოდ ერთი მნიშვნელობის მხარდაჭერა გააჩნიათ." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "დაბრუნებული ობიექტის იტერაცია შეუძლებელია" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python-ის ფუნქციებმა, რომლებიც სეტებს აბრუნებენ, იტერირებადი ობიექტი უნდა დააბრუნონ." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "იტერატორიდან შემდეგი ჩანაწერის მოთხოვის შეცდომა" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python -ის პროცედურამ None არ დააბრუნა" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python -ის ფუნქციამ, რომელიც აბრუნებს ტიპს \"void\", რაღაც დააბრუნა" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "ტრიგერის პროცედურის მოულოდნელი დაბრუნებული მნიშვნელობა" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "ველოდებოდი არაფერს ან სტრიქონს." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python -ის ტრიგერმა ფუნქციამ DELETE ტრიგერში \"MODIFY\" დააბრუნა. -- იგნორირებულია" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "მოსალოდნელია არცერთი, \"OK\", \"SKIP\", ან \"MODIFY\"." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() -ის შეცდომა არგუმენტების მორგებისას" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() -ის შეცდომა არგუმენტების მორგებისას" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "ფუნქცია, რომელიც ჩანაწერს აბრუნებს, გამოძახებულია კონტექსტში, რომელსაც ჩანაწერის მიღება არ შეუძლია" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "დასაბრუნებელი მნიშვნელობის შექმნისას" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] წაშლილია, მწკრივის შეცვლა შეუძლებელია" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] ლექსიკონი არაა" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "TD[\"new\"] ლექსიკონის გასაღები ორდინალურ მდებარეობაზე %d სტრიქონს არ წარმოადგენს" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "\"TD[\"new\"]-ში ნაპოვნი გასაღები (%s) დამატრიგერებელი მწკრივის სვეტად არ არსებობს" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "სისტემური ატრიბუტის დაყენების შეცდომა: \"%s\"" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "გენერირებული სვეტის დაყენება შეუძლებელია: %s" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "ტრიგერის მწკრივის შეცვლისას" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "ქვეტრანზაქცია, რომელიც ჯერ არ დასრულებულა, ძალით დასრულდება" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "სესია Python-ის ერთზე მეტ ბიბლიოთეკას შეიცავს" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "ერთ სესიაში Python-ის მხოლოდ ერთი ძირითადი ვერსია შეგიძლიათ გამოიყენოთ." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "დაუჭერელი შეცდომა ინიციალიზაციისას" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "\"__main__\" მოდულის შემოტანის შეცდომა" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "გლობალების ინიციალიზაციის შეცდომა" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python -ის პროცედურა \"%s\"" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python -ის ფუნქცია \"%s\"" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python -ის ანონიმური კოდის ბლოკი" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "\"plpy\" მოდულის შემოტანის შეცდომა" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "spiexceptions მოდულის შექმნის შეცდომა" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "spiexceptions მოდულის დამატების შეცდომა" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "\"SPI\" გამონაკლისების გენერაციის შეცდომა" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "plpy.elog-ში არგუმენტების გაშლის შეცდომა" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "plpy.elog-ში შეცდომის შეტყობინების დამუშავების შეცდომა" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "არგუმენტი 'message', მოცემული სახელითა მდებარეობით" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "%s ამ ფუნქციის არასწორი არგუმენტია" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "არასწორი SQLSTATE კოდი" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "ტრიგერის ფუნქციების გამოძახება მხოლოდ ტრიგერებად შეიძლება" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python ფუნქციებს ამ ტიპის დაბრუნება არ შეუძლიათ: %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python ფუნქციებს ამ ტიპის მიღება არ შეუძლიათ: %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "\"PL/Python\"-ის ფუნქციის კომპილაციის შეცდომა: \"%s\"" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "\"PL/Python\"-ის კოდის ანონიმური ბლოკის კომპილაციის შეცდომა" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "ბრძანებამ შედეგი არ გამოიღო" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepare -ის მეორე არგუმენტი მიმდევრობა უნდა იყოს" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: ტიპის სახელი ორდინალურ მდგომარეობაში %d სტრიქონს არ წარმოადგენს" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute გეგმას ან მოთხოვნას მოელოდა" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute მეორე არგუმენტად მიმდევრობას იღებს" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan -ის შეცდომა: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute -ის შეცდომა: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "ეს ქვეტრანზაქცია უკვე დაიწყო" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "ეს ქვეტრანზაქცია უკვე დასრულდა" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "ეს ქვეტრანზაქცია არ დაწყებულა" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "გამოსასვლელი ქვეტრანზაქციები არ არსებობს" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "ათობითი კონსტრუქტორისთვის მოდულის შემოტანის პრობლემა" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "მოდულს ათობითს ატრიბუტი არ გააჩნია" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "რიცხვითიდან ათობითში გადაყვანის შეცდომა" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "\"Python\"-ის ობექტის ბაიტების რეპრეზენტაციის შექმნის შეცდომა" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "\"Python\"-ის ობექტის სტრიქონების რეპრეზენტაციის შექმნის შეცდომა" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "შეცდომა Python-ის ობიექტის cstring-ში გადაყვანისას: Python-ის სტრიქონის გამოხატულება ნულოვან ბაიტებს შეიცავს" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "ფუნქციის დაბრულების მნიშვნელობა მასივის დაბრუნების ტიპით Python-ის მიმდევრობა არაა" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "ფუნქციის დაბრუნებული მნიშვნელობის მიმდევრობის სიგრძის განსაზღვრა შეუძლებელია" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "მრავალგანზომილებიან მასივებს უნდა ჰქონდეთ მასივის გამოსახულებები შესაბამისი ზომებით" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "მასივის ზომების რაოდენობა მაქსიმუმ დასაშვებზე (%d) დიდია" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "ჩანაწერის არასწორი სტრიქონი: %s" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "აკლია მარჯვენა ფრჩხილი." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "მასივში კომპოზიტური ტიპის დასაბრუნებლად კომპოზიტური ტიპი, როგორც Python-ის კოლაჟი, ისე დააბრუნეთ. მაგ: \"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "ბმაში გასაღები %s ნაპოვნი არაა" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "სვეტში ნულის დასაბრუნებლად ამ სვეტის სახელის მქონე გასაღების მიბმას მნიშვნელობა None დაამატეთ." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "დაბრუნებული მიმდევრობის სიგრძე მწკრივში სვეტების რაოდენობას არ ემთხვევა" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "ატრიბუტი \"%s\" Python -ის ობიექტში არ არსებობს" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "სვეტში ნულის დასაბრუნებლად დასაბრუნებელ ობიექტს მიანიჭეთ სვეტის სახელის მქონე ატრიბუტი , მნიშვნელობით \"None\"." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "\"Python Unicode\" ტიპის ობიექტის ბაიტებად გარდაქმნის შეცდომა" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "ბაიტების ამოღების შეცდომა კოდირებული სტრიქონიდან" + +#, c-format +#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length." +#~ msgstr "მრავალგანზომილებიანი მასივის ასაშენებლად ყველა შიდა მიმდევრობის სიგრძე ტოლი უნდა იყოს." + +#, c-format +#~ msgid "array size exceeds the maximum allowed" +#~ msgstr "მასივის ზომა მაქსიმალურ დასაშვებს აჭარბებს" + +#, c-format +#~ msgid "wrong length of inner sequence: has length %d, but %d was expected" +#~ msgstr "შიდა მიმდევრობის არასწორი სიგრძე: სიგრძე: %d. უნდა იყოს: %d" diff --git a/src/pl/plpython/po/ko.po b/src/pl/plpython/po/ko.po new file mode 100644 index 0000000..ef64d5c --- /dev/null +++ b/src/pl/plpython/po/ko.po @@ -0,0 +1,478 @@ +# LANGUAGE message translation file for plpython +# Copyright (C) 2015 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# Ioseph Kim <ioseph@uri.sarang.net>, 2015. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 16\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-09-07 05:38+0000\n" +"PO-Revision-Date: 2023-05-30 12:40+0900\n" +"Last-Translator: Ioseph Kim <ioseph@uri.sarang.net>\n" +"Language-Team: Korean <pgsql-kr@postgresql.kr>\n" +"Language: ko\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor 객체는 쿼리나 plpy.prepare 객체를 인자로 사용합니다" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "" +"plpy.cursor 객체의 인자로 plpy.prepare 객체를 사용한 경우 두번째 인자는 " +"prepare 객체의 매개변수가 있어야 합니다." + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "plpy.prepare 객체를 실행할 수 없음" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "%d 개의 인자가 필요한데, %d개의 인자를 지정했음: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "이미 닫긴 커서에서 다음 자료를 요구하고 있음" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "중지된 서브 트랜잭션에 있는 커서에서 다음 자료를 요구하고 있음" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "닫긴 커서에서 fetch" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "쿼리 결과가 Python 리스트로 담기에는 너무 많습니다" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "중지된 서브트랜잭션에서 커서를 닫고 있음" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "지원하지 않는 집합 함수 리턴 모드" + +#: plpy_exec.c:140 +#, c-format +msgid "" +"PL/Python set-returning functions only support returning one value per call." +msgstr "" +"PL/Python 집합-반환 함수는 한번의 호출에 대해서 하나의 값만 반환할 수 있습니" +"다." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "반환하는 객체가 iterable 형이 아님" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python 집합-반환 함수는 iterable 객체를 반환해야 합니다." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "iterator에서 다음 아이템을 가져올 수 없음" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python 프로시져가 None을 반환하지 않았음" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "" +"반환 자료형이 \"void\"인 PL/Python 함수가 return None으로 끝나지 않았음" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "트리거 프로시져가 예상치 못한 값을 반환했습니다" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "None 이나 문자열이 있어야합니다." + +#: plpy_exec.c:383 +#, c-format +msgid "" +"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "" +"PL/Python 트리거 함수가 DELETE 트리거에서 \"MODIFY\"를 반환했음 -- 무시함" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "None, \"OK\", \"SKIP\", 또는 \"MODIFY\"를 사용해야 함." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() 함수가 인자 설정하는 중 실패" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() 함수가 인자 설정하는 중 실패" + +#: plpy_exec.c:460 +#, c-format +msgid "" +"function returning record called in context that cannot accept type record" +msgstr "반환 자료형이 record인데 함수가 그 자료형으로 반환하지 않음" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "반환값을 만들고 있은 중" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] 변수가 삭제되었음, 로우를 수정할 수 없음" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] 변수가 딕션너리 형태가 아님" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "%d 번째 TD[\"new\"] 딕션너리 키가 문자열이 아님" + +#: plpy_exec.c:961 +#, c-format +msgid "" +"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering " +"row" +msgstr "" +"로우 트리거 작업에서 칼럼으로 사용되는 \"%s\" 키가 TD[\"new\"] 변수에 없음." + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "\"%s\" 시스템 속성을 지정할 수 없음" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "\"%s\" 계산된 칼럼을 지정할 수 없음" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "로우 변경 트리거 작업 도중" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "서브트랜잭션이 중지됨으로 강제로 중지됨" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "세션에서 여러 Python 라이브러리가 사용되고 있습니다" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "하나의 세션에서는 하나의 Python 메이져 버전만 사용할 수 있습니다." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "plpy 모듈 초기화 실패" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "\"__main__\" 모듈은 임포트 할 수 없음" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "전역변수들을 초기화 할 수 없음" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "\"%s\" PL/Python 프로시져" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "\"%s\" PL/Python 함수" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python 익명 코드 블럭" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "\"plpy\" 모듈을 임포트 할 수 없음" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "spiexceptions 모듈을 만들 수 없음" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "spiexceptions 모듈을 추가할 수 없음" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "SPI 예외처리를 생성할 수 없음" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "잘못된 인자로 구성된 plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "plpy.elog 에서 오류 메시지를 분석할 수 없음" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "'message' 인자는 이름과 위치가 있어야 함" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' 값은 이 함수에서 잘못된 예약어 인자입니다" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "잘못된 SQLSTATE 코드" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "트리거 함수는 트리거로만 호출될 수 있음" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python 함수는 %s 자료형을 반환할 수 없음" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python 함수는 %s 자료형을 사용할 수 없음" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "\"%s\" PL/Python 함수를 컴파일 할 수 없음" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "anonymous PL/Python 코드 블록을 컴파일 할 수 없음" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "명령의 결과값이 없음" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepare 함수의 두번째 인자는 Python 시퀀스형이어야 함" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: %d 번째 인자의 자료형이 문자열이 아님" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute 함수의 인자는 쿼리문이나 plpy.prepare 객체여야 함" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execut 함수의 두번째 인자는 python 시퀀스형이 와야함" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan 실패: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute 실패: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "이 서브트랜잭션은 이미 시작되었음" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "이 서브트랜잭션은 이미 끝났음" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "이 서브트랜잭션이 시작되지 않았음" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "종료할 서브트랜잭션이 없음, 위치:" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "Decimal 자료형 처리를 위해 모듈을 임포트 할 수 없음" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "모듈안에 Decimal 속성이 없음" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "numeric 형을 Decimal 형으로 변환할 수 없음" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "Python 객체를 bytea 자료형으로 변환할 수 없음" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "Python 객체를 문자열 자료형으로 변환할 수 없음" + +#: plpy_typeio.c:1060 +#, c-format +msgid "" +"could not convert Python object into cstring: Python string representation " +"appears to contain null bytes" +msgstr "" +"Python 객체를 cstring 형으로 변환할 수 없음: Python string 변수에 null문자열" +"이 포함되어 있음" + +#: plpy_typeio.c:1157 +#, c-format +msgid "" +"return value of function with array return type is not a Python sequence" +msgstr "배열형으로 넘길 자료형이 Python 시퀀스형이 아님" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "함수 반환 값으로 시퀀스 길이를 결정할 수 없음" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "" +"multidimensional arrays must have array expressions with matching dimensions" +msgstr "다차원 배열에는 일치하는 차원이 포함된 배열 식이 있어야 함" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "배열 차원이 최대치 (%d)를 초과 했습니다." + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "잘못된 레코드 표현: \"%s\"" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "왼쪽 괄호가 없음." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "" +"To return a composite type in an array, return the composite type as a " +"Python tuple, e.g., \"[('foo',)]\"." +msgstr "" +"배열에서 복합 자료형을 반환하려면, Python 튜플 형을 사용하세요. 예: " +"\"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "맵 안에 \"%s\" 키가 없음" + +#: plpy_typeio.c:1379 +#, c-format +msgid "" +"To return null in a column, add the value None to the mapping with the key " +"named after the column." +msgstr "" +"칼럼값으로 null을 반환하려면, 칼럼 다음에 해당 키 이름과 맵핑 되는 None값을 " +"지정하세요" + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "반환되는 시퀀스형 변수의 길이가 로우의 칼럼수와 일치하지 않음" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "Python 객체 가운데 \"%s\" 속성이 없음" + +#: plpy_typeio.c:1533 +#, c-format +msgid "" +"To return null in a column, let the returned object have an attribute named " +"after column with value None." +msgstr "" +"칼럼 값으로 null 을 반환하려면, 값으로 None 값을 가지는 칼럼 뒤에, 속성 이름" +"이 있는 객체를 반환하세요" + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "Python 유니코드 객체를 UTF-8 문자열로 변환할 수 없음" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "해당 인코드 문자열을 Python에서 사용할 수 없음" diff --git a/src/pl/plpython/po/meson.build b/src/pl/plpython/po/meson.build new file mode 100644 index 0000000..ddaa191 --- /dev/null +++ b/src/pl/plpython/po/meson.build @@ -0,0 +1,3 @@ +# Copyright (c) 2022-2023, PostgreSQL Global Development Group + +nls_targets += [i18n.gettext('plpython-' + pg_version_major.to_string())] diff --git a/src/pl/plpython/po/pl.po b/src/pl/plpython/po/pl.po new file mode 100644 index 0000000..4abd6f5 --- /dev/null +++ b/src/pl/plpython/po/pl.po @@ -0,0 +1,507 @@ +# plpython message translation file for plpython +# Copyright (C) 2011 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# Begina Felicysym <begina.felicysym@wp.eu>, 2011, 2012. +# grzegorz <begina.felicysym@wp.eu>, 2014, 2015, 2016, 2017. +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL 9.1)\n" +"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n" +"POT-Creation-Date: 2017-04-09 21:07+0000\n" +"PO-Revision-Date: 2017-04-11 23:15+0200\n" +"Last-Translator: grzegorz <begina.felicysym@wp.eu>\n" +"Language-Team: begina.felicysym@wp.eu\n" +"Language: pl\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2);\n" +"X-Generator: Virtaal 0.7.1\n" + +#: plpy_cursorobject.c:100 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor oczekuje kwerendy lub planu" + +#: plpy_cursorobject.c:176 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor przyjmuje sekwencję jako drugi argument" + +#: plpy_cursorobject.c:192 plpy_spi.c:226 +#, c-format +msgid "could not execute plan" +msgstr "nie można wykonać planu" + +#: plpy_cursorobject.c:195 plpy_spi.c:229 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Oczekiwano sekwencji z %d argumentem, mamy %d: %s" +msgstr[1] "Oczekiwano sekwencji z %d argumentami, mamy %d: %s" +msgstr[2] "Oczekiwano sekwencji z %d argumentami, mamy %d: %s" + +#: plpy_cursorobject.c:350 +#, c-format +msgid "iterating a closed cursor" +msgstr "iteracja zamkniętego kursora" + +#: plpy_cursorobject.c:358 plpy_cursorobject.c:423 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iteracja kursora w przerwanej podtransakcji" + +#: plpy_cursorobject.c:415 +#, c-format +msgid "fetch from a closed cursor" +msgstr "pobranie z zamkniętego kursora" + +#: plpy_cursorobject.c:463 plpy_spi.c:434 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "wynik zapytania ma za dużo wierszy by pomieścić w liście Python" + +#: plpy_cursorobject.c:504 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "zamknięcie kursora w przerwanej podtransakcji" + +#: plpy_elog.c:127 plpy_elog.c:128 plpy_plpymodule.c:548 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:140 +#, c-format +msgid "unsupported set function return mode" +msgstr "nieobsługiwany tryb zwracania przez funkcję grupy" + +#: plpy_exec.c:141 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "funkcje zwracające grupę PL/Python obsługuje tylko zwracanie jednej wartości w wywołaniu." + +#: plpy_exec.c:154 +#, c-format +msgid "returned object cannot be iterated" +msgstr "zwrócony obiekt nie może być przeiterowany" + +#: plpy_exec.c:155 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "funkcje PL/Python zwracające grupę muszą zwracać iterowalny obiekt." + +#: plpy_exec.c:169 +#, c-format +msgid "error fetching next item from iterator" +msgstr "błąd pobierania następnego elementu z iteratora" + +#: plpy_exec.c:210 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "funkcja PL/Python zwracająca typ \"void\" nie zwróciła wartości None" + +#: plpy_exec.c:379 plpy_exec.c:405 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "nieoczekiwana wartość zwracana przez procedury wyzwalacza" + +#: plpy_exec.c:380 +#, c-format +msgid "Expected None or a string." +msgstr "Oczekiwano None lub ciąg znaków." + +#: plpy_exec.c:395 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "funkcja wyzwalacza PL/Python zwróciła \"MODIFY\" w wyzwalaczu DELETE -- zignorowano" + +#: plpy_exec.c:406 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Oczekiwano None, \"OK\", \"SKIP\", lub \"MODIFY\"." + +#: plpy_exec.c:487 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "nie powiodło się PyList_SetItem() podczas ustawiania argumentów" + +#: plpy_exec.c:491 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "nie powiodło się PyDict_SetItemString() podczas ustawiania argumentów" + +#: plpy_exec.c:503 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "funkcja zwracająca rekord w wywołaniu, które nie akceptuje typów złożonych" + +#: plpy_exec.c:719 +#, c-format +msgid "while creating return value" +msgstr "podczas tworzenia wartości zwracanej" + +#: plpy_exec.c:743 +#, c-format +msgid "could not create new dictionary while building trigger arguments" +msgstr "nie można utworzyć nowego słownika w czasie tworzenia argumentów wyzwalacza" + +#: plpy_exec.c:931 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "usunięto TD[\"new\"], nie można zmienić wiersza" + +#: plpy_exec.c:936 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] nie jest słownikiem" + +#: plpy_exec.c:963 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "klucz słownika TD[\"new\"] na pozycji porządkowej %d nie jest ciągiem znaków" + +#: plpy_exec.c:970 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "klucz \"%s\" znaleziony w TD[\"new\"] nie istnieje jako kolumna w wierszu obsługiwanym przez wyzwalacz" + +#: plpy_exec.c:975 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "nie można ustawić atrybutu systemowego \"%s\"" + +#: plpy_exec.c:1046 +#, c-format +msgid "while modifying trigger row" +msgstr "podczas modyfikowania wiersza wyzwalacza" + +#: plpy_exec.c:1107 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "wymuszone przerywanie podtransakcji, która nie została zakończona" + +#: plpy_main.c:125 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "kilka bibliotek Python jest dostępne w sesji" + +#: plpy_main.c:126 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Tylko jedna podstawowa wersja Python może być używana w sesji." + +#: plpy_main.c:142 +#, c-format +msgid "untrapped error in initialization" +msgstr "niewyłapany błąd w inicjacji" + +#: plpy_main.c:165 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "nie można zaimportować modułu \"__main__\"" + +#: plpy_main.c:170 +#, c-format +msgid "could not create globals" +msgstr "nie można utworzyć zmiennych globalnych" + +#: plpy_main.c:174 +#, c-format +msgid "could not initialize globals" +msgstr "nie można zainicjować zmiennych globalnych" + +#: plpy_main.c:387 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "funkcja PL/Python \"%s\"" + +#: plpy_main.c:394 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "anonimowy blok kodu PL/Python" + +#: plpy_plpymodule.c:181 plpy_plpymodule.c:184 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "nie można zaimportować modułu \"plpy\"" + +#: plpy_plpymodule.c:199 +#, c-format +#| msgid "could not add the spiexceptions module" +msgid "could not create the spiexceptions module" +msgstr "nie udało się utworzyć modułu spiexceptions" + +#: plpy_plpymodule.c:207 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "nie udało się dodać modułu spiexceptions" + +#: plpy_plpymodule.c:236 +#, c-format +#| msgid "could not create directory \"%s\": %m" +msgid "could not create exception \"%s\"" +msgstr "nie można utworzyć wyjątku \"%s\"" + +#: plpy_plpymodule.c:271 plpy_plpymodule.c:275 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "nie można wygenerować wyjątków SPI" + +#: plpy_plpymodule.c:443 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "nie można rozpakować argumentów w plpy.elog" + +#: plpy_plpymodule.c:452 +msgid "could not parse error message in plpy.elog" +msgstr "nie można przetworzyć komunikatu błędu w plpy.elog" + +#: plpy_plpymodule.c:469 +#, c-format +msgid "Argument 'message' given by name and position" +msgstr "Argument 'message' przekazany przez nazwę i pozycję" + +#: plpy_plpymodule.c:496 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' jest niepoprawnym słowem kluczowym argumentu dla tej funkcji" + +#: plpy_plpymodule.c:507 plpy_plpymodule.c:513 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "błędny kod SQLSTATE" + +#: plpy_procedure.c:230 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "procedury wyzwalaczy mogą być wywoływane jedynie przez wyzwalacze" + +#: plpy_procedure.c:235 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "funkcje PL/Python nie mogą zwracać wartości typu %s" + +#: plpy_procedure.c:316 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "funkcje PL/Python nie obsługują typu %s" + +#: plpy_procedure.c:412 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "nie powiodła się kompilacja funkcji PL/Python \"%s\"" + +#: plpy_procedure.c:415 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "nie udało się skompilować anonimowego bloku kodu PL/Python" + +#: plpy_resultobject.c:145 plpy_resultobject.c:165 plpy_resultobject.c:185 +#, c-format +msgid "command did not produce a result set" +msgstr "polecenie nie utworzyło zbioru wynikowego" + +#: plpy_spi.c:59 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "drugi argument plpy.prepare musi być sekwencją" + +#: plpy_spi.c:115 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: nazwa typu na pozycji porządkowej %d nie jest ciągiem znaków" + +#: plpy_spi.c:191 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute oczekuje kwerendy lub planu" + +#: plpy_spi.c:210 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute przyjmuje sekwencję jako drugi argument" + +#: plpy_spi.c:335 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "nie powiódł się SPI_execute_plan: %s" + +#: plpy_spi.c:377 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "nie powiódł się SPI_execute: %s" + +#: plpy_subxactobject.c:122 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "ta podtransakcja już została wprowadzona" + +#: plpy_subxactobject.c:128 plpy_subxactobject.c:186 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "ta podtransakcja już została zakończona" + +#: plpy_subxactobject.c:180 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "ta podtransakcja nie została wprowadzona" + +#: plpy_subxactobject.c:192 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "brak podtransakcji by z niej wyjść" + +#: plpy_typeio.c:292 +#, c-format +msgid "could not create new dictionary" +msgstr "nie można utworzyć nowego słownika" + +#: plpy_typeio.c:560 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "nie można zaimportować modułu dla konstruktora Decimal" + +#: plpy_typeio.c:564 +#, c-format +msgid "no Decimal attribute in module" +msgstr "brak atrybutu Decimal w module" + +#: plpy_typeio.c:570 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "konwersja z numeric na Decimal nie powiodła się" + +#: plpy_typeio.c:772 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "nie można utworzyć reprezentacji bajtowej obiektu Python" + +#: plpy_typeio.c:881 +#, c-format +msgid "could not create string representation of Python object" +msgstr "nie można utworzyć reprezentacji znakowej obiektu Python" + +#: plpy_typeio.c:892 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "nie można zmienić obiektu Python na cstring: reprezentacja ciągu znaków Python wydaje się zawierać puste bajty" + +#: plpy_typeio.c:949 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "nieprawidłowy literał rekordu: \"%s\"" + +#: plpy_typeio.c:950 +#, c-format +msgid "Missing left parenthesis." +msgstr "Brak lewego nawiasu." + +#: plpy_typeio.c:951 plpy_typeio.c:1389 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g. \"[('foo')]\"" +msgstr "" +"By zwrócić typ złożony w tablicy, zwracaj typ złożony jako krotkę Pythona, " +"np. \"[('foo')]\"" + +#: plpy_typeio.c:1000 +#, c-format +#| msgid "number of array dimensions (%d) exceeds the maximum allowed (%d)" +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "liczba wymiarów tablicy przekracza dozwolone maksimum (%d)" + +#: plpy_typeio.c:1004 +#, c-format +#| msgid "cannot determine OID of function lo_truncate\n" +msgid "cannot determine sequence length for function return value" +msgstr "" +"nie można ustalić długości sekwencji dla zwracanej wartości przez funkcję" + +#: plpy_typeio.c:1007 plpy_typeio.c:1011 +#, c-format +#| msgid "array size exceeds the maximum allowed (%d)" +msgid "array size exceeds the maximum allowed" +msgstr "rozmiar tablicy przekracza dozwolone maksimum" + +#: plpy_typeio.c:1037 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "wartość zwrócona przez funkcję zwracającą tablicę nie jest sekwencją Python" + +#: plpy_typeio.c:1090 +#, c-format +#| msgid "multidimensional arrays must have array expressions with matching dimensions" +msgid "multidimensional arrays must have array expressions with matching dimensions. PL/Python function return value has sequence length %d while expected %d" +msgstr "" +"wielowymiarowe tablice muszą mieć wyrażenia tablicowe z pasującymi " +"wymiarami. Wartość zwracana przez funkcję PL/Python ma długość sekwencji %d " +"gdy oczekiwano %d" + +#: plpy_typeio.c:1212 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "nie odnaleziono klucza \"%s\" w mapowaniu" + +#: plpy_typeio.c:1213 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Aby zwrócić null w kolumnie, dodaj wartość None do mapowania z kluczem nazwanym wedle kolumny." + +#: plpy_typeio.c:1264 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "długość zwróconej sekwencji nie jest równa liczbie kolumn w wierszu" + +#: plpy_typeio.c:1387 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "atrybut \"%s\" nie istnieje w obiekcie Python" + +#: plpy_typeio.c:1390 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Aby zwrócić null w kolumnie, niech zwrócony obiekt posiada atrybut nazwany wedle kolumny z wartością None." + +#: plpy_util.c:36 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "nie można zmienić obiektu unikodowego Python na bajty" + +#: plpy_util.c:42 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "nie można wyciągnąć bajtów z kodowanego ciągu znaków" + +#~ msgid "Python major version mismatch in session" +#~ msgstr "niezgodna wersja główna Python w sesji" + +#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +#~ msgstr "Ta sesja używała poprzednio Python w głównej wersji %d, teraz próbuje użyć Python w głównej wersji %d." + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "Uruchom nową sesję aby użyć innej głównej wersji Python." + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare nie obsługuje typów złożonych" + +#~ msgid "PL/Python does not support conversion to arrays of row types." +#~ msgstr "PL/Python nie obsługuje konwersji typów wierszowych na tablice." + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "nierozpoznany błąd w PLy_spi_execute_fetch_result" + +#~ msgid "could not create new Python list" +#~ msgstr "nie można utworzyć nowej listy Python" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python obsługuje tylko jednowymiarowe tablice." + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "nie można skonwertować tablicy wielowymiarowej na listę Python" + +#~ msgid "could not create the base SPI exceptions" +#~ msgstr "nie można stworzyć bazowych wyjątków SPI" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status nie przyjmuje żadnych argumentów" diff --git a/src/pl/plpython/po/pt_BR.po b/src/pl/plpython/po/pt_BR.po new file mode 100644 index 0000000..ddeae15 --- /dev/null +++ b/src/pl/plpython/po/pt_BR.po @@ -0,0 +1,461 @@ +# Brazilian Portuguese message translation file for plpython +# +# Copyright (C) 2009-2022 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# +# Euler Taveira <euler@eulerto.com>, 2009-2022. +# +msgid "" +msgstr "" +"Project-Id-Version: PostgreSQL 15\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2022-09-27 13:15-0300\n" +"PO-Revision-Date: 2009-05-10 01:15-0300\n" +"Last-Translator: Euler Taveira <euler@eulerto.com>\n" +"Language-Team: Brazilian Portuguese <pgsql-translators@postgresql.org>\n" +"Language: pt_BR\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n>1);\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor esperava uma consulta ou um plano" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor tem uma sequência como seu segundo argumento" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "não pôde executar plano" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Sequência esperada de %d argumento, recebeu %d: %s" +msgstr[1] "Sequência esperada de %d argumentos, recebeu %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "iterando um cursor fechado" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iterando um cursor em uma subtransação abortada" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "busca em um cursor fechado" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "resultado da consulta tem muitos registros para caber em uma lista Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "fechando um cursor em uma subtransação abortada" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "modo de retorno da função que retorna conjunto não é suportado" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "funções PL/Python que retornam conjunto só suportam retornar um valor por chamada." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "objeto retornado não pode ser iterado" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "funções PL/Python que retornam conjunto devem retornar um objeto iterável." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "erro ao buscar próximo item do iterador" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "procedimento PL/Python não retornou None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "função PL/Python com tipo de retorno \"void\" não retornou None" + +#: plpy_exec.c:369 plpy_exec.c:395 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "função de gatilho retornou valor inesperado" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "None ou uma cadeia de caracteres era esperado." + +#: plpy_exec.c:385 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "função de gatilho PL/Python retornou \"MODIFY\" em um gatilho DELETE -- ignorado" + +#: plpy_exec.c:396 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Era esperado None, \"OK\", \"SKIP\" ou \"MODIFY\"." + +#: plpy_exec.c:441 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() falhou ao definir argumentos" + +#: plpy_exec.c:445 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() falhou ao definir argumentos" + +#: plpy_exec.c:457 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "função que retorna record foi chamada em um contexto que não pode aceitar tipo record" + +#: plpy_exec.c:674 +#, c-format +msgid "while creating return value" +msgstr "ao criar valor de retorno" + +#: plpy_exec.c:908 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] removido, não pode modificar registro" + +#: plpy_exec.c:913 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] não é um dicionário" + +#: plpy_exec.c:938 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "chave do dicionário TD[\"new\"] na posição %d não é uma cadeia de caracteres" + +#: plpy_exec.c:945 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "chave \"%s\" encontrada em TD[\"new\"] não existe como uma coluna no registro do gatilho" + +#: plpy_exec.c:950 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "não pode definir atributo do sistema \"%s\"" + +#: plpy_exec.c:955 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "não pode definir coluna gerada \"%s\"" + +#: plpy_exec.c:1013 +#, c-format +msgid "while modifying trigger row" +msgstr "ao modificar registro de gatilho" + +#: plpy_exec.c:1071 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "forçado a abortar subtransação que não foi concluída" + +#: plpy_main.c:111 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "múltiplas bibliotecas do Python estão presentes na sessão" + +#: plpy_main.c:112 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Apenas uma versão do Python pode ser utilizada na sessão." + +#: plpy_main.c:124 +#, c-format +msgid "untrapped error in initialization" +msgstr "erro não interceptado na inicialização" + +#: plpy_main.c:147 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "não pôde importar módulo \"__main__\"" + +#: plpy_main.c:156 +#, c-format +msgid "could not initialize globals" +msgstr "não pôde inicializar globais" + +#: plpy_main.c:354 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "procedimento PL/Python \"%s\"" + +#: plpy_main.c:357 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "função PL/Python \"%s\"" + +#: plpy_main.c:365 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "bloco de código PL/Python anônimo" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "não pôde importar módulo \"plpy\"" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "não pôde criar o módulo spiexceptions" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "não pôde adicionar o módulo spiexceptions" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "não pôde gerar exceções da SPI" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "não pode desempacotar argumentos em plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "não pode analisar mensagem de erro em plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "argumento 'message' informado por nome e posição" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' é um argumento inválido para esta função" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "código SQLSTATE inválido" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "funções de gatilho só podem ser chamadas como gatilhos" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "funções PL/Python não podem retornar tipo %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "funções PL/Python não podem aceitar tipo %s" + +#: plpy_procedure.c:397 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "não pôde compilar função PL/Python \"%s\"" + +#: plpy_procedure.c:400 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "não pôde compilar bloco de código PL/Python anônimo" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "comando não produziu um conjunto de resultados" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "segundo argumento de plpy.prepare deve ser uma sequência" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: nome do tipo na posição %d não é uma cadeia de caracteres" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute espera uma consulta ou um plano" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute recebe uma sequência como segundo argumento" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan falhou: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute falhou: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "essa subtransação já foi iniciada" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "essa subtransação já foi concluída" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "essa subtransação não foi iniciada" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "não há uma subtransação a ser concluída" + +#: plpy_typeio.c:587 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "não pôde importar módulo para construtor Decimal" + +#: plpy_typeio.c:591 +#, c-format +msgid "no Decimal attribute in module" +msgstr "nenhum atributo Decimal no módulo" + +#: plpy_typeio.c:597 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "conversão de numeric para Decimal falhou" + +#: plpy_typeio.c:911 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "não pôde criar representação de bytes de um objeto Python" + +#: plpy_typeio.c:1048 +#, c-format +msgid "could not create string representation of Python object" +msgstr "não pôde criar representação de cadeia de caracteres de um objeto Python" + +#: plpy_typeio.c:1059 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "não pôde converter objeto Python em cstring: representação de cadeia de caracteres Python parece conter bytes nulos" + +#: plpy_typeio.c:1170 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "número de dimensões da matriz excede o máximo permitido (%d)" + +#: plpy_typeio.c:1175 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "não pôde determinar tamanho da sequência para valor de retorno da função" + +#: plpy_typeio.c:1180 plpy_typeio.c:1186 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "tamanho da matriz excede o máximo permitido" + +#: plpy_typeio.c:1214 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "valor de retorno da função do tipo matriz retorna tipo que não é uma sequência Python" + +#: plpy_typeio.c:1261 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "tamanho incorreto da sequência interna: tem tamanho %d, mas %d era esperado" + +#: plpy_typeio.c:1263 +#, c-format +msgid "To construct a multidimensional array, the inner sequences must all have the same length." +msgstr "Para construir uma matriz multidimensional, todas as sequências internas devem ter o mesmo tamanho." + +#: plpy_typeio.c:1342 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "matriz mal formada: \"%s\"" + +#: plpy_typeio.c:1343 +#, c-format +msgid "Missing left parenthesis." +msgstr "Faltando parêntese esquerdo." + +#: plpy_typeio.c:1344 plpy_typeio.c:1545 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Para retornar um tipo composto em uma matriz, retorne o tipo composto como uma tupla do Python, i.e., \"[('foo',)]\"." + +#: plpy_typeio.c:1391 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "chave \"%s\" não foi encontrada no mapeamento" + +#: plpy_typeio.c:1392 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Para retornar nulo em uma coluna, adicionar o valor None no mapeamento cuja chave é o nome da coluna." + +#: plpy_typeio.c:1445 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "tamanho da sequência retornada não combina com número de colunas no registro" + +#: plpy_typeio.c:1543 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "atributo \"%s\" não existe no objeto Python" + +#: plpy_typeio.c:1546 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Para retornar nulo na coluna, deixe o objeto retornado ter um atributo cuja chave é o nome do coluna e o valor é None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "não pôde converter objeto Unicode Python para bytes" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "não pôde extrair bytes de cadeia de caracteres codificada" diff --git a/src/pl/plpython/po/ru.po b/src/pl/plpython/po/ru.po new file mode 100644 index 0000000..b3a6d40 --- /dev/null +++ b/src/pl/plpython/po/ru.po @@ -0,0 +1,561 @@ +# Russian message translation file for plpython +# Copyright (C) 2012-2016 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# Alexander Lakhin <exclusion@gmail.com>, 2012-2017, 2018, 2019. +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL current)\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-11-03 09:08+0300\n" +"PO-Revision-Date: 2019-08-29 15:42+0300\n" +"Last-Translator: Alexander Lakhin <exclusion@gmail.com>\n" +"Language-Team: Russian <pgsql-ru-general@postgresql.org>\n" +"Language: ru\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor ожидает запрос или план" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor принимает в качестве второго аргумента последовательность" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "нельзя выполнить план" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Ожидалась последовательность из %d аргумента, получено %d: %s" +msgstr[1] "Ожидалась последовательность из %d аргументов, получено %d: %s" +msgstr[2] "Ожидалась последовательность из %d аргументов, получено %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "перемещение закрытого курсора" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "перемещение курсора в прерванной подтранзакции" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "выборка из закрытого курсора" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "" +"результат запроса содержит слишком много строк для передачи в списке Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "закрытие курсора в прерванной подтранзакции" + +#: plpy_elog.c:122 plpy_elog.c:123 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "неподдерживаемый режим возврата для функции с результатом-множеством" + +#: plpy_exec.c:140 +#, c-format +msgid "" +"PL/Python set-returning functions only support returning one value per call." +msgstr "" +"Функции PL/Python с результатом-множеством могут возвращать только одно " +"значение за вызов." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "возвращаемый объект не поддерживает итерации" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "" +"Функции PL/Python с результатом-множеством должны возвращать объекты с " +"возможностью итерации." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "ошибка получения следующего элемента из итератора" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "процедура PL/Python вернула не None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "функция PL/Python с типом результата \"void\" вернула не None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "триггерная процедура вернула недопустимое значение" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Ожидалось None или строка." + +#: plpy_exec.c:383 +#, c-format +msgid "" +"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "" +"триггерная функция PL/Python вернула \"MODIFY\" в триггере DELETE -- " +"игнорируется" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Ожидалось None, \"OK\", \"SKIP\" или \"MODIFY\"." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "ошибка в PyList_SetItem() при настройке аргументов" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "ошибка в PyDict_SetItemString() при настройке аргументов" + +#: plpy_exec.c:460 +#, c-format +msgid "" +"function returning record called in context that cannot accept type record" +msgstr "" +"функция, возвращающая запись, вызвана в контексте, не допускающем этот тип" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "при создании возвращаемого значения" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "элемент TD[\"new\"] удалён -- изменить строку нельзя" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] - не словарь" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "ключ словаря TD[\"new\"] с порядковым номером %d не является строкой" + +#: plpy_exec.c:961 +#, c-format +msgid "" +"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering " +"row" +msgstr "" +"ключу \"%s\", найденному в TD[\"new\"], не соответствует столбец в строке, " +"обрабатываемой триггером" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "присвоить значение системному атрибуту \"%s\" нельзя" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "присвоить значение генерируемому столбцу \"%s\" нельзя" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "при изменении строки в триггере" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "принудительное прерывание незавершённой подтранзакции" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "в сеансе представлено несколько библиотек Python" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "В одном сеансе нельзя использовать Python разных старших версий." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "необработанная ошибка при инициализации" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "не удалось импортировать модуль \"__main__\"" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "не удалось инициализировать глобальные данные" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "процедура PL/Python \"%s\"" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "функция PL/Python \"%s\"" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "анонимный блок кода PL/Python" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "не удалось импортировать модуль \"plpy\"" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "не удалось создать модуль spiexceptions" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "не удалось добавить модуль spiexceptions" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "не удалось сгенерировать исключения SPI" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "не удалось распаковать аргументы в plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "не удалось разобрать сообщение об ошибке в plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "аргумент 'message' задан и по имени, и по позиции" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' - недопустимое ключевое слово (аргумент) для этой функции" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "неверный код SQLSTATE" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "триггерные функции могут вызываться только в триггерах" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "функции PL/Python не могут возвращать тип %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "функции PL/Python не могут принимать тип %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "не удалось скомпилировать функцию PL/Python \"%s\"" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "не удалось скомпилировать анонимный блок кода PL/Python" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "команда не выдала результирующий набор" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "вторым аргументом plpy.prepare должна быть последовательность" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: имя типа с порядковым номером %d не является строкой" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute ожидает запрос или план" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute принимает в качестве второго аргумента последовательность" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "ошибка в SPI_execute_plan: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "ошибка в SPI_execute: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "эта подтранзакция уже начата" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "эта подтранзакция уже закончена" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "эта подтранзакция ещё не начата" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "нет подтранзакции, которую нужно закончить" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "не удалось импортировать модуль для конструктора Decimal" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "в модуле нет атрибута Decimal" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "не удалось преобразовать numeric в Decimal" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "не удалось создать байтовое представление объекта Python" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "не удалось создать строковое представление объекта Python" + +#: plpy_typeio.c:1060 +#, c-format +msgid "" +"could not convert Python object into cstring: Python string representation " +"appears to contain null bytes" +msgstr "" +"не удалось преобразовать объект Python в cstring: похоже, представление " +"строки Python содержит нулевые байты" + +#: plpy_typeio.c:1157 +#, c-format +msgid "" +"return value of function with array return type is not a Python sequence" +msgstr "" +"возвращаемое значение функции с результатом-массивом не является " +"последовательностью" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "" +"не удалось определить длину последовательности в возвращаемом функцией " +"значении" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "" +"multidimensional arrays must have array expressions with matching dimensions" +msgstr "" +"для многомерных массивов должны задаваться выражения с соответствующими " +"размерностями" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "число размерностей массива превышает предел (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "ошибка в литерале записи: \"%s\"" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Отсутствует левая скобка." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "" +"To return a composite type in an array, return the composite type as a " +"Python tuple, e.g., \"[('foo',)]\"." +msgstr "" +"Чтобы возвратить составной тип в массиве, нужно возвратить составное " +"значение в виде кортежа Python, например: \"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "ключ \"%s\" не найден в сопоставлении" + +#: plpy_typeio.c:1379 +#, c-format +msgid "" +"To return null in a column, add the value None to the mapping with the key " +"named after the column." +msgstr "" +"Чтобы присвоить столбцу NULL, добавьте в сопоставление значение None с " +"ключом-именем столбца." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "длина возвращённой последовательности не равна числу столбцов в строке" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "в объекте Python не существует атрибут \"%s\"" + +#: plpy_typeio.c:1533 +#, c-format +msgid "" +"To return null in a column, let the returned object have an attribute named " +"after column with value None." +msgstr "" +"Чтобы присвоить столбцу NULL, присвойте возвращаемому значению атрибут с " +"именем столбца и значением None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "не удалось преобразовать объект Python Unicode в байты" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "не удалось извлечь байты из кодированной строки" + +#, c-format +#~ msgid "array size exceeds the maximum allowed" +#~ msgstr "размер массива превышает предел" + +#, c-format +#~ msgid "wrong length of inner sequence: has length %d, but %d was expected" +#~ msgstr "неверная длина внутренней последовательности: %d (ожидалось: %d)" + +#, c-format +#~ msgid "" +#~ "To construct a multidimensional array, the inner sequences must all have " +#~ "the same length." +#~ msgstr "" +#~ "Для образования многомерного массива внутренние последовательности должны " +#~ "иметь одинаковую длину." + +#~ msgid "could not create new dictionary while building trigger arguments" +#~ msgstr "не удалось создать словарь для передачи аргументов триггера" + +#~ msgid "could not create globals" +#~ msgstr "не удалось создать глобальные данные" + +#~ msgid "could not create exception \"%s\"" +#~ msgstr "не удалось сгенерировать исключение \"%s\"" + +#~ msgid "could not create new dictionary" +#~ msgstr "не удалось создать словарь" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status не принимает аргументы" + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "преобразовать многомерный массив в список Python нельзя" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python поддерживает только одномерные массивы." + +#~ msgid "could not create new Python list" +#~ msgstr "не удалось создать список Python" + +#~ msgid "could not create the base SPI exceptions" +#~ msgstr "не удалось создать базовые объекты исключений SPI" + +#~ msgid "the message is already specified" +#~ msgstr "сообщение уже указано" + +#~ msgid "Python major version mismatch in session" +#~ msgstr "несовпадение базовой версии Python в сеансе" + +#~ msgid "" +#~ "This session has previously used Python major version %d, and it is now " +#~ "attempting to use Python major version %d." +#~ msgstr "" +#~ "В данном сеансе до этого использовался Python базовой версии %d, а сейчас " +#~ "планируется использовать Python версии %d." + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "" +#~ "Чтобы переключиться на другую базовую версию Python, начните новый сеанс." + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare не поддерживает составные типы" + +#~ msgid "PL/Python does not support conversion to arrays of row types." +#~ msgstr "PL/Python не поддерживает преобразование в массивы кортежей." + +#~ msgid "could not initialize plpy" +#~ msgstr "не удалось инициализировать plpy" + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "нераспознанная ошибка в PLy_spi_execute_fetch_result" diff --git a/src/pl/plpython/po/sv.po b/src/pl/plpython/po/sv.po new file mode 100644 index 0000000..8642c20 --- /dev/null +++ b/src/pl/plpython/po/sv.po @@ -0,0 +1,461 @@ +# Swedish message translation file for plpython +# Copyright (C) 2017 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# Dennis Björklund <db@zigo.dhs.org>, 2017, 2018, 2019, 2020, 2021, 2022, 2023. +# +msgid "" +msgstr "" +"Project-Id-Version: PostgreSQL 16\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-08-02 08:08+0000\n" +"PO-Revision-Date: 2023-08-02 12:03+0200\n" +"Last-Translator: Dennis Björklund <db@zigo.dhs.org>\n" +"Language-Team: Swedish <pgsql-translators@postgresql.org>\n" +"Language: sv\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor förväntade sig en fråga eller en plan" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor tar en sekvens som sitt andra argument" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "kunde inte exekvera plan" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Förväntade sekvens med %d argument, fick %d: %s" +msgstr[1] "Förväntade sekvens med %d argument, fick %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "itererar med en stängd markör" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "itererar med en markör i en avbruten subtransaktion" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "hämta från en stängd markör" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "frågeresultet har för många rader för att få plats i en Python-lista" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "stänger en markör i en avbruten subtransaktion" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "ej supportat returläge för mängdfunktion" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python mängdreturnerande funktioner stöder bara ett värde per anrop." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "returnerat objekt kan inte itereras" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python mängdreturnerande funktioner måste returnera ett itererbart objekt." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "fel vid hämtning av nästa del från iteratorn" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python-procedur returnerade inte None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python-funktion med returtyp \"void\" returnerade inte None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "oväntat returvärde från triggerprocedur" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Förväntade None eller en sträng." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python-triggerfunktion returnerade \"MODIFY\" i en DELETE-trigger -- ignorerad" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Förväntade None, \"OK\", \"SKIP\" eller \"MODIFY\"." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() misslyckades vid uppsättning av argument" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() misslyckades vid uppsättning av argument" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "en funktion med post som värde anropades i sammanhang där poster inte kan godtagas." + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "vid skapande av returvärde" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] raderad, kan inte modifiera rad" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] är inte en dictionary" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "TD[\"new\"] dictionary-nyckel vid numerisk position %d är inte en sträng" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "nyckel \"%s\" hittad i TD[\"new\"] finns inte som en kolumn i den triggande raden" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "kan inte sätta systemattribut \"%s\"" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "kan inte sätta genererad kolumn \"%s\"" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "vid modifiering av triggerrad" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "tvingar avbrytande av subtransaktion som inte har avslutats" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "multipla Python-bibliotek är aktiva i sessionen" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Bara en major-version av Python kan användas i en session." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "ej fångar fel i initiering" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "kunde inte importera \"__main__\"-modul" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "kunde inte initierar globaler" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python-procedur \"%s\"" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python-funktion \"%s\"" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python anonymt kodblock" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "kunde inte importera \"plpy\"-modul" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "kunde inte skapa modulen spiexceptions" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "kunde inte lägga till modulen spiexceptions" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "kunde inte skapa SPI-undantag" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "kunde inte packa upp argument i plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "kunde inte parsa felmeddelande i plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "argumentet 'message' angivet med namn och position" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' är ett ogiltigt nyckelordsargument för denna funktion" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "ogiltig SQLSTATE-kod" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "Triggningsfunktioner kan bara anropas vid triggning." + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python-funktioner kan inte returnera typ %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python-funktioner kan inte ta emot typ %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "kunde inte kompilera PL/Python-funktion \"%s\"" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "kunde inte kompilera anonymt PL/Python-kodblock" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "kommandot producerade inte en resultatmängd" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "andra argumentet till plpy.prepare måste vara en sekvens" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: typnamn vid numerisk position %d är inte en sträng" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute förväntade en fråga eller en plan" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute tar en sekvens som sitt andra argument" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan misslyckades: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute misslyckades: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "denna subtransaktion har redan gåtts in i" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "denna subtransaktion har redan avslutat" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "denna subtransaktion har inte gåtts in i" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "det finns ingen subtransaktion att avsluta från" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "kunde inte importera en modul för Decimal-konstruktorn" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "inga Decimal-attribut i modulen" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "konvertering från numeric till Decimal misslyckades" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "kunde inte skapa byte-representation av Python-objekt" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "kunde inte skapa strängrepresentation av Python-objekt" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "kunde inte konvertera Python-objekt till cstring: Python-strängrepresentationen verkar innehålla noll-bytes" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "returvärde för funktion med array-returtyp är inte en Python-sekvens" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "kunde inte bestämma sekvenslängd för funktionens returvärde" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "flerdimensionella vektorer måste ha array-uttryck av passande dimensioner" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "antal array-dimensioner överskriver maximalt tillåtna (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "felaktig postliteral: \"%s\"" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Saknar vänster parentes" + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "För att returnera en composite-typ i en array, returnera composite-typen som en Python-tupel, t.ex. \"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "nyckeln \"%s\" hittades inte i mapping" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "För att returnera null i en kolumn så lägg till värdet None till mappningen med nyckelnamn taget från kolumnen." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "längden på den returnerade sekvensen matchade inte antal kolumner i raden" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "attributet \"%s\" finns inte i Python-objektet" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "För att returnera null i en kolumn så låt det returnerade objektet ha ett attribut med namn efter kolumnen och med värdet None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "kunde inte konvertera Python-unicode-objekt till bytes" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "kunde inte extrahera bytes från kodad sträng" + +#, c-format +#~ msgid "To construct a multidimensional array, the inner sequences must all have the same length." +#~ msgstr "För att skapa en multidimensionell array så skall alla de inre sekvenserna ha samma längd." + +#, c-format +#~ msgid "array size exceeds the maximum allowed" +#~ msgstr "array-storlek överskrider maximalt tillåtna" + +#, c-format +#~ msgid "wrong length of inner sequence: has length %d, but %d was expected" +#~ msgstr "fel längd på inre sekvens: har längd %d, men %d förväntades" diff --git a/src/pl/plpython/po/tr.po b/src/pl/plpython/po/tr.po new file mode 100644 index 0000000..330162a --- /dev/null +++ b/src/pl/plpython/po/tr.po @@ -0,0 +1,537 @@ +# LANGUAGE message translation file for plpython +# Copyright (C) 2009 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2009. +# +msgid "" +msgstr "" +"Project-Id-Version: PostgreSQL 8.4\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2019-04-26 13:38+0000\n" +"PO-Revision-Date: 2019-06-13 17:10+0300\n" +"Last-Translator: Abdullah G. Gülner <agulner@gmail.com>\n" +"Language-Team: Turkish <devrim@gunduz.org>\n" +"Language: tr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 1.8.7.1\n" + +#: plpy_cursorobject.c:78 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor bir sorgu ya da bir plan bekledi" + +#: plpy_cursorobject.c:161 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor bir sequence'ı ikinci argüman olarak alır" + +#: plpy_cursorobject.c:177 plpy_spi.c:211 +#, c-format +msgid "could not execute plan" +msgstr "plan çalıştırılamadı" + +#: plpy_cursorobject.c:180 plpy_spi.c:214 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "%d argümanının sequence'ı beklendi; %d alındı: %s" + +#: plpy_cursorobject.c:329 +#, c-format +msgid "iterating a closed cursor" +msgstr "kapalı bir imleç (cursor) yineleniyor" + +#: plpy_cursorobject.c:337 plpy_cursorobject.c:403 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "iptal edilen bir alt-işlemdeki (subtransaction) bir cursor yineleniyor" + +#: plpy_cursorobject.c:395 +#, c-format +msgid "fetch from a closed cursor" +msgstr "kapalı bir cursor'dan getir" + +#: plpy_cursorobject.c:438 plpy_spi.c:409 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "sorgu sonucundaki satır sayısı bir Python listesine sığabilecekten çok fazla " + +#: plpy_cursorobject.c:490 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "iptal edilen bir alt-işlemdeki (subtransaction) bir cursor kapatılıyor" + +#: plpy_elog.c:129 plpy_elog.c:130 plpy_plpymodule.c:553 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:143 +#, c-format +msgid "unsupported set function return mode" +msgstr "desteklenmeyen küme fonksiyonu dönüş modu" + +#: plpy_exec.c:144 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python küme dönen fonksiyonları sadece her çağrı içinde bir değer döndürmeyi desteklerler" + +#: plpy_exec.c:157 +#, c-format +msgid "returned object cannot be iterated" +msgstr "dönen nesne yinelenemez" + +#: plpy_exec.c:158 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python küme dönen fonksiyonları yinelenebilir bir nesne dönmelidir." + +#: plpy_exec.c:172 +#, c-format +msgid "error fetching next item from iterator" +msgstr "yineleticiden sonraki öğeyi alırken hata" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python prosedürü None döndürmedi" + +#: plpy_exec.c:219 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "dönüş tipi \"void\" olan PL/Python fonksiyonu None döndürmedi" + +#: plpy_exec.c:375 plpy_exec.c:401 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "trigger yordamından beklenmeyen dönüş değeri" + +#: plpy_exec.c:376 +#, c-format +msgid "Expected None or a string." +msgstr "None ya da string bekleniyordu." + +#: plpy_exec.c:391 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python trigger fonksiyonu DELETE triggerında \"MODIFY\" döndürdü -- gözardı edildi" + +#: plpy_exec.c:402 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "None, \"OK\", \"SKIP\", ya da \"MODIFY\" bekleniyordu" + +#: plpy_exec.c:452 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "PyList_SetItem() bağımsız değişkenler ayarlanırken başarısız oldu" + +#: plpy_exec.c:456 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "PyDict_SetItemString() bağımsız değişkenler ayarlanırken başarısız oldu" + +#: plpy_exec.c:468 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "tip kaydı içermeyen alanda çağırılan ve kayıt döndüren fonksiyon" + +#: plpy_exec.c:685 +#, c-format +msgid "while creating return value" +msgstr "dönüş değeri yaratılırken" + +#: plpy_exec.c:919 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] silindi, satır düzenlenemiyor" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] bir sözlük değil" + +#: plpy_exec.c:951 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "%d sıra pozisyonundaki TD[\"new\"] sözlük anahtarı dizi değil" + +#: plpy_exec.c:958 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "TD[\"new\"] içinde bulunan \"%s\" anahtarı tetikleyen satırda bir kolon olarak bulunmuyor" + +#: plpy_exec.c:963 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "\"%s\" sistem niteliği ayarlanamıyor" + +#: plpy_exec.c:968 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "oluşturulan \"%s\" sütunu ayarlanamıyor" + +#: plpy_exec.c:1026 +#, c-format +msgid "while modifying trigger row" +msgstr "tetikleyici satırını düzenlerken" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "çıkış yapılmamış bir alt-işlem (subtransaction) zorla iptal ediliyor" + +#: plpy_main.c:125 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "oturumda birden çok Python kütüphanesi mevcut" + +#: plpy_main.c:126 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Bir oturumda sadece bir Python ana sürümü kullanılabilir." + +#: plpy_main.c:142 +#, c-format +msgid "untrapped error in initialization" +msgstr "ilklendirme aşamasında yakalanamayan hata" + +#: plpy_main.c:165 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "\"__main__\" modülü alınamadı" + +#: plpy_main.c:174 +#, c-format +msgid "could not initialize globals" +msgstr "global değerler ilklendirilemedi" + +#: plpy_main.c:399 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "\"%s\" PL/Python prosedürü" + +#: plpy_main.c:402 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "\"%s\" PL/Python fonksiyonu" + +#: plpy_main.c:410 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python anonim kod bloğu" + +#: plpy_plpymodule.c:186 plpy_plpymodule.c:189 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "\"plpy\" modülü alınamadı" + +#: plpy_plpymodule.c:204 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "spiexceptions modülü oluşturulamadı" + +#: plpy_plpymodule.c:212 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "spiexceptions modülü eklenemedi" + +#: plpy_plpymodule.c:280 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "SPI istisnaları (exception) üretilemedi" + +#: plpy_plpymodule.c:448 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "plpy.elog dosyasındaki argümanlar unpack edilemedi" + +#: plpy_plpymodule.c:457 +msgid "could not parse error message in plpy.elog" +msgstr "plpy.elog dosyasındaki hata mesajı ayrıştırılamadı" + +#: plpy_plpymodule.c:474 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "ad ve konum tarafından verilen argüman 'mesajı'" + +#: plpy_plpymodule.c:501 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' bu fonksiyon için geçersiz bir anahtar kelime argümanıdır" + +#: plpy_plpymodule.c:512 plpy_plpymodule.c:518 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "geçersiz SQLSTATE kodu" + +#: plpy_procedure.c:230 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "trigger fonksiyonları sadece trigger olarak çağırılabilirler." + +#: plpy_procedure.c:234 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python fonksiyonları %s tipini döndüremezler" + +#: plpy_procedure.c:312 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python fonksiyonlar %s tipini kabul etmezler" + +#: plpy_procedure.c:402 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "\"%s\" PL/Python fonksiyonu derlenemedi" + +#: plpy_procedure.c:405 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "anonim PL/Python kod bloğu derlenemedi" + +#: plpy_resultobject.c:121 plpy_resultobject.c:147 plpy_resultobject.c:173 +#, c-format +msgid "command did not produce a result set" +msgstr "komut bir sonuç kümesi üretmedi" + +#: plpy_spi.c:60 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepare'in ikinci argümanı sequence olmalıdır" + +#: plpy_spi.c:104 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: %d sıra posizyonundaki veri tipi dizi değil" + +#: plpy_spi.c:176 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute bir sorgu ya da bir plan bekledi" + +#: plpy_spi.c:195 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute bir sequence'ı ikinci argüman olarak alır" + +#: plpy_spi.c:305 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan başarısız oldu: %s" + +#: plpy_spi.c:347 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute başarısız oldu: %s" + +#: plpy_subxactobject.c:97 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "bu alt-işleme (subtransaction) zaten girilmiş" + +#: plpy_subxactobject.c:103 plpy_subxactobject.c:161 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "bu alt-işlemden (subtransaction) zaten çıkılmış" + +#: plpy_subxactobject.c:155 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "bu alt-işleme (subtransaction) girilmemiş" + +#: plpy_subxactobject.c:167 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "çıkılacak bir alt-işlem (subtransaction) yok" + +#: plpy_typeio.c:591 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "Decimal constructor için bir modül alınamadı" + +#: plpy_typeio.c:595 +#, c-format +msgid "no Decimal attribute in module" +msgstr "modülde Decimal niteliği yok" + +#: plpy_typeio.c:601 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "numeric'ten Decimal'e dönüşüm başarısız oldu" + +#: plpy_typeio.c:915 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "Python nesnesinin bytes gösterimi yaratılamadı" + +#: plpy_typeio.c:1063 +#, c-format +msgid "could not create string representation of Python object" +msgstr "Python nesnesinin dizgi gösterimi yaratılamadı" + +#: plpy_typeio.c:1074 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "Python nesnesi cstring'e dönüştürülemedi: Python dizgi gösterimi null bayt içeriyor olabilir." + +#: plpy_typeio.c:1183 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "dizi (array) boyut sayısı izin verilen en yüksek değeri (%d) aşmaktadır" + +#: plpy_typeio.c:1187 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "fonksiyon dönüş değeri için sequence uzunluğu belirlenemedi" + +#: plpy_typeio.c:1190 plpy_typeio.c:1194 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "dizi (array) boyutu izin verilen en yüksek değeri aşmaktadır" + +#: plpy_typeio.c:1220 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "dizi dönüp tipli dönüş değeri olan fonksiyon Python sequence'ı değildir" + +#: plpy_typeio.c:1266 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "iç sequence'in uzunluğu yanlış: %d uzunlukta, fakat %d bekleniyordu" + +#: plpy_typeio.c:1268 +#, c-format +msgid "To construct a multidimensional array, the inner sequences must all have the same length." +msgstr "Çok boyutlu bir dizi oluşturmak için, iç sequence'lerin tamamı aynı uzunlukta olmalı." + +#: plpy_typeio.c:1347 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "hatalı değer: \"%s\"" + +#: plpy_typeio.c:1348 +#, c-format +msgid "Missing left parenthesis." +msgstr "Sol parantez eksik." + +#: plpy_typeio.c:1349 plpy_typeio.c:1550 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Bir bileşik türü dizi (array) içinde döndürmek için, bileşik türü bir Python tuple, e.g., \"[('foo',)]\"." + +#: plpy_typeio.c:1396 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "\"%s\" anahtarı planlamada bulunnamadı" + +#: plpy_typeio.c:1397 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Bir kolondan Null döndürmek için, kolonun ismindeki eşleşmenin anahtarına, NONE değerini ekleyin" + +#: plpy_typeio.c:1450 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "Dönen sequence'in uzunluğu satırdaki kolonların sayısı ile eşleşmiyor." + +#: plpy_typeio.c:1548 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "\"%s\" niteliği Python nesnesinde bulunmaz" + +#: plpy_typeio.c:1551 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr " Bir kolondan null döndürmek için, döndürdüğünüz nesnenin, kolonun adına sahip bir özelliğinin olmasını ve bu özelliğin değerinin NONE olmasını sağlamanız gerekir" + +#: plpy_util.c:35 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "Python unicode nesnesi baytlara dönüştürülemedi." + +#: plpy_util.c:41 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "kodlanmış string den baytlar çıkarılamadı" + +#~ msgid "could not create new dictionary while building trigger arguments" +#~ msgstr "trigger argümanlarını oluştururken yeni sözlük yaratılamadı" + +#~ msgid "could not create globals" +#~ msgstr "evrensel değerler (globals) oluşturulamadı" + +#~ msgid "could not create exception \"%s\"" +#~ msgstr "\"%s\" istisnası (exception) oluşturulamadı" + +#~ msgid "could not create new dictionary" +#~ msgstr "Yeni sözlük yaratılamadı" + +#~ msgid "PL/Python function \"%s\" could not execute plan" +#~ msgstr "\"%s\" PL/Python fonksiyonu planı çalıştıramadı" + +#~ msgid "PL/Python function \"%s\" failed" +#~ msgstr "\"%s\" PL/Python fonksiyonu başarısız oldu" + +#~ msgid "could not create string representation of Python object in PL/Python function \"%s\" while creating return value" +#~ msgstr "dönüş değeri yaratılırken \"%s\" Pl/Python fonksiyonunun içindeki Python ensnesinin dizi gösterimi yaratılamadı" + +#~ msgid "could not compute string representation of Python object in PL/Python function \"%s\" while modifying trigger row" +#~ msgstr "tetikleyici satırı düzenlerken \"%s\" PL/Python fonksiyonunun içindeki Python nesnesinin dizi gösterimi hesaplanamadı" + +#~ msgid "out of memory" +#~ msgstr "yetersiz bellek" + +#~ msgid "PL/Python: %s" +#~ msgstr "PL/Python: %s" + +#~ msgid "could not create procedure cache" +#~ msgstr "yordam önbelleği yaratılamadı" + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "Farklı bir Python ana sürümü kullanmak için yeni bir oturum açın." + +#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +#~ msgstr "Bu oturum daha önceden %d Python ana sürümünü kullandı, ve şimdi %d ana sürümünü kullanmayı deniyor." + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "PLy_spi_execute_fetch_result içinde tanımlanamayan hata" + +#~ msgid "unrecognized error in PLy_spi_execute_query" +#~ msgstr "PLy_spi_execute_query içinde tanımlanamayan hata" + +#~ msgid "unrecognized error in PLy_spi_execute_plan" +#~ msgstr "PLy_spi_execute_plan içinde beklenmeyen hata" + +#~ msgid "unrecognized error in PLy_spi_prepare" +#~ msgstr "PLy_spi_prepare içinde tanımlanamayan hata" + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare kompozit tipleri desteklemez" + +#~ msgid "invalid arguments for plpy.prepare" +#~ msgstr "plpy.prepare için geçersiz argümanlar" + +#~ msgid "transaction aborted" +#~ msgstr "transaction iptal edildi" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status bir argüman almaz" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python sadece bir boyutlu dizileri destekler." + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "çok boyutlu dizi, Python listesine dönüştürülemedi" + +#~ msgid "PL/Python does not support conversion to arrays of row types." +#~ msgstr "PL/Python satır tiplerinin dizilere dönüşümünü desteklemez." + +#~ msgid "PyCObject_FromVoidPtr() failed" +#~ msgstr "PyCObject_FromVoidPtr() başarısız oldu" + +#~ msgid "PyCObject_AsVoidPtr() failed" +#~ msgstr "PyCObject_AsVoidPtr() başarısız oldu" diff --git a/src/pl/plpython/po/uk.po b/src/pl/plpython/po/uk.po new file mode 100644 index 0000000..0388016 --- /dev/null +++ b/src/pl/plpython/po/uk.po @@ -0,0 +1,452 @@ +msgid "" +msgstr "" +"Project-Id-Version: postgresql\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-12-17 22:08+0000\n" +"PO-Revision-Date: 2023-12-20 11:53\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_16_STABLE/plpython.pot\n" +"X-Crowdin-File-ID: 933\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor очікував запит або план" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.сursor приймає як другий аргумент послідовність" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "не вдалося виконати план" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Очікувалась послідовність %d аргументів, отримано %d: %s" +msgstr[1] "Очікувалась послідовність %d аргументів, отримано %d: %s" +msgstr[2] "Очікувалась послідовність %d аргументів, отримано %d: %s" +msgstr[3] "Очікувалась послідовність %d аргумента, отримано %d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "ітерація закритого курсора" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "ітерація курсора в перерваній субтранзакції" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "витяг з закритого курсору" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "результат запиту має забагато рядків для передачі у список Python" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "закриття курсора в перерваній транзакції" + +#: plpy_elog.c:122 plpy_elog.c:123 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "режим не підтримується для функцій, що повертають набір" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "функції PL/Python підтримують лише одне значення на виклик, коли повертають набір." + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "повернутий об'єкт не підтримує ітерації" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "функції PL/Python повинні повертати об'єкт з підтримкою ітерації, коли повертають набір." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "помилка отримання наступного елемента від ітератора" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "процедура PL/Python не повернула None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python функція з типом результату \"void\" не повернули None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "неочікуване значення процедури тригера" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "Очікувалось None або рядок." + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "Тригерна функція PL/Python повернула \"MODIFY\" в тригері DELETE -- проігноровано" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Очікувалось None, \"OK\", \"SKIP\" або \"MODIFY\"." + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "помилка PyList_SetItem() під час встановлення параметрів" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "помилка PyDict_SetItemString() під час встановлення параметрів" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "функція, що повертає набір, викликана у контексті, що не приймає тип запис" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "під час створення значення результату" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] видалено, неможливо змінити рядок" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] не є словником" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "ключ словника TD[\"new\"] на порядковий позиції %d не є рядком" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "ключ \"%s\" знайдений у TD[\"new\"] не існує як стовпець у рядку тригера" + +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "не вдалося встановити системний атрибут \"%s\"" + +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "неможливо оновити згенерований стовпець \"%s\"" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "під час зміни рядка тригера" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "примусове переривання субтранзакції, яка не вийшла" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "декілька бібліотек Python присутні у сесії" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "За один сеанс може використовуватися лише одна основна версія Python." + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "неопрацьована помилка під час ініціалізації" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "не вдалося імпортувати \"__main__\" модуль" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "не вдалося ініціалізувати globals" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python процедура \"%s\"" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python функція \"%s\"" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "анонімні коди блоку PL/Python" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "не вдалося імпортувати \"plpy\" модуль" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "не вдалося створити spiexceptions модуль" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "не вдалося додати spiexceptions модуль" + +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "не вдається створити винятки SPI" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "не вдалося розпакувати аргументи в plpy.elog" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "не вдалося проаналізувати повідомлення про помилку в plpy.elog" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "аргумент 'повідомлення' виданий за ім'ям та розташуванням" + +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' є неприпустимим ключовим словом-аргументом для цієї функції" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "неприпустимий код SQLSTATE" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "тригер-функція може викликатися лише як тригер" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python функції не можуть повернути тип %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python функції не можуть прийняти тип %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "не вдалося скомпілювати функцію PL/Python \"%s\"" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "не вдалося скомпілювати анонімні коди блоку PL/Python" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "команда не створила набір результатів" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "другий аргумент plpy.prepare має бути послідовністю" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: ім'я на порядковий позиції %d не є рядком" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute очікував запит або план" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute приймає як другий аргумент послідовність" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan не спрацював: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute не спрацював: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "ця субтранзакція вже почалась" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "ця субтранзакція вже вийшла" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "ця субтранзакція ще не почалася" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "немає субтранзакції, щоб з неї вийти" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "не вдалося імпортувати модуль для конструктора Decimal" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "відсутній атрибут Decimal у модулі" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "не вдалося виконати перетворення з numeric на Decimal" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "не вдалося створити байтову репрезентацію об'єкта Python" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "не вдалося створити рядкову репрезентацію об'єкта Python" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "не вдалося перетворити об'єкт Python на cstring: репрезентація рядка Python містить значення null-байти" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "значення функції з масивом в якості результату не є послідовністю Python" + +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "не вдалося визначити довжину послідовності для значення функція" + +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "для багатовимірних масивів повинні задаватись вирази з відповідними вимірами" + +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "кількість вимірів масиву перевищує максимально дозволену (%d)" + +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "невірно сформований літерал запису: \"%s\"" + +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "Відсутня ліва дужка." + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "Щоб повернути складений тип в масиві, треба повернути композитний тип як кортеж Python, наприклад, \"[('foo',)]\"." + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "ключ \"%s\" не знайдено в зіставленні" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "Для повернення значення null в стовпці, додайте значення None з ключом, що дорівнює імені стовпця." + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "довжина повернутої послідовності не відповідає кількості стовпців у рядку" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "атрибут \"%s\" не існує в об'єкті Python" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "Щоб повернути null в стовпці, результуючий об'єкт має мати атрибут з іменем стовпця зі значенням None." + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "не вдалося конвертувати об'єкт Python Unicode в байти" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "не можливо отримати байт з закодованого рядка" + diff --git a/src/pl/plpython/po/vi.po b/src/pl/plpython/po/vi.po new file mode 100644 index 0000000..58e0505 --- /dev/null +++ b/src/pl/plpython/po/vi.po @@ -0,0 +1,485 @@ +# LANGUAGE message translation file for plpython +# Copyright (C) 2018 PostgreSQL Global Development Group +# This file is distributed under the same license as the plpython (PostgreSQL) package. +# FIRST AUTHOR <kakalot49@gmail.com>, 2018. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 11\n" +"Report-Msgid-Bugs-To: pgsql-bugs@postgresql.org\n" +"POT-Creation-Date: 2018-04-22 12:08+0000\n" +"PO-Revision-Date: 2018-05-06 22:57+0900\n" +"Language-Team: <pgvn_translators@postgresql.vn>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 2.0.6\n" +"Last-Translator: Dang Minh Huong <kakalot49@gmail.com>\n" +"Language: vi_VN\n" + +#: plpy_cursorobject.c:101 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor kỳ vọng một câu truy vấn hoặc một plan" + +#: plpy_cursorobject.c:184 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor lấy một chuỗi làm đối số thứ hai" + +#: plpy_cursorobject.c:200 plpy_spi.c:211 +#, c-format +msgid "could not execute plan" +msgstr "không thể chạy plan" + +#: plpy_cursorobject.c:203 plpy_spi.c:214 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "Kỳ vọng chuỗi của đối số %d, đã nhận %d: %s" + +#: plpy_cursorobject.c:352 +#, c-format +msgid "iterating a closed cursor" +msgstr "lặp lại con trỏ đã đóng" + +#: plpy_cursorobject.c:360 plpy_cursorobject.c:426 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "lặp lại một con trỏ trong một subtransaction đã bị hủy bỏ" + +#: plpy_cursorobject.c:418 +#, c-format +msgid "fetch from a closed cursor" +msgstr "fetch từ một con trỏ đã bị đóng" + +#: plpy_cursorobject.c:461 plpy_spi.c:409 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "kết quả câu truy vấn có quá nhiều hàng để vừa với một danh sách Python" + +#: plpy_cursorobject.c:512 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "đóng một con trỏ trong một subtransaction bị hủy bỏ" + +#: plpy_elog.c:127 plpy_elog.c:128 plpy_plpymodule.c:559 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:142 +#, c-format +msgid "unsupported set function return mode" +msgstr "không hỗ trợ thiết lập hàm trả về mode" + +#: plpy_exec.c:143 +#, c-format +msgid "" +"PL/Python set-returning functions only support returning one value per call." +msgstr "" +"PL/Python hàm thiết lập-trả về chỉ hỗ trợ trả về một giá trị cho một lần gọi." + +#: plpy_exec.c:156 +#, c-format +msgid "returned object cannot be iterated" +msgstr "đối tượng trả về không thể được lặp lại" + +#: plpy_exec.c:157 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python hàm thiết lập-trả về phải trả về một iterable object." + +#: plpy_exec.c:171 +#, c-format +msgid "error fetching next item from iterator" +msgstr "lỗi khi fetch item tiếp theo từ iterator" + +#: plpy_exec.c:214 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "Thủ tục PL/Python đã không trả về None" + +#: plpy_exec.c:218 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "Hàm PL/Python với kiểu trả về là \"void\" đã không trả về None" + +#: plpy_exec.c:374 plpy_exec.c:400 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "không mong đợi giá trị trả về từ thủ tục trigger" + +#: plpy_exec.c:375 +#, c-format +msgid "Expected None or a string." +msgstr "Kỳ vọng None hoặc một chuỗi." + +#: plpy_exec.c:390 +#, c-format +msgid "" +"PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "" +"Hàm trigger PL/Python đã trả về \"MODIFY\" trong một DELETE trigger -- bỏ qua" + +#: plpy_exec.c:401 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "Kỳ vọng None, \"OK\", \"SKIP\", hoặc \"MODIFY\"." + +#: plpy_exec.c:451 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "Lỗi PyList_SetItem(), trong khi thiết lập đối số" + +#: plpy_exec.c:455 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "Lỗi PyDict_SetItemString(), trong khi thiết lập đối số" + +#: plpy_exec.c:467 +#, c-format +msgid "" +"function returning record called in context that cannot accept type record" +msgstr "" +"hàm trả về bản ghi được gọi trong ngữ cảnh không thể chấp nhận kiểu bản ghi" + +#: plpy_exec.c:684 +#, c-format +msgid "while creating return value" +msgstr "trong khi tạo ra giá trị trả về" + +#: plpy_exec.c:909 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] đã bị xóa, không thể sửa đổi hàng" + +#: plpy_exec.c:914 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] không phải là từ điển" + +#: plpy_exec.c:941 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "Khóa từ điển TD[\"new\"] ở vị trí thứ tự %d không phải là chuỗi" + +#: plpy_exec.c:948 +#, c-format +msgid "" +"key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering " +"row" +msgstr "" +"khóa \"%s\" được tìm thấy trong TD[\"new\"] không tồn tại như là trigger mức " +"độ hàng" + +#: plpy_exec.c:953 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "không thể thiết lập thuộc tính hệ thống \"%s\"" + +#: plpy_exec.c:1011 +#, c-format +msgid "while modifying trigger row" +msgstr "trong khi sửa đổi trigger mức độ hàng" + +#: plpy_exec.c:1072 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "buộc phải hủy bỏ một subtransaction chưa được thoát" + +#: plpy_main.c:125 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "có nhiều thư viện Python trong một phiên" + +#: plpy_main.c:126 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "Chỉ có thể sử dụng một phiên bản chính của Python trong một phiên." + +#: plpy_main.c:142 +#, c-format +msgid "untrapped error in initialization" +msgstr "lỗi chưa được bẫy trong lúc khởi tạo" + +#: plpy_main.c:165 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "không thể nhập mô-đun \"__main__\"" + +#: plpy_main.c:174 +#, c-format +msgid "could not initialize globals" +msgstr "không thể khởi tạo biến global" + +#: plpy_main.c:399 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "Thủ tục PL/Python \"%s\"" + +#: plpy_main.c:402 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "Hàm PL/Python \"%s\"" + +#: plpy_main.c:410 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "Khối mã ẩn danh PL/Python" + +#: plpy_plpymodule.c:192 plpy_plpymodule.c:195 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "không thể nhập mô-đun \"plpy\"" + +#: plpy_plpymodule.c:210 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "không thể tạo mô-đun spiexceptions" + +#: plpy_plpymodule.c:218 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "không thể thêm mô-đun spiexceptions" + +#: plpy_plpymodule.c:286 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "không thể tạo exception SPI" + +#: plpy_plpymodule.c:454 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "không thể giải nén đối số trong plpy.elog" + +#: plpy_plpymodule.c:463 +msgid "could not parse error message in plpy.elog" +msgstr "không thể phân tích cú pháp thông điệp lỗi trong plpy.elog" + +#: plpy_plpymodule.c:480 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "đối số 'message' được chỉ định theo tên và vị trí" + +#: plpy_plpymodule.c:507 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "'%s' là đối số từ khóa không hợp lệ cho hàm này" + +#: plpy_plpymodule.c:518 plpy_plpymodule.c:524 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "mã SQLSTATE không hợp lệ" + +#: plpy_procedure.c:230 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "hàm trigger chỉ có thể được gọi như trigger" + +#: plpy_procedure.c:234 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "Hàm PL/Python không thể trả về kiểu %s" + +#: plpy_procedure.c:312 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "Các hàm PL/Python không thể chấp nhận kiểu %s" + +#: plpy_procedure.c:402 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "không thể biên dịch hàm PL/Python \"%s\"" + +#: plpy_procedure.c:405 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "không thể biên dịch khối mã ẩn danh PL/Python" + +#: plpy_resultobject.c:150 plpy_resultobject.c:176 plpy_resultobject.c:202 +#, c-format +msgid "command did not produce a result set" +msgstr "lệnh không tạo ra một tập hợp kết quả" + +#: plpy_spi.c:60 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "đối số thứ hai của plpy.prepare phải là một chuỗi" + +#: plpy_spi.c:104 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: gõ tên tại vị trí thứ tự %d không phải là một chuỗi" + +#: plpy_spi.c:176 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute kỳ vọng một truy vấn hoặc một plan" + +#: plpy_spi.c:195 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute lấy một chuỗi làm đối số thứ hai" + +#: plpy_spi.c:305 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan lỗi: %s" + +#: plpy_spi.c:347 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute lỗi: %s" + +#: plpy_subxactobject.c:122 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "subtransaction này đã được nhập" + +#: plpy_subxactobject.c:128 plpy_subxactobject.c:186 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "subtransaction này đã được thoát" + +#: plpy_subxactobject.c:180 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "subtransaction này chưa được nhập" + +#: plpy_subxactobject.c:192 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "không có subtransaction để thoát khỏi" + +#: plpy_typeio.c:591 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "không thể nhập mô-đun cho Decimal constructor" + +#: plpy_typeio.c:595 +#, c-format +msgid "no Decimal attribute in module" +msgstr "không có thuộc tính thập phân trong mô-đun" + +#: plpy_typeio.c:601 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "chuyển đổi từ numeric sang thập phân không thành công" + +#: plpy_typeio.c:908 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "không thể tạo đại diện cho của đối tượng Python" + +#: plpy_typeio.c:1056 +#, c-format +msgid "could not create string representation of Python object" +msgstr "không thể tạo ra chuỗi đại diện cho đối tượng Python" + +#: plpy_typeio.c:1067 +#, c-format +msgid "" +"could not convert Python object into cstring: Python string representation " +"appears to contain null bytes" +msgstr "" +"không thể chuyển đổi đối tượng Python thành cstring: đại diện chuỗi Python " +"chứa byte null" + +#: plpy_typeio.c:1176 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "số lượng hướng của mảng vượt quá số lượng tối đa cho phép (%d)" + +#: plpy_typeio.c:1180 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "không thể xác định độ dài chuỗi cho giá trị trả về hàm" + +#: plpy_typeio.c:1183 plpy_typeio.c:1187 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "kích thước mảng vượt quá mức tối đa cho phép" + +#: plpy_typeio.c:1213 +#, c-format +msgid "" +"return value of function with array return type is not a Python sequence" +msgstr "" +"giá trị trả về của hàm với kiểu trả về là mảng không phải là một chuỗi Python" + +#: plpy_typeio.c:1259 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "sai độ dài của chuỗi bên trong: có độ dài %d, nhưng %d được mong đợi" + +#: plpy_typeio.c:1261 +#, c-format +msgid "" +"To construct a multidimensional array, the inner sequences must all have the " +"same length." +msgstr "" +"Để xây dựng một mảng đa chiều, các chuỗi bên trong phải có cùng độ dài." + +#: plpy_typeio.c:1340 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "bản ghi literal không đúng định dạng: \"%s\"" + +#: plpy_typeio.c:1341 +#, c-format +msgid "Missing left parenthesis." +msgstr "Thiếu dấu ngoặc đơn trái." + +#: plpy_typeio.c:1342 plpy_typeio.c:1543 +#, c-format +msgid "" +"To return a composite type in an array, return the composite type as a " +"Python tuple, e.g., \"[('foo',)]\"." +msgstr "" +"Để trả về kiểu phức hợp trong một mảng, hãy trả về kiểu phức hợp dưới dạng " +"một hàng Python, ví dụ: \"[('foo',)]\"." + +#: plpy_typeio.c:1389 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "không tìm thấy khóa \"%s\" trong ánh xạ" + +#: plpy_typeio.c:1390 +#, c-format +msgid "" +"To return null in a column, add the value None to the mapping with the key " +"named after the column." +msgstr "" +"Để trả về null trong một cột, thêm giá trị None vào ánh xạ với khóa được đặt " +"tên sau cột." + +#: plpy_typeio.c:1443 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "độ dài của chuỗi được trả về không khớp với số cột trong hàng" + +#: plpy_typeio.c:1541 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "thuộc tính \"%s\" không tồn tại trong đối tượng Python" + +#: plpy_typeio.c:1544 +#, c-format +msgid "" +"To return null in a column, let the returned object have an attribute named " +"after column with value None." +msgstr "" +"Để trả về null trong một cột, hãy để đối tượng trả về có một thuộc tính được " +"đặt tên sau cột với giá trị None." + +#: plpy_util.c:35 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "không thể chuyển đổi đối tượng Python Unicode thành byte" + +#: plpy_util.c:41 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "không thể trích xuất byte từ chuỗi đã được mã hóa" diff --git a/src/pl/plpython/po/zh_CN.po b/src/pl/plpython/po/zh_CN.po new file mode 100644 index 0000000..264d4f5 --- /dev/null +++ b/src/pl/plpython/po/zh_CN.po @@ -0,0 +1,459 @@ +# LANGUAGE message translation file for plpython +# Copyright (C) 2010 PostgreSQL Global Development Group +# This file is distributed under the same license as the PostgreSQL package. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2010. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 14\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2021-08-14 05:38+0000\n" +"PO-Revision-Date: 2021-08-16 18:00+0800\n" +"Last-Translator: Jie Zhang <zhangjie2@fujitsu.com>\n" +"Language-Team: Chinese (Simplified) <zhangjie2@fujitsu.com>\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Poedit 1.5.7\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor期望一个查询或一个计划" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.cursor将一个序列作为它的第二个参数" + +#: plpy_cursorobject.c:171 plpy_spi.c:207 +#, c-format +msgid "could not execute plan" +msgstr "无法执行计划" + +#: plpy_cursorobject.c:174 plpy_spi.c:210 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "期望%d序列参数,但是得到%d: %s" + +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "遍历一个关闭的游标" + +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "在终止的子事务里遍历一个游标" + +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "从关闭的游标里获取结果" + +#: plpy_cursorobject.c:430 plpy_spi.c:403 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "查询结果中的行太多,无法放在一个Python列表中" + +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "在终止的子事务里关闭一个游标" + +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:548 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "不支持集合函数返回模式" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python集合返回函数只支持在每次调用时返回一个值。" + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "所返回的对象无法迭代" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python集合返回函数必须返回一个可迭代的对象." + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "当从迭代器中取回下一个成员时出现错误" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python过程没有返回None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "返回类型为\"void\"的PL/Python函数不返回None" + +#: plpy_exec.c:371 plpy_exec.c:397 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "在触发器存储过程出现非期望的返回值" + +#: plpy_exec.c:372 +#, c-format +msgid "Expected None or a string." +msgstr "期望空值或一个字符串" + +#: plpy_exec.c:387 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "在DELETE触发器中的PL/Python 触发器函数返回 \"MODIFY\" -- 忽略" + +#: plpy_exec.c:398 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "期望None, \"OK\", \"SKIP\", 或\"MODIFY\"" + +#: plpy_exec.c:443 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "当设置参数的同时, 执行PyList_SetItem()失败" + +#: plpy_exec.c:447 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "当设置参数的同时, 执行PyDict_SetItemString()失败" + +#: plpy_exec.c:459 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "返回值类型是记录的函数在不接受使用记录类型的环境中调用" + +#: plpy_exec.c:676 +#, c-format +msgid "while creating return value" +msgstr "同时在创建返回值" + +#: plpy_exec.c:910 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] 已删除,无法修改记录" + +#: plpy_exec.c:915 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"]不是一个字典" + +#: plpy_exec.c:942 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "在顺序位置%d的TD[\"new\"]字典键值不是字符串" + +#: plpy_exec.c:949 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "在 TD[\"new\"]中找到的键 \"%s\"在正在触发的记录中不是作为列而存在." + +#: plpy_exec.c:954 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "不能设置系统属性\"%s\"" + +#: plpy_exec.c:959 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "无法设置生成的列 \"%s\"" + +#: plpy_exec.c:1017 +#, c-format +msgid "while modifying trigger row" +msgstr "同时正在修改触发器记录" + +#: plpy_exec.c:1075 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "强行终止一个还未退出的子事务" + +#: plpy_main.c:121 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "会话中存在多个Python库" + +#: plpy_main.c:122 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "一个会话中只能使用一个Python主版本." + +#: plpy_main.c:138 +#, c-format +msgid "untrapped error in initialization" +msgstr "在初始化过程中出现无法捕获的错误" + +#: plpy_main.c:161 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "无法导入模块\"__main__\" " + +#: plpy_main.c:170 +#, c-format +msgid "could not initialize globals" +msgstr "无法初始化全局变量" + +#: plpy_main.c:393 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python过程\"%s\"" + +#: plpy_main.c:396 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python函数\"%s\"" + +#: plpy_main.c:404 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python匿名代码块" + +#: plpy_plpymodule.c:182 plpy_plpymodule.c:185 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "无法导入模块\"plpy\" " + +#: plpy_plpymodule.c:200 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "无法创建spiexceptions模块" + +#: plpy_plpymodule.c:208 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "无法添加spiexceptions模块 " + +#: plpy_plpymodule.c:275 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "无法产生SPI异常" + +#: plpy_plpymodule.c:443 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "无法解析plpy.elog中的参数" + +#: plpy_plpymodule.c:452 +msgid "could not parse error message in plpy.elog" +msgstr "无法解析在plpy.elog中的错误消息" + +#: plpy_plpymodule.c:469 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "由名称和位置提供的参数'message'" + +#: plpy_plpymodule.c:496 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "对于这个函数,'%s'是一个无效的关键词参数" + +#: plpy_plpymodule.c:507 plpy_plpymodule.c:513 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "无效的SQLSTATE代码" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "触发器函数只能以触发器的形式调用" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python函数不能返回类型%s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python函数不能接受类型%s" + +#: plpy_procedure.c:397 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "无法编译PL/Python函数\"%s\"" + +#: plpy_procedure.c:400 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "无法编译PL/Python中的匿名代码块" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "命令没有产生结果集" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepare的第二个参数必须是一个序列" + +#: plpy_spi.c:100 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: 在顺序位置%d的类型名称不是string" + +#: plpy_spi.c:172 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute期望一个查询或一个计划" + +#: plpy_spi.c:191 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute将一个序列作为它的第二个参数" + +#: plpy_spi.c:299 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "执行SPI_execute_plan失败: %s" + +#: plpy_spi.c:341 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute执行失败: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "已经进入该子事务" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "已经退出该子事务" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "该子事务仍没有进入" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "没有子事务可以退出" + +#: plpy_typeio.c:587 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "无法为十进制构造函数导入模块" + +#: plpy_typeio.c:591 +#, c-format +msgid "no Decimal attribute in module" +msgstr "模块中没有小数位属性" + +#: plpy_typeio.c:597 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "由numeric数值到Decimal小数转换失败" + +#: plpy_typeio.c:911 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "无法创建Python对象的字节表达式" + +#: plpy_typeio.c:1056 +#, c-format +msgid "could not create string representation of Python object" +msgstr "无法创建Python对象的字符串表达式" + +#: plpy_typeio.c:1067 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "无法将Python对象转换为cstring: Python字符串表达式可能包含空字节" + +#: plpy_typeio.c:1178 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "数组的维数超过最大允许值(%d)" + +#: plpy_typeio.c:1183 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "无法确定函数返回值的序列长度" + +#: plpy_typeio.c:1188 plpy_typeio.c:1194 +#, c-format +msgid "array size exceeds the maximum allowed" +msgstr "数组的大小超过了最大允许值" + +#: plpy_typeio.c:1222 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "带有数组返回类型的函数返回值不是一个Python序列" + +#: plpy_typeio.c:1269 +#, c-format +msgid "wrong length of inner sequence: has length %d, but %d was expected" +msgstr "内部序列的长度错误:长度为%d,但应为%d" + +#: plpy_typeio.c:1271 +#, c-format +msgid "To construct a multidimensional array, the inner sequences must all have the same length." +msgstr "要构造多维数组,内部序列的长度必须相同." + +#: plpy_typeio.c:1350 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "有缺陷的记录常量: \"%s\"" + +#: plpy_typeio.c:1351 +#, c-format +msgid "Missing left parenthesis." +msgstr "缺少一个左括弧" + +#: plpy_typeio.c:1352 plpy_typeio.c:1553 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "要返回数组中的复合类型,请将复合类型作为Python元组返回,例如 \"[('foo',)]\"." + +#: plpy_typeio.c:1399 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "在映射中没有找到键\"%s\"" + +#: plpy_typeio.c:1400 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "为了在一列中返回空值, 需要在列的后面对带有已命名键的映射添加值None" + +#: plpy_typeio.c:1453 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "所返回序列的长度与在记录中列的数量不匹配" + +#: plpy_typeio.c:1551 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "在Python对象中不存在属性\"%s\"" + +#: plpy_typeio.c:1554 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "为了在一列中返回空值, 需要让返回的对象在带有值None的列后面的带有已命名属性" + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "无法将Python中以Unicode编码的对象转换为PostgreSQL服务器字节码" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "无法从已编码字符串里提取相应字节码值" diff --git a/src/pl/plpython/po/zh_TW.po b/src/pl/plpython/po/zh_TW.po new file mode 100644 index 0000000..d698881 --- /dev/null +++ b/src/pl/plpython/po/zh_TW.po @@ -0,0 +1,496 @@ +# Traditional Chinese message translation file for plpython +# Copyright (C) 2023 PostgreSQL Global Development Group +# This file is distributed under the same license as the plpython (PostgreSQL) package. +# Zhenbang Wei <znbang@gmail.com>, 2011. +# +msgid "" +msgstr "" +"Project-Id-Version: plpython (PostgreSQL) 16\n" +"Report-Msgid-Bugs-To: pgsql-bugs@lists.postgresql.org\n" +"POT-Creation-Date: 2023-09-11 20:38+0000\n" +"PO-Revision-Date: 2023-11-06 08:50+0800\n" +"Last-Translator: Zhenbang Wei <znbang@gmail.com>\n" +"Language-Team: \n" +"Language: zh_TW\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 3.4.1\n" + +#: plpy_cursorobject.c:72 +#, c-format +msgid "plpy.cursor expected a query or a plan" +msgstr "plpy.cursor 預期查詢或計劃" + +#: plpy_cursorobject.c:155 +#, c-format +msgid "plpy.cursor takes a sequence as its second argument" +msgstr "plpy.execute 接受序列做為第二個參數" + +#: plpy_cursorobject.c:171 plpy_spi.c:205 +#, c-format +msgid "could not execute plan" +msgstr "無法執行計畫" + +#: plpy_cursorobject.c:174 plpy_spi.c:208 +#, c-format +msgid "Expected sequence of %d argument, got %d: %s" +msgid_plural "Expected sequence of %d arguments, got %d: %s" +msgstr[0] "預期 %d 個參數序列,收到 %d 個: %s" + +# sql_help.h:345 +#: plpy_cursorobject.c:321 +#, c-format +msgid "iterating a closed cursor" +msgstr "迭代已關閉的游標" + +# postmaster/postmaster.c:1832 +#: plpy_cursorobject.c:329 plpy_cursorobject.c:395 +#, c-format +msgid "iterating a cursor in an aborted subtransaction" +msgstr "在已中止的子交易中迭代游標" + +# sql_help.h:109 +#: plpy_cursorobject.c:387 +#, c-format +msgid "fetch from a closed cursor" +msgstr "從已關閉的游標中擷取資料" + +#: plpy_cursorobject.c:430 plpy_spi.c:401 +#, c-format +msgid "query result has too many rows to fit in a Python list" +msgstr "查詢結果的列數過多,無法放入 Python 串列" + +# postmaster/postmaster.c:1832 +#: plpy_cursorobject.c:482 +#, c-format +msgid "closing a cursor in an aborted subtransaction" +msgstr "在已中止的子交易中關閉游標" + +# commands/vacuum.c:2258 commands/vacuumlazy.c:489 commands/vacuumlazy.c:770 +# nodes/print.c:86 storage/lmgr/deadlock.c:888 tcop/postgres.c:3285 +#: plpy_elog.c:125 plpy_elog.c:126 plpy_plpymodule.c:530 +#, c-format +msgid "%s" +msgstr "%s" + +#: plpy_exec.c:139 +#, c-format +msgid "unsupported set function return mode" +msgstr "不支援的集合函數回傳模式" + +#: plpy_exec.c:140 +#, c-format +msgid "PL/Python set-returning functions only support returning one value per call." +msgstr "PL/Python 的集合回傳函數僅支援每次呼叫回傳一個值。" + +#: plpy_exec.c:153 +#, c-format +msgid "returned object cannot be iterated" +msgstr "無法迭代回傳的物件" + +#: plpy_exec.c:154 +#, c-format +msgid "PL/Python set-returning functions must return an iterable object." +msgstr "PL/Python 的集合回傳函數必須回傳一個可迭代的物件。" + +#: plpy_exec.c:168 +#, c-format +msgid "error fetching next item from iterator" +msgstr "從迭代器取得下一個項目時發生錯誤" + +#: plpy_exec.c:211 +#, c-format +msgid "PL/Python procedure did not return None" +msgstr "PL/Python 程序未回傳 None" + +#: plpy_exec.c:215 +#, c-format +msgid "PL/Python function with return type \"void\" did not return None" +msgstr "PL/Python 函數的回傳型別為 \"void\" 但未回傳 None" + +#: plpy_exec.c:369 plpy_exec.c:393 +#, c-format +msgid "unexpected return value from trigger procedure" +msgstr "觸發程序回傳非預期的值" + +#: plpy_exec.c:370 +#, c-format +msgid "Expected None or a string." +msgstr "預期 None 或字串。" + +#: plpy_exec.c:383 +#, c-format +msgid "PL/Python trigger function returned \"MODIFY\" in a DELETE trigger -- ignored" +msgstr "PL/Python 觸發函數在 DELETE 觸發器中回傳 \"MODIFY\" -- 已忽略" + +#: plpy_exec.c:394 +#, c-format +msgid "Expected None, \"OK\", \"SKIP\", or \"MODIFY\"." +msgstr "預期 None 或 \"OK\" 或 \"SKIP\" 或 \"MODIFY\"。" + +#: plpy_exec.c:444 +#, c-format +msgid "PyList_SetItem() failed, while setting up arguments" +msgstr "設定 PyList_SetItem() 的參數時失敗" + +#: plpy_exec.c:448 +#, c-format +msgid "PyDict_SetItemString() failed, while setting up arguments" +msgstr "設定 PyDict_SetItemString() 的參數時失敗" + +#: plpy_exec.c:460 +#, c-format +msgid "function returning record called in context that cannot accept type record" +msgstr "在不接受紀錄型別的 context 中呼叫回傳紀錄的函數" + +#: plpy_exec.c:677 +#, c-format +msgid "while creating return value" +msgstr "在建立回傳值時" + +#: plpy_exec.c:924 +#, c-format +msgid "TD[\"new\"] deleted, cannot modify row" +msgstr "TD[\"new\"] 已刪除,無法修改資料列" + +#: plpy_exec.c:929 +#, c-format +msgid "TD[\"new\"] is not a dictionary" +msgstr "TD[\"new\"] 不是字典" + +#: plpy_exec.c:954 +#, c-format +msgid "TD[\"new\"] dictionary key at ordinal position %d is not a string" +msgstr "位於順序位置 %d 的 TD[\"new\"] 字典鍵不是字串" + +#: plpy_exec.c:961 +#, c-format +msgid "key \"%s\" found in TD[\"new\"] does not exist as a column in the triggering row" +msgstr "TD[\"new\"] 中的鍵 \"%s\" 不在觸發資料列的欄位中" + +# commands/tablecmds.c:5425 +#: plpy_exec.c:966 +#, c-format +msgid "cannot set system attribute \"%s\"" +msgstr "無法設定系統屬性 \"%s\"" + +# commands/tablecmds.c:4580 +#: plpy_exec.c:971 +#, c-format +msgid "cannot set generated column \"%s\"" +msgstr "無法設定產生的欄位 \"%s\"" + +#: plpy_exec.c:1029 +#, c-format +msgid "while modifying trigger row" +msgstr "在修改觸發資料列時" + +#: plpy_exec.c:1087 +#, c-format +msgid "forcibly aborting a subtransaction that has not been exited" +msgstr "強制中止尚未退出的子交易" + +#: plpy_main.c:109 +#, c-format +msgid "multiple Python libraries are present in session" +msgstr "工作階段中有多個 Python 程式庫" + +#: plpy_main.c:110 +#, c-format +msgid "Only one Python major version can be used in one session." +msgstr "同一個工作階段中只能使用一個 Python 主要版本。" + +#: plpy_main.c:122 +#, c-format +msgid "untrapped error in initialization" +msgstr "初始化中未被捕獲的錯誤" + +#: plpy_main.c:145 +#, c-format +msgid "could not import \"__main__\" module" +msgstr "無法匯入 \"__main__\" 模組" + +#: plpy_main.c:154 +#, c-format +msgid "could not initialize globals" +msgstr "無法初始化全域變數" + +#: plpy_main.c:352 +#, c-format +msgid "PL/Python procedure \"%s\"" +msgstr "PL/Python 程序 \"%s\"" + +#: plpy_main.c:355 +#, c-format +msgid "PL/Python function \"%s\"" +msgstr "PL/Python 函數 \"%s\"" + +#: plpy_main.c:363 +#, c-format +msgid "PL/Python anonymous code block" +msgstr "PL/Python 匿名程式區塊" + +#: plpy_plpymodule.c:168 plpy_plpymodule.c:171 +#, c-format +msgid "could not import \"plpy\" module" +msgstr "無法匯入 \"plpy\" 模組" + +#: plpy_plpymodule.c:182 +#, c-format +msgid "could not create the spiexceptions module" +msgstr "無法建立 spiexceptions 模組" + +#: plpy_plpymodule.c:190 +#, c-format +msgid "could not add the spiexceptions module" +msgstr "無法新增 spiexceptions 模組" + +# access/transam/xlog.c:4111 +#: plpy_plpymodule.c:257 +#, c-format +msgid "could not generate SPI exceptions" +msgstr "無法產生 SPI 例外" + +#: plpy_plpymodule.c:425 +#, c-format +msgid "could not unpack arguments in plpy.elog" +msgstr "無法解析 plpy.elog 中的參數" + +#: plpy_plpymodule.c:434 +msgid "could not parse error message in plpy.elog" +msgstr "無法解析 plpy.elog 中的錯誤訊息" + +#: plpy_plpymodule.c:451 +#, c-format +msgid "argument 'message' given by name and position" +msgstr "參數 'message' 被同時以名稱和位置給定" + +# commands/define.c:279 +#: plpy_plpymodule.c:478 +#, c-format +msgid "'%s' is an invalid keyword argument for this function" +msgstr "這個函數的 '%s' 是無效關鍵字參數" + +#: plpy_plpymodule.c:489 plpy_plpymodule.c:495 +#, c-format +msgid "invalid SQLSTATE code" +msgstr "無效的 SQLSTATE 碼" + +#: plpy_procedure.c:225 +#, c-format +msgid "trigger functions can only be called as triggers" +msgstr "觸發函數只能作為觸發器被呼叫" + +#: plpy_procedure.c:229 +#, c-format +msgid "PL/Python functions cannot return type %s" +msgstr "PL/Python 函數無法回傳型別 %s" + +#: plpy_procedure.c:307 +#, c-format +msgid "PL/Python functions cannot accept type %s" +msgstr "PL/Python 函數不能接受型別 %s" + +#: plpy_procedure.c:395 +#, c-format +msgid "could not compile PL/Python function \"%s\"" +msgstr "無法編譯 PL/Python 函數 \"%s\"" + +#: plpy_procedure.c:398 +#, c-format +msgid "could not compile anonymous PL/Python code block" +msgstr "無法編譯匿名 PL/Python 程式區塊" + +#: plpy_resultobject.c:117 plpy_resultobject.c:143 plpy_resultobject.c:169 +#, c-format +msgid "command did not produce a result set" +msgstr "命令未產生結果集" + +#: plpy_spi.c:56 +#, c-format +msgid "second argument of plpy.prepare must be a sequence" +msgstr "plpy.prepare 的第二個參數必須是序列" + +#: plpy_spi.c:98 +#, c-format +msgid "plpy.prepare: type name at ordinal position %d is not a string" +msgstr "plpy.prepare: 順序位置 %d 的型別名稱不是字串" + +#: plpy_spi.c:170 +#, c-format +msgid "plpy.execute expected a query or a plan" +msgstr "plpy.execute 預期查詢或計畫" + +#: plpy_spi.c:189 +#, c-format +msgid "plpy.execute takes a sequence as its second argument" +msgstr "plpy.execute 接受序列做為第二個參數" + +#: plpy_spi.c:297 +#, c-format +msgid "SPI_execute_plan failed: %s" +msgstr "SPI_execute_plan 失敗: %s" + +#: plpy_spi.c:339 +#, c-format +msgid "SPI_execute failed: %s" +msgstr "SPI_execute 失敗: %s" + +#: plpy_subxactobject.c:92 +#, c-format +msgid "this subtransaction has already been entered" +msgstr "已進入這個子交易" + +#: plpy_subxactobject.c:98 plpy_subxactobject.c:156 +#, c-format +msgid "this subtransaction has already been exited" +msgstr "已結束這個子交易" + +#: plpy_subxactobject.c:150 +#, c-format +msgid "this subtransaction has not been entered" +msgstr "尚未進入這個子交易" + +#: plpy_subxactobject.c:162 +#, c-format +msgid "there is no subtransaction to exit from" +msgstr "無需結束任何子交易" + +#: plpy_typeio.c:588 +#, c-format +msgid "could not import a module for Decimal constructor" +msgstr "無法匯入 Decimal 建構子的模組" + +#: plpy_typeio.c:592 +#, c-format +msgid "no Decimal attribute in module" +msgstr "模組中找不到 Decimal 屬性" + +#: plpy_typeio.c:598 +#, c-format +msgid "conversion from numeric to Decimal failed" +msgstr "從數值轉換為 Decimal 失敗" + +#: plpy_typeio.c:912 +#, c-format +msgid "could not create bytes representation of Python object" +msgstr "無法建立 Python 物件的位元組表示" + +#: plpy_typeio.c:1049 +#, c-format +msgid "could not create string representation of Python object" +msgstr "無法建立 Python 物件的字串表示" + +#: plpy_typeio.c:1060 +#, c-format +msgid "could not convert Python object into cstring: Python string representation appears to contain null bytes" +msgstr "無法將 Python 物件轉換為 cstring: Python 字串表示中似乎包含 null 位元組" + +#: plpy_typeio.c:1157 +#, c-format +msgid "return value of function with array return type is not a Python sequence" +msgstr "回傳型別是陣列的函數的回傳值並非 Python 序列" + +# access/common/tupdesc.c:679 +#: plpy_typeio.c:1202 +#, c-format +msgid "could not determine sequence length for function return value" +msgstr "無法確定函數回傳值的序列長度" + +# executor/execQual.c:2085 utils/adt/arrayfuncs.c:507 +#: plpy_typeio.c:1222 plpy_typeio.c:1237 plpy_typeio.c:1253 +#, c-format +msgid "multidimensional arrays must have array expressions with matching dimensions" +msgstr "多維陣列必須有相同維度的陣列表達式" + +# executor/execQual.c:257 executor/execQual.c:285 executor/execQual.c:2065 +# utils/adt/array_userfuncs.c:362 utils/adt/arrayfuncs.c:216 +# utils/adt/arrayfuncs.c:472 utils/adt/arrayfuncs.c:1153 +# utils/adt/arrayfuncs.c:2421 +#: plpy_typeio.c:1227 +#, c-format +msgid "number of array dimensions exceeds the maximum allowed (%d)" +msgstr "陣列維度數超過允許的最大值(%d)" + +# utils/adt/rowtypes.c:125 utils/adt/rowtypes.c:152 utils/adt/rowtypes.c:176 +# utils/adt/rowtypes.c:184 utils/adt/rowtypes.c:234 utils/adt/rowtypes.c:242 +#: plpy_typeio.c:1329 +#, c-format +msgid "malformed record literal: \"%s\"" +msgstr "格式錯誤的記錄文字: \"%s\"" + +# utils/adt/rowtypes.c:126 +#: plpy_typeio.c:1330 +#, c-format +msgid "Missing left parenthesis." +msgstr "缺少左括號。" + +#: plpy_typeio.c:1331 plpy_typeio.c:1532 +#, c-format +msgid "To return a composite type in an array, return the composite type as a Python tuple, e.g., \"[('foo',)]\"." +msgstr "若要在陣列中回傳複合型別,請將複合型別回傳為 Python 的 tuple,例如: \"[('foo',)]\"。" + +#: plpy_typeio.c:1378 +#, c-format +msgid "key \"%s\" not found in mapping" +msgstr "對照表中找不到鍵 \"%s\"" + +#: plpy_typeio.c:1379 +#, c-format +msgid "To return null in a column, add the value None to the mapping with the key named after the column." +msgstr "若要在欄位回傳 null,以欄位名稱為鍵值將 None 加入對照表。" + +#: plpy_typeio.c:1432 +#, c-format +msgid "length of returned sequence did not match number of columns in row" +msgstr "回傳的序列長度與資料列中的欄位數不一致" + +#: plpy_typeio.c:1530 +#, c-format +msgid "attribute \"%s\" does not exist in Python object" +msgstr "Python 物件中不存在屬性 \"%s\"" + +#: plpy_typeio.c:1533 +#, c-format +msgid "To return null in a column, let the returned object have an attribute named after column with value None." +msgstr "若要在欄位回傳 null,以欄位名稱為回傳物件的屬性名稱並以 None 為值。" + +#: plpy_util.c:31 +#, c-format +msgid "could not convert Python Unicode object to bytes" +msgstr "無法將 Python 的 Unicode 物件轉換為位元組" + +#: plpy_util.c:37 +#, c-format +msgid "could not extract bytes from encoded string" +msgstr "無法從編碼字串中提取位元組" + +#~ msgid "PL/Python only supports one-dimensional arrays." +#~ msgstr "PL/Python 只支援一維陣列" + +#~ msgid "Start a new session to use a different Python major version." +#~ msgstr "用不同 Python 主版號開始新 session" + +#, c-format +#~ msgid "This session has previously used Python major version %d, and it is now attempting to use Python major version %d." +#~ msgstr "session 原來用 Python 主版號 %d,現在嘗試用 Python 主版號 %d。" + +#~ msgid "cannot convert multidimensional array to Python list" +#~ msgstr "無法將多維陣列轉成 Python list" + +#~ msgid "could not create new dictionary" +#~ msgstr "無法建立新字典" + +#~ msgid "could not create new dictionary while building trigger arguments" +#~ msgstr "建立觸發程序參數時無法建立新字典" + +#~ msgid "plan.status takes no arguments" +#~ msgstr "plan.status 不接受參數" + +#~ msgid "plpy.prepare does not support composite types" +#~ msgstr "plpy.prepare 不支援複合型別" + +#~ msgid "unrecognized error in PLy_spi_execute_fetch_result" +#~ msgstr "PLy_spi_execute_fetch_result 有非預期錯誤" diff --git a/src/pl/plpython/spiexceptions.h b/src/pl/plpython/spiexceptions.h new file mode 100644 index 0000000..999e6bb --- /dev/null +++ b/src/pl/plpython/spiexceptions.h @@ -0,0 +1,998 @@ +/* autogenerated from src/backend/utils/errcodes.txt, do not edit */ +/* there is deliberately not an #ifndef SPIEXCEPTIONS_H here */ + +{ + "spiexceptions.SqlStatementNotYetComplete", "SqlStatementNotYetComplete", ERRCODE_SQL_STATEMENT_NOT_YET_COMPLETE +}, + +{ + "spiexceptions.ConnectionException", "ConnectionException", ERRCODE_CONNECTION_EXCEPTION +}, + +{ + "spiexceptions.ConnectionDoesNotExist", "ConnectionDoesNotExist", ERRCODE_CONNECTION_DOES_NOT_EXIST +}, + +{ + "spiexceptions.ConnectionFailure", "ConnectionFailure", ERRCODE_CONNECTION_FAILURE +}, + +{ + "spiexceptions.SqlclientUnableToEstablishSqlconnection", "SqlclientUnableToEstablishSqlconnection", ERRCODE_SQLCLIENT_UNABLE_TO_ESTABLISH_SQLCONNECTION +}, + +{ + "spiexceptions.SqlserverRejectedEstablishmentOfSqlconnection", "SqlserverRejectedEstablishmentOfSqlconnection", ERRCODE_SQLSERVER_REJECTED_ESTABLISHMENT_OF_SQLCONNECTION +}, + +{ + "spiexceptions.TransactionResolutionUnknown", "TransactionResolutionUnknown", ERRCODE_TRANSACTION_RESOLUTION_UNKNOWN +}, + +{ + "spiexceptions.ProtocolViolation", "ProtocolViolation", ERRCODE_PROTOCOL_VIOLATION +}, + +{ + "spiexceptions.TriggeredActionException", "TriggeredActionException", ERRCODE_TRIGGERED_ACTION_EXCEPTION +}, + +{ + "spiexceptions.FeatureNotSupported", "FeatureNotSupported", ERRCODE_FEATURE_NOT_SUPPORTED +}, + +{ + "spiexceptions.InvalidTransactionInitiation", "InvalidTransactionInitiation", ERRCODE_INVALID_TRANSACTION_INITIATION +}, + +{ + "spiexceptions.LocatorException", "LocatorException", ERRCODE_LOCATOR_EXCEPTION +}, + +{ + "spiexceptions.InvalidLocatorSpecification", "InvalidLocatorSpecification", ERRCODE_L_E_INVALID_SPECIFICATION +}, + +{ + "spiexceptions.InvalidGrantor", "InvalidGrantor", ERRCODE_INVALID_GRANTOR +}, + +{ + "spiexceptions.InvalidGrantOperation", "InvalidGrantOperation", ERRCODE_INVALID_GRANT_OPERATION +}, + +{ + "spiexceptions.InvalidRoleSpecification", "InvalidRoleSpecification", ERRCODE_INVALID_ROLE_SPECIFICATION +}, + +{ + "spiexceptions.DiagnosticsException", "DiagnosticsException", ERRCODE_DIAGNOSTICS_EXCEPTION +}, + +{ + "spiexceptions.StackedDiagnosticsAccessedWithoutActiveHandler", "StackedDiagnosticsAccessedWithoutActiveHandler", ERRCODE_STACKED_DIAGNOSTICS_ACCESSED_WITHOUT_ACTIVE_HANDLER +}, + +{ + "spiexceptions.CaseNotFound", "CaseNotFound", ERRCODE_CASE_NOT_FOUND +}, + +{ + "spiexceptions.CardinalityViolation", "CardinalityViolation", ERRCODE_CARDINALITY_VIOLATION +}, + +{ + "spiexceptions.DataException", "DataException", ERRCODE_DATA_EXCEPTION +}, + +{ + "spiexceptions.ArraySubscriptError", "ArraySubscriptError", ERRCODE_ARRAY_SUBSCRIPT_ERROR +}, + +{ + "spiexceptions.CharacterNotInRepertoire", "CharacterNotInRepertoire", ERRCODE_CHARACTER_NOT_IN_REPERTOIRE +}, + +{ + "spiexceptions.DatetimeFieldOverflow", "DatetimeFieldOverflow", ERRCODE_DATETIME_FIELD_OVERFLOW +}, + +{ + "spiexceptions.DivisionByZero", "DivisionByZero", ERRCODE_DIVISION_BY_ZERO +}, + +{ + "spiexceptions.ErrorInAssignment", "ErrorInAssignment", ERRCODE_ERROR_IN_ASSIGNMENT +}, + +{ + "spiexceptions.EscapeCharacterConflict", "EscapeCharacterConflict", ERRCODE_ESCAPE_CHARACTER_CONFLICT +}, + +{ + "spiexceptions.IndicatorOverflow", "IndicatorOverflow", ERRCODE_INDICATOR_OVERFLOW +}, + +{ + "spiexceptions.IntervalFieldOverflow", "IntervalFieldOverflow", ERRCODE_INTERVAL_FIELD_OVERFLOW +}, + +{ + "spiexceptions.InvalidArgumentForLogarithm", "InvalidArgumentForLogarithm", ERRCODE_INVALID_ARGUMENT_FOR_LOG +}, + +{ + "spiexceptions.InvalidArgumentForNtileFunction", "InvalidArgumentForNtileFunction", ERRCODE_INVALID_ARGUMENT_FOR_NTILE +}, + +{ + "spiexceptions.InvalidArgumentForNthValueFunction", "InvalidArgumentForNthValueFunction", ERRCODE_INVALID_ARGUMENT_FOR_NTH_VALUE +}, + +{ + "spiexceptions.InvalidArgumentForPowerFunction", "InvalidArgumentForPowerFunction", ERRCODE_INVALID_ARGUMENT_FOR_POWER_FUNCTION +}, + +{ + "spiexceptions.InvalidArgumentForWidthBucketFunction", "InvalidArgumentForWidthBucketFunction", ERRCODE_INVALID_ARGUMENT_FOR_WIDTH_BUCKET_FUNCTION +}, + +{ + "spiexceptions.InvalidCharacterValueForCast", "InvalidCharacterValueForCast", ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST +}, + +{ + "spiexceptions.InvalidDatetimeFormat", "InvalidDatetimeFormat", ERRCODE_INVALID_DATETIME_FORMAT +}, + +{ + "spiexceptions.InvalidEscapeCharacter", "InvalidEscapeCharacter", ERRCODE_INVALID_ESCAPE_CHARACTER +}, + +{ + "spiexceptions.InvalidEscapeOctet", "InvalidEscapeOctet", ERRCODE_INVALID_ESCAPE_OCTET +}, + +{ + "spiexceptions.InvalidEscapeSequence", "InvalidEscapeSequence", ERRCODE_INVALID_ESCAPE_SEQUENCE +}, + +{ + "spiexceptions.NonstandardUseOfEscapeCharacter", "NonstandardUseOfEscapeCharacter", ERRCODE_NONSTANDARD_USE_OF_ESCAPE_CHARACTER +}, + +{ + "spiexceptions.InvalidIndicatorParameterValue", "InvalidIndicatorParameterValue", ERRCODE_INVALID_INDICATOR_PARAMETER_VALUE +}, + +{ + "spiexceptions.InvalidParameterValue", "InvalidParameterValue", ERRCODE_INVALID_PARAMETER_VALUE +}, + +{ + "spiexceptions.InvalidPrecedingOrFollowingSize", "InvalidPrecedingOrFollowingSize", ERRCODE_INVALID_PRECEDING_OR_FOLLOWING_SIZE +}, + +{ + "spiexceptions.InvalidRegularExpression", "InvalidRegularExpression", ERRCODE_INVALID_REGULAR_EXPRESSION +}, + +{ + "spiexceptions.InvalidRowCountInLimitClause", "InvalidRowCountInLimitClause", ERRCODE_INVALID_ROW_COUNT_IN_LIMIT_CLAUSE +}, + +{ + "spiexceptions.InvalidRowCountInResultOffsetClause", "InvalidRowCountInResultOffsetClause", ERRCODE_INVALID_ROW_COUNT_IN_RESULT_OFFSET_CLAUSE +}, + +{ + "spiexceptions.InvalidTablesampleArgument", "InvalidTablesampleArgument", ERRCODE_INVALID_TABLESAMPLE_ARGUMENT +}, + +{ + "spiexceptions.InvalidTablesampleRepeat", "InvalidTablesampleRepeat", ERRCODE_INVALID_TABLESAMPLE_REPEAT +}, + +{ + "spiexceptions.InvalidTimeZoneDisplacementValue", "InvalidTimeZoneDisplacementValue", ERRCODE_INVALID_TIME_ZONE_DISPLACEMENT_VALUE +}, + +{ + "spiexceptions.InvalidUseOfEscapeCharacter", "InvalidUseOfEscapeCharacter", ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER +}, + +{ + "spiexceptions.MostSpecificTypeMismatch", "MostSpecificTypeMismatch", ERRCODE_MOST_SPECIFIC_TYPE_MISMATCH +}, + +{ + "spiexceptions.NullValueNotAllowed", "NullValueNotAllowed", ERRCODE_NULL_VALUE_NOT_ALLOWED +}, + +{ + "spiexceptions.NullValueNoIndicatorParameter", "NullValueNoIndicatorParameter", ERRCODE_NULL_VALUE_NO_INDICATOR_PARAMETER +}, + +{ + "spiexceptions.NumericValueOutOfRange", "NumericValueOutOfRange", ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE +}, + +{ + "spiexceptions.SequenceGeneratorLimitExceeded", "SequenceGeneratorLimitExceeded", ERRCODE_SEQUENCE_GENERATOR_LIMIT_EXCEEDED +}, + +{ + "spiexceptions.StringDataLengthMismatch", "StringDataLengthMismatch", ERRCODE_STRING_DATA_LENGTH_MISMATCH +}, + +{ + "spiexceptions.StringDataRightTruncation", "StringDataRightTruncation", ERRCODE_STRING_DATA_RIGHT_TRUNCATION +}, + +{ + "spiexceptions.SubstringError", "SubstringError", ERRCODE_SUBSTRING_ERROR +}, + +{ + "spiexceptions.TrimError", "TrimError", ERRCODE_TRIM_ERROR +}, + +{ + "spiexceptions.UnterminatedCString", "UnterminatedCString", ERRCODE_UNTERMINATED_C_STRING +}, + +{ + "spiexceptions.ZeroLengthCharacterString", "ZeroLengthCharacterString", ERRCODE_ZERO_LENGTH_CHARACTER_STRING +}, + +{ + "spiexceptions.FloatingPointException", "FloatingPointException", ERRCODE_FLOATING_POINT_EXCEPTION +}, + +{ + "spiexceptions.InvalidTextRepresentation", "InvalidTextRepresentation", ERRCODE_INVALID_TEXT_REPRESENTATION +}, + +{ + "spiexceptions.InvalidBinaryRepresentation", "InvalidBinaryRepresentation", ERRCODE_INVALID_BINARY_REPRESENTATION +}, + +{ + "spiexceptions.BadCopyFileFormat", "BadCopyFileFormat", ERRCODE_BAD_COPY_FILE_FORMAT +}, + +{ + "spiexceptions.UntranslatableCharacter", "UntranslatableCharacter", ERRCODE_UNTRANSLATABLE_CHARACTER +}, + +{ + "spiexceptions.NotAnXmlDocument", "NotAnXmlDocument", ERRCODE_NOT_AN_XML_DOCUMENT +}, + +{ + "spiexceptions.InvalidXmlDocument", "InvalidXmlDocument", ERRCODE_INVALID_XML_DOCUMENT +}, + +{ + "spiexceptions.InvalidXmlContent", "InvalidXmlContent", ERRCODE_INVALID_XML_CONTENT +}, + +{ + "spiexceptions.InvalidXmlComment", "InvalidXmlComment", ERRCODE_INVALID_XML_COMMENT +}, + +{ + "spiexceptions.InvalidXmlProcessingInstruction", "InvalidXmlProcessingInstruction", ERRCODE_INVALID_XML_PROCESSING_INSTRUCTION +}, + +{ + "spiexceptions.DuplicateJsonObjectKeyValue", "DuplicateJsonObjectKeyValue", ERRCODE_DUPLICATE_JSON_OBJECT_KEY_VALUE +}, + +{ + "spiexceptions.InvalidArgumentForSqlJsonDatetimeFunction", "InvalidArgumentForSqlJsonDatetimeFunction", ERRCODE_INVALID_ARGUMENT_FOR_SQL_JSON_DATETIME_FUNCTION +}, + +{ + "spiexceptions.InvalidJsonText", "InvalidJsonText", ERRCODE_INVALID_JSON_TEXT +}, + +{ + "spiexceptions.InvalidSqlJsonSubscript", "InvalidSqlJsonSubscript", ERRCODE_INVALID_SQL_JSON_SUBSCRIPT +}, + +{ + "spiexceptions.MoreThanOneSqlJsonItem", "MoreThanOneSqlJsonItem", ERRCODE_MORE_THAN_ONE_SQL_JSON_ITEM +}, + +{ + "spiexceptions.NoSqlJsonItem", "NoSqlJsonItem", ERRCODE_NO_SQL_JSON_ITEM +}, + +{ + "spiexceptions.NonNumericSqlJsonItem", "NonNumericSqlJsonItem", ERRCODE_NON_NUMERIC_SQL_JSON_ITEM +}, + +{ + "spiexceptions.NonUniqueKeysInAJsonObject", "NonUniqueKeysInAJsonObject", ERRCODE_NON_UNIQUE_KEYS_IN_A_JSON_OBJECT +}, + +{ + "spiexceptions.SingletonSqlJsonItemRequired", "SingletonSqlJsonItemRequired", ERRCODE_SINGLETON_SQL_JSON_ITEM_REQUIRED +}, + +{ + "spiexceptions.SqlJsonArrayNotFound", "SqlJsonArrayNotFound", ERRCODE_SQL_JSON_ARRAY_NOT_FOUND +}, + +{ + "spiexceptions.SqlJsonMemberNotFound", "SqlJsonMemberNotFound", ERRCODE_SQL_JSON_MEMBER_NOT_FOUND +}, + +{ + "spiexceptions.SqlJsonNumberNotFound", "SqlJsonNumberNotFound", ERRCODE_SQL_JSON_NUMBER_NOT_FOUND +}, + +{ + "spiexceptions.SqlJsonObjectNotFound", "SqlJsonObjectNotFound", ERRCODE_SQL_JSON_OBJECT_NOT_FOUND +}, + +{ + "spiexceptions.TooManyJsonArrayElements", "TooManyJsonArrayElements", ERRCODE_TOO_MANY_JSON_ARRAY_ELEMENTS +}, + +{ + "spiexceptions.TooManyJsonObjectMembers", "TooManyJsonObjectMembers", ERRCODE_TOO_MANY_JSON_OBJECT_MEMBERS +}, + +{ + "spiexceptions.SqlJsonScalarRequired", "SqlJsonScalarRequired", ERRCODE_SQL_JSON_SCALAR_REQUIRED +}, + +{ + "spiexceptions.SqlJsonItemCannotBeCastToTargetType", "SqlJsonItemCannotBeCastToTargetType", ERRCODE_SQL_JSON_ITEM_CANNOT_BE_CAST_TO_TARGET_TYPE +}, + +{ + "spiexceptions.IntegrityConstraintViolation", "IntegrityConstraintViolation", ERRCODE_INTEGRITY_CONSTRAINT_VIOLATION +}, + +{ + "spiexceptions.RestrictViolation", "RestrictViolation", ERRCODE_RESTRICT_VIOLATION +}, + +{ + "spiexceptions.NotNullViolation", "NotNullViolation", ERRCODE_NOT_NULL_VIOLATION +}, + +{ + "spiexceptions.ForeignKeyViolation", "ForeignKeyViolation", ERRCODE_FOREIGN_KEY_VIOLATION +}, + +{ + "spiexceptions.UniqueViolation", "UniqueViolation", ERRCODE_UNIQUE_VIOLATION +}, + +{ + "spiexceptions.CheckViolation", "CheckViolation", ERRCODE_CHECK_VIOLATION +}, + +{ + "spiexceptions.ExclusionViolation", "ExclusionViolation", ERRCODE_EXCLUSION_VIOLATION +}, + +{ + "spiexceptions.InvalidCursorState", "InvalidCursorState", ERRCODE_INVALID_CURSOR_STATE +}, + +{ + "spiexceptions.InvalidTransactionState", "InvalidTransactionState", ERRCODE_INVALID_TRANSACTION_STATE +}, + +{ + "spiexceptions.ActiveSqlTransaction", "ActiveSqlTransaction", ERRCODE_ACTIVE_SQL_TRANSACTION +}, + +{ + "spiexceptions.BranchTransactionAlreadyActive", "BranchTransactionAlreadyActive", ERRCODE_BRANCH_TRANSACTION_ALREADY_ACTIVE +}, + +{ + "spiexceptions.HeldCursorRequiresSameIsolationLevel", "HeldCursorRequiresSameIsolationLevel", ERRCODE_HELD_CURSOR_REQUIRES_SAME_ISOLATION_LEVEL +}, + +{ + "spiexceptions.InappropriateAccessModeForBranchTransaction", "InappropriateAccessModeForBranchTransaction", ERRCODE_INAPPROPRIATE_ACCESS_MODE_FOR_BRANCH_TRANSACTION +}, + +{ + "spiexceptions.InappropriateIsolationLevelForBranchTransaction", "InappropriateIsolationLevelForBranchTransaction", ERRCODE_INAPPROPRIATE_ISOLATION_LEVEL_FOR_BRANCH_TRANSACTION +}, + +{ + "spiexceptions.NoActiveSqlTransactionForBranchTransaction", "NoActiveSqlTransactionForBranchTransaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION_FOR_BRANCH_TRANSACTION +}, + +{ + "spiexceptions.ReadOnlySqlTransaction", "ReadOnlySqlTransaction", ERRCODE_READ_ONLY_SQL_TRANSACTION +}, + +{ + "spiexceptions.SchemaAndDataStatementMixingNotSupported", "SchemaAndDataStatementMixingNotSupported", ERRCODE_SCHEMA_AND_DATA_STATEMENT_MIXING_NOT_SUPPORTED +}, + +{ + "spiexceptions.NoActiveSqlTransaction", "NoActiveSqlTransaction", ERRCODE_NO_ACTIVE_SQL_TRANSACTION +}, + +{ + "spiexceptions.InFailedSqlTransaction", "InFailedSqlTransaction", ERRCODE_IN_FAILED_SQL_TRANSACTION +}, + +{ + "spiexceptions.IdleInTransactionSessionTimeout", "IdleInTransactionSessionTimeout", ERRCODE_IDLE_IN_TRANSACTION_SESSION_TIMEOUT +}, + +{ + "spiexceptions.InvalidSqlStatementName", "InvalidSqlStatementName", ERRCODE_INVALID_SQL_STATEMENT_NAME +}, + +{ + "spiexceptions.TriggeredDataChangeViolation", "TriggeredDataChangeViolation", ERRCODE_TRIGGERED_DATA_CHANGE_VIOLATION +}, + +{ + "spiexceptions.InvalidAuthorizationSpecification", "InvalidAuthorizationSpecification", ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION +}, + +{ + "spiexceptions.InvalidPassword", "InvalidPassword", ERRCODE_INVALID_PASSWORD +}, + +{ + "spiexceptions.DependentPrivilegeDescriptorsStillExist", "DependentPrivilegeDescriptorsStillExist", ERRCODE_DEPENDENT_PRIVILEGE_DESCRIPTORS_STILL_EXIST +}, + +{ + "spiexceptions.DependentObjectsStillExist", "DependentObjectsStillExist", ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST +}, + +{ + "spiexceptions.InvalidTransactionTermination", "InvalidTransactionTermination", ERRCODE_INVALID_TRANSACTION_TERMINATION +}, + +{ + "spiexceptions.SqlRoutineException", "SqlRoutineException", ERRCODE_SQL_ROUTINE_EXCEPTION +}, + +{ + "spiexceptions.FunctionExecutedNoReturnStatement", "FunctionExecutedNoReturnStatement", ERRCODE_S_R_E_FUNCTION_EXECUTED_NO_RETURN_STATEMENT +}, + +{ + "spiexceptions.ModifyingSqlDataNotPermitted", "ModifyingSqlDataNotPermitted", ERRCODE_S_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED +}, + +{ + "spiexceptions.ProhibitedSqlStatementAttempted", "ProhibitedSqlStatementAttempted", ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED +}, + +{ + "spiexceptions.ReadingSqlDataNotPermitted", "ReadingSqlDataNotPermitted", ERRCODE_S_R_E_READING_SQL_DATA_NOT_PERMITTED +}, + +{ + "spiexceptions.InvalidCursorName", "InvalidCursorName", ERRCODE_INVALID_CURSOR_NAME +}, + +{ + "spiexceptions.ExternalRoutineException", "ExternalRoutineException", ERRCODE_EXTERNAL_ROUTINE_EXCEPTION +}, + +{ + "spiexceptions.ContainingSqlNotPermitted", "ContainingSqlNotPermitted", ERRCODE_E_R_E_CONTAINING_SQL_NOT_PERMITTED +}, + +{ + "spiexceptions.ModifyingSqlDataNotPermitted", "ModifyingSqlDataNotPermitted", ERRCODE_E_R_E_MODIFYING_SQL_DATA_NOT_PERMITTED +}, + +{ + "spiexceptions.ProhibitedSqlStatementAttempted", "ProhibitedSqlStatementAttempted", ERRCODE_E_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED +}, + +{ + "spiexceptions.ReadingSqlDataNotPermitted", "ReadingSqlDataNotPermitted", ERRCODE_E_R_E_READING_SQL_DATA_NOT_PERMITTED +}, + +{ + "spiexceptions.ExternalRoutineInvocationException", "ExternalRoutineInvocationException", ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION +}, + +{ + "spiexceptions.InvalidSqlstateReturned", "InvalidSqlstateReturned", ERRCODE_E_R_I_E_INVALID_SQLSTATE_RETURNED +}, + +{ + "spiexceptions.NullValueNotAllowed", "NullValueNotAllowed", ERRCODE_E_R_I_E_NULL_VALUE_NOT_ALLOWED +}, + +{ + "spiexceptions.TriggerProtocolViolated", "TriggerProtocolViolated", ERRCODE_E_R_I_E_TRIGGER_PROTOCOL_VIOLATED +}, + +{ + "spiexceptions.SrfProtocolViolated", "SrfProtocolViolated", ERRCODE_E_R_I_E_SRF_PROTOCOL_VIOLATED +}, + +{ + "spiexceptions.EventTriggerProtocolViolated", "EventTriggerProtocolViolated", ERRCODE_E_R_I_E_EVENT_TRIGGER_PROTOCOL_VIOLATED +}, + +{ + "spiexceptions.SavepointException", "SavepointException", ERRCODE_SAVEPOINT_EXCEPTION +}, + +{ + "spiexceptions.InvalidSavepointSpecification", "InvalidSavepointSpecification", ERRCODE_S_E_INVALID_SPECIFICATION +}, + +{ + "spiexceptions.InvalidCatalogName", "InvalidCatalogName", ERRCODE_INVALID_CATALOG_NAME +}, + +{ + "spiexceptions.InvalidSchemaName", "InvalidSchemaName", ERRCODE_INVALID_SCHEMA_NAME +}, + +{ + "spiexceptions.TransactionRollback", "TransactionRollback", ERRCODE_TRANSACTION_ROLLBACK +}, + +{ + "spiexceptions.TransactionIntegrityConstraintViolation", "TransactionIntegrityConstraintViolation", ERRCODE_T_R_INTEGRITY_CONSTRAINT_VIOLATION +}, + +{ + "spiexceptions.SerializationFailure", "SerializationFailure", ERRCODE_T_R_SERIALIZATION_FAILURE +}, + +{ + "spiexceptions.StatementCompletionUnknown", "StatementCompletionUnknown", ERRCODE_T_R_STATEMENT_COMPLETION_UNKNOWN +}, + +{ + "spiexceptions.DeadlockDetected", "DeadlockDetected", ERRCODE_T_R_DEADLOCK_DETECTED +}, + +{ + "spiexceptions.SyntaxErrorOrAccessRuleViolation", "SyntaxErrorOrAccessRuleViolation", ERRCODE_SYNTAX_ERROR_OR_ACCESS_RULE_VIOLATION +}, + +{ + "spiexceptions.SyntaxError", "SyntaxError", ERRCODE_SYNTAX_ERROR +}, + +{ + "spiexceptions.InsufficientPrivilege", "InsufficientPrivilege", ERRCODE_INSUFFICIENT_PRIVILEGE +}, + +{ + "spiexceptions.CannotCoerce", "CannotCoerce", ERRCODE_CANNOT_COERCE +}, + +{ + "spiexceptions.GroupingError", "GroupingError", ERRCODE_GROUPING_ERROR +}, + +{ + "spiexceptions.WindowingError", "WindowingError", ERRCODE_WINDOWING_ERROR +}, + +{ + "spiexceptions.InvalidRecursion", "InvalidRecursion", ERRCODE_INVALID_RECURSION +}, + +{ + "spiexceptions.InvalidForeignKey", "InvalidForeignKey", ERRCODE_INVALID_FOREIGN_KEY +}, + +{ + "spiexceptions.InvalidName", "InvalidName", ERRCODE_INVALID_NAME +}, + +{ + "spiexceptions.NameTooLong", "NameTooLong", ERRCODE_NAME_TOO_LONG +}, + +{ + "spiexceptions.ReservedName", "ReservedName", ERRCODE_RESERVED_NAME +}, + +{ + "spiexceptions.DatatypeMismatch", "DatatypeMismatch", ERRCODE_DATATYPE_MISMATCH +}, + +{ + "spiexceptions.IndeterminateDatatype", "IndeterminateDatatype", ERRCODE_INDETERMINATE_DATATYPE +}, + +{ + "spiexceptions.CollationMismatch", "CollationMismatch", ERRCODE_COLLATION_MISMATCH +}, + +{ + "spiexceptions.IndeterminateCollation", "IndeterminateCollation", ERRCODE_INDETERMINATE_COLLATION +}, + +{ + "spiexceptions.WrongObjectType", "WrongObjectType", ERRCODE_WRONG_OBJECT_TYPE +}, + +{ + "spiexceptions.GeneratedAlways", "GeneratedAlways", ERRCODE_GENERATED_ALWAYS +}, + +{ + "spiexceptions.UndefinedColumn", "UndefinedColumn", ERRCODE_UNDEFINED_COLUMN +}, + +{ + "spiexceptions.UndefinedFunction", "UndefinedFunction", ERRCODE_UNDEFINED_FUNCTION +}, + +{ + "spiexceptions.UndefinedTable", "UndefinedTable", ERRCODE_UNDEFINED_TABLE +}, + +{ + "spiexceptions.UndefinedParameter", "UndefinedParameter", ERRCODE_UNDEFINED_PARAMETER +}, + +{ + "spiexceptions.UndefinedObject", "UndefinedObject", ERRCODE_UNDEFINED_OBJECT +}, + +{ + "spiexceptions.DuplicateColumn", "DuplicateColumn", ERRCODE_DUPLICATE_COLUMN +}, + +{ + "spiexceptions.DuplicateCursor", "DuplicateCursor", ERRCODE_DUPLICATE_CURSOR +}, + +{ + "spiexceptions.DuplicateDatabase", "DuplicateDatabase", ERRCODE_DUPLICATE_DATABASE +}, + +{ + "spiexceptions.DuplicateFunction", "DuplicateFunction", ERRCODE_DUPLICATE_FUNCTION +}, + +{ + "spiexceptions.DuplicatePreparedStatement", "DuplicatePreparedStatement", ERRCODE_DUPLICATE_PSTATEMENT +}, + +{ + "spiexceptions.DuplicateSchema", "DuplicateSchema", ERRCODE_DUPLICATE_SCHEMA +}, + +{ + "spiexceptions.DuplicateTable", "DuplicateTable", ERRCODE_DUPLICATE_TABLE +}, + +{ + "spiexceptions.DuplicateAlias", "DuplicateAlias", ERRCODE_DUPLICATE_ALIAS +}, + +{ + "spiexceptions.DuplicateObject", "DuplicateObject", ERRCODE_DUPLICATE_OBJECT +}, + +{ + "spiexceptions.AmbiguousColumn", "AmbiguousColumn", ERRCODE_AMBIGUOUS_COLUMN +}, + +{ + "spiexceptions.AmbiguousFunction", "AmbiguousFunction", ERRCODE_AMBIGUOUS_FUNCTION +}, + +{ + "spiexceptions.AmbiguousParameter", "AmbiguousParameter", ERRCODE_AMBIGUOUS_PARAMETER +}, + +{ + "spiexceptions.AmbiguousAlias", "AmbiguousAlias", ERRCODE_AMBIGUOUS_ALIAS +}, + +{ + "spiexceptions.InvalidColumnReference", "InvalidColumnReference", ERRCODE_INVALID_COLUMN_REFERENCE +}, + +{ + "spiexceptions.InvalidColumnDefinition", "InvalidColumnDefinition", ERRCODE_INVALID_COLUMN_DEFINITION +}, + +{ + "spiexceptions.InvalidCursorDefinition", "InvalidCursorDefinition", ERRCODE_INVALID_CURSOR_DEFINITION +}, + +{ + "spiexceptions.InvalidDatabaseDefinition", "InvalidDatabaseDefinition", ERRCODE_INVALID_DATABASE_DEFINITION +}, + +{ + "spiexceptions.InvalidFunctionDefinition", "InvalidFunctionDefinition", ERRCODE_INVALID_FUNCTION_DEFINITION +}, + +{ + "spiexceptions.InvalidPreparedStatementDefinition", "InvalidPreparedStatementDefinition", ERRCODE_INVALID_PSTATEMENT_DEFINITION +}, + +{ + "spiexceptions.InvalidSchemaDefinition", "InvalidSchemaDefinition", ERRCODE_INVALID_SCHEMA_DEFINITION +}, + +{ + "spiexceptions.InvalidTableDefinition", "InvalidTableDefinition", ERRCODE_INVALID_TABLE_DEFINITION +}, + +{ + "spiexceptions.InvalidObjectDefinition", "InvalidObjectDefinition", ERRCODE_INVALID_OBJECT_DEFINITION +}, + +{ + "spiexceptions.WithCheckOptionViolation", "WithCheckOptionViolation", ERRCODE_WITH_CHECK_OPTION_VIOLATION +}, + +{ + "spiexceptions.InsufficientResources", "InsufficientResources", ERRCODE_INSUFFICIENT_RESOURCES +}, + +{ + "spiexceptions.DiskFull", "DiskFull", ERRCODE_DISK_FULL +}, + +{ + "spiexceptions.OutOfMemory", "OutOfMemory", ERRCODE_OUT_OF_MEMORY +}, + +{ + "spiexceptions.TooManyConnections", "TooManyConnections", ERRCODE_TOO_MANY_CONNECTIONS +}, + +{ + "spiexceptions.ConfigurationLimitExceeded", "ConfigurationLimitExceeded", ERRCODE_CONFIGURATION_LIMIT_EXCEEDED +}, + +{ + "spiexceptions.ProgramLimitExceeded", "ProgramLimitExceeded", ERRCODE_PROGRAM_LIMIT_EXCEEDED +}, + +{ + "spiexceptions.StatementTooComplex", "StatementTooComplex", ERRCODE_STATEMENT_TOO_COMPLEX +}, + +{ + "spiexceptions.TooManyColumns", "TooManyColumns", ERRCODE_TOO_MANY_COLUMNS +}, + +{ + "spiexceptions.TooManyArguments", "TooManyArguments", ERRCODE_TOO_MANY_ARGUMENTS +}, + +{ + "spiexceptions.ObjectNotInPrerequisiteState", "ObjectNotInPrerequisiteState", ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE +}, + +{ + "spiexceptions.ObjectInUse", "ObjectInUse", ERRCODE_OBJECT_IN_USE +}, + +{ + "spiexceptions.CantChangeRuntimeParam", "CantChangeRuntimeParam", ERRCODE_CANT_CHANGE_RUNTIME_PARAM +}, + +{ + "spiexceptions.LockNotAvailable", "LockNotAvailable", ERRCODE_LOCK_NOT_AVAILABLE +}, + +{ + "spiexceptions.UnsafeNewEnumValueUsage", "UnsafeNewEnumValueUsage", ERRCODE_UNSAFE_NEW_ENUM_VALUE_USAGE +}, + +{ + "spiexceptions.OperatorIntervention", "OperatorIntervention", ERRCODE_OPERATOR_INTERVENTION +}, + +{ + "spiexceptions.QueryCanceled", "QueryCanceled", ERRCODE_QUERY_CANCELED +}, + +{ + "spiexceptions.AdminShutdown", "AdminShutdown", ERRCODE_ADMIN_SHUTDOWN +}, + +{ + "spiexceptions.CrashShutdown", "CrashShutdown", ERRCODE_CRASH_SHUTDOWN +}, + +{ + "spiexceptions.CannotConnectNow", "CannotConnectNow", ERRCODE_CANNOT_CONNECT_NOW +}, + +{ + "spiexceptions.DatabaseDropped", "DatabaseDropped", ERRCODE_DATABASE_DROPPED +}, + +{ + "spiexceptions.IdleSessionTimeout", "IdleSessionTimeout", ERRCODE_IDLE_SESSION_TIMEOUT +}, + +{ + "spiexceptions.SystemError", "SystemError", ERRCODE_SYSTEM_ERROR +}, + +{ + "spiexceptions.IoError", "IoError", ERRCODE_IO_ERROR +}, + +{ + "spiexceptions.UndefinedFile", "UndefinedFile", ERRCODE_UNDEFINED_FILE +}, + +{ + "spiexceptions.DuplicateFile", "DuplicateFile", ERRCODE_DUPLICATE_FILE +}, + +{ + "spiexceptions.SnapshotTooOld", "SnapshotTooOld", ERRCODE_SNAPSHOT_TOO_OLD +}, + +{ + "spiexceptions.ConfigFileError", "ConfigFileError", ERRCODE_CONFIG_FILE_ERROR +}, + +{ + "spiexceptions.LockFileExists", "LockFileExists", ERRCODE_LOCK_FILE_EXISTS +}, + +{ + "spiexceptions.FdwError", "FdwError", ERRCODE_FDW_ERROR +}, + +{ + "spiexceptions.FdwColumnNameNotFound", "FdwColumnNameNotFound", ERRCODE_FDW_COLUMN_NAME_NOT_FOUND +}, + +{ + "spiexceptions.FdwDynamicParameterValueNeeded", "FdwDynamicParameterValueNeeded", ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED +}, + +{ + "spiexceptions.FdwFunctionSequenceError", "FdwFunctionSequenceError", ERRCODE_FDW_FUNCTION_SEQUENCE_ERROR +}, + +{ + "spiexceptions.FdwInconsistentDescriptorInformation", "FdwInconsistentDescriptorInformation", ERRCODE_FDW_INCONSISTENT_DESCRIPTOR_INFORMATION +}, + +{ + "spiexceptions.FdwInvalidAttributeValue", "FdwInvalidAttributeValue", ERRCODE_FDW_INVALID_ATTRIBUTE_VALUE +}, + +{ + "spiexceptions.FdwInvalidColumnName", "FdwInvalidColumnName", ERRCODE_FDW_INVALID_COLUMN_NAME +}, + +{ + "spiexceptions.FdwInvalidColumnNumber", "FdwInvalidColumnNumber", ERRCODE_FDW_INVALID_COLUMN_NUMBER +}, + +{ + "spiexceptions.FdwInvalidDataType", "FdwInvalidDataType", ERRCODE_FDW_INVALID_DATA_TYPE +}, + +{ + "spiexceptions.FdwInvalidDataTypeDescriptors", "FdwInvalidDataTypeDescriptors", ERRCODE_FDW_INVALID_DATA_TYPE_DESCRIPTORS +}, + +{ + "spiexceptions.FdwInvalidDescriptorFieldIdentifier", "FdwInvalidDescriptorFieldIdentifier", ERRCODE_FDW_INVALID_DESCRIPTOR_FIELD_IDENTIFIER +}, + +{ + "spiexceptions.FdwInvalidHandle", "FdwInvalidHandle", ERRCODE_FDW_INVALID_HANDLE +}, + +{ + "spiexceptions.FdwInvalidOptionIndex", "FdwInvalidOptionIndex", ERRCODE_FDW_INVALID_OPTION_INDEX +}, + +{ + "spiexceptions.FdwInvalidOptionName", "FdwInvalidOptionName", ERRCODE_FDW_INVALID_OPTION_NAME +}, + +{ + "spiexceptions.FdwInvalidStringLengthOrBufferLength", "FdwInvalidStringLengthOrBufferLength", ERRCODE_FDW_INVALID_STRING_LENGTH_OR_BUFFER_LENGTH +}, + +{ + "spiexceptions.FdwInvalidStringFormat", "FdwInvalidStringFormat", ERRCODE_FDW_INVALID_STRING_FORMAT +}, + +{ + "spiexceptions.FdwInvalidUseOfNullPointer", "FdwInvalidUseOfNullPointer", ERRCODE_FDW_INVALID_USE_OF_NULL_POINTER +}, + +{ + "spiexceptions.FdwTooManyHandles", "FdwTooManyHandles", ERRCODE_FDW_TOO_MANY_HANDLES +}, + +{ + "spiexceptions.FdwOutOfMemory", "FdwOutOfMemory", ERRCODE_FDW_OUT_OF_MEMORY +}, + +{ + "spiexceptions.FdwNoSchemas", "FdwNoSchemas", ERRCODE_FDW_NO_SCHEMAS +}, + +{ + "spiexceptions.FdwOptionNameNotFound", "FdwOptionNameNotFound", ERRCODE_FDW_OPTION_NAME_NOT_FOUND +}, + +{ + "spiexceptions.FdwReplyHandle", "FdwReplyHandle", ERRCODE_FDW_REPLY_HANDLE +}, + +{ + "spiexceptions.FdwSchemaNotFound", "FdwSchemaNotFound", ERRCODE_FDW_SCHEMA_NOT_FOUND +}, + +{ + "spiexceptions.FdwTableNotFound", "FdwTableNotFound", ERRCODE_FDW_TABLE_NOT_FOUND +}, + +{ + "spiexceptions.FdwUnableToCreateExecution", "FdwUnableToCreateExecution", ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION +}, + +{ + "spiexceptions.FdwUnableToCreateReply", "FdwUnableToCreateReply", ERRCODE_FDW_UNABLE_TO_CREATE_REPLY +}, + +{ + "spiexceptions.FdwUnableToEstablishConnection", "FdwUnableToEstablishConnection", ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION +}, + +{ + "spiexceptions.PlpgsqlError", "PlpgsqlError", ERRCODE_PLPGSQL_ERROR +}, + +{ + "spiexceptions.RaiseException", "RaiseException", ERRCODE_RAISE_EXCEPTION +}, + +{ + "spiexceptions.NoDataFound", "NoDataFound", ERRCODE_NO_DATA_FOUND +}, + +{ + "spiexceptions.TooManyRows", "TooManyRows", ERRCODE_TOO_MANY_ROWS +}, + +{ + "spiexceptions.AssertFailure", "AssertFailure", ERRCODE_ASSERT_FAILURE +}, + +{ + "spiexceptions.InternalError", "InternalError", ERRCODE_INTERNAL_ERROR +}, + +{ + "spiexceptions.DataCorrupted", "DataCorrupted", ERRCODE_DATA_CORRUPTED +}, + +{ + "spiexceptions.IndexCorrupted", "IndexCorrupted", ERRCODE_INDEX_CORRUPTED +}, diff --git a/src/pl/plpython/sql/plpython_call.sql b/src/pl/plpython/sql/plpython_call.sql new file mode 100644 index 0000000..daa4bc3 --- /dev/null +++ b/src/pl/plpython/sql/plpython_call.sql @@ -0,0 +1,80 @@ +-- +-- Tests for procedures / CALL syntax +-- + +CREATE PROCEDURE test_proc1() +LANGUAGE plpython3u +AS $$ +pass +$$; + +CALL test_proc1(); + + +-- error: can't return non-None +CREATE PROCEDURE test_proc2() +LANGUAGE plpython3u +AS $$ +return 5 +$$; + +CALL test_proc2(); + + +CREATE TABLE test1 (a int); + +CREATE PROCEDURE test_proc3(x int) +LANGUAGE plpython3u +AS $$ +plpy.execute("INSERT INTO test1 VALUES (%s)" % x) +$$; + +CALL test_proc3(55); + +SELECT * FROM test1; + + +-- output arguments + +CREATE PROCEDURE test_proc5(INOUT a text) +LANGUAGE plpython3u +AS $$ +return [a + '+' + a] +$$; + +CALL test_proc5('abc'); + + +CREATE PROCEDURE test_proc6(a int, INOUT b int, INOUT c int) +LANGUAGE plpython3u +AS $$ +return (b * a, c * a) +$$; + +CALL test_proc6(2, 3, 4); + + +-- OUT parameters + +CREATE PROCEDURE test_proc9(IN a int, OUT b int) +LANGUAGE plpython3u +AS $$ +plpy.notice("a: %s" % (a)) +return (a * 2,) +$$; + +DO $$ +DECLARE _a int; _b int; +BEGIN + _a := 10; _b := 30; + CALL test_proc9(_a, _b); + RAISE NOTICE '_a: %, _b: %', _a, _b; +END +$$; + + +DROP PROCEDURE test_proc1; +DROP PROCEDURE test_proc2; +DROP PROCEDURE test_proc3; + +DROP TABLE test1; diff --git a/src/pl/plpython/sql/plpython_composite.sql b/src/pl/plpython/sql/plpython_composite.sql new file mode 100644 index 0000000..2175770 --- /dev/null +++ b/src/pl/plpython/sql/plpython_composite.sql @@ -0,0 +1,224 @@ +CREATE FUNCTION multiout_simple(OUT i integer, OUT j integer) AS $$ +return (1, 2) +$$ LANGUAGE plpython3u; + +SELECT multiout_simple(); +SELECT * FROM multiout_simple(); +SELECT i, j + 2 FROM multiout_simple(); +SELECT (multiout_simple()).j + 3; + +CREATE FUNCTION multiout_simple_setof(n integer = 1, OUT integer, OUT integer) RETURNS SETOF record AS $$ +return [(1, 2)] * n +$$ LANGUAGE plpython3u; + +SELECT multiout_simple_setof(); +SELECT * FROM multiout_simple_setof(); +SELECT * FROM multiout_simple_setof(3); + +CREATE FUNCTION multiout_record_as(typ text, + first text, OUT first text, + second integer, OUT second integer, + retnull boolean) RETURNS record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_record_as('dict', 'foo', 1, 'f'); +SELECT multiout_record_as('dict', 'foo', 1, 'f'); + +SELECT * FROM multiout_record_as('dict', null, null, false); +SELECT * FROM multiout_record_as('dict', 'one', null, false); +SELECT * FROM multiout_record_as('dict', null, 2, false); +SELECT * FROM multiout_record_as('dict', 'three', 3, false); +SELECT * FROM multiout_record_as('dict', null, null, true); + +SELECT * FROM multiout_record_as('tuple', null, null, false); +SELECT * FROM multiout_record_as('tuple', 'one', null, false); +SELECT * FROM multiout_record_as('tuple', null, 2, false); +SELECT * FROM multiout_record_as('tuple', 'three', 3, false); +SELECT * FROM multiout_record_as('tuple', null, null, true); + +SELECT * FROM multiout_record_as('list', null, null, false); +SELECT * FROM multiout_record_as('list', 'one', null, false); +SELECT * FROM multiout_record_as('list', null, 2, false); +SELECT * FROM multiout_record_as('list', 'three', 3, false); +SELECT * FROM multiout_record_as('list', null, null, true); + +SELECT * FROM multiout_record_as('obj', null, null, false); +SELECT * FROM multiout_record_as('obj', 'one', null, false); +SELECT * FROM multiout_record_as('obj', null, 2, false); +SELECT * FROM multiout_record_as('obj', 'three', 3, false); +SELECT * FROM multiout_record_as('obj', null, null, true); + +SELECT * FROM multiout_record_as('str', 'one', 1, false); +SELECT * FROM multiout_record_as('str', 'one', 2, false); + +SELECT *, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', NULL, 'f') AS f(f, s); +SELECT *, f IS NULL AS fnull, s IS NULL AS snull FROM multiout_record_as('tuple', 'xxx', 1, 't') AS f(f, s); +SELECT * FROM multiout_record_as('obj', NULL, 10, 'f'); + +CREATE FUNCTION multiout_setof(n integer, + OUT power_of_2 integer, + OUT length integer) RETURNS SETOF record AS $$ +for i in range(n): + power = 2 ** i + length = plpy.execute("select length('%d')" % power)[0]['length'] + yield power, length +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_setof(3); +SELECT multiout_setof(5); + +CREATE FUNCTION multiout_return_table() RETURNS TABLE (x integer, y text) AS $$ +return [{'x': 4, 'y' :'four'}, + {'x': 7, 'y' :'seven'}, + {'x': 0, 'y' :'zero'}] +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_return_table(); + +CREATE FUNCTION multiout_array(OUT integer[], OUT text) RETURNS SETOF record AS $$ +yield [[1], 'a'] +yield [[1,2], 'b'] +yield [[1,2,3], None] +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_array(); + +CREATE FUNCTION singleout_composite(OUT type_record) AS $$ +return {'first': 1, 'second': 2} +$$ LANGUAGE plpython3u; + +CREATE FUNCTION multiout_composite(OUT type_record) RETURNS SETOF type_record AS $$ +return [{'first': 1, 'second': 2}, + {'first': 3, 'second': 4 }] +$$ LANGUAGE plpython3u; + +SELECT * FROM singleout_composite(); +SELECT * FROM multiout_composite(); + +-- composite OUT parameters in functions returning RECORD not supported yet +CREATE FUNCTION multiout_composite(INOUT n integer, OUT type_record) AS $$ +return (n, (n * 2, n * 3)) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION multiout_table_type_setof(typ text, returnnull boolean, INOUT n integer, OUT table_record) RETURNS SETOF record AS $$ +if returnnull: + d = None +elif typ == 'dict': + d = {'first': n * 2, 'second': n * 3, 'extra': 'not important'} +elif typ == 'tuple': + d = (n * 2, n * 3) +elif typ == 'list': + d = [ n * 2, n * 3 ] +elif typ == 'obj': + class d: pass + d.first = n * 2 + d.second = n * 3 +elif typ == 'str': + d = "(%r,%r)" % (n * 2, n * 3) +for i in range(n): + yield (i, d) +$$ LANGUAGE plpython3u; + +SELECT * FROM multiout_composite(2); +SELECT * FROM multiout_table_type_setof('dict', 'f', 3); +SELECT * FROM multiout_table_type_setof('dict', 'f', 7); +SELECT * FROM multiout_table_type_setof('tuple', 'f', 2); +SELECT * FROM multiout_table_type_setof('tuple', 'f', 3); +SELECT * FROM multiout_table_type_setof('list', 'f', 2); +SELECT * FROM multiout_table_type_setof('list', 'f', 3); +SELECT * FROM multiout_table_type_setof('obj', 'f', 4); +SELECT * FROM multiout_table_type_setof('obj', 'f', 5); +SELECT * FROM multiout_table_type_setof('str', 'f', 6); +SELECT * FROM multiout_table_type_setof('str', 'f', 7); +SELECT * FROM multiout_table_type_setof('dict', 't', 3); + +-- check what happens if a type changes under us + +CREATE TABLE changing ( + i integer, + j integer +); + +CREATE FUNCTION changing_test(OUT n integer, OUT changing) RETURNS SETOF record AS $$ +return [(1, {'i': 1, 'j': 2}), + (1, (3, 4))] +$$ LANGUAGE plpython3u; + +SELECT * FROM changing_test(); +ALTER TABLE changing DROP COLUMN j; +SELECT * FROM changing_test(); +SELECT * FROM changing_test(); +ALTER TABLE changing ADD COLUMN j integer; +SELECT * FROM changing_test(); + +-- tables of composite types + +CREATE FUNCTION composite_types_table(OUT tab table_record[], OUT typ type_record[] ) RETURNS SETOF record AS $$ +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +yield {'tab': [('first', 1), ('second', 2)], + 'typ': [{'first': 'third', 'second': 3}, + {'first': 'fourth', 'second': 4}]} +$$ LANGUAGE plpython3u; + +SELECT * FROM composite_types_table(); + +-- check what happens if the output record descriptor changes +CREATE FUNCTION return_record(t text) RETURNS record AS $$ +return {'t': t, 'val': 10} +$$ LANGUAGE plpython3u; + +SELECT * FROM return_record('abc') AS r(t text, val integer); +SELECT * FROM return_record('abc') AS r(t text, val bigint); +SELECT * FROM return_record('abc') AS r(t text, val integer); +SELECT * FROM return_record('abc') AS r(t varchar(30), val integer); +SELECT * FROM return_record('abc') AS r(t varchar(100), val integer); +SELECT * FROM return_record('999') AS r(val text, t integer); + +CREATE FUNCTION return_record_2(t text) RETURNS record AS $$ +return {'v1':1,'v2':2,t:3} +$$ LANGUAGE plpython3u; + +SELECT * FROM return_record_2('v3') AS (v3 int, v2 int, v1 int); +SELECT * FROM return_record_2('v3') AS (v2 int, v3 int, v1 int); +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); +SELECT * FROM return_record_2('v4') AS (v1 int, v4 int, v2 int); +-- error +SELECT * FROM return_record_2('v4') AS (v1 int, v3 int, v2 int); +-- works +SELECT * FROM return_record_2('v3') AS (v1 int, v3 int, v2 int); +SELECT * FROM return_record_2('v3') AS (v1 int, v2 int, v3 int); + +-- multi-dimensional array of composite types. +CREATE FUNCTION composite_type_as_list() RETURNS type_record[] AS $$ + return [[('first', 1), ('second', 1)], [('first', 2), ('second', 2)], [('first', 3), ('second', 3)]]; +$$ LANGUAGE plpython3u; +SELECT * FROM composite_type_as_list(); + +-- Starting with PostgreSQL 10, a composite type in an array cannot be +-- represented as a Python list, because it's ambiguous with multi-dimensional +-- arrays. So this throws an error now. The error should contain a useful hint +-- on the issue. +CREATE FUNCTION composite_type_as_list_broken() RETURNS type_record[] AS $$ + return [['first', 1]]; +$$ LANGUAGE plpython3u; +SELECT * FROM composite_type_as_list_broken(); diff --git a/src/pl/plpython/sql/plpython_do.sql b/src/pl/plpython/sql/plpython_do.sql new file mode 100644 index 0000000..d494132 --- /dev/null +++ b/src/pl/plpython/sql/plpython_do.sql @@ -0,0 +1,3 @@ +DO $$ plpy.notice("This is plpython3u.") $$ LANGUAGE plpython3u; + +DO $$ raise Exception("error test") $$ LANGUAGE plpython3u; diff --git a/src/pl/plpython/sql/plpython_drop.sql b/src/pl/plpython/sql/plpython_drop.sql new file mode 100644 index 0000000..e4f373b --- /dev/null +++ b/src/pl/plpython/sql/plpython_drop.sql @@ -0,0 +1,6 @@ +-- +-- For paranoia's sake, don't leave an untrusted language sitting around +-- +SET client_min_messages = WARNING; + +DROP EXTENSION plpython3u CASCADE; diff --git a/src/pl/plpython/sql/plpython_ereport.sql b/src/pl/plpython/sql/plpython_ereport.sql new file mode 100644 index 0000000..13d6ce7 --- /dev/null +++ b/src/pl/plpython/sql/plpython_ereport.sql @@ -0,0 +1,135 @@ +CREATE FUNCTION elog_test() RETURNS void +AS $$ +plpy.debug('debug', detail='some detail') +plpy.log('log', detail='some detail') +plpy.info('info', detail='some detail') +plpy.info() +plpy.info('the question', detail=42); +plpy.info('This is message text.', + detail='This is detail text', + hint='This is hint text.', + sqlstate='XX000', + schema_name='any info about schema', + table_name='any info about table', + column_name='any info about column', + datatype_name='any info about datatype', + constraint_name='any info about constraint') +plpy.notice('notice', detail='some detail') +plpy.warning('warning', detail='some detail') +plpy.error('stop on error', detail='some detail', hint='some hint') +$$ LANGUAGE plpython3u; + +SELECT elog_test(); + +DO $$ plpy.info('other types', detail=(10, 20)) $$ LANGUAGE plpython3u; + +DO $$ +import time; +from datetime import date +plpy.info('other types', detail=date(2016, 2, 26)) +$$ LANGUAGE plpython3u; + +DO $$ +basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana'] +plpy.info('other types', detail=basket) +$$ LANGUAGE plpython3u; + +-- should fail +DO $$ plpy.info('wrong sqlstate', sqlstate='54444A') $$ LANGUAGE plpython3u; +DO $$ plpy.info('unsupported argument', blabla='fooboo') $$ LANGUAGE plpython3u; +DO $$ plpy.info('first message', message='second message') $$ LANGUAGE plpython3u; +DO $$ plpy.info('first message', 'second message', message='third message') $$ LANGUAGE plpython3u; + +-- raise exception in python, handle exception in plpgsql +CREATE OR REPLACE FUNCTION raise_exception(_message text, _detail text DEFAULT NULL, _hint text DEFAULT NULL, + _sqlstate text DEFAULT NULL, + _schema_name text DEFAULT NULL, + _table_name text DEFAULT NULL, + _column_name text DEFAULT NULL, + _datatype_name text DEFAULT NULL, + _constraint_name text DEFAULT NULL) +RETURNS void AS $$ +kwargs = { + "message": _message, "detail": _detail, "hint": _hint, + "sqlstate": _sqlstate, "schema_name": _schema_name, "table_name": _table_name, + "column_name": _column_name, "datatype_name": _datatype_name, + "constraint_name": _constraint_name +} +# ignore None values +plpy.error(**dict((k, v) for k, v in iter(kwargs.items()) if v)) +$$ LANGUAGE plpython3u; + +SELECT raise_exception('hello', 'world'); +SELECT raise_exception('message text', 'detail text', _sqlstate => 'YY333'); +SELECT raise_exception(_message => 'message text', + _detail => 'detail text', + _hint => 'hint text', + _sqlstate => 'XX555', + _schema_name => 'schema text', + _table_name => 'table text', + _column_name => 'column text', + _datatype_name => 'datatype text', + _constraint_name => 'constraint text'); + +SELECT raise_exception(_message => 'message text', + _hint => 'hint text', + _schema_name => 'schema text', + _column_name => 'column text', + _constraint_name => 'constraint text'); + +DO $$ +DECLARE + __message text; + __detail text; + __hint text; + __sqlstate text; + __schema_name text; + __table_name text; + __column_name text; + __datatype_name text; + __constraint_name text; +BEGIN + BEGIN + PERFORM raise_exception(_message => 'message text', + _detail => 'detail text', + _hint => 'hint text', + _sqlstate => 'XX555', + _schema_name => 'schema text', + _table_name => 'table text', + _column_name => 'column text', + _datatype_name => 'datatype text', + _constraint_name => 'constraint text'); + EXCEPTION WHEN SQLSTATE 'XX555' THEN + GET STACKED DIAGNOSTICS __message = MESSAGE_TEXT, + __detail = PG_EXCEPTION_DETAIL, + __hint = PG_EXCEPTION_HINT, + __sqlstate = RETURNED_SQLSTATE, + __schema_name = SCHEMA_NAME, + __table_name = TABLE_NAME, + __column_name = COLUMN_NAME, + __datatype_name = PG_DATATYPE_NAME, + __constraint_name = CONSTRAINT_NAME; + RAISE NOTICE 'handled exception' + USING DETAIL = format('message:(%s), detail:(%s), hint: (%s), sqlstate: (%s), ' + 'schema_name:(%s), table_name:(%s), column_name:(%s), datatype_name:(%s), constraint_name:(%s)', + __message, __detail, __hint, __sqlstate, __schema_name, + __table_name, __column_name, __datatype_name, __constraint_name); + END; +END; +$$; + +DO $$ +try: + plpy.execute("select raise_exception(_message => 'my message', _sqlstate => 'XX987', _hint => 'some hint', _table_name => 'users_tab', _datatype_name => 'user_type')") +except Exception as e: + plpy.info(e.spidata) + raise e +$$ LANGUAGE plpython3u; + +DO $$ +try: + plpy.error(message = 'my message', sqlstate = 'XX987', hint = 'some hint', table_name = 'users_tab', datatype_name = 'user_type') +except Exception as e: + plpy.info('sqlstate: %s, hint: %s, table_name: %s, datatype_name: %s' % (e.sqlstate, e.hint, e.table_name, e.datatype_name)) + raise e +$$ LANGUAGE plpython3u; diff --git a/src/pl/plpython/sql/plpython_error.sql b/src/pl/plpython/sql/plpython_error.sql new file mode 100644 index 0000000..9bb1c0b --- /dev/null +++ b/src/pl/plpython/sql/plpython_error.sql @@ -0,0 +1,357 @@ +-- test error handling, i forgot to restore Warn_restart in +-- the trigger handler once. the errors and subsequent core dump were +-- interesting. + +/* Flat out Python syntax error + */ +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; + +/* With check_function_bodies = false the function should get defined + * and the error reported when called + */ +SET check_function_bodies = false; + +CREATE FUNCTION python_syntax_error() RETURNS text + AS +'.syntaxerror' + LANGUAGE plpython3u; + +SELECT python_syntax_error(); +/* Run the function twice to check if the hashtable entry gets cleaned up */ +SELECT python_syntax_error(); + +RESET check_function_bodies; + +/* Flat out syntax error + */ +CREATE FUNCTION sql_syntax_error() RETURNS text + AS +'plpy.execute("syntax error")' + LANGUAGE plpython3u; + +SELECT sql_syntax_error(); + + +/* check the handling of uncaught python exceptions + */ +CREATE FUNCTION exception_index_invalid(text) RETURNS text + AS +'return args[1]' + LANGUAGE plpython3u; + +SELECT exception_index_invalid('test'); + + +/* check handling of nested exceptions + */ +CREATE FUNCTION exception_index_invalid_nested() RETURNS text + AS +'rv = plpy.execute("SELECT test5(''foo'')") +return rv[0]' + LANGUAGE plpython3u; + +SELECT exception_index_invalid_nested(); + + +/* a typo + */ +CREATE FUNCTION invalid_type_uncaught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + SD["plan"] = plpy.prepare(q, [ "test" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_uncaught('rick'); + + +/* for what it's worth catch the exception generated by + * the typo, and return None + */ +CREATE FUNCTION invalid_type_caught(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.notice(str(ex)) + return None +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_caught('rick'); + + +/* for what it's worth catch the exception generated by + * the typo, and reraise it as a plain error + */ +CREATE FUNCTION invalid_type_reraised(a text) RETURNS text + AS +'if "plan" not in SD: + q = "SELECT fname FROM users WHERE lname = $1" + try: + SD["plan"] = plpy.prepare(q, [ "test" ]) + except plpy.SPIError as ex: + plpy.error(str(ex)) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT invalid_type_reraised('rick'); + + +/* no typo no messing about + */ +CREATE FUNCTION valid_type(a text) RETURNS text + AS +'if "plan" not in SD: + SD["plan"] = plpy.prepare("SELECT fname FROM users WHERE lname = $1", [ "text" ]) +rv = plpy.execute(SD["plan"], [ a ]) +if len(rv): + return rv[0]["fname"] +return None +' + LANGUAGE plpython3u; + +SELECT valid_type('rick'); + +/* error in nested functions to get a traceback +*/ +CREATE FUNCTION nested_error() RETURNS text + AS +'def fun1(): + plpy.error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; + +SELECT nested_error(); + +/* raising plpy.Error is just like calling plpy.error +*/ +CREATE FUNCTION nested_error_raise() RETURNS text + AS +'def fun1(): + raise plpy.Error("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "not reached" +' + LANGUAGE plpython3u; + +SELECT nested_error_raise(); + +/* using plpy.warning should not produce a traceback +*/ +CREATE FUNCTION nested_warning() RETURNS text + AS +'def fun1(): + plpy.warning("boom") + +def fun2(): + fun1() + +def fun3(): + fun2() + +fun3() +return "you''ve been warned" +' + LANGUAGE plpython3u; + +SELECT nested_warning(); + +/* AttributeError at toplevel used to give segfaults with the traceback +*/ +CREATE FUNCTION toplevel_attribute_error() RETURNS void AS +$$ +plpy.nonexistent +$$ LANGUAGE plpython3u; + +SELECT toplevel_attribute_error(); + +/* Calling PL/Python functions from SQL and vice versa should not lose context. + */ +CREATE OR REPLACE FUNCTION python_traceback() RETURNS void AS $$ +def first(): + second() + +def second(): + third() + +def third(): + plpy.execute("select sql_error()") + +first() +$$ LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION sql_error() RETURNS void AS $$ +begin + select 1/0; +end +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION python_from_sql_error() RETURNS void AS $$ +begin + select python_traceback(); +end +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION sql_from_python_error() RETURNS void AS $$ +plpy.execute("select sql_error()") +$$ LANGUAGE plpython3u; + +SELECT python_traceback(); +SELECT sql_error(); +SELECT python_from_sql_error(); +SELECT sql_from_python_error(); + +/* check catching specific types of exceptions + */ +CREATE TABLE specific ( + i integer PRIMARY KEY +); + +CREATE FUNCTION specific_exception(i integer) RETURNS void AS +$$ +from plpy import spiexceptions +try: + plpy.execute("insert into specific values (%s)" % (i or "NULL")); +except spiexceptions.NotNullViolation as e: + plpy.notice("Violated the NOT NULL constraint, sqlstate %s" % e.sqlstate) +except spiexceptions.UniqueViolation as e: + plpy.notice("Violated the UNIQUE constraint, sqlstate %s" % e.sqlstate) +$$ LANGUAGE plpython3u; + +SELECT specific_exception(2); +SELECT specific_exception(NULL); +SELECT specific_exception(2); + +/* SPI errors in PL/Python functions should preserve the SQLSTATE value + */ +CREATE FUNCTION python_unique_violation() RETURNS void AS $$ +plpy.execute("insert into specific values (1)") +plpy.execute("insert into specific values (1)") +$$ LANGUAGE plpython3u; + +CREATE FUNCTION catch_python_unique_violation() RETURNS text AS $$ +begin + begin + perform python_unique_violation(); + exception when unique_violation then + return 'ok'; + end; + return 'not reached'; +end; +$$ language plpgsql; + +SELECT catch_python_unique_violation(); + +/* manually starting subtransactions - a bad idea + */ +CREATE FUNCTION manual_subxact() RETURNS void AS $$ +plpy.execute("savepoint save") +plpy.execute("create table foo(x integer)") +plpy.execute("rollback to save") +$$ LANGUAGE plpython3u; + +SELECT manual_subxact(); + +/* same for prepared plans + */ +CREATE FUNCTION manual_subxact_prepared() RETURNS void AS $$ +save = plpy.prepare("savepoint save") +rollback = plpy.prepare("rollback to save") +plpy.execute(save) +plpy.execute("create table foo(x integer)") +plpy.execute(rollback) +$$ LANGUAGE plpython3u; + +SELECT manual_subxact_prepared(); + +/* raising plpy.spiexception.* from python code should preserve sqlstate + */ +CREATE FUNCTION plpy_raise_spiexception() RETURNS void AS $$ +raise plpy.spiexceptions.DivisionByZero() +$$ LANGUAGE plpython3u; + +DO $$ +BEGIN + SELECT plpy_raise_spiexception(); +EXCEPTION WHEN division_by_zero THEN + -- NOOP +END +$$ LANGUAGE plpgsql; + +/* setting a custom sqlstate should be handled + */ +CREATE FUNCTION plpy_raise_spiexception_override() RETURNS void AS $$ +exc = plpy.spiexceptions.DivisionByZero() +exc.sqlstate = 'SILLY' +raise exc +$$ LANGUAGE plpython3u; + +DO $$ +BEGIN + SELECT plpy_raise_spiexception_override(); +EXCEPTION WHEN SQLSTATE 'SILLY' THEN + -- NOOP +END +$$ LANGUAGE plpgsql; + +/* test the context stack trace for nested execution levels + */ +CREATE FUNCTION notice_innerfunc() RETURNS int AS $$ +plpy.execute("DO LANGUAGE plpython3u $x$ plpy.notice('inside DO') $x$") +return 1 +$$ LANGUAGE plpython3u; + +CREATE FUNCTION notice_outerfunc() RETURNS int AS $$ +plpy.execute("SELECT notice_innerfunc()") +return 1 +$$ LANGUAGE plpython3u; + +\set SHOW_CONTEXT always + +SELECT notice_outerfunc(); + +/* test error logged with an underlying exception that includes a detail + * string (bug #18070). + */ +CREATE FUNCTION python_error_detail() RETURNS SETOF text AS $$ + plan = plpy.prepare("SELECT to_date('xy', 'DD') d") + for row in plpy.cursor(plan): + yield row['d'] +$$ LANGUAGE plpython3u; + +SELECT python_error_detail(); diff --git a/src/pl/plpython/sql/plpython_global.sql b/src/pl/plpython/sql/plpython_global.sql new file mode 100644 index 0000000..96d2049 --- /dev/null +++ b/src/pl/plpython/sql/plpython_global.sql @@ -0,0 +1,38 @@ +-- +-- check static and global data (SD and GD) +-- + +CREATE FUNCTION global_test_one() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_one" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_one" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; + +CREATE FUNCTION global_test_two() returns text + AS +'if "global_test" not in SD: + SD["global_test"] = "set by global_test_two" +if "global_test" not in GD: + GD["global_test"] = "set by global_test_two" +return "SD: " + SD["global_test"] + ", GD: " + GD["global_test"]' + LANGUAGE plpython3u; + + +CREATE FUNCTION static_test() returns int4 + AS +'if "call" in SD: + SD["call"] = SD["call"] + 1 +else: + SD["call"] = 1 +return SD["call"] +' + LANGUAGE plpython3u; + + +SELECT static_test(); +SELECT static_test(); +SELECT global_test_one(); +SELECT global_test_two(); diff --git a/src/pl/plpython/sql/plpython_import.sql b/src/pl/plpython/sql/plpython_import.sql new file mode 100644 index 0000000..3031eef --- /dev/null +++ b/src/pl/plpython/sql/plpython_import.sql @@ -0,0 +1,68 @@ +-- import python modules + +CREATE FUNCTION import_fail() returns text + AS +'try: + import foosocket +except ImportError: + return "failed as expected" +return "succeeded, that wasn''t supposed to happen"' + LANGUAGE plpython3u; + + +CREATE FUNCTION import_succeed() returns text + AS +'try: + import array + import bisect + import calendar + import cmath + import errno + import math + import operator + import random + import re + import string + import time +except Exception as ex: + plpy.notice("import failed -- %s" % str(ex)) + return "failed, that wasn''t supposed to happen" +return "succeeded, as expected"' + LANGUAGE plpython3u; + +CREATE FUNCTION import_test_one(p text) RETURNS text + AS +'try: + import hashlib + digest = hashlib.sha1(p.encode("ascii")) +except ImportError: + import sha + digest = sha.new(p) +return digest.hexdigest()' + LANGUAGE plpython3u; + +CREATE FUNCTION import_test_two(u users) RETURNS text + AS +'plain = u["fname"] + u["lname"] +try: + import hashlib + digest = hashlib.sha1(plain.encode("ascii")) +except ImportError: + import sha + digest = sha.new(plain); +return "sha hash of " + plain + " is " + digest.hexdigest()' + LANGUAGE plpython3u; + + +-- import python modules +-- +SELECT import_fail(); +SELECT import_succeed(); + +-- test import and simple argument handling +-- +SELECT import_test_one('sha hash of this string'); + +-- test import and tuple argument handling +-- +select import_test_two(users) from users where fname = 'willem'; diff --git a/src/pl/plpython/sql/plpython_newline.sql b/src/pl/plpython/sql/plpython_newline.sql new file mode 100644 index 0000000..cb22ba9 --- /dev/null +++ b/src/pl/plpython/sql/plpython_newline.sql @@ -0,0 +1,20 @@ +-- +-- Universal Newline Support +-- + +CREATE OR REPLACE FUNCTION newline_lf() RETURNS integer AS +E'x = 100\ny = 23\nreturn x + y\n' +LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION newline_cr() RETURNS integer AS +E'x = 100\ry = 23\rreturn x + y\r' +LANGUAGE plpython3u; + +CREATE OR REPLACE FUNCTION newline_crlf() RETURNS integer AS +E'x = 100\r\ny = 23\r\nreturn x + y\r\n' +LANGUAGE plpython3u; + + +SELECT newline_lf(); +SELECT newline_cr(); +SELECT newline_crlf(); diff --git a/src/pl/plpython/sql/plpython_params.sql b/src/pl/plpython/sql/plpython_params.sql new file mode 100644 index 0000000..8bab488 --- /dev/null +++ b/src/pl/plpython/sql/plpython_params.sql @@ -0,0 +1,42 @@ +-- +-- Test named and nameless parameters +-- + +CREATE FUNCTION test_param_names0(integer, integer) RETURNS int AS $$ +return args[0] + args[1] +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_param_names1(a0 integer, a1 text) RETURNS boolean AS $$ +assert a0 == args[0] +assert a1 == args[1] +return True +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_param_names2(u users) RETURNS text AS $$ +assert u == args[0] +if isinstance(u, dict): + # stringify dict the hard way because otherwise the order is implementation-dependent + u_keys = list(u.keys()) + u_keys.sort() + s = '{' + ', '.join([repr(k) + ': ' + repr(u[k]) for k in u_keys]) + '}' +else: + s = str(u) +return s +$$ LANGUAGE plpython3u; + +-- use deliberately wrong parameter names +CREATE FUNCTION test_param_names3(a0 integer) RETURNS boolean AS $$ +try: + assert a1 == args[0] + return False +except NameError as e: + assert e.args[0].find("a1") > -1 + return True +$$ LANGUAGE plpython3u; + + +SELECT test_param_names0(2,7); +SELECT test_param_names1(1,'text'); +SELECT test_param_names2(users) from users; +SELECT test_param_names2(NULL); +SELECT test_param_names3(1); diff --git a/src/pl/plpython/sql/plpython_populate.sql b/src/pl/plpython/sql/plpython_populate.sql new file mode 100644 index 0000000..cc1e19b --- /dev/null +++ b/src/pl/plpython/sql/plpython_populate.sql @@ -0,0 +1,27 @@ +INSERT INTO users (fname, lname, username) VALUES ('jane', 'doe', 'j_doe'); +INSERT INTO users (fname, lname, username) VALUES ('john', 'doe', 'johnd'); +INSERT INTO users (fname, lname, username) VALUES ('willem', 'doe', 'w_doe'); +INSERT INTO users (fname, lname, username) VALUES ('rick', 'smith', 'slash'); + + +-- multi table tests +-- + +INSERT INTO taxonomy (name) VALUES ('HIV I') ; +INSERT INTO taxonomy (name) VALUES ('HIV II') ; +INSERT INTO taxonomy (name) VALUES ('HCV') ; + +INSERT INTO entry (accession, txid) VALUES ('A00001', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00002', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00003', '1') ; +INSERT INTO entry (accession, txid) VALUES ('A00004', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00005', '2') ; +INSERT INTO entry (accession, txid) VALUES ('A00006', '3') ; + +INSERT INTO sequences (sequence, eid, product, multipart) VALUES ('ABCDEF', 1, 'env', 'true') ; +INSERT INTO xsequences (sequence, pid) VALUES ('GHIJKL', 1) ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 2, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 3, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 4, 'gag') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 5, 'env') ; +INSERT INTO sequences (sequence, eid, product) VALUES ('ABCDEF', 6, 'ns1') ; diff --git a/src/pl/plpython/sql/plpython_quote.sql b/src/pl/plpython/sql/plpython_quote.sql new file mode 100644 index 0000000..a1133e7 --- /dev/null +++ b/src/pl/plpython/sql/plpython_quote.sql @@ -0,0 +1,33 @@ +-- test quoting functions + +CREATE FUNCTION quote(t text, how text) RETURNS text AS $$ + if how == "literal": + return plpy.quote_literal(t) + elif how == "nullable": + return plpy.quote_nullable(t) + elif how == "ident": + return plpy.quote_ident(t) + else: + raise plpy.Error("unrecognized quote type %s" % how) +$$ LANGUAGE plpython3u; + +SELECT quote(t, 'literal') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + ('xyzv')) AS v(t); + +SELECT quote(t, 'nullable') FROM (VALUES + ('abc'), + ('a''bc'), + ('''abc'''), + (''), + (''''), + (NULL)) AS v(t); + +SELECT quote(t, 'ident') FROM (VALUES + ('abc'), + ('a b c'), + ('a " ''abc''')) AS v(t); diff --git a/src/pl/plpython/sql/plpython_record.sql b/src/pl/plpython/sql/plpython_record.sql new file mode 100644 index 0000000..52bad8b --- /dev/null +++ b/src/pl/plpython/sql/plpython_record.sql @@ -0,0 +1,163 @@ +-- +-- Test returning tuples +-- + +CREATE TABLE table_record ( + first text, + second int4 + ) ; + +CREATE TYPE type_record AS ( + first text, + second int4 + ) ; + + +CREATE FUNCTION test_table_record_as(typ text, first text, second integer, retnull boolean) RETURNS table_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_type_record_as(typ text, first text, second integer, retnull boolean) RETURNS type_record AS $$ +if retnull: + return None +if typ == 'dict': + return { 'first': first, 'second': second, 'additionalfield': 'must not cause trouble' } +elif typ == 'tuple': + return ( first, second ) +elif typ == 'list': + return [ first, second ] +elif typ == 'obj': + class type_record: pass + type_record.first = first + type_record.second = second + return type_record +elif typ == 'str': + return "('%s',%r)" % (first, second) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_in_out_params(first in text, second out text) AS $$ +return first + '_in_to_out'; +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_in_out_params_multi(first in text, + second out text, third out text) AS $$ +return (first + '_record_in_to_out_1', first + '_record_in_to_out_2'); +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_inout_params(first inout text) AS $$ +return first + '_inout'; +$$ LANGUAGE plpython3u; + + +-- Test tuple returning functions +SELECT * FROM test_table_record_as('dict', null, null, false); +SELECT * FROM test_table_record_as('dict', 'one', null, false); +SELECT * FROM test_table_record_as('dict', null, 2, false); +SELECT * FROM test_table_record_as('dict', 'three', 3, false); +SELECT * FROM test_table_record_as('dict', null, null, true); + +SELECT * FROM test_table_record_as('tuple', null, null, false); +SELECT * FROM test_table_record_as('tuple', 'one', null, false); +SELECT * FROM test_table_record_as('tuple', null, 2, false); +SELECT * FROM test_table_record_as('tuple', 'three', 3, false); +SELECT * FROM test_table_record_as('tuple', null, null, true); + +SELECT * FROM test_table_record_as('list', null, null, false); +SELECT * FROM test_table_record_as('list', 'one', null, false); +SELECT * FROM test_table_record_as('list', null, 2, false); +SELECT * FROM test_table_record_as('list', 'three', 3, false); +SELECT * FROM test_table_record_as('list', null, null, true); + +SELECT * FROM test_table_record_as('obj', null, null, false); +SELECT * FROM test_table_record_as('obj', 'one', null, false); +SELECT * FROM test_table_record_as('obj', null, 2, false); +SELECT * FROM test_table_record_as('obj', 'three', 3, false); +SELECT * FROM test_table_record_as('obj', null, null, true); + +SELECT * FROM test_type_record_as('dict', null, null, false); +SELECT * FROM test_type_record_as('dict', 'one', null, false); +SELECT * FROM test_type_record_as('dict', null, 2, false); +SELECT * FROM test_type_record_as('dict', 'three', 3, false); +SELECT * FROM test_type_record_as('dict', null, null, true); + +SELECT * FROM test_type_record_as('tuple', null, null, false); +SELECT * FROM test_type_record_as('tuple', 'one', null, false); +SELECT * FROM test_type_record_as('tuple', null, 2, false); +SELECT * FROM test_type_record_as('tuple', 'three', 3, false); +SELECT * FROM test_type_record_as('tuple', null, null, true); + +SELECT * FROM test_type_record_as('list', null, null, false); +SELECT * FROM test_type_record_as('list', 'one', null, false); +SELECT * FROM test_type_record_as('list', null, 2, false); +SELECT * FROM test_type_record_as('list', 'three', 3, false); +SELECT * FROM test_type_record_as('list', null, null, true); + +SELECT * FROM test_type_record_as('obj', null, null, false); +SELECT * FROM test_type_record_as('obj', 'one', null, false); +SELECT * FROM test_type_record_as('obj', null, 2, false); +SELECT * FROM test_type_record_as('obj', 'three', 3, false); +SELECT * FROM test_type_record_as('obj', null, null, true); + +SELECT * FROM test_type_record_as('str', 'one', 1, false); + +SELECT * FROM test_in_out_params('test_in'); +SELECT * FROM test_in_out_params_multi('test_in'); +SELECT * FROM test_inout_params('test_in'); + +-- try changing the return types and call functions again + +ALTER TABLE table_record DROP COLUMN first; +ALTER TABLE table_record DROP COLUMN second; +ALTER TABLE table_record ADD COLUMN first text; +ALTER TABLE table_record ADD COLUMN second int4; + +SELECT * FROM test_table_record_as('obj', 'one', 1, false); + +ALTER TYPE type_record DROP ATTRIBUTE first; +ALTER TYPE type_record DROP ATTRIBUTE second; +ALTER TYPE type_record ADD ATTRIBUTE first text; +ALTER TYPE type_record ADD ATTRIBUTE second int4; + +SELECT * FROM test_type_record_as('obj', 'one', 1, false); + +-- errors cases + +CREATE FUNCTION test_type_record_error1() RETURNS type_record AS $$ + return { 'first': 'first' } +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error1(); + + +CREATE FUNCTION test_type_record_error2() RETURNS type_record AS $$ + return [ 'first' ] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error2(); + + +CREATE FUNCTION test_type_record_error3() RETURNS type_record AS $$ + class type_record: pass + type_record.first = 'first' + return type_record +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error3(); + +CREATE FUNCTION test_type_record_error4() RETURNS type_record AS $$ + return 'foo' +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_record_error4(); diff --git a/src/pl/plpython/sql/plpython_schema.sql b/src/pl/plpython/sql/plpython_schema.sql new file mode 100644 index 0000000..a5bdbda --- /dev/null +++ b/src/pl/plpython/sql/plpython_schema.sql @@ -0,0 +1,39 @@ +CREATE TABLE users ( + fname text not null, + lname text not null, + username text, + userid serial, + PRIMARY KEY(lname, fname) + ) ; + +CREATE INDEX users_username_idx ON users(username); +CREATE INDEX users_fname_idx ON users(fname); +CREATE INDEX users_lname_idx ON users(lname); +CREATE INDEX users_userid_idx ON users(userid); + + +CREATE TABLE taxonomy ( + id serial primary key, + name text unique + ) ; + +CREATE TABLE entry ( + accession text not null primary key, + eid serial unique, + txid int2 not null references taxonomy(id) + ) ; + +CREATE TABLE sequences ( + eid int4 not null references entry(eid), + pid serial primary key, + product text not null, + sequence text not null, + multipart bool default 'false' + ) ; +CREATE INDEX sequences_product_idx ON sequences(product) ; + +CREATE TABLE xsequences ( + pid int4 not null references sequences(pid), + sequence text not null + ) ; +CREATE INDEX xsequences_pid_idx ON xsequences(pid) ; diff --git a/src/pl/plpython/sql/plpython_setof.sql b/src/pl/plpython/sql/plpython_setof.sql new file mode 100644 index 0000000..4cfb101 --- /dev/null +++ b/src/pl/plpython/sql/plpython_setof.sql @@ -0,0 +1,97 @@ +-- +-- Test returning SETOF +-- + +CREATE FUNCTION test_setof_error() RETURNS SETOF text AS $$ +return 37 +$$ LANGUAGE plpython3u; + +SELECT test_setof_error(); + + +CREATE FUNCTION test_setof_as_list(count integer, content text) RETURNS SETOF text AS $$ +return [ content ]*count +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_as_tuple(count integer, content text) RETURNS SETOF text AS $$ +t = () +for i in range(count): + t += ( content, ) +return t +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_as_iterator(count integer, content text) RETURNS SETOF text AS $$ +class producer: + def __init__ (self, icount, icontent): + self.icontent = icontent + self.icount = icount + def __iter__ (self): + return self + def __next__ (self): + if self.icount == 0: + raise StopIteration + self.icount -= 1 + return self.icontent +return producer(count, content) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_setof_spi_in_iterator() RETURNS SETOF text AS +$$ + for s in ('Hello', 'Brave', 'New', 'World'): + plpy.execute('select 1') + yield s + plpy.execute('select 2') +$$ +LANGUAGE plpython3u; + + +-- Test set returning functions +SELECT test_setof_as_list(0, 'list'); +SELECT test_setof_as_list(1, 'list'); +SELECT test_setof_as_list(2, 'list'); +SELECT test_setof_as_list(2, null); + +SELECT test_setof_as_tuple(0, 'tuple'); +SELECT test_setof_as_tuple(1, 'tuple'); +SELECT test_setof_as_tuple(2, 'tuple'); +SELECT test_setof_as_tuple(2, null); + +SELECT test_setof_as_iterator(0, 'list'); +SELECT test_setof_as_iterator(1, 'list'); +SELECT test_setof_as_iterator(2, 'list'); +SELECT test_setof_as_iterator(2, null); + +SELECT test_setof_spi_in_iterator(); + +-- set-returning function that modifies its parameters +CREATE OR REPLACE FUNCTION ugly(x int, lim int) RETURNS SETOF int AS $$ +global x +while x <= lim: + yield x + x = x + 1 +$$ LANGUAGE plpython3u; + +SELECT ugly(1, 5); + +-- interleaved execution of such a function +SELECT ugly(1,3), ugly(7,8); + +-- returns set of named-composite-type tuples +CREATE OR REPLACE FUNCTION get_user_records() +RETURNS SETOF users +AS $$ + return plpy.execute("SELECT * FROM users ORDER BY username") +$$ LANGUAGE plpython3u; + +SELECT get_user_records(); +SELECT * FROM get_user_records(); + +-- same, but returning set of RECORD +CREATE OR REPLACE FUNCTION get_user_records2() +RETURNS TABLE(fname text, lname text, username text, userid int) +AS $$ + return plpy.execute("SELECT * FROM users ORDER BY username") +$$ LANGUAGE plpython3u; + +SELECT get_user_records2(); +SELECT * FROM get_user_records2(); diff --git a/src/pl/plpython/sql/plpython_spi.sql b/src/pl/plpython/sql/plpython_spi.sql new file mode 100644 index 0000000..fcd113a --- /dev/null +++ b/src/pl/plpython/sql/plpython_spi.sql @@ -0,0 +1,322 @@ +-- +-- nested calls +-- + +CREATE FUNCTION nested_call_one(a text) RETURNS text + AS +'q = "SELECT nested_call_two(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; + +CREATE FUNCTION nested_call_two(a text) RETURNS text + AS +'q = "SELECT nested_call_three(''%s'')" % a +r = plpy.execute(q) +return r[0]' + LANGUAGE plpython3u ; + +CREATE FUNCTION nested_call_three(a text) RETURNS text + AS +'return a' + LANGUAGE plpython3u ; + +-- some spi stuff + +CREATE FUNCTION spi_prepared_plan_test_one(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = plpy.execute(SD["myplan"], [a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION spi_prepared_plan_test_two(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT count(*) FROM users WHERE lname = $1" + SD["myplan"] = plpy.prepare(q, [ "text" ]) +try: + rv = SD["myplan"].execute([a]) + return "there are " + str(rv[0]["count"]) + " " + str(a) + "s" +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION spi_prepared_plan_test_nested(a text) RETURNS text + AS +'if "myplan" not in SD: + q = "SELECT spi_prepared_plan_test_one(''%s'') as count" % a + SD["myplan"] = plpy.prepare(q) +try: + rv = plpy.execute(SD["myplan"]) + if len(rv): + return rv[0]["count"] +except Exception as ex: + plpy.error(str(ex)) +return None +' + LANGUAGE plpython3u; + +CREATE FUNCTION join_sequences(s sequences) RETURNS text + AS +'if not s["multipart"]: + return s["sequence"] +q = "SELECT sequence FROM xsequences WHERE pid = ''%s''" % s["pid"] +rv = plpy.execute(q) +seq = s["sequence"] +for r in rv: + seq = seq + r["sequence"] +return seq +' + LANGUAGE plpython3u; + +CREATE FUNCTION spi_recursive_sum(a int) RETURNS int + AS +'r = 0 +if a > 1: + r = plpy.execute("SELECT spi_recursive_sum(%d) as a" % (a-1))[0]["a"] +return a + r +' + LANGUAGE plpython3u; + +-- +-- spi and nested calls +-- +select nested_call_one('pass this along'); +select spi_prepared_plan_test_one('doe'); +select spi_prepared_plan_test_two('smith'); +select spi_prepared_plan_test_nested('smith'); + +SELECT join_sequences(sequences) FROM sequences; +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^A'; +SELECT join_sequences(sequences) FROM sequences + WHERE join_sequences(sequences) ~* '^B'; + +SELECT spi_recursive_sum(10); + +-- +-- plan and result objects +-- + +CREATE FUNCTION result_metadata_test(cmd text) RETURNS int +AS $$ +plan = plpy.prepare(cmd) +plpy.info(plan.status()) # not really documented or useful +result = plpy.execute(plan) +if result.status() > 0: + plpy.info(result.colnames()) + plpy.info(result.coltypes()) + plpy.info(result.coltypmods()) + return result.nrows() +else: + return None +$$ LANGUAGE plpython3u; + +SELECT result_metadata_test($$SELECT 1 AS foo, '11'::text AS bar UNION SELECT 2, '22'$$); +SELECT result_metadata_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + +CREATE FUNCTION result_nrows_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return result.nrows() +$$ LANGUAGE plpython3u; + +SELECT result_nrows_test($$SELECT 1$$); +SELECT result_nrows_test($$CREATE TEMPORARY TABLE foo2 (a int, b text)$$); +SELECT result_nrows_test($$INSERT INTO foo2 VALUES (1, 'one'), (2, 'two')$$); +SELECT result_nrows_test($$UPDATE foo2 SET b = '' WHERE a = 2$$); + +CREATE FUNCTION result_len_test(cmd text) RETURNS int +AS $$ +result = plpy.execute(cmd) +return len(result) +$$ LANGUAGE plpython3u; + +SELECT result_len_test($$SELECT 1$$); +SELECT result_len_test($$CREATE TEMPORARY TABLE foo3 (a int, b text)$$); +SELECT result_len_test($$INSERT INTO foo3 VALUES (1, 'one'), (2, 'two')$$); +SELECT result_len_test($$UPDATE foo3 SET b= '' WHERE a = 2$$); + +CREATE FUNCTION result_subscript_test() RETURNS void +AS $$ +result = plpy.execute("SELECT 1 AS c UNION ALL SELECT 2 " + "UNION ALL SELECT 3 UNION ALL SELECT 4") + +plpy.info(result[1]['c']) +plpy.info(result[-1]['c']) + +plpy.info([item['c'] for item in result[1:3]]) +plpy.info([item['c'] for item in result[::2]]) + +result[-1] = {'c': 1000} +result[:2] = [{'c': 10}, {'c': 100}] +plpy.info([item['c'] for item in result[:]]) + +# raises TypeError, catch so further tests could be added +try: + plpy.info(result['foo']) +except TypeError: + pass +else: + assert False, "TypeError not raised" + +$$ LANGUAGE plpython3u; + +SELECT result_subscript_test(); + +CREATE FUNCTION result_empty_test() RETURNS void +AS $$ +result = plpy.execute("select 1 where false") + +plpy.info(result[:]) + +$$ LANGUAGE plpython3u; + +SELECT result_empty_test(); + +CREATE FUNCTION result_str_test(cmd text) RETURNS text +AS $$ +plan = plpy.prepare(cmd) +result = plpy.execute(plan) +return str(result) +$$ LANGUAGE plpython3u; + +SELECT result_str_test($$SELECT 1 AS foo UNION SELECT 2$$); +SELECT result_str_test($$CREATE TEMPORARY TABLE foo1 (a int, b text)$$); + +-- cursor objects + +CREATE FUNCTION simple_cursor_test() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +does = 0 +for row in res: + if row['lname'] == 'doe': + does += 1 +return does +$$ LANGUAGE plpython3u; + +CREATE FUNCTION double_cursor_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +res.close() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +assert len(res.fetch(3)) == 3 +assert len(res.fetch(3)) == 1 +assert len(res.fetch(3)) == 0 +assert len(res.fetch(3)) == 0 +try: + # use next() or __next__(), the method name changed in + # http://www.python.org/dev/peps/pep-3114/ + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_mix_next_and_fetch() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users order by fname") +assert len(res.fetch(2)) == 2 + +item = None +try: + item = res.next() +except AttributeError: + item = res.__next__() +assert item['fname'] == 'rick' + +assert len(res.fetch(2)) == 1 +$$ LANGUAGE plpython3u; + +CREATE FUNCTION fetch_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + res.fetch(1) +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION next_after_close() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users") +res.close() +try: + try: + res.next() + except AttributeError: + res.__next__() +except ValueError: + pass +else: + assert False, "ValueError not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_fetch_next_empty() RETURNS int AS $$ +res = plpy.cursor("select fname, lname from users where false") +assert len(res.fetch(1)) == 0 +try: + try: + res.next() + except AttributeError: + res.__next__() +except StopIteration: + pass +else: + assert False, "StopIteration not raised" +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan() RETURNS SETOF text AS $$ +plan = plpy.prepare( + "select fname, lname from users where fname like $1 || '%' order by fname", + ["text"]) +for row in plpy.cursor(plan, ["w"]): + yield row['fname'] +for row in plan.cursor(["j"]): + yield row['fname'] +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan_wrong_args() RETURNS SETOF text AS $$ +plan = plpy.prepare("select fname, lname from users where fname like $1 || '%'", + ["text"]) +c = plpy.cursor(plan, ["a", "b"]) +$$ LANGUAGE plpython3u; + +CREATE TYPE test_composite_type AS ( + a1 int, + a2 varchar +); + +CREATE OR REPLACE FUNCTION plan_composite_args() RETURNS test_composite_type AS $$ +plan = plpy.prepare("select $1 as c1", ["test_composite_type"]) +res = plpy.execute(plan, [{"a1": 3, "a2": "label"}]) +return res[0]["c1"] +$$ LANGUAGE plpython3u; + +SELECT simple_cursor_test(); +SELECT double_cursor_close(); +SELECT cursor_fetch(); +SELECT cursor_mix_next_and_fetch(); +SELECT fetch_after_close(); +SELECT next_after_close(); +SELECT cursor_fetch_next_empty(); +SELECT cursor_plan(); +SELECT cursor_plan_wrong_args(); +SELECT plan_composite_args(); diff --git a/src/pl/plpython/sql/plpython_subtransaction.sql b/src/pl/plpython/sql/plpython_subtransaction.sql new file mode 100644 index 0000000..c65c380 --- /dev/null +++ b/src/pl/plpython/sql/plpython_subtransaction.sql @@ -0,0 +1,262 @@ +-- +-- Test explicit subtransactions +-- + +-- Test table to see if transactions get properly rolled back + +CREATE TABLE subtransaction_tbl ( + i integer +); + +CREATE FUNCTION subtransaction_ctx_test(what_error text = NULL) RETURNS text +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + if what_error == "SPI": + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('oops')") + elif what_error == "Python": + raise Exception("Python exception") +$$ LANGUAGE plpython3u; + +SELECT subtransaction_ctx_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('SPI'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; +SELECT subtransaction_ctx_test('Python'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Nested subtransactions + +CREATE FUNCTION subtransaction_nested_test(swallow boolean = 'f') RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (3)") + plpy.execute("error") + except plpy.SPIError as e: + if not swallow: + raise + plpy.notice("Swallowed %s(%r)" % (e.__class__.__name__, e.args[0])) +return "ok" +$$ LANGUAGE plpython3u; + +SELECT subtransaction_nested_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +SELECT subtransaction_nested_test('t'); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Nested subtransactions that recursively call code dealing with +-- subtransactions + +CREATE FUNCTION subtransaction_deeply_nested_test() RETURNS text +AS $$ +plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (2)") + plpy.execute("SELECT subtransaction_nested_test('t')") +return "ok" +$$ LANGUAGE plpython3u; + +SELECT subtransaction_deeply_nested_test(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Error conditions from not opening/closing subtransactions + +CREATE FUNCTION subtransaction_exit_without_enter() RETURNS void +AS $$ +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_without_exit() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__exit__(None, None, None) +plpy.subtransaction().__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_twice() RETURNS void +AS $$ +plpy.subtransaction().__enter__() +plpy.subtransaction().__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__exit__(None, None, None) +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_enter_same_subtransaction_twice() RETURNS void +AS $$ +s = plpy.subtransaction() +s.__enter__() +s.__enter__() +s.__exit__(None, None, None) +$$ LANGUAGE plpython3u; + +-- No warnings here, as the subtransaction gets indeed closed +CREATE FUNCTION subtransaction_enter_subtransaction_in_with() RETURNS void +AS $$ +with plpy.subtransaction() as s: + s.__enter__() +$$ LANGUAGE plpython3u; + +CREATE FUNCTION subtransaction_exit_subtransaction_in_with() RETURNS void +AS $$ +try: + with plpy.subtransaction() as s: + s.__exit__(None, None, None) +except ValueError as e: + raise ValueError(e) +$$ LANGUAGE plpython3u; + +SELECT subtransaction_exit_without_enter(); +SELECT subtransaction_enter_without_exit(); +SELECT subtransaction_exit_twice(); +SELECT subtransaction_enter_twice(); +SELECT subtransaction_exit_same_subtransaction_twice(); +SELECT subtransaction_enter_same_subtransaction_twice(); +SELECT subtransaction_enter_subtransaction_in_with(); +SELECT subtransaction_exit_subtransaction_in_with(); + +-- Make sure we don't get a "current transaction is aborted" error +SELECT 1 as test; + +-- Mix explicit subtransactions and normal SPI calls + +CREATE FUNCTION subtransaction_mix_explicit_and_implicit() RETURNS void +AS $$ +p = plpy.prepare("INSERT INTO subtransaction_tbl VALUES ($1)", ["integer"]) +try: + with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error from an explicit subtransaction") + +try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + plpy.execute(p, [2]) + plpy.execute(p, ["wrong"]) +except plpy.SPIError: + plpy.warning("Caught a SPI error") +$$ LANGUAGE plpython3u; + +SELECT subtransaction_mix_explicit_and_implicit(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +-- Alternative method names for Python <2.6 + +CREATE FUNCTION subtransaction_alternative_names() RETURNS void +AS $$ +s = plpy.subtransaction() +s.enter() +s.exit(None, None, None) +$$ LANGUAGE plpython3u; + +SELECT subtransaction_alternative_names(); + +-- try/catch inside a subtransaction block + +CREATE FUNCTION try_catch_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES ('a')") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; + +SELECT try_catch_inside_subtransaction(); +SELECT * FROM subtransaction_tbl; +TRUNCATE subtransaction_tbl; + +ALTER TABLE subtransaction_tbl ADD PRIMARY KEY (i); + +CREATE FUNCTION pk_violation_inside_subtransaction() RETURNS void +AS $$ +with plpy.subtransaction(): + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + try: + plpy.execute("INSERT INTO subtransaction_tbl VALUES (1)") + except plpy.SPIError: + plpy.notice("caught") +$$ LANGUAGE plpython3u; + +SELECT pk_violation_inside_subtransaction(); +SELECT * FROM subtransaction_tbl; + +DROP TABLE subtransaction_tbl; + +-- cursor/subtransactions interactions + +CREATE FUNCTION cursor_in_subxact() RETURNS int AS $$ +with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10) +fetched = cur.fetch(10); +return int(fetched[5]["i"]) +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor("select * from generate_series(1, 20) as gen(i)") + cur.fetch(10); + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(10) + return int(fetched[5]["i"]) +return 0 # not reached +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_plan_aborted_subxact() RETURNS int AS $$ +try: + with plpy.subtransaction(): + plpy.execute('create temporary table tmp(i) ' + 'as select generate_series(1, 10)') + plan = plpy.prepare("select i from tmp") + cur = plpy.cursor(plan) + plpy.execute("select no_such_function()") +except plpy.SPIError: + fetched = cur.fetch(5) + return fetched[2]["i"] +return 0 # not reached +$$ LANGUAGE plpython3u; + +CREATE FUNCTION cursor_close_aborted_subxact() RETURNS boolean AS $$ +try: + with plpy.subtransaction(): + cur = plpy.cursor('select 1') + plpy.execute("select no_such_function()") +except plpy.SPIError: + cur.close() + return True +return False # not reached +$$ LANGUAGE plpython3u; + +SELECT cursor_in_subxact(); +SELECT cursor_aborted_subxact(); +SELECT cursor_plan_aborted_subxact(); +SELECT cursor_close_aborted_subxact(); diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql new file mode 100644 index 0000000..aa22a27 --- /dev/null +++ b/src/pl/plpython/sql/plpython_test.sql @@ -0,0 +1,52 @@ +-- first some tests of basic functionality +CREATE EXTENSION plpython3u; + +-- really stupid function just to get the module loaded +CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; + +select stupid(); + +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython3u; + +select stupidn(); + +-- test multiple arguments and odd characters in function name +CREATE FUNCTION "Argument test #1"(u users, a1 text, a2 text) RETURNS text + AS +'keys = list(u.keys()) +keys.sort() +out = [] +for key in keys: + out.append("%s: %s" % (key, u[key])) +words = a1 + " " + a2 + " => {" + ", ".join(out) + "}" +return words' + LANGUAGE plpython3u; + +select "Argument test #1"(users, fname, lname) from users where lname = 'doe' order by 1; + + +-- check module contents +CREATE FUNCTION module_contents() RETURNS SETOF text AS +$$ +contents = list(filter(lambda x: not x.startswith("__"), dir(plpy))) +contents.sort() +return contents +$$ LANGUAGE plpython3u; + +select module_contents(); + +CREATE FUNCTION elog_test_basic() RETURNS void +AS $$ +plpy.debug('debug') +plpy.log('log') +plpy.info('info') +plpy.info(37) +plpy.info() +plpy.info('info', 37, [1, 2, 3]) +plpy.notice('notice') +plpy.warning('warning') +plpy.error('error') +$$ LANGUAGE plpython3u; + +SELECT elog_test_basic(); diff --git a/src/pl/plpython/sql/plpython_transaction.sql b/src/pl/plpython/sql/plpython_transaction.sql new file mode 100644 index 0000000..c939ba7 --- /dev/null +++ b/src/pl/plpython/sql/plpython_transaction.sql @@ -0,0 +1,182 @@ +CREATE TABLE test1 (a int, b text); + + +CREATE PROCEDURE transaction_test1() +LANGUAGE plpython3u +AS $$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; + +CALL transaction_test1(); + +SELECT * FROM test1; + + +TRUNCATE test1; + +DO +LANGUAGE plpython3u +$$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; + +SELECT * FROM test1; + + +TRUNCATE test1; + +-- not allowed in a function +CREATE FUNCTION transaction_test2() RETURNS int +LANGUAGE plpython3u +AS $$ +for i in range(0, 10): + plpy.execute("INSERT INTO test1 (a) VALUES (%d)" % i) + if i % 2 == 0: + plpy.commit() + else: + plpy.rollback() +return 1 +$$; + +SELECT transaction_test2(); + +SELECT * FROM test1; + + +-- also not allowed if procedure is called from a function +CREATE FUNCTION transaction_test3() RETURNS int +LANGUAGE plpython3u +AS $$ +plpy.execute("CALL transaction_test1()") +return 1 +$$; + +SELECT transaction_test3(); + +SELECT * FROM test1; + + +-- DO block inside function +CREATE FUNCTION transaction_test4() RETURNS int +LANGUAGE plpython3u +AS $$ +plpy.execute("DO LANGUAGE plpython3u $x$ plpy.commit() $x$") +return 1 +$$; + +SELECT transaction_test4(); + + +-- commit inside subtransaction (prohibited) +DO LANGUAGE plpython3u $$ +s = plpy.subtransaction() +s.enter() +plpy.commit() +$$; + + +-- commit inside cursor loop +CREATE TABLE test2 (x int); +INSERT INTO test2 VALUES (0), (1), (2), (3), (4); + +TRUNCATE test1; + +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + plpy.commit() +$$; + +SELECT * FROM test1; + +-- check that this doesn't leak a holdable portal +SELECT * FROM pg_cursors; + + +-- error in cursor loop with commit +TRUNCATE test1; + +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (12/(%s-2))" % row['x']) + plpy.commit() +$$; + +SELECT * FROM test1; + +SELECT * FROM pg_cursors; + + +-- rollback inside cursor loop +TRUNCATE test1; + +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + plpy.rollback() +$$; + +SELECT * FROM test1; + +SELECT * FROM pg_cursors; + + +-- first commit then rollback inside cursor loop +TRUNCATE test1; + +DO LANGUAGE plpython3u $$ +for row in plpy.cursor("SELECT * FROM test2 ORDER BY x"): + plpy.execute("INSERT INTO test1 (a) VALUES (%s)" % row['x']) + if row['x'] % 2 == 0: + plpy.commit() + else: + plpy.rollback() +$$; + +SELECT * FROM test1; + +SELECT * FROM pg_cursors; + + +-- check handling of an error during COMMIT +CREATE TABLE testpk (id int PRIMARY KEY); +CREATE TABLE testfk(f1 int REFERENCES testpk DEFERRABLE INITIALLY DEFERRED); + +DO LANGUAGE plpython3u $$ +# this insert will fail during commit: +plpy.execute("INSERT INTO testfk VALUES (0)") +plpy.commit() +plpy.warning('should not get here') +$$; + +SELECT * FROM testpk; +SELECT * FROM testfk; + +DO LANGUAGE plpython3u $$ +# this insert will fail during commit: +plpy.execute("INSERT INTO testfk VALUES (0)") +try: + plpy.commit() +except Exception as e: + plpy.info('sqlstate: %s' % (e.sqlstate)) +# these inserts should work: +plpy.execute("INSERT INTO testpk VALUES (1)") +plpy.execute("INSERT INTO testfk VALUES (1)") +$$; + +SELECT * FROM testpk; +SELECT * FROM testfk; + + +DROP TABLE test1; +DROP TABLE test2; diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql new file mode 100644 index 0000000..e5504b9 --- /dev/null +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -0,0 +1,469 @@ +-- these triggers are dedicated to HPHC of RI who +-- decided that my kid's name was william not willem, and +-- vigorously resisted all efforts at correction. they have +-- since gone bankrupt... + +CREATE FUNCTION users_insert() returns trigger + AS +'if TD["new"]["fname"] == None or TD["new"]["lname"] == None: + return "SKIP" +if TD["new"]["username"] == None: + TD["new"]["username"] = TD["new"]["fname"][:1] + "_" + TD["new"]["lname"] + rv = "MODIFY" +else: + rv = None +if TD["new"]["fname"] == "william": + TD["new"]["fname"] = TD["args"][0] + rv = "MODIFY" +return rv' + LANGUAGE plpython3u; + + +CREATE FUNCTION users_update() returns trigger + AS +'if TD["event"] == "UPDATE": + if TD["old"]["fname"] != TD["new"]["fname"] and TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; + + +CREATE FUNCTION users_delete() RETURNS trigger + AS +'if TD["old"]["fname"] == TD["args"][0]: + return "SKIP" +return None' + LANGUAGE plpython3u; + + +CREATE TRIGGER users_insert_trig BEFORE INSERT ON users FOR EACH ROW + EXECUTE PROCEDURE users_insert ('willem'); + +CREATE TRIGGER users_update_trig BEFORE UPDATE ON users FOR EACH ROW + EXECUTE PROCEDURE users_update ('willem'); + +CREATE TRIGGER users_delete_trig BEFORE DELETE ON users FOR EACH ROW + EXECUTE PROCEDURE users_delete ('willem'); + + +-- quick peek at the table +-- +SELECT * FROM users; + +-- should fail +-- +UPDATE users SET fname = 'william' WHERE fname = 'willem'; + +-- should modify william to willem and create username +-- +INSERT INTO users (fname, lname) VALUES ('william', 'smith'); +INSERT INTO users (fname, lname, username) VALUES ('charles', 'darwin', 'beagle'); + +SELECT * FROM users; + + +-- dump trigger data + +CREATE TABLE trigger_test + (i int, v text ); + +CREATE TABLE trigger_test_generated ( + i int, + j int GENERATED ALWAYS AS (i * 2) STORED +); + +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpython3u AS $$ + +if 'relid' in TD: + TD['relid'] = "bogus:12345" + +skeys = list(TD.keys()) +skeys.sort() +for key in skeys: + val = TD[key] + if not isinstance(val, dict): + plpy.notice("TD[" + key + "] => " + str(val)) + else: + # print dicts the hard way because otherwise the order is implementation-dependent + valkeys = list(val.keys()) + valkeys.sort() + plpy.notice("TD[" + key + "] => " + '{' + ', '.join([repr(k) + ': ' + repr(val[k]) for k in valkeys]) + '}') + +return None + +$$; + +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +CREATE TRIGGER show_trigger_data_trig_stmt +BEFORE INSERT OR UPDATE OR DELETE OR TRUNCATE ON trigger_test +FOR EACH STATEMENT EXECUTE PROCEDURE trigger_data(23,'skidoo'); + +insert into trigger_test values(1,'insert'); +update trigger_test set v = 'update' where i = 1; +delete from trigger_test; +truncate table trigger_test; + +DROP TRIGGER show_trigger_data_trig_stmt on trigger_test; +DROP TRIGGER show_trigger_data_trig_before on trigger_test; +DROP TRIGGER show_trigger_data_trig_after on trigger_test; + +CREATE TRIGGER show_trigger_data_trig_before +BEFORE INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +CREATE TRIGGER show_trigger_data_trig_after +AFTER INSERT OR UPDATE OR DELETE ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE trigger_data(); + +insert into trigger_test_generated (i) values (1); +update trigger_test_generated set i = 11 where i = 1; +delete from trigger_test_generated; + +DROP TRIGGER show_trigger_data_trig_before ON trigger_test_generated; +DROP TRIGGER show_trigger_data_trig_after ON trigger_test_generated; + +insert into trigger_test values(1,'insert'); +CREATE VIEW trigger_test_view AS SELECT * FROM trigger_test; + +CREATE TRIGGER show_trigger_data_trig +INSTEAD OF INSERT OR UPDATE OR DELETE ON trigger_test_view +FOR EACH ROW EXECUTE PROCEDURE trigger_data(24,'skidoo view'); + +insert into trigger_test_view values(2,'insert'); +update trigger_test_view set v = 'update' where i = 1; +delete from trigger_test_view; + +DROP FUNCTION trigger_data() CASCADE; +DROP VIEW trigger_test_view; +delete from trigger_test; + + +-- +-- trigger error handling +-- + +INSERT INTO trigger_test VALUES (0, 'zero'); + + +-- returning non-string from trigger function + +CREATE FUNCTION stupid1() RETURNS trigger +AS $$ + return 37 +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger1 +BEFORE INSERT ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid1(); + +INSERT INTO trigger_test VALUES (1, 'one'); + +DROP TRIGGER stupid_trigger1 ON trigger_test; + + +-- returning MODIFY from DELETE trigger + +CREATE FUNCTION stupid2() RETURNS trigger +AS $$ + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger2 +BEFORE DELETE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid2(); + +DELETE FROM trigger_test WHERE i = 0; + +DROP TRIGGER stupid_trigger2 ON trigger_test; + +INSERT INTO trigger_test VALUES (0, 'zero'); + + +-- returning unrecognized string from trigger function + +CREATE FUNCTION stupid3() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger3 ON trigger_test; + + +-- Unicode variant + +CREATE FUNCTION stupid3u() RETURNS trigger +AS $$ + return "foo" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger3 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid3u(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger3 ON trigger_test; + + +-- deleting the TD dictionary + +CREATE FUNCTION stupid4() RETURNS trigger +AS $$ + del TD["new"] + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger4 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid4(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger4 ON trigger_test; + + +-- TD not a dictionary + +CREATE FUNCTION stupid5() RETURNS trigger +AS $$ + TD["new"] = ['foo', 'bar'] + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger5 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid5(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger5 ON trigger_test; + + +-- TD not having string keys + +CREATE FUNCTION stupid6() RETURNS trigger +AS $$ + TD["new"] = {1: 'foo', 2: 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger6 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid6(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger6 ON trigger_test; + + +-- TD keys not corresponding to row columns + +CREATE FUNCTION stupid7() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY"; +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger7 ON trigger_test; + + +-- Unicode variant + +CREATE FUNCTION stupid7u() RETURNS trigger +AS $$ + TD["new"] = {'v': 'foo', 'a': 'bar'} + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER stupid_trigger7 +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE stupid7u(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER stupid_trigger7 ON trigger_test; + + +-- calling a trigger function directly + +SELECT stupid7(); + + +-- +-- Null values +-- + +SELECT * FROM trigger_test; + +CREATE FUNCTION test_null() RETURNS trigger +AS $$ + TD["new"]['v'] = None + return "MODIFY" +$$ LANGUAGE plpython3u; + +CREATE TRIGGER test_null_trigger +BEFORE UPDATE ON trigger_test +FOR EACH ROW EXECUTE PROCEDURE test_null(); + +UPDATE trigger_test SET v = 'null' WHERE i = 0; + +DROP TRIGGER test_null_trigger ON trigger_test; + +SELECT * FROM trigger_test; + + +-- +-- Test that triggers honor typmod when assigning to tuple fields, +-- as per an early 9.0 bug report +-- + +SET DateStyle = 'ISO'; + +CREATE FUNCTION set_modif_time() RETURNS trigger AS $$ + TD['new']['modif_time'] = '2010-10-13 21:57:28.930486' + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TABLE pb (a TEXT, modif_time TIMESTAMP(0) WITHOUT TIME ZONE); + +CREATE TRIGGER set_modif_time BEFORE UPDATE ON pb + FOR EACH ROW EXECUTE PROCEDURE set_modif_time(); + +INSERT INTO pb VALUES ('a', '2010-10-09 21:57:33.930486'); +SELECT * FROM pb; +UPDATE pb SET a = 'b'; +SELECT * FROM pb; + + +-- triggers for tables with composite types + +CREATE TABLE comp1 (i integer, j boolean); +CREATE TYPE comp2 AS (k integer, l boolean); + +CREATE TABLE composite_trigger_test (f1 comp1, f2 comp2); + +CREATE FUNCTION composite_trigger_f() RETURNS trigger AS $$ + TD['new']['f1'] = (3, False) + TD['new']['f2'] = {'k': 7, 'l': 'yes', 'ignored': 10} + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger BEFORE INSERT ON composite_trigger_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_f(); + +INSERT INTO composite_trigger_test VALUES (NULL, NULL); +SELECT * FROM composite_trigger_test; + + +-- triggers with composite type columns (bug #6559) + +CREATE TABLE composite_trigger_noop_test (f1 comp1, f2 comp2); + +CREATE FUNCTION composite_trigger_noop_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger_noop BEFORE INSERT ON composite_trigger_noop_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_noop_f(); + +INSERT INTO composite_trigger_noop_test VALUES (NULL, NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(1, 'f'), NULL); +INSERT INTO composite_trigger_noop_test VALUES (ROW(NULL, 't'), ROW(1, 'f')); +SELECT * FROM composite_trigger_noop_test; + + +-- nested composite types + +CREATE TYPE comp3 AS (c1 comp1, c2 comp2, m integer); + +CREATE TABLE composite_trigger_nested_test(c comp3); + +CREATE FUNCTION composite_trigger_nested_f() RETURNS trigger AS $$ + return 'MODIFY' +$$ LANGUAGE plpython3u; + +CREATE TRIGGER composite_trigger_nested BEFORE INSERT ON composite_trigger_nested_test + FOR EACH ROW EXECUTE PROCEDURE composite_trigger_nested_f(); + +INSERT INTO composite_trigger_nested_test VALUES (NULL); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(1, 'f'), NULL, 3)); +INSERT INTO composite_trigger_nested_test VALUES (ROW(ROW(NULL, 't'), ROW(1, 'f'), NULL)); +SELECT * FROM composite_trigger_nested_test; + +-- check that using a function as a trigger over two tables works correctly +CREATE FUNCTION trig1234() RETURNS trigger LANGUAGE plpython3u AS $$ + TD["new"]["data"] = '1234' + return 'MODIFY' +$$; + +CREATE TABLE a(data text); +CREATE TABLE b(data int); -- different type conversion + +CREATE TRIGGER a_t BEFORE INSERT ON a FOR EACH ROW EXECUTE PROCEDURE trig1234(); +CREATE TRIGGER b_t BEFORE INSERT ON b FOR EACH ROW EXECUTE PROCEDURE trig1234(); + +INSERT INTO a DEFAULT VALUES; +SELECT * FROM a; +DROP TABLE a; +INSERT INTO b DEFAULT VALUES; +SELECT * FROM b; + +-- check that SQL run in trigger code can see transition tables + +CREATE TABLE transition_table_test (id int, name text); +INSERT INTO transition_table_test VALUES (1, 'a'); + +CREATE FUNCTION transition_table_test_f() RETURNS trigger LANGUAGE plpython3u AS +$$ + rv = plpy.execute("SELECT * FROM old_table") + assert(rv.nrows() == 1) + plpy.info("old: " + str(rv[0]["id"]) + " -> " + rv[0]["name"]) + rv = plpy.execute("SELECT * FROM new_table") + assert(rv.nrows() == 1) + plpy.info("new: " + str(rv[0]["id"]) + " -> " + rv[0]["name"]) + return None +$$; + +CREATE TRIGGER a_t AFTER UPDATE ON transition_table_test + REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table + FOR EACH STATEMENT EXECUTE PROCEDURE transition_table_test_f(); +UPDATE transition_table_test SET name = 'b'; + +DROP TABLE transition_table_test; +DROP FUNCTION transition_table_test_f(); + + +-- dealing with generated columns + +CREATE FUNCTION generated_test_func1() RETURNS trigger +LANGUAGE plpython3u +AS $$ +TD['new']['j'] = 5 # not allowed +return 'MODIFY' +$$; + +CREATE TRIGGER generated_test_trigger1 BEFORE INSERT ON trigger_test_generated +FOR EACH ROW EXECUTE PROCEDURE generated_test_func1(); + +TRUNCATE trigger_test_generated; +INSERT INTO trigger_test_generated (i) VALUES (1); +SELECT * FROM trigger_test_generated; diff --git a/src/pl/plpython/sql/plpython_types.sql b/src/pl/plpython/sql/plpython_types.sql new file mode 100644 index 0000000..0985a9c --- /dev/null +++ b/src/pl/plpython/sql/plpython_types.sql @@ -0,0 +1,622 @@ +-- +-- Test data type behavior +-- + +-- +-- Base/common types +-- + +CREATE FUNCTION test_type_conversion_bool(x bool) RETURNS bool AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bool(true); +SELECT * FROM test_type_conversion_bool(false); +SELECT * FROM test_type_conversion_bool(null); + + +-- test various other ways to express Booleans in Python +CREATE FUNCTION test_type_conversion_bool_other(n int) RETURNS bool AS $$ +# numbers +if n == 0: + ret = 0 +elif n == 1: + ret = 5 +# strings +elif n == 2: + ret = '' +elif n == 3: + ret = 'fa' # true in Python, false in PostgreSQL +# containers +elif n == 4: + ret = [] +elif n == 5: + ret = [0] +plpy.info(ret, not not ret) +return ret +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bool_other(0); +SELECT * FROM test_type_conversion_bool_other(1); +SELECT * FROM test_type_conversion_bool_other(2); +SELECT * FROM test_type_conversion_bool_other(3); +SELECT * FROM test_type_conversion_bool_other(4); +SELECT * FROM test_type_conversion_bool_other(5); + + +CREATE FUNCTION test_type_conversion_char(x char) RETURNS char AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_char('a'); +SELECT * FROM test_type_conversion_char(null); + + +CREATE FUNCTION test_type_conversion_int2(x int2) RETURNS int2 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int2(100::int2); +SELECT * FROM test_type_conversion_int2(-100::int2); +SELECT * FROM test_type_conversion_int2(null); + + +CREATE FUNCTION test_type_conversion_int4(x int4) RETURNS int4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int4(100); +SELECT * FROM test_type_conversion_int4(-100); +SELECT * FROM test_type_conversion_int4(null); + + +CREATE FUNCTION test_type_conversion_int8(x int8) RETURNS int8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_int8(100); +SELECT * FROM test_type_conversion_int8(-100); +SELECT * FROM test_type_conversion_int8(5000000000); +SELECT * FROM test_type_conversion_int8(null); + + +CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ +# print just the class name, not the type, to avoid differences +# between decimal and cdecimal +plpy.info(str(x), x.__class__.__name__) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_numeric(100); +SELECT * FROM test_type_conversion_numeric(-100); +SELECT * FROM test_type_conversion_numeric(100.0); +SELECT * FROM test_type_conversion_numeric(100.00); +SELECT * FROM test_type_conversion_numeric(5000000000.5); +SELECT * FROM test_type_conversion_numeric(1234567890.0987654321); +SELECT * FROM test_type_conversion_numeric(-1234567890.0987654321); +SELECT * FROM test_type_conversion_numeric(null); + + +CREATE FUNCTION test_type_conversion_float4(x float4) RETURNS float4 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_float4(100); +SELECT * FROM test_type_conversion_float4(-100); +SELECT * FROM test_type_conversion_float4(5000.5); +SELECT * FROM test_type_conversion_float4(null); + + +CREATE FUNCTION test_type_conversion_float8(x float8) RETURNS float8 AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_float8(100); +SELECT * FROM test_type_conversion_float8(-100); +SELECT * FROM test_type_conversion_float8(5000000000.5); +SELECT * FROM test_type_conversion_float8(null); +SELECT * FROM test_type_conversion_float8(100100100.654321); + + +CREATE FUNCTION test_type_conversion_oid(x oid) RETURNS oid AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_oid(100); +SELECT * FROM test_type_conversion_oid(2147483649); +SELECT * FROM test_type_conversion_oid(null); + + +CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_text('hello world'); +SELECT * FROM test_type_conversion_text(null); + + +CREATE FUNCTION test_type_conversion_bytea(x bytea) RETURNS bytea AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bytea('hello world'); +SELECT * FROM test_type_conversion_bytea(E'null\\000byte'); +SELECT * FROM test_type_conversion_bytea(null); + + +CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$ +import marshal +return marshal.dumps('hello world') +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$ +import marshal +try: + return marshal.loads(x) +except ValueError as e: + return 'FAILED: ' + str(e) +$$ LANGUAGE plpython3u; + +SELECT test_type_unmarshal(x) FROM test_type_marshal() x; + + +-- +-- Domains +-- + +CREATE DOMAIN booltrue AS bool CHECK (VALUE IS TRUE OR VALUE IS NULL); + +CREATE FUNCTION test_type_conversion_booltrue(x booltrue, y bool) RETURNS booltrue AS $$ +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_booltrue(true, true); +SELECT * FROM test_type_conversion_booltrue(false, true); +SELECT * FROM test_type_conversion_booltrue(true, false); + + +CREATE DOMAIN uint2 AS int2 CHECK (VALUE >= 0); + +CREATE FUNCTION test_type_conversion_uint2(x uint2, y int) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_uint2(100::uint2, 50); +SELECT * FROM test_type_conversion_uint2(100::uint2, -50); +SELECT * FROM test_type_conversion_uint2(null, 1); + + +CREATE DOMAIN nnint AS int CHECK (VALUE IS NOT NULL); + +CREATE FUNCTION test_type_conversion_nnint(x nnint, y int) RETURNS nnint AS $$ +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_nnint(10, 20); +SELECT * FROM test_type_conversion_nnint(null, 20); +SELECT * FROM test_type_conversion_nnint(10, null); + + +CREATE DOMAIN bytea10 AS bytea CHECK (octet_length(VALUE) = 10 AND VALUE IS NOT NULL); + +CREATE FUNCTION test_type_conversion_bytea10(x bytea10, y bytea) RETURNS bytea10 AS $$ +plpy.info(x, type(x)) +return y +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); +SELECT * FROM test_type_conversion_bytea10('hello world', 'hello wold'); +SELECT * FROM test_type_conversion_bytea10('hello word', 'hello world'); +SELECT * FROM test_type_conversion_bytea10(null, 'hello word'); +SELECT * FROM test_type_conversion_bytea10('hello word', null); + + +-- +-- Arrays +-- + +CREATE FUNCTION test_type_conversion_array_int4(x int4[]) RETURNS int4[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]); +SELECT * FROM test_type_conversion_array_int4(NULL); +SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]); +SELECT * FROM test_type_conversion_array_int4(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]); +SELECT * FROM test_type_conversion_array_int4('[2:4]={1,2,3}'); + +CREATE FUNCTION test_type_conversion_array_int8(x int8[]) RETURNS int8[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_int8(ARRAY[[[1,2,NULL],[NULL,5,6]],[[NULL,8,9],[10,11,12]]]::int8[]); + +CREATE FUNCTION test_type_conversion_array_date(x date[]) RETURNS date[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_date(ARRAY[[['2016-09-21','2016-09-22',NULL],[NULL,'2016-10-21','2016-10-22']], + [[NULL,'2016-11-21','2016-10-21'],['2015-09-21','2015-09-22','2014-09-21']]]::date[]); + +CREATE FUNCTION test_type_conversion_array_timestamp(x timestamp[]) RETURNS timestamp[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_timestamp(ARRAY[[['2016-09-21 15:34:24.078792-04','2016-10-22 11:34:24.078795-04',NULL], + [NULL,'2016-10-21 11:34:25.078792-04','2016-10-21 11:34:24.098792-04']], + [[NULL,'2016-01-21 11:34:24.078792-04','2016-11-21 11:34:24.108792-04'], + ['2015-09-21 11:34:24.079792-04','2014-09-21 11:34:24.078792-04','2013-09-21 11:34:24.078792-04']]]::timestamp[]); + + +CREATE OR REPLACE FUNCTION pyreturnmultidemint4(h int4, i int4, j int4, k int4 ) RETURNS int4[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; + +select pyreturnmultidemint4(8,5,3,2); + +CREATE OR REPLACE FUNCTION pyreturnmultidemint8(h int4, i int4, j int4, k int4 ) RETURNS int8[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; + +select pyreturnmultidemint8(5,5,3,2); + +CREATE OR REPLACE FUNCTION pyreturnmultidemfloat4(h int4, i int4, j int4, k int4 ) RETURNS float4[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; + +select pyreturnmultidemfloat4(6,5,3,2); + +CREATE OR REPLACE FUNCTION pyreturnmultidemfloat8(h int4, i int4, j int4, k int4 ) RETURNS float8[] AS $BODY$ +m = [[[[x for x in range(h)] for y in range(i)] for z in range(j)] for w in range(k)] +plpy.info(m, type(m)) +return m +$BODY$ LANGUAGE plpython3u; + +select pyreturnmultidemfloat8(7,5,3,2); + +CREATE FUNCTION test_type_conversion_array_text(x text[]) RETURNS text[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_text(ARRAY['foo', 'bar']); +SELECT * FROM test_type_conversion_array_text(ARRAY[['foo', 'bar'],['foo2', 'bar2']]); + + +CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); + + +CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_mixed1(); + + +CREATE FUNCTION test_type_conversion_array_mixed2() RETURNS int[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_mixed2(); + + +-- check output of multi-dimensional arrays +CREATE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [['a'], ['b'], ['c']] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], []] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], [1]] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); -- fail + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[], 1] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); -- fail + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [1, []] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); -- fail + +CREATE OR REPLACE FUNCTION test_type_conversion_md_array_out() RETURNS text[] AS $$ +return [[1], [[]]] +$$ LANGUAGE plpython3u; + +select test_type_conversion_md_array_out(); -- fail + + +CREATE FUNCTION test_type_conversion_mdarray_malformed() RETURNS int[] AS $$ +return [[1,2,3],[4,5]] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_mdarray_malformed(); + +CREATE FUNCTION test_type_conversion_mdarray_malformed2() RETURNS text[] AS $$ +return [[1,2,3], "abc"] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_mdarray_malformed2(); + +CREATE FUNCTION test_type_conversion_mdarray_malformed3() RETURNS text[] AS $$ +return ["abc", [1,2,3]] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_mdarray_malformed3(); + +CREATE FUNCTION test_type_conversion_mdarray_toodeep() RETURNS int[] AS $$ +return [[[[[[[1]]]]]]] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_mdarray_toodeep(); + + +CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ +return [{'first': 'one', 'second': 42}, {'first': 'two', 'second': 11}] +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_record(); + + +CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ +return 'abc' +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_string(); + +CREATE FUNCTION test_type_conversion_array_tuple() RETURNS text[] AS $$ +return ('abc', 'def') +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_tuple(); + +CREATE FUNCTION test_type_conversion_array_error() RETURNS int[] AS $$ +return 5 +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_error(); + + +-- +-- Domains over arrays +-- + +CREATE DOMAIN ordered_pair_domain AS integer[] CHECK (array_length(VALUE,1)=2 AND VALUE[1] < VALUE[2]); + +CREATE FUNCTION test_type_conversion_array_domain(x ordered_pair_domain) RETURNS ordered_pair_domain AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpython3u; + +SELECT * FROM test_type_conversion_array_domain(ARRAY[0, 100]::ordered_pair_domain); +SELECT * FROM test_type_conversion_array_domain(NULL::ordered_pair_domain); + +CREATE FUNCTION test_type_conversion_array_domain_check_violation() RETURNS ordered_pair_domain AS $$ +return [2,1] +$$ LANGUAGE plpython3u; +SELECT * FROM test_type_conversion_array_domain_check_violation(); + + +-- +-- Arrays of domains +-- + +CREATE FUNCTION test_read_uint2_array(x uint2[]) RETURNS uint2 AS $$ +plpy.info(x, type(x)) +return x[0] +$$ LANGUAGE plpython3u; + +select test_read_uint2_array(array[1::uint2]); + +CREATE FUNCTION test_build_uint2_array(x int2) RETURNS uint2[] AS $$ +return [x, x] +$$ LANGUAGE plpython3u; + +select test_build_uint2_array(1::int2); +select test_build_uint2_array(-1::int2); -- fail + +-- +-- ideally this would work, but for now it doesn't, because the return value +-- is [[2,4], [2,4]] which our conversion code thinks should become a 2-D +-- integer array, not an array of arrays. +-- +CREATE FUNCTION test_type_conversion_domain_array(x integer[]) + RETURNS ordered_pair_domain[] AS $$ +return [x, x] +$$ LANGUAGE plpython3u; + +select test_type_conversion_domain_array(array[2,4]); +select test_type_conversion_domain_array(array[4,2]); -- fail + +CREATE FUNCTION test_type_conversion_domain_array2(x ordered_pair_domain) + RETURNS integer AS $$ +plpy.info(x, type(x)) +return x[1] +$$ LANGUAGE plpython3u; + +select test_type_conversion_domain_array2(array[2,4]); +select test_type_conversion_domain_array2(array[4,2]); -- fail + +CREATE FUNCTION test_type_conversion_array_domain_array(x ordered_pair_domain[]) + RETURNS ordered_pair_domain AS $$ +plpy.info(x, type(x)) +return x[0] +$$ LANGUAGE plpython3u; + +select test_type_conversion_array_domain_array(array[array[2,4]::ordered_pair_domain]); + + +--- +--- Composite types +--- + +CREATE TABLE employee ( + name text, + basesalary integer, + bonus integer +); + +INSERT INTO employee VALUES ('John', 100, 10), ('Mary', 200, 10); + +CREATE OR REPLACE FUNCTION test_composite_table_input(e employee) RETURNS integer AS $$ +return e['basesalary'] + e['bonus'] +$$ LANGUAGE plpython3u; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +ALTER TABLE employee DROP bonus; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +ALTER TABLE employee ADD bonus integer; +UPDATE employee SET bonus = 10; + +SELECT name, test_composite_table_input(employee.*) FROM employee; + +CREATE TYPE named_pair AS ( + i integer, + j integer +); + +CREATE OR REPLACE FUNCTION test_composite_type_input(p named_pair) RETURNS integer AS $$ +return sum(p.values()) +$$ LANGUAGE plpython3u; + +SELECT test_composite_type_input(row(1, 2)); + +ALTER TYPE named_pair RENAME TO named_pair_2; + +SELECT test_composite_type_input(row(1, 2)); + + +-- +-- Domains within composite +-- + +CREATE TYPE nnint_container AS (f1 int, f2 nnint); + +CREATE FUNCTION nnint_test(x int, y int) RETURNS nnint_container AS $$ +return {'f1': x, 'f2': y} +$$ LANGUAGE plpython3u; + +SELECT nnint_test(null, 3); +SELECT nnint_test(3, null); -- fail + + +-- +-- Domains of composite +-- + +CREATE DOMAIN ordered_named_pair AS named_pair_2 CHECK((VALUE).i <= (VALUE).j); + +CREATE FUNCTION read_ordered_named_pair(p ordered_named_pair) RETURNS integer AS $$ +return p['i'] + p['j'] +$$ LANGUAGE plpython3u; + +SELECT read_ordered_named_pair(row(1, 2)); +SELECT read_ordered_named_pair(row(2, 1)); -- fail + +CREATE FUNCTION build_ordered_named_pair(i int, j int) RETURNS ordered_named_pair AS $$ +return {'i': i, 'j': j} +$$ LANGUAGE plpython3u; + +SELECT build_ordered_named_pair(1,2); +SELECT build_ordered_named_pair(2,1); -- fail + +CREATE FUNCTION build_ordered_named_pairs(i int, j int) RETURNS ordered_named_pair[] AS $$ +return [{'i': i, 'j': j}, {'i': i, 'j': j+1}] +$$ LANGUAGE plpython3u; + +SELECT build_ordered_named_pairs(1,2); +SELECT build_ordered_named_pairs(2,1); -- fail + + +-- +-- Prepared statements +-- + +CREATE OR REPLACE FUNCTION test_prep_bool_input() RETURNS int +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT CASE WHEN $1 THEN 1 ELSE 0 END AS val", ['boolean']) +rv = plpy.execute(plan, ['fa'], 5) # 'fa' is true in Python +return rv[0]['val'] +$$; + +SELECT test_prep_bool_input(); -- 1 + + +CREATE OR REPLACE FUNCTION test_prep_bool_output() RETURNS bool +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT $1 = 1 AS val", ['int']) +rv = plpy.execute(plan, [0], 5) +plpy.info(rv[0]) +return rv[0]['val'] +$$; + +SELECT test_prep_bool_output(); -- false + + +CREATE OR REPLACE FUNCTION test_prep_bytea_input(bb bytea) RETURNS int +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT octet_length($1) AS val", ['bytea']) +rv = plpy.execute(plan, [bb], 5) +return rv[0]['val'] +$$; + +SELECT test_prep_bytea_input(E'a\\000b'); -- 3 (embedded null formerly truncated value) + + +CREATE OR REPLACE FUNCTION test_prep_bytea_output() RETURNS bytea +LANGUAGE plpython3u +AS $$ +plan = plpy.prepare("SELECT decode('aa00bb', 'hex') AS val") +rv = plpy.execute(plan, [], 5) +plpy.info(rv[0]) +return rv[0]['val'] +$$; + +SELECT test_prep_bytea_output(); diff --git a/src/pl/plpython/sql/plpython_unicode.sql b/src/pl/plpython/sql/plpython_unicode.sql new file mode 100644 index 0000000..14f7b4e --- /dev/null +++ b/src/pl/plpython/sql/plpython_unicode.sql @@ -0,0 +1,45 @@ +-- +-- Unicode handling +-- +-- Note: this test case is known to fail if the database encoding is +-- EUC_CN, EUC_JP, EUC_KR, or EUC_TW, for lack of any equivalent to +-- U+00A0 (no-break space) in those encodings. However, testing with +-- plain ASCII data would be rather useless, so we must live with that. +-- + +SET client_encoding TO UTF8; + +CREATE TABLE unicode_test ( + testvalue text NOT NULL +); + +CREATE FUNCTION unicode_return() RETURNS text AS E' +return "\\xA0" +' LANGUAGE plpython3u; + +CREATE FUNCTION unicode_trigger() RETURNS trigger AS E' +TD["new"]["testvalue"] = "\\xA0" +return "MODIFY" +' LANGUAGE plpython3u; + +CREATE TRIGGER unicode_test_bi BEFORE INSERT ON unicode_test + FOR EACH ROW EXECUTE PROCEDURE unicode_trigger(); + +CREATE FUNCTION unicode_plan1() RETURNS text AS E' +plan = plpy.prepare("SELECT $1 AS testvalue", ["text"]) +rv = plpy.execute(plan, ["\\xA0"], 1) +return rv[0]["testvalue"] +' LANGUAGE plpython3u; + +CREATE FUNCTION unicode_plan2() RETURNS text AS E' +plan = plpy.prepare("SELECT $1 || $2 AS testvalue", ["text", "text"]) +rv = plpy.execute(plan, ["foo", "bar"], 1) +return rv[0]["testvalue"] +' LANGUAGE plpython3u; + + +SELECT unicode_return(); +INSERT INTO unicode_test (testvalue) VALUES ('test'); +SELECT * FROM unicode_test; +SELECT unicode_plan1(); +SELECT unicode_plan2(); diff --git a/src/pl/plpython/sql/plpython_void.sql b/src/pl/plpython/sql/plpython_void.sql new file mode 100644 index 0000000..5a1a671 --- /dev/null +++ b/src/pl/plpython/sql/plpython_void.sql @@ -0,0 +1,22 @@ +-- +-- Tests for functions that return void +-- + +CREATE FUNCTION test_void_func1() RETURNS void AS $$ +x = 10 +$$ LANGUAGE plpython3u; + +-- illegal: can't return non-None value in void-returning func +CREATE FUNCTION test_void_func2() RETURNS void AS $$ +return 10 +$$ LANGUAGE plpython3u; + +CREATE FUNCTION test_return_none() RETURNS int AS $$ +None +$$ LANGUAGE plpython3u; + + +-- Tests for functions returning void +SELECT test_void_func1(), test_void_func1() IS NULL AS "is null"; +SELECT test_void_func2(); -- should fail +SELECT test_return_none(), test_return_none() IS NULL AS "is null"; |