#include #include "datetime.h" #include "postgres.h" #include "multicorn.h" #include "catalog/pg_user_mapping.h" #include "access/reloptions.h" #include "miscadmin.h" #include "utils/numeric.h" #include "utils/date.h" #include "utils/timestamp.h" #include "utils/array.h" #include "utils/catcache.h" #include "utils/memutils.h" #include "utils/resowner.h" #include "utils/rel.h" #include "utils/rel.h" #include "executor/nodeSubplan.h" #include "bytesobject.h" #include "mb/pg_wchar.h" #include "access/xact.h" #include "utils/lsyscache.h" List *getOptions(Oid foreigntableid); bool compareOptions(List *options1, List *options2); void getColumnsFromTable(TupleDesc desc, PyObject **p_columns, List **columns); bool compareColumns(List *columns1, List *columns2); PyObject *getClass(PyObject *className); PyObject *valuesToPySet(List *targetlist); PyObject *qualDefsToPyList(List *quallist, ConversionInfo ** cinfo); PyObject *pythonQual(char *operatorname, PyObject *value, ConversionInfo * cinfo, bool is_array, bool use_or, Oid typeoid); PyObject *getSortKey(MulticornDeparsedSortGroup *key); MulticornDeparsedSortGroup *getDeparsedSortGroup(PyObject *key); Datum pyobjectToDatum(PyObject *object, StringInfo buffer, ConversionInfo * cinfo); PyObject *qualdefToPython(MulticornConstQual * qualdef, ConversionInfo ** cinfo); PyObject *paramDefToPython(List *paramdef, ConversionInfo ** cinfos, Oid typeoid, Datum value); PyObject *datumToPython(Datum node, Oid typeoid, ConversionInfo * cinfo); PyObject *datumStringToPython(Datum node, ConversionInfo * cinfo); PyObject *datumNumberToPython(Datum node, ConversionInfo * cinfo); PyObject *datumDateToPython(Datum datum, ConversionInfo * cinfo); PyObject *datumTimestampToPython(Datum datum, ConversionInfo * cinfo); PyObject *datumIntToPython(Datum datum, ConversionInfo * cinfo); PyObject *datumArrayToPython(Datum datum, Oid type, ConversionInfo * cinfo); PyObject *datumByteaToPython(Datum datum, ConversionInfo * cinfo); PyObject *datumUnknownToPython(Datum datum, ConversionInfo * cinfo, Oid type); void pythonDictToTuple(PyObject *p_value, TupleTableSlot *slot, ConversionInfo ** cinfos, StringInfo buffer); void pythonSequenceToTuple(PyObject *p_value, TupleTableSlot *slot, ConversionInfo ** cinfos, StringInfo buffer); /* Python to cstring functions */ void pyobjectToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pynumberToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pyunicodeToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pystringToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pysequenceToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pymappingToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pydateToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void pyunknownToCstring(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo); void appendBinaryStringInfoQuote(StringInfo buffer, char *tempbuffer, Py_ssize_t strlength, bool need_quote); static void begin_remote_xact(CacheEntry * entry); /* * Get a (python) encoding name for an attribute. */ const char * getPythonEncodingName() { const char *encoding_name = GetDatabaseEncodingName(); if (strcmp(encoding_name, "SQL_ASCII") == 0) { encoding_name = "ascii"; } return encoding_name; } char * PyUnicode_AsPgString(PyObject *p_unicode) { char *message = NULL; PyObject *pTempStr; if (p_unicode == NULL) { elog(ERROR, "Received a null pointer in pyunicode_aspgstring"); } pTempStr = PyUnicode_AsEncodedString(p_unicode, getPythonEncodingName(), NULL); errorCheck(); message = strdup(PyBytes_AsString(pTempStr)); errorCheck(); Py_DECREF(pTempStr); return message; } #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. */ PyObject * PyString_FromStringAndSize(const char *s, Py_ssize_t size) { char *utf8string; PyObject *o; utf8string = (char *) pg_do_encoding_conversion((unsigned char *) s, strlen(s), GetDatabaseEncoding(), PG_UTF8); if (size < 0) { o = PyUnicode_FromString(utf8string); } else { o = PyUnicode_FromStringAndSize(utf8string, size); } if (utf8string != s) pfree(utf8string); return o; } PyObject * PyString_FromString(const char *s) { return PyString_FromStringAndSize(s, -1); } char * PyString_AsString(PyObject *unicode) { char *rv; PyObject *o = PyUnicode_AsEncodedString(unicode, GetDatabaseEncodingName(), NULL); errorCheck(); rv = pstrdup(PyBytes_AsString(o)); Py_XDECREF(o); return rv; } int PyString_AsStringAndSize(PyObject *obj, char **buffer, Py_ssize_t *length) { PyObject *o; int rv; char *tempbuffer; if (PyUnicode_Check(obj)) { o = PyUnicode_AsEncodedString(obj, GetDatabaseEncodingName(), NULL); errorCheck(); rv = PyBytes_AsStringAndSize(o, &tempbuffer, length); *buffer = pstrdup(tempbuffer); Py_XDECREF(o); return rv; } return PyBytes_AsStringAndSize(obj, buffer, length); } #endif /* PY_MAJOR_VERSION >= 3 */ /* * Utility function responsible for importing, and returning, a class by name * * Returns a new reference to the class. */ PyObject * getClass(PyObject *className) { PyObject *p_multicorn = PyImport_ImportModule("multicorn"), *p_class = PyObject_CallMethod(p_multicorn, "get_class", "(O)", className); errorCheck(); Py_DECREF(p_multicorn); return p_class; } void appendBinaryStringInfoQuote(StringInfo buffer, char *tempbuffer, Py_ssize_t strlength, bool need_quote) { if (need_quote) { char *c; int i; appendStringInfoChar(buffer, '"'); for (c = tempbuffer, i = 0; i < strlength; ++i, ++c) { if (*c == '"') { appendBinaryStringInfo(buffer, "\\\"", 2); } else if (*c == '\\') { appendBinaryStringInfo(buffer, "\\\\", 2); } else { appendStringInfoChar(buffer, *c); } } appendStringInfoChar(buffer, '"'); } else { appendBinaryStringInfo(buffer, tempbuffer, strlength); } } /* * Convert a list of Value nodes containing the column name as a string to a * pyset of python unicode strings. */ PyObject * valuesToPySet(List *targetlist) { PyObject *result = PySet_New(0); ListCell *lc; foreach(lc, targetlist) { Value *value = (Value *) lfirst(lc); PyObject *pyString = PyString_FromString(strVal(value)); PySet_Add(result, pyString); Py_DECREF(pyString); } return result; } PyObject * qualDefsToPyList(List *qual_list, ConversionInfo ** cinfos) { ListCell *lc; PyObject *p_quals = PyList_New(0); foreach(lc, qual_list) { MulticornBaseQual *qual_def = (MulticornBaseQual *) lfirst(lc); if (qual_def->right_type == T_Const) { PyObject *python_qual = qualdefToPython((MulticornConstQual *) qual_def, cinfos); if (python_qual != NULL) { PyList_Append(p_quals, python_qual); Py_DECREF(python_qual); } } } return p_quals; } /* * Same as getClass, but accepts a C-String argument instead of a python * string. * * Returns a new reference to the class. */ PyObject * getClassString(const char *className) { PyObject *p_classname = PyString_FromString(className), *p_class = getClass(p_classname); Py_DECREF(p_classname); return p_class; } List * getOptions(Oid foreigntableid) { ForeignTable *f_table; ForeignServer *f_server; UserMapping *mapping; List *options; f_table = GetForeignTable(foreigntableid); f_server = GetForeignServer(f_table->serverid); options = NIL; options = list_concat(options, f_table->options); options = list_concat(options, f_server->options); /* An error might occur if no user mapping is defined. */ /* In that case, just ignore it */ mapping = multicorn_GetUserMapping(GetUserId(), f_table->serverid); if (mapping) options = list_concat(options, mapping->options); return options; } /* * Reimplementation of GetUserMapping, which returns NULL instead of throwing an * error when the mapping is not found. */ UserMapping * multicorn_GetUserMapping(Oid userid, Oid serverid) { Datum datum; HeapTuple tp; bool isnull; UserMapping *um; tp = SearchSysCache2(USERMAPPINGUSERSERVER, ObjectIdGetDatum(userid), ObjectIdGetDatum(serverid)); if (!HeapTupleIsValid(tp)) { /* Not found for the specific user -- try PUBLIC */ tp = SearchSysCache2(USERMAPPINGUSERSERVER, ObjectIdGetDatum(InvalidOid), ObjectIdGetDatum(serverid)); } if (!HeapTupleIsValid(tp)) return NULL; um = (UserMapping *) palloc(sizeof(UserMapping)); um->userid = userid; um->serverid = serverid; /* Extract the umoptions */ datum = SysCacheGetAttr(USERMAPPINGUSERSERVER, tp, Anum_pg_user_mapping_umoptions, &isnull); if (isnull) um->options = NIL; else um->options = untransformRelOptions(datum); ReleaseSysCache(tp); return um; } /* * Collect and validate options. * Only one option is required "wrapper". * * Returns a new reference to a dictionary. * * */ PyObject * optionsListToPyDict(List *options) { ListCell *lc; PyObject *p_options_dict = PyDict_New(); foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); PyObject *pStr = PyString_FromString((char *) defGetString(def)); PyDict_SetItemString(p_options_dict, def->defname, pStr); Py_DECREF(pStr); } return p_options_dict; } bool compareOptions(List *options1, List *options2) { ListCell *lc1, *lc2; if (list_length(options1) != list_length(options2)) { return false; } forboth(lc1, options1, lc2, options2) { DefElem *def1 = (DefElem *) lfirst(lc1); DefElem *def2 = (DefElem *) lfirst(lc2); if (def1 == NULL || def2 == NULL || strcmp(def1->defname, def2->defname) != 0) { return false; } if (strcmp(defGetString(def1), defGetString(def2)) != 0) { return false; } } return true; } void getColumnsFromTable(TupleDesc desc, PyObject **p_columns, List **columns) { PyObject *columns_dict = *p_columns; List *columns_list = *columns; if ((columns_dict != NULL) && (columns_list != NULL)) { return; } else { int i; PyObject *p_columnclass = getClassString("multicorn." "ColumnDefinition"), *p_collections = PyImport_ImportModule("collections"), *p_dictclass = PyObject_GetAttrString(p_collections, "OrderedDict"); columns_dict = PyObject_CallFunction(p_dictclass, "()"); for (i = 0; i < desc->natts; i++) { Form_pg_attribute att = TupleDescAttr(desc, i); if (!att->attisdropped) { Oid typOid = att->atttypid; char *key = NameStr(att->attname); int32 typmod = att->atttypmod; char *base_type = format_type_be(typOid); char *modded_type = format_type_with_typemod(typOid, typmod); List *options = GetForeignColumnOptions(att->attrelid, att->attnum); PyObject *p_options = optionsListToPyDict(options); PyObject *column = PyObject_CallFunction(p_columnclass, "(s,i,i,s,s,O)", key, typOid, typmod, modded_type, base_type, p_options); List *columnDef = NULL; errorCheck(); columnDef = lappend(columnDef, makeString(pstrdup(key))); columnDef = lappend(columnDef, makeConst(TYPEOID, -1, InvalidOid, 4, ObjectIdGetDatum(typOid), false, true)); columnDef = lappend(columnDef, makeConst(INT4OID, -1, InvalidOid, 4, Int32GetDatum(typmod), false, true)); columnDef = lappend(columnDef, options); columns_list = lappend(columns_list, columnDef); PyMapping_SetItemString(columns_dict, key, column); Py_DECREF(p_options); Py_DECREF(column); } } Py_DECREF(p_columnclass); Py_DECREF(p_collections); Py_DECREF(p_dictclass); errorCheck(); *p_columns = columns_dict; *columns = columns_list; } } bool compareColumns(List *columns1, List *columns2) { ListCell *lc1, *lc2; if (columns1->length != columns2->length) { return false; } forboth(lc1, columns1, lc2, columns2) { List *coldef1 = lfirst(lc1); List *coldef2 = lfirst(lc2); ListCell *cell1 = list_head(coldef1), *cell2 = list_head(coldef2); /* Compare column name */ if (strcmp(strVal(lfirst(cell1)), strVal(lfirst(cell2))) != 0) { return false; } cell1 = lnext(cell1); cell2 = lnext(cell2); /* Compare typoid */ if (((Const *) (lfirst(cell1)))->constvalue != ((Const *) lfirst(cell2))->constvalue) { return false; } cell1 = lnext(cell1); cell2 = lnext(cell2); /* Compare typmod */ if (((Const *) (lfirst(cell1)))->constvalue != ((Const *) lfirst(cell2))->constvalue) { return false; } cell1 = lnext(cell1); cell2 = lnext(cell2); /* Compare column options */ if (!compareOptions(lfirst(cell1), lfirst(cell2))) { return false; } } return true; } CacheEntry * getCacheEntry(Oid foreigntableid) { /* * create a temporary context. If we have to (re)create the python * instance, it will be promoted to a cachememorycontext. Otherwise, it * will be freed before returning the instance */ MemoryContext tempContext = AllocSetContextCreate(CurrentMemoryContext, "multicorn temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE), oldContext = MemoryContextSwitchTo(tempContext); CacheEntry *entry = NULL; bool found = false; List *options = getOptions(foreigntableid); List *columns = NULL; PyObject *p_columns = NULL; ForeignTable *ftable = GetForeignTable(foreigntableid); Relation rel = RelationIdGetRelation(ftable->relid); TupleDesc desc = rel->rd_att; bool needInitialization = false; entry = hash_search(InstancesHash, &foreigntableid, HASH_ENTER, &found); if (!found || entry->value == NULL) { entry->options = NULL; entry->columns = NULL; entry->cacheContext = NULL; entry->xact_depth = 0; needInitialization = true; } else { /* Even if found, we have to check several things */ if (!compareOptions(entry->options, options)) { /* Options have changed, we must purge the cache. */ Py_XDECREF(entry->value); needInitialization = true; } else { /* Options have not changed, we should look at columns. */ getColumnsFromTable(desc, &p_columns, &columns); if (!compareColumns(columns, entry->columns)) { Py_XDECREF(entry->value); needInitialization = true; } else { Py_XDECREF(p_columns); } } } if (needInitialization) { PyObject *p_options = optionsListToPyDict(options), *p_class = getClass(PyDict_GetItemString(p_options, "wrapper")), *p_instance; entry->value = NULL; getColumnsFromTable(desc, &p_columns, &columns); PyDict_DelItemString(p_options, "wrapper"); p_instance = PyObject_CallFunction(p_class, "(O,O)", p_options, p_columns); errorCheck(); /* Cleanup the old context, containing the old columns and options */ /* values */ if (entry->cacheContext != NULL) { MemoryContextDelete(entry->cacheContext); } /* Promote this tempcontext. */ MemoryContextSetParent(tempContext, CacheMemoryContext); entry->cacheContext = tempContext; entry->options = options; entry->columns = columns; entry->xact_depth = 0; Py_DECREF(p_class); Py_DECREF(p_options); Py_DECREF(p_columns); errorCheck(); entry->value = p_instance; MemoryContextSwitchTo(oldContext); } else { MemoryContextSwitchTo(oldContext); MemoryContextDelete(tempContext); } RelationClose(rel); Py_INCREF(entry->value); /* * Start a new transaction or subtransaction if needed. */ begin_remote_xact(entry); return entry; } /* * Returns the fdw_instance associated with the foreigntableid. * * For performance reasons, it is cached in hash table. */ PyObject * getInstance(Oid foreigntableid) { return getCacheEntry(foreigntableid)->value; } static void begin_remote_xact(CacheEntry * entry) { int curlevel = GetCurrentTransactionNestLevel(); PyObject *rv; /* Start main transaction if we haven't yet */ if (entry->xact_depth <= 0) { rv = PyObject_CallMethod(entry->value, "begin", "(i)", IsolationIsSerializable()); Py_XDECREF(rv); errorCheck(); entry->xact_depth = 1; } while (entry->xact_depth < curlevel) { entry->xact_depth++; rv = PyObject_CallMethod(entry->value, "sub_begin", "(i)", entry->xact_depth); Py_XDECREF(rv); errorCheck(); } } /* * Returns the relation estimated size, in term of number of rows and width. * This is done by calling the getRelSize python method. * */ void getRelSize(MulticornPlanState * state, PlannerInfo *root, double *rows, int *width) { PyObject *p_targets_set, *p_quals, *p_rows_and_width, *p_rows, *p_width, *p_startup_cost; p_targets_set = valuesToPySet(state->target_list); p_quals = qualDefsToPyList(state->qual_list, state->cinfos); p_rows_and_width = PyObject_CallMethod(state->fdw_instance, "get_rel_size", "(O,O)", p_quals, p_targets_set); errorCheck(); Py_DECREF(p_targets_set); Py_DECREF(p_quals); if ((p_rows_and_width == Py_None) || PyTuple_Size(p_rows_and_width) != 2) { Py_DECREF(p_rows_and_width); elog(ERROR, "The get_rel_size python method should return a tuple of length 2"); } p_rows = PyNumber_Long(PyTuple_GetItem(p_rows_and_width, 0)); p_width = PyNumber_Long(PyTuple_GetItem(p_rows_and_width, 1)); p_startup_cost = PyNumber_Long( PyObject_GetAttrString(state->fdw_instance, "_startup_cost")); *rows = PyLong_AsDouble(p_rows); *width = (int) PyLong_AsLong(p_width); state->startupCost = (int) PyLong_AsLong(p_startup_cost); Py_DECREF(p_rows); Py_DECREF(p_width); Py_DECREF(p_rows_and_width); } PyObject * qualdefToPython(MulticornConstQual * qualdef, ConversionInfo ** cinfos) { int arrayindex = qualdef->base.varattno - 1; char *operatorname = qualdef->base.opname; ConversionInfo *cinfo = cinfos[arrayindex]; bool is_array = qualdef->base.isArray, use_or = qualdef->base.useOr; Oid typeoid = qualdef->base.typeoid; Datum value = qualdef->value; PyObject *p_value; if (qualdef->isnull) { p_value = Py_None; Py_INCREF(Py_None); } else { if (typeoid == InvalidOid) { typeoid = cinfo->atttypoid; } p_value = datumToPython(value, typeoid, cinfo); if (p_value == NULL) { return NULL; } } if (typeoid <= 0) { typeoid = cinfo->atttypoid; } return pythonQual(operatorname, p_value, cinfo, is_array, use_or, typeoid); } PyObject * pythonQual(char *operatorname, PyObject *value, ConversionInfo * cinfo, bool is_array, bool use_or, Oid typeoid) { PyObject *qualClass = getClassString("multicorn.Qual"), *qualInstance, *p_operatorname, *operator, *columnName; p_operatorname = PyUnicode_Decode(operatorname, strlen(operatorname), getPythonEncodingName(), NULL); errorCheck(); if (is_array) { PyObject *arrayOpType; if (use_or) { arrayOpType = Py_True; } else { arrayOpType = Py_False; } operator = Py_BuildValue("(O, O)", p_operatorname, arrayOpType); Py_DECREF(p_operatorname); errorCheck(); } else { operator = p_operatorname; } columnName = PyUnicode_Decode(cinfo->attrname, strlen(cinfo->attrname), getPythonEncodingName(), NULL); qualInstance = PyObject_CallFunction(qualClass, "(O,O,O)", columnName, operator, value); errorCheck(); Py_DECREF(value); Py_DECREF(operator); Py_DECREF(qualClass); Py_DECREF(columnName); return qualInstance; } PyObject * getSortKey(MulticornDeparsedSortGroup *key) { PyObject *SortKeyClass = getClassString("multicorn.SortKey"), *SortKeyInstance, *p_attname, *p_reversed, *p_nulls_first, *p_collate; p_attname = PyUnicode_Decode(NameStr(*(key->attname)), strlen(NameStr(*(key->attname))), getPythonEncodingName(), NULL); if (key->reversed) p_reversed = Py_True; else p_reversed = Py_False; if (key->nulls_first) p_nulls_first = Py_True; else p_nulls_first = Py_False; if(key->collate == NULL){ p_collate = Py_None; Py_INCREF(p_collate); } else p_collate = PyUnicode_Decode(NameStr(*(key->collate)), strlen(NameStr(*(key->collate))), getPythonEncodingName(), NULL); SortKeyInstance = PyObject_CallFunction(SortKeyClass, "(O,i,O,O,O)", p_attname, key->attnum, p_reversed, p_nulls_first, p_collate); errorCheck(); Py_DECREF(p_attname); Py_DECREF(p_collate); Py_DECREF(SortKeyClass); return SortKeyInstance; } MulticornDeparsedSortGroup * getDeparsedSortGroup(PyObject *sortKey) { MulticornDeparsedSortGroup *md = palloc0(sizeof(MulticornDeparsedSortGroup)); PyObject * p_temp; p_temp = PyObject_GetAttrString(sortKey, "attname"); md->attname = (Name) strdup(PyUnicode_AS_DATA(p_temp)); Py_DECREF(p_temp); p_temp = PyObject_GetAttrString(sortKey, "attnum"); md->attnum = (int) PyLong_AsLong(p_temp); Py_DECREF(p_temp); p_temp = PyObject_GetAttrString(sortKey, "is_reversed"); md->reversed = PyObject_IsTrue(p_temp); Py_DECREF(p_temp); p_temp = PyObject_GetAttrString(sortKey, "nulls_first"); md->nulls_first = PyObject_IsTrue(PyObject_GetAttrString(sortKey, "nulls_first")); Py_DECREF(p_temp); p_temp = PyObject_GetAttrString(sortKey, "collate"); if(p_temp == Py_None) md->collate = 0; else md->collate = (Name) strdup(PyUnicode_AS_DATA(p_temp)); Py_DECREF(p_temp); return md; } /* * Execute the query in the python fdw, and returns an iterator. */ PyObject * execute(ForeignScanState *node, ExplainState *es) { MulticornExecState *state = node->fdw_state; PyObject *p_targets_set, *p_quals = PyList_New(0), *p_pathkeys = PyList_New(0), *p_iterable, *p_method; ListCell *lc; ExprContext *econtext = node->ss.ps.ps_ExprContext; foreach(lc, state->qual_list) { MulticornBaseQual *qual = lfirst(lc); MulticornConstQual *newqual = NULL; bool isNull; ExprState *expr_state = NULL; switch (qual->right_type) { case T_Param: expr_state = ExecInitExpr(((MulticornParamQual *) qual)->expr, (PlanState *) node); newqual = palloc0(sizeof(MulticornConstQual)); newqual->base.right_type = T_Const; newqual->base.varattno = qual->varattno; newqual->base.opname = qual->opname; newqual->base.isArray = qual->isArray; newqual->base.useOr = qual->useOr; #if PG_VERSION_NUM >= 100000 newqual->value = ExecEvalExpr(expr_state, econtext, &isNull); #else newqual->value = ExecEvalExpr(expr_state, econtext, &isNull, NULL); #endif newqual->base.typeoid = ((Param*) ((MulticornParamQual *) qual)->expr)->paramtype; newqual->isnull = isNull; break; case T_Const: newqual = (MulticornConstQual *) qual; break; default: break; } if (newqual != NULL) { PyObject *python_qual = qualdefToPython((MulticornConstQual *) newqual, state->cinfos); if (python_qual != NULL) { PyList_Append(p_quals, python_qual); Py_DECREF(python_qual); } } } /* Transform every object to a suitable python representation */ p_targets_set = valuesToPySet(state->target_list); foreach(lc, state->pathkeys) { MulticornDeparsedSortGroup *pathkey = (MulticornDeparsedSortGroup *) lfirst(lc); PyObject *python_sortkey = getSortKey(pathkey); PyList_Append(p_pathkeys, python_sortkey); Py_DECREF(python_sortkey); } { PyObject * args, * kwargs = PyDict_New(); if(PyList_Size(p_pathkeys) > 0){ PyDict_SetItemString(kwargs, "sortkeys", p_pathkeys); } if(es != NULL){ PyObject * verbose; if(es->verbose){ verbose = Py_True; } else { verbose = Py_False; } p_method = PyObject_GetAttrString(state->fdw_instance, "explain"); args = PyTuple_Pack(2, p_quals, p_targets_set); PyDict_SetItemString(kwargs, "verbose", verbose); errorCheck(); } else { p_method = PyObject_GetAttrString(state->fdw_instance, "execute"); errorCheck(); args = PyTuple_Pack(2, p_quals, p_targets_set); errorCheck(); } p_iterable = PyObject_Call(p_method, args, kwargs); errorCheck(); Py_DECREF(p_method); Py_DECREF(args); Py_DECREF(kwargs); } errorCheck(); if (p_iterable == Py_None){ state->p_iterator = p_iterable; } else { state->p_iterator = PyObject_GetIter(p_iterable); } Py_DECREF(p_quals); Py_DECREF(p_targets_set); Py_DECREF(p_pathkeys); Py_DECREF(p_iterable); errorCheck(); return state->p_iterator; } void pynumberToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { PyObject *pTempStr; char *tempbuffer; Py_ssize_t strlength = 0; pTempStr = PyObject_Str(pyobject); PyString_AsStringAndSize(pTempStr, &tempbuffer, &strlength); appendBinaryStringInfo(buffer, tempbuffer, strlength); Py_DECREF(pTempStr); } void pyunicodeToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { char *tempbuffer; Py_ssize_t strlength = 0; PyObject *pTempStr; pTempStr = PyUnicode_AsEncodedString(pyobject, getPythonEncodingName(), NULL); errorCheck(); PyBytes_AsStringAndSize(pTempStr, &tempbuffer, &strlength); appendBinaryStringInfoQuote(buffer, tempbuffer, strlength, cinfo->need_quote); Py_DECREF(pTempStr); } void pystringToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { char *tempbuffer; Py_ssize_t strlength = 0; if (PyString_AsStringAndSize(pyobject, &tempbuffer, &strlength) < 0) { ereport(WARNING, (errmsg("An error occured while decoding the %s column", cinfo->attrname), errhint("You should maybe return unicode instead?"))); } appendBinaryStringInfoQuote(buffer, tempbuffer, strlength, cinfo->need_quote); } void pysequenceToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { /* Its an array */ Py_ssize_t i, size = PySequence_Size(pyobject); PyObject *p_item; int previous_dims = cinfo->attndims, previous_needquote = cinfo->need_quote; if (cinfo->attndims == 0) { /* We are not supposed to be converted to an array. */ pyunknownToCstring(pyobject, buffer, cinfo); return; } appendStringInfoChar(buffer, '{'); /* We are an array, so we need to quote stuff */ cinfo->need_quote = true; cinfo->attndims = cinfo->attndims - 1; for (i = 0; i < size; i++) { p_item = PySequence_GetItem(pyobject, i); pyobjectToCString(p_item, buffer, cinfo); Py_DECREF(p_item); if (i != size - 1) { appendBinaryStringInfo(buffer, ", ", 2); } } appendStringInfoChar(buffer, '}'); cinfo->attndims = previous_dims; cinfo->need_quote = previous_needquote; } void pymappingToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { PyObject *items = PyMapping_Items(pyobject); PyObject *current_tuple; Py_ssize_t i; Py_ssize_t size = PyList_Size(items); bool need_quote = cinfo->need_quote; cinfo->need_quote = true; for (i = 0; i < size; i++) { current_tuple = PySequence_GetItem(items, i); pyobjectToCString(PyTuple_GetItem(current_tuple, 0), buffer, cinfo); appendBinaryStringInfo(buffer, "=>", 2); pyobjectToCString(PyTuple_GetItem(current_tuple, 1), buffer, cinfo); if (i != size - 1) { appendBinaryStringInfo(buffer, ", ", 2); } Py_DECREF(current_tuple); } Py_DECREF(items); cinfo->need_quote = need_quote; } void pydateToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { char *tempbuffer; Py_ssize_t strlength = 0; PyObject *formatted_date; formatted_date = PyObject_CallMethod(pyobject, "isoformat", "()"); PyString_AsStringAndSize(formatted_date, &tempbuffer, &strlength); appendBinaryStringInfo(buffer, tempbuffer, strlength); Py_DECREF(formatted_date); } void pyobjectToCString(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { if (pyobject == NULL || pyobject == Py_None) { return; } if (PyNumber_Check(pyobject)) { pynumberToCString(pyobject, buffer, cinfo); return; } if (PyUnicode_Check(pyobject)) { pyunicodeToCString(pyobject, buffer, cinfo); return; } if (PyBytes_Check(pyobject)) { pystringToCString(pyobject, buffer, cinfo); return; } if (PySequence_Check(pyobject)) { pysequenceToCString(pyobject, buffer, cinfo); return; } if (PyMapping_Check(pyobject)) { pymappingToCString(pyobject, buffer, cinfo); return; } PyDateTime_IMPORT; if (PyDate_Check(pyobject)) { pydateToCString(pyobject, buffer, cinfo); return; } pyunknownToCstring(pyobject, buffer, cinfo); } void pyunknownToCstring(PyObject *pyobject, StringInfo buffer, ConversionInfo * cinfo) { PyObject *pTempStr = PyObject_Str(pyobject); char *tempbuffer; Py_ssize_t strlength; PyString_AsStringAndSize(pTempStr, &tempbuffer, &strlength); errorCheck(); appendBinaryStringInfoQuote(buffer, tempbuffer, strlength, cinfo->need_quote); Py_DECREF(pTempStr); return; } void pythonDictToTuple(PyObject *p_value, TupleTableSlot *slot, ConversionInfo ** cinfos, StringInfo buffer) { int i; PyObject *p_object; Datum *values = slot->tts_values; bool *nulls = slot->tts_isnull; for (i = 0; i < slot->tts_tupleDescriptor->natts; i++) { char *key; Form_pg_attribute attr = TupleDescAttr(slot->tts_tupleDescriptor,i); AttrNumber cinfo_idx = attr->attnum - 1; if (cinfos[cinfo_idx] == NULL) { continue; } key = cinfos[cinfo_idx]->attrname; p_object = PyMapping_GetItemString(p_value, key); if (p_object != NULL && p_object != Py_None) { resetStringInfo(buffer); values[i] = pyobjectToDatum(p_object, buffer, cinfos[cinfo_idx]); if (buffer->data == NULL) { nulls[i] = true; } else { nulls[i] = false; } } else { /* "KeyError", doesnt matter. */ PyErr_Clear(); values[i] = (Datum) NULL; nulls[i] = true; } Py_XDECREF(p_object); } } void pythonSequenceToTuple(PyObject *p_value, TupleTableSlot *slot, ConversionInfo ** cinfos, StringInfo buffer) { int i, j; Datum *values = slot->tts_values; bool *nulls = slot->tts_isnull; for (i = 0, j = 0; i < slot->tts_tupleDescriptor->natts; i++) { PyObject *p_object; Form_pg_attribute attr = TupleDescAttr(slot->tts_tupleDescriptor,i); AttrNumber cinfo_idx = attr->attnum - 1; if (cinfos[cinfo_idx] == NULL) { continue; } p_object = PySequence_GetItem(p_value, j); if(p_object == NULL || p_object == Py_None){ nulls[i] = true; values[i] = 0; } else { resetStringInfo(buffer); values[i] = pyobjectToDatum(p_object, buffer, cinfos[cinfo_idx]); if (buffer->data == NULL) { nulls[i] = true; } else { nulls[i] = false; } } errorCheck(); Py_DECREF(p_object); j++; } } /* * Convert a python result (a sequence or a dictionary) to a tupletableslot. */ void pythonResultToTuple(PyObject *p_value, TupleTableSlot *slot, ConversionInfo ** cinfos, StringInfo buffer) { if (PySequence_Check(p_value)) { pythonSequenceToTuple(p_value, slot, cinfos, buffer); } else { if (PyMapping_Check(p_value)) { pythonDictToTuple(p_value, slot, cinfos, buffer); } else { elog(ERROR, "Cannot transform anything else than mappings and" "sequences to rows"); } } } Datum pyobjectToDatum(PyObject *object, StringInfo buffer, ConversionInfo * cinfo) { Datum value = 0; pyobjectToCString(object, buffer, cinfo); if (buffer->len >= 0) { if (cinfo->atttypoid == BYTEAOID || cinfo->atttypoid == TEXTOID || cinfo->atttypoid == VARCHAROID) { /* * Special case, since the value is already a byte string. */ value = PointerGetDatum(cstring_to_text_with_len(buffer->data, buffer->len)); } else { value = InputFunctionCall(cinfo->attinfunc, buffer->data, cinfo->attioparam, cinfo->atttypmod); } } return value; } PyObject * datumStringToPython(Datum datum, ConversionInfo * cinfo) { char *temp; ssize_t size; PyObject *result; temp = datum == 0 ? "?" : TextDatumGetCString(datum); size = strlen(temp); result = PyUnicode_Decode(temp, size, getPythonEncodingName(), NULL); return result; } PyObject * datumUnknownToPython(Datum datum, ConversionInfo * cinfo, Oid type) { char *temp; ssize_t size; PyObject *result; Oid outfuncoid; bool isvarlena; FmgrInfo *fmout = palloc0(sizeof(FmgrInfo)); getTypeOutputInfo(type, &outfuncoid, &isvarlena); fmgr_info(outfuncoid, fmout); temp = OutputFunctionCall(fmout, datum); size = strlen(temp); result = PyUnicode_Decode(temp, size, getPythonEncodingName(), NULL); pfree(fmout); return result; } PyObject * datumNumberToPython(Datum datum, ConversionInfo * cinfo) { ssize_t numvalue = (ssize_t) DatumGetNumeric(datum); char *tempvalue = (char *) DirectFunctionCall1(numeric_out, numvalue); PyObject *buffer = PyString_FromString(tempvalue), #if PY_MAJOR_VERSION >= 3 *value = PyFloat_FromString(buffer); #else *value = PyFloat_FromString(buffer, NULL); #endif Py_DECREF(buffer); return value; } PyObject * datumDateToPython(Datum datum, ConversionInfo * cinfo) { struct pg_tm *pg_tm_value = palloc(sizeof(struct pg_tm)); PyObject *result; fsec_t fsec; PyDateTime_IMPORT; datum = DirectFunctionCall1(date_timestamp, datum); timestamp2tm(DatumGetTimestamp(datum), NULL, pg_tm_value, &fsec, NULL, NULL); result = PyDate_FromDate(pg_tm_value->tm_year, pg_tm_value->tm_mon, pg_tm_value->tm_mday); pfree(pg_tm_value); return result; } PyObject * datumTimestampToPython(Datum datum, ConversionInfo * cinfo) { struct pg_tm *pg_tm_value = palloc(sizeof(struct pg_tm)); PyObject *result; fsec_t fsec; PyDateTime_IMPORT; timestamp2tm(DatumGetTimestamp(datum), NULL, pg_tm_value, &fsec, NULL, NULL); result = PyDateTime_FromDateAndTime(pg_tm_value->tm_year, pg_tm_value->tm_mon, pg_tm_value->tm_mday, pg_tm_value->tm_hour, pg_tm_value->tm_min, pg_tm_value->tm_sec, 0); pfree(pg_tm_value); return result; } PyObject * datumIntToPython(Datum datum, ConversionInfo * cinfo) { return PyLong_FromLong(DatumGetInt32(datum)); } PyObject * datumArrayToPython(Datum datum, Oid type, ConversionInfo * cinfo) { #if PG_VERSION_NUM >= 90500 ArrayIterator iterator = array_create_iterator(DatumGetArrayTypeP(datum), 0, NULL); # else ArrayIterator iterator = array_create_iterator(DatumGetArrayTypeP(datum), 0); # endif Datum elem = (Datum) NULL; bool isnull; PyObject *result = PyList_New(0), *pyitem; while (array_iterate(iterator, &elem, &isnull)) { if (isnull) { PyList_Append(result, Py_None); } else { HeapTuple tuple; Form_pg_type typeStruct; tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); if (!HeapTupleIsValid(tuple)) { elog(ERROR, "lookup failed for type %u", type); } typeStruct = (Form_pg_type) GETSTRUCT(tuple); pyitem = datumToPython(elem, typeStruct->typelem, cinfo); ReleaseSysCache(tuple); PyList_Append(result, pyitem); Py_DECREF(pyitem); } } return result; } PyObject * datumByteaToPython(Datum datum, ConversionInfo * cinfo) { text *txt = DatumGetByteaP(datum); char *str = txt == NULL ? "?" : VARDATA(txt); size_t size = VARSIZE(txt) - VARHDRSZ; #if PY_MAJOR_VERSION >= 3 return PyBytes_FromStringAndSize(str, size); #else return PyString_FromStringAndSize(str, size); #endif } PyObject * datumToPython(Datum datum, Oid type, ConversionInfo * cinfo) { HeapTuple tuple; Form_pg_type typeStruct; switch (type) { case BYTEAOID: return datumByteaToPython(datum, cinfo); case TEXTOID: case VARCHAROID: return datumStringToPython(datum, cinfo); case NUMERICOID: return datumNumberToPython(datum, cinfo); case DATEOID: return datumDateToPython(datum, cinfo); case TIMESTAMPOID: return datumTimestampToPython(datum, cinfo); case INT4OID: return datumIntToPython(datum, cinfo); default: /* Case for the array ? */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(type)); if (!HeapTupleIsValid(tuple)) { elog(ERROR, "lookup failed for type %u", type); } typeStruct = (Form_pg_type) GETSTRUCT(tuple); ReleaseSysCache(tuple); if ((typeStruct->typelem != 0) && (typeStruct->typlen == -1)) { /* Its an array. */ return datumArrayToPython(datum, type, cinfo); } return datumUnknownToPython(datum, cinfo, type); } } /* * Call the path_keys method from the python implementation, and convert the * result to a list of "tuples" (list) of the form: * * - Bitmapset of attnums - Cost (integer) */ List * pathKeys(MulticornPlanState * state) { List *result = NULL; Py_ssize_t i; PyObject *fdw_instance = state->fdw_instance, *p_pathkeys; p_pathkeys = PyObject_CallMethod(fdw_instance, "get_path_keys", "()"); errorCheck(); for (i = 0; i < PySequence_Length(p_pathkeys); i++) { PyObject *p_item = PySequence_GetItem(p_pathkeys, i), *p_keys = PySequence_GetItem(p_item, 0), *p_cost = PySequence_GetItem(p_item, 1), *p_cost_long = PyNumber_Long(p_cost); double rows = PyLong_AsDouble(p_cost_long); ssize_t j; List *attnums = NULL; List *item = NULL; for (j = 0; j < PySequence_Length(p_keys); j++) { PyObject *p_key = PySequence_GetItem(p_keys, j); ssize_t k; /* Lookup the attribute number by its key. */ for (k = 0; k < state->numattrs; k++) { ConversionInfo *cinfo = state->cinfos[k]; if (cinfo == NULL) { continue; } if (p_key != Py_None && strcmp(cinfo->attrname, PyString_AsString(p_key)) == 0) { attnums = list_append_unique_int(attnums, cinfo->attnum); break; } } Py_DECREF(p_key); } item = lappend(item, attnums); item = lappend(item, makeConst(INT4OID, -1, InvalidOid, 4, rows, false, true)); result = lappend(result, item); Py_DECREF(p_keys); Py_DECREF(p_cost); Py_DECREF(p_cost_long); Py_DECREF(p_item); } Py_DECREF(p_pathkeys); return result; } /* * Call the can_sort method from the python implementation. We provide a deparsed * version of the requested fields to sort with all detail as needed (nulls, * collate...), and convert the result to a list of "tuples" (list) of the form: * * - Bitmapset of attnums * * representing the fields that the foreign data wrapper can be sort as * we requested. */ List * canSort(MulticornPlanState * state, List *deparsed) { List *result = NULL; ListCell *lc; Py_ssize_t i; PyObject *fdw_instance = state->fdw_instance, *p_pathkeys = PyList_New(0), *p_sortable; foreach(lc, deparsed) { MulticornDeparsedSortGroup *pathkey = (MulticornDeparsedSortGroup *) lfirst(lc); PyObject *python_sortkey = getSortKey(pathkey); PyList_Append(p_pathkeys, python_sortkey); Py_DECREF(python_sortkey); } p_sortable = PyObject_CallMethod(fdw_instance, "can_sort", "(O)", p_pathkeys); errorCheck(); for (i = 0; i < PySequence_Length(p_sortable); i++) { PyObject *p_key = PySequence_GetItem(p_sortable, i); MulticornDeparsedSortGroup *md = getDeparsedSortGroup(p_key); result = lappend(result, md); Py_DECREF(p_key); } Py_DECREF(p_pathkeys); Py_DECREF(p_sortable); return result; } PyObject * tupleTableSlotToPyObject(TupleTableSlot *slot, ConversionInfo ** cinfos) { PyObject *result = PyDict_New(); TupleDesc tupdesc = slot->tts_tupleDescriptor; int i; for (i = 0; i < tupdesc->natts; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc,i); bool isnull; Datum value; PyObject *item; AttrNumber cinfo_idx = attr->attnum - 1; if (attr->attisdropped || cinfos[cinfo_idx] == NULL) { continue; } value = slot_getattr(slot, i + 1, &isnull); if (isnull) { item = Py_None; Py_INCREF(item); } else { item = datumToPython(value, cinfos[cinfo_idx]->atttypoid, cinfos[cinfo_idx]); errorCheck(); } PyDict_SetItemString(result, cinfos[cinfo_idx]->attrname, item); Py_DECREF(item); } return result; } /* * Get the rowid column name */ char * getRowIdColumn(PyObject *fdw_instance) { PyObject *value = PyObject_GetAttrString(fdw_instance, "rowid_column"); char *result; errorCheck(); if (value == Py_None) { Py_DECREF(value); elog(ERROR, "This FDW does not support the writable API"); } result = PyString_AsString(value); Py_DECREF(value); return result; }