aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordrh <>2023-05-01 18:28:48 +0000
committerdrh <>2023-05-01 18:28:48 +0000
commitabdcfcef83991c6dba82befbc6683441ffb6619f (patch)
tree73d5116978e22b40e44bbe00d91b2358408ab278
parent5ecee3dae30dc88248f501868dca975582a77f42 (diff)
parente210c9390321379f9a5c0b7a205e67128ab12192 (diff)
downloadsqlite-abdcfcef83991c6dba82befbc6683441ffb6619f.tar.gz
sqlite-abdcfcef83991c6dba82befbc6683441ffb6619f.zip
Add support for JSON5.
FossilOrigin-Name: f8c3ed23a6931b1da3b93b3274b132387078112a5c8e8d06b5312c47987d3937
-rw-r--r--ext/misc/randomjson.c202
-rw-r--r--manifest34
-rw-r--r--manifest.uuid2
-rw-r--r--src/global.c2
-rw-r--r--src/json.c975
-rw-r--r--src/sqliteInt.h4
-rw-r--r--src/util.c4
-rw-r--r--test/json/README.md27
-rw-r--r--test/json/json-generator.tcl401
-rw-r--r--test/json/json-q1.txt4
-rwxr-xr-xtest/json/json-speed-check.sh80
-rw-r--r--test/json101.test27
-rw-r--r--test/json102.test33
-rw-r--r--test/json104.test42
-rw-r--r--test/json501.test304
-rw-r--r--test/json502.test25
16 files changed, 1956 insertions, 210 deletions
diff --git a/ext/misc/randomjson.c b/ext/misc/randomjson.c
new file mode 100644
index 000000000..3a6f545fe
--- /dev/null
+++ b/ext/misc/randomjson.c
@@ -0,0 +1,202 @@
+/*
+** 2023-04-28
+**
+** The author disclaims copyright to this source code. In place of
+** a legal notice, here is a blessing:
+**
+** May you do good and not evil.
+** May you find forgiveness for yourself and forgive others.
+** May you share freely, never taking more than you give.
+**
+******************************************************************************
+**
+** This SQLite extension implements a the random_json(SEED) and
+** random_json5(SEED) functions. Given a numeric SEED value, these
+** routines generate pseudo-random JSON or JSON5, respectively. The
+** same value is always generated for the same seed.
+**
+** These SQL functions are intended for testing. They do not have any
+** practical real-world use, that we know of.
+**
+** COMPILE:
+**
+** gcc --shared -fPIC -o randomjson.so -I. ext/misc/randomjson.c
+**
+** USING FROM THE CLI:
+**
+** .load ./randomjson
+** SELECT random_json(1);
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include <assert.h>
+#include <string.h>
+#include <stdlib.h>
+
+/* Pseudo-random number generator */
+typedef struct Prng {
+ unsigned int x, y;
+} Prng;
+
+/* Reseed the PRNG */
+static void prngSeed(Prng *p, unsigned int iSeed){
+ p->x = iSeed | 1;
+ p->y = iSeed;
+}
+
+/* Extract a random number */
+static unsigned int prngInt(Prng *p){
+ p->x = (p->x>>1) ^ ((1+~(p->x&1)) & 0xd0000001);
+ p->y = p->y*1103515245 + 12345;
+ return p->x ^ p->y;
+}
+
+static const char *azJsonAtoms[] = {
+ /* JSON /* JSON-5 */
+ "0", "0",
+ "1", "1",
+ "-1", "-1",
+ "2", "+2",
+ "3", "3",
+ "2.5", "2.5",
+ "0.75", ".75",
+ "-4.0e2", "-4.e2",
+ "5.0e-3", "+5e-3",
+ "0", "0x0",
+ "512", "0x200",
+ "256", "+0x100",
+ "-2748", "-0xabc",
+ "true", "true",
+ "false", "false",
+ "null", "null",
+ "9.0e999", "Infinity",
+ "-9.0e999", "-Infinity",
+ "9.0e999", "+Infinity",
+ "null", "NaN",
+ "-0.0005123", "-0.0005123",
+ "4.35e-3", "+4.35e-3",
+ "\"gem\\\"hay\"", "\"gem\\\"hay\"",
+ "\"icy'joy\"", "'icy\\'joy\'",
+ "\"keylog\"", "\"key\\\nlog\"",
+ "\"mix\\\\\\tnet\"", "\"mix\\\\\\tnet\"",
+ "{}", "{}",
+ "[]", "[]",
+ "[]", "[/*empty*/]",
+ "{}", "{//empty\n}",
+ "\"ask\"", "\"ask\"",
+ "\"bag\"", "\"bag\"",
+ "\"can\"", "\"can\"",
+ "\"day\"", "\"day\"",
+ "\"end\"", "'end'",
+ "\"fly\"", "\"fly\"",
+ "\"\"", "\"\"",
+};
+static const char *azJsonTemplate[] = {
+ /* JSON JSON-5 */
+ "{\"a\":%,\"b\":%,\"c\":%}", "{a:%,b:%,c:%}",
+ "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"e\":%}", "{a:%,b:%,c:%,d:%,e:%}",
+ "{\"a\":%,\"b\":%,\"c\":%,\"d\":%,\"\":%}", "{a:%,b:%,c:%,d:%,\"\":%}",
+ "{\"d\":%}", "{d:%}",
+ "{\"eeee\":%, \"ffff\":%}", "{eeee:% /*and*/, ffff:%}",
+ "{\"$g\":%,\"_h_\":%}", "{$g:%,_h_:%,}",
+ "{\"x\":%,\n \"y\":%}", "{\"x\":%,\n \"y\":%}",
+ "{\"a b c d\":%,\"e\":%,\"f\":%,\"x\":%,\"y\":%}",
+ "{\"a b c d\":%,e:%,f:%,x:%,y:%}",
+ "{\"Z\":%}", "{Z:%,}",
+ "[%]", "[%,]",
+ "[%,%]", "[%,%]",
+ "[%,%,%]", "[%,%,%,]",
+ "[%,%,%,%]", "[%,%,%,%]",
+ "[%,%,%,%,%]", "[%,%,%,%,%]",
+};
+
+#define count(X) (sizeof(X)/sizeof(X[0]))
+
+#define STRSZ 10000
+
+static void jsonExpand(
+ const char *zSrc,
+ char *zDest,
+ Prng *p,
+ int eType, /* 0 for JSON, 1 for JSON5 */
+ unsigned int r /* Growth probability 0..1000. 0 means no growth */
+){
+ unsigned int i, j, k;
+ const char *z;
+ size_t n;
+
+ j = 0;
+ if( zSrc==0 ){
+ k = prngInt(p)%(count(azJsonTemplate)/2);
+ k = k*2 + eType;
+ zSrc = azJsonTemplate[k];
+ }
+ if( strlen(zSrc)>=STRSZ/10 ) r = 0;
+ for(i=0; zSrc[i]; i++){
+ if( zSrc[i]!='%' ){
+ if( j<STRSZ ) zDest[j++] = zSrc[i];
+ continue;
+ }
+ if( r==0 || (r<1000 && (prngInt(p)%1000)<=r) ){
+ /* Fill in without values without any new % */
+ k = prngInt(p)%(count(azJsonAtoms)/2);
+ k = k*2 + eType;
+ z = azJsonAtoms[k];
+ }else{
+ /* Add new % terms */
+ k = prngInt(p)%(count(azJsonTemplate)/2);
+ k = k*2 + eType;
+ z = azJsonTemplate[k];
+ }
+ n = strlen(z);
+ if( j+n<STRSZ ){
+ memcpy(&zDest[j], z, n);
+ j += n;
+ }
+ }
+ zDest[STRSZ-1] = 0;
+ if( j<STRSZ ) zDest[j] = 0;
+}
+
+static void randJsonFunc(
+ sqlite3_context *context,
+ int argc,
+ sqlite3_value **argv
+){
+ unsigned int iSeed;
+ int eType = *(int*)sqlite3_user_data(context);
+ Prng prng;
+ char z1[STRSZ+1], z2[STRSZ+1];
+
+ iSeed = (unsigned int)sqlite3_value_int(argv[0]);
+ prngSeed(&prng, iSeed);
+ jsonExpand(0, z2, &prng, eType, 1000);
+ jsonExpand(z2, z1, &prng, eType, 1000);
+ jsonExpand(z1, z2, &prng, eType, 100);
+ jsonExpand(z2, z1, &prng, eType, 0);
+ sqlite3_result_text(context, z1, -1, SQLITE_TRANSIENT);
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_randomjson_init(
+ sqlite3 *db,
+ char **pzErrMsg,
+ const sqlite3_api_routines *pApi
+){
+ static int cOne = 1;
+ static int cZero = 0;
+ int rc = SQLITE_OK;
+ SQLITE_EXTENSION_INIT2(pApi);
+ (void)pzErrMsg; /* Unused parameter */
+ rc = sqlite3_create_function(db, "random_json", 1,
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
+ &cZero, randJsonFunc, 0, 0);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3_create_function(db, "random_json5", 1,
+ SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC,
+ &cOne, randJsonFunc, 0, 0);
+ }
+ return rc;
+}
diff --git a/manifest b/manifest
index 141110069..a64e69349 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\stypos\sin\scomments\sin\ssqlite3session.h\spreventing\sdocumentation\sfrom\sbeing\scorrectly\sgenerated.
-D 2023-05-01T15:59:20.750
+C Add\ssupport\sfor\sJSON5.
+D 2023-05-01T18:28:48.240
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -302,6 +302,7 @@ F ext/misc/normalize.c bd84355c118e297522aba74de34a4fd286fc775524e0499b14473918d
F ext/misc/percentile.c b9086e223d583bdaf8cb73c98a6539d501a2fc4282654adbfea576453d82e691
F ext/misc/prefixes.c 0f4f8cff5aebc00a7e3ac4021fd59cfe1a8e17c800ceaf592859ecb9cbc38196
F ext/misc/qpvtab.c 09738419e25f603a35c0ac8bd0a04daab794f48d08a9bc07a6085b9057b99009
+F ext/misc/randomjson.c 7dd13664155319d47b9facc0d8dbf45e13062966a47168e54e3f26d48240d7ea
F ext/misc/regexp.c f50ab59bfa8934b7ed98de069d2c74c187f2ef523fb09e85f8840f6459a90942
F ext/misc/remember.c add730f0f7e7436cd15ea3fd6a90fd83c3f706ab44169f7f048438b7d6baa69c
F ext/misc/rot13.c 51ac5f51e9d5fd811db58a9c23c628ad5f333c173f1fc53c8491a3603d38556c
@@ -588,13 +589,13 @@ F src/expr.c 871cfd80c516ee39d90414b2d3da2b5bc9c9e21fe87b7eb787ea7ae4b6461758
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
F src/fkey.c 03c134cc8bffe54835f742ddea0b72ebfc8f6b32773d175c71b8afeea6cb5c83
F src/func.c d187be57a886ddf4e6b7ef584a494361899be3df5eee6d4a747b68ff4aff4122
-F src/global.c 428d2580a1cdf5dbe1f356d1feab83710ae0cc862ece0fb57bc8259e43838c74
+F src/global.c bd0892ade7289f6e20bff44c07d06371f2ff9b53cea359e7854b9b72f65adc30
F src/hash.c c6af5f96a7a76d000f07c5402c48c318c2566beecdee9e78b9d9f60ce7119565
F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
F src/hwtime.h b638809e083b601b618df877b2e89cb87c2a47a01f4def10be4c4ebb54664ac7
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
F src/insert.c a8de1db43335fc4946370a7a7e47d89975ad678ddb15078a150e993ba2fb37d4
-F src/json.c edae65fe1f66ce8b1e7fa6eb036a3d8cf525dacd91d58f12068e80a81ac34f61
+F src/json.c dd76caa9c9f1b417f7d7cdee06baabcbfc4c5ee18a578150d8f19c28260abeac
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c be5af440f3192c58681b5d43167dbca3ccbfce394d89faa22378a14264781136
F src/main.c 09bc5191f75dc48fc4dfddda143cb864c0c3dbc3297eb9a9c8e01fea58ff847d
@@ -639,7 +640,7 @@ F src/shell.c.in 09097e1b9df1f8092e85bf89979e12ca7b608d7efc84551b5d0c8de4dded779
F src/sqlite.h.in 6066996620c2a97193518148ab2d3cedf37d8ee7667dafa96c207a86152b2cfb
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
-F src/sqliteInt.h bf15f7db635d2e64a227bbf86845bc19755dbd932021a6461d6dd15b0da2cfd3
+F src/sqliteInt.h 6766b36c215e33d2cbfd48e632b7cc516092273d909672fdc095bcbd8c005ba1
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -702,7 +703,7 @@ F src/trigger.c ad6ab9452715fa9a8075442e15196022275b414b9141b566af8cdb7a1605f2b0
F src/update.c 3f4fb5ad7c9b48d7911974d6579192bb3a6c27f46140b6cbb9139cc8a77b8691
F src/upsert.c 5303dc6c518fa7d4b280ec65170f465c7a70b7ac2b22491598f6d0b4875b3145
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
-F src/util.c b1d8d87c4c8c77e70f48c43f91444fd66d91532693573b70b837afd572010176
+F src/util.c d4bcb560471cd94e6e17d448311f8d5bf81a7e5276295a53501058ef1b95dd1a
F src/vacuum.c 84ce7f01f8a7a08748e107a441db83bcec13970190ddcb0c9ff522adbc1c23fd
F src/vdbe.c 94d5520d2a287216c47e6fb641ee88ffd934b0d40c235d693d38bcd0e0750357
F src/vdbe.h 637ae853b7d42ae3951034cc63ab7c8af837861f79504cdb5399552fcd89a884
@@ -1251,11 +1252,17 @@ F test/journal3.test 7c3cf23ffc77db06601c1fcfc9743de8441cb77db9d1aa931863d94f5ff
F test/jrnlmode.test 9b5bc01dac22223cb60ec2d5f97acf568d73820794386de5634dcadbea9e1946
F test/jrnlmode2.test 8759a1d4657c064637f8b079592651530db738419e1d649c6df7048cd724363d
F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa
-F test/json101.test c7707ee623c57a24845ef260d4388a6fade1eb294e450caadd7f1d9ced19dea7
-F test/json102.test 327e77275f338c028faefa2da5164daf6b142a165e3015ff2a6e4251ddc6a0ac
+F test/json/README.md 506af1f54574b524106acb50d1a341ab5ddfa6d83fe25095007892b07e663e85
+F test/json/json-generator.tcl dc0dd0f393800c98658fc4c47eaa6af29d4e17527380cd28656fb261bddc8a3f
+F test/json/json-q1.txt 335a7c8ab291d354f33b7decc9559e99a2823d4142291c4be7aa339a631f3c2d
+F test/json/json-speed-check.sh 8b7babf530faa58bd59d6d362cec8e9036a68c5457ff46f3b1f1511d21af6737 x
+F test/json101.test 211d75638782370c07e10a847bd66e501ea1537f39c7da4447bfa055c0f261db
+F test/json102.test 13dc9e7b7f359ecb861e02f9bd7019f7342a63d1c354273b0a8f3904050560a8
F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a8880112dbe
-F test/json104.test a502dc01853aada95d721b3b275afbe2dc18fffdac1fea6e96fb20c13586bbb5
+F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1
F test/json105.test 11670a4387f4308ae0318cadcbd6a918ea7edcd19fbafde020720a073952675d
+F test/json501.test f71710f60fa45b19dc336fbaac9e8362f70f80cf81badefdb845ed3f7c7c2ccc
+F test/json502.test 66d150cc098674b8bf4354526a8dd411b926f43ca892306bcb3b6d3f93fef7be
F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff
F test/kvtest.c feb4358fb022da8ebd098c45811f2f6507688bb6c43aa72b3e840df19026317b
F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63
@@ -2061,8 +2068,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 d74011a3c495719fe1816e15251269824396ac2a40e41f7b96f0dd507c9be609
-R 94e403657c9a5bd8b38c0149c27e975a
-U dan
-Z fbd061bc3bd6b18ca241f57c968aece1
+P ab75170d5609c477613466e8880f20b74b0069281db6536fe09db06ea9d7cff3 1b991c78141a9915ae9350ecb347a758e50d7d25c8a0f4cc098ae10d47c27043
+R d47f0442b266c4b31be73caa106551c0
+T +closed 1b991c78141a9915ae9350ecb347a758e50d7d25c8a0f4cc098ae10d47c27043
+U drh
+Z 86cda36754b86452b62afe6022ca194a
# Remove this line to create a well-formed Fossil manifest.
diff --git a/manifest.uuid b/manifest.uuid
index 00af3754c..0d7ef6682 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-ab75170d5609c477613466e8880f20b74b0069281db6536fe09db06ea9d7cff3 \ No newline at end of file
+f8c3ed23a6931b1da3b93b3274b132387078112a5c8e8d06b5312c47987d3937 \ No newline at end of file
diff --git a/src/global.c b/src/global.c
index b018c5002..fcba7d7fa 100644
--- a/src/global.c
+++ b/src/global.c
@@ -97,7 +97,7 @@ const unsigned char *sqlite3aGTb = &sqlite3UpperToLower[256+12-OP_Ne];
** isalnum() 0x06
** isxdigit() 0x08
** toupper() 0x20
-** SQLite identifier character 0x40
+** SQLite identifier character 0x40 $, _, or non-ascii
** Quote character 0x80
**
** Bit 0x20 is set if the mapped character requires translation to upper
diff --git a/src/json.c b/src/json.c
index aa8bf64c8..aae48c291 100644
--- a/src/json.c
+++ b/src/json.c
@@ -104,6 +104,7 @@ static const char * const jsonType[] = {
#define JNODE_PATCH 0x10 /* Patch with JsonNode.u.pPatch */
#define JNODE_APPEND 0x20 /* More ARRAY/OBJECT entries at u.iAppend */
#define JNODE_LABEL 0x40 /* Is a label of an object */
+#define JNODE_JSON5 0x80 /* Node contains JSON5 enhancements */
/* A single node of parsed JSON
@@ -130,10 +131,12 @@ struct JsonParse {
JsonNode *aNode; /* Array of nodes containing the parse */
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 */
u16 iDepth; /* Nesting depth */
+ u8 nErr; /* Number of errors seen */
+ u8 oom; /* Set to true if out of memory */
+ u8 hasNonstd; /* True if input uses non-standard features like JSON5 */
int nJson; /* Length of the zJson string in bytes */
+ u32 iErr; /* Error location in zJson[] */
u32 iHold; /* Replace cache line with the lowest iHold value */
};
@@ -295,6 +298,129 @@ static void jsonAppendString(JsonString *p, const char *zIn, u32 N){
}
/*
+** 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){
+ int i;
+ jsonAppendChar(p, '"');
+ zIn++;
+ N -= 2;
+ while( N>0 ){
+ for(i=0; i<N && zIn[i]!='\\'; i++){}
+ if( i>0 ){
+ jsonAppendRaw(p, zIn, i);
+ zIn += i;
+ N -= i;
+ if( N==0 ) break;
+ }
+ assert( zIn[0]=='\\' );
+ switch( (u8)zIn[1] ){
+ case '\'':
+ jsonAppendChar(p, '\'');
+ break;
+ case 'v':
+ jsonAppendRaw(p, "\\u0009", 6);
+ break;
+ case 'x':
+ jsonAppendRaw(p, "\\u00", 4);
+ jsonAppendRaw(p, &zIn[2], 2);
+ zIn += 2;
+ N -= 2;
+ break;
+ case '0':
+ jsonAppendRaw(p, "\\u0000", 6);
+ break;
+ case '\r':
+ if( zIn[2]=='\n' ){
+ zIn++;
+ N--;
+ }
+ break;
+ case '\n':
+ break;
+ case 0xe2:
+ assert( N>=4 );
+ assert( 0x80==(u8)zIn[2] );
+ assert( 0xa8==(u8)zIn[3] || 0xa9==(u8)zIn[3] );
+ zIn += 2;
+ N -= 2;
+ break;
+ default:
+ jsonAppendRaw(p, zIn, 2);
+ break;
+ }
+ 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){
+ 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 );
+ jsonAppendRaw(p, "9.0e999", 7);
+ }
+ return;
+ }
+ jsonAppendRaw(p, zIn, N);
+}
+
+/*
+** 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){
+ int 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 ){
+ jsonAppendRaw(p, zIn, N);
+ }
+}
+
+
+
+/*
** Append a function parameter value to the JSON string under
** construction.
*/
@@ -424,17 +550,38 @@ static void jsonRenderNode(
break;
}
case JSON_STRING: {
+ assert( pNode->eU==1 );
if( pNode->jnFlags & JNODE_RAW ){
- assert( pNode->eU==1 );
- jsonAppendString(pOut, pNode->u.zJContent, pNode->n);
- break;
+ if( pNode->jnFlags & JNODE_LABEL ){
+ jsonAppendChar(pOut, '"');
+ jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
+ jsonAppendChar(pOut, '"');
+ }else{
+ jsonAppendString(pOut, pNode->u.zJContent, pNode->n);
+ }
+ }else if( pNode->jnFlags & JNODE_JSON5 ){
+ jsonAppendNormalizedString(pOut, pNode->u.zJContent, pNode->n);
+ }else{
+ jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
}
- /* no break */ deliberate_fall_through
+ break;
+ }
+ case JSON_REAL: {
+ assert( pNode->eU==1 );
+ if( pNode->jnFlags & JNODE_JSON5 ){
+ jsonAppendNormalizedReal(pOut, pNode->u.zJContent, pNode->n);
+ }else{
+ jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
+ }
+ break;
}
- case JSON_REAL:
case JSON_INT: {
assert( pNode->eU==1 );
- jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
+ if( pNode->jnFlags & JNODE_JSON5 ){
+ jsonAppendNormalizedInt(pOut, pNode->u.zJContent, pNode->n);
+ }else{
+ jsonAppendRaw(pOut, pNode->u.zJContent, pNode->n);
+ }
break;
}
case JSON_ARRAY: {
@@ -550,59 +697,41 @@ static void jsonReturn(
}
case JSON_INT: {
sqlite3_int64 i = 0;
+ int rc;
+ int bNeg = 0;
const char *z;
+
+
assert( pNode->eU==1 );
z = pNode->u.zJContent;
- if( z[0]=='-' ){ z++; }
- while( z[0]>='0' && z[0]<='9' ){
- unsigned v = *(z++) - '0';
- if( i>=LARGEST_INT64/10 ){
- if( i>LARGEST_INT64/10 ) goto int_as_real;
- if( z[0]>='0' && z[0]<='9' ) goto int_as_real;
- if( v==9 ) goto int_as_real;
- if( v==8 ){
- if( pNode->u.zJContent[0]=='-' ){
- sqlite3_result_int64(pCtx, SMALLEST_INT64);
- goto int_done;
- }else{
- goto int_as_real;
- }
- }
- }
- i = i*10 + v;
+ if( z[0]=='-' ){ z++; bNeg = 1; }
+ else if( z[0]=='+' ){ z++; }
+ rc = sqlite3DecOrHexToI64(z, &i);
+ if( rc<=1 ){
+ sqlite3_result_int64(pCtx, bNeg ? -i : i);
+ }else if( rc==3 && bNeg ){
+ sqlite3_result_int64(pCtx, SMALLEST_INT64);
+ }else{
+ goto to_double;
}
- if( pNode->u.zJContent[0]=='-' ){ i = -i; }
- sqlite3_result_int64(pCtx, i);
- int_done:
break;
- int_as_real: ; /* no break */ deliberate_fall_through
}
case JSON_REAL: {
double r;
-#ifdef SQLITE_AMALGAMATION
const char *z;
assert( pNode->eU==1 );
+ to_double:
z = pNode->u.zJContent;
sqlite3AtoF(z, &r, sqlite3Strlen30(z), SQLITE_UTF8);
-#else
- assert( pNode->eU==1 );
- r = strtod(pNode->u.zJContent, 0);
-#endif
sqlite3_result_double(pCtx, r);
break;
}
case JSON_STRING: {
-#if 0 /* Never happens because JNODE_RAW is only set by json_set(),
- ** json_insert() and json_replace() and those routines do not
- ** call jsonReturn() */
if( pNode->jnFlags & JNODE_RAW ){
assert( pNode->eU==1 );
sqlite3_result_text(pCtx, pNode->u.zJContent, pNode->n,
SQLITE_TRANSIENT);
- }else
-#endif
- assert( (pNode->jnFlags & JNODE_RAW)==0 );
- if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){
+ }else if( (pNode->jnFlags & JNODE_ESCAPE)==0 ){
/* JSON formatted without any backslash-escapes */
assert( pNode->eU==1 );
sqlite3_result_text(pCtx, pNode->u.zJContent+1, pNode->n-2,
@@ -614,18 +743,17 @@ static void jsonReturn(
const char *z;
char *zOut;
u32 j;
+ u32 nOut = n;
assert( pNode->eU==1 );
z = pNode->u.zJContent;
- zOut = sqlite3_malloc( n+1 );
+ zOut = sqlite3_malloc( nOut+1 );
if( zOut==0 ){
sqlite3_result_error_nomem(pCtx);
break;
}
for(i=1, j=0; i<n-1; i++){
char c = z[i];
- if( c!='\\' ){
- zOut[j++] = c;
- }else{
+ if( c=='\\' ){
c = z[++i];
if( c=='u' ){
u32 v = jsonHexToInt4(z+i+1);
@@ -657,22 +785,40 @@ static void jsonReturn(
zOut[j++] = 0x80 | (v&0x3f);
}
}
+ continue;
+ }else if( c=='b' ){
+ c = '\b';
+ }else if( c=='f' ){
+ c = '\f';
+ }else if( c=='n' ){
+ c = '\n';
+ }else if( c=='r' ){
+ c = '\r';
+ }else if( c=='t' ){
+ c = '\t';
+ }else if( c=='v' ){
+ c = '\v';
+ }else if( c=='\'' || c=='"' || c=='/' || c=='\\' ){
+ /* pass through unchanged */
+ }else if( c=='0' ){
+ c = 0;
+ }else if( c=='x' ){
+ c = (jsonHexToInt(z[i+1])<<4) | jsonHexToInt(z[i+2]);
+ i += 2;
+ }else if( c=='\r' && z[i+1]=='\n' ){
+ i++;
+ continue;
+ }else if( 0xe2==(u8)c ){
+ assert( 0x80==(u8)z[i+1] );
+ assert( 0xa8==(u8)z[i+2] || 0xa9==(u8)z[i+2] );
+ i += 2;
+ continue;
}else{
- if( c=='b' ){
- c = '\b';
- }else if( c=='f' ){
- c = '\f';
- }else if( c=='n' ){
- c = '\n';
- }else if( c=='r' ){
- c = '\r';
- }else if( c=='t' ){
- c = '\t';
- }
- zOut[j++] = c;
+ continue;
}
- }
- }
+ } /* end if( c=='\\' ) */
+ zOut[j++] = c;
+ } /* end for() */
zOut[j] = 0;
sqlite3_result_text(pCtx, zOut, j, sqlite3_free);
}
@@ -740,8 +886,8 @@ static int jsonParseAddNode(
return jsonParseAddNodeExpand(pParse, eType, n, zContent);
}
p = &pParse->aNode[pParse->nNode];
- p->eType = (u8)eType;
- p->jnFlags = 0;
+ p->eType = (u8)(eType & 0xff);
+ p->jnFlags = (u8)(eType >> 8);
VVA( p->eU = zContent ? 1 : 0 );
p->n = n;
p->u.zJContent = zContent;
@@ -749,15 +895,146 @@ static int jsonParseAddNode(
}
/*
+** Return true if z[] begins with 2 (or more) hexadecimal digits
+*/
+static int jsonIs2Hex(const char *z){
+ return sqlite3Isxdigit(z[0]) && sqlite3Isxdigit(z[1]);
+}
+
+/*
** Return true if z[] begins with 4 (or more) hexadecimal digits
*/
static int jsonIs4Hex(const char *z){
- int i;
- for(i=0; i<4; i++) if( !sqlite3Isxdigit(z[i]) ) return 0;
- return 1;
+ return jsonIs2Hex(z) && jsonIs2Hex(&z[2]);
+}
+
+/*
+** Return the number of bytes of JSON5 whitespace at the beginning of
+** the input string z[].
+**
+** JSON5 whitespace consists of any of the following characters:
+**
+** Unicode UTF-8 Name
+** U+0009 09 horizontal tab
+** U+000a 0a line feed
+** U+000b 0b vertical tab
+** U+000c 0c form feed
+** U+000d 0d carriage return
+** U+0020 20 space
+** U+00a0 c2 a0 non-breaking space
+** U+1680 e1 9a 80 ogham space mark
+** U+2000 e2 80 80 en quad
+** U+2001 e2 80 81 em quad
+** U+2002 e2 80 82 en space
+** U+2003 e2 80 83 em space
+** U+2004 e2 80 84 three-per-em space
+** U+2005 e2 80 85 four-per-em space
+** U+2006 e2 80 86 six-per-em space
+** U+2007 e2 80 87 figure space
+** U+2008 e2 80 88 punctuation space
+** U+2009 e2 80 89 thin space
+** U+200a e2 80 8a hair space
+** U+2028 e2 80 a8 line separator
+** U+2029 e2 80 a9 paragraph separator
+** U+202f e2 80 af narrow no-break space (NNBSP)
+** U+205f e2 81 9f medium mathematical space (MMSP)
+** U+3000 e3 80 80 ideographical space
+** U+FEFF ef bb bf byte order mark
+**
+** In addition, comments between '/', '*' and '*', '/' and
+** from '/', '/' to end-of-line are also considered to be whitespace.
+*/
+static int json5Whitespace(const char *zIn){
+ int n = 0;
+ const u8 *z = (u8*)zIn;
+ while( 1 /*exit by "goto whitespace_done"*/ ){
+ switch( z[n] ){
+ case 0x09:
+ case 0x0a:
+ case 0x0b:
+ case 0x0c:
+ case 0x0d:
+ case 0x20: {
+ n++;
+ break;
+ }
+ case '/': {
+ if( z[n+1]=='*' && z[n+2]!=0 ){
+ int j;
+ for(j=n+3; z[j]!='/' || z[j-1]!='*'; j++){
+ if( z[j]==0 ) goto whitespace_done;
+ }
+ n = j+1;
+ break;
+ }else if( z[n+1]=='/' ){
+ int j;
+ char c;
+ for(j=n+2; (c = z[j])!=0; j++){
+ if( c=='\n' || c=='\r' ) break;
+ if( 0xe2==(u8)c && 0x80==(u8)z[j+1]
+ && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2])
+ ){
+ j += 2;
+ break;
+ }
+ }
+ n = j;
+ if( z[n] ) n++;
+ break;
+ }
+ goto whitespace_done;
+ }
+ case 0xc2: {
+ if( z[n+1]==0xa0 ){
+ n += 2;
+ break;
+ }
+ goto whitespace_done;
+ }
+ case 0xe1: {
+ if( z[n+1]==0x9a && z[n+2]==0x80 ){
+ n += 3;
+ break;
+ }
+ goto whitespace_done;
+ }
+ case 0xe2: {
+ if( z[n+1]==0x80 ){
+ u8 c = z[n+2];
+ if( c<0x80 ) goto whitespace_done;
+ if( c<=0x8a || c==0xa8 || c==0xa9 || c==0xaf ){
+ n += 3;
+ break;
+ }
+ }else if( z[n+1]==0x81 && z[n+2]==0x9f ){
+ n += 3;
+ break;
+ }
+ goto whitespace_done;
+ }
+ case 0xe3: {
+ if( z[n+1]==0x80 && z[n+2]==0x80 ){
+ n += 3;
+ break;
+ }
+ goto whitespace_done;
+ }
+ case 0xef: {
+ if( z[n+1]==0xbb && z[n+2]==0xbf ){
+ n += 3;
+ break;
+ }
+ goto whitespace_done;
+ }
+ default: {
+ goto whitespace_done;
+ }
+ }
+ }
+ whitespace_done:
+ return n;
}
-#ifdef SQLITE_ENABLE_JSON_NAN_INF
/*
** Extra floating-point literals to allow in JSON.
*/
@@ -775,16 +1052,20 @@ static const struct NanInfName {
{ 'n', 'N', 3, JSON_NULL, 4, "NaN", "null" },
{ 'q', 'Q', 4, JSON_NULL, 4, "QNaN", "null" },
{ 's', 'S', 4, JSON_NULL, 4, "SNaN", "null" },
-};
-#endif /* SQLITE_ENABLE_JSON_NAN_INF */
+};
/*
** Parse a single JSON value which begins at pParse->zJson[i]. Return the
** index of the first character past the end of the value parsed.
**
-** Return negative for a syntax error. Special cases: return -2 if the
-** first non-whitespace character is '}' and return -3 if the first
-** non-whitespace character is ']'.
+** Special return values:
+**
+** 0 End if input
+** -1 Syntax error
+** -2 '}' seen
+** -3 ']' seen
+** -4 ',' seen
+** -5 ':' seen
*/
static int jsonParseValue(JsonParse *pParse, u32 i){
char c;
@@ -793,169 +1074,414 @@ static int jsonParseValue(JsonParse *pParse, u32 i){
int x;
JsonNode *pNode;
const char *z = pParse->zJson;
- while( fast_isspace(z[i]) ){ i++; }
- if( (c = z[i])=='{' ){
+json_parse_restart:
+ switch( (u8)z[i] ){
+ case '{': {
/* Parse object */
iThis = jsonParseAddNode(pParse, JSON_OBJECT, 0, 0);
if( iThis<0 ) return -1;
for(j=i+1;;j++){
- while( fast_isspace(z[j]) ){ j++; }
- if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1;
- x = jsonParseValue(pParse, j);
- if( x<0 ){
- pParse->iDepth--;
- if( x==(-2) && pParse->nNode==(u32)iThis+1 ) return j+1;
+ if( ++pParse->iDepth > JSON_MAX_DEPTH ){
+ pParse->iErr = j;
return -1;
}
+ x = jsonParseValue(pParse, j);
+ if( x<=0 ){
+ if( x==(-2) ){
+ j = pParse->iErr;
+ if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1;
+ pParse->iDepth--;
+ break;
+ }
+ j += json5Whitespace(&z[j]);
+ if( sqlite3JsonId1(z[j])
+ || (z[j]=='\\' && z[j+1]=='u' && jsonIs4Hex(&z[j+2]))
+ ){
+ int k = j+1;
+ while( (sqlite3JsonId2(z[k]) && json5Whitespace(&z[k])==0)
+ || (z[k]=='\\' && z[k+1]=='u' && jsonIs4Hex(&z[k+2]))
+ ){
+ k++;
+ }
+ jsonParseAddNode(pParse, JSON_STRING | (JNODE_RAW<<8), k-j, &z[j]);
+ pParse->hasNonstd = 1;
+ x = k;
+ }else{
+ if( x!=-1 ) pParse->iErr = j;
+ return -1;
+ }
+ }
if( pParse->oom ) return -1;
pNode = &pParse->aNode[pParse->nNode-1];
- if( pNode->eType!=JSON_STRING ) return -1;
+ if( pNode->eType!=JSON_STRING ){
+ pParse->iErr = j;
+ return -1;
+ }
pNode->jnFlags |= JNODE_LABEL;
j = x;
- while( fast_isspace(z[j]) ){ j++; }
- if( z[j]!=':' ) return -1;
- j++;
+ if( z[j]==':' ){
+ j++;
+ }else{
+ if( fast_isspace(z[j]) ){
+ do{ j++; }while( fast_isspace(z[j]) );
+ if( z[j]==':' ){
+ j++;
+ goto parse_object_value;
+ }
+ }
+ x = jsonParseValue(pParse, j);
+ if( x!=(-5) ){
+ if( x!=(-1) ) pParse->iErr = j;
+ return -1;
+ }
+ j = pParse->iErr+1;
+ }
+ parse_object_value:
x = jsonParseValue(pParse, j);
pParse->iDepth--;
- if( x<0 ) return -1;
+ if( x<=0 ){
+ if( x!=(-1) ) pParse->iErr = j;
+ return -1;
+ }
j = x;
- while( fast_isspace(z[j]) ){ j++; }
- c = z[j];
- if( c==',' ) continue;
- if( c!='}' ) return -1;
- break;
+ if( z[j]==',' ){
+ continue;
+ }else if( z[j]=='}' ){
+ break;
+ }else{
+ if( fast_isspace(z[j]) ){
+ do{ j++; }while( fast_isspace(z[j]) );
+ if( z[j]==',' ){
+ continue;
+ }else if( z[j]=='}' ){
+ break;
+ }
+ }
+ x = jsonParseValue(pParse, j);
+ if( x==(-4) ){
+ j = pParse->iErr;
+ continue;
+ }
+ if( x==(-2) ){
+ j = pParse->iErr;
+ break;
+ }
+ }
+ pParse->iErr = j;
+ return -1;
}
pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
return j+1;
- }else if( c=='[' ){
+ }
+ case '[': {
/* Parse array */
iThis = jsonParseAddNode(pParse, JSON_ARRAY, 0, 0);
if( iThis<0 ) return -1;
memset(&pParse->aNode[iThis].u, 0, sizeof(pParse->aNode[iThis].u));
for(j=i+1;;j++){
- while( fast_isspace(z[j]) ){ j++; }
- if( ++pParse->iDepth > JSON_MAX_DEPTH ) return -1;
+ if( ++pParse->iDepth > JSON_MAX_DEPTH ){
+ pParse->iErr = j;
+ return -1;
+ }
x = jsonParseValue(pParse, j);
pParse->iDepth--;
- if( x<0 ){
- if( x==(-3) && pParse->nNode==(u32)iThis+1 ) return j+1;
+ if( x<=0 ){
+ if( x==(-3) ){
+ j = pParse->iErr;
+ if( pParse->nNode!=(u32)iThis+1 ) pParse->hasNonstd = 1;
+ break;
+ }
+ if( x!=(-1) ) pParse->iErr = j;
return -1;
}
j = x;
- while( fast_isspace(z[j]) ){ j++; }
- c = z[j];
- if( c==',' ) continue;
- if( c!=']' ) return -1;
- break;
+ if( z[j]==',' ){
+ continue;
+ }else if( z[j]==']' ){
+ break;
+ }else{
+ if( fast_isspace(z[j]) ){
+ do{ j++; }while( fast_isspace(z[j]) );
+ if( z[j]==',' ){
+ continue;
+ }else if( z[j]==']' ){
+ break;
+ }
+ }
+ x = jsonParseValue(pParse, j);
+ if( x==(-4) ){
+ j = pParse->iErr;
+ continue;
+ }
+ if( x==(-3) ){
+ j = pParse->iErr;
+ break;
+ }
+ }
+ pParse->iErr = j;
+ return -1;
}
pParse->aNode[iThis].n = pParse->nNode - (u32)iThis - 1;
return j+1;
- }else if( c=='"' ){
+ }
+ case '\'': {
+ u8 jnFlags;
+ char cDelim;
+ pParse->hasNonstd = 1;
+ jnFlags = JNODE_JSON5;
+ goto parse_string;
+ case '"':
/* Parse string */
- u8 jnFlags = 0;
+ jnFlags = 0;
+ parse_string:
+ cDelim = z[i];
j = i+1;
for(;;){
c = z[j];
if( (c & ~0x1f)==0 ){
/* Control characters are not allowed in strings */
+ pParse->iErr = j;
return -1;
}
if( c=='\\' ){
c = z[++j];
if( c=='"' || c=='\\' || c=='/' || c=='b' || c=='f'
|| c=='n' || c=='r' || c=='t'
- || (c=='u' && jsonIs4Hex(z+j+1)) ){
- jnFlags = JNODE_ESCAPE;
+ || (c=='u' && jsonIs4Hex(&z[j+1])) ){
+ jnFlags |= JNODE_ESCAPE;
+ }else if( c=='\'' || c=='0' || c=='v' || c=='\n'
+ || (0xe2==(u8)c && 0x80==(u8)z[j+1]
+ && (0xa8==(u8)z[j+2] || 0xa9==(u8)z[j+2]))
+ || (c=='x' && jsonIs2Hex(&z[j+1])) ){
+ jnFlags |= (JNODE_ESCAPE|JNODE_JSON5);
+ pParse->hasNonstd = 1;
+ }else if( c=='\r' ){
+ if( z[j+1]=='\n' ) j++;
+ jnFlags |= (JNODE_ESCAPE|JNODE_JSON5);
+ pParse->hasNonstd = 1;
}else{
+ pParse->iErr = j;
return -1;
}
- }else if( c=='"' ){
+ }else if( c==cDelim ){
break;
}
j++;
}
- jsonParseAddNode(pParse, JSON_STRING, j+1-i, &z[i]);
- if( !pParse->oom ) pParse->aNode[pParse->nNode-1].jnFlags = jnFlags;
+ jsonParseAddNode(pParse, JSON_STRING | (jnFlags<<8), j+1-i, &z[i]);
return j+1;
- }else if( c=='n'
- && strncmp(z+i,"null",4)==0
- && !sqlite3Isalnum(z[i+4]) ){
- jsonParseAddNode(pParse, JSON_NULL, 0, 0);
- return i+4;
- }else if( c=='t'
- && strncmp(z+i,"true",4)==0
- && !sqlite3Isalnum(z[i+4]) ){
- jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
- return i+4;
- }else if( c=='f'
- && strncmp(z+i,"false",5)==0
- && !sqlite3Isalnum(z[i+5]) ){
- jsonParseAddNode(pParse, JSON_FALSE, 0, 0);
- return i+5;
- }else if( c=='-' || (c>='0' && c<='9') ){
+ }
+ case 'n': {
+ if( strncmp(z+i,"null",4)==0 && !sqlite3Isalnum(z[i+4]) ){
+ jsonParseAddNode(pParse, JSON_NULL, 0, 0);
+ return i+4;
+ }
+ pParse->iErr = i;
+ return -1;
+ }
+ case 't': {
+ if( strncmp(z+i,"true",4)==0 && !sqlite3Isalnum(z[i+4]) ){
+ jsonParseAddNode(pParse, JSON_TRUE, 0, 0);
+ return i+4;
+ }
+ pParse->iErr = i;
+ return -1;
+ }
+ case 'f': {
+ if( strncmp(z+i,"false",5)==0 && !sqlite3Isalnum(z[i+5]) ){
+ jsonParseAddNode(pParse, JSON_FALSE, 0, 0);
+ return i+5;
+ }
+ pParse->iErr = i;
+ return -1;
+ }
+ case '+': {
+ u8 seenDP, seenE, jnFlags;
+ pParse->hasNonstd = 1;
+ jnFlags = JNODE_JSON5;
+ goto parse_number;
+ case '.':
+ if( sqlite3Isdigit(z[i+1]) ){
+ pParse->hasNonstd = 1;
+ jnFlags = JNODE_JSON5;
+ seenE = 0;
+ seenDP = JSON_REAL;
+ goto parse_number_2;
+ }
+ pParse->iErr = i;
+ return -1;
+ case '-':
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
/* Parse number */
- u8 seenDP = 0;
- u8 seenE = 0;
+ jnFlags = 0;
+ parse_number:
+ seenDP = JSON_INT;
+ seenE = 0;
assert( '-' < '0' );
+ assert( '+' < '0' );
+ assert( '.' < '0' );
+ c = z[i];
+
if( c<='0' ){
- j = c=='-' ? i+1 : i;
- if( z[j]=='0' && z[j+1]>='0' && z[j+1]<='9' ) return -1;
+ if( c=='0' ){
+ if( (z[i+1]=='x' || z[i+1]=='X') && sqlite3Isxdigit(z[i+2]) ){
+ assert( seenDP==JSON_INT );
+ pParse->hasNonstd = 1;
+ jnFlags |= JNODE_JSON5;
+ for(j=i+3; sqlite3Isxdigit(z[j]); j++){}
+ goto parse_number_finish;
+ }else if( sqlite3Isdigit(z[i+1]) ){
+ pParse->iErr = i+1;
+ return -1;
+ }
+ }else{
+ if( !sqlite3Isdigit(z[i+1]) ){
+ /* JSON5 allows for "+Infinity" and "-Infinity" using exactly
+ ** that case. SQLite also allows these in any case and it allows
+ ** "+inf" and "-inf". */
+ if( (z[i+1]=='I' || z[i+1]=='i')
+ && sqlite3StrNICmp(&z[i+1], "inf",3)==0
+ ){
+ pParse->hasNonstd = 1;
+ if( z[i]=='-' ){
+ jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999");
+ }else{
+ jsonParseAddNode(pParse, JSON_REAL, 7, "9.0e999");
+ }
+ return i + (sqlite3StrNICmp(&z[i+4],"inity",5)==0 ? 9 : 4);
+ }
+ if( z[i+1]=='.' ){
+ pParse->hasNonstd = 1;
+ jnFlags |= JNODE_JSON5;
+ goto parse_number_2;
+ }
+ pParse->iErr = i;
+ return -1;
+ }
+ if( z[i+1]=='0' ){
+ if( sqlite3Isdigit(z[i+2]) ){
+ pParse->iErr = i+1;
+ return -1;
+ }else if( (z[i+2]=='x' || z[i+2]=='X') && sqlite3Isxdigit(z[i+3]) ){
+ pParse->hasNonstd = 1;
+ jnFlags |= JNODE_JSON5;
+ for(j=i+4; sqlite3Isxdigit(z[j]); j++){}
+ goto parse_number_finish;
+ }
+ }
+ }
}
- j = i+1;
- for(;; j++){
+ parse_number_2:
+ for(j=i+1;; j++){
c = z[j];
- if( c>='0' && c<='9' ) continue;
+ if( sqlite3Isdigit(c) ) continue;
if( c=='.' ){
- if( z[j-1]=='-' ) return -1;
- if( seenDP ) return -1;
- seenDP = 1;
+ if( seenDP==JSON_REAL ){
+ pParse->iErr = j;
+ return -1;
+ }
+ seenDP = JSON_REAL;
continue;
}
if( c=='e' || c=='E' ){
- if( z[j-1]<'0' ) return -1;
- if( seenE ) return -1;
- seenDP = seenE = 1;
+ if( z[j-1]<'0' ){
+ if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){
+ pParse->hasNonstd = 1;
+ jnFlags |= JNODE_JSON5;
+ }else{
+ pParse->iErr = j;
+ return -1;
+ }
+ }
+ if( seenE ){
+ pParse->iErr = j;
+ return -1;
+ }
+ seenDP = JSON_REAL;
+ seenE = 1;
c = z[j+1];
if( c=='+' || c=='-' ){
j++;
c = z[j+1];
}
- if( c<'0' || c>'9' ) return -1;
- continue;
- }
-#ifdef SQLITE_ENABLE_JSON_NAN_INF
- /* Non-standard JSON: Allow "-Inf" (in any case)
- ** to be understood as floating point literals. */
- if( (c=='i' || c=='I')
- && j==i+1
- && z[i]=='-'
- && sqlite3StrNICmp(&z[j], "inf",3)==0
- ){
- if( !sqlite3Isalnum(z[j+3]) ){
- jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999");
- return i+4;
- }else if( (sqlite3StrNICmp(&z[j],"infinity",8)==0 &&
- !sqlite3Isalnum(z[j+8])) ){
- jsonParseAddNode(pParse, JSON_REAL, 8, "-9.0e999");
- return i+9;
+ if( c<'0' || c>'9' ){
+ pParse->iErr = j;
+ return -1;
}
+ continue;
}
-#endif
break;
}
- if( z[j-1]<'0' ) return -1;
- jsonParseAddNode(pParse, seenDP ? JSON_REAL : JSON_INT,
- j - i, &z[i]);
+ if( z[j-1]<'0' ){
+ if( ALWAYS(z[j-1]=='.') && ALWAYS(j-2>=i) && sqlite3Isdigit(z[j-2]) ){
+ pParse->hasNonstd = 1;
+ jnFlags |= JNODE_JSON5;
+ }else{
+ pParse->iErr = j;
+ return -1;
+ }
+ }
+ parse_number_finish:
+ jsonParseAddNode(pParse, seenDP | (jnFlags<<8), j - i, &z[i]);
return j;
- }else if( c=='}' ){
+ }
+ case '}': {
+ pParse->iErr = i;
return -2; /* End of {...} */
- }else if( c==']' ){
+ }
+ case ']': {
+ pParse->iErr = i;
return -3; /* End of [...] */
- }else if( c==0 ){
+ }
+ case ',': {
+ pParse->iErr = i;
+ return -4; /* List separator */
+ }
+ case ':': {
+ pParse->iErr = i;
+ return -5; /* Object label/value separator */
+ }
+ case 0: {
return 0; /* End of file */
- }else{
-#ifdef SQLITE_ENABLE_JSON_NAN_INF
+ }
+ case 0x09:
+ case 0x0a:
+ case 0x0d:
+ case 0x20: {
+ do{
+ i++;
+ }while( fast_isspace(z[i]) );
+ goto json_parse_restart;
+ }
+ case 0x0b:
+ case 0x0c:
+ case '/':
+ case 0xc2:
+ case 0xe1:
+ case 0xe2:
+ case 0xe3:
+ case 0xef: {
+ j = json5Whitespace(&z[i]);
+ if( j>0 ){
+ i += j;
+ pParse->hasNonstd = 1;
+ goto json_parse_restart;
+ }
+ pParse->iErr = i;
+ return -1;
+ }
+ default: {
int k, nn;
+ c = z[i];
for(k=0; k<sizeof(aNanInfName)/sizeof(aNanInfName[0]); k++){
if( c!=aNanInfName[k].c1 && c!=aNanInfName[k].c2 ) continue;
nn = aNanInfName[k].n;
@@ -965,11 +1491,13 @@ static int jsonParseValue(JsonParse *pParse, u32 i){
if( sqlite3Isalnum(z[i+nn]) ) continue;
jsonParseAddNode(pParse, aNanInfName[k].eType,
aNanInfName[k].nRepl, aNanInfName[k].zRepl);
+ pParse->hasNonstd = 1;
return i + nn;
}
-#endif
+ pParse->iErr = i;
return -1; /* Syntax error */
}
+ } /* End switch(z[i]) */
}
/*
@@ -993,7 +1521,14 @@ static int jsonParse(
if( i>0 ){
assert( pParse->iDepth==0 );
while( fast_isspace(zJson[i]) ) i++;
- if( zJson[i] ) i = -1;
+ if( zJson[i] ){
+ i += json5Whitespace(&zJson[i]);
+ if( zJson[i] ){
+ jsonParseReset(pParse);
+ return 1;
+ }
+ pParse->hasNonstd = 1;
+ }
}
if( i<=0 ){
if( pCtx!=0 ){
@@ -1064,6 +1599,15 @@ static int jsonParseFindParents(JsonParse *pParse){
** is no longer valid, parse the JSON again and return the new parse,
** and also register the new parse so that it will be available for
** future sqlite3_get_auxdata() calls.
+**
+** If an error occurs and pErrCtx!=0 then report the error on pErrCtx
+** and return NULL.
+**
+** If an error occurs and pErrCtx==0 then return the Parse object with
+** JsonParse.nErr non-zero. If the caller invokes this routine with
+** pErrCtx==0 and it gets back a JsonParse with nErr!=0, then the caller
+** is responsible for invoking jsonParseFree() on the returned value.
+** But the caller may invoke jsonParseFree() *only* if pParse->nErr!=0.
*/
static JsonParse *jsonParseCached(
sqlite3_context *pCtx,
@@ -1113,6 +1657,10 @@ static JsonParse *jsonParseCached(
p->zJson = (char*)&p[1];
memcpy((char*)p->zJson, zJson, nJson+1);
if( jsonParse(p, pErrCtx, p->zJson) ){
+ if( pErrCtx==0 ){
+ p->nErr = 1;
+ return p;
+ }
sqlite3_free(p);
return 0;
}
@@ -1127,7 +1675,7 @@ static JsonParse *jsonParseCached(
** Compare the OBJECT label at pNode against zKey,nKey. Return true on
** a match.
*/
-static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){
+static int jsonLabelCompare(const JsonNode *pNode, const char *zKey, u32 nKey){
assert( pNode->eU==1 );
if( pNode->jnFlags & JNODE_RAW ){
if( pNode->n!=nKey ) return 0;
@@ -1137,6 +1685,15 @@ static int jsonLabelCompare(JsonNode *pNode, const char *zKey, u32 nKey){
return strncmp(pNode->u.zJContent+1, zKey, nKey)==0;
}
}
+static int jsonSameLabel(const JsonNode *p1, const JsonNode *p2){
+ if( p1->jnFlags & JNODE_RAW ){
+ return jsonLabelCompare(p2, p1->u.zJContent, p1->n);
+ }else if( p2->jnFlags & JNODE_RAW ){
+ return jsonLabelCompare(p1, p2->u.zJContent, p2->n);
+ }else{
+ return p1->n==p2->n && strncmp(p1->u.zJContent,p2->u.zJContent,p1->n)==0;
+ }
+}
/* forward declaration */
static JsonNode *jsonLookupAppend(JsonParse*,const char*,int*,const char**);
@@ -1607,7 +2164,7 @@ static void jsonExtractFunc(
zPath = (const char*)sqlite3_value_text(argv[1]);
if( zPath==0 ) return;
if( flags & JSON_ABPATH ){
- if( zPath[0]!='$' ){
+ if( zPath[0]!='$' || (zPath[1]!='.' && zPath[1]!='[' && zPath[1]!=0) ){
/* The -> and ->> operators accept abbreviated PATH arguments. This
** is mostly for compatibility with PostgreSQL, but also for
** convenience.
@@ -1698,12 +2255,10 @@ static JsonNode *jsonMergePatch(
assert( pPatch[i].eU==1 );
nKey = pPatch[i].n;
zKey = pPatch[i].u.zJContent;
- assert( (pPatch[i].jnFlags & JNODE_RAW)==0 );
for(j=1; j<pTarget->n; j += jsonNodeSize(&pTarget[j+1])+1 ){
assert( pTarget[j].eType==JSON_STRING );
assert( pTarget[j].jnFlags & JNODE_LABEL );
- assert( (pPatch[i].jnFlags & JNODE_RAW)==0 );
- if( pTarget[j].n==nKey && strncmp(pTarget[j].u.zJContent,zKey,nKey)==0 ){
+ if( jsonSameLabel(&pPatch[i], &pTarget[j]) ){
if( pTarget[j+1].jnFlags & (JNODE_REMOVE|JNODE_PATCH) ) break;
if( pPatch[i+1].eType==JSON_NULL ){
pTarget[j+1].jnFlags |= JNODE_REMOVE;
@@ -1990,8 +2545,8 @@ static void jsonTypeFunc(
/*
** json_valid(JSON)
**
-** Return 1 if JSON is a well-formed JSON string according to RFC-7159.
-** Return 0 otherwise.
+** Return 1 if JSON is a well-formed canonical JSON string according
+** to RFC-7159. Return 0 otherwise.
*/
static void jsonValidFunc(
sqlite3_context *ctx,
@@ -2001,7 +2556,66 @@ static void jsonValidFunc(
JsonParse *p; /* The parse */
UNUSED_PARAMETER(argc);
p = jsonParseCached(ctx, argv, 0);
- sqlite3_result_int(ctx, p!=0);
+ if( p==0 || p->oom ){
+ sqlite3_result_error_nomem(ctx);
+ sqlite3_free(p);
+ }else{
+ sqlite3_result_int(ctx, p->nErr==0 && p->hasNonstd==0);
+ if( p->nErr ) jsonParseFree(p);
+ }
+}
+
+/*
+** json_error_position(JSON)
+**
+** If the argument is not an interpretable JSON string, then return the 1-based
+** character position at which the parser first recognized that the input
+** was in error. The left-most character is 1. If the string is valid
+** JSON, then return 0.
+**
+** Note that json_valid() is only true for strictly conforming canonical JSON.
+** But this routine returns zero if the input contains extension. Thus:
+**
+** (1) If the input X is strictly conforming canonical JSON:
+**
+** json_valid(X) returns true
+** json_error_position(X) returns 0
+**
+** (2) If the input X is JSON but it includes extension (such as JSON5) that
+** are not part of RFC-8259:
+**
+** json_valid(X) returns false
+** json_error_position(X) return 0
+**
+** (3) If the input X cannot be interpreted as JSON even taking extensions
+** into account:
+**
+** json_valid(X) return false
+** json_error_position(X) returns 1 or more
+*/
+static void jsonErrorFunc(
+ sqlite3_context *ctx,
+ int argc,
+ sqlite3_value **argv
+){
+ JsonParse *p; /* The parse */
+ UNUSED_PARAMETER(argc);
+ p = jsonParseCached(ctx, argv, 0);
+ if( p==0 || p->oom ){
+ sqlite3_result_error_nomem(ctx);
+ sqlite3_free(p);
+ }else if( p->nErr==0 ){
+ sqlite3_result_int(ctx, 0);
+ }else{
+ int n = 1;
+ int i;
+ const char *z = p->zJson;
+ for(i=0; i<p->iErr && ALWAYS(z[i]); i++){
+ if( (z[i]&0xc0)!=0x80 ) n++;
+ }
+ sqlite3_result_int(ctx, n);
+ jsonParseFree(p);
+ }
}
@@ -2345,14 +2959,16 @@ static void jsonAppendObjectPathElement(
assert( pNode->eU==1 );
z = pNode->u.zJContent;
nn = pNode->n;
- assert( nn>=2 );
- assert( z[0]=='"' );
- assert( z[nn-1]=='"' );
- if( nn>2 && sqlite3Isalpha(z[1]) ){
- for(jj=2; jj<nn-1 && sqlite3Isalnum(z[jj]); jj++){}
- if( jj==nn-1 ){
- z++;
- nn -= 2;
+ if( (pNode->jnFlags & JNODE_RAW)==0 ){
+ assert( nn>=2 );
+ assert( z[0]=='"' || z[0]=='\'' );
+ assert( z[nn-1]=='"' || z[0]=='\'' );
+ if( nn>2 && sqlite3Isalpha(z[1]) ){
+ for(jj=2; jj<nn-1 && sqlite3Isalnum(z[jj]); jj++){}
+ if( jj==nn-1 ){
+ z++;
+ nn -= 2;
+ }
}
}
jsonPrintf(nn+2, pStr, ".%.*s", nn, z);
@@ -2712,6 +3328,7 @@ void sqlite3RegisterJsonFunctions(void){
JFUNCTION(json_array, -1, 0, jsonArrayFunc),
JFUNCTION(json_array_length, 1, 0, jsonArrayLengthFunc),
JFUNCTION(json_array_length, 2, 0, jsonArrayLengthFunc),
+ JFUNCTION(json_error_position,1, 0, jsonErrorFunc),
JFUNCTION(json_extract, -1, 0, jsonExtractFunc),
JFUNCTION(->, 2, JSON_JSON, jsonExtractFunc),
JFUNCTION(->>, 2, JSON_SQL, jsonExtractFunc),
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 4739951a5..b06e19255 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -4456,6 +4456,8 @@ int sqlite3CantopenError(int);
# define sqlite3Isxdigit(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x08)
# define sqlite3Tolower(x) (sqlite3UpperToLower[(unsigned char)(x)])
# define sqlite3Isquote(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x80)
+# define sqlite3JsonId1(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x42)
+# define sqlite3JsonId2(x) (sqlite3CtypeMap[(unsigned char)(x)]&0x46)
#else
# define sqlite3Toupper(x) toupper((unsigned char)(x))
# define sqlite3Isspace(x) isspace((unsigned char)(x))
@@ -4465,6 +4467,8 @@ int sqlite3CantopenError(int);
# define sqlite3Isxdigit(x) isxdigit((unsigned char)(x))
# define sqlite3Tolower(x) tolower((unsigned char)(x))
# define sqlite3Isquote(x) ((x)=='"'||(x)=='\''||(x)=='['||(x)=='`')
+# define sqlite3JsonId1(x) (sqlite3IsIdChar(x)&&(x)<'0')
+# define sqlite3JsonId2(x) sqlite3IsIdChar(x)
#endif
int sqlite3IsIdChar(u8);
diff --git a/src/util.c b/src/util.c
index 72e8a18b2..3d4e01438 100644
--- a/src/util.c
+++ b/src/util.c
@@ -843,7 +843,9 @@ int sqlite3DecOrHexToI64(const char *z, i64 *pOut){
u = u*16 + sqlite3HexToInt(z[k]);
}
memcpy(pOut, &u, 8);
- return (z[k]==0 && k-i<=16) ? 0 : 2;
+ if( k-i>16 ) return 2;
+ if( z[k]!=0 ) return 1;
+ return 0;
}else
#endif /* SQLITE_OMIT_HEX_INTEGER */
{
diff --git a/test/json/README.md b/test/json/README.md
new file mode 100644
index 000000000..6a1611492
--- /dev/null
+++ b/test/json/README.md
@@ -0,0 +1,27 @@
+The files in this subdirectory are used to help measure the performance
+of the SQLite JSON parser.
+
+# 1.0 Prerequisites
+
+ 1. Valgrind
+
+ 2. Fossil
+
+# 2.0 Setup
+
+ 1. Run: "`tclsh json-generator.tcl | sqlite3 json100mb.db`" to create
+ the 100 megabyte test database. Do this so that the "json100mb.db"
+ file lands in the directory from which you will run tests, not in
+ the test/json subdirectory of the source tree.
+
+ 2. Build the baseline sqlite3.c file. ("`make sqlite3.c`")
+
+ 3. Run "`sh json-speed-check-1.sh trunk`". This creates the baseline
+ profile in "jout-trunk.txt".
+
+# 3.0 Testing
+
+ 1. Build the sqlite3.c to be tested.
+
+ 2. Run "`sh json-speed-check-1.sh x1`". The profile output will appear
+ in jout-x1.txt. Substitute any label you want in place of "x1".
diff --git a/test/json/json-generator.tcl b/test/json/json-generator.tcl
new file mode 100644
index 000000000..d499bc730
--- /dev/null
+++ b/test/json/json-generator.tcl
@@ -0,0 +1,401 @@
+#!/usr/bin/tclsh
+#
+# Generate SQL that will populate an SQLite database with about 100 megabytes
+# of pseudo-random JSON text.
+#
+# tclsh json-generator.tcl | sqlite3 json110mb.db
+#
+# srand() is used to initialize the random seed so that the same JSON
+# is generated for every run.
+#
+expr srand(12345678)
+set wordlist {
+ ability able abroad access account act
+ action active actor add address adept
+ adroit advance advice affect age ageless
+ agency agent agile agree air airfare
+ airline airport alert almond alpha always
+ amend amount amplify analyst anchor angel
+ angelic angle ankle annual answer antique
+ anybody anyhow appeal apple apricot apt
+ area argon arm army arrival arsenic
+ art artful article arugula aside ask
+ aspect assist assume atom atone attempt
+ author autumn average avocado award awl
+ azure back bacon bag bagel bake
+ baker balance ball balloon bamboo banana
+ band banjo bank barium base basil
+ basin basis basket bass bat bath
+ battery beach beak bean bear bearcub
+ beauty beef beet beige being bell
+ belly belt bench bend benefit best
+ beta better beyond bicycle bid big
+ bike bill bird biscuit bismuth bisque
+ bit black blank blest blind bliss
+ block bloom blue board boat body
+ bokchoy bone bonus book bookish boot
+ border boron boss bossy bottle bottom
+ bow bowl bowtie box brain brainy
+ branch brave bravely bread break breath
+ breezy brick bridge brie brief briefly
+ bright broad broil bromine bronze brother
+ brow brown brush buddy budget buffalo
+ bug bugle bull bunch burger burly
+ burrito bus busy butter button buy
+ buyer byte cab cabbage cabinet cable
+ cadet cadmium caesium cake calcium caliper
+ call caller calm calmly camera camp
+ can canary cancel candle candy cap
+ capable caper capital captain car carbon
+ card care career careful carp carpet
+ carrot carry case cash cassava casual
+ cat catch catfish catsear catsup cause
+ cave celery cell century chain chair
+ chalk chance change channel chapter chard
+ charge charity chart check cheddar cheery
+ cheese chicken chicory chiffon child chin
+ chip chives choice chowder chum church
+ circle city claim clam class classic
+ classy clay clean cleaner clear clearly
+ clerk click client climate clock clorine
+ closet clothes cloud clown club clue
+ cluster coach coast coat cobbler cobolt
+ cod code coffee colby cold collar
+ college comb combine comet comfort command
+ comment common company complex concept concern
+ concert conduit consist contact contest context
+ control convert cook cookie copilot copper
+ copy coral cordial corn corner corny
+ correct cost count counter country county
+ couple courage course court cover cow
+ cowbird crab crack craft crash crazy
+ cream credit creek cress crevice crew
+ crimson croaker crop cross crowd cube
+ cuckoo cuisine culture cup current curve
+ cut cyan cycle dagger daily dance
+ dare darter data date day daylily
+ deal dear dearly debate debit decade
+ decimal deep deft deftly degree delay
+ deluxe deposit depth design desk detail
+ device dew diamond diet dig dill
+ dinner dip direct dirt dish disk
+ display diver divide divine doctor dodger
+ donut door dot double dough draft
+ drag dragon drama draw drawer drawing
+ dream drill drink drive driver drop
+ drum dry dryer drywall duck due
+ dump dusk dust duty dye eagle
+ ear earring earth ease east easy
+ eat economy edge editor eel effect
+ effort egg eight elbow elegant element
+ elf elk email emerald employ end
+ endive endless energy engine enjoy enter
+ entry equal equip error escape essay
+ eternal evening event exam example excuse
+ exit expert extent extreme eye face
+ fact factor factual fail failure fair
+ fajita fall family fan fang farm
+ farmer fat fault feature feed feel
+ feeling fench fennel festive few fiber
+ field fig figure file fill film
+ filter final finance finding finger finish
+ fire fish fishing fit fitting five
+ fix flier flight floor floral florine
+ flour flow flower fly flying focus
+ fold folding food foot force forest
+ forever forgive form formal format fortune
+ forum frame free freedom freely fresh
+ friend frog front fruit fuchsia fuel
+ fun funny future gain galaxy gallium
+ game gamma gap garage garden garlic
+ gas gate gather gauge gear gem
+ gene general gentle gently gherkin ghost
+ gift give glad glass gleeful glossy
+ glove glue goal goat goby gold
+ goldeye golf good gouda goulash gourd
+ grab grace grade gram grand grape
+ grapes grass gravy gray great green
+ grits grocery ground group grouper grout
+ growth guard guave guess guest guide
+ guitar gumbo guppy habit hacksaw haddock
+ hafnium hagfish hair half halibut hall
+ hammer hand handle handy hanger happy
+ hat havarti hay haybale head health
+ healthy hearing heart hearty heat heavy
+ heel height helium hello help helpful
+ herald herring hide high highly highway
+ hill hip hipster hire history hit
+ hoki hold hole holiday holly home
+ honest honey hook hope hopeful horizon
+ horn horse host hotel hour house
+ housing human humane humor hunt hurry
+ ice icecube icefish icy idea ideal
+ image impact impress inch income indigo
+ initial inkpen insect inside intense invite
+ iodine iridium iron island issue item
+ ivory jacket jargon javelin jello jelly
+ jewel job jocund join joint joke
+ jovial joy joyful joyous judge juice
+ jump junior jury just justice kale
+ keel keep kelp ketchup key keyhole
+ keyway khaki kick kid kidney kiloohm
+ kind kindly king kitchen kite kiwi
+ knee knife krill krypton kumquat lab
+ lace lack ladder lake lamp lamprey
+ land laser laugh law lawn lawyer
+ layer lead leader leading leaf leafy
+ league leather leave lecture leek leg
+ lemon length lentil lesson let letter
+ lettuce level library life lift light
+ lily lime limit line linen link
+ lip list listen lithium lively living
+ lizard load loan lobster local lock
+ log long longfin look lotus love
+ lovely loving low lucid luck luffa
+ lunch lung machine magenta magnet mail
+ main major make mall manager mango
+ manner many map march market maroon
+ martian master match math matter maximum
+ maybe meal meaning meat media medium
+ meet meeting melody melon member memory
+ mention menu mercury merry mess message
+ messy metal meter method micron middle
+ might mile milk mind mine minimum
+ minnow minor mint minute mirror miss
+ mission misty mix mixer mixture mobile
+ mode model moment monitor monk month
+ moon moray morning most motor mouse
+ mouth move mover movie much mud
+ mudfish muffin mullet munster muon muscle
+ music mustard nail name nation native
+ natural nature navy neat neatly nebula
+ neck needle neon nerve net network
+ neutron news nibble nice nickel night
+ niobium nobody noise noodle normal north
+ nose note nothing notice nova novel
+ number nurse nursery oar object offer
+ office officer oil okay okra old
+ olive one onion open opening opinion
+ option orange orbit orchid order oregano
+ other ounce outcome outside oven owner
+ oxygen oyster pace pack package page
+ pager paint pair pale pan pancake
+ papaya paper pardon parent park parking
+ parsley parsnip part partner party pass
+ passage past pasta path patient pattern
+ pause pay pea peace peach peacock
+ peahen peak peanut pear pearl pen
+ penalty pencil pension people pepper perch
+ perfect period permit person phase phone
+ photo phrase physics piano pick picture
+ pie piece pigeon pike pilot pin
+ pink pinkie pious pipe pitch pizza
+ place plan plane planet plant planter
+ plastic plate play player playful plenty
+ pliers plum pod poem poet poetry
+ point police policy pollock pony pool
+ pop popover poptart pork port portal
+ post pot potato pound powder power
+ present press price pride primary print
+ prior private prize problem process produce
+ product profile profit program project promise
+ prompt proof proper protein proton public
+ puff puffer pull pumpkin pup pupfish
+ pure purple purpose push put quality
+ quark quarter quiet quill quit quote
+ rabbit raccoon race radiant radio radish
+ radium radon rain rainbow raise ramp
+ ranch range rasp rate ratio ray
+ razor reach read reading real reality
+ reason recipe record recover red redeem
+ reed reef refuse region regret regular
+ relaxed release relief relish remote remove
+ rent repair repeat reply report request
+ reserve resist resolve resort rest result
+ return reveal review reward ribbon rice
+ rich ride ridge right ring rise
+ risk river rivet road roast rock
+ rocket role roll roof room rope
+ rose rough roughy round row royal
+ rub ruby rudder ruin rule run
+ runner rush rust sacred saddle safe
+ safety sail salad salami sale salmon
+ salt sample sand sander sandy sauce
+ save saving saw scale scampi scene
+ scheme school score screen script sea
+ search season seat second secret sector
+ seemly self sell senate senior sense
+ series serve set shake shape share
+ shark shell shift shine shiny ship
+ shock shoe shoot shop shovel show
+ side sign signal silk silly silver
+ simple sing singer single sink site
+ size skill skin sky slate sleep
+ sleepy slice slide slip smart smell
+ smelt smile smoke smooth snap snipe
+ snow snowy sock socket sodium soft
+ softly soil sole solid song sorrel
+ sort soul sound soup source south
+ space spare speech speed spell spend
+ sphere spice spider spirit spite split
+ spoon sport spot spray spread spring
+ squab square squash stable staff stage
+ stand staple star start state status
+ stay steak steel step stern stew
+ stick still stock stone stop store
+ storm story strain street stress strike
+ string stroke strong studio study stuff
+ style sugar suit sulfur summer sun
+ sunny sunset super superb surf survey
+ sweet swim swing switch symbol system
+ table tackle tail tale talk tan
+ tank tap tape target task taste
+ tau tea teach teal team tear
+ tell ten tender tennis tent term
+ test tetra text thanks theme theory
+ thing think thread throat thumb ticket
+ tidy tie tiger till time timely
+ tin tip title toast today toe
+ tomato tone tongue tool tooth top
+ topic total touch tough tour towel
+ tower town track trade train trash
+ travel tray treat tree trick trip
+ trout trowel truck trupet trust truth
+ try tube tuna tune turf turkey
+ turn turnip tutor tux tweet twist
+ two type union unique unit upbeat
+ upper use useful user usual valley
+ value van vase vast veil vein
+ velvet verse very vessel vest video
+ view violet visit visual vivid voice
+ volume vowel voyage waffle wait wake
+ walk wall warm warmth wasabi wash
+ watch water wave wax way wealth
+ wear web wedge week weekly weight
+ west whale what wheat wheel when
+ where while who whole why will
+ win wind window wing winner winter
+ wire wish witty wolf wonder wood
+ wool woolly word work worker world
+ worry worth worthy wrap wrench wrist
+ writer xenon yak yam yard yarrow
+ year yearly yellow yew yogurt young
+ youth zebra zephyr zinc zone zoo
+}
+set nwordlist [llength $wordlist]
+
+proc random_char {} {
+ return [string index \
+ "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" \
+ [expr {int(rand()*52)}]]
+}
+proc random_label {} {
+ set label [random_char]
+ while {rand()>0.8} {
+ append label [random_char]
+ }
+ if {rand()>0.9} {append label -}
+ append label [format %d [expr {int(rand()*100)}]]
+ return $label
+}
+proc random_numeric {} {
+ set n [expr {(rand()*2-1.0)*1e6}]
+ switch [expr {int(rand()*6)}] {
+ 0 {set format %.3f}
+ 1 {set format %.6E}
+ 2 {set format %.4e}
+ default {set format %g}
+ }
+ return [format $format $n]
+}
+
+
+proc random_json {limit indent} {
+ global nwordlist wordlist
+ set res {}
+ if {$indent==0 || ($limit>0 && rand()>0.5)} {
+ incr limit -1
+ incr indent 2
+ set n [expr {int(rand()*5)+1}]
+ if {$n==5} {incr n [expr {int(rand()*10)}]}
+ if {rand()>0.5} {
+ set res \173\n
+ for {set i 0} {$i<$n} {incr i} {
+ append res [string repeat { } $indent]
+ if {rand()>0.8} {
+ if {rand()>0.5} {
+ set sep ":\n [string repeat { } $indent]"
+ } else {
+ set sep " : "
+ }
+ } else {
+ set sep :
+ }
+ append res \"[random_label]\"$sep[random_json $limit $indent]
+ if {$i<$n-1} {append res ,}
+ append res \n
+ }
+ incr indent -2
+ append res [string repeat { } $indent]
+ append res \175
+ return $res
+ } else {
+ set res \[\n
+ for {set i 0} {$i<$n} {incr i} {
+ append res [string repeat { } $indent]
+ append res [random_json $limit $indent]
+ if {$i<$n-1} {append res ,}
+ append res \n
+ }
+ incr indent -2
+ append res [string repeat { } $indent]
+ append res \]
+ return $res
+ }
+ } elseif {rand()>0.9} {
+ if {rand()>0.7} {return "true"}
+ if {rand()>0.5} {return "false"}
+ return "null"
+ } elseif {rand()>0.5} {
+ return [random_numeric]
+ } else {
+ set res \"
+ set n [expr {int(rand()*4)+1}]
+ if {$n>=4} {set n [expr {$n+int(rand()*6)}]}
+ for {set i 0} {$i<$n} {incr i} {
+ if {rand()<0.05} {
+ set w [random_numeric]
+ } else {
+ set k [expr {int(rand()*$nwordlist)}]
+ set w [lindex $wordlist $k]
+ }
+ if {rand()<0.07} {
+ set w \\\"$w\\\"
+ }
+ if {$i<$n-1} {
+ switch [expr {int(rand()*9)}] {
+ 0 {set sp {, }}
+ 1 {set sp "\\n "}
+ 2 {set sp "-"}
+ default {set sp { }}
+ }
+ append res $w$sp
+ } else {
+ append res $w
+ if {rand()<0.2} {append res .}
+ }
+ }
+ return $res\"
+ }
+}
+
+puts "CREATE TABLE IF NOT EXISTS data1(x JSON);"
+puts "BEGIN;"
+set sz 0
+for {set i 0} {$sz<100000000} {incr i} {
+ set j [random_json 7 0]
+ incr sz [string length $j]
+ puts "INSERT INTO data1(x) VALUES('$j');"
+}
+puts "COMMIT;"
+puts "SELECT sum(length(x)) FROM data1;"
diff --git a/test/json/json-q1.txt b/test/json/json-q1.txt
new file mode 100644
index 000000000..0395f0c06
--- /dev/null
+++ b/test/json/json-q1.txt
@@ -0,0 +1,4 @@
+.mode qbox
+.timer on
+.param set $label 'q87'
+SELECT rowid, x->>$label FROM data1 WHERE x->>$label IS NOT NULL;
diff --git a/test/json/json-speed-check.sh b/test/json/json-speed-check.sh
new file mode 100755
index 000000000..f7e7f1be5
--- /dev/null
+++ b/test/json/json-speed-check.sh
@@ -0,0 +1,80 @@
+#!/bin/bash
+#
+# This is a template for a script used for day-to-day size and
+# performance monitoring of SQLite. Typical usage:
+#
+# sh speed-check.sh trunk # Baseline measurement of trunk
+# sh speed-check.sh x1 # Measure some experimental change
+# fossil xdiff --tk jout-trunk.txt jout-x1.txt # View chanages
+#
+# There are multiple output files, all with a base name given by
+# the first argument:
+#
+# summary-$BASE.txt # Copy of standard output
+# jout-$BASE.txt # cachegrind output
+# explain-$BASE.txt # EXPLAIN listings (only with --explain)
+#
+if test "$1" = ""
+then
+ echo "Usage: $0 OUTPUTFILE [OPTIONS]"
+ exit
+fi
+NAME=$1
+shift
+#CC_OPTS="-DSQLITE_ENABLE_RTREE -DSQLITE_ENABLE_MEMSYS5"
+CC_OPTS="-DSQLITE_ENABLE_MEMSYS5"
+CC=gcc
+LEAN_OPTS="-DSQLITE_THREADSAFE=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_MEMSTATUS=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_DEFAULT_WAL_SYNCHRONOUS=1"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_LIKE_DOESNT_MATCH_BLOBS"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_MAX_EXPR_DEPTH=0"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DECLTYPE"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_DEPRECATED"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_PROGRESS_CALLBACK"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_OMIT_SHARED_CACHE"
+LEAN_OPTS="$LEAN_OPTS -DSQLITE_USE_ALLOCA"
+BASELINE="trunk"
+doExplain=0
+doCachegrind=1
+doVdbeProfile=0
+doWal=1
+doDiff=1
+while test "$1" != ""; do
+ case $1 in
+ --nodiff)
+ doDiff=0
+ ;;
+ --lean)
+ CC_OPTS="$CC_OPTS $LEAN_OPTS"
+ ;;
+ --clang)
+ CC=clang
+ ;;
+ --gcc7)
+ CC=gcc-7
+ ;;
+ -*)
+ CC_OPTS="$CC_OPTS $1"
+ ;;
+ *)
+ BASELINE=$1
+ ;;
+ esac
+ shift
+done
+echo "NAME = $NAME" | tee summary-$NAME.txt
+echo "CC_OPTS = $CC_OPTS" | tee -a summary-$NAME.txt
+rm -f cachegrind.out.* jsonshell
+$CC -g -Os -Wall -I. $CC_OPTS ./shell.c ./sqlite3.c -o jsonshell -ldl -lpthread
+ls -l jsonshell | tee -a summary-$NAME.txt
+home=`echo $0 | sed -e 's,/[^/]*$,,'`
+echo ./jsonshell json100mb.db "<$home/json-q1.txt"
+valgrind --tool=cachegrind ./jsonshell json100mb.db <$home/json-q1.txt \
+ 2>&1 | tee -a summary-$NAME.txt
+cg_anno.tcl cachegrind.out.* >jout-$NAME.txt
+echo '*****************************************************' >>jout-$NAME.txt
+sed 's/^[0-9=-]\{9\}/==00000==/' summary-$NAME.txt >>jout-$NAME.txt
+if test "$NAME" != "$BASELINE"; then
+ fossil xdiff --tk -c 20 jout-$BASELINE.txt jout-$NAME.txt
+fi
diff --git a/test/json101.test b/test/json101.test
index 596a08496..298dc3bdb 100644
--- a/test/json101.test
+++ b/test/json101.test
@@ -310,12 +310,33 @@ do_execsql_test json-6.1 {
SELECT json_valid('{"a":55,"b":72,}');
} {0}
do_execsql_test json-6.2 {
+ SELECT json_error_position('{"a":55,"b":72,}');
+} {0}
+do_execsql_test json-6.3 {
+ SELECT json_valid(json('{"a":55,"b":72,}'));
+} {1}
+do_execsql_test json-6.4 {
+ SELECT json_valid('{"a":55,"b":72 , }');
+} {0}
+do_execsql_test json-6.5 {
+ SELECT json_error_position('{"a":55,"b":72 , }');
+} {0}
+do_execsql_test json-6.6 {
+ SELECT json_error_position('{"a":55,"b":72,,}');
+} {16}
+do_execsql_test json-6.7 {
SELECT json_valid('{"a":55,"b":72}');
} {1}
-do_execsql_test json-6.3 {
- SELECT json_valid('["a",55,"b",72,]');
+do_execsql_test json-6.8 {
+ SELECT json_error_position('["a",55,"b",72,]');
} {0}
-do_execsql_test json-6.4 {
+do_execsql_test json-6.9 {
+ SELECT json_error_position('["a",55,"b",72 , ]');
+} {0}
+do_execsql_test json-6.10 {
+ SELECT json_error_position('["a",55,"b",72,,]');
+} {16}
+do_execsql_test json-6.11 {
SELECT json_valid('["a",55,"b",72]');
} {1}
diff --git a/test/json102.test b/test/json102.test
index f551c4b82..bfd5e7ed0 100644
--- a/test/json102.test
+++ b/test/json102.test
@@ -301,18 +301,27 @@ for {set i 0} {$i<100} {incr i} {
# allowing them. The following tests verify that the problem is now
# fixed.
#
-do_execsql_test json102-1401 { SELECT json_valid('{"x":01}') } 0
-do_execsql_test json102-1402 { SELECT json_valid('{"x":-01}') } 0
-do_execsql_test json102-1403 { SELECT json_valid('{"x":0}') } 1
-do_execsql_test json102-1404 { SELECT json_valid('{"x":-0}') } 1
-do_execsql_test json102-1405 { SELECT json_valid('{"x":0.1}') } 1
-do_execsql_test json102-1406 { SELECT json_valid('{"x":-0.1}') } 1
-do_execsql_test json102-1407 { SELECT json_valid('{"x":0.0000}') } 1
-do_execsql_test json102-1408 { SELECT json_valid('{"x":-0.0000}') } 1
-do_execsql_test json102-1409 { SELECT json_valid('{"x":01.5}') } 0
-do_execsql_test json102-1410 { SELECT json_valid('{"x":-01.5}') } 0
-do_execsql_test json102-1411 { SELECT json_valid('{"x":00}') } 0
-do_execsql_test json102-1412 { SELECT json_valid('{"x":-00}') } 0
+foreach {id j x0 x5} {
+ 1401 {'{"x":01}'} 0 0
+ 1402 {'{"x":-01}'} 0 0
+ 1403 {'{"x":0}'} 1 1
+ 1404 {'{"x":-0}'} 1 1
+ 1405 {'{"x":0.1}'} 1 1
+ 1406 {'{"x":-0.1}'} 1 1
+ 1407 {'{"x":0.0000}'} 1 1
+ 1408 {'{"x":-0.0000}'} 1 1
+ 1409 {'{"x":01.5}'} 0 0
+ 1410 {'{"x":-01.5}'} 0 0
+ 1411 {'{"x":00}'} 0 0
+ 1412 {'{"x":-00}'} 0 0
+ 1413 {'{"x":+0}'} 0 1
+ 1414 {'{"x":+5}'} 0 1
+ 1415 {'{"x":+5.5}'} 0 1
+} {
+ do_execsql_test json102-$id "
+ SELECT json_valid($j), NOT json_error_position($j);
+ " [list $x0 $x5]
+}
#------------------------------------------------------------------------
# 2017-04-10 ticket 6c9b5514077fed34551f98e64c09a10dc2fc8e16
diff --git a/test/json104.test b/test/json104.test
index 56dd2738c..c3c43d1e9 100644
--- a/test/json104.test
+++ b/test/json104.test
@@ -30,6 +30,48 @@ do_execsql_test json104-100 {
}
}');
} {{{"a":"z","c":{"d":"e"}}}}
+do_execsql_test json104-101 {
+ SELECT json_patch('{
+ "a": "b",
+ "c": {
+ "d": "e",
+ "f": "g"
+ }
+ }','{
+ a:"z",
+ c: {
+ f: null
+ }
+ }');
+} {{{"a":"z","c":{"d":"e"}}}}
+do_execsql_test json104-102 {
+ SELECT json_patch('{
+ a: "b",
+ c: {
+ d: "e",
+ f: "g"
+ }
+ }','{
+ "a":"z",
+ "c": {
+ "f": null
+ }
+ }');
+} {{{"a":"z","c":{"d":"e"}}}}
+do_execsql_test json104-103 {
+ SELECT json_patch('{
+ a: "b",
+ c: {
+ d: "e",
+ f: "g"
+ }
+ }','{
+ a:"z",
+ c: {
+ f: null
+ }
+ }');
+} {{{"a":"z","c":{"d":"e"}}}}
# This is the example from pages 4 and 5 of RFC-7396
diff --git a/test/json501.test b/test/json501.test
new file mode 100644
index 000000000..a37326973
--- /dev/null
+++ b/test/json501.test
@@ -0,0 +1,304 @@
+# 2023-04-27
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements tests for the JSON5 enhancements to the
+# JSON SQL functions extension to the SQLite library.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix json501
+
+# From https://spec.json5.org/#introduction
+#
+#-----------------------------------------------------------------------------
+# Summary of Features
+#
+# The following ECMAScript 5.1 features, which are not supported in JSON, have
+# been extended to JSON5.
+#
+# Objects
+#
+# 1) Object keys may be an ECMAScript 5.1 IdentifierName.
+# 2) Objects may have a single trailing comma.
+#
+# Arrays
+#
+# 3) Arrays may have a single trailing comma.
+#
+# Strings
+#
+# 4) Strings may be single quoted.
+# 5) Strings may span multiple lines by escaping new line characters.
+# 6) Strings may include character escapes.
+#
+# Numbers
+#
+# 7) Numbers may be hexadecimal.
+# 8) Numbers may have a leading or trailing decimal point.
+# 9) Numbers may be IEEE 754 positive infinity, negative infinity, and NaN.
+# 10) Numbers may begin with an explicit plus sign.
+#
+# Comments
+#
+# 11) Single and multi-line comments are allowed.
+#
+# White Space
+#
+# 12) Additional white space characters are allowed.
+#-----------------------------------------------------------------------------
+#
+# Test number in this file are of the form X.Y where X is one of the item
+# numbers in the feature list above and Y is the test sequence number.
+#
+
+###############################################################################
+# 1) Object keys may be an ECMAScript 5.1 IdentifierName.
+do_execsql_test 1.1 {
+ WITH c(x) AS (VALUES('{a:5,b:6}'))
+ SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c;
+} {5 {{"a":5,"b":6}} 0 1}
+do_execsql_test 1.2 {
+ SELECT '[7,null,{a:5,b:6},[8,9]]'->>'$[2].b';
+} {6}
+do_execsql_test 1.3 {
+ SELECT '{ $123 : 789 }'->>'$."$123"';
+} 789
+do_execsql_test 1.4 {
+ SELECT '{ _123$xyz : 789 }'->>'$."_123$xyz"';
+} 789
+do_execsql_test 1.5 {
+ SELECT '{ MNO_123$xyz : 789 }'->>'$."MNO_123$xyz"';
+} 789
+
+do_execsql_test 1.6 {
+ SELECT json('{ MNO_123$xyz : 789 }');
+} [list {{"MNO_123$xyz":789}}]
+
+do_catchsql_test 1.10 {
+ SELECT json('{ MNO_123/xyz : 789 }');
+} {1 {malformed JSON}}
+
+do_execsql_test 1.11 {
+ SELECT '{ MNO_123æxyz : 789 }'->>'MNO_123æxyz';
+} {789}
+
+###############################################################################
+# 2) Objects may have a single trailing comma.
+
+do_execsql_test 2.1 {
+ WITH c(x) AS (VALUES('{"a":5, "b":6, }'))
+ SELECT x->>'b', json(x), json_valid(x), NOT json_error_position(x) FROM c;
+} {6 {{"a":5,"b":6}} 0 1}
+do_execsql_test 2.2 {
+ SELECT '{a:5, b:6 , }'->>'b';
+} 6
+do_catchsql_test 2.3 {
+ SELECT '{a:5, b:6 ,, }'->>'b';
+} {1 {malformed JSON}}
+do_catchsql_test 2.4 {
+ SELECT '{a:5, b:6, ,}'->>'b';
+} {1 {malformed JSON}}
+
+###############################################################################
+# 3) Arrays may have a single trailing comma.
+
+do_execsql_test 3.1 {
+ WITH c(x) AS (VALUES('[5, 6,]'))
+ SELECT x->>1, json(x), json_valid(x), NOT json_error_position(x) FROM c;
+} {6 {[5,6]} 0 1}
+do_execsql_test 3.2 {
+ SELECT '[5, 6 , ]'->>1;
+} 6
+do_catchsql_test 3.3 {
+ SELECT '[5, 6,,]'->>1;
+} {1 {malformed JSON}}
+do_catchsql_test 3.4 {
+ SELECT '[5, 6 , , ]'->>1;
+} {1 {malformed JSON}}
+
+###############################################################################
+# 4) Strings may be single quoted.
+
+do_execsql_test 4.1 {
+ WITH c(x) AS (VALUES('{"a": ''abcd''}'))
+ SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c;
+} {abcd {{"a":"abcd"}} 0 1}
+do_execsql_test 4.2 {
+ SELECT '{b: 123, ''a'': ''ab\''cd''}'->>'a';
+} {ab'cd}
+
+###############################################################################
+# 5) Strings may span multiple lines by escaping new line characters.
+
+do_execsql_test 5.1 {
+ WITH c(x) AS (VALUES('{a: "abc'||char(0x5c,0x0a)||'xyz"}'))
+ SELECT x->>'a', json(x), json_valid(x), NOT json_error_position(x) FROM c;
+} {abcxyz {{"a":"abcxyz"}} 0 1}
+do_execsql_test 5.2 {
+ SELECT ('{a: "abc'||char(0x5c,0x0d)||'xyz"}')->>'a';
+} {abcxyz}
+do_execsql_test 5.3 {
+ SELECT ('{a: "abc'||char(0x5c,0x0d,0x0a)||'xyz"}')->>'a';
+} {abcxyz}
+do_execsql_test 5.4 {
+ SELECT ('{a: "abc'||char(0x5c,0x2028)||'xyz"}')->>'a';
+} {abcxyz}
+do_execsql_test 5.5 {
+ SELECT ('{a: "abc'||char(0x5c,0x2029)||'xyz"}')->>'a';
+} {abcxyz}
+
+
+###############################################################################
+# 6) Strings may include character escapes.
+
+do_execsql_test 6.1 {
+ SELECT ('{a: "abc'||char(0x5c,0x27)||'xyz"}')->>'a';
+} {abc'xyz}
+do_execsql_test 6.2 {
+ SELECT ('{a: "abc'||char(0x5c,0x22)||'xyz"}')->>'a';
+} {abc"xyz}
+do_execsql_test 6.3 {
+ SELECT ('{a: "abc'||char(0x5c,0x5c)||'xyz"}')->>'a';
+} {{abc\xyz}}
+do_execsql_test 6.4 {
+ SELECT hex(('{a: "abc\bxyz"}')->>'a');
+} {6162630878797A}
+do_execsql_test 6.5 {
+ SELECT hex(('{a: "abc\f\n\r\t\vxyz"}')->>'a');
+} {6162630C0A0D090B78797A}
+do_execsql_test 6.6 {
+ SELECT hex(('{a: "abc\0xyz"}')->>'a');
+} {6162630078797A}
+do_execsql_test 6.7 {
+ SELECT '{a: "abc\x35\x4f\x6Exyz"}'->>'a';
+} {abc5Onxyz}
+do_execsql_test 6.8 {
+ SELECT '{a: "\x6a\x6A\x6b\x6B\x6c\x6C\x6d\x6D\x6e\x6E\x6f\x6F"}'->>'a';
+} {jjkkllmmnnoo}
+
+###############################################################################
+# 7) Numbers may be hexadecimal.
+
+do_execsql_test 7.1 {
+ SELECT '{a: 0x0}'->>'a';
+} 0
+do_execsql_test 7.2 {
+ SELECT '{a: -0x0}'->>'a';
+} 0
+do_execsql_test 7.3 {
+ SELECT '{a: +0x0}'->>'a';
+} 0
+do_execsql_test 7.4 {
+ SELECT '{a: 0xabcdef}'->>'a';
+} 11259375
+do_execsql_test 7.5 {
+ SELECT '{a: -0xaBcDeF}'->>'a';
+} -11259375
+do_execsql_test 7.6 {
+ SELECT '{a: +0xABCDEF}'->>'a';
+} 11259375
+
+###############################################################################
+# 8) Numbers may have a leading or trailing decimal point.
+
+do_execsql_test 8.1 {
+ WITH c(x) AS (VALUES('{x: 4.}')) SELECT x->>'x', json(x) FROM c;
+} {4.0 {{"x":4.0}}}
+do_execsql_test 8.2 {
+ WITH c(x) AS (VALUES('{x: +4.}')) SELECT x->>'x', json(x) FROM c;
+} {4.0 {{"x":4.0}}}
+do_execsql_test 8.3 {
+ WITH c(x) AS (VALUES('{x: -4.}')) SELECT x->>'x', json(x) FROM c;
+} {-4.0 {{"x":-4.0}}}
+do_execsql_test 8.3 {
+ WITH c(x) AS (VALUES('{x: .5}')) SELECT x->>'x', json(x) FROM c;
+} {0.5 {{"x":0.5}}}
+do_execsql_test 8.4 {
+ WITH c(x) AS (VALUES('{x: -.5}')) SELECT x->>'x', json(x) FROM c;
+} {-0.5 {{"x":-0.5}}}
+do_execsql_test 8.5 {
+ WITH c(x) AS (VALUES('{x: +.5}')) SELECT x->>'x', json(x) FROM c;
+} {0.5 {{"x":0.5}}}
+do_execsql_test 8.6 {
+ WITH c(x) AS (VALUES('{x: 4.e0}')) SELECT x->>'x', json(x) FROM c;
+} {4.0 {{"x":4.0e0}}}
+do_execsql_test 8.7 {
+ WITH c(x) AS (VALUES('{x: +4.e1}')) SELECT x->>'x', json(x) FROM c;
+} {40.0 {{"x":4.0e1}}}
+do_execsql_test 8.8 {
+ WITH c(x) AS (VALUES('{x: -4.e2}')) SELECT x->>'x', json(x) FROM c;
+} {-400.0 {{"x":-4.0e2}}}
+do_execsql_test 8.9 {
+ WITH c(x) AS (VALUES('{x: .5e3}')) SELECT x->>'x', json(x) FROM c;
+} {500.0 {{"x":0.5e3}}}
+do_execsql_test 8.10 {
+ WITH c(x) AS (VALUES('{x: -.5e-1}')) SELECT x->>'x', json(x) FROM c;
+} {-0.05 {{"x":-0.5e-1}}}
+do_execsql_test 8.11 {
+ WITH c(x) AS (VALUES('{x: +.5e-2}')) SELECT x->>'x', json(x) FROM c;
+} {0.005 {{"x":0.5e-2}}}
+
+
+###############################################################################
+# 9) Numbers may be IEEE 754 positive infinity, negative infinity, and NaN.
+
+do_execsql_test 9.1 {
+ WITH c(x) AS (VALUES('{x: +Infinity}')) SELECT x->>'x', json(x) FROM c;
+} {Inf {{"x":9.0e999}}}
+do_execsql_test 9.2 {
+ WITH c(x) AS (VALUES('{x: -Infinity}')) SELECT x->>'x', json(x) FROM c;
+} {-Inf {{"x":-9.0e999}}}
+do_execsql_test 9.3 {
+ WITH c(x) AS (VALUES('{x: Infinity}')) SELECT x->>'x', json(x) FROM c;
+} {Inf {{"x":9.0e999}}}
+do_execsql_test 9.4 {
+ WITH c(x) AS (VALUES('{x: NaN}')) SELECT x->>'x', json(x) FROM c;
+} {{} {{"x":null}}}
+
+###############################################################################
+# 10) Numbers may begin with an explicit plus sign.
+
+do_execsql_test 10.1 {
+ SELECT '{a: +123}'->'a';
+} 123
+
+###############################################################################
+# 11) Single and multi-line comments are allowed.
+
+do_execsql_test 11.1 {
+ SELECT ' /* abc */ { /*def*/ aaa /* xyz */ : // to the end of line
+ 123 /* xyz */ , /* 123 */ }'->>'aaa';
+} 123
+
+###############################################################################
+# 12) Additional white space characters are allowed.
+
+do_execsql_test 12.1 {
+ SELECT (char(0x09,0x0a,0x0b,0x0c,0x0d,0x20,0xa0,0x2028,0x2029)
+ || '{a: "xyz"}')->>'a';
+} xyz
+do_execsql_test 12.2 {
+ SELECT ('{a:' || char(0x09,0x0a,0x0b,0x0c,0x0d,0x20,0xa0,0x2028,0x2029)
+ || '"xyz"}')->>'a';
+} xyz
+do_execsql_test 12.3 {
+ SELECT (char(0x1680,0x2000,0x2001,0x2002,0x2003,0x2004,0x2005,
+ 0x2006,0x2007,0x2008,0x2009,0x200a,0x3000,0xfeff)
+ || '{a: "xyz"}')->>'a';
+} xyz
+do_execsql_test 12.4 {
+ SELECT ('{a: ' ||char(0x1680,0x2000,0x2001,0x2002,0x2003,0x2004,0x2005,
+ 0x2006,0x2007,0x2008,0x2009,0x200a,0x3000,0xfeff)
+ || ' "xyz"}')->>'a';
+} xyz
+
+
+finish_test
diff --git a/test/json502.test b/test/json502.test
new file mode 100644
index 000000000..b5e570320
--- /dev/null
+++ b/test/json502.test
@@ -0,0 +1,25 @@
+# 2023-04-28
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements tests for the JSON5 enhancements to the
+# JSON SQL functions extension to the SQLite library.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix json502
+
+do_execsql_test 1.1 {
+ CREATE TABLE t1(x JSON);
+ INSERT INTO t1(x) VALUES('{a:{b:{c:"hello",},},}');
+ SELECT fullkey FROM t1, json_tree(x);
+} {{$} {$.a} {$.a.b} {$.a.b.c}}
+
+finish_test