| | @@ -0,0 +1,1006 @@ |
| 1 | +/*
|
| 2 | +** Copyright (c) 2024 D. Richard Hipp
|
| 3 | +**
|
| 4 | +** This program is free software; you can redistribute it and/or
|
| 5 | +** modify it under the terms of the Simplified BSD License (also
|
| 6 | +** known as the "2-Clause License" or "FreeBSD License".)
|
| 7 | +**
|
| 8 | +** This program is distributed in the hope that it will be useful,
|
| 9 | +** but without any warranty; without even the implied warranty of
|
| 10 | +** merchantability or fitness for a particular purpose.
|
| 11 | +**
|
| 12 | +** Author contact information:
|
| 13 | +** [email protected]
|
| 14 | +** http://www.hwaci.com/drh/
|
| 15 | +**
|
| 16 | +*******************************************************************************
|
| 17 | +**
|
| 18 | +** This file contains code used to compute a "diff" between two SQLite
|
| 19 | +** database files for display by Fossil.
|
| 20 | +**
|
| 21 | +** Fossil normally only computes diffs on text files. But I was inspired
|
| 22 | +** by a Hacker News post to add support for diffs of other kinds of files
|
| 23 | +** as well. The HN post in question is:
|
| 24 | +**
|
| 25 | +** https://news.ycombinator.com/item?id=42141370
|
| 26 | +**
|
| 27 | +** eternityforest | on: On Building Git for Lawyers
|
| 28 | +** I really think Git should just add builtin support for binaries,
|
| 29 | +** and diffing for SQLite and .zip. it's not like it would be all
|
| 30 | +** that much code....
|
| 31 | +**
|
| 32 | +** This file borrows a lot of code from the "sqldiff.c" module of
|
| 33 | +** SQLite. (https://sqlite.org/src/file/tool/sqldiff.c)
|
| 34 | +*/
|
| 35 | +#include "config.h"
|
| 36 | +#include "sqldiff.h"
|
| 37 | +#include <ctype.h>
|
| 38 | +
|
| 39 | +#if INTERFACE
|
| 40 | +/*
|
| 41 | +** Context for an SQL diff
|
| 42 | +*/
|
| 43 | +struct SqlDiffCtx {
|
| 44 | + int bSchemaOnly; /* Only show schema differences */
|
| 45 | + int bSchemaPK; /* Use the schema-defined PK, not the true PK */
|
| 46 | + int bHandleVtab; /* Handle fts3, fts4, fts5 and rtree vtabs */
|
| 47 | + unsigned fDebug; /* Debug flags */
|
| 48 | + int bSchemaCompare; /* Doing single-table sqlite_schema compare */
|
| 49 | + int nErr; /* Number of errors encountered */
|
| 50 | + Blob *out; /* Write the diff output here */
|
| 51 | + sqlite3 *db; /* The database connection */
|
| 52 | +};
|
| 53 | +
|
| 54 | +/*
|
| 55 | +** Allowed values for SqlDiffCtx.fDebug
|
| 56 | +*/
|
| 57 | +#define SQLDIFF_COLUMN_NAMES 0x000001
|
| 58 | +#define SQLDIFF_DIFF_SQL 0x000002
|
| 59 | +#define SQLDIFF_SHOW_ERRORS 0x000004
|
| 60 | +
|
| 61 | +#endif /* INTERFACE */
|
| 62 | +
|
| 63 | +
|
| 64 | +/*
|
| 65 | +** Return true if the input Blob superficially resembles an SQLite
|
| 66 | +** database file.
|
| 67 | +*/
|
| 68 | +static int looks_like_sqlite_db(const Blob *pDb){
|
| 69 | + int sz = blob_size(pDb);
|
| 70 | + const u8 *a = (const u8*)blob_buffer(pDb);
|
| 71 | + static const u8 aSqliteHeader[16] = {
|
| 72 | + 0x53, 0x51, 0x4c, 0x69, 0x74, 0x65, 0x20, 0x66,
|
| 73 | + 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x20, 0x33, 0x00
|
| 74 | + };
|
| 75 | +
|
| 76 | + if( sz<512 ) return 0;
|
| 77 | + if( (sz%512)!=0 ) return 0;
|
| 78 | + if( memcmp(aSqliteHeader,a,16)!=0 ) return 0;
|
| 79 | + return 1;
|
| 80 | +}
|
| 81 | +
|
| 82 | +/*
|
| 83 | +** Clear and free an sqlite3_str object
|
| 84 | +*/
|
| 85 | +static void strFree(sqlite3_str *pStr){
|
| 86 | + sqlite3_free(sqlite3_str_finish(pStr));
|
| 87 | +}
|
| 88 | +
|
| 89 | +/*
|
| 90 | +** Print an error message for an error that occurs at runtime.
|
| 91 | +*/
|
| 92 | +static void sqldiffError(SqlDiffCtx *p, const char *zFormat, ...){
|
| 93 | + if( p->fDebug & SQLDIFF_SHOW_ERRORS ){
|
| 94 | + sqlite3_str *pOut = sqlite3_str_new(0);
|
| 95 | + va_list ap;
|
| 96 | + va_start(ap, zFormat);
|
| 97 | + sqlite3_str_vappendf(pOut, zFormat, ap);
|
| 98 | + va_end(ap);
|
| 99 | + fossil_print("%s\n", sqlite3_str_value(pOut));
|
| 100 | + strFree(pOut);
|
| 101 | + }
|
| 102 | + p->nErr++;
|
| 103 | +}
|
| 104 | +
|
| 105 | +/* Safely quote an SQL identifier. Use the minimum amount of transformation
|
| 106 | +** necessary to allow the string to be used with %s.
|
| 107 | +**
|
| 108 | +** Space to hold the returned string is obtained from sqlite3_malloc(). The
|
| 109 | +** caller is responsible for ensuring this space is freed when no longer
|
| 110 | +** needed.
|
| 111 | +*/
|
| 112 | +static char *safeId(const char *zId){
|
| 113 | + int i, x;
|
| 114 | + char c;
|
| 115 | + if( zId[0]==0 ) return sqlite3_mprintf("\"\"");
|
| 116 | + for(i=x=0; (c = zId[i])!=0; i++){
|
| 117 | + if( !isalpha(c) && c!='_' ){
|
| 118 | + if( i>0 && isdigit(c) ){
|
| 119 | + x++;
|
| 120 | + }else{
|
| 121 | + return sqlite3_mprintf("\"%w\"", zId);
|
| 122 | + }
|
| 123 | + }
|
| 124 | + }
|
| 125 | + if( x || !sqlite3_keyword_check(zId,i) ){
|
| 126 | + return sqlite3_mprintf("%s", zId);
|
| 127 | + }
|
| 128 | + return sqlite3_mprintf("\"%w\"", zId);
|
| 129 | +}
|
| 130 | +
|
| 131 | +/*
|
| 132 | +** Prepare a new SQL statement. Print an error and abort if anything
|
| 133 | +** goes wrong.
|
| 134 | +*/
|
| 135 | +static sqlite3_stmt *sqldiff_vprepare(
|
| 136 | + SqlDiffCtx *p,
|
| 137 | + const char *zFormat,
|
| 138 | + va_list ap
|
| 139 | +){
|
| 140 | + char *zSql;
|
| 141 | + int rc;
|
| 142 | + sqlite3_stmt *pStmt;
|
| 143 | +
|
| 144 | + zSql = sqlite3_vmprintf(zFormat, ap);
|
| 145 | + if( zSql==0 ) fossil_fatal("out of memory\n");
|
| 146 | + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0);
|
| 147 | + if( rc ){
|
| 148 | + sqldiffError(p, "SQL statement error: %s\n\"%s\"", sqlite3_errmsg(p->db),
|
| 149 | + zSql);
|
| 150 | + sqlite3_finalize(pStmt);
|
| 151 | + pStmt = 0;
|
| 152 | + }
|
| 153 | + sqlite3_free(zSql);
|
| 154 | + return pStmt;
|
| 155 | +}
|
| 156 | +static sqlite3_stmt *sqldiff_prepare(SqlDiffCtx *p, const char *zFormat, ...){
|
| 157 | + va_list ap;
|
| 158 | + sqlite3_stmt *pStmt;
|
| 159 | + va_start(ap, zFormat);
|
| 160 | + pStmt = sqldiff_vprepare(p, zFormat, ap);
|
| 161 | + va_end(ap);
|
| 162 | + return pStmt;
|
| 163 | +}
|
| 164 | +
|
| 165 | +/*
|
| 166 | +** Free a list of strings
|
| 167 | +*/
|
| 168 | +static void namelistFree(char **az){
|
| 169 | + if( az ){
|
| 170 | + int i;
|
| 171 | + for(i=0; az[i]; i++) sqlite3_free(az[i]);
|
| 172 | + sqlite3_free(az);
|
| 173 | + }
|
| 174 | +}
|
| 175 | +
|
| 176 | +/*
|
| 177 | +** Return a list of column names [a] for the table zDb.zTab. Space to
|
| 178 | +** hold the list is obtained from sqlite3_malloc() and should released
|
| 179 | +** using namelistFree() when no longer needed.
|
| 180 | +**
|
| 181 | +** Primary key columns are listed first, followed by data columns.
|
| 182 | +** The number of columns in the primary key is returned in *pnPkey.
|
| 183 | +**
|
| 184 | +** Normally [a], the "primary key" in the previous sentence is the true
|
| 185 | +** primary key - the rowid or INTEGER PRIMARY KEY for ordinary tables
|
| 186 | +** or the declared PRIMARY KEY for WITHOUT ROWID tables. However, if
|
| 187 | +** the p->bSchemaPK flag is set, then the schema-defined PRIMARY KEY is
|
| 188 | +** used in all cases. In that case, entries that have NULL values in
|
| 189 | +** any of their primary key fields will be excluded from the analysis.
|
| 190 | +**
|
| 191 | +** If the primary key for a table is the rowid but rowid is inaccessible,
|
| 192 | +** then this routine returns a NULL pointer.
|
| 193 | +**
|
| 194 | +** [a. If the lone, named table is "sqlite_schema", "rootpage" column is
|
| 195 | +** omitted and the "type" and "name" columns are made to be the PK.]
|
| 196 | +**
|
| 197 | +** Examples:
|
| 198 | +** CREATE TABLE t1(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(c));
|
| 199 | +** *pnPKey = 1;
|
| 200 | +** az = { "rowid", "a", "b", "c", 0 } // Normal case
|
| 201 | +** az = { "c", "a", "b", 0 } // g.bSchemaPK==1
|
| 202 | +**
|
| 203 | +** CREATE TABLE t2(a INT UNIQUE, b INTEGER, c TEXT, PRIMARY KEY(b));
|
| 204 | +** *pnPKey = 1;
|
| 205 | +** az = { "b", "a", "c", 0 }
|
| 206 | +**
|
| 207 | +** CREATE TABLE t3(x,y,z,PRIMARY KEY(y,z));
|
| 208 | +** *pnPKey = 1 // Normal case
|
| 209 | +** az = { "rowid", "x", "y", "z", 0 } // Normal case
|
| 210 | +** *pnPKey = 2 // g.bSchemaPK==1
|
| 211 | +** az = { "y", "x", "z", 0 } // g.bSchemaPK==1
|
| 212 | +**
|
| 213 | +** CREATE TABLE t4(x,y,z,PRIMARY KEY(y,z)) WITHOUT ROWID;
|
| 214 | +** *pnPKey = 2
|
| 215 | +** az = { "y", "z", "x", 0 }
|
| 216 | +**
|
| 217 | +** CREATE TABLE t5(rowid,_rowid_,oid);
|
| 218 | +** az = 0 // The rowid is not accessible
|
| 219 | +*/
|
| 220 | +static char **columnNames(
|
| 221 | + SqlDiffCtx *p, /* Diffing context */
|
| 222 | + const char *zDb, /* Database ("aaa" or "bbb") to query */
|
| 223 | + const char *zTab, /* Name of table to return details of */
|
| 224 | + int *pnPKey, /* OUT: Number of PK columns */
|
| 225 | + int *pbRowid /* OUT: True if PK is an implicit rowid */
|
| 226 | +){
|
| 227 | + char **az = 0; /* List of column names to be returned */
|
| 228 | + int naz = 0; /* Number of entries in az[] */
|
| 229 | + sqlite3_stmt *pStmt; /* SQL statement being run */
|
| 230 | + char *zPkIdxName = 0; /* Name of the PRIMARY KEY index */
|
| 231 | + int truePk = 0; /* PRAGMA table_info indentifies the PK to use */
|
| 232 | + int nPK = 0; /* Number of PRIMARY KEY columns */
|
| 233 | + int i, j; /* Loop counters */
|
| 234 | +
|
| 235 | + if( p->bSchemaPK==0 ){
|
| 236 | + /* Normal case: Figure out what the true primary key is for the table.
|
| 237 | + ** * For WITHOUT ROWID tables, the true primary key is the same as
|
| 238 | + ** the schema PRIMARY KEY, which is guaranteed to be present.
|
| 239 | + ** * For rowid tables with an INTEGER PRIMARY KEY, the true primary
|
| 240 | + ** key is the INTEGER PRIMARY KEY.
|
| 241 | + ** * For all other rowid tables, the rowid is the true primary key.
|
| 242 | + */
|
| 243 | + pStmt = sqldiff_prepare(p, "PRAGMA %s.index_list=%Q", zDb, zTab);
|
| 244 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 245 | + if( sqlite3_stricmp((const char*)sqlite3_column_text(pStmt,3),"pk")==0 ){
|
| 246 | + zPkIdxName = sqlite3_mprintf("%s", sqlite3_column_text(pStmt, 1));
|
| 247 | + break;
|
| 248 | + }
|
| 249 | + }
|
| 250 | + sqlite3_finalize(pStmt);
|
| 251 | + if( zPkIdxName ){
|
| 252 | + int nKey = 0;
|
| 253 | + int nCol = 0;
|
| 254 | + truePk = 0;
|
| 255 | + pStmt = sqldiff_prepare(p, "PRAGMA %s.index_xinfo=%Q", zDb, zPkIdxName);
|
| 256 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 257 | + nCol++;
|
| 258 | + if( sqlite3_column_int(pStmt,5) ){ nKey++; continue; }
|
| 259 | + if( sqlite3_column_int(pStmt,1)>=0 ) truePk = 1;
|
| 260 | + }
|
| 261 | + if( nCol==nKey ) truePk = 1;
|
| 262 | + if( truePk ){
|
| 263 | + nPK = nKey;
|
| 264 | + }else{
|
| 265 | + nPK = 1;
|
| 266 | + }
|
| 267 | + sqlite3_finalize(pStmt);
|
| 268 | + sqlite3_free(zPkIdxName);
|
| 269 | + }else{
|
| 270 | + truePk = 1;
|
| 271 | + nPK = 1;
|
| 272 | + }
|
| 273 | + pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
|
| 274 | + }else{
|
| 275 | + /* The p->bSchemaPK==1 case: Use whatever primary key is declared
|
| 276 | + ** in the schema. The "rowid" will still be used as the primary key
|
| 277 | + ** if the table definition does not contain a PRIMARY KEY.
|
| 278 | + */
|
| 279 | + nPK = 0;
|
| 280 | + pStmt = sqldiff_prepare(p, "PRAGMA %s.table_info=%Q", zDb, zTab);
|
| 281 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 282 | + if( sqlite3_column_int(pStmt,5)>0 ) nPK++;
|
| 283 | + }
|
| 284 | + sqlite3_reset(pStmt);
|
| 285 | + if( nPK==0 ) nPK = 1;
|
| 286 | + truePk = 1;
|
| 287 | + }
|
| 288 | + if( p->bSchemaCompare ){
|
| 289 | + assert( sqlite3_stricmp(zTab,"sqlite_schema")==0
|
| 290 | + || sqlite3_stricmp(zTab,"sqlite_master")==0 );
|
| 291 | + /* For sqlite_schema, will use type and name as the PK. */
|
| 292 | + nPK = 2;
|
| 293 | + truePk = 0;
|
| 294 | + }
|
| 295 | + *pnPKey = nPK;
|
| 296 | + naz = nPK;
|
| 297 | + az = sqlite3_malloc( sizeof(char*)*(nPK+1) );
|
| 298 | + if( az==0 ) fossil_fatal("out of memory\n");
|
| 299 | + memset(az, 0, sizeof(char*)*(nPK+1));
|
| 300 | + if( p->bSchemaCompare ){
|
| 301 | + az[0] = sqlite3_mprintf("%s", "type");
|
| 302 | + az[1] = sqlite3_mprintf("%s", "name");
|
| 303 | + }
|
| 304 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 305 | + char * sid = safeId((char*)sqlite3_column_text(pStmt,1));
|
| 306 | + int iPKey;
|
| 307 | + if( truePk && (iPKey = sqlite3_column_int(pStmt,5))>0 ){
|
| 308 | + az[iPKey-1] = sid;
|
| 309 | + }else{
|
| 310 | + if( !p->bSchemaCompare
|
| 311 | + || !(strcmp(sid,"rootpage")==0
|
| 312 | + ||strcmp(sid,"name")==0
|
| 313 | + ||strcmp(sid,"type")==0)){
|
| 314 | + az = sqlite3_realloc(az, sizeof(char*)*(naz+2) );
|
| 315 | + if( az==0 ) fossil_fatal("out of memory\n");
|
| 316 | + az[naz++] = sid;
|
| 317 | + }
|
| 318 | + }
|
| 319 | + }
|
| 320 | + sqlite3_finalize(pStmt);
|
| 321 | + if( az ) az[naz] = 0;
|
| 322 | +
|
| 323 | + /* If it is non-NULL, set *pbRowid to indicate whether or not the PK of
|
| 324 | + ** this table is an implicit rowid (*pbRowid==1) or not (*pbRowid==0). */
|
| 325 | + if( pbRowid ) *pbRowid = (az[0]==0);
|
| 326 | +
|
| 327 | + /* If this table has an implicit rowid for a PK, figure out how to refer
|
| 328 | + ** to it. There are usually three options - "rowid", "_rowid_" and "oid".
|
| 329 | + ** Any of these will work, unless the table has an explicit column of the
|
| 330 | + ** same name or the sqlite_schema tables are to be compared. In the latter
|
| 331 | + ** case, pretend that the "true" primary key is the name column, which
|
| 332 | + ** avoids extraneous diffs against the schemas due to rowid variance. */
|
| 333 | + if( az[0]==0 ){
|
| 334 | + const char *azRowid[] = { "rowid", "_rowid_", "oid" };
|
| 335 | + for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
|
| 336 | + for(j=1; j<naz; j++){
|
| 337 | + if( sqlite3_stricmp(az[j], azRowid[i])==0 ) break;
|
| 338 | + }
|
| 339 | + if( j>=naz ){
|
| 340 | + az[0] = sqlite3_mprintf("%s", azRowid[i]);
|
| 341 | + break;
|
| 342 | + }
|
| 343 | + }
|
| 344 | + if( az[0]==0 ){
|
| 345 | + for(i=1; i<naz; i++) sqlite3_free(az[i]);
|
| 346 | + sqlite3_free(az);
|
| 347 | + az = 0;
|
| 348 | + }
|
| 349 | + }
|
| 350 | + return az;
|
| 351 | +}
|
| 352 | +
|
| 353 | +/*
|
| 354 | +** Print the sqlite3_value X as an SQL literal.
|
| 355 | +*/
|
| 356 | +static void printQuoted(Blob *out, sqlite3_value *X){
|
| 357 | + switch( sqlite3_value_type(X) ){
|
| 358 | + case SQLITE_FLOAT: {
|
| 359 | + double r1;
|
| 360 | + char zBuf[50];
|
| 361 | + r1 = sqlite3_value_double(X);
|
| 362 | + sqlite3_snprintf(sizeof(zBuf), zBuf, "%!.15g", r1);
|
| 363 | + blob_appendf(out, "%s", zBuf);
|
| 364 | + break;
|
| 365 | + }
|
| 366 | + case SQLITE_INTEGER: {
|
| 367 | + blob_appendf(out, "%lld", sqlite3_value_int64(X));
|
| 368 | + break;
|
| 369 | + }
|
| 370 | + case SQLITE_BLOB: {
|
| 371 | + const unsigned char *zBlob = sqlite3_value_blob(X);
|
| 372 | + int nBlob = sqlite3_value_bytes(X);
|
| 373 | + if( zBlob ){
|
| 374 | + int i;
|
| 375 | + blob_appendf(out, "x'");
|
| 376 | + for(i=0; i<nBlob; i++){
|
| 377 | + blob_appendf(out, "%02x", zBlob[i]);
|
| 378 | + }
|
| 379 | + blob_appendf(out, "'");
|
| 380 | + }else{
|
| 381 | + /* Could be an OOM, could be a zero-byte blob */
|
| 382 | + blob_appendf(out, "X''");
|
| 383 | + }
|
| 384 | + break;
|
| 385 | + }
|
| 386 | + case SQLITE_TEXT: {
|
| 387 | + const unsigned char *zArg = sqlite3_value_text(X);
|
| 388 | +
|
| 389 | + if( zArg==0 ){
|
| 390 | + blob_appendf(out, "NULL");
|
| 391 | + }else{
|
| 392 | + int inctl = 0;
|
| 393 | + int i, j;
|
| 394 | + blob_appendf(out, "'");
|
| 395 | + for(i=j=0; zArg[i]; i++){
|
| 396 | + char c = zArg[i];
|
| 397 | + int ctl = iscntrl((unsigned char)c);
|
| 398 | + if( ctl>inctl ){
|
| 399 | + inctl = ctl;
|
| 400 | + blob_appendf(out, "%.*s'||X'%02x", i-j, &zArg[j], c);
|
| 401 | + j = i+1;
|
| 402 | + }else if( ctl ){
|
| 403 | + blob_appendf(out, "%02x", c);
|
| 404 | + j = i+1;
|
| 405 | + }else{
|
| 406 | + if( inctl ){
|
| 407 | + inctl = 0;
|
| 408 | + blob_appendf(out, "'\n||'");
|
| 409 | + }
|
| 410 | + if( c=='\'' ){
|
| 411 | + blob_appendf(out, "%.*s'", i-j+1, &zArg[j]);
|
| 412 | + j = i+1;
|
| 413 | + }
|
| 414 | + }
|
| 415 | + }
|
| 416 | + blob_appendf(out, "%s'", &zArg[j]);
|
| 417 | + }
|
| 418 | + break;
|
| 419 | + }
|
| 420 | + case SQLITE_NULL: {
|
| 421 | + blob_appendf(out, "NULL");
|
| 422 | + break;
|
| 423 | + }
|
| 424 | + }
|
| 425 | +}
|
| 426 | +
|
| 427 | +/*
|
| 428 | +** Output SQL that will recreate the bbb.zTab table.
|
| 429 | +*/
|
| 430 | +static void dump_table(SqlDiffCtx *p, const char *zTab){
|
| 431 | + char *zId = safeId(zTab); /* Name of the table */
|
| 432 | + char **az = 0; /* List of columns */
|
| 433 | + int nPk; /* Number of true primary key columns */
|
| 434 | + int nCol; /* Number of data columns */
|
| 435 | + int i; /* Loop counter */
|
| 436 | + sqlite3_stmt *pStmt; /* SQL statement */
|
| 437 | + const char *zSep; /* Separator string */
|
| 438 | + sqlite3_str *pIns; /* Beginning of the INSERT statement */
|
| 439 | +
|
| 440 | + pStmt = sqldiff_prepare(p,
|
| 441 | + "SELECT sql FROM bbb.sqlite_schema WHERE name=%Q", zTab);
|
| 442 | + if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 443 | + blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
|
| 444 | + }
|
| 445 | + sqlite3_finalize(pStmt);
|
| 446 | + if( !p->bSchemaOnly ){
|
| 447 | + az = columnNames(p, "bbb", zTab, &nPk, 0);
|
| 448 | + pIns = sqlite3_str_new(0);
|
| 449 | + if( az==0 ){
|
| 450 | + pStmt = sqldiff_prepare(p, "SELECT * FROM bbb.%s", zId);
|
| 451 | + sqlite3_str_appendf(pIns,"INSERT INTO %s VALUES", zId);
|
| 452 | + }else{
|
| 453 | + sqlite3_str *pSql = sqlite3_str_new(0);
|
| 454 | + zSep = "SELECT";
|
| 455 | + for(i=0; az[i]; i++){
|
| 456 | + sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
|
| 457 | + zSep = ",";
|
| 458 | + }
|
| 459 | + sqlite3_str_appendf(pSql," FROM bbb.%s", zId);
|
| 460 | + zSep = " ORDER BY";
|
| 461 | + for(i=1; i<=nPk; i++){
|
| 462 | + sqlite3_str_appendf(pSql, "%s %d", zSep, i);
|
| 463 | + zSep = ",";
|
| 464 | + }
|
| 465 | + pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
|
| 466 | + strFree(pSql);
|
| 467 | + sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
|
| 468 | + zSep = "(";
|
| 469 | + for(i=0; az[i]; i++){
|
| 470 | + sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
|
| 471 | + zSep = ",";
|
| 472 | + }
|
| 473 | + sqlite3_str_appendf(pIns,") VALUES");
|
| 474 | + namelistFree(az);
|
| 475 | + }
|
| 476 | + nCol = sqlite3_column_count(pStmt);
|
| 477 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 478 | + blob_appendf(p->out, "%s",sqlite3_str_value(pIns));
|
| 479 | + zSep = "(";
|
| 480 | + for(i=0; i<nCol; i++){
|
| 481 | + blob_appendf(p->out, "%s",zSep);
|
| 482 | + printQuoted(p->out, sqlite3_column_value(pStmt,i));
|
| 483 | + zSep = ",";
|
| 484 | + }
|
| 485 | + blob_appendf(p->out, ");\n");
|
| 486 | + }
|
| 487 | + sqlite3_finalize(pStmt);
|
| 488 | + strFree(pIns);
|
| 489 | + } /* endif !p->bSchemaOnly */
|
| 490 | + pStmt = sqldiff_prepare(p, "SELECT sql FROM bbb.sqlite_schema"
|
| 491 | + " WHERE type='index' AND tbl_name=%Q AND sql IS NOT NULL",
|
| 492 | + zTab);
|
| 493 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 494 | + blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
|
| 495 | + }
|
| 496 | + sqlite3_finalize(pStmt);
|
| 497 | + sqlite3_free(zId);
|
| 498 | +}
|
| 499 | +
|
| 500 | +
|
| 501 | +/*
|
| 502 | +** Compute all differences for a single table, except if the
|
| 503 | +** table name is sqlite_schema, ignore the rootpage column.
|
| 504 | +*/
|
| 505 | +static void diff_one_table(SqlDiffCtx *p, const char *zTab){
|
| 506 | + char *zId = safeId(zTab); /* Name of table (translated for us in SQL) */
|
| 507 | + char **az = 0; /* Columns in aaa */
|
| 508 | + char **az2 = 0; /* Columns in bbb */
|
| 509 | + int nPk; /* Primary key columns in aaa */
|
| 510 | + int nPk2; /* Primary key columns in bbb */
|
| 511 | + int n = 0; /* Number of columns in aaa */
|
| 512 | + int n2; /* Number of columns in bbb */
|
| 513 | + int nQ; /* Number of output columns in the diff query */
|
| 514 | + int i; /* Loop counter */
|
| 515 | + const char *zSep; /* Separator string */
|
| 516 | + sqlite3_str *pSql; /* Comparison query */
|
| 517 | + sqlite3_stmt *pStmt; /* Query statement to do the diff */
|
| 518 | + const char *zLead = /* Becomes line-comment for sqlite_schema */
|
| 519 | + (p->bSchemaCompare)? "-- " : "";
|
| 520 | +
|
| 521 | + pSql = sqlite3_str_new(0);
|
| 522 | + if( p->fDebug==SQLDIFF_COLUMN_NAMES ){
|
| 523 | + /* Simply run columnNames() on all tables of the origin
|
| 524 | + ** database and show the results. This is used for testing
|
| 525 | + ** and debugging of the columnNames() function.
|
| 526 | + */
|
| 527 | + az = columnNames(p, "bbb",zTab, &nPk, 0);
|
| 528 | + if( az==0 ){
|
| 529 | + fossil_print("Rowid not accessible for %s\n", zId);
|
| 530 | + }else{
|
| 531 | + fossil_print("%s:", zId);
|
| 532 | + for(i=0; az[i]; i++){
|
| 533 | + fossil_print(" %s", az[i]);
|
| 534 | + if( i+1==nPk ) fossil_print(" *");
|
| 535 | + }
|
| 536 | + fossil_print("\n");
|
| 537 | + }
|
| 538 | + goto end_diff_one_table;
|
| 539 | + }
|
| 540 | +
|
| 541 | + if( sqlite3_table_column_metadata(p->db,"bbb",zTab,0,0,0,0,0,0) ){
|
| 542 | + if( !sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
|
| 543 | + /* Table missing from second database. */
|
| 544 | + if( p->bSchemaCompare ){
|
| 545 | + blob_appendf(p->out, "-- 2nd DB has no %s table\n", zTab);
|
| 546 | + }else{
|
| 547 | + blob_appendf(p->out, "DROP TABLE %s;\n", zId);
|
| 548 | + }
|
| 549 | + }
|
| 550 | + goto end_diff_one_table;
|
| 551 | + }
|
| 552 | +
|
| 553 | + if( sqlite3_table_column_metadata(p->db,"aaa",zTab,0,0,0,0,0,0) ){
|
| 554 | + /* Table missing from source */
|
| 555 | + if( p->bSchemaCompare ){
|
| 556 | + blob_appendf(p->out, "-- 1st DB has no %s table\n", zTab);
|
| 557 | + }else{
|
| 558 | + dump_table(p, zTab);
|
| 559 | + }
|
| 560 | + goto end_diff_one_table;
|
| 561 | + }
|
| 562 | +
|
| 563 | + az = columnNames(p, "aaa", zTab, &nPk, 0);
|
| 564 | + az2 = columnNames(p, "bbb", zTab, &nPk2, 0);
|
| 565 | + if( az && az2 ){
|
| 566 | + for(n=0; az[n] && az2[n]; n++){
|
| 567 | + if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
|
| 568 | + }
|
| 569 | + }
|
| 570 | + if( az==0
|
| 571 | + || az2==0
|
| 572 | + || nPk!=nPk2
|
| 573 | + || az[n]
|
| 574 | + ){
|
| 575 | + /* Schema mismatch */
|
| 576 | + blob_appendf(p->out, "%sDROP TABLE %s; -- due to schema mismatch\n",
|
| 577 | + zLead, zId);
|
| 578 | + dump_table(p, zTab);
|
| 579 | + goto end_diff_one_table;
|
| 580 | + }
|
| 581 | +
|
| 582 | + /* Build the comparison query */
|
| 583 | + for(n2=n; az2[n2]; n2++){
|
| 584 | + char *zNTab = safeId(az2[n2]);
|
| 585 | + blob_appendf(p->out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
|
| 586 | + sqlite3_free(zNTab);
|
| 587 | + }
|
| 588 | + nQ = nPk2+1+2*(n2-nPk2);
|
| 589 | + if( n2>nPk2 ){
|
| 590 | + zSep = "SELECT ";
|
| 591 | + for(i=0; i<nPk; i++){
|
| 592 | + sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
|
| 593 | + zSep = ", ";
|
| 594 | + }
|
| 595 | + sqlite3_str_appendf(pSql, ", 1 /* changed row */");
|
| 596 | + while( az[i] ){
|
| 597 | + sqlite3_str_appendf(pSql, ", A.%s IS NOT B.%s, B.%s",
|
| 598 | + az[i], az2[i], az2[i]);
|
| 599 | + i++;
|
| 600 | + }
|
| 601 | + while( az2[i] ){
|
| 602 | + sqlite3_str_appendf(pSql, ", B.%s IS NOT NULL, B.%s",
|
| 603 | + az2[i], az2[i]);
|
| 604 | + i++;
|
| 605 | + }
|
| 606 | + sqlite3_str_appendf(pSql, "\n FROM aaa.%s A, bbb.%s B\n", zId, zId);
|
| 607 | + zSep = " WHERE";
|
| 608 | + for(i=0; i<nPk; i++){
|
| 609 | + sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
|
| 610 | + zSep = " AND";
|
| 611 | + }
|
| 612 | + zSep = "\n AND (";
|
| 613 | + while( az[i] ){
|
| 614 | + sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s%s\n",
|
| 615 | + zSep, az[i], az2[i], az2[i+1]==0 ? ")" : "");
|
| 616 | + zSep = " OR ";
|
| 617 | + i++;
|
| 618 | + }
|
| 619 | + while( az2[i] ){
|
| 620 | + sqlite3_str_appendf(pSql, "%sB.%s IS NOT NULL%s\n",
|
| 621 | + zSep, az2[i], az2[i+1]==0 ? ")" : "");
|
| 622 | + zSep = " OR ";
|
| 623 | + i++;
|
| 624 | + }
|
| 625 | + sqlite3_str_appendf(pSql, " UNION ALL\n");
|
| 626 | + }
|
| 627 | + zSep = "SELECT ";
|
| 628 | + for(i=0; i<nPk; i++){
|
| 629 | + sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
|
| 630 | + zSep = ", ";
|
| 631 | + }
|
| 632 | + sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
|
| 633 | + while( az2[i] ){
|
| 634 | + sqlite3_str_appendf(pSql, ", NULL, NULL");
|
| 635 | + i++;
|
| 636 | + }
|
| 637 | + sqlite3_str_appendf(pSql, "\n FROM aaa.%s A\n", zId);
|
| 638 | + sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM bbb.%s B\n", zId);
|
| 639 | + zSep = " WHERE";
|
| 640 | + for(i=0; i<nPk; i++){
|
| 641 | + sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
|
| 642 | + zSep = " AND";
|
| 643 | + }
|
| 644 | + sqlite3_str_appendf(pSql, ")\n");
|
| 645 | + zSep = " UNION ALL\nSELECT ";
|
| 646 | + for(i=0; i<nPk; i++){
|
| 647 | + sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
|
| 648 | + zSep = ", ";
|
| 649 | + }
|
| 650 | + sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
|
| 651 | + while( az2[i] ){
|
| 652 | + sqlite3_str_appendf(pSql, ", 1, B.%s", az2[i]);
|
| 653 | + i++;
|
| 654 | + }
|
| 655 | + sqlite3_str_appendf(pSql, "\n FROM bbb.%s B\n", zId);
|
| 656 | + sqlite3_str_appendf(pSql, " WHERE NOT EXISTS(SELECT 1 FROM aaa.%s A\n", zId);
|
| 657 | + zSep = " WHERE";
|
| 658 | + for(i=0; i<nPk; i++){
|
| 659 | + sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
|
| 660 | + zSep = " AND";
|
| 661 | + }
|
| 662 | + sqlite3_str_appendf(pSql, ")\n ORDER BY");
|
| 663 | + zSep = " ";
|
| 664 | + for(i=1; i<=nPk; i++){
|
| 665 | + sqlite3_str_appendf(pSql, "%s%d", zSep, i);
|
| 666 | + zSep = ", ";
|
| 667 | + }
|
| 668 | + sqlite3_str_appendf(pSql, ";\n");
|
| 669 | +
|
| 670 | + if( p->fDebug & SQLDIFF_DIFF_SQL ){
|
| 671 | + fossil_print("SQL for %s:\n%s\n", zId, sqlite3_str_value(pSql));
|
| 672 | + goto end_diff_one_table;
|
| 673 | + }
|
| 674 | +
|
| 675 | + /* Drop indexes that are missing in the destination */
|
| 676 | + pStmt = sqldiff_prepare(p,
|
| 677 | + "SELECT name FROM aaa.sqlite_schema"
|
| 678 | + " WHERE type='index' AND tbl_name=%Q"
|
| 679 | + " AND sql IS NOT NULL"
|
| 680 | + " AND sql NOT IN (SELECT sql FROM bbb.sqlite_schema"
|
| 681 | + " WHERE type='index' AND tbl_name=%Q"
|
| 682 | + " AND sql IS NOT NULL)",
|
| 683 | + zTab, zTab);
|
| 684 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 685 | + char *z = safeId((const char*)sqlite3_column_text(pStmt,0));
|
| 686 | + blob_appendf(p->out, "DROP INDEX %s;\n", z);
|
| 687 | + sqlite3_free(z);
|
| 688 | + }
|
| 689 | + sqlite3_finalize(pStmt);
|
| 690 | +
|
| 691 | + /* Run the query and output differences */
|
| 692 | + if( !p->bSchemaOnly ){
|
| 693 | + pStmt = sqldiff_prepare(p, "%s", sqlite3_str_value(pSql));
|
| 694 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 695 | + int iType = sqlite3_column_int(pStmt, nPk);
|
| 696 | + if( iType==1 || iType==2 ){
|
| 697 | + if( iType==1 ){ /* Change the content of a row */
|
| 698 | + blob_appendf(p->out, "%sUPDATE %s", zLead, zId);
|
| 699 | + zSep = " SET";
|
| 700 | + for(i=nPk+1; i<nQ; i+=2){
|
| 701 | + if( sqlite3_column_int(pStmt,i)==0 ) continue;
|
| 702 | + blob_appendf(p->out, "%s %s=", zSep, az2[(i+nPk-1)/2]);
|
| 703 | + zSep = ",";
|
| 704 | + printQuoted(p->out, sqlite3_column_value(pStmt,i+1));
|
| 705 | + }
|
| 706 | + }else{ /* Delete a row */
|
| 707 | + blob_appendf(p->out, "%sDELETE FROM %s", zLead, zId);
|
| 708 | + }
|
| 709 | + zSep = " WHERE";
|
| 710 | + for(i=0; i<nPk; i++){
|
| 711 | + blob_appendf(p->out, "%s %s=", zSep, az2[i]);
|
| 712 | + printQuoted(p->out, sqlite3_column_value(pStmt,i));
|
| 713 | + zSep = " AND";
|
| 714 | + }
|
| 715 | + blob_appendf(p->out, ";\n");
|
| 716 | + }else{ /* Insert a row */
|
| 717 | + blob_appendf(p->out, "%sINSERT INTO %s(%s", zLead, zId, az2[0]);
|
| 718 | + for(i=1; az2[i]; i++) blob_appendf(p->out, ",%s", az2[i]);
|
| 719 | + blob_appendf(p->out, ") VALUES");
|
| 720 | + zSep = "(";
|
| 721 | + for(i=0; i<nPk2; i++){
|
| 722 | + blob_appendf(p->out, "%s", zSep);
|
| 723 | + zSep = ",";
|
| 724 | + printQuoted(p->out, sqlite3_column_value(pStmt,i));
|
| 725 | + }
|
| 726 | + for(i=nPk2+2; i<nQ; i+=2){
|
| 727 | + blob_appendf(p->out, ",");
|
| 728 | + printQuoted(p->out, sqlite3_column_value(pStmt,i));
|
| 729 | + }
|
| 730 | + blob_appendf(p->out, ");\n");
|
| 731 | + }
|
| 732 | + }
|
| 733 | + sqlite3_finalize(pStmt);
|
| 734 | + } /* endif !p->bSchemaOnly */
|
| 735 | +
|
| 736 | + /* Create indexes that are missing in the source */
|
| 737 | + pStmt = sqldiff_prepare(p,
|
| 738 | + "SELECT sql FROM bbb.sqlite_schema"
|
| 739 | + " WHERE type='index' AND tbl_name=%Q"
|
| 740 | + " AND sql IS NOT NULL"
|
| 741 | + " AND sql NOT IN (SELECT sql FROM aaa.sqlite_schema"
|
| 742 | + " WHERE type='index' AND tbl_name=%Q"
|
| 743 | + " AND sql IS NOT NULL)",
|
| 744 | + zTab, zTab);
|
| 745 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 746 | + blob_appendf(p->out, "%s;\n", sqlite3_column_text(pStmt,0));
|
| 747 | + }
|
| 748 | + sqlite3_finalize(pStmt);
|
| 749 | +
|
| 750 | +end_diff_one_table:
|
| 751 | + strFree(pSql);
|
| 752 | + sqlite3_free(zId);
|
| 753 | + namelistFree(az);
|
| 754 | + namelistFree(az2);
|
| 755 | + return;
|
| 756 | +}
|
| 757 | +
|
| 758 | +#if 0
|
| 759 | +/*
|
| 760 | +** Check that table zTab exists and has the same schema in both the "aaa"
|
| 761 | +** and "bbb" databases currently opened by the global db handle. If they
|
| 762 | +** do not, output an error message on stderr and exit(1). Otherwise, if
|
| 763 | +** the schemas do match, return control to the caller.
|
| 764 | +*/
|
| 765 | +static void checkSchemasMatch(SqlDiffCtx *p, const char *zTab){
|
| 766 | + sqlite3_stmt *pStmt = sqldiff_prepare(p,
|
| 767 | + "SELECT A.sql=B.sql FROM aaa.sqlite_schema A, bbb.sqlite_schema B"
|
| 768 | + " WHERE A.name=%Q AND B.name=%Q", zTab, zTab
|
| 769 | + );
|
| 770 | + if( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 771 | + if( sqlite3_column_int(pStmt,0)==0 ){
|
| 772 | + sqldiffError(p, "schema changes for table %s", safeId(zTab));
|
| 773 | + }
|
| 774 | + }else{
|
| 775 | + sqldiffError(p, "table %s missing from one or both databases",safeId(zTab));
|
| 776 | + }
|
| 777 | + sqlite3_finalize(pStmt);
|
| 778 | +}
|
| 779 | +#endif
|
| 780 | +
|
| 781 | +/*
|
| 782 | +** Return true if the ascii character passed as the only argument is a
|
| 783 | +** whitespace character. Otherwise return false.
|
| 784 | +*/
|
| 785 | +static int is_whitespace(char x){
|
| 786 | + return (x==' ' || x=='\t' || x=='\n' || x=='\r');
|
| 787 | +}
|
| 788 | +
|
| 789 | +/*
|
| 790 | +** Extract the next SQL keyword or quoted string from buffer zIn and copy it
|
| 791 | +** (or a prefix of it if it will not fit) into buffer zBuf, size nBuf bytes.
|
| 792 | +** Return a pointer to the character within zIn immediately following
|
| 793 | +** the token or quoted string just extracted.
|
| 794 | +*/
|
| 795 | +static const char *gobble_token(const char *zIn, char *zBuf, int nBuf){
|
| 796 | + const char *p = zIn;
|
| 797 | + char *pOut = zBuf;
|
| 798 | + char *pEnd = &pOut[nBuf-1];
|
| 799 | + char q = 0; /* quote character, if any */
|
| 800 | +
|
| 801 | + if( p==0 ) return 0;
|
| 802 | + while( is_whitespace(*p) ) p++;
|
| 803 | + switch( *p ){
|
| 804 | + case '"': q = '"'; break;
|
| 805 | + case '\'': q = '\''; break;
|
| 806 | + case '`': q = '`'; break;
|
| 807 | + case '[': q = ']'; break;
|
| 808 | + }
|
| 809 | +
|
| 810 | + if( q ){
|
| 811 | + p++;
|
| 812 | + while( *p && pOut<pEnd ){
|
| 813 | + if( *p==q ){
|
| 814 | + p++;
|
| 815 | + if( *p!=q ) break;
|
| 816 | + }
|
| 817 | + if( pOut<pEnd ) *pOut++ = *p;
|
| 818 | + p++;
|
| 819 | + }
|
| 820 | + }else{
|
| 821 | + while( *p && !is_whitespace(*p) && *p!='(' ){
|
| 822 | + if( pOut<pEnd ) *pOut++ = *p;
|
| 823 | + p++;
|
| 824 | + }
|
| 825 | + }
|
| 826 | +
|
| 827 | + *pOut = '\0';
|
| 828 | + return p;
|
| 829 | +}
|
| 830 | +
|
| 831 | +/*
|
| 832 | +** This function is the implementation of SQL scalar function "module_name":
|
| 833 | +**
|
| 834 | +** module_name(SQL)
|
| 835 | +**
|
| 836 | +** The only argument should be an SQL statement of the type that may appear
|
| 837 | +** in the sqlite_schema table. If the statement is a "CREATE VIRTUAL TABLE"
|
| 838 | +** statement, then the value returned is the name of the module that it
|
| 839 | +** uses. Otherwise, if the statement is not a CVT, NULL is returned.
|
| 840 | +*/
|
| 841 | +static void module_name_func(
|
| 842 | + sqlite3_context *pCtx,
|
| 843 | + int nVal, sqlite3_value **apVal
|
| 844 | +){
|
| 845 | + const char *zSql;
|
| 846 | + char zToken[32];
|
| 847 | +
|
| 848 | + assert( nVal==1 );
|
| 849 | + zSql = (const char*)sqlite3_value_text(apVal[0]);
|
| 850 | +
|
| 851 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 852 | + if( zSql==0 || sqlite3_stricmp(zToken, "create") ) return;
|
| 853 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 854 | + if( zSql==0 || sqlite3_stricmp(zToken, "virtual") ) return;
|
| 855 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 856 | + if( zSql==0 || sqlite3_stricmp(zToken, "table") ) return;
|
| 857 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 858 | + if( zSql==0 ) return;
|
| 859 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 860 | + if( zSql==0 || sqlite3_stricmp(zToken, "using") ) return;
|
| 861 | + zSql = gobble_token(zSql, zToken, sizeof(zToken));
|
| 862 | +
|
| 863 | + sqlite3_result_text(pCtx, zToken, -1, SQLITE_TRANSIENT);
|
| 864 | +}
|
| 865 | +
|
| 866 | +/*
|
| 867 | +** Return the text of an SQL statement that itself returns the list of
|
| 868 | +** tables to process within the database.
|
| 869 | +*/
|
| 870 | +const char *all_tables_sql(SqlDiffCtx *p){
|
| 871 | + if( p->bHandleVtab ){
|
| 872 | + int rc;
|
| 873 | +
|
| 874 | + rc = sqlite3_exec(p->db,
|
| 875 | + "CREATE TEMP TABLE tblmap(module COLLATE nocase, postfix);"
|
| 876 | + "INSERT INTO temp.tblmap VALUES"
|
| 877 | + "('fts3', '_content'), ('fts3', '_segments'), ('fts3', '_segdir'),"
|
| 878 | +
|
| 879 | + "('fts4', '_content'), ('fts4', '_segments'), ('fts4', '_segdir'),"
|
| 880 | + "('fts4', '_docsize'), ('fts4', '_stat'),"
|
| 881 | +
|
| 882 | + "('fts5', '_data'), ('fts5', '_idx'), ('fts5', '_content'),"
|
| 883 | + "('fts5', '_docsize'), ('fts5', '_config'),"
|
| 884 | +
|
| 885 | + "('rtree', '_node'), ('rtree', '_rowid'), ('rtree', '_parent');"
|
| 886 | + , 0, 0, 0
|
| 887 | + );
|
| 888 | + assert( rc==SQLITE_OK );
|
| 889 | +
|
| 890 | + rc = sqlite3_create_function(
|
| 891 | + p->db, "module_name", 1, SQLITE_UTF8, 0, module_name_func, 0, 0
|
| 892 | + );
|
| 893 | + assert( rc==SQLITE_OK );
|
| 894 | +
|
| 895 | + return
|
| 896 | + "SELECT name FROM aaa.sqlite_schema\n"
|
| 897 | + " WHERE type='table' AND (\n"
|
| 898 | + " module_name(sql) IS NULL OR \n"
|
| 899 | + " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
|
| 900 | + " ) AND name NOT IN (\n"
|
| 901 | + " SELECT a.name || b.postfix \n"
|
| 902 | + "FROM aaa.sqlite_schema AS a, temp.tblmap AS b \n"
|
| 903 | + "WHERE module_name(a.sql) = b.module\n"
|
| 904 | + " )\n"
|
| 905 | + "UNION \n"
|
| 906 | + "SELECT name FROM bbb.sqlite_schema\n"
|
| 907 | + " WHERE type='table' AND (\n"
|
| 908 | + " module_name(sql) IS NULL OR \n"
|
| 909 | + " module_name(sql) IN (SELECT module FROM temp.tblmap)\n"
|
| 910 | + " ) AND name NOT IN (\n"
|
| 911 | + " SELECT a.name || b.postfix \n"
|
| 912 | + "FROM bbb.sqlite_schema AS a, temp.tblmap AS b \n"
|
| 913 | + "WHERE module_name(a.sql) = b.module\n"
|
| 914 | + " )\n"
|
| 915 | + " ORDER BY name";
|
| 916 | + }else{
|
| 917 | + return
|
| 918 | + "SELECT name FROM aaa.sqlite_schema\n"
|
| 919 | + " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
|
| 920 | + " UNION\n"
|
| 921 | + "SELECT name FROM bbb.sqlite_schema\n"
|
| 922 | + " WHERE type='table' AND sql NOT LIKE 'CREATE VIRTUAL%%'\n"
|
| 923 | + " ORDER BY name";
|
| 924 | + }
|
| 925 | +}
|
| 926 | +
|
| 927 | +/*
|
| 928 | +** Check to see if the two input blobs, pA and pB, are both
|
| 929 | +** SQLite database files. If they are, then output an SQL diff
|
| 930 | +** into pOut and return true. If either of the inputs is not
|
| 931 | +** a well-formed SQLite database, then return 0.
|
| 932 | +**
|
| 933 | +** A semantic-level diff is computed. In other words, it is the
|
| 934 | +** content of the database that matters. If the databases have
|
| 935 | +** different page sizes or text representations or if the pages
|
| 936 | +** are in a different order, that does not affect the output.
|
| 937 | +** Only content differences are shown.
|
| 938 | +*/
|
| 939 | +int sqldiff(
|
| 940 | + Blob *pA, /* FROM file */
|
| 941 | + Blob *pB, /* TO file */
|
| 942 | + Blob *pOut, /* Write diff here */
|
| 943 | + DiffConfig *pCfg /* Configuration options */
|
| 944 | +){
|
| 945 | + SqlDiffCtx s;
|
| 946 | + sqlite3_stmt *pStmt;
|
| 947 | + int rc;
|
| 948 | + u8 *aA, *aB;
|
| 949 | + int szA, szB;
|
| 950 | + u8 aModeA[2];
|
| 951 | + u8 aModeB[2];
|
| 952 | +
|
| 953 | + if( pOut==0 ) return 0;
|
| 954 | + if( !looks_like_sqlite_db(pA) ) return 0;
|
| 955 | + if( !looks_like_sqlite_db(pB) ) return 0;
|
| 956 | + memset(&s, 0, sizeof(s));
|
| 957 | + s.out = pOut;
|
| 958 | + rc = sqlite3_open(":memory:", &s.db);
|
| 959 | + if( rc ){
|
| 960 | + fossil_fatal("Unable to open an auxiliary in-memory database\n");
|
| 961 | + }
|
| 962 | + rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS aaa;", 0, 0, 0);
|
| 963 | + if( rc ){
|
| 964 | + fossil_fatal("Unable to attach an in-memory database\n");
|
| 965 | + }
|
| 966 | + rc = sqlite3_exec(s.db, "ATTACH ':memory:' AS bbb;", 0, 0, 0);
|
| 967 | + if( rc ){
|
| 968 | + fossil_fatal("Unable to attach an in-memory database\n");
|
| 969 | + }
|
| 970 | + aA = (u8*)blob_buffer(pA);
|
| 971 | + szA = blob_size(pA);
|
| 972 | + memcpy(aModeA, &aA[18], 2);
|
| 973 | + aA[18] = aA[19] = 1;
|
| 974 | + aB = (u8*)blob_buffer(pB);
|
| 975 | + szB = blob_size(pB);
|
| 976 | + memcpy(aModeB, &aB[18], 2);
|
| 977 | + aB[18] = aB[19] = 1;
|
| 978 | + rc = sqlite3_deserialize(s.db, "aaa", aA, szA, szA,
|
| 979 | + SQLITE_DESERIALIZE_READONLY);
|
| 980 | + if( rc ){
|
| 981 | + s.nErr++;
|
| 982 | + goto not_a_valid_diff;
|
| 983 | + }
|
| 984 | + rc = sqlite3_deserialize(s.db, "bbb", aB, szB, szB,
|
| 985 | + SQLITE_DESERIALIZE_READONLY);
|
| 986 | + if( rc ){
|
| 987 | + s.nErr++;
|
| 988 | + goto not_a_valid_diff;
|
| 989 | + }
|
| 990 | + if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "<pre>\n");
|
| 991 | + pStmt = sqldiff_prepare(&s, "%s", all_tables_sql(&s) );
|
| 992 | + if( pStmt ){
|
| 993 | + while( SQLITE_ROW==sqlite3_step(pStmt) ){
|
| 994 | + diff_one_table(&s, (const char*)sqlite3_column_text(pStmt,0));
|
| 995 | + }
|
| 996 | + sqlite3_finalize(pStmt);
|
| 997 | + }
|
| 998 | + if( pCfg->diffFlags & DIFF_HTML ) blob_appendf(pOut, "</pre>\n");
|
| 999 | +
|
| 1000 | +not_a_valid_diff:
|
| 1001 | + sqlite3_close(s.db);
|
| 1002 | + if( s.nErr ) blob_reset(pOut);
|
| 1003 | + memcpy(&aA[18], aModeA, 2);
|
| 1004 | + memcpy(&aB[18], aModeB, 2);
|
| 1005 | + return s.nErr==0;
|
| 1006 | +}
|