diff --git a/manifest b/manifest index 393b7c556d..0b047f5b82 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Convert\sthe\sjson_error_position()\sroutine\sto\suse\sonly\sJSONB\sinternally. -D 2023-11-29T20:06:49.393 +C Convert\sjson_insert(),\sjson_replace(),\sand\sjson_set()\sover\sto\susing\sonly\nJSONB\sinternally. +D 2023-11-30T19:11:14.034 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 d93f5e7d0e0a50c5842657439c3e820802f862da0d2aaf28df08cb3528acc0aa +F src/json.c 92cc05dcf87cdace6fbf2a76e0cb2b030f04ddb1819e024af4ce8885261b4cef F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36 F src/main.c 1b89f3de98d1b59fec5bac1d66d6ece21f703821b8eaa0d53d9604c35309f6f9 @@ -1325,7 +1325,7 @@ F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd286 F test/json/json-q1-b.txt 606818a5fba6d9e418c9f4ea7d8418af026775042dad81439b72447a147a462c F test/json/json-q1.txt 65f9d1cdcc4cffa9823fb73ed936aae5658700cd001fde448f68bfb91c807307 F test/json/json-speed-check.sh b060a9a6c696c0a807d8929400fa11bd7113edc58b0d66b9795f424f8d0db326 x -F test/json101.test 8f5d1a3350c36945c8b58f538e1c92cfd87fd50ab6f5e3d5f4cf3cdb03b9546d +F test/json101.test 70587d7d35ef9e2126364ba70f0c951f70827cfbd28649d779ff3df7e8f87547 F test/json102.test 557a46e16df1aa9bdbc4076a71a45814ea0e7503d6621d87d42a8c04cbc2b0ef F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1 @@ -2145,8 +2145,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P fee19d0098242110d2c44ec7b9620c1210ef3f87913305f66ec85d277dd96ab6 -R de34ae25adb13e4614fb6787c64e8886 +P e7a8ba35bff6fde55827f978de5b343b6c134c7fa53827f5c63915a9dc2598ad cc7a641ab5ae739d31c24f0ad0caeb15a481a63fa8f13720718ea922c25862ff +R 4a2861d7ab7860f80e426ce44e32c50f +T +closed cc7a641ab5ae739d31c24f0ad0caeb15a481a63fa8f13720718ea922c25862ff U drh -Z beaa9ff647aa958b069175a31fff1f35 +Z 60442442b5f0c2166085d00e76659699 # Remove this line to create a well-formed Fossil manifest. diff --git a/manifest.uuid b/manifest.uuid index 0114d98f13..24560706af 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -e7a8ba35bff6fde55827f978de5b343b6c134c7fa53827f5c63915a9dc2598ad \ No newline at end of file +4e2083e86f19ef7634f0b253fb924e52014b43ed0ce8acc51c36f3c5682180a6 \ No newline at end of file diff --git a/src/json.c b/src/json.c index ba15e12e17..b8b01bf452 100644 --- a/src/json.c +++ b/src/json.c @@ -371,16 +371,22 @@ struct JsonParse { */ #define JSON_MAX_DEPTH 1000 +/* +** Allowed values for the flgs argument to jsonParseFuncArg(); +*/ +#define JSON_EDITABLE 0x01 /* Generate a writable JsonParse object */ + /************************************************************************** ** Forward references **************************************************************************/ static void jsonReturnStringAsBlob(JsonString*); -static void jsonXlateNodeToBlob(JsonParse*,JsonNode*,JsonParse*); static int jsonParseAddNode(JsonParse*,u32,u32,const char*); static int jsonXlateBlobToNode(JsonParse *pParse, u32 i); static int jsonFuncArgMightBeBinary(sqlite3_value *pJson); static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); static u32 jsonXlateBlobToText(const JsonParse*,u32,JsonString*); +static void jsonReturnParse(sqlite3_context*,JsonParse*); +static JsonParse *jsonParseFuncArg(sqlite3_context*,sqlite3_value*,u32); /************************************************************************** ** Utility routines for dealing with JsonString objects @@ -592,159 +598,6 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ assert( p->nUsednAlloc ); } -/* -** The zIn[0..N] string is a JSON5 string literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedString(JsonString *p, const char *zIn, u32 N){ - u32 i; - jsonAppendChar(p, '"'); - while( N>0 ){ - for(i=0; i0 ){ - jsonAppendRawNZ(p, zIn, i); - if( i>=N ) break; - zIn += i; - N -= i; - } - if( N<2 ){ - p->eErr |= JSTRING_MALFORMED; - break; - } - if( zIn[0]=='"' ){ - jsonAppendRawNZ(p, "\\\"", 2); - zIn++; - N--; - continue; - } - assert( zIn[0]=='\\' ); - switch( (u8)zIn[1] ){ - case '\'': - jsonAppendChar(p, '\''); - break; - case 'v': - jsonAppendRawNZ(p, "\\u0009", 6); - break; - case 'x': - if( N<4 ){ - N = 2; - p->eErr |= JSTRING_MALFORMED; - break; - } - jsonAppendRawNZ(p, "\\u00", 4); - jsonAppendRawNZ(p, &zIn[2], 2); - zIn += 2; - N -= 2; - break; - case '0': - jsonAppendRawNZ(p, "\\u0000", 6); - break; - case '\r': - if( N>2 && zIn[2]=='\n' ){ - zIn++; - N--; - } - break; - case '\n': - break; - case 0xe2: /* \ followed by U+2028 or U+2029 line terminator ignored */ - if( N<4 - || 0x80!=(u8)zIn[2] - || (0xa8!=(u8)zIn[3] && 0xa9!=(u8)zIn[3]) - ){ - N = 2; - p->eErr |= JSTRING_MALFORMED; - break; - } - assert( N>=4 ); - assert( 0x80==(u8)zIn[2] ); - assert( 0xa8==(u8)zIn[3] || 0xa9==(u8)zIn[3] ); - zIn += 2; - N -= 2; - break; - default: - jsonAppendRawNZ(p, zIn, 2); - break; - } - assert( N>=2 ); - zIn += 2; - N -= 2; - } - jsonAppendChar(p, '"'); -} - -/* -** The zIn[0..N] string is a JSON5 integer literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedInt(JsonString *p, const char *zIn, u32 N){ - char *zBuf = sqlite3_malloc64( N+1 ); - if( zBuf==0 ){ - p->eErr |= JSTRING_OOM; - return; - } - memcpy(zBuf, zIn, N); - zBuf[N] = 0; - zIn = zBuf; - if( zIn[0]=='+' ){ - zIn++; - N--; - }else if( zIn[0]=='-' ){ - jsonAppendChar(p, '-'); - zIn++; - N--; - } - if( zIn[0]=='0' && (zIn[1]=='x' || zIn[1]=='X') ){ - sqlite3_int64 i = 0; - int rc = sqlite3DecOrHexToI64(zIn, &i); - if( rc<=1 ){ - jsonPrintf(100,p,"%lld",i); - }else{ - assert( rc==2 ); - jsonAppendRawNZ(p, "9.0e999", 7); - } - }else{ - assert( N>0 ); - jsonAppendRawNZ(p, zIn, N); - } - sqlite3_free(zBuf); -} - -/* -** The zIn[0..N] string is a JSON5 real literal. Append to p a translation -** of the string literal that standard JSON and that omits all JSON5 -** features. -*/ -static void jsonAppendNormalizedReal(JsonString *p, const char *zIn, u32 N){ - u32 i; - if( zIn[0]=='+' ){ - zIn++; - N--; - }else if( zIn[0]=='-' ){ - jsonAppendChar(p, '-'); - zIn++; - N--; - } - if( zIn[0]=='.' ){ - jsonAppendChar(p, '0'); - } - for(i=0; i0 ){ - jsonAppendRawNZ(p, zIn, N); - } -} - /* ** Append an sqlite3_value (such as a function parameter) to the JSON ** string under construction in p. @@ -895,194 +748,6 @@ static void jsonParseFree(JsonParse *pParse){ } } -/* -** Add a cleanup task to the JsonParse object. -** -** If an OOM occurs, the cleanup operation happens immediately -** and this function returns SQLITE_NOMEM. -*/ -static int jsonParseAddCleanup( - JsonParse *pParse, /* Add the cleanup task to this parser */ - void(*xOp)(void*), /* The cleanup task */ - void *pArg /* Argument to the cleanup */ -){ - JsonCleanup *pTask = sqlite3_malloc64( sizeof(*pTask) ); - if( pTask==0 ){ - pParse->oom = 1; - xOp(pArg); - return SQLITE_ERROR; - } - pTask->pJCNext = pParse->pClup; - pParse->pClup = pTask; - pTask->xOp = xOp; - pTask->pArg = pArg; - return SQLITE_OK; -} - -/* -** Translate the JsonNode pNode into a pure JSON string and -** append that string on pOut. Subsubstructure is also included. -*/ -static void jsonXlateNodeToText( - JsonParse *pParse, /* the complete parse of the JSON */ - JsonNode *pNode, /* The node to render */ - JsonString *pOut /* Write JSON here */ -){ - assert( pNode!=0 ); - while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ - u32 idx = (u32)(pNode - pParse->aNode); - u32 i = pParse->iSubst; - while( 1 /*exit-by-break*/ ){ - assert( inNode ); - assert( pParse->aNode[i].eType==JSON_SUBST ); - assert( pParse->aNode[i].eU==4 ); - assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ - pNode = &pParse->aNode[i+1]; - break; - } - i = pParse->aNode[i].u.iPrev; - } - } - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - jsonAppendRawNZ(pOut, "null", 4); - break; - } - case JSON_TRUE: { - jsonAppendRawNZ(pOut, "true", 4); - break; - } - case JSON_FALSE: { - jsonAppendRawNZ(pOut, "false", 5); - break; - } - case JSON_STRING: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_RAW ){ - jsonAppendString(pOut, pNode->u.zJContent, pNode->n); - }else if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n); - }else{ - jsonAppendChar(pOut, '"'); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - jsonAppendChar(pOut, '"'); - } - break; - } - case JSON_REAL: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n); - }else{ - assert( pNode->n>0 ); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - } - break; - } - case JSON_INT: { - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n); - }else{ - assert( pNode->n>0 ); - jsonAppendRawNZ(pOut, pNode->u.zJContent, pNode->n); - } - break; - } - case JSON_ARRAY: { - u32 j = 1; - jsonAppendChar(pOut, '['); - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonAppendSeparator(pOut); - jsonXlateNodeToText(pParse, &pNode[j], pOut); - } - j += jsonNodeSize(&pNode[j]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, ']'); - break; - } - case JSON_OBJECT: { - u32 j = 1; - jsonAppendChar(pOut, '{'); - for(;;){ - while( jn ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonAppendSeparator(pOut); - jsonXlateNodeToText(pParse, &pNode[j], pOut); - jsonAppendChar(pOut, ':'); - jsonXlateNodeToText(pParse, &pNode[j+1], pOut); - } - j += 1 + jsonNodeSize(&pNode[j+1]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonAppendChar(pOut, '}'); - break; - } - } -} - -/* -** Make the return value of an SQL function be the JSON encoded by pNode. -** -** By default, the node is rendered as RFC-8259 JSON text (canonical -** JSON text without any JSON-5 enhancements). However if the -** JSON_BLOB flag is set in the user-data for the function, then the -** node is rendered into the JSONB format and returned as a BLOB. -*/ -static void jsonReturnNodeAsJson( - JsonParse *pParse, /* The complete JSON */ - JsonNode *pNode, /* Node to return */ - sqlite3_context *pCtx, /* Return value for this function */ - int bGenerateAlt, /* Also store the rendered text in zAlt */ - int omitSubtype /* Do not call sqlite3_result_subtype() */ -){ - int flags; - JsonString s; - if( pParse->oom ){ - sqlite3_result_error_nomem(pCtx); - return; - } - if( pParse->nErr ){ - return; - } - flags = SQLITE_PTR_TO_INT(sqlite3_user_data(pCtx)); - if( flags & JSON_BLOB ){ - JsonParse x; - memset(&x, 0, sizeof(x)); - jsonXlateNodeToBlob(pParse, pNode, &x); - if( x.oom ){ - sqlite3_result_error_nomem(pCtx); - sqlite3_free(x.aBlob); - }else{ - sqlite3_result_blob(pCtx, x.aBlob, x.nBlob, sqlite3_free); - } - }else{ - jsonStringInit(&s, pCtx); - jsonXlateNodeToText(pParse, pNode, &s); - if( bGenerateAlt && pParse->zAlt==0 && jsonForceRCStr(&s) ){ - pParse->zAlt = sqlite3RCStrRef(s.zBuf); - pParse->nAlt = s.nUsed; - } - jsonReturnString(&s); - if( !omitSubtype ) sqlite3_result_subtype(pCtx, JSON_SUBTYPE); - } -} - /* ** Translate a single byte of Hex into an integer. ** This routine only works if h really is a valid hexadecimal @@ -1178,52 +843,6 @@ static int jsonParseAddNode( return pParse->nNode++; } -/* -** Add an array of new nodes to the current pParse->aNode array. -** Return the index of the first node added. -** -** If an OOM error occurs, set pParse->oom. -*/ -static void jsonParseAddNodeArray( - JsonParse *pParse, /* Append the node to this object */ - JsonNode *aNode, /* Array of nodes to add */ - u32 nNode /* Number of elements in aNew */ -){ - assert( aNode!=0 ); - assert( nNode>=1 ); - if( pParse->nNode + nNode > pParse->nAlloc ){ - u32 nNew = pParse->nNode + nNode; - JsonNode *aNew = sqlite3_realloc64(pParse->aNode, nNew*sizeof(JsonNode)); - if( aNew==0 ){ - pParse->oom = 1; - return; - } - pParse->nAlloc = sqlite3_msize(aNew)/sizeof(JsonNode); - pParse->aNode = aNew; - } - memcpy(&pParse->aNode[pParse->nNode], aNode, nNode*sizeof(JsonNode)); - pParse->nNode += nNode; -} - -/* -** Add a new JSON_SUBST node. The node immediately following -** this new node will be the substitute content for iNode. -*/ -static int jsonParseAddSubstNode( - JsonParse *pParse, /* Add the JSON_SUBST here */ - u32 iNode /* References this node */ -){ - int idx = jsonParseAddNode(pParse, JSON_SUBST, iNode, 0); - if( pParse->oom ) return -1; - pParse->aNode[iNode].jnFlags |= JNODE_REPLACE; - pParse->aNode[idx].eU = 4; - pParse->aNode[idx].u.iPrev = pParse->iSubst; - pParse->iSubst = idx; - pParse->hasMod = 1; - pParse->useMod = 1; - return idx; -} - /* ** Return true if z[] begins with 2 (or more) hexadecimal digits */ @@ -3446,138 +3065,6 @@ static int jsonXlateBlobToNode(JsonParse *pParse, u32 i){ return i+x+sz; } -/* -** Translate pNode (which is always a node found in pParse->aNode[]) into -** the JSONB representation and append the translation onto the end of the -** pOut->aBlob[] array. -*/ -static void jsonXlateNodeToBlob( - JsonParse *pParse, /* the complete parse of the JSON */ - JsonNode *pNode, /* The node to render */ - JsonParse *pOut /* Write the BLOB rendering of JSON here */ -){ - assert( pNode!=0 ); - while( (pNode->jnFlags & JNODE_REPLACE)!=0 && pParse->useMod ){ - u32 idx = (u32)(pNode - pParse->aNode); - u32 i = pParse->iSubst; - while( 1 /*exit-by-break*/ ){ - assert( inNode ); - assert( pParse->aNode[i].eType==JSON_SUBST ); - assert( pParse->aNode[i].eU==4 ); - assert( pParse->aNode[i].u.iPrevaNode[i].n==idx ){ - pNode = &pParse->aNode[i+1]; - break; - } - i = pParse->aNode[i].u.iPrev; - } - } - switch( pNode->eType ){ - default: { - assert( pNode->eType==JSON_NULL ); - jsonBlobAppendNodeType(pOut, JSONB_NULL, 0); - break; - } - case JSON_TRUE: { - jsonBlobAppendNodeType(pOut, JSONB_TRUE, 0); - break; - } - case JSON_FALSE: { - jsonBlobAppendNodeType(pOut, JSONB_FALSE, 0); - break; - } - case JSON_STRING: { - int op; - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_RAW ){ - if( memchr(pNode->u.zJContent, '"', pNode->n)==0 - && memchr(pNode->u.zJContent, '\\', pNode->n)==0 - ){ - op = JSONB_TEXT; - }else{ - op = JSONB_TEXTRAW; - } - }else if( pNode->jnFlags & JNODE_JSON5 ){ - op = JSONB_TEXT5; - }else{ - op = JSONB_TEXTJ; - } - jsonBlobAppendNodeType(pOut, op, pNode->n); - jsonBlobAppendNBytes(pOut, (const u8*)pNode->u.zJContent, pNode->n); - break; - } - case JSON_REAL: { - int op; - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - op = JSONB_FLOAT5; - }else{ - assert( pNode->n>0 ); - op = JSONB_FLOAT; - } - jsonBlobAppendNodeType(pOut, op, pNode->n); - jsonBlobAppendNBytes(pOut, (const u8*)pNode->u.zJContent, pNode->n); - break; - } - case JSON_INT: { - int op; - assert( pNode->eU==1 ); - if( pNode->jnFlags & JNODE_JSON5 ){ - op = JSONB_INT5; - }else{ - assert( pNode->n>0 ); - op = JSONB_INT; - } - jsonBlobAppendNodeType(pOut, op, pNode->n); - jsonBlobAppendNBytes(pOut, (const u8*)pNode->u.zJContent, pNode->n); - break; - } - case JSON_ARRAY: { - u32 j = 1; - u32 iStart, iThis = pOut->nBlob; - jsonBlobAppendNodeType(pOut, JSONB_ARRAY, pParse->nJson*2); - iStart = pOut->nBlob; - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonXlateNodeToBlob(pParse, &pNode[j], pOut); - } - j += jsonNodeSize(&pNode[j]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonBlobChangePayloadSize(pOut, iThis, pOut->nBlob - iStart); - break; - } - case JSON_OBJECT: { - u32 j = 1; - u32 iStart, iThis = pOut->nBlob; - jsonBlobAppendNodeType(pOut, JSONB_OBJECT, pParse->nJson*2); - iStart = pOut->nBlob; - for(;;){ - while( j<=pNode->n ){ - if( (pNode[j+1].jnFlags & JNODE_REMOVE)==0 || pParse->useMod==0 ){ - jsonXlateNodeToBlob(pParse, &pNode[j], pOut); - jsonXlateNodeToBlob(pParse, &pNode[j+1], pOut); - } - j += 1 + jsonNodeSize(&pNode[j+1]); - } - if( (pNode->jnFlags & JNODE_APPEND)==0 ) break; - if( pParse->useMod==0 ) break; - assert( pNode->eU==2 ); - pNode = &pParse->aNode[pNode->u.iAppend]; - j = 1; - } - jsonBlobChangePayloadSize(pOut, iThis, pOut->nBlob - iStart); - break; - } - } -} - /* ** Given that a JSONB_ARRAY object starts at offset i, return ** the number of entries in that array. @@ -3667,6 +3154,7 @@ static u32 jsonLookupBlobStep( u32 i, j, k, nKey, sz, n, iEnd, rc; const char *zKey; u8 x; + static const u8 emptyObject[] = { JSONB_ARRAY, JSONB_OBJECT }; if( zPath[0]==0 ){ if( pParse->eEdit && jsonBlobMakeEditable(pParse, pParse->nIns) ){ @@ -3738,26 +3226,44 @@ static u32 jsonLookupBlobStep( } if( j>iEnd ) return JSON_BLOB_ERROR; if( pParse->eEdit>=JEDIT_INS ){ - u32 nIns; - u8 aLabel[16]; - JsonParse ix; + u32 nIns; /* Total bytes to insert (label+value) */ + JsonParse v; /* BLOB encoding of the value to be inserted */ + JsonParse ix; /* Header of the label to be inserted */ + u8 aLabel[16]; /* Buffer space for ix */ testcase( pParse->eEdit==JEDIT_INS ); testcase( pParse->eEdit==JEDIT_SET ); memset(&ix, 0, sizeof(ix)); ix.aBlob = aLabel; ix.nBlobAlloc = sizeof(aLabel); jsonBlobAppendNodeType(&ix,JSONB_TEXTRAW, nKey); - if( jsonBlobMakeEditable(pParse, ix.nBlob+nKey+pParse->nIns) ){ - nIns = ix.nBlob + nKey + pParse->nIns; + memset(&v, 0, sizeof(v)); + if( zPath[i]==0 ){ + v.nBlob = pParse->nIns; + v.aBlob = pParse->aIns; + }else{ + v.nBlob = 1; + v.aBlob = (u8*)&emptyObject[zPath[i]=='.']; + v.eEdit = pParse->eEdit; + v.nIns = pParse->nIns; + v.aIns = pParse->aIns; + rc = jsonLookupBlobStep(&v, 0, &zPath[i], 0); + if( JSON_BLOB_ISERROR(rc) || v.oom ){ + pParse->oom |= v.oom; + jsonParseReset(&v); + return rc; + } + } + if( jsonBlobMakeEditable(pParse, ix.nBlob+nKey+v.nBlob) ){ + nIns = ix.nBlob + nKey + v.nBlob; jsonBlobEdit(pParse, j, 0, 0, nIns); - assert( pParse->nBlob + pParse->nIns <= pParse->nBlobAlloc ); memcpy(&pParse->aBlob[j], ix.aBlob, ix.nBlob); k = j + ix.nBlob; memcpy(&pParse->aBlob[k], zKey, nKey); k += nKey; - memcpy(&pParse->aBlob[k], pParse->aIns, pParse->nIns); - jsonAfterEditSizeAdjust(pParse, iRoot); + memcpy(&pParse->aBlob[k], v.aBlob, v.nBlob); + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); } + jsonParseReset(&v); return j; } }else if( zPath[0]=='[' ){ @@ -3805,14 +3311,33 @@ static u32 jsonLookupBlobStep( j += n+sz; } if( j>iEnd ) return JSON_BLOB_ERROR; - if( k>1 ) return JSON_BLOB_NOTFOUND; - if( pParse->eEdit>=JEDIT_INS - && jsonBlobMakeEditable(pParse, pParse->nIns) - ){ + if( k>0 ) return JSON_BLOB_NOTFOUND; + if( pParse->eEdit>=JEDIT_INS ){ + JsonParse v; testcase( pParse->eEdit==JEDIT_INS ); testcase( pParse->eEdit==JEDIT_SET ); - jsonBlobEdit(pParse, j, 0, pParse->aIns, pParse->nIns); - jsonAfterEditSizeAdjust(pParse, iRoot); + memset(&v, 0, sizeof(v)); + if( zPath[i+1]==0 ){ + v.aBlob = pParse->aIns; + v.nBlob = pParse->nIns; + }else{ + v.nBlob = 1; + v.aBlob = (u8*)&emptyObject[zPath[i+1]=='.']; + v.eEdit = pParse->eEdit; + v.nIns = pParse->nIns; + v.aIns = pParse->aIns; + rc = jsonLookupBlobStep(&v, 0, &zPath[i+1], 0); + if( JSON_BLOB_ISERROR(rc) || v.oom ){ + pParse->oom |= v.oom; + jsonParseReset(&v); + return rc; + } + } + if( jsonBlobMakeEditable(pParse, v.nBlob) ){ + jsonBlobEdit(pParse, j, 0, v.aBlob, v.nBlob); + } + jsonParseReset(&v); + if( pParse->delta ) jsonAfterEditSizeAdjust(pParse, iRoot); return j; } }else{ @@ -4088,9 +3613,27 @@ static int jsonFunctionArgToBlob( break; } } - return 0; + if( pParse->oom ){ + sqlite3_result_error_nomem(ctx); + return 1; + }else{ + return 0; + } } - + +/* +** Generate a bad path error for json_extract() +*/ +static void jsonBadPathError( + sqlite3_context *ctx, /* The function call containing the error */ + const char *zPath /* The path with the problem */ +){ + sqlite3 *db = sqlite3_context_db_handle(ctx); + char *zMsg = sqlite3MPrintf(db, "bad JSON path: %Q", zPath); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3DbFree(db, zMsg); +} + /* argv[0] is a BLOB that seems likely to be a JSONB. Subsequent ** arguments come in parse where each pair contains a JSON path and ** content to insert or set at that patch. Do the updates @@ -4109,61 +3652,57 @@ static void jsonInsertIntoBlob( u32 rc = 0; const char *zPath = 0; int flgs; - JsonParse px, ax; + JsonParse *p; + JsonParse ax; assert( (argc&1)==1 ); - 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; + flgs = argc==1 ? 0 : JSON_EDITABLE; + p = jsonParseFuncArg(ctx, argv[0], flgs); + if( p==0 ) return; for(i=1; inBlob, ax.aBlob, ax.nBlob); + } + rc = 0; + }else{ + p->eEdit = eEdit; + p->nIns = ax.nBlob; + p->aIns = ax.aBlob; + p->delta = 0; + rc = jsonLookupBlobStep(p, 0, zPath+1, 0); } - px.eEdit = eEdit; - px.nIns = ax.nBlob; - px.aIns = ax.aBlob; - px.delta = 0; - rc = jsonLookupBlobStep(&px, 0, zPath+1, 0); jsonParseReset(&ax); if( rc==JSON_BLOB_NOTFOUND ) continue; if( JSON_BLOB_ISERROR(rc) ) goto jsonInsertIntoBlob_patherror; } - flgs = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); - if( flgs & JSON_BLOB ){ - sqlite3_result_blob(ctx, px.aBlob, px.nBlob, - px.nBlobAlloc>0 ? SQLITE_DYNAMIC : SQLITE_TRANSIENT); - }else{ - JsonString s; - jsonStringInit(&s, ctx); - jsonXlateBlobToText(&px, 0, &s); - jsonReturnString(&s); - jsonParseReset(&px); - } + jsonReturnParse(ctx, p); + jsonParseFree(p); return; jsonInsertIntoBlob_patherror: - jsonParseReset(&px); + jsonParseFree(p); if( rc==JSON_BLOB_ERROR ){ sqlite3_result_error(ctx, "malformed JSON", -1); }else{ - jsonPathSyntaxError(zPath, ctx); + jsonBadPathError(ctx, zPath); } 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 @@ -4254,6 +3793,10 @@ static void jsonReturnParse( JsonParse *p ){ int flgs; + if( p->oom ){ + sqlite3_result_error_nomem(ctx); + return; + } flgs = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); if( flgs & JSON_BLOB ){ sqlite3_result_blob(ctx, p->aBlob, p->nBlob, @@ -4531,19 +4074,6 @@ static void jsonArrayFunc( sqlite3_result_subtype(ctx, JSON_SUBTYPE); } -/* -** Generate a bad path error for json_extract() -*/ -static void jsonBadPathError( - sqlite3_context *ctx, /* The function call containing the error */ - const char *zPath /* The path with the problem */ -){ - sqlite3 *db = sqlite3_context_db_handle(ctx); - char *zMsg = sqlite3MPrintf(db, "bad JSON path: %Q", zPath); - sqlite3_result_error(ctx, zMsg, -1); - sqlite3DbFree(db, zMsg); -} - /* ** json_array_length(JSON) ** json_array_length(JSON, PATH) @@ -5074,103 +4604,6 @@ json_remove_return_null: return; } -/* -** Substitute the value at iNode with the pValue parameter. -*/ -static void jsonReplaceNode( - sqlite3_context *pCtx, - JsonParse *p, - int iNode, - sqlite3_value *pValue -){ - int idx = jsonParseAddSubstNode(p, iNode); - if( idx<=0 ){ - assert( p->oom ); - return; - } - switch( sqlite3_value_type(pValue) ){ - case SQLITE_NULL: { - jsonParseAddNode(p, JSON_NULL, 0, 0); - break; - } - case SQLITE_FLOAT: { - char *z = sqlite3_mprintf("%!0.15g", sqlite3_value_double(pValue)); - int n; - if( z==0 ){ - p->oom = 1; - break; - } - n = sqlite3Strlen30(z); - jsonParseAddNode(p, JSON_REAL, n, z); - jsonParseAddCleanup(p, sqlite3_free, z); - break; - } - case SQLITE_INTEGER: { - char *z = sqlite3_mprintf("%lld", sqlite3_value_int64(pValue)); - int n; - if( z==0 ){ - p->oom = 1; - break; - } - n = sqlite3Strlen30(z); - jsonParseAddNode(p, JSON_INT, n, z); - jsonParseAddCleanup(p, sqlite3_free, z); - - break; - } - case SQLITE_TEXT: { - const char *z = (const char*)sqlite3_value_text(pValue); - u32 n = (u32)sqlite3_value_bytes(pValue); - if( z==0 ){ - p->oom = 1; - break; - } - if( sqlite3_value_subtype(pValue)!=JSON_SUBTYPE ){ - char *zCopy = sqlite3_malloc64( n+1 ); - int k; - if( zCopy ){ - memcpy(zCopy, z, n); - zCopy[n] = 0; - jsonParseAddCleanup(p, sqlite3_free, zCopy); - }else{ - p->oom = 1; - sqlite3_result_error_nomem(pCtx); - } - k = jsonParseAddNode(p, JSON_STRING, n, zCopy); - assert( k>0 || p->oom ); - if( p->oom==0 ) p->aNode[k].jnFlags |= JNODE_RAW; - break; - } - replace_with_json: - { - JsonParse *pPatch = jsonParseCached(pCtx, pValue, pCtx, 1); - if( pPatch==0 ){ - p->oom = 1; - break; - } - jsonParseAddNodeArray(p, pPatch->aNode, pPatch->nNode); - /* The nodes copied out of pPatch and into p likely contain - ** u.zJContent pointers into pPatch->zJson. So preserve the - ** content of pPatch until p is destroyed. */ - assert( pPatch->nJPRef>=1 ); - pPatch->nJPRef++; - jsonParseAddCleanup(p, (void(*)(void*))jsonParseFree, pPatch); - } - break; - } - default: { - if( jsonFuncArgMightBeBinary(pValue) ){ - goto replace_with_json; - }else{ - jsonParseAddNode(p, JSON_NULL, 0, 0); - sqlite3_result_error(pCtx, "JSON cannot hold BLOB values", -1); - p->nErr++; - } - break; - } - } -} - /* ** json_replace(JSON, PATH, VALUE, ...) ** @@ -5182,35 +4615,12 @@ static void jsonReplaceFunc( int argc, sqlite3_value **argv ){ - JsonParse *pParse; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - if( argc<1 ) return; if( (argc&1)==0 ) { jsonWrongNumArgs(ctx, "replace"); return; } - if( jsonFuncArgMightBeBinary(argv[0]) && argc>=3 ){ - jsonInsertIntoBlob(ctx, argc, argv, JEDIT_REPL); - return; - } - pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); - if( pParse==0 ) return; - pParse->nJPRef++; - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - pParse->useMod = 1; - pNode = jsonLookup(pParse, zPath, 0, ctx); - if( pParse->nErr ) goto replace_err; - if( pNode ){ - jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); - } - } - jsonReturnNodeAsJson(pParse, pParse->aNode, ctx, 1, 0); -replace_err: - jsonParseFree(pParse); + jsonInsertIntoBlob(ctx, argc, argv, JEDIT_REPL); } @@ -5231,11 +4641,7 @@ static void jsonSetFunc( int argc, sqlite3_value **argv ){ - JsonParse *pParse; /* The parse */ - JsonNode *pNode; - const char *zPath; - u32 i; - int bApnd; + int flags = SQLITE_PTR_TO_INT(sqlite3_user_data(ctx)); int bIsSet = (flags&JSON_ISSET)!=0; @@ -5244,30 +4650,7 @@ static void jsonSetFunc( jsonWrongNumArgs(ctx, bIsSet ? "set" : "insert"); return; } - if( jsonFuncArgMightBeBinary(argv[0]) && argc>=3 ){ - jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); - return; - } - pParse = jsonParseCached(ctx, argv[0], ctx, argc>1); - if( pParse==0 ) return; - pParse->nJPRef++; - for(i=1; i<(u32)argc; i+=2){ - zPath = (const char*)sqlite3_value_text(argv[i]); - bApnd = 0; - pParse->useMod = 1; - pNode = jsonLookup(pParse, zPath, &bApnd, ctx); - if( pParse->oom ){ - sqlite3_result_error_nomem(ctx); - goto jsonSetDone; - }else if( pParse->nErr ){ - goto jsonSetDone; - }else if( pNode && (bApnd || bIsSet) ){ - jsonReplaceNode(ctx, pParse, (u32)(pNode - pParse->aNode), argv[i+1]); - } - } - jsonReturnNodeAsJson(pParse, pParse->aNode, ctx, 1, 0); -jsonSetDone: - jsonParseFree(pParse); + jsonInsertIntoBlob(ctx, argc, argv, bIsSet ? JEDIT_SET : JEDIT_INS); } /* diff --git a/test/json101.test b/test/json101.test index 3c3b1a5ec0..bae68e7344 100644 --- a/test/json101.test +++ b/test/json101.test @@ -1139,4 +1139,29 @@ do_execsql_test json101-23.2 { FROM (SELECT json_set('[]','$[#]',0,'$[#]',1) AS j); } {{[0,1]} 0 1} +# Insert/Set/Replace where the path specifies substructure that +# does not yet exist +# +proc tx x {return [string map [list ( \173 ) \175 ' \042 < \133 > \135] $x]} +foreach {id start path ins set repl} { + 1 {{}} {$.a.b.c} ('a':('b':('c':9))) ('a':('b':('c':9))) () + 2 {{a:4}} {$.a.b.c} ('a':4) ('a':4) ('a':4) + 3 {{a:{}}} {$.a.b.c} ('a':('b':('c':9))) ('a':('b':('c':9))) ('a':()) + 4 {[0,1,2]} {$[3].a[0].b} <0,1,2,('a':<('b':9)>)> <0,1,2,('a':<('b':9)>)> <0,1,2> + 5 {[0,1,2]} {$[1].a[0].b} <0,1,2> <0,1,2> <0,1,2> + 6 {[0,{},2]} {$[1].a[0].b} <0,('a':<('b':9)>),2> <0,('a':<('b':9)>),2> <0,(),2> + 7 {[0,1,2]} {$[3][0].b} <0,1,2,<('b':9)>> <0,1,2,<('b':9)>> <0,1,2> + 8 {[0,1,2]} {$[1][0].b} <0,1,2> <0,1,2> <0,1,2> +} { + do_execsql_test json101-24.$id.insert { + SELECT json_insert($start,$path,9); + } [list [tx $ins]] + do_execsql_test json101-24.$id.set { + SELECT json_set($start,$path,9); + } [list [tx $set]] + do_execsql_test json101-24.$id.replace { + SELECT json_replace($start,$path,9); + } [list [tx $repl]] +} + finish_test