Initial implementation of the json_mergepatch(A,B) SQL function.
FossilOrigin-Name: a267444039af519f088dd8f8ee33f686cc3071c087677075af2364ebc2587514
This commit is contained in:
parent
918938f9c2
commit
633647af75
138
ext/misc/json1.c
138
ext/misc/json1.c
@ -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 },
|
||||
|
16
manifest
16
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
|
||||
|
@ -1 +1 @@
|
||||
055b36f1c1593bb123f7319a07c476143d71af052b5b8d34afcd0d500f197882
|
||||
a267444039af519f088dd8f8ee33f686cc3071c087677075af2364ebc2587514
|
63
test/json104.test
Normal file
63
test/json104.test
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user