Re: Resolving the python 2 -> python 3 mess - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Resolving the python 2 -> python 3 mess |
Date | |
Msg-id | 21257.1582087168@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Resolving the python 2 -> python 3 mess (Corey Huinker <corey.huinker@gmail.com>) |
Responses |
Re: Resolving the python 2 -> python 3 mess
|
List | pgsql-hackers |
After thinking about this awhile longer, I'm starting to believe we should do some of each. That is, the stub replacement for plpython2.so should redirect "plpythonu" functions to plpython3.so, but throw errors for "plpython2u" functions. This is not because of any technical difference between plpythonu and plpython2u --- up to now, there wasn't any --- but because it seems like users would be expecting that if they've read what we have said in https://www.postgresql.org/docs/current/plpython-python23.html Admittedly, what it says there is that plpythonu might become Python 3 in some "distant" future release, not next year. But at least there's a direct line between that documentation and this behavior. So attached is a pair of draft patches that do it like that. 0001 creates an extension with two conversion functions, based on the script I showed in the other thread. Almost independently of that, 0002 provides code to generate a stub version of plpython2.so that behaves as stated above. 0002 is incomplete, because I haven't looked into what is needed in the MSVC build scripts. Maybe we could create some regression tests, too. But I think these are potentially committable with those additions, if people approve of this approach. regards, tom lane diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 1921915..ac989a3 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -164,13 +164,6 @@ </para> <para> - See also the - document <ulink url="https://docs.python.org/3/whatsnew/3.0.html">What's - New In Python 3.0</ulink> for more information about porting to - Python 3. - </para> - - <para> It is not allowed to use PL/Python based on Python 2 and PL/Python based on Python 3 in the same session, because the symbols in the dynamic modules would clash, which could result in crashes of the @@ -179,6 +172,90 @@ a mismatch is detected. It is possible, however, to use both PL/Python variants in the same database, from separate sessions. </para> + + <sect2 id="plpython-python3-conversion"> + <title>Converting from Python 2 to Python 3</title> + + <para> + See the + document <ulink url="https://docs.python.org/3/whatsnew/3.0.html">What's + New In Python 3.0</ulink> for the Python community's information and + recommendations about porting to Python 3. + </para> + + <para> + <productname>PostgreSQL</productname> provides some support for helping + you to convert existing Python 2 routines to Python 3. In an + installation built with Python 3, there is an + extension <filename>convert_python3</filename> that changes functions + and procedures from the <literal>plpythonu</literal> + and <literal>plpython2u</literal> languages to + the <literal>plpython3u</literal> language. While doing so, it applies + the <filename>2to3</filename> tool described in the above document to + the body of each such routine. + </para> + + <para> + Using <filename>convert_python3</filename> can be as simple as: +<programlisting> +CREATE EXTENSION convert_python3; +CALL convert_python3_all(); +</programlisting> + This must be done as database superuser. If you wish, you can drop the + extension once you're done converting everything. + </para> + + <para> + Since <filename>convert_python3</filename> is Python 3 code, be careful + not to install or run it in a session that has previously executed any + Python 2 code. As explained above, that won't work. + </para> + + <para> + <function>convert_python3_all</function> has two optional arguments: the + name of the conversion tool to use (by default <literal>2to3</literal>, + but you might for instance need to provide a full path name) and any + special command-line options to provide to it. You might for example + want to adjust the set of <quote>fixer</quote> rules + that <literal>2to3</literal> applies: +<programlisting> +CALL convert_python3_all(options => '-f idioms -x apply'); +</programlisting> + See <literal>2to3</literal>'s + <ulink url="https://docs.python.org/3/library/2to3.html">documentation</ulink> + for more information. + </para> + + <para> + The <filename>convert_python3</filename> extension also provides a + procedure that converts just one Python 2 function at a time: +<programlisting> +CALL convert_python3_one('myfunc(int)'); +</programlisting> + The argument is the target function's OID, which can be written as + a <type>regprocedure</type> constant (see + <xref linkend="datatype-oid"/>). The main reason to use this would be + if you need to use different options for different functions. It has + the same optional arguments as <function>convert_python3_all</function>: +<programlisting> +CALL convert_python3_one('otherfunc(text)', tool => '/usr/bin/2to3', + options => '-f idioms'); +</programlisting> + </para> + + <para> + If you have needs that go beyond this, consult the source code for + the <filename>convert_python3</filename> extension (it's just a + couple of <literal>plpython3u</literal> procedures) and adapt those + procedures as necessary. + </para> + + <para> + Keep in mind that if you've constructed any <command>DO</command> blocks + that use Python 2 code, those will have to be fixed up manually, + wherever the source code for them exists. + </para> + </sect2> </sect1> <sect1 id="plpython-funcs"> diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 0d53d3d..f0de8ff 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -38,6 +38,9 @@ DATA = $(NAME)u.control $(NAME)u--1.0.sql $(NAME)u--unpackaged--1.0.sql ifeq ($(python_majorversion),2) DATA += plpythonu.control plpythonu--1.0.sql plpythonu--unpackaged--1.0.sql endif +ifeq ($(python_majorversion),3) +DATA += convert_python3.control convert_python3--1.0.sql +endif # header files to install - it's not clear which of these might be needed # so install them all. diff --git a/src/pl/plpython/convert_python3--1.0.sql b/src/pl/plpython/convert_python3--1.0.sql new file mode 100644 index 0000000..ad5d86e --- /dev/null +++ b/src/pl/plpython/convert_python3--1.0.sql @@ -0,0 +1,148 @@ +/* convert_python3--1.0.sql */ + +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION convert_python3" to load this file. \quit + +-- This module provides two procedures, one to convert all python2 +-- functions and one to do just one. They're nearly identical, and +-- in principle convert_python3_all() could be written as a loop +-- around convert_python3_one(). It's not done that way since +-- creating a temp directory for each function in a bulk conversion +-- could get expensive. + +-- For some benighted reason, lib2to3 has exactly no documented API, +-- so we must use the command-line API "2to3" instead. User may pass +-- in the name of that program (in case it's not in the server's PATH) +-- as well as any desired options for it (perhaps some -f switches). + +create procedure convert_python3_all(tool text default '2to3', + options text default '') +language plpython3u as $$ +import re, subprocess, tempfile + +# pattern to extract just the function header from pg_get_functiondef result +aspat = re.compile("^(.*?\nAS )", re.DOTALL) +# pattern for replacing LANGUAGE portion +langpat = re.compile("\n LANGUAGE plpython2?u\n") + +# collect info about functions to update +rv = plpy.execute(""" +select p.oid::pg_catalog.regprocedure as funcid, + pg_catalog.pg_get_functiondef(p.oid) as fd, + prosrc as body +from pg_catalog.pg_proc p join pg_catalog.pg_language l on p.prolang = l.oid +where lanname in ('plpythonu', 'plpython2u') +""") + +# Make a temp directory to hold the file for 2to3 to work on. +with tempfile.TemporaryDirectory() as tmpdirname: + + # process each function + for r in rv: + # emit notices so user can tell which function failed, if one does + plpy.notice("converting function " + r["funcid"]) + + # extract everything but the body from pg_get_functiondef result + m = aspat.match(r["fd"]) + if not m: + raise ValueError('unexpected match failure') + fheader = m.group(1) + + # replace the language clause + fheader = langpat.sub("\n LANGUAGE plpython3u\n", fheader, 1) + + # put body in a temp file so we can apply 2to3 + f = open(tmpdirname + "/temp.py", mode = 'w') + f.write(r["body"]) + f.close() + + # apply 2to3 to body + subprocess.check_call(tool + " " + options + " --no-diffs -w " + tmpdirname + "/temp.py", shell=True) + f = open(tmpdirname + "/temp.py", mode = 'r') + fbody = f.read() + f.close() + + # ensure check_function_bodies is enabled + plpy.execute("set local check_function_bodies = true") + + # construct and execute SQL command to replace the function + newstmt = fheader + plpy.quote_literal(fbody) + # uncomment this for debugging purposes: + # plpy.info(newstmt) + plpy.execute(newstmt) + + # commit after each successful replacement, in case a later one fails + plpy.commit() +$$; + +-- The above procedure has to be superuser-only since it trivially allows +-- executing random programs. But you'd have to be superuser anyway +-- to replace the definitions of plpython functions. + +revoke all on procedure convert_python3_all(text, text) from public; + + +-- Here's the one-function version. + +create procedure convert_python3_one(funcid regprocedure, + tool text default '2to3', + options text default '') +language plpython3u as $$ +import re, subprocess, tempfile + +# pattern to extract just the function header from pg_get_functiondef result +aspat = re.compile("^(.*?\nAS )", re.DOTALL) +# pattern for replacing LANGUAGE portion +langpat = re.compile("\n LANGUAGE plpython2?u\n") + +# collect info about function to update, making sure it's the right language +plan = plpy.prepare(""" +select p.oid::pg_catalog.regprocedure as funcid, + pg_catalog.pg_get_functiondef(p.oid) as fd, + prosrc as body +from pg_catalog.pg_proc p join pg_catalog.pg_language l on p.prolang = l.oid +where p.oid = $1 and lanname in ('plpythonu', 'plpython2u') +""", ["pg_catalog.regprocedure"]) + +rv = plpy.execute(plan, [funcid]) + +# Make a temp directory to hold the file for 2to3 to work on. +with tempfile.TemporaryDirectory() as tmpdirname: + + # process each function (we only expect one, but it's easy to loop) + for r in rv: + # extract everything but the body from pg_get_functiondef result + m = aspat.match(r["fd"]) + if not m: + raise ValueError('unexpected match failure') + fheader = m.group(1) + + # replace the language clause + fheader = langpat.sub("\n LANGUAGE plpython3u\n", fheader, 1) + + # put body in a temp file so we can apply 2to3 + f = open(tmpdirname + "/temp.py", mode = 'w') + f.write(r["body"]) + f.close() + + # apply 2to3 to body + subprocess.check_call(tool + " " + options + " --no-diffs -w " + tmpdirname + "/temp.py", shell=True) + f = open(tmpdirname + "/temp.py", mode = 'r') + fbody = f.read() + f.close() + + # ensure check_function_bodies is enabled + plpy.execute("set local check_function_bodies = true") + + # construct and execute SQL command to replace the function + newstmt = fheader + plpy.quote_literal(fbody) + # uncomment this for debugging purposes: + # plpy.info(newstmt) + plpy.execute(newstmt) +$$; + +-- The above procedure has to be superuser-only since it trivially allows +-- executing random programs. But you'd have to be superuser anyway +-- to replace the definitions of plpython functions. + +revoke all on procedure convert_python3_one(regprocedure, text, text) from public; diff --git a/src/pl/plpython/convert_python3.control b/src/pl/plpython/convert_python3.control new file mode 100644 index 0000000..8debb3b --- /dev/null +++ b/src/pl/plpython/convert_python3.control @@ -0,0 +1,5 @@ +# convert_python3 extension +comment = 'convert plpython[2]u functions to plpython3u' +default_version = '1.0' +relocatable = true +requires = 'plpython3u' diff --git a/configure b/configure index 37aa82d..4887dee 100755 --- a/configure +++ b/configure @@ -714,6 +714,7 @@ with_ldap with_krb_srvnam krb_srvtab with_gssapi +with_python_stub with_python with_perl with_tcl @@ -847,6 +848,7 @@ with_tcl with_tclconfig with_perl with_python +with_python_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-python-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-python-stub was given. +if test "${with_python_stub+set}" = set; then : + withval=$with_python_stub; + case $withval in + yes) + : + ;; + no) + : + ;; + *) + as_fn_error $? "no argument expected for --with-python-stub option" "$LINENO" 5 + ;; + esac + +else + with_python_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_python_stub=no + fi +else + with_python_stub=no fi if test "$cross_compiling" = yes && test -z "$with_system_tzdata"; then diff --git a/configure.in b/configure.in index 8adb409..ba2ff2d 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, python-stub, no, [build Python 2 compatibility stub]) +AC_SUBST(with_python_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_python_stub=no + fi +else + with_python_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 f1adcc3..2cf34d2 100644 --- a/doc/src/sgml/installation.sgml +++ b/doc/src/sgml/installation.sgml @@ -877,6 +877,23 @@ build-postgresql: </varlistentry> <varlistentry> + <term><option>--with-python-stub</option></term> + <listitem> + <para> + Build a stub version of the Python + 2 <application>PL/Python</application> language, to aid in + transitioning old <application>PL/Python</application> code. + This option is recommended if you are + building <application>PL/Python</application> 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 <option>--with-python</option>, or if the selected Python + implementation is Python 2. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><option>--with-tcl</option></term> <listitem> <para> diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml index 1921915..14b9a4b 100644 --- a/doc/src/sgml/plpython.sgml +++ b/doc/src/sgml/plpython.sgml @@ -77,13 +77,7 @@ <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. + determined by the person who builds and installs PostgreSQL. </para> </listitem> </itemizedlist> @@ -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 <envar>PYTHON</envar> environment variable; - see <xref linkend="install-procedure"/>. To make both variants of - PL/Python available in one installation, the source tree has to be - configured and built twice. + see <xref linkend="install-procedure"/>. To make working versions of + both variants of PL/Python available in one installation, the source + tree has to be configured and built twice. + </para> + + <para> + However, if the builder specifies both <option>--with-python</option> + and <option>--with-python-stub</option> while building with Python 3, + then in addition to the working Python-3-based PL/Python, + a <quote>stub</quote> version of <literal>plpython2u</literal> is + produced. This stub is non-functional and will simply throw errors if + any <literal>plpython2u</literal> function is executed, but its + presence allows such functions to be loaded into the database in + preparation for conversion to <literal>plpython3u</literal>. This + configuration also causes <literal>plpythonu</literal> to become + Python 3 rather than Python 2. </para> </tip> @@ -115,49 +122,38 @@ <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. + Use the language name <literal>plpythonu</literal> 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. </para> <para> 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. </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 + 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 <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. + 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. </para> </listitem> <listitem> <para> - Daredevils, who want to build a Python-3-only operating system - environment, can change the contents of - <literal>plpythonu</literal>'s extension control and script files - 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. + If you know your code requires Python 3, use + the <literal>plpython3u</literal> 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. </para> </listitem> </itemizedlist> @@ -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 <quote>stub</quote> version of <literal>plpython2u</literal> + does not present any conflicts.) </para> <sect2 id="plpython-python3-conversion"> diff --git a/src/Makefile.global.in b/src/Makefile.global.in index e4db3e8..f039fb4 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_python_stub = @with_python_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..c011607 100644 --- a/src/pl/Makefile +++ b/src/pl/Makefile @@ -26,6 +26,12 @@ else ALWAYS_SUBDIRS += plpython endif +ifeq ($(with_python_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 0d53d3d..e40844b 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 $(NAME)u--unpackaged--1.0.sql ifeq ($(python_majorversion),2) DATA += plpythonu.control plpythonu--1.0.sql plpythonu--unpackaged--1.0.sql +else ifeq ($(with_python_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 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 */ +}
pgsql-hackers by date: