diff options
author | Peter Eisentraut | 2015-04-26 14:33:14 +0000 |
---|---|---|
committer | Peter Eisentraut | 2015-04-26 14:33:14 +0000 |
commit | cac76582053ef8ea07df65fed0757f352da23705 (patch) | |
tree | 6ae01041aa61db9d686638b9d4c3ccd30d7c6487 /src/pl | |
parent | f320cbb615e0374b18836337713239da58705cf3 (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/GNUmakefile | 4 | ||||
-rw-r--r-- | src/pl/plperl/plperl.c | 47 | ||||
-rw-r--r-- | src/pl/plperl/plperl_helpers.h | 2 | ||||
-rw-r--r-- | src/pl/plpython/Makefile | 40 | ||||
-rw-r--r-- | src/pl/plpython/plpy_main.c | 1 | ||||
-rw-r--r-- | src/pl/plpython/plpy_procedure.c | 43 | ||||
-rw-r--r-- | src/pl/plpython/plpy_procedure.h | 2 | ||||
-rw-r--r-- | src/pl/plpython/plpy_spi.c | 3 | ||||
-rw-r--r-- | src/pl/plpython/plpy_typeio.c | 159 | ||||
-rw-r--r-- | src/pl/plpython/plpy_typeio.h | 9 | ||||
-rw-r--r-- | src/pl/plpython/plpy_util.c | 21 | ||||
-rw-r--r-- | src/pl/plpython/plpy_util.h | 1 | ||||
-rw-r--r-- | src/pl/plpython/plpython.h | 1 | ||||
-rw-r--r-- | src/pl/plpython/regress-python3-mangle.mk | 35 |
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 |