]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Begin adding an extension that provides JSON SQL functions.
authordrh <drh@noemail.net>
Wed, 12 Aug 2015 16:49:40 +0000 (16:49 +0000)
committerdrh <drh@noemail.net>
Wed, 12 Aug 2015 16:49:40 +0000 (16:49 +0000)
FossilOrigin-Name: dde8afdd8dba1d92560326dca7c1cdfedbe5e070

Makefile.in
Makefile.msc
ext/misc/json.c [new file with mode: 0644]
main.mk
manifest
manifest.uuid
src/test1.c
test/json1.test [new file with mode: 0644]

index a0f536cb23c6890a33cf629abbbcfea4d780e80e..4c83d9dfa358154163ee7bfb6b08e6ea11bd8cf1 100644 (file)
@@ -417,6 +417,7 @@ TESTSRC += \
   $(TOP)/ext/fts5/fts5_tcl.c \
   $(TOP)/ext/fts5/fts5_test_mi.c \
   $(TOP)/ext/misc/ieee754.c \
+  $(TOP)/ext/misc/json.c \
   $(TOP)/ext/misc/nextchar.c \
   $(TOP)/ext/misc/percentile.c \
   $(TOP)/ext/misc/regexp.c \
index 22d3fb523ea72869d4fd4c3594d1d10a53a65e6e..52769889aa4b044b4fd4cbe82af9ac54d81c9bc3 100644 (file)
@@ -1083,6 +1083,7 @@ TESTEXT = \
   $(TOP)\ext\fts5\fts5_tcl.c \
   $(TOP)\ext\fts5\fts5_test_mi.c \
   $(TOP)\ext\misc\ieee754.c \
+  $(TOP)\ext\misc\json.c \
   $(TOP)\ext\misc\nextchar.c \
   $(TOP)\ext\misc\percentile.c \
   $(TOP)\ext\misc\regexp.c \
diff --git a/ext/misc/json.c b/ext/misc/json.c
new file mode 100644 (file)
index 0000000..53442a9
--- /dev/null
@@ -0,0 +1,225 @@
+/*
+** 2015-08-12
+**
+** 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 JSON functions.  The interface is
+** modeled after MySQL JSON functions:
+**
+**     https://dev.mysql.com/doc/refman/5.7/en/json.html
+**
+** JSON is pure text.  JSONB is a binary encoding that is smaller and easier
+** to parse but which holds the equivalent information.  Conversions between
+** JSON and JSONB are lossless.
+**
+** Most of the functions here will accept either JSON or JSONB input.  The
+** input is understood to be JSONB if it a BLOB and JSON if the input is
+** of any other type.  Functions that begin with the "json_" prefix return
+** JSON and functions that begin with "jsonb_" return JSONB.
+*/
+#include "sqlite3ext.h"
+SQLITE_EXTENSION_INIT1
+#include <assert.h>
+#include <string.h>
+
+/* Unsigned integer types */
+typedef sqlite3_uint64 u64;
+typedef unsigned int u32;
+typedef unsigned char u8;
+
+/* An instance of this object represents a JSON string under construction.
+*/
+typedef struct Json Json;
+struct Json {
+  sqlite3_context *pCtx;   /* Function context - put error messages here */
+  char *zBuf;              /* Append JSON text here */
+  u64 nAlloc;              /* Bytes of storage available in zBuf[] */
+  u64 nUsed;               /* Bytes of zBuf[] currently used */
+  u8 bStatic;              /* True if zBuf is static space */
+  u8 mallocFailed;         /* True if an OOM has been encountered */
+  char zSpace[100];        /* Initial static space */
+};
+
+/* Set the Json object to an empty string
+*/
+static void jsonZero(Json *p){
+  p->zBuf = p->zSpace;
+  p->nAlloc = sizeof(p->zSpace);
+  p->nUsed = 0;
+  p->bStatic = 1;
+}
+
+/* Initialize the Json object
+*/
+static void jsonInit(Json *p, sqlite3_context *pCtx){
+  p->pCtx = pCtx;
+  p->mallocFailed = 0;
+  jsonZero(p);
+}
+
+
+/* Free all allocated memory and reset the Json object back to its
+** initial state.
+*/
+static void jsonReset(Json *p){
+  if( !p->bStatic ) sqlite3_free(p->zBuf);
+  jsonZero(p);
+}
+
+
+/* Report an out-of-memory (OOM) condition 
+*/
+static void jsonOom(Json *p){
+  p->mallocFailed = 1;
+  sqlite3_result_error_nomem(p->pCtx);
+  jsonReset(p);
+}
+
+/* Enlarge pJson->zBuf so that it can hold at least N more bytes.
+** Return zero on success.  Return non-zero on an OOM error
+*/
+static int jsonGrow(Json *p, u32 N){
+  u64 nTotal = N<p->nAlloc ? p->nAlloc*2 : p->nAlloc+N+100;
+  char *zNew;
+  if( p->bStatic ){
+    if( p->mallocFailed ) return SQLITE_NOMEM;
+    zNew = sqlite3_malloc64(nTotal);
+    if( zNew==0 ){
+      jsonOom(p);
+      return SQLITE_NOMEM;
+    }
+    memcpy(zNew, p->zBuf, p->nUsed);
+    p->zBuf = zNew;
+    p->bStatic = 0;
+  }else{
+    zNew = sqlite3_realloc64(p->zBuf, nTotal);
+    if( zNew==0 ){
+      jsonOom(p);
+      return SQLITE_NOMEM;
+    }
+    p->zBuf = zNew;
+  }
+  p->nAlloc = nTotal;
+  return SQLITE_OK;
+}
+
+/* Append N bytes from zIn onto the end of the Json string.
+*/
+static void jsonAppendRaw(Json *p, const char *zIn, u32 N){
+  if( (N+p->nUsed >= p->nAlloc) && jsonGrow(p,N)!=0 ) return;
+  memcpy(p->zBuf+p->nUsed, zIn, N);
+  p->nUsed += N;
+}
+
+/* Append the N-byte string in zIn to the end of the Json string
+** under construction.  Enclose the string in "..." and escape
+** any double-quotes or backslash characters contained within the
+** string.
+*/
+static void jsonAppendString(Json *p, const char *zIn, u32 N){
+  u32 i;
+  if( (N+p->nUsed+2 >= p->nAlloc) && jsonGrow(p,N+2)!=0 ) return;
+  p->zBuf[p->nUsed++] = '"';
+  for(i=0; i<N; i++){
+    char c = zIn[i];
+    if( c=='"' || c=='\\' ){
+      if( (p->nUsed+N+1-i > p->nAlloc) && jsonGrow(p,N+1-i)!=0 ) return;
+      p->zBuf[p->nUsed++] = '\\';
+    }
+    p->zBuf[p->nUsed++] = c;
+  }
+  p->zBuf[p->nUsed++] = '"';
+}
+
+/* Make pJson the result of the SQL function.
+*/
+static void jsonResult(Json *p){
+  if( p->mallocFailed==0 ){
+    sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, 
+                          p->bStatic ? SQLITE_TRANSIENT : sqlite3_free,
+                          SQLITE_UTF8);
+    jsonZero(p);
+  }
+  assert( p->bStatic );
+}
+
+/*
+** Implementation of the json_array(VALUE,...) function.  Return a JSON
+** array that contains all values given in arguments.  Or if any argument
+** is a BLOB, throw an error.
+*/
+static void jsonArrayFunc(
+  sqlite3_context *context,
+  int argc,
+  sqlite3_value **argv
+){
+  int i;
+  Json jx;
+  char cSep = '[';
+
+  jsonInit(&jx, context);
+  for(i=0; i<argc; i++){
+    jsonAppendRaw(&jx, &cSep, 1);
+    cSep = ',';
+    switch( sqlite3_value_type(argv[i]) ){
+      case SQLITE_NULL: {
+        jsonAppendRaw(&jx, "null", 4);
+        break;
+      }
+      case SQLITE_INTEGER:
+      case SQLITE_FLOAT: {
+        const char *z = (const char*)sqlite3_value_text(argv[i]);
+        u32 n = (u32)sqlite3_value_bytes(argv[i]);
+        jsonAppendRaw(&jx, z, n);
+        break;
+      }
+      case SQLITE_TEXT: {
+        const char *z = (const char*)sqlite3_value_text(argv[i]);
+        u32 n = (u32)sqlite3_value_bytes(argv[i]);
+        jsonAppendString(&jx, z, n);
+        break;
+      }
+      default: {
+        jsonZero(&jx);
+        sqlite3_result_error(context, "JSON cannot hold BLOB values", -1);
+        return;
+      }
+    }
+  }
+  jsonAppendRaw(&jx, "]", 1);
+  jsonResult(&jx);      
+}
+
+#ifdef _WIN32
+__declspec(dllexport)
+#endif
+int sqlite3_json_init(
+  sqlite3 *db, 
+  char **pzErrMsg, 
+  const sqlite3_api_routines *pApi
+){
+  int rc = SQLITE_OK;
+  int i;
+  static const struct {
+     const char *zName;
+     int nArg;
+     void (*xFunc)(sqlite3_context*,int,sqlite3_value**);
+  } aFunc[] = {
+    { "json_array",  -1,  jsonArrayFunc },
+  };
+  SQLITE_EXTENSION_INIT2(pApi);
+  (void)pzErrMsg;  /* Unused parameter */
+  for(i=0; i<sizeof(aFunc)/sizeof(aFunc[0]) && rc==SQLITE_OK; i++){
+    rc = sqlite3_create_function(db, aFunc[i].zName, aFunc[i].nArg,
+                                 SQLITE_UTF8 | SQLITE_DETERMINISTIC, 0,
+                                 aFunc[i].xFunc, 0, 0);
+  }
+  return rc;
+}
diff --git a/main.mk b/main.mk
index f3bdae6a8313d048b1c3e4eb078f40d1ee5e2c5d..40ea3c1c3062b29346bc0808073e5cc04d1df58a 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -297,6 +297,7 @@ TESTSRC += \
   $(TOP)/ext/misc/fileio.c \
   $(TOP)/ext/misc/fuzzer.c \
   $(TOP)/ext/misc/ieee754.c \
+  $(TOP)/ext/misc/json.c \
   $(TOP)/ext/misc/nextchar.c \
   $(TOP)/ext/misc/percentile.c \
   $(TOP)/ext/misc/regexp.c \
index f14c5e00bead4971b4131b0bb740489b3ee6003d..14c4613435cca81690d807c37e3a450f7f06ffdc 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,9 +1,9 @@
-C Add\sthe\sxPhraseFirst()\sand\sxPhraseNext()\sfts5\sAPIs,\sfor\sfaster\siteration\sthrough\sa\ssingle\sphrases\sposition\slist.\sAlso\soptimize\sxInst()\sand\sxInstCount()\sa\sbit.
-D 2015-08-12T12:11:28.744
+C Begin\sadding\san\sextension\sthat\sprovides\sJSON\sSQL\sfunctions.
+D 2015-08-12T16:49:40.032
 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
-F Makefile.in 2fc9ca6bf5949d415801c007ed3004a4bdb7c380
+F Makefile.in 7669f34c487f5b328de6b508f374ee1e56558bb0
 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
-F Makefile.msc 5f7861c62c41fe8e3205ef14b90ebed28fa21f1b
+F Makefile.msc 0e9dbc907fbd3e08444b5dd4c03e1919442575b8
 F Makefile.vxworks e1b65dea203f054e71653415bd8f96dcaed47858
 F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
 F VERSION ccfc4d1576dbfdeece0a4372a2e6a2e37d3e7975
@@ -192,6 +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/json.c 172375d024d799aa6506b8a329128ee0b67e1a94
 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342
 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63
 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc
@@ -255,7 +256,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
 F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
 F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 73167b34b0e67c0be32c1da2d988a376851c9ab1
+F main.mk d79682036d2dbb4197b09bc5c6a448fd35e7fc9a
 F mkopcodec.awk c2ff431854d702cdd2d779c9c0d1f58fa16fa4ea
 F mkopcodeh.awk 0e7f04a8eb90f92259e47d80110e4e98d7ce337a
 F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
@@ -344,7 +345,7 @@ F src/sqliteLimit.h 216557999cb45f2e3578ed53ebefe228d779cb46
 F src/status.c f266ad8a2892d659b74f0f50cb6a88b6e7c12179
 F src/table.c 51b46b2a62d1b3a959633d593b89bab5e2c9155e
 F src/tclsqlite.c d9439b6a910985b7fff43ba6756bcef00de22649
-F src/test1.c d339ae9b9baf9221c657c9628c9061d88bd831f6
+F src/test1.c 0fcf7ae97001ced91bfbfaf738e9689779d9bf61
 F src/test2.c 577961fe48961b2f2e5c8b56ee50c3f459d3359d
 F src/test3.c 64d2afdd68feac1bb5e2ffb8226c8c639f798622
 F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e
@@ -807,6 +808,7 @@ F test/journal3.test ff8af941f9e06161d3db1b46bb9f965ff0e7f307
 F test/jrnlmode.test 7864d59cf7f6e552b9b99ba0f38acd167edc10fa
 F test/jrnlmode2.test 81610545a4e6ed239ea8fa661891893385e23a1d
 F test/jrnlmode3.test 556b447a05be0e0963f4311e95ab1632b11c9eaa
+F test/json1.test f09587c250426291faff60cacc5aae759216dbd7
 F test/keyword1.test 37ef6bba5d2ed5b07ecdd6810571de2956599dff
 F test/lastinsert.test 42e948fd6442f07d60acbd15d33fb86473e0ef63
 F test/laststmtchanges.test ae613f53819206b3222771828d024154d51db200
@@ -1372,7 +1374,10 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b
 F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 61cb2fc6c12810863c965c74e90bc502e20cf810
-R d3b6dadb6d0d16c8d1ad296c14b96300
-U dan
-Z 2a4c5177ad3c19913de9e61965a0ba07
+P f7682435278419829a46bb4cc9b5625d46549e22
+R 400e916c0bc56219e72f7a73e2b83997
+T *branch * json
+T *sym-json *
+T -sym-trunk *
+U drh
+Z 44e10dc14fd52cdaf44120cd6aeb4786
index 24756f2611fd8ab943e6614f136cc245b6150bf9..ab95b84a1110e37b08a1621fcb7fcad781f55fd9 100644 (file)
@@ -1 +1 @@
-f7682435278419829a46bb4cc9b5625d46549e22
\ No newline at end of file
+dde8afdd8dba1d92560326dca7c1cdfedbe5e070
\ No newline at end of file
index ef9783a3c059e7f5c38a3dee73b15debe9a7ca91..63727263ddb57a11e58c5b8a633ee18e0b56e7a7 100644 (file)
@@ -6377,6 +6377,7 @@ static int tclLoadStaticExtensionCmd(
   extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_fuzzer_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_ieee_init(sqlite3*,char**,const sqlite3_api_routines*);
+  extern int sqlite3_json_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_nextchar_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_percentile_init(sqlite3*,char**,const sqlite3_api_routines*);
   extern int sqlite3_regexp_init(sqlite3*,char**,const sqlite3_api_routines*);
@@ -6397,6 +6398,7 @@ static int tclLoadStaticExtensionCmd(
     { "fileio",                sqlite3_fileio_init               },
     { "fuzzer",                sqlite3_fuzzer_init               },
     { "ieee754",               sqlite3_ieee_init                 },
+    { "json",                  sqlite3_json_init                 },
     { "nextchar",              sqlite3_nextchar_init             },
     { "percentile",            sqlite3_percentile_init           },
     { "regexp",                sqlite3_regexp_init               },
diff --git a/test/json1.test b/test/json1.test
new file mode 100644 (file)
index 0000000..f9612b3
--- /dev/null
@@ -0,0 +1,40 @@
+# 2015-08-12
+#
+# 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 JSON SQL functions extension to the
+# SQLite library.
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+load_static_extension db json
+do_execsql_test json1-1.1 {
+  SELECT json_array(1,2.5,null,'hello');
+} {[1,2.5,null,"hello"]}
+do_execsql_test json1-1.2 {
+  SELECT hex(json_array('String "\ Test'));
+} {5B22537472696E67205C225C5C2054657374225D}
+do_catchsql_test json1-1.3 {
+  SELECT json_array(1,2,x'abcd',3);
+} {1 {JSON cannot hold BLOB values}}
+do_execsql_test json1-1.4 {
+  SELECT json_array(-9223372036854775808,9223372036854775807,0,1,-1,
+                    0.0, 1.0, -1.0, -1e99, +2e100,
+                    'one','two','three',
+                    4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
+                    19, NULL, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31,
+                    'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ',
+                    'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ',
+                    'abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ',
+                    99);
+} {[-9223372036854775808,9223372036854775807,0,1,-1,0.0,1.0,-1.0,-1.0e+99,2.0e+100,"one","two","three",4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,null,21,22,23,24,25,26,27,28,29,30,31,"abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ","abcdefghijklmnopqrstuvwyxzABCDEFGHIJKLMNOPQRSTUVWXYZ",99]}
+
+finish_test