]> git.8kb.co.uk Git - postgresql/pg_jsonb_opx/commitdiff
Changes:
authorglyn <glyn@8kb.co.uk>
Fri, 30 Jan 2015 21:36:35 +0000 (21:36 +0000)
committerglyn <glyn@8kb.co.uk>
Fri, 30 Jan 2015 21:36:35 +0000 (21:36 +0000)
* attempt to remove object restrictions and allow operations on arrays and scalars.
* add a single text only key delete function
* unsure how operations on jsonb arrays should work, if for example arrays elements are numeric or boolean
possible angles are:
do some funny casting to text :-(
create separate functions taking (jsonb, int), (jsonb, bool) etc :-(

# Please enter the commit message for your changes. Lines starting

.gitignore [changed mode: 0644->0755]
README.md
jsonb_opx.c
jsonb_opx.sql.in

old mode 100644 (file)
new mode 100755 (executable)
index 1073f68b6c8a69ae2a4a9113d573a24a8ab129d2..b38e3155696b74d6488ef96cb58269c83dda297c 100755 (executable)
--- a/README.md
+++ b/README.md
@@ -1,9 +1,113 @@
 jsonb_opx
 =========
 
 jsonb_opx
 =========
 
-Test delete and concatenation operators for PostgreSQL 9.4
+Test delete and concatenation operators for PostgreSQL 9.4, this is purely for experimentation and contain errors and bad form.  
 
 
-This is purely experimentation and will contain many errors and bad form.
+**USE ON PRODUCTION SYSTEMS AT OWN RISK**
 
 
-**DO NOT USE ON PRODUCTION SYSTEMS**
+* delete operator **"-"** provided by functions *jsonb_delete(jsonb, text) jsonb_delete(jsonb, text[]) and jsonb_delete(jsonb, jsonb)*
+    Provides:
+        jsonb - text
+        jsonb - text[]
+        jsonb - jsonb
 
 
+    Note: When using text type operators on jsonb arrays only elements of type text will be deleted. E.g.
+
+```sql
+TEST=# SELECT '[1, "1", "2", 2]'::jsonb - '2'::text;
+  ?column?   
+-------------
+ [1, "1", 2]
+(1 row)
+
+TEST=# SELECT '[1, "1", "2", 2]'::jsonb - '"2"'::text;
+     ?column?     
+------------------
+ [1, "1", "2", 2]
+(1 row)
+
+TEST=# SELECT '[1, "1", "2", 2]'::jsonb - array['2']::text[];
+  ?column?   
+-------------
+ [1, "1", 2]
+(1 row)
+
+TEST=# SELECT '[1, "1", "2", 2]'::jsonb - array['"2"']::text[];
+     ?column?     
+------------------
+ [1, "1", "2", 2]
+(1 row)
+
+```
+
+    More. E.g.
+```sql
+TEST=# SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - 'b'::text;
+     ?column?     
+------------------
+ {"a": 1, "c": 3}
+(1 row)
+
+TEST=# SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - ARRAY['a','b'];
+ ?column? 
+----------
+ {"c": 3}
+(1 row)
+
+TEST=# SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb - '{"a": 4, "b": 2}'::jsonb;
+     ?column?     
+------------------
+ {"a": 1, "c": 3}
+(1 row)
+
+TEST=# SELECT '{"a": 1, "b": 2, "c": 3, "d": {"a": 4}}'::jsonb - '{"d": {"a": 4}, "b": 2}'::jsonb;
+     ?column?     
+------------------
+ {"a": 1, "c": 3}
+(1 row)
+
+TEST=# SELECT '{"a": 4, "b": 2, "c": 3, "d": {"a": 4}}'::jsonb - '{"a": 4, "b": 2}'::jsonb;
+        ?column?         
+-------------------------
+ {"c": 3, "d": {"a": 4}}
+(1 row)
+```
+    
+* concatenation operator  **"||"** provided by function *jsonb_concat(jsonb, jsonb)*
+    Provides:
+         jsonb || jsonb
+
+    E.g.
+
+```sql
+TEST=#  SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb || '{"a": 4, "b": 2, "d": 4}'::jsonb;
+             ?column?             
+----------------------------------
+ {"a": 4, "b": 2, "c": 3, "d": 4}
+(1 row)
+
+TEST=#  SELECT '["a", "b"]'::jsonb || '["c"]'::jsonb;
+    ?column?     
+-----------------
+ ["a", "b", "c"]
+(1 row)
+
+TEST=#  SELECT '[1,2,3]'::jsonb || '[3,4,5]'::jsonb;
+      ?column?      
+--------------------
+ [1, 2, 3, 3, 4, 5]
+(1 row)
+
+TEST=#  SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb || '[1,2,3]'::jsonb;
+              ?column?               
+-------------------------------------
+ [{"a": 1, "b": 2, "c": 3}, 1, 2, 3]
+(1 row)
+
+TEST=#  SELECT '{"a": 1, "b": 2, "c": 3}'::jsonb || '[1,2,3]'::jsonb || '"a"'::jsonb;
+                 ?column?                 
+------------------------------------------
+ [{"a": 1, "b": 2, "c": 3}, 1, 2, 3, "a"]
+(1 row)
+```
index a27de35ee977ff133d709ad7950f9e524de51d67..556028a3711e7392f7be82e2062fb2f2257a6896 100755 (executable)
        PG_MODULE_MAGIC;
 #endif 
 
        PG_MODULE_MAGIC;
 #endif 
 
-Datum jsonb_delete_text(PG_FUNCTION_ARGS);
+Datum jsonb_delete_key(PG_FUNCTION_ARGS);
 
 
-PG_FUNCTION_INFO_V1(jsonb_delete_text);
+PG_FUNCTION_INFO_V1(jsonb_delete_key);
+
+/*
+ * Operator function to delete key from left operand where a match is found in
+ * the right operand.
+ *
+ * jsonb, text -> jsonb
+ *
+ */
+Datum
+jsonb_delete_key(PG_FUNCTION_ARGS)
+{
+    /* pointers to incoming jsonb and text data */
+    Jsonb *input_jsonb = PG_GETARG_JSONB(0);
+    text  *input_text = PG_GETARG_TEXT_P(1);
+   
+    /* pointers to return jsonb value data and state to be converted to jsonb on return */
+    JsonbParseState *state = NULL;
+    JsonbValue *return_jsonb_value = NULL;
+
+    /* pointer to iterator for input_jsonb and lookup value data */
+    JsonbValue  jsonb_lookup_value;
+    JsonbValue *jsonb_value = NULL;
+    JsonbIterator *jsonb_iterator;
+    JsonbValue  jsonb_iterator_value;
+    int32 jsonb_iterator_token;
+
+    /* variables used for skip logic */
+    int32 skip_key = 0;
+    int32 nest_level = 0;
+    int32 array_level = 0;
+
+    /* if we are not deaing with an array first check to make sure the key exists - this is potentially just extra unwanted work */
+    if (!JB_ROOT_IS_ARRAY(input_jsonb)) 
+    {
+        jsonb_lookup_value.type = jbvString;
+        jsonb_lookup_value.val.string.val = VARDATA_ANY(input_text);
+        jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(input_text);
+
+        jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
+            JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
+
+        if (jsonb_value == NULL)
+            PG_RETURN_JSONB(input_jsonb);
+    }
+
+    /*
+    * If we've been supplied with an existing key iterate round json data and rebuild with key/value excluded.
+    *
+    * skip_key, nest_level and array_level are crude counts to check if the the value for the key is closed
+    * and ensure we don't match on keys within nested objects.  Because we are recursing into nested elements
+    * but blindly just pushing them onto the return value we can get away without deeper knowledge of the json?
+    */
+
+    jsonb_iterator = JsonbIteratorInit(&input_jsonb->root);
+
+    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
+    {
+        switch (jsonb_iterator_token)
+        {
+        case WJB_BEGIN_ARRAY:
+            array_level++;
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_ARRAY, &jsonb_iterator_value);
+            break;
+        case WJB_BEGIN_OBJECT:
+            nest_level++;
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
+            break;
+        case WJB_ELEM:
+            /* only match array elements if they are text */
+            if (skip_key == 0 && nest_level == 0 && array_level > 0) 
+            {
+                if (jsonb_iterator_value.type == jbvString) 
+                {
+                    if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
+                        VARDATA_ANY(input_text), VARSIZE_ANY_EXHDR(input_text)) == 0) 
+                        break;
+                }
+            }
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
+            break;
+        case WJB_KEY:
+            /* Check each key against our array of keys */
+            if (skip_key > 0) 
+            {
+                skip_key++;
+            }
+            else if (nest_level == 1)
+            {
+                if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
+                    VARDATA_ANY(input_text), VARSIZE_ANY_EXHDR(input_text)) == 0) {
+                    skip_key++;
+                    break;
+                }
+            }
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_value);
+            break;
+        case WJB_VALUE:
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_VALUE, &jsonb_iterator_value);
+            else if (skip_key > 0)
+                skip_key--;
+            break;
+        case WJB_END_ARRAY:
+            array_level--;
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_END_ARRAY, NULL);
+            else if (skip_key > 0 && array_level == 0)
+                skip_key--;
+            break;
+        case WJB_END_OBJECT:
+            nest_level--;
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_END_OBJECT, NULL);
+            else if (skip_key > 0)
+                skip_key--;
+            break;
+        default:
+            elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);                
+        }
+    }
+    PG_FREE_IF_COPY(input_jsonb, 0);
+    PG_FREE_IF_COPY(input_text, 1);
+
+    PG_RETURN_JSONB(JsonbValueToJsonb(return_jsonb_value));
+}
+
+Datum jsonb_delete_keys(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(jsonb_delete_keys);
 
 /*
  * Operator function to delete keys from left operand where a match is found in
 
 /*
  * Operator function to delete keys from left operand where a match is found in
@@ -34,7 +167,7 @@ PG_FUNCTION_INFO_V1(jsonb_delete_text);
  *
  */
 Datum 
  *
  */
 Datum 
-jsonb_delete_text(PG_FUNCTION_ARGS)
+jsonb_delete_keys(PG_FUNCTION_ARGS)
 {
     /* general loops */
     int i;
 {
     /* general loops */
     int i;
@@ -43,7 +176,7 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
     Jsonb *input_jsonb = PG_GETARG_JSONB(0);
     ArrayType *input_array = PG_GETARG_ARRAYTYPE_P(1);
     
     Jsonb *input_jsonb = PG_GETARG_JSONB(0);
     ArrayType *input_array = PG_GETARG_ARRAYTYPE_P(1);
     
-    /* pointers to return jsonb_value data and state to be converted to jsonb on return */
+    /* pointers to return jsonb value data and state to be converted to jsonb on return */
     JsonbParseState *state = NULL;
     JsonbValue *return_jsonb_value = NULL;
 
     JsonbParseState *state = NULL;
     JsonbValue *return_jsonb_value = NULL;
 
@@ -68,12 +201,6 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
     text *array_element_text;
     bool exists = false;
 
     text *array_element_text;
     bool exists = false;
 
-    /* check that supplied jsonb isn't non object, i.e. scalar or array */
-    if (!JB_ROOT_IS_OBJECT(input_jsonb))
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("cannot call on a non-object")));
-
     /* assert input_array is a text array type */
     Assert(ARR_ELEMTYPE(input_array) == TEXTOID);
 
     /* assert input_array is a text array type */
     Assert(ARR_ELEMTYPE(input_array) == TEXTOID);
 
@@ -91,29 +218,33 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
     if (count == 0) 
         PG_RETURN_JSONB(input_jsonb);
 
     if (count == 0) 
         PG_RETURN_JSONB(input_jsonb);
 
-    /* first check to make sure at least one key exists - this is potentially just extra unwanted work */
-    for (i=0; i<count; i++)
+    /* if we are not deaing with an array first check to make sure the key exists - this is potentially just extra unwanted work */
+    if (!JB_ROOT_IS_ARRAY(input_jsonb)) 
     {
     {
-        if (nulls[i])
-            continue;
+        for (i=0; i<count; i++)
+        {
+            if (nulls[i])
+                continue;
 
 
-        array_element_text = DatumGetTextP(datums[i]);
+            array_element_text = DatumGetTextP(datums[i]);
 
 
-        jsonb_lookup_value.type = jbvString;
-        jsonb_lookup_value.val.string.val = VARDATA_ANY(array_element_text);
-        jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(array_element_text);
+            jsonb_lookup_value.type = jbvString;
+            jsonb_lookup_value.val.string.val = VARDATA_ANY(array_element_text);
+            jsonb_lookup_value.val.string.len = VARSIZE_ANY_EXHDR(array_element_text);
             
             
-        jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
-            JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
+            jsonb_value = findJsonbValueFromContainer(&input_jsonb->root,
+                JB_FOBJECT | JB_FARRAY, &jsonb_lookup_value);
 
 
-        if (jsonb_value != NULL) {
-            exists = true;
-            break;
+            if (jsonb_value != NULL) 
+            {
+                exists = true;
+                break;
+            }
         }
         }
-    }
 
 
-    if (!exists) 
-        PG_RETURN_JSONB(input_jsonb);
+        if (!exists) 
+            PG_RETURN_JSONB(input_jsonb);
+    }
 
     /*
     * If we've been supplied with existing keys iterate round json data matching those keys.
 
     /*
     * If we've been supplied with existing keys iterate round json data matching those keys.
@@ -139,12 +270,44 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
             if (skip_key == 0)
                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
             break;
             if (skip_key == 0)
                 return_jsonb_value = pushJsonbValue(&state, WJB_BEGIN_OBJECT, &jsonb_iterator_value);
             break;
+        case WJB_ELEM:
+            /* only match array elements if they are text */
+            if (skip_key == 0 && nest_level == 0 && array_level > 0) 
+            {
+                if (jsonb_iterator_value.type == jbvString) 
+                {
+                    for (i=0; i<count; i++)
+                    {
+                        if (nulls[i])
+                            continue;
+
+                        array_element_text = DatumGetTextP(datums[i]);
+
+                        if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
+                            VARDATA_ANY(array_element_text), VARSIZE_ANY_EXHDR(array_element_text)) == 0) 
+                        {
+                            skip_key = 1;
+                            break;
+                        }
+                    }
+                    if (skip_key == 1) 
+                    {
+                        skip_key = 0;
+                        break;
+                    }
+                }
+            }
+            if (skip_key == 0)
+                return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
+            break;
         case WJB_KEY:
             /* Check each key against our array of keys */
         case WJB_KEY:
             /* Check each key against our array of keys */
-            if (skip_key > 0) {
+            if (skip_key > 0) 
+            {
                 skip_key++;
             }
                 skip_key++;
             }
-            else if (nest_level == 1){
+            else if (nest_level == 1)
+            {
                 for (i=0; i<count; i++)
                 {
                     if (nulls[i])
                 for (i=0; i<count; i++)
                 {
                     if (nulls[i])
@@ -152,7 +315,9 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
 
                     array_element_text = DatumGetTextP(datums[i]);
 
 
                     array_element_text = DatumGetTextP(datums[i]);
 
-                    if (strcmp(VARDATA(array_element_text), pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len)) == 0) {
+                    if (strncmp(pnstrdup(jsonb_iterator_value.val.string.val,jsonb_iterator_value.val.string.len),
+                        VARDATA_ANY(array_element_text), VARSIZE_ANY_EXHDR(array_element_text)) == 0) 
+                    {
                         skip_key++;
                         break;
                     }
                         skip_key++;
                         break;
                     }
@@ -168,10 +333,6 @@ jsonb_delete_text(PG_FUNCTION_ARGS)
             else if (skip_key > 0)
                 skip_key--;
             break;
             else if (skip_key > 0)
                 skip_key--;
             break;
-        case WJB_ELEM:
-            if (skip_key == 0)
-                return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
-            break;
         case WJB_END_ARRAY:
             array_level--;
             if (skip_key == 0)
         case WJB_END_ARRAY:
             array_level--;
             if (skip_key == 0)
@@ -214,7 +375,7 @@ jsonb_delete_jsonb(PG_FUNCTION_ARGS)
     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
     
     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
     
-    /* pointers to return jsonb_value data and state to be converted to jsonb on return */
+    /* pointers to return jsonb value data and state to be converted to jsonb on return */
     JsonbValue *return_jsonb_value = NULL;
     JsonbParseState *state = NULL;
 
     JsonbValue *return_jsonb_value = NULL;
     JsonbParseState *state = NULL;
 
@@ -230,51 +391,49 @@ jsonb_delete_jsonb(PG_FUNCTION_ARGS)
     JsonbIterator *nest_jsonb_iterator;
     bool push = true;
 
     JsonbIterator *nest_jsonb_iterator;
     bool push = true;
 
-    /* inner iterator for iterating around jbvBinary types */
-    JsonbValue  nest_jsonb_iterator_value;
-    int32 nest_jsonb_iterator_token;
-
     /* pointer to lookup on input_jsonb_b */
     JsonbValue *jsonb_lookup_value = NULL;
     
     /* pointer to lookup on input_jsonb_b */
     JsonbValue *jsonb_lookup_value = NULL;
     
-    /* check that supplied jsonb isn't non object, i.e. scalar or array */
-    if (!JB_ROOT_IS_OBJECT(input_jsonb_a) || !JB_ROOT_IS_OBJECT(input_jsonb_b))
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("cannot call on a non-object")));
-
     /*
     /*
-     * check if either supplied jsonb is empty and return the other if so
-     * this idea was copied from https://github.com/erthalion/jsonbx/blob/master/jsonbx.c
+     * check if either right jsonb is empty and return left if so
      */
     if (JB_ROOT_COUNT(input_jsonb_b) == 0)
         PG_RETURN_JSONB(input_jsonb_a);
 
     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
 
      */
     if (JB_ROOT_COUNT(input_jsonb_b) == 0)
         PG_RETURN_JSONB(input_jsonb_a);
 
     jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
 
-    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
+    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE)
+    {
         skip_nested = true;
         skip_nested = true;
+        push = true;
 
         switch (jsonb_iterator_token)
         {
         case WJB_BEGIN_ARRAY:
         case WJB_BEGIN_OBJECT:
 
         switch (jsonb_iterator_token)
         {
         case WJB_BEGIN_ARRAY:
         case WJB_BEGIN_OBJECT:
-        case WJB_ELEM:
         case WJB_END_ARRAY:
         case WJB_END_OBJECT:
             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
         break;
         case WJB_END_ARRAY:
         case WJB_END_OBJECT:
             return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
         break;
-        case WJB_KEY:
-             jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
-             jsonb_iterator_key = jsonb_iterator_value;
+        case WJB_ELEM:
+            jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
+            if (jsonb_lookup_value == NULL)
+                return_jsonb_value = pushJsonbValue(&state, WJB_ELEM, &jsonb_iterator_value);
+            break;
+        case WJB_KEY :
+            jsonb_lookup_value = findJsonbValueFromContainer(&input_jsonb_b->root, JB_FOBJECT | JB_FARRAY, &jsonb_iterator_value);
 
 
-             jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested);
-             if (jsonb_iterator_token != WJB_VALUE)
-                 elog(ERROR, "invalid JsonbIteratorNext (expected WJB_VALUE) rc: %d", jsonb_iterator_token);
+            jsonb_iterator_key = jsonb_iterator_value;
+            jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested);
+            if (jsonb_iterator_token != WJB_VALUE)
+                elog(ERROR, "invalid JsonbIteratorNext (expected WJB_VALUE) rc: %d", jsonb_iterator_token);
 
 
-             if (jsonb_lookup_value != NULL) {
-                if (jsonb_lookup_value->type == jsonb_iterator_value.type) {
-                    switch (jsonb_lookup_value->type) {
+            if (jsonb_lookup_value != NULL)
+            {
+                if (jsonb_lookup_value->type == jsonb_iterator_value.type) 
+                {
+                    switch (jsonb_lookup_value->type) 
+                    {
                         case jbvNumeric:
                             if (strcmp(
                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_lookup_value->val.numeric))),
                         case jbvNumeric:
                             if (strcmp(
                                 DatumGetCString(DirectFunctionCall1(numeric_out, PointerGetDatum(jsonb_lookup_value->val.numeric))),
@@ -305,29 +464,31 @@ jsonb_delete_jsonb(PG_FUNCTION_ARGS)
                         case jbvObject:
                         /* should not be possible? */
                         default:
                         case jbvObject:
                         /* should not be possible? */
                         default:
-                        ereport(NOTICE, (errcode(ERRCODE_SUCCESSFUL_COMPLETION), errmsg("unexpected lookup type")));
+                        ereport(ERROR, (errcode(ERRCODE_SUCCESSFUL_COMPLETION), errmsg("unexpected lookup type %i", jsonb_iterator_token)));
                     }
                 }
             }
 
                     }
                 }
             }
 
-            if (push) {                
+            if (push) 
+            {                
                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_key);
 
                 /* if our value is nested binary data, iterate separately pushing each val */
                 return_jsonb_value = pushJsonbValue(&state, WJB_KEY, &jsonb_iterator_key);
 
                 /* if our value is nested binary data, iterate separately pushing each val */
-                if (jsonb_iterator_value.type == jbvBinary) {
+                if (jsonb_iterator_value.type == jbvBinary) 
+                {
                     nest_jsonb_container_a = jsonb_iterator_value.val.binary.data;
 
                     nest_jsonb_iterator = JsonbIteratorInit(nest_jsonb_container_a);
                     nest_jsonb_container_a = jsonb_iterator_value.val.binary.data;
 
                     nest_jsonb_iterator = JsonbIteratorInit(nest_jsonb_container_a);
-                    while ((nest_jsonb_iterator_token = JsonbIteratorNext(&nest_jsonb_iterator, &nest_jsonb_iterator_value, false)) != WJB_DONE) {
-                        return_jsonb_value = pushJsonbValue(&state, nest_jsonb_iterator_token, &nest_jsonb_iterator_value);
+                    while ((jsonb_iterator_token = JsonbIteratorNext(&nest_jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
+                    {
+                        return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
                     }
                 }
                     }
                 }
-                else {
+                else 
+                {
                     return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
                 }
             }
                     return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
                 }
             }
-            else 
-                push = true;
             break;
         case WJB_VALUE:
             /* should not be possible */
             break;
         case WJB_VALUE:
             /* should not be possible */
@@ -361,34 +522,42 @@ jsonb_concat_jsonb(PG_FUNCTION_ARGS)
     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
     
     Jsonb *input_jsonb_a = PG_GETARG_JSONB(0);
     Jsonb *input_jsonb_b = PG_GETARG_JSONB(1);
     
-    /* return jsonb_value data to be converted to jsonb on return */
+    /* return jsonb value data to be converted to jsonb on return */
+    JsonbParseState *state = NULL;
     JsonbValue *return_jsonb_value = NULL;
 
     /* iterator for input_jsonb_b */
     JsonbIterator *jsonb_iterator;
     JsonbValue *return_jsonb_value = NULL;
 
     /* iterator for input_jsonb_b */
     JsonbIterator *jsonb_iterator;
-    JsonbValue  jsonb_iterator_value;
+    JsonbValue jsonb_iterator_value;
     int32 jsonb_iterator_token;
     int32 jsonb_iterator_token;
-
-    JsonbParseState *state = NULL;
-    bool skip_nested = false;
+    int32 jsonb_root_open;
+    int32 jsonb_root_close;
 
     int32 nest_level = 0;
    
 
     int32 nest_level = 0;
    
-    /* check that supplied jsonb isn't non object, i.e. scalar or array */
-    if (!JB_ROOT_IS_OBJECT(input_jsonb_a) || !JB_ROOT_IS_OBJECT(input_jsonb_b))
-        ereport(ERROR,
-            (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
-                errmsg("cannot call on a non-object")));
-    
     /*
      * check if either supplied jsonb is empty and return the other if so
     /*
      * check if either supplied jsonb is empty and return the other if so
-     * this idea was copied from https://github.com/erthalion/jsonbx/blob/master/jsonbx.c
      */
     if (JB_ROOT_COUNT(input_jsonb_a) == 0)
         PG_RETURN_JSONB(input_jsonb_b);
     else if (JB_ROOT_COUNT(input_jsonb_b) == 0)
         PG_RETURN_JSONB(input_jsonb_a);
 
      */
     if (JB_ROOT_COUNT(input_jsonb_a) == 0)
         PG_RETURN_JSONB(input_jsonb_b);
     else if (JB_ROOT_COUNT(input_jsonb_b) == 0)
         PG_RETURN_JSONB(input_jsonb_a);
 
+    /* 
+     * rather than restrict concatenation to objects, allow any jsonb root
+     * but if one is an array use an array as the root container else
+     * default to object
+     */
+    if (JB_ROOT_IS_ARRAY(input_jsonb_a) || JB_ROOT_IS_ARRAY(input_jsonb_b))
+    {
+        jsonb_root_open = WJB_BEGIN_ARRAY;
+        jsonb_root_close = WJB_END_ARRAY; 
+    } else
+    {
+        jsonb_root_open = WJB_BEGIN_OBJECT;
+        jsonb_root_close = WJB_END_OBJECT; 
+    }
+
     /*
      * The following is essentially a cut 'n shut job; discarding the closing root 
      * object token from the first jsonb value and the opening one from the second.
     /*
      * The following is essentially a cut 'n shut job; discarding the closing root 
      * object token from the first jsonb value and the opening one from the second.
@@ -396,63 +565,51 @@ jsonb_concat_jsonb(PG_FUNCTION_ARGS)
      * deduplication down to lower level jsonb logic.
      */
 
      * deduplication down to lower level jsonb logic.
      */
 
-    jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
+    return_jsonb_value = pushJsonbValue(&state, jsonb_root_open, NULL);
 
 
-    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
+    jsonb_iterator = JsonbIteratorInit(&input_jsonb_a->root);
 
 
-        switch (jsonb_iterator_token)
+    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE) 
+    {
+        if (jsonb_iterator_token == jsonb_root_open) 
         {
         {
-        case WJB_BEGIN_ARRAY:
-        case WJB_KEY:
-        case WJB_VALUE:
-        case WJB_ELEM:
-        case WJB_END_ARRAY:
-            return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
-            break;
-        case WJB_BEGIN_OBJECT:
-            return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
             nest_level++;
             nest_level++;
-            break;
-        case WJB_END_OBJECT:
+            if (nest_level == 1) 
+                continue;
+        }
+        else if (jsonb_iterator_token == jsonb_root_close) 
+        {
             nest_level--;
             nest_level--;
-            if (nest_level != 0)
-                return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
-            break;
-        default:
-            elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
+            if (nest_level == 0) 
+                continue;
         }
 
         }
 
+        return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
     }
 
     nest_level = 0;
     jsonb_iterator = JsonbIteratorInit(&input_jsonb_b->root);
 
     }
 
     nest_level = 0;
     jsonb_iterator = JsonbIteratorInit(&input_jsonb_b->root);
 
-    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, skip_nested)) != WJB_DONE) {
-
-        switch (jsonb_iterator_token)
+    while ((jsonb_iterator_token = JsonbIteratorNext(&jsonb_iterator, &jsonb_iterator_value, false)) != WJB_DONE)
+    {
+        if (jsonb_iterator_token == jsonb_root_open) 
         {
         {
-        case WJB_BEGIN_ARRAY:
-        case WJB_KEY:
-        case WJB_VALUE:
-        case WJB_ELEM:
-        case WJB_END_ARRAY:
-            return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
-            break;
-        case WJB_BEGIN_OBJECT:
-            if (nest_level != 0)
-                return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
             nest_level++;
             nest_level++;
-            break;
-        case WJB_END_OBJECT:
+            if (nest_level == 1) 
+                continue;
+        }
+        else if (jsonb_iterator_token == jsonb_root_close) 
+        {
             nest_level--;
             nest_level--;
-            return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
-            break;
-        default:
-            elog(ERROR, "invalid JsonbIteratorNext rc: %d", jsonb_iterator_token);
+            if (nest_level == 0) 
+                continue;
         }
 
         }
 
+        return_jsonb_value = pushJsonbValue(&state, jsonb_iterator_token, &jsonb_iterator_value);
     }
 
     }
 
+    return_jsonb_value = pushJsonbValue(&state, jsonb_root_close, NULL);
+
     PG_FREE_IF_COPY(input_jsonb_a, 0); 
     PG_FREE_IF_COPY(input_jsonb_b, 1); 
 
     PG_FREE_IF_COPY(input_jsonb_a, 0); 
     PG_FREE_IF_COPY(input_jsonb_b, 1); 
 
index 782beffa0c881f1d40dfc69389bea7270ab2af03..b5477b3f3b3d4ddc425ed2ce5f1dd9d820eef161 100755 (executable)
@@ -1,25 +1,30 @@
-CREATE OR REPLACE FUNCTION public.jsonb_delete(jsonb, text[]) 
-RETURNS jsonb
-       AS 'MODULE_PATHNAME', 'jsonb_delete_text'
-LANGUAGE C IMMUTABLE STRICT;
-COMMENT ON FUNCTION public.jsonb_delete(jsonb, text[]) IS 'delete keys in second argument from first argument';
-
--- DROP OPERATOR - (jsonb, text[]);
-CREATE OPERATOR - ( PROCEDURE = public.jsonb_delete, LEFTARG = jsonb, RIGHTARG = text[]);
-COMMENT ON OPERATOR - (jsonb, text[]) IS 'delete keys from left operand';
-
---
+-- CREATE OR REPLACE FUNCTION public.jsonb_delete (jsonb, text) 
+-- RETURNS jsonb
+--     AS 'SELECT jsonb_delete($1, ARRAY[$2]);'
+-- LANGUAGE SQL IMMUTABLE STRICT; 
+-- COMMENT ON FUNCTION public.jsonb_delete(jsonb, text) IS 'delete key in second argument from first argument';
 
 CREATE OR REPLACE FUNCTION public.jsonb_delete (jsonb, text) 
 RETURNS jsonb
 
 CREATE OR REPLACE FUNCTION public.jsonb_delete (jsonb, text) 
 RETURNS jsonb
-    AS 'SELECT jsonb_delete($1, ARRAY[$2]);'
-LANGUAGE SQL IMMUTABLE STRICT; 
+    AS 'MODULE_PATHNAME', 'jsonb_delete_key'
+LANGUAGE C IMMUTABLE STRICT; 
 COMMENT ON FUNCTION public.jsonb_delete(jsonb, text) IS 'delete key in second argument from first argument';
 
 -- DROP OPERATOR - (jsonb, text);
 CREATE OPERATOR - ( PROCEDURE = public.jsonb_delete, LEFTARG = jsonb, RIGHTARG = text);
 COMMENT ON OPERATOR - (jsonb, text) IS 'delete key from left operand';
 
 COMMENT ON FUNCTION public.jsonb_delete(jsonb, text) IS 'delete key in second argument from first argument';
 
 -- DROP OPERATOR - (jsonb, text);
 CREATE OPERATOR - ( PROCEDURE = public.jsonb_delete, LEFTARG = jsonb, RIGHTARG = text);
 COMMENT ON OPERATOR - (jsonb, text) IS 'delete key from left operand';
 
+--
+CREATE OR REPLACE FUNCTION public.jsonb_delete(jsonb, text[]) 
+RETURNS jsonb
+       AS 'MODULE_PATHNAME', 'jsonb_delete_keys'
+LANGUAGE C IMMUTABLE STRICT;
+COMMENT ON FUNCTION public.jsonb_delete(jsonb, text[]) IS 'delete keys in second argument from first argument';
+
+-- DROP OPERATOR - (jsonb, text[]);
+CREATE OPERATOR - ( PROCEDURE = public.jsonb_delete, LEFTARG = jsonb, RIGHTARG = text[]);
+COMMENT ON OPERATOR - (jsonb, text[]) IS 'delete keys from left operand';
+
 --
 
 CREATE OR REPLACE FUNCTION public.jsonb_delete(jsonb, jsonb) 
 --
 
 CREATE OR REPLACE FUNCTION public.jsonb_delete(jsonb, jsonb)