I wrote:
> On tor, 2009-11-12 at 16:06 -0500, Tom Lane wrote:
> > There was considerable debate earlier about whether we wanted to treat
> > Python 3 as a separate PL so it could be available in parallel with
> > plpython 2, because of the user-level coding incompatibilities. It
> > looks like this patch simply ignores that problem. What is going to
> > happen to plpython functions that depend on 2.x behavior?
>
> I have a proposal for how to handle this, and a prototype patch
> attached. This follows essentially what the CPython distribution itself
> does, which will make this tolerably easy to follow for users.
>
> We install plpython as plpython2.so or plpython3.so, depending on the
> version used to build it. Then, plpython.so is a symlink to
> plpython2.so.
So here is the potentially final patch for this, including the original
port of plpython.c itself, build system adjustments, and documentation.
Really attached this time.
diff --git a/config/python.m4 b/config/python.m4
index 9160a2b..32fff43 100644
--- a/config/python.m4
+++ b/config/python.m4
@@ -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 009a177..be51281 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 4746e68..f9221c9 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -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 502a5bc..e6c998a 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -14,7 +14,8 @@
<para>
To install PL/Python in a particular database, use
- <literal>createlang plpythonu <replaceable>dbname</></literal>.
+ <literal>createlang plpythonu <replaceable>dbname</></literal> (but
+ see also <xref linkend="plpython-python23">).
</para>
<tip>
@@ -42,6 +43,112 @@
</para>
</note>
+ <sect1 id="plpython-python23">
+ <title>Python 2 vs. Python 3</title>
+
+ <para>
+ 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:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ The PostgreSQL language named <literal>plpython2u</literal>
+ implements PL/Python based on the Python 2 language variant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The PostgreSQL language named <literal>plpython3u</literal>
+ implements PL/Python based on the Python 3 language variant.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ The language named <literal>plpythonu</literal> 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 <quote>default</quote>, for example,
+ what <filename>/usr/bin/python</filename> 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.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ It depends on the build configuration or the installed packages
+ whether PL/Python for Python 2 or Python 3 or both are available.
+ </para>
+
+ <para>
+ This results in the following usage and migration strategy:
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ Existing users and users who are currently not interested in
+ Python 3 use the language name <literal>plpythonu</literal> and
+ don't have to change anything for the foreseeable future. It is
+ recommended to gradually <quote>future-proof</quote> the code
+ via migration to Python 2.6/2.7 to simplify the eventual
+ migration to Python 3.
+ </para>
+
+ <para>
+ In practice, many PL/Python functions will migrate to Python 3
+ with few or no changes.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Users who know that they have heavily Python 2 dependent code
+ and don't plan to ever change it can make use of
+ the <literal>plpython2u</literal> language name. This will
+ continue to work into the very distant future, until Python 2
+ support might be completely dropped by PostgreSQL.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Users who want to dive into Python 3 can use
+ the <literal>plpython3u</literal> 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 <quote>3</quote> for aesthetic reasons.
+ </para>
+ </listitem>
+
+ <listitem>
+ <para>
+ Daredevils, who want to build a Python-3-only operating system
+ environment, can change the build scripts to
+ make <literal>plpythonu</literal> be equivalent
+ to <literal>plpython3u</literal>, keeping in mind that this
+ would make their installation incompatible with most of the rest
+ of the world.
+ </para>
+ </listitem>
+ </itemizedlist>
+ </para>
+
+ <para>
+ See also the
+ document <ulink url="http://docs.python.org/dev/3.0/whatsnew/3.0.html">What's
+ New In Python 3.0</ulink> for more information about porting to
+ Python 3.
+ </para>
+ </sect1>
+
<sect1 id="plpython-funcs">
<title>PL/Python Functions</title>
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index ca7f499..7a6e3a9 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -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 a2d1859..e0bd98b 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200912111
+#define CATALOG_VERSION_NO 200912131
#endif
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
index 8cdedb4..cbb0a33 100644
--- a/src/include/catalog/pg_pltemplate.h
+++ b/src/include/catalog/pg_pltemplate.h
@@ -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 373bc79..50b8ea1 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -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/* $(srcdir)/expected/*; do \
+ sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
+ -e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
+ -e "s/<type 'long'>/<class 'int'>/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 47f31e8..a187937 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 c5cfe5a..a229b18 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 cf5c758..3192ff1 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 0000000..3fcb0f4
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_types_3.out
@@ -0,0 +1,479 @@
+--
+-- 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, <class 'bool'>)
+CONTEXT: PL/Python function "test_type_conversion_bool"
+ test_type_conversion_bool
+---------------------------
+ t
+(1 row)
+
+SELECT * FROM test_type_conversion_bool(false);
+INFO: (False, <class 'bool'>)
+CONTEXT: PL/Python function "test_type_conversion_bool"
+ test_type_conversion_bool
+---------------------------
+ f
+(1 row)
+
+SELECT * FROM test_type_conversion_bool(null);
+INFO: (None, <class 'NoneType'>)
+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', <class 'str'>)
+CONTEXT: PL/Python function "test_type_conversion_char"
+ test_type_conversion_char
+---------------------------
+ a
+(1 row)
+
+SELECT * FROM test_type_conversion_char(null);
+INFO: (None, <class 'NoneType'>)
+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, <class 'int'>)
+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, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int2"
+ test_type_conversion_int2
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int2(null);
+INFO: (None, <class 'NoneType'>)
+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, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int4"
+ test_type_conversion_int4
+---------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4(-100);
+INFO: (-100, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int4"
+ test_type_conversion_int4
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int4(null);
+INFO: (None, <class 'NoneType'>)
+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: (100, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int8"
+ test_type_conversion_int8
+---------------------------
+ 100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(-100);
+INFO: (-100, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int8"
+ test_type_conversion_int8
+---------------------------
+ -100
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(5000000000);
+INFO: (5000000000, <class 'int'>)
+CONTEXT: PL/Python function "test_type_conversion_int8"
+ test_type_conversion_int8
+---------------------------
+ 5000000000
+(1 row)
+
+SELECT * FROM test_type_conversion_int8(null);
+INFO: (None, <class 'NoneType'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'NoneType'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'NoneType'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'float'>)
+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, <class 'NoneType'>)
+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', <class 'str'>)
+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, <class 'NoneType'>)
+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', <class 'bytes'>)
+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', <class 'bytes'>)
+CONTEXT: PL/Python function "test_type_conversion_bytea"
+ test_type_conversion_bytea
+----------------------------
+ \x6e756c6c0062797465
+(1 row)
+
+SELECT * FROM test_type_conversion_bytea(null);
+INFO: (None, <class 'NoneType'>)
+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 as 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, <class 'int'>)
+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, <class 'int'>)
+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, <class 'NoneType'>)
+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', <class 'bytes'>)
+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', <class 'bytes'>)
+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', <class 'bytes'>)
+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"
diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c
index 6a2a12f..fd69953 100644
--- a/src/pl/plpython/plpython.c
+++ b/src/pl/plpython/plpython.c
@@ -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 161399f..7cae124 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 d6f441f..c60a673 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"
--
Sent via pgsql-hackers mailing list ([email protected])
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers