Merge recent trunk enhancements, include table-valued functions.
FossilOrigin-Name: e9196d566690de0e9815f8cd85be7844322b5a79
This commit is contained in:
+10
-3
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# Convert input text into a C string
|
||||
#
|
||||
{
|
||||
gsub(/\\/,"\\\\");
|
||||
gsub(/\"/,"\\\"");
|
||||
print "\"" $0 "\\n\"";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user