aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordrh <>2023-07-25 15:08:18 +0000
committerdrh <>2023-07-25 15:08:18 +0000
commitf02cc9a3248b07095847f9a5e93e092e4fa6e116 (patch)
tree44aa63dcc95e939bdd3f2aeed233e1bb347fd811 /src
parentb715fe9d80b3656fd0afb82847932e6592306609 (diff)
downloadsqlite-f02cc9a3248b07095847f9a5e93e092e4fa6e116.tar.gz
sqlite-f02cc9a3248b07095847f9a5e93e092e4fa6e116.zip
Create the new RCStr class of strings and try to use them for JSON storage.
FossilOrigin-Name: c1b8725089bb3d006ec69add28f4fcb3f4e79412c7f438b5b1067c2227e77b9c
Diffstat (limited to 'src')
-rw-r--r--src/json.c42
-rw-r--r--src/printf.c143
-rw-r--r--src/sqliteInt.h52
-rw-r--r--src/vdbemem.c23
4 files changed, 245 insertions, 15 deletions
diff --git a/src/json.c b/src/json.c
index 0ce68f1fc..3bfa0c748 100644
--- a/src/json.c
+++ b/src/json.c
@@ -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);
diff --git a/src/printf.c b/src/printf.c
index 3fb1a322a..7b8428816 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -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;
+}
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index f214862f7..adf4d34de 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -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*);
diff --git a/src/vdbemem.c b/src/vdbemem.c
index b5a794ae8..87dfbbebd 100644
--- a/src/vdbemem.c
+++ b/src/vdbemem.c
@@ -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.
*/