/* ** 2015-04-06 ** ** 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. ** ************************************************************************* ** ** This is a utility problem that computes the differences in content ** between two SQLite databases. */ #include #include #include #include #include #include "sqlite3.h" /* ** All global variables are gathered into the "g" singleton. */ struct GlobalVars { const char *zArgv0; /* Name of program */ int bSchemaOnly; /* Only show schema differences */ unsigned fDebug; /* Debug flags */ sqlite3 *db; /* The database connection */ } g; /* ** Allowed values for g.fDebug */ #define DEBUG_COLUMN_NAMES 0x000001 #define DEBUG_DIFF_SQL 0x000002 /* ** Dynamic string object */ typedef struct Str Str; struct Str { char *z; /* Text of the string */ int nAlloc; /* Bytes allocated in z[] */ int nUsed; /* Bytes actually used in z[] */ }; /* ** Initialize a Str object */ static void strInit(Str *p){ p->z = 0; p->nAlloc = 0; p->nUsed = 0; } /* ** Print an error resulting from faulting command-line arguments and ** abort the program. */ static void cmdlineError(const char *zFormat, ...){ va_list ap; fprintf(stderr, "%s: ", g.zArgv0); va_start(ap, zFormat); vfprintf(stderr, zFormat, ap); va_end(ap); fprintf(stderr, "\n\"%s --help\" for more help\n", g.zArgv0); exit(1); } /* ** Print an error message for an error that occurs at runtime, then ** abort the program. */ static void runtimeError(const char *zFormat, ...){ va_list ap; fprintf(stderr, "%s: ", g.zArgv0); va_start(ap, zFormat); vfprintf(stderr, zFormat, ap); va_end(ap); fprintf(stderr, "\n"); exit(1); } /* ** Free all memory held by a Str object */ static void strFree(Str *p){ sqlite3_free(p->z); strInit(p); } /* ** Add formatted text to the end of a Str object */ static void strPrintf(Str *p, const char *zFormat, ...){ int nNew; for(;;){ if( p->z ){ va_list ap; va_start(ap, zFormat); sqlite3_vsnprintf(p->nAlloc-p->nUsed, p->z+p->nUsed, zFormat, ap); va_end(ap); nNew = (int)strlen(p->z + p->nUsed); }else{ nNew = p->nAlloc; } if( p->nUsed+nNew < p->nAlloc-1 ){ p->nUsed += nNew; break; } p->nAlloc = p->nAlloc*2 + 1000; p->z = sqlite3_realloc(p->z, p->nAlloc); if( p->z==0 ) runtimeError("out of memory"); } } /* Safely quote an SQL identifier. Use the minimum amount of transformation ** necessary to allow the string to be used with %s. ** ** Space to hold the returned string is obtained from sqlite3_malloc(). The ** caller is responsible for ensuring this space is freed when no longer ** needed. */ static char *safeId(const char *zId){ /* All SQLite keywords, in alphabetical order */ static const char *azKeywords[] = { "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ANALYZE", "AND", "AS", "ASC", "ATTACH", "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BY", "CASCADE", "CASE", "CAST", "CHECK", "COLLATE", "COLUMN", "COMMIT", "CONFLICT", "CONSTRAINT", "CREATE", "CROSS", "CURRENT_DATE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT", "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT", "DROP", "EACH", "ELSE", "END", "ESCAPE", "EXCEPT", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL", "FOR", "FOREIGN", "FROM", "FULL", "GLOB", "GROUP", "HAVING", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX", "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LEFT", "LIKE", "LIMIT", "MATCH", "NATURAL", "NO", "NOT", "NOTNULL", "NULL", "OF", "OFFSET", "ON", "OR", "ORDER", "OUTER", "PLAN", "PRAGMA", "PRIMARY", "QUERY", "RAISE", "RECURSIVE", "REFERENCES", "REGEXP", "REINDEX", "RELEASE", "RENAME", "REPLACE", "RESTRICT", "RIGHT", "ROLLBACK", "ROW", "SAVEPOINT", "SELECT", "SET", "TABLE", "TEMP", "TEMPORARY", "THEN", "TO", "TRANSACTION", "TRIGGER", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM", "VALUES", "VIEW", "VIRTUAL", "WHEN", "WHERE", "WITH", "WITHOUT", }; int lwr, upr, mid, c, i, x; for(i=x=0; (c = zId[i])!=0; i++){ if( !isalpha(c) && c!='_' ){ if( i>0 && isdigit(c) ){ x++; }else{ return sqlite3_mprintf("\"%w\"", zId); } } } if( x ) return sqlite3_mprintf("%s", zId); lwr = 0; upr = sizeof(azKeywords)/sizeof(azKeywords[0]) - 1; while( lwr<=upr ){ mid = (lwr+upr)/2; c = sqlite3_stricmp(azKeywords[mid], zId); if( c==0 ) return sqlite3_mprintf("\"%w\"", zId); if( c<0 ){ lwr = mid+1; }else{ upr = mid-1; } } return sqlite3_mprintf("%s", zId); } /* ** Prepare a new SQL statement. Print an error and abort if anything ** goes wrong. */ static sqlite3_stmt *db_vprepare(const char *zFormat, va_list ap){ char *zSql; int rc; sqlite3_stmt *pStmt; zSql = sqlite3_vmprintf(zFormat, ap); if( zSql==0 ) runtimeError("out of memory"); rc = sqlite3_prepare_v2(g.db, zSql, -1, &pStmt, 0); if( rc ){ runtimeError("SQL statement error: %s\n\"%s\"", sqlite3_errmsg(g.db), zSql); } sqlite3_free(zSql); return pStmt; } static sqlite3_stmt *db_prepare(const char *zFormat, ...){ va_list ap; sqlite3_stmt *pStmt; va_start(ap, zFormat); pStmt = db_vprepare(zFormat, ap); va_end(ap); return pStmt; } /* ** Free a list of strings */ static void namelistFree(char **az){ if( az ){ int i; for(i=0; az[i]; i++) sqlite3_free(az[i]); sqlite3_free(az); } } /* ** Return a list of column names for the table zDb.zTab. Space to ** old the list is obtained from malloc() and should released by calling ** namelistFree() when no longer needed. ** ** Primary key columns are listed first, followed by data columns. The ** "primary key" in the previous sentence is the true primary key - the ** rowid or INTEGER PRIMARY KEY for ordinary tables or the declared ** PRIMARY KEY for WITHOUT ROWID tables. The number of columns in the ** primary key is returned in *pNPkey. ** ** If the table is a rowid table for which the rowid is inaccessible, ** then this routine returns a NULL pointer. ** ** Examples: ** CREATE TABLE t1(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(c)); ** *pnPKey = 1; ** az = { "rowid", "a", "b", "c", 0 } ** ** CREATE TABLE t2(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(b)); ** *pnPKey = 1; ** az = { "b", "a", "c", 0 } ** ** CREATE TABLE t3(x,y,z,PRIMARY KEY(y,z)); ** *pnPKey = 1 ** az = { "rowid", "x", "y", "z", 0 } ** ** CREATE TABLE t4(x,y,z,PRIMARY KEY(y,z)) WITHOUT ROWID; ** *pnPKey = 2 ** az = { "y", "z", "x", 0 } ** ** CREATE TABLE t5(rowid,_rowid_,oid); ** az = 0 // The rowid is not accessible */ static char **columnNames(const char *zDb, const char *zTab, int *pnPKey){ char **az = 0; int naz = 0; sqlite3_stmt *pStmt; char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */ int truePk = 0; /* PRAGMA table_info indentifies the true PK */ int nPK = 0; /* Number of PRIMARY KEY columns */ int i, j; pStmt = db_prepare("PRAGMA %s.index_list=%Q", zDb, zTab); while( SQLITE_ROW==sqlite3_step(pStmt) ){ if( sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,3),"pk")==0 ){ zPkIdxName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1)); break; } } sqlite3_finalize(pStmt); if( zPkIdxName ){ int nKey = 0; int nCol = 0; truePk = 0; pStmt = db_prepare("PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName); while( SQLITE_ROW==sqlite3_step(pStmt) ){ nCol++; if( sqlite3_column_int(pStmt,5) ){ nKey++; continue; } if( sqlite3_column_int(pStmt,1)>=0 ) truePk = 1; } if( nCol==nKey ) truePk = 1; if( truePk ){ nPK = nKey; }else{ nPK = 1; } sqlite3_finalize(pStmt); sqlite3_free(zPkIdxName); }else{ truePk = 1; nPK = 1; } *pnPKey = nPK; naz = nPK; az = sqlite3_malloc( sizeof(char*)*(nPK+1) ); if( az==0 ) runtimeError("out of memory"); memset(az, 0, sizeof(char*)*(nPK+1)); pStmt = db_prepare("PRAGMA %s.table_info=%Q", zDb, zTab); while( SQLITE_ROW==sqlite3_step(pStmt) ){ int iPKey; if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){ az[iPKey-1] = safeId((char*)sqlite3_column_text(pStmt,1)); }else{ az = sqlite3_realloc(az, sizeof(char*)*(naz+2) ); if( az==0 ) runtimeError("out of memory"); az[naz++] = safeId((char*)sqlite3_column_text(pStmt,1)); } } sqlite3_finalize(pStmt); if( az ) az[naz] = 0; if( az[0]==0 ){ const char *azRowid[] = { "rowid", "_rowid_", "oid" }; for(i=0; i=naz ){ az[0] = sqlite3_mprintf("%s", azRowid[i]); break; } } if( az[0]==0 ){ for(i=1; inPk2 ){ zSep = "SELECT "; for(i=0; i