diff options
-rw-r--r-- | Makefile.in | 6 | ||||
-rw-r--r-- | Makefile.msc | 8 | ||||
-rw-r--r-- | ext/misc/json1.c | 342 | ||||
-rw-r--r-- | ext/rbu/sqlite3rbu.c | 11 | ||||
-rw-r--r-- | main.mk | 6 | ||||
-rw-r--r-- | manifest | 32 | ||||
-rw-r--r-- | manifest.uuid | 2 | ||||
-rw-r--r-- | src/build.c | 2 | ||||
-rw-r--r-- | src/shell.c | 7 | ||||
-rw-r--r-- | src/wal.c | 32 | ||||
-rw-r--r-- | test/json101.test | 241 | ||||
-rw-r--r-- | test/rowid.test | 3 | ||||
-rw-r--r-- | test/wal6.test | 42 |
13 files changed, 596 insertions, 138 deletions
diff --git a/Makefile.in b/Makefile.in index 8f3d6f7df..99af3e44e 100644 --- a/Makefile.in +++ b/Makefile.in @@ -566,9 +566,9 @@ libtclsqlite3.la: tclsqlite.lo libsqlite3.la -version-info "8:6:8" \ -avoid-version -sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h - $(LTLINK) $(READLINE_FLAGS) \ - -o $@ $(TOP)/src/shell.c libsqlite3.la \ +sqlite3$(TEXE): $(TOP)/src/shell.c libsqlite3.la sqlite3.h $(TOP)/ext/misc/json1.c + $(LTLINK) $(READLINE_FLAGS) -DSQLITE_ENABLE_JSON1 -o $@ \ + $(TOP)/src/shell.c $(TOP)/ext/misc/json1.c libsqlite3.la \ $(LIBREADLINE) $(TLIBS) -rpath "$(libdir)" sqldiff$(TEXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h diff --git a/Makefile.msc b/Makefile.msc index 441c499df..7b971aa2f 100644 --- a/Makefile.msc +++ b/Makefile.msc @@ -390,9 +390,9 @@ CORE_LINK_OPTS = /DEF:sqlite3.def # !IFNDEF SHELL_COMPILE_OPTS !IF $(DYNAMIC_SHELL)!=0 -SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport) +SHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 $(SHELL_CCONV_OPTS) -DSQLITE_API=__declspec(dllimport) !ELSE -SHELL_COMPILE_OPTS = $(SHELL_CCONV_OPTS) +SHELL_COMPILE_OPTS = -DSQLITE_ENABLE_JSON1 $(SHELL_CCONV_OPTS) !ENDIF !ENDIF @@ -1224,8 +1224,8 @@ libsqlite3.lib: $(LIBOBJ) libtclsqlite3.lib: tclsqlite.lo libsqlite3.lib $(LTLIB) $(LTLIBOPTS) $(LTLIBPATHS) /OUT:$@ tclsqlite.lo libsqlite3.lib $(LIBTCL:tcl=tclstub) $(TLIBS) -sqlite3.exe: $(TOP)\src\shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) sqlite3.h - $(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) $(TOP)\src\shell.c \ +sqlite3.exe: $(TOP)\src\shell.c $(TOP)\ext\misc\json1.c $(SHELL_CORE_DEP) $(LIBRESOBJS) sqlite3.h + $(LTLINK) $(SHELL_COMPILE_OPTS) $(READLINE_FLAGS) $(TOP)\src\shell.c $(TOP)\ext\misc\json1.c \ /link /pdb:sqlite3sh.pdb $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) sqldiff.exe: $(TOP)\tool\sqldiff.c sqlite3.c sqlite3.h diff --git a/ext/misc/json1.c b/ext/misc/json1.c index cd4531bcc..5df7551de 100644 --- a/ext/misc/json1.c +++ b/ext/misc/json1.c @@ -21,7 +21,9 @@ ** This implementation parses JSON text at 250 MB/s, so it is hard to see ** how JSONB might improve on that.) */ +#if !defined(_SQLITEINT_H_) #include "sqlite3ext.h" +#endif SQLITE_EXTENSION_INIT1 #include <assert.h> #include <string.h> @@ -80,6 +82,7 @@ static const char * const jsonType[] = { #define JNODE_REMOVE 0x04 /* Do not output */ #define JNODE_REPLACE 0x08 /* Replace with JsonNode.iVal */ #define JNODE_APPEND 0x10 /* More ARRAY/OBJECT entries at u.iAppend */ +#define JNODE_JSON 0x20 /* Treat REPLACE as JSON text */ /* A single node of parsed JSON @@ -105,6 +108,7 @@ struct JsonParse { const char *zJson; /* Original JSON string */ u32 *aUp; /* Index of parent of each node */ u8 oom; /* Set to true if out of memory */ + u8 nErr; /* Number of errors seen */ }; /************************************************************************** @@ -238,7 +242,8 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){ */ static void jsonAppendValue( JsonString *p, /* Append to this JSON string */ - sqlite3_value *pValue /* Value to append */ + sqlite3_value *pValue, /* Value to append */ + u8 textIsJson /* Try to treat text values as JSON */ ){ switch( sqlite3_value_type(pValue) ){ case SQLITE_NULL: { @@ -255,7 +260,11 @@ static void jsonAppendValue( case SQLITE_TEXT: { const char *z = (const char*)sqlite3_value_text(pValue); u32 n = (u32)sqlite3_value_bytes(pValue); - jsonAppendString(p, z, n); + if( textIsJson ){ + jsonAppendRaw(p, z, n); + }else{ + jsonAppendString(p, z, n); + } break; } default: { @@ -355,7 +364,8 @@ static void jsonRenderNode( if( pNode[j].jnFlags & (JNODE_REMOVE|JNODE_REPLACE) ){ if( pNode[j].jnFlags & JNODE_REPLACE ){ jsonAppendSeparator(pOut); - jsonAppendValue(pOut, aReplace[pNode[j].iVal]); + jsonAppendValue(pOut, aReplace[pNode[j].iVal], + (pNode[j].jnFlags & JNODE_JSON)!=0); } }else{ jsonAppendSeparator(pOut); @@ -380,7 +390,8 @@ static void jsonRenderNode( jsonRenderNode(&pNode[j], pOut, aReplace); jsonAppendChar(pOut, ':'); if( pNode[j+1].jnFlags & JNODE_REPLACE ){ - jsonAppendValue(pOut, aReplace[pNode[j+1].iVal]); + jsonAppendValue(pOut, aReplace[pNode[j+1].iVal], + (pNode[j+1].jnFlags & JNODE_JSON)!=0); }else{ jsonRenderNode(&pNode[j+1], pOut, aReplace); } @@ -398,6 +409,20 @@ static void jsonRenderNode( } /* +** Return a JsonNode and all its descendents as a JSON string. +*/ +static void jsonReturnJson( + JsonNode *pNode, /* Node to return */ + sqlite3_context *pCtx, /* Return value for this function */ + sqlite3_value **aReplace /* Array of replacement values */ +){ + JsonString s; + jsonInit(&s, pCtx); + jsonRenderNode(pNode, &s, aReplace); + jsonResult(&s); +} + +/* ** Make the JsonNode the return value of the function. */ static void jsonReturn( @@ -501,10 +526,7 @@ static void jsonReturn( } case JSON_ARRAY: case JSON_OBJECT: { - JsonString s; - jsonInit(&s, pCtx); - jsonRenderNode(pNode, &s, aReplace); - jsonResult(&s); + jsonReturnJson(pNode, pCtx, aReplace); break; } } @@ -668,7 +690,7 @@ static int jsonParseValue(JsonParse *pParse, u32 i){ j++; c = pParse->zJson[j+1]; } - if( c<'0' || c>'0' ) return -1; + if( c<'0' || c>'9' ) return -1; continue; } break; @@ -708,8 +730,14 @@ static int jsonParse( while( isspace(zJson[i]) ) i++; if( zJson[i] ) i = -1; } - if( i<0 ){ - if( pParse->oom && pCtx!=0 ) sqlite3_result_error_nomem(pCtx); + if( i<=0 ){ + if( pCtx!=0 ){ + if( pParse->oom ){ + sqlite3_result_error_nomem(pCtx); + }else{ + sqlite3_result_error(pCtx, "malformed JSON", -1); + } + } jsonParseReset(pParse); return 1; } @@ -759,7 +787,7 @@ static int jsonParseFindParents(JsonParse *pParse){ } /* forward declaration */ -static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*); +static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**); /* ** Search along zPath to find the node specified. Return a pointer @@ -770,11 +798,12 @@ static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*); ** possible to do so and if no existing node corresponds to zPath. If ** new nodes are appended *pApnd is set to 1. */ -static JsonNode *jsonLookup( +static JsonNode *jsonLookupStep( JsonParse *pParse, /* The JSON to search */ u32 iRoot, /* Begin the search at this node */ const char *zPath, /* The path to search */ - int *pApnd /* Append nodes to complete path if not NULL */ + int *pApnd, /* Append nodes to complete path if not NULL */ + const char **pzErr /* Make *pzErr point to any syntax error in zPath */ ){ u32 i, j, nKey; const char *zKey; @@ -793,14 +822,17 @@ static JsonNode *jsonLookup( for(i=0; zPath[i] && zPath[i]!='.' && zPath[i]!='['; i++){} nKey = i; } - if( nKey==0 ) return 0; + if( nKey==0 ){ + *pzErr = zPath; + return 0; + } j = 1; for(;;){ while( j<=pRoot->n ){ if( pRoot[j].n==nKey+2 && strncmp(&pRoot[j].u.zJContent[1],zKey,nKey)==0 ){ - return jsonLookup(pParse, iRoot+j+1, &zPath[i], pApnd); + return jsonLookupStep(pParse, iRoot+j+1, &zPath[i], pApnd, pzErr); } j++; j += jsonNodeSize(&pRoot[j]); @@ -816,7 +848,7 @@ static JsonNode *jsonLookup( iStart = jsonParseAddNode(pParse, JSON_OBJECT, 2, 0); iLabel = jsonParseAddNode(pParse, JSON_STRING, i, zPath); zPath += i; - pNode = jsonLookupAppend(pParse, zPath, pApnd); + pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; @@ -834,7 +866,10 @@ static JsonNode *jsonLookup( i = i*10 + zPath[0] - '0'; zPath++; } - if( zPath[0]!=']' ) return 0; + if( zPath[0]!=']' ){ + *pzErr = zPath; + return 0; + } zPath++; j = 1; for(;;){ @@ -848,13 +883,13 @@ static JsonNode *jsonLookup( j = 1; } if( j<=pRoot->n ){ - return jsonLookup(pParse, iRoot+j, zPath, pApnd); + return jsonLookupStep(pParse, iRoot+j, zPath, pApnd, pzErr); } if( i==0 && pApnd ){ u32 iStart; JsonNode *pNode; iStart = jsonParseAddNode(pParse, JSON_ARRAY, 1, 0); - pNode = jsonLookupAppend(pParse, zPath, pApnd); + pNode = jsonLookupAppend(pParse, zPath, pApnd, pzErr); if( pParse->oom ) return 0; if( pNode ){ pRoot = &pParse->aNode[iRoot]; @@ -863,6 +898,8 @@ static JsonNode *jsonLookup( } return pNode; } + }else if( zPath[0]!=0 ){ + *pzErr = zPath; } return 0; } @@ -874,7 +911,8 @@ static JsonNode *jsonLookup( static JsonNode *jsonLookupAppend( JsonParse *pParse, /* Append content to the JSON parse */ const char *zPath, /* Description of content to append */ - int *pApnd /* Set this flag to 1 */ + int *pApnd, /* Set this flag to 1 */ + const char **pzErr /* Make this point to any syntax error */ ){ *pApnd = 1; if( zPath[0]==0 ){ @@ -889,9 +927,76 @@ static JsonNode *jsonLookupAppend( return 0; } if( pParse->oom ) return 0; - return jsonLookup(pParse, pParse->nNode-1, zPath, pApnd); + return jsonLookupStep(pParse, pParse->nNode-1, zPath, pApnd, pzErr); +} + +/* +** Return the text of a syntax error message on a JSON path. Space is +** obtained from sqlite3_malloc(). +*/ +static char *jsonPathSyntaxError(const char *zErr){ + return sqlite3_mprintf("JSON path error near '%q'", zErr); +} + +/* +** Do a node lookup using zPath. Return a pointer to the node on success. +** Return NULL if not found or if there is an error. +** +** On an error, write an error message into pCtx and increment the +** pParse->nErr counter. +** +** If pApnd!=NULL then try to append missing nodes and set *pApnd = 1 if +** nodes are appended. +** +** If the path starts with $$ then set *pFlags to JNODE_REPLACE|JNODE_JSON +** as a single to the caller that the input text to be inserted should be +** interpreted as JSON rather than as ordinary text. +*/ +static JsonNode *jsonLookup( + JsonParse *pParse, /* The JSON to search */ + const char *zPath, /* The path to search */ + int *pApnd, /* Append nodes to complete path if not NULL */ + sqlite3_context *pCtx, /* Report errors here, if not NULL */ + u8 *pFlags /* Write JNODE_REPLACE or _REPLACE|_JSON here */ +){ + const char *zErr = 0; + JsonNode *pNode = 0; + u8 fg = JNODE_REPLACE; + + if( zPath==0 ) return 0; + if( zPath[0]!='$' ){ + zErr = zPath; + goto lookup_err; + } + zPath++; + if( zPath[0]=='$' ){ + if( pFlags==0 ){ + zErr = zPath; + goto lookup_err; + } + zPath++; + fg = JNODE_REPLACE|JNODE_JSON; + } + if( pFlags ) *pFlags = fg; + pNode = jsonLookupStep(pParse, 0, zPath, pApnd, &zErr); + return pNode; + +lookup_err: + pParse->nErr++; + if( zErr!=0 && pCtx!=0 ){ + char *z = jsonPathSyntaxError(zErr); + if( z ){ + sqlite3_result_error(pCtx, z, -1); + sqlite3_free(z); + }else{ + sqlite3_result_error_nomem(pCtx); + } + } + if( pFlags ) *pFlags = fg; + return 0; } + /* ** Report the wrong number of arguments for json_insert(), json_replace() ** or json_set(). @@ -906,6 +1011,7 @@ static void jsonWrongNumArgs( sqlite3_free(zMsg); } + /**************************************************************************** ** SQL functions used for testing and debugging ****************************************************************************/ @@ -952,7 +1058,7 @@ static void jsonTest1Func( ){ JsonParse x; /* The parse */ if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - jsonReturn(x.aNode, ctx, 0); + jsonReturnJson(x.aNode, ctx, 0); jsonParseReset(&x); } @@ -993,7 +1099,7 @@ static void jsonArrayFunc( jsonAppendChar(&jx, '['); for(i=0; i<argc; i++){ jsonAppendSeparator(&jx); - jsonAppendValue(&jx, argv[i]); + jsonAppendValue(&jx, argv[i], 0); } jsonAppendChar(&jx, ']'); jsonResult(&jx); @@ -1015,37 +1121,36 @@ static void jsonArrayLengthFunc( JsonParse x; /* The parse */ sqlite3_int64 n = 0; u32 i; - const char *zPath; - if( argc==2 ){ - zPath = (const char*)sqlite3_value_text(argv[1]); - if( zPath==0 ) return; - if( zPath[0]!='$' ) return; - zPath++; - }else{ - zPath = 0; - } - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0]))==0 ){ - if( x.nNode ){ - JsonNode *pNode = x.aNode; - if( zPath ) pNode = jsonLookup(&x, 0, zPath, 0); - if( pNode->eType==JSON_ARRAY ){ - assert( (pNode->jnFlags & JNODE_APPEND)==0 ); - for(i=1; i<=pNode->n; n++){ - i += jsonNodeSize(&pNode[i]); - } + if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; + if( x.nNode ){ + JsonNode *pNode; + if( argc==2 ){ + const char *zPath = (const char*)sqlite3_value_text(argv[1]); + pNode = jsonLookup(&x, zPath, 0, ctx, 0); + }else{ + pNode = x.aNode; + } + if( pNode==0 ){ + x.nErr = 1; + }else if( pNode->eType==JSON_ARRAY ){ + assert( (pNode->jnFlags & JNODE_APPEND)==0 ); + for(i=1; i<=pNode->n; n++){ + i += jsonNodeSize(&pNode[i]); } } - jsonParseReset(&x); } - if( !x.oom ) sqlite3_result_int64(ctx, n); + if( x.nErr==0 ) sqlite3_result_int64(ctx, n); + jsonParseReset(&x); } /* -** json_extract(JSON, PATH) +** json_extract(JSON, PATH, ...) ** -** Return the element described by PATH. Return NULL if JSON is not -** valid JSON or if there is no PATH element or if PATH is malformed. +** Return the element described by PATH. Return NULL if there is no +** PATH element. If there are multiple PATHs, then return a JSON array +** with the result from each path. Throw an error if the JSON or any PATH +** is malformed. */ static void jsonExtractFunc( sqlite3_context *ctx, @@ -1055,16 +1160,33 @@ static void jsonExtractFunc( JsonParse x; /* The parse */ JsonNode *pNode; const char *zPath; - assert( argc==2 ); - zPath = (const char*)sqlite3_value_text(argv[1]); - if( zPath==0 ) return; - if( zPath[0]!='$' ) return; - zPath++; + JsonString jx; + int i; + + if( argc<2 ) return; if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; - pNode = jsonLookup(&x, 0, zPath, 0); - if( pNode ){ - jsonReturn(pNode, ctx, 0); + jsonInit(&jx, ctx); + jsonAppendChar(&jx, '['); + for(i=1; i<argc; i++){ + zPath = (const char*)sqlite3_value_text(argv[i]); + pNode = jsonLookup(&x, zPath, 0, ctx, 0); + if( x.nErr ) break; + if( argc>2 ){ + jsonAppendSeparator(&jx); + if( pNode ){ + jsonRenderNode(pNode, &jx, 0); + }else{ + jsonAppendRaw(&jx, "null", 4); + } + }else if( pNode ){ + jsonReturn(pNode, ctx, 0); + } + } + if( argc>2 && i==argc ){ + jsonAppendChar(&jx, ']'); + jsonResult(&jx); } + jsonReset(&jx); jsonParseReset(&x); } @@ -1101,7 +1223,7 @@ static void jsonObjectFunc( n = (u32)sqlite3_value_bytes(argv[i]); jsonAppendString(&jx, z, n); jsonAppendChar(&jx, ':'); - jsonAppendValue(&jx, argv[i+1]); + jsonAppendValue(&jx, argv[i+1], 0); } jsonAppendChar(&jx, '}'); jsonResult(&jx); @@ -1111,9 +1233,8 @@ static void jsonObjectFunc( /* ** json_remove(JSON, PATH, ...) ** -** Remove the named elements from JSON and return the result. Ill-formed -** PATH arguments are silently ignored. If JSON is ill-formed, then NULL -** is returned. +** Remove the named elements from JSON and return the result. malformed +** JSON or PATH arguments result in an error. */ static void jsonRemoveFunc( sqlite3_context *ctx, @@ -1130,15 +1251,16 @@ static void jsonRemoveFunc( if( x.nNode ){ for(i=1; i<(u32)argc; i++){ zPath = (const char*)sqlite3_value_text(argv[i]); - if( zPath==0 ) continue; - if( zPath[0]!='$' ) continue; - pNode = jsonLookup(&x, 0, &zPath[1], 0); + if( zPath==0 ) goto remove_done; + pNode = jsonLookup(&x, zPath, 0, ctx, 0); + if( x.nErr ) goto remove_done; if( pNode ) pNode->jnFlags |= JNODE_REMOVE; } if( (x.aNode[0].jnFlags & JNODE_REMOVE)==0 ){ - jsonReturn(x.aNode, ctx, 0); + jsonReturnJson(x.aNode, ctx, 0); } } +remove_done: jsonParseReset(&x); } @@ -1146,7 +1268,7 @@ static void jsonRemoveFunc( ** json_replace(JSON, PATH, VALUE, ...) ** ** Replace the value at PATH with VALUE. If PATH does not already exist, -** this routine is a no-op. If JSON is ill-formed, return NULL. +** this routine is a no-op. If JSON or PATH is malformed, throw an error. */ static void jsonReplaceFunc( sqlite3_context *ctx, @@ -1166,21 +1288,23 @@ static void jsonReplaceFunc( if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; if( x.nNode ){ for(i=1; i<(u32)argc; i+=2){ + u8 jnFlags = JNODE_REPLACE; zPath = (const char*)sqlite3_value_text(argv[i]); - if( zPath==0 ) continue; - if( zPath[0]!='$' ) continue; - pNode = jsonLookup(&x, 0, &zPath[1], 0); + pNode = jsonLookup(&x, zPath, 0, ctx, &jnFlags); + if( x.nErr ) goto replace_err; if( pNode ){ - pNode->jnFlags |= JNODE_REPLACE; + pNode->jnFlags &= ~JNODE_JSON; + pNode->jnFlags |= jnFlags; pNode->iVal = i+1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ sqlite3_result_value(ctx, argv[x.aNode[0].iVal]); }else{ - jsonReturn(x.aNode, ctx, argv); + jsonReturnJson(x.aNode, ctx, argv); } } +replace_err: jsonParseReset(&x); } @@ -1189,12 +1313,12 @@ static void jsonReplaceFunc( ** ** Set the value at PATH to VALUE. Create the PATH if it does not already ** exist. Overwrite existing values that do exist. -** If JSON is ill-formed, return NULL. +** If JSON or PATH is malformed, throw an error. ** ** json_insert(JSON, PATH, VALUE, ...) ** ** Create PATH and initialize it to VALUE. If PATH already exists, this -** routine is a no-op. If JSON is ill-formed, return NULL. +** routine is a no-op. If JSON or PATH is malformed, throw an error. */ static void jsonSetFunc( sqlite3_context *ctx, @@ -1216,23 +1340,25 @@ static void jsonSetFunc( if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; if( x.nNode ){ for(i=1; i<(u32)argc; i+=2){ + u8 jnFlags = JNODE_REPLACE; zPath = (const char*)sqlite3_value_text(argv[i]); - if( zPath==0 ) continue; - if( zPath[0]!='$' ) continue; bApnd = 0; - pNode = jsonLookup(&x, 0, &zPath[1], &bApnd); + pNode = jsonLookup(&x, zPath, &bApnd, ctx, &jnFlags); if( x.oom ){ sqlite3_result_error_nomem(ctx); goto jsonSetDone; + }else if( x.nErr ){ + goto jsonSetDone; }else if( pNode && (bApnd || bIsSet) ){ - pNode->jnFlags |= JNODE_REPLACE; + pNode->jnFlags &= ~JNODE_JSON; + pNode->jnFlags |= jnFlags; pNode->iVal = i+1; } } if( x.aNode[0].jnFlags & JNODE_REPLACE ){ sqlite3_result_value(ctx, argv[x.aNode[0].iVal]); }else{ - jsonReturn(x.aNode, ctx, argv); + jsonReturnJson(x.aNode, ctx, argv); } } jsonSetDone: @@ -1243,8 +1369,8 @@ jsonSetDone: ** json_type(JSON) ** json_type(JSON, PATH) ** -** Return the top-level "type" of a JSON string. Return NULL if the -** input is not a well-formed JSON string. +** Return the top-level "type" of a JSON string. Throw an error if +** either the JSON or PATH inputs are not well-formed. */ static void jsonTypeFunc( sqlite3_context *ctx, @@ -1254,18 +1380,15 @@ static void jsonTypeFunc( JsonParse x; /* The parse */ const char *zPath; - if( argc==2 ){ - zPath = (const char*)sqlite3_value_text(argv[1]); - if( zPath==0 ) return; - if( zPath[0]!='$' ) return; - zPath++; - }else{ - zPath = 0; - } if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0])) ) return; if( x.nNode ){ - JsonNode *pNode = x.aNode; - if( zPath ) pNode = jsonLookup(&x, 0, zPath, 0); + JsonNode *pNode; + if( argc==2 ){ + zPath = (const char*)sqlite3_value_text(argv[1]); + pNode = jsonLookup(&x, zPath, 0, ctx, 0); + }else{ + pNode = x.aNode; + } if( pNode ){ sqlite3_result_text(ctx, jsonType[pNode->eType], -1, SQLITE_STATIC); } @@ -1276,7 +1399,8 @@ static void jsonTypeFunc( /* ** json_valid(JSON) ** -** Return 1 if JSON is a valid JSON string. Return 0 otherwise. +** Return 1 if JSON is a well-formed JSON string according to RFC-7159. +** Return 0 otherwise. */ static void jsonValidFunc( sqlite3_context *ctx, @@ -1286,7 +1410,7 @@ static void jsonValidFunc( JsonParse x; /* The parse */ int rc = 0; - if( jsonParse(&x, ctx, (const char*)sqlite3_value_text(argv[0]))==0 + if( jsonParse(&x, 0, (const char*)sqlite3_value_text(argv[0]))==0 && x.nNode>0 ){ rc = 1; @@ -1295,6 +1419,7 @@ static void jsonValidFunc( sqlite3_result_int(ctx, rc); } +#ifndef SQLITE_OMIT_VIRTUALTABLE /**************************************************************************** ** The json_each virtual table ****************************************************************************/ @@ -1642,27 +1767,45 @@ static int jsonEachFilter( if( z==0 ) return SQLITE_OK; if( idxNum&2 ){ zPath = (const char*)sqlite3_value_text(argv[1]); - if( zPath==0 || zPath[0]!='$' ) return SQLITE_OK; + if( zPath==0 ) return SQLITE_OK; + if( zPath[0]!='$' ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = jsonPathSyntaxError(zPath); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; + } } n = sqlite3_value_bytes(argv[0]); p->zJson = sqlite3_malloc64( n+1 ); if( p->zJson==0 ) return SQLITE_NOMEM; memcpy(p->zJson, z, (size_t)n+1); - if( jsonParse(&p->sParse, 0, p->zJson) - || (p->bRecursive && jsonParseFindParents(&p->sParse)) - ){ + if( jsonParse(&p->sParse, 0, p->zJson) ){ + int rc = SQLITE_NOMEM; + if( p->sParse.oom==0 ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = sqlite3_mprintf("malformed JSON"); + if( cur->pVtab->zErrMsg ) rc = SQLITE_ERROR; + } + jsonEachCursorReset(p); + return rc; + }else if( p->bRecursive && jsonParseFindParents(&p->sParse) ){ jsonEachCursorReset(p); + return SQLITE_NOMEM; }else{ JsonNode *pNode; if( idxNum==3 ){ + const char *zErr = 0; p->bRecursive = 0; n = sqlite3_value_bytes(argv[1]); p->zPath = sqlite3_malloc64( n+1 ); if( p->zPath==0 ) return SQLITE_NOMEM; memcpy(p->zPath, zPath, (size_t)n+1); - pNode = jsonLookup(&p->sParse, 0, p->zPath+1, 0); - if( pNode==0 ){ + pNode = jsonLookupStep(&p->sParse, 0, p->zPath+1, 0, &zErr); + if( p->sParse.nErr ){ + sqlite3_free(cur->pVtab->zErrMsg); + cur->pVtab->zErrMsg = jsonPathSyntaxError(zErr); jsonEachCursorReset(p); + return cur->pVtab->zErrMsg ? SQLITE_ERROR : SQLITE_NOMEM; + }else if( pNode==0 ){ return SQLITE_OK; } }else{ @@ -1734,6 +1877,7 @@ static sqlite3_module jsonTreeModule = { 0, /* xRelease */ 0 /* xRollbackTo */ }; +#endif /* SQLITE_OMIT_VIRTUALTABLE */ /**************************************************************************** ** The following routine is the only publically visible identifier in this @@ -1760,7 +1904,7 @@ int sqlite3_json_init( { "json_array", -1, 0, jsonArrayFunc }, { "json_array_length", 1, 0, jsonArrayLengthFunc }, { "json_array_length", 2, 0, jsonArrayLengthFunc }, - { "json_extract", 2, 0, jsonExtractFunc }, + { "json_extract", -1, 0, jsonExtractFunc }, { "json_insert", -1, 0, jsonSetFunc }, { "json_object", -1, 0, jsonObjectFunc }, { "json_remove", -1, 0, jsonRemoveFunc }, @@ -1777,6 +1921,7 @@ int sqlite3_json_init( { "json_nodecount", 1, 0, jsonNodeCountFunc }, #endif }; +#ifndef SQLITE_OMIT_VIRTUALTABLE static const struct { const char *zName; sqlite3_module *pModule; @@ -1784,6 +1929,7 @@ int sqlite3_json_init( { "json_each", &jsonEachModule }, { "json_tree", &jsonTreeModule }, }; +#endif SQLITE_EXTENSION_INIT2(pApi); (void)pzErrMsg; /* Unused parameter */ for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){ @@ -1792,8 +1938,10 @@ int sqlite3_json_init( (void*)&aFunc[i].flag, aFunc[i].xFunc, 0, 0); } +#ifndef SQLITE_OMIT_VIRTUALTABLE for(i=0; i<sizeof(aMod)/sizeof(aMod[0]) && rc==SQLITE_OK; i++){ rc = sqlite3_create_module(db, aMod[i].zName, aMod[i].pModule, 0); } +#endif return rc; } diff --git a/ext/rbu/sqlite3rbu.c b/ext/rbu/sqlite3rbu.c index 7c7480bcf..d76d2f4cc 100644 --- a/ext/rbu/sqlite3rbu.c +++ b/ext/rbu/sqlite3rbu.c @@ -489,7 +489,7 @@ static int rbuDeltaApply( /* ERROR: copy exceeds output file size */ return -1; } - if( ofst+cnt > lenSrc ){ + if( (int)(ofst+cnt) > lenSrc ){ /* ERROR: copy extends past end of input */ return -1; } @@ -504,7 +504,7 @@ static int rbuDeltaApply( /* ERROR: insert command gives an output larger than predicted */ return -1; } - if( cnt>lenDelta ){ + if( (int)cnt>lenDelta ){ /* ERROR: insert count exceeds size of delta */ return -1; } @@ -1117,7 +1117,7 @@ static void rbuTableType( } rbuTableType_end: { - int i; + unsigned int i; for(i=0; i<sizeof(aStmt)/sizeof(aStmt[0]); i++){ rbuFinalize(p, aStmt[i]); } @@ -1530,7 +1530,7 @@ static char *rbuObjIterGetSetlist( if( p->rc==SQLITE_OK ){ int i; - if( strlen(zMask)!=pIter->nTblCol ){ + if( (int)strlen(zMask)!=pIter->nTblCol ){ rbuBadControlError(p); }else{ const char *zSep = ""; @@ -3680,7 +3680,8 @@ static int rbuVfsOpen( rbuVfsShmMap, /* xShmMap */ rbuVfsShmLock, /* xShmLock */ rbuVfsShmBarrier, /* xShmBarrier */ - rbuVfsShmUnmap /* xShmUnmap */ + rbuVfsShmUnmap, /* xShmUnmap */ + 0, 0 /* xFetch, xUnfetch */ }; rbu_vfs *pRbuVfs = (rbu_vfs*)pVfs; sqlite3_vfs *pRealVfs = pRbuVfs->pRealVfs; @@ -439,9 +439,9 @@ libsqlite3.a: $(LIBOBJ) $(AR) libsqlite3.a $(LIBOBJ) $(RANLIB) libsqlite3.a -sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h - $(TCCX) $(READLINE_FLAGS) -o sqlite3$(EXE) \ - $(TOP)/src/shell.c \ +sqlite3$(EXE): $(TOP)/src/shell.c libsqlite3.a sqlite3.h $(TOP)/ext/misc/json1.c + $(TCCX) $(READLINE_FLAGS) -DSQLITE_ENABLE_JSON1 -o sqlite3$(EXE) \ + $(TOP)/src/shell.c $(TOP)/ext/misc/json1.c \ libsqlite3.a $(LIBREADLINE) $(TLIBS) $(THREADLIB) sqldiff$(EXE): $(TOP)/tool/sqldiff.c sqlite3.c sqlite3.h @@ -1,9 +1,9 @@ -C Fix\sthe\sOR-optimization\sso\sthat\sit\salways\signores\ssubplans\sthat\sdo\snot\suse\san\sindex. -D 2015-08-27T23:42:43.629 +C Merge\sthe\slatest\senhancements\sfrom\strunk. +D 2015-08-31T14:27:29.928 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f -F Makefile.in e2218eb228374422969de7b1680eda6864affcef +F Makefile.in f85066ce844a28b671aaeeff320921cd0ce36239 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 -F Makefile.msc 10af19cc089862481d49b347acd99c02635ddc49 +F Makefile.msc b268d8be2e800b9d35f074b1ed6b2f698deebdd6 F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858 F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7 F VERSION ccfc4d1576dbfdeece0a4372a2e6a2e37d3e7975 @@ -192,7 +192,7 @@ F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2 F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f F ext/misc/fuzzer.c 4c84635c71c26cfa7c2e5848cf49fe2d2cfcd767 F ext/misc/ieee754.c b0362167289170627659e84173f5d2e8fee8566e -F ext/misc/json1.c 541004e47235cefc2843ab03c100517452931913 +F ext/misc/json1.c bd51e8c1e8ce580e6f21493bd8e94ed5aca3d777 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc @@ -226,7 +226,7 @@ F ext/rbu/rbufault.test cc0be8d5d392d98b0c2d6a51be377ea989250a89 F ext/rbu/rbufault2.test 9a7f19edd6ea35c4c9f807d8a3db0a03a5670c06 F ext/rbu/rbufts.test 828cd689da825f0a7b7c53ffc1f6f7fdb6fa5bda F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48 -F ext/rbu/sqlite3rbu.c 1650e682b3568db0ed97ff2c7ba5d1c8ea060a84 +F ext/rbu/sqlite3rbu.c 4ba82bd850aa012f73c31dd242d570f18c9cc35a F ext/rbu/sqlite3rbu.h 5357f070cd8c0bcad459b620651ec1656859e4d0 F ext/rbu/test_rbu.c 2a3652241fa45d5eaa141775e4ae68c1d3582c03 F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761 @@ -258,7 +258,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60 -F main.mk 04840e8277ab5159af16172eafd214dae7cffff5 +F main.mk 8da13ed011a7ae19450b7554910ff4afa3bd22b7 F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83 @@ -282,7 +282,7 @@ F src/btmutex.c 45a968cc85afed9b5e6cf55bf1f42f8d18107f79 F src/btree.c f48b3ef91676c06a90a8832987ecef6b94c931ee F src/btree.h 969adc948e89e449220ff0ff724c94bb2a52e9f1 F src/btreeInt.h 8177c9ab90d772d6d2c6c517e05bed774b7c92c0 -F src/build.c 6b7f6ccacd9cbd113f1948b4268cb81a87ee513a +F src/build.c e0902658fc86dbd60a5c6772ca45429c69ee81fe F src/callback.c 7b44ce59674338ad48b0e84e7b72f935ea4f68b0 F src/complete.c addcd8160b081131005d5bc2d34adf20c1c5c92f F src/ctime.c 5a0b735dc95604766f5dac73973658eef782ee8b @@ -338,7 +338,7 @@ F src/random.c ba2679f80ec82c4190062d756f22d0c358180696 F src/resolve.c e6dc5a5490cf93afc1cc2cb58280c98da56acb3c F src/rowset.c eccf6af6d620aaa4579bd3b72c1b6395d9e9fa1e F src/select.c b52c80f2b1bdb62491f9ce40eea0c5f80c78d105 -F src/shell.c 5a08835e85c502978bde35a89d4045833f772876 +F src/shell.c 6332ef06db1390ef812cfdff1fc97b4fd76cdd42 F src/sqlite.h.in 378bebc8fe6a88bade25e5f23b7e6123fdc64b00 F src/sqlite3.rc 992c9f5fb8285ae285d6be28240a7e8d3a7f2bad F src/sqlite3ext.h f700e6a9dd1fdcccc9951ab022b366fb66b9e413 @@ -411,7 +411,7 @@ F src/vdbesort.c f5009e7a35e3065635d8918b9a31f498a499976b F src/vdbetrace.c 8befe829faff6d9e6f6e4dee5a7d3f85cc85f1a0 F src/vtab.c d31174e4c8f592febab3fa7f69e18320b4fd657a F src/vxworks.h c18586c8edc1bddbc15c004fa16aeb1e1342b4fb -F src/wal.c 6fb6b68969e4692593c2552c4e7bff5882de2cb8 +F src/wal.c 8cd07f1f99e1a81346db1c9da879bef6c6f97cf6 F src/wal.h df01efe09c5cb8c8e391ff1715cca294f89668a4 F src/walker.c 2e14d17f592d176b6dc879c33fbdec4fbccaa2ba F src/where.c acec45dc602a4f58e80e6fa088b9379ccfffd3a4 @@ -810,7 +810,7 @@ F test/journal3.test ff8af941f9e06161d3db1b46bb9f965ff0e7f307 F test/jrnlmode.test 7864d59cf7f6e552b9b99ba0f38acd167edc10fa F test/jrnlmode2.test 81610545a4e6ed239ea8fa661891893385e23a1d F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa -F test/json101.test 950ed4e8deb8ad4c10bd4fbc858eb54143de9867 +F test/json101.test 11535d8986184500f4c30cc2f0b154b4ab05cc4e F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200 @@ -942,7 +942,7 @@ F test/rollback2.test fc14cf6d1a2b250d2735ef16124b971bce152f14 F test/rollbackfault.test 6a004f71087cc399296cffbb5429ea6da655ae65 F test/rowallock.test 3f88ec6819489d0b2341c7a7528ae17c053ab7cc F test/rowhash.test 0bc1d31415e4575d10cacf31e1a66b5cc0f8be81 -F test/rowid.test 09fcded0c96fbc0ed11fb75faa3b0bad32cb011a +F test/rowid.test 5b7509f384f4f6fae1af3c8c104c8ca299fea18d F test/rtree.test 0c8d9dd458d6824e59683c19ab2ffa9ef946f798 F test/run-wordcount.sh 891e89c4c2d16e629cd45951d4ed899ad12afc09 F test/savepoint.test c671fdbd34cd3bfe1518a777526ada595180cf8d @@ -1264,7 +1264,7 @@ F test/wal2.test 1f841d2048080d32f552942e333fd99ce541dada F test/wal3.test 2b5445e5da44780b9b44712f5a38523f7aeb0941 F test/wal4.test 4744e155cd6299c6bd99d3eab1c82f77db9cdb3c F test/wal5.test 88b5d9a6a3d1532497ee9f4296f010d66f07e33c -F test/wal6.test 527581f5527bf9c24394991e2be83000aace5f9e +F test/wal6.test 4421cd5a2fa99d29cc91ef12fb23bed171ed3a4c F test/wal64k.test 163655ecd2cb8afef4737cac2a40fdd2eeaf20b8 F test/wal7.test 2ae8f427d240099cc4b2dfef63cff44e2a68a1bd F test/wal8.test 75c42e1bc4545c277fed212f8fc9b7723cd02216 @@ -1380,7 +1380,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 73d361ce9e4d72c943def8b0b3caa227f9199aed 66f92a16866e5825363636b9cc4b8f9b29d9e84d -R b576dddb4c7e794820789b114785ae51 +P cf452028d1be2c5578a07f6e21b4d8b613373eb8 1da60c3dda4254620052a83c853c2d2b6dd5009f +R 64522485917d5b93eb19dc63143d2895 U drh -Z e54726d52fd301793beed6904550b28b +Z 590b51e0f03c976eccd82950be2fb3fc diff --git a/manifest.uuid b/manifest.uuid index 7a8a35cd8..a7185b179 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -cf452028d1be2c5578a07f6e21b4d8b613373eb8
\ No newline at end of file +7bde6d4d8cf05e1beb9bdf20b85760dc3e7a76c9
\ No newline at end of file diff --git a/src/build.c b/src/build.c index 93fc33108..59c2a7302 100644 --- a/src/build.c +++ b/src/build.c @@ -356,7 +356,7 @@ Table *sqlite3LocateTable( p = sqlite3FindTable(pParse->db, zName, zDbase); if( p==0 ){ const char *zMsg = isView ? "no such view" : "no such table"; -#ifndef SQLITE_OMIT_VIRTUAL_TABLE +#ifndef SQLITE_OMIT_VIRTUALTABLE /* If zName is the not the name of a table in the schema created using ** CREATE, then check to see if it is the name of an virtual table that ** can be an eponymous virtual table. */ diff --git a/src/shell.c b/src/shell.c index da7f78e10..e5eb394ea 100644 --- a/src/shell.c +++ b/src/shell.c @@ -4619,6 +4619,13 @@ int SQLITE_CDECL main(int argc, char **argv){ } data.out = stdout; +#ifdef SQLITE_ENABLE_JSON1 + { + extern int sqlite3_json_init(sqlite3*); + sqlite3_auto_extension((void(*)(void))sqlite3_json_init); + } +#endif + /* Go ahead and open the database file if it already exists. If the ** file does not exist, delay opening it. This prevents empty database ** files from being created if a user mistypes the database name argument @@ -428,6 +428,7 @@ struct Wal { u8 syncHeader; /* Fsync the WAL header if true */ u8 padToSectorBoundary; /* Pad transactions out to the next sector */ WalIndexHdr hdr; /* Wal-index header for current transaction */ + u32 minFrame; /* Ignore wal frames before this one */ const char *zWalName; /* Name of WAL file */ u32 nCkpt; /* Checkpoint sequence counter in the wal-header */ #ifdef SQLITE_DEBUG @@ -2296,12 +2297,27 @@ static int walTryBeginRead(Wal *pWal, int *pChanged, int useWal, int cnt){ ** pWal->hdr.mxFrame risks reading a corrupted snapshot. So, retry ** instead. ** - ** This does not guarantee that the copy of the wal-index header is up to - ** date before proceeding. That would not be possible without somehow - ** blocking writers. It only guarantees that a dangerous checkpoint or - ** log-wrap (either of which would require an exclusive lock on - ** WAL_READ_LOCK(mxI)) has not occurred since the snapshot was valid. + ** Before checking that the live wal-index header has not changed + ** since it was read, set Wal.minFrame to the first frame in the wal + ** file that has not yet been checkpointed. This client will not need + ** to read any frames earlier than minFrame from the wal file - they + ** can be safely read directly from the database file. + ** + ** Because a ShmBarrier() call is made between taking the copy of + ** nBackfill and checking that the wal-header in shared-memory still + ** matches the one cached in pWal->hdr, it is guaranteed that the + ** checkpointer that set nBackfill was not working with a wal-index + ** header newer than that cached in pWal->hdr. If it were, that could + ** cause a problem. The checkpointer could omit to checkpoint + ** a version of page X that lies before pWal->minFrame (call that version + ** A) on the basis that there is a newer version (version B) of the same + ** page later in the wal file. But if version B happens to like past + ** frame pWal->hdr.mxFrame - then the client would incorrectly assume + ** that it can read version A from the database file. However, since + ** we can guarantee that the checkpointer that set nBackfill could not + ** see any pages past pWal->hdr.mxFrame, this problem does not come up. */ + pWal->minFrame = pInfo->nBackfill+1; walShmBarrier(pWal); if( pInfo->aReadMark[mxI]!=mxReadMark || memcmp((void *)walIndexHdr(pWal), &pWal->hdr, sizeof(WalIndexHdr)) @@ -2372,6 +2388,7 @@ int sqlite3WalFindFrame( u32 iRead = 0; /* If !=0, WAL frame to return data from */ u32 iLast = pWal->hdr.mxFrame; /* Last page in WAL for this reader */ int iHash; /* Used to loop through N hash tables */ + int iMinHash; /* This routine is only be called from within a read transaction. */ assert( pWal->readLock>=0 || pWal->lockError ); @@ -2412,7 +2429,8 @@ int sqlite3WalFindFrame( ** This condition filters out entries that were added to the hash ** table after the current read-transaction had started. */ - for(iHash=walFramePage(iLast); iHash>=0 && iRead==0; iHash--){ + iMinHash = walFramePage(pWal->minFrame); + for(iHash=walFramePage(iLast); iHash>=iMinHash && iRead==0; iHash--){ volatile ht_slot *aHash; /* Pointer to hash table */ volatile u32 *aPgno; /* Pointer to array of page numbers */ u32 iZero; /* Frame number corresponding to aPgno[0] */ @@ -2427,7 +2445,7 @@ int sqlite3WalFindFrame( nCollide = HASHTABLE_NSLOT; for(iKey=walHash(pgno); aHash[iKey]; iKey=walNextHash(iKey)){ u32 iFrame = aHash[iKey] + iZero; - if( iFrame<=iLast && aPgno[aHash[iKey]]==pgno ){ + if( iFrame<=iLast && iFrame>=pWal->minFrame && aPgno[aHash[iKey]]==pgno ){ assert( iFrame>iRead || CORRUPT_DB ); iRead = iFrame; } diff --git a/test/json101.test b/test/json101.test index 1d788a6be..1a84a5fc5 100644 --- a/test/json101.test +++ b/test/json101.test @@ -50,5 +50,246 @@ do_catchsql_test json1-2.4 { SELECT json_object('a',1,'b',x'abcd'); } {1 {JSON cannot hold BLOB values}} +do_execsql_test json1-3.1 { + SELECT json_replace('{"a":1,"b":2}','$.a','[3,4,5]'); +} {{{"a":"[3,4,5]","b":2}}} +do_execsql_test json1-3.2 { + SELECT json_replace('{"a":1,"b":2}','$$.a','[3,4,5]'); +} {{{"a":[3,4,5],"b":2}}} +do_execsql_test json1-3.3 { + SELECT json_type(json_set('{"a":1,"b":2}','$.b','{"x":3,"y":4}'),'$.b'); +} {text} +do_execsql_test json1-3.4 { + SELECT json_type(json_set('{"a":1,"b":2}','$$.b','{"x":3,"y":4}'),'$.b'); +} {object} + +# Per rfc7159, any JSON value is allowed at the top level, and whitespace +# is permitting before and/or after that value. +# +do_execsql_test json1-4.1 { + CREATE TABLE j1(x); + INSERT INTO j1(x) + VALUES('true'),('false'),('null'),('123'),('-234'),('34.5e+6'), + ('""'),('"\""'),('"\\"'),('"abcdefghijlmnopqrstuvwxyz"'), + ('[]'),('{}'),('[true,false,null,123,-234,34.5e+6,{},[]]'), + ('{"a":true,"b":{"c":false}}'); + SELECT * FROM j1 WHERE NOT json_valid(x); +} {} +do_execsql_test json1-4.2 { + SELECT * FROM j1 WHERE NOT json_valid(char(0x20,0x09,0x0a,0x0d)||x); +} {} +do_execsql_test json1-4.3 { + SELECT * FROM j1 WHERE NOT json_valid(x||char(0x20,0x09,0x0a,0x0d)); +} {} + +# But an empty string, or a string of pure whitespace is not valid JSON. +# +do_execsql_test json1-4.4 { + SELECT json_valid(''), json_valid(char(0x20,0x09,0x0a,0x0d)); +} {0 0} + +# json_remove() and similar functions with no edit operations return their +# input unchanged. +# +do_execsql_test json1-4.5 { + SELECT x FROM j1 WHERE json_remove(x)<>x; +} {} +do_execsql_test json1-4.6 { + SELECT x FROM j1 WHERE json_replace(x)<>x; +} {} +do_execsql_test json1-4.7 { + SELECT x FROM j1 WHERE json_set(x)<>x; +} {} +do_execsql_test json1-4.8 { + SELECT x FROM j1 WHERE json_insert(x)<>x; +} {} + +# json_extract(JSON,'$') will return objects and arrays without change. +# +do_execsql_test json-4.10 { + SELECT count(*) FROM j1 WHERE json_type(x) IN ('object','array'); + SELECT x FROM j1 + WHERE json_extract(x,'$')<>x + AND json_type(x) IN ('object','array'); +} {4} + +do_execsql_test json-5.1 { + CREATE TABLE j2(id INTEGER PRIMARY KEY, json, src); + INSERT INTO j2(id,json,src) + VALUES(1,'{ + "firstName": "John", + "lastName": "Smith", + "isAlive": true, + "age": 25, + "address": { + "streetAddress": "21 2nd Street", + "city": "New York", + "state": "NY", + "postalCode": "10021-3100" + }, + "phoneNumbers": [ + { + "type": "home", + "number": "212 555-1234" + }, + { + "type": "office", + "number": "646 555-4567" + } + ], + "children": [], + "spouse": null + }','https://en.wikipedia.org/wiki/JSON'); + INSERT INTO j2(id,json,src) + VALUES(2, '{ + "id": "0001", + "type": "donut", + "name": "Cake", + "ppu": 0.55, + "batters": + { + "batter": + [ + { "id": "1001", "type": "Regular" }, + { "id": "1002", "type": "Chocolate" }, + { "id": "1003", "type": "Blueberry" }, + { "id": "1004", "type": "Devil''s Food" } + ] + }, + "topping": + [ + { "id": "5001", "type": "None" }, + { "id": "5002", "type": "Glazed" }, + { "id": "5005", "type": "Sugar" }, + { "id": "5007", "type": "Powdered Sugar" }, + { "id": "5006", "type": "Chocolate with Sprinkles" }, + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] + }','https://adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html'); + INSERT INTO j2(id,json,src) + VALUES(3,'[ + { + "id": "0001", + "type": "donut", + "name": "Cake", + "ppu": 0.55, + "batters": + { + "batter": + [ + { "id": "1001", "type": "Regular" }, + { "id": "1002", "type": "Chocolate" }, + { "id": "1003", "type": "Blueberry" }, + { "id": "1004", "type": "Devil''s Food" } + ] + }, + "topping": + [ + { "id": "5001", "type": "None" }, + { "id": "5002", "type": "Glazed" }, + { "id": "5005", "type": "Sugar" }, + { "id": "5007", "type": "Powdered Sugar" }, + { "id": "5006", "type": "Chocolate with Sprinkles" }, + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] + }, + { + "id": "0002", + "type": "donut", + "name": "Raised", + "ppu": 0.55, + "batters": + { + "batter": + [ + { "id": "1001", "type": "Regular" } + ] + }, + "topping": + [ + { "id": "5001", "type": "None" }, + { "id": "5002", "type": "Glazed" }, + { "id": "5005", "type": "Sugar" }, + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] + }, + { + "id": "0003", + "type": "donut", + "name": "Old Fashioned", + "ppu": 0.55, + "batters": + { + "batter": + [ + { "id": "1001", "type": "Regular" }, + { "id": "1002", "type": "Chocolate" } + ] + }, + "topping": + [ + { "id": "5001", "type": "None" }, + { "id": "5002", "type": "Glazed" }, + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] + } + ]','https://adobe.github.io/Spry/samples/data_region/JSONDataSetSample.html'); + SELECT count(*) FROM j2; +} {3} + +do_execsql_test json-5.2 { + SELECT id, json_valid(json), json_type(json), '|' FROM j2 ORDER BY id; +} {1 1 object | 2 1 object | 3 1 array |} + +ifcapable !vtab { + finish_test + return +} + +# fullkey is always the same as path+key (with appropriate formatting) +# +do_execsql_test json-5.3 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_tree(j2.json) AS jx + WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' + ELSE '.'||key END); +} {} +do_execsql_test json-5.4 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_each(j2.json) AS jx + WHERE fullkey!=(path || CASE WHEN typeof(key)=='integer' THEN '['||key||']' + ELSE '.'||key END); +} {} + + +# Verify that the json_each.json and json_tree.json output is always the +# same as input. +# +do_execsql_test json-5.5 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_each(j2.json) AS jx + WHERE jx.json<>j2.json; +} {} +do_execsql_test json-5.6 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_tree(j2.json) AS jx + WHERE jx.json<>j2.json; +} {} +do_execsql_test json-5.7 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_each(j2.json) AS jx + WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); +} {} +do_execsql_test json-5.8 { + SELECT j2.rowid, jx.rowid, fullkey, path, key + FROM j2, json_tree(j2.json) AS jx + WHERE jx.value<>jx.atom AND type NOT IN ('array','object'); +} {} + + finish_test diff --git a/test/rowid.test b/test/rowid.test index 21696f683..56336453f 100644 --- a/test/rowid.test +++ b/test/rowid.test @@ -144,7 +144,8 @@ do_test rowid-2.8 { execsql {SELECT x FROM t1 ORDER BY x} } {1 3 5 7 9} -if 0 { # we can now.... +if 0 { # With the index-on-expressions enhancement, creating + # an index on ROWID has become possible. # We cannot index by ROWID # do_test rowid-2.9 { diff --git a/test/wal6.test b/test/wal6.test index 2498907ea..d96166ef5 100644 --- a/test/wal6.test +++ b/test/wal6.test @@ -193,5 +193,47 @@ do_test 3.x { db2 close } {} +#------------------------------------------------------------------------- +# Check that if a wal file has been partially checkpointed, no frames are +# read from the checkpointed part. +# +reset_db +do_execsql_test 4.1 { + PRAGMA page_size = 1024; + PRAGMA journal_mode = wal; + CREATE TABLE t1(a, b); + CREATE TABLE t2(a, b); + PRAGMA wal_checkpoint = truncate; +} {wal 0 0 0} + +do_test 4.2 { + execsql { INSERT INTO t1 VALUES(1, 2) } + file size test.db-wal +} [wal_file_size 1 1024] + +do_test 4.3 { + sqlite3 db2 test.db + execsql { + BEGIN; + INSERT INTO t2 VALUES(3, 4); + } + execsql { PRAGMA wal_checkpoint = passive } db2 +} {0 1 1} + +do_test 4.3 { + execsql { COMMIT } + db2 close + hexio_write test.db-wal 0 [string repeat 00 2000] + sqlite3 db2 test.db +} {} + +do_test 4.4.1 { + catchsql { SELECT * FROM t1 } db2 +} {0 {1 2}} +do_test 4.4.2 { + catchsql { SELECT * FROM t2 } db2 +} {1 {database disk image is malformed}} + + finish_test |