diff options
Diffstat (limited to 'tool/sqldiff.c')
-rw-r--r-- | tool/sqldiff.c | 152 |
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); } |