]> git.8kb.co.uk Git - postgresql/pg_jsonb_opx/blob - jsonb_opx.c
Changes:
[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_key(PG_FUNCTION_ARGS);
26
27 PG_FUNCTION_INFO_V1(jsonb_delete_key);
28
29 /*
30  * Operator function to delete key 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_key(PG_FUNCTION_ARGS)
38 {
39     /* pointers to incoming jsonb and text data */
40     Jsonb *input_jsonb = PG_GETARG_JSONB(0);
41     text  *input_text = PG_GETARG_TEXT_P(1);
42    
43     /* pointers to return jsonb value data and state to be converted to jsonb on return */
44     JsonbParseState *state = NULL;
45     JsonbValue *return_jsonb_value = NULL;
46
47     /* pointer to iterator for input_jsonb and lookup value data */
48     JsonbValue  jsonb_lookup_value;
49     JsonbValue *jsonb_value = NULL;
50     JsonbIterator *jsonb_iterator;
51     JsonbValue  jsonb_iterator_value;
52     int32 jsonb_iterator_token;
53
54     /* variables used for skip logic */
55     int32 skip_key = 0;
56     int32 nest_level = 0;
57     int32 array_level = 0;
58
59     /* if we are not deaing with an array first check to make sure the key exists - this is potentially just extra unwanted work */
60     if (!JB_ROOT_IS_ARRAY(input_jsonb)) 
61     {
62         jsonb_lookup_value.type = jbvString;
63         jsonb_lookup_value.val.string.val = VARDATA_ANY(input_text);
64         jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(input_text);
65
66         jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
67             JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
68
69         if (jsonb_value == NULL)
70             PG_RETURN_JSONB(input_jsonb);
71     }
72
73     /*
74     * If we've been supplied with an existing key iterate round json data and rebuild with key/value excluded.
75     *
76     * skip_key, nest_level and array_level are crude counts to check if the the value for the key is closed
77     * and ensure we don't match on keys within nested objects.  Because we are recursing into nested elements
78     * but blindly just pushing them onto the return value we can get away without deeper knowledge of the json?
79     */
80
81     jsonb_iterator = JsonbIteratorInit(&input_jsonb->root);
82
83     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
84     {
85         switch (jsonb_iterator_token)
86         {
87         case WJB_BEGIN_ARRAY:
88             array_level++;
89             if (skip_key == 0)
90                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_ARRAY, &jsonb_iterator_value);
91             break;
92         case WJB_BEGIN_OBJECT:
93             nest_level++;
94             if (skip_key == 0)
95                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
96             break;
97         case WJB_ELEM:
98             /* only match array elements if they are text */
99             if (skip_key == 0 && nest_level == 0 && array_level > 0) 
100             {
101                 if (jsonb_iterator_value.type == jbvString) 
102                 {
103                     if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
104                         VARDATA_ANY(input_text), VARSIZE_ANY_EXHDR(input_text)) == 0) 
105                         break;
106                 }
107             }
108             if (skip_key == 0)
109                 return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
110             break;
111         case WJB_KEY:
112             /* Check each key against our array of keys */
113             if (skip_key > 0) 
114             {
115                 skip_key++;
116             }
117             else if (nest_level == 1)
118             {
119                 if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
120                     VARDATA_ANY(input_text), VARSIZE_ANY_EXHDR(input_text)) == 0) {
121                     skip_key++;
122                     break;
123                 }
124             }
125             if (skip_key == 0)
126                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_value);
127             break;
128         case WJB_VALUE:
129             if (skip_key == 0)
130                 return_jsonb_value = pushJsonbValue(&state, WJB_VALUE, &jsonb_iterator_value);
131             else if (skip_key > 0)
132                 skip_key--;
133             break;
134         case WJB_END_ARRAY:
135             array_level--;
136             if (skip_key == 0)
137                 return_jsonb_value = pushJsonbValue(&state, WJB_END_ARRAY, NULL);
138             else if (skip_key > 0 && array_level == 0)
139                 skip_key--;
140             break;
141         case WJB_END_OBJECT:
142             nest_level--;
143             if (skip_key == 0)
144                 return_jsonb_value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
145             else if (skip_key > 0)
146                 skip_key--;
147             break;
148         default:
149             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);                
150         }
151     }
152     PG_FREE_IF_COPY(input_jsonb, 0);
153     PG_FREE_IF_COPY(input_text, 1);
154
155     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
156 }
157
158 Datum jsonb_delete_keys(PG_FUNCTION_ARGS);
159
160 PG_FUNCTION_INFO_V1(jsonb_delete_keys);
161
162 /*
163  * Operator function to delete keys from left operand where a match is found in
164  * the right operand.
165  *
166  * jsonb, text[] -> jsonb
167  *
168  */
169 Datum 
170 jsonb_delete_keys(PG_FUNCTION_ARGS)
171 {
172     /* general loops */
173     int i;
174
175     /* pointers to incoming jsonb and text[] data */
176     Jsonb *input_jsonb = PG_GETARG_JSONB(0);
177     ArrayType *input_array = PG_GETARG_ARRAYTYPE_P(1);
178     
179     /* pointers to return jsonb value data and state to be converted to jsonb on return */
180     JsonbParseState *state = NULL;
181     JsonbValue *return_jsonb_value = NULL;
182
183     /* pointer to iterator for input_jsonb and lookup value data */
184     JsonbValue  jsonb_lookup_value;
185     JsonbValue *jsonb_value = NULL;
186     JsonbIterator *jsonb_iterator;
187     JsonbValue  jsonb_iterator_value;
188     int32 jsonb_iterator_token;
189
190     /* variables used for skip logic */
191     int32 skip_key = 0;
192     int32 nest_level = 0;
193     int32 array_level = 0;
194
195     /* array element variables for use during deconstruction */
196     Datum *datums;
197     bool *nulls;
198     int32 count;
199
200     /* individual array values values from incoming text[] */
201     text *array_element_text;
202     bool exists = false;
203
204     /* assert input_array is a text array type */
205     Assert(ARR_ELEMTYPE(input_array) == TEXTOID);
206
207     /* check input_array is one-dimensional */
208     if (ARR_NDIM(input_array) > 1)
209         ereport(ERROR, 
210             (errcode(ERRCODE_ARRAY_SUBSCRIPT_ERROR),
211                 errmsg("1 dimensional text array expected")));
212
213     /* deconstruct array elements */
214     deconstruct_array(input_array, TEXTOID, -1, false, 'i', 
215                        &datums, &nulls, &count);
216
217     /* if the array is empty there's no work to do so return the input value */ 
218     if (count == 0) 
219         PG_RETURN_JSONB(input_jsonb);
220
221     /* if we are not deaing with an array first check to make sure the key exists - this is potentially just extra unwanted work */
222     if (!JB_ROOT_IS_ARRAY(input_jsonb)) 
223     {
224         for (i=0; i<count; i++)
225         {
226             if (nulls[i])
227                 continue;
228
229             array_element_text = DatumGetTextP(datums[i]);
230
231             jsonb_lookup_value.type = jbvString;
232             jsonb_lookup_value.val.string.val = VARDATA_ANY(array_element_text);
233             jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(array_element_text);
234             
235             jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
236                 JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
237
238             if (jsonb_value != NULL) 
239             {
240                 exists = true;
241                 break;
242             }
243         }
244
245         if (!exists) 
246             PG_RETURN_JSONB(input_jsonb);
247     }
248
249     /*
250     * If we've been supplied with existing keys iterate round json data matching those keys.
251     *
252     * skip_key, nest_level and array_level are crude counts to check if the the value for the key is closed
253     * and ensure we don't match on keys within nested objects.  Because we are recursing into nested elements
254     * but blindly just pushing them onto the return value we can get away without deeper knowledge of the json?
255     */
256
257     jsonb_iterator = JsonbIteratorInit(&input_jsonb->root);
258
259     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) {
260
261         switch (jsonb_iterator_token)
262         {
263         case WJB_BEGIN_ARRAY:
264             array_level++;
265             if (skip_key == 0)
266                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_ARRAY, &jsonb_iterator_value);
267             break;
268         case WJB_BEGIN_OBJECT:
269             nest_level++;
270             if (skip_key == 0)
271                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
272             break;
273         case WJB_ELEM:
274             /* only match array elements if they are text */
275             if (skip_key == 0 && nest_level == 0 && array_level > 0) 
276             {
277                 if (jsonb_iterator_value.type == jbvString) 
278                 {
279                     for (i=0; i<count; i++)
280                     {
281                         if (nulls[i])
282                             continue;
283
284                         array_element_text = DatumGetTextP(datums[i]);
285
286                         if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
287                             VARDATA_ANY(array_element_text), VARSIZE_ANY_EXHDR(array_element_text)) == 0) 
288                         {
289                             skip_key = 1;
290                             break;
291                         }
292                     }
293                     if (skip_key == 1) 
294                     {
295                         skip_key = 0;
296                         break;
297                     }
298                 }
299             }
300             if (skip_key == 0)
301                 return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
302             break;
303         case WJB_KEY:
304             /* Check each key against our array of keys */
305             if (skip_key > 0) 
306             {
307                 skip_key++;
308             }
309             else if (nest_level == 1)
310             {
311                 for (i=0; i<count; i++)
312                 {
313                     if (nulls[i])
314                         continue;
315
316                     array_element_text = DatumGetTextP(datums[i]);
317
318                     if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
319                         VARDATA_ANY(array_element_text), VARSIZE_ANY_EXHDR(array_element_text)) == 0) 
320                     {
321                         skip_key++;
322                         break;
323                     }
324                 }
325             }
326
327             if (skip_key == 0)
328                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_value);
329             break;
330         case WJB_VALUE:
331             if (skip_key == 0)
332                 return_jsonb_value = pushJsonbValue(&state, WJB_VALUE, &jsonb_iterator_value);
333             else if (skip_key > 0)
334                 skip_key--;
335             break;
336         case WJB_END_ARRAY:
337             array_level--;
338             if (skip_key == 0)
339                 return_jsonb_value = pushJsonbValue(&state, WJB_END_ARRAY, NULL);
340             else if (skip_key > 0 && array_level == 0)
341                 skip_key--;
342             break;
343         case WJB_END_OBJECT:
344             nest_level--;
345             if (skip_key == 0)
346                 return_jsonb_value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
347             else if (skip_key > 0)
348                 skip_key--;
349             break;
350         default:
351             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
352         } 
353     }
354     PG_FREE_IF_COPY(input_jsonb, 0); 
355     PG_FREE_IF_COPY(input_array, 1); 
356
357     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
358 }
359
360 Datum jsonb_delete_jsonb(PG_FUNCTION_ARGS);
361
362 PG_FUNCTION_INFO_V1(jsonb_delete_jsonb);
363
364 /*
365  * Operator function to delete keys and values from left operand where a match 
366  * is found in the right operand.
367  *
368  * jsonb, jsonb -> jsonb
369  *
370  */
371 Datum 
372 jsonb_delete_jsonb(PG_FUNCTION_ARGS)
373 {
374     /* pointers to incoming jsonb and text[] data */
375     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
376     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
377     
378     /* pointers to return jsonb value data and state to be converted to jsonb on return */
379     JsonbValue *return_jsonb_value = NULL;
380     JsonbParseState *state = NULL;
381
382     /* pointer to iterator for input_jsonb_a and temporary value data */
383     JsonbIterator *jsonb_iterator;
384     JsonbValue  jsonb_iterator_value;
385     JsonbValue  jsonb_iterator_key;
386     int32 jsonb_iterator_token;
387     bool skip_nested = false;
388     
389     /* pointer to iterator and container for pushing nested parts of input_jsonb_a */
390     JsonbContainer *nest_jsonb_container_a;
391     JsonbIterator *nest_jsonb_iterator;
392     bool push = true;
393
394     /* pointer to lookup on input_jsonb_b */
395     JsonbValue *jsonb_lookup_value = NULL;
396     
397     /*
398      * check if either right jsonb is empty and return left if so
399      */
400     if (JB_ROOT_COUNT(input_jsonb_b) == 0)
401         PG_RETURN_JSONB(input_jsonb_a);
402
403     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
404
405     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE)
406     {
407         skip_nested = true;
408         push = true;
409
410         switch (jsonb_iterator_token)
411         {
412         case WJB_BEGIN_ARRAY:
413         case WJB_BEGIN_OBJECT:
414         case WJB_END_ARRAY:
415         case WJB_END_OBJECT:
416             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
417         break;
418         case WJB_ELEM:
419             jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
420             if (jsonb_lookup_value == NULL)
421                 return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
422             break;
423         case WJB_KEY :
424             jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
425
426             jsonb_iterator_key = jsonb_iterator_value;
427             jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested);
428             if (jsonb_iterator_token != WJB_VALUE)
429                 elog(ERROR, "invalid JsonbIteratorNext (expected WJB_VALUE) rc: %d", jsonb_iterator_token);
430
431             if (jsonb_lookup_value != NULL)
432             {
433                 if (jsonb_lookup_value->type == jsonb_iterator_value.type) 
434                 {
435                     switch (jsonb_lookup_value->type) 
436                     {
437                         case jbvNumeric:
438                             if (strcmp(
439                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_lookup_value->val.numeric))),
440                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_iterator_value.val.numeric)))
441                                 ) == 0) 
442                                 push = false;
443                         break;
444                         case jbvString:
445                             if (strcmp(
446                                 pnstrdup(jsonb_lookup_value->val.string.val,jsonb_lookup_value->val.string.len),
447                                 pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len)
448                                 ) == 0)
449                                 push = false;
450                         break;
451                         case jbvBinary:
452                             if (strcmp(
453                                 JsonbToCString(NULL, jsonb_lookup_value->val.binary.data, jsonb_lookup_value->val.binary.len),
454                                 JsonbToCString(NULL, jsonb_iterator_value.val.binary.data, jsonb_lookup_value->val.binary.len)
455                                 ) == 0)
456                                 push = false;
457                         break;
458                         case jbvBool:
459                             if (jsonb_lookup_value->val.boolean == jsonb_iterator_value.val.boolean)
460                                 push = false;
461                         break;
462                         case jbvArray:
463                         /* should not be possible? */
464                         case jbvObject:
465                         /* should not be possible? */
466                         default:
467                         ereport(ERROR, (errcode(ERRCODE_SUCCESSFUL_COMPLETION), errmsg("unexpected lookup type %i", jsonb_iterator_token)));
468                     }
469                 }
470             }
471
472             if (push) 
473             {                
474                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_key);
475
476                 /* if our value is nested binary data, iterate separately pushing each val */
477                 if (jsonb_iterator_value.type == jbvBinary) 
478                 {
479                     nest_jsonb_container_a = jsonb_iterator_value.val.binary.data;
480
481                     nest_jsonb_iterator = JsonbIteratorInit(nest_jsonb_container_a);
482                     while ((jsonb_iterator_token = JsonbIteratorNext(&nest_jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
483                     {
484                         return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
485                     }
486                 }
487                 else 
488                 {
489                     return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
490                 }
491             }
492             break;
493         case WJB_VALUE:
494             /* should not be possible */
495         default:
496             elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
497         }
498
499     }
500
501     PG_FREE_IF_COPY(input_jsonb_a, 0); 
502     PG_FREE_IF_COPY(input_jsonb_b, 1); 
503
504     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
505 }
506
507 Datum jsonb_concat_jsonb(PG_FUNCTION_ARGS);
508
509 PG_FUNCTION_INFO_V1(jsonb_concat_jsonb);
510
511 /*
512  * Operator function to concatenate json from left operand where a match 
513  * is found in the right operand.
514  *
515  * jsonb, jsonb -> jsonb
516  *
517  */
518 Datum 
519 jsonb_concat_jsonb(PG_FUNCTION_ARGS)
520 {
521     /* incoming jsonb and text[] data */
522     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
523     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
524     
525     /* return jsonb value data to be converted to jsonb on return */
526     JsonbParseState *state = NULL;
527     JsonbValue *return_jsonb_value = NULL;
528
529     /* iterator for input_jsonb_b */
530     JsonbIterator *jsonb_iterator;
531     JsonbValue jsonb_iterator_value;
532     int32 jsonb_iterator_token;
533     int32 jsonb_root_open;
534     int32 jsonb_root_close;
535
536     int32 nest_level = 0;
537    
538     /*
539      * check if either supplied jsonb is empty and return the other if so
540      */
541     if (JB_ROOT_COUNT(input_jsonb_a) == 0)
542         PG_RETURN_JSONB(input_jsonb_b);
543     else if (JB_ROOT_COUNT(input_jsonb_b) == 0)
544         PG_RETURN_JSONB(input_jsonb_a);
545
546     /* 
547      * rather than restrict concatenation to objects, allow any jsonb root
548      * but if one is an array use an array as the root container else
549      * default to object
550      */
551     if (JB_ROOT_IS_ARRAY(input_jsonb_a) || JB_ROOT_IS_ARRAY(input_jsonb_b))
552     {
553         jsonb_root_open = WJB_BEGIN_ARRAY;
554         jsonb_root_close = WJB_END_ARRAY; 
555     } else
556     {
557         jsonb_root_open = WJB_BEGIN_OBJECT;
558         jsonb_root_close = WJB_END_OBJECT; 
559     }
560
561     /*
562      * The following is essentially a cut 'n shut job; discarding the closing root 
563      * object token from the first jsonb value and the opening one from the second.
564      * Values from each are just blindly pushed onto the return value leaving
565      * deduplication down to lower level jsonb logic.
566      */
567
568     return_jsonb_value = pushJsonbValue(&state, jsonb_root_open, NULL);
569
570     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
571
572     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
573     {
574         if (jsonb_iterator_token == jsonb_root_open) 
575         {
576             nest_level++;
577             if (nest_level == 1) 
578                 continue;
579         }
580         else if (jsonb_iterator_token == jsonb_root_close) 
581         {
582             nest_level--;
583             if (nest_level == 0) 
584                 continue;
585         }
586
587         return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
588     }
589
590     nest_level = 0;
591     jsonb_iterator = JsonbIteratorInit(&input_jsonb_b->root);
592
593     while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE)
594     {
595         if (jsonb_iterator_token == jsonb_root_open) 
596         {
597             nest_level++;
598             if (nest_level == 1) 
599                 continue;
600         }
601         else if (jsonb_iterator_token == jsonb_root_close) 
602         {
603             nest_level--;
604             if (nest_level == 0) 
605                 continue;
606         }
607
608         return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
609     }
610
611     return_jsonb_value = pushJsonbValue(&state, jsonb_root_close, NULL);
612
613     PG_FREE_IF_COPY(input_jsonb_a, 0); 
614     PG_FREE_IF_COPY(input_jsonb_b, 1); 
615
616     PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
617 }