From ef97f8360a7063ea2191824cccd0414d86b607a1 Mon Sep 17 00:00:00 2001 From: drh <> Date: Tue, 28 Nov 2023 18:16:02 +0000 Subject: [PATCH] The json_remove() function now uses only JSONB, never JsonNodes, internally. FossilOrigin-Name: b69786e746ae2b927b64d9871fd120b7f8f06cc53739fd46a4da51aa16cf8576 --- manifest | 15 +- manifest.uuid | 2 +- src/json.c | 354 ++++++++++++++++++++++++---------------------- test/json501.test | 6 +- 4 files changed, 193 insertions(+), 184 deletions(-) diff --git a/manifest b/manifest index af38aa5b5a..510a2234e3 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Activate\sthe\sability\sof\sjson_patch()\sto\swork\son\sJSONB. -D 2023-11-28T13:38:22.987 +C The\sjson_remove()\sfunction\snow\suses\sonly\sJSONB,\snever\sJsonNodes,\sinternally. +D 2023-11-28T18:16:02.028 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -688,7 +688,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276 -F src/json.c fa3a1b6f2432848dc03448d359a07040f7e4a44d868e9e16512c6066b65aeef9 +F src/json.c 442ff1ce9ebb82728aca5ddfcab21af46c3230e9f672307b91489f43cd6d6fdc F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 1b89f3de98d1b59fec5bac1d66d6ece21f703821b8eaa0d53d9604c35309f6f9 @@ -1330,7 +1330,7 @@ F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2 F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d -F test/json501.test c419deb835b70c1a2c8532936927bcc1146730328edd2052276715bfd209724d +F test/json501.test ab168a12eb6eb14d479f8c1cdae3ac062fd5a4679f17f976e96f1af518408330 F test/json502.test 98c38e3c4573841028a1381dfb81d4c3f9b105d39668167da10d055e503f6d0b F test/jsonb01.test cace70765b36a36aec9a85a41ea65667d3bbf647d4400ddc3ac76f8fe7d94f90 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff @@ -2145,9 +2145,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fbca9570fd2e1465739e4d3a8d9bb40fad594fd78ab49b2cb34efa27ebdd8361 2c436806b8d5f57de99c00f6154b038454fb9ae427d00d7b4a46ab9c7c69bcb9 -R e5b38e7d70999e4556fdfbbe91a82c08 -T +closed 2c436806b8d5f57de99c00f6154b038454fb9ae427d00d7b4a46ab9c7c69bcb9 +P 11aba347ff7c639500eec904e212eabe889b077351b946cfeac2b74b9703672a +R 82f8a4e62603e32381a0117063382c7a U drh -Z 1523e6413d446a9df2c7c937b4cb4eb3 +Z 37d5559550d9d10448cc0117446ead8c # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index b1acbb8add..a64ec6f678 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -11aba347ff7c639500eec904e212eabe889b077351b946cfeac2b74b9703672a \ No newline at end of file +b69786e746ae2b927b64d9871fd120b7f8f06cc53739fd46a4da51aa16cf8576 \ No newline at end of file diff --git a/src/json.c b/src/json.c index bdc9087323..4baffa7fa3 100644 --- a/src/json.c +++ b/src/json.c @@ -885,11 +885,13 @@ static void jsonParseReset(JsonParse *pParse){ ** that this recursive destructor sequence terminates harmlessly. */ static void jsonParseFree(JsonParse *pParse){ - if( pParse->nJPRef>1 ){ - pParse->nJPRef--; - }else{ - jsonParseReset(pParse); - sqlite3_free(pParse); + if( pParse ){ + if( pParse->nJPRef>1 ){ + pParse->nJPRef--; + }else{ + jsonParseReset(pParse); + sqlite3_free(pParse); + } } } @@ -2636,7 +2638,7 @@ static void jsonBlobAppendNodeType( /* Change the payload size for the node at index i to be szPayload. */ -static void jsonBlobChangePayloadSize( +static int jsonBlobChangePayloadSize( JsonParse *pParse, u32 i, u32 szPayload @@ -2645,8 +2647,8 @@ static void jsonBlobChangePayloadSize( u8 szType; u8 nExtra; u8 nNeeded; - i8 delta; - if( pParse->oom ) return; + int delta; + if( pParse->oom ) return 0; a = &pParse->aBlob[i]; szType = a[0]>>4; if( szType<=11 ){ @@ -2672,7 +2674,7 @@ static void jsonBlobChangePayloadSize( u32 newSize = pParse->nBlob + delta; if( delta>0 ){ if( newSize>pParse->nBlobAlloc && jsonBlobExpand(pParse, newSize) ){ - return; /* OOM error. Error state recorded in pParse->oom. */ + return 0; /* OOM error. Error state recorded in pParse->oom. */ } a = &pParse->aBlob[i]; memmove(&a[1+delta], &a[1], pParse->nBlob - (i+1)); @@ -2697,6 +2699,7 @@ static void jsonBlobChangePayloadSize( a[3] = (szPayload >> 8) & 0xff; a[4] = szPayload & 0xff; } + return delta; } /* @@ -3376,13 +3379,13 @@ static u32 jsonXlateBlobToText( } break; } - case JSONB_TEXT: case JSONB_TEXTJ: { jsonAppendChar(pOut, '"'); jsonAppendRaw(pOut, (const char*)&pParse->aBlob[i+n], sz); jsonAppendChar(pOut, '"'); break; } + case JSONB_TEXT: case JSONB_TEXT5: { const char *zIn; u32 k; @@ -3390,7 +3393,7 @@ static u32 jsonXlateBlobToText( zIn = (const char*)&pParse->aBlob[i+n]; jsonAppendChar(pOut, '"'); while( sz2>0 ){ - for(k=0; k0 ){ jsonAppendRawNZ(pOut, zIn, k); if( k>=sz2 ){ @@ -3399,6 +3402,12 @@ static u32 jsonXlateBlobToText( zIn += k; sz2 -= k; } + if( zIn[0]=='"' ){ + jsonAppendRawNZ(pOut, "\\\"", 2); + zIn++; + sz2--; + continue; + } if( sz2<2 ){ if( sz2>0 ) pOut->eErr |= JSTRING_MALFORMED; if( sz2==0 ) break; @@ -3514,9 +3523,9 @@ static int jsonFuncArgMightBeBinary(sqlite3_value *pJson){ int nBlob; JsonParse s; if( sqlite3_value_type(pJson)!=SQLITE_BLOB ) return 0; + aBlob = sqlite3_value_blob(pJson); nBlob = sqlite3_value_bytes(pJson); if( nBlob<1 ) return 0; - aBlob = sqlite3_value_blob(pJson); if( aBlob==0 || (aBlob[0] & 0x0f)>JSONB_OBJECT ) return 0; memset(&s, 0, sizeof(s)); s.aBlob = (u8*)aBlob; @@ -3797,7 +3806,7 @@ static void jsonAfterEditSizeAdjust(JsonParse *pParse, u32 iRoot){ (void)jsonbPayloadSize(pParse, iRoot, &sz); pParse->nBlob = nBlob; sz += pParse->delta; - jsonBlobChangePayloadSize(pParse, iRoot, sz); + pParse->delta += jsonBlobChangePayloadSize(pParse, iRoot, sz); } /* @@ -4268,58 +4277,6 @@ static void jsonExtractFromBlob( sqlite3_free(zMsg); } } - -/* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent -** arguments are JSON paths of elements to be removed. Do that removal -** and return the result. -*/ -static void jsonRemoveFromBlob( - sqlite3_context *ctx, - int argc, - sqlite3_value **argv -){ - int i; - u32 rc; - const char *zPath = 0; - int flgs; - JsonParse px; - - memset(&px, 0, sizeof(px)); - px.nBlob = sqlite3_value_bytes(argv[0]); - px.aBlob = (u8*)sqlite3_value_blob(argv[0]); - if( px.aBlob==0 ) return; - for(i=1; i0 ? SQLITE_DYNAMIC : SQLITE_TRANSIENT); - }else{ - JsonString s; - jsonStringInit(&s, ctx); - jsonXlateBlobToText(&px, 0, &s); - jsonReturnString(&s); - jsonParseReset(&px); - } - return; - -jsonRemoveFromBlob_patherror: - jsonParseReset(&px); - jsonPathSyntaxError(zPath, ctx); - return; -} /* ** pArg is a function argument that might be an SQL value or a JSON @@ -4463,48 +4420,119 @@ jsonInsertIntoBlob_patherror: return; } +/* +** Allowed values for the flgs argument to jsonParseFuncArg(); +*/ +#define JSON_EDITABLE 0x01 /* Generate a writable JsonParse object */ + +/* +** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob, +** from the SQL function argument pArg. Return a pointer to the new +** JsonParse object. +** +** Ownership of the new JsonParse object is passed to the caller. The +** caller should invoke jsonParseFree() on the return value when it +** has finished using it. +** +** If any errors are detected, an appropriate error messages is set +** using sqlite3_result_error() or the equivalent and this routine +** returns NULL. This routine also returns NULL if the pArg argument +** is an SQL NULL value, but no error message is set in that case. This +** is so that SQL functions that are given NULL arguments will return +** a NULL value. +*/ +static JsonParse *jsonParseFuncArg( + sqlite3_context *ctx, + sqlite3_value *pArg, + u32 flgs +){ + int eType; /* Datatype of pArg */ + JsonParse *p = 0; /* Value to be returned */ + + assert( ctx!=0 ); + eType = sqlite3_value_type(pArg); + if( eType==SQLITE_NULL ){ + return 0; + } + p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) goto json_pfa_oom; + memset(p, 0, sizeof(*p)); + if( eType==SQLITE_BLOB ){ + u32 n, sz = 0; + p->aBlob = (u8*)sqlite3_value_blob(pArg); + p->nBlob = (u32)sqlite3_value_bytes(pArg); + if( p->nBlob==0 ){ + goto json_pfa_malformed; + } + if( p->aBlob==0 ){ + goto json_pfa_oom; + } + if( (p->aBlob[0] & 0x0f)>JSONB_OBJECT ){ + goto json_pfa_malformed; + } + n = jsonbPayloadSize(p, 0, &sz); + if( n==0 + || sz+n!=p->nBlob + || ((p->aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0) + || sz+n!=p->nBlob + ){ + goto json_pfa_malformed; + } + if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){ + goto json_pfa_oom; + } + return p; + } + /* TODO: Check in the cache */ + p->zJson = (char*)sqlite3_value_text(pArg); + p->nJson = sqlite3_value_bytes(pArg); + if( p->nJson==0 ) goto json_pfa_malformed; + if( p->zJson==0 ) goto json_pfa_oom; + if( jsonConvertTextToBlob(p, ctx) ){ + jsonParseFree(p); + return 0; + } + return p; + +json_pfa_malformed: + jsonParseFree(p); + sqlite3_result_error(ctx, "malformed JSON", -1); + return 0; + +json_pfa_oom: + jsonParseFree(p); + sqlite3_result_error_nomem(ctx); + return 0; +} + +/* +** Make the return value of a JSON function either the raw JSONB blob +** or make it JSON text, depending on whether the JSON_BLOB flag is +** set on the function. +*/ +static void jsonReturnParse( + sqlite3_context *ctx, + JsonParse *p +){ + int flgs; + flgs = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); + if( flgs & JSON_BLOB ){ + sqlite3_result_blob(ctx, p->aBlob, p->nBlob, + p->nBlobAlloc>0 ? SQLITE_DYNAMIC : SQLITE_TRANSIENT); + p->nBlobAlloc = 0; + }else{ + JsonString s; + jsonStringInit(&s, ctx); + jsonXlateBlobToText(p, 0, &s); + jsonReturnString(&s); + sqlite3_result_subtype(ctx, JSON_SUBTYPE); + } +} + /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ -#if SQLITE_DEBUG -/* -** Print N node entries. -*/ -static void jsonDebugPrintNodeEntries( - JsonNode *aNode, /* First node entry to print */ - int N /* Number of node entries to print */ -){ - int i; - for(i=0; inBlob); + } + jsonDebugPrintBlob(pParse, 0, pParse->nBlob, 0); +} #endif /* SQLITE_DEBUG */ - -#if 0 /* 1 for debugging. 0 normally. Requires -DSQLITE_DEBUG too */ -static void jsonDebugPrintParse(JsonParse *p){ - jsonDebugPrintNodeEntries(p->aNode, p->nNode); -} -static void jsonDebugPrintNode(JsonNode *pNode){ - jsonDebugPrintNodeEntries(pNode, jsonNodeSize(pNode)); -} -#else - /* The usual case */ -# define jsonDebugPrintNode(X) -# define jsonDebugPrintParse(X) -#endif - #ifdef SQLITE_DEBUG /* ** SQL function: json_parse(JSON) ** -** Parse JSON using jsonParseCached(). Then print a dump of that -** parse on standard output. Return the mimified JSON result, just -** like the json() function. +** Parse JSON using jsonParseFuncArg(). Then print a dump of that +** parse on standard output. */ static void jsonParseFunc( sqlite3_context *ctx, @@ -4613,30 +4635,9 @@ static void jsonParseFunc( JsonParse *p; /* The parse */ assert( argc==1 ); - if( jsonFuncArgMightBeBinary(argv[0]) ){ - JsonParse x; - memset(&x, 0, sizeof(x)); - x.nBlob = sqlite3_value_bytes(argv[0]); - x.aBlob = (u8*)sqlite3_value_blob(argv[0]); - jsonDebugPrintBlob(&x, 0, x.nBlob, 0); - return; - } - p = jsonParseCached(ctx, argv[0], ctx, 0); - if( p==0 ) return; - printf("nNode = %u\n", p->nNode); - printf("nAlloc = %u\n", p->nAlloc); - printf("nJson = %d\n", p->nJson); - printf("nAlt = %d\n", p->nAlt); - printf("nErr = %u\n", p->nErr); - printf("oom = %u\n", p->oom); - printf("hasNonstd = %u\n", p->hasNonstd); - printf("useMod = %u\n", p->useMod); - printf("hasMod = %u\n", p->hasMod); - printf("nJPRef = %u\n", p->nJPRef); - printf("iSubst = %u\n", p->iSubst); - printf("iHold = %u\n", p->iHold); - jsonDebugPrintNodeEntries(p->aNode, p->nNode); - jsonReturnNodeAsJson(p, p->aNode, ctx, 1, 0); + p = jsonParseFuncArg(ctx, argv[0], 0); + jsonShowParse(p); + jsonParseFree(p); } /* @@ -5278,8 +5279,6 @@ static void jsonPatchFunc( }else if( pX->nErr ){ sqlite3_result_error(ctx, "malformed JSON", -1); }else if( pResult ){ - jsonDebugPrintParse(pX); - jsonDebugPrintNode(pResult); jsonReturnNodeAsJson(pX, pResult, ctx, 0, 0); } } @@ -5337,34 +5336,47 @@ static void jsonRemoveFunc( int argc, sqlite3_value **argv ){ - JsonParse *pParse; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; + JsonParse *p; /* The parse */ + const char *zPath = 0; /* Path of element to be removed */ + u32 i; /* Loop counter */ + int rc; /* Subroutine return code */ if( argc<1 ) return; - if( jsonFuncArgMightBeBinary(argv[0]) ){ - jsonRemoveFromBlob(ctx, argc, argv); - return; - } - pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); - if( pParse==0 ) return; - for(i=1; i<(u32)argc; i++){ - zPath = (const char*)sqlite3_value_text(argv[i]); - if( zPath==0 ) goto remove_done; - pNode = jsonLookup(pParse, zPath, 0, ctx); - if( pParse->nErr ) goto remove_done; - if( pNode ){ - pNode->jnFlags |= JNODE_REMOVE; - pParse->hasMod = 1; - pParse->useMod = 1; + p = jsonParseFuncArg(ctx, argv[0], JSON_EDITABLE); + if( p==0 ) return; + for(i=1; ieEdit = JEDIT_DEL; + p->delta = 0; + rc = jsonLookupBlobStep(p, 0, zPath+1, 0); + if( rc==JSON_BLOB_NOTFOUND ) continue; + if( JSON_BLOB_ISERROR(rc) ) goto json_remove_patherror; } - if( (pParse->aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturnNodeAsJson(pParse, pParse->aNode, ctx, 1, 0); - } -remove_done: - jsonDebugPrintParse(p); + jsonReturnParse(ctx, p); + jsonParseFree(p); + return; + +json_remove_patherror: + jsonParseFree(p); + jsonPathSyntaxError(zPath, ctx); + return; + +json_remove_return_null: + jsonParseFree(p); + return; } /* @@ -5503,7 +5515,6 @@ static void jsonReplaceFunc( } jsonReturnNodeAsJson(pParse, pParse->aNode, ctx, 1, 0); replace_err: - jsonDebugPrintParse(pParse); jsonParseFree(pParse); } @@ -5559,7 +5570,6 @@ static void jsonSetFunc( jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); } } - jsonDebugPrintParse(pParse); jsonReturnNodeAsJson(pParse, pParse->aNode, ctx, 1, 0); jsonSetDone: jsonParseFree(pParse); diff --git a/test/json501.test b/test/json501.test index 3318eea7f2..40b3b563db 100644 --- a/test/json501.test +++ b/test/json501.test @@ -252,13 +252,13 @@ do_execsql_test 8.11 { do_execsql_test 9.1 { WITH c(x) AS (VALUES('{x: +Infinity}')) SELECT x->>'x', json(x) FROM c; -} {Inf {{"x":9.0e999}}} +} {Inf {{"x":9e999}}} do_execsql_test 9.2 { WITH c(x) AS (VALUES('{x: -Infinity}')) SELECT x->>'x', json(x) FROM c; -} {-Inf {{"x":-9.0e999}}} +} {-Inf {{"x":-9e999}}} do_execsql_test 9.3 { WITH c(x) AS (VALUES('{x: Infinity}')) SELECT x->>'x', json(x) FROM c; -} {Inf {{"x":9.0e999}}} +} {Inf {{"x":9e999}}} do_execsql_test 9.4 { WITH c(x) AS (VALUES('{x: NaN}')) SELECT x->>'x', json(x) FROM c; } {{} {{"x":null}}}