From dd4cd55c15886c46378dc27f44f59a6de8c4d45b Mon Sep 17 00:00:00 2001 From: Peter Eisentraut Date: Tue, 15 Dec 2009 22:59:55 +0000 Subject: [PATCH] Python 3 support in PL/Python Behaves more or less unchanged compared to Python 2, but the new language variant is called plpython3u. Documentation describing the naming scheme is included. --- config/python.m4 | 4 +- configure | 2 + doc/src/sgml/installation.sgml | 10 +- doc/src/sgml/plpython.sgml | 111 +++- src/Makefile.global.in | 3 +- src/include/catalog/catversion.h | 4 +- src/include/catalog/pg_pltemplate.h | 4 +- src/pl/plpython/Makefile | 43 +- src/pl/plpython/expected/README | 2 + src/pl/plpython/expected/plpython_test.out | 9 + src/pl/plpython/expected/plpython_trigger.out | 2 +- src/pl/plpython/expected/plpython_types_3.out | 589 ++++++++++++++++++ src/pl/plpython/plpython.c | 180 +++++- src/pl/plpython/sql/plpython_test.sql | 5 + src/pl/plpython/sql/plpython_trigger.sql | 2 +- 15 files changed, 929 insertions(+), 41 deletions(-) create mode 100644 src/pl/plpython/expected/plpython_types_3.out diff --git a/config/python.m4 b/config/python.m4 index a3d66435fb..a84e9437de 100644 --- a/config/python.m4 +++ b/config/python.m4 @@ -1,7 +1,7 @@ # # Autoconf macros for configuring the build of Python extension modules # -# $PostgreSQL: pgsql/config/python.m4,v 1.16 2009/10/14 21:59:15 petere Exp $ +# $PostgreSQL: pgsql/config/python.m4,v 1.17 2009/12/15 22:59:53 petere Exp $ # # PGAC_PATH_PYTHON @@ -30,10 +30,12 @@ else AC_MSG_ERROR([distutils module not found]) fi AC_MSG_CHECKING([Python configuration directory]) +python_majorversion=`${PYTHON} -c "import sys; print(sys.version[[0]])"` python_version=`${PYTHON} -c "import sys; print(sys.version[[:3]])"` python_configdir=`${PYTHON} -c "from distutils.sysconfig import get_python_lib as f; import os; print(os.path.join(f(plat_specific=1,standard_lib=1),'config'))"` python_includespec=`${PYTHON} -c "import distutils.sysconfig; print('-I'+distutils.sysconfig.get_python_inc())"` +AC_SUBST(python_majorversion)[]dnl AC_SUBST(python_version)[]dnl AC_SUBST(python_configdir)[]dnl AC_SUBST(python_includespec)[]dnl diff --git a/configure b/configure index 009a177b08..be5128194f 100755 --- a/configure +++ b/configure @@ -677,6 +677,7 @@ python_libdir python_includespec python_configdir python_version +python_majorversion PYTHON perl_embed_ldflags perl_useshrplib @@ -6964,6 +6965,7 @@ $as_echo "$as_me: error: distutils module not found" >&2;} fi { $as_echo "$as_me:$LINENO: checking Python configuration directory" >&5 $as_echo_n "checking Python configuration directory... " >&6; } +python_majorversion=`${PYTHON} -c "import sys; print(sys.version[0])"` python_version=`${PYTHON} -c "import sys; print(sys.version[:3])"` python_configdir=`${PYTHON} -c "from distutils.sysconfig import get_python_lib as f; import os; print(os.path.join(f(plat_specific=1,standard_lib=1),'config'))"` python_includespec=`${PYTHON} -c "import distutils.sysconfig; print('-I'+distutils.sysconfig.get_python_inc())"` diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml index f087ea594e..8337227521 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -1,4 +1,4 @@ - + <![%standalone-include[<productname>PostgreSQL</>]]> @@ -195,8 +195,12 @@ su - postgres <para> To build the <application>PL/Python</> server programming language, you need a <productname>Python</productname> - installation with the header files and the <application>distutils</application> module. - The minimum required version is <productname>Python</productname> 2.2. + installation with the header files and + the <application>distutils</application> module. The minimum + required version is <productname>Python</productname> + 2.2. <productname>Python 3</productname> is supported with + version 3.1 or later; but see <xref linkend="plpython-python23"> + when using Python 3. </para> <para> diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 5820872039..01feab8ec1 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -1,4 +1,4 @@ -<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.41 2009/12/10 20:43:40 petere Exp $ --> +<!-- $PostgreSQL: pgsql/doc/src/sgml/plpython.sgml,v 1.42 2009/12/15 22:59:53 petere Exp $ --> <chapter id="plpython"> <title>PL/Python - Python Procedural Language @@ -14,7 +14,8 @@ To install PL/Python in a particular database, use - createlang plpythonu dbname. + createlang plpythonu dbname (but + see also ). @@ -42,6 +43,112 @@ + + Python 2 vs. Python 3 + + + PL/Python supports both the Python 2 and Python 3 language + variants. (The PostgreSQL installation instructions might contain + more precise information about the exact supported minor versions + of Python.) Because the Python 2 and Python 3 language variants + are incompatible in some important aspects, the following naming + and transitioning scheme is used by PL/Python to avoid mixing them: + + + + + The PostgreSQL language named plpython2u + implements PL/Python based on the Python 2 language variant. + + + + + + The PostgreSQL language named plpython3u + implements PL/Python based on the Python 3 language variant. + + + + + + The language named plpythonu implements + PL/Python based on the default Python language variant, which is + currently Python 2. (This default is independent of what any + local Python installations might consider to be + their default, for example, + what /usr/bin/python might be.) The + default will probably be changed to Python 3 in a distant future + release of PostgreSQL, depending on the progress of the + migration to Python 3 in the Python community. + + + + + It depends on the build configuration or the installed packages + whether PL/Python for Python 2 or Python 3 or both are available. + + + + This results in the following usage and migration strategy: + + + + + Existing users and users who are currently not interested in + Python 3 use the language name plpythonu and + don't have to change anything for the foreseeable future. It is + recommended to gradually future-proof the code + via migration to Python 2.6/2.7 to simplify the eventual + migration to Python 3. + + + + In practice, many PL/Python functions will migrate to Python 3 + with few or no changes. + + + + + + Users who know that they have heavily Python 2 dependent code + and don't plan to ever change it can make use of + the plpython2u language name. This will + continue to work into the very distant future, until Python 2 + support might be completely dropped by PostgreSQL. + + + + + + Users who want to dive into Python 3 can use + the plpython3u language name, which will keep + working forever by today's standards. In the distant future, + when Python 3 might become the default, they might like to + remove the 3 for aesthetic reasons. + + + + + + Daredevils, who want to build a Python-3-only operating system + environment, can change the build scripts to + make plpythonu be equivalent + to plpython3u, keeping in mind that this + would make their installation incompatible with most of the rest + of the world. + + + + + + + See also the + document What's + New In Python 3.0 for more information about porting to + Python 3. + + + PL/Python Functions diff --git a/src/Makefile.global.in b/src/Makefile.global.in index 37441a8003..9ee115f8ca 100644 --- a/src/Makefile.global.in +++ b/src/Makefile.global.in @@ -1,5 +1,5 @@ # -*-makefile-*- -# $PostgreSQL: pgsql/src/Makefile.global.in,v 1.259 2009/11/03 21:28:10 petere Exp $ +# $PostgreSQL: pgsql/src/Makefile.global.in,v 1.260 2009/12/15 22:59:54 petere Exp $ #------------------------------------------------------------------------------ # All PostgreSQL makefiles include this file and use the variables it sets, @@ -171,6 +171,7 @@ python_libdir = @python_libdir@ python_libspec = @python_libspec@ python_additional_libs = @python_additional_libs@ python_configdir = @python_configdir@ +python_majorversion = @python_majorversion@ python_version = @python_version@ krb_srvtab = @krb_srvtab@ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 14663347c5..9b83c87384 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.558 2009/12/15 17:57:47 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.559 2009/12/15 22:59:54 petere Exp $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200912151 +#define CATALOG_VERSION_NO 200912161 #endif diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h index 06f3e98dde..c23b480cdc 100644 --- a/src/include/catalog/pg_pltemplate.h +++ b/src/include/catalog/pg_pltemplate.h @@ -8,7 +8,7 @@ * Portions Copyright (c) 1996-2009, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * - * $PostgreSQL: pgsql/src/include/catalog/pg_pltemplate.h,v 1.9 2009/11/29 03:02:27 tgl Exp $ + * $PostgreSQL: pgsql/src/include/catalog/pg_pltemplate.h,v 1.10 2009/12/15 22:59:54 petere Exp $ * * NOTES * the genbki.sh script reads this file and generates .bki @@ -73,5 +73,7 @@ DATA(insert ( "pltclu" f f "pltclu_call_handler" _null_ _null_ "$libdir/pltcl" DATA(insert ( "plperl" t t "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ )); DATA(insert ( "plperlu" f f "plperl_call_handler" "plperl_inline_handler" "plperl_validator" "$libdir/plperl" _null_ )); DATA(insert ( "plpythonu" f f "plpython_call_handler" _null_ _null_ "$libdir/plpython" _null_ )); +DATA(insert ( "plpython2u" f f "plpython_call_handler" _null_ _null_ "$libdir/plpython2" _null_ )); +DATA(insert ( "plpython3u" f f "plpython_call_handler" _null_ _null_ "$libdir/plpython3" _null_ )); #endif /* PG_PLTEMPLATE_H */ diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 11cd5a8811..5db880b08e 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -1,4 +1,4 @@ -# $PostgreSQL: pgsql/src/pl/plpython/Makefile,v 1.34 2009/08/14 13:42:16 petere Exp $ +# $PostgreSQL: pgsql/src/pl/plpython/Makefile,v 1.35 2009/12/15 22:59:54 petere Exp $ subdir = src/pl/plpython top_builddir = ../../.. @@ -36,7 +36,7 @@ override CPPFLAGS := -I$(srcdir) $(python_includespec) $(CPPFLAGS) rpathdir = $(python_libdir) -NAME = plpython +NAME = plpython$(python_majorversion) OBJS = plpython.o @@ -56,7 +56,12 @@ endif SHLIB_LINK = $(python_libspec) $(python_additional_libs) $(filter -lintl,$(LIBS)) -REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-language=plpythonu +REGRESS_OPTS = --dbname=$(PL_TESTDB) +# Only load plpythonu with Python 2. The test files themselves load +# the versioned language plpython(2|3)u. +ifeq ($(python_majorversion),2) +REGRESS_OPTS += --load-language=plpythonu +endif REGRESS = \ plpython_schema \ plpython_populate \ @@ -83,13 +88,45 @@ include $(top_srcdir)/src/Makefile.shlib all: all-lib install: all installdirs install-lib +ifeq ($(python_majorversion),2) + cd '$(DESTDIR)$(pkglibdir)' && rm -f plpython$(DLSUFFIX) && $(LN_S) $(shlib) plpython$(DLSUFFIX) +endif installdirs: installdirs-lib uninstall: uninstall-lib +ifeq ($(python_majorversion),2) + rm -f '$(DESTDIR)$(pkglibdir)/plpython$(DLSUFFIX)' +endif +ifeq ($(python_majorversion),3) +# Adjust regression tests for Python 3 compatibility +prep3: + $(MKDIR_P) python3 python3/sql python3/expected + for file in $(srcdir)/sql/*.sql $(srcdir)/expected/*.out; do \ + sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \ + -e "s///g" \ + -e "s///g" \ + -e "s/\([0-9][0-9]*\)L/\1/g" \ + -e 's/\([ [{]\)u"/\1"/g' \ + -e "s/\([ [{]\)u'/\1'/g" \ + -e "s/def next/def __next__/g" \ + -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \ + -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \ + $$file >`echo $$file | sed 's,$(srcdir),python3,'`; \ + done + +clean3: + rm -rf python3/ + +installcheck: submake prep3 + $(top_builddir)/src/test/regress/pg_regress --inputdir=./python3 --outputdir=./python3 --psqldir=$(PSQLDIR) $(REGRESS_OPTS) $(REGRESS) + +clean: clean3 +else installcheck: submake $(top_builddir)/src/test/regress/pg_regress --inputdir=$(srcdir) --psqldir=$(PSQLDIR) $(REGRESS_OPTS) $(REGRESS) +endif .PHONY: submake submake: diff --git a/src/pl/plpython/expected/README b/src/pl/plpython/expected/README index 47f31e86d3..a187937540 100644 --- a/src/pl/plpython/expected/README +++ b/src/pl/plpython/expected/README @@ -8,3 +8,5 @@ plpython_unicode_0.out any version, when server encoding != SQL_ASCII and clien plpython_unicode_2.out Python 2.2 plpython_unicode_3.out Python 2.3, 2.4 plpython_unicode_5.out Python 2.5, 2.6 + +plpython_types_3.out Python 3.1 diff --git a/src/pl/plpython/expected/plpython_test.out b/src/pl/plpython/expected/plpython_test.out index c5cfe5a94f..a229b18f44 100644 --- a/src/pl/plpython/expected/plpython_test.out +++ b/src/pl/plpython/expected/plpython_test.out @@ -1,4 +1,5 @@ -- first some tests of basic functionality +CREATE LANGUAGE plpython2u; -- really stupid function just to get the module loaded CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu; select stupid(); @@ -7,6 +8,14 @@ select stupid(); zarkon (1 row) +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython2u; +select stupidn(); + stupidn +--------- + zarkon +(1 row) + -- test multiple arguments CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text AS diff --git a/src/pl/plpython/expected/plpython_trigger.out b/src/pl/plpython/expected/plpython_trigger.out index cf5c758fb2..3192ff1d48 100644 --- a/src/pl/plpython/expected/plpython_trigger.out +++ b/src/pl/plpython/expected/plpython_trigger.out @@ -67,7 +67,7 @@ SELECT * FROM users; -- dump trigger data CREATE TABLE trigger_test (i int, v text ); -CREATE FUNCTION trigger_data() returns trigger language plpythonu as $$ +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: TD['relid'] = "bogus:12345" diff --git a/src/pl/plpython/expected/plpython_types_3.out b/src/pl/plpython/expected/plpython_types_3.out new file mode 100644 index 0000000000..297a0f8af3 --- /dev/null +++ b/src/pl/plpython/expected/plpython_types_3.out @@ -0,0 +1,589 @@ +-- +-- 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 plpythonu; +SELECT * FROM test_type_conversion_bool(true); +INFO: (True, ) +CONTEXT: PL/Python function "test_type_conversion_bool" + test_type_conversion_bool +--------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool(false); +INFO: (False, ) +CONTEXT: PL/Python function "test_type_conversion_bool" + test_type_conversion_bool +--------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_bool" + 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 plpythonu; +SELECT * FROM test_type_conversion_bool_other(0); +INFO: (0, False) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(1); +INFO: (5, True) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(2); +INFO: ('', False) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(3); +INFO: ('fa', True) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + test_type_conversion_bool_other +--------------------------------- + t +(1 row) + +SELECT * FROM test_type_conversion_bool_other(4); +INFO: ([], False) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + test_type_conversion_bool_other +--------------------------------- + f +(1 row) + +SELECT * FROM test_type_conversion_bool_other(5); +INFO: ([0], True) +CONTEXT: PL/Python function "test_type_conversion_bool_other" + 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 plpythonu; +SELECT * FROM test_type_conversion_char('a'); +INFO: ('a', ) +CONTEXT: PL/Python function "test_type_conversion_char" + test_type_conversion_char +--------------------------- + a +(1 row) + +SELECT * FROM test_type_conversion_char(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_char" + 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 plpythonu; +SELECT * FROM test_type_conversion_int2(100::int2); +INFO: (100, ) +CONTEXT: PL/Python function "test_type_conversion_int2" + test_type_conversion_int2 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int2(-100::int2); +INFO: (-100, ) +CONTEXT: PL/Python function "test_type_conversion_int2" + test_type_conversion_int2 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int2(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_int2" + 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 plpythonu; +SELECT * FROM test_type_conversion_int4(100); +INFO: (100, ) +CONTEXT: PL/Python function "test_type_conversion_int4" + test_type_conversion_int4 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int4(-100); +INFO: (-100, ) +CONTEXT: PL/Python function "test_type_conversion_int4" + test_type_conversion_int4 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int4(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_int4" + 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 plpythonu; +SELECT * FROM test_type_conversion_int8(100); +INFO: (100L, ) +CONTEXT: PL/Python function "test_type_conversion_int8" + test_type_conversion_int8 +--------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_int8(-100); +INFO: (-100L, ) +CONTEXT: PL/Python function "test_type_conversion_int8" + test_type_conversion_int8 +--------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_int8(5000000000); +INFO: (5000000000L, ) +CONTEXT: PL/Python function "test_type_conversion_int8" + test_type_conversion_int8 +--------------------------- + 5000000000 +(1 row) + +SELECT * FROM test_type_conversion_int8(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_int8" + test_type_conversion_int8 +--------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_numeric(x numeric) RETURNS numeric AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpythonu; +/* The current implementation converts numeric to float. */ +SELECT * FROM test_type_conversion_numeric(100); +INFO: (100.0, ) +CONTEXT: PL/Python function "test_type_conversion_numeric" + test_type_conversion_numeric +------------------------------ + 100.0 +(1 row) + +SELECT * FROM test_type_conversion_numeric(-100); +INFO: (-100.0, ) +CONTEXT: PL/Python function "test_type_conversion_numeric" + test_type_conversion_numeric +------------------------------ + -100.0 +(1 row) + +SELECT * FROM test_type_conversion_numeric(5000000000.5); +INFO: (5000000000.5, ) +CONTEXT: PL/Python function "test_type_conversion_numeric" + test_type_conversion_numeric +------------------------------ + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_numeric(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_numeric" + 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 plpythonu; +SELECT * FROM test_type_conversion_float4(100); +INFO: (100.0, ) +CONTEXT: PL/Python function "test_type_conversion_float4" + test_type_conversion_float4 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float4(-100); +INFO: (-100.0, ) +CONTEXT: PL/Python function "test_type_conversion_float4" + test_type_conversion_float4 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float4(5000.5); +INFO: (5000.5, ) +CONTEXT: PL/Python function "test_type_conversion_float4" + test_type_conversion_float4 +----------------------------- + 5000.5 +(1 row) + +SELECT * FROM test_type_conversion_float4(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_float4" + 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 plpythonu; +SELECT * FROM test_type_conversion_float8(100); +INFO: (100.0, ) +CONTEXT: PL/Python function "test_type_conversion_float8" + test_type_conversion_float8 +----------------------------- + 100 +(1 row) + +SELECT * FROM test_type_conversion_float8(-100); +INFO: (-100.0, ) +CONTEXT: PL/Python function "test_type_conversion_float8" + test_type_conversion_float8 +----------------------------- + -100 +(1 row) + +SELECT * FROM test_type_conversion_float8(5000000000.5); +INFO: (5000000000.5, ) +CONTEXT: PL/Python function "test_type_conversion_float8" + test_type_conversion_float8 +----------------------------- + 5000000000.5 +(1 row) + +SELECT * FROM test_type_conversion_float8(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_float8" + test_type_conversion_float8 +----------------------------- + +(1 row) + +CREATE FUNCTION test_type_conversion_text(x text) RETURNS text AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpythonu; +SELECT * FROM test_type_conversion_text('hello world'); +INFO: ('hello world', ) +CONTEXT: PL/Python function "test_type_conversion_text" + test_type_conversion_text +--------------------------- + hello world +(1 row) + +SELECT * FROM test_type_conversion_text(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_text" + 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 plpythonu; +SELECT * FROM test_type_conversion_bytea('hello world'); +INFO: (b'hello world', ) +CONTEXT: PL/Python function "test_type_conversion_bytea" + test_type_conversion_bytea +---------------------------- + \x68656c6c6f20776f726c64 +(1 row) + +SELECT * FROM test_type_conversion_bytea(E'null\\000byte'); +INFO: (b'null\x00byte', ) +CONTEXT: PL/Python function "test_type_conversion_bytea" + test_type_conversion_bytea +---------------------------- + \x6e756c6c0062797465 +(1 row) + +SELECT * FROM test_type_conversion_bytea(null); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_bytea" + test_type_conversion_bytea +---------------------------- + +(1 row) + +CREATE FUNCTION test_type_marshal() RETURNS bytea AS $$ +import marshal +return marshal.dumps('hello world') +$$ LANGUAGE plpythonu; +CREATE FUNCTION test_type_unmarshal(x bytea) RETURNS text AS $$ +import marshal +try: + return marshal.loads(x) +except ValueError, e: + return 'FAILED: ' + str(e) +$$ LANGUAGE plpythonu; +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 plpythonu; +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 plpythonu; +SELECT * FROM test_type_conversion_uint2(100::uint2, 50); +INFO: (100, ) +CONTEXT: PL/Python function "test_type_conversion_uint2" + test_type_conversion_uint2 +---------------------------- + 50 +(1 row) + +SELECT * FROM test_type_conversion_uint2(100::uint2, -50); +INFO: (100, ) +CONTEXT: PL/Python function "test_type_conversion_uint2" +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, ) +CONTEXT: PL/Python function "test_type_conversion_uint2" + 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 plpythonu; +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 plpythonu; +SELECT * FROM test_type_conversion_bytea10('hello wold', 'hello wold'); +INFO: (b'hello wold', ) +CONTEXT: PL/Python function "test_type_conversion_bytea10" + 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', ) +CONTEXT: PL/Python function "test_type_conversion_bytea10" +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', ) +CONTEXT: PL/Python function "test_type_conversion_bytea10" +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 plpythonu; +SELECT * FROM test_type_conversion_array_int4(ARRAY[0, 100]); +INFO: ([0, 100], ) +CONTEXT: PL/Python function "test_type_conversion_array_int4" + test_type_conversion_array_int4 +--------------------------------- + {0,100} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[0,-100,55]); +INFO: ([0, -100, 55], ) +CONTEXT: PL/Python function "test_type_conversion_array_int4" + test_type_conversion_array_int4 +--------------------------------- + {0,-100,55} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[NULL,1]); +INFO: ([None, 1], ) +CONTEXT: PL/Python function "test_type_conversion_array_int4" + test_type_conversion_array_int4 +--------------------------------- + {NULL,1} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[]::integer[]); +INFO: ([], ) +CONTEXT: PL/Python function "test_type_conversion_array_int4" + test_type_conversion_array_int4 +--------------------------------- + {} +(1 row) + +SELECT * FROM test_type_conversion_array_int4(NULL); +INFO: (None, ) +CONTEXT: PL/Python function "test_type_conversion_array_int4" + test_type_conversion_array_int4 +--------------------------------- + +(1 row) + +SELECT * FROM test_type_conversion_array_int4(ARRAY[[1,2,3],[4,5,6]]); +ERROR: cannot convert multidimensional array to Python list +DETAIL: PL/Python only supports one-dimensional arrays. +CONTEXT: PL/Python function "test_type_conversion_array_int4" +CREATE FUNCTION test_type_conversion_array_bytea(x bytea[]) RETURNS bytea[] AS $$ +plpy.info(x, type(x)) +return x +$$ LANGUAGE plpythonu; +SELECT * FROM test_type_conversion_array_bytea(ARRAY[E'\\xdeadbeef'::bytea, NULL]); +INFO: ([b'\xde\xad\xbe\xef', None], ) +CONTEXT: PL/Python function "test_type_conversion_array_bytea" + test_type_conversion_array_bytea +---------------------------------- + {"\\xdeadbeef",NULL} +(1 row) + +CREATE FUNCTION test_type_conversion_array_mixed1() RETURNS text[] AS $$ +return [123, 'abc'] +$$ LANGUAGE plpythonu; +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 plpythonu; +SELECT * FROM test_type_conversion_array_mixed2(); +ERROR: invalid input syntax for integer: "abc" +CONTEXT: while creating return value +PL/Python function "test_type_conversion_array_mixed2" +CREATE FUNCTION test_type_conversion_array_record() RETURNS type_record[] AS $$ +return [None] +$$ LANGUAGE plpythonu; +SELECT * FROM test_type_conversion_array_record(); +ERROR: PL/Python functions cannot return type type_record[] +DETAIL: PL/Python does not support conversion to arrays of row types. +CREATE FUNCTION test_type_conversion_array_string() RETURNS text[] AS $$ +return 'abc' +$$ LANGUAGE plpythonu; +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 plpythonu; +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 plpythonu; +SELECT * FROM test_type_conversion_array_error(); +ERROR: PL/Python: 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" diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index 8e1c8689c9..085f0ea8d7 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,7 +1,7 @@ /********************************************************************** * plpython.c - python as a procedural language for PostgreSQL * - * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.133 2009/12/10 20:43:40 petere Exp $ + * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.134 2009/12/15 22:59:54 petere Exp $ * ********************************************************************* */ @@ -40,6 +40,48 @@ typedef int Py_ssize_t; #define PyBool_FromLong(x) PyInt_FromLong(x) #endif +/* + * Python 2/3 strings/unicode/bytes handling. Python 2 has strings + * and unicode, Python 3 has strings, which are unicode on the C + * level, and bytes. The porting convention, which is similarly used + * in Python 2.6, is that "Unicode" is always unicode, and "Bytes" are + * bytes in Python 3 and strings in Python 2. Since we keep + * supporting Python 2 and its usual strings, we provide a + * compatibility layer for Python 3 that when asked to convert a C + * string to a Python string it converts the C string from the + * PostgreSQL server encoding to a Python Unicode object. + */ + +#if PY_VERSION_HEX < 0x02060000 +/* This is exactly the compatibility layer that Python 2.6 uses. */ +#define PyBytes_AsString PyString_AsString +#define PyBytes_FromStringAndSize PyString_FromStringAndSize +#define PyBytes_Size PyString_Size +#define PyObject_Bytes PyObject_Str +#endif + +#if PY_MAJOR_VERSION >= 3 +#define PyString_Check(x) 0 +#define PyString_AsString(x) PLyUnicode_AsString(x) +#define PyString_FromString(x) PLyUnicode_FromString(x) +#endif + +/* + * Python 3 only has long. + */ +#if PY_MAJOR_VERSION >= 3 +#define PyInt_FromLong(x) PyLong_FromLong(x) +#endif + +/* + * PyVarObject_HEAD_INIT was added in Python 2.6. Its use is + * necessary to handle both Python 2 and 3. This replacement + * definition is for Python <=2.5 + */ +#ifndef PyVarObject_HEAD_INIT +#define PyVarObject_HEAD_INIT(type, size) \ + PyObject_HEAD_INIT(type) size, +#endif #include "postgres.h" @@ -246,7 +288,11 @@ static char *PLy_strdup(const char *); static void PLy_free(void *); static PyObject*PLyUnicode_Str(PyObject *unicode); +static PyObject*PLyUnicode_Bytes(PyObject *unicode); static char *PLyUnicode_AsString(PyObject *unicode); +#if PY_MAJOR_VERSION >= 3 +static PyObject *PLyUnicode_FromString(const char *s); +#endif /* sub handlers for functions and triggers */ static Datum PLy_function_handler(FunctionCallInfo fcinfo, PLyProcedure *); @@ -288,7 +334,7 @@ static PyObject *PLyFloat_FromNumeric(PLyDatumToOb *arg, Datum d); static PyObject *PLyInt_FromInt16(PLyDatumToOb *arg, Datum d); static PyObject *PLyInt_FromInt32(PLyDatumToOb *arg, Datum d); static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d); -static PyObject *PLyString_FromBytea(PLyDatumToOb *arg, Datum d); +static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d); static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d); static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d); @@ -1760,7 +1806,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup) arg->func = PLyLong_FromInt64; break; case BYTEAOID: - arg->func = PLyString_FromBytea; + arg->func = PLyBytes_FromBytea; break; default: arg->func = PLyString_FromDatum; @@ -1859,13 +1905,13 @@ PLyLong_FromInt64(PLyDatumToOb *arg, Datum d) } static PyObject * -PLyString_FromBytea(PLyDatumToOb *arg, Datum d) +PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d) { text *txt = DatumGetByteaP(d); char *str = VARDATA(txt); size_t size = VARSIZE(txt) - VARHDRSZ; - return PyString_FromStringAndSize(str, size); + return PyBytes_FromStringAndSize(str, size); } static PyObject * @@ -2001,14 +2047,14 @@ PLyObject_ToBytea(PLyTypeInfo *info, Assert(plrv != Py_None); - plrv_so = PyObject_Str(plrv); + plrv_so = PyObject_Bytes(plrv); if (!plrv_so) - PLy_elog(ERROR, "could not create string representation of Python object"); + PLy_elog(ERROR, "could not create bytes representation of Python object"); PG_TRY(); { - char *plrv_sc = PyString_AsString(plrv_so); - size_t len = PyString_Size(plrv_so); + char *plrv_sc = PyBytes_AsString(plrv_so); + size_t len = PyBytes_Size(plrv_so); size_t size = len + VARHDRSZ; bytea *result = palloc(size); @@ -2040,22 +2086,30 @@ PLyObject_ToDatum(PLyTypeInfo *info, PLyObToDatum *arg, PyObject *plrv) { - PyObject *volatile plrv_so = NULL; + PyObject *volatile plrv_bo = NULL; Datum rv; Assert(plrv != Py_None); if (PyUnicode_Check(plrv)) - plrv_so = PLyUnicode_Str(plrv); + plrv_bo = PLyUnicode_Bytes(plrv); else - plrv_so = PyObject_Str(plrv); - if (!plrv_so) + { +#if PY_MAJOR_VERSION >= 3 + PyObject *s = PyObject_Str(plrv); + plrv_bo = PLyUnicode_Bytes(s); + Py_XDECREF(s); +#else + plrv_bo = PyObject_Str(plrv); +#endif + } + if (!plrv_bo) PLy_elog(ERROR, "could not create string representation of Python object"); PG_TRY(); { - char *plrv_sc = PyString_AsString(plrv_so); - size_t plen = PyString_Size(plrv_so); + char *plrv_sc = PyBytes_AsString(plrv_bo); + size_t plen = PyBytes_Size(plrv_bo); size_t slen = strlen(plrv_sc); if (slen < plen) @@ -2068,12 +2122,12 @@ PLyObject_ToDatum(PLyTypeInfo *info, } PG_CATCH(); { - Py_XDECREF(plrv_so); + Py_XDECREF(plrv_bo); PG_RE_THROW(); } PG_END_TRY(); - Py_XDECREF(plrv_so); + Py_XDECREF(plrv_bo); return rv; } @@ -2368,8 +2422,7 @@ static PyMethodDef PLy_plan_methods[] = { }; static PyTypeObject PLy_PlanType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) "PLyPlan", /* tp_name */ sizeof(PLyPlanObject), /* tp_size */ 0, /* tp_itemsize */ @@ -2420,8 +2473,7 @@ static PyMethodDef PLy_result_methods[] = { }; static PyTypeObject PLy_ResultType = { - PyObject_HEAD_INIT(NULL) - 0, /* ob_size */ + PyVarObject_HEAD_INIT(NULL, 0) "PLyResult", /* tp_name */ sizeof(PLyResultObject), /* tp_size */ 0, /* tp_itemsize */ @@ -2480,6 +2532,15 @@ static PyMethodDef PLy_methods[] = { {NULL, NULL, 0, NULL} }; +#if PY_MAJOR_VERSION >= 3 +static PyModuleDef PLy_module = { + PyModuleDef_HEAD_INIT, /* m_base */ + "plpy", /* m_name */ + NULL, /* m_doc */ + -1, /* m_size */ + PLy_methods, /* m_methods */ +}; +#endif /* plan object methods */ static PyObject * @@ -3067,6 +3128,15 @@ PLy_spi_execute_fetch_result(SPITupleTable *tuptable, int rows, int status) * language handler and interpreter initialization */ +#if PY_MAJOR_VERSION >= 3 +static PyMODINIT_FUNC +PyInit_plpy(void) +{ + return PyModule_Create(&PLy_module); +} +#endif + + /* * _PG_init() - library load-time initialization * @@ -3083,7 +3153,13 @@ _PG_init(void) pg_bindtextdomain(TEXTDOMAIN); +#if PY_MAJOR_VERSION >= 3 + PyImport_AppendInittab("plpy", PyInit_plpy); +#endif Py_Initialize(); +#if PY_MAJOR_VERSION >= 3 + PyImport_ImportModule("plpy"); +#endif PLy_init_interp(); PLy_init_plpy(); if (PyErr_Occurred()) @@ -3129,7 +3205,11 @@ PLy_init_plpy(void) if (PyType_Ready(&PLy_ResultType) < 0) elog(ERROR, "could not initialize PLy_ResultType"); +#if PY_MAJOR_VERSION >= 3 + plpy = PyModule_Create(&PLy_module); +#else plpy = Py_InitModule("plpy", PLy_methods); +#endif plpy_dict = PyModule_GetDict(plpy); /* PyDict_SetItemString(plpy, "PlanType", (PyObject *) &PLy_PlanType); */ @@ -3475,12 +3555,29 @@ PLy_free(void *ptr) } /* - * Convert a Python unicode object to a Python string object in + * Convert a Unicode object to a Python string. + */ +static PyObject* +PLyUnicode_Str(PyObject *unicode) +{ +#if PY_MAJOR_VERSION >= 3 + /* In Python 3, this is a noop. */ + Py_INCREF(unicode); + return unicode; +#else + /* In Python 2, this means converting the Unicode to bytes in the + * server encoding. */ + return PLyUnicode_Bytes(unicode); +#endif +} + +/* + * Convert a Python unicode object to a Python string/bytes object in * PostgreSQL server encoding. Reference ownership is passed to the * caller. */ static PyObject* -PLyUnicode_Str(PyObject *unicode) +PLyUnicode_Bytes(PyObject *unicode) { PyObject *rv; const char *serverenc; @@ -3502,13 +3599,44 @@ PLyUnicode_Str(PyObject *unicode) /* * Convert a Python unicode object to a C string in PostgreSQL server * encoding. No Python object reference is passed out of this - * function. + * function. The result is palloc'ed. + * + * Note that this function is disguised as PyString_AsString() when + * using Python 3. That function retuns a pointer into the internal + * memory of the argument, which isn't exactly the interface of this + * function. But in either case you get a rather short-lived + * reference that you ought to better leave alone. */ static char * PLyUnicode_AsString(PyObject *unicode) { - PyObject *o = PLyUnicode_Str(unicode); - char *rv = PyString_AsString(o); + PyObject *o = PLyUnicode_Bytes(unicode); + char *rv = pstrdup(PyBytes_AsString(o)); Py_XDECREF(o); return rv; } + +#if PY_MAJOR_VERSION >= 3 +/* + * Convert a C string in the PostgreSQL server encoding to a Python + * unicode object. Reference ownership is passed to the caller. + */ +static PyObject * +PLyUnicode_FromString(const char *s) +{ + char *utf8string; + PyObject *o; + + utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, + strlen(s), + GetDatabaseEncoding(), + PG_UTF8); + + o = PyUnicode_FromString(utf8string); + + if (utf8string != s) + pfree(utf8string); + + return o; +} +#endif /* PY_MAJOR_VERSION >= 3 */ diff --git a/src/pl/plpython/sql/plpython_test.sql b/src/pl/plpython/sql/plpython_test.sql index 161399f2ec..7cae124d98 100644 --- a/src/pl/plpython/sql/plpython_test.sql +++ b/src/pl/plpython/sql/plpython_test.sql @@ -1,10 +1,15 @@ -- first some tests of basic functionality +CREATE LANGUAGE plpython2u; -- really stupid function just to get the module loaded CREATE FUNCTION stupid() RETURNS text AS 'return "zarkon"' LANGUAGE plpythonu; select stupid(); +-- check 2/3 versioning +CREATE FUNCTION stupidn() RETURNS text AS 'return "zarkon"' LANGUAGE plpython2u; + +select stupidn(); -- test multiple arguments CREATE FUNCTION argument_test_one(u users, a1 text, a2 text) RETURNS text diff --git a/src/pl/plpython/sql/plpython_trigger.sql b/src/pl/plpython/sql/plpython_trigger.sql index d6f441f827..c60a673780 100644 --- a/src/pl/plpython/sql/plpython_trigger.sql +++ b/src/pl/plpython/sql/plpython_trigger.sql @@ -67,7 +67,7 @@ SELECT * FROM users; CREATE TABLE trigger_test (i int, v text ); -CREATE FUNCTION trigger_data() returns trigger language plpythonu as $$ +CREATE FUNCTION trigger_data() RETURNS trigger LANGUAGE plpythonu AS $$ if 'relid' in TD: TD['relid'] = "bogus:12345" -- GitLab