summaryrefslogtreecommitdiff
path: root/src/pl
diff options
context:
space:
mode:
authorPeter Eisentraut2015-04-26 14:33:14 +0000
committerPeter Eisentraut2015-04-26 14:33:14 +0000
commitcac76582053ef8ea07df65fed0757f352da23705 (patch)
tree6ae01041aa61db9d686638b9d4c3ccd30d7c6487 /src/pl
parentf320cbb615e0374b18836337713239da58705cf3 (diff)
Add transforms feature
This provides a mechanism for specifying conversions between SQL data types and procedural languages. As examples, there are transforms for hstore and ltree for PL/Perl and PL/Python. reviews by Pavel Stěhule and Andres Freund
Diffstat (limited to 'src/pl')
-rw-r--r--src/pl/plperl/GNUmakefile4
-rw-r--r--src/pl/plperl/plperl.c47
-rw-r--r--src/pl/plperl/plperl_helpers.h2
-rw-r--r--src/pl/plpython/Makefile40
-rw-r--r--src/pl/plpython/plpy_main.c1
-rw-r--r--src/pl/plpython/plpy_procedure.c43
-rw-r--r--src/pl/plpython/plpy_procedure.h2
-rw-r--r--src/pl/plpython/plpy_spi.c3
-rw-r--r--src/pl/plpython/plpy_typeio.c159
-rw-r--r--src/pl/plpython/plpy_typeio.h9
-rw-r--r--src/pl/plpython/plpy_util.c21
-rw-r--r--src/pl/plpython/plpy_util.h1
-rw-r--r--src/pl/plpython/plpython.h1
-rw-r--r--src/pl/plpython/regress-python3-mangle.mk35
14 files changed, 261 insertions, 107 deletions
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 2b698477c26..cebffde79d7 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -99,15 +99,17 @@ Util.c: Util.xs plperl_helpers.h
install: all install-lib install-data
installdirs: installdirs-lib
- $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
uninstall: uninstall-lib uninstall-data
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(srcdir)/plperl.h $(srcdir)/ppport.h '$(DESTDIR)$(includedir_server)'
uninstall-data:
rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plperl.h ppport.h)
.PHONY: install-data uninstall-data
diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c
index e3dda5d63bc..840df2ee0b8 100644
--- a/src/pl/plperl/plperl.c
+++ b/src/pl/plperl/plperl.c
@@ -20,6 +20,7 @@
#include "access/xact.h"
#include "catalog/pg_language.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_proc_fn.h"
#include "catalog/pg_type.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
@@ -110,6 +111,8 @@ typedef struct plperl_proc_desc
SV *reference; /* CODE reference for Perl sub */
plperl_interp_desc *interp; /* interpreter it's created in */
bool fn_readonly; /* is function readonly (not volatile)? */
+ Oid lang_oid;
+ List *trftypes;
bool lanpltrusted; /* is it plperl, rather than plperlu? */
bool fn_retistuple; /* true, if function returns tuple */
bool fn_retisset; /* true, if function returns set */
@@ -210,6 +213,7 @@ typedef struct plperl_array_info
bool *nulls;
int *nelems;
FmgrInfo proc;
+ FmgrInfo transform_proc;
} plperl_array_info;
/**********************************************************************
@@ -1272,6 +1276,7 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
bool *isnull)
{
FmgrInfo tmp;
+ Oid funcid;
/* we might recurse */
check_stack_depth();
@@ -1295,6 +1300,8 @@ plperl_sv_to_datum(SV *sv, Oid typid, int32 typmod,
/* must call typinput in case it wants to reject NULL */
return InputFunctionCall(finfo, NULL, typioparam, typmod);
}
+ else if ((funcid = get_transform_tosql(typid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ return OidFunctionCall1(funcid, PointerGetDatum(sv));
else if (SvROK(sv))
{
/* handle references */
@@ -1407,6 +1414,7 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
typdelim;
Oid typioparam;
Oid typoutputfunc;
+ Oid transform_funcid;
int i,
nitems,
*dims;
@@ -1414,14 +1422,17 @@ plperl_ref_from_pg_array(Datum arg, Oid typid)
SV *av;
HV *hv;
- info = palloc(sizeof(plperl_array_info));
+ info = palloc0(sizeof(plperl_array_info));
/* get element type information, including output conversion function */
get_type_io_data(elementtype, IOFunc_output,
&typlen, &typbyval, &typalign,
&typdelim, &typioparam, &typoutputfunc);
- perm_fmgr_info(typoutputfunc, &info->proc);
+ if ((transform_funcid = get_transform_fromsql(elementtype, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ perm_fmgr_info(transform_funcid, &info->transform_proc);
+ else
+ perm_fmgr_info(typoutputfunc, &info->proc);
info->elem_is_rowtype = type_is_rowtype(elementtype);
@@ -1502,8 +1513,10 @@ make_array_ref(plperl_array_info *info, int first, int last)
{
Datum itemvalue = info->elements[i];
- /* Handle composite type elements */
- if (info->elem_is_rowtype)
+ if (info->transform_proc.fn_oid)
+ av_push(result, (SV *) DatumGetPointer(FunctionCall1(&info->transform_proc, itemvalue)));
+ else if (info->elem_is_rowtype)
+ /* Handle composite type elements */
av_push(result, plperl_hash_from_datum(itemvalue));
else
{
@@ -1812,6 +1825,8 @@ plperl_inline_handler(PG_FUNCTION_ARGS)
desc.proname = "inline_code_block";
desc.fn_readonly = false;
+ desc.lang_oid = codeblock->langOid;
+ desc.trftypes = NIL;
desc.lanpltrusted = codeblock->langIsTrusted;
desc.fn_retistuple = false;
@@ -2076,6 +2091,8 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
SV *retval;
int i;
int count;
+ Oid *argtypes = NULL;
+ int nargs = 0;
ENTER;
SAVETMPS;
@@ -2083,6 +2100,9 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
PUSHMARK(SP);
EXTEND(sp, desc->nargs);
+ if (fcinfo->flinfo->fn_oid)
+ get_func_signature(fcinfo->flinfo->fn_oid, &argtypes, &nargs);
+
for (i = 0; i < desc->nargs; i++)
{
if (fcinfo->argnull[i])
@@ -2096,9 +2116,12 @@ plperl_call_perl_func(plperl_proc_desc *desc, FunctionCallInfo fcinfo)
else
{
SV *sv;
+ Oid funcid;
if (OidIsValid(desc->arg_arraytype[i]))
sv = plperl_ref_from_pg_array(fcinfo->arg[i], desc->arg_arraytype[i]);
+ else if ((funcid = get_transform_fromsql(argtypes[i], current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, fcinfo->arg[i]));
else
{
char *tmp;
@@ -2569,6 +2592,7 @@ free_plperl_function(plperl_proc_desc *prodesc)
/* (FmgrInfo subsidiary info will get leaked ...) */
if (prodesc->proname)
free(prodesc->proname);
+ list_free(prodesc->trftypes);
free(prodesc);
}
@@ -2631,6 +2655,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
HeapTuple typeTup;
Form_pg_language langStruct;
Form_pg_type typeStruct;
+ Datum protrftypes_datum;
Datum prosrcdatum;
bool isnull;
char *proc_source;
@@ -2661,6 +2686,16 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
prodesc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
+ {
+ MemoryContext oldcxt;
+
+ protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes, &isnull);
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ prodesc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+ MemoryContextSwitchTo(oldcxt);
+ }
+
/************************************************************
* Lookup the pg_language tuple by Oid
************************************************************/
@@ -2673,6 +2708,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger, bool is_event_trigger)
procStruct->prolang);
}
langStruct = (Form_pg_language) GETSTRUCT(langTup);
+ prodesc->lang_oid = HeapTupleGetOid(langTup);
prodesc->lanpltrusted = langStruct->lanpltrusted;
ReleaseSysCache(langTup);
@@ -2906,9 +2942,12 @@ plperl_hash_from_tuple(HeapTuple tuple, TupleDesc tupdesc)
else
{
SV *sv;
+ Oid funcid;
if (OidIsValid(get_base_element_type(tupdesc->attrs[i]->atttypid)))
sv = plperl_ref_from_pg_array(attr, tupdesc->attrs[i]->atttypid);
+ else if ((funcid = get_transform_fromsql(tupdesc->attrs[i]->atttypid, current_call_data->prodesc->lang_oid, current_call_data->prodesc->trftypes)))
+ sv = (SV *) DatumGetPointer(OidFunctionCall1(funcid, attr));
else
{
char *outputstr;
diff --git a/src/pl/plperl/plperl_helpers.h b/src/pl/plperl/plperl_helpers.h
index c1c7c297cc5..fab0a7ba081 100644
--- a/src/pl/plperl/plperl_helpers.h
+++ b/src/pl/plperl/plperl_helpers.h
@@ -1,6 +1,8 @@
#ifndef PL_PERL_HELPERS_H
#define PL_PERL_HELPERS_H
+#include "mb/pg_wchar.h"
+
/*
* convert from utf8 to database encoding
*
diff --git a/src/pl/plpython/Makefile b/src/pl/plpython/Makefile
index c58e56b4008..e70e285611d 100644
--- a/src/pl/plpython/Makefile
+++ b/src/pl/plpython/Makefile
@@ -123,54 +123,22 @@ all: all-lib
install: all install-lib install-data
installdirs: installdirs-lib
- $(MKDIR_P) '$(DESTDIR)$(datadir)/extension'
+ $(MKDIR_P) '$(DESTDIR)$(datadir)/extension' '$(DESTDIR)$(includedir_server)'
uninstall: uninstall-lib uninstall-data
install-data: installdirs
$(INSTALL_DATA) $(addprefix $(srcdir)/, $(DATA)) '$(DESTDIR)$(datadir)/extension/'
+ $(INSTALL_DATA) $(srcdir)/plpython.h $(srcdir)/plpy_util.h '$(DESTDIR)$(includedir_server)'
uninstall-data:
rm -f $(addprefix '$(DESTDIR)$(datadir)/extension'/, $(notdir $(DATA)))
+ rm -f $(addprefix '$(DESTDIR)$(includedir_server)'/, plpython.h plpy_util.h)
.PHONY: install-data uninstall-data
-ifeq ($(python_majorversion),3)
-# Adjust regression tests for Python 3 compatibility
-#
-# Mention those regression test files that need to be mangled in the
-# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
-# subdirectory python3/ and have their Python syntax and other bits
-# adjusted to work with Python 3.
-
-# Note that the order of the tests needs to be preserved in this
-# expression.
-REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
-
-.PHONY: pgregress-python3-mangle
-pgregress-python3-mangle:
- $(MKDIR_P) sql/python3 expected/python3 results/python3
- for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
- sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
- -e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
- -e "s/<type 'long'>/<class 'int'>/g" \
- -e "s/\([0-9][0-9]*\)L/\1/g" \
- -e 's/\([ [{]\)u"/\1"/g' \
- -e "s/\([ [{]\)u'/\1'/g" \
- -e "s/def next/def __next__/g" \
- -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
- -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
- -e "s/EXTENSION plpythonu/EXTENSION plpython3u/g" \
- -e "s/EXTENSION plpython2u/EXTENSION plpython3u/g" \
- $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
- done
-
-check installcheck: pgregress-python3-mangle
-
-pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
-
-endif # Python 3
+include $(srcdir)/regress-python3-mangle.mk
check: all submake
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index e2513cf1e05..63a284e2384 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -278,6 +278,7 @@ plpython_inline_handler(PG_FUNCTION_ARGS)
MemSet(&proc, 0, sizeof(PLyProcedure));
proc.pyname = PLy_strdup("__plpython_inline_block");
+ proc.langid = codeblock->langOid;
proc.result.out.d.typoid = VOIDOID;
/*
diff --git a/src/pl/plpython/plpy_procedure.c b/src/pl/plpython/plpy_procedure.c
index bd483012d35..858cecc64d2 100644
--- a/src/pl/plpython/plpy_procedure.c
+++ b/src/pl/plpython/plpy_procedure.c
@@ -10,9 +10,12 @@
#include "access/transam.h"
#include "funcapi.h"
#include "catalog/pg_proc.h"
+#include "catalog/pg_proc_fn.h"
#include "catalog/pg_type.h"
#include "utils/builtins.h"
#include "utils/hsearch.h"
+#include "utils/inval.h"
+#include "utils/memutils.h"
#include "utils/syscache.h"
#include "plpython.h"
@@ -26,6 +29,7 @@
static HTAB *PLy_procedure_cache = NULL;
static PLyProcedure *PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger);
+static void invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue);
static bool PLy_procedure_argument_valid(PLyTypeInfo *arg);
static bool PLy_procedure_valid(PLyProcedure *proc, HeapTuple procTup);
static char *PLy_procedure_munge_source(const char *name, const char *src);
@@ -41,6 +45,29 @@ init_procedure_caches(void)
hash_ctl.entrysize = sizeof(PLyProcedureEntry);
PLy_procedure_cache = hash_create("PL/Python procedures", 32, &hash_ctl,
HASH_ELEM | HASH_BLOBS);
+ CacheRegisterSyscacheCallback(TRFTYPELANG,
+ invalidate_procedure_caches,
+ (Datum) 0);
+}
+
+static void
+invalidate_procedure_caches(Datum arg, int cacheid, uint32 hashvalue)
+{
+ HASH_SEQ_STATUS status;
+ PLyProcedureEntry *hentry;
+
+ Assert(PLy_procedure_cache != NULL);
+
+ /* flush all entries */
+ hash_seq_init(&status, PLy_procedure_cache);
+
+ while ((hentry = (PLyProcedureEntry *) hash_seq_search(&status)))
+ {
+ if (hash_search(PLy_procedure_cache,
+ (void *) &hentry->key,
+ HASH_REMOVE, NULL) == NULL)
+ elog(ERROR, "hash table corrupted");
+ }
}
/*
@@ -165,6 +192,16 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
for (i = 0; i < FUNC_MAX_ARGS; i++)
PLy_typeinfo_init(&proc->args[i]);
proc->nargs = 0;
+ proc->langid = procStruct->prolang;
+ {
+ MemoryContext oldcxt;
+
+ Datum protrftypes_datum = SysCacheGetAttr(PROCOID, procTup,
+ Anum_pg_proc_protrftypes, &isnull);
+ oldcxt = MemoryContextSwitchTo(TopMemoryContext);
+ proc->trftypes = isnull ? NIL : oid_array_to_list(protrftypes_datum);
+ MemoryContextSwitchTo(oldcxt);
+ }
proc->code = proc->statics = NULL;
proc->globals = NULL;
proc->is_setof = procStruct->proretset;
@@ -219,7 +256,7 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
else
{
/* do the real work */
- PLy_output_datum_func(&proc->result, rvTypeTup);
+ PLy_output_datum_func(&proc->result, rvTypeTup, proc->langid, proc->trftypes);
}
ReleaseSysCache(rvTypeTup);
@@ -293,7 +330,9 @@ PLy_procedure_create(HeapTuple procTup, Oid fn_oid, bool is_trigger)
default:
PLy_input_datum_func(&(proc->args[pos]),
types[i],
- argTypeTup);
+ argTypeTup,
+ proc->langid,
+ proc->trftypes);
break;
}
diff --git a/src/pl/plpython/plpy_procedure.h b/src/pl/plpython/plpy_procedure.h
index f1c8510dafc..6d4b00ba7c8 100644
--- a/src/pl/plpython/plpy_procedure.h
+++ b/src/pl/plpython/plpy_procedure.h
@@ -27,6 +27,8 @@ typedef struct PLyProcedure
char **argnames; /* Argument names */
PLyTypeInfo args[FUNC_MAX_ARGS];
int nargs;
+ Oid langid; /* OID of plpython pg_language entry */
+ List *trftypes; /* OID list of transform types */
PyObject *code; /* compiled procedure code */
PyObject *statics; /* data saved across calls, local scope */
PyObject *globals; /* data saved across calls, global scope */
diff --git a/src/pl/plpython/plpy_spi.c b/src/pl/plpython/plpy_spi.c
index 465b316f967..d0e255f8359 100644
--- a/src/pl/plpython/plpy_spi.c
+++ b/src/pl/plpython/plpy_spi.c
@@ -76,6 +76,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
PG_TRY();
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
/*
* the other loop might throw an exception, if PLyTypeInfo member
@@ -128,7 +129,7 @@ PLy_spi_prepare(PyObject *self, PyObject *args)
optr = NULL;
plan->types[i] = typeId;
- PLy_output_datum_func(&plan->args[i], typeTup);
+ PLy_output_datum_func(&plan->args[i], typeTup, exec_ctx->curr_proc->langid, exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}
diff --git a/src/pl/plpython/plpy_typeio.c b/src/pl/plpython/plpy_typeio.c
index 8c70c7c9783..7b65a931831 100644
--- a/src/pl/plpython/plpy_typeio.c
+++ b/src/pl/plpython/plpy_typeio.c
@@ -29,8 +29,8 @@
/* I/O function caching */
-static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup);
-static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup);
+static void PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
+static void PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes);
/* conversion from Datums to Python objects */
static PyObject *PLyBool_FromBool(PLyDatumToOb *arg, Datum d);
@@ -43,6 +43,7 @@ static PyObject *PLyLong_FromInt64(PLyDatumToOb *arg, Datum d);
static PyObject *PLyLong_FromOid(PLyDatumToOb *arg, Datum d);
static PyObject *PLyBytes_FromBytea(PLyDatumToOb *arg, Datum d);
static PyObject *PLyString_FromDatum(PLyDatumToOb *arg, Datum d);
+static PyObject *PLyObject_FromTransform(PLyDatumToOb *arg, Datum d);
static PyObject *PLyList_FromArray(PLyDatumToOb *arg, Datum d);
/* conversion from Python objects to Datums */
@@ -50,6 +51,7 @@ static Datum PLyObject_ToBool(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToBytea(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
+static Datum PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
static Datum PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv);
/* conversion from Python objects to composite Datums (used by triggers and SRFs) */
@@ -102,27 +104,28 @@ PLy_typeinfo_dealloc(PLyTypeInfo *arg)
* PostgreSQL, and vice versa.
*/
void
-PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup)
+PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
{
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for Tuple");
arg->is_rowtype = 0;
- PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup);
+ PLy_input_datum_func2(&(arg->in.d), typeOid, typeTup, langid, trftypes);
}
void
-PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup)
+PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes)
{
if (arg->is_rowtype > 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Tuple");
arg->is_rowtype = 0;
- PLy_output_datum_func2(&(arg->out.d), typeTup);
+ PLy_output_datum_func2(&(arg->out.d), typeTup, langid, trftypes);
}
void
PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -181,7 +184,9 @@ PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
PLy_input_datum_func2(&(arg->in.r.atts[i]),
desc->attrs[i]->atttypid,
- typeTup);
+ typeTup,
+ exec_ctx->curr_proc->langid,
+ exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}
@@ -191,6 +196,7 @@ void
PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
{
int i;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
if (arg->is_rowtype == 0)
elog(ERROR, "PLyTypeInfo struct is initialized for a Datum");
@@ -243,7 +249,9 @@ PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc)
elog(ERROR, "cache lookup failed for type %u",
desc->attrs[i]->atttypid);
- PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup);
+ PLy_output_datum_func2(&(arg->out.r.atts[i]), typeTup,
+ exec_ctx->curr_proc->langid,
+ exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
}
@@ -362,10 +370,12 @@ PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObject *plrv)
}
static void
-PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
+PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup, Oid langid, List *trftypes)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
Oid element_type;
+ Oid base_type;
+ Oid funcid;
perm_fmgr_info(typeStruct->typinput, &arg->typfunc);
arg->typoid = HeapTupleGetOid(typeTup);
@@ -374,12 +384,24 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->typbyval = typeStruct->typbyval;
element_type = get_base_element_type(arg->typoid);
+ base_type = getBaseType(element_type ? element_type : arg->typoid);
/*
* Select a conversion function to convert Python objects to PostgreSQL
- * datums. Most data types can go through the generic function.
+ * datums.
*/
- switch (getBaseType(element_type ? element_type : arg->typoid))
+
+ if ((funcid = get_transform_tosql(base_type, langid, trftypes)))
+ {
+ arg->func = PLyObject_ToTransform;
+ perm_fmgr_info(funcid, &arg->typtransform);
+ }
+ else if (typeStruct->typtype == TYPTYPE_COMPOSITE)
+ {
+ arg->func = PLyObject_ToComposite;
+ }
+ else
+ switch (base_type)
{
case BOOLOID:
arg->func = PLyObject_ToBool;
@@ -392,12 +414,6 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
break;
}
- /* Composite types need their own input routine, though */
- if (typeStruct->typtype == TYPTYPE_COMPOSITE)
- {
- arg->func = PLyObject_ToComposite;
- }
-
if (element_type)
{
char dummy_delim;
@@ -408,6 +424,7 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
+ arg->elm->typtransform = arg->typtransform;
arg->func = PLySequence_ToArray;
arg->elm->typoid = element_type;
@@ -420,12 +437,12 @@ PLy_output_datum_func2(PLyObToDatum *arg, HeapTuple typeTup)
}
static void
-PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
+PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes)
{
Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup);
-
- /* It's safe to handle domains of array types as its base array type. */
- Oid element_type = get_base_element_type(typeOid);
+ Oid element_type;
+ Oid base_type;
+ Oid funcid;
/* Get the type's conversion information */
perm_fmgr_info(typeStruct->typoutput, &arg->typfunc);
@@ -437,7 +454,17 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->typalign = typeStruct->typalign;
/* Determine which kind of Python object we will convert to */
- switch (getBaseType(element_type ? element_type : typeOid))
+
+ element_type = get_base_element_type(typeOid);
+ base_type = getBaseType(element_type ? element_type : typeOid);
+
+ if ((funcid = get_transform_fromsql(base_type, langid, trftypes)))
+ {
+ arg->func = PLyObject_FromTransform;
+ perm_fmgr_info(funcid, &arg->typtransform);
+ }
+ else
+ switch (base_type)
{
case BOOLOID:
arg->func = PLyBool_FromBool;
@@ -478,6 +505,7 @@ PLy_input_datum_func2(PLyDatumToOb *arg, Oid typeOid, HeapTuple typeTup)
arg->elm = PLy_malloc0(sizeof(*arg->elm));
arg->elm->func = arg->func;
+ arg->elm->typtransform = arg->typtransform;
arg->func = PLyList_FromArray;
arg->elm->typoid = element_type;
arg->elm->typmod = -1;
@@ -597,6 +625,12 @@ PLyString_FromDatum(PLyDatumToOb *arg, Datum d)
}
static PyObject *
+PLyObject_FromTransform(PLyDatumToOb *arg, Datum d)
+{
+ return (PyObject *) DatumGetPointer(FunctionCall1(&arg->typtransform, d));
+}
+
+static PyObject *
PLyList_FromArray(PLyDatumToOb *arg, Datum d)
{
ArrayType *array = DatumGetArrayTypeP(d);
@@ -747,16 +781,15 @@ PLyObject_ToComposite(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
/*
- * Generic conversion function: Convert PyObject to cstring and
- * cstring into PostgreSQL type.
+ * Convert Python object to C string in server encoding.
*/
-static Datum
-PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+char *
+PLyObject_AsString(PyObject *plrv)
{
- PyObject *volatile plrv_bo = NULL;
- Datum rv;
-
- Assert(plrv != Py_None);
+ PyObject *plrv_bo;
+ char *plrv_sc;
+ size_t plen;
+ size_t slen;
if (PyUnicode_Check(plrv))
plrv_bo = PLyUnicode_Bytes(plrv);
@@ -786,36 +819,47 @@ PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
if (!plrv_bo)
PLy_elog(ERROR, "could not create string representation of Python object");
- PG_TRY();
- {
- char *plrv_sc = PyBytes_AsString(plrv_bo);
- size_t plen = PyBytes_Size(plrv_bo);
- size_t slen = strlen(plrv_sc);
-
- if (slen < plen)
- ereport(ERROR,
- (errcode(ERRCODE_DATATYPE_MISMATCH),
- errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
- else if (slen > plen)
- elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
- pg_verifymbstr(plrv_sc, slen, false);
- rv = InputFunctionCall(&arg->typfunc,
- plrv_sc,
- arg->typioparam,
- typmod);
- }
- PG_CATCH();
- {
- Py_XDECREF(plrv_bo);
- PG_RE_THROW();
- }
- PG_END_TRY();
+ plrv_sc = pstrdup(PyBytes_AsString(plrv_bo));
+ plen = PyBytes_Size(plrv_bo);
+ slen = strlen(plrv_sc);
Py_XDECREF(plrv_bo);
- return rv;
+ if (slen < plen)
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("could not convert Python object into cstring: Python string representation appears to contain null bytes")));
+ else if (slen > plen)
+ elog(ERROR, "could not convert Python object into cstring: Python string longer than reported length");
+ pg_verifymbstr(plrv_sc, slen, false);
+
+ return plrv_sc;
}
+
+/*
+ * Generic conversion function: Convert PyObject to cstring and
+ * cstring into PostgreSQL type.
+ */
+static Datum
+PLyObject_ToDatum(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+ Assert(plrv != Py_None);
+
+ return InputFunctionCall(&arg->typfunc,
+ PLyObject_AsString(plrv),
+ arg->typioparam,
+ typmod);
+}
+
+
+static Datum
+PLyObject_ToTransform(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
+{
+ return FunctionCall1(&arg->typtransform, PointerGetDatum(plrv));
+}
+
+
static Datum
PLySequence_ToArray(PLyObToDatum *arg, int32 typmod, PyObject *plrv)
{
@@ -869,12 +913,15 @@ static Datum
PLyString_ToComposite(PLyTypeInfo *info, TupleDesc desc, PyObject *string)
{
HeapTuple typeTup;
+ PLyExecutionContext *exec_ctx = PLy_current_execution_context();
typeTup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(desc->tdtypeid));
if (!HeapTupleIsValid(typeTup))
elog(ERROR, "cache lookup failed for type %u", desc->tdtypeid);
- PLy_output_datum_func2(&info->out.d, typeTup);
+ PLy_output_datum_func2(&info->out.d, typeTup,
+ exec_ctx->curr_proc->langid,
+ exec_ctx->curr_proc->trftypes);
ReleaseSysCache(typeTup);
diff --git a/src/pl/plpython/plpy_typeio.h b/src/pl/plpython/plpy_typeio.h
index 82e472a3127..b01151b0fc0 100644
--- a/src/pl/plpython/plpy_typeio.h
+++ b/src/pl/plpython/plpy_typeio.h
@@ -17,6 +17,7 @@ typedef struct PLyDatumToOb
{
PLyDatumToObFunc func;
FmgrInfo typfunc; /* The type's output function */
+ FmgrInfo typtransform; /* from-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
@@ -48,6 +49,7 @@ typedef struct PLyObToDatum
{
PLyObToDatumFunc func;
FmgrInfo typfunc; /* The type's input function */
+ FmgrInfo typtransform; /* to-SQL transform */
Oid typoid; /* The OID of the type */
int32 typmod; /* The typmod of the type */
Oid typioparam;
@@ -91,8 +93,8 @@ typedef struct PLyTypeInfo
extern void PLy_typeinfo_init(PLyTypeInfo *arg);
extern void PLy_typeinfo_dealloc(PLyTypeInfo *arg);
-extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup);
-extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup);
+extern void PLy_input_datum_func(PLyTypeInfo *arg, Oid typeOid, HeapTuple typeTup, Oid langid, List *trftypes);
+extern void PLy_output_datum_func(PLyTypeInfo *arg, HeapTuple typeTup, Oid langid, List *trftypes);
extern void PLy_input_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
extern void PLy_output_tuple_funcs(PLyTypeInfo *arg, TupleDesc desc);
@@ -105,4 +107,7 @@ extern Datum PLyObject_ToCompositeDatum(PLyTypeInfo *info, TupleDesc desc, PyObj
/* conversion from heap tuples to Python dictionaries */
extern PyObject *PLyDict_FromTuple(PLyTypeInfo *info, HeapTuple tuple, TupleDesc desc);
+/* conversion from Python objects to C strings */
+extern char *PLyObject_AsString(PyObject *plrv);
+
#endif /* PLPY_TYPEIO_H */
diff --git a/src/pl/plpython/plpy_util.c b/src/pl/plpython/plpy_util.c
index 36958cb10f3..b6b92557678 100644
--- a/src/pl/plpython/plpy_util.c
+++ b/src/pl/plpython/plpy_util.c
@@ -142,19 +142,30 @@ PLyUnicode_AsString(PyObject *unicode)
* unicode object. Reference ownership is passed to the caller.
*/
PyObject *
-PLyUnicode_FromString(const char *s)
+PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size)
{
char *utf8string;
PyObject *o;
- utf8string = pg_server_to_any(s, strlen(s), PG_UTF8);
-
- o = PyUnicode_FromString(utf8string);
+ utf8string = pg_server_to_any(s, size, PG_UTF8);
- if (utf8string != s)
+ if (utf8string == s)
+ {
+ o = PyUnicode_FromStringAndSize(s, size);
+ }
+ else
+ {
+ o = PyUnicode_FromString(utf8string);
pfree(utf8string);
+ }
return o;
}
+PyObject *
+PLyUnicode_FromString(const char *s)
+{
+ return PLyUnicode_FromStringAndSize(s, strlen(s));
+}
+
#endif /* PY_MAJOR_VERSION >= 3 */
diff --git a/src/pl/plpython/plpy_util.h b/src/pl/plpython/plpy_util.h
index f93e8379fb2..4c29f9aea3c 100644
--- a/src/pl/plpython/plpy_util.h
+++ b/src/pl/plpython/plpy_util.h
@@ -16,6 +16,7 @@ extern char *PLyUnicode_AsString(PyObject *unicode);
#if PY_MAJOR_VERSION >= 3
extern PyObject *PLyUnicode_FromString(const char *s);
+extern PyObject *PLyUnicode_FromStringAndSize(const char *s, Py_ssize_t size);
#endif
#endif /* PLPY_UTIL_H */
diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h
index 69d7e9b229a..ea540af39e3 100644
--- a/src/pl/plpython/plpython.h
+++ b/src/pl/plpython/plpython.h
@@ -91,6 +91,7 @@ typedef int Py_ssize_t;
#define PyString_Check(x) 0
#define PyString_AsString(x) PLyUnicode_AsString(x)
#define PyString_FromString(x) PLyUnicode_FromString(x)
+#define PyString_FromStringAndSize(x, size) PLyUnicode_FromStringAndSize(x, size)
#endif
/*
diff --git a/src/pl/plpython/regress-python3-mangle.mk b/src/pl/plpython/regress-python3-mangle.mk
new file mode 100644
index 00000000000..d2c7490b8a3
--- /dev/null
+++ b/src/pl/plpython/regress-python3-mangle.mk
@@ -0,0 +1,35 @@
+ifeq ($(python_majorversion),3)
+# Adjust regression tests for Python 3 compatibility
+#
+# Mention those regression test files that need to be mangled in the
+# variable REGRESS_PLPYTHON3_MANGLE. They will be copied to a
+# subdirectory python3/ and have their Python syntax and other bits
+# adjusted to work with Python 3.
+
+# Note that the order of the tests needs to be preserved in this
+# expression.
+REGRESS := $(foreach test,$(REGRESS),$(if $(filter $(test),$(REGRESS_PLPYTHON3_MANGLE)),python3/$(test),$(test)))
+
+.PHONY: pgregress-python3-mangle
+pgregress-python3-mangle:
+ $(MKDIR_P) sql/python3 expected/python3 results/python3
+ for file in $(patsubst %,$(srcdir)/sql/%.sql,$(REGRESS_PLPYTHON3_MANGLE)) $(patsubst %,$(srcdir)/expected/%*.out,$(REGRESS_PLPYTHON3_MANGLE)); do \
+ sed -e 's/except \([[:alpha:]][[:alpha:].]*\), *\([[:alpha:]][[:alpha:]]*\):/except \1 as \2:/g' \
+ -e "s/<type 'exceptions\.\([[:alpha:]]*\)'>/<class '\1'>/g" \
+ -e "s/<type 'long'>/<class 'int'>/g" \
+ -e "s/\([0-9][0-9]*\)L/\1/g" \
+ -e 's/\([ [{]\)u"/\1"/g' \
+ -e "s/\([ [{]\)u'/\1'/g" \
+ -e "s/def next/def __next__/g" \
+ -e "s/LANGUAGE plpythonu/LANGUAGE plpython3u/g" \
+ -e "s/LANGUAGE plpython2u/LANGUAGE plpython3u/g" \
+ -e "s/EXTENSION \([^ ]*_\)*plpythonu/EXTENSION \1plpython3u/g" \
+ -e "s/EXTENSION \([^ ]*_\)*plpython2u/EXTENSION \1plpython3u/g" \
+ $$file >`echo $$file | sed 's,^.*/\([^/][^/]*/\)\([^/][^/]*\)$$,\1python3/\2,'` || exit; \
+ done
+
+check installcheck: pgregress-python3-mangle
+
+pg_regress_clean_files += sql/python3/ expected/python3/ results/python3/
+
+endif # Python 3