mirror of https://github.com/postgres/postgres
This is basically a finger exercise to prove that it's possible for an extension module to add subscripting ability. Subscripted fetch from an hstore is not different from the existing "hstore -> text" operator. Subscripted update does seem to be a little easier to use than the traditional update method using hstore concatenation, but it's not a fundamentally new ability. However, there may be some value in the code as sample code, since it shows what's basically the minimum-complexity way to implement subscripting when one needn't consider nested container objects. Discussion: https://postgr.es/m/3724341.1607551174@sss.pgh.pa.uspull/59/head
parent
8c15a29745
commit
0ec5f7e782
@ -0,0 +1,13 @@ |
|||||||
|
/* contrib/hstore/hstore--1.7--1.8.sql */ |
||||||
|
|
||||||
|
-- complain if script is sourced in psql, rather than via ALTER EXTENSION |
||||||
|
\echo Use "ALTER EXTENSION hstore UPDATE TO '1.8'" to load this file. \quit |
||||||
|
|
||||||
|
CREATE FUNCTION hstore_subscript_handler(internal) |
||||||
|
RETURNS internal |
||||||
|
AS 'MODULE_PATHNAME', 'hstore_subscript_handler' |
||||||
|
LANGUAGE C STRICT IMMUTABLE PARALLEL SAFE; |
||||||
|
|
||||||
|
ALTER TYPE hstore SET ( |
||||||
|
SUBSCRIPT = hstore_subscript_handler |
||||||
|
); |
@ -1,6 +1,6 @@ |
|||||||
# hstore extension |
# hstore extension |
||||||
comment = 'data type for storing sets of (key, value) pairs' |
comment = 'data type for storing sets of (key, value) pairs' |
||||||
default_version = '1.7' |
default_version = '1.8' |
||||||
module_pathname = '$libdir/hstore' |
module_pathname = '$libdir/hstore' |
||||||
relocatable = true |
relocatable = true |
||||||
trusted = true |
trusted = true |
||||||
|
@ -0,0 +1,297 @@ |
|||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
* |
||||||
|
* 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-2020, 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); |
||||||
|
} |
Loading…
Reference in new issue