/* * contrib/hstore/hstore_io.c */ #include "postgres.h" #include #include "access/htup_details.h" #include "catalog/pg_type.h" #include "catalog/pg_cast.h" #include "funcapi.h" #include "libpq/pqformat.h" #include "miscadmin.h" #include "parser/parse_coerce.h" #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/json.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/syscache.h" #include "utils/typcache.h" #include "hstore.h" PG_MODULE_MAGIC; /* old names for C functions */ HSTORE_POLLUTE(hstore_from_text, tconvert); /* GUC variables */ static bool pretty_print_var = false; #define SET_PRETTY_PRINT_VAR(x) ((pretty_print_var) ? \ ((x) | PrettyPrint) : (x)) static void recvHStore(StringInfo buf, HStoreValue *v, uint32 level, uint32 header); static Oid searchCast(Oid src, Oid dst, CoercionMethod *method); static size_t hstoreCheckKeyLen(size_t len) { if (len > HSTORE_MAX_KEY_LEN) ereport(ERROR, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("string too long for hstore key"))); return len; } static size_t hstoreCheckValLen(size_t len) { if (len > HSTORE_MAX_VALUE_LEN) ereport(ERROR, (errcode(ERRCODE_STRING_DATA_RIGHT_TRUNCATION), errmsg("string too long for hstore value"))); return len; } static HStore* hstoreDump(HStoreValue *p) { uint32 buflen; HStore *out; if (p == NULL || (p->type == hsvArray && p->array.nelems == 0) || (p->type == hsvHash && p->hash.npairs == 0)) { buflen = 0; out = palloc(VARHDRSZ); } else { buflen = VARHDRSZ + p->size; out = palloc(buflen); SET_VARSIZE(out, buflen); buflen = compressHStore(p, VARDATA(out)); } SET_VARSIZE(out, buflen + VARHDRSZ); return out; } PG_FUNCTION_INFO_V1(hstore_in); Datum hstore_in(PG_FUNCTION_ARGS); Datum hstore_in(PG_FUNCTION_ARGS) { PG_RETURN_POINTER(hstoreDump(parseHStore(PG_GETARG_CSTRING(0), -1, false))); } static void recvHStoreValue(StringInfo buf, HStoreValue *v, uint32 level, int c) { uint32 hentry = c & HENTRY_TYPEMASK; check_stack_depth(); if (c == -1 /* compatibility */ || hentry == HENTRY_ISNULL) { v->type = hsvNull; v->size = sizeof(HEntry); } else if (hentry == HENTRY_ISHASH || hentry == HENTRY_ISARRAY || hentry == HENTRY_ISSCALAR) { recvHStore(buf, v, level + 1, (uint32)c); } else if (hentry == HENTRY_ISFALSE || hentry == HENTRY_ISTRUE) { v->type = hsvBool; v->size = sizeof(HEntry); v->boolean = (hentry == HENTRY_ISFALSE) ? false : true; } else if (hentry == HENTRY_ISNUMERIC) { v->type = hsvNumeric; v->numeric = DatumGetNumeric(DirectFunctionCall3(numeric_recv, PointerGetDatum(buf), Int32GetDatum(0), Int32GetDatum(-1))); v->size = sizeof(HEntry) * 2 + VARSIZE_ANY(v->numeric); } else if (hentry == HENTRY_ISSTRING) { v->type = hsvString; v->string.val = pq_getmsgtext(buf, c & ~HENTRY_TYPEMASK, &c); v->string.len = hstoreCheckKeyLen(c); v->size = sizeof(HEntry) + v->string.len; } else { ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("unknown hstore value"))); } } static void recvHStore(StringInfo buf, HStoreValue *v, uint32 level, uint32 header) { uint32 hentry; uint32 i; hentry = header & HENTRY_TYPEMASK; if (level == 0 && hentry == 0) hentry = HENTRY_ISHASH; /* old version */ check_stack_depth(); v->size = 3 * sizeof(HEntry); if (hentry == HENTRY_ISHASH) { v->type = hsvHash; v->hash.npairs = header & HS_COUNT_MASK; if (v->hash.npairs > (buf->len - buf->cursor) / (2 * sizeof(uint32)) || v->hash.npairs > MaxAllocSize / sizeof(HStorePair)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("too much elements in hstore hash"))); if (v->hash.npairs > 0) { v->hash.pairs = palloc(sizeof(*v->hash.pairs) * v->hash.npairs); for(i=0; ihash.npairs; i++) { recvHStoreValue(buf, &v->hash.pairs[i].key, level, pq_getmsgint(buf, 4)); if (v->hash.pairs[i].key.type != hsvString) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("hstore's key could be only a string"))); recvHStoreValue(buf, &v->hash.pairs[i].value, level, pq_getmsgint(buf, 4)); v->size += v->hash.pairs[i].key.size + v->hash.pairs[i].value.size; } uniqueHStoreValue(v); } } else if (hentry == HENTRY_ISARRAY || hentry == HENTRY_ISSCALAR) { v->type = hsvArray; v->array.nelems = header & HS_COUNT_MASK; v->array.scalar = (hentry == HENTRY_ISSCALAR) ? true : false; if (v->array.nelems > (buf->len - buf->cursor) / sizeof(uint32) || v->array.nelems > MaxAllocSize / sizeof(HStoreValue)) ereport(ERROR, (errcode(ERRCODE_NUMERIC_VALUE_OUT_OF_RANGE), errmsg("too much elements in hstore array"))); if (v->array.scalar && v->array.nelems != 1) ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("wrong scalar representation"))); if (v->array.nelems > 0) { v->array.elems = palloc(sizeof(*v->array.elems) * v->array.nelems); for(i=0; iarray.nelems; i++) { recvHStoreValue(buf, v->array.elems + i, level, pq_getmsgint(buf, 4)); v->size += v->array.elems[i].size; } } } else { ereport(ERROR, (errcode(ERRCODE_INVALID_BINARY_REPRESENTATION), errmsg("unknown hstore element"))); } } PG_FUNCTION_INFO_V1(hstore_recv); Datum hstore_recv(PG_FUNCTION_ARGS); Datum hstore_recv(PG_FUNCTION_ARGS) { StringInfo buf = (StringInfo) PG_GETARG_POINTER(0); HStoreValue v; recvHStore(buf, &v, 0, pq_getmsgint(buf, 4)); PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_text); Datum hstore_from_text(PG_FUNCTION_ARGS); Datum hstore_from_text(PG_FUNCTION_ARGS) { text *key; HStoreValue v; HStorePair pair; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); key = PG_GETARG_TEXT_PP(0); pair.key.type = hsvString; pair.key.string.val = VARDATA_ANY(key); pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); pair.key.size = pair.key.string.len + sizeof(HEntry); if (PG_ARGISNULL(1)) { pair.value.type = hsvNull; pair.value.size = sizeof(HEntry); } else { text *val = NULL; val = PG_GETARG_TEXT_PP(1); pair.value.type = hsvString; pair.value.string.val = VARDATA_ANY(val); pair.value.string.len = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val)); pair.value.size = pair.value.string.len + sizeof(HEntry); } v.type = hsvHash; v.size = sizeof(HEntry) + pair.key.size + pair.value.size; v.hash.npairs = 1; v.hash.pairs = &pair; PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_bool); Datum hstore_from_bool(PG_FUNCTION_ARGS); Datum hstore_from_bool(PG_FUNCTION_ARGS) { text *key; HStoreValue v; HStorePair pair; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); key = PG_GETARG_TEXT_PP(0); pair.key.type = hsvString; pair.key.string.val = VARDATA_ANY(key); pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); pair.key.size = pair.key.string.len + sizeof(HEntry); if (PG_ARGISNULL(1)) { pair.value.type = hsvNull; pair.value.size = sizeof(HEntry); } else { pair.value.type = hsvBool; pair.value.boolean = PG_GETARG_BOOL(1); pair.value.size = sizeof(HEntry); } v.type = hsvHash; v.size = sizeof(HEntry) + pair.key.size + pair.value.size; v.hash.npairs = 1; v.hash.pairs = &pair; PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_numeric); Datum hstore_from_numeric(PG_FUNCTION_ARGS); Datum hstore_from_numeric(PG_FUNCTION_ARGS) { text *key; HStoreValue v; HStorePair pair; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); key = PG_GETARG_TEXT_PP(0); pair.key.type = hsvString; pair.key.string.val = VARDATA_ANY(key); pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); pair.key.size = pair.key.string.len + sizeof(HEntry); if (PG_ARGISNULL(1)) { pair.value.type = hsvNull; pair.value.size = sizeof(HEntry); } else { pair.value.type = hsvNumeric; pair.value.numeric = PG_GETARG_NUMERIC(1); pair.value.size = sizeof(HEntry) + sizeof(HEntry) + VARSIZE_ANY(pair.value.numeric); } v.type = hsvHash; v.size = sizeof(HEntry) + pair.key.size + pair.value.size; v.hash.npairs = 1; v.hash.pairs = &pair; PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_th); Datum hstore_from_th(PG_FUNCTION_ARGS); Datum hstore_from_th(PG_FUNCTION_ARGS) { text *key; HStoreValue v; HStorePair pair; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); key = PG_GETARG_TEXT_PP(0); pair.key.type = hsvString; pair.key.string.val = VARDATA_ANY(key); pair.key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); pair.key.size = pair.key.string.len + sizeof(HEntry); if (PG_ARGISNULL(1)) { pair.value.type = hsvNull; pair.value.size = sizeof(HEntry); } else { HStore *val = NULL; val = PG_GETARG_HS(1); pair.value.type = hsvBinary; pair.value.binary.data = VARDATA_ANY(val); pair.value.binary.len = VARSIZE_ANY_EXHDR(val); pair.value.size = pair.value.binary.len + sizeof(HEntry) * 2; } v.type = hsvHash; v.size = sizeof(HEntry) + pair.key.size + pair.value.size; v.hash.npairs = 1; v.hash.pairs = &pair; PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_arrays); PG_FUNCTION_INFO_V1(hstore_scalar_from_text); Datum hstore_scalar_from_text(PG_FUNCTION_ARGS); Datum hstore_scalar_from_text(PG_FUNCTION_ARGS) { HStoreValue a, v; if (PG_ARGISNULL(0)) { v.type = hsvNull; v.size = sizeof(HEntry); } else { text *scalar; scalar = PG_GETARG_TEXT_PP(0); v.type = hsvString; v.string.val = VARDATA_ANY(scalar); v.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(scalar)); v.size = v.string.len + sizeof(HEntry); } a.type = hsvArray; a.size = sizeof(HEntry) + v.size; a.array.nelems = 1; a.array.elems = &v; a.array.scalar = true; PG_RETURN_POINTER(hstoreDump(&a)); } PG_FUNCTION_INFO_V1(hstore_scalar_from_bool); Datum hstore_scalar_from_bool(PG_FUNCTION_ARGS); Datum hstore_scalar_from_bool(PG_FUNCTION_ARGS) { HStoreValue a, v; if (PG_ARGISNULL(0)) { v.type = hsvNull; v.size = sizeof(HEntry); } else { v.type = hsvBool; v.boolean = PG_GETARG_BOOL(0); v.size = sizeof(HEntry); } a.type = hsvArray; a.size = sizeof(HEntry) + v.size; a.array.nelems = 1; a.array.elems = &v; a.array.scalar = true; PG_RETURN_POINTER(hstoreDump(&a)); } PG_FUNCTION_INFO_V1(hstore_scalar_from_numeric); Datum hstore_scalar_from_numeric(PG_FUNCTION_ARGS); Datum hstore_scalar_from_numeric(PG_FUNCTION_ARGS) { HStoreValue a, v; if (PG_ARGISNULL(0)) { v.type = hsvNull; v.size = sizeof(HEntry); } else { v.type = hsvNumeric; v.numeric = PG_GETARG_NUMERIC(0); v.size = VARSIZE_ANY(v.numeric) + 2*sizeof(HEntry); } a.type = hsvArray; a.size = sizeof(HEntry) + v.size; a.array.nelems = 1; a.array.elems = &v; a.array.scalar = true; PG_RETURN_POINTER(hstoreDump(&a)); } Datum hstore_from_arrays(PG_FUNCTION_ARGS); Datum hstore_from_arrays(PG_FUNCTION_ARGS) { HStoreValue v; Datum *key_datums; bool *key_nulls; int key_count; Datum *value_datums; bool *value_nulls; int value_count; ArrayType *key_array; ArrayType *value_array; int i; if (PG_ARGISNULL(0)) PG_RETURN_NULL(); key_array = PG_GETARG_ARRAYTYPE_P(0); Assert(ARR_ELEMTYPE(key_array) == TEXTOID); /* * must check >1 rather than != 1 because empty arrays have 0 dimensions, * not 1 */ if (ARR_NDIM(key_array) > 1) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); deconstruct_array(key_array, TEXTOID, -1, false, 'i', &key_datums, &key_nulls, &key_count); /* see discussion in arrayToHStoreSortedArray() */ if (key_count > MaxAllocSize / sizeof(HStorePair)) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", key_count, (int) (MaxAllocSize / sizeof(HStorePair))))); /* value_array might be NULL */ if (PG_ARGISNULL(1)) { value_array = NULL; value_count = key_count; value_datums = NULL; value_nulls = NULL; } else { value_array = PG_GETARG_ARRAYTYPE_P(1); Assert(ARR_ELEMTYPE(value_array) == TEXTOID); if (ARR_NDIM(value_array) > 1) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); if ((ARR_NDIM(key_array) > 0 || ARR_NDIM(value_array) > 0) && (ARR_NDIM(key_array) != ARR_NDIM(value_array) || ARR_DIMS(key_array)[0] != ARR_DIMS(value_array)[0] || ARR_LBOUND(key_array)[0] != ARR_LBOUND(value_array)[0])) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("arrays must have same bounds"))); deconstruct_array(value_array, TEXTOID, -1, false, 'i', &value_datums, &value_nulls, &value_count); Assert(key_count == value_count); } v.type = hsvHash; v.size = 2 * sizeof(HEntry); v.hash.pairs = palloc(key_count * sizeof(*v.hash.pairs)); v.hash.npairs = key_count; for (i = 0; i < key_count; ++i) { if (key_nulls[i]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for hstore key"))); v.hash.pairs[i].key.type = hsvString; v.hash.pairs[i].key.string.val = VARDATA_ANY(key_datums[i]); v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key_datums[i])); v.hash.pairs[i].key.size = sizeof(HEntry) + v.hash.pairs[i].key.string.len; if (!value_nulls || value_nulls[i]) { v.hash.pairs[i].value.type = hsvNull; v.hash.pairs[i].value.size = sizeof(HEntry); } else { v.hash.pairs[i].value.type = hsvString; v.hash.pairs[i].value.size = sizeof(HEntry); v.hash.pairs[i].value.string.val = VARDATA_ANY(value_datums[i]); v.hash.pairs[i].value.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(value_datums[i])); v.hash.pairs[i].value.size = sizeof(HEntry) + v.hash.pairs[i].value.string.len; } v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size; } uniqueHStoreValue(&v); PG_RETURN_POINTER(hstoreDump(&v)); } PG_FUNCTION_INFO_V1(hstore_from_array); Datum hstore_from_array(PG_FUNCTION_ARGS); Datum hstore_from_array(PG_FUNCTION_ARGS) { ArrayType *in_array = PG_GETARG_ARRAYTYPE_P(0); int ndims = ARR_NDIM(in_array); int count; HStoreValue v; Datum *in_datums; bool *in_nulls; int in_count; int i; Assert(ARR_ELEMTYPE(in_array) == TEXTOID); switch (ndims) { case 0: PG_RETURN_POINTER(hstoreDump(NULL)); case 1: if ((ARR_DIMS(in_array)[0]) % 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have even number of elements"))); break; case 2: if ((ARR_DIMS(in_array)[1]) != 2) ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("array must have two columns"))); break; default: ereport(ERROR, (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR), errmsg("wrong number of array subscripts"))); } deconstruct_array(in_array, TEXTOID, -1, false, 'i', &in_datums, &in_nulls, &in_count); count = in_count / 2; /* see discussion in arrayToHStoreSortedArray() */ if (count > MaxAllocSize / sizeof(HStorePair)) ereport(ERROR, (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED), errmsg("number of pairs (%d) exceeds the maximum allowed (%d)", count, (int) (MaxAllocSize / sizeof(HStorePair))))); v.type = hsvHash; v.size = 2*sizeof(HEntry); v.hash.npairs = count; v.hash.pairs = palloc(count * sizeof(HStorePair)); for (i = 0; i < count; ++i) { if (in_nulls[i * 2]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("null value not allowed for hstore key"))); v.hash.pairs[i].key.type = hsvString; v.hash.pairs[i].key.string.val = VARDATA_ANY(in_datums[i * 2]); v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2])); v.hash.pairs[i].key.size = sizeof(HEntry) + v.hash.pairs[i].key.string.len; if (in_nulls[i * 2 + 1]) { v.hash.pairs[i].value.type = hsvNull; v.hash.pairs[i].value.size = sizeof(HEntry); } else { v.hash.pairs[i].value.type = hsvString; v.hash.pairs[i].value.size = sizeof(HEntry); v.hash.pairs[i].value.string.val = VARDATA_ANY(in_datums[i * 2 + 1]); v.hash.pairs[i].value.string.len = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(in_datums[i * 2 + 1])); v.hash.pairs[i].value.size = sizeof(HEntry) + v.hash.pairs[i].value.string.len; } v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size; } uniqueHStoreValue(&v); PG_RETURN_POINTER(hstoreDump(&v)); } /* most of hstore_from_record is shamelessly swiped from record_out */ /* * structure to cache metadata needed for record I/O */ typedef struct ColumnIOData { Oid column_type; Oid typiofunc; Oid typioparam; FmgrInfo proc; } ColumnIOData; typedef struct RecordIOData { Oid record_type; int32 record_typmod; int ncolumns; ColumnIOData columns[1]; /* VARIABLE LENGTH ARRAY */ } RecordIOData; PG_FUNCTION_INFO_V1(hstore_from_record); Datum hstore_from_record(PG_FUNCTION_ARGS); Datum hstore_from_record(PG_FUNCTION_ARGS) { HeapTupleHeader rec; HStore *out; HStoreValue v; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; RecordIOData *my_extra; int ncolumns; int i; Datum *values; bool *nulls; if (PG_ARGISNULL(0)) { Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); /* * have no tuple to look at, so the only source of type info is the * argtype. The lookup_rowtype_tupdesc call below will error out if we * don't have a known composite type oid here. */ tupType = argtype; tupTypmod = -1; rec = NULL; } else { rec = PG_GETARG_HEAPTUPLEHEADER(0); /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); } tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } v.type = hsvHash; v.size = 2*sizeof(HEntry); v.hash.npairs = ncolumns; Assert(ncolumns <= MaxTupleAttributeNumber); /* thus, no overflow */ v.hash.pairs = palloc(ncolumns * sizeof(HStorePair)); if (rec) { /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); } else { values = NULL; nulls = NULL; } for (i = 0; i < ncolumns; ++i) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; char *value; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) continue; v.hash.pairs[i].key.type = hsvString; v.hash.pairs[i].key.string.val = NameStr(tupdesc->attrs[i]->attname); v.hash.pairs[i].key.string.len = hstoreCheckKeyLen(strlen(v.hash.pairs[i].key.string.val)); v.hash.pairs[i].key.size = sizeof(HEntry) + v.hash.pairs[i].key.string.len; if (!nulls || nulls[i]) { v.hash.pairs[i].value.type = hsvNull; v.hash.pairs[i].value.size = sizeof(HEntry); } else { /* * Convert the column value to hstore's values */ if (column_type == BOOLOID) { v.hash.pairs[i].value.type = hsvBool; v.hash.pairs[i].value.boolean = DatumGetBool(values[i]); v.hash.pairs[i].value.size = sizeof(HEntry); } else if (TypeCategory(column_type) == TYPCATEGORY_NUMERIC) { Oid castOid = InvalidOid; CoercionMethod method; v.hash.pairs[i].value.type = hsvNumeric; castOid = searchCast(column_type, NUMERICOID, &method); if (castOid == InvalidOid) { if (method != COERCION_METHOD_BINARY) elog(ERROR, "Could not cast numeric category type to numeric '%c'", (char)method); v.hash.pairs[i].value.numeric = DatumGetNumeric(values[i]); } else { v.hash.pairs[i].value.numeric = DatumGetNumeric(OidFunctionCall1(castOid, values[i])); } v.hash.pairs[i].value.size = 2*sizeof(HEntry) + VARSIZE_ANY(v.hash.pairs[i].value.numeric); } else { if (column_info->column_type != column_type) { bool typIsVarlena; getTypeOutputInfo(column_type, &column_info->typiofunc, &typIsVarlena); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } value = OutputFunctionCall(&column_info->proc, values[i]); v.hash.pairs[i].value.type = hsvString; v.hash.pairs[i].value.string.val = value; v.hash.pairs[i].value.string.len = hstoreCheckValLen(strlen(value)); v.hash.pairs[i].value.size = sizeof(HEntry) + v.hash.pairs[i].value.string.len; } } v.size += v.hash.pairs[i].key.size + v.hash.pairs[i].value.size; } uniqueHStoreValue(&v); out = hstoreDump(&v); ReleaseTupleDesc(tupdesc); PG_RETURN_POINTER(out); } PG_FUNCTION_INFO_V1(hstore_populate_record); Datum hstore_populate_record(PG_FUNCTION_ARGS); Datum hstore_populate_record(PG_FUNCTION_ARGS) { Oid argtype = get_fn_expr_argtype(fcinfo->flinfo, 0); HStore *hs; HeapTupleHeader rec; Oid tupType; int32 tupTypmod; TupleDesc tupdesc; HeapTupleData tuple; HeapTuple rettuple; RecordIOData *my_extra; int ncolumns; int i; Datum *values; bool *nulls; if (!type_is_rowtype(argtype)) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("first argument must be a rowtype"))); if (PG_ARGISNULL(0)) { if (PG_ARGISNULL(1)) PG_RETURN_NULL(); rec = NULL; /* * have no tuple to look at, so the only source of type info is the * argtype. The lookup_rowtype_tupdesc call below will error out if we * don't have a known composite type oid here. */ tupType = argtype; tupTypmod = -1; } else { rec = PG_GETARG_HEAPTUPLEHEADER(0); if (PG_ARGISNULL(1)) PG_RETURN_POINTER(rec); /* Extract type info from the tuple itself */ tupType = HeapTupleHeaderGetTypeId(rec); tupTypmod = HeapTupleHeaderGetTypMod(rec); } hs = PG_GETARG_HS(1); /* * if the input hstore is empty, we can only skip the rest if we were * passed in a non-null record, since otherwise there may be issues with * domain nulls. */ if (HS_ISEMPTY(hs) && rec) PG_RETURN_POINTER(rec); tupdesc = lookup_rowtype_tupdesc(tupType, tupTypmod); ncolumns = tupdesc->natts; if (rec) { /* Build a temporary HeapTuple control structure */ tuple.t_len = HeapTupleHeaderGetDatumLength(rec); ItemPointerSetInvalid(&(tuple.t_self)); tuple.t_tableOid = InvalidOid; tuple.t_data = rec; } /* * We arrange to look up the needed I/O info just once per series of * calls, assuming the record type doesn't change underneath us. */ my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns != ncolumns) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra = (RecordIOData *) fcinfo->flinfo->fn_extra; my_extra->record_type = InvalidOid; my_extra->record_typmod = 0; } if (my_extra->record_type != tupType || my_extra->record_typmod != tupTypmod) { MemSet(my_extra, 0, sizeof(RecordIOData) - sizeof(ColumnIOData) + ncolumns * sizeof(ColumnIOData)); my_extra->record_type = tupType; my_extra->record_typmod = tupTypmod; my_extra->ncolumns = ncolumns; } values = (Datum *) palloc(ncolumns * sizeof(Datum)); nulls = (bool *) palloc(ncolumns * sizeof(bool)); if (rec) { /* Break down the tuple into fields */ heap_deform_tuple(&tuple, tupdesc, values, nulls); } else { for (i = 0; i < ncolumns; ++i) { values[i] = (Datum) 0; nulls[i] = true; } } for (i = 0; i < ncolumns; ++i) { ColumnIOData *column_info = &my_extra->columns[i]; Oid column_type = tupdesc->attrs[i]->atttypid; HStoreValue *v = NULL; /* Ignore dropped columns in datatype */ if (tupdesc->attrs[i]->attisdropped) { nulls[i] = true; continue; } if (!HS_ISEMPTY(hs)) { char *key = NameStr(tupdesc->attrs[i]->attname); v = findUncompressedHStoreValue(VARDATA(hs), HS_FLAG_HSTORE, NULL, key, strlen(key)); } /* * we can't just skip here if the key wasn't found since we might have * a domain to deal with. If we were passed in a non-null record * datum, we assume that the existing values are valid (if they're * not, then it's not our fault), but if we were passed in a null, * then every field which we don't populate needs to be run through * the input function just in case it's a domain type. */ if (v == NULL && rec) continue; /* * Prepare to convert the column value from text */ if (column_info->column_type != column_type) { getTypeInputInfo(column_type, &column_info->typiofunc, &column_info->typioparam); fmgr_info_cxt(column_info->typiofunc, &column_info->proc, fcinfo->flinfo->fn_mcxt); column_info->column_type = column_type; } if (v == NULL || v->type == hsvNull) { /* * need InputFunctionCall to happen even for nulls, so that domain * checks are done */ values[i] = InputFunctionCall(&column_info->proc, NULL, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = true; } else { char *s = NULL; if (v->type == hsvString) s = pnstrdup(v->string.val, v->string.len); else if (v->type == hsvBool) s = pnstrdup((v->boolean) ? "t" : "f", 1); else if (v->type == hsvNumeric) s = DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(v->numeric))); else if (v->type == hsvBinary && column_type == JSONOID) s = hstoreToCString(NULL, v->binary.data, v->binary.len, SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated)); else if (v->type == hsvBinary && type_is_array(column_type)) s = hstoreToCString(NULL, v->binary.data, v->binary.len, SET_PRETTY_PRINT_VAR(ArrayCurlyBraces)); else if (v->type == hsvBinary) s = hstoreToCString(NULL, v->binary.data, v->binary.len, SET_PRETTY_PRINT_VAR(0)); else elog(PANIC, "Wrong hstore"); values[i] = InputFunctionCall(&column_info->proc, s, column_info->typioparam, tupdesc->attrs[i]->atttypmod); nulls[i] = false; } } rettuple = heap_form_tuple(tupdesc, values, nulls); ReleaseTupleDesc(tupdesc); PG_RETURN_DATUM(HeapTupleGetDatum(rettuple)); } bool stringIsNumber(char *string, int len, bool jsonNumber) { enum { SIN_FIRSTINT, SIN_ZEROINT, SIN_INT, SIN_SCALE, SIN_MSIGN, SIN_MANTISSA } sinState; char *c; bool r; if (*string == '-' || *string == '+') { string++; len--; } c = string; r = true; sinState = SIN_FIRSTINT; while(r && c - string < len) { switch(sinState) { case SIN_FIRSTINT: if (*c == '0' && jsonNumber) sinState = SIN_ZEROINT; else if (*c == '.') sinState = SIN_SCALE; else if (isdigit(*c)) sinState = SIN_INT; else r = false; break; case SIN_ZEROINT: if (*c == '.') sinState = SIN_SCALE; else r = false; break; case SIN_INT: if (*c == '.') sinState = SIN_SCALE; else if (*c == 'e' || *c == 'E') sinState = SIN_MSIGN; else if (!isdigit(*c)) r = false; break; case SIN_SCALE: if (*c == 'e' || *c == 'E') sinState = SIN_MSIGN; else if (!isdigit(*c)) r = false; break; case SIN_MSIGN: if (*c == '-' || *c == '+' || isdigit(*c)) sinState = SIN_MANTISSA; else r = false; break; case SIN_MANTISSA: if (!isdigit(*c)) r = false; break; default: abort(); } c++; } if (sinState == SIN_MSIGN) r = false; return r; } static void printIndent(StringInfo out, bool isRootHash, HStoreOutputKind kind, int level) { if (kind & PrettyPrint) { int i; if (isRootHash && (kind & RootHashDecorated) == 0) level--; for(i=0; i<4*level; i++) appendStringInfoCharMacro(out, ' '); } } static void printCR(StringInfo out, HStoreOutputKind kind) { if (kind & PrettyPrint) appendStringInfoCharMacro(out, '\n'); } static void escape_hstore(StringInfo out, char *string, uint32 len) { char *ptr = string; appendStringInfoCharMacro(out, '"'); while (ptr - string < len) { if (*ptr == '"' || *ptr == '\\') appendStringInfoCharMacro(out, '\\'); appendStringInfoCharMacro(out, *ptr); ptr++; } appendStringInfoCharMacro(out, '"'); } static void putEscapedString(StringInfo out, HStoreOutputKind kind, char *string, uint32 len) { if (kind & LooseOutput) { if (len == 1 && *string == 't') appendStringInfoString(out, (kind & JsonOutput) ? "true" : "t" ); else if (len == 1 && *string == 'f') appendStringInfoString(out, (kind & JsonOutput) ? "false" : "f"); else if (len > 0 && stringIsNumber(string, len, true)) appendBinaryStringInfo(out, string, len); else if (kind & JsonOutput) escape_json(out, pnstrdup(string, len)); else escape_hstore(out, string, len); } else { if (kind & JsonOutput) escape_json(out, pnstrdup(string, len)); else escape_hstore(out, string, len); } } static void putEscapedValue(StringInfo out, HStoreOutputKind kind, HStoreValue *v) { switch(v->type) { case hsvNull: appendBinaryStringInfo(out, (kind & JsonOutput) ? "null" : "NULL", 4); break; case hsvString: putEscapedString(out, kind, v->string.val, v->string.len); break; case hsvBool: if ((kind & JsonOutput) == 0) appendBinaryStringInfo(out, (v->boolean) ? "t" : "f", 1); else if (v->boolean) appendBinaryStringInfo(out, "true", 4); else appendBinaryStringInfo(out, "false", 5); break; case hsvNumeric: appendStringInfoString(out, DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(v->numeric)))); break; default: elog(PANIC, "Unknown type"); } } static bool needBrackets(int level, bool isArray, HStoreOutputKind kind, bool isScalar) { bool res; if (isArray && isScalar) res = false; else if (level == 0) res = (isArray || (kind & RootHashDecorated)) ? true : false; else res = true; return res; } static bool isArrayBrackets(HStoreOutputKind kind) { return ((kind & ArrayCurlyBraces) == 0) ? true : false; } char* hstoreToCString(StringInfo out, char *in, int len /* just estimation */, HStoreOutputKind kind) { bool first = true; HStoreIterator *it; int type; HStoreValue v; int level = 0; bool isRootHash = false; if (out == NULL) out = makeStringInfo(); if (in == NULL) { appendStringInfoString(out, ""); return out->data; } enlargeStringInfo(out, (len >= 0) ? len : 64); it = HStoreIteratorInit(in); while((type = HStoreIteratorGet(&it, &v, false)) != 0) { reout: switch(type) { case WHS_BEGIN_ARRAY: if (first == false) { appendBinaryStringInfo(out, ", ", 2); printCR(out, kind); } first = true; if (needBrackets(level, true, kind, v.array.scalar)) { printIndent(out, isRootHash, kind, level); appendStringInfoChar(out, isArrayBrackets(kind) ? '[' : '{'); printCR(out, kind); } level++; break; case WHS_BEGIN_HASH: if (first == false) { appendBinaryStringInfo(out, ", ", 2); printCR(out, kind); } first = true; if (level == 0) isRootHash = true; if (needBrackets(level, false, kind, false)) { printIndent(out, isRootHash, kind, level); appendStringInfoCharMacro(out, '{'); printCR(out, kind); } level++; break; case WHS_KEY: if (first == false) { appendBinaryStringInfo(out, ", ", 2); printCR(out, kind); } first = true; printIndent(out, isRootHash, kind, level); /* key should not be loose */ putEscapedValue(out, kind & ~LooseOutput, &v); appendBinaryStringInfo(out, (kind & JsonOutput) ? ": " : "=>", 2); type = HStoreIteratorGet(&it, &v, false); if (type == WHS_VALUE) { first = false; putEscapedValue(out, kind, &v); } else { Assert(type == WHS_BEGIN_HASH || type == WHS_BEGIN_ARRAY); printCR(out, kind); /* * We need to rerun current switch() due to put * in current place object which we just got * from iterator. */ goto reout; } break; case WHS_ELEM: if (first == false) { appendBinaryStringInfo(out, ", ", 2); printCR(out, kind); } else { first = false; } printIndent(out, isRootHash, kind, level); putEscapedValue(out, kind, &v); break; case WHS_END_ARRAY: level--; if (needBrackets(level, true, kind, v.array.scalar)) { printCR(out, kind); printIndent(out, isRootHash, kind, level); appendStringInfoChar(out, isArrayBrackets(kind) ? ']' : '}'); } first = false; break; case WHS_END_HASH: level--; if (needBrackets(level, false, kind, false)) { printCR(out, kind); printIndent(out, isRootHash, kind, level); appendStringInfoCharMacro(out, '}'); } first = false; break; default: elog(PANIC, "Wrong flags"); } } Assert(level == 0); return out->data; } text* HStoreValueToText(HStoreValue *v) { text *out; if (v == NULL || v->type == hsvNull) { out = NULL; } else if (v->type == hsvString) { out = cstring_to_text_with_len(v->string.val, v->string.len); } else if (v->type == hsvBool) { out = cstring_to_text_with_len((v->boolean) ? "t" : "f", 1); } else if (v->type == hsvNumeric) { out = cstring_to_text(DatumGetCString( DirectFunctionCall1(numeric_out, PointerGetDatum(v->numeric)) )); } else { StringInfo str; str = makeStringInfo(); appendBinaryStringInfo(str, " ", 4); /* VARHDRSZ */ hstoreToCString(str, v->binary.data, v->binary.len, SET_PRETTY_PRINT_VAR(0)); out = (text*)str->data; SET_VARSIZE(out, str->len); } return out; } PG_FUNCTION_INFO_V1(hstore_out); Datum hstore_out(PG_FUNCTION_ARGS); Datum hstore_out(PG_FUNCTION_ARGS) { HStore *hs = PG_GETARG_HS(0); char *out; out = hstoreToCString(NULL, (HS_ISEMPTY(hs)) ? NULL : VARDATA(hs), VARSIZE(hs), SET_PRETTY_PRINT_VAR(0)); PG_RETURN_CSTRING(out); } PG_FUNCTION_INFO_V1(hstore_send); Datum hstore_send(PG_FUNCTION_ARGS); Datum hstore_send(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HS(0); StringInfoData buf; pq_begintypsend(&buf); if (HS_ISEMPTY(in)) { pq_sendint(&buf, 0, 4); } else { HStoreIterator *it; int type; HStoreValue v; uint32 flag; bytea *nbuf; enlargeStringInfo(&buf, VARSIZE_ANY(in) /* just estimation */); it = HStoreIteratorInit(VARDATA_ANY(in)); while((type = HStoreIteratorGet(&it, &v, false)) != 0) { switch(type) { case WHS_BEGIN_ARRAY: flag = (v.array.scalar) ? HENTRY_ISSCALAR : HENTRY_ISARRAY; pq_sendint(&buf, v.array.nelems | flag, 4); break; case WHS_BEGIN_HASH: pq_sendint(&buf, v.hash.npairs | HENTRY_ISHASH, 4); break; case WHS_KEY: pq_sendint(&buf, v.string.len | HENTRY_ISSTRING, 4); pq_sendtext(&buf, v.string.val, v.string.len); break; case WHS_ELEM: case WHS_VALUE: switch(v.type) { case hsvNull: pq_sendint(&buf, HENTRY_ISNULL, 4); break; case hsvString: pq_sendint(&buf, v.string.len | HENTRY_ISSTRING, 4); pq_sendtext(&buf, v.string.val, v.string.len); break; case hsvBool: pq_sendint(&buf, (v.boolean) ? HENTRY_ISTRUE : HENTRY_ISFALSE, 4); break; case hsvNumeric: nbuf = DatumGetByteaP(DirectFunctionCall1(numeric_send, NumericGetDatum(v.numeric))); pq_sendint(&buf, ((int)VARSIZE_ANY_EXHDR(nbuf)) | HENTRY_ISNUMERIC, 4); pq_sendbytes(&buf, VARDATA(nbuf), (int)VARSIZE_ANY_EXHDR(nbuf)); break; default: elog(PANIC, "Wrong type: %u", v.type); } break; case WHS_END_ARRAY: case WHS_END_HASH: break; default: elog(PANIC, "Wrong flags"); } } } PG_RETURN_BYTEA_P(pq_endtypsend(&buf)); } /* * hstore_to_json_loose * * This is a heuristic conversion to json which treats * 't' and 'f' as booleans and strings that look like numbers as numbers, * as long as they don't start with a leading zero followed by another digit * (think zip codes or phone numbers starting with 0). */ PG_FUNCTION_INFO_V1(hstore_to_json_loose); Datum hstore_to_json_loose(PG_FUNCTION_ARGS); Datum hstore_to_json_loose(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HS(0); text *out; if (HS_ISEMPTY(in)) { out = cstring_to_text_with_len("{}",2); } else { StringInfo str; str = makeStringInfo(); appendBinaryStringInfo(str, " ", 4); /* VARHDRSZ */ hstoreToCString(str, VARDATA_ANY(in), VARSIZE_ANY(in), SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated | LooseOutput)); out = (text*)str->data; SET_VARSIZE(out, str->len); } PG_RETURN_TEXT_P(out); } PG_FUNCTION_INFO_V1(hstore_to_json); Datum hstore_to_json(PG_FUNCTION_ARGS); Datum hstore_to_json(PG_FUNCTION_ARGS) { HStore *in = PG_GETARG_HS(0); text *out; if (HS_ISEMPTY(in)) { out = cstring_to_text_with_len("{}",2); } else { StringInfo str; str = makeStringInfo(); appendBinaryStringInfo(str, " ", 4); /* VARHDRSZ */ hstoreToCString(str, HS_ISEMPTY(in) ? NULL : VARDATA_ANY(in), VARSIZE_ANY(in), SET_PRETTY_PRINT_VAR(JsonOutput | RootHashDecorated)); out = (text*)str->data; SET_VARSIZE(out, str->len); } PG_RETURN_TEXT_P(out); } PG_FUNCTION_INFO_V1(json_to_hstore); Datum json_to_hstore(PG_FUNCTION_ARGS); Datum json_to_hstore(PG_FUNCTION_ARGS) { text *json = PG_GETARG_TEXT_PP(0); PG_RETURN_POINTER(hstoreDump(parseHStore(VARDATA_ANY(json), VARSIZE_ANY_EXHDR(json), true))); } static Oid searchCast(Oid src, Oid dst, CoercionMethod *method) { Oid funcOid = InvalidOid, baseSrc; HeapTuple tuple; if (src == dst) { *method = COERCION_METHOD_BINARY; return InvalidOid; } tuple = SearchSysCache2(CASTSOURCETARGET, ObjectIdGetDatum(src), ObjectIdGetDatum(dst)); *method = 0; if (HeapTupleIsValid(tuple)) { Form_pg_cast castForm = (Form_pg_cast) GETSTRUCT(tuple); if (castForm->castmethod == COERCION_METHOD_FUNCTION) funcOid = castForm->castfunc; *method = castForm->castmethod; ReleaseSysCache(tuple); } else if ((baseSrc = getBaseType(src)) != src && OidIsValid(baseSrc)) { /* domain type */ funcOid = searchCast(baseSrc, dst, method); } return funcOid; } PG_FUNCTION_INFO_V1(array_to_hstore); Datum array_to_hstore(PG_FUNCTION_ARGS); Datum array_to_hstore(PG_FUNCTION_ARGS) { ArrayType *array = PG_GETARG_ARRAYTYPE_P(0); ArrayIterator iterator; int i = 0; Datum datum; bool isnull; int ncounters = ARR_NDIM(array), *counters = palloc0(sizeof(*counters) * ncounters), *dims = ARR_DIMS(array); ToHStoreState *state = NULL; HStoreValue value, *result; Oid castOid = InvalidOid; int valueType = hsvString; FmgrInfo castInfo; CoercionMethod method; if (ArrayGetNItems(ARR_NDIM(array), ARR_DIMS(array)) == 0) PG_RETURN_POINTER(hstoreDump(NULL)); switch(ARR_ELEMTYPE(array)) { case BOOLOID: valueType = hsvBool; break; case NUMERICOID: valueType = hsvNumeric; break; case TEXTOID: valueType = hsvString; break; default: if (TypeCategory(ARR_ELEMTYPE(array)) == TYPCATEGORY_NUMERIC) { castOid = searchCast(ARR_ELEMTYPE(array), NUMERICOID, &method); if (castOid == InvalidOid && method != COERCION_METHOD_BINARY) elog(ERROR, "Could not cast array's element type to numeric"); valueType = hsvNumeric; break; } else { castOid = searchCast(ARR_ELEMTYPE(array), TEXTOID, &method); if (castOid == InvalidOid && method != COERCION_METHOD_BINARY) elog(ERROR, "Could not cast array's element type to text"); valueType = hsvString; break; } } if (castOid != InvalidOid) fmgr_info(castOid, &castInfo); iterator = array_create_iterator(array, 0); value.type = hsvArray; value.array.scalar = false; for(i=0; i= dims[i]) { while(i>=0 && counters[i] >= dims[i]) { counters[i] = 0; result = pushHStoreValue(&state, WHS_END_ARRAY, NULL); i--; } Assert(i>=0); counters[i]++; value.type = hsvArray; value.array.scalar = false; for(i = i + 1; idata; SET_VARSIZE(out, str->len); PG_RETURN_TEXT_P(out); } void _PG_init(void); void _PG_init(void) { DefineCustomBoolVariable( "hstore.pretty_print", "Enable pretty print", "Enable pretty print of hstore type", &pretty_print_var, pretty_print_var, PGC_USERSET, GUC_NOT_IN_SAMPLE, NULL, NULL, NULL ); EmitWarningsOnPlaceholders("hstore"); }