Convert json_insert(), json_replace(), and json_set() over to using only

JSONB internally.

FossilOrigin-Name: 4e2083e86f19ef7634f0b253fb924e52014b43ed0ce8acc51c36f3c5682180a6
This commit is contained in:
drh 2023-11-30 19:11:14 +00:00
commit 0ab1e25468
4 changed files with 149 additions and 740 deletions

View File

@ -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.

View File

@ -1 +1 @@
e7a8ba35bff6fde55827f978de5b343b6c134c7fa53827f5c63915a9dc2598ad
4e2083e86f19ef7634f0b253fb924e52014b43ed0ce8acc51c36f3c5682180a6

View File

@ -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);
}
/*

View File

@ -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