Here's the patch to support Python >=3.1 with PL/Python. The compatibility code is mostly in line with the usual 2->3 C porting practice and is documented inline.
I needed to create an arguably weird hack to manage the regression tests. Instead of creating a new expected file for pretty much every test file and also for some input files (where Python syntax had changed), a sed script creates a complete Python 3 compatible set of input and output files. Doesn't look pretty but works quite well. If anyone has a better idea, please let me know.
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile index 373bc79..840b874 100644 --- a/src/pl/plpython/Makefile +++ b/src/pl/plpython/Makefile @@ -88,8 +88,32 @@ installdirs: installdirs-lib uninstall: uninstall-lib +ifneq (,$(findstring 3.,$(python_version))) +# Adjust regression tests for Python 3 compatibility +prep3: + mkdir -p 3 3/sql 3/expected + for file in $(srcdir)/sql/* $(srcdir)/expected/*; do \ + sed -r -e 's/except ([[:alpha:].]+), ?(\w+):/except \1 as \2:/g' \ + -e "s/<type 'exceptions\.(\w+)'>/<class '\1'>/g" \ + -e "s/<type 'long'>/<class 'int'>/g" \ + -e "s/\b([0-9]+)L\b/\1/g" \ + -e 's/\bu"/"/g' \ + -e "s/\bu'/'/g" \ + -e "s/def next\b/def __next__/g" \ + $$file >`echo $$file | sed 's,$(srcdir),3,'`; \ + done + +clean3: + rm -rf 3/ + +installcheck: submake prep3 + cd 3 && $(top_builddir)/../src/test/regress/pg_regress --inputdir=. --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_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 6fd4aca..0d6c261 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" @@ -240,7 +282,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 *); @@ -282,7 +328,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 *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); @@ -1723,7 +1769,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; @@ -1814,13 +1860,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 * @@ -1917,14 +1963,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); @@ -1956,22 +2002,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) @@ -1984,12 +2038,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; } @@ -2241,8 +2295,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 */ @@ -2293,8 +2346,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 */ @@ -2353,6 +2405,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 * @@ -2940,6 +3001,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 * @@ -2956,7 +3026,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()) @@ -3002,7 +3078,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); */ @@ -3348,12 +3428,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; @@ -3375,13 +3472,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 */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers