]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Create the new RCStr class of strings and try to use them for JSON storage.
authordrh <>
Tue, 25 Jul 2023 15:08:18 +0000 (15:08 +0000)
committerdrh <>
Tue, 25 Jul 2023 15:08:18 +0000 (15:08 +0000)
FossilOrigin-Name: c1b8725089bb3d006ec69add28f4fcb3f4e79412c7f438b5b1067c2227e77b9c

manifest
manifest.uuid
src/json.c
src/printf.c
src/sqliteInt.h
src/vdbemem.c

index 192ed6f6b305e13dff19277c6e9f213ae1a954bd..b4a115361a4db47865a37367560699b20487e0c8 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C It\sis\san\serror\sto\stry\sto\sinsert\sa\sBLOB\svalue\sinto\sJSON.
-D 2023-07-24T23:27:05.772
+C Create\sthe\snew\sRCStr\sclass\sof\sstrings\sand\stry\sto\suse\sthem\sfor\sJSON\sstorage.
+D 2023-07-25T15:08:18.543
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -598,7 +598,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
 F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
 F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276
-F src/json.c fc5b67025d4ca026d5c0696cfd5c265c1cd0fa2f669fbe78d268e955f853e75c
+F src/json.c 3add12eb29f09a99dc260bcde427ea925f600cf0bb0d9abb0618a03d7e50eead
 F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
 F src/loadext.c 176d6b2cb18a6ad73b133db17f6fc351c4d9a2d510deebdb76c22bde9cfd1465
 F src/main.c 512b1d45bc556edf4471a845afb7ba79e64bd5b832ab222dc195c469534cd002
@@ -634,7 +634,7 @@ F src/pcache1.c 602acb23c471bb8d557a6f0083cc2be641d6cafcafa19e481eba7ef4c9ca0f00
 F src/pragma.c 37b8fb02d090262280c86e1e2654bf59d8dbfbfe8dc6733f2b968a11374c095a
 F src/pragma.h e690a356c18e98414d2e870ea791c1be1545a714ba623719deb63f7f226d8bb7
 F src/prepare.c d6c4354f8ea0dc06962fbabc4b68c4471a45276a2918c929be00f9f537f69eb1
-F src/printf.c 84b7b4b647f336934a5ab2e7f0c52555833cc0778d2d60e016cca52ee8c6cd8f
+F src/printf.c 21e410b0a3904dddb39ef1b245cfb991302022a6f0fc16f0a8a13539d6d2f713
 F src/random.c 606b00941a1d7dd09c381d3279a058d771f406c5213c9932bbd93d5587be4b9c
 F src/resolve.c 37953a5f36c60bea413c3c04efcd433b6177009f508ef2ace0494728912fe2e9
 F src/rowset.c 8432130e6c344b3401a8874c3cb49fefe6873fec593294de077afea2dce5ec97
@@ -643,7 +643,7 @@ F src/shell.c.in d320d8a13636de06d777cc1eab981caca304e175464e98183cf4ea68d93db81
 F src/sqlite.h.in f999ef3642f381d69679b2516b430dbcb6c5a2a951b7f5e43dc4751b474a5774
 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
 F src/sqlite3ext.h da473ce2b3d0ae407a6300c4a164589b9a6bfdbec9462688a8593ff16f3bb6e4
-F src/sqliteInt.h dcb1a885e8b6cb78df618944b89d44361a99d0fe33e1bba2c150a855f7dc5599
+F src/sqliteInt.h 59b755dec944aa3b068e962ef53264de6fde3d6b6df2d5869ea3afdb7facdf60
 F src/sqliteLimit.h 33b1c9baba578d34efe7dfdb43193b366111cdf41476b1e82699e14c11ee1fb6
 F src/status.c 160c445d7d28c984a0eae38c144f6419311ed3eace59b44ac6dafc20db4af749
 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@@ -714,7 +714,7 @@ F src/vdbeInt.h 401813862f9d75af01bdb2ab99253ad019e9d6ddcc8058e4fa61a43e9a60d1f7
 F src/vdbeapi.c dde6c4d0f87486f056b9db4d1ea185bb1d84a6839102b86e76316ba590d07cc7
 F src/vdbeaux.c b5e3f7e158518b4eca6f166ac43900640a3fe9735c710e12bfa119af21059339
 F src/vdbeblob.c 2516697b3ee8154eb8915f29466fb5d4f1ae39ee8b755ea909cefaf57ec5e2ce
-F src/vdbemem.c 40afb83ed848e235848ffdd3ba25adca4ba602111b8ed3b05ae3b1b12e0eacee
+F src/vdbemem.c aee9ac636666616494d9a395d29efc3fe9e1404a9f043db81c82560b43b78f35
 F src/vdbesort.c 0d40dca073c94e158ead752ef4225f4fee22dee84145e8c00ca2309afb489015
 F src/vdbetrace.c fe0bc29ebd4e02c8bc5c1945f1d2e6be5927ec12c06d89b03ef2a4def34bf823
 F src/vdbevtab.c aae4bd769410eb7e1d02c42613eec961d514459b1c3c1c63cfc84e92a137daac
@@ -2044,8 +2044,11 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 327fff501e36f75d4901c520123c5ca45e0e0da1d9cc8fa8fa877ceb68c686d2
-R bdfb959c0be321608fe703b9663eda08
+P 28c6e964b1e2257527df02b352f02e135f7c4b764b8b41eda6461f9538f5d042
+R 4c5edeff0b59e42b46536f2b483d14f6
+T *branch * json-opt-rcstr
+T *sym-json-opt-rcstr *
+T -sym-json-opt *
 U drh
-Z 1ec3b6ff4cb1459575429b1da04d690b
+Z 3f6f4e5856b228a13fb14e603baeb559
 # Remove this line to create a well-formed Fossil manifest.
index 9eac82e0e2441423b3181511a5fec51e130904e0..59c5808d178d7e5afa8408d3a4c92b0077575828 100644 (file)
@@ -1 +1 @@
-28c6e964b1e2257527df02b352f02e135f7c4b764b8b41eda6461f9538f5d042
\ No newline at end of file
+c1b8725089bb3d006ec69add28f4fcb3f4e79412c7f438b5b1067c2227e77b9c
\ No newline at end of file
index 0ce68f1fcb298ec2eae89fc884e590845ecac5c6..3bfa0c7484e747d3e11b3633c49c83e4f53e6992 100644 (file)
@@ -191,7 +191,7 @@ static void jsonInit(JsonString *p, sqlite3_context *pCtx){
 ** initial state.
 */
 static void jsonReset(JsonString *p){
-  if( !p->bStatic ) sqlite3_free(p->zBuf);
+  if( !p->bStatic ) sqlite3RCStrUnref(p->zBuf);
   jsonZero(p);
 }
 
@@ -212,7 +212,7 @@ static int jsonGrow(JsonString *p, u32 N){
   char *zNew;
   if( p->bStatic ){
     if( p->bErr ) return 1;
-    zNew = sqlite3_malloc64(nTotal);
+    zNew = sqlite3RCStrNew(nTotal);
     if( zNew==0 ){
       jsonOom(p);
       return SQLITE_NOMEM;
@@ -221,12 +221,12 @@ static int jsonGrow(JsonString *p, u32 N){
     p->zBuf = zNew;
     p->bStatic = 0;
   }else{
-    zNew = sqlite3_realloc64(p->zBuf, nTotal);
-    if( zNew==0 ){
-      jsonOom(p);
+    p->zBuf = sqlite3RCStrResize(p->zBuf, nTotal);
+    if( p->zBuf==0 ){
+      p->bErr = 1;
+      jsonZero(p);
       return SQLITE_NOMEM;
     }
-    p->zBuf = zNew;
   }
   p->nAlloc = nTotal;
   return SQLITE_OK;
@@ -514,16 +514,26 @@ static void jsonAppendValue(
 
 
 /* Make the JSON in p the result of the SQL function.
+**
+** The JSON string is reset.
 */
 static void jsonResult(JsonString *p){
   if( p->bErr==0 ){
-    jsonAppendChar(p, 0);
-    sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed-1,
-                          p->bStatic ? SQLITE_TRANSIENT : sqlite3_free,
-                          SQLITE_UTF8);
-    jsonZero(p);
+    if( p->bStatic ){
+      sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed, SQLITE_TRANSIENT,
+                            SQLITE_UTF8);
+    }else{
+      jsonAppendChar(p, 0);
+      p->nUsed--;
+      sqlite3RCStrRef(p->zBuf);
+      sqlite3_result_text64(p->pCtx, p->zBuf, p->nUsed,
+                            (void(*)(void*))sqlite3RCStrUnref,
+                            SQLITE_UTF8);
+    }
+  }else{
+    sqlite3_result_error_nomem(p->pCtx);
   }
-  assert( p->bStatic );
+  jsonReset(p);
 }
 
 /**************************************************************************
@@ -1885,7 +1895,7 @@ static JsonNode *jsonLookupStep(
   JsonNode *pRoot = &pParse->aNode[iRoot];
   while( (pRoot->jnFlags & JNODE_REPLACE)!=0 ){
     u32 idx = (u32)(pRoot - pParse->aNode);
-    u32 i = pParse->iSubst;
+    i = pParse->iSubst;
     while( 1 /*exit-by-break*/ ){
       assert( i<pParse->nNode );
       assert( pParse->aNode[i].eType==JSON_SUBST );
@@ -2950,7 +2960,8 @@ static void jsonArrayCompute(sqlite3_context *ctx, int isFinal){
       assert( pStr->bStatic );
     }else if( isFinal ){
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
-                          pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free);
+                          pStr->bStatic ? SQLITE_TRANSIENT :
+                              (void(*)(void*))sqlite3RCStrUnref);
       pStr->bStatic = 1;
     }else{
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
@@ -3058,7 +3069,8 @@ static void jsonObjectCompute(sqlite3_context *ctx, int isFinal){
       assert( pStr->bStatic );
     }else if( isFinal ){
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed,
-                          pStr->bStatic ? SQLITE_TRANSIENT : sqlite3_free);
+                          pStr->bStatic ? SQLITE_TRANSIENT :
+                          (void(*)(void*))sqlite3RCStrUnref);
       pStr->bStatic = 1;
     }else{
       sqlite3_result_text(ctx, pStr->zBuf, (int)pStr->nUsed, SQLITE_TRANSIENT);
index 3fb1a322a00ac05f516465527c4d976d86120d01..7b842881686850005d38bae9590b573b546d41b3 100644 (file)
@@ -1366,3 +1366,146 @@ void sqlite3_str_appendf(StrAccum *p, const char *zFormat, ...){
   sqlite3_str_vappendf(p, zFormat, ap);
   va_end(ap);
 }
+
+
+/*****************************************************************************
+** Reference counted string storage
+*****************************************************************************/
+
+/*
+** Increase the reference count of the string by one.
+**
+** The input parameter is returned.
+*/
+char *sqlite3RCStrRef(char *z){
+  RCStr *p = (RCStr*)z;
+  assert( p!=0 );
+  p--;
+  p->nRCRef++;
+  return z;
+}
+
+/*
+** Decrease the reference count by one.  Free the string when the
+** reference count reaches zero.
+*/
+void sqlite3RCStrUnref(char *z){
+  RCStr *p = (RCStr*)z;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef>0 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  if( p->nRCRef>=2 ){
+    p->nRCRef--;
+  }else{
+    if( p->xFree ) p->xFree(p->pAttach);
+#ifdef SQLITE_DEBUG
+    p->uMagic = 0;
+#endif
+    sqlite3_free(p);
+  }
+}
+
+/*
+** Return true if the reference count on the string is exactly one, meaning
+** that the string can be modified.  Return false if the reference count
+** is greater than one.
+*/
+int sqlite3RCStrIsWriteable(char *z){
+  RCStr *p = (RCStr*)z;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef>0 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  return p->nRCRef==1;
+}
+
+/*
+** Create a new string that is capable of holding N bytes of text, not counting
+** the zero byte at the end.  The string is uninitialized.
+**
+** The reference count is initially 1.  Call sqlite3RCStrUnref() to free the
+** newly allocated string.
+**
+** This routine returns 0 on an OOM.
+*/
+char *sqlite3RCStrNew(u64 N){
+  RCStr *p = sqlite3_malloc64( N + sizeof(*p) );
+  if( p==0 ) return 0;
+  p->nRCRef = 1;
+  p->xFree = 0;
+  p->pAttach = 0;
+#ifdef SQLITE_DEBUG
+  p->uMagic = SQLITE_RCSTR_MAGIC;
+#endif
+  return (char*)&p[1];
+}
+
+/*
+** Return the number of bytes allocated to the string.  The value returned
+** does not include the space for the zero-terminator at the end.
+*/
+u64 sqlite3RCStrSize(char *z){
+  RCStr *p = (RCStr*)z;
+  u64 N;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef>0 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  N = sqlite3_msize(p);
+  N -= sizeof(p) + 1;
+  return N;
+}
+
+/*
+** Change the size of the string so that it is able to hold N bytes.
+** The string might be reallocated, so return the new allocation.
+*/
+char *sqlite3RCStrResize(char *z, u64 N){
+  RCStr *p = (RCStr*)z;
+  RCStr *pNew;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef==1 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  pNew = sqlite3_realloc64(p, N+sizeof(RCStr)+1);
+  if( pNew==0 ){
+    sqlite3_free(p);
+    return 0;
+  }else{
+    return (char*)&pNew[1];
+  }
+}
+
+/*
+** Add a new attachment to the string.
+**
+** A string may have no more than one attachment.  When a new attachment
+** is added, any prior attachment is destroyed.  Remove an attachment
+** by adding a zero-attachment.
+*/
+void sqlite3RCStrAttach(char *z, void *pAttach, void(*xFree)(void*)){
+  RCStr *p = (RCStr*)z;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef>0 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  if( p->xFree ) p->xFree(p->pAttach);
+  p->xFree = xFree;
+  p->pAttach = pAttach;
+}
+  
+/*
+** Return the attachment associated with a string if the attachment
+** has the destructure specified in the second argument.  If the
+** string has no attachment or if the destructor does not match,
+** then return a NULL pointr.
+*/
+void *sqlite3RCStrGetAttachment(char *z, void(*xFree)(void*)){
+  RCStr *p = (RCStr*)z;
+  assert( p!=0 );
+  p--;
+  assert( p->nRCRef>0 );
+  assert( p->uMagic==SQLITE_RCSTR_MAGIC );
+  return p->xFree==xFree ? p->pAttach : 0;
+}
index f214862f7421c0d96a3a85c7c61b48c4d771b9f9..adf4d34dec8fe92c4dfb9d27f9054ce500e279b7 100644 (file)
@@ -1281,6 +1281,7 @@ typedef struct Parse Parse;
 typedef struct ParseCleanup ParseCleanup;
 typedef struct PreUpdate PreUpdate;
 typedef struct PrintfArguments PrintfArguments;
+typedef struct RCStr RCStr;
 typedef struct RenameToken RenameToken;
 typedef struct Returning Returning;
 typedef struct RowSet RowSet;
@@ -4061,6 +4062,47 @@ struct sqlite3_str {
 
 #define isMalloced(X)  (((X)->printfFlags & SQLITE_PRINTF_MALLOCED)!=0)
 
+/*
+** The following object is the header for an "RCStr" or "reference-counted
+** string".  An RCStr is passed around and used like any other char*
+** that has been dynamically allocated.  The important interface
+** difference is that it uses sqlite3RCStrUnref() as its destructor
+** rather than sqlite3_free().  Other than that the two are interchangeable.
+**
+** Thus to return an RCStr object as the result of an SQL function use:
+** 
+**    sqlite3_result_text64(ctx,z,sz,sqlite3RCStrUnref,SQLITE_UTF8)
+**                                   ^^^^^^^^^^^^^^^^^
+**                                   Instead of sqlite3_free() or similar
+**
+** An SQL function can check its arguments to see if they are RCStr
+** strings using the sqlite3ValueIsOfClass() function:
+**
+**    sqlite3ValueIsOfClass(argv[i], sqlite3RCStrUnref);
+**
+** An RCStr string might be better than an ordinary string in some cases
+** because:
+**
+**    (1)  You can duplicate it using sqlite3RCStrRef(x).
+**
+**    (2)  You can also add an associated object to the string.  For
+**         example, if the string is JSON, perhaps the associated object
+**         is a parse of that JSON.
+**
+** Methods for an RCStr string begin with "sqlite3RCStr...".
+*/
+struct RCStr {
+  u32 nRCRef;            /* Number of references */
+#ifdef SQLITE_DEBUG
+  u32 uMagic;            /* Magic number for sanity checking */
+#endif
+  void *pAttach;         /* Attachment to this string */
+  void (*xFree)(void*);  /* Destructor for the attachment */
+};
+
+/* The Magic number used by RCStr for sanity checking.  SQLITE_DEBUG only. */
+#define SQLITE_RCSTR_MAGIC 0x3dc05d54
+
 
 /*
 ** A pointer to this structure is used to communicate information
@@ -5180,6 +5222,7 @@ void sqlite3FileSuffix3(const char*, char*);
 u8 sqlite3GetBoolean(const char *z,u8);
 
 const void *sqlite3ValueText(sqlite3_value*, u8);
+int sqlite3ValueIsOfClass(const sqlite3_value*, void(*)(void*));
 int sqlite3ValueBytes(sqlite3_value*, u8);
 void sqlite3ValueSetStr(sqlite3_value*, int, const void *,u8,
                         void(*)(void*));
@@ -5287,6 +5330,15 @@ void sqlite3OomClear(sqlite3*);
 int sqlite3ApiExit(sqlite3 *db, int);
 int sqlite3OpenTempDatabase(Parse *);
 
+char *sqlite3RCStrRef(char*);
+void sqlite3RCStrUnref(char*);
+char *sqlite3RCStrNew(u64);
+u64 sqlite3RCStrSize(char*);
+char *sqlite3RCStrResize(char*,u64);
+int sqlite3RCStrIsWriteable(char*);
+void sqlite3RCStrAttach(char*, void*, void(*)(void*));
+void *sqlite3RCStrGetAttachment(char*,void(*)(void*));
+
 void sqlite3StrAccumInit(StrAccum*, sqlite3*, char*, int, int);
 int sqlite3StrAccumEnlarge(StrAccum*, i64);
 char *sqlite3StrAccumFinish(StrAccum*);
index b5a794ae8f7b8e8f93f508b10cb6438335d84861..87dfbbebd8cab53365e3bed717779be5ddcc77b1 100644 (file)
@@ -333,6 +333,11 @@ void sqlite3VdbeMemZeroTerminateIfAble(Mem *pMem){
       pMem->flags |= MEM_Term;
       return;
     }
+    if( pMem->xDel==(void(*)(void*))sqlite3RCStrUnref ){
+      /* Blindly assume that all RCStr objects are zero-terminated */
+      pMem->flags |= MEM_Term;
+      return;
+    }
   }else if( pMem->szMalloc>0 && pMem->szMalloc >= pMem->n+1 ){
     pMem->z[pMem->n] = 0;
     pMem->flags |= MEM_Term;
@@ -1363,6 +1368,24 @@ const void *sqlite3ValueText(sqlite3_value* pVal, u8 enc){
   return valueToText(pVal, enc);
 }
 
+/* Return true if sqlit3_value object pVal is a string or blob value
+** that uses the destructor specified in the second argument.
+**
+** TODO:  Maybe someday promote this interface into a published API so
+** that third-party extensions can get access to it?
+*/
+int sqlite3ValueIsOfClass(const sqlite3_value *pVal, void(*xFree)(void*)){
+  if( ALWAYS(pVal!=0)
+   && (pVal->flags & (MEM_Str|MEM_Blob))!=0
+   && (pVal->flags & MEM_Dyn)!=0
+   && pVal->xDel==xFree
+  ){
+    return 1;
+  }else{
+    return 0;
+  }
+}
+
 /*
 ** Create a new sqlite3_value object.
 */