From: drh Date: Wed, 19 Aug 2015 22:47:17 +0000 (+0000) Subject: Add the json_each(JSON,PATH) table-valued-function. X-Git-Tag: version-3.9.0~204^2~8 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=cb6c6c6f45955918e2b73653ae7c6676288de0b2;p=thirdparty%2Fsqlite.git Add the json_each(JSON,PATH) table-valued-function. FossilOrigin-Name: 3335ac17bbcb09dc915173d69bf42048f84ad563 --- diff --git a/ext/misc/json.c b/ext/misc/json.c index 274749726d..7e1d210e37 100644 --- a/ext/misc/json.c +++ b/ext/misc/json.c @@ -17,7 +17,9 @@ ** ** For the time being, all JSON is stored as pure text. (We might add ** a JSONB type in the future which stores a binary encoding of JSON in -** a BLOB, but there is no support for JSONB in the current implementation.) +** a BLOB, but there is no support for JSONB in the current implementation. +** This implementation parses JSON text at 250 MB/s, so it is hard to see +** how JSONB might improve on that.) */ #include "sqlite3ext.h" SQLITE_EXTENSION_INIT1 @@ -1156,6 +1158,276 @@ static void jsonTypeFunc( sqlite3_free(x.aNode); } +/**************************************************************************** +** The json_each virtual table +****************************************************************************/ +typedef struct JsonEachCursor JsonEachCursor; +struct JsonEachCursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + u32 iRowid; /* The rowid */ + u32 i; /* Index in sParse.aNode[] of current row */ + u32 iEnd; /* EOF when i equals or exceeds this value */ + u8 eType; /* Type of top-level element */ + char *zJson; /* Input json */ + char *zPath; /* Path by which to filter zJson */ + JsonParse sParse; /* The input json */ +}; + +/* Constructor for the json_each virtual table */ +static int jsonEachConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + sqlite3_vtab *pNew; + pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + +/* Column numbers */ +#define JEACH_KEY 0 +#define JEACH_VALUE 1 +#define JEACH_JSON 2 +#define JEACH_PATH 3 + + sqlite3_declare_vtab(db, "CREATE TABLE x(key,value,json hidden,path hidden)"); + memset(pNew, 0, sizeof(*pNew)); + return SQLITE_OK; +} + +/* destructor for json_each virtual table */ +static int jsonEachDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* constructor for a JsonEachCursor object. */ +static int jsonEachOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + JsonEachCursor *pCur; + pCur = sqlite3_malloc( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} + +/* Reset a JsonEachCursor back to its original state. Free any memory +** held. */ +static void jsonEachCursorReset(JsonEachCursor *p){ + sqlite3_free(p->zJson); + sqlite3_free(p->zPath); + sqlite3_free(p->sParse.aNode); + p->iRowid = 0; + p->i = 0; + p->iEnd = 0; + p->eType = 0; + memset(&p->sParse, 0, sizeof(p->sParse)); + p->zJson = 0; + p->zPath = 0; +} + +/* Destructor for a jsonEachCursor object */ +static int jsonEachClose(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + jsonEachCursorReset(p); + sqlite3_free(cur); + return SQLITE_OK; +} + +/* Return TRUE if the jsonEachCursor object has been advanced off the end +** of the JSON object */ +static int jsonEachEof(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + return p->i >= p->iEnd; +} + +/* Advance the cursor to the next top-level element of the current +** JSON string */ +static int jsonEachNext(sqlite3_vtab_cursor *cur){ + JsonEachCursor *p = (JsonEachCursor*)cur; + switch( p->eType ){ + case JSON_ARRAY: { + p->i += jsonSize(&p->sParse.aNode[p->i]); + p->iRowid++; + break; + } + case JSON_OBJECT: { + p->i += 1 + jsonSize(&p->sParse.aNode[p->i+1]); + p->iRowid++; + break; + } + default: { + p->i = p->iEnd; + break; + } + } + return SQLITE_OK; +} + +/* Return the value of a column */ +static int jsonEachColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + JsonEachCursor *p = (JsonEachCursor*)cur; + switch( i ){ + case JEACH_KEY: { + if( p->eType==JSON_OBJECT ){ + jsonReturn(&p->sParse.aNode[p->i], ctx, 0); + }else{ + sqlite3_result_int64(ctx, p->iRowid); + } + break; + } + case JEACH_VALUE: { + if( p->eType==JSON_OBJECT ){ + jsonReturn(&p->sParse.aNode[p->i+1], ctx, 0); + }else{ + jsonReturn(&p->sParse.aNode[p->i], ctx, 0); + } + break; + } + case JEACH_PATH: { + const char *zPath = p->zPath; + if( zPath==0 ) zPath = "$"; + sqlite3_result_text(ctx, zPath, -1, SQLITE_STATIC); + break; + } + default: { + sqlite3_result_text(ctx, p->sParse.zJson, -1, SQLITE_STATIC); + break; + } + } + return SQLITE_OK; +} + +/* Return the current rowid value */ +static int jsonEachRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + JsonEachCursor *p = (JsonEachCursor*)cur; + *pRowid = p->iRowid; + return SQLITE_OK; +} + +/* The query strategy is to look for an equality constraint on the json +** column. Without such a constraint, the table cannot operate. idxNum is +** 1 if the constraint is found, 3 if the constraint and zPath are found, +** and 0 otherwise. +*/ +static int jsonEachBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; + int jsonIdx = -1; + int pathIdx = -1; + const struct sqlite3_index_constraint *pConstraint; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->usable==0 ) continue; + if( pConstraint->op!=SQLITE_INDEX_CONSTRAINT_EQ ) continue; + switch( pConstraint->iColumn ){ + case JEACH_JSON: jsonIdx = i; break; + case JEACH_PATH: pathIdx = i; break; + default: /* no-op */ break; + } + } + if( jsonIdx<0 ){ + pIdxInfo->idxNum = 0; + pIdxInfo->estimatedCost = (double)2000000000; + }else{ + pIdxInfo->estimatedCost = (double)1; + pIdxInfo->aConstraintUsage[jsonIdx].argvIndex = 1; + pIdxInfo->aConstraintUsage[jsonIdx].omit = 1; + if( pathIdx<0 ){ + pIdxInfo->idxNum = 1; + }else{ + pIdxInfo->aConstraintUsage[pathIdx].argvIndex = 2; + pIdxInfo->aConstraintUsage[pathIdx].omit = 1; + pIdxInfo->idxNum = 3; + } + } + return SQLITE_OK; +} + +/* Start a search on a new JSON string */ +static int jsonEachFilter( + sqlite3_vtab_cursor *cur, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + JsonEachCursor *p = (JsonEachCursor*)cur; + const char *z; + const char *zPath; + sqlite3_int64 n; + + jsonEachCursorReset(p); + if( idxNum==0 ) return SQLITE_OK; + z = (const char*)sqlite3_value_text(argv[0]); + if( z==0 ) return SQLITE_OK; + if( idxNum&2 ){ + zPath = (const char*)sqlite3_value_text(argv[1]); + if( zPath==0 || zPath[0]!='$' ) return SQLITE_OK; + } + n = sqlite3_value_bytes(argv[0]); + p->zJson = sqlite3_malloc( n+1 ); + if( p->zJson==0 ) return SQLITE_NOMEM; + memcpy(p->zJson, z, n+1); + if( jsonParse(&p->sParse, p->zJson) ){ + jsonEachCursorReset(p); + }else{ + JsonNode *pNode; + if( idxNum==3 ){ + n = sqlite3_value_bytes(argv[1]); + p->zPath = sqlite3_malloc( n+1 ); + if( p->zPath==0 ) return SQLITE_NOMEM; + memcpy(p->zPath, zPath, n+1); + pNode = jsonLookup(&p->sParse, 0, p->zPath+1, 0); + if( pNode==0 ){ + jsonEachCursorReset(p); + return SQLITE_OK; + } + }else{ + pNode = p->sParse.aNode; + } + p->i = (int)(pNode - p->sParse.aNode); + p->eType = pNode->eType; + if( p->eType>=JSON_ARRAY ){ + p->i++; + p->iEnd = p->i + pNode->n; + }else{ + p->iEnd = p->i+1; + } + } + return SQLITE_OK; +} + +/* The methods of the json_each virtual table */ +static sqlite3_module jsonEachModule = { + 0, /* iVersion */ + 0, /* xCreate */ + jsonEachConnect, /* xConnect */ + jsonEachBestIndex, /* xBestIndex */ + jsonEachDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + jsonEachOpen, /* xOpen - open a cursor */ + jsonEachClose, /* xClose - close a cursor */ + jsonEachFilter, /* xFilter - configure scan constraints */ + jsonEachNext, /* xNext - advance a cursor */ + jsonEachEof, /* xEof - check for end of scan */ + jsonEachColumn, /* xColumn - read data */ + jsonEachRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ +}; + + #ifdef _WIN32 __declspec(dllexport) #endif @@ -1199,5 +1471,8 @@ int sqlite3_json_init( (void*)&aFunc[i].flag, aFunc[i].xFunc, 0, 0); } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_module(db, "json_each", &jsonEachModule, 0); + } return rc; } diff --git a/manifest b/manifest index fa1a720a00..d5dc07e75e 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Merge\sthe\stable-valued-function\srowid\sfix. -D 2015-08-19T19:26:13.526 +C Add\sthe\sjson_each(JSON,PATH)\stable-valued-function. +D 2015-08-19T22:47:17.476 F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f F Makefile.in 90f3097efb9a53f5fc59a4f8a08be07cf9f52c02 F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23 @@ -192,7 +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 3b265167a9f8f006042bf0226a838c773a098c03 +F ext/misc/json.c 57a9f747b2813edc5f481eb808889d304a1c5f79 F ext/misc/nextchar.c 35c8b8baacb96d92abbb34a83a997b797075b342 F ext/misc/percentile.c bcbee3c061b884eccb80e21651daaae8e1e43c63 F ext/misc/regexp.c af92cdaa5058fcec1451e49becc7ba44dba023dc @@ -1378,7 +1378,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1 F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4 F tool/warnings.sh 48bd54594752d5be3337f12c72f28d2080cb630b F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f -P 96a5d44d9fcb7b159c04630ad2d956fe27df5a43 a325a08599759471047e234ef9cfcc3cb110aafd -R dcfe96af8a1b65c426e62b553bd60db8 +P a06a6392bd48baa8b9bac2624869c0cc7da7e779 +R 43d8c9f353202a0c5a0252660436f9ac U drh -Z 00eb910bc67976561bafd5a76ff4be3d +Z 41b78b71d0425818407424da18ee9720 diff --git a/manifest.uuid b/manifest.uuid index ab65dd6ddb..a1d2dfbb42 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -a06a6392bd48baa8b9bac2624869c0cc7da7e779 \ No newline at end of file +3335ac17bbcb09dc915173d69bf42048f84ad563 \ No newline at end of file