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 | 10303.1582663692@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Resolving the python 2 -> python 3 mess (Tom Lane <tgl@sss.pgh.pa.us>) |
List | pgsql-hackers |
I wrote: > Here's an updated pair of patches that attempt to fix the MSVC > scripts (pretty blindly) and provide a very simple regression test. A little *too* blindly, evidently. Try again ... 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 9e95285..03f858b 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -38,6 +38,9 @@ DATA = $(NAME)u.control $(NAME)u--1.0.sql ifeq ($(python_majorversion),2) DATA += plpythonu.control plpythonu--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..3444ac0 --- /dev/null +++ b/src/pl/plpython/convert_python3--1.0.sql @@ -0,0 +1,149 @@ +/* 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') +order by proname +""") + +# 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 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: </varlistentry> <varlistentry> + <term><option>--with-python2-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 ac989a3..9014750 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-python2-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..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..64d7431 100644 --- a/src/tools/msvc/Mkvcbuild.pm +++ b/src/tools/msvc/Mkvcbuild.pm @@ -531,6 +531,13 @@ 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=<path> perl => undef, # --with-perl=<path> python => undef, # --with-python=<path> + python2_stub => 1, # --with-python2-stub (ignored unless Python is v3) openssl => undef, # --with-openssl=<path> uuid => undef, # --with-uuid=<path> xml => undef, # --with-libxml=<path>
pgsql-hackers by date: