static jvalue _Array_coerceDatum(Type self, Datum arg) { jvalue result; jsize idx; Type elemType = Type_getElementType(self); int16 elemLength = Type_getLength(elemType); char elemAlign = Type_getAlign(elemType); bool elemByValue = Type_isByValue(elemType); ArrayType* v = DatumGetArrayTypeP(arg); jsize nElems = (jsize)ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)); jobjectArray objArray = JNI_newObjectArray(nElems, Type_getJavaClass(elemType), 0); const char* values = ARR_DATA_PTR(v); bits8* nullBitMap = ARR_NULLBITMAP(v); for(idx = 0; idx < nElems; ++idx) { if(arrayIsNull(nullBitMap, idx)) JNI_setObjectArrayElement(objArray, idx, 0); else { Datum value = fetch_att(values, elemByValue, elemLength); jvalue obj = Type_coerceDatum(elemType, value); JNI_setObjectArrayElement(objArray, idx, obj.l); JNI_deleteLocalRef(obj.l); values = att_addlength_datum(values, elemLength, PointerGetDatum(values)); values = (char*)att_align_nominal(values, elemAlign); } } result.l = (jobject)objArray; return result; }
static jvalue _doubleArray_coerceDatum(Type self, Datum arg) { jvalue result; ArrayType* v = DatumGetArrayTypeP(arg); jsize nElems = (jsize)ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)); jdoubleArray doubleArray = JNI_newDoubleArray(nElems); #if (PGSQL_MAJOR_VER == 8 && PGSQL_MINOR_VER < 2) JNI_setDoubleArrayRegion(doubleArray, 0, nElems, (jdouble*)ARR_DATA_PTR(v)); #else if(ARR_HASNULL(v)) { jsize idx; jboolean isCopy = JNI_FALSE; bits8* nullBitMap = ARR_NULLBITMAP(v); jdouble* values = (jdouble*)ARR_DATA_PTR(v); jdouble* elems = JNI_getDoubleArrayElements(doubleArray, &isCopy); for(idx = 0; idx < nElems; ++idx) { if(arrayIsNull(nullBitMap, idx)) elems[idx] = 0; else elems[idx] = *values++; } JNI_releaseDoubleArrayElements(doubleArray, elems, JNI_COMMIT); } else JNI_setDoubleArrayRegion(doubleArray, 0, nElems, (jdouble*)ARR_DATA_PTR(v)); #endif result.l = (jobject)doubleArray; return result; }
static PyObj array_get_nelements(PyObj self, void *closure) { long nelements; ArrayType *at; int ndim, *dims; at = DatumGetArrayTypeP(PyPgObject_GetDatum(self)); ndim = ARR_NDIM(at); dims = ARR_DIMS(at); if (ndim == 0) nelements = 0; else { int i; nelements = 1; for (i = 0; i < ndim; ++i) { nelements = (dims[i] * nelements); } } return(PyLong_FromLong(nelements)); }
Datum plvsubst_string_string(PG_FUNCTION_ARGS) { Datum r; ArrayType *array; FunctionCallInfoData locfcinfo; init_c_subst(); if (PG_ARGISNULL(0) || PG_ARGISNULL(1)) PG_RETURN_NULL(); /* * I can't use DirectFunctionCall2 */ InitFunctionCallInfoData(locfcinfo, fcinfo->flinfo, 2, NULL, NULL); locfcinfo.arg[0] = PG_GETARG_DATUM(1); locfcinfo.arg[1] = PG_GETARG_IF_EXISTS(2, DATUM, CStringGetTextDatum(",")); locfcinfo.argnull[0] = false; locfcinfo.argnull[1] = false; r = text_to_array(&locfcinfo); if (locfcinfo.isnull || r == (Datum) 0) array = NULL; else array = DatumGetArrayTypeP(r); PG_RETURN_TEXT_P(plvsubst_string(PG_GETARG_TEXT_P(0), array, PG_GETARG_IF_EXISTS(3, TEXT_P, c_subst), fcinfo)); }
/* * HeapTupleOfForeignConstraintIncludesColumn fetches the columns from the foreign * constraint and checks if the given column name matches one of them. */ static bool HeapTupleOfForeignConstraintIncludesColumn(HeapTuple heapTuple, Oid relationId, int pgConstraintKey, char *columnName) { Datum columnsDatum = 0; Datum *columnArray = NULL; int columnCount = 0; int attrIdx = 0; bool isNull = false; columnsDatum = SysCacheGetAttr(CONSTROID, heapTuple, pgConstraintKey, &isNull); deconstruct_array(DatumGetArrayTypeP(columnsDatum), INT2OID, 2, true, 's', &columnArray, NULL, &columnCount); for (attrIdx = 0; attrIdx < columnCount; ++attrIdx) { AttrNumber attrNo = DatumGetInt16(columnArray[attrIdx]); char *colName = get_relid_attribute_name(relationId, attrNo); if (strncmp(colName, columnName, NAMEDATALEN) == 0) { return true; } } return false; }
static PyObj array_get_dimensions(PyObj self, void *closure) { ArrayType *at; PyObj rob; int i, ndim, *dims; at = DatumGetArrayTypeP(PyPgObject_GetDatum(self)); ndim = ARR_NDIM(at); dims = ARR_DIMS(at); rob = PyTuple_New(ndim); for (i = 0; i < ndim; ++i) { PyObj ob; ob = PyLong_FromLong(dims[i]); if (ob == NULL) { Py_DECREF(rob); return(NULL); } PyTuple_SET_ITEM(rob, i, ob); } return(rob); }
Datum plr_array_accum(PG_FUNCTION_ARGS) { Datum v; Datum newelem; ArrayType *result; /* return NULL if both arguments are NULL */ if (PG_ARGISNULL(0) && PG_ARGISNULL(1)) PG_RETURN_NULL(); /* create a new array from the second argument if first is NULL */ if (PG_ARGISNULL(0)) PG_RETURN_ARRAYTYPE_P(plr_array_create(fcinfo, 1, 1)); /* return the first argument if the second is NULL */ if (PG_ARGISNULL(1)) PG_RETURN_ARRAYTYPE_P(PG_GETARG_ARRAYTYPE_P_COPY(0)); v = PG_GETARG_DATUM(0); newelem = PG_GETARG_DATUM(1); result = DatumGetArrayTypeP(DirectFunctionCall2(plr_array_push, v, newelem)); PG_RETURN_ARRAYTYPE_P(result); }
static jvalue _booleanArray_coerceDatum(Type self, Datum arg) { jvalue result; ArrayType* v = DatumGetArrayTypeP(arg); jsize nElems = (jsize)ArrayGetNItems(ARR_NDIM(v), ARR_DIMS(v)); jbooleanArray booleanArray = JNI_newBooleanArray(nElems); if(ARR_HASNULL(v)) { jsize idx; jboolean isCopy = JNI_FALSE; bits8* nullBitMap = ARR_NULLBITMAP(v); jboolean* values = (jboolean*)ARR_DATA_PTR(v); jboolean* elems = JNI_getBooleanArrayElements(booleanArray, &isCopy); for(idx = 0; idx < nElems; ++idx) { if(arrayIsNull(nullBitMap, idx)) elems[idx] = 0; else elems[idx] = *values++; } JNI_releaseBooleanArrayElements(booleanArray, elems, JNI_COMMIT); } else JNI_setBooleanArrayRegion(booleanArray, 0, nElems, (jboolean*)ARR_DATA_PTR(v)); result.l = (jobject)booleanArray; return result; }
/* * Helper function used to extract interesting information from * the state tuple. */ static void get_nb_state(HeapTupleHeader tuple, nb_classify_state *state, int nclasses) { Datum class_datum, accum_datum, total_datum; bool isnull[3]; /* * When called correctly nb_classify should never fill in the state * tuple with any invalid values, but if a user is calling the * functions by hand for some reason then something may be funny */ class_datum = GetAttributeByNum(tuple, 1, &isnull[0]); accum_datum = GetAttributeByNum(tuple, 2, &isnull[1]); total_datum = GetAttributeByNum(tuple, 3, &isnull[2]); if (isnull[0] || isnull[1] || isnull[2]) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("nb_classify: invalid accumulation state"))); } state->classes = DatumGetArrayTypeP(class_datum); state->accum = DatumGetArrayTypeP(accum_datum); state->total = DatumGetArrayTypeP(total_datum); /* All must have the correct dimensionality */ if (ARR_NDIM(state->classes) != 1 || ARR_NDIM(state->accum) != 1 || ARR_NDIM(state->total) != 1 || ARR_DIMS(state->accum)[0] != ARR_DIMS(state->classes)[0] || ARR_DIMS(state->classes)[0] != ARR_DIMS(state->classes)[0]) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("nb_classify: invalid accumulation state"))); } /* Check that lengths matchup with what was expected */ if (nclasses > 0 && ARR_DIMS(state->classes)[0] != nclasses) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("nb_classify: mismatched inter-row input lengths"))); } }
static PyObj array_get_ndim(PyObj self, void *closure) { PyObj rob; rob = PyLong_FromLong(ARR_NDIM(DatumGetArrayTypeP(PyPgObject_GetDatum(self)))); return(rob); }
/** * Convert postgres Datum into a ConcreteValue object. */ AbstractValueSPtr AbstractPGValue::DatumToValue(bool inMemoryIsWritable, Oid inTypeID, Datum inDatum) const { // First check if datum is rowtype if (type_is_rowtype(inTypeID)) { HeapTupleHeader pgTuple = DatumGetHeapTupleHeader(inDatum); return AbstractValueSPtr(new PGValue<HeapTupleHeader>(pgTuple)); } else if (type_is_array(inTypeID)) { ArrayType *pgArray = DatumGetArrayTypeP(inDatum); if (ARR_NDIM(pgArray) != 1) throw std::invalid_argument("Multidimensional arrays not yet supported"); if (ARR_HASNULL(pgArray)) throw std::invalid_argument("Arrays with NULLs not yet supported"); switch (ARR_ELEMTYPE(pgArray)) { case FLOAT8OID: { MemHandleSPtr memoryHandle(new PGArrayHandle(pgArray)); if (inMemoryIsWritable) { return AbstractValueSPtr( new ConcreteValue<Array<double> >( Array<double>(memoryHandle, boost::extents[ ARR_DIMS(pgArray)[0] ]) ) ); } else { return AbstractValueSPtr( new ConcreteValue<Array_const<double> >( Array_const<double>(memoryHandle, boost::extents[ ARR_DIMS(pgArray)[0] ]) ) ); } } } } switch (inTypeID) { case BOOLOID: return AbstractValueSPtr( new ConcreteValue<bool>( DatumGetBool(inDatum) )); case INT2OID: return AbstractValueSPtr( new ConcreteValue<int16_t>( DatumGetInt16(inDatum) )); case INT4OID: return AbstractValueSPtr( new ConcreteValue<int32_t>( DatumGetInt32(inDatum) )); case INT8OID: return AbstractValueSPtr( new ConcreteValue<int64_t>( DatumGetInt64(inDatum) )); case FLOAT4OID: return AbstractValueSPtr( new ConcreteValue<float>( DatumGetFloat4(inDatum) )); case FLOAT8OID: return AbstractValueSPtr( new ConcreteValue<double>( DatumGetFloat8(inDatum) )); } return AbstractValueSPtr(); }
/* * len(o) - Python semantics */ static Py_ssize_t py_array_length(PyObj self) { ArrayType *at; at = DatumGetArrayTypeP(PyPgObject_GetDatum(self)); if (ARR_NDIM(at) == 0) return(0); else return(ARR_DIMS(at)[0]); }
/* * Transform a relation options list (list of DefElem) into the text array * format that is kept in pg_class.reloptions. * * This is used for three cases: CREATE TABLE/INDEX, ALTER TABLE SET, and * ALTER TABLE RESET. In the ALTER cases, oldOptions is the existing * reloptions value (possibly NULL), and we replace or remove entries * as needed. * * If ignoreOids is true, then we should ignore any occurrence of "oids" * in the list (it will be or has been handled by interpretOidsOption()). * * Note that this is not responsible for determining whether the options * are valid. * * Both oldOptions and the result are text arrays (or NULL for "default"), * but we declare them as Datums to avoid including array.h in reloptions.h. */ Datum transformRelOptions(Datum oldOptions, List *defList, bool ignoreOids, bool isReset) { Datum result; ArrayBuildState *astate; ListCell *cell; /* no change if empty list */ if (defList == NIL) return oldOptions; /* We build new array using accumArrayResult */ astate = NULL; /* Copy any oldOptions that aren't to be replaced */ if (DatumGetPointer(oldOptions) != 0) { ArrayType *array = DatumGetArrayTypeP(oldOptions); Datum *oldoptions; int noldoptions; int i; Assert(ARR_ELEMTYPE(array) == TEXTOID); deconstruct_array(array, TEXTOID, -1, false, 'i', &oldoptions, NULL, &noldoptions); for (i = 0; i < noldoptions; i++) { text *oldoption = DatumGetTextP(oldoptions[i]); char *text_str = VARDATA(oldoption); int text_len = VARSIZE(oldoption) - VARHDRSZ; /* Search for a match in defList */ foreach(cell, defList) { DefElem *def = lfirst(cell); int kw_len = strlen(def->defname); if (text_len > kw_len && text_str[kw_len] == '=' && pg_strncasecmp(text_str, def->defname, kw_len) == 0) break; } if (!cell) { /* No match, so keep old option */ astate = accumArrayResult(astate, oldoptions[i], false, TEXTOID, CurrentMemoryContext); } }
static PyObj array_has_null(PyObj self, void *closure) { PyObj rob; if (ARR_HASNULL(DatumGetArrayTypeP(PyPgObject_GetDatum(self)))) rob = Py_True; else rob = Py_False; Py_INCREF(rob); return(rob); }
/* * Turn an array into JSON. */ static void array_to_json_internal(Datum array, StringInfo result, bool use_line_feeds) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); int *dim; int ndim; int nitems; int count = 0; Datum *elements; bool *nulls; int16 typlen; bool typbyval; char typalign, typdelim; Oid typioparam; Oid typoutputfunc; TYPCATEGORY tcategory; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); nitems = ArrayGetNItems(ndim, dim); if (nitems <= 0) { appendStringInfoString(result,"[]"); return; } get_type_io_data(element_type, IOFunc_output, &typlen, &typbyval, &typalign, &typdelim, &typioparam, &typoutputfunc); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); if (element_type == RECORDOID) tcategory = TYPCATEGORY_COMPOSITE; else if (element_type == JSONOID) tcategory = TYPCATEGORY_JSON; else tcategory = TypeCategory(element_type); array_dim_to_json(result, 0, ndim, dim, elements, &count, tcategory, typoutputfunc, use_line_feeds); pfree(elements); pfree(nulls); }
Datum _ltree_compress(PG_FUNCTION_ARGS) { GISTENTRY *entry = (GISTENTRY *) PG_GETARG_POINTER(0); GISTENTRY *retval = entry; if (entry->leafkey) { /* ltree */ ltree_gist *key; ArrayType *val = DatumGetArrayTypeP(entry->key); int4 len = LTG_HDRSIZE + ASIGLEN; int num = ArrayGetNItems(ARR_NDIM(val), ARR_DIMS(val)); ltree *item = (ltree *) ARR_DATA_PTR(val); if (ARR_NDIM(val) != 1) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must be one-dimensional"))); key = (ltree_gist *) palloc(len); key->len = len; key->flag = 0; MemSet(LTG_SIGN(key), 0, ASIGLEN); while (num > 0) { hashing(LTG_SIGN(key), item); num--; item = NEXTVAL(item); } retval = (GISTENTRY *) palloc(sizeof(GISTENTRY)); gistentryinit(*retval, PointerGetDatum(key), entry->rel, entry->page, entry->offset, key->len, FALSE); } else if (!LTG_ISALLTRUE(entry->key)) { int4 i, len; ltree_gist *key; BITVECP sign = LTG_SIGN(DatumGetPointer(entry->key)); ALOOPBYTE( if ((sign[i] & 0xff) != 0xff) PG_RETURN_POINTER(retval); );
/* * ProcessRoleGUC -- * We now process pg_authid.rolconfig separately from InitializeSessionUserId, * since it's too early to access toast table before initializing all * relcaches in phase3. */ static void ProcessRoleGUC(void) { cqContext *pcqCtx; Oid roleId; HeapTuple roleTup; Datum datum; bool isnull; /* This should have been set by now */ roleId = GetUserId(); Assert(OidIsValid(roleId)); pcqCtx = caql_beginscan( NULL, cql("SELECT * FROM pg_authid " " WHERE oid = :1", ObjectIdGetDatum(roleId))); roleTup = caql_getnext(pcqCtx); if (!HeapTupleIsValid(roleTup)) ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role %u does not exist", roleId), errSendAlert(false))); /* * Set up user-specific configuration variables. This is a good place to * do it so we don't have to read pg_authid twice during session startup. */ datum = caql_getattr(pcqCtx, Anum_pg_authid_rolconfig, &isnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); /* * We process all the options at SUSET level. We assume that the * right to insert an option into pg_authid was checked when it was * inserted. */ ProcessGUCArray(a, PGC_SUSET, PGC_S_USER, GUC_ACTION_SET); } caql_endscan(pcqCtx); }
/* * Extract len and pointer to buffer from an int16[] (vector) Datum * representing a PostgreSQL INT2OID type. */ static void extract_INT2OID_array(Datum array_datum, int *lenp, int16 **vecp) { ArrayType *array_type; Assert(lenp != NULL); Assert(vecp != NULL); array_type = DatumGetArrayTypeP(array_datum); Assert(ARR_NDIM(array_type) == 1); Assert(ARR_ELEMTYPE(array_type) == INT2OID); Assert(ARR_LBOUND(array_type)[0] == 1); *lenp = ARR_DIMS(array_type)[0]; *vecp = (int16 *) ARR_DATA_PTR(array_type); return; }
static PyObject * PLyList_FromArray(PLyDatumToOb *arg, Datum d) { ArrayType *array = DatumGetArrayTypeP(d); PLyDatumToOb *elm = arg->elm; PyObject *list; int length; int lbound; int i; if (ARR_NDIM(array) == 0) return PyList_New(0); if (ARR_NDIM(array) != 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot convert multidimensional array to Python list"), errdetail("PL/Python only supports one-dimensional arrays."))); length = ARR_DIMS(array)[0]; lbound = ARR_LBOUND(array)[0]; list = PyList_New(length); if (list == NULL) PLy_elog(ERROR, "could not create new Python list"); for (i = 0; i < length; i++) { Datum elem; bool isnull; int offset; offset = lbound + i; elem = array_ref(array, 1, &offset, arg->typlen, elm->typlen, elm->typbyval, elm->typalign, &isnull); if (isnull) { Py_INCREF(Py_None); PyList_SET_ITEM(list, i, Py_None); } else PyList_SET_ITEM(list, i, elm->func(elm, elem)); } return list; }
Datum pcpatch_from_pcpoint_array(PG_FUNCTION_ARGS) { ArrayType *array; PCPATCH *pa; SERIALIZED_PATCH *serpa; if ( PG_ARGISNULL(0) ) PG_RETURN_NULL(); array = DatumGetArrayTypeP(PG_GETARG_DATUM(0)); pa = pcpatch_from_point_array(array, fcinfo); if ( ! pa ) PG_RETURN_NULL(); serpa = pc_patch_serialize(pa, NULL); pc_patch_free(pa); PG_RETURN_POINTER(serpa); }
/* * Turn an array into JSON. */ static void array_to_jsonb_internal(Datum array, JsonbInState *result) { ArrayType *v = DatumGetArrayTypeP(array); Oid element_type = ARR_ELEMTYPE(v); int *dim; int ndim; int nitems; int count = 0; Datum *elements; bool *nulls; int16 typlen; bool typbyval; char typalign; JsonbTypeCategory tcategory; Oid outfuncoid; ndim = ARR_NDIM(v); dim = ARR_DIMS(v); nitems = ArrayGetNItems(ndim, dim); if (nitems <= 0) { result->res = pushJsonbValue(&result->parseState, WJB_BEGIN_ARRAY, NULL); result->res = pushJsonbValue(&result->parseState, WJB_END_ARRAY, NULL); return; } get_typlenbyvalalign(element_type, &typlen, &typbyval, &typalign); jsonb_categorize_type(element_type, &tcategory, &outfuncoid); deconstruct_array(v, element_type, typlen, typbyval, typalign, &elements, &nulls, &nitems); array_dim_to_jsonb(result, 0, ndim, dim, elements, nulls, &count, tcategory, outfuncoid); pfree(elements); pfree(nulls); }
/* * Scan pg_db_role_setting looking for applicable settings, and load them on * the current process. * * relsetting is pg_db_role_setting, already opened and locked. * * Note: we only consider setting for the exact databaseid/roleid combination. * This probably needs to be called more than once, with InvalidOid passed as * databaseid/roleid. */ void ApplySetting(Snapshot snapshot, Oid databaseid, Oid roleid, Relation relsetting, GucSource source) { SysScanDesc scan; ScanKeyData keys[2]; HeapTuple tup; ScanKeyInit(&keys[0], Anum_pg_db_role_setting_setdatabase, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(databaseid)); ScanKeyInit(&keys[1], Anum_pg_db_role_setting_setrole, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(roleid)); scan = systable_beginscan(relsetting, DbRoleSettingDatidRolidIndexId, true, snapshot, 2, keys); while (HeapTupleIsValid(tup = systable_getnext(scan))) { bool isnull; Datum datum; datum = heap_getattr(tup, Anum_pg_db_role_setting_setconfig, RelationGetDescr(relsetting), &isnull); if (!isnull) { ArrayType *a = DatumGetArrayTypeP(datum); /* * We process all the options at SUSET level. We assume that the * right to insert an option into pg_db_role_setting was checked * when it was inserted. */ ProcessGUCArray(a, PGC_SUSET, source, GUC_ACTION_SET); } } systable_endscan(scan); }
Datum pcpatch_agg_final_pcpatch(PG_FUNCTION_ARGS) { ArrayType *array; abs_trans *a; PCPATCH *pa; SERIALIZED_PATCH *serpa; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); /* returns null iff no input values */ a = (abs_trans*) PG_GETARG_POINTER(0); array = DatumGetArrayTypeP(pointcloud_agg_final(a, CurrentMemoryContext, fcinfo)); pa = pcpatch_from_patch_array(array, fcinfo); if ( ! pa ) PG_RETURN_NULL(); serpa = pc_patch_serialize(pa, NULL); pc_patch_free(pa); PG_RETURN_POINTER(serpa); }
/* * Decode text[] to an array of C strings. * * We could avoid a bit of overhead here if we were willing to duplicate some * of the logic from deconstruct_array, but it doesn't seem worth the code * complexity. */ static int DecodeTextArrayToCString(Datum array, char ***cstringp) { ArrayType *arr = DatumGetArrayTypeP(array); Datum *elems; char **cstring; int i; int nelems; if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) elog(ERROR, "expected 1-D text array"); deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems); cstring = palloc(nelems * sizeof(char *)); for (i = 0; i < nelems; ++i) cstring[i] = TextDatumGetCString(elems[i]); pfree(elems); *cstringp = cstring; return nelems; }
/* * Convert a SQL array to a Python list. */ static PyObject * PLyList_FromArray(PLyDatumToOb *arg, Datum d) { ArrayType *array = DatumGetArrayTypeP(d); PLyDatumToOb *elm = arg->u.array.elm; int ndim; int *dims; char *dataptr; bits8 *bitmap; int bitmask; if (ARR_NDIM(array) == 0) return PyList_New(0); /* Array dimensions and left bounds */ ndim = ARR_NDIM(array); dims = ARR_DIMS(array); Assert(ndim < MAXDIM); /* * We iterate the SQL array in the physical order it's stored in the * datum. For example, for a 3-dimensional array the order of iteration * would be the following: [0,0,0] elements through [0,0,k], then [0,1,0] * through [0,1,k] till [0,m,k], then [1,0,0] through [1,0,k] till * [1,m,k], and so on. * * In Python, there are no multi-dimensional lists as such, but they are * represented as a list of lists. So a 3-d array of [n,m,k] elements is a * list of n m-element arrays, each element of which is k-element array. * PLyList_FromArray_recurse() builds the Python list for a single * dimension, and recurses for the next inner dimension. */ dataptr = ARR_DATA_PTR(array); bitmap = ARR_NULLBITMAP(array); bitmask = 1; return PLyList_FromArray_recurse(elm, dims, ndim, 0, &dataptr, &bitmap, &bitmask); }
/* * get_func_trftypes * * Returns a number of transformated types used by function. */ int get_func_trftypes(HeapTuple procTup, Oid **p_trftypes) { Datum protrftypes; ArrayType *arr; int nelems; bool isNull; protrftypes = SysCacheGetAttr(PROCOID, procTup, Anum_pg_proc_protrftypes, &isNull); if (!isNull) { /* * We expect the arrays to be 1-D arrays of the right types; verify * that. For the OID and char arrays, we don't need to use * deconstruct_array() since the array data is just going to look like * a C array of values. */ arr = DatumGetArrayTypeP(protrftypes); /* ensure not toasted */ nelems = ARR_DIMS(arr)[0]; if (ARR_NDIM(arr) != 1 || nelems < 0 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != OIDOID) elog(ERROR, "protrftypes is not a 1-D Oid array"); Assert(nelems >= ((Form_pg_proc) GETSTRUCT(procTup))->pronargs); *p_trftypes = (Oid *) palloc(nelems * sizeof(Oid)); memcpy(*p_trftypes, ARR_DATA_PTR(arr), nelems * sizeof(Oid)); return nelems; } else return 0; }
static PyObj array_slice(PyObj self, Py_ssize_t from, Py_ssize_t to) { PyObj elm; PyPgTypeInfo etc; ArrayType *at, *rat = NULL; PyObj rob = NULL; int idx_lower[MAXDIM] = {(int) from+1, 0,}; int idx_upper[MAXDIM] = {(int) to+1, 0,}; elm = PyPgType_GetElementType(Py_TYPE(self)); Assert(elm != NULL); etc = PyPgTypeInfo(elm); Assert(etc != NULL); at = DatumGetArrayTypeP(PyPgObject_GetDatum(self)); Assert(at != NULL); PG_TRY(); { rat = array_get_slice(at, 1, idx_upper, idx_lower, PyPgTypeInfo(Py_TYPE(self))->typlen, etc->typlen, etc->typbyval, etc->typalign); rob = PyPgObject_New(Py_TYPE(self), PointerGetDatum(rat)); if (rob == NULL) pfree(rat); } PG_CATCH(); { PyErr_SetPgError(false); return(NULL); } PG_END_TRY(); return(rob); }
/* * ExecIndexEvalArrayKeys * Evaluate any array key values, and set up to iterate through arrays. * * Returns TRUE if there are array elements to consider; FALSE means there * is at least one null or empty array, so no match is possible. On TRUE * result, the scankeys are initialized with the first elements of the arrays. */ bool ExecIndexEvalArrayKeys(ExprContext *econtext, IndexArrayKeyInfo *arrayKeys, int numArrayKeys) { bool result = true; int j; MemoryContext oldContext; /* We want to keep the arrays in per-tuple memory */ oldContext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); for (j = 0; j < numArrayKeys; j++) { ScanKey scan_key = arrayKeys[j].scan_key; ExprState *array_expr = arrayKeys[j].array_expr; Datum arraydatum; bool isNull; ArrayType *arrayval; int16 elmlen; bool elmbyval; char elmalign; int num_elems; Datum *elem_values; bool *elem_nulls; /* * Compute and deconstruct the array expression. (Notes in * ExecIndexEvalRuntimeKeys() apply here too.) */ arraydatum = ExecEvalExpr(array_expr, econtext, &isNull, NULL); if (isNull) { result = false; break; /* no point in evaluating more */ } arrayval = DatumGetArrayTypeP(arraydatum); /* We could cache this data, but not clear it's worth it */ get_typlenbyvalalign(ARR_ELEMTYPE(arrayval), &elmlen, &elmbyval, &elmalign); deconstruct_array(arrayval, ARR_ELEMTYPE(arrayval), elmlen, elmbyval, elmalign, &elem_values, &elem_nulls, &num_elems); if (num_elems <= 0) { result = false; break; /* no point in evaluating more */ } /* * Note: we expect the previous array data, if any, to be * automatically freed by resetting the per-tuple context; hence no * pfree's here. */ arrayKeys[j].elem_values = elem_values; arrayKeys[j].elem_nulls = elem_nulls; arrayKeys[j].num_elems = num_elems; scan_key->sk_argument = elem_values[0]; if (elem_nulls[0]) scan_key->sk_flags |= SK_ISNULL; else scan_key->sk_flags &= ~SK_ISNULL; arrayKeys[j].next_elem = 1; } MemoryContextSwitchTo(oldContext); return result; }
/* * Calculate selectivity for "arraycolumn @> const", "arraycolumn && const" * or "arraycolumn <@ const" based on the statistics * * This function is mainly responsible for extracting the pg_statistic data * to be used; we then pass the problem on to mcelem_array_selec(). */ static Selectivity calc_arraycontsel(VariableStatData *vardata, Datum constval, Oid elemtype, Oid operator) { Selectivity selec; TypeCacheEntry *typentry; FmgrInfo *cmpfunc; ArrayType *array; /* Get element type's default comparison function */ typentry = lookup_type_cache(elemtype, TYPECACHE_CMP_PROC_FINFO); if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid)) return DEFAULT_SEL(operator); cmpfunc = &typentry->cmp_proc_finfo; /* * The caller made sure the const is an array with same element type, so * get it now */ array = DatumGetArrayTypeP(constval); if (HeapTupleIsValid(vardata->statsTuple) && statistic_proc_security_check(vardata, cmpfunc->fn_oid)) { Form_pg_statistic stats; AttStatsSlot sslot; AttStatsSlot hslot; stats = (Form_pg_statistic) GETSTRUCT(vardata->statsTuple); /* MCELEM will be an array of same type as column */ if (get_attstatsslot(&sslot, vardata->statsTuple, STATISTIC_KIND_MCELEM, InvalidOid, ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS)) { /* * For "array <@ const" case we also need histogram of distinct * element counts. */ if (operator != OID_ARRAY_CONTAINED_OP || !get_attstatsslot(&hslot, vardata->statsTuple, STATISTIC_KIND_DECHIST, InvalidOid, ATTSTATSSLOT_NUMBERS)) memset(&hslot, 0, sizeof(hslot)); /* Use the most-common-elements slot for the array Var. */ selec = mcelem_array_selec(array, typentry, sslot.values, sslot.nvalues, sslot.numbers, sslot.nnumbers, hslot.numbers, hslot.nnumbers, operator, cmpfunc); free_attstatsslot(&hslot); free_attstatsslot(&sslot); } else { /* No most-common-elements info, so do without */ selec = mcelem_array_selec(array, typentry, NULL, 0, NULL, 0, NULL, 0, operator, cmpfunc); } /* * MCE stats count only non-null rows, so adjust for null rows. */ selec *= (1.0 - stats->stanullfrac); } else { /* No stats at all, so do without */ selec = mcelem_array_selec(array, typentry, NULL, 0, NULL, 0, NULL, 0, operator, cmpfunc); /* we assume no nulls here, so no stanullfrac correction */ } /* If constant was toasted, release the copy we made */ if (PointerGetDatum(array) != constval) pfree(array); return selec; }
/* * ErrorIfUnsupportedForeignConstraint runs checks related to foreign constraints and * errors out if it is not possible to create one of the foreign constraint in distributed * environment. * * To support foreign constraints, we require that; * - If referencing and referenced tables are hash-distributed * - Referencing and referenced tables are co-located. * - Foreign constraint is defined over distribution column. * - ON DELETE/UPDATE SET NULL, ON DELETE/UPDATE SET DEFAULT and ON UPDATE CASCADE options * are not used. * - Replication factors of referencing and referenced table are 1. * - If referenced table is a reference table * - ON DELETE/UPDATE SET NULL, ON DELETE/UPDATE SET DEFAULT and ON UPDATE CASCADE options * are not used on the distribution key of the referencing column. * - If referencing table is a reference table, error out */ void ErrorIfUnsupportedForeignConstraint(Relation relation, char distributionMethod, Var *distributionColumn, uint32 colocationId) { Relation pgConstraint = NULL; SysScanDesc scanDescriptor = NULL; ScanKeyData scanKey[1]; int scanKeyCount = 1; HeapTuple heapTuple = NULL; Oid referencingTableId = relation->rd_id; Oid referencedTableId = InvalidOid; uint32 referencedTableColocationId = INVALID_COLOCATION_ID; Var *referencedTablePartitionColumn = NULL; Datum referencingColumnsDatum = 0; Datum *referencingColumnArray = NULL; int referencingColumnCount = 0; Datum referencedColumnsDatum = 0; Datum *referencedColumnArray = NULL; int referencedColumnCount = 0; bool isNull = false; int attrIdx = 0; bool foreignConstraintOnPartitionColumn = false; bool selfReferencingTable = false; bool referencedTableIsAReferenceTable = false; bool referencingColumnsIncludeDistKey = false; pgConstraint = heap_open(ConstraintRelationId, AccessShareLock); ScanKeyInit(&scanKey[0], Anum_pg_constraint_conrelid, BTEqualStrategyNumber, F_OIDEQ, relation->rd_id); scanDescriptor = systable_beginscan(pgConstraint, ConstraintRelidIndexId, true, NULL, scanKeyCount, scanKey); heapTuple = systable_getnext(scanDescriptor); while (HeapTupleIsValid(heapTuple)) { Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(heapTuple); bool singleReplicatedTable = true; if (constraintForm->contype != CONSTRAINT_FOREIGN) { heapTuple = systable_getnext(scanDescriptor); continue; } /* * We should make this check in this loop because the error message will only * be given if the table has a foreign constraint and the table is a reference * table. */ if (distributionMethod == DISTRIBUTE_BY_NONE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint because " "reference tables are not supported as the " "referencing table of a foreign constraint"), errdetail("Reference tables are only supported as the " "referenced table of a foreign key when the " "referencing table is a hash distributed " "table"))); } referencedTableId = constraintForm->confrelid; selfReferencingTable = referencingTableId == referencedTableId; /* * Some checks are not meaningful if foreign key references the table itself. * Therefore we will skip those checks. */ if (!selfReferencingTable) { if (!IsDistributedTable(referencedTableId)) { ereport(ERROR, (errcode(ERRCODE_INVALID_TABLE_DEFINITION), errmsg("cannot create foreign key constraint"), errdetail("Referenced table must be a distributed " "table."))); } /* * PartitionMethod errors out when it is called for non-distributed * tables. This is why we make this check under !selfReferencingTable * and after !IsDistributedTable(referencedTableId). */ if (PartitionMethod(referencedTableId) == DISTRIBUTE_BY_NONE) { referencedTableIsAReferenceTable = true; } /* * To enforce foreign constraints, tables must be co-located unless a * reference table is referenced. */ referencedTableColocationId = TableColocationId(referencedTableId); if (colocationId == INVALID_COLOCATION_ID || (colocationId != referencedTableColocationId && !referencedTableIsAReferenceTable)) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint since " "relations are not colocated or not referencing " "a reference table"), errdetail( "A distributed table can only have foreign keys " "if it is referencing another colocated hash " "distributed table or a reference table"))); } referencedTablePartitionColumn = DistPartitionKey(referencedTableId); } else { /* * If the referenced table is not a reference table, the distribution * column in referencing table should be the distribution column in * referenced table as well. */ referencedTablePartitionColumn = distributionColumn; } /* * Column attributes are not available in Form_pg_constraint, therefore we need * to find them in the system catalog. After finding them, we iterate over column * attributes together because partition column must be at the same place in both * referencing and referenced side of the foreign key constraint */ referencingColumnsDatum = SysCacheGetAttr(CONSTROID, heapTuple, Anum_pg_constraint_conkey, &isNull); referencedColumnsDatum = SysCacheGetAttr(CONSTROID, heapTuple, Anum_pg_constraint_confkey, &isNull); deconstruct_array(DatumGetArrayTypeP(referencingColumnsDatum), INT2OID, 2, true, 's', &referencingColumnArray, NULL, &referencingColumnCount); deconstruct_array(DatumGetArrayTypeP(referencedColumnsDatum), INT2OID, 2, true, 's', &referencedColumnArray, NULL, &referencedColumnCount); Assert(referencingColumnCount == referencedColumnCount); for (attrIdx = 0; attrIdx < referencingColumnCount; ++attrIdx) { AttrNumber referencingAttrNo = DatumGetInt16(referencingColumnArray[attrIdx]); AttrNumber referencedAttrNo = DatumGetInt16(referencedColumnArray[attrIdx]); if (distributionColumn->varattno == referencingAttrNo && (!referencedTableIsAReferenceTable && referencedTablePartitionColumn->varattno == referencedAttrNo)) { foreignConstraintOnPartitionColumn = true; } if (distributionColumn->varattno == referencingAttrNo) { referencingColumnsIncludeDistKey = true; } } /* * If columns in the foreign key includes the distribution key from the * referencing side, we do not allow update/delete operations through * foreign key constraints (e.g. ... ON UPDATE SET NULL) */ if (referencingColumnsIncludeDistKey) { /* * ON DELETE SET NULL and ON DELETE SET DEFAULT is not supported. Because we do * not want to set partition column to NULL or default value. */ if (constraintForm->confdeltype == FKCONSTR_ACTION_SETNULL || constraintForm->confdeltype == FKCONSTR_ACTION_SETDEFAULT) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), errdetail("SET NULL or SET DEFAULT is not supported" " in ON DELETE operation when distribution " "key is included in the foreign key constraint"))); } /* * ON UPDATE SET NULL, ON UPDATE SET DEFAULT and UPDATE CASCADE is not supported. * Because we do not want to set partition column to NULL or default value. Also * cascading update operation would require re-partitioning. Updating partition * column value is not allowed anyway even outside of foreign key concept. */ if (constraintForm->confupdtype == FKCONSTR_ACTION_SETNULL || constraintForm->confupdtype == FKCONSTR_ACTION_SETDEFAULT || constraintForm->confupdtype == FKCONSTR_ACTION_CASCADE) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), errdetail("SET NULL, SET DEFAULT or CASCADE is not " "supported in ON UPDATE operation when " "distribution key included in the foreign " "constraint."))); } } /* * if tables are hash-distributed and colocated, we need to make sure that * the distribution key is included in foreign constraint. */ if (!referencedTableIsAReferenceTable && !foreignConstraintOnPartitionColumn) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), errdetail("Foreign keys are supported in two cases, " "either in between two colocated tables including " "partition column in the same ordinal in the both " "tables or from distributed to reference tables"))); } /* * We do not allow to create foreign constraints if shard replication factor is * greater than 1. Because in our current design, multiple replicas may cause * locking problems and inconsistent shard contents. * * Note that we allow referenced table to be a reference table (e.g., not a * single replicated table). This is allowed since (a) we are sure that * placements always be in the same state (b) executors are aware of reference * tables and handle concurrency related issues accordingly. */ if (IsDistributedTable(referencingTableId)) { /* check whether ALTER TABLE command is applied over single replicated table */ if (!SingleReplicatedTable(referencingTableId)) { singleReplicatedTable = false; } } else { Assert(distributionMethod == DISTRIBUTE_BY_HASH); /* check whether creating single replicated table with foreign constraint */ if (ShardReplicationFactor > 1) { singleReplicatedTable = false; } } if (!singleReplicatedTable) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot create foreign key constraint"), errdetail("Citus Community Edition currently supports " "foreign key constraints only for " "\"citus.shard_replication_factor = 1\"."), errhint("Please change \"citus.shard_replication_factor to " "1\". To learn more about using foreign keys with " "other replication factors, please contact us at " "https://citusdata.com/about/contact_us."))); } heapTuple = systable_getnext(scanDescriptor); } /* clean up scan and close system catalog */ systable_endscan(scanDescriptor); heap_close(pgConstraint, AccessShareLock); }