#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include "sqlite3.h"
struct GlobalVars {
const char *zArgv0;
int bSchemaOnly;
int bSchemaPK;
int bHandleVtab;
unsigned fDebug;
int bSchemaCompare;
sqlite3 *db;
} g;
#define DEBUG_COLUMN_NAMES 0x000001
#define DEBUG_DIFF_SQL 0x000002
typedef struct Str Str;
struct Str {
char *z;
int nAlloc;
int nUsed;
};
static void strInit(Str *p){
p->z = 0;
p->nAlloc = 0;
p->nUsed = 0;
}
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);
}
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);
}
static void strFree(Str *p){
sqlite3_free(p->z);
strInit(p);
}
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");
}
}
static char *safeId(const char *zId){
int i, x;
char c;
if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
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 || !sqlite3_keyword_check(zId,i) ){
return sqlite3_mprintf("%s", zId);
}
return sqlite3_mprintf("\"%w\"", zId);
}
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;
}
static void namelistFree(char **az){
if( az ){
int i;
for(i=0; az[i]; i++) sqlite3_free(az[i]);
sqlite3_free(az);
}
}
static char **columnNames(
const char *zDb,
const char *zTab,
int *pnPKey,
int *pbRowid
){
char **az = 0;
int naz = 0;
sqlite3_stmt *pStmt;
char *zPkIdxName = 0;
int truePk = 0;
int nPK = 0;
int i, j;
if( g.bSchemaPK==0 ){
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;
}
pStmt = db_prepare("PRAGMA %s.table_info=%Q", zDb, zTab);
}else{
nPK = 0;
pStmt = db_prepare("PRAGMA %s.table_info=%Q", zDb, zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
if( sqlite3_column_int(pStmt,5)>0 ) nPK++;
}
sqlite3_reset(pStmt);
if( nPK==0 ) nPK = 1;
truePk = 1;
}
if( g.bSchemaCompare ){
assert( sqlite3_stricmp(zTab,"sqlite_schema")==0
|| sqlite3_stricmp(zTab,"sqlite_master")==0 );
nPK = 2;
truePk = 0;
}
*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));
if( g.bSchemaCompare ){
az[0] = sqlite3_mprintf("%s", "type");
az[1] = sqlite3_mprintf("%s", "name");
}
while( SQLITE_ROW==sqlite3_step(pStmt) ){
char * sid = safeId((char*)sqlite3_column_text(pStmt,1));
int iPKey;
if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){
az[iPKey-1] = sid;
}else{
if( !g.bSchemaCompare
|| !(strcmp(sid,"rootpage")==0
||strcmp(sid,"name")==0
||strcmp(sid,"type")==0)){
az = sqlite3_realloc(az, sizeof(char*)*(naz+2) );
if( az==0 ) runtimeError("out of memory");
az[naz++] = sid;
}
}
}
sqlite3_finalize(pStmt);
if( az ) az[naz] = 0;
if( pbRowid ) *pbRowid = (az[0]==0);
if( az[0]==0 ){
const char *azRowid[] = { "rowid", "_rowid_", "oid" };
for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
for(j=1; j<naz; j++){
if( sqlite3_stricmp(az[j], azRowid[i])==0 ) break;
}
if( j>=naz ){
az[0] = sqlite3_mprintf("%s", azRowid[i]);
break;
}
}
if( az[0]==0 ){
for(i=1; i<naz; i++) sqlite3_free(az[i]);
sqlite3_free(az);
az = 0;
}
}
return az;
}
static void printQuoted(FILE *out, sqlite3_value *X){
switch( sqlite3_value_type(X) ){
case SQLITE_FLOAT: {
double r1;
char zBuf[50];
r1 = sqlite3_value_double(X);
sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
fprintf(out, "%s", zBuf);
break;
}
case SQLITE_INTEGER: {
fprintf(out, "%lld", sqlite3_value_int64(X));
break;
}
case SQLITE_BLOB: {
const unsigned char *zBlob = sqlite3_value_blob(X);
int nBlob = sqlite3_value_bytes(X);
if( zBlob ){
int i;
fprintf(out, "x'");
for(i=0; i<nBlob; i++){
fprintf(out, "%02x", zBlob[i]);
}
fprintf(out, "'");
}else{
fprintf(out, "X''");
}
break;
}
case SQLITE_TEXT: {
const unsigned char *zArg = sqlite3_value_text(X);
if( zArg==0 ){
fprintf(out, "NULL");
}else{
int inctl = 0;
int i, j;
fprintf(out, "'");
for(i=j=0; zArg[i]; i++){
char c = zArg[i];
int ctl = iscntrl(c);
if( ctl>inctl ){
inctl = ctl;
fprintf(out, "%.*s'||X'%02x", i-j, &zArg[j], c);
j = i+1;
}else if( ctl ){
fprintf(out, "%02x", c);
j = i+1;
}else{
if( inctl ){
inctl = 0;
fprintf(out, "'\n||'");
}
if( c=='\'' ){
fprintf(out, "%.*s'", i-j+1, &zArg[j]);
j = i+1;
}
}
}
fprintf(out, "%s'", &zArg[j]);
}
break;
}
case SQLITE_NULL: {
fprintf(out, "NULL");
break;
}
}
}
static void dump_table(const char *zTab, FILE *out){
char *zId = safeId(zTab);
char **az = 0;
int nPk;
int nCol;
int i;
sqlite3_stmt *pStmt;
const char *zSep;
Str ins;
pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema WHERE name=%Q", zTab);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
if( !g.bSchemaOnly ){
az = columnNames("aux", zTab, &nPk, 0);
strInit(&ins);
if( az==0 ){
pStmt = db_prepare("SELECT * FROM aux.%s", zId);
strPrintf(&ins,"INSERT INTO %s VALUES", zId);
}else{
Str sql;
strInit(&sql);
zSep = "SELECT";
for(i=0; az[i]; i++){
strPrintf(&sql, "%s %s", zSep, az[i]);
zSep = ",";
}
strPrintf(&sql," FROM aux.%s", zId);
zSep = " ORDER BY";
for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s %d", zSep, i);
zSep = ",";
}
pStmt = db_prepare("%s", sql.z);
strFree(&sql);
strPrintf(&ins, "INSERT INTO %s", zId);
zSep = "(";
for(i=0; az[i]; i++){
strPrintf(&ins, "%s%s", zSep, az[i]);
zSep = ",";
}
strPrintf(&ins,") VALUES");
namelistFree(az);
}
nCol = sqlite3_column_count(pStmt);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s",ins.z);
zSep = "(";
for(i=0; i<nCol; i++){
fprintf(out, "%s",zSep);
printQuoted(out, sqlite3_column_value(pStmt,i));
zSep = ",";
}
fprintf(out, ");\n");
}
sqlite3_finalize(pStmt);
strFree(&ins);
}
pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
sqlite3_free(zId);
}
static void diff_one_table(const char *zTab, FILE *out){
char *zId = safeId(zTab);
char **az = 0;
char **az2 = 0;
int nPk;
int nPk2;
int n = 0;
int n2;
int nQ;
int i;
const char *zSep;
Str sql;
sqlite3_stmt *pStmt;
const char *zLead =
(g.bSchemaCompare)? "-- " : "";
strInit(&sql);
if( g.fDebug==DEBUG_COLUMN_NAMES ){
az = columnNames("aux",zTab, &nPk, 0);
if( az==0 ){
printf("Rowid not accessible for %s\n", zId);
}else{
printf("%s:", zId);
for(i=0; az[i]; i++){
printf(" %s", az[i]);
if( i+1==nPk ) printf(" *");
}
printf("\n");
}
goto end_diff_one_table;
}
if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){
if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
if( g.bSchemaCompare )
fprintf(out, "-- 2nd DB has no %s table\n", zTab);
else
fprintf(out, "DROP TABLE %s;\n", zId);
}
goto end_diff_one_table;
}
if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
if( g.bSchemaCompare )
fprintf(out, "-- 1st DB has no %s table\n", zTab);
else
dump_table(zTab, out);
goto end_diff_one_table;
}
az = columnNames("main", zTab, &nPk, 0);
az2 = columnNames("aux", zTab, &nPk2, 0);
if( az && az2 ){
for(n=0; az[n] && az2[n]; n++){
if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
}
}
if( az==0
|| az2==0
|| nPk!=nPk2
|| az[n]
){
fprintf(out, "%sDROP TABLE %s; -- due to schema mismatch\n", zLead, zId);
dump_table(zTab, out);
goto end_diff_one_table;
}
for(n2=n; az2[n2]; n2++){
fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, safeId(az2[n2]));
}
nQ = nPk2+1+2*(n2-nPk2);
if( n2>nPk2 ){
zSep = "SELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 1 /* changed row */");
while( az[i] ){
strPrintf(&sql, ", A.%s IS NOT B.%s, B.%s",
az[i], az2[i], az2[i]);
i++;
}
while( az2[i] ){
strPrintf(&sql, ", B.%s IS NOT NULL, B.%s",
az2[i], az2[i]);
i++;
}
strPrintf(&sql, "\n FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
zSep = "\n AND (";
while( az[i] ){
strPrintf(&sql, "%sA.%s IS NOT B.%s%s\n",
zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
zSep = " OR ";
i++;
}
while( az2[i] ){
strPrintf(&sql, "%sB.%s IS NOT NULL%s\n",
zSep, az2[i], az2[i+1]==0 ? ")" : "");
zSep = " OR ";
i++;
}
strPrintf(&sql, " UNION ALL\n");
}
zSep = "SELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sA.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 2 /* deleted row */");
while( az2[i] ){
strPrintf(&sql, ", NULL, NULL");
i++;
}
strPrintf(&sql, "\n FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n");
zSep = " UNION ALL\nSELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 3 /* inserted row */");
while( az2[i] ){
strPrintf(&sql, ", 1, B.%s", az2[i]);
i++;
}
strPrintf(&sql, "\n FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n ORDER BY");
zSep = " ";
for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s%d", zSep, i);
zSep = ", ";
}
strPrintf(&sql, ";\n");
if( g.fDebug & DEBUG_DIFF_SQL ){
printf("SQL for %s:\n%s\n", zId, sql.z);
goto end_diff_one_table;
}
pStmt = db_prepare(
"SELECT name FROM main.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q"
" AND sql IS NOT NULL"
" AND sql NOT IN (SELECT sql FROM aux.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q"
" AND sql IS NOT NULL)",
zTab, zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
char *z = safeId((const char*)sqlite3_column_text(pStmt,0));
fprintf(out, "DROP INDEX %s;\n", z);
sqlite3_free(z);
}
sqlite3_finalize(pStmt);
if( !g.bSchemaOnly ){
pStmt = db_prepare("%s", sql.z);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt, nPk);
if( iType==1 || iType==2 ){
if( iType==1 ){
fprintf(out, "%sUPDATE %s", zLead, zId);
zSep = " SET";
for(i=nPk+1; i<nQ; i+=2){
if( sqlite3_column_int(pStmt,i)==0 ) continue;
fprintf(out, "%s %s=", zSep, az2[(i+nPk-1)/2]);
zSep = ",";
printQuoted(out, sqlite3_column_value(pStmt,i+1));
}
}else{
fprintf(out, "%sDELETE FROM %s", zLead, zId);
}
zSep = " WHERE";
for(i=0; i<nPk; i++){
fprintf(out, "%s %s=", zSep, az2[i]);
printQuoted(out, sqlite3_column_value(pStmt,i));
zSep = " AND";
}
fprintf(out, ";\n");
}else{
fprintf(out, "%sINSERT INTO %s(%s", zLead, zId, az2[0]);
for(i=1; az2[i]; i++) fprintf(out, ",%s", az2[i]);
fprintf(out, ") VALUES");
zSep = "(";
for(i=0; i<nPk2; i++){
fprintf(out, "%s", zSep);
zSep = ",";
printQuoted(out, sqlite3_column_value(pStmt,i));
}
for(i=nPk2+2; i<nQ; i+=2){
fprintf(out, ",");
printQuoted(out, sqlite3_column_value(pStmt,i));
}
fprintf(out, ");\n");
}
}
sqlite3_finalize(pStmt);
}
pStmt = db_prepare(
"SELECT sql FROM aux.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q"
" AND sql IS NOT NULL"
" AND sql NOT IN (SELECT sql FROM main.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q"
" AND sql IS NOT NULL)",
zTab, zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
end_diff_one_table:
strFree(&sql);
sqlite3_free(zId);
namelistFree(az);
namelistFree(az2);
return;
}
static void checkSchemasMatch(const char *zTab){
sqlite3_stmt *pStmt = db_prepare(
"SELECT A.sql=B.sql FROM main.sqlite_schema A, aux.sqlite_schema B"
" WHERE A.name=%Q AND B.name=%Q", zTab, zTab
);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
if( sqlite3_column_int(pStmt,0)==0 ){
runtimeError("schema changes for table %s", safeId(zTab));
}
}else{
runtimeError("table %s missing from one or both databases", safeId(zTab));
}
sqlite3_finalize(pStmt);
}
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u8;
#define NHASH 16
typedef struct hash hash;
struct hash {
u16 a, b;
u16 i;
char z[NHASH];
};
static void hash_init(hash *pHash, const char *z){
u16 a, b, i;
a = b = 0;
for(i=0; i<NHASH; i++){
a += z[i];
b += (NHASH-i)*z[i];
pHash->z[i] = z[i];
}
pHash->a = a & 0xffff;
pHash->b = b & 0xffff;
pHash->i = 0;
}
static void hash_next(hash *pHash, int c){
u16 old = pHash->z[pHash->i];
pHash->z[pHash->i] = (char)c;
pHash->i = (pHash->i+1)&(NHASH-1);
pHash->a = pHash->a - old + (char)c;
pHash->b = pHash->b - NHASH*old + pHash->a;
}
static u32 hash_32bit(hash *pHash){
return (pHash->a & 0xffff) | (((u32)(pHash->b & 0xffff))<<16);
}
static void putInt(unsigned int v, char **pz){
static const char zDigits[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
int i, j;
char zBuf[20];
if( v==0 ){
*(*pz)++ = '0';
return;
}
for(i=0; v>0; i++, v>>=6){
zBuf[i] = zDigits[v&0x3f];
}
for(j=i-1; j>=0; j--){
*(*pz)++ = zBuf[j];
}
}
static int digit_count(int v){
unsigned int i, x;
for(i=1, x=64; (unsigned int)v>=x; i++, x <<= 6){}
return i;
}
static unsigned int checksum(const char *zIn, size_t N){
const unsigned char *z = (const unsigned char *)zIn;
unsigned sum0 = 0;
unsigned sum1 = 0;
unsigned sum2 = 0;
unsigned sum3 = 0;
while(N >= 16){
sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
z += 16;
N -= 16;
}
while(N >= 4){
sum0 += z[0];
sum1 += z[1];
sum2 += z[2];
sum3 += z[3];
z += 4;
N -= 4;
}
sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
switch(N){
case 3: sum3 += (z[2] << 8);
case 2: sum3 += (z[1] << 16);
case 1: sum3 += (z[0] << 24);
default: ;
}
return sum3;
}
static int rbuDeltaCreate(
const char *zSrc,
unsigned int lenSrc,
const char *zOut,
unsigned int lenOut,
char *zDelta
){
unsigned int i, base;
char *zOrigDelta = zDelta;
hash h;
int nHash;
int *landmark;
int *collide;
int lastRead = -1;
putInt(lenOut, &zDelta);
*(zDelta++) = '\n';
if( lenSrc<=NHASH ){
putInt(lenOut, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, zOut, lenOut);
zDelta += lenOut;
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
return (int)(zDelta - zOrigDelta);
}
nHash = lenSrc/NHASH;
collide = sqlite3_malloc( nHash*2*sizeof(int) );
landmark = &collide[nHash];
memset(landmark, -1, nHash*sizeof(int));
memset(collide, -1, nHash*sizeof(int));
for(i=0; i<lenSrc-NHASH; i+=NHASH){
int hv;
hash_init(&h, &zSrc[i]);
hv = hash_32bit(&h) % nHash;
collide[i/NHASH] = landmark[hv];
landmark[hv] = i/NHASH;
}
base = 0;
while( base+NHASH<lenOut ){
int iSrc, iBlock;
int bestCnt, bestOfst=0, bestLitsz=0;
hash_init(&h, &zOut[base]);
i = 0;
bestCnt = 0;
while( 1 ){
int hv;
int limit = 250;
hv = hash_32bit(&h) % nHash;
iBlock = landmark[hv];
while( iBlock>=0 && (limit--)>0 ){
int cnt, ofst, litsz;
int j, k, x, y;
int sz;
iSrc = iBlock*NHASH;
for(
j=0, x=iSrc, y=base+i;
(unsigned int)x<lenSrc && (unsigned int)y<lenOut;
j++, x++, y++
){
if( zSrc[x]!=zOut[y] ) break;
}
j--;
for(k=1; k<iSrc && (unsigned int)k<=i; k++){
if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
}
k--;
ofst = iSrc-k;
cnt = j+k+1;
litsz = i-k;
sz = digit_count(i-k)+digit_count(cnt)+digit_count(ofst)+3;
if( cnt>=sz && cnt>bestCnt ){
bestCnt = cnt;
bestOfst = iSrc-k;
bestLitsz = litsz;
}
iBlock = collide[iBlock];
}
if( bestCnt>0 ){
if( bestLitsz>0 ){
putInt(bestLitsz,&zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], bestLitsz);
zDelta += bestLitsz;
base += bestLitsz;
}
base += bestCnt;
putInt(bestCnt, &zDelta);
*(zDelta++) = '@';
putInt(bestOfst, &zDelta);
*(zDelta++) = ',';
if( bestOfst + bestCnt -1 > lastRead ){
lastRead = bestOfst + bestCnt - 1;
}
bestCnt = 0;
break;
}
if( base+i+NHASH>=lenOut ){
putInt(lenOut-base, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], lenOut-base);
zDelta += lenOut-base;
base = lenOut;
break;
}
hash_next(&h, zOut[base+i+NHASH]);
i++;
}
}
if( base<lenOut ){
putInt(lenOut-base, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], lenOut-base);
zDelta += lenOut-base;
}
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
sqlite3_free(collide);
return (int)(zDelta - zOrigDelta);
}
static void strPrintfArray(
Str *pStr,
const char *zSep,
const char *zFmt,
char **az, int n
){
int i;
for(i=0; az[i] && (i<n || n<0); i++){
if( i!=0 ) strPrintf(pStr, "%s", zSep);
strPrintf(pStr, zFmt, az[i], az[i], az[i]);
}
}
static void getRbudiffQuery(
const char *zTab,
char **azCol,
int nPK,
int bOtaRowid,
Str *pSql
){
int i;
strPrintf(pSql, "SELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, -1);
strPrintf(pSql, ", 0, ");
strPrintfArray(pSql, ", ", "NULL", azCol, -1);
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 = o.%Q)", azCol, nPK);
strPrintf(pSql, "\n) AND ");
strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
strPrintf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, nPK);
if( azCol[nPK] ){
strPrintf(pSql, ", ");
strPrintfArray(pSql, ", ", "NULL", &azCol[nPK], -1);
}
strPrintf(pSql, ", 1, ");
strPrintfArray(pSql, ", ", "NULL", azCol, -1);
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 = o.%Q)", azCol, nPK);
strPrintf(pSql, "\n) AND ");
strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
if( azCol[nPK] ){
strPrintf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "n.%s", azCol, nPK);
strPrintf(pSql, ",\n");
strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE n.%s END", &azCol[nPK], -1
);
if( bOtaRowid==0 ){
strPrintf(pSql, ", '");
strPrintfArray(pSql, "", ".", azCol, nPK);
strPrintf(pSql, "' ||\n");
}else{
strPrintf(pSql, ",\n");
}
strPrintfArray(pSql, " ||\n",
" CASE WHEN n.%s IS o.%s THEN '.' ELSE 'x' END", &azCol[nPK], -1
);
strPrintf(pSql, "\nAS ota_control, ");
strPrintfArray(pSql, ", ", "NULL", azCol, nPK);
strPrintf(pSql, ",\n");
strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE o.%s END", &azCol[nPK], -1
);
strPrintf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ", zTab, zTab);
strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
strPrintf(pSql, " AND ota_control LIKE '%%x%%'");
}
strPrintf(pSql, "\nORDER BY ");
for(i=1; i<=nPK; i++) strPrintf(pSql, "%s%d", ((i>1)?", ":""), i);
}
static void rbudiff_one_table(const char *zTab, FILE *out){
int bOtaRowid;
int nPK;
char **azCol;
int i;
int nCol;
Str ct = {0, 0, 0};
Str sql = {0, 0, 0};
Str insert = {0, 0, 0};
sqlite3_stmt *pStmt = 0;
int nRow = 0;
g.bSchemaPK = 1;
checkSchemasMatch(zTab);
azCol = columnNames("main", zTab, &nPK, &bOtaRowid);
if( azCol==0 ){
runtimeError("table %s has no usable PK columns", zTab);
}
for(nCol=0; azCol[nCol]; nCol++);
strPrintf(&ct, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab);
if( bOtaRowid ) strPrintf(&ct, "rbu_rowid, ");
strPrintfArray(&ct, ", ", "%s", &azCol[bOtaRowid], -1);
strPrintf(&ct, ", rbu_control);");
getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, &sql);
strPrintf(&insert, "INSERT INTO 'data_%q' (", zTab);
if( bOtaRowid ) strPrintf(&insert, "rbu_rowid, ");
strPrintfArray(&insert, ", ", "%s", &azCol[bOtaRowid], -1);
strPrintf(&insert, ", rbu_control) VALUES(");
pStmt = db_prepare("%s", sql.z);
while( sqlite3_step(pStmt)==SQLITE_ROW ){
if( ct.z ){
fprintf(out, "%s\n", ct.z);
strFree(&ct);
}
fprintf(out, "%s", insert.z);
nRow++;
if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){
for(i=0; i<=nCol; i++){
if( i>0 ) fprintf(out, ", ");
printQuoted(out, sqlite3_column_value(pStmt, i));
}
}else{
char *zOtaControl;
int nOtaControl = sqlite3_column_bytes(pStmt, nCol);
zOtaControl = (char*)sqlite3_malloc(nOtaControl+1);
memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1);
for(i=0; i<nCol; i++){
int bDone = 0;
if( i>=nPK
&& sqlite3_column_type(pStmt, i)==SQLITE_BLOB
&& sqlite3_column_type(pStmt, nCol+1+i)==SQLITE_BLOB
){
const char *aSrc = sqlite3_column_blob(pStmt, nCol+1+i);
int nSrc = sqlite3_column_bytes(pStmt, nCol+1+i);
const char *aFinal = sqlite3_column_blob(pStmt, i);
int nFinal = sqlite3_column_bytes(pStmt, i);
char *aDelta;
int nDelta;
aDelta = sqlite3_malloc(nFinal + 60);
nDelta = rbuDeltaCreate(aSrc, nSrc, aFinal, nFinal, aDelta);
if( nDelta<nFinal ){
int j;
fprintf(out, "x'");
for(j=0; j<nDelta; j++) fprintf(out, "%02x", (u8)aDelta[j]);
fprintf(out, "'");
zOtaControl[i-bOtaRowid] = 'f';
bDone = 1;
}
sqlite3_free(aDelta);
}
if( bDone==0 ){
printQuoted(out, sqlite3_column_value(pStmt, i));
}
fprintf(out, ", ");
}
fprintf(out, "'%s'", zOtaControl);
sqlite3_free(zOtaControl);
}
fprintf(out, ");\n");
}
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);
strFree(&insert);
}
static void summarize_one_table(const char *zTab, FILE *out){
char *zId = safeId(zTab);
char **az = 0;
char **az2 = 0;
int nPk;
int nPk2;
int n = 0;
int n2;
int i;
const char *zSep;
Str sql;
sqlite3_stmt *pStmt;
sqlite3_int64 nUpdate;
sqlite3_int64 nUnchanged;
sqlite3_int64 nDelete;
sqlite3_int64 nInsert;
strInit(&sql);
if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){
if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
fprintf(out, "%s: missing from second database\n", zTab);
}
goto end_summarize_one_table;
}
if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
fprintf(out, "%s: missing from first database\n", zTab);
goto end_summarize_one_table;
}
az = columnNames("main", zTab, &nPk, 0);
az2 = columnNames("aux", zTab, &nPk2, 0);
if( az && az2 ){
for(n=0; az[n]; n++){
if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
}
}
if( az==0
|| az2==0
|| nPk!=nPk2
|| az[n]
){
fprintf(out, "%s: incompatible schema\n", zTab);
goto end_summarize_one_table;
}
for(n2=n; az[n2]; n2++){}
strPrintf(&sql, "SELECT 1, count(*)");
if( n2==nPk2 ){
strPrintf(&sql, ", 0\n");
}else{
zSep = ", sum(";
for(i=nPk; az[i]; i++){
strPrintf(&sql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]);
zSep = " OR ";
}
strPrintf(&sql, ")\n");
}
strPrintf(&sql, " FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, " UNION ALL\n");
strPrintf(&sql, "SELECT 2, count(*), 0\n");
strPrintf(&sql, " FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B ", zId);
zSep = "WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n");
strPrintf(&sql, " UNION ALL\n");
strPrintf(&sql, "SELECT 3, count(*), 0\n");
strPrintf(&sql, " FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A ", zId);
zSep = "WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n ORDER BY 1;\n");
if( (g.fDebug & DEBUG_DIFF_SQL)!=0 ){
printf("SQL for %s:\n%s\n", zId, sql.z);
goto end_summarize_one_table;
}
pStmt = db_prepare("%s", sql.z);
nUpdate = 0;
nInsert = 0;
nDelete = 0;
nUnchanged = 0;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
switch( sqlite3_column_int(pStmt,0) ){
case 1:
nUpdate = sqlite3_column_int64(pStmt,2);
nUnchanged = sqlite3_column_int64(pStmt,1) - nUpdate;
break;
case 2:
nDelete = sqlite3_column_int64(pStmt,1);
break;
case 3:
nInsert = sqlite3_column_int64(pStmt,1);
break;
}
}
sqlite3_finalize(pStmt);
fprintf(out, "%s: %lld changes, %lld inserts, %lld deletes, %lld unchanged\n",
zTab, nUpdate, nInsert, nDelete, nUnchanged);
end_summarize_one_table:
strFree(&sql);
sqlite3_free(zId);
namelistFree(az);
namelistFree(az2);
return;
}
static void putsVarint(FILE *out, sqlite3_uint64 v){
int i, n;
unsigned char p[12];
if( v & (((sqlite3_uint64)0xff000000)<<32) ){
p[8] = (unsigned char)v;
v >>= 8;
for(i=7; i>=0; i--){
p[i] = (unsigned char)((v & 0x7f) | 0x80);
v >>= 7;
}
fwrite(p, 8, 1, out);
}else{
n = 9;
do{
p[n--] = (unsigned char)((v & 0x7f) | 0x80);
v >>= 7;
}while( v!=0 );
p[9] &= 0x7f;
fwrite(p+n+1, 9-n, 1, out);
}
}
static void putValue(FILE *out, sqlite3_stmt *pStmt, int k){
int iDType = sqlite3_column_type(pStmt, k);
sqlite3_int64 iX;
double rX;
sqlite3_uint64 uX;
int j;
putc(iDType, out);
switch( iDType ){
case SQLITE_INTEGER:
iX = sqlite3_column_int64(pStmt, k);
memcpy(&uX, &iX, 8);
for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out);
break;
case SQLITE_FLOAT:
rX = sqlite3_column_double(pStmt, k);
memcpy(&uX, &rX, 8);
for(j=56; j>=0; j-=8) putc((uX>>j)&0xff, out);
break;
case SQLITE_TEXT:
iX = sqlite3_column_bytes(pStmt, k);
putsVarint(out, (sqlite3_uint64)iX);
fwrite(sqlite3_column_text(pStmt, k),1,(size_t)iX,out);
break;
case SQLITE_BLOB:
iX = sqlite3_column_bytes(pStmt, k);
putsVarint(out, (sqlite3_uint64)iX);
fwrite(sqlite3_column_blob(pStmt, k),1,(size_t)iX,out);
break;
case SQLITE_NULL:
break;
}
}
static void changeset_one_table(const char *zTab, FILE *out){
sqlite3_stmt *pStmt;
char *zId = safeId(zTab);
char **azCol = 0;
int nCol = 0;
int *aiFlg = 0;
int *aiPk = 0;
int nPk = 0;
Str sql;
int i, k;
const char *zSep;
checkSchemasMatch(zTab);
strInit(&sql);
pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
nCol++;
azCol = sqlite3_realloc(azCol, sizeof(char*)*nCol);
if( azCol==0 ) runtimeError("out of memory");
aiFlg = sqlite3_realloc(aiFlg, sizeof(int)*nCol);
if( aiFlg==0 ) runtimeError("out of memory");
azCol[nCol-1] = safeId((const char*)sqlite3_column_text(pStmt,1));
aiFlg[nCol-1] = i = sqlite3_column_int(pStmt,5);
if( i>0 ){
if( i>nPk ){
nPk = i;
aiPk = sqlite3_realloc(aiPk, sizeof(int)*nPk);
if( aiPk==0 ) runtimeError("out of memory");
}
aiPk[i-1] = nCol-1;
}
}
sqlite3_finalize(pStmt);
if( nPk==0 ) goto end_changeset_one_table;
if( nCol>nPk ){
strPrintf(&sql, "SELECT %d", SQLITE_UPDATE);
for(i=0; i<nCol; i++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]);
}else{
strPrintf(&sql, ",\n A.%s IS NOT B.%s, A.%s, B.%s",
azCol[i], azCol[i], azCol[i], azCol[i]);
}
}
strPrintf(&sql,"\n FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND";
}
zSep = "\n AND (";
for(i=0; i<nCol; i++){
if( aiFlg[i] ) continue;
strPrintf(&sql, "%sA.%s IS NOT B.%s", zSep, azCol[i], azCol[i]);
zSep = " OR\n ";
}
strPrintf(&sql,")\n UNION ALL\n");
}
strPrintf(&sql, "SELECT %d", SQLITE_DELETE);
for(i=0; i<nCol; i++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]);
}else{
strPrintf(&sql, ",\n 1, A.%s, NULL", azCol[i]);
}
}
strPrintf(&sql, "\n FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND";
}
strPrintf(&sql, ")\n UNION ALL\n");
strPrintf(&sql, "SELECT %d", SQLITE_INSERT);
for(i=0; i<nCol; i++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n B.%s", azCol[i]);
}else{
strPrintf(&sql, ",\n 1, NULL, B.%s", azCol[i]);
}
}
strPrintf(&sql, "\n FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
zSep = " WHERE";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND";
}
strPrintf(&sql, ")\n");
strPrintf(&sql, " ORDER BY");
zSep = " ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%s %d", zSep, aiPk[i]+2);
zSep = ",";
}
strPrintf(&sql, ";\n");
if( g.fDebug & DEBUG_DIFF_SQL ){
printf("SQL for %s:\n%s\n", zId, sql.z);
goto end_changeset_one_table;
}
putc('T', out);
putsVarint(out, (sqlite3_uint64)nCol);
for(i=0; i<nCol; i++) putc(aiFlg[i], out);
fwrite(zTab, 1, strlen(zTab), out);
putc(0, out);
pStmt = db_prepare("%s", sql.z);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt,0);
putc(iType, out);
putc(0, out);
switch( sqlite3_column_int(pStmt,0) ){
case SQLITE_UPDATE: {
for(k=1, i=0; i<nCol; i++){
if( aiFlg[i] ){
putValue(out, pStmt, k);
k++;
}else if( sqlite3_column_int(pStmt,k) ){
putValue(out, pStmt, k+1);
k += 3;
}else{
putc(0, out);
k += 3;
}
}
for(k=1, i=0; i<nCol; i++){
if( aiFlg[i] ){
putc(0, out);
k++;
}else if( sqlite3_column_int(pStmt,k) ){
putValue(out, pStmt, k+2);
k += 3;
}else{
putc(0, out);
k += 3;
}
}
break;
}
case SQLITE_INSERT: {
for(k=1, i=0; i<nCol; i++){
if( aiFlg[i] ){
putValue(out, pStmt, k);
k++;
}else{
putValue(out, pStmt, k+2);
k += 3;
}
}
break;
}
case SQLITE_DELETE: {
for(k=1, i=0; i<nCol; i++){
if( aiFlg[i] ){
putValue(out, pStmt, k);
k++;
}else{
putValue(out, pStmt, k+1);
k += 3;
}
}
break;
}
}
}
sqlite3_finalize(pStmt);
end_changeset_one_table:
while( nCol>0 ) sqlite3_free(azCol[--nCol]);
sqlite3_free(azCol);
sqlite3_free(aiPk);
sqlite3_free(zId);
sqlite3_free(aiFlg);
strFree(&sql);
}
static int is_whitespace(char x){
return (x==' ' || x=='\t' || x=='\n' || x=='\r');
}
static 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;
if( p==0 ) return 0;
while( is_whitespace(*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 && !is_whitespace(*p) && *p!='(' ){
if( pOut<pEnd ) *pOut++ = *p;
p++;
}
}
*pOut = '\0';
return p;
}
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);
}
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_schema\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_schema AS a, temp.tblmap AS b \n"
"WHERE module_name(a.sql) = b.module\n"
" )\n"
"UNION \n"
"SELECT name FROM aux.sqlite_schema\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_schema 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_schema\n"
" WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
" UNION\n"
"SELECT name FROM aux.sqlite_schema\n"
" WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
" ORDER BY name";
}
}
static void showHelp(void){
printf("Usage: %s [options] DB1 DB2\n", g.zArgv0);
printf(
"Output SQL text that would transform DB1 into DB2.\n"
"Options:\n"
" --changeset FILE Write a CHANGESET into FILE\n"
" -L|--lib LIBRARY Load an SQLite extension library\n"
" --primarykey Use schema-defined PRIMARY KEYs\n"
" --rbu Output SQL to create/populate RBU table(s)\n"
" --schema Show only differences in the schema\n"
" --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"
"See https://sqlite.org/sqldiff.html for detailed explanation.\n"
);
}
int main(int argc, char **argv){
const char *zDb1 = 0;
const char *zDb2 = 0;
int i;
int rc;
char *zErrMsg = 0;
char *zSql;
sqlite3_stmt *pStmt;
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;
g.zArgv0 = argv[0];
sqlite3_config(SQLITE_CONFIG_SINGLETHREAD);
for(i=1; i<argc; i++){
const char *z = argv[i];
if( z[0]=='-' ){
z++;
if( z[0]=='-' ) z++;
if( strcmp(z,"changeset")==0 ){
if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
out = fopen(argv[++i], "wb");
if( out==0 ) cmdlineError("cannot open: %s", argv[i]);
xDiff = changeset_one_table;
neverUseTransaction = 1;
}else
if( strcmp(z,"debug")==0 ){
if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
g.fDebug = strtol(argv[++i], 0, 0);
}else
if( strcmp(z,"help")==0 ){
showHelp();
return 0;
}else
#ifndef SQLITE_OMIT_LOAD_EXTENSION
if( strcmp(z,"lib")==0 || strcmp(z,"L")==0 ){
if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
azExt = realloc(azExt, sizeof(azExt[0])*(nExt+1));
if( azExt==0 ) cmdlineError("out of memory");
azExt[nExt++] = argv[++i];
}else
#endif
if( strcmp(z,"primarykey")==0 ){
g.bSchemaPK = 1;
}else
if( strcmp(z,"rbu")==0 ){
xDiff = rbudiff_one_table;
}else
if( strcmp(z,"schema")==0 ){
g.bSchemaOnly = 1;
}else
if( strcmp(z,"summary")==0 ){
xDiff = summarize_one_table;
}else
if( strcmp(z,"table")==0 ){
if( i==argc-1 ) cmdlineError("missing argument to %s", argv[i]);
zTab = argv[++i];
g.bSchemaCompare =
sqlite3_stricmp(zTab, "sqlite_schema")==0
|| sqlite3_stricmp(zTab, "sqlite_master")==0;
}else
if( strcmp(z,"transaction")==0 ){
useTransaction = 1;
}else
if( strcmp(z,"vtab")==0 ){
g.bHandleVtab = 1;
}else
{
cmdlineError("unknown option: %s", argv[i]);
}
}else if( zDb1==0 ){
zDb1 = argv[i];
}else if( zDb2==0 ){
zDb2 = argv[i];
}else{
cmdlineError("unknown argument: %s", argv[i]);
}
}
if( zDb2==0 ){
cmdlineError("two database arguments required");
}
if( g.bSchemaOnly && g.bSchemaCompare ){
cmdlineError("The --schema option is useless with --table %s .", zTab);
}
rc = sqlite3_open(zDb1, &g.db);
if( rc ){
cmdlineError("cannot open database file \"%s\"", zDb1);
}
rc = sqlite3_exec(g.db, "SELECT * FROM sqlite_schema", 0, 0, &zErrMsg);
if( rc || zErrMsg ){
cmdlineError("\"%s\" does not appear to be a valid SQLite database", zDb1);
}
#ifndef SQLITE_OMIT_LOAD_EXTENSION
sqlite3_enable_load_extension(g.db, 1);
for(i=0; i<nExt; i++){
rc = sqlite3_load_extension(g.db, azExt[i], 0, &zErrMsg);
if( rc || zErrMsg ){
cmdlineError("error loading %s: %s", azExt[i], zErrMsg);
}
}
free(azExt);
#endif
zSql = sqlite3_mprintf("ATTACH %Q as aux;", zDb2);
rc = sqlite3_exec(g.db, zSql, 0, 0, &zErrMsg);
sqlite3_free(zSql);
zSql = 0;
if( rc || zErrMsg ){
cmdlineError("cannot attach database \"%s\"", zDb2);
}
rc = sqlite3_exec(g.db, "SELECT * FROM aux.sqlite_schema", 0, 0, &zErrMsg);
if( rc || zErrMsg ){
cmdlineError("\"%s\" does not appear to be a valid SQLite database", zDb2);
}
if( neverUseTransaction ) useTransaction = 0;
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{
pStmt = db_prepare("%s", all_tables_sql() );
while( SQLITE_ROW==sqlite3_step(pStmt) ){
xDiff((const char*)sqlite3_column_text(pStmt,0), out);
}
sqlite3_finalize(pStmt);
}
if( useTransaction ) printf("COMMIT;\n");
sqlite3_close(g.db);
return 0;
}