diff options
author | James Turner <jturner@cvs.openbsd.org> | 2015-12-23 20:07:39 +0000 |
---|---|---|
committer | James Turner <jturner@cvs.openbsd.org> | 2015-12-23 20:07:39 +0000 |
commit | e428b61d371061fb076d32510c0e21eb2c4aa538 (patch) | |
tree | 1b5c34c2f5b7e70da3895ec94da2de72090c8f6b /lib/libsqlite3/tool | |
parent | 683347a40bb36f2f05d126582b768efaf0b409cd (diff) |
Update sqlite3 to 3.9.2. Bump major, regen .pc and header. Changes
available here: http://sqlite.org/changes.html
Tested in bulk by aja@. ok landry@
Diffstat (limited to 'lib/libsqlite3/tool')
-rwxr-xr-x | lib/libsqlite3/tool/build-all-msvc.bat | 35 | ||||
-rw-r--r-- | lib/libsqlite3/tool/fuzzershell.c | 6 | ||||
-rw-r--r-- | lib/libsqlite3/tool/lemon.c | 299 | ||||
-rw-r--r-- | lib/libsqlite3/tool/lempar.c | 112 | ||||
-rw-r--r-- | lib/libsqlite3/tool/mksqlite3c.tcl | 5 | ||||
-rw-r--r-- | lib/libsqlite3/tool/mksqlite3h.tcl | 57 | ||||
-rw-r--r-- | lib/libsqlite3/tool/mkvsix.tcl | 5 | ||||
-rw-r--r-- | lib/libsqlite3/tool/showdb.c | 330 | ||||
-rw-r--r-- | lib/libsqlite3/tool/spaceanal.tcl | 58 | ||||
-rw-r--r-- | lib/libsqlite3/tool/sqldiff.c | 648 | ||||
-rw-r--r-- | lib/libsqlite3/tool/tostr.awk | 1 |
11 files changed, 1249 insertions, 307 deletions
diff --git a/lib/libsqlite3/tool/build-all-msvc.bat b/lib/libsqlite3/tool/build-all-msvc.bat index 9f5176db9de..4842dc40744 100755 --- a/lib/libsqlite3/tool/build-all-msvc.bat +++ b/lib/libsqlite3/tool/build-all-msvc.bat @@ -313,6 +313,27 @@ IF "%VisualStudioVersion%" == "11.0" ( )
REM
+REM NOTE: This is the name of the sub-directory where the UCRT libraries may
+REM be found. It is only used when compiling against the UCRT.
+REM
+IF DEFINED UCRTVersion (
+ SET NUCRTVER=%UCRTVersion%
+) ELSE (
+ SET NUCRTVER=10.0.10240.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
+IF DEFINED WindowsSDKLibVersion (
+ SET WIN10SDKVER=%WindowsSDKLibVersion:\=%
+) ELSE (
+ SET WIN10SDKVER=%NUCRTVER%
+)
+
+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
REM values involved here may contain parenthesis, using GOTO instead of
@@ -354,6 +375,7 @@ FOR %%P IN (%PLATFORMS%) DO ( REM
CALL :fn_UnsetVariable CommandPromptType
CALL :fn_UnsetVariable DevEnvDir
+ CALL :fn_UnsetVariable DNX_HOME
CALL :fn_UnsetVariable ExtensionSdkDir
CALL :fn_UnsetVariable Framework35Version
CALL :fn_UnsetVariable Framework40Version
@@ -365,14 +387,19 @@ FOR %%P IN (%PLATFORMS%) DO ( CALL :fn_UnsetVariable INCLUDE
CALL :fn_UnsetVariable LIB
CALL :fn_UnsetVariable LIBPATH
+ CALL :fn_UnsetVariable NETFXSDKDir
CALL :fn_UnsetVariable Platform
+ CALL :fn_UnsetVariable UCRTVersion
CALL :fn_UnsetVariable UniversalCRTSdkDir
REM CALL :fn_UnsetVariable VCINSTALLDIR
CALL :fn_UnsetVariable VSINSTALLDIR
+ CALL :fn_UnsetVariable WindowsLibPath
CALL :fn_UnsetVariable WindowsPhoneKitDir
CALL :fn_UnsetVariable WindowsSdkDir
CALL :fn_UnsetVariable WindowsSdkDir_35
CALL :fn_UnsetVariable WindowsSdkDir_old
+ CALL :fn_UnsetVariable WindowsSDKLibVersion
+ CALL :fn_UnsetVariable WindowsSDKVersion
CALL :fn_UnsetVariable WindowsSDK_ExecutablePath_x86
CALL :fn_UnsetVariable WindowsSDK_ExecutablePath_x64
@@ -482,9 +509,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_CopyVariable UniversalCRTSdkDir PSDKLIBPATH
- CALL :fn_AppendVariable PSDKLIBPATH Lib\10.0.10030.0\um\%%D
+ CALL :fn_AppendVariable NSDKLIBPATH \..\10\lib\%WIN10SDKVER%\um\x86
+ CALL :fn_CopyVariable WindowsSdkDir PSDKLIBPATH
+ CALL :fn_AppendVariable PSDKLIBPATH lib\%WIN10SDKVER%\um\%%D
) ELSE IF DEFINED USE_WINV63_NSDKLIBPATH (
CALL :fn_AppendVariable NSDKLIBPATH \lib\winv6.3\um\x86
) ELSE IF "%VisualStudioVersion%" == "12.0" (
@@ -507,7 +534,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\%NUCRTVER%\ucrt\x86
)
)
diff --git a/lib/libsqlite3/tool/fuzzershell.c b/lib/libsqlite3/tool/fuzzershell.c index 2778c9d2f76..6754a071e37 100644 --- a/lib/libsqlite3/tool/fuzzershell.c +++ b/lib/libsqlite3/tool/fuzzershell.c @@ -322,6 +322,7 @@ static void showHelp(void){ "Options:\n" " --autovacuum Enable AUTOVACUUM mode\n" " --database FILE Use database FILE instead of an in-memory database\n" +" --disable-lookaside Turn off lookaside memory\n" " --heap SZ MIN Memory allocator uses SZ bytes & min allocation MIN\n" " --help Show this help text\n" " --lookaside N SZ Configure lookaside for N slots of SZ bytes each\n" @@ -457,6 +458,7 @@ int main(int argc, char **argv){ const char *zDbName = 0; /* Name of an on-disk database file to open */ iBegin = timeOfDay(); + sqlite3_shutdown(); zFailCode = getenv("TEST_FAILURE"); g.zArgv0 = argv[0]; zPrompt = "<stdin>"; @@ -473,6 +475,10 @@ int main(int argc, char **argv){ zDbName = argv[i+1]; i += 1; }else + if( strcmp(z,"disable-lookaside")==0 ){ + nLook = 1; + szLook = 0; + }else if( strcmp(z, "f")==0 && i+1<argc ){ i++; goto addNewInFile; diff --git a/lib/libsqlite3/tool/lemon.c b/lib/libsqlite3/tool/lemon.c index 89d992c37ec..2e8054b5ccb 100644 --- a/lib/libsqlite3/tool/lemon.c +++ b/lib/libsqlite3/tool/lemon.c @@ -55,7 +55,7 @@ static char *msort(char*,char**,int(*)(const char*,const char*)); ** saying they are unsafe. So we define our own versions of those routines too. ** ** There are three routines here: lemon_sprintf(), lemon_vsprintf(), and -** lemon_addtext(). The first two are replacements for sprintf() and vsprintf(). +** lemon_addtext(). The first two are replacements for sprintf() and vsprintf(). ** The third is a helper routine for vsnprintf() that adds texts to the end of a ** buffer, making sure the buffer is always zero-terminated. ** @@ -316,7 +316,8 @@ enum e_action { RRCONFLICT, /* Was a reduce, but part of a conflict */ SH_RESOLVED, /* Was a shift. Precedence resolved conflict */ RD_RESOLVED, /* Was reduce. Precedence resolved conflict */ - NOT_USED /* Deleted by compression */ + NOT_USED, /* Deleted by compression */ + SHIFTREDUCE /* Shift first, then reduce */ }; /* Every shift or reduce operation is stored as one of the following */ @@ -340,7 +341,9 @@ struct state { struct action *ap; /* Array of actions for this state */ int nTknAct, nNtAct; /* Number of actions on terminals and nonterminals */ int iTknOfst, iNtOfst; /* yy_action[] offset for terminals and nonterms */ - int iDflt; /* Default action */ + int iDfltReduce; /* Default action is to REDUCE by this rule */ + struct rule *pDfltReduce;/* The default REDUCE rule. */ + int autoReduce; /* True if this is an auto-reduce state */ }; #define NO_OFFSET (-2147483647) @@ -360,6 +363,7 @@ struct lemon { struct state **sorted; /* Table of states sorted by state number */ struct rule *rule; /* List of all rules */ int nstate; /* Number of states */ + int nxstate; /* nstate with tail degenerate states removed */ int nrule; /* Number of rules */ int nsymbol; /* Number of terminal and nonterminal symbols */ int nterminal; /* Number of terminal symbols */ @@ -385,7 +389,8 @@ struct lemon { char *outname; /* Name of the current output file */ char *tokenprefix; /* A prefix added to token names in the .h file */ int nconflict; /* Number of parsing conflicts */ - int tablesize; /* Size of the parse tables */ + int nactiontab; /* Number of entries in the yy_action[] table */ + int tablesize; /* Total table size of all tables in bytes */ int basisflag; /* Print only basis configurations */ int has_fallback; /* True if any %fallback is seen in the grammar */ int nolinenosflag; /* True if #line statements should not be printed */ @@ -483,7 +488,7 @@ static int actioncmp( if( rc==0 ){ rc = (int)ap1->type - (int)ap2->type; } - if( rc==0 && ap1->type==REDUCE ){ + if( rc==0 && (ap1->type==REDUCE || ap1->type==SHIFTREDUCE) ){ rc = ap1->x.rp->index - ap2->x.rp->index; } if( rc==0 ){ @@ -1375,14 +1380,16 @@ void Configlist_closure(struct lemon *lemp) /* Sort the configuration list */ void Configlist_sort(){ - current = (struct config *)msort((char *)current,(char **)&(current->next),Configcmp); + current = (struct config*)msort((char*)current,(char**)&(current->next), + Configcmp); currentend = 0; return; } /* Sort the basis configuration list */ void Configlist_sortbasis(){ - basis = (struct config *)msort((char *)current,(char **)&(current->bp),Configcmp); + basis = (struct config*)msort((char*)current,(char**)&(current->bp), + Configcmp); basisend = 0; return; } @@ -1480,6 +1487,18 @@ static void handle_T_option(char *z){ lemon_strcpy(user_templatename, z); } +/* forward reference */ +static const char *minimum_size_type(int lwr, int upr, int *pnByte); + +/* Print a single line of the "Parser Stats" output +*/ +static void stats_line(const char *zLabel, int iValue){ + int nLabel = lemonStrlen(zLabel); + printf(" %s%.*s %5d\n", zLabel, + 35-nLabel, "................................", + iValue); +} + /* The main program. Parse the command line and do it... */ int main(int argc, char **argv) { @@ -1611,10 +1630,15 @@ int main(int argc, char **argv) if( !mhflag ) ReportHeader(&lem); } if( statistics ){ - printf("Parser statistics: %d terminals, %d nonterminals, %d rules\n", - lem.nterminal, lem.nsymbol - lem.nterminal, lem.nrule); - printf(" %d states, %d parser table entries, %d conflicts\n", - lem.nstate, lem.tablesize, lem.nconflict); + printf("Parser statistics:\n"); + stats_line("terminal symbols", lem.nterminal); + stats_line("non-terminal symbols", lem.nsymbol - lem.nterminal); + stats_line("total symbols", lem.nsymbol); + stats_line("rules", lem.nrule); + stats_line("states", lem.nxstate); + stats_line("conflicts", lem.nconflict); + stats_line("action table entries", lem.nactiontab); + stats_line("total table size (bytes)", lem.tablesize); } if( lem.nconflict > 0 ){ fprintf(stderr,"%d parsing conflicts.\n",lem.nconflict); @@ -1873,7 +1897,8 @@ static int handleswitch(int i, FILE *err) dv = strtod(cp,&end); if( *end ){ if( err ){ - fprintf(err,"%sillegal character in floating-point argument.\n",emsg); + fprintf(err, + "%sillegal character in floating-point argument.\n",emsg); errline(i,(int)((char*)end-(char*)argv[i]),err); } errcnt++; @@ -2939,15 +2964,14 @@ void Reprint(struct lemon *lemp) } } -void ConfigPrint(FILE *fp, struct config *cfp) -{ - struct rule *rp; +/* Print a single rule. +*/ +void RulePrint(FILE *fp, struct rule *rp, int iCursor){ struct symbol *sp; int i, j; - rp = cfp->rp; fprintf(fp,"%s ::=",rp->lhs->name); for(i=0; i<=rp->nrhs; i++){ - if( i==cfp->dot ) fprintf(fp," *"); + if( i==iCursor ) fprintf(fp," *"); if( i==rp->nrhs ) break; sp = rp->rhs[i]; if( sp->type==MULTITERMINAL ){ @@ -2961,6 +2985,12 @@ void ConfigPrint(FILE *fp, struct config *cfp) } } +/* Print the rule for a configuration. +*/ +void ConfigPrint(FILE *fp, struct config *cfp){ + RulePrint(fp, cfp->rp, cfp->dot); +} + /* #define TEST */ #if 0 /* Print a set */ @@ -3000,15 +3030,30 @@ char *tag; /* Print an action to the given file descriptor. Return FALSE if ** nothing was actually printed. */ -int PrintAction(struct action *ap, FILE *fp, int indent){ +int PrintAction( + struct action *ap, /* The action to print */ + FILE *fp, /* Print the action here */ + int indent /* Indent by this amount */ +){ int result = 1; switch( ap->type ){ - case SHIFT: - fprintf(fp,"%*s shift %d",indent,ap->sp->name,ap->x.stp->statenum); + case SHIFT: { + struct state *stp = ap->x.stp; + fprintf(fp,"%*s shift %-7d",indent,ap->sp->name,stp->statenum); + break; + } + case REDUCE: { + struct rule *rp = ap->x.rp; + fprintf(fp,"%*s reduce %-7d",indent,ap->sp->name,rp->index); + RulePrint(fp, rp, -1); break; - case REDUCE: - fprintf(fp,"%*s reduce %d",indent,ap->sp->name,ap->x.rp->index); + } + case SHIFTREDUCE: { + struct rule *rp = ap->x.rp; + fprintf(fp,"%*s shift-reduce %-7d",indent,ap->sp->name,rp->index); + RulePrint(fp, rp, -1); break; + } case ACCEPT: fprintf(fp,"%*s accept",indent,ap->sp->name); break; @@ -3017,16 +3062,16 @@ int PrintAction(struct action *ap, FILE *fp, int indent){ break; case SRCONFLICT: case RRCONFLICT: - fprintf(fp,"%*s reduce %-3d ** Parsing conflict **", + fprintf(fp,"%*s reduce %-7d ** Parsing conflict **", indent,ap->sp->name,ap->x.rp->index); break; case SSCONFLICT: - fprintf(fp,"%*s shift %-3d ** Parsing conflict **", + fprintf(fp,"%*s shift %-7d ** Parsing conflict **", indent,ap->sp->name,ap->x.stp->statenum); break; case SH_RESOLVED: if( showPrecedenceConflict ){ - fprintf(fp,"%*s shift %-3d -- dropped by precedence", + fprintf(fp,"%*s shift %-7d -- dropped by precedence", indent,ap->sp->name,ap->x.stp->statenum); }else{ result = 0; @@ -3034,7 +3079,7 @@ int PrintAction(struct action *ap, FILE *fp, int indent){ break; case RD_RESOLVED: if( showPrecedenceConflict ){ - fprintf(fp,"%*s reduce %-3d -- dropped by precedence", + fprintf(fp,"%*s reduce %-7d -- dropped by precedence", indent,ap->sp->name,ap->x.rp->index); }else{ result = 0; @@ -3047,7 +3092,7 @@ int PrintAction(struct action *ap, FILE *fp, int indent){ return result; } -/* Generate the "y.output" log file */ +/* Generate the "*.out" log file */ void ReportOutput(struct lemon *lemp) { int i; @@ -3058,7 +3103,7 @@ void ReportOutput(struct lemon *lemp) fp = file_open(lemp,".out","wb"); if( fp==0 ) return; - for(i=0; i<lemp->nstate; i++){ + for(i=0; i<lemp->nxstate; i++){ stp = lemp->sorted[i]; fprintf(fp,"State %d:\n",stp->statenum); if( lemp->basisflag ) cfp=stp->bp; @@ -3166,10 +3211,11 @@ PRIVATE int compute_action(struct lemon *lemp, struct action *ap) { int act; switch( ap->type ){ - case SHIFT: act = ap->x.stp->statenum; break; - case REDUCE: act = ap->x.rp->index + lemp->nstate; break; - case ERROR: act = lemp->nstate + lemp->nrule; break; - case ACCEPT: act = lemp->nstate + lemp->nrule + 1; break; + case SHIFT: act = ap->x.stp->statenum; break; + case SHIFTREDUCE: act = ap->x.rp->index + lemp->nstate; break; + case REDUCE: act = ap->x.rp->index + lemp->nstate+lemp->nrule; break; + case ERROR: act = lemp->nstate + lemp->nrule*2; break; + case ACCEPT: act = lemp->nstate + lemp->nrule*2 + 1; break; default: act = -1; break; } return act; @@ -3228,7 +3274,8 @@ PRIVATE FILE *tplt_open(struct lemon *lemp) } in = fopen(user_templatename,"rb"); if( in==0 ){ - fprintf(stderr,"Can't open the template file \"%s\".\n",user_templatename); + fprintf(stderr,"Can't open the template file \"%s\".\n", + user_templatename); lemp->errorcnt++; return 0; } @@ -3313,7 +3360,10 @@ void emit_destructor_code( }else if( sp->destructor ){ cp = sp->destructor; fprintf(out,"{\n"); (*lineno)++; - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,sp->destLineno,lemp->filename); } + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,sp->destLineno,lemp->filename); + } }else if( lemp->vardest ){ cp = lemp->vardest; if( cp==0 ) return; @@ -3510,13 +3560,19 @@ PRIVATE void emit_code( /* Generate code to do the reduce action */ if( rp->code ){ - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,rp->line,lemp->filename); } + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,rp->line,lemp->filename); + } fprintf(out,"{%s",rp->code); for(cp=rp->code; *cp; cp++){ if( *cp=='\n' ) (*lineno)++; } /* End loop */ fprintf(out,"}\n"); (*lineno)++; - if (!lemp->nolinenosflag) { (*lineno)++; tplt_linedir(out,*lineno,lemp->outname); } + if( !lemp->nolinenosflag ){ + (*lineno)++; + tplt_linedir(out,*lineno,lemp->outname); + } } /* End if( rp->code ) */ return; @@ -3647,24 +3703,32 @@ void print_stack_union( /* ** Return the name of a C datatype able to represent values between -** lwr and upr, inclusive. +** lwr and upr, inclusive. If pnByte!=NULL then also write the sizeof +** for that type (1, 2, or 4) into *pnByte. */ -static const char *minimum_size_type(int lwr, int upr){ +static const char *minimum_size_type(int lwr, int upr, int *pnByte){ + const char *zType = "int"; + int nByte = 4; if( lwr>=0 ){ if( upr<=255 ){ - return "unsigned char"; + zType = "unsigned char"; + nByte = 1; }else if( upr<65535 ){ - return "unsigned short int"; + zType = "unsigned short int"; + nByte = 2; }else{ - return "unsigned int"; + zType = "unsigned int"; + nByte = 4; } }else if( lwr>=-127 && upr<=127 ){ - return "signed char"; + zType = "signed char"; + nByte = 1; }else if( lwr>=-32767 && upr<32767 ){ - return "short"; - }else{ - return "int"; + zType = "short"; + nByte = 2; } + if( pnByte ) *pnByte = nByte; + return zType; } /* @@ -3689,7 +3753,7 @@ static int axset_compare(const void *a, const void *b){ int c; c = p2->nAction - p1->nAction; if( c==0 ){ - c = p2->iOrder - p1->iOrder; + c = p1->iOrder - p2->iOrder; } assert( c!=0 || p1==p2 ); return c; @@ -3728,7 +3792,9 @@ void ReportTable( struct action *ap; struct rule *rp; struct acttab *pActtab; - int i, j, n; + int i, j, n, sz; + int szActionType; /* sizeof(YYACTIONTYPE) */ + int szCodeType; /* sizeof(YYCODETYPE) */ const char *name; int mnTknOfst, mxTknOfst; int mnNtOfst, mxNtOfst; @@ -3769,10 +3835,10 @@ void ReportTable( /* Generate the defines */ fprintf(out,"#define YYCODETYPE %s\n", - minimum_size_type(0, lemp->nsymbol+1)); lineno++; + minimum_size_type(0, lemp->nsymbol+1, &szCodeType)); lineno++; fprintf(out,"#define YYNOCODE %d\n",lemp->nsymbol+1); lineno++; fprintf(out,"#define YYACTIONTYPE %s\n", - minimum_size_type(0, lemp->nstate+lemp->nrule+5)); lineno++; + minimum_size_type(0,lemp->nstate+lemp->nrule*2+5,&szActionType)); lineno++; if( lemp->wildcard ){ fprintf(out,"#define YYWILDCARD %d\n", lemp->wildcard->index); lineno++; @@ -3808,36 +3874,24 @@ void ReportTable( if( mhflag ){ fprintf(out,"#endif\n"); lineno++; } - fprintf(out,"#define YYNSTATE %d\n",lemp->nstate); lineno++; - fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; if( lemp->errsym->useCnt ){ - fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; - fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; + fprintf(out,"#define YYERRORSYMBOL %d\n",lemp->errsym->index); lineno++; + fprintf(out,"#define YYERRSYMDT yy%d\n",lemp->errsym->dtnum); lineno++; } if( lemp->has_fallback ){ fprintf(out,"#define YYFALLBACK 1\n"); lineno++; } - tplt_xfer(lemp->name,in,out,&lineno); - /* Generate the action table and its associates: - ** - ** yy_action[] A single table containing all actions. - ** yy_lookahead[] A table containing the lookahead for each entry in - ** yy_action. Used to detect hash collisions. - ** yy_shift_ofst[] For each state, the offset into yy_action for - ** shifting terminals. - ** yy_reduce_ofst[] For each state, the offset into yy_action for - ** shifting non-terminals after a reduce. - ** yy_default[] Default action for each state. + /* Compute the action table, but do not output it yet. The action + ** table must be computed before generating the YYNSTATE macro because + ** we need to know how many states can be eliminated. */ - - /* Compute the actions on all states and count them up */ - ax = (struct axset *) calloc(lemp->nstate*2, sizeof(ax[0])); + ax = (struct axset *) calloc(lemp->nxstate*2, sizeof(ax[0])); if( ax==0 ){ fprintf(stderr,"malloc failed\n"); exit(1); } - for(i=0; i<lemp->nstate; i++){ + for(i=0; i<lemp->nxstate; i++){ stp = lemp->sorted[i]; ax[i*2].stp = stp; ax[i*2].isTkn = 1; @@ -3848,15 +3902,12 @@ void ReportTable( } mxTknOfst = mnTknOfst = 0; mxNtOfst = mnNtOfst = 0; - - /* Compute the action table. In order to try to keep the size of the - ** action table to a minimum, the heuristic of placing the largest action - ** sets first is used. - */ - for(i=0; i<lemp->nstate*2; i++) ax[i].iOrder = i; - qsort(ax, lemp->nstate*2, sizeof(ax[0]), axset_compare); + /* In an effort to minimize the action table size, use the heuristic + ** of placing the largest action sets first */ + for(i=0; i<lemp->nxstate*2; i++) ax[i].iOrder = i; + qsort(ax, lemp->nxstate*2, sizeof(ax[0]), axset_compare); pActtab = acttab_alloc(); - for(i=0; i<lemp->nstate*2 && ax[i].nAction>0; i++){ + for(i=0; i<lemp->nxstate*2 && ax[i].nAction>0; i++){ stp = ax[i].stp; if( ax[i].isTkn ){ for(ap=stp->ap; ap; ap=ap->next){ @@ -3882,11 +3933,50 @@ void ReportTable( if( stp->iNtOfst<mnNtOfst ) mnNtOfst = stp->iNtOfst; if( stp->iNtOfst>mxNtOfst ) mxNtOfst = stp->iNtOfst; } +#if 0 /* Uncomment for a trace of how the yy_action[] table fills out */ + { int jj, nn; + for(jj=nn=0; jj<pActtab->nAction; jj++){ + if( pActtab->aAction[jj].action<0 ) nn++; + } + printf("%4d: State %3d %s n: %2d size: %5d freespace: %d\n", + i, stp->statenum, ax[i].isTkn ? "Token" : "Var ", + ax[i].nAction, pActtab->nAction, nn); + } +#endif } free(ax); + /* Finish rendering the constants now that the action table has + ** been computed */ + fprintf(out,"#define YYNSTATE %d\n",lemp->nxstate); lineno++; + fprintf(out,"#define YYNRULE %d\n",lemp->nrule); lineno++; + fprintf(out,"#define YY_MAX_SHIFT %d\n",lemp->nxstate-1); lineno++; + fprintf(out,"#define YY_MIN_SHIFTREDUCE %d\n",lemp->nstate); lineno++; + i = lemp->nstate + lemp->nrule; + fprintf(out,"#define YY_MAX_SHIFTREDUCE %d\n", i-1); lineno++; + fprintf(out,"#define YY_MIN_REDUCE %d\n", i); lineno++; + i = lemp->nstate + lemp->nrule*2; + fprintf(out,"#define YY_MAX_REDUCE %d\n", i-1); lineno++; + fprintf(out,"#define YY_ERROR_ACTION %d\n", i); lineno++; + fprintf(out,"#define YY_ACCEPT_ACTION %d\n", i+1); lineno++; + fprintf(out,"#define YY_NO_ACTION %d\n", i+2); lineno++; + tplt_xfer(lemp->name,in,out,&lineno); + + /* Now output the action table and its associates: + ** + ** yy_action[] A single table containing all actions. + ** yy_lookahead[] A table containing the lookahead for each entry in + ** yy_action. Used to detect hash collisions. + ** yy_shift_ofst[] For each state, the offset into yy_action for + ** shifting terminals. + ** yy_reduce_ofst[] For each state, the offset into yy_action for + ** shifting non-terminals after a reduce. + ** yy_default[] Default action for each state. + */ + /* Output the yy_action table */ - n = acttab_size(pActtab); + lemp->nactiontab = n = acttab_size(pActtab); + lemp->tablesize += n*szActionType; fprintf(out,"#define YY_ACTTAB_COUNT (%d)\n", n); lineno++; fprintf(out,"static const YYACTIONTYPE yy_action[] = {\n"); lineno++; for(i=j=0; i<n; i++){ @@ -3904,6 +3994,7 @@ void ReportTable( fprintf(out, "};\n"); lineno++; /* Output the yy_lookahead table */ + lemp->tablesize += n*szCodeType; fprintf(out,"static const YYCODETYPE yy_lookahead[] = {\n"); lineno++; for(i=j=0; i<n; i++){ int la = acttab_yylookahead(pActtab, i); @@ -3921,13 +4012,14 @@ void ReportTable( /* Output the yy_shift_ofst[] table */ fprintf(out, "#define YY_SHIFT_USE_DFLT (%d)\n", mnTknOfst-1); lineno++; - n = lemp->nstate; + n = lemp->nxstate; while( n>0 && lemp->sorted[n-1]->iTknOfst==NO_OFFSET ) n--; fprintf(out, "#define YY_SHIFT_COUNT (%d)\n", n-1); lineno++; fprintf(out, "#define YY_SHIFT_MIN (%d)\n", mnTknOfst); lineno++; fprintf(out, "#define YY_SHIFT_MAX (%d)\n", mxTknOfst); lineno++; fprintf(out, "static const %s yy_shift_ofst[] = {\n", - minimum_size_type(mnTknOfst-1, mxTknOfst)); lineno++; + minimum_size_type(mnTknOfst-1, mxTknOfst, &sz)); lineno++; + lemp->tablesize += n*sz; for(i=j=0; i<n; i++){ int ofst; stp = lemp->sorted[i]; @@ -3946,13 +4038,14 @@ void ReportTable( /* Output the yy_reduce_ofst[] table */ fprintf(out, "#define YY_REDUCE_USE_DFLT (%d)\n", mnNtOfst-1); lineno++; - n = lemp->nstate; + n = lemp->nxstate; while( n>0 && lemp->sorted[n-1]->iNtOfst==NO_OFFSET ) n--; fprintf(out, "#define YY_REDUCE_COUNT (%d)\n", n-1); lineno++; fprintf(out, "#define YY_REDUCE_MIN (%d)\n", mnNtOfst); lineno++; fprintf(out, "#define YY_REDUCE_MAX (%d)\n", mxNtOfst); lineno++; fprintf(out, "static const %s yy_reduce_ofst[] = {\n", - minimum_size_type(mnNtOfst-1, mxNtOfst)); lineno++; + minimum_size_type(mnNtOfst-1, mxNtOfst, &sz)); lineno++; + lemp->tablesize += n*sz; for(i=j=0; i<n; i++){ int ofst; stp = lemp->sorted[i]; @@ -3971,11 +4064,12 @@ void ReportTable( /* Output the default action table */ fprintf(out, "static const YYACTIONTYPE yy_default[] = {\n"); lineno++; - n = lemp->nstate; + n = lemp->nxstate; + lemp->tablesize += n*szActionType; for(i=j=0; i<n; i++){ stp = lemp->sorted[i]; if( j==0 ) fprintf(out," /* %5d */ ", i); - fprintf(out, " %4d,", stp->iDflt); + fprintf(out, " %4d,", stp->iDfltReduce+lemp->nstate+lemp->nrule); if( j==9 || i==n-1 ){ fprintf(out, "\n"); lineno++; j = 0; @@ -3991,6 +4085,7 @@ void ReportTable( if( lemp->has_fallback ){ int mx = lemp->nterminal - 1; while( mx>0 && lemp->symbols[mx]->fallback==0 ){ mx--; } + lemp->tablesize += (mx+1)*szCodeType; for(i=0; i<=mx; i++){ struct symbol *p = lemp->symbols[i]; if( p->fallback==0 ){ @@ -4255,6 +4350,32 @@ void CompressTables(struct lemon *lemp) if( ap->type==REDUCE && ap->x.rp==rbest ) ap->type = NOT_USED; } stp->ap = Action_sort(stp->ap); + + for(ap=stp->ap; ap; ap=ap->next){ + if( ap->type==SHIFT ) break; + if( ap->type==REDUCE && ap->x.rp!=rbest ) break; + } + if( ap==0 ){ + stp->autoReduce = 1; + stp->pDfltReduce = rbest; + } + } + + /* Make a second pass over all states and actions. Convert + ** every action that is a SHIFT to an autoReduce state into + ** a SHIFTREDUCE action. + */ + for(i=0; i<lemp->nstate; i++){ + stp = lemp->sorted[i]; + for(ap=stp->ap; ap; ap=ap->next){ + struct state *pNextState; + if( ap->type!=SHIFT ) continue; + pNextState = ap->x.stp; + if( pNextState->autoReduce && pNextState->pDfltReduce!=0 ){ + ap->type = SHIFTREDUCE; + ap->x.rp = pNextState->pDfltReduce; + } + } } } @@ -4295,17 +4416,19 @@ void ResortStates(struct lemon *lemp) for(i=0; i<lemp->nstate; i++){ stp = lemp->sorted[i]; stp->nTknAct = stp->nNtAct = 0; - stp->iDflt = lemp->nstate + lemp->nrule; + stp->iDfltReduce = lemp->nrule; /* Init dflt action to "syntax error" */ stp->iTknOfst = NO_OFFSET; stp->iNtOfst = NO_OFFSET; for(ap=stp->ap; ap; ap=ap->next){ - if( compute_action(lemp,ap)>=0 ){ + int iAction = compute_action(lemp,ap); + if( iAction>=0 ){ if( ap->sp->index<lemp->nterminal ){ stp->nTknAct++; }else if( ap->sp->index<lemp->nsymbol ){ stp->nNtAct++; }else{ - stp->iDflt = compute_action(lemp, ap); + assert( stp->autoReduce==0 || stp->pDfltReduce==ap->x.rp ); + stp->iDfltReduce = iAction - lemp->nstate - lemp->nrule; } } } @@ -4315,6 +4438,10 @@ void ResortStates(struct lemon *lemp) for(i=0; i<lemp->nstate; i++){ lemp->sorted[i]->statenum = i; } + lemp->nxstate = lemp->nstate; + while( lemp->nxstate>1 && lemp->sorted[lemp->nxstate-1]->autoReduce ){ + lemp->nxstate--; + } } diff --git a/lib/libsqlite3/tool/lempar.c b/lib/libsqlite3/tool/lempar.c index fe56d2dc166..cdf4ca5a1a5 100644 --- a/lib/libsqlite3/tool/lempar.c +++ b/lib/libsqlite3/tool/lempar.c @@ -50,15 +50,19 @@ ** ParseARG_PDECL A parameter declaration for the %extra_argument ** ParseARG_STORE Code to store %extra_argument into yypParser ** ParseARG_FETCH Code to extract %extra_argument from yypParser -** YYNSTATE the combined number of states. -** YYNRULE the number of rules in the grammar ** YYERRORSYMBOL is the code number of the error symbol. If not ** defined, then do no error processing. +** YYNSTATE the combined number of states. +** YYNRULE the number of rules in the grammar +** YY_MAX_SHIFT Maximum value for shift actions +** YY_MIN_SHIFTREDUCE Minimum value for shift-reduce actions +** YY_MAX_SHIFTREDUCE Maximum value for shift-reduce actions +** YY_MIN_REDUCE Maximum value for reduce actions +** YY_ERROR_ACTION The yy_action[] code for syntax error +** YY_ACCEPT_ACTION The yy_action[] code for accept +** YY_NO_ACTION The yy_action[] code for no-op */ %% -#define YY_NO_ACTION (YYNSTATE+YYNRULE+2) -#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1) -#define YY_ERROR_ACTION (YYNSTATE+YYNRULE) /* The yyzerominor constant is used to initialize instances of ** YYMINORTYPE objects to zero. */ @@ -85,16 +89,20 @@ static const YYMINORTYPE yyzerominor = { 0 }; ** Suppose the action integer is N. Then the action is determined as ** follows ** -** 0 <= N < YYNSTATE Shift N. That is, push the lookahead +** 0 <= N <= YY_MAX_SHIFT Shift N. That is, push the lookahead ** token onto the stack and goto state N. ** -** YYNSTATE <= N < YYNSTATE+YYNRULE Reduce by rule N-YYNSTATE. +** N between YY_MIN_SHIFTREDUCE Shift to an arbitrary state then +** and YY_MAX_SHIFTREDUCE reduce by rule N-YY_MIN_SHIFTREDUCE. ** -** N == YYNSTATE+YYNRULE A syntax error has occurred. +** N between YY_MIN_REDUCE Reduce by rule N-YY_MIN_REDUCE +** and YY_MAX_REDUCE + +** N == YY_ERROR_ACTION A syntax error has occurred. ** -** N == YYNSTATE+YYNRULE+1 The parser accepts its input. +** N == YY_ACCEPT_ACTION The parser accepts its input. ** -** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused +** N == YY_NO_ACTION No such action. Denotes unused ** slots in the yy_action[] table. ** ** The action table is constructed as a single large table named yy_action[]. @@ -153,9 +161,13 @@ static const YYCODETYPE yyFallback[] = { ** + The semantic value stored at this level of the stack. This is ** the information used by the action routines in the grammar. ** It is sometimes called the "minor" token. +** +** After the "shift" half of a SHIFTREDUCE action, the stateno field +** actually contains the reduce action for the second half of the +** SHIFTREDUCE. */ struct yyStackEntry { - YYACTIONTYPE stateno; /* The state-number */ + YYACTIONTYPE stateno; /* The state-number, or reduce action in SHIFTREDUCE */ YYCODETYPE major; /* The major token value. This is the code ** number for the token at this stack level */ YYMINORTYPE minor; /* The user-supplied minor token value. This @@ -384,11 +396,11 @@ static int yy_find_shift_action( ){ int i; int stateno = pParser->yystack[pParser->yyidx].stateno; - - if( stateno>YY_SHIFT_COUNT - || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){ - return yy_default[stateno]; - } + + if( stateno>=YY_MIN_REDUCE ) return stateno; + assert( stateno <= YY_SHIFT_COUNT ); + i = yy_shift_ofst[stateno]; + if( i==YY_SHIFT_USE_DFLT ) return yy_default[stateno]; assert( iLookAhead!=YYNOCODE ); i += iLookAhead; if( i<0 || i>=YY_ACTTAB_COUNT || yy_lookahead[i]!=iLookAhead ){ @@ -489,7 +501,29 @@ static void yyStackOverflow(yyParser *yypParser, YYMINORTYPE *yypMinor){ } /* -** Perform a shift action. +** Print tracing information for a SHIFT action +*/ +#ifndef NDEBUG +static void yyTraceShift(yyParser *yypParser, int yyNewState){ + if( yyTraceFILE ){ + int i; + if( yyNewState<YYNSTATE ){ + fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); + fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); + for(i=1; i<=yypParser->yyidx; i++) + fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); + fprintf(yyTraceFILE,"\n"); + }else{ + fprintf(yyTraceFILE,"%sShift *\n",yyTracePrompt); + } + } +} +#else +# define yyTraceShift(X,Y) +#endif + +/* +** Perform a shift action. Return the number of errors. */ static void yy_shift( yyParser *yypParser, /* The parser to be shifted */ @@ -522,16 +556,7 @@ static void yy_shift( yytos->stateno = (YYACTIONTYPE)yyNewState; yytos->major = (YYCODETYPE)yyMajor; yytos->minor = *yypMinor; -#ifndef NDEBUG - if( yyTraceFILE && yypParser->yyidx>0 ){ - int i; - fprintf(yyTraceFILE,"%sShift %d\n",yyTracePrompt,yyNewState); - fprintf(yyTraceFILE,"%sStack:",yyTracePrompt); - for(i=1; i<=yypParser->yyidx; i++) - fprintf(yyTraceFILE," %s",yyTokenName[yypParser->yystack[i].major]); - fprintf(yyTraceFILE,"\n"); - } -#endif + yyTraceShift(yypParser, yyNewState); } /* The following table contains information about every rule that @@ -564,8 +589,9 @@ static void yy_reduce( #ifndef NDEBUG if( yyTraceFILE && yyruleno>=0 && yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){ - fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt, - yyRuleName[yyruleno]); + yysize = yyRuleInfo[yyruleno].nrhs; + fprintf(yyTraceFILE, "%sReduce [%s] -> state %d.\n", yyTracePrompt, + yyRuleName[yyruleno], yymsp[-yysize].stateno); } #endif /* NDEBUG */ @@ -602,9 +628,9 @@ static void yy_reduce( yysize = yyRuleInfo[yyruleno].nrhs; yypParser->yyidx -= yysize; yyact = yy_find_reduce_action(yymsp[-yysize].stateno,(YYCODETYPE)yygoto); - if( yyact < YYNSTATE ){ -#ifdef NDEBUG - /* If we are not debugging and the reduce action popped at least + if( yyact <= YY_MAX_SHIFTREDUCE ){ + if( yyact>YY_MAX_SHIFT ) yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; + /* If the reduce action popped at least ** one element off the stack, then we can push the new element back ** onto the stack here, and skip the stack overflow test in yy_shift(). ** That gives a significant speed improvement. */ @@ -614,13 +640,12 @@ static void yy_reduce( yymsp->stateno = (YYACTIONTYPE)yyact; yymsp->major = (YYCODETYPE)yygoto; yymsp->minor = yygotominor; - }else -#endif - { + yyTraceShift(yypParser, yyact); + }else{ yy_shift(yypParser,yyact,yygoto,&yygotominor); } }else{ - assert( yyact == YYNSTATE + YYNRULE + 1 ); + assert( yyact == YY_ACCEPT_ACTION ); yy_accept(yypParser); } } @@ -740,13 +765,13 @@ void Parse( do{ yyact = yy_find_shift_action(yypParser,(YYCODETYPE)yymajor); - if( yyact<YYNSTATE ){ - assert( !yyendofinput ); /* Impossible to shift the $ token */ + if( yyact <= YY_MAX_SHIFTREDUCE ){ + if( yyact > YY_MAX_SHIFT ) yyact += YY_MIN_REDUCE - YY_MIN_SHIFTREDUCE; yy_shift(yypParser,yyact,yymajor,&yyminorunion); yypParser->yyerrcnt--; yymajor = YYNOCODE; - }else if( yyact < YYNSTATE + YYNRULE ){ - yy_reduce(yypParser,yyact-YYNSTATE); + }else if( yyact <= YY_MAX_REDUCE ){ + yy_reduce(yypParser,yyact-YY_MIN_REDUCE); }else{ assert( yyact == YY_ERROR_ACTION ); #ifdef YYERRORSYMBOL @@ -796,7 +821,7 @@ void Parse( yymx != YYERRORSYMBOL && (yyact = yy_find_reduce_action( yypParser->yystack[yypParser->yyidx].stateno, - YYERRORSYMBOL)) >= YYNSTATE + YYERRORSYMBOL)) >= YY_MIN_REDUCE ){ yy_pop_parser_stack(yypParser); } @@ -846,5 +871,10 @@ void Parse( #endif } }while( yymajor!=YYNOCODE && yypParser->yyidx>=0 ); +#ifndef NDEBUG + if( yyTraceFILE ){ + fprintf(yyTraceFILE,"%sReturn\n",yyTracePrompt); + } +#endif return; } diff --git a/lib/libsqlite3/tool/mksqlite3c.tcl b/lib/libsqlite3/tool/mksqlite3c.tcl index e52b036612a..23241e27a6e 100644 --- a/lib/libsqlite3/tool/mksqlite3c.tcl +++ b/lib/libsqlite3/tool/mksqlite3c.tcl @@ -211,7 +211,8 @@ proc copy_file {filename} { puts $out "#if 0" } elseif {!$linemacros && [regexp {^#line} $line]} { # Skip #line directives. - } elseif {$addstatic && ![regexp {^(static|typedef)} $line]} { + } elseif {$addstatic + && ![regexp {^(static|typedef|SQLITE_PRIVATE)} $line]} { # Skip adding the SQLITE_PRIVATE or SQLITE_API keyword before # functions if this header file does not need it. if {![info exists varonly_hdr($tail)] @@ -378,6 +379,8 @@ foreach file { fts3_icu.c sqlite3rbu.c dbstat.c + json1.c + fts5.c } { copy_file tsrc/$file } diff --git a/lib/libsqlite3/tool/mksqlite3h.tcl b/lib/libsqlite3/tool/mksqlite3h.tcl index f68f61a3689..3f59aef4675 100644 --- a/lib/libsqlite3/tool/mksqlite3h.tcl +++ b/lib/libsqlite3/tool/mksqlite3h.tcl @@ -63,14 +63,33 @@ close $in # Set up patterns for recognizing API declarations. # set varpattern {^[a-zA-Z][a-zA-Z_0-9 *]+sqlite3_[_a-zA-Z0-9]+(\[|;| =)} -set declpattern {^ *[a-zA-Z][a-zA-Z_0-9 ]+ \**sqlite3_[_a-zA-Z0-9]+\(} +set declpattern {^ *([a-zA-Z][a-zA-Z_0-9 ]+ \**)(sqlite3_[_a-zA-Z0-9]+)(\(.*)$} # Force the output to use unix line endings, even on Windows. fconfigure stdout -translation lf -# Process the src/sqlite.h.in ext/rtree/sqlite3rtree.h files. +set filelist [subst { + $TOP/src/sqlite.h.in + $TOP/ext/rtree/sqlite3rtree.h + $TOP/ext/fts5/fts5.h +}] + +# These are the functions that accept a variable number of arguments. They +# always need to use the "cdecl" calling convention even when another calling +# convention (e.g. "stcall") is being used for the rest of the library. +set cdecllist { + sqlite3_config + sqlite3_db_config + sqlite3_log + sqlite3_mprintf + sqlite3_snprintf + sqlite3_test_control + sqlite3_vtab_config +} + +# Process the source files. # -foreach file [list $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h] { +foreach file $filelist { set in [open $file] while {![eof $in]} { @@ -79,26 +98,28 @@ foreach file [list $TOP/src/sqlite.h.in $TOP/ext/rtree/sqlite3rtree.h] { # File sqlite3rtree.h contains a line "#include <sqlite3.h>". Omit this # line when copying sqlite3rtree.h into sqlite3.h. # - if {[string match {*#include*<sqlite3.h>*} $line]} continue + if {[string match {*#include*[<"]sqlite3.h[>"]*} $line]} continue regsub -- --VERS-- $line $zVersion line regsub -- --VERSION-NUMBER-- $line $nVersion line regsub -- --SOURCE-ID-- $line "$zDate $zUuid" line - - if {[regexp {define SQLITE_EXTERN extern} $line]} { - puts $line - puts [gets $in] - puts "" - puts "#ifndef SQLITE_API" - puts "# define SQLITE_API" - puts "#endif" - set line "" - } - - if {([regexp $varpattern $line] && ![regexp {^ *typedef} $line]) - || ([regexp $declpattern $line]) - } { + + if {[regexp $varpattern $line] && ![regexp {^ *typedef} $line]} { set line "SQLITE_API $line" + } else { + if {[regexp $declpattern $line all rettype funcname rest]} { + set line SQLITE_API + append line " " [string trim $rettype] + if {[string index $rettype end] ne "*"} { + append line " " + } + if {[lsearch -exact $cdecllist $funcname] >= 0} { + append line SQLITE_CDECL + } else { + append line SQLITE_STDCALL + } + append line " " $funcname $rest + } } puts $line } diff --git a/lib/libsqlite3/tool/mkvsix.tcl b/lib/libsqlite3/tool/mkvsix.tcl index 15ae2b0d3ae..b7c5983dbe2 100644 --- a/lib/libsqlite3/tool/mkvsix.tcl +++ b/lib/libsqlite3/tool/mkvsix.tcl @@ -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 "" diff --git a/lib/libsqlite3/tool/showdb.c b/lib/libsqlite3/tool/showdb.c index c90f410d95f..892cacd5520 100644 --- a/lib/libsqlite3/tool/showdb.c +++ b/lib/libsqlite3/tool/showdb.c @@ -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 */ @@ -57,24 +64,122 @@ static void out_of_memory(void){ } /* +** 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. */ static unsigned char *print_byte_range( @@ -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]); - exit(1); + 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--; + } } - db = open(argv[1], O_RDONLY); - if( db<0 ){ - fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]); + + if( nArg<2 ){ + usage(zPrg); 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; } diff --git a/lib/libsqlite3/tool/spaceanal.tcl b/lib/libsqlite3/tool/spaceanal.tcl index cd3785bd7d1..38d954162e4 100644 --- a/lib/libsqlite3/tool/spaceanal.tcl +++ b/lib/libsqlite3/tool/spaceanal.tcl @@ -26,7 +26,19 @@ 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-filename" + puts stderr { +Analyze the SQLite3 database file specified by the "database-filename" +argument and output a report detailing size and storage efficiency +information for the database and its constituent tables and indexes. + +Options: + + --stats Output SQL text that creates a new database containing + statistics about the database that was analyzed + + --pageinfo Show how each page of the database-file is used +} exit 1 } set file_to_analyze {} @@ -142,6 +154,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 +177,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 +188,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 +237,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 +347,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 +386,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 +404,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]} { diff --git a/lib/libsqlite3/tool/sqldiff.c b/lib/libsqlite3/tool/sqldiff.c index 6d72303545c..9f0b705c40a 100644 --- a/lib/libsqlite3/tool/sqldiff.c +++ b/lib/libsqlite3/tool/sqldiff.c @@ -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; @@ -720,6 +735,606 @@ end_diff_one_table: } /* +** 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 diff --git a/lib/libsqlite3/tool/tostr.awk b/lib/libsqlite3/tool/tostr.awk index b4f48d3db55..83c6cc1a50a 100644 --- a/lib/libsqlite3/tool/tostr.awk +++ b/lib/libsqlite3/tool/tostr.awk @@ -3,6 +3,7 @@ # Convert input text into a C string # { + gsub(/\\/,"\\\\"); gsub(/\"/,"\\\""); print "\"" $0 "\\n\""; } |