aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile.in6
-rw-r--r--Makefile.msc8
-rw-r--r--ext/misc/json1.c342
-rw-r--r--ext/rbu/sqlite3rbu.c11
-rw-r--r--main.mk6
-rw-r--r--manifest32
-rw-r--r--manifest.uuid2
-rw-r--r--src/build.c2
-rw-r--r--src/shell.c7
-rw-r--r--src/wal.c32
-rw-r--r--test/json101.test241
-rw-r--r--test/rowid.test3
-rw-r--r--test/wal6.test42
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;
diff --git a/main.mk b/main.mk
index 9988fe277..7cc09d6c8 100644
--- a/main.mk
+++ b/main.mk
@@ -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
diff --git a/manifest b/manifest
index aab422faa..561200305 100644
--- a/manifest
+++ b/manifest
@@ -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
diff --git a/src/wal.c b/src/wal.c
index f7e259400..cf8f1d4e6 100644
--- a/src/wal.c
+++ b/src/wal.c
@@ -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