298 lines
7.8 KiB
C
298 lines
7.8 KiB
C
|
/*-------------------------------------------------------------------------
|
||
|
*
|
||
|
* 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);
|
||
|
}
|