Convert json_insert(), json_replace(), and json_set() over to using only
JSONB internally. FossilOrigin-Name: 4e2083e86f19ef7634f0b253fb924e52014b43ed0ce8acc51c36f3c5682180a6
This commit is contained in:
commit
0ab1e25468
15
manifest
15
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.
|
||||
|
@ -1 +1 @@
|
||||
e7a8ba35bff6fde55827f978de5b343b6c134c7fa53827f5c63915a9dc2598ad
|
||||
4e2083e86f19ef7634f0b253fb924e52014b43ed0ce8acc51c36f3c5682180a6
|
847
src/json.c
847
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->nUsed<p->nAlloc );
|
||||
}
|
||||
|
||||
/*
|
||||
** 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; i<N && zIn[i]!='\\' && zIn[i]!='"'; i++){}
|
||||
if( i>0 ){
|
||||
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; i<N; i++){
|
||||
if( zIn[i]=='.' && (i+1==N || !sqlite3Isdigit(zIn[i+1])) ){
|
||||
i++;
|
||||
jsonAppendRaw(p, zIn, i);
|
||||
zIn += i;
|
||||
N -= i;
|
||||
jsonAppendChar(p, '0');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( N>0 ){
|
||||
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( i<pParse->nNode );
|
||||
assert( pParse->aNode[i].eType==JSON_SUBST );
|
||||
assert( pParse->aNode[i].eU==4 );
|
||||
assert( pParse->aNode[i].u.iPrev<i );
|
||||
if( pParse->aNode[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( j<pNode->n ){
|
||||
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( i<pParse->nNode );
|
||||
assert( pParse->aNode[i].eType==JSON_SUBST );
|
||||
assert( pParse->aNode[i].eU==4 );
|
||||
assert( pParse->aNode[i].u.iPrev<i );
|
||||
if( pParse->aNode[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; i<argc-1; i+=2){
|
||||
const char *zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
if( zPath==0 ) goto jsonInsertIntoBlob_patherror;
|
||||
if( sqlite3_value_type(argv[i])==SQLITE_NULL ) continue;
|
||||
zPath = (const char*)sqlite3_value_text(argv[i]);
|
||||
if( zPath==0 ){
|
||||
sqlite3_result_error_nomem(ctx);
|
||||
jsonParseFree(p);
|
||||
return;
|
||||
}
|
||||
if( zPath[0]!='$' ) goto jsonInsertIntoBlob_patherror;
|
||||
if( jsonFunctionArgToBlob(ctx, argv[i+1], &ax) ){
|
||||
break;
|
||||
jsonParseReset(&ax);
|
||||
jsonParseFree(p);
|
||||
return;
|
||||
}
|
||||
if( zPath[1]==0 ){
|
||||
jsonParseReset(&px);
|
||||
return; /* return NULL if $ is removed */
|
||||
if( eEdit==JEDIT_REPL || eEdit==JEDIT_SET ){
|
||||
jsonBlobEdit(p, 0, p->nBlob, 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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user