/*------------------------------------------------------------------------- * * hstore_subs.c * Subscripting support functions for hstore. * * This is a great deal simpler than array_subs.c, because the result of * subscripting an hstore is just a text string (the value for the key). * We do not need to support array slicing notation, nor multiple subscripts. * Less obviously, because the subscript result is never a SQL container * type, there will never be any nested-assignment scenarios, so we do not * need a fetch_old function. In turn, that means we can drop the * check_subscripts function and just let the fetch and assign functions * do everything. * * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * contrib/hstore/hstore_subs.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "executor/execExpr.h" #include "hstore.h" #include "nodes/nodeFuncs.h" #include "nodes/subscripting.h" #include "parser/parse_coerce.h" #include "parser/parse_expr.h" #include "utils/builtins.h" /* * Finish parse analysis of a SubscriptingRef expression for hstore. * * Verify there's just one subscript, coerce it to text, * and set the result type of the SubscriptingRef node. */ static void hstore_subscript_transform(SubscriptingRef *sbsref, List *indirection, ParseState *pstate, bool isSlice, bool isAssignment) { A_Indices *ai; Node *subexpr; /* We support only single-subscript, non-slice cases */ if (isSlice || list_length(indirection) != 1) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("hstore allows only one subscript"), parser_errposition(pstate, exprLocation((Node *) indirection)))); /* Transform the subscript expression to type text */ ai = linitial_node(A_Indices, indirection); Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice); subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind); /* If it's not text already, try to coerce */ subexpr = coerce_to_target_type(pstate, subexpr, exprType(subexpr), TEXTOID, -1, COERCION_ASSIGNMENT, COERCE_IMPLICIT_CAST, -1); if (subexpr == NULL) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("hstore subscript must have type text"), parser_errposition(pstate, exprLocation(ai->uidx)))); /* ... and store the transformed subscript into the SubscriptRef node */ sbsref->refupperindexpr = list_make1(subexpr); sbsref->reflowerindexpr = NIL; /* Determine the result type of the subscripting operation; always text */ sbsref->refrestype = TEXTOID; sbsref->reftypmod = -1; } /* * Evaluate SubscriptingRef fetch for hstore. * * Source container is in step's result variable (it's known not NULL, since * we set fetch_strict to true), and the subscript expression is in the * upperindex[] array. */ static void hstore_subscript_fetch(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; HStore *hs; text *key; HEntry *entries; int idx; text *out; /* Should not get here if source hstore is null */ Assert(!(*op->resnull)); /* Check for null subscript */ if (sbsrefstate->upperindexnull[0]) { *op->resnull = true; return; } /* OK, fetch/detoast the hstore and subscript */ hs = DatumGetHStoreP(*op->resvalue); key = DatumGetTextPP(sbsrefstate->upperindex[0]); /* The rest is basically the same as hstore_fetchval() */ entries = ARRPTR(hs); idx = hstoreFindKey(hs, NULL, VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key)); if (idx < 0 || HSTORE_VALISNULL(entries, idx)) { *op->resnull = true; return; } out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx), HSTORE_VALLEN(entries, idx)); *op->resvalue = PointerGetDatum(out); } /* * Evaluate SubscriptingRef assignment for hstore. * * Input container (possibly null) is in result area, replacement value is in * SubscriptingRefState's replacevalue/replacenull. */ static void hstore_subscript_assign(ExprState *state, ExprEvalStep *op, ExprContext *econtext) { SubscriptingRefState *sbsrefstate = op->d.sbsref.state; text *key; Pairs p; HStore *out; /* Check for null subscript */ if (sbsrefstate->upperindexnull[0]) ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("hstore subscript in assignment must not be null"))); /* OK, fetch/detoast the subscript */ key = DatumGetTextPP(sbsrefstate->upperindex[0]); /* Create a Pairs entry for subscript + replacement value */ p.needfree = false; p.key = VARDATA_ANY(key); p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key)); if (sbsrefstate->replacenull) { p.vallen = 0; p.isnull = true; } else { text *val = DatumGetTextPP(sbsrefstate->replacevalue); p.val = VARDATA_ANY(val); p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val)); p.isnull = false; } if (*op->resnull) { /* Just build a one-element hstore (cf. hstore_from_text) */ out = hstorePairs(&p, 1, p.keylen + p.vallen); } else { /* * Otherwise, merge the new key into the hstore. Based on * hstore_concat. */ HStore *hs = DatumGetHStoreP(*op->resvalue); int s1count = HS_COUNT(hs); int outcount = 0; int vsize; char *ps1, *bufd, *pd; HEntry *es1, *ed; int s1idx; int s2idx; /* Allocate result without considering possibility of duplicate */ vsize = CALCDATASIZE(s1count + 1, VARSIZE(hs) + p.keylen + p.vallen); out = palloc(vsize); SET_VARSIZE(out, vsize); HS_SETCOUNT(out, s1count + 1); ps1 = STRPTR(hs); bufd = pd = STRPTR(out); es1 = ARRPTR(hs); ed = ARRPTR(out); for (s1idx = s2idx = 0; s1idx < s1count || s2idx < 1; ++outcount) { int difference; if (s1idx >= s1count) difference = 1; else if (s2idx >= 1) difference = -1; else { int s1keylen = HSTORE_KEYLEN(es1, s1idx); int s2keylen = p.keylen; if (s1keylen == s2keylen) difference = memcmp(HSTORE_KEY(es1, ps1, s1idx), p.key, s1keylen); else difference = (s1keylen > s2keylen) ? 1 : -1; } if (difference >= 0) { HS_ADDITEM(ed, bufd, pd, p); ++s2idx; if (difference == 0) ++s1idx; } else { HS_COPYITEM(ed, bufd, pd, HSTORE_KEY(es1, ps1, s1idx), HSTORE_KEYLEN(es1, s1idx), HSTORE_VALLEN(es1, s1idx), HSTORE_VALISNULL(es1, s1idx)); ++s1idx; } } HS_FINALIZE(out, outcount, bufd, pd); } *op->resvalue = PointerGetDatum(out); *op->resnull = false; } /* * Set up execution state for an hstore subscript operation. */ static void hstore_exec_setup(const SubscriptingRef *sbsref, SubscriptingRefState *sbsrefstate, SubscriptExecSteps *methods) { /* Assert we are dealing with one subscript */ Assert(sbsrefstate->numlower == 0); Assert(sbsrefstate->numupper == 1); /* We can't check upperprovided[0] here, but it must be true */ /* Pass back pointers to appropriate step execution functions */ methods->sbs_check_subscripts = NULL; methods->sbs_fetch = hstore_subscript_fetch; methods->sbs_assign = hstore_subscript_assign; methods->sbs_fetch_old = NULL; } /* * hstore_subscript_handler * Subscripting handler for hstore. */ PG_FUNCTION_INFO_V1(hstore_subscript_handler); Datum hstore_subscript_handler(PG_FUNCTION_ARGS) { static const SubscriptRoutines sbsroutines = { .transform = hstore_subscript_transform, .exec_setup = hstore_exec_setup, .fetch_strict = true, /* fetch returns NULL for NULL inputs */ .fetch_leakproof = true, /* fetch returns NULL for bad subscript */ .store_leakproof = false /* ... but assignment throws error */ }; PG_RETURN_POINTER(&sbsroutines); }