1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-30 19:03:16 +03:00

Bring the extra-src branch up to date with the trunk.

FossilOrigin-Name: 12ff5c5c4162951a29b638a5bc6cffa50e057c5a5e8f5e9c627af5f4ab1e4cdb
This commit is contained in:
stephan
2024-02-27 00:58:51 +00:00
416 changed files with 35602 additions and 13757 deletions

11
tool/cktclsh.sh Normal file
View File

@ -0,0 +1,11 @@
# Fail with an error if the TCLSH named in $2 is not tclsh version $1 or later.
#
echo "set vers $1" >cktclsh$1.tcl
echo 'if {$tcl_version<$vers} {exit 1}' >>cktclsh$1.tcl
if ! $2 cktclsh$1.tcl
then
echo "ERROR: This makefile target requires tclsh $1 or later."
rm cktclsh$1.tcl
exit 1
fi
rm cktclsh$1.tcl

View File

@ -680,6 +680,11 @@ static sqlite3_module seriesModule = {
0, /* xRollback */
0, /* xFindMethod */
0, /* xRename */
0, /* xSavepoint */
0, /* xRelease */
0, /* xRollbackTo */
0, /* xShadowName */
0 /* xIntegrity */
};
/* END the generate_series(START,END,STEP) implementation
*********************************************************************************/

View File

@ -418,6 +418,8 @@ struct lemon {
char *filename; /* Name of the input file */
char *outname; /* Name of the current output file */
char *tokenprefix; /* A prefix added to token names in the .h file */
char *reallocFunc; /* Function to use to allocate stack space */
char *freeFunc; /* Function to use to free stack space */
int nconflict; /* Number of parsing conflicts */
int nactiontab; /* Number of entries in the yy_action[] table */
int nlookaheadtab; /* Number of entries in yy_lookahead[] */
@ -2531,6 +2533,12 @@ static void parseonetoken(struct pstate *psp)
}else if( strcmp(x,"default_type")==0 ){
psp->declargslot = &(psp->gp->vartype);
psp->insertLineMacro = 0;
}else if( strcmp(x,"realloc")==0 ){
psp->declargslot = &(psp->gp->reallocFunc);
psp->insertLineMacro = 0;
}else if( strcmp(x,"free")==0 ){
psp->declargslot = &(psp->gp->freeFunc);
psp->insertLineMacro = 0;
}else if( strcmp(x,"stack_size")==0 ){
psp->declargslot = &(psp->gp->stacksize);
psp->insertLineMacro = 0;
@ -4309,7 +4317,7 @@ void ReportTable(
struct action *ap;
struct rule *rp;
struct acttab *pActtab;
int i, j, n, sz;
int i, j, n, sz, mn, mx;
int nLookAhead;
int szActionType; /* sizeof(YYACTIONTYPE) */
int szCodeType; /* sizeof(YYCODETYPE) */
@ -4501,6 +4509,21 @@ void ReportTable(
fprintf(out,"#define %sARG_FETCH\n",name); lineno++;
fprintf(out,"#define %sARG_STORE\n",name); lineno++;
}
if( lemp->reallocFunc ){
fprintf(out,"#define YYREALLOC %s\n", lemp->reallocFunc); lineno++;
}else{
fprintf(out,"#define YYREALLOC realloc\n"); lineno++;
}
if( lemp->freeFunc ){
fprintf(out,"#define YYFREE %s\n", lemp->freeFunc); lineno++;
}else{
fprintf(out,"#define YYFREE free\n"); lineno++;
}
if( lemp->reallocFunc && lemp->freeFunc ){
fprintf(out,"#define YYDYNSTACK 1\n"); lineno++;
}else{
fprintf(out,"#define YYDYNSTACK 0\n"); lineno++;
}
if( lemp->ctx && lemp->ctx[0] ){
i = lemonStrlen(lemp->ctx);
while( i>=1 && ISSPACE(lemp->ctx[i-1]) ) i--;
@ -4624,6 +4647,22 @@ void ReportTable(
fprintf(out,"#define YY_MIN_REDUCE %d\n", lemp->minReduce); lineno++;
i = lemp->minReduce + lemp->nrule;
fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++;
/* Minimum and maximum token values that have a destructor */
mn = mx = 0;
for(i=0; i<lemp->nsymbol; i++){
struct symbol *sp = lemp->symbols[i];
if( sp && sp->type!=TERMINAL && sp->destructor ){
if( mn==0 || sp->index<mn ) mn = sp->index;
if( sp->index>mx ) mx = sp->index;
}
}
if( lemp->tokendest ) mn = 0;
if( lemp->vardest ) mx = lemp->nsymbol-1;
fprintf(out,"#define YY_MIN_DSTRCTR %d\n", mn); lineno++;
fprintf(out,"#define YY_MAX_DSTRCTR %d\n", mx); lineno++;
tplt_xfer(lemp->name,in,out,&lineno);
/* Now output the action table and its associates:
@ -4767,7 +4806,7 @@ void ReportTable(
/* Generate the table of fallback tokens.
*/
if( lemp->has_fallback ){
int mx = lemp->nterminal - 1;
mx = lemp->nterminal - 1;
/* 2019-08-28: Generate fallback entries for every token to avoid
** having to do a range check on the index */
/* while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } */

View File

@ -67,6 +67,9 @@
** ParseARG_STORE Code to store %extra_argument into yypParser
** ParseARG_FETCH Code to extract %extra_argument from yypParser
** ParseCTX_* As ParseARG_ except for %extra_context
** YYREALLOC Name of the realloc() function to use
** YYFREE Name of the free() function to use
** YYDYNSTACK True if stack space should be extended on heap
** YYERRORSYMBOL is the code number of the error symbol. If not
** defined, then do no error processing.
** YYNSTATE the combined number of states.
@ -80,6 +83,8 @@
** YY_NO_ACTION The yy_action[] code for no-op
** YY_MIN_REDUCE Minimum value for reduce actions
** YY_MAX_REDUCE Maximum value for reduce actions
** YY_MIN_DSTRCTR Minimum symbol value that has a destructor
** YY_MAX_DSTRCTR Maximum symbol value that has a destructor
*/
#ifndef INTERFACE
# define INTERFACE 1
@ -101,6 +106,22 @@
# define yytestcase(X)
#endif
/* Macro to determine if stack space has the ability to grow using
** heap memory.
*/
#if YYSTACKDEPTH<=0 || YYDYNSTACK
# define YYGROWABLESTACK 1
#else
# define YYGROWABLESTACK 0
#endif
/* Guarantee a minimum number of initial stack slots.
*/
#if YYSTACKDEPTH<=0
# undef YYSTACKDEPTH
# define YYSTACKDEPTH 2 /* Need a minimum stack size */
#endif
/* Next are the tables used to determine what action to take based on the
** current state and lookahead token. These tables are used to implement
@ -212,14 +233,9 @@ struct yyParser {
#endif
ParseARG_SDECL /* A place to hold %extra_argument */
ParseCTX_SDECL /* A place to hold %extra_context */
#if YYSTACKDEPTH<=0
int yystksz; /* Current side of the stack */
yyStackEntry *yystack; /* The parser's stack */
yyStackEntry yystk0; /* First stack entry */
#else
yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
yyStackEntry *yystackEnd; /* Last entry in the stack */
#endif
yyStackEntry *yystackEnd; /* Last entry in the stack */
yyStackEntry *yystack; /* The parser stack */
yyStackEntry yystk0[YYSTACKDEPTH]; /* Initial stack space */
};
typedef struct yyParser yyParser;
@ -273,37 +289,45 @@ static const char *const yyRuleName[] = {
#endif /* NDEBUG */
#if YYSTACKDEPTH<=0
#if YYGROWABLESTACK
/*
** Try to increase the size of the parser stack. Return the number
** of errors. Return 0 on success.
*/
static int yyGrowStack(yyParser *p){
int oldSize = 1 + (int)(p->yystackEnd - p->yystack);
int newSize;
int idx;
yyStackEntry *pNew;
newSize = p->yystksz*2 + 100;
idx = p->yytos ? (int)(p->yytos - p->yystack) : 0;
if( p->yystack==&p->yystk0 ){
pNew = malloc(newSize*sizeof(pNew[0]));
if( pNew ) pNew[0] = p->yystk0;
newSize = oldSize*2 + 100;
idx = (int)(p->yytos - p->yystack);
if( p->yystack==p->yystk0 ){
pNew = YYREALLOC(0, newSize*sizeof(pNew[0]));
if( pNew==0 ) return 1;
memcpy(pNew, p->yystack, oldSize*sizeof(pNew[0]));
}else{
pNew = realloc(p->yystack, newSize*sizeof(pNew[0]));
pNew = YYREALLOC(p->yystack, newSize*sizeof(pNew[0]));
if( pNew==0 ) return 1;
}
if( pNew ){
p->yystack = pNew;
p->yytos = &p->yystack[idx];
p->yystack = pNew;
p->yytos = &p->yystack[idx];
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n",
yyTracePrompt, p->yystksz, newSize);
}
#endif
p->yystksz = newSize;
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sStack grows from %d to %d entries.\n",
yyTracePrompt, oldSize, newSize);
}
return pNew==0;
#endif
p->yystackEnd = &p->yystack[newSize-1];
return 0;
}
#endif /* YYGROWABLESTACK */
#if !YYGROWABLESTACK
/* For builds that do no have a growable stack, yyGrowStack always
** returns an error.
*/
# define yyGrowStack(X) 1
#endif
/* Datatype of the argument to the memory allocated passed as the
@ -323,24 +347,14 @@ void ParseInit(void *yypRawParser ParseCTX_PDECL){
#ifdef YYTRACKMAXSTACKDEPTH
yypParser->yyhwm = 0;
#endif
#if YYSTACKDEPTH<=0
yypParser->yytos = NULL;
yypParser->yystack = NULL;
yypParser->yystksz = 0;
if( yyGrowStack(yypParser) ){
yypParser->yystack = &yypParser->yystk0;
yypParser->yystksz = 1;
}
#endif
yypParser->yystack = yypParser->yystk0;
yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
#ifndef YYNOERRORRECOVERY
yypParser->yyerrcnt = -1;
#endif
yypParser->yytos = yypParser->yystack;
yypParser->yystack[0].stateno = 0;
yypParser->yystack[0].major = 0;
#if YYSTACKDEPTH>0
yypParser->yystackEnd = &yypParser->yystack[YYSTACKDEPTH-1];
#endif
}
#ifndef Parse_ENGINEALWAYSONSTACK
@ -426,9 +440,26 @@ static void yy_pop_parser_stack(yyParser *pParser){
*/
void ParseFinalize(void *p){
yyParser *pParser = (yyParser*)p;
while( pParser->yytos>pParser->yystack ) yy_pop_parser_stack(pParser);
#if YYSTACKDEPTH<=0
if( pParser->yystack!=&pParser->yystk0 ) free(pParser->yystack);
/* In-lined version of calling yy_pop_parser_stack() for each
** element left in the stack */
yyStackEntry *yytos = pParser->yytos;
while( yytos>pParser->yystack ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sPopping %s\n",
yyTracePrompt,
yyTokenName[yytos->major]);
}
#endif
if( yytos->major>=YY_MIN_DSTRCTR ){
yy_destructor(pParser, yytos->major, &yytos->minor);
}
yytos--;
}
#if YYGROWABLESTACK
if( pParser->yystack!=pParser->yystk0 ) YYFREE(pParser->yystack);
#endif
}
@ -654,25 +685,19 @@ static void yy_shift(
assert( yypParser->yyhwm == (int)(yypParser->yytos - yypParser->yystack) );
}
#endif
#if YYSTACKDEPTH>0
if( yypParser->yytos>yypParser->yystackEnd ){
yypParser->yytos--;
yyStackOverflow(yypParser);
return;
}
#else
if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz] ){
yytos = yypParser->yytos;
if( yytos>yypParser->yystackEnd ){
if( yyGrowStack(yypParser) ){
yypParser->yytos--;
yyStackOverflow(yypParser);
return;
}
yytos = yypParser->yytos;
assert( yytos <= yypParser->yystackEnd );
}
#endif
if( yyNewState > YY_MAX_SHIFT ){
yyNewState += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE;
}
yytos = yypParser->yytos;
yytos->stateno = yyNewState;
yytos->major = yyMajor;
yytos->minor.yy0 = yyMinor;
@ -911,19 +936,12 @@ void Parse(
(int)(yypParser->yytos - yypParser->yystack));
}
#endif
#if YYSTACKDEPTH>0
if( yypParser->yytos>=yypParser->yystackEnd ){
yyStackOverflow(yypParser);
break;
}
#else
if( yypParser->yytos>=&yypParser->yystack[yypParser->yystksz-1] ){
if( yyGrowStack(yypParser) ){
yyStackOverflow(yypParser);
break;
}
}
#endif
}
yyact = yy_reduce(yypParser,yyruleno,yymajor,yyminor ParseCTX_PARAM);
}else if( yyact <= YY_MAX_SHIFTREDUCE ){

View File

@ -25,6 +25,14 @@ VERSION=`cat $TOP/VERSION`
HASH=`sed 's/^\(..........\).*/\1/' $TOP/manifest.uuid`
DATETIME=`grep '^D' $TOP/manifest | sed -e 's/[^0-9]//g' -e 's/\(............\).*/\1/'`
# Verify that the version number in the TEA autoconf file is correct.
# Fail with an error if not.
#
if grep $VERSION $TOP/autoconf/tea/configure.ac
then echo "TEA version number ok"
else echo "TEA version number mismatch. Should be $VERSION"; exit 1
fi
# If this script is given an argument of --snapshot, then generate a
# snapshot tarball named for the current checkout SHA1 hash, rather than
# the version number.

View File

@ -7,6 +7,13 @@
# definition used in src/ctime.c, run this script from
# the checkout root. It generates src/ctime.c .
#
# Results are normally written into src/ctime.c. But if an argument is
# provided, results are written there instead. Examples:
#
# tclsh tool/mkctimec.tcl ;# <-- results to src/ctime.c
#
# tclsh tool/mkctimec.tcl /dev/tty ;# <-- results to the terminal
#
set ::headWarning {/* DO NOT EDIT!
@ -239,6 +246,7 @@ set boolean_defnil_options {
SQLITE_OMIT_REINDEX
SQLITE_OMIT_SCHEMA_PRAGMAS
SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS
SQLITE_OMIT_SEH
SQLITE_OMIT_SHARED_CACHE
SQLITE_OMIT_SHUTDOWN_DIRECTORIES
SQLITE_OMIT_SUBQUERY
@ -428,10 +436,15 @@ foreach v $value2_options {
}]
}
set ctime_c "src/ctime.c"
if {$argc>0} {
set destfile [lindex $argv 0]
} else {
set destfile "[file dir [file dir [file normal $argv0]]]/src/ctime.c"
puts "Overwriting $destfile..."
}
if {[catch {set cfd [open $ctime_c w]}]!=0} {
puts stderr "File '$ctime_c' unwritable."
if {[catch {set cfd [open $destfile w]}]!=0} {
puts stderr "File '$destfile' unwritable."
exit 1;
}

View File

@ -9,6 +9,14 @@
# Then add the extra "case PragTyp_XXXXX:" and subsequent code for the
# new pragma in ../src/pragma.c.
#
# The results are normally written into the ../src/pragma.h file. However,
# if an alternative output file name is provided as an argument, then
# results are written into the alternative. For example:
#
# tclsh tool/mkpragmatab.tcl ;# <--- Results to src/pragma.h
#
# tclsh tool/mkpragmatab.tcl /dev/tty ;# <-- results to terminal
#
# Flag meanings:
set flagMeaning(NeedSchema) {Force schema load before running}
@ -402,8 +410,12 @@ set pragma_def {
# Open the output file
#
set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h"
puts "Overwriting $destfile with new pragma table..."
if {$argc>0} {
set destfile [lindex $argv 0]
} else {
set destfile "[file dir [file dir [file normal $argv0]]]/src/pragma.h"
puts "Overwriting $destfile with new pragma table..."
}
set fd [open $destfile wb]
puts $fd {/* DO NOT EDIT!
** This file is automatically generated by the script at

View File

@ -119,7 +119,7 @@ if {$tcl_platform(platform)=="windows"} {
if {[file executable $vsrcprog] && [file readable $srcroot/manifest]} {
set res [string trim [split [exec $vsrcprog -x $srcroot]] \n]
puts $out "** The content in this amalgamation comes from Fossil check-in"
puts -nonewline $out "** [string range [lindex $res 0] 1 35]"
puts -nonewline $out "** [string range [lindex $res 0] 0 35]"
if {[llength $res]==1} {
puts $out "."
} else {

69
tool/mktoolzip.tcl Normal file
View File

@ -0,0 +1,69 @@
#!/usr/bin/tclsh
#
# Run this script in order to generate a ZIP archive containing various
# command-line tools.
#
# The makefile that invokes this script must first build the following
# binaries:
#
# testfixture -- used to run this script
# sqlite3 -- the SQLite CLI
# sqldiff -- Program to diff two databases
# sqlite3_analyzer -- Space analyzer
#
switch $tcl_platform(os) {
{Windows NT} {
set OS win32
set EXE .exe
}
Linux {
set OS linux
set EXE {}
}
Darwin {
set OS osx
set EXE {}
}
default {
set OS unknown
set EXE {}
}
}
switch $tcl_platform(machine) {
arm64 {
set ARCH arm64
}
x86_64 {
set ARCH x64
}
amd64 -
intel {
if {$tcl_platform(pointerSize)==4} {
set ARCH x86
} else {
set ARCH x64
}
}
default {
set ARCH unk
}
}
set in [open [file join [file dirname [file dirname [info script]]] VERSION]]
set vers [read $in]
close $in
scan $vers %d.%d.%d v1 v2 v3
set v2 [format 3%02d%02d00 $v2 $v3]
set name sqlite-tools-$OS-$ARCH-$v2.zip
if {$OS=="win32"} {
# The win32 tar.exe supports the -a ("auto-compress") option. This causes
# tar to create an archive type based on the extension of the output file.
# In this case, a zip file.
puts "tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
puts [exec tar -a -cf $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
puts "$name: [file size $name] bytes"
} else {
puts "zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE"
puts [exec zip $name sqlite3$EXE sqldiff$EXE sqlite3_analyzer$EXE]
puts [exec ls -l $name]
}

View File

@ -959,6 +959,10 @@ static void page_usage_freelist(u32 pgno){
a = fileRead((pgno-1)*g.pagesize, g.pagesize);
iNext = decodeInt32(a);
n = decodeInt32(a+4);
if( n>(g.pagesize - 8)/4 ){
printf("ERROR: page %d too many freelist entries (%d)\n", pgno, n);
n = (g.pagesize - 8)/4;
}
for(i=0; i<n; i++){
int child = decodeInt32(a + (i*4+8));
page_usage_msg(child, "freelist leaf, child %d of trunk page %d",

View File

@ -543,15 +543,26 @@ int main(int argc, char **argv){
}
zPgSz[0] = 0;
zPgSz[1] = 0;
lseek(fd, 8, SEEK_SET);
read(fd, zPgSz, 4);
fstat(fd, &sbuf);
if( sbuf.st_size<32 ){
printf("%s: file too small to be a WAL - only %d bytes\n",
argv[1], (int)sbuf.st_size);
return 0;
}
if( lseek(fd, 8, SEEK_SET)!=8 ){
printf("\"%s\" seems to not be a valid WAL file\n", argv[1]);
return 1;
}
if( read(fd, zPgSz, 4)!=4 ){
printf("\"%s\": cannot read the page size\n", argv[1]);
return 1;
}
pagesize = zPgSz[1]*65536 + zPgSz[2]*256 + zPgSz[3];
if( pagesize==0 ) pagesize = 1024;
printf("Pagesize: %d\n", pagesize);
fstat(fd, &sbuf);
if( sbuf.st_size<32 ){
printf("file too small to be a WAL\n");
return 0;
if( (pagesize & (pagesize-1))!=0 || pagesize<512 || pagesize>65536 ){
printf("\"%s\": invalid page size.\n", argv[1]);
return 1;
}
mxFrame = (sbuf.st_size - 32)/(pagesize + 24);
printf("Available pages: 1..%d\n", mxFrame);

View File

@ -581,6 +581,7 @@ set inuse_pgcnt [expr wide([mem eval $sql])]
set inuse_percent [percent $inuse_pgcnt $file_pgcnt]
set free_pgcnt [expr {$file_pgcnt-$inuse_pgcnt-$av_pgcnt}]
if {$file_bytes>1073741824 && $free_pgcnt>0} {incr free_pgcnt -1}
set free_percent [percent $free_pgcnt $file_pgcnt]
set free_pgcnt2 [db one {PRAGMA freelist_count}]
set free_percent2 [percent $free_pgcnt2 $file_pgcnt]

View File

@ -13,7 +13,8 @@
** This is a utility program that computes the differences in content
** 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
** run the utility.
@ -26,6 +27,19 @@
#include <assert.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.
*/
@ -46,22 +60,10 @@ struct GlobalVars {
#define DEBUG_DIFF_SQL 0x000002
/*
** Dynamic string object
** Clear and free an sqlite3_str object
*/
typedef struct Str Str;
struct Str {
char *z; /* Text of the string */
int nAlloc; /* Bytes allocated in z[] */
int nUsed; /* Bytes actually used in z[] */
};
/*
** Initialize a Str object
*/
static void strInit(Str *p){
p->z = 0;
p->nAlloc = 0;
p->nUsed = 0;
static void strFree(sqlite3_str *pStr){
sqlite3_free(sqlite3_str_finish(pStr));
}
/*
@ -69,12 +71,14 @@ static void strInit(Str *p){
** abort the program.
*/
static void cmdlineError(const char *zFormat, ...){
sqlite3_str *pOut = sqlite3_str_new(0);
va_list ap;
fprintf(stderr, "%s: ", g.zArgv0);
va_start(ap, zFormat);
vfprintf(stderr, zFormat, ap);
sqlite3_str_vappendf(pOut, zFormat, 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);
}
@ -83,49 +87,16 @@ static void cmdlineError(const char *zFormat, ...){
** abort the program.
*/
static void runtimeError(const char *zFormat, ...){
sqlite3_str *pOut = sqlite3_str_new(0);
va_list ap;
fprintf(stderr, "%s: ", g.zArgv0);
va_start(ap, zFormat);
vfprintf(stderr, zFormat, ap);
sqlite3_str_vappendf(pOut, zFormat, ap);
va_end(ap);
fprintf(stderr, "\n");
Wfprintf(stderr, "%s: %s\n", g.zArgv0, sqlite3_str_value(pOut));
strFree(pOut);
exit(1);
}
/*
** Free all memory held by a Str object
*/
static void strFree(Str *p){
sqlite3_free(p->z);
strInit(p);
}
/*
** Add formatted text to the end of a Str object
*/
static void strPrintf(Str *p, const char *zFormat, ...){
int nNew;
for(;;){
if( p->z ){
va_list ap;
va_start(ap, zFormat);
sqlite3_vsnprintf(p->nAlloc-p->nUsed, p->z+p->nUsed, zFormat, ap);
va_end(ap);
nNew = (int)strlen(p->z + p->nUsed);
}else{
nNew = p->nAlloc;
}
if( p->nUsed+nNew < p->nAlloc-1 ){
p->nUsed += nNew;
break;
}
p->nAlloc = p->nAlloc*2 + 1000;
p->z = sqlite3_realloc(p->z, p->nAlloc);
if( p->z==0 ) runtimeError("out of memory");
}
}
/* Safely quote an SQL identifier. Use the minimum amount of transformation
** necessary to allow the string to be used with %s.
@ -453,7 +424,7 @@ static void dump_table(const char *zTab, FILE *out){
int i; /* Loop counter */
sqlite3_stmt *pStmt; /* SQL statement */
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);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
@ -462,54 +433,53 @@ static void dump_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt);
if( !g.bSchemaOnly ){
az = columnNames("aux", zTab, &nPk, 0);
strInit(&ins);
pIns = sqlite3_str_new(0);
if( az==0 ){
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{
Str sql;
strInit(&sql);
sqlite3_str *pSql = sqlite3_str_new(0);
zSep = "SELECT";
for(i=0; az[i]; i++){
strPrintf(&sql, "%s %s", zSep, az[i]);
sqlite3_str_appendf(pSql, "%s %s", zSep, az[i]);
zSep = ",";
}
strPrintf(&sql," FROM aux.%s", zId);
sqlite3_str_appendf(pSql," FROM aux.%s", zId);
zSep = " ORDER BY";
for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s %d", zSep, i);
sqlite3_str_appendf(pSql, "%s %d", zSep, i);
zSep = ",";
}
pStmt = db_prepare("%s", sql.z);
strFree(&sql);
strPrintf(&ins, "INSERT INTO %s", zId);
pStmt = db_prepare("%s", sqlite3_str_value(pSql));
strFree(pSql);
sqlite3_str_appendf(pIns, "INSERT INTO %s", zId);
zSep = "(";
for(i=0; az[i]; i++){
strPrintf(&ins, "%s%s", zSep, az[i]);
sqlite3_str_appendf(pIns, "%s%s", zSep, az[i]);
zSep = ",";
}
strPrintf(&ins,") VALUES");
sqlite3_str_appendf(pIns,") VALUES");
namelistFree(az);
}
nCol = sqlite3_column_count(pStmt);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
fprintf(out, "%s",ins.z);
Wfprintf(out, "%s",sqlite3_str_value(pIns));
zSep = "(";
for(i=0; i<nCol; i++){
fprintf(out, "%s",zSep);
Wfprintf(out, "%s",zSep);
printQuoted(out, sqlite3_column_value(pStmt,i));
zSep = ",";
}
fprintf(out, ");\n");
Wfprintf(out, ");\n");
}
sqlite3_finalize(pStmt);
strFree(&ins);
strFree(pIns);
} /* endif !g.bSchemaOnly */
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));
Wfprintf(out, "%s;\n", sqlite3_column_text(pStmt,0));
}
sqlite3_finalize(pStmt);
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 i; /* Loop counter */
const char *zSep; /* Separator string */
Str sql; /* Comparison query */
sqlite3_str *pSql; /* Comparison query */
sqlite3_stmt *pStmt; /* Query statement to do the diff */
const char *zLead = /* Becomes line-comment for sqlite_schema */
(g.bSchemaCompare)? "-- " : "";
strInit(&sql);
pSql = sqlite3_str_new(0);
if( g.fDebug==DEBUG_COLUMN_NAMES ){
/* Simply run columnNames() on all tables of the origin
** 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);
if( az==0 ){
printf("Rowid not accessible for %s\n", zId);
Wfprintf(stdout, "Rowid not accessible for %s\n", zId);
}else{
printf("%s:", zId);
Wfprintf(stdout, "%s:", zId);
for(i=0; az[i]; i++){
printf(" %s", az[i]);
if( i+1==nPk ) printf(" *");
Wfprintf(stdout, " %s", az[i]);
if( i+1==nPk ) Wfprintf(stdout, " *");
}
printf("\n");
Wfprintf(stdout, "\n");
}
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) ){
/* Table missing from second database. */
if( g.bSchemaCompare )
fprintf(out, "-- 2nd DB has no %s table\n", zTab);
Wfprintf(out, "-- 2nd DB has no %s table\n", zTab);
else
fprintf(out, "DROP TABLE %s;\n", zId);
Wfprintf(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) ){
/* Table missing from source */
if( g.bSchemaCompare )
fprintf(out, "-- 1st DB has no %s table\n", zTab);
else
if( g.bSchemaCompare ){
Wfprintf(out, "-- 1st DB has no %s table\n", zTab);
}else{
dump_table(zTab, out);
}
goto end_diff_one_table;
}
@ -589,101 +560,101 @@ static void diff_one_table(const char *zTab, FILE *out){
|| az[n]
){
/* 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);
goto end_diff_one_table;
}
/* Build the comparison query */
for(n2=n; az2[n2]; n2++){
char *zTab = safeId(az2[n2]);
fprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zTab);
sqlite3_free(zTab);
char *zNTab = safeId(az2[n2]);
Wfprintf(out, "ALTER TABLE %s ADD COLUMN %s;\n", zId, zNTab);
sqlite3_free(zNTab);
}
nQ = nPk2+1+2*(n2-nPk2);
if( n2>nPk2 ){
zSep = "SELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]);
sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 1 /* changed row */");
sqlite3_str_appendf(pSql, ", 1 /* changed row */");
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]);
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]);
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";
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 = "\n AND (";
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 = " OR ";
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 = " OR ";
i++;
}
strPrintf(&sql, " UNION ALL\n");
sqlite3_str_appendf(pSql, " UNION ALL\n");
}
zSep = "SELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sA.%s", zSep, az[i]);
sqlite3_str_appendf(pSql, "%sA.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 2 /* deleted row */");
sqlite3_str_appendf(pSql, ", 2 /* deleted row */");
while( az2[i] ){
strPrintf(&sql, ", NULL, NULL");
sqlite3_str_appendf(pSql, ", 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);
sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
sqlite3_str_appendf(pSql, " 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]);
sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n");
sqlite3_str_appendf(pSql, ")\n");
zSep = " UNION ALL\nSELECT ";
for(i=0; i<nPk; i++){
strPrintf(&sql, "%sB.%s", zSep, az[i]);
sqlite3_str_appendf(pSql, "%sB.%s", zSep, az[i]);
zSep = ", ";
}
strPrintf(&sql, ", 3 /* inserted row */");
sqlite3_str_appendf(pSql, ", 3 /* inserted row */");
while( az2[i] ){
strPrintf(&sql, ", 1, B.%s", az2[i]);
sqlite3_str_appendf(pSql, ", 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);
sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
sqlite3_str_appendf(pSql, " 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]);
sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
zSep = " AND";
}
strPrintf(&sql, ")\n ORDER BY");
sqlite3_str_appendf(pSql, ")\n ORDER BY");
zSep = " ";
for(i=1; i<=nPk; i++){
strPrintf(&sql, "%s%d", zSep, i);
sqlite3_str_appendf(pSql, "%s%d", zSep, i);
zSep = ", ";
}
strPrintf(&sql, ";\n");
sqlite3_str_appendf(pSql, ";\n");
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;
}
@ -705,7 +676,7 @@ static void diff_one_table(const char *zTab, FILE *out){
/* Run the query and output differences */
if( !g.bSchemaOnly ){
pStmt = db_prepare("%s", sql.z);
pStmt = db_prepare("%s", sqlite3_str_value(pSql));
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt, nPk);
if( iType==1 || iType==2 ){
@ -763,7 +734,7 @@ static void diff_one_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt);
end_diff_one_table:
strFree(&sql);
strFree(pSql);
sqlite3_free(zId);
namelistFree(az);
namelistFree(az2);
@ -1171,15 +1142,15 @@ static int rbuDeltaCreate(
**************************************************************************/
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 *zFmt, /* Format for each entry */
char **az, int n /* Array of strings & its size (or -1) */
){
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]);
if( i!=0 ) sqlite3_str_appendf(pStr, "%s", zSep);
sqlite3_str_appendf(pStr, zFmt, az[i], az[i], az[i]);
}
}
@ -1188,74 +1159,75 @@ static void getRbudiffQuery(
char **azCol,
int nPK,
int bOtaRowid,
Str *pSql
sqlite3_str *pSql
){
int i;
/* First the newly inserted rows: **/
strPrintf(pSql, "SELECT ");
sqlite3_str_appendf(pSql, "SELECT ");
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);
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);
sqlite3_str_appendf(pSql, " FROM aux.%Q AS n WHERE NOT EXISTS (\n", zTab);
sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
sqlite3_str_appendf(pSql, " main.%Q AS o WHERE ", zTab);
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);
/* Deleted rows: */
strPrintf(pSql, "\nUNION ALL\nSELECT ");
sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, nPK);
if( azCol[nPK] ){
strPrintf(pSql, ", ");
sqlite3_str_appendf(pSql, ", ");
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);
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);
sqlite3_str_appendf(pSql, " FROM main.%Q AS n WHERE NOT EXISTS (\n", zTab);
sqlite3_str_appendf(pSql, " SELECT 1 FROM ", zTab);
sqlite3_str_appendf(pSql, " aux.%Q AS o WHERE ", zTab);
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);
/* 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
** be omitted altogether. */
if( azCol[nPK] ){
strPrintf(pSql, "\nUNION ALL\nSELECT ");
sqlite3_str_appendf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "n.%s", azCol, nPK);
strPrintf(pSql, ",\n");
sqlite3_str_appendf(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, ", '");
sqlite3_str_appendf(pSql, ", '");
strPrintfArray(pSql, "", ".", azCol, nPK);
strPrintf(pSql, "' ||\n");
sqlite3_str_appendf(pSql, "' ||\n");
}else{
strPrintf(pSql, ",\n");
sqlite3_str_appendf(pSql, ",\n");
}
strPrintfArray(pSql, " ||\n",
" 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);
strPrintf(pSql, ",\n");
sqlite3_str_appendf(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);
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);
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. */
strPrintf(pSql, "\nORDER BY ");
for(i=1; i<=nPK; i++) strPrintf(pSql, "%s%d", ((i>1)?", ":""), i);
sqlite3_str_appendf(pSql, "\nORDER BY ");
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){
@ -1264,14 +1236,17 @@ static void rbudiff_one_table(const char *zTab, FILE *out){
char **azCol; /* NULL terminated array of col names */
int i;
int nCol;
Str ct = {0, 0, 0}; /* The "CREATE TABLE data_xxx" statement */
Str sql = {0, 0, 0}; /* Query to find differences */
Str insert = {0, 0, 0}; /* First part of output INSERT statement */
sqlite3_str *pCt; /* The "CREATE TABLE data_xxx" statement */
sqlite3_str *pSql; /* Query to find differences */
sqlite3_str *pInsert; /* First part of output INSERT statement */
sqlite3_stmt *pStmt = 0;
int nRow = 0; /* Total rows in data_xxx table */
/* --rbu mode must use real primary keys. */
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. */
checkSchemasMatch(zTab);
@ -1285,35 +1260,35 @@ static void rbudiff_one_table(const char *zTab, FILE *out){
for(nCol=0; azCol[nCol]; nCol++);
/* Build and output the CREATE TABLE statement for the data_xxx table */
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);");
sqlite3_str_appendf(pCt, "CREATE TABLE IF NOT EXISTS 'data_%q'(", zTab);
if( bOtaRowid ) sqlite3_str_appendf(pCt, "rbu_rowid, ");
strPrintfArray(pCt, ", ", "%s", &azCol[bOtaRowid], -1);
sqlite3_str_appendf(pCt, ", rbu_control);");
/* 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
** in the data_xxx table. */
strPrintf(&insert, "INSERT INTO 'data_%q' (", zTab);
if( bOtaRowid ) strPrintf(&insert, "rbu_rowid, ");
strPrintfArray(&insert, ", ", "%s", &azCol[bOtaRowid], -1);
strPrintf(&insert, ", rbu_control) VALUES(");
sqlite3_str_appendf(pInsert, "INSERT INTO 'data_%q' (", zTab);
if( bOtaRowid ) sqlite3_str_appendf(pInsert, "rbu_rowid, ");
strPrintfArray(pInsert, ", ", "%s", &azCol[bOtaRowid], -1);
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 ){
/* 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. */
if( ct.z ){
fprintf(out, "%s\n", ct.z);
strFree(&ct);
if( sqlite3_str_length(pCt) ){
fprintf(out, "%s\n", sqlite3_str_value(pCt));
sqlite3_str_reset(pCt);
}
/* Output the first part of the INSERT statement */
fprintf(out, "%s", insert.z);
fprintf(out, "%s", sqlite3_str_value(pInsert));
nRow++;
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);
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);
sqlite3_str *pCnt = sqlite3_str_new(0);
sqlite3_str_appendf(pCnt,
"INSERT INTO rbu_count VALUES('data_%q', %d);", zTab, nRow);
fprintf(out, "%s\n", sqlite3_str_value(pCnt));
strFree(pCnt);
}
strFree(&ct);
strFree(&sql);
strFree(&insert);
strFree(pCt);
strFree(pSql);
strFree(pInsert);
}
/*
@ -1399,25 +1375,25 @@ static void summarize_one_table(const char *zTab, FILE *out){
int n2; /* Number of columns in aux */
int i; /* Loop counter */
const char *zSep; /* Separator string */
Str sql; /* Comparison query */
sqlite3_str *pSql; /* Comparison query */
sqlite3_stmt *pStmt; /* Query statement to do the diff */
sqlite3_int64 nUpdate; /* Number of updated rows */
sqlite3_int64 nUnchanged; /* Number of unmodified rows */
sqlite3_int64 nDelete; /* Number of deleted 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,"main",zTab,0,0,0,0,0,0) ){
/* 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;
}
if( sqlite3_table_column_metadata(g.db,"main",zTab,0,0,0,0,0,0) ){
/* 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;
}
@ -1434,57 +1410,57 @@ static void summarize_one_table(const char *zTab, FILE *out){
|| az[n]
){
/* Schema mismatch */
fprintf(out, "%s: incompatible schema\n", zTab);
Wfprintf(out, "%s: incompatible schema\n", zTab);
goto end_summarize_one_table;
}
/* Build the comparison query */
for(n2=n; az[n2]; n2++){}
strPrintf(&sql, "SELECT 1, count(*)");
sqlite3_str_appendf(pSql, "SELECT 1, count(*)");
if( n2==nPk2 ){
strPrintf(&sql, ", 0\n");
sqlite3_str_appendf(pSql, ", 0\n");
}else{
zSep = ", sum(";
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 ";
}
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";
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";
}
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);
sqlite3_str_appendf(pSql, " UNION ALL\n");
sqlite3_str_appendf(pSql, "SELECT 2, count(*), 0\n");
sqlite3_str_appendf(pSql, " FROM main.%s A\n", zId);
sqlite3_str_appendf(pSql, " 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]);
sqlite3_str_appendf(pSql, "%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);
sqlite3_str_appendf(pSql, ")\n");
sqlite3_str_appendf(pSql, " UNION ALL\n");
sqlite3_str_appendf(pSql, "SELECT 3, count(*), 0\n");
sqlite3_str_appendf(pSql, " FROM aux.%s B\n", zId);
sqlite3_str_appendf(pSql, " 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]);
sqlite3_str_appendf(pSql, "%s A.%s=B.%s", zSep, az[i], az[i]);
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 ){
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;
}
/* Run the query and output difference summary */
pStmt = db_prepare("%s", sql.z);
pStmt = db_prepare("%s", sqlite3_str_value(pSql));
nUpdate = 0;
nInsert = 0;
nDelete = 0;
@ -1504,11 +1480,12 @@ static void summarize_one_table(const char *zTab, FILE *out){
}
}
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);
end_summarize_one_table:
strFree(&sql);
strFree(pSql);
sqlite3_free(zId);
namelistFree(az);
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 *aiPk = 0; /* Column numbers for each PK column */
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 */
const char *zSep; /* List separator */
/* Check that the schemas of the two tables match. Exit early otherwise. */
checkSchemasMatch(zTab);
strInit(&sql);
pSql = sqlite3_str_new(0);
pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
@ -1617,71 +1594,74 @@ static void changeset_one_table(const char *zTab, FILE *out){
sqlite3_finalize(pStmt);
if( nPk==0 ) goto end_changeset_one_table;
if( nCol>nPk ){
strPrintf(&sql, "SELECT %d", SQLITE_UPDATE);
sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_UPDATE);
for(i=0; i<nCol; i++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]);
sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
}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]);
}
}
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";
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 = "\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]);
sqlite3_str_appendf(pSql, "%sA.%s IS NOT B.%s", zSep, azCol[i], azCol[i]);
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++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n A.%s", azCol[i]);
sqlite3_str_appendf(pSql, ",\n A.%s", azCol[i]);
}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);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM aux.%s B\n", zId);
sqlite3_str_appendf(pSql, "\n FROM main.%s A\n", zId);
sqlite3_str_appendf(pSql, " 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]]);
sqlite3_str_appendf(pSql, "%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);
sqlite3_str_appendf(pSql, ")\n UNION ALL\n");
sqlite3_str_appendf(pSql, "SELECT %d", SQLITE_INSERT);
for(i=0; i<nCol; i++){
if( aiFlg[i] ){
strPrintf(&sql, ",\n B.%s", azCol[i]);
sqlite3_str_appendf(pSql, ",\n B.%s", azCol[i]);
}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);
strPrintf(&sql, " WHERE NOT EXISTS(SELECT 1 FROM main.%s A\n", zId);
sqlite3_str_appendf(pSql, "\n FROM aux.%s B\n", zId);
sqlite3_str_appendf(pSql, " 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]]);
sqlite3_str_appendf(pSql, "%s A.%s=B.%s",
zSep, azCol[aiPk[i]], azCol[aiPk[i]]);
zSep = " AND";
}
strPrintf(&sql, ")\n");
strPrintf(&sql, " ORDER BY");
sqlite3_str_appendf(pSql, ")\n");
sqlite3_str_appendf(pSql, " ORDER BY");
zSep = " ";
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 = ",";
}
strPrintf(&sql, ";\n");
sqlite3_str_appendf(pSql, ";\n");
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;
}
@ -1691,7 +1671,7 @@ static void changeset_one_table(const char *zTab, FILE *out){
fwrite(zTab, 1, strlen(zTab), out);
putc(0, out);
pStmt = db_prepare("%s", sql.z);
pStmt = db_prepare("%s", sqlite3_str_value(pSql));
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int iType = sqlite3_column_int(pStmt,0);
putc(iType, out);
@ -1758,7 +1738,7 @@ end_changeset_one_table:
sqlite3_free(aiPk);
sqlite3_free(zId);
sqlite3_free(aiFlg);
strFree(&sql);
strFree(pSql);
}
/*
@ -1911,8 +1891,8 @@ const char *all_tables_sql(){
** Print sketchy documentation for this utility program
*/
static void showHelp(void){
printf("Usage: %s [options] DB1 DB2\n", g.zArgv0);
printf(
Wfprintf(stdout, "Usage: %s [options] DB1 DB2\n", g.zArgv0);
Wfprintf(stdout,
"Output SQL text that would transform DB1 into DB2.\n"
"Options:\n"
" --changeset FILE Write a CHANGESET into FILE\n"
@ -2049,9 +2029,9 @@ int main(int argc, char **argv){
}
if( neverUseTransaction ) useTransaction = 0;
if( useTransaction ) fprintf(out, "BEGIN TRANSACTION;\n");
if( useTransaction ) Wfprintf(out, "BEGIN TRANSACTION;\n");
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) "
"WITHOUT ROWID;\n"
);
@ -2066,7 +2046,7 @@ int main(int argc, char **argv){
}
sqlite3_finalize(pStmt);
}
if( useTransaction ) printf("COMMIT;\n");
if( useTransaction ) Wfprintf(stdout,"COMMIT;\n");
/* TBD: Handle trigger differences */
/* TBD: Handle view differences */

View File

@ -19,8 +19,55 @@ INCLUDE sqlite3.c
#endif
INCLUDE $ROOT/src/tclsqlite.c
#if defined(_WIN32)
INCLUDE $ROOT/ext/consio/console_io.h
INCLUDE $ROOT/ext/consio/console_io.c
/* Substitute "puts" command. Only these forms recognized:
**
** puts STRING
** puts stderr STRING
** puts -nonewline STRING
*/
static int subst_puts(
void *NotUsed,
Tcl_Interp *interp,
int objc,
Tcl_Obj *const*objv
){
FILE *pOut = stdout;
const char *zOut;
int addNewLine = 1;
if( objc==2 ){
zOut = Tcl_GetString(objv[1]);
}else if( objc!=3 ){
Tcl_WrongNumArgs(interp, 1, objv, "?stderr|-nonewline? STRING");
return TCL_ERROR;
}else{
const char *zArg = Tcl_GetString(objv[1]);
if( zArg==0 ) return TCL_ERROR;
zOut = Tcl_GetString(objv[2]);
if( strcmp(zArg, "stderr")==0 ){
pOut = stderr;
}else if( strcmp(zArg, "-nonewline")==0 ){
addNewLine = 0;
}else{
Tcl_AppendResult(interp, "bad argument: ", zArg, 0);
return TCL_ERROR;
}
}
fPutsUtf8(zOut, pOut);
if( addNewLine ) fPutsUtf8("\n", pOut);
return TCL_OK;
}
#endif /* defined(_WIN32) */
const char *sqlite3_analyzer_init_proc(Tcl_Interp *interp){
#if defined(_WIN32)
Tcl_CreateObjCommand(interp, "puts", subst_puts, 0, 0);
#else
(void)interp;
#endif
return
BEGIN_STRING
INCLUDE $ROOT/tool/spaceanal.tcl

103
tool/srctree-check.tcl Normal file
View File

@ -0,0 +1,103 @@
#!/usr/bin/tclsh
#
# Run this script from the top of the source tree in order to confirm that
# various aspects of the source tree are up-to-date. Items checked include:
#
# * Makefile.msc and autoconf/Makefile.msc agree
# * src/ctime.tcl is consistent with tool/mkctimec.tcl
# * VERSION agrees with autoconf/tea/configure.ac
# * src/pragma.h agrees with tool/mkpragmatab.tcl
#
# Other tests might be added later.
#
# Error messages are printed and the process exists non-zero if problems
# are found. If everything is ok, no output is generated and the process
# exits with 0.
#
# Read an entire file.
#
proc readfile {filename} {
set fd [open $filename rb]
set txt [read $fd]
close $fd
return $txt
}
# Find the root of the tree.
#
set ROOT [file dir [file dir [file normalize $argv0]]]
# Name of the TCL interpreter
#
set TCLSH [info nameofexe]
# Number of errors seen.
#
set NERR 0
######################### configure ###########################################
set conf [readfile $ROOT/configure]
set vers [readfile $ROOT/VERSION]
if {[string first $vers $conf]<=0} {
puts "ERROR: ./configure does not agree with ./VERSION"
puts "...... Fix: run autoconf"
incr NERR
}
unset conf
######################### autoconf/tea/configure.ac ###########################
set confac [readfile $ROOT/autoconf/tea/configure.ac]
set vers [readfile $ROOT/VERSION]
set pattern {AC_INIT([sqlite],[}
append pattern [string trim $vers]
append pattern {])}
if {[string first $pattern $confac]<=0} {
puts "ERROR: ./autoconf/tea/configure.ac does not agree with ./VERSION"
puts "...... Fix: manually edit ./autoconf/tea/configure.ac and put the"
puts "...... correct version number in AC_INIT()"
incr NERR
}
unset confac
######################### autoconf/Makefile.msc ###############################
set f1 [readfile $ROOT/autoconf/Makefile.msc]
exec $TCLSH $ROOT/tool/mkmsvcmin.tcl $ROOT/Makefile.msc tmp1.txt
set f2 [readfile tmp1.txt]
file delete tmp1.txt
if {$f1 != $f2} {
puts "ERROR: ./autoconf/Makefile.msc does not agree with ./Makefile.msc"
puts "...... Fix: tclsh tool/mkmsvcmin.tcl"
incr NERR
}
######################### src/pragma.h ########################################
set f1 [readfile $ROOT/src/pragma.h]
exec $TCLSH $ROOT/tool/mkpragmatab.tcl tmp2.txt
set f2 [readfile tmp2.txt]
file delete tmp2.txt
if {$f1 != $f2} {
puts "ERROR: ./src/pragma.h does not agree with ./tool/mkpragmatab.tcl"
puts "...... Fix: tclsh tool/mkpragmatab.tcl"
incr NERR
}
######################### src/ctime.c ########################################
set f1 [readfile $ROOT/src/ctime.c]
exec $TCLSH $ROOT/tool/mkctimec.tcl tmp3.txt
set f2 [readfile tmp3.txt]
file delete tmp3.txt
if {$f1 != $f2} {
puts "ERROR: ./src/ctime.c does not agree with ./tool/mkctimec.tcl"
puts "..... Fix: tclsh tool/mkctimec.tcl"
incr NERR
}
# If any errors are seen, exit 1 so that the build will fail.
#
if {$NERR>0} {exit 1}