diff options
author | drh <> | 2023-07-25 15:08:18 +0000 |
---|---|---|
committer | drh <> | 2023-07-25 15:08:18 +0000 |
commit | f02cc9a3248b07095847f9a5e93e092e4fa6e116 (patch) | |
tree | 44aa63dcc95e939bdd3f2aeed233e1bb347fd811 /src | |
parent | b715fe9d80b3656fd0afb82847932e6592306609 (diff) | |
download | sqlite-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.c | 42 | ||||
-rw-r--r-- | src/printf.c | 143 | ||||
-rw-r--r-- | src/sqliteInt.h | 52 | ||||
-rw-r--r-- | src/vdbemem.c | 23 |
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. */ |