From d096059fca8f84284b8fa66c2e0ad6fa32197750 Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 17 Aug 2015 21:22:32 +0000 Subject: [PATCH] Initial implementation of json_replace(). FossilOrigin-Name: 3c4bee65d93efc7f03f0f11817a068b02068d37c --- ext/misc/json.c | 218 ++++++++++++++++++++++++++++++------------------ manifest | 12 +-- manifest.uuid | 2 +- 3 files changed, 145 insertions(+), 87 deletions(-) diff --git a/ext/misc/json.c b/ext/misc/json.c index dc27349c0f..d865fb6152 100644 --- a/ext/misc/json.c +++ b/ext/misc/json.c @@ -42,7 +42,7 @@ struct Json { u64 nAlloc; /* Bytes of storage available in zBuf[] */ u64 nUsed; /* Bytes of zBuf[] currently used */ u8 bStatic; /* True if zBuf is static space */ - u8 oom; /* True if an OOM has been encountered */ + u8 bErr; /* True if an error has been encountered */ char zSpace[100]; /* Initial static space */ }; @@ -69,6 +69,7 @@ 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 */ /* A single node of parsed JSON @@ -77,6 +78,7 @@ typedef struct JsonNode JsonNode; 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 */ const char *zJContent; /* JSON content */ }; @@ -97,7 +99,7 @@ struct JsonParse { ** the parsed JSON at pNode. The minimum answer is 1. For ARRAY and ** OBJECT types, the number might be larger. */ -static u32 jsonSizeof(JsonNode *pNode){ +static u32 jsonSize(JsonNode *pNode){ return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1; } @@ -114,7 +116,7 @@ static void jsonZero(Json *p){ */ static void jsonInit(Json *p, sqlite3_context *pCtx){ p->pCtx = pCtx; - p->oom = 0; + p->bErr = 0; jsonZero(p); } @@ -131,9 +133,11 @@ static void jsonReset(Json *p){ /* Report an out-of-memory (OOM) condition */ static void jsonOom(Json *p){ - p->oom = 1; - sqlite3_result_error_nomem(p->pCtx); - jsonReset(p); + if( !p->bErr ){ + p->bErr = 1; + sqlite3_result_error_nomem(p->pCtx); + jsonReset(p); + } } /* Enlarge pJson->zBuf so that it can hold at least N more bytes. @@ -143,7 +147,7 @@ static int jsonGrow(Json *p, u32 N){ u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+10; char *zNew; if( p->bStatic ){ - if( p->oom ) return SQLITE_NOMEM; + if( p->bErr ) return 1; zNew = sqlite3_malloc64(nTotal); if( zNew==0 ){ jsonOom(p); @@ -172,11 +176,13 @@ static void jsonAppendRaw(Json *p, const char *zIn, u32 N){ p->nUsed += N; } +#ifdef SQLITE_DEBUG /* Append the zero-terminated string zIn */ static void jsonAppend(Json *p, const char *zIn){ jsonAppendRaw(p, zIn, (u32)strlen(zIn)); } +#endif /* Append a single character */ @@ -215,10 +221,48 @@ static void jsonAppendString(Json *p, const char *zIn, u32 N){ p->zBuf[p->nUsed++] = '"'; } +/* +** Append a function parameter value to the JSON string under +** construction. +*/ +static void jsonAppendValue( + Json *p, /* Append to this JSON string */ + sqlite3_value *pValue /* Value to append */ +){ + switch( sqlite3_value_type(pValue) ){ + case SQLITE_NULL: { + jsonAppendRaw(p, "null", 4); + break; + } + case SQLITE_INTEGER: + case SQLITE_FLOAT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + jsonAppendRaw(p, z, n); + break; + } + case SQLITE_TEXT: { + const char *z = (const char*)sqlite3_value_text(pValue); + u32 n = (u32)sqlite3_value_bytes(pValue); + jsonAppendString(p, z, n); + break; + } + default: { + if( p->bErr==0 ){ + sqlite3_result_error(p->pCtx, "JSON cannot hold BLOB values", -1); + p->bErr = 1; + jsonReset(p); + } + break; + } + } +} + + /* Make the JSON in p the result of the SQL function. */ static void jsonResult(Json *p){ - if( p->oom==0 ){ + if( p->bErr==0 ){ sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, p->bStatic ? SQLITE_TRANSIENT : sqlite3_free, SQLITE_UTF8); @@ -232,7 +276,11 @@ static void jsonResult(Json *p){ ** append to pOut. Subsubstructure is also included. Return ** the number of JsonNode objects that are encoded. */ -static int jsonRenderNode(JsonNode *pNode, Json *pOut){ +static int jsonRenderNode( + JsonNode *pNode, /* The node to render */ + Json *pOut, /* Write JSON here */ + sqlite3_value **aReplace /* Replacement values */ +){ u32 j = 1; switch( pNode->eType ){ case JSON_NULL: { @@ -262,11 +310,15 @@ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ case JSON_ARRAY: { jsonAppendChar(pOut, '['); while( j<=pNode->n ){ - if( pNode[j].jnFlags & JNODE_REMOVE ){ - j += jsonSizeof(&pNode[j]); + if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){ + if( pNode[j].jnFlags & JNODE_REPLACE ){ + jsonAppendSeparator(pOut); + jsonAppendValue(pOut, aReplace[pNode[j].iVal]); + } + j += jsonSize(&pNode[j]); }else{ jsonAppendSeparator(pOut); - j += jsonRenderNode(&pNode[j], pOut); + j += jsonRenderNode(&pNode[j], pOut, aReplace); } } jsonAppendChar(pOut, ']'); @@ -276,12 +328,17 @@ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ jsonAppendChar(pOut, '{'); while( j<=pNode->n ){ if( pNode[j+1].jnFlags & JNODE_REMOVE ){ - j += 1 + jsonSizeof(&pNode[j+1]); + j += 1 + jsonSize(&pNode[j+1]); }else{ jsonAppendSeparator(pOut); - jsonRenderNode(&pNode[j], pOut); + jsonRenderNode(&pNode[j], pOut, aReplace); jsonAppendChar(pOut, ':'); - j += 1 + jsonRenderNode(&pNode[j+1], pOut); + if( pNode[j+1].jnFlags & JNODE_REPLACE ){ + jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]); + j += 1 + jsonSize(&pNode[j+1]); + }else{ + j += 1 + jsonRenderNode(&pNode[j+1], pOut, aReplace); + } } } jsonAppendChar(pOut, '}'); @@ -294,7 +351,11 @@ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ /* ** Make the JsonNode the return value of the function. */ -static void jsonReturn(JsonNode *pNode, sqlite3_context *pCtx){ +static void jsonReturn( + JsonNode *pNode, /* Node to return */ + sqlite3_context *pCtx, /* Return value for this function */ + sqlite3_value **aReplace /* Array of replacement values */ +){ switch( pNode->eType ){ case JSON_NULL: { sqlite3_result_null(pCtx); @@ -398,7 +459,7 @@ static void jsonReturn(JsonNode *pNode, sqlite3_context *pCtx){ case JSON_OBJECT: { Json s; jsonInit(&s, pCtx); - jsonRenderNode(pNode, &s); + jsonRenderNode(pNode, &s, aReplace); jsonResult(&s); break; } @@ -437,6 +498,7 @@ static int jsonParseAddNode( p = &pParse->aNode[pParse->nNode]; p->eType = (u8)eType; p->jnFlags = 0; + p->iVal = 0; p->n = n; p->zJContent = zContent; return pParse->nNode++; @@ -623,7 +685,7 @@ static JsonNode *jsonLookup(JsonNode *pRoot, const char *zPath){ return jsonLookup(&pRoot[j+1], &zPath[i]); } j++; - j += jsonSizeof(&pRoot[j]); + j += jsonSize(&pRoot[j]); } }else if( zPath[0]=='[' && isdigit(zPath[1]) ){ if( pRoot->eType!=JSON_ARRAY ) return 0; @@ -637,7 +699,7 @@ static JsonNode *jsonLookup(JsonNode *pRoot, const char *zPath){ zPath++; j = 1; while( i>0 && j<=pRoot->n ){ - j += jsonSizeof(&pRoot[j]); + j += jsonSize(&pRoot[j]); i--; } if( j<=pRoot->n ){ @@ -694,7 +756,7 @@ static void jsonTest1Func( ){ JsonParse x; /* The parse */ if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; - jsonReturn(x.aNode, context); + jsonReturn(x.aNode, context, 0); sqlite3_free(x.aNode); } @@ -730,38 +792,14 @@ static void jsonArrayFunc( ){ int i; Json jx; - char cSep = '['; jsonInit(&jx, context); + jsonAppendChar(&jx, '['); for(i=0; ieType==JSON_ARRAY ){ for(i=1; i<=pNode->n; n++){ - i += jsonSizeof(&pNode[i]); + i += jsonSize(&pNode[i]); } } } @@ -828,7 +866,7 @@ static void jsonExtractFunc( if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; pNode = jsonLookup(x.aNode, zPath); if( pNode ){ - jsonReturn(pNode, context); + jsonReturn(pNode, context, 0); } sqlite3_free(x.aNode); } @@ -845,7 +883,6 @@ static void jsonObjectFunc( ){ int i; Json jx; - char cSep = '{'; const char *z; u32 n; @@ -855,44 +892,21 @@ static void jsonObjectFunc( return; } jsonInit(&jx, context); + jsonAppendChar(&jx, '{'); for(i=0; ijnFlags |= JNODE_REMOVE; } if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturn(x.aNode, context); + jsonReturn(x.aNode, context, 0); + } + } + sqlite3_free(x.aNode); +} + +/* +** json_replace(JSON, PATH, VALUE, ...) +** +** Replace the value at PATH with VALUE. If PATH does not already exist, +** this routine is a no-op. If JSON is ill-formed, return NULL. +*/ +static void jsonReplaceFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + JsonParse x; /* The parse */ + JsonNode *pNode; + const char *zPath; + u32 i; + + if( argc<1 ) return; + if( (argc&1)==0 ) { + sqlite3_result_error(context, + "json_replace() needs an odd number of arguments", -1); + return; + } + if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; + if( x.nNode ){ + for(i=1; ijnFlags |= JNODE_REPLACE; + pNode->iVal = i+1; + } + } + if( x.aNode[0].jnFlags & JNODE_REPLACE ){ + sqlite3_result_value(context, argv[x.aNode[0].iVal]); + }else{ + jsonReturn(x.aNode, context, argv); } } sqlite3_free(x.aNode); @@ -984,6 +1041,7 @@ int sqlite3_json_init( { "json_extract", 2, jsonExtractFunc }, { "json_object", -1, jsonObjectFunc }, { "json_remove", -1, jsonRemoveFunc }, + { "json_replace", -1, jsonReplaceFunc }, { "json_type", 1, jsonTypeFunc }, { "json_type", 2, jsonTypeFunc }, diff --git a/manifest b/manifest index 4fc2dcacd6..7d15376185 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Add\san\sinitial\simplementation\sfor\sjson_remove(). -D 2015-08-17T20:14:19.276 +C Initial\simplementation\sof\sjson_replace(). +D 2015-08-17T21:22:32.495 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 7669f34c487f5b328de6b508f374ee1e56558bb0 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -192,7 +192,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f F ext/misc/fuzzer.c 4c84635c71c26cfa7c2e5848cf49fe2d2cfcd767 F ext/misc/ieee754.c b0362167289170627659e84173f5d2e8fee8566e -F ext/misc/json.c 30fd85ea1fba24031952aa0c635156cfd8ca02ea +F ext/misc/json.c d96116de8aafdb117b99712b2a83144d86755350 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc @@ -1374,7 +1374,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 39983204515837e7bd574cf47918e493acc03d1f -R 9ed2e57314d456e476aa0e6edd9d8c55 +P 2a8267209dbda36a37c1b5f65000b2f763c62341 +R f98c8d887493a196dac50851b7b4599f U drh -Z 25ffed392c0fb3616f3b027e2b1bb1db +Z 432e296535146c125ab741ef125c263a diff --git a/manifest.uuid b/manifest.uuid index 3cd093bc94..e0120e1b2e 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -2a8267209dbda36a37c1b5f65000b2f763c62341 \ No newline at end of file +3c4bee65d93efc7f03f0f11817a068b02068d37c \ No newline at end of file -- 2.47.2