diff --git a/configure b/configure
index d2c74e5..3feebf6 100755
--- a/configure
+++ b/configure
@@ -714,6 +714,7 @@ with_ldap
with_krb_srvnam
krb_srvtab
with_gssapi
+with_python2_stub
with_python
with_perl
with_tcl
@@ -847,6 +848,7 @@ with_tcl
with_tclconfig
with_perl
with_python
+with_python2_stub
with_gssapi
with_krb_srvnam
with_pam
@@ -1546,6 +1548,7 @@ Optional Packages:
--with-tclconfig=DIR tclConfig.sh is in DIR
--with-perl build Perl modules (PL/Perl)
--with-python build Python modules (PL/Python)
+ --with-python2-stub build Python 2 compatibility stub
--with-gssapi build with GSSAPI support
--with-krb-srvnam=NAME default service principal name in Kerberos (GSSAPI)
[postgres]
@@ -7644,6 +7647,32 @@ fi
$as_echo "$with_python" >&6; }
+
+
+
+# Check whether --with-python2-stub was given.
+if test "${with_python2_stub+set}" = set; then :
+ withval=$with_python2_stub;
+ case $withval in
+ yes)
+ :
+ ;;
+ no)
+ :
+ ;;
+ *)
+ as_fn_error $? "no argument expected for --with-python2-stub option" "$LINENO" 5
+ ;;
+ esac
+
+else
+ with_python2_stub=no
+
+fi
+
+
+
+
#
# GSSAPI
#
@@ -9745,6 +9774,12 @@ $as_echo "${python_libspec} ${python_additional_libs}" >&6; }
+ # Disable building Python 2 stub if primary version isn't Python 3
+ if test "$python_majorversion" -lt 3; then
+ with_python2_stub=no
+ fi
+else
+ with_python2_stub=no
fi
if test "$cross_compiling" = yes && test -z "$with_system_tzdata"; then
diff --git a/configure.in b/configure.in
index 0b0a871..ffa49c9 100644
--- a/configure.in
+++ b/configure.in
@@ -766,6 +766,9 @@ PGAC_ARG_BOOL(with, python, no, [build Python modules (PL/Python)])
AC_MSG_RESULT([$with_python])
AC_SUBST(with_python)
+PGAC_ARG_BOOL(with, python2-stub, no, [build Python 2 compatibility stub])
+AC_SUBST(with_python2_stub)
+
#
# GSSAPI
#
@@ -1042,6 +1045,12 @@ fi
if test "$with_python" = yes; then
PGAC_PATH_PYTHON
PGAC_CHECK_PYTHON_EMBED_SETUP
+ # Disable building Python 2 stub if primary version isn't Python 3
+ if test "$python_majorversion" -lt 3; then
+ with_python2_stub=no
+ fi
+else
+ with_python2_stub=no
fi
if test "$cross_compiling" = yes && test -z "$with_system_tzdata"; then
diff --git a/doc/src/sgml/installation.sgml b/doc/src/sgml/installation.sgml
index cc242dc..ff81ea8 100644
--- a/doc/src/sgml/installation.sgml
+++ b/doc/src/sgml/installation.sgml
@@ -877,6 +877,23 @@ build-postgresql:
+
+
+
+ Build a stub version of the Python
+ 2 PL/Python language, to aid in
+ transitioning old PL/Python code.
+ This option is recommended if you are
+ building PL/Python for Python 3
+ and do not intend to also build a version for Python 2.
+ This option is ignored if you do not also
+ specify , or if the selected Python
+ implementation is Python 2.
+
+
+
+
+
diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index ac989a3..9014750 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -77,13 +77,7 @@
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.
+ determined by the person who builds and installs PostgreSQL.
@@ -103,9 +97,22 @@
The built variant depends on which Python version was found during
the installation or which version was explicitly set using
the PYTHON environment variable;
- see . To make both variants of
- PL/Python available in one installation, the source tree has to be
- configured and built twice.
+ see . To make working versions of
+ both variants of PL/Python available in one installation, the source
+ tree has to be configured and built twice.
+
+
+
+ However, if the builder specifies both
+ and while building with Python 3,
+ then in addition to the working Python-3-based PL/Python,
+ a stub version of plpython2u is
+ produced. This stub is non-functional and will simply throw errors if
+ any plpython2u function is executed, but its
+ presence allows such functions to be loaded into the database in
+ preparation for conversion to plpython3u. This
+ configuration also causes plpythonu to become
+ Python 3 rather than Python 2.
@@ -115,49 +122,38 @@
- 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.
+ Use the language name plpythonu if you have
+ simple Python code that works for either Python 2 or Python 3;
+ or if you are prepared to migrate your code whenever your packager
+ decides to change the default Python version.
In practice, many PL/Python functions will migrate to Python 3
- with few or no changes.
+ with few or no changes. However, there are some cases that
+ will require more work.
- Users who know that they have heavily Python 2 dependent code
- and don't plan to ever change it can make use of
+ If you know that you have heavily Python 2 dependent code
+ and don't plan to ever change it, you 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.
+ continue to work for as long as your operating system platform
+ continues to provide Python 2. PostgreSQL itself may eventually
+ drop support for this option, but that is unlikely to happen
+ as long as any platform support remains in the wild.
- Daredevils, who want to build a Python-3-only operating system
- environment, can change the contents of
- plpythonu's extension control and script files
- 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.
+ If you know your code requires Python 3, use
+ the plpython3u language name. You can also
+ use this language name to tag functions that have been successfully
+ converted from Python 2, so as to keep track of which functions have
+ been converted and which have not.
@@ -171,6 +167,8 @@
Python major versions in a session, which will abort the session if
a mismatch is detected. It is possible, however, to use both
PL/Python variants in the same database, from separate sessions.
+ (Also, the stub version of plpython2u
+ does not present any conflicts.)
diff --git a/src/Makefile.global.in b/src/Makefile.global.in
index e4db3e8..1f880b6 100644
--- a/src/Makefile.global.in
+++ b/src/Makefile.global.in
@@ -180,6 +180,7 @@ bitcodedir = $(pkglibdir)/bitcode
with_icu = @with_icu@
with_perl = @with_perl@
with_python = @with_python@
+with_python2_stub = @with_python2_stub@
with_tcl = @with_tcl@
with_openssl = @with_openssl@
with_readline = @with_readline@
diff --git a/src/pl/Makefile b/src/pl/Makefile
index c4a0d1c..fe9cd0f 100644
--- a/src/pl/Makefile
+++ b/src/pl/Makefile
@@ -26,6 +26,12 @@ else
ALWAYS_SUBDIRS += plpython
endif
+ifeq ($(with_python2_stub), yes)
+SUBDIRS += stub_plpython2
+else
+ALWAYS_SUBDIRS += stub_plpython2
+endif
+
ifeq ($(with_tcl), yes)
SUBDIRS += tcl
else
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index 03f858b..ac262fa 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -37,6 +37,9 @@ OBJS = \
DATA = $(NAME)u.control $(NAME)u--1.0.sql
ifeq ($(python_majorversion),2)
DATA += plpythonu.control plpythonu--1.0.sql
+else ifeq ($(with_python2_stub), yes)
+# install extension files for the stub module (see ../stub_plpython2)
+DATA += plpythonu.control plpythonu--1.0.sql plpython2u.control plpython2u--1.0.sql
endif
ifeq ($(python_majorversion),3)
DATA += convert_python3.control convert_python3--1.0.sql
@@ -109,6 +112,7 @@ REGRESS = \
plpython_composite \
plpython_subtransaction \
plpython_transaction \
+ plpython_stub \
plpython_drop
REGRESS_PLPYTHON3_MANGLE := $(REGRESS)
diff --git a/src/pl/plpython/expected/plpython_stub.out b/src/pl/plpython/expected/plpython_stub.out
new file mode 100644
index 0000000..2a565b4
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_stub.out
@@ -0,0 +1,71 @@
+--
+-- Test stub plpython2 and convert_python3 extension
+--
+-- skip test unless we have python3 installed and the others are available
+SELECT (SELECT count(*) FROM pg_extension WHERE extname = 'plpython3u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpython2u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpythonu') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'convert_python3') = 0
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+-- Funny formatting of some commands below is to prevent
+-- regress-python3-mangle.mk from being "helpful".
+-- install the additional extensions
+CREATE EXTENSION
+plpython2u;
+CREATE EXTENSION
+plpythonu;
+CREATE EXTENSION convert_python3;
+-- create some functions that need conversion
+create function convert1() returns int
+as 'return 123l'
+language
+plpython2u
+immutable;
+-- disable syntax checking since if plpythonu is python 3, this'll fail
+set check_function_bodies = false;
+create function convert2() returns int
+as 'return 123l'
+language
+plpythonu
+immutable;
+-- can't use \sf here because mangling would affect the LANGUAGE clauses
+select proname, lanname, prosrc
+from pg_proc p join pg_language l on prolang = l.oid
+where proname = 'convert1';
+ proname | lanname | prosrc
+----------+------------+-------------
+ convert1 | plpython2u | return 123l
+(1 row)
+
+select proname, lanname, prosrc
+from pg_proc p join pg_language l on prolang = l.oid
+where proname = 'convert2';
+ proname | lanname | prosrc
+----------+-----------+-------------
+ convert2 | plpythonu | return 123l
+(1 row)
+
+call convert_python3_all();
+NOTICE: converting function convert1()
+NOTICE: converting function convert2()
+\sf convert1()
+CREATE OR REPLACE FUNCTION public.convert1()
+ RETURNS integer
+ LANGUAGE plpython3u
+ IMMUTABLE
+AS $function$return 123$function$
+\sf convert2()
+CREATE OR REPLACE FUNCTION public.convert2()
+ RETURNS integer
+ LANGUAGE plpython3u
+ IMMUTABLE
+AS $function$return 123$function$
+-- clean up
+DROP EXTENSION
+plpython2u;
+DROP EXTENSION
+plpythonu;
+DROP EXTENSION convert_python3;
diff --git a/src/pl/plpython/expected/plpython_stub_1.out b/src/pl/plpython/expected/plpython_stub_1.out
new file mode 100644
index 0000000..bb625b9
--- /dev/null
+++ b/src/pl/plpython/expected/plpython_stub_1.out
@@ -0,0 +1,11 @@
+--
+-- Test stub plpython2 and convert_python3 extension
+--
+-- skip test unless we have python3 installed and the others are available
+SELECT (SELECT count(*) FROM pg_extension WHERE extname = 'plpython3u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpython2u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpythonu') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'convert_python3') = 0
+ AS skip_test \gset
+\if :skip_test
+\quit
diff --git a/src/pl/plpython/sql/plpython_stub.sql b/src/pl/plpython/sql/plpython_stub.sql
new file mode 100644
index 0000000..8c587fd
--- /dev/null
+++ b/src/pl/plpython/sql/plpython_stub.sql
@@ -0,0 +1,60 @@
+--
+-- Test stub plpython2 and convert_python3 extension
+--
+
+-- skip test unless we have python3 installed and the others are available
+SELECT (SELECT count(*) FROM pg_extension WHERE extname = 'plpython3u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpython2u') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'plpythonu') = 0 OR
+ (SELECT count(*) FROM pg_available_extensions WHERE name = 'convert_python3') = 0
+ AS skip_test \gset
+\if :skip_test
+\quit
+\endif
+
+-- Funny formatting of some commands below is to prevent
+-- regress-python3-mangle.mk from being "helpful".
+
+-- install the additional extensions
+CREATE EXTENSION
+plpython2u;
+CREATE EXTENSION
+plpythonu;
+CREATE EXTENSION convert_python3;
+
+-- create some functions that need conversion
+create function convert1() returns int
+as 'return 123l'
+language
+plpython2u
+immutable;
+
+-- disable syntax checking since if plpythonu is python 3, this'll fail
+set check_function_bodies = false;
+
+create function convert2() returns int
+as 'return 123l'
+language
+plpythonu
+immutable;
+
+-- can't use \sf here because mangling would affect the LANGUAGE clauses
+select proname, lanname, prosrc
+from pg_proc p join pg_language l on prolang = l.oid
+where proname = 'convert1';
+
+select proname, lanname, prosrc
+from pg_proc p join pg_language l on prolang = l.oid
+where proname = 'convert2';
+
+call convert_python3_all();
+
+\sf convert1()
+\sf convert2()
+
+-- clean up
+DROP EXTENSION
+plpython2u;
+DROP EXTENSION
+plpythonu;
+DROP EXTENSION convert_python3;
diff --git a/src/pl/stub_plpython2/Makefile b/src/pl/stub_plpython2/Makefile
new file mode 100644
index 0000000..67e7559
--- /dev/null
+++ b/src/pl/stub_plpython2/Makefile
@@ -0,0 +1,38 @@
+# src/pl/stub_plpython2/Makefile
+
+# Note that this Makefile only builds and installs a quasi-dummy
+# version of plpython2.so. The control and script files for the
+# plpythonu and plpython2u extensions are installed by ../plpython;
+# they are the same whether we're using real or stub plpython2.
+# (We'd probably not have this separate subdirectory at all, except
+# that Makefile.shlib can only build one shlib per directory.)
+
+subdir = src/pl/stub_plpython2
+top_builddir = ../../..
+include $(top_builddir)/src/Makefile.global
+
+PGFILEDESC = "PL/Python - procedural language stub for Python 2"
+
+NAME = plpython2
+
+OBJS = \
+ $(WIN32RES) \
+ stub_plpython2.o
+
+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
+
+installdirs: installdirs-lib
+
+uninstall: uninstall-lib
+
+clean distclean: clean-lib
+ rm -f $(OBJS)
+
+maintainer-clean: distclean
diff --git a/src/pl/stub_plpython2/stub_plpython2.c b/src/pl/stub_plpython2/stub_plpython2.c
new file mode 100644
index 0000000..4ade88b
--- /dev/null
+++ b/src/pl/stub_plpython2/stub_plpython2.c
@@ -0,0 +1,105 @@
+/*
+ * PL/Python stub for Python 2, in an environment that has only Python 3
+ *
+ * Our strategy is to pass through "plpythonu" functions to Python 3,
+ * but throw a not-implemented error for "plpython2u".
+ *
+ * Pass-through is implemented by using dfmgr.c to look up the appropriate
+ * function in plpython3.so, rather than trying to resolve the reference
+ * directly. This greatly simplifies building this as an independent
+ * shared library, and it ensures that we can't somehow pull in a different
+ * version of plpython3 (and thence libpython) than would get loaded for
+ * a plpython3u function.
+ *
+ * src/pl/stub_plpython2/stub_plpython2.c
+ */
+
+#include "postgres.h"
+
+#include "fmgr.h"
+
+#define PLPYTHON_LIBNAME "$libdir/plpython3"
+
+/*
+ * exported functions
+ */
+
+PG_MODULE_MAGIC;
+
+PG_FUNCTION_INFO_V1(plpython_validator);
+PG_FUNCTION_INFO_V1(plpython_call_handler);
+PG_FUNCTION_INFO_V1(plpython_inline_handler);
+
+PG_FUNCTION_INFO_V1(plpython2_validator);
+PG_FUNCTION_INFO_V1(plpython2_call_handler);
+PG_FUNCTION_INFO_V1(plpython2_inline_handler);
+
+
+Datum
+plpython_validator(PG_FUNCTION_ARGS)
+{
+ static PGFunction plpython3_validator = NULL;
+
+ if (plpython3_validator == NULL)
+ plpython3_validator =
+ load_external_function(PLPYTHON_LIBNAME,
+ "plpython3_validator",
+ true, NULL);
+
+ return (*plpython3_validator) (fcinfo);
+}
+
+Datum
+plpython_call_handler(PG_FUNCTION_ARGS)
+{
+ static PGFunction plpython3_call_handler = NULL;
+
+ if (plpython3_call_handler == NULL)
+ plpython3_call_handler =
+ load_external_function(PLPYTHON_LIBNAME,
+ "plpython3_call_handler",
+ true, NULL);
+
+ return (*plpython3_call_handler) (fcinfo);
+}
+
+Datum
+plpython_inline_handler(PG_FUNCTION_ARGS)
+{
+ static PGFunction plpython3_inline_handler = NULL;
+
+ if (plpython3_inline_handler == NULL)
+ plpython3_inline_handler =
+ load_external_function(PLPYTHON_LIBNAME,
+ "plpython3_inline_handler",
+ true, NULL);
+
+ return (*plpython3_inline_handler) (fcinfo);
+}
+
+Datum
+plpython2_validator(PG_FUNCTION_ARGS)
+{
+ /* It seems more convenient to do nothing here than throw an error. */
+ PG_RETURN_VOID();
+}
+
+Datum
+plpython2_call_handler(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Python 2 is no longer supported"),
+ errhint("Convert the function to use plpython3u.")));
+ PG_RETURN_NULL(); /* keep compiler quiet */
+}
+
+Datum
+plpython2_inline_handler(PG_FUNCTION_ARGS)
+{
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("Python 2 is no longer supported"),
+ errhint("Convert the DO block to use plpython3u.")));
+ PG_RETURN_NULL(); /* keep compiler quiet */
+}
diff --git a/src/tools/msvc/Install.pm b/src/tools/msvc/Install.pm
index 1a92ed2..cf8b1e2 100644
--- a/src/tools/msvc/Install.pm
+++ b/src/tools/msvc/Install.pm
@@ -154,6 +154,7 @@ sub Install
my @pldirs = ('src/pl/plpgsql/src');
push @pldirs, "src/pl/plperl" if $config->{perl};
push @pldirs, "src/pl/plpython" if $config->{python};
+ push @pldirs, "src/pl/stub_plpython2" if $config->{python2_stub};
push @pldirs, "src/pl/tcl" if $config->{tcl};
File::Find::find(
{
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 727a8fd..1d28607 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -531,7 +531,12 @@ sub mkvcbuild
'ltree', 'contrib');
$ltree_plpython->AddDefine(
'PLPYTHON_LIBNAME="plpython' . $pymajorver . '"');
+
+ # Ignore --with-python2-stub if building real plpython2
+ $solution->{options}->{python2_stub} = 0 if $pymajorver < 3;
}
+ else
+ $solution->{options}->{python2_stub} = 0;
if ($solution->{options}->{perl})
{
diff --git a/src/tools/msvc/Solution.pm b/src/tools/msvc/Solution.pm
index 6b4a6ee..848e118 100644
--- a/src/tools/msvc/Solution.pm
+++ b/src/tools/msvc/Solution.pm
@@ -1145,6 +1145,7 @@ sub GetFakeConfigure
$cfg .= ' --with-tcl' if ($self->{options}->{tcl});
$cfg .= ' --with-perl' if ($self->{options}->{perl});
$cfg .= ' --with-python' if ($self->{options}->{python});
+ $cfg .= ' --with-python2-stub' if ($self->{options}->{python2_stub});
return $cfg;
}
diff --git a/src/tools/msvc/clean.bat b/src/tools/msvc/clean.bat
index 672bb2d..4ad5f07 100755
--- a/src/tools/msvc/clean.bat
+++ b/src/tools/msvc/clean.bat
@@ -26,6 +26,7 @@ if exist src\interfaces\ecpg\test\win32ver.rc del /q src\interfaces\ecpg\test\wi
if exist src\pl\plperl\win32ver.rc del /q src\pl\plperl\win32ver.rc
if exist src\pl\plpgsql\src\win32ver.rc del /q src\pl\plpgsql\src\win32ver.rc
if exist src\pl\plpython\win32ver.rc del /q src\pl\plpython\win32ver.rc
+if exist src\pl\stub_plpython2\win32ver.rc del /q src\pl\stub_plpython2\win32ver.rc
if exist src\pl\tcl\win32ver.rc del /q src\pl\tcl\win32ver.rc
if exist src\test\isolation\win32ver.rc del /q src\test\isolation\win32ver.rc
if exist src\test\regress\win32ver.rc del /q src\test\regress\win32ver.rc
diff --git a/src/tools/msvc/config_default.pl b/src/tools/msvc/config_default.pl
index 2ef2cfc..d7f9b60 100644
--- a/src/tools/msvc/config_default.pl
+++ b/src/tools/msvc/config_default.pl
@@ -16,6 +16,7 @@ our $config = {
tcl => undef, # --with-tcl=
perl => undef, # --with-perl=
python => undef, # --with-python=
+ python2_stub => 1, # --with-python2-stub (ignored unless Python is v3)
openssl => undef, # --with-openssl=
uuid => undef, # --with-uuid=
xml => undef, # --with-libxml=