/ Check-in [a2674440]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Initial implementation of the json_mergepatch(A,B) SQL function.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | json_mergepatch
Files: files | file ages | folders
SHA3-256: a267444039af519f088dd8f8ee33f686cc3071c087677075af2364ebc2587514
User & Date: drh 2017-03-22 21:24:31
Context
2017-03-22
21:45
Change the name of the new function to "json_merge_patch()". check-in: 53bf70f3 user: drh tags: json_mergepatch
21:24
Initial implementation of the json_mergepatch(A,B) SQL function. check-in: a2674440 user: drh tags: json_mergepatch
2017-03-21
20:17
New simplified memory initialization for MacOS. check-in: 055b36f1 user: drh tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/misc/json1.c.

   134    134   };
   135    135   
   136    136   /* Bit values for the JsonNode.jnFlag field
   137    137   */
   138    138   #define JNODE_RAW     0x01         /* Content is raw, not JSON encoded */
   139    139   #define JNODE_ESCAPE  0x02         /* Content is text with \ escapes */
   140    140   #define JNODE_REMOVE  0x04         /* Do not output */
   141         -#define JNODE_REPLACE 0x08         /* Replace with JsonNode.iVal */
   142         -#define JNODE_APPEND  0x10         /* More ARRAY/OBJECT entries at u.iAppend */
   143         -#define JNODE_LABEL   0x20         /* Is a label of an object */
          141  +#define JNODE_REPLACE 0x08         /* Replace with JsonNode.u.iReplace */
          142  +#define JNODE_PATCH   0x10         /* Patch with JsonNode.u.pPatch */
          143  +#define JNODE_APPEND  0x20         /* More ARRAY/OBJECT entries at u.iAppend */
          144  +#define JNODE_LABEL   0x40         /* Is a label of an object */
   144    145   
   145    146   
   146    147   /* A single node of parsed JSON
   147    148   */
   148    149   struct JsonNode {
   149    150     u8 eType;              /* One of the JSON_ type values */
   150    151     u8 jnFlags;            /* JNODE flags */
   151         -  u8 iVal;               /* Replacement value when JNODE_REPLACE */
   152    152     u32 n;                 /* Bytes of content, or number of sub-nodes */
   153    153     union {
   154    154       const char *zJContent; /* Content for INT, REAL, and STRING */
   155    155       u32 iAppend;           /* More terms for ARRAY and OBJECT */
   156    156       u32 iKey;              /* Key for ARRAY objects in json_tree() */
          157  +    u32 iReplace;          /* Replacement content for JNODE_REPLACE */
          158  +    JsonNode *pPatch;      /* Node chain of patch for JNODE_PATCH */
   157    159     } u;
   158    160   };
   159    161   
   160    162   /* A completely parsed JSON string
   161    163   */
   162    164   struct JsonParse {
   163    165     u32 nNode;         /* Number of slots of aNode[] used */
................................................................................
   406    408   ** the number of JsonNode objects that are encoded.
   407    409   */
   408    410   static void jsonRenderNode(
   409    411     JsonNode *pNode,               /* The node to render */
   410    412     JsonString *pOut,              /* Write JSON here */
   411    413     sqlite3_value **aReplace       /* Replacement values */
   412    414   ){
          415  +  if( pNode->jnFlags & (JNODE_REPLACE|JNODE_PATCH) ){
          416  +    if( pNode->jnFlags & JNODE_REPLACE ){
          417  +      jsonAppendValue(pOut, aReplace[pNode->u.iReplace]);
          418  +      return;
          419  +    }
          420  +    pNode = pNode->u.pPatch;
          421  +  }
   413    422     switch( pNode->eType ){
   414    423       default: {
   415    424         assert( pNode->eType==JSON_NULL );
   416    425         jsonAppendRaw(pOut, "null", 4);
   417    426         break;
   418    427       }
   419    428       case JSON_TRUE: {
................................................................................
   437    446         break;
   438    447       }
   439    448       case JSON_ARRAY: {
   440    449         u32 j = 1;
   441    450         jsonAppendChar(pOut, '[');
   442    451         for(;;){
   443    452           while( j<=pNode->n ){
   444         -          if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){
   445         -            if( pNode[j].jnFlags & JNODE_REPLACE ){
   446         -              jsonAppendSeparator(pOut);
   447         -              jsonAppendValue(pOut, aReplace[pNode[j].iVal]);
   448         -            }
   449         -          }else{
          453  +          if( (pNode[j].jnFlags & JNODE_REMOVE)==0 ){
   450    454               jsonAppendSeparator(pOut);
   451    455               jsonRenderNode(&pNode[j], pOut, aReplace);
   452    456             }
   453    457             j += jsonNodeSize(&pNode[j]);
   454    458           }
   455    459           if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
   456    460           pNode = &pNode[pNode->u.iAppend];
................................................................................
   464    468         jsonAppendChar(pOut, '{');
   465    469         for(;;){
   466    470           while( j<=pNode->n ){
   467    471             if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 ){
   468    472               jsonAppendSeparator(pOut);
   469    473               jsonRenderNode(&pNode[j], pOut, aReplace);
   470    474               jsonAppendChar(pOut, ':');
   471         -            if( pNode[j+1].jnFlags & JNODE_REPLACE ){
   472         -              jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]);
   473         -            }else{
   474         -              jsonRenderNode(&pNode[j+1], pOut, aReplace);
   475         -            }
          475  +            jsonRenderNode(&pNode[j+1], pOut, aReplace);
   476    476             }
   477    477             j += 1 + jsonNodeSize(&pNode[j+1]);
   478    478           }
   479    479           if( (pNode->jnFlags & JNODE_APPEND)==0 ) break;
   480    480           pNode = &pNode[pNode->u.iAppend];
   481    481           j = 1;
   482    482         }
................................................................................
   695    695     JsonNode *p;
   696    696     if( pParse->nNode>=pParse->nAlloc ){
   697    697       return jsonParseAddNodeExpand(pParse, eType, n, zContent);
   698    698     }
   699    699     p = &pParse->aNode[pParse->nNode];
   700    700     p->eType = (u8)eType;
   701    701     p->jnFlags = 0;
   702         -  p->iVal = 0;
   703    702     p->n = n;
   704    703     p->u.zJContent = zContent;
   705    704     return pParse->nNode++;
   706    705   }
   707    706   
   708    707   /*
   709    708   ** Return true if z[] begins with 4 (or more) hexadecimal digits
................................................................................
  1352   1351       jsonAppendChar(&jx, ']');
  1353   1352       jsonResult(&jx);
  1354   1353       sqlite3_result_subtype(ctx, JSON_SUBTYPE);
  1355   1354     }
  1356   1355     jsonReset(&jx);
  1357   1356     jsonParseReset(&x);
  1358   1357   }
         1358  +
         1359  +/* This is the RFC 7396 MergePatch algorithm.
         1360  +*/
         1361  +static JsonNode *jsonMergePatch(
         1362  +  JsonParse *pParse,   /* The JSON parser that contains the TARGET */
         1363  +  int iTarget,         /* Node of the TARGET in pParse */
         1364  +  JsonNode *pPatch     /* The PATCH */
         1365  +){
         1366  +  int i, j;
         1367  +  int iApnd;
         1368  +  JsonNode *pTarget;
         1369  +  if( pPatch->eType!=JSON_OBJECT ){
         1370  +    return pPatch;
         1371  +  }
         1372  +  assert( iTarget>=0 && iTarget<pParse->nNode );
         1373  +  pTarget = &pParse->aNode[iTarget];
         1374  +  assert( (pPatch->jnFlags & JNODE_APPEND)==0 );
         1375  +  if( pTarget->eType!=JSON_OBJECT ){
         1376  +    for(i=2; i<pPatch->n; i += jsonNodeSize(&pPatch[i])+1){
         1377  +      if( pPatch[i].eType==JSON_NULL ){
         1378  +        pPatch[i-1].jnFlags |= JNODE_REMOVE;
         1379  +      }
         1380  +    }
         1381  +    return pPatch;
         1382  +  }
         1383  +  iApnd = iTarget;
         1384  +  for(i=1; i<pPatch->n; i += jsonNodeSize(&pPatch[i+1])+1){
         1385  +    int nKey;
         1386  +    const char *zKey;
         1387  +    assert( pPatch[i].eType==JSON_STRING );
         1388  +    assert( pPatch[i].jnFlags & JNODE_LABEL );
         1389  +    nKey = pPatch[i].n;
         1390  +    zKey = pPatch[i].u.zJContent;
         1391  +    if( (pPatch[i].jnFlags & JNODE_RAW)==0 ){
         1392  +      assert( nKey>=2 && zKey[0]=='"' && zKey[nKey-1]=='"' );
         1393  +      nKey -= 2;
         1394  +      zKey ++;
         1395  +    }
         1396  +    for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){
         1397  +      assert( pTarget[j].eType==JSON_STRING );
         1398  +      assert( pTarget[j].jnFlags & JNODE_LABEL );
         1399  +      if( jsonLabelCompare(&pTarget[j], zKey, nKey)
         1400  +       && (pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH))==0
         1401  +      ){
         1402  +        if( pPatch[i+1].eType==JSON_NULL ){
         1403  +          pTarget[j+1].jnFlags |= JNODE_REMOVE;
         1404  +        }else{
         1405  +          JsonNode *pNew = jsonMergePatch(pParse, j+1, &pPatch[i+1]);
         1406  +          if( pNew==0 ) return 0;
         1407  +          pTarget = &pParse->aNode[iTarget];
         1408  +          if( pNew!=&pTarget[j+1] ){
         1409  +            pTarget[j+1].u.pPatch = pNew;
         1410  +            pTarget[j+1].jnFlags |= JNODE_PATCH;
         1411  +          }
         1412  +        }
         1413  +        break;
         1414  +      }
         1415  +    }
         1416  +    if( j>=pTarget->n ){
         1417  +      int iStart, iPatch;
         1418  +      iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0);
         1419  +      jsonParseAddNode(pParse, JSON_STRING, nKey, zKey);
         1420  +      iPatch = jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
         1421  +      if( pParse->oom ) return 0;
         1422  +      pTarget = &pParse->aNode[iTarget];
         1423  +      pParse->aNode[iApnd].jnFlags |= JNODE_APPEND;
         1424  +      pParse->aNode[iApnd].u.iAppend = iStart;
         1425  +      iApnd = iStart;
         1426  +      pParse->aNode[iPatch].jnFlags |= JNODE_PATCH;
         1427  +      pParse->aNode[iPatch].u.pPatch = &pPatch[i+1];
         1428  +    }
         1429  +  }
         1430  +  return pTarget;
         1431  +}
         1432  +
         1433  +/*
         1434  +** Implementation of the json_mergepatch(JSON1,JSON2) function.  Return a JSON
         1435  +** object that is the result of running the RFC 7396 MergePatch() algorithm
         1436  +** on the two arguments.
         1437  +*/
         1438  +static void jsonMergePatchFunc(
         1439  +  sqlite3_context *ctx,
         1440  +  int argc,
         1441  +  sqlite3_value **argv
         1442  +){
         1443  +  JsonParse x;     /* The JSON that is being patched */
         1444  +  JsonParse y;     /* The patch */
         1445  +
         1446  +  if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return;
         1447  +  if( jsonParse(&y, ctx, (const char*)sqlite3_value_text(argv[1])) ){
         1448  +    jsonParseReset(&x);
         1449  +    return;
         1450  +  }
         1451  +  jsonReturnJson(jsonMergePatch(&x, 0, y.aNode), ctx, 0);
         1452  +  jsonParseReset(&x);
         1453  +  jsonParseReset(&y);
         1454  +}
         1455  +
  1359   1456   
  1360   1457   /*
  1361   1458   ** Implementation of the json_object(NAME,VALUE,...) function.  Return a JSON
  1362   1459   ** object that contains all name/value given in arguments.  Or if any name
  1363   1460   ** is not a string or if any value is a BLOB, throw an error.
  1364   1461   */
  1365   1462   static void jsonObjectFunc(
................................................................................
  1456   1553     assert( x.nNode );
  1457   1554     for(i=1; i<(u32)argc; i+=2){
  1458   1555       zPath = (const char*)sqlite3_value_text(argv[i]);
  1459   1556       pNode = jsonLookup(&x, zPath, 0, ctx);
  1460   1557       if( x.nErr ) goto replace_err;
  1461   1558       if( pNode ){
  1462   1559         pNode->jnFlags |= (u8)JNODE_REPLACE;
  1463         -      pNode->iVal = (u8)(i+1);
         1560  +      pNode->u.iReplace = i + 1;
  1464   1561       }
  1465   1562     }
  1466   1563     if( x.aNode[0].jnFlags & JNODE_REPLACE ){
  1467         -    sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
         1564  +    sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
  1468   1565     }else{
  1469   1566       jsonReturnJson(x.aNode, ctx, argv);
  1470   1567     }
  1471   1568   replace_err:
  1472   1569     jsonParseReset(&x);
  1473   1570   }
  1474   1571   
................................................................................
  1510   1607       if( x.oom ){
  1511   1608         sqlite3_result_error_nomem(ctx);
  1512   1609         goto jsonSetDone;
  1513   1610       }else if( x.nErr ){
  1514   1611         goto jsonSetDone;
  1515   1612       }else if( pNode && (bApnd || bIsSet) ){
  1516   1613         pNode->jnFlags |= (u8)JNODE_REPLACE;
  1517         -      pNode->iVal = (u8)(i+1);
         1614  +      pNode->u.iReplace = i + 1;
  1518   1615       }
  1519   1616     }
  1520   1617     if( x.aNode[0].jnFlags & JNODE_REPLACE ){
  1521         -    sqlite3_result_value(ctx, argv[x.aNode[0].iVal]);
         1618  +    sqlite3_result_value(ctx, argv[x.aNode[0].u.iReplace]);
  1522   1619     }else{
  1523   1620       jsonReturnJson(x.aNode, ctx, argv);
  1524   1621     }
  1525   1622   jsonSetDone:
  1526   1623     jsonParseReset(&x);
  1527   1624   }
  1528   1625   
................................................................................
  2156   2253     } aFunc[] = {
  2157   2254       { "json",                 1, 0,   jsonRemoveFunc        },
  2158   2255       { "json_array",          -1, 0,   jsonArrayFunc         },
  2159   2256       { "json_array_length",    1, 0,   jsonArrayLengthFunc   },
  2160   2257       { "json_array_length",    2, 0,   jsonArrayLengthFunc   },
  2161   2258       { "json_extract",        -1, 0,   jsonExtractFunc       },
  2162   2259       { "json_insert",         -1, 0,   jsonSetFunc           },
         2260  +    { "json_mergepatch",      2, 0,   jsonMergePatchFunc    },
  2163   2261       { "json_object",         -1, 0,   jsonObjectFunc        },
  2164   2262       { "json_quote",           1, 0,   jsonQuoteFunc         },
  2165   2263       { "json_remove",         -1, 0,   jsonRemoveFunc        },
  2166   2264       { "json_replace",        -1, 0,   jsonReplaceFunc       },
  2167   2265       { "json_set",            -1, 1,   jsonSetFunc           },
  2168   2266       { "json_type",            1, 0,   jsonTypeFunc          },
  2169   2267       { "json_type",            2, 0,   jsonTypeFunc          },

Added test/json104.test.

            1  +# 2017-03-22
            2  +#
            3  +# The author disclaims copyright to this source code.  In place of
            4  +# a legal notice, here is a blessing:
            5  +#
            6  +#    May you do good and not evil.
            7  +#    May you find forgiveness for yourself and forgive others.
            8  +#    May you share freely, never taking more than you give.
            9  +#
           10  +#***********************************************************************
           11  +# This file implements tests for json_mergepatch(A,B) SQL function.
           12  +#
           13  +
           14  +set testdir [file dirname $argv0]
           15  +source $testdir/tester.tcl
           16  +
           17  +ifcapable !json1 {
           18  +  finish_test
           19  +  return
           20  +}
           21  +
           22  +# This is the example from pages 2 and 3 of RFC-7396
           23  +do_execsql_test json104-100 {
           24  +  SELECT json_mergepatch(
           25  +    json('{
           26  +       "a": "b",
           27  +       "c": {
           28  +         "d": "e",
           29  +         "f": "g"
           30  +       }
           31  +     }'),
           32  +    json('{
           33  +       "a":"z",
           34  +       "c": {
           35  +         "f": null
           36  +       }
           37  +     }'));
           38  +} {{{"a":"z","c":{"d":"e"}}}}
           39  +
           40  +
           41  +# This is the example from pages 4 and 5 of RFC-7396 
           42  +do_execsql_test json104-110 {
           43  +  SELECT json_mergepatch(
           44  +    json('{
           45  +       "title": "Goodbye!",
           46  +       "author" : {
           47  +         "givenName" : "John",
           48  +         "familyName" : "Doe"
           49  +       },
           50  +       "tags":[ "example", "sample" ],
           51  +       "content": "This will be unchanged"
           52  +     }'),
           53  +    json('{
           54  +       "title": "Hello!",
           55  +       "phoneNumber": "+01-123-456-7890",
           56  +       "author": {
           57  +         "familyName": null
           58  +       },
           59  +       "tags": [ "example" ]
           60  +     }'));
           61  +} {{{"title":"Hello!","author":{"givenName":"John"},"tags":["example"],"content":"This will be unchanged",phoneNumber:"+01-123-456-7890"}}}
           62  +
           63  +finish_test