Merge recent trunk enhancements, include table-valued functions.

FossilOrigin-Name: e9196d566690de0e9815f8cd85be7844322b5a79
This commit is contained in:
drh
2015-08-20 23:54:25 +00:00
84 changed files with 4451 additions and 998 deletions
+10 -3
View File
@@ -312,6 +312,13 @@ IF "%VisualStudioVersion%" == "11.0" (
)
)
REM
REM NOTE: This is the name of the sub-directory where the Windows 10.0 SDK
REM libraries may be found. It is only used when compiling with the
REM Windows 10.0 SDK.
REM
SET WIN10LIBDIR=10.0.10240.0
REM
REM NOTE: Check if this is the Windows Phone SDK. If so, a different batch
REM file is necessary to setup the build environment. Since the variable
@@ -482,9 +489,9 @@ FOR %%P IN (%PLATFORMS%) DO (
REM different directory naming conventions.
REM
IF DEFINED USE_WINV100_NSDKLIBPATH (
CALL :fn_AppendVariable NSDKLIBPATH \..\10\lib\10.0.10030.0\um\x86
CALL :fn_AppendVariable NSDKLIBPATH \..\10\lib\%WIN10LIBDIR%\um\x86
CALL :fn_CopyVariable UniversalCRTSdkDir PSDKLIBPATH
CALL :fn_AppendVariable PSDKLIBPATH Lib\10.0.10030.0\um\%%D
CALL :fn_AppendVariable PSDKLIBPATH Lib\%WIN10LIBDIR%\um\%%D
) ELSE IF DEFINED USE_WINV63_NSDKLIBPATH (
CALL :fn_AppendVariable NSDKLIBPATH \lib\winv6.3\um\x86
) ELSE IF "%VisualStudioVersion%" == "12.0" (
@@ -507,7 +514,7 @@ FOR %%P IN (%PLATFORMS%) DO (
IF DEFINED SET_NUCRTLIBPATH (
IF DEFINED UniversalCRTSdkDir (
CALL :fn_CopyVariable UniversalCRTSdkDir NUCRTLIBPATH
CALL :fn_AppendVariable NUCRTLIBPATH \lib\winv10.0\ucrt\x86
CALL :fn_AppendVariable NUCRTLIBPATH \lib\%WIN10LIBDIR%\ucrt\x86
)
)
+1 -4
View File
@@ -261,12 +261,9 @@ proc getExtraFileListXmlChunk { packageFlavor vsVersion } {
"\r\n " {DependsOn="Microsoft.VCLibs, version=12.0"}]
}
2015 {
#
# TODO: Is the ".AppLocal" suffix always needed here?
#
return [appendArgs \
"\r\n " AppliesTo=\" $appliesTo \" \
"\r\n " {DependsOn="Microsoft.VCLibs.AppLocal, version=14.0"}]
"\r\n " {DependsOn="Microsoft.VCLibs, version=14.0"}]
}
default {
return ""
+223 -109
View File
@@ -15,13 +15,20 @@
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "sqlite3.h"
static int pagesize = 1024; /* Size of a database page */
static int db = -1; /* File descriptor for reading the DB */
static int mxPage = 0; /* Last page number */
static int perLine = 16; /* HEX elements to print per line */
static struct GlobalData {
int pagesize; /* Size of a database page */
int dbfd; /* File descriptor for reading the DB */
int mxPage; /* Last page number */
int perLine; /* HEX elements to print per line */
int bRaw; /* True to access db file via OS APIs */
sqlite3_file *pFd; /* File descriptor for non-raw mode */
sqlite3 *pDb; /* Database handle that owns pFd */
} g = {1024, -1, 0, 16, 0, 0, 0};
typedef long long int i64; /* Datatype for 64-bit integers */
@@ -56,24 +63,122 @@ static void out_of_memory(void){
exit(1);
}
/*
** Open a database connection.
*/
static sqlite3 *openDatabase(const char *zPrg, const char *zName){
sqlite3 *db = 0;
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_URI;
int rc = sqlite3_open_v2(zName, &db, flags, 0);
if( rc!=SQLITE_OK ){
const char *zErr = sqlite3_errmsg(db);
fprintf(stderr, "%s: can't open %s (%s)\n", zPrg, zName, zErr);
sqlite3_close(db);
exit(1);
}
return db;
}
/**************************************************************************
** Beginning of low-level file access functions.
**
** All low-level access to the database file read by this program is
** performed using the following four functions:
**
** fileOpen() - open the db file
** fileClose() - close the db file
** fileRead() - read raw data from the db file
** fileGetsize() - return the size of the db file in bytes
*/
/*
** Open the database file.
*/
static void fileOpen(const char *zPrg, const char *zName){
assert( g.dbfd<0 );
if( g.bRaw==0 ){
int rc;
void *pArg = (void *)(&g.pFd);
g.pDb = openDatabase(zPrg, zName);
rc = sqlite3_file_control(g.pDb, "main", SQLITE_FCNTL_FILE_POINTER, pArg);
if( rc!=SQLITE_OK ){
fprintf(stderr,
"%s: failed to obtain fd for %s (SQLite too old?)\n", zPrg, zName
);
exit(1);
}
}else{
g.dbfd = open(zName, O_RDONLY);
if( g.dbfd<0 ){
fprintf(stderr,"%s: can't open %s\n", zPrg, zName);
exit(1);
}
}
}
/*
** Close the database file opened by fileOpen()
*/
static void fileClose(){
if( g.bRaw==0 ){
sqlite3_close(g.pDb);
g.pDb = 0;
g.pFd = 0;
}else{
close(g.dbfd);
g.dbfd = -1;
}
}
/*
** Read content from the file.
**
** Space to hold the content is obtained from malloc() and needs to be
** freed by the caller.
** Space to hold the content is obtained from sqlite3_malloc() and needs
** to be freed by the caller.
*/
static unsigned char *getContent(int ofst, int nByte){
static unsigned char *fileRead(sqlite3_int64 ofst, int nByte){
unsigned char *aData;
int got;
aData = malloc(nByte+32);
aData = sqlite3_malloc(nByte+32);
if( aData==0 ) out_of_memory();
memset(aData, 0, nByte+32);
lseek(db, ofst, SEEK_SET);
got = read(db, aData, nByte);
if( got>0 && got<nByte ) memset(aData+got, 0, nByte-got);
if( g.bRaw==0 ){
int rc = g.pFd->pMethods->xRead(g.pFd, (void*)aData, nByte, ofst);
if( rc!=SQLITE_OK && rc!=SQLITE_IOERR_SHORT_READ ){
fprintf(stderr, "error in xRead() - %d\n", rc);
exit(1);
}
}else{
lseek(g.dbfd, ofst, SEEK_SET);
got = read(g.dbfd, aData, nByte);
if( got>0 && got<nByte ) memset(aData+got, 0, nByte-got);
}
return aData;
}
/*
** Return the size of the file in byte.
*/
static sqlite3_int64 fileGetsize(void){
sqlite3_int64 res = 0;
if( g.bRaw==0 ){
int rc = g.pFd->pMethods->xFileSize(g.pFd, &res);
if( rc!=SQLITE_OK ){
fprintf(stderr, "error in xFileSize() - %d\n", rc);
exit(1);
}
}else{
struct stat sbuf;
fstat(g.dbfd, &sbuf);
res = (sqlite3_int64)(sbuf.st_size);
}
return res;
}
/*
** End of low-level file access functions.
**************************************************************************/
/*
** Print a range of bytes as hex and as ascii.
*/
@@ -98,17 +203,17 @@ static unsigned char *print_byte_range(
zOfstFmt = " %08x: ";
}
aData = getContent(ofst, nByte);
for(i=0; i<nByte; i += perLine){
aData = fileRead(ofst, nByte);
for(i=0; i<nByte; i += g.perLine){
fprintf(stdout, zOfstFmt, i+printOfst);
for(j=0; j<perLine; j++){
for(j=0; j<g.perLine; j++){
if( i+j>nByte ){
fprintf(stdout, " ");
}else{
fprintf(stdout,"%02x ", aData[i+j]);
}
}
for(j=0; j<perLine; j++){
for(j=0; j<g.perLine; j++){
if( i+j>nByte ){
fprintf(stdout, " ");
}else{
@@ -126,11 +231,11 @@ static unsigned char *print_byte_range(
static void print_page(int iPg){
int iStart;
unsigned char *aData;
iStart = (iPg-1)*pagesize;
iStart = (iPg-1)*g.pagesize;
fprintf(stdout, "Page %d: (offsets 0x%x..0x%x)\n",
iPg, iStart, iStart+pagesize-1);
aData = print_byte_range(iStart, pagesize, 0);
free(aData);
iPg, iStart, iStart+g.pagesize-1);
aData = print_byte_range(iStart, g.pagesize, 0);
sqlite3_free(aData);
}
@@ -267,14 +372,14 @@ static i64 localPayload(i64 nPayload, char cType){
i64 nLocal;
if( cType==13 ){
/* Table leaf */
maxLocal = pagesize-35;
minLocal = (pagesize-12)*32/255-23;
maxLocal = g.pagesize-35;
minLocal = (g.pagesize-12)*32/255-23;
}else{
maxLocal = (pagesize-12)*64/255-23;
minLocal = (pagesize-12)*32/255-23;
maxLocal = (g.pagesize-12)*64/255-23;
minLocal = (g.pagesize-12)*32/255-23;
}
if( nPayload>maxLocal ){
surplus = minLocal + (nPayload-minLocal)%(pagesize-4);
surplus = minLocal + (nPayload-minLocal)%(g.pagesize-4);
if( surplus<=maxLocal ){
nLocal = surplus;
}else{
@@ -581,8 +686,8 @@ static void decode_btree_page(
printf(" key: lx=left-child n=payload-size r=rowid\n");
}
if( showMap ){
zMap = malloc(pagesize);
memset(zMap, '.', pagesize);
zMap = sqlite3_malloc(g.pagesize);
memset(zMap, '.', g.pagesize);
memset(zMap, '1', hdrSize);
memset(&zMap[hdrSize], 'H', iCellPtr);
memset(&zMap[hdrSize+iCellPtr], 'P', 2*nCell);
@@ -611,10 +716,10 @@ static void decode_btree_page(
}
if( showMap ){
printf("Page map: (H=header P=cell-index 1=page-1-header .=free-space)\n");
for(i=0; i<pagesize; i+=64){
for(i=0; i<g.pagesize; i+=64){
printf(" %03x: %.64s\n", i, &zMap[i]);
}
free(zMap);
sqlite3_free(zMap);
}
}
@@ -623,14 +728,13 @@ static void decode_btree_page(
*/
static void decode_trunk_page(
int pgno, /* The page number */
int pagesize, /* Size of each page */
int detail, /* Show leaf pages if true */
int recursive /* Follow the trunk change if true */
){
int n, i;
unsigned char *a;
while( pgno>0 ){
a = getContent((pgno-1)*pagesize, pagesize);
a = fileRead((pgno-1)*g.pagesize, g.pagesize);
printf("Decode of freelist trunk page %d:\n", pgno);
print_decode_line(a, 0, 4, "Next freelist trunk page");
print_decode_line(a, 4, 4, "Number of entries on this page");
@@ -650,7 +754,7 @@ static void decode_trunk_page(
}else{
pgno = (int)decodeInt32(&a[0]);
}
free(a);
sqlite3_free(a);
}
}
@@ -669,9 +773,9 @@ static void page_usage_msg(int pgno, const char *zFormat, ...){
va_start(ap, zFormat);
zMsg = sqlite3_vmprintf(zFormat, ap);
va_end(ap);
if( pgno<=0 || pgno>mxPage ){
if( pgno<=0 || pgno>g.mxPage ){
printf("ERROR: page %d out of range 1..%d: %s\n",
pgno, mxPage, zMsg);
pgno, g.mxPage, zMsg);
sqlite3_free(zMsg);
return;
}
@@ -719,12 +823,12 @@ static void page_usage_cell(
if( nLocal<nPayload ){
int ovfl = decodeInt32(a+nLocal);
int cnt = 0;
while( ovfl && (cnt++)<mxPage ){
while( ovfl && (cnt++)<g.mxPage ){
page_usage_msg(ovfl, "overflow %d from cell %d of page %d",
cnt, cellno, pgno);
a = getContent((ovfl-1)*pagesize, 4);
a = fileRead((ovfl-1)*g.pagesize, 4);
ovfl = decodeInt32(a);
free(a);
sqlite3_free(a);
}
}
}
@@ -745,8 +849,8 @@ static void page_usage_btree(
int i;
int hdr = pgno==1 ? 100 : 0;
if( pgno<=0 || pgno>mxPage ) return;
a = getContent((pgno-1)*pagesize, pagesize);
if( pgno<=0 || pgno>g.mxPage ) return;
a = fileRead((pgno-1)*g.pagesize, g.pagesize);
switch( a[hdr] ){
case 2: zType = "interior node of index"; break;
case 5: zType = "interior node of table"; break;
@@ -783,7 +887,7 @@ static void page_usage_btree(
page_usage_cell(a[hdr], a+ofst, pgno, i);
}
}
free(a);
sqlite3_free(a);
}
/*
@@ -797,9 +901,9 @@ static void page_usage_freelist(int pgno){
int iNext;
int parent = 1;
while( pgno>0 && pgno<=mxPage && (cnt++)<mxPage ){
while( pgno>0 && pgno<=g.mxPage && (cnt++)<g.mxPage ){
page_usage_msg(pgno, "freelist trunk #%d child of %d", cnt, parent);
a = getContent((pgno-1)*pagesize, pagesize);
a = fileRead((pgno-1)*g.pagesize, g.pagesize);
iNext = decodeInt32(a);
n = decodeInt32(a+4);
for(i=0; i<n; i++){
@@ -807,7 +911,7 @@ static void page_usage_freelist(int pgno){
page_usage_msg(child, "freelist leaf, child %d of trunk page %d",
i, pgno);
}
free(a);
sqlite3_free(a);
parent = pgno;
pgno = iNext;
}
@@ -818,10 +922,10 @@ static void page_usage_freelist(int pgno){
*/
static void page_usage_ptrmap(unsigned char *a){
if( a[55] ){
int usable = pagesize - a[20];
int usable = g.pagesize - a[20];
int pgno = 2;
int perPage = usable/5;
while( pgno<=mxPage ){
while( pgno<=g.mxPage ){
page_usage_msg(pgno, "PTRMAP page covering %d..%d",
pgno+1, pgno+perPage);
pgno += perPage + 1;
@@ -832,7 +936,7 @@ static void page_usage_ptrmap(unsigned char *a){
/*
** Try to figure out how every page in the database file is being used.
*/
static void page_usage_report(const char *zDbName){
static void page_usage_report(const char *zPrg, const char *zDbName){
int i, j;
int rc;
sqlite3 *db;
@@ -841,30 +945,25 @@ static void page_usage_report(const char *zDbName){
char zQuery[200];
/* Avoid the pathological case */
if( mxPage<1 ){
if( g.mxPage<1 ){
printf("empty database\n");
return;
}
/* Open the database file */
rc = sqlite3_open(zDbName, &db);
if( rc ){
printf("cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return;
}
db = openDatabase(zPrg, zDbName);
/* Set up global variables zPageUse[] and mxPage to record page
/* Set up global variables zPageUse[] and g.mxPage to record page
** usages */
zPageUse = sqlite3_malloc( sizeof(zPageUse[0])*(mxPage+1) );
zPageUse = sqlite3_malloc( sizeof(zPageUse[0])*(g.mxPage+1) );
if( zPageUse==0 ) out_of_memory();
memset(zPageUse, 0, sizeof(zPageUse[0])*(mxPage+1));
memset(zPageUse, 0, sizeof(zPageUse[0])*(g.mxPage+1));
/* Discover the usage of each page */
a = getContent(0, 100);
a = fileRead(0, 100);
page_usage_freelist(decodeInt32(a+32));
page_usage_ptrmap(a);
free(a);
sqlite3_free(a);
page_usage_btree(1, 0, 0, "sqlite_master");
sqlite3_exec(db, "PRAGMA writable_schema=ON", 0, 0, 0);
for(j=0; j<2; j++){
@@ -886,7 +985,7 @@ static void page_usage_report(const char *zDbName){
sqlite3_close(db);
/* Print the report and free memory used */
for(i=1; i<=mxPage; i++){
for(i=1; i<=g.mxPage; i++){
printf("%5d: %s\n", i, zPageUse[i] ? zPageUse[i] : "???");
sqlite3_free(zPageUse[i]);
}
@@ -906,26 +1005,26 @@ static void ptrmap_coverage_report(const char *zDbName){
int i;
/* Avoid the pathological case */
if( mxPage<1 ){
if( g.mxPage<1 ){
printf("empty database\n");
return;
}
/* Make sure PTRMAPs are used in this database */
aHdr = getContent(0, 100);
aHdr = fileRead(0, 100);
if( aHdr[55]==0 ){
printf("database does not use PTRMAP pages\n");
return;
}
usable = pagesize - aHdr[20];
usable = g.pagesize - aHdr[20];
perPage = usable/5;
free(aHdr);
sqlite3_free(aHdr);
printf("%5d: root of sqlite_master\n", 1);
for(pgno=2; pgno<=mxPage; pgno += perPage+1){
for(pgno=2; pgno<=g.mxPage; pgno += perPage+1){
printf("%5d: PTRMAP page covering %d..%d\n", pgno,
pgno+1, pgno+perPage);
a = getContent((pgno-1)*pagesize, usable);
for(i=0; i+5<=usable && pgno+1+i/5<=mxPage; i+=5){
a = fileRead((pgno-1)*g.pagesize, usable);
for(i=0; i+5<=usable && pgno+1+i/5<=g.mxPage; i+=5){
const char *zType = "???";
unsigned int iFrom = decodeInt32(&a[i+1]);
switch( a[i] ){
@@ -937,7 +1036,7 @@ static void ptrmap_coverage_report(const char *zDbName){
}
printf("%5d: %s, parent=%u\n", pgno+1+i/5, zType, iFrom);
}
free(a);
sqlite3_free(a);
}
}
@@ -945,8 +1044,10 @@ static void ptrmap_coverage_report(const char *zDbName){
** Print a usage comment
*/
static void usage(const char *argv0){
fprintf(stderr, "Usage %s FILENAME ?args...?\n\n", argv0);
fprintf(stderr, "Usage %s ?--uri? FILENAME ?args...?\n\n", argv0);
fprintf(stderr,
"switches:\n"
" --raw Read db file directly, bypassing SQLite VFS\n"
"args:\n"
" dbheader Show database header\n"
" pgidx Index of how each page is used\n"
@@ -964,58 +1065,71 @@ static void usage(const char *argv0){
}
int main(int argc, char **argv){
struct stat sbuf;
unsigned char zPgSz[2];
if( argc<2 ){
usage(argv[0]);
sqlite3_int64 szFile;
unsigned char *zPgSz;
const char *zPrg = argv[0]; /* Name of this executable */
char **azArg = argv;
int nArg = argc;
/* Check for the "--uri" or "-uri" switch. */
if( nArg>1 ){
if( sqlite3_stricmp("-raw", azArg[1])==0
|| sqlite3_stricmp("--raw", azArg[1])==0
){
g.bRaw = 1;
azArg++;
nArg--;
}
}
if( nArg<2 ){
usage(zPrg);
exit(1);
}
db = open(argv[1], O_RDONLY);
if( db<0 ){
fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
exit(1);
}
zPgSz[0] = 0;
zPgSz[1] = 0;
lseek(db, 16, SEEK_SET);
if( read(db, zPgSz, 2)<2 ) memset(zPgSz, 0, 2);
pagesize = zPgSz[0]*256 + zPgSz[1]*65536;
if( pagesize==0 ) pagesize = 1024;
printf("Pagesize: %d\n", pagesize);
fstat(db, &sbuf);
mxPage = (sbuf.st_size+pagesize-1)/pagesize;
printf("Available pages: 1..%d\n", mxPage);
if( argc==2 ){
fileOpen(zPrg, azArg[1]);
szFile = fileGetsize();
zPgSz = fileRead(16, 2);
g.pagesize = zPgSz[0]*256 + zPgSz[1]*65536;
if( g.pagesize==0 ) g.pagesize = 1024;
sqlite3_free(zPgSz);
printf("Pagesize: %d\n", g.pagesize);
g.mxPage = (szFile+g.pagesize-1)/g.pagesize;
printf("Available pages: 1..%d\n", g.mxPage);
if( nArg==2 ){
int i;
for(i=1; i<=mxPage; i++) print_page(i);
for(i=1; i<=g.mxPage; i++) print_page(i);
}else{
int i;
for(i=2; i<argc; i++){
for(i=2; i<nArg; i++){
int iStart, iEnd;
char *zLeft;
if( strcmp(argv[i], "dbheader")==0 ){
if( strcmp(azArg[i], "dbheader")==0 ){
print_db_header();
continue;
}
if( strcmp(argv[i], "pgidx")==0 ){
page_usage_report(argv[1]);
if( strcmp(azArg[i], "pgidx")==0 ){
page_usage_report(zPrg, azArg[1]);
continue;
}
if( strcmp(argv[i], "ptrmap")==0 ){
ptrmap_coverage_report(argv[1]);
if( strcmp(azArg[i], "ptrmap")==0 ){
ptrmap_coverage_report(azArg[1]);
continue;
}
if( strcmp(argv[i], "help")==0 ){
usage(argv[0]);
if( strcmp(azArg[i], "help")==0 ){
usage(zPrg);
continue;
}
if( !isdigit(argv[i][0]) ){
fprintf(stderr, "%s: unknown option: [%s]\n", argv[0], argv[i]);
if( !isdigit(azArg[i][0]) ){
fprintf(stderr, "%s: unknown option: [%s]\n", zPrg, azArg[i]);
continue;
}
iStart = strtol(argv[i], &zLeft, 0);
iStart = strtol(azArg[i], &zLeft, 0);
if( zLeft && strcmp(zLeft,"..end")==0 ){
iEnd = mxPage;
iEnd = g.mxPage;
}else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){
iEnd = strtol(&zLeft[2], 0, 0);
}else if( zLeft && zLeft[0]=='b' ){
@@ -1023,15 +1137,15 @@ int main(int argc, char **argv){
unsigned char *a;
if( iStart==1 ){
ofst = hdrSize = 100;
nByte = pagesize-100;
nByte = g.pagesize-100;
}else{
hdrSize = 0;
ofst = (iStart-1)*pagesize;
nByte = pagesize;
ofst = (iStart-1)*g.pagesize;
nByte = g.pagesize;
}
a = getContent(ofst, nByte);
a = fileRead(ofst, nByte);
decode_btree_page(a, iStart, hdrSize, &zLeft[1]);
free(a);
sqlite3_free(a);
continue;
}else if( zLeft && zLeft[0]=='t' ){
int detail = 0;
@@ -1041,15 +1155,15 @@ int main(int argc, char **argv){
if( zLeft[i]=='r' ) recursive = 1;
if( zLeft[i]=='d' ) detail = 1;
}
decode_trunk_page(iStart, pagesize, detail, recursive);
decode_trunk_page(iStart, detail, recursive);
continue;
}else{
iEnd = iStart;
}
if( iStart<1 || iEnd<iStart || iEnd>mxPage ){
if( iStart<1 || iEnd<iStart || iEnd>g.mxPage ){
fprintf(stderr,
"Page argument should be LOWER?..UPPER?. Range 1 to %d\n",
mxPage);
g.mxPage);
exit(1);
}
while( iStart<=iEnd ){
@@ -1058,6 +1172,6 @@ int main(int argc, char **argv){
}
}
}
close(db);
fileClose();
return 0;
}
+20 -26
View File
@@ -26,7 +26,7 @@ proc is_without_rowid {tname} {
#
proc usage {} {
set argv0 [file rootname [file tail [info nameofexecutable]]]
puts stderr "Usage: $argv0 database-name"
puts stderr "Usage: $argv0 \[--pageinfo] \[--stats] database-name"
exit 1
}
set file_to_analyze {}
@@ -142,6 +142,7 @@ set tabledef {CREATE TABLE space_used(
is_index boolean, -- TRUE if it is an index, false for a table
nentry int, -- Number of entries in the BTree
leaf_entries int, -- Number of leaf entries
depth int, -- Depth of the b-tree
payload int, -- Total amount of data stored in this table or index
ovfl_payload int, -- Total amount of data stored on overflow pages
ovfl_cnt int, -- Number of entries that use overflow
@@ -164,22 +165,9 @@ db eval {CREATE TEMP TABLE dbstat AS SELECT * FROM temp.stat
ORDER BY name, path}
db eval {DROP TABLE temp.stat}
proc isleaf {pagetype is_index} {
return [expr {$pagetype == "leaf" || ($pagetype == "internal" && $is_index)}]
}
proc isoverflow {pagetype is_index} {
return [expr {$pagetype == "overflow"}]
}
proc isinternal {pagetype is_index} {
return [expr {$pagetype == "internal" && $is_index==0}]
}
db func isleaf isleaf
db func isinternal isinternal
db func isoverflow isoverflow
set isCompressed 0
set compressOverhead 0
set depth 0
set sql { SELECT name, tbl_name FROM sqlite_master WHERE rootpage>0 }
foreach {name tblname} [concat sqlite_master sqlite_master [db eval $sql]] {
@@ -188,18 +176,20 @@ foreach {name tblname} [concat sqlite_master sqlite_master [db eval $sql]] {
db eval {
SELECT
sum(ncell) AS nentry,
sum(isleaf(pagetype, $idx_btree) * ncell) AS leaf_entries,
sum((pagetype=='leaf')*ncell) AS leaf_entries,
sum(payload) AS payload,
sum(isoverflow(pagetype, $idx_btree) * payload) AS ovfl_payload,
sum((pagetype=='overflow') * payload) AS ovfl_payload,
sum(path LIKE '%+000000') AS ovfl_cnt,
max(mx_payload) AS mx_payload,
sum(isinternal(pagetype, $idx_btree)) AS int_pages,
sum(isleaf(pagetype, $idx_btree)) AS leaf_pages,
sum(isoverflow(pagetype, $idx_btree)) AS ovfl_pages,
sum(isinternal(pagetype, $idx_btree) * unused) AS int_unused,
sum(isleaf(pagetype, $idx_btree) * unused) AS leaf_unused,
sum(isoverflow(pagetype, $idx_btree) * unused) AS ovfl_unused,
sum(pgsize) AS compressed_size
sum(pagetype=='internal') AS int_pages,
sum(pagetype=='leaf') AS leaf_pages,
sum(pagetype=='overflow') AS ovfl_pages,
sum((pagetype=='internal') * unused) AS int_unused,
sum((pagetype=='leaf') * unused) AS leaf_unused,
sum((pagetype=='overflow') * unused) AS ovfl_unused,
sum(pgsize) AS compressed_size,
max((length(CASE WHEN path LIKE '%+%' THEN '' ELSE path END)+3)/4)
AS depth
FROM temp.dbstat WHERE name = $name
} break
@@ -235,6 +225,7 @@ foreach {name tblname} [concat sqlite_master sqlite_master [db eval $sql]] {
$is_index,
$nentry,
$leaf_entries,
$depth,
$payload,
$ovfl_payload,
$ovfl_cnt,
@@ -344,7 +335,9 @@ proc subreport {title where showFrag} {
int(sum(int_unused)) AS int_unused,
int(sum(ovfl_unused)) AS ovfl_unused,
int(sum(gap_cnt)) AS gap_cnt,
int(sum(compressed_size)) AS compressed_size
int(sum(compressed_size)) AS compressed_size,
int(max(depth)) AS depth,
count(*) AS cnt
FROM space_used WHERE $where" {} {}
# Output the sub-report title, nicely decorated with * characters.
@@ -381,7 +374,7 @@ proc subreport {title where showFrag} {
"]
set avg_fanout [mem eval "
SELECT (sum(leaf_pages+int_pages)-$nTab)/sum(int_pages) FROM space_used
WHERE $where AND is_index = 0
WHERE $where
"]
set avg_fanout [format %.2f $avg_fanout]
}
@@ -399,6 +392,7 @@ proc subreport {title where showFrag} {
statline {Bytes used after compression} $compressed_size $pct
}
statline {Bytes of payload} $payload $payload_percent
if {$cnt==1} {statline {B-tree depth} $depth}
statline {Average payload per entry} $avg_payload
statline {Average unused bytes per entry} $avg_unused
if {[info exists avg_fanout]} {
+629 -19
View File
@@ -23,6 +23,7 @@
#include <stdarg.h>
#include <ctype.h>
#include <string.h>
#include <assert.h>
#include "sqlite3.h"
/*
@@ -259,7 +260,12 @@ static void namelistFree(char **az){
** CREATE TABLE t5(rowid,_rowid_,oid);
** az = 0 // The rowid is not accessible
*/
static char **columnNames(const char *zDb, const char *zTab, int *pnPKey){
static char **columnNames(
const char *zDb, /* Database ("main" or "aux") to query */
const char *zTab, /* Name of table to return details of */
int *pnPKey, /* OUT: Number of PK columns */
int *pbRowid /* OUT: True if PK is an implicit rowid */
){
char **az = 0; /* List of column names to be returned */
int naz = 0; /* Number of entries in az[] */
sqlite3_stmt *pStmt; /* SQL statement being run */
@@ -338,6 +344,15 @@ static char **columnNames(const char *zDb, const char *zTab, int *pnPKey){
}
sqlite3_finalize(pStmt);
if( az ) az[naz] = 0;
/* If it is non-NULL, set *pbRowid to indicate whether or not the PK of
** this table is an implicit rowid (*pbRowid==1) or not (*pbRowid==0). */
if( pbRowid ) *pbRowid = (az[0]==0);
/* If this table has an implicit rowid for a PK, figure out how to refer
** to it. There are three options - "rowid", "_rowid_" and "oid". Any
** of these will work, unless the table has an explicit column of the
** same name. */
if( az[0]==0 ){
const char *azRowid[] = { "rowid", "_rowid_", "oid" };
for(i=0; i<sizeof(azRowid)/sizeof(azRowid[0]); i++){
@@ -434,7 +449,7 @@ static void dump_table(const char *zTab, FILE *out){
}
sqlite3_finalize(pStmt);
if( !g.bSchemaOnly ){
az = columnNames("aux", zTab, &nPk);
az = columnNames("aux", zTab, &nPk, 0);
strInit(&ins);
if( az==0 ){
pStmt = db_prepare("SELECT * FROM aux.%s", zId);
@@ -511,7 +526,7 @@ static void diff_one_table(const char *zTab, FILE *out){
** database and show the results. This is used for testing
** and debugging of the columnNames() function.
*/
az = columnNames("aux",zTab, &nPk);
az = columnNames("aux",zTab, &nPk, 0);
if( az==0 ){
printf("Rowid not accessible for %s\n", zId);
}else{
@@ -540,8 +555,8 @@ static void diff_one_table(const char *zTab, FILE *out){
goto end_diff_one_table;
}
az = columnNames("main", zTab, &nPk);
az2 = columnNames("aux", zTab, &nPk2);
az = columnNames("main", zTab, &nPk, 0);
az2 = columnNames("aux", zTab, &nPk2, 0);
if( az && az2 ){
for(n=0; az[n]; n++){
if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
@@ -719,6 +734,606 @@ end_diff_one_table:
return;
}
/*
** Check that table zTab exists and has the same schema in both the "main"
** and "aux" databases currently opened by the global db handle. If they
** do not, output an error message on stderr and exit(1). Otherwise, if
** the schemas do match, return control to the caller.
*/
static void checkSchemasMatch(const char *zTab){
sqlite3_stmt *pStmt = db_prepare(
"SELECT A.sql=B.sql FROM main.sqlite_master A, aux.sqlite_master B"
" WHERE A.name=%Q AND B.name=%Q", zTab, zTab
);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
if( sqlite3_column_int(pStmt,0)==0 ){
runtimeError("schema changes for table %s", safeId(zTab));
}
}else{
runtimeError("table %s missing from one or both databases", safeId(zTab));
}
sqlite3_finalize(pStmt);
}
/**************************************************************************
** The following code is copied from fossil. It is used to generate the
** fossil delta blobs sometimes used in RBU update records.
*/
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u8;
/*
** The width of a hash window in bytes. The algorithm only works if this
** is a power of 2.
*/
#define NHASH 16
/*
** The current state of the rolling hash.
**
** z[] holds the values that have been hashed. z[] is a circular buffer.
** z[i] is the first entry and z[(i+NHASH-1)%NHASH] is the last entry of
** the window.
**
** Hash.a is the sum of all elements of hash.z[]. Hash.b is a weighted
** sum. Hash.b is z[i]*NHASH + z[i+1]*(NHASH-1) + ... + z[i+NHASH-1]*1.
** (Each index for z[] should be module NHASH, of course. The %NHASH operator
** is omitted in the prior expression for brevity.)
*/
typedef struct hash hash;
struct hash {
u16 a, b; /* Hash values */
u16 i; /* Start of the hash window */
char z[NHASH]; /* The values that have been hashed */
};
/*
** Initialize the rolling hash using the first NHASH characters of z[]
*/
static void hash_init(hash *pHash, const char *z){
u16 a, b, i;
a = b = 0;
for(i=0; i<NHASH; i++){
a += z[i];
b += (NHASH-i)*z[i];
pHash->z[i] = z[i];
}
pHash->a = a & 0xffff;
pHash->b = b & 0xffff;
pHash->i = 0;
}
/*
** Advance the rolling hash by a single character "c"
*/
static void hash_next(hash *pHash, int c){
u16 old = pHash->z[pHash->i];
pHash->z[pHash->i] = (char)c;
pHash->i = (pHash->i+1)&(NHASH-1);
pHash->a = pHash->a - old + (char)c;
pHash->b = pHash->b - NHASH*old + pHash->a;
}
/*
** Return a 32-bit hash value
*/
static u32 hash_32bit(hash *pHash){
return (pHash->a & 0xffff) | (((u32)(pHash->b & 0xffff))<<16);
}
/*
** Write an base-64 integer into the given buffer.
*/
static void putInt(unsigned int v, char **pz){
static const char zDigits[] =
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
/* 123456789 123456789 123456789 123456789 123456789 123456789 123 */
int i, j;
char zBuf[20];
if( v==0 ){
*(*pz)++ = '0';
return;
}
for(i=0; v>0; i++, v>>=6){
zBuf[i] = zDigits[v&0x3f];
}
for(j=i-1; j>=0; j--){
*(*pz)++ = zBuf[j];
}
}
/*
** Return the number digits in the base-64 representation of a positive integer
*/
static int digit_count(int v){
unsigned int i, x;
for(i=1, x=64; (unsigned int)v>=x; i++, x <<= 6){}
return i;
}
/*
** Compute a 32-bit checksum on the N-byte buffer. Return the result.
*/
static unsigned int checksum(const char *zIn, size_t N){
const unsigned char *z = (const unsigned char *)zIn;
unsigned sum0 = 0;
unsigned sum1 = 0;
unsigned sum2 = 0;
unsigned sum3 = 0;
while(N >= 16){
sum0 += ((unsigned)z[0] + z[4] + z[8] + z[12]);
sum1 += ((unsigned)z[1] + z[5] + z[9] + z[13]);
sum2 += ((unsigned)z[2] + z[6] + z[10]+ z[14]);
sum3 += ((unsigned)z[3] + z[7] + z[11]+ z[15]);
z += 16;
N -= 16;
}
while(N >= 4){
sum0 += z[0];
sum1 += z[1];
sum2 += z[2];
sum3 += z[3];
z += 4;
N -= 4;
}
sum3 += (sum2 << 8) + (sum1 << 16) + (sum0 << 24);
switch(N){
case 3: sum3 += (z[2] << 8);
case 2: sum3 += (z[1] << 16);
case 1: sum3 += (z[0] << 24);
default: ;
}
return sum3;
}
/*
** Create a new delta.
**
** The delta is written into a preallocated buffer, zDelta, which
** should be at least 60 bytes longer than the target file, zOut.
** The delta string will be NUL-terminated, but it might also contain
** embedded NUL characters if either the zSrc or zOut files are
** binary. This function returns the length of the delta string
** in bytes, excluding the final NUL terminator character.
**
** Output Format:
**
** The delta begins with a base64 number followed by a newline. This
** number is the number of bytes in the TARGET file. Thus, given a
** delta file z, a program can compute the size of the output file
** simply by reading the first line and decoding the base-64 number
** found there. The delta_output_size() routine does exactly this.
**
** After the initial size number, the delta consists of a series of
** literal text segments and commands to copy from the SOURCE file.
** A copy command looks like this:
**
** NNN@MMM,
**
** where NNN is the number of bytes to be copied and MMM is the offset
** into the source file of the first byte (both base-64). If NNN is 0
** it means copy the rest of the input file. Literal text is like this:
**
** NNN:TTTTT
**
** where NNN is the number of bytes of text (base-64) and TTTTT is the text.
**
** The last term is of the form
**
** NNN;
**
** In this case, NNN is a 32-bit bigendian checksum of the output file
** that can be used to verify that the delta applied correctly. All
** numbers are in base-64.
**
** Pure text files generate a pure text delta. Binary files generate a
** delta that may contain some binary data.
**
** Algorithm:
**
** The encoder first builds a hash table to help it find matching
** patterns in the source file. 16-byte chunks of the source file
** sampled at evenly spaced intervals are used to populate the hash
** table.
**
** Next we begin scanning the target file using a sliding 16-byte
** window. The hash of the 16-byte window in the target is used to
** search for a matching section in the source file. When a match
** is found, a copy command is added to the delta. An effort is
** made to extend the matching section to regions that come before
** and after the 16-byte hash window. A copy command is only issued
** if the result would use less space that just quoting the text
** literally. Literal text is added to the delta for sections that
** do not match or which can not be encoded efficiently using copy
** commands.
*/
static int rbuDeltaCreate(
const char *zSrc, /* The source or pattern file */
unsigned int lenSrc, /* Length of the source file */
const char *zOut, /* The target file */
unsigned int lenOut, /* Length of the target file */
char *zDelta /* Write the delta into this buffer */
){
unsigned int i, base;
char *zOrigDelta = zDelta;
hash h;
int nHash; /* Number of hash table entries */
int *landmark; /* Primary hash table */
int *collide; /* Collision chain */
int lastRead = -1; /* Last byte of zSrc read by a COPY command */
/* Add the target file size to the beginning of the delta
*/
putInt(lenOut, &zDelta);
*(zDelta++) = '\n';
/* If the source file is very small, it means that we have no
** chance of ever doing a copy command. Just output a single
** literal segment for the entire target and exit.
*/
if( lenSrc<=NHASH ){
putInt(lenOut, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, zOut, lenOut);
zDelta += lenOut;
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
return zDelta - zOrigDelta;
}
/* Compute the hash table used to locate matching sections in the
** source file.
*/
nHash = lenSrc/NHASH;
collide = sqlite3_malloc( nHash*2*sizeof(int) );
landmark = &collide[nHash];
memset(landmark, -1, nHash*sizeof(int));
memset(collide, -1, nHash*sizeof(int));
for(i=0; i<lenSrc-NHASH; i+=NHASH){
int hv;
hash_init(&h, &zSrc[i]);
hv = hash_32bit(&h) % nHash;
collide[i/NHASH] = landmark[hv];
landmark[hv] = i/NHASH;
}
/* Begin scanning the target file and generating copy commands and
** literal sections of the delta.
*/
base = 0; /* We have already generated everything before zOut[base] */
while( base+NHASH<lenOut ){
int iSrc, iBlock;
int bestCnt, bestOfst=0, bestLitsz=0;
hash_init(&h, &zOut[base]);
i = 0; /* Trying to match a landmark against zOut[base+i] */
bestCnt = 0;
while( 1 ){
int hv;
int limit = 250;
hv = hash_32bit(&h) % nHash;
iBlock = landmark[hv];
while( iBlock>=0 && (limit--)>0 ){
/*
** The hash window has identified a potential match against
** landmark block iBlock. But we need to investigate further.
**
** Look for a region in zOut that matches zSrc. Anchor the search
** at zSrc[iSrc] and zOut[base+i]. Do not include anything prior to
** zOut[base] or after zOut[outLen] nor anything after zSrc[srcLen].
**
** Set cnt equal to the length of the match and set ofst so that
** zSrc[ofst] is the first element of the match. litsz is the number
** of characters between zOut[base] and the beginning of the match.
** sz will be the overhead (in bytes) needed to encode the copy
** command. Only generate copy command if the overhead of the
** copy command is less than the amount of literal text to be copied.
*/
int cnt, ofst, litsz;
int j, k, x, y;
int sz;
/* Beginning at iSrc, match forwards as far as we can. j counts
** the number of characters that match */
iSrc = iBlock*NHASH;
for(
j=0, x=iSrc, y=base+i;
(unsigned int)x<lenSrc && (unsigned int)y<lenOut;
j++, x++, y++
){
if( zSrc[x]!=zOut[y] ) break;
}
j--;
/* Beginning at iSrc-1, match backwards as far as we can. k counts
** the number of characters that match */
for(k=1; k<iSrc && (unsigned int)k<=i; k++){
if( zSrc[iSrc-k]!=zOut[base+i-k] ) break;
}
k--;
/* Compute the offset and size of the matching region */
ofst = iSrc-k;
cnt = j+k+1;
litsz = i-k; /* Number of bytes of literal text before the copy */
/* sz will hold the number of bytes needed to encode the "insert"
** command and the copy command, not counting the "insert" text */
sz = digit_count(i-k)+digit_count(cnt)+digit_count(ofst)+3;
if( cnt>=sz && cnt>bestCnt ){
/* Remember this match only if it is the best so far and it
** does not increase the file size */
bestCnt = cnt;
bestOfst = iSrc-k;
bestLitsz = litsz;
}
/* Check the next matching block */
iBlock = collide[iBlock];
}
/* We have a copy command that does not cause the delta to be larger
** than a literal insert. So add the copy command to the delta.
*/
if( bestCnt>0 ){
if( bestLitsz>0 ){
/* Add an insert command before the copy */
putInt(bestLitsz,&zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], bestLitsz);
zDelta += bestLitsz;
base += bestLitsz;
}
base += bestCnt;
putInt(bestCnt, &zDelta);
*(zDelta++) = '@';
putInt(bestOfst, &zDelta);
*(zDelta++) = ',';
if( bestOfst + bestCnt -1 > lastRead ){
lastRead = bestOfst + bestCnt - 1;
}
bestCnt = 0;
break;
}
/* If we reach this point, it means no match is found so far */
if( base+i+NHASH>=lenOut ){
/* We have reached the end of the file and have not found any
** matches. Do an "insert" for everything that does not match */
putInt(lenOut-base, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], lenOut-base);
zDelta += lenOut-base;
base = lenOut;
break;
}
/* Advance the hash by one character. Keep looking for a match */
hash_next(&h, zOut[base+i+NHASH]);
i++;
}
}
/* Output a final "insert" record to get all the text at the end of
** the file that does not match anything in the source file.
*/
if( base<lenOut ){
putInt(lenOut-base, &zDelta);
*(zDelta++) = ':';
memcpy(zDelta, &zOut[base], lenOut-base);
zDelta += lenOut-base;
}
/* Output the final checksum record. */
putInt(checksum(zOut, lenOut), &zDelta);
*(zDelta++) = ';';
sqlite3_free(collide);
return zDelta - zOrigDelta;
}
/*
** End of code copied from fossil.
**************************************************************************/
static void strPrintfArray(
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]);
}
}
static void getRbudiffQuery(
const char *zTab,
char **azCol,
int nPK,
int bOtaRowid,
Str *pSql
){
int i;
/* First the newly inserted rows: **/
strPrintf(pSql, "SELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, -1);
strPrintf(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);
strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK);
strPrintf(pSql, "\n)");
/* Deleted rows: */
strPrintf(pSql, "\nUNION ALL\nSELECT ");
strPrintfArray(pSql, ", ", "%s", azCol, nPK);
if( azCol[nPK] ){
strPrintf(pSql, ", ");
strPrintfArray(pSql, ", ", "NULL", &azCol[nPK], -1);
}
strPrintf(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);
strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK);
strPrintf(pSql, "\n) ");
/* 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 ");
strPrintfArray(pSql, ", ", "n.%s", azCol, nPK);
strPrintf(pSql, ",\n");
strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE n.%s END", &azCol[nPK], -1
);
if( bOtaRowid==0 ){
strPrintf(pSql, ", '");
strPrintfArray(pSql, "", ".", azCol, nPK);
strPrintf(pSql, "' ||\n");
}else{
strPrintf(pSql, ",\n");
}
strPrintfArray(pSql, " ||\n",
" CASE WHEN n.%s IS o.%s THEN '.' ELSE 'x' END", &azCol[nPK], -1
);
strPrintf(pSql, "\nAS ota_control, ");
strPrintfArray(pSql, ", ", "NULL", azCol, nPK);
strPrintf(pSql, ",\n");
strPrintfArray(pSql, " ,\n",
" CASE WHEN n.%s IS o.%s THEN NULL ELSE o.%s END", &azCol[nPK], -1
);
strPrintf(pSql, "\nFROM main.%Q AS o, aux.%Q AS n\nWHERE ", zTab, zTab);
strPrintfArray(pSql, " AND ", "(n.%Q IS o.%Q)", azCol, nPK);
strPrintf(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);
}
static void rbudiff_one_table(const char *zTab, FILE *out){
int bOtaRowid; /* True to use an ota_rowid column */
int nPK; /* Number of primary key columns in table */
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_stmt *pStmt = 0;
/* --rbu mode must use real primary keys. */
g.bSchemaPK = 1;
/* Check that the schemas of the two tables match. Exit early otherwise. */
checkSchemasMatch(zTab);
/* Grab the column names and PK details for the table(s). If no usable PK
** columns are found, bail out early. */
azCol = columnNames("main", zTab, &nPK, &bOtaRowid);
if( azCol==0 ){
runtimeError("table %s has no usable PK columns", zTab);
}
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);");
/* Get the SQL for the query to retrieve data from the two databases */
getRbudiffQuery(zTab, azCol, nPK, bOtaRowid, &sql);
/* 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(");
pStmt = db_prepare("%s", sql.z);
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
** printed again. */
if( ct.z ){
fprintf(out, "%s\n", ct.z);
strFree(&ct);
}
/* Output the first part of the INSERT statement */
fprintf(out, "%s", insert.z);
if( sqlite3_column_type(pStmt, nCol)==SQLITE_INTEGER ){
for(i=0; i<=nCol; i++){
if( i>0 ) fprintf(out, ", ");
printQuoted(out, sqlite3_column_value(pStmt, i));
}
}else{
char *zOtaControl;
int nOtaControl = sqlite3_column_bytes(pStmt, nCol);
zOtaControl = (char*)sqlite3_malloc(nOtaControl);
memcpy(zOtaControl, sqlite3_column_text(pStmt, nCol), nOtaControl+1);
for(i=0; i<nCol; i++){
int bDone = 0;
if( i>=nPK
&& sqlite3_column_type(pStmt, i)==SQLITE_BLOB
&& sqlite3_column_type(pStmt, nCol+1+i)==SQLITE_BLOB
){
const char *aSrc = sqlite3_column_blob(pStmt, nCol+1+i);
int nSrc = sqlite3_column_bytes(pStmt, nCol+1+i);
const char *aFinal = sqlite3_column_blob(pStmt, i);
int nFinal = sqlite3_column_bytes(pStmt, i);
char *aDelta;
int nDelta;
aDelta = sqlite3_malloc(nFinal + 60);
nDelta = rbuDeltaCreate(aSrc, nSrc, aFinal, nFinal, aDelta);
if( nDelta<nFinal ){
int j;
fprintf(out, "x'");
for(j=0; j<nDelta; j++) fprintf(out, "%02x", (u8)aDelta[j]);
fprintf(out, "'");
zOtaControl[i-bOtaRowid] = 'f';
bDone = 1;
}
sqlite3_free(aDelta);
}
if( bDone==0 ){
printQuoted(out, sqlite3_column_value(pStmt, i));
}
fprintf(out, ", ");
}
fprintf(out, "'%s'", zOtaControl);
sqlite3_free(zOtaControl);
}
/* And the closing bracket of the insert statement */
fprintf(out, ");\n");
}
sqlite3_finalize(pStmt);
strFree(&ct);
strFree(&sql);
strFree(&insert);
}
/*
** Display a summary of differences between two versions of the same
** table table.
@@ -760,8 +1375,8 @@ static void summarize_one_table(const char *zTab, FILE *out){
goto end_summarize_one_table;
}
az = columnNames("main", zTab, &nPk);
az2 = columnNames("aux", zTab, &nPk2);
az = columnNames("main", zTab, &nPk, 0);
az2 = columnNames("aux", zTab, &nPk2, 0);
if( az && az2 ){
for(n=0; az[n]; n++){
if( sqlite3_stricmp(az[n],az2[n])!=0 ) break;
@@ -931,18 +1546,9 @@ static void changeset_one_table(const char *zTab, FILE *out){
int i, k; /* Loop counters */
const char *zSep; /* List separator */
pStmt = db_prepare(
"SELECT A.sql=B.sql FROM main.sqlite_master A, aux.sqlite_master B"
" WHERE A.name=%Q AND B.name=%Q", zTab, zTab
);
if( SQLITE_ROW==sqlite3_step(pStmt) ){
if( sqlite3_column_int(pStmt,0)==0 ){
runtimeError("schema changes for table %s", safeId(zTab));
}
}else{
runtimeError("table %s missing from one or both databases", safeId(zTab));
}
sqlite3_finalize(pStmt);
/* Check that the schemas of the two tables match. Exit early otherwise. */
checkSchemasMatch(zTab);
pStmt = db_prepare("PRAGMA main.table_info=%Q", zTab);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
nCol++;
@@ -1118,6 +1724,7 @@ static void showHelp(void){
" --changeset FILE Write a CHANGESET into FILE\n"
" -L|--lib LIBRARY Load an SQLite extension library\n"
" --primarykey Use schema-defined PRIMARY KEYs\n"
" --rbu Output SQL to create/populate RBU table(s)\n"
" --schema Show only differences in the schema\n"
" --summary Show only a summary of the differences\n"
" --table TAB Show only differences in table TAB\n"
@@ -1170,6 +1777,9 @@ int main(int argc, char **argv){
if( strcmp(z,"primarykey")==0 ){
g.bSchemaPK = 1;
}else
if( strcmp(z,"rbu")==0 ){
xDiff = rbudiff_one_table;
}else
if( strcmp(z,"schema")==0 ){
g.bSchemaOnly = 1;
}else
+1
View File
@@ -3,6 +3,7 @@
# Convert input text into a C string
#
{
gsub(/\\/,"\\\\");
gsub(/\"/,"\\\"");
print "\"" $0 "\\n\"";
}