1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-07 02:42:48 +03:00

Update the sqldiff.exe utility program so that it uses the sqlite3_str

string interface, and so that it does console output using the
ext/consio extension.

FossilOrigin-Name: 4443b7e592da97d1cb1b3b79ed0559452d8057a33aba4d184c2fffbf200e05f5
This commit is contained in:
drh
2024-01-03 12:26:02 +00:00
parent d059153deb
commit 4167033eaf
4 changed files with 220 additions and 241 deletions

View File

@@ -1828,8 +1828,8 @@ $(SQLITE3EXE): shell.c $(SHELL_CORE_DEP) $(LIBRESOBJS) $(SHELL_CORE_SRC) $(SQLIT
/link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS) /link $(SQLITE3EXEPDB) $(LDFLAGS) $(LTLINKOPTS) $(SHELL_LINK_OPTS) $(LTLIBPATHS) $(LIBRESOBJS) $(LIBREADLINE) $(LTLIBS) $(TLIBS)
# <<mark>> # <<mark>>
sqldiff.exe: $(TOP)\tool\sqldiff.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS) sqldiff.exe: $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.h $(TOP)\ext\consio\console_io.c $(SQLITE3C) $(SQLITE3H) $(LIBRESOBJS)
$(LTLINK) $(NO_WARN) $(TOP)\tool\sqldiff.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS) $(LTLINK) $(NO_WARN) -I$(TOP)\ext\consio $(TOP)\tool\sqldiff.c $(TOP)\ext\consio\console_io.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LIBRESOBJS)
dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H) dbhash.exe: $(TOP)\tool\dbhash.c $(SQLITE3C) $(SQLITE3H)
$(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS) $(LTLINK) $(NO_WARN) $(TOP)\tool\dbhash.c $(SQLITE3C) /link $(LDFLAGS) $(LTLINKOPTS)

View File

@@ -1,11 +1,11 @@
C Back\sout\s[b517a52fa36df0a0]\swhich\sis\sno\slonger\sreachable\sdue\sto\searly\nerror\sdetection\senhancements\sin\s[166e82dd20efbfd3]. C Update\sthe\ssqldiff.exe\sutility\sprogram\sso\sthat\sit\suses\sthe\ssqlite3_str\nstring\sinterface,\sand\sso\sthat\sit\sdoes\sconsole\soutput\susing\sthe\next/consio\sextension.
D 2024-01-02T22:49:14.643 D 2024-01-03T12:26:02.221
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79 F Makefile.in 24be65ae641c5727bbc247d60286a15ecc24fb80f14f8fb2d32533bf0ec96e79
F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6 F Makefile.linux-gcc f3842a0b1efbfbb74ac0ef60e56b301836d05b4d867d014f714fa750048f1ab6
F Makefile.msc ddca98e096947fc26e3bb1a20678999dd0a590456278ad7dc55b882fca108bd6 F Makefile.msc 8d4860af68385187806e16757bcbca0b852a7ae1f0618fa2eb42fe51166c5bb6
F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3 F README.md 6358805260a03ebead84e168bbf3740ddf3f683b477e478567186aa7afb490d3
F VERSION 73573d4545343f001bf5dc5461173a7c78c203dd046cabcf99153878cf25d3a6 F VERSION 73573d4545343f001bf5dc5461173a7c78c203dd046cabcf99153878cf25d3a6
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50 F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
@@ -2117,7 +2117,7 @@ F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd F tool/speedtest8inst1.c 7ce07da76b5e745783e703a834417d725b7d45fd
F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x F tool/spellsift.tcl 52b4b04dc4333c7ab024f09d9d66ed6b6f7c6eb00b38497a09f338fa55d40618 x
F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60 F tool/split-sqlite3c.tcl 5aa60643afca558bc732b1444ae81a522326f91e1dc5665b369c54f09e20de60
F tool/sqldiff.c fcccbc07da942b4534d0c769e9fcc21c67cbd7086ddc1c8f13372c40a83d4634 F tool/sqldiff.c 985452ffc8554baee0f945d078859d393a22abc0bbf553a9f12acae1b6e1fdd0
F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c F tool/sqlite3_analyzer.c.in f88615bf33098945e0a42f17733f472083d150b58bdaaa5555a7129d0a51621c
F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898 F tool/sqltclsh.c.in 1bcc2e9da58fadf17b0bf6a50e68c1159e602ce057210b655d50bad5aaaef898
F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848 F tool/sqltclsh.tcl 862f4cf1418df5e1315b5db3b5ebe88969e2a784525af5fbf9596592f14ed848
@@ -2156,9 +2156,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P eed670ea2a9424f7df4eeb01c152fc38f7190a5e39aa891651b28dc91fcdc019 P 704943e96f2620b99260667ac9922c2f72bc3e92e2dfe1d9c2a91c7b704564d9
Q -b517a52fa36df0a0854a75858b5e81861771d2e9032a5a0ad79aa76ae64130a2 R e56f90f682975bdbaf88cd4673a534d1
R 9543e433ed40d6e55dfb715e04bc981d
U drh U drh
Z 20a621e221a4c4291644be7f432a4510 Z a3427ef23d574bfd370854aa9641acf2
# Remove this line to create a well-formed Fossil manifest. # Remove this line to create a well-formed Fossil manifest.

View File

@@ -1 +1 @@
704943e96f2620b99260667ac9922c2f72bc3e92e2dfe1d9c2a91c7b704564d9 4443b7e592da97d1cb1b3b79ed0559452d8057a33aba4d184c2fffbf200e05f5

View File

@@ -13,7 +13,8 @@
** This is a utility program that computes the differences in content ** This is a utility program that computes the differences in content
** between two SQLite databases. ** between two SQLite databases.
** **
** To compile, simply link against SQLite. ** To compile, simply link against SQLite. (Windows builds must also link
** against ext/consio/console_io.c.)
** **
** See the showHelp() routine below for a brief description of how to ** See the showHelp() routine below for a brief description of how to
** run the utility. ** run the utility.
@@ -26,6 +27,19 @@
#include <assert.h> #include <assert.h>
#include "sqlite3.h" #include "sqlite3.h"
/* Output function substitutions that cause UTF8 characters to be rendered
** correctly on Windows:
**
** fprintf() -> Wfprintf()
**
*/
#if defined(_WIN32)
# include "console_io.h"
# define Wfprintf fPrintfUtf8
#else
# define Wfprintf fprintf
#endif
/* /*
** All global variables are gathered into the "g" singleton. ** All global variables are gathered into the "g" singleton.
*/ */
@@ -46,22 +60,10 @@ struct GlobalVars {
#define DEBUG_DIFF_SQL 0x000002 #define DEBUG_DIFF_SQL 0x000002
/* /*
** Dynamic string object ** Clear and free an sqlite3_str object
*/ */
typedef struct Str Str; static void strFree(sqlite3_str *pStr){
struct Str { sqlite3_free(sqlite3_str_finish(pStr));
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;
} }
/* /*
@@ -69,12 +71,14 @@ static void strInit(Str *p){
** abort the program. ** abort the program.
*/ */
static void cmdlineError(const char *zFormat, ...){ static void cmdlineError(const char *zFormat, ...){
sqlite3_str *pOut = sqlite3_str_new(0);
va_list ap; va_list ap;
fprintf(stderr, "%s: ", g.zArgv0);
va_start(ap, zFormat); va_start(ap, zFormat);
vfprintf(stderr, zFormat, ap); sqlite3_str_vappendf(pOut, zFormat, ap);
va_end(ap); va_end(ap);
fprintf(stderr, "\n\"%s --help\" for more help\n", g.zArgv0); Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut));
strFree(pOut);
Wfprintf(stderr, "\"%s --help\" for more help\n", g.zArgv0);
exit(1); exit(1);
} }
@@ -83,49 +87,16 @@ static void cmdlineError(const char *zFormat, ...){
** abort the program. ** abort the program.
*/ */
static void runtimeError(const char *zFormat, ...){ static void runtimeError(const char *zFormat, ...){
sqlite3_str *pOut = sqlite3_str_new(0);
va_list ap; va_list ap;
fprintf(stderr, "%s: ", g.zArgv0);
va_start(ap, zFormat); va_start(ap, zFormat);
vfprintf(stderr, zFormat, ap); sqlite3_str_vappendf(pOut, zFormat, ap);
va_end(ap); va_end(ap);
fprintf(stderr, "\n"); Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut));
strFree(pOut);
exit(1); 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 /* Safely quote an SQL identifier. Use the minimum amount of transformation
** necessary to allow the string to be used with %s. ** necessary to allow the string to be used with %s.
@@ -453,7 +424,7 @@ static void dump_table(const char *zTab, FILE *out){
int i; /* Loop counter */ int i; /* Loop counter */
sqlite3_stmt *pStmt; /* SQL statement */ sqlite3_stmt *pStmt; /* SQL statement */
const char *zSep; /* Separator string */ const char *zSep; /* Separator string */
Str ins; /* Beginning of the INSERT statement */ sqlite3_str *pIns; /* Beginning of the INSERT statement */
pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema WHERE name=%Q", zTab); pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema WHERE name=%Q", zTab);
if( SQLITE_ROW==sqlite3_step(pStmt) ){ if( SQLITE_ROW==sqlite3_step(pStmt) ){
@@ -462,54 +433,53 @@ static void dump_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
if( !g.bSchemaOnly ){ if( !g.bSchemaOnly ){
az = columnNames("aux", zTab, &nPk, 0); az = columnNames("aux", zTab, &nPk, 0);
strInit(&ins); pIns = sqlite3_str_new(0);
if( az==0 ){ if( az==0 ){
pStmt = db_prepare("SELECT * FROM aux.%s", zId); pStmt = db_prepare("SELECT * FROM aux.%s", zId);
strPrintf(&ins,"INSERT INTO %s VALUES", zId); sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId);
}else{ }else{
Str sql; sqlite3_str *pSql = sqlite3_str_new(0);
strInit(&sql);
zSep = "SELECT"; zSep = "SELECT";
for(i=0; az[i]; i++){ for(i=0; az[i]; i++){
strPrintf(&sql, "%s %s", zSep, az[i]); sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
zSep = ","; zSep = ",";
} }
strPrintf(&sql," FROM aux.%s", zId); sqlite3_str_appendf(pSql," FROM aux.%s", zId);
zSep = " ORDER BY"; zSep = " ORDER BY";
for(i=1; i<=nPk; i++){ for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s %d", zSep, i); sqlite3_str_appendf(pSql, "%s %d", zSep, i);
zSep = ","; zSep = ",";
} }
pStmt = db_prepare("%s", sql.z); pStmt = db_prepare("%s", sqlite3_str_value(pSql));
strFree(&sql); strFree(pSql);
strPrintf(&ins, "INSERT INTO %s", zId); sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
zSep = "("; zSep = "(";
for(i=0; az[i]; i++){ for(i=0; az[i]; i++){
strPrintf(&ins, "%s%s", zSep, az[i]); sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
zSep = ","; zSep = ",";
} }
strPrintf(&ins,") VALUES"); sqlite3_str_appendf(pIns,") VALUES");
namelistFree(az); namelistFree(az);
} }
nCol = sqlite3_column_count(pStmt); nCol = sqlite3_column_count(pStmt);
while( SQLITE_ROW==sqlite3_step(pStmt) ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s",ins.z); Wfprintf(out, "%s",sqlite3_str_value(pIns));
zSep = "("; zSep = "(";
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
fprintf(out, "%s",zSep); Wfprintf(out, "%s",zSep);
printQuoted(out, sqlite3_column_value(pStmt,i)); printQuoted(out, sqlite3_column_value(pStmt,i));
zSep = ","; zSep = ",";
} }
fprintf(out, ");\n"); Wfprintf(out, ");\n");
} }
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
strFree(&ins); strFree(pIns);
} /* endif !g.bSchemaOnly */ } /* endif !g.bSchemaOnly */
pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema" pStmt = db_prepare("SELECT sql FROM aux.sqlite_schema"
" WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL", " WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
zTab); zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s;\n", sqlite3_column_text(pStmt,0)); Wfprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
} }
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
sqlite3_free(zId); sqlite3_free(zId);
@@ -531,12 +501,12 @@ static void diff_one_table(const char *zTab, FILE *out){
int nQ; /* Number of output columns in the diff query */ int nQ; /* Number of output columns in the diff query */
int i; /* Loop counter */ int i; /* Loop counter */
const char *zSep; /* Separator string */ const char *zSep; /* Separator string */
Str sql; /* Comparison query */ sqlite3_str *pSql; /* Comparison query */
sqlite3_stmt *pStmt; /* Query statement to do the diff */ sqlite3_stmt *pStmt; /* Query statement to do the diff */
const char *zLead = /* Becomes line-comment for sqlite_schema */ const char *zLead = /* Becomes line-comment for sqlite_schema */
(g.bSchemaCompare)? "-- " : ""; (g.bSchemaCompare)? "-- " : "";
strInit(&sql); pSql = sqlite3_str_new(0);
if( g.fDebug==DEBUG_COLUMN_NAMES ){ if( g.fDebug==DEBUG_COLUMN_NAMES ){
/* Simply run columnNames() on all tables of the origin /* Simply run columnNames() on all tables of the origin
** database and show the results. This is used for testing ** database and show the results. This is used for testing
@@ -544,14 +514,14 @@ static void diff_one_table(const char *zTab, FILE *out){
*/ */
az = columnNames("aux",zTab, &nPk, 0); az = columnNames("aux",zTab, &nPk, 0);
if( az==0 ){ if( az==0 ){
printf("Rowid not accessible for %s\n", zId); Wfprintf(stdout, "Rowid not accessible for %s\n", zId);
}else{ }else{
printf("%s:", zId); Wfprintf(stdout, "%s:", zId);
for(i=0; az[i]; i++){ for(i=0; az[i]; i++){
printf(" %s", az[i]); Wfprintf(stdout, " %s", az[i]);
if( i+1==nPk ) printf(" *"); if( i+1==nPk ) Wfprintf(stdout, " *");
} }
printf("\n"); Wfprintf(stdout, "\n");
} }
goto end_diff_one_table; goto end_diff_one_table;
} }
@@ -560,19 +530,20 @@ static void diff_one_table(const char *zTab, FILE *out){
if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){ if( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
/* Table missing from second database. */ /* Table missing from second database. */
if( g.bSchemaCompare ) if( g.bSchemaCompare )
fprintf(out, "-- 2nd DB has no %s table\n", zTab); Wfprintf(out, "-- 2nd DB has no %s table\n", zTab);
else else
fprintf(out, "DROP TABLE %s;\n", zId); Wfprintf(out, "DROP TABLE %s;\n", zId);
} }
goto end_diff_one_table; goto end_diff_one_table;
} }
if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){ if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
/* Table missing from source */ /* Table missing from source */
if( g.bSchemaCompare ) if( g.bSchemaCompare ){
fprintf(out, "-- 1st DB has no %s table\n", zTab); Wfprintf(out, "-- 1st DB has no %s table\n", zTab);
else }else{
dump_table(zTab, out); dump_table(zTab, out);
}
goto end_diff_one_table; goto end_diff_one_table;
} }
@@ -589,7 +560,7 @@ static void diff_one_table(const char *zTab, FILE *out){
|| az[n] || az[n]
){ ){
/* Schema mismatch */ /* Schema mismatch */
fprintf(out, "%sDROP TABLE %s; -- due to schema mismatch\n", zLead, zId); Wfprintf(out, "%sDROP TABLE %s; -- due to schema mismatch\n", zLead, zId);
dump_table(zTab, out); dump_table(zTab, out);
goto end_diff_one_table; goto end_diff_one_table;
} }
@@ -597,93 +568,93 @@ static void diff_one_table(const char *zTab, FILE *out){
/* Build the comparison query */ /* Build the comparison query */
for(n2=n; az2[n2]; n2++){ for(n2=n; az2[n2]; n2++){
char *zNTab = safeId(az2[n2]); char *zNTab = safeId(az2[n2]);
fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab); Wfprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
sqlite3_free(zNTab); sqlite3_free(zNTab);
} }
nQ = nPk2+1+2*(n2-nPk2); nQ = nPk2+1+2*(n2-nPk2);
if( n2>nPk2 ){ if( n2>nPk2 ){
zSep = "SELECT "; zSep = "SELECT ";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]); sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
zSep = ", "; zSep = ", ";
} }
strPrintf(&sql, ", 1 /* changed row */"); sqlite3_str_appendf(pSql, ", 1 /* changed row */");
while( az[i] ){ while( az[i] ){
strPrintf(&sql, ", A.%s IS NOT B.%s, B.%s", sqlite3_str_appendf(pSql, ", A.%s IS NOT B.%s, B.%s",
az[i], az2[i], az2[i]); az[i], az2[i], az2[i]);
i++; i++;
} }
while( az2[i] ){ while( az2[i] ){
strPrintf(&sql, ", B.%s IS NOT NULL, B.%s", sqlite3_str_appendf(pSql, ", B.%s IS NOT NULL, B.%s",
az2[i], az2[i]); az2[i], az2[i]);
i++; i++;
} }
strPrintf(&sql, "\n FROM main.%s A, aux.%s B\n", zId, zId); sqlite3_str_appendf(pSql, "\n FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
zSep = "\n AND ("; zSep = "\n AND (";
while( az[i] ){ while( az[i] ){
strPrintf(&sql, "%sA.%s IS NOT B.%s%s\n", sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s%s\n",
zSep, az[i], az2[i], az2[i+1]==0 ? ")" : ""); zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
zSep = " OR "; zSep = " OR ";
i++; i++;
} }
while( az2[i] ){ while( az2[i] ){
strPrintf(&sql, "%sB.%s IS NOT NULL%s\n", sqlite3_str_appendf(pSql, "%sB.%s IS NOT NULL%s\n",
zSep, az2[i], az2[i+1]==0 ? ")" : ""); zSep, az2[i], az2[i+1]==0 ? ")" : "");
zSep = " OR "; zSep = " OR ";
i++; i++;
} }
strPrintf(&sql, " UNION ALL\n"); sqlite3_str_appendf(pSql, " UNION ALL\n");
} }
zSep = "SELECT "; zSep = "SELECT ";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%sA.%s", zSep, az[i]); sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
zSep = ", "; zSep = ", ";
} }
strPrintf(&sql, ", 2 /* deleted row */"); sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
while( az2[i] ){ while( az2[i] ){
strPrintf(&sql, ", NULL, NULL"); sqlite3_str_appendf(pSql, ", NULL, NULL");
i++; i++;
} }
strPrintf(&sql, "\n FROM main.%s A\n", zId); sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n"); sqlite3_str_appendf(pSql, ")\n");
zSep = " UNION ALL\nSELECT "; zSep = " UNION ALL\nSELECT ";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]); sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
zSep = ", "; zSep = ", ";
} }
strPrintf(&sql, ", 3 /* inserted row */"); sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
while( az2[i] ){ while( az2[i] ){
strPrintf(&sql, ", 1, B.%s", az2[i]); sqlite3_str_appendf(pSql, ", 1, B.%s", az2[i]);
i++; i++;
} }
strPrintf(&sql, "\n FROM aux.%s B\n", zId); sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n ORDER BY"); sqlite3_str_appendf(pSql, ")\n ORDER BY");
zSep = " "; zSep = " ";
for(i=1; i<=nPk; i++){ for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s%d", zSep, i); sqlite3_str_appendf(pSql, "%s%d", zSep, i);
zSep = ", "; zSep = ", ";
} }
strPrintf(&sql, ";\n"); sqlite3_str_appendf(pSql, ";\n");
if( g.fDebug & DEBUG_DIFF_SQL ){ if( g.fDebug & DEBUG_DIFF_SQL ){
printf("SQL for %s:\n%s\n", zId, sql.z); printf("SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
goto end_diff_one_table; goto end_diff_one_table;
} }
@@ -705,7 +676,7 @@ static void diff_one_table(const char *zTab, FILE *out){
/* Run the query and output differences */ /* Run the query and output differences */
if( !g.bSchemaOnly ){ if( !g.bSchemaOnly ){
pStmt = db_prepare("%s", sql.z); pStmt = db_prepare("%s", sqlite3_str_value(pSql));
while( SQLITE_ROW==sqlite3_step(pStmt) ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt, nPk); int iType = sqlite3_column_int(pStmt, nPk);
if( iType==1 || iType==2 ){ if( iType==1 || iType==2 ){
@@ -763,7 +734,7 @@ static void diff_one_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
end_diff_one_table: end_diff_one_table:
strFree(&sql); strFree(pSql);
sqlite3_free(zId); sqlite3_free(zId);
namelistFree(az); namelistFree(az);
namelistFree(az2); namelistFree(az2);
@@ -1171,15 +1142,15 @@ static int rbuDeltaCreate(
**************************************************************************/ **************************************************************************/
static void strPrintfArray( static void strPrintfArray(
Str *pStr, /* String object to append to */ sqlite3_str *pStr, /* String object to append to */
const char *zSep, /* Separator string */ const char *zSep, /* Separator string */
const char *zFmt, /* Format for each entry */ const char *zFmt, /* Format for each entry */
char **az, int n /* Array of strings & its size (or -1) */ char **az, int n /* Array of strings & its size (or -1) */
){ ){
int i; int i;
for(i=0; az[i] && (i<n || n<0); i++){ for(i=0; az[i] && (i<n || n<0); i++){
if( i!=0 ) strPrintf(pStr, "%s", zSep); if( i!=0 ) sqlite3_str_appendf(pStr, "%s", zSep);
strPrintf(pStr, zFmt, az[i], az[i], az[i]); sqlite3_str_appendf(pStr, zFmt, az[i], az[i], az[i]);
} }
} }
@@ -1188,74 +1159,75 @@ static void getRbudiffQuery(
char **azCol, char **azCol,
int nPK, int nPK,
int bOtaRowid, int bOtaRowid,
Str *pSql sqlite3_str *pSql
){ ){
int i; int i;
/* First the newly inserted rows: **/ /* First the newly inserted rows: **/
strPrintf(pSql, "SELECT "); sqlite3_str_appendf(pSql, "SELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, -1); strPrintfArray(pSql, ", ", "%s", azCol, -1);
strPrintf(pSql, ", 0, "); /* Set ota_control to 0 for an insert */ sqlite3_str_appendf(pSql, ", 0, "); /* Set ota_control to 0 for an insert */
strPrintfArray(pSql, ", ", "NULL", azCol, -1); strPrintfArray(pSql, ", ", "NULL", azCol, -1);
strPrintf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab); sqlite3_str_appendf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab);
strPrintf(pSql, " SELECT 1 FROM ", zTab); sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
strPrintf(pSql, " main.%Q AS o WHERE ", zTab); sqlite3_str_appendf(pSql, " main.%Q AS o WHERE ", zTab);
strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
strPrintf(pSql, "\n) AND "); sqlite3_str_appendf(pSql, "\n) AND ");
strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
/* Deleted rows: */ /* Deleted rows: */
strPrintf(pSql, "\nUNION ALL\nSELECT "); sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, nPK); strPrintfArray(pSql, ", ", "%s", azCol, nPK);
if( azCol[nPK] ){ if( azCol[nPK] ){
strPrintf(pSql, ", "); sqlite3_str_appendf(pSql, ", ");
strPrintfArray(pSql, ", ", "NULL", &azCol[nPK], -1); strPrintfArray(pSql, ", ", "NULL", &azCol[nPK], -1);
} }
strPrintf(pSql, ", 1, "); /* Set ota_control to 1 for a delete */ sqlite3_str_appendf(pSql, ", 1, "); /* Set ota_control to 1 for a delete */
strPrintfArray(pSql, ", ", "NULL", azCol, -1); strPrintfArray(pSql, ", ", "NULL", azCol, -1);
strPrintf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab); sqlite3_str_appendf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab);
strPrintf(pSql, " SELECT 1 FROM ", zTab); sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
strPrintf(pSql, " aux.%Q AS o WHERE ", zTab); sqlite3_str_appendf(pSql, " aux.%Q AS o WHERE ", zTab);
strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
strPrintf(pSql, "\n) AND "); sqlite3_str_appendf(pSql, "\n) AND ");
strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK); strPrintfArray(pSql, " AND ", "(n.%Q IS NOT NULL)", azCol, nPK);
/* Updated rows. If all table columns are part of the primary key, there /* 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 ** can be no updates. In this case this part of the compound SELECT can
** be omitted altogether. */ ** be omitted altogether. */
if( azCol[nPK] ){ if( azCol[nPK] ){
strPrintf(pSql, "\nUNION ALL\nSELECT "); sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "n.%s", azCol, nPK); strPrintfArray(pSql, ", ", "n.%s", azCol, nPK);
strPrintf(pSql, ",\n"); sqlite3_str_appendf(pSql, ",\n");
strPrintfArray(pSql, " ,\n", strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE n.%s END", &azCol[nPK], -1 " CASE WHEN n.%s IS o.%s THEN NULL ELSE n.%s END", &azCol[nPK], -1
); );
if( bOtaRowid==0 ){ if( bOtaRowid==0 ){
strPrintf(pSql, ", '"); sqlite3_str_appendf(pSql, ", '");
strPrintfArray(pSql, "", ".", azCol, nPK); strPrintfArray(pSql, "", ".", azCol, nPK);
strPrintf(pSql, "' ||\n"); sqlite3_str_appendf(pSql, "' ||\n");
}else{ }else{
strPrintf(pSql, ",\n"); sqlite3_str_appendf(pSql, ",\n");
} }
strPrintfArray(pSql, " ||\n", strPrintfArray(pSql, " ||\n",
" CASE WHEN n.%s IS o.%s THEN '.' ELSE 'x' END", &azCol[nPK], -1 " CASE WHEN n.%s IS o.%s THEN '.' ELSE 'x' END", &azCol[nPK], -1
); );
strPrintf(pSql, "\nAS ota_control, "); sqlite3_str_appendf(pSql, "\nAS ota_control, ");
strPrintfArray(pSql, ", ", "NULL", azCol, nPK); strPrintfArray(pSql, ", ", "NULL", azCol, nPK);
strPrintf(pSql, ",\n"); sqlite3_str_appendf(pSql, ",\n");
strPrintfArray(pSql, " ,\n", strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE o.%s END", &azCol[nPK], -1 " 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); sqlite3_str_appendf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ",
zTab, zTab);
strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK); strPrintfArray(pSql, " AND ", "(n.%Q = o.%Q)", azCol, nPK);
strPrintf(pSql, " AND ota_control LIKE '%%x%%'"); sqlite3_str_appendf(pSql, " AND ota_control LIKE '%%x%%'");
} }
/* Now add an ORDER BY clause to sort everything by PK. */ /* Now add an ORDER BY clause to sort everything by PK. */
strPrintf(pSql, "\nORDER BY "); sqlite3_str_appendf(pSql, "\nORDER BY ");
for(i=1; i<=nPK; i++) strPrintf(pSql, "%s%d", ((i>1)?", ":""), i); for(i=1; i<=nPK; i++) sqlite3_str_appendf(pSql, "%s%d", ((i>1)?", ":""), i);
} }
static void rbudiff_one_table(const char *zTab, FILE *out){ static void rbudiff_one_table(const char *zTab, FILE *out){
@@ -1264,14 +1236,17 @@ static void rbudiff_one_table(const char *zTab, FILE *out){
char **azCol; /* NULL terminated array of col names */ char **azCol; /* NULL terminated array of col names */
int i; int i;
int nCol; int nCol;
Str ct = {0, 0, 0}; /* The "CREATE TABLE data_xxx" statement */ sqlite3_str *pCt; /* The "CREATE TABLE data_xxx" statement */
Str sql = {0, 0, 0}; /* Query to find differences */ sqlite3_str *pSql; /* Query to find differences */
Str insert = {0, 0, 0}; /* First part of output INSERT statement */ sqlite3_str *pInsert; /* First part of output INSERT statement */
sqlite3_stmt *pStmt = 0; sqlite3_stmt *pStmt = 0;
int nRow = 0; /* Total rows in data_xxx table */ int nRow = 0; /* Total rows in data_xxx table */
/* --rbu mode must use real primary keys. */ /* --rbu mode must use real primary keys. */
g.bSchemaPK = 1; g.bSchemaPK = 1;
pCt = sqlite3_str_new(0);
pSql = sqlite3_str_new(0);
pInsert = sqlite3_str_new(0);
/* Check that the schemas of the two tables match. Exit early otherwise. */ /* Check that the schemas of the two tables match. Exit early otherwise. */
checkSchemasMatch(zTab); checkSchemasMatch(zTab);
@@ -1285,35 +1260,35 @@ static void rbudiff_one_table(const char *zTab, FILE *out){
for(nCol=0; azCol[nCol]; nCol++); for(nCol=0; azCol[nCol]; nCol++);
/* Build and output the CREATE TABLE statement for the data_xxx table */ /* Build and output the CREATE TABLE statement for the data_xxx table */
strPrintf(&ct, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab); sqlite3_str_appendf(pCt, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab);
if( bOtaRowid ) strPrintf(&ct, "rbu_rowid, "); if( bOtaRowid ) sqlite3_str_appendf(pCt, "rbu_rowid, ");
strPrintfArray(&ct, ", ", "%s", &azCol[bOtaRowid], -1); strPrintfArray(pCt, ", ", "%s", &azCol[bOtaRowid], -1);
strPrintf(&ct, ", rbu_control);"); sqlite3_str_appendf(pCt, ", rbu_control);");
/* Get the SQL for the query to retrieve data from the two databases */ /* Get the SQL for the query to retrieve data from the two databases */
getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, &sql); getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, pSql);
/* Build the first part of the INSERT statement output for each row /* Build the first part of the INSERT statement output for each row
** in the data_xxx table. */ ** in the data_xxx table. */
strPrintf(&insert, "INSERT INTO 'data_%q' (", zTab); sqlite3_str_appendf(pInsert, "INSERT INTO 'data_%q' (", zTab);
if( bOtaRowid ) strPrintf(&insert, "rbu_rowid, "); if( bOtaRowid ) sqlite3_str_appendf(pInsert, "rbu_rowid, ");
strPrintfArray(&insert, ", ", "%s", &azCol[bOtaRowid], -1); strPrintfArray(pInsert, ", ", "%s", &azCol[bOtaRowid], -1);
strPrintf(&insert, ", rbu_control) VALUES("); sqlite3_str_appendf(pInsert, ", rbu_control) VALUES(");
pStmt = db_prepare("%s", sql.z); pStmt = db_prepare("%s", sqlite3_str_value(pSql));
while( sqlite3_step(pStmt)==SQLITE_ROW ){ while( sqlite3_step(pStmt)==SQLITE_ROW ){
/* If this is the first row output, print out the CREATE TABLE /* If this is the first row output, print out the CREATE TABLE
** statement first. And then set ct.z to NULL so that it is not ** statement first. And reset pCt so that it will not be
** printed again. */ ** printed again. */
if( ct.z ){ if( sqlite3_str_length(pCt) ){
fprintf(out, "%s\n", ct.z); fprintf(out, "%s\n", sqlite3_str_value(pCt));
strFree(&ct); sqlite3_str_reset(pCt);
} }
/* Output the first part of the INSERT statement */ /* Output the first part of the INSERT statement */
fprintf(out, "%s", insert.z); fprintf(out, "%s", sqlite3_str_value(pInsert));
nRow++; nRow++;
if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){ if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){
@@ -1369,15 +1344,16 @@ static void rbudiff_one_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
if( nRow>0 ){ if( nRow>0 ){
Str cnt = {0, 0, 0}; sqlite3_str *pCnt = sqlite3_str_new(0);
strPrintf(&cnt, "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow); sqlite3_str_appendf(pCnt,
fprintf(out, "%s\n", cnt.z); "INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow);
strFree(&cnt); fprintf(out, "%s\n", sqlite3_str_value(pCnt));
strFree(pCnt);
} }
strFree(&ct); strFree(pCt);
strFree(&sql); strFree(pSql);
strFree(&insert); strFree(pInsert);
} }
/* /*
@@ -1399,25 +1375,25 @@ static void summarize_one_table(const char *zTab, FILE *out){
int n2; /* Number of columns in aux */ int n2; /* Number of columns in aux */
int i; /* Loop counter */ int i; /* Loop counter */
const char *zSep; /* Separator string */ const char *zSep; /* Separator string */
Str sql; /* Comparison query */ sqlite3_str *pSql; /* Comparison query */
sqlite3_stmt *pStmt; /* Query statement to do the diff */ sqlite3_stmt *pStmt; /* Query statement to do the diff */
sqlite3_int64 nUpdate; /* Number of updated rows */ sqlite3_int64 nUpdate; /* Number of updated rows */
sqlite3_int64 nUnchanged; /* Number of unmodified rows */ sqlite3_int64 nUnchanged; /* Number of unmodified rows */
sqlite3_int64 nDelete; /* Number of deleted rows */ sqlite3_int64 nDelete; /* Number of deleted rows */
sqlite3_int64 nInsert; /* Number of inserted rows */ sqlite3_int64 nInsert; /* Number of inserted rows */
strInit(&sql); pSql = sqlite3_str_new(0);
if( sqlite3_table_column_metadata(g.db,"aux",zTab,0,0,0,0,0,0) ){ 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( !sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
/* Table missing from second database. */ /* Table missing from second database. */
fprintf(out, "%s: missing from second database\n", zTab); Wfprintf(out, "%s: missing from second database\n", zTab);
} }
goto end_summarize_one_table; goto end_summarize_one_table;
} }
if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){ if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
/* Table missing from source */ /* Table missing from source */
fprintf(out, "%s: missing from first database\n", zTab); Wfprintf(out, "%s: missing from first database\n", zTab);
goto end_summarize_one_table; goto end_summarize_one_table;
} }
@@ -1434,57 +1410,57 @@ static void summarize_one_table(const char *zTab, FILE *out){
|| az[n] || az[n]
){ ){
/* Schema mismatch */ /* Schema mismatch */
fprintf(out, "%s: incompatible schema\n", zTab); Wfprintf(out, "%s: incompatible schema\n", zTab);
goto end_summarize_one_table; goto end_summarize_one_table;
} }
/* Build the comparison query */ /* Build the comparison query */
for(n2=n; az[n2]; n2++){} for(n2=n; az[n2]; n2++){}
strPrintf(&sql, "SELECT 1, count(*)"); sqlite3_str_appendf(pSql, "SELECT 1, count(*)");
if( n2==nPk2 ){ if( n2==nPk2 ){
strPrintf(&sql, ", 0\n"); sqlite3_str_appendf(pSql, ", 0\n");
}else{ }else{
zSep = ", sum("; zSep = ", sum(";
for(i=nPk; az[i]; i++){ for(i=nPk; az[i]; i++){
strPrintf(&sql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, az[i], az[i]);
zSep = " OR "; zSep = " OR ";
} }
strPrintf(&sql, ")\n"); sqlite3_str_appendf(pSql, ")\n");
} }
strPrintf(&sql, " FROM main.%s A, aux.%s B\n", zId, zId); sqlite3_str_appendf(pSql, " FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, " UNION ALL\n"); sqlite3_str_appendf(pSql, " UNION ALL\n");
strPrintf(&sql, "SELECT 2, count(*), 0\n"); sqlite3_str_appendf(pSql, "SELECT 2, count(*), 0\n");
strPrintf(&sql, " FROM main.%s A\n", zId); sqlite3_str_appendf(pSql, " FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B ", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B ", zId);
zSep = "WHERE"; zSep = "WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n"); sqlite3_str_appendf(pSql, ")\n");
strPrintf(&sql, " UNION ALL\n"); sqlite3_str_appendf(pSql, " UNION ALL\n");
strPrintf(&sql, "SELECT 3, count(*), 0\n"); sqlite3_str_appendf(pSql, "SELECT 3, count(*), 0\n");
strPrintf(&sql, " FROM aux.%s B\n", zId); sqlite3_str_appendf(pSql, " FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A ", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A ", zId);
zSep = "WHERE"; zSep = "WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, az[i], az[i]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n ORDER BY 1;\n"); sqlite3_str_appendf(pSql, ")\n ORDER BY 1;\n");
if( (g.fDebug & DEBUG_DIFF_SQL)!=0 ){ if( (g.fDebug & DEBUG_DIFF_SQL)!=0 ){
printf("SQL for %s:\n%s\n", zId, sql.z); Wfprintf(stdout, "SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
goto end_summarize_one_table; goto end_summarize_one_table;
} }
/* Run the query and output difference summary */ /* Run the query and output difference summary */
pStmt = db_prepare("%s", sql.z); pStmt = db_prepare("%s", sqlite3_str_value(pSql));
nUpdate = 0; nUpdate = 0;
nInsert = 0; nInsert = 0;
nDelete = 0; nDelete = 0;
@@ -1504,11 +1480,12 @@ static void summarize_one_table(const char *zTab, FILE *out){
} }
} }
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
fprintf(out, "%s: %lld changes, %lld inserts, %lld deletes, %lld unchanged\n", Wfprintf(out,
"%s: %lld changes, %lld inserts, %lld deletes, %lld unchanged\n",
zTab, nUpdate, nInsert, nDelete, nUnchanged); zTab, nUpdate, nInsert, nDelete, nUnchanged);
end_summarize_one_table: end_summarize_one_table:
strFree(&sql); strFree(pSql);
sqlite3_free(zId); sqlite3_free(zId);
namelistFree(az); namelistFree(az);
namelistFree(az2); namelistFree(az2);
@@ -1588,13 +1565,13 @@ static void changeset_one_table(const char *zTab, FILE *out){
int *aiFlg = 0; /* 0 if column is not part of PK */ int *aiFlg = 0; /* 0 if column is not part of PK */
int *aiPk = 0; /* Column numbers for each PK column */ int *aiPk = 0; /* Column numbers for each PK column */
int nPk = 0; /* Number of PRIMARY KEY columns */ int nPk = 0; /* Number of PRIMARY KEY columns */
Str sql; /* SQL for the diff query */ sqlite3_str *pSql; /* SQL for the diff query */
int i, k; /* Loop counters */ int i, k; /* Loop counters */
const char *zSep; /* List separator */ const char *zSep; /* List separator */
/* Check that the schemas of the two tables match. Exit early otherwise. */ /* Check that the schemas of the two tables match. Exit early otherwise. */
checkSchemasMatch(zTab); checkSchemasMatch(zTab);
strInit(&sql); pSql = sqlite3_str_new(0);
pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab); pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){
@@ -1617,71 +1594,74 @@ static void changeset_one_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
if( nPk==0 ) goto end_changeset_one_table; if( nPk==0 ) goto end_changeset_one_table;
if( nCol>nPk ){ if( nCol>nPk ){
strPrintf(&sql, "SELECT %d", SQLITE_UPDATE); sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_UPDATE);
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
if( aiFlg[i] ){ if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]); sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
}else{ }else{
strPrintf(&sql, ",\n A.%s IS NOT B.%s, A.%s, B.%s", sqlite3_str_appendf(pSql, ",\n A.%s IS NOT B.%s, A.%s, B.%s",
azCol[i], azCol[i], azCol[i], azCol[i]); azCol[i], azCol[i], azCol[i], azCol[i]);
} }
} }
strPrintf(&sql,"\n FROM main.%s A, aux.%s B\n", zId, zId); sqlite3_str_appendf(pSql,"\n FROM main.%s A, aux.%s B\n", zId, zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND"; zSep = " AND";
} }
zSep = "\n AND ("; zSep = "\n AND (";
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
if( aiFlg[i] ) continue; if( aiFlg[i] ) continue;
strPrintf(&sql, "%sA.%s IS NOT B.%s", zSep, azCol[i], azCol[i]); sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, azCol[i], azCol[i]);
zSep = " OR\n "; zSep = " OR\n ";
} }
strPrintf(&sql,")\n UNION ALL\n"); sqlite3_str_appendf(pSql,")\n UNION ALL\n");
} }
strPrintf(&sql, "SELECT %d", SQLITE_DELETE); sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_DELETE);
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
if( aiFlg[i] ){ if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]); sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
}else{ }else{
strPrintf(&sql, ",\n 1, A.%s, NULL", azCol[i]); sqlite3_str_appendf(pSql, ",\n 1, A.%s, NULL", azCol[i]);
} }
} }
strPrintf(&sql, "\n FROM main.%s A\n", zId); sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n UNION ALL\n"); sqlite3_str_appendf(pSql, ")\n UNION ALL\n");
strPrintf(&sql, "SELECT %d", SQLITE_INSERT); sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_INSERT);
for(i=0; i<nCol; i++){ for(i=0; i<nCol; i++){
if( aiFlg[i] ){ if( aiFlg[i] ){
strPrintf(&sql, ",\n B.%s", azCol[i]); sqlite3_str_appendf(pSql, ",\n B.%s", azCol[i]);
}else{ }else{
strPrintf(&sql, ",\n 1, NULL, B.%s", azCol[i]); sqlite3_str_appendf(pSql, ",\n 1, NULL, B.%s", azCol[i]);
} }
} }
strPrintf(&sql, "\n FROM aux.%s B\n", zId); sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId); sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
zSep = " WHERE"; zSep = " WHERE";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s A.%s=B.%s", zSep, azCol[aiPk[i]], azCol[aiPk[i]]); sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND"; zSep = " AND";
} }
strPrintf(&sql, ")\n"); sqlite3_str_appendf(pSql, ")\n");
strPrintf(&sql, " ORDER BY"); sqlite3_str_appendf(pSql, " ORDER BY");
zSep = " "; zSep = " ";
for(i=0; i<nPk; i++){ for(i=0; i<nPk; i++){
strPrintf(&sql, "%s %d", zSep, aiPk[i]+2); sqlite3_str_appendf(pSql, "%s %d", zSep, aiPk[i]+2);
zSep = ","; zSep = ",";
} }
strPrintf(&sql, ";\n"); sqlite3_str_appendf(pSql, ";\n");
if( g.fDebug & DEBUG_DIFF_SQL ){ if( g.fDebug & DEBUG_DIFF_SQL ){
printf("SQL for %s:\n%s\n", zId, sql.z); Wfprintf(stdout, "SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
goto end_changeset_one_table; goto end_changeset_one_table;
} }
@@ -1691,7 +1671,7 @@ static void changeset_one_table(const char *zTab, FILE *out){
fwrite(zTab, 1, strlen(zTab), out); fwrite(zTab, 1, strlen(zTab), out);
putc(0, out); putc(0, out);
pStmt = db_prepare("%s", sql.z); pStmt = db_prepare("%s", sqlite3_str_value(pSql));
while( SQLITE_ROW==sqlite3_step(pStmt) ){ while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt,0); int iType = sqlite3_column_int(pStmt,0);
putc(iType, out); putc(iType, out);
@@ -1758,7 +1738,7 @@ end_changeset_one_table:
sqlite3_free(aiPk); sqlite3_free(aiPk);
sqlite3_free(zId); sqlite3_free(zId);
sqlite3_free(aiFlg); sqlite3_free(aiFlg);
strFree(&sql); strFree(pSql);
} }
/* /*
@@ -1911,8 +1891,8 @@ const char *all_tables_sql(){
** Print sketchy documentation for this utility program ** Print sketchy documentation for this utility program
*/ */
static void showHelp(void){ static void showHelp(void){
printf("Usage: %s [options] DB1 DB2\n", g.zArgv0); Wfprintf(stdout, "Usage: %s [options] DB1 DB2\n", g.zArgv0);
printf( Wfprintf(stdout,
"Output SQL text that would transform DB1 into DB2.\n" "Output SQL text that would transform DB1 into DB2.\n"
"Options:\n" "Options:\n"
" --changeset FILE Write a CHANGESET into FILE\n" " --changeset FILE Write a CHANGESET into FILE\n"
@@ -2049,9 +2029,9 @@ int main(int argc, char **argv){
} }
if( neverUseTransaction ) useTransaction = 0; if( neverUseTransaction ) useTransaction = 0;
if( useTransaction ) fprintf(out, "BEGIN TRANSACTION;\n"); if( useTransaction ) Wfprintf(out, "BEGIN TRANSACTION;\n");
if( xDiff==rbudiff_one_table ){ if( xDiff==rbudiff_one_table ){
fprintf(out, "CREATE TABLE IF NOT EXISTS rbu_count" Wfprintf(out, "CREATE TABLE IF NOT EXISTS rbu_count"
"(tbl TEXT PRIMARY KEY COLLATE NOCASE, cnt INTEGER) " "(tbl TEXT PRIMARY KEY COLLATE NOCASE, cnt INTEGER) "
"WITHOUT ROWID;\n" "WITHOUT ROWID;\n"
); );
@@ -2066,7 +2046,7 @@ int main(int argc, char **argv){
} }
sqlite3_finalize(pStmt); sqlite3_finalize(pStmt);
} }
if( useTransaction ) printf("COMMIT;\n"); if( useTransaction ) Wfprintf(stdout,"COMMIT;\n");
/* TBD: Handle trigger differences */ /* TBD: Handle trigger differences */
/* TBD: Handle view differences */ /* TBD: Handle view differences */