On 23/12/10 15:15, Jan Urbański wrote: > Here's a patch implementing custom parsers for data types mentioned in > http://archives.postgresql.org/pgsql-hackers/2010-12/msg01991.php. It's > an incremental patch on top of the plpython-refactor patch sent eariler.
Updated to master.
diff --git a/contrib/hstore/Makefile b/contrib/hstore/Makefile index 1d533fd..4e6ba7b 100644 *** a/contrib/hstore/Makefile --- b/contrib/hstore/Makefile *************** *** 1,8 **** # contrib/hstore/Makefile MODULE_big = hstore OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! crc32.o DATA_built = hstore.sql DATA = uninstall_hstore.sql --- 1,17 ---- # contrib/hstore/Makefile MODULE_big = hstore + OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \ ! hstore_plpython.o crc32.o ! ! ifeq ($(with_python),yes) ! ! PG_CPPFLAGS := -I$(srcdir) -I$(top_builddir)/src/pl/plpython \ ! $(python_includespec) -DHSTORE_PLPYTHON_SUPPORT ! SHLIB_LINK = $(python_libspec) $(python_additional_libs) \ ! $(filter -lintl,$(LIBS)) $(CPPFLAGS) ! endif DATA_built = hstore.sql DATA = uninstall_hstore.sql diff --git a/contrib/hstore/hstore.h b/contrib/hstore/hstore.h index 8906397..6edfc70 100644 *** a/contrib/hstore/hstore.h --- b/contrib/hstore/hstore.h *************** extern Pairs *hstoreArrayToPairs(ArrayTy *** 174,179 **** --- 174,182 ---- #define HStoreExistsAllStrategyNumber 11 #define HStoreOldContainsStrategyNumber 13 /* backwards compatibility */ + /* PL/Python support */ + extern void hstore_plpython_init(void); + /* * defining HSTORE_POLLUTE_NAMESPACE=0 will prevent use of old function names; * for now, we default to on for the benefit of people restoring old dumps diff --git a/contrib/hstore/hstore_io.c b/contrib/hstore/hstore_io.c index 0d6f0b6..92c8db9 100644 *** a/contrib/hstore/hstore_io.c --- b/contrib/hstore/hstore_io.c *************** PG_MODULE_MAGIC; *** 20,25 **** --- 20,26 ---- /* old names for C functions */ HSTORE_POLLUTE(hstore_from_text, tconvert); + void _PG_init(void); typedef struct { *************** hstore_send(PG_FUNCTION_ARGS) *** 1211,1213 **** --- 1212,1220 ---- PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } + + void + _PG_init(void) + { + hstore_plpython_init(); + } diff --git a/contrib/hstore/hstore_plpython.c b/contrib/hstore/hstore_plpython.c index ...4ba111a . *** a/contrib/hstore/hstore_plpython.c --- b/contrib/hstore/hstore_plpython.c *************** *** 0 **** --- 1,251 ---- + /* + * contrib/src/hstore_plpython.c + * + * bidirectional transformation between hstores and Python dictionary objects + */ + + /* Only build if PL/Python support is needed */ + #if defined(HSTORE_PLPYTHON_SUPPORT) + + #if defined(_MSC_VER) && defined(_DEBUG) + /* Python uses #pragma to bring in a non-default libpython on VC++ if + * _DEBUG is defined */ + #undef _DEBUG + /* Also hide away errcode, since we load Python.h before postgres.h */ + #define errcode __msvc_errcode + #include <Python.h> + #undef errcode + #define _DEBUG + #elif defined (_MSC_VER) + #define errcode __msvc_errcode + #include <Python.h> + #undef errcode + #else + #include <Python.h> + #endif + + #include "postgres.h" + #include "utils/guc.h" + #include "utils/builtins.h" + #include "utils/syscache.h" + #include "catalog/namespace.h" + + #include "plpython.h" + #include "hstore.h" + + static Oid get_hstore_oid(const char *name); + static void set_hstore_parsers(Oid); + + static PyObject *hstore_to_dict(void *, Datum); + static Datum dict_to_hstore(void *, int32, PyObject *); + + /* GUC variables */ + + static char *hstore_name; + + /* Previous hstore OID */ + + static Oid previous; + + PLyParsers parsers = { + .in = hstore_to_dict, + .out = dict_to_hstore + }; + + static PyObject * + hstore_to_dict(void *ignored, Datum d) + { + HStore *hstore = DatumGetHStoreP(d); + char *base; + HEntry *entries; + int count; + int i; + PyObject *ret; + + base = STRPTR(hstore); + entries = ARRPTR(hstore); + + ret = PyDict_New(); + + count = HS_COUNT(hstore); + + for (i = 0; i < count; i++) + { + PyObject *key, *val; + + key = PyString_FromStringAndSize(HS_KEY(entries, base, i), + HS_KEYLEN(entries, i)); + if (HS_VALISNULL(entries, i)) { + Py_INCREF(Py_None); + val = Py_None; + } + else { + val = PyString_FromStringAndSize(HS_VAL(entries, base, i), + HS_VALLEN(entries, i)); + } + + PyDict_SetItem(ret, key, val); + } + + return ret; + } + + static Datum + dict_to_hstore(void *ignored, int32 typmod, PyObject *dict) + { + HStore *hstore; + int pcount; + Pairs *pairs; + PyObject *key; + PyObject *value; + Py_ssize_t pos; + char *keys; + char *vals; + int keylen; + int vallen; + int buflen; + int i; + + if (!PyDict_Check(dict)) + ereport(ERROR, + (errmsg("hstores can only be constructed " + "from Python dictionaries"))); + + pcount = PyDict_Size(dict); + pairs = palloc(pcount * sizeof(Pairs)); + pos = i = 0; + /* loop over the dictionary, creating a Pair for each key/value pair */ + while (PyDict_Next(dict, &pos, &key, &value)) { + if (!PyString_Check(key)) + elog(ERROR, "hstore keys have to be strings"); + + PyString_AsStringAndSize(key, &keys, &keylen); + + if (strlen(keys) != keylen) + elog(ERROR, "hstore keys cannot contain NUL bytes"); + + pairs[i].key = pstrdup(keys); + pairs[i].keylen = hstoreCheckKeyLen(keylen); + pairs[i].needfree = true; + + if (value == Py_None) { + pairs[i].val = NULL; + pairs[i].vallen = 0; + pairs[i].isnull = true; + } + else { + if (!PyString_Check(value)) + elog(ERROR, "hstore values have to be strings"); + + PyString_AsStringAndSize(value, &vals, &vallen); + + if (strlen(vals) != vallen) + elog(ERROR, "hstore values cannot contain NUL bytes"); + + pairs[i].val = pstrdup(vals); + pairs[i].vallen = hstoreCheckValLen(vallen); + pairs[i].isnull = false; + } + + i++; + } + pcount = hstoreUniquePairs(pairs, pcount, &buflen); + hstore = hstorePairs(pairs, pcount, buflen); + + return PointerGetDatum(hstore); + } + + static const char * + recheck_hstore_oid(const char *newvalue, bool doit, GucSource source) + { + Oid hstore_oid; + + if (newvalue == NULL) + return NULL; + + hstore_oid = get_hstore_oid(newvalue); + + if (*newvalue && !OidIsValid(hstore_oid)) + return NULL; + + if (doit) + set_hstore_parsers(hstore_oid); + + return newvalue; + } + + void + hstore_plpython_init(void) + { + DefineCustomStringVariable("plpython.hstore", + "The fully qualified name of the hstore type.", + NULL, + &hstore_name, + NULL, + PGC_SUSET, + 0, + recheck_hstore_oid, + NULL); + + EmitWarningsOnPlaceholders("plpython"); + + previous = InvalidOid; + + if (hstore_name && *hstore_name) + recheck_hstore_oid(hstore_name, true, PGC_S_FILE); + } + + static Oid + get_hstore_oid(const char *name) + { + text *text_name; + List *hstore_name; + char *type_name; + Oid type_namespace; + Oid typoid; + + Assert(name != NULL); + + if (!(*name)) + return InvalidOid; + + text_name = cstring_to_text(name); + hstore_name = textToQualifiedNameList(text_name); + pfree(text_name); + + type_namespace = QualifiedNameGetCreationNamespace(hstore_name, &type_name); + + typoid = GetSysCacheOid2(TYPENAMENSP, + CStringGetDatum(type_name), + ObjectIdGetDatum(type_namespace)); + + return typoid; + } + + static void + set_hstore_parsers(Oid hstore_oid) + { + char name[NAMEDATALEN]; + + if (OidIsValid(previous)) + { + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, previous); + *find_rendezvous_variable(name) = NULL; + } + + if (OidIsValid(hstore_oid)) + { + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, hstore_oid); + *find_rendezvous_variable(name) = &parsers; + previous = hstore_oid; + } + } + + #else /* !defined(HSTORE_PLPYTHON_SUPPORT) */ + + extern void hstore_plpython_init(void); + + void + hstore_plpython_init(void) {}; + + #endif /* defined(HSTORE_PLPYTHON_SUPPORT) */ diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index aafe556..8841af1 100644 *** a/src/pl/plpython/plpython.c --- b/src/pl/plpython/plpython.c *************** typedef int Py_ssize_t; *** 90,95 **** --- 90,97 ---- #include <fcntl.h> /* postgreSQL stuff */ + #include "plpython.h" + #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/trigger.h" *************** static PyObject *PLyList_FromArray(PLyDa *** 354,359 **** --- 356,364 ---- static PyObject *PLyDict_FromTuple(PLyTypeInfo *, HeapTuple, TupleDesc); + static PLyParserIn PLy_get_custom_input_function(Oid oid); + static PLyParserOut PLy_get_custom_output_function(Oid oid); + static Datum PLyObject_ToBool(PLyObToDatum *, int32, PyObject *); static Datum PLyObject_ToBytea(PLyObToDatum *, int32, PyObject *); static Datum PLyObject_ToDatum(PLyObToDatum *, int32, PyObject *); *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1779,1784 **** --- 1784,1790 ---- { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); Oid element_type; + Oid argument_type; perm_fmgr_info(typeStruct->typinput, &arg->typfunc); arg->typoid = HeapTupleGetOid(typeTup); *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1786,1797 **** arg->typbyval = typeStruct->typbyval; element_type = get_element_type(arg->typoid); /* * Select a conversion function to convert Python objects to PostgreSQL * datums. Most data types can go through the generic function. */ ! switch (getBaseType(element_type ? element_type : arg->typoid)) { case BOOLOID: arg->func = PLyObject_ToBool; --- 1792,1804 ---- arg->typbyval = typeStruct->typbyval; element_type = get_element_type(arg->typoid); + argument_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. */ ! switch (argument_type) { case BOOLOID: arg->func = PLyObject_ToBool; *************** PLy_output_datum_func2(PLyObToDatum *arg *** 1800,1806 **** arg->func = PLyObject_ToBytea; break; default: ! arg->func = PLyObject_ToDatum; break; } --- 1807,1819 ---- arg->func = PLyObject_ToBytea; break; default: ! /* Last ditch effort of finding a rendezvous variable pointing to ! * a parser function, useful for extension modules plugging in ! * their own parsers ! */ ! arg->func = (PLyObToDatumFunc) PLy_get_custom_output_function(argument_type); ! if (arg->func == NULL) ! arg->func = PLyObject_ToDatum; break; } *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1842,1847 **** --- 1855,1861 ---- { Form_pg_type typeStruct = (Form_pg_type) GETSTRUCT(typeTup); Oid element_type = get_element_type(typeOid); + Oid argument_type; /* Get the type's conversion information */ perm_fmgr_info(typeStruct->typoutput, &arg->typfunc); *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1851,1858 **** arg->typlen = typeStruct->typlen; arg->typalign = typeStruct->typalign; /* Determine which kind of Python object we will convert to */ ! switch (getBaseType(element_type ? element_type : typeOid)) { case BOOLOID: arg->func = PLyBool_FromBool; --- 1865,1874 ---- arg->typlen = typeStruct->typlen; arg->typalign = typeStruct->typalign; + argument_type = getBaseType(element_type ? element_type : typeOid); + /* Determine which kind of Python object we will convert to */ ! switch (argument_type) { case BOOLOID: arg->func = PLyBool_FromBool; *************** PLy_input_datum_func2(PLyDatumToOb *arg, *** 1879,1885 **** arg->func = PLyBytes_FromBytea; break; default: ! arg->func = PLyString_FromDatum; break; } --- 1895,1907 ---- arg->func = PLyBytes_FromBytea; break; default: ! /* Last ditch effort of finding a rendezvous variable pointing to ! * a parser function, useful for extension modules plugging in ! * their own parsers ! */ ! arg->func = (PLyDatumToObFunc) PLy_get_custom_input_function(argument_type); ! if (arg->func == NULL) ! arg->func = PLyString_FromDatum; break; } *************** PLy_typeinfo_dealloc(PLyTypeInfo *arg) *** 1920,1925 **** --- 1942,1981 ---- } } + /* + * Getting the parser functions from a rendezvous variable set by another + * extension. + */ + static PLyParserIn + PLy_get_custom_input_function(Oid oid) + { + PLyParsers *parsers; + char name[NAMEDATALEN]; + + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, oid); + parsers = *find_rendezvous_variable(name); + + if (parsers == NULL) + return NULL; + + return parsers->in; + } + + static PLyParserOut + PLy_get_custom_output_function(Oid oid) + { + PLyParsers *parsers; + char name[NAMEDATALEN]; + + snprintf(name, NAMEDATALEN, PARSERS_VARIABLE_PATTERN, oid); + parsers = *find_rendezvous_variable(name); + + if (parsers == NULL) + return NULL; + + return parsers->out; + } + static PyObject * PLyBool_FromBool(PLyDatumToOb *arg, Datum d) { diff --git a/src/pl/plpython/plpython.h b/src/pl/plpython/plpython.h index ...53d25b7 . *** a/src/pl/plpython/plpython.h --- b/src/pl/plpython/plpython.h *************** *** 0 **** --- 1,40 ---- + /* + * src/pl/plpython/plpython.h + */ + #ifndef __PLPYTHON_H__ + #define __PLPYTHON_H__ + + + + /* + * Rendezvous variable pattern for parsers exported from other extensions + * + * An extension providing parsres for type X should look up the type's OID and + * set a rendezvous variable using this pattern that points to a PLyParsers + * structure. PL/Python will then use these parsers for arguments with that + * OID. + */ + #define PARSERS_VARIABLE_PATTERN "plpython_%u_parsers" + + /* + * Types for parsres functions that other modules can export to transform + * Datums into PyObjects and back. The types need to be compatible with + * PLyObToDatumFunc and PLyDatumToObFunc, but we don't want to expose too much + * of plpython.c's guts here, so the first arguments is mandated to be a void + * pointer that should not be touched. An extension should know exactly what + * it's dealing with, so there's no need for it to look at anything contained + * in PLyTypeInfo, which is what gets passed here. + * + * The output parser also gets the type's typmod, which might actually be + * useful. + */ + typedef PyObject *(*PLyParserIn) (void *, Datum); + typedef Datum (*PLyParserOut) (void *, int32, PyObject *); + + typedef struct PLyParsers + { + PLyParserIn in; + PLyParserOut out; + } PLyParsers; + + #endif /* __PLPYTHON_H__ */
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers