]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Initial implementation of the json_mergepatch(A,B) SQL function.
authordrh <drh@noemail.net>
Wed, 22 Mar 2017 21:24:31 +0000 (21:24 +0000)
committerdrh <drh@noemail.net>
Wed, 22 Mar 2017 21:24:31 +0000 (21:24 +0000)
FossilOrigin-Name: a267444039af519f088dd8f8ee33f686cc3071c087677075af2364ebc2587514

ext/misc/json1.c
manifest
manifest.uuid
test/json104.test [new file with mode: 0644]

index 3063f9f261a8bbb365853bbf1a53dddb94662fd2..0db946fdc6a65279a669aa597e7014b25852ae45 100644 (file)
@@ -138,9 +138,10 @@ static const char * const jsonType[] = {
 #define JNODE_RAW     0x01         /* Content is raw, not JSON encoded */
 #define JNODE_ESCAPE  0x02         /* Content is text with \ escapes */
 #define JNODE_REMOVE  0x04         /* Do not output */
-#define JNODE_REPLACE 0x08         /* Replace with JsonNode.iVal */
-#define JNODE_APPEND  0x10         /* More ARRAY/OBJECT entries at u.iAppend */
-#define JNODE_LABEL   0x20         /* Is a label of an object */
+#define JNODE_REPLACE 0x08         /* Replace with JsonNode.u.iReplace */
+#define JNODE_PATCH   0x10         /* Patch with JsonNode.u.pPatch */
+#define JNODE_APPEND  0x20         /* More ARRAY/OBJECT entries at u.iAppend */
+#define JNODE_LABEL   0x40         /* Is a label of an object */
 
 
 /* A single node of parsed JSON
@@ -148,12 +149,13 @@ static const char * const jsonType[] = {
 struct JsonNode {
   u8 eType;              /* One of the JSON_ type values */
   u8 jnFlags;            /* JNODE flags */
-  u8 iVal;               /* Replacement value when JNODE_REPLACE */
   u32 n;                 /* Bytes of content, or number of sub-nodes */
   union {
     const char *zJContent; /* Content for INT, REAL, and STRING */
     u32 iAppend;           /* More terms for ARRAY and OBJECT */
     u32 iKey;              /* Key for ARRAY objects in json_tree() */
+    u32 iReplace;          /* Replacement content for JNODE_REPLACE */
+    JsonNode *pPatch;      /* Node chain of patch for JNODE_PATCH */
   } u;
 };
 
@@ -410,6 +412,13 @@ static void jsonRenderNode(
   JsonString *pOut,              /* Write JSON here */
   sqlite3_value **aReplace       /* Replacement values */
 ){
+  if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){
+    if( pNode->jnFlags & JNODE_REPLACE ){
+      jsonAppendValue(pOut, aReplace[pNode->u.iReplace]);
+      return;
+    }
+    pNode = pNode->u.pPatch;
+  }
   switch( pNode->eType ){
     default: {
       assert( pNode->eType==JSON_NULL );
@@ -441,12 +450,7 @@ static void jsonRenderNode(
       jsonAppendChar(pOut, '[');
       for(;;){
         while( j<=pNode->n ){
-          if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){
-            if( pNode[j].jnFlags & JNODE_REPLACE ){
-              jsonAppendSeparator(pOut);
-              jsonAppendValue(pOut, aReplace[pNode[j].iVal]);
-            }
-          }else{
+          if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){
             jsonAppendSeparator(pOut);
             jsonRenderNode(&pNode[j], pOut, aReplace);
           }
@@ -468,11 +472,7 @@ static void jsonRenderNode(
             jsonAppendSeparator(pOut);
             jsonRenderNode(&pNode[j], pOut, aReplace);
             jsonAppendChar(pOut, ':');
-            if( pNode[j+1].jnFlags & JNODE_REPLACE ){
-              jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]);
-            }else{
-              jsonRenderNode(&pNode[j+1], pOut, aReplace);
-            }
+            jsonRenderNode(&pNode[j+1], pOut, aReplace);
           }
           j += 1 + jsonNodeSize(&pNode[j+1]);
         }
@@ -699,7 +699,6 @@ static int jsonParseAddNode(
   p = &pParse->aNode[pParse->nNode];
   p->eType = (u8)eType;
   p->jnFlags = 0;
-  p->iVal = 0;
   p->n = n;
   p->u.zJContent = zContent;
   return pParse->nNode++;
@@ -1357,6 +1356,104 @@ static void jsonExtractFunc(
   jsonParseReset(&x);
 }
 
+/* This is the RFC 7396 MergePatch algorithm.
+*/
+static JsonNode *jsonMergePatch(
+  JsonParse *pParse,   /* The JSON parser that contains the TARGET */
+  int iTarget,         /* Node of the TARGET in pParse */
+  JsonNode *pPatch     /* The PATCH */
+){
+  int i, j;
+  int iApnd;
+  JsonNode *pTarget;
+  if( pPatch->eType!=JSON_OBJECT ){
+    return pPatch;
+  }
+  assert( iTarget>=0 && iTarget<pParse->nNode );
+  pTarget = &pParse->aNode[iTarget];
+  assert( (pPatch->jnFlags & JNODE_APPEND)==0 );
+  if( pTarget->eType!=JSON_OBJECT ){
+    for(i=2; i<pPatch->n; i += jsonNodeSize(&pPatch[i])+1){
+      if( pPatch[i].eType==JSON_NULL ){
+        pPatch[i-1].jnFlags |= JNODE_REMOVE;
+      }
+    }
+    return pPatch;
+  }
+  iApnd = iTarget;
+  for(i=1; i<pPatch->n; i += jsonNodeSize(&pPatch[i+1])+1){
+    int nKey;
+    const char *zKey;
+    assert( pPatch[i].eType==JSON_STRING );
+    assert( pPatch[i].jnFlags & JNODE_LABEL );
+    nKey = pPatch[i].n;
+    zKey = pPatch[i].u.zJContent;
+    if( (pPatch[i].jnFlags & JNODE_RAW)==0 ){
+      assert( nKey>=2 && zKey[0]=='"' && zKey[nKey-1]=='"' );
+      nKey -= 2;
+      zKey ++;
+    }
+    for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){
+      assert( pTarget[j].eType==JSON_STRING );
+      assert( pTarget[j].jnFlags & JNODE_LABEL );
+      if( jsonLabelCompare(&pTarget[j], zKey, nKey)
+       && (pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH))==0
+      ){
+        if( pPatch[i+1].eType==JSON_NULL ){
+          pTarget[j+1].jnFlags |= JNODE_REMOVE;
+        }else{
+          JsonNode *pNew = jsonMergePatch(pParse, j+1, &pPatch[i+1]);
+          if( pNew==0 ) return 0;
+          pTarget = &pParse->aNode[iTarget];
+          if( pNew!=&pTarget[j+1] ){
+            pTarget[j+1].u.pPatch = pNew;
+            pTarget[j+1].jnFlags |= JNODE_PATCH;
+          }
+        }
+        break;
+      }
+    }
+    if( j>=pTarget->n ){
+      int iStart, iPatch;
+      iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0);
+      jsonParseAddNode(pParse, JSON_STRING, nKey, zKey);
+      iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
+      if( pParse->oom ) return 0;
+      pTarget = &pParse->aNode[iTarget];
+      pParse->aNode[iApnd].jnFlags |= JNODE_APPEND;
+      pParse->aNode[iApnd].u.iAppend = iStart;
+      iApnd = iStart;
+      pParse->aNode[iPatch].jnFlags |= JNODE_PATCH;
+      pParse->aNode[iPatch].u.pPatch = &pPatch[i+1];
+    }
+  }
+  return pTarget;
+}
+
+/*
+** Implementation of the json_mergepatch(JSON1,JSON2) function.  Return a JSON
+** object that is the result of running the RFC 7396 MergePatch() algorithm
+** on the two arguments.
+*/
+static void jsonMergePatchFunc(
+  sqlite3_context *ctx,
+  int argc,
+  sqlite3_value **argv
+){
+  JsonParse x;     /* The JSON that is being patched */
+  JsonParse y;     /* The patch */
+
+  if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
+  if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){
+    jsonParseReset(&x);
+    return;
+  }
+  jsonReturnJson(jsonMergePatch(&x, 0, y.aNode), ctx, 0);
+  jsonParseReset(&x);
+  jsonParseReset(&y);
+}
+
+
 /*
 ** Implementation of the json_object(NAME,VALUE,...) function.  Return a JSON
 ** object that contains all name/value given in arguments.  Or if any name
@@ -1460,11 +1557,11 @@ static void jsonReplaceFunc(
     if( x.nErr ) goto replace_err;
     if( pNode ){
       pNode->jnFlags |= (u8)JNODE_REPLACE;
-      pNode->iVal = (u8)(i+1);
+      pNode->u.iReplace = i + 1;
     }
   }
   if( x.aNode[0].jnFlags & JNODE_REPLACE ){
-    sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
+    sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
   }else{
     jsonReturnJson(x.aNode, ctx, argv);
   }
@@ -1514,11 +1611,11 @@ static void jsonSetFunc(
       goto jsonSetDone;
     }else if( pNode && (bApnd || bIsSet) ){
       pNode->jnFlags |= (u8)JNODE_REPLACE;
-      pNode->iVal = (u8)(i+1);
+      pNode->u.iReplace = i + 1;
     }
   }
   if( x.aNode[0].jnFlags & JNODE_REPLACE ){
-    sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
+    sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
   }else{
     jsonReturnJson(x.aNode, ctx, argv);
   }
@@ -2160,6 +2257,7 @@ int sqlite3Json1Init(sqlite3 *db){
     { "json_array_length",    2, 0,   jsonArrayLengthFunc   },
     { "json_extract",        -1, 0,   jsonExtractFunc       },
     { "json_insert",         -1, 0,   jsonSetFunc           },
+    { "json_mergepatch",      2, 0,   jsonMergePatchFunc    },
     { "json_object",         -1, 0,   jsonObjectFunc        },
     { "json_quote",           1, 0,   jsonQuoteFunc         },
     { "json_remove",         -1, 0,   jsonRemoveFunc        },
index 834fc1ba0fbd85558b1ec67e50b8e5ef0391bf0c..cceedf00d153b9b461959ca27b4cdf52acb913fe 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C New\ssimplified\smemory\sinitialization\sfor\sMacOS.
-D 2017-03-21T20:17:24.121
+C Initial\simplementation\sof\sthe\sjson_mergepatch(A,B)\sSQL\sfunction.
+D 2017-03-22T21:24:31.714
 F Makefile.in 1cc758ce3374a32425e4d130c2fe7b026b20de5b8843243de75f087c0a2661fb
 F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
 F Makefile.msc 1faf9f06aadc9284c212dea7bbc7c0dea7e8337f0287c81001eff500912c790a
@@ -218,7 +218,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f
 F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
 F ext/misc/ieee754.c f190d0cc5182529acb15babd177781be1ac1718c
-F ext/misc/json1.c 552a7d730863419e05dc947265b28bd411680e2e
+F ext/misc/json1.c ca27a98c0a7a90fcbaccd3157204e19afc7eec0ba3f4c08ed06bb638b773f523
 F ext/misc/memvfs.c e5225bc22e79dde6b28380f3a068ddf600683a33
 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342
 F ext/misc/percentile.c 92699c8cd7d517ff610e6037e56506f8904dae2e
@@ -913,6 +913,7 @@ F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa
 F test/json101.test c0897616f32d95431f37fd291cb78742181980ac
 F test/json102.test bf3fe7a706d30936a76a0f7a0375e1e8e73aff5a
 F test/json103.test c5f6b85e69de05f6b3195f9f9d5ce9cd179099a0
+F test/json104.test 83fd7a15eadb0cde34a37200842318d1cd98abe908e2847fb93d337d969815cc
 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff
 F test/kvtest.c b9a9822dda05a1aa481215a52e2fc93cd8b22ee5
 F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63
@@ -1567,7 +1568,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P ad741976c8c29bcc94f9ea9ed7deb580bb00c8a81d1a7fba1a4e03849728433d
-R c272fa429602630b64ee19437bd8d503
+P 055b36f1c1593bb123f7319a07c476143d71af052b5b8d34afcd0d500f197882
+R 1d5a57aea0e07c508840a776caf26b14
+T *branch * json_mergepatch
+T *sym-json_mergepatch *
+T -sym-trunk *
 U drh
-Z a3a73b80a262fb4daeb6a94d5a29c210
+Z 2961dcef6bde9e861663934a25c90d24
index e5a19cd1b9ae203aa713462ade8c3ddf2cb19108..980cac0acf4921798e530a2cad3179a1902269a2 100644 (file)
@@ -1 +1 @@
-055b36f1c1593bb123f7319a07c476143d71af052b5b8d34afcd0d500f197882
\ No newline at end of file
+a267444039af519f088dd8f8ee33f686cc3071c087677075af2364ebc2587514
\ No newline at end of file
diff --git a/test/json104.test b/test/json104.test
new file mode 100644 (file)
index 0000000..fc2e0e3
--- /dev/null
@@ -0,0 +1,63 @@
+# 2017-03-22
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements tests for json_mergepatch(A,B) SQL function.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+ifcapable !json1 {
+  finish_test
+  return
+}
+
+# This is the example from pages 2 and 3 of RFC-7396
+do_execsql_test json104-100 {
+  SELECT json_mergepatch(
+    json('{
+       "a": "b",
+       "c": {
+         "d": "e",
+         "f": "g"
+       }
+     }'),
+    json('{
+       "a":"z",
+       "c": {
+         "f": null
+       }
+     }'));
+} {{{"a":"z","c":{"d":"e"}}}}
+
+
+# This is the example from pages 4 and 5 of RFC-7396 
+do_execsql_test json104-110 {
+  SELECT json_mergepatch(
+    json('{
+       "title": "Goodbye!",
+       "author" : {
+         "givenName" : "John",
+         "familyName" : "Doe"
+       },
+       "tags":[ "example", "sample" ],
+       "content": "This will be unchanged"
+     }'),
+    json('{
+       "title": "Hello!",
+       "phoneNumber": "+01-123-456-7890",
+       "author": {
+         "familyName": null
+       },
+       "tags": [ "example" ]
+     }'));
+} {{{"title":"Hello!","author":{"givenName":"John"},"tags":["example"],"content":"This will be unchanged",phoneNumber:"+01-123-456-7890"}}}
+
+finish_test