]> git.8kb.co.uk Git - postgresql/pg_jsonb_opx/blob - jsonb_opx.c
a27de35ee977ff133d709ad7950f9e524de51d67
[postgresql/pg_jsonb_opx] / jsonb_opx.c
1 /* 
2  * jsonb_opx.c 
3  *     Test jsonb delete and concatenate operator functions for 9.4
4  *
5  * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group
6  * Portions Copyright (c) 1994, Regents of the University of California
7  * Author: Glyn Astill <glyn@8kb.co.uk>
8  *
9  * This is purely experimentation and will contain many errors and bad form
10  * DO NOT USE ON PRODUCTION SYSTEMS.
11  *
12  */
13
14 #include "postgres.h"
15 #include "fmgr.h"
16 #include "utils/array.h"
17 #include "utils/jsonb.h"
18 #include "catalog/pg_type.h"
19 #include "utils/builtins.h"
20
21 #ifdef PG_MODULE_MAGIC
22         PG_MODULE_MAGIC;
23 #endif 
24
25 Datum jsonb_delete_text(PG_FUNCTION_ARGS);
26
27 PG_FUNCTION_INFO_V1(jsonb_delete_text);
28
29 /*
30  * Operator function to delete keys from left operand where a match is found in
31  * the right operand.
32  *
33  * jsonb, text[] -> jsonb
34  *
35  */
36 Datum 
37 jsonb_delete_text(PG_FUNCTION_ARGS)
38 {
39     /* general loops */
40     int i;
41
42     /* pointers to incoming jsonb and text[] data */
43     Jsonb *input_jsonb = PG_GETARG_JSONB(0);
44     ArrayType *input_array = PG_GETARG_ARRAYTYPE_P(1);
45     
46     /* pointers to return jsonb_value data and state to be converted to jsonb on return */
47     JsonbParseState *state = NULL;
48     JsonbValue *return_jsonb_value = NULL;
49
50     /* pointer to iterator for input_jsonb and lookup value data */
51     JsonbValue  jsonb_lookup_value;
52     JsonbValue *jsonb_value = NULL;
53     JsonbIterator *jsonb_iterator;
54     JsonbValue  jsonb_iterator_value;
55     int32 jsonb_iterator_token;
56
57     /* variables used for skip logic */
58     int32 skip_key = 0;
59     int32 nest_level = 0;
60     int32 array_level = 0;
61
62     /* array element variables for use during deconstruction */
63     Datum *datums;
64     bool *nulls;
65     int32 count;
66
67     /* individual array values values from incoming text[] */
68     text *array_element_text;
69     bool exists = false;
70
71     /* check that supplied jsonb isn't non object, i.e. scalar or array */
72     if (!JB_ROOT_IS_OBJECT(input_jsonb))
73         ereport(ERROR,
74             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
75                 errmsg("cannot call on a non-object")));
76
77     /* assert input_array is a text array type */
78     Assert(ARR_ELEMTYPE(input_array) == TEXTOID);
79
80     /* check input_array is one-dimensional */
81     if (ARR_NDIM(input_array) > 1)
82         ereport(ERROR, 
83             (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
84                 errmsg("1 dimensional text array expected")));
85
86     /* deconstruct array elements */
87     deconstruct_array(input_array, TEXTOID, -1, false, 'i', 
88                        &datums, &nulls, &count);
89
90     /* if the array is empty there's no work to do so return the input value */ 
91     if (count == 0) 
92         PG_RETURN_JSONB(input_jsonb);
93
94     /* first check to make sure at least one key exists - this is potentially just extra unwanted work */
95     for (i=0; i<count; i++)
96     {
97         if (nulls[i])
98             continue;
99
100         array_element_text = DatumGetTextP(datums[i]);
101
102         jsonb_lookup_value.type = jbvString;
103         jsonb_lookup_value.val.string.val = VARDATA_ANY(array_element_text);
104         jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(array_element_text);
105             
106         jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
107             JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
108
109         if (jsonb_value != NULL) {
110             exists = true;
111             break;
112         }
113     }
114
115     if (!exists) 
116         PG_RETURN_JSONB(input_jsonb);
117
118     /*
119     * If we've been supplied with existing keys iterate round json data matching those keys.
120     *
121     * skip_key, nest_level and array_level are crude counts to check if the the value for the key is closed
122     * and ensure we don't match on keys within nested objects.  Because we are recursing into nested elements
123     * but blindly just pushing them onto the return value we can get away without deeper knowledge of the json?
124     */
125
126     jsonb_iterator = JsonbIteratorInit(&input_jsonb->root);
127
128     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) {
129
130         switch (jsonb_iterator_token)
131         {
132         case WJB_BEGIN_ARRAY:
133             array_level++;
134             if (skip_key == 0)
135                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_ARRAY, &jsonb_iterator_value);
136             break;
137         case WJB_BEGIN_OBJECT:
138             nest_level++;
139             if (skip_key == 0)
140                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
141             break;
142         case WJB_KEY:
143             /* Check each key against our array of keys */
144             if (skip_key > 0) {
145                 skip_key++;
146             }
147             else if (nest_level == 1){
148                 for (i=0; i<count; i++)
149                 {
150                     if (nulls[i])
151                         continue;
152
153                     array_element_text = DatumGetTextP(datums[i]);
154
155                     if (strcmp(VARDATA(array_element_text), pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len)) == 0) {
156                         skip_key++;
157                         break;
158                     }
159                 }
160             }
161
162             if (skip_key == 0)
163                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_value);
164             break;
165         case WJB_VALUE:
166             if (skip_key == 0)
167                 return_jsonb_value = pushJsonbValue(&state, WJB_VALUE, &jsonb_iterator_value);
168             else if (skip_key > 0)
169                 skip_key--;
170             break;
171         case WJB_ELEM:
172             if (skip_key == 0)
173                 return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
174             break;
175         case WJB_END_ARRAY:
176             array_level--;
177             if (skip_key == 0)
178                 return_jsonb_value = pushJsonbValue(&state, WJB_END_ARRAY, NULL);
179             else if (skip_key > 0 && array_level == 0)
180                 skip_key--;
181             break;
182         case WJB_END_OBJECT:
183             nest_level--;
184             if (skip_key == 0)
185                 return_jsonb_value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
186             else if (skip_key > 0)
187                 skip_key--;
188             break;
189         default:
190             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
191         } 
192     }
193     PG_FREE_IF_COPY(input_jsonb, 0); 
194     PG_FREE_IF_COPY(input_array, 1); 
195
196     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
197 }
198
199 Datum jsonb_delete_jsonb(PG_FUNCTION_ARGS);
200
201 PG_FUNCTION_INFO_V1(jsonb_delete_jsonb);
202
203 /*
204  * Operator function to delete keys and values from left operand where a match 
205  * is found in the right operand.
206  *
207  * jsonb, jsonb -> jsonb
208  *
209  */
210 Datum 
211 jsonb_delete_jsonb(PG_FUNCTION_ARGS)
212 {
213     /* pointers to incoming jsonb and text[] data */
214     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
215     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
216     
217     /* pointers to return jsonb_value data and state to be converted to jsonb on return */
218     JsonbValue *return_jsonb_value = NULL;
219     JsonbParseState *state = NULL;
220
221     /* pointer to iterator for input_jsonb_a and temporary value data */
222     JsonbIterator *jsonb_iterator;
223     JsonbValue  jsonb_iterator_value;
224     JsonbValue  jsonb_iterator_key;
225     int32 jsonb_iterator_token;
226     bool skip_nested = false;
227     
228     /* pointer to iterator and container for pushing nested parts of input_jsonb_a */
229     JsonbContainer *nest_jsonb_container_a;
230     JsonbIterator *nest_jsonb_iterator;
231     bool push = true;
232
233     /* inner iterator for iterating around jbvBinary types */
234     JsonbValue  nest_jsonb_iterator_value;
235     int32 nest_jsonb_iterator_token;
236
237     /* pointer to lookup on input_jsonb_b */
238     JsonbValue *jsonb_lookup_value = NULL;
239     
240     /* check that supplied jsonb isn't non object, i.e. scalar or array */
241     if (!JB_ROOT_IS_OBJECT(input_jsonb_a) || !JB_ROOT_IS_OBJECT(input_jsonb_b))
242         ereport(ERROR,
243             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
244                 errmsg("cannot call on a non-object")));
245
246     /*
247      * check if either supplied jsonb is empty and return the other if so
248      * this idea was copied from https://github.com/erthalion/jsonbx/blob/master/jsonbx.c
249      */
250     if (JB_ROOT_COUNT(input_jsonb_b) == 0)
251         PG_RETURN_JSONB(input_jsonb_a);
252
253     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
254
255     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
256         skip_nested = true;
257
258         switch (jsonb_iterator_token)
259         {
260         case WJB_BEGIN_ARRAY:
261         case WJB_BEGIN_OBJECT:
262         case WJB_ELEM:
263         case WJB_END_ARRAY:
264         case WJB_END_OBJECT:
265             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
266         break;
267         case WJB_KEY:
268              jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
269              jsonb_iterator_key = jsonb_iterator_value;
270
271              jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested);
272              if (jsonb_iterator_token != WJB_VALUE)
273                  elog(ERROR, "invalid JsonbIteratorNext (expected WJB_VALUE) rc: %d", jsonb_iterator_token);
274
275              if (jsonb_lookup_value != NULL) {
276                 if (jsonb_lookup_value->type == jsonb_iterator_value.type) {
277                     switch (jsonb_lookup_value->type) {
278                         case jbvNumeric:
279                             if (strcmp(
280                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_lookup_value->val.numeric))),
281                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_iterator_value.val.numeric)))
282                                 ) == 0) 
283                                 push = false;
284                         break;
285                         case jbvString:
286                             if (strcmp(
287                                 pnstrdup(jsonb_lookup_value->val.string.val,jsonb_lookup_value->val.string.len),
288                                 pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len)
289                                 ) == 0)
290                                 push = false;
291                         break;
292                         case jbvBinary:
293                             if (strcmp(
294                                 JsonbToCString(NULL, jsonb_lookup_value->val.binary.data, jsonb_lookup_value->val.binary.len),
295                                 JsonbToCString(NULL, jsonb_iterator_value.val.binary.data, jsonb_lookup_value->val.binary.len)
296                                 ) == 0)
297                                 push = false;
298                         break;
299                         case jbvBool:
300                             if (jsonb_lookup_value->val.boolean == jsonb_iterator_value.val.boolean)
301                                 push = false;
302                         break;
303                         case jbvArray:
304                         /* should not be possible? */
305                         case jbvObject:
306                         /* should not be possible? */
307                         default:
308                         ereport(NOTICE, (errcode(ERRCODE_SUCCESSFUL_COMPLETION), errmsg("unexpected lookup type")));
309                     }
310                 }
311             }
312
313             if (push) {                
314                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_key);
315
316                 /* if our value is nested binary data, iterate separately pushing each val */
317                 if (jsonb_iterator_value.type == jbvBinary) {
318                     nest_jsonb_container_a = jsonb_iterator_value.val.binary.data;
319
320                     nest_jsonb_iterator = JsonbIteratorInit(nest_jsonb_container_a);
321                     while ((nest_jsonb_iterator_token = JsonbIteratorNext(&nest_jsonb_iterator, &nest_jsonb_iterator_value, false)) != WJB_DONE) {
322                         return_jsonb_value = pushJsonbValue(&state, nest_jsonb_iterator_token, &nest_jsonb_iterator_value);
323                     }
324                 }
325                 else {
326                     return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
327                 }
328             }
329             else 
330                 push = true;
331             break;
332         case WJB_VALUE:
333             /* should not be possible */
334         default:
335             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
336         }
337
338     }
339
340     PG_FREE_IF_COPY(input_jsonb_a, 0); 
341     PG_FREE_IF_COPY(input_jsonb_b, 1); 
342
343     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
344 }
345
346 Datum jsonb_concat_jsonb(PG_FUNCTION_ARGS);
347
348 PG_FUNCTION_INFO_V1(jsonb_concat_jsonb);
349
350 /*
351  * Operator function to concatenate json from left operand where a match 
352  * is found in the right operand.
353  *
354  * jsonb, jsonb -> jsonb
355  *
356  */
357 Datum 
358 jsonb_concat_jsonb(PG_FUNCTION_ARGS)
359 {
360     /* incoming jsonb and text[] data */
361     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
362     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
363     
364     /* return jsonb_value data to be converted to jsonb on return */
365     JsonbValue *return_jsonb_value = NULL;
366
367     /* iterator for input_jsonb_b */
368     JsonbIterator *jsonb_iterator;
369     JsonbValue  jsonb_iterator_value;
370     int32 jsonb_iterator_token;
371
372     JsonbParseState *state = NULL;
373     bool skip_nested = false;
374
375     int32 nest_level = 0;
376    
377     /* check that supplied jsonb isn't non object, i.e. scalar or array */
378     if (!JB_ROOT_IS_OBJECT(input_jsonb_a) || !JB_ROOT_IS_OBJECT(input_jsonb_b))
379         ereport(ERROR,
380             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
381                 errmsg("cannot call on a non-object")));
382     
383     /*
384      * check if either supplied jsonb is empty and return the other if so
385      * this idea was copied from https://github.com/erthalion/jsonbx/blob/master/jsonbx.c
386      */
387     if (JB_ROOT_COUNT(input_jsonb_a) == 0)
388         PG_RETURN_JSONB(input_jsonb_b);
389     else if (JB_ROOT_COUNT(input_jsonb_b) == 0)
390         PG_RETURN_JSONB(input_jsonb_a);
391
392     /*
393      * The following is essentially a cut 'n shut job; discarding the closing root 
394      * object token from the first jsonb value and the opening one from the second.
395      * Values from each are just blindly pushed onto the return value leaving
396      * deduplication down to lower level jsonb logic.
397      */
398
399     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
400
401     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
402
403         switch (jsonb_iterator_token)
404         {
405         case WJB_BEGIN_ARRAY:
406         case WJB_KEY:
407         case WJB_VALUE:
408         case WJB_ELEM:
409         case WJB_END_ARRAY:
410             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
411             break;
412         case WJB_BEGIN_OBJECT:
413             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
414             nest_level++;
415             break;
416         case WJB_END_OBJECT:
417             nest_level--;
418             if (nest_level != 0)
419                 return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
420             break;
421         default:
422             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
423         }
424
425     }
426
427     nest_level = 0;
428     jsonb_iterator = JsonbIteratorInit(&input_jsonb_b->root);
429
430     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
431
432         switch (jsonb_iterator_token)
433         {
434         case WJB_BEGIN_ARRAY:
435         case WJB_KEY:
436         case WJB_VALUE:
437         case WJB_ELEM:
438         case WJB_END_ARRAY:
439             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
440             break;
441         case WJB_BEGIN_OBJECT:
442             if (nest_level != 0)
443                 return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
444             nest_level++;
445             break;
446         case WJB_END_OBJECT:
447             nest_level--;
448             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
449             break;
450         default:
451             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
452         }
453
454     }
455
456     PG_FREE_IF_COPY(input_jsonb_a, 0); 
457     PG_FREE_IF_COPY(input_jsonb_b, 1); 
458
459     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
460 }