Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * hstore_subs.c
4 : * Subscripting support functions for hstore.
5 : *
6 : * This is a great deal simpler than array_subs.c, because the result of
7 : * subscripting an hstore is just a text string (the value for the key).
8 : * We do not need to support array slicing notation, nor multiple subscripts.
9 : * Less obviously, because the subscript result is never a SQL container
10 : * type, there will never be any nested-assignment scenarios, so we do not
11 : * need a fetch_old function. In turn, that means we can drop the
12 : * check_subscripts function and just let the fetch and assign functions
13 : * do everything.
14 : *
15 : * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group
16 : * Portions Copyright (c) 1994, Regents of the University of California
17 : *
18 : *
19 : * IDENTIFICATION
20 : * contrib/hstore/hstore_subs.c
21 : *
22 : *-------------------------------------------------------------------------
23 : */
24 : #include "postgres.h"
25 :
26 : #include "catalog/pg_type_d.h"
27 : #include "executor/execExpr.h"
28 : #include "hstore.h"
29 : #include "nodes/nodeFuncs.h"
30 : #include "nodes/subscripting.h"
31 : #include "parser/parse_coerce.h"
32 : #include "parser/parse_expr.h"
33 : #include "utils/builtins.h"
34 :
35 :
36 : /*
37 : * Finish parse analysis of a SubscriptingRef expression for hstore.
38 : *
39 : * Verify there's just one subscript, coerce it to text,
40 : * and set the result type of the SubscriptingRef node.
41 : */
42 : static void
43 9 : hstore_subscript_transform(SubscriptingRef *sbsref,
44 : List *indirection,
45 : ParseState *pstate,
46 : bool isSlice,
47 : bool isAssignment)
48 : {
49 : A_Indices *ai;
50 : Node *subexpr;
51 :
52 : /* We support only single-subscript, non-slice cases */
53 9 : if (isSlice || list_length(indirection) != 1)
54 2 : ereport(ERROR,
55 : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
56 : errmsg("hstore allows only one subscript"),
57 : parser_errposition(pstate,
58 : exprLocation((Node *) indirection))));
59 :
60 : /* Transform the subscript expression to type text */
61 7 : ai = linitial_node(A_Indices, indirection);
62 : Assert(ai->uidx != NULL && ai->lidx == NULL && !ai->is_slice);
63 :
64 7 : subexpr = transformExpr(pstate, ai->uidx, pstate->p_expr_kind);
65 : /* If it's not text already, try to coerce */
66 7 : subexpr = coerce_to_target_type(pstate,
67 : subexpr, exprType(subexpr),
68 : TEXTOID, -1,
69 : COERCION_ASSIGNMENT,
70 : COERCE_IMPLICIT_CAST,
71 : -1);
72 7 : if (subexpr == NULL)
73 0 : ereport(ERROR,
74 : (errcode(ERRCODE_DATATYPE_MISMATCH),
75 : errmsg("hstore subscript must have type text"),
76 : parser_errposition(pstate, exprLocation(ai->uidx))));
77 :
78 : /* ... and store the transformed subscript into the SubscriptingRef node */
79 7 : sbsref->refupperindexpr = list_make1(subexpr);
80 7 : sbsref->reflowerindexpr = NIL;
81 :
82 : /* Determine the result type of the subscripting operation; always text */
83 7 : sbsref->refrestype = TEXTOID;
84 7 : sbsref->reftypmod = -1;
85 7 : }
86 :
87 : /*
88 : * Evaluate SubscriptingRef fetch for hstore.
89 : *
90 : * Source container is in step's result variable (it's known not NULL, since
91 : * we set fetch_strict to true), and the subscript expression is in the
92 : * upperindex[] array.
93 : */
94 : static void
95 7 : hstore_subscript_fetch(ExprState *state,
96 : ExprEvalStep *op,
97 : ExprContext *econtext)
98 : {
99 7 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
100 : HStore *hs;
101 : text *key;
102 : HEntry *entries;
103 : int idx;
104 : text *out;
105 :
106 : /* Should not get here if source hstore is null */
107 : Assert(!(*op->resnull));
108 :
109 : /* Check for null subscript */
110 7 : if (sbsrefstate->upperindexnull[0])
111 : {
112 0 : *op->resnull = true;
113 0 : return;
114 : }
115 :
116 : /* OK, fetch/detoast the hstore and subscript */
117 7 : hs = DatumGetHStoreP(*op->resvalue);
118 7 : key = DatumGetTextPP(sbsrefstate->upperindex[0]);
119 :
120 : /* The rest is basically the same as hstore_fetchval() */
121 7 : entries = ARRPTR(hs);
122 7 : idx = hstoreFindKey(hs, NULL,
123 7 : VARDATA_ANY(key), VARSIZE_ANY_EXHDR(key));
124 :
125 7 : if (idx < 0 || HSTORE_VALISNULL(entries, idx))
126 : {
127 2 : *op->resnull = true;
128 2 : return;
129 : }
130 :
131 5 : out = cstring_to_text_with_len(HSTORE_VAL(entries, STRPTR(hs), idx),
132 5 : HSTORE_VALLEN(entries, idx));
133 :
134 5 : *op->resvalue = PointerGetDatum(out);
135 : }
136 :
137 : /*
138 : * Evaluate SubscriptingRef assignment for hstore.
139 : *
140 : * Input container (possibly null) is in result area, replacement value is in
141 : * SubscriptingRefState's replacevalue/replacenull.
142 : */
143 : static void
144 7 : hstore_subscript_assign(ExprState *state,
145 : ExprEvalStep *op,
146 : ExprContext *econtext)
147 : {
148 7 : SubscriptingRefState *sbsrefstate = op->d.sbsref.state;
149 : text *key;
150 : Pairs p;
151 : HStore *out;
152 :
153 : /* Check for null subscript */
154 7 : if (sbsrefstate->upperindexnull[0])
155 0 : ereport(ERROR,
156 : (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
157 : errmsg("hstore subscript in assignment must not be null")));
158 :
159 : /* OK, fetch/detoast the subscript */
160 7 : key = DatumGetTextPP(sbsrefstate->upperindex[0]);
161 :
162 : /* Create a Pairs entry for subscript + replacement value */
163 7 : p.needfree = false;
164 7 : p.key = VARDATA_ANY(key);
165 7 : p.keylen = hstoreCheckKeyLen(VARSIZE_ANY_EXHDR(key));
166 :
167 7 : if (sbsrefstate->replacenull)
168 : {
169 1 : p.vallen = 0;
170 1 : p.isnull = true;
171 : }
172 : else
173 : {
174 6 : text *val = DatumGetTextPP(sbsrefstate->replacevalue);
175 :
176 6 : p.val = VARDATA_ANY(val);
177 6 : p.vallen = hstoreCheckValLen(VARSIZE_ANY_EXHDR(val));
178 6 : p.isnull = false;
179 : }
180 :
181 7 : if (*op->resnull)
182 : {
183 : /* Just build a one-element hstore (cf. hstore_from_text) */
184 2 : out = hstorePairs(&p, 1, p.keylen + p.vallen);
185 : }
186 : else
187 : {
188 : /*
189 : * Otherwise, merge the new key into the hstore. Based on
190 : * hstore_concat.
191 : */
192 5 : HStore *hs = DatumGetHStoreP(*op->resvalue);
193 5 : int s1count = HS_COUNT(hs);
194 5 : int outcount = 0;
195 : int vsize;
196 : char *ps1,
197 : *bufd,
198 : *pd;
199 : HEntry *es1,
200 : *ed;
201 : int s1idx;
202 : int s2idx;
203 :
204 : /* Allocate result without considering possibility of duplicate */
205 5 : vsize = CALCDATASIZE(s1count + 1, VARSIZE(hs) + p.keylen + p.vallen);
206 5 : out = palloc(vsize);
207 5 : SET_VARSIZE(out, vsize);
208 5 : HS_SETCOUNT(out, s1count + 1);
209 :
210 5 : ps1 = STRPTR(hs);
211 5 : bufd = pd = STRPTR(out);
212 5 : es1 = ARRPTR(hs);
213 5 : ed = ARRPTR(out);
214 :
215 37 : for (s1idx = s2idx = 0; s1idx < s1count || s2idx < 1; ++outcount)
216 : {
217 : int difference;
218 :
219 32 : if (s1idx >= s1count)
220 1 : difference = 1;
221 31 : else if (s2idx >= 1)
222 10 : difference = -1;
223 : else
224 : {
225 21 : int s1keylen = HSTORE_KEYLEN(es1, s1idx);
226 21 : int s2keylen = p.keylen;
227 :
228 21 : if (s1keylen == s2keylen)
229 19 : difference = memcmp(HSTORE_KEY(es1, ps1, s1idx),
230 19 : p.key,
231 : s1keylen);
232 : else
233 2 : difference = (s1keylen > s2keylen) ? 1 : -1;
234 : }
235 :
236 32 : if (difference >= 0)
237 : {
238 5 : HS_ADDITEM(ed, bufd, pd, p);
239 5 : ++s2idx;
240 5 : if (difference == 0)
241 2 : ++s1idx;
242 : }
243 : else
244 : {
245 27 : HS_COPYITEM(ed, bufd, pd,
246 : HSTORE_KEY(es1, ps1, s1idx),
247 : HSTORE_KEYLEN(es1, s1idx),
248 : HSTORE_VALLEN(es1, s1idx),
249 : HSTORE_VALISNULL(es1, s1idx));
250 27 : ++s1idx;
251 : }
252 : }
253 :
254 5 : HS_FINALIZE(out, outcount, bufd, pd);
255 : }
256 :
257 7 : *op->resvalue = PointerGetDatum(out);
258 7 : *op->resnull = false;
259 7 : }
260 :
261 : /*
262 : * Set up execution state for an hstore subscript operation.
263 : */
264 : static void
265 7 : hstore_exec_setup(const SubscriptingRef *sbsref,
266 : SubscriptingRefState *sbsrefstate,
267 : SubscriptExecSteps *methods)
268 : {
269 : /* Assert we are dealing with one subscript */
270 : Assert(sbsrefstate->numlower == 0);
271 : Assert(sbsrefstate->numupper == 1);
272 : /* We can't check upperprovided[0] here, but it must be true */
273 :
274 : /* Pass back pointers to appropriate step execution functions */
275 7 : methods->sbs_check_subscripts = NULL;
276 7 : methods->sbs_fetch = hstore_subscript_fetch;
277 7 : methods->sbs_assign = hstore_subscript_assign;
278 7 : methods->sbs_fetch_old = NULL;
279 7 : }
280 :
281 : /*
282 : * hstore_subscript_handler
283 : * Subscripting handler for hstore.
284 : */
285 8 : PG_FUNCTION_INFO_V1(hstore_subscript_handler);
286 : Datum
287 16 : hstore_subscript_handler(PG_FUNCTION_ARGS)
288 : {
289 : static const SubscriptRoutines sbsroutines = {
290 : .transform = hstore_subscript_transform,
291 : .exec_setup = hstore_exec_setup,
292 : .fetch_strict = true, /* fetch returns NULL for NULL inputs */
293 : .fetch_leakproof = true, /* fetch returns NULL for bad subscript */
294 : .store_leakproof = false /* ... but assignment throws error */
295 : };
296 :
297 16 : PG_RETURN_POINTER(&sbsroutines);
298 : }
|