Index: third_party/sqlite/src/tool/sqldiff.c |
diff --git a/third_party/sqlite/src/tool/sqldiff.c b/third_party/sqlite/src/tool/sqldiff.c |
index 56ff53ee25766d6e4941c60a1f85c5a73a89c73a..67f3197bbfeb75a04a75fa43a84a9b59ae9642d5 100644 |
--- a/third_party/sqlite/src/tool/sqldiff.c |
+++ b/third_party/sqlite/src/tool/sqldiff.c |
@@ -33,6 +33,7 @@ struct GlobalVars { |
const char *zArgv0; /* Name of program */ |
int bSchemaOnly; /* Only show schema differences */ |
int bSchemaPK; /* Use the schema-defined PK, not the true PK */ |
+ int bHandleVtab; /* Handle fts3, fts4, fts5 and rtree vtabs */ |
unsigned fDebug; /* Debug flags */ |
sqlite3 *db; /* The database connection */ |
} g; |
@@ -402,7 +403,8 @@ static void printQuoted(FILE *out, sqlite3_value *X){ |
} |
fprintf(out, "'"); |
}else{ |
- fprintf(out, "NULL"); |
+ /* Could be an OOM, could be a zero-byte blob */ |
+ fprintf(out, "X''"); |
} |
break; |
} |
@@ -683,7 +685,7 @@ static void diff_one_table(const char *zTab, FILE *out){ |
/* Run the query and output differences */ |
if( !g.bSchemaOnly ){ |
- pStmt = db_prepare(sql.z); |
+ pStmt = db_prepare("%s", sql.z); |
while( SQLITE_ROW==sqlite3_step(pStmt) ){ |
int iType = sqlite3_column_int(pStmt, nPk); |
if( iType==1 || iType==2 ){ |
@@ -994,7 +996,7 @@ static int rbuDeltaCreate( |
zDelta += lenOut; |
putInt(checksum(zOut, lenOut), &zDelta); |
*(zDelta++) = ';'; |
- return zDelta - zOrigDelta; |
+ return (int)(zDelta - zOrigDelta); |
} |
/* Compute the hash table used to locate matching sections in the |
@@ -1141,7 +1143,7 @@ static int rbuDeltaCreate( |
putInt(checksum(zOut, lenOut), &zDelta); |
*(zDelta++) = ';'; |
sqlite3_free(collide); |
- return zDelta - zOrigDelta; |
+ return (int)(zDelta - zOrigDelta); |
} |
/* |
@@ -1178,8 +1180,9 @@ static void getRbudiffQuery( |
strPrintf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab); |
strPrintf(pSql, " SELECT 1 FROM ", zTab); |
strPrintf(pSql, " main.%Q AS o WHERE ", zTab); |
- strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); |
- strPrintf(pSql, "\n)"); |
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); |
+ strPrintf(pSql, "\n) AND "); |
+ strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); |
/* Deleted rows: */ |
strPrintf(pSql, "\nUNION ALL\nSELECT "); |
@@ -1193,8 +1196,9 @@ static void getRbudiffQuery( |
strPrintf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab); |
strPrintf(pSql, " SELECT 1 FROM ", zTab); |
strPrintf(pSql, " aux.%Q AS o WHERE ", zTab); |
- strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); |
- strPrintf(pSql, "\n) "); |
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); |
+ strPrintf(pSql, "\n) AND "); |
+ strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); |
/* Updated rows. If all table columns are part of the primary key, there |
** can be no updates. In this case this part of the compound SELECT can |
@@ -1225,7 +1229,7 @@ static void getRbudiffQuery( |
); |
strPrintf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ", zTab, zTab); |
- strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK); |
+ strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); |
strPrintf(pSql, " AND ota_control LIKE '%%x%%'"); |
} |
@@ -1244,6 +1248,7 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ |
Str sql = {0, 0, 0}; /* Query to find differences */ |
Str insert = {0, 0, 0}; /* First part of output INSERT statement */ |
sqlite3_stmt *pStmt = 0; |
+ int nRow = 0; /* Total rows in data_xxx table */ |
/* --rbu mode must use real primary keys. */ |
g.bSchemaPK = 1; |
@@ -1289,6 +1294,7 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ |
/* Output the first part of the INSERT statement */ |
fprintf(out, "%s", insert.z); |
+ nRow++; |
if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){ |
for(i=0; i<=nCol; i++){ |
@@ -1299,7 +1305,7 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ |
char *zOtaControl; |
int nOtaControl = sqlite3_column_bytes(pStmt, nCol); |
- zOtaControl = (char*)sqlite3_malloc(nOtaControl); |
+ zOtaControl = (char*)sqlite3_malloc(nOtaControl+1); |
memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1); |
for(i=0; i<nCol; i++){ |
@@ -1342,6 +1348,12 @@ static void rbudiff_one_table(const char *zTab, FILE *out){ |
} |
sqlite3_finalize(pStmt); |
+ if( nRow>0 ){ |
+ Str cnt = {0, 0, 0}; |
+ strPrintf(&cnt, "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow); |
+ fprintf(out, "%s\n", cnt.z); |
+ strFree(&cnt); |
+ } |
strFree(&ct); |
strFree(&sql); |
@@ -1452,7 +1464,7 @@ static void summarize_one_table(const char *zTab, FILE *out){ |
} |
/* Run the query and output difference summary */ |
- pStmt = db_prepare(sql.z); |
+ pStmt = db_prepare("%s", sql.z); |
nUpdate = 0; |
nInsert = 0; |
nDelete = 0; |
@@ -1728,6 +1740,144 @@ end_changeset_one_table: |
} |
/* |
+** Extract the next SQL keyword or quoted string from buffer zIn and copy it |
+** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes. |
+** Return a pointer to the character within zIn immediately following |
+** the token or quoted string just extracted. |
+*/ |
+const char *gobble_token(const char *zIn, char *zBuf, int nBuf){ |
+ const char *p = zIn; |
+ char *pOut = zBuf; |
+ char *pEnd = &pOut[nBuf-1]; |
+ char q = 0; /* quote character, if any */ |
+ |
+ if( p==0 ) return 0; |
+ while( *p==' ' ) p++; |
+ switch( *p ){ |
+ case '"': q = '"'; break; |
+ case '\'': q = '\''; break; |
+ case '`': q = '`'; break; |
+ case '[': q = ']'; break; |
+ } |
+ |
+ if( q ){ |
+ p++; |
+ while( *p && pOut<pEnd ){ |
+ if( *p==q ){ |
+ p++; |
+ if( *p!=q ) break; |
+ } |
+ if( pOut<pEnd ) *pOut++ = *p; |
+ p++; |
+ } |
+ }else{ |
+ while( *p && *p!=' ' && *p!='(' ){ |
+ if( pOut<pEnd ) *pOut++ = *p; |
+ p++; |
+ } |
+ } |
+ |
+ *pOut = '\0'; |
+ return p; |
+} |
+ |
+/* |
+** This function is the implementation of SQL scalar function "module_name": |
+** |
+** module_name(SQL) |
+** |
+** The only argument should be an SQL statement of the type that may appear |
+** in the sqlite_master table. If the statement is a "CREATE VIRTUAL TABLE" |
+** statement, then the value returned is the name of the module that it |
+** uses. Otherwise, if the statement is not a CVT, NULL is returned. |
+*/ |
+static void module_name_func( |
+ sqlite3_context *pCtx, |
+ int nVal, sqlite3_value **apVal |
+){ |
+ const char *zSql; |
+ char zToken[32]; |
+ |
+ assert( nVal==1 ); |
+ zSql = (const char*)sqlite3_value_text(apVal[0]); |
+ |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ if( zSql==0 || sqlite3_stricmp(zToken, "create") ) return; |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ if( zSql==0 || sqlite3_stricmp(zToken, "virtual") ) return; |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ if( zSql==0 || sqlite3_stricmp(zToken, "table") ) return; |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ if( zSql==0 ) return; |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ if( zSql==0 || sqlite3_stricmp(zToken, "using") ) return; |
+ zSql = gobble_token(zSql, zToken, sizeof(zToken)); |
+ |
+ sqlite3_result_text(pCtx, zToken, -1, SQLITE_TRANSIENT); |
+} |
+ |
+/* |
+** Return the text of an SQL statement that itself returns the list of |
+** tables to process within the database. |
+*/ |
+const char *all_tables_sql(){ |
+ if( g.bHandleVtab ){ |
+ int rc; |
+ |
+ rc = sqlite3_exec(g.db, |
+ "CREATE TEMP TABLE tblmap(module COLLATE nocase, postfix);" |
+ "INSERT INTO temp.tblmap VALUES" |
+ "('fts3', '_content'), ('fts3', '_segments'), ('fts3', '_segdir')," |
+ |
+ "('fts4', '_content'), ('fts4', '_segments'), ('fts4', '_segdir')," |
+ "('fts4', '_docsize'), ('fts4', '_stat')," |
+ |
+ "('fts5', '_data'), ('fts5', '_idx'), ('fts5', '_content')," |
+ "('fts5', '_docsize'), ('fts5', '_config')," |
+ |
+ "('rtree', '_node'), ('rtree', '_rowid'), ('rtree', '_parent');" |
+ , 0, 0, 0 |
+ ); |
+ assert( rc==SQLITE_OK ); |
+ |
+ rc = sqlite3_create_function( |
+ g.db, "module_name", 1, SQLITE_UTF8, 0, module_name_func, 0, 0 |
+ ); |
+ assert( rc==SQLITE_OK ); |
+ |
+ return |
+ "SELECT name FROM main.sqlite_master\n" |
+ " WHERE type='table' AND (\n" |
+ " module_name(sql) IS NULL OR \n" |
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n" |
+ " ) AND name NOT IN (\n" |
+ " SELECT a.name || b.postfix \n" |
+ "FROM main.sqlite_master AS a, temp.tblmap AS b \n" |
+ "WHERE module_name(a.sql) = b.module\n" |
+ " )\n" |
+ "UNION \n" |
+ "SELECT name FROM aux.sqlite_master\n" |
+ " WHERE type='table' AND (\n" |
+ " module_name(sql) IS NULL OR \n" |
+ " module_name(sql) IN (SELECT module FROM temp.tblmap)\n" |
+ " ) AND name NOT IN (\n" |
+ " SELECT a.name || b.postfix \n" |
+ "FROM aux.sqlite_master AS a, temp.tblmap AS b \n" |
+ "WHERE module_name(a.sql) = b.module\n" |
+ " )\n" |
+ " ORDER BY name"; |
+ }else{ |
+ return |
+ "SELECT name FROM main.sqlite_master\n" |
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n" |
+ " UNION\n" |
+ "SELECT name FROM aux.sqlite_master\n" |
+ " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n" |
+ " ORDER BY name"; |
+ } |
+} |
+ |
+/* |
** Print sketchy documentation for this utility program |
*/ |
static void showHelp(void){ |
@@ -1743,6 +1893,7 @@ static void showHelp(void){ |
" --summary Show only a summary of the differences\n" |
" --table TAB Show only differences in table TAB\n" |
" --transaction Show SQL output inside a transaction\n" |
+" --vtab Handle fts3, fts4, fts5 and rtree tables\n" |
); |
} |
@@ -1757,8 +1908,10 @@ int main(int argc, char **argv){ |
char *zTab = 0; |
FILE *out = stdout; |
void (*xDiff)(const char*,FILE*) = diff_one_table; |
+#ifndef SQLITE_OMIT_LOAD_EXTENSION |
int nExt = 0; |
char **azExt = 0; |
+#endif |
int useTransaction = 0; |
int neverUseTransaction = 0; |
@@ -1811,6 +1964,9 @@ int main(int argc, char **argv){ |
if( strcmp(z,"transaction")==0 ){ |
useTransaction = 1; |
}else |
+ if( strcmp(z,"vtab")==0 ){ |
+ g.bHandleVtab = 1; |
+ }else |
{ |
cmdlineError("unknown option: %s", argv[i]); |
} |
@@ -1841,8 +1997,8 @@ int main(int argc, char **argv){ |
cmdlineError("error loading %s: %s", azExt[i], zErrMsg); |
} |
} |
-#endif |
free(azExt); |
+#endif |
zSql = sqlite3_mprintf("ATTACH %Q as aux;", zDb2); |
rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg); |
if( rc || zErrMsg ){ |
@@ -1854,19 +2010,18 @@ int main(int argc, char **argv){ |
} |
if( neverUseTransaction ) useTransaction = 0; |
- if( useTransaction ) printf("BEGIN TRANSACTION;\n"); |
+ if( useTransaction ) fprintf(out, "BEGIN TRANSACTION;\n"); |
+ if( xDiff==rbudiff_one_table ){ |
+ fprintf(out, "CREATE TABLE IF NOT EXISTS rbu_count" |
+ "(tbl TEXT PRIMARY KEY COLLATE NOCASE, cnt INTEGER) " |
+ "WITHOUT ROWID;\n" |
+ ); |
+ } |
if( zTab ){ |
xDiff(zTab, out); |
}else{ |
/* Handle tables one by one */ |
- pStmt = db_prepare( |
- "SELECT name FROM main.sqlite_master\n" |
- " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n" |
- " UNION\n" |
- "SELECT name FROM aux.sqlite_master\n" |
- " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n" |
- " ORDER BY name" |
- ); |
+ pStmt = db_prepare("%s", all_tables_sql() ); |
while( SQLITE_ROW==sqlite3_step(pStmt) ){ |
xDiff((const char*)sqlite3_column_text(pStmt,0), out); |
} |