aboutsummaryrefslogtreecommitdiff
path: root/tool/sqldiff.c
diff options
context:
space:
mode:
Diffstat (limited to 'tool/sqldiff.c')
-rw-r--r--tool/sqldiff.c152
1 files changed, 144 insertions, 8 deletions
diff --git a/tool/sqldiff.c b/tool/sqldiff.c
index 319acafdb..4a5389c60 100644
--- a/tool/sqldiff.c
+++ b/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;
@@ -1736,6 +1737,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, 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){
@@ -1751,6 +1890,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"
);
}
@@ -1821,6 +1961,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]);
}
@@ -1875,14 +2018,7 @@ int main(int argc, char **argv){
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( all_tables_sql() );
while( SQLITE_ROW==sqlite3_step(pStmt) ){
xDiff((const char*)sqlite3_column_text(pStmt,0), out);
}