ADDED ext/rbu/rbupartial.test Index: ext/rbu/rbupartial.test ================================================================== --- /dev/null +++ ext/rbu/rbupartial.test @@ -0,0 +1,86 @@ +# 2019 April 11 +# +# 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. +# +#*********************************************************************** +# + +source [file join [file dirname [info script]] rbu_common.tcl] +set ::testprefix rbupartial + +db close +sqlite3_shutdown +sqlite3_config_uri 1 + +foreach {tn without_rowid a b c d} { + 1 "" a b c d + 2 "WITHOUT ROWID" aaa bbb ccc ddd + 3 "WITHOUT ROWID" "\"hello\"" {"one'two"} {[c]} ddd + 4 "WITHOUT ROWID" {`a b`} {"one'two"} {[c c c]} ddd + 5 "" a b c {"d""d"} + 6 "" {'one''two'} b {"c""c"} {"d""d"} +} { + eval [string map [list \ + %WITHOUT_ROWID% $without_rowid %A% $a %B% $b %C% $c %D% $d + ] { + reset_db + do_execsql_test $tn.1.0 { + CREATE TABLE t1(%A% PRIMARY KEY, %B%, %C%, %D%) %WITHOUT_ROWID% ; + CREATE INDEX i1b ON t1(%B%); + CREATE INDEX i1b2 ON t1(%B%) WHERE %C%<5; + CREATE INDEX i1b3 ON t1(%B%) WHERE %C%>=5; + + CREATE INDEX i1c ON t1(%C%); + CREATE INDEX i1c2 ON t1(%C%) WHERE %C% IS NULL; + CREATE INDEX i1c3 ON t1(%C%) WHERE %C% IS NOT NULL; + + CREATE INDEX i1c4 ON t1(%C%) WHERE %D% < 'd'; + } + + do_execsql_test $tn.1.1 { + INSERT INTO t1 VALUES(0, NULL, NULL, 'a'); + INSERT INTO t1 VALUES(1, 2, 3, 'b'); + INSERT INTO t1 VALUES(4, 5, 6, 'c'); + INSERT INTO t1 VALUES(7, 8, 9, 'd'); + } + + forcedelete rbu.db + do_test $tn.1.2 { + sqlite3 rbu rbu.db + rbu eval { + CREATE TABLE data_t1(%A%, %B%, %C%, %D%, rbu_control); + + INSERT INTO data_t1 VALUES(10, 11, 12, 'e', 0); + INSERT INTO data_t1 VALUES(13, 14, NULL, 'f', 0); + + INSERT INTO data_t1 VALUES(0, NULL, NULL, NULL, 1); + INSERT INTO data_t1 VALUES(4, NULL, NULL, NULL, 1); + + INSERT INTO data_t1 VALUES(7, NULL, 4, NULL, '..x.'); + INSERT INTO data_t1 VALUES(1, 10, NULL, NULL, '.xx.'); + } + rbu close + } {} + + do_test $tn.1.3 { + run_rbu test.db rbu.db + execsql { PRAGMA integrity_check } + } {ok} + + do_execsql_test $tn.1.4 { + SELECT * FROM t1 ORDER BY %A%; + } { + 1 10 {} b 7 8 4 d 10 11 12 e 13 14 {} f + } + + set step 0 + do_rbu_vacuum_test $tn.1.5 0 + }] +} + +finish_test Index: ext/rbu/sqlite3rbu.c ================================================================== --- ext/rbu/sqlite3rbu.c +++ ext/rbu/sqlite3rbu.c @@ -238,10 +238,15 @@ ** abIndexed: ** If the table has no indexes on it, abIndexed is set to NULL. Otherwise, ** it points to an array of flags nTblCol elements in size. The flag is ** set for each column that is either a part of the PK or a part of an ** index. Or clear otherwise. +** +** If there are one or more partial indexes on the table, all fields of +** this array set set to 1. This is because in that case, the module has +** no way to tell which fields will be required to add and remove entries +** from the partial indexes. ** */ struct RbuObjIter { sqlite3_stmt *pTblIter; /* Iterate through tables */ sqlite3_stmt *pIdxIter; /* Index iterator */ @@ -1248,12 +1253,16 @@ } pIter->nIndex = 0; while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pList) ){ const char *zIdx = (const char*)sqlite3_column_text(pList, 1); + int bPartial = sqlite3_column_int(pList, 4); sqlite3_stmt *pXInfo = 0; if( zIdx==0 ) break; + if( bPartial ){ + memset(pIter->abIndexed, 0x01, sizeof(u8)*pIter->nTblCol); + } p->rc = prepareFreeAndCollectError(p->dbMain, &pXInfo, &p->zErrmsg, sqlite3_mprintf("PRAGMA main.index_xinfo = %Q", zIdx) ); while( p->rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pXInfo) ){ int iCid = sqlite3_column_int(pXInfo, 1); @@ -1955,10 +1964,66 @@ if( rc!=SQLITE_OK ){ sqlite3_result_error_code(pCtx, rc); } } + +static char *rbuObjIterGetIndexWhere(sqlite3rbu *p, RbuObjIter *pIter){ + sqlite3_stmt *pStmt = 0; + int rc = p->rc; + char *zRet = 0; + + if( rc==SQLITE_OK ){ + rc = prepareAndCollectError(p->dbMain, &pStmt, &p->zErrmsg, + "SELECT trim(sql) FROM sqlite_master WHERE type='index' AND name=?" + ); + } + if( rc==SQLITE_OK ){ + int rc2; + rc = sqlite3_bind_text(pStmt, 1, pIter->zIdx, -1, SQLITE_STATIC); + if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ + const char *zSql = (const char*)sqlite3_column_text(pStmt, 0); + if( zSql ){ + int nParen = 0; /* Number of open parenthesis */ + int i; + for(i=0; zSql[i]; i++){ + char c = zSql[i]; + if( c=='(' ){ + nParen++; + } + else if( c==')' ){ + nParen--; + if( nParen==0 ){ + i++; + break; + } + }else if( c=='"' || c=='\'' || c=='`' ){ + for(i++; 1; i++){ + if( zSql[i]==c ){ + if( zSql[i+1]!=c ) break; + i++; + } + } + }else if( c=='[' ){ + for(i++; 1; i++){ + if( zSql[i]==']' ) break; + } + } + } + if( zSql[i] ){ + zRet = rbuStrndup(&zSql[i], &rc); + } + } + } + + rc2 = sqlite3_finalize(pStmt); + if( rc==SQLITE_OK ) rc = rc2; + } + + p->rc = rc; + return zRet; +} /* ** Ensure that the SQLite statement handles required to update the ** target database object currently indicated by the iterator passed ** as the second argument are available. @@ -1985,17 +2050,19 @@ const char *zTbl = pIter->zTbl; char *zImposterCols = 0; /* Columns for imposter table */ char *zImposterPK = 0; /* Primary key declaration for imposter */ char *zWhere = 0; /* WHERE clause on PK columns */ char *zBind = 0; + char *zPart = 0; int nBind = 0; assert( pIter->eType!=RBU_PK_VTAB ); zCollist = rbuObjIterGetIndexCols( p, pIter, &zImposterCols, &zImposterPK, &zWhere, &nBind ); zBind = rbuObjIterGetBindlist(p, nBind); + zPart = rbuObjIterGetIndexWhere(p, pIter); /* Create the imposter table used to write to this index. */ sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 0, 1); sqlite3_test_control(SQLITE_TESTCTRL_IMPOSTER, p->dbMain, "main", 1,tnum); rbuMPrintfExec(p, p->dbMain, @@ -2024,32 +2091,34 @@ /* Create the SELECT statement to read keys in sorted order */ if( p->rc==SQLITE_OK ){ char *zSql; if( rbuIsVacuum(p) ){ zSql = sqlite3_mprintf( - "SELECT %s, 0 AS rbu_control FROM '%q' ORDER BY %s%s", + "SELECT %s, 0 AS rbu_control FROM '%q' %s ORDER BY %s%s", zCollist, pIter->zDataTbl, - zCollist, zLimit + zPart, zCollist, zLimit ); }else if( pIter->eType==RBU_PK_EXTERNAL || pIter->eType==RBU_PK_NONE ){ zSql = sqlite3_mprintf( - "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' ORDER BY %s%s", + "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s ORDER BY %s%s", zCollist, p->zStateDb, pIter->zDataTbl, - zCollist, zLimit + zPart, zCollist, zLimit ); }else{ zSql = sqlite3_mprintf( - "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' " + "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' %s " "UNION ALL " "SELECT %s, rbu_control FROM '%q' " - "WHERE typeof(rbu_control)='integer' AND rbu_control!=1 " + "%s %s typeof(rbu_control)='integer' AND rbu_control!=1 " "ORDER BY %s%s", - zCollist, p->zStateDb, pIter->zDataTbl, + zCollist, p->zStateDb, pIter->zDataTbl, zPart, zCollist, pIter->zDataTbl, + zPart, + (zPart ? "AND" : "WHERE"), zCollist, zLimit ); } p->rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pSelect, pz, zSql); } @@ -2056,10 +2125,11 @@ sqlite3_free(zImposterCols); sqlite3_free(zImposterPK); sqlite3_free(zWhere); sqlite3_free(zBind); + sqlite3_free(zPart); }else{ int bRbuRowid = (pIter->eType==RBU_PK_VTAB) ||(pIter->eType==RBU_PK_NONE) ||(pIter->eType==RBU_PK_EXTERNAL && rbuIsVacuum(p)); const char *zTbl = pIter->zTbl; /* Table this step applies to */ Index: main.mk ================================================================== --- main.mk +++ main.mk @@ -376,11 +376,10 @@ $(TOP)/ext/misc/series.c \ $(TOP)/ext/misc/spellfix.c \ $(TOP)/ext/misc/totype.c \ $(TOP)/ext/misc/unionvtab.c \ $(TOP)/ext/misc/wholenumber.c \ - $(TOP)/ext/misc/vfslog.c \ $(TOP)/ext/misc/zipfile.c \ $(TOP)/ext/fts5/fts5_tcl.c \ $(TOP)/ext/fts5/fts5_test_mi.c \ $(TOP)/ext/fts5/fts5_test_tok.c Index: src/btree.c ================================================================== --- src/btree.c +++ src/btree.c @@ -6168,11 +6168,11 @@ MemPage *pTrunk = 0; /* Free-list trunk page */ Pgno iTrunk = 0; /* Page number of free-list trunk page */ MemPage *pPage1 = pBt->pPage1; /* Local reference to page 1 */ MemPage *pPage; /* Page being freed. May be NULL. */ int rc; /* Return Code */ - int nFree; /* Initial number of pages on free-list */ + u32 nFree; /* Initial number of pages on free-list */ assert( sqlite3_mutex_held(pBt->mutex) ); assert( CORRUPT_DB || iPage>1 ); assert( !pMemPage || pMemPage->pgno==iPage ); Index: src/build.c ================================================================== --- src/build.c +++ src/build.c @@ -4396,11 +4396,12 @@ char *zErr; int j; StrAccum errMsg; Table *pTab = pIdx->pTable; - sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, 200); + sqlite3StrAccumInit(&errMsg, pParse->db, 0, 0, + pParse->db->aLimit[SQLITE_LIMIT_LENGTH]); if( pIdx->aColExpr ){ sqlite3_str_appendf(&errMsg, "index '%q'", pIdx->zName); }else{ for(j=0; jnKeyCol; j++){ char *zCol; Index: src/printf.c ================================================================== --- src/printf.c +++ src/printf.c @@ -135,10 +135,11 @@ */ static void setStrAccumError(StrAccum *p, u8 eError){ assert( eError==SQLITE_NOMEM || eError==SQLITE_TOOBIG ); p->accError = eError; if( p->mxAlloc ) sqlite3_str_reset(p); + if( eError==SQLITE_TOOBIG ) sqlite3ErrorToParser(p->db, eError); } /* ** Extra argument values from a PrintfArguments object */ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -3832,10 +3832,11 @@ #endif void sqlite3SetString(char **, sqlite3*, const char*); void sqlite3ErrorMsg(Parse*, const char*, ...); +int sqlite3ErrorToParser(sqlite3*,int); void sqlite3Dequote(char*); void sqlite3DequoteExpr(Expr*); void sqlite3TokenInit(Token*,char*); int sqlite3KeywordCode(const unsigned char*, int); int sqlite3RunParser(Parse*, const char*, char **); Index: src/test4.c ================================================================== --- src/test4.c +++ src/test4.c @@ -62,11 +62,11 @@ /* ** The main loop for a thread. Threads use busy waiting. */ -static void *thread_main(void *pArg){ +static void *test_thread_main(void *pArg){ Thread *p = (Thread*)pArg; if( p->db ){ sqlite3_close(p->db); } sqlite3_open(p->zFilename, &p->db); @@ -149,11 +149,11 @@ threadset[i].busy = 1; sqlite3_free(threadset[i].zFilename); threadset[i].zFilename = sqlite3_mprintf("%s", argv[2]); threadset[i].opnum = 1; threadset[i].completed = 0; - rc = pthread_create(&x, 0, thread_main, &threadset[i]); + rc = pthread_create(&x, 0, test_thread_main, &threadset[i]); if( rc ){ Tcl_AppendResult(interp, "failed to create the thread", 0); sqlite3_free(threadset[i].zFilename); threadset[i].busy = 0; return TCL_ERROR; @@ -163,11 +163,11 @@ } /* ** Wait for a thread to reach its idle state. */ -static void thread_wait(Thread *p){ +static void test_thread_wait(Thread *p){ while( p->opnum>p->completed ) sched_yield(); } /* ** Usage: thread_wait ID @@ -191,22 +191,22 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); return TCL_OK; } /* ** Stop a thread. */ -static void stop_thread(Thread *p){ - thread_wait(p); +static void test_stop_thread(Thread *p){ + test_thread_wait(p); p->xOp = 0; p->opnum++; - thread_wait(p); + test_thread_wait(p); sqlite3_free(p->zArg); p->zArg = 0; sqlite3_free(p->zFilename); p->zFilename = 0; p->busy = 0; @@ -231,20 +231,20 @@ " ID", 0); return TCL_ERROR; } if( argv[1][0]=='*' && argv[1][1]==0 ){ for(i=0; i=threadset[i].argc ){ Tcl_AppendResult(interp, "column number out of range", 0); return TCL_ERROR; } Tcl_AppendResult(interp, threadset[i].argv[n], 0); @@ -340,11 +340,11 @@ if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } if( Tcl_GetInt(interp, argv[2], &n) ) return TCL_ERROR; - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); if( n<0 || n>=threadset[i].argc ){ Tcl_AppendResult(interp, "column number out of range", 0); return TCL_ERROR; } Tcl_AppendResult(interp, threadset[i].colv[n], 0); @@ -375,11 +375,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); zName = sqlite3ErrName(threadset[i].rc); Tcl_AppendResult(interp, zName, 0); return TCL_OK; } @@ -406,11 +406,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); Tcl_AppendResult(interp, threadset[i].zErr, 0); return TCL_OK; } /* @@ -450,11 +450,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); threadset[i].xOp = do_compile; sqlite3_free(threadset[i].zArg); threadset[i].zArg = sqlite3_mprintf("%s", argv[2]); threadset[i].opnum++; return TCL_OK; @@ -503,11 +503,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); threadset[i].xOp = do_step; threadset[i].opnum++; return TCL_OK; } @@ -545,11 +545,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); threadset[i].xOp = do_finalize; sqlite3_free(threadset[i].zArg); threadset[i].zArg = 0; threadset[i].opnum++; return TCL_OK; @@ -577,18 +577,18 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); j = parse_thread_id(interp, argv[2]); if( j<0 ) return TCL_ERROR; if( !threadset[j].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[j]); + test_thread_wait(&threadset[j]); temp = threadset[i].db; threadset[i].db = threadset[j].db; threadset[j].db = temp; return TCL_OK; } @@ -618,11 +618,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); sqlite3TestMakePointerStr(interp, zBuf, threadset[i].db); threadset[i].db = 0; Tcl_AppendResult(interp, zBuf, (char*)0); return TCL_OK; } @@ -649,11 +649,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); assert( !threadset[i].db ); threadset[i].db = (sqlite3*)sqlite3TestTextToPtr(argv[2]); return TCL_OK; } @@ -681,11 +681,11 @@ if( i<0 ) return TCL_ERROR; if( !threadset[i].busy ){ Tcl_AppendResult(interp, "no such thread", 0); return TCL_ERROR; } - thread_wait(&threadset[i]); + test_thread_wait(&threadset[i]); sqlite3TestMakePointerStr(interp, zBuf, threadset[i].pStmt); threadset[i].pStmt = 0; Tcl_AppendResult(interp, zBuf, (char*)0); return TCL_OK; } Index: src/util.c ================================================================== --- src/util.c +++ src/util.c @@ -230,10 +230,23 @@ sqlite3DbFree(db, pParse->zErrMsg); pParse->zErrMsg = zMsg; pParse->rc = SQLITE_ERROR; } } + +/* +** If database connection db is currently parsing SQL, then transfer +** error code errCode to that parser if the parser has not already +** encountered some other kind of error. +*/ +int sqlite3ErrorToParser(sqlite3 *db, int errCode){ + Parse *pParse; + if( db==0 || (pParse = db->pParse)==0 ) return errCode; + pParse->rc = errCode; + pParse->nErr++; + return errCode; +} /* ** Convert an SQL-style quoted string into a normal string by removing ** the quote characters. The conversion is done in-place. If the ** input does not begin with a quote character, then this routine Index: src/vdbemem.c ================================================================== --- src/vdbemem.c +++ src/vdbemem.c @@ -190,14 +190,11 @@ assert( !sqlite3VdbeMemIsRowSet(pMem) ); testcase( pMem->db==0 ); /* If the bPreserve flag is set to true, then the memory cell must already ** contain a valid string or blob value. */ - assert( bPreserve==0 - || pMem->flags&(MEM_Blob|MEM_Str) - || MemNullNochng(pMem) - ); + assert( bPreserve==0 || pMem->flags&(MEM_Blob|MEM_Str) ); testcase( bPreserve && pMem->z==0 ); assert( pMem->szMalloc==0 || pMem->szMalloc==sqlite3DbMallocSize(pMem->db, pMem->zMalloc) ); if( pMem->szMalloc>0 && bPreserve && pMem->z==pMem->zMalloc ){ @@ -307,10 +304,11 @@ assert( pMem->db==0 || sqlite3_mutex_held(pMem->db->mutex) ); /* Set nByte to the number of bytes required to store the expanded blob. */ nByte = pMem->n + pMem->u.nZero; if( nByte<=0 ){ + if( (pMem->flags & MEM_Blob)==0 ) return SQLITE_OK; nByte = 1; } if( sqlite3VdbeMemGrow(pMem, nByte, 1) ){ return SQLITE_NOMEM_BKPT; } @@ -1063,11 +1061,11 @@ u32 nAlloc = nByte; if( flags&MEM_Term ){ nAlloc += (enc==SQLITE_UTF8?1:2); } if( nByte>iLimit ){ - return SQLITE_TOOBIG; + return sqlite3ErrorToParser(pMem->db, SQLITE_TOOBIG); } testcase( nAlloc==0 ); testcase( nAlloc==31 ); testcase( nAlloc==32 ); if( sqlite3VdbeMemClearAndResize(pMem, (int)MAX(nAlloc,32)) ){ Index: test/sqllimits1.test ================================================================== --- test/sqllimits1.test +++ test/sqllimits1.test @@ -887,6 +887,16 @@ foreach {key value} [array get saved] { catch {set $key $value} } + +#------------------------------------------------------------------------- +# At one point the following caused an assert() to fail. +# +sqlite3_limit db SQLITE_LIMIT_LENGTH 10000 +set nm [string repeat x 10000] +do_catchsql_test sqllimits1-17.1 " + CREATE TABLE $nm (x PRIMARY KEY) +" {1 {string or blob too big}} + finish_test Index: tool/omittest.tcl ================================================================== --- tool/omittest.tcl +++ tool/omittest.tcl @@ -190,53 +190,62 @@ SQLITE_OMIT_AUTORESET \ SQLITE_OMIT_AUTOVACUUM \ SQLITE_OMIT_BETWEEN_OPTIMIZATION \ SQLITE_OMIT_BLOB_LITERAL \ SQLITE_OMIT_BTREECOUNT \ - SQLITE_OMIT_BUILTIN_TEST \ SQLITE_OMIT_CAST \ SQLITE_OMIT_CHECK \ SQLITE_OMIT_COMPILEOPTION_DIAGS \ SQLITE_OMIT_COMPLETE \ SQLITE_OMIT_COMPOUND_SELECT \ + SQLITE_OMIT_CONFLICT_CLAUSE \ SQLITE_OMIT_CTE \ SQLITE_OMIT_DATETIME_FUNCS \ SQLITE_OMIT_DECLTYPE \ SQLITE_OMIT_DEPRECATED \ + SQLITE_OMIT_DISKIO \ SQLITE_OMIT_EXPLAIN \ SQLITE_OMIT_FLAG_PRAGMAS \ SQLITE_OMIT_FLOATING_POINT \ SQLITE_OMIT_FOREIGN_KEY \ SQLITE_OMIT_GET_TABLE \ + SQLITE_OMIT_HEX_INTEGER \ SQLITE_OMIT_INCRBLOB \ SQLITE_OMIT_INTEGRITY_CHECK \ SQLITE_OMIT_LIKE_OPTIMIZATION \ SQLITE_OMIT_LOAD_EXTENSION \ SQLITE_OMIT_LOCALTIME \ SQLITE_OMIT_LOOKASIDE \ SQLITE_OMIT_MEMORYDB \ + SQLITE_OMIT_MEMORY_ALLOCATION \ SQLITE_OMIT_OR_OPTIMIZATION \ SQLITE_OMIT_PAGER_PRAGMAS \ + SQLITE_OMIT_PARSER_TRACE \ + SQLITE_OMIT_POPEN \ SQLITE_OMIT_PRAGMA \ SQLITE_OMIT_PROGRESS_CALLBACK \ SQLITE_OMIT_QUICKBALANCE \ + SQLITE_OMIT_RANDOMNESS \ SQLITE_OMIT_REINDEX \ SQLITE_OMIT_SCHEMA_PRAGMAS \ SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS \ SQLITE_OMIT_SHARED_CACHE \ + SQLITE_OMIT_SHUTDOWN_DIRECTORIES \ SQLITE_OMIT_SUBQUERY \ SQLITE_OMIT_TCL_VARIABLE \ SQLITE_OMIT_TEMPDB \ + SQLITE_OMIT_TEST_CONTROL \ SQLITE_OMIT_TRACE \ SQLITE_OMIT_TRIGGER \ SQLITE_OMIT_TRUNCATE_OPTIMIZATION \ - SQLITE_OMIT_UNIQUE_ENFORCEMENT \ + SQLITE_OMIT_UPSERT \ SQLITE_OMIT_UTF16 \ SQLITE_OMIT_VACUUM \ SQLITE_OMIT_VIEW \ SQLITE_OMIT_VIRTUALTABLE \ SQLITE_OMIT_WAL \ + SQLITE_OMIT_WINDOWFUNC \ SQLITE_OMIT_WSD \ SQLITE_OMIT_XFER_OPT \ ] set ::ENABLE_SYMBOLS [list \