From 301eeccaa700ea954abf0c63c2f8526f93d6578e Mon Sep 17 00:00:00 2001 From: drh Date: Mon, 17 Aug 2015 20:14:19 +0000 Subject: [PATCH] Add an initial implementation for json_remove(). FossilOrigin-Name: 2a8267209dbda36a37c1b5f65000b2f763c62341 --- ext/misc/json.c | 148 ++++++++++++++++++++++++++++++++---------------- manifest | 12 ++-- manifest.uuid | 2 +- 3 files changed, 107 insertions(+), 55 deletions(-) diff --git a/ext/misc/json.c b/ext/misc/json.c index 1b2b6ff5c2..dc27349c0f 100644 --- a/ext/misc/json.c +++ b/ext/misc/json.c @@ -64,14 +64,19 @@ static const char * const jsonType[] = { "null", "true", "false", "integer", "real", "text", "array", "object" }; +/* Bit values for the JsonNode.jnFlag field +*/ +#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 */ + /* A single node of parsed JSON */ typedef struct JsonNode JsonNode; struct JsonNode { u8 eType; /* One of the JSON_ type values */ - u8 bRaw; /* Content is raw, rather than JSON encoded */ - u8 bBackslash; /* Formatted JSON_STRING contains \ escapes */ + u8 jnFlags; /* JNODE flags */ u32 n; /* Bytes of content, or number of sub-nodes */ const char *zJContent; /* JSON content */ }; @@ -87,6 +92,15 @@ struct JsonParse { u8 oom; /* Set to true if out of memory */ }; +/* +** Return the number of consecutive JsonNode slots need to represent +** 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){ + return pNode->eType>=JSON_ARRAY ? pNode->n+1 : 1; +} + /* Set the Json object to an empty string */ static void jsonZero(Json *p){ @@ -126,7 +140,7 @@ static void jsonOom(Json *p){ ** Return zero on success. Return non-zero on an OOM error */ static int jsonGrow(Json *p, u32 N){ - u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+100; + u64 nTotal = NnAlloc ? p->nAlloc*2 : p->nAlloc+N+10; char *zNew; if( p->bStatic ){ if( p->oom ) return SQLITE_NOMEM; @@ -171,6 +185,16 @@ static void jsonAppendChar(Json *p, char c){ p->zBuf[p->nUsed++] = c; } +/* Append a comma separator to the output buffer, if the previous +** character is not '[' or '{'. +*/ +static void jsonAppendSeparator(Json *p){ + char c; + if( p->nUsed==0 ) return; + c = p->zBuf[p->nUsed-1]; + if( c!='[' && c!='{' ) jsonAppendChar(p, ','); +} + /* Append the N-byte string in zIn to the end of the Json string ** under construction. Enclose the string in "..." and escape ** any double-quotes or backslash characters contained within the @@ -209,7 +233,7 @@ static void jsonResult(Json *p){ ** the number of JsonNode objects that are encoded. */ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ - u32 j = 0; + u32 j = 1; switch( pNode->eType ){ case JSON_NULL: { jsonAppendRaw(pOut, "null", 4); @@ -224,7 +248,7 @@ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ break; } case JSON_STRING: { - if( pNode->bRaw ){ + if( pNode->jnFlags & JNODE_RAW ){ jsonAppendString(pOut, pNode->zJContent, pNode->n); break; } @@ -237,28 +261,34 @@ static int jsonRenderNode(JsonNode *pNode, Json *pOut){ } case JSON_ARRAY: { jsonAppendChar(pOut, '['); - j = 0; - while( jn ){ - if( j>0 ) jsonAppendChar(pOut, ','); - j += jsonRenderNode(&pNode[j+1], pOut); + while( j<=pNode->n ){ + if( pNode[j].jnFlags & JNODE_REMOVE ){ + j += jsonSizeof(&pNode[j]); + }else{ + jsonAppendSeparator(pOut); + j += jsonRenderNode(&pNode[j], pOut); + } } jsonAppendChar(pOut, ']'); break; } case JSON_OBJECT: { jsonAppendChar(pOut, '{'); - j = 0; - while( jn ){ - if( j>0 ) jsonAppendChar(pOut, ','); - j += jsonRenderNode(&pNode[j+1], pOut); - jsonAppendChar(pOut, ':'); - j += jsonRenderNode(&pNode[j+1], pOut); + while( j<=pNode->n ){ + if( pNode[j+1].jnFlags & JNODE_REMOVE ){ + j += 1 + jsonSizeof(&pNode[j+1]); + }else{ + jsonAppendSeparator(pOut); + jsonRenderNode(&pNode[j], pOut); + jsonAppendChar(pOut, ':'); + j += 1 + jsonRenderNode(&pNode[j+1], pOut); + } } jsonAppendChar(pOut, '}'); break; } } - return j+1; + return j; } /* @@ -293,9 +323,9 @@ static void jsonReturn(JsonNode *pNode, sqlite3_context *pCtx){ break; } case JSON_STRING: { - if( pNode->bRaw ){ + if( pNode->jnFlags & JNODE_RAW ){ sqlite3_result_text(pCtx, pNode->zJContent, pNode->n, SQLITE_TRANSIENT); - }else if( !pNode->bBackslash ){ + }else if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){ /* JSON formatted without any backslash-escapes */ sqlite3_result_text(pCtx, pNode->zJContent+1, pNode->n-2, SQLITE_TRANSIENT); @@ -406,8 +436,7 @@ static int jsonParseAddNode( } p = &pParse->aNode[pParse->nNode]; p->eType = (u8)eType; - p->bRaw = 0; - p->bBackslash = 0; + p->jnFlags = 0; p->n = n; p->zJContent = zContent; return pParse->nNode++; @@ -477,7 +506,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ return j+1; }else if( c=='"' ){ /* Parse string */ - u8 bBackslash = 0; + u8 jnFlags = 0; j = i+1; for(;;){ c = pParse->zJson[j]; @@ -485,14 +514,14 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ if( c=='\\' ){ c = pParse->zJson[++j]; if( c==0 ) return -1; - bBackslash = 1; + jnFlags = JNODE_ESCAPE; }else if( c=='"' ){ break; } j++; } jsonParseAddNode(pParse, JSON_STRING, j+1-i, &pParse->zJson[i]); - if( bBackslash ) pParse->aNode[pParse->nNode-1].bBackslash = 1; + pParse->aNode[pParse->nNode-1].jnFlags = jnFlags; return j+1; }else if( c=='n' && strncmp(pParse->zJson+i,"null",4)==0 @@ -572,6 +601,7 @@ static int jsonParse(JsonParse *pParse, const char *zJson){ } return 0; } + /* ** Search along zPath to find the node specified. Return a pointer ** to that node, or NULL if zPath is malformed or if there is no such @@ -593,10 +623,7 @@ static JsonNode *jsonLookup(JsonNode *pRoot, const char *zPath){ return jsonLookup(&pRoot[j+1], &zPath[i]); } j++; - if( pRoot[j].eType==JSON_ARRAY || pRoot[j].eType==JSON_OBJECT ){ - j += pRoot[j].n; - } - j++; + j += jsonSizeof(&pRoot[j]); } }else if( zPath[0]=='[' && isdigit(zPath[1]) ){ if( pRoot->eType!=JSON_ARRAY ) return 0; @@ -610,10 +637,7 @@ static JsonNode *jsonLookup(JsonNode *pRoot, const char *zPath){ zPath++; j = 1; while( i>0 && j<=pRoot->n ){ - if( pRoot[j].eType==JSON_ARRAY || pRoot[j].eType==JSON_OBJECT ){ - j += pRoot[j].n; - } - j++; + j += jsonSizeof(&pRoot[j]); i--; } if( j<=pRoot->n ){ @@ -627,6 +651,7 @@ static JsonNode *jsonLookup(JsonNode *pRoot, const char *zPath){ ** SQL functions used for testing and debugging ****************************************************************************/ +#ifdef SQLITE_DEBUG /* ** The json_parse(JSON) function returns a string which describes ** a parse of the JSON provided. Or it returns NULL if JSON is not @@ -640,26 +665,17 @@ static void jsonParseFunc( Json s; /* Output string - not real JSON */ JsonParse x; /* The parse */ u32 i; - char zBuf[50]; + char zBuf[100]; assert( argc==1 ); if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; jsonInit(&s, context); for(i=0; i=JSON_INT ){ - sqlite3_snprintf(sizeof(zBuf), zBuf, " n: %u\n", x.aNode[i].n); - jsonAppend(&s, zBuf); - } if( x.aNode[i].zJContent!=0 ){ - sqlite3_snprintf(sizeof(zBuf), zBuf, " ofst: %u\n", - (u32)(x.aNode[i].zJContent - x.zJson)); - jsonAppend(&s, zBuf); - jsonAppendRaw(&s, " text: ", 8); + jsonAppendRaw(&s, " text: ", 10); jsonAppendRaw(&s, x.aNode[i].zJContent, x.aNode[i].n); jsonAppendRaw(&s, "\n", 1); } @@ -696,6 +712,7 @@ static void jsonNodeCountFunc( sqlite3_result_int64(context, x.nNode); sqlite3_free(x.aNode); } +#endif /* SQLITE_DEBUG */ /**************************************************************************** ** SQL function implementations @@ -779,10 +796,8 @@ static void jsonArrayLengthFunc( JsonNode *pNode = x.aNode; if( zPath ) pNode = jsonLookup(pNode, zPath); if( pNode->eType==JSON_ARRAY ){ - for(i=1; i<=pNode->n; i++, n++){ - if( pNode[i].eType==JSON_ARRAY || pNode[i].eType==JSON_OBJECT ){ - i += pNode[i].n; - } + for(i=1; i<=pNode->n; n++){ + i += jsonSizeof(&pNode[i]); } } } @@ -882,6 +897,40 @@ static void jsonObjectFunc( } +/* +** json_remove(JSON, PATH, ...) +** +** Remove the named elements from JSON and return the result. Ill-formed +** PATH arguments are silently ignored. If JSON is ill-formed, then NULL +** is returned. +*/ +static void jsonRemoveFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + JsonParse x; /* The parse */ + JsonNode *pNode; + const char *zPath; + u32 i; + + if( argc<1 ) return; + if( jsonParse(&x, (const char*)sqlite3_value_text(argv[0])) ) return; + if( x.nNode ){ + for(i=1; ijnFlags |= JNODE_REMOVE; + } + if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ + jsonReturn(x.aNode, context); + } + } + sqlite3_free(x.aNode); +} + /* ** json_type(JSON) ** json_type(JSON, PATH) @@ -934,13 +983,16 @@ int sqlite3_json_init( { "json_array_length", 2, jsonArrayLengthFunc }, { "json_extract", 2, jsonExtractFunc }, { "json_object", -1, jsonObjectFunc }, + { "json_remove", -1, jsonRemoveFunc }, { "json_type", 1, jsonTypeFunc }, { "json_type", 2, jsonTypeFunc }, +#if SQLITE_DEBUG /* DEBUG and TESTING functions */ { "json_parse", 1, jsonParseFunc }, { "json_test1", 1, jsonTest1Func }, { "json_nodecount", 1, jsonNodeCountFunc }, +#endif }; SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* Unused parameter */ diff --git a/manifest b/manifest index 67b31769fb..4fc2dcacd6 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Initial\simplementation\sfor\sjson_array_length(),\sjson_extract(),\sand\njson_type(). -D 2015-08-17T15:17:37.780 +C Add\san\sinitial\simplementation\sfor\sjson_remove(). +D 2015-08-17T20:14:19.276 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 f26cbaa8ba1e396b3bf1e29aa116abed2a27ef95 +F ext/misc/json.c 30fd85ea1fba24031952aa0c635156cfd8ca02ea 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 9703c0aa18ae43375af876474b818e504e1c10a5 -R cfeb418e7f67bb21a131fd01c852e6d6 +P 39983204515837e7bd574cf47918e493acc03d1f +R 9ed2e57314d456e476aa0e6edd9d8c55 U drh -Z 903f29a5c61013c958dfe3ed9e5ebef0 +Z 25ffed392c0fb3616f3b027e2b1bb1db diff --git a/manifest.uuid b/manifest.uuid index be625468c7..3cd093bc94 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -39983204515837e7bd574cf47918e493acc03d1f \ No newline at end of file +2a8267209dbda36a37c1b5f65000b2f763c62341 \ No newline at end of file