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:
11
tool/cktclsh.sh
Normal file
11
tool/cktclsh.sh
Normal 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
|
@ -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
|
||||
*********************************************************************************/
|
||||
|
43
tool/lemon.c
43
tool/lemon.c
@ -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--; } */
|
||||
|
132
tool/lempar.c
132
tool/lempar.c
@ -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 ){
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
69
tool/mktoolzip.tcl
Normal 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]
|
||||
}
|
@ -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",
|
||||
|
@ -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);
|
||||
|
@ -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]
|
||||
|
444
tool/sqldiff.c
444
tool/sqldiff.c
@ -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 */
|
||||
|
@ -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
103
tool/srctree-check.tcl
Normal 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}
|
Reference in New Issue
Block a user