add sqlite 3.3.8 to in tree libs

git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@3735 d0543943-73ff-0310-b7d9-9358b9ac24b2
This commit is contained in:
Michael Jerris
2006-12-19 20:11:50 +00:00
parent 3b35430557
commit 165f180162
387 changed files with 234653 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
/*
** A utility for printing the differences between two SQLite database files.
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#define PAGESIZE 1024
static int db1 = -1;
static int db2 = -1;
int main(int argc, char **argv){
int iPg;
unsigned char a1[PAGESIZE], a2[PAGESIZE];
if( argc!=3 ){
fprintf(stderr,"Usage: %s FILENAME FILENAME\n", argv[0]);
exit(1);
}
db1 = open(argv[1], O_RDONLY);
if( db1<0 ){
fprintf(stderr,"%s: can't open %s\n", argv[0], argv[1]);
exit(1);
}
db2 = open(argv[2], O_RDONLY);
if( db2<0 ){
fprintf(stderr,"%s: can't open %s\n", argv[0], argv[2]);
exit(1);
}
iPg = 1;
while( read(db1, a1, PAGESIZE)==PAGESIZE && read(db2,a2,PAGESIZE)==PAGESIZE ){
if( memcmp(a1,a2,PAGESIZE) ){
printf("Page %d\n", iPg);
}
iPg++;
}
printf("%d pages checked\n", iPg-1);
close(db1);
close(db2);
}
File diff suppressed because it is too large Load Diff
+730
View File
@@ -0,0 +1,730 @@
/* Driver template for the LEMON parser generator.
** The author disclaims copyright to this source code.
*/
/* First off, code is include which follows the "include" declaration
** in the input file. */
#include <stdio.h>
%%
/* Next is all token values, in a form suitable for use by makeheaders.
** This section will be null unless lemon is run with the -m switch.
*/
/*
** These constants (all generated automatically by the parser generator)
** specify the various kinds of tokens (terminals) that the parser
** understands.
**
** Each symbol here is a terminal symbol in the grammar.
*/
%%
/* Make sure the INTERFACE macro is defined.
*/
#ifndef INTERFACE
# define INTERFACE 1
#endif
/* The next thing included is series of defines which control
** various aspects of the generated parser.
** YYCODETYPE is the data type used for storing terminal
** and nonterminal numbers. "unsigned char" is
** used if there are fewer than 250 terminals
** and nonterminals. "int" is used otherwise.
** YYNOCODE is a number of type YYCODETYPE which corresponds
** to no legal terminal or nonterminal number. This
** number is used to fill in empty slots of the hash
** table.
** YYFALLBACK If defined, this indicates that one or more tokens
** have fall-back values which should be used if the
** original value of the token will not parse.
** YYACTIONTYPE is the data type used for storing terminal
** and nonterminal numbers. "unsigned char" is
** used if there are fewer than 250 rules and
** states combined. "int" is used otherwise.
** ParseTOKENTYPE is the data type used for minor tokens given
** directly to the parser from the tokenizer.
** YYMINORTYPE is the data type used for all minor tokens.
** This is typically a union of many types, one of
** which is ParseTOKENTYPE. The entry in the union
** for base tokens is called "yy0".
** YYSTACKDEPTH is the maximum depth of the parser's stack.
** ParseARG_SDECL A static variable declaration for the %extra_argument
** 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.
*/
%%
#define YY_NO_ACTION (YYNSTATE+YYNRULE+2)
#define YY_ACCEPT_ACTION (YYNSTATE+YYNRULE+1)
#define YY_ERROR_ACTION (YYNSTATE+YYNRULE)
/* Next are that tables used to determine what action to take based on the
** current state and lookahead token. These tables are used to implement
** functions that take a state number and lookahead value and return an
** action integer.
**
** Suppose the action integer is N. Then the action is determined as
** follows
**
** 0 <= N < YYNSTATE 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 == YYNSTATE+YYNRULE A syntax error has occurred.
**
** N == YYNSTATE+YYNRULE+1 The parser accepts its input.
**
** N == YYNSTATE+YYNRULE+2 No such action. Denotes unused
** slots in the yy_action[] table.
**
** The action table is constructed as a single large table named yy_action[].
** Given state S and lookahead X, the action is computed as
**
** yy_action[ yy_shift_ofst[S] + X ]
**
** If the index value yy_shift_ofst[S]+X is out of range or if the value
** yy_lookahead[yy_shift_ofst[S]+X] is not equal to X or if yy_shift_ofst[S]
** is equal to YY_SHIFT_USE_DFLT, it means that the action is not in the table
** and that yy_default[S] should be used instead.
**
** The formula above is for computing the action when the lookahead is
** a terminal symbol. If the lookahead is a non-terminal (as occurs after
** a reduce action) then the yy_reduce_ofst[] array is used in place of
** the yy_shift_ofst[] array and YY_REDUCE_USE_DFLT is used in place of
** YY_SHIFT_USE_DFLT.
**
** The following are the tables generated in this section:
**
** 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.
*/
%%
#define YY_SZ_ACTTAB (int)(sizeof(yy_action)/sizeof(yy_action[0]))
/* The next table maps tokens into fallback tokens. If a construct
** like the following:
**
** %fallback ID X Y Z.
**
** appears in the grammer, then ID becomes a fallback token for X, Y,
** and Z. Whenever one of the tokens X, Y, or Z is input to the parser
** but it does not parse, the type of the token is changed to ID and
** the parse is retried before an error is thrown.
*/
#ifdef YYFALLBACK
static const YYCODETYPE yyFallback[] = {
%%
};
#endif /* YYFALLBACK */
/* The following structure represents a single element of the
** parser's stack. Information stored includes:
**
** + The state number for the parser at this level of the stack.
**
** + The value of the token stored at this level of the stack.
** (In other words, the "major" token.)
**
** + 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.
*/
struct yyStackEntry {
int stateno; /* The state-number */
int 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
** is the value of the token */
};
typedef struct yyStackEntry yyStackEntry;
/* The state of the parser is completely contained in an instance of
** the following structure */
struct yyParser {
int yyidx; /* Index of top element in stack */
int yyerrcnt; /* Shifts left before out of the error */
ParseARG_SDECL /* A place to hold %extra_argument */
yyStackEntry yystack[YYSTACKDEPTH]; /* The parser's stack */
};
typedef struct yyParser yyParser;
#ifndef NDEBUG
#include <stdio.h>
static FILE *yyTraceFILE = 0;
static char *yyTracePrompt = 0;
#endif /* NDEBUG */
#ifndef NDEBUG
/*
** Turn parser tracing on by giving a stream to which to write the trace
** and a prompt to preface each trace message. Tracing is turned off
** by making either argument NULL
**
** Inputs:
** <ul>
** <li> A FILE* to which trace output should be written.
** If NULL, then tracing is turned off.
** <li> A prefix string written at the beginning of every
** line of trace output. If NULL, then tracing is
** turned off.
** </ul>
**
** Outputs:
** None.
*/
void ParseTrace(FILE *TraceFILE, char *zTracePrompt){
yyTraceFILE = TraceFILE;
yyTracePrompt = zTracePrompt;
if( yyTraceFILE==0 ) yyTracePrompt = 0;
else if( yyTracePrompt==0 ) yyTraceFILE = 0;
}
#endif /* NDEBUG */
#ifndef NDEBUG
/* For tracing shifts, the names of all terminals and nonterminals
** are required. The following table supplies these names */
static const char *const yyTokenName[] = {
%%
};
#endif /* NDEBUG */
#ifndef NDEBUG
/* For tracing reduce actions, the names of all rules are required.
*/
static const char *const yyRuleName[] = {
%%
};
#endif /* NDEBUG */
/*
** This function returns the symbolic name associated with a token
** value.
*/
const char *ParseTokenName(int tokenType){
#ifndef NDEBUG
if( tokenType>0 && tokenType<(sizeof(yyTokenName)/sizeof(yyTokenName[0])) ){
return yyTokenName[tokenType];
}else{
return "Unknown";
}
#else
return "";
#endif
}
/*
** This function allocates a new parser.
** The only argument is a pointer to a function which works like
** malloc.
**
** Inputs:
** A pointer to the function used to allocate memory.
**
** Outputs:
** A pointer to a parser. This pointer is used in subsequent calls
** to Parse and ParseFree.
*/
void *ParseAlloc(void *(*mallocProc)(size_t)){
yyParser *pParser;
pParser = (yyParser*)(*mallocProc)( (size_t)sizeof(yyParser) );
if( pParser ){
pParser->yyidx = -1;
}
return pParser;
}
/* The following function deletes the value associated with a
** symbol. The symbol can be either a terminal or nonterminal.
** "yymajor" is the symbol code, and "yypminor" is a pointer to
** the value.
*/
static void yy_destructor(YYCODETYPE yymajor, YYMINORTYPE *yypminor){
switch( yymajor ){
/* Here is inserted the actions which take place when a
** terminal or non-terminal is destroyed. This can happen
** when the symbol is popped from the stack during a
** reduce or during error processing or when a parser is
** being destroyed before it is finished parsing.
**
** Note: during a reduce, the only symbols destroyed are those
** which appear on the RHS of the rule, but which are not used
** inside the C code.
*/
%%
default: break; /* If no destructor action specified: do nothing */
}
}
/*
** Pop the parser's stack once.
**
** If there is a destructor routine associated with the token which
** is popped from the stack, then call it.
**
** Return the major token number for the symbol popped.
*/
static int yy_pop_parser_stack(yyParser *pParser){
YYCODETYPE yymajor;
yyStackEntry *yytos = &pParser->yystack[pParser->yyidx];
if( pParser->yyidx<0 ) return 0;
#ifndef NDEBUG
if( yyTraceFILE && pParser->yyidx>=0 ){
fprintf(yyTraceFILE,"%sPopping %s\n",
yyTracePrompt,
yyTokenName[yytos->major]);
}
#endif
yymajor = yytos->major;
yy_destructor( yymajor, &yytos->minor);
pParser->yyidx--;
return yymajor;
}
/*
** Deallocate and destroy a parser. Destructors are all called for
** all stack elements before shutting the parser down.
**
** Inputs:
** <ul>
** <li> A pointer to the parser. This should be a pointer
** obtained from ParseAlloc.
** <li> A pointer to a function used to reclaim memory obtained
** from malloc.
** </ul>
*/
void ParseFree(
void *p, /* The parser to be deleted */
void (*freeProc)(void*) /* Function used to reclaim memory */
){
yyParser *pParser = (yyParser*)p;
if( pParser==0 ) return;
while( pParser->yyidx>=0 ) yy_pop_parser_stack(pParser);
(*freeProc)((void*)pParser);
}
/*
** Find the appropriate action for a parser given the terminal
** look-ahead token iLookAhead.
**
** If the look-ahead token is YYNOCODE, then check to see if the action is
** independent of the look-ahead. If it is, return the action, otherwise
** return YY_NO_ACTION.
*/
static int yy_find_shift_action(
yyParser *pParser, /* The parser */
YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
int stateno = pParser->yystack[pParser->yyidx].stateno;
if( stateno>YY_SHIFT_MAX || (i = yy_shift_ofst[stateno])==YY_SHIFT_USE_DFLT ){
return yy_default[stateno];
}
if( iLookAhead==YYNOCODE ){
return YY_NO_ACTION;
}
i += iLookAhead;
if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
if( iLookAhead>0 ){
#ifdef YYFALLBACK
int iFallback; /* Fallback token */
if( iLookAhead<sizeof(yyFallback)/sizeof(yyFallback[0])
&& (iFallback = yyFallback[iLookAhead])!=0 ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE, "%sFALLBACK %s => %s\n",
yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[iFallback]);
}
#endif
return yy_find_shift_action(pParser, iFallback);
}
#endif
#ifdef YYWILDCARD
{
int j = i - iLookAhead + YYWILDCARD;
if( j>=0 && j<YY_SZ_ACTTAB && yy_lookahead[j]==YYWILDCARD ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE, "%sWILDCARD %s => %s\n",
yyTracePrompt, yyTokenName[iLookAhead], yyTokenName[YYWILDCARD]);
}
#endif /* NDEBUG */
return yy_action[j];
}
}
#endif /* YYWILDCARD */
}
return yy_default[stateno];
}else{
return yy_action[i];
}
}
/*
** Find the appropriate action for a parser given the non-terminal
** look-ahead token iLookAhead.
**
** If the look-ahead token is YYNOCODE, then check to see if the action is
** independent of the look-ahead. If it is, return the action, otherwise
** return YY_NO_ACTION.
*/
static int yy_find_reduce_action(
int stateno, /* Current state number */
YYCODETYPE iLookAhead /* The look-ahead token */
){
int i;
/* int stateno = pParser->yystack[pParser->yyidx].stateno; */
if( stateno>YY_REDUCE_MAX ||
(i = yy_reduce_ofst[stateno])==YY_REDUCE_USE_DFLT ){
return yy_default[stateno];
}
if( iLookAhead==YYNOCODE ){
return YY_NO_ACTION;
}
i += iLookAhead;
if( i<0 || i>=YY_SZ_ACTTAB || yy_lookahead[i]!=iLookAhead ){
return yy_default[stateno];
}else{
return yy_action[i];
}
}
/*
** Perform a shift action.
*/
static void yy_shift(
yyParser *yypParser, /* The parser to be shifted */
int yyNewState, /* The new state to shift in */
int yyMajor, /* The major token to shift in */
YYMINORTYPE *yypMinor /* Pointer ot the minor token to shift in */
){
yyStackEntry *yytos;
yypParser->yyidx++;
if( yypParser->yyidx>=YYSTACKDEPTH ){
ParseARG_FETCH;
yypParser->yyidx--;
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sStack Overflow!\n",yyTracePrompt);
}
#endif
while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
/* Here code is inserted which will execute if the parser
** stack every overflows */
%%
ParseARG_STORE; /* Suppress warning about unused %extra_argument var */
return;
}
yytos = &yypParser->yystack[yypParser->yyidx];
yytos->stateno = yyNewState;
yytos->major = 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
}
/* The following table contains information about every rule that
** is used during the reduce.
*/
static const struct {
YYCODETYPE lhs; /* Symbol on the left-hand side of the rule */
unsigned char nrhs; /* Number of right-hand side symbols in the rule */
} yyRuleInfo[] = {
%%
};
static void yy_accept(yyParser*); /* Forward Declaration */
/*
** Perform a reduce action and the shift that must immediately
** follow the reduce.
*/
static void yy_reduce(
yyParser *yypParser, /* The parser */
int yyruleno /* Number of the rule by which to reduce */
){
int yygoto; /* The next state */
int yyact; /* The next action */
YYMINORTYPE yygotominor; /* The LHS of the rule reduced */
yyStackEntry *yymsp; /* The top of the parser's stack */
int yysize; /* Amount to pop the stack */
ParseARG_FETCH;
yymsp = &yypParser->yystack[yypParser->yyidx];
#ifndef NDEBUG
if( yyTraceFILE && yyruleno>=0
&& yyruleno<(int)(sizeof(yyRuleName)/sizeof(yyRuleName[0])) ){
fprintf(yyTraceFILE, "%sReduce [%s].\n", yyTracePrompt,
yyRuleName[yyruleno]);
}
#endif /* NDEBUG */
#ifndef NDEBUG
/* Silence complaints from purify about yygotominor being uninitialized
** in some cases when it is copied into the stack after the following
** switch. yygotominor is uninitialized when a rule reduces that does
** not set the value of its left-hand side nonterminal. Leaving the
** value of the nonterminal uninitialized is utterly harmless as long
** as the value is never used. So really the only thing this code
** accomplishes is to quieten purify.
*/
memset(&yygotominor, 0, sizeof(yygotominor));
#endif
switch( yyruleno ){
/* Beginning here are the reduction cases. A typical example
** follows:
** case 0:
** #line <lineno> <grammarfile>
** { ... } // User supplied code
** #line <lineno> <thisfile>
** break;
*/
%%
};
yygoto = yyRuleInfo[yyruleno].lhs;
yysize = yyRuleInfo[yyruleno].nrhs;
yypParser->yyidx -= yysize;
yyact = yy_find_reduce_action(yymsp[-yysize].stateno,yygoto);
if( yyact < YYNSTATE ){
#ifdef NDEBUG
/* If we are not debugging and 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. */
if( yysize ){
yypParser->yyidx++;
yymsp -= yysize-1;
yymsp->stateno = yyact;
yymsp->major = yygoto;
yymsp->minor = yygotominor;
}else
#endif
{
yy_shift(yypParser,yyact,yygoto,&yygotominor);
}
}else if( yyact == YYNSTATE + YYNRULE + 1 ){
yy_accept(yypParser);
}
}
/*
** The following code executes when the parse fails
*/
static void yy_parse_failed(
yyParser *yypParser /* The parser */
){
ParseARG_FETCH;
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sFail!\n",yyTracePrompt);
}
#endif
while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
/* Here code is inserted which will be executed whenever the
** parser fails */
%%
ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
}
/*
** The following code executes when a syntax error first occurs.
*/
static void yy_syntax_error(
yyParser *yypParser, /* The parser */
int yymajor, /* The major type of the error token */
YYMINORTYPE yyminor /* The minor type of the error token */
){
ParseARG_FETCH;
#define TOKEN (yyminor.yy0)
%%
ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
}
/*
** The following is executed when the parser accepts
*/
static void yy_accept(
yyParser *yypParser /* The parser */
){
ParseARG_FETCH;
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sAccept!\n",yyTracePrompt);
}
#endif
while( yypParser->yyidx>=0 ) yy_pop_parser_stack(yypParser);
/* Here code is inserted which will be executed whenever the
** parser accepts */
%%
ParseARG_STORE; /* Suppress warning about unused %extra_argument variable */
}
/* The main parser program.
** The first argument is a pointer to a structure obtained from
** "ParseAlloc" which describes the current state of the parser.
** The second argument is the major token number. The third is
** the minor token. The fourth optional argument is whatever the
** user wants (and specified in the grammar) and is available for
** use by the action routines.
**
** Inputs:
** <ul>
** <li> A pointer to the parser (an opaque structure.)
** <li> The major token number.
** <li> The minor token number.
** <li> An option argument of a grammar-specified type.
** </ul>
**
** Outputs:
** None.
*/
void Parse(
void *yyp, /* The parser */
int yymajor, /* The major token code number */
ParseTOKENTYPE yyminor /* The value for the token */
ParseARG_PDECL /* Optional %extra_argument parameter */
){
YYMINORTYPE yyminorunion;
int yyact; /* The parser action. */
int yyendofinput; /* True if we are at the end of input */
int yyerrorhit = 0; /* True if yymajor has invoked an error */
yyParser *yypParser; /* The parser */
/* (re)initialize the parser, if necessary */
yypParser = (yyParser*)yyp;
if( yypParser->yyidx<0 ){
/* if( yymajor==0 ) return; // not sure why this was here... */
yypParser->yyidx = 0;
yypParser->yyerrcnt = -1;
yypParser->yystack[0].stateno = 0;
yypParser->yystack[0].major = 0;
}
yyminorunion.yy0 = yyminor;
yyendofinput = (yymajor==0);
ParseARG_STORE;
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sInput %s\n",yyTracePrompt,yyTokenName[yymajor]);
}
#endif
do{
yyact = yy_find_shift_action(yypParser,yymajor);
if( yyact<YYNSTATE ){
yy_shift(yypParser,yyact,yymajor,&yyminorunion);
yypParser->yyerrcnt--;
if( yyendofinput && yypParser->yyidx>=0 ){
yymajor = 0;
}else{
yymajor = YYNOCODE;
}
}else if( yyact < YYNSTATE + YYNRULE ){
yy_reduce(yypParser,yyact-YYNSTATE);
}else if( yyact == YY_ERROR_ACTION ){
int yymx;
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sSyntax Error!\n",yyTracePrompt);
}
#endif
#ifdef YYERRORSYMBOL
/* A syntax error has occurred.
** The response to an error depends upon whether or not the
** grammar defines an error token "ERROR".
**
** This is what we do if the grammar does define ERROR:
**
** * Call the %syntax_error function.
**
** * Begin popping the stack until we enter a state where
** it is legal to shift the error symbol, then shift
** the error symbol.
**
** * Set the error count to three.
**
** * Begin accepting and shifting new tokens. No new error
** processing will occur until three tokens have been
** shifted successfully.
**
*/
if( yypParser->yyerrcnt<0 ){
yy_syntax_error(yypParser,yymajor,yyminorunion);
}
yymx = yypParser->yystack[yypParser->yyidx].major;
if( yymx==YYERRORSYMBOL || yyerrorhit ){
#ifndef NDEBUG
if( yyTraceFILE ){
fprintf(yyTraceFILE,"%sDiscard input token %s\n",
yyTracePrompt,yyTokenName[yymajor]);
}
#endif
yy_destructor(yymajor,&yyminorunion);
yymajor = YYNOCODE;
}else{
while(
yypParser->yyidx >= 0 &&
yymx != YYERRORSYMBOL &&
(yyact = yy_find_reduce_action(
yypParser->yystack[yypParser->yyidx].stateno,
YYERRORSYMBOL)) >= YYNSTATE
){
yy_pop_parser_stack(yypParser);
}
if( yypParser->yyidx < 0 || yymajor==0 ){
yy_destructor(yymajor,&yyminorunion);
yy_parse_failed(yypParser);
yymajor = YYNOCODE;
}else if( yymx!=YYERRORSYMBOL ){
YYMINORTYPE u2;
u2.YYERRSYMDT = 0;
yy_shift(yypParser,yyact,YYERRORSYMBOL,&u2);
}
}
yypParser->yyerrcnt = 3;
yyerrorhit = 1;
#else /* YYERRORSYMBOL is not defined */
/* This is what we do if the grammar does not define ERROR:
**
** * Report an error message, and throw away the input token.
**
** * If the input token is $, then fail the parse.
**
** As before, subsequent error messages are suppressed until
** three input tokens have been successfully shifted.
*/
if( yypParser->yyerrcnt<=0 ){
yy_syntax_error(yypParser,yymajor,yyminorunion);
}
yypParser->yyerrcnt = 3;
yy_destructor(yymajor,&yyminorunion);
if( yyendofinput ){
yy_parse_failed(yypParser);
}
yymajor = YYNOCODE;
#endif
}else{
yy_accept(yypParser);
yymajor = YYNOCODE;
}
}while( yymajor!=YYNOCODE && yypParser->yyidx>=0 );
return;
}
+29
View File
@@ -0,0 +1,29 @@
#
# This script looks for memory leaks by analyzing the output of "sqlite"
# when compiled with the SQLITE_DEBUG=2 option.
#
/[0-9]+ malloc / {
mem[$6] = $0
}
/[0-9]+ realloc / {
mem[$8] = "";
mem[$10] = $0
}
/[0-9]+ free / {
if (mem[$6]=="") {
print "*** free without a malloc at",$6
}
mem[$6] = "";
str[$6] = ""
}
/^string at / {
addr = $4
sub("string at " addr " is ","")
str[addr] = $0
}
END {
for(addr in mem){
if( mem[addr]=="" ) continue
print mem[addr], str[addr]
}
}
+29
View File
@@ -0,0 +1,29 @@
# This AWK script reads the output of testfixture when compiled for memory
# debugging. It generates SQL commands that can be fed into an sqlite
# instance to determine what memory is never freed. A typical usage would
# be as follows:
#
# make -f memleak.mk fulltest 2>mem.out
# awk -f ../sqlite/tool/memleak2.awk mem.out | ./sqlite :memory:
#
# The job performed by this script is the same as that done by memleak.awk.
# The difference is that this script uses much less memory when the size
# of the mem.out file is huge.
#
BEGIN {
print "CREATE TABLE mem(loc INTEGER PRIMARY KEY, src);"
}
/[0-9]+ malloc / {
print "INSERT INTO mem VALUES(" strtonum($6) ",'" $0 "');"
}
/[0-9]+ realloc / {
print "INSERT INTO mem VALUES(" strtonum($10) \
",(SELECT src FROM mem WHERE loc=" strtonum($8) "));"
print "DELETE FROM mem WHERE loc=" strtonum($8) ";"
}
/[0-9]+ free / {
print "DELETE FROM mem WHERE loc=" strtonum($6) ";"
}
END {
print "SELECT src FROM mem;"
}
+233
View File
@@ -0,0 +1,233 @@
#/bin/sh
# \
exec `which tclsh` $0 "$@"
#
# The author disclaims copyright to this source code. In place of
# a legal notice, here is a blessing:
#
# May you do good and not evil.
# May you find forgiveness for yourself and forgive others.
# May you share freely, never taking more than you give.
######################################################################
set doco "
This script is a tool to help track down memory leaks in the sqlite
library. The library must be compiled with the preprocessor symbol
SQLITE_MEMDEBUG set to at least 2. It must be set to 3 to enable stack
traces.
To use, run the leaky application and save the standard error output.
Then, execute this program with the first argument the name of the
application binary (or interpreter) and the second argument the name of the
text file that contains the collected stderr output.
If all goes well a summary of unfreed allocations is printed out. If the
GNU C library is in use and SQLITE_DEBUG is 3 or greater a stack trace is
printed out for each unmatched allocation.
If the \"-r <n>\" option is passed, then the program stops and prints out
the state of the heap immediately after the <n>th call to malloc() or
realloc().
Example:
$ ./testfixture ../sqlite/test/select1.test 2> memtrace.out
$ tclsh $argv0 ?-r <malloc-number>? ./testfixture memtrace.out
"
proc usage {} {
set prg [file tail $::argv0]
puts "Usage: $prg ?-r <malloc-number>? <binary file> <mem trace file>"
puts ""
puts [string trim $::doco]
exit -1
}
proc shift {listvar} {
upvar $listvar l
set ret [lindex $l 0]
set l [lrange $l 1 end]
return $ret
}
# Argument handling. The following vars are set:
#
# $exe - the name of the executable (i.e. "testfixture" or "./sqlite3")
# $memfile - the name of the file containing the trace output.
# $report_at - The malloc number to stop and report at. Or -1 to read
# all of $memfile.
#
set report_at -1
while {[llength $argv]>2} {
set arg [shift argv]
switch -- $arg {
"-r" {
set report_at [shift argv]
}
default {
usage
}
}
}
if {[llength $argv]!=2} usage
set exe [lindex $argv 0]
set memfile [lindex $argv 1]
# If stack traces are enabled, the 'addr2line' program is called to
# translate a binary stack address into a human-readable form.
set addr2line addr2line
# When the SQLITE_MEMDEBUG is set as described above, SQLite prints
# out a line for each malloc(), realloc() or free() call that the
# library makes. If SQLITE_MEMDEBUG is 3, then a stack trace is printed
# out before each malloc() and realloc() line.
#
# This program parses each line the SQLite library outputs and updates
# the following global Tcl variables to reflect the "current" state of
# the heap used by SQLite.
#
set nBytes 0 ;# Total number of bytes currently allocated.
set nMalloc 0 ;# Total number of malloc()/realloc() calls.
set nPeak 0 ;# Peak of nBytes.
set iPeak 0 ;# nMalloc when nPeak was set.
#
# More detailed state information is stored in the $memmap array.
# Each key in the memmap array is the address of a chunk of memory
# currently allocated from the heap. The value is a list of the
# following form
#
# {<number-of-bytes> <malloc id> <stack trace>}
#
array unset memmap
proc process_input {input_file array_name} {
upvar $array_name mem
set input [open $input_file]
set MALLOC {([[:digit:]]+) malloc ([[:digit:]]+) bytes at 0x([[:xdigit:]]+)}
# set STACK {^[[:digit:]]+: STACK: (.*)$}
set STACK {^STACK: (.*)$}
set FREE {[[:digit:]]+ free ([[:digit:]]+) bytes at 0x([[:xdigit:]]+)}
set REALLOC {([[:digit:]]+) realloc ([[:digit:]]+) to ([[:digit:]]+)}
append REALLOC { bytes at 0x([[:xdigit:]]+) to 0x([[:xdigit:]]+)}
set stack ""
while { ![eof $input] } {
set line [gets $input]
if {[regexp $STACK $line dummy stack]} {
# Do nothing. The variable $stack now stores the hexadecimal stack dump
# for the next malloc() or realloc().
} elseif { [regexp $MALLOC $line dummy mallocid bytes addr] } {
# If this is a 'malloc' line, set an entry in the mem array. Each entry
# is a list of length three, the number of bytes allocated , the malloc
# number and the stack dump when it was allocated.
set mem($addr) [list $bytes "malloc $mallocid" $stack]
set stack ""
# Increase the current heap usage
incr ::nBytes $bytes
# Increase the number of malloc() calls
incr ::nMalloc
if {$::nBytes > $::nPeak} {
set ::nPeak $::nBytes
set ::iPeak $::nMalloc
}
} elseif { [regexp $FREE $line dummy bytes addr] } {
# If this is a 'free' line, remove the entry from the mem array. If the
# entry does not exist, or is the wrong number of bytes, announce a
# problem. This is more likely a bug in the regular expressions for
# this script than an SQLite defect.
if { [lindex $mem($addr) 0] != $bytes } {
error "byte count mismatch"
}
unset mem($addr)
# Decrease the current heap usage
incr ::nBytes [expr -1 * $bytes]
} elseif { [regexp $REALLOC $line dummy mallocid ob b oa a] } {
# "free" the old allocation in the internal model:
incr ::nBytes [expr -1 * $ob]
unset mem($oa);
# "malloc" the new allocation
set mem($a) [list $b "realloc $mallocid" $stack]
incr ::nBytes $b
set stack ""
# Increase the number of malloc() calls
incr ::nMalloc
if {$::nBytes > $::nPeak} {
set ::nPeak $::nBytes
set ::iPeak $::nMalloc
}
} else {
# puts "REJECT: $line"
}
if {$::nMalloc==$::report_at} report
}
close $input
}
proc printstack {stack} {
set fcount 10
if {[llength $stack]<10} {
set fcount [llength $stack]
}
foreach frame [lrange $stack 1 $fcount] {
foreach {f l} [split [exec $::addr2line -f --exe=$::exe $frame] \n] {}
puts [format "%-30s %s" $f $l]
}
if {[llength $stack]>0 } {puts ""}
}
proc report {} {
foreach key [array names ::memmap] {
set stack [lindex $::memmap($key) 2]
set bytes [lindex $::memmap($key) 0]
lappend summarymap($stack) $bytes
}
set sorted [list]
foreach stack [array names summarymap] {
set allocs $summarymap($stack)
set sum 0
foreach a $allocs {
incr sum $a
}
lappend sorted [list $sum $stack]
}
set sorted [lsort -integer -index 0 $sorted]
foreach s $sorted {
set sum [lindex $s 0]
set stack [lindex $s 1]
set allocs $summarymap($stack)
puts "$sum bytes in [llength $allocs] chunks ($allocs)"
printstack $stack
}
# Print out summary statistics
puts "Total allocations : $::nMalloc"
puts "Total outstanding allocations: [array size ::memmap]"
puts "Current heap usage : $::nBytes bytes"
puts "Peak heap usage : $::nPeak bytes (malloc #$::iPeak)"
exit
}
process_input $memfile memmap
report
+509
View File
@@ -0,0 +1,509 @@
/*
** Compile and run this standalone program in order to generate code that
** implements a function that will translate alphabetic identifiers into
** parser token codes.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
** All the keywords of the SQL language are stored as in a hash
** table composed of instances of the following structure.
*/
typedef struct Keyword Keyword;
struct Keyword {
char *zName; /* The keyword name */
char *zTokenType; /* Token value for this keyword */
int mask; /* Code this keyword if non-zero */
int id; /* Unique ID for this record */
int hash; /* Hash on the keyword */
int offset; /* Offset to start of name string */
int len; /* Length of this keyword, not counting final \000 */
int prefix; /* Number of characters in prefix */
int iNext; /* Index in aKeywordTable[] of next with same hash */
int substrId; /* Id to another keyword this keyword is embedded in */
int substrOffset; /* Offset into substrId for start of this keyword */
};
/*
** Define masks used to determine which keywords are allowed
*/
#ifdef SQLITE_OMIT_ALTERTABLE
# define ALTER 0
#else
# define ALTER 0x00000001
#endif
#define ALWAYS 0x00000002
#ifdef SQLITE_OMIT_ANALYZE
# define ANALYZE 0
#else
# define ANALYZE 0x00000004
#endif
#ifdef SQLITE_OMIT_ATTACH
# define ATTACH 0
#else
# define ATTACH 0x00000008
#endif
#ifdef SQLITE_OMIT_AUTOINCREMENT
# define AUTOINCR 0
#else
# define AUTOINCR 0x00000010
#endif
#ifdef SQLITE_OMIT_CAST
# define CAST 0
#else
# define CAST 0x00000020
#endif
#ifdef SQLITE_OMIT_COMPOUND_SELECT
# define COMPOUND 0
#else
# define COMPOUND 0x00000040
#endif
#ifdef SQLITE_OMIT_CONFLICT_CLAUSE
# define CONFLICT 0
#else
# define CONFLICT 0x00000080
#endif
#ifdef SQLITE_OMIT_EXPLAIN
# define EXPLAIN 0
#else
# define EXPLAIN 0x00000100
#endif
#ifdef SQLITE_OMIT_FOREIGN_KEY
# define FKEY 0
#else
# define FKEY 0x00000200
#endif
#ifdef SQLITE_OMIT_PRAGMA
# define PRAGMA 0
#else
# define PRAGMA 0x00000400
#endif
#ifdef SQLITE_OMIT_REINDEX
# define REINDEX 0
#else
# define REINDEX 0x00000800
#endif
#ifdef SQLITE_OMIT_SUBQUERY
# define SUBQUERY 0
#else
# define SUBQUERY 0x00001000
#endif
#ifdef SQLITE_OMIT_TRIGGER
# define TRIGGER 0
#else
# define TRIGGER 0x00002000
#endif
#ifdef SQLITE_OMIT_VACUUM
# define VACUUM 0
#else
# define VACUUM 0x00004000
#endif
#ifdef SQLITE_OMIT_VIEW
# define VIEW 0
#else
# define VIEW 0x00008000
#endif
#ifdef SQLITE_OMIT_VIRTUALTABLE
# define VTAB 0
#else
# define VTAB 0x00010000
#endif
/*
** These are the keywords
*/
static Keyword aKeywordTable[] = {
{ "ABORT", "TK_ABORT", CONFLICT|TRIGGER },
{ "ADD", "TK_ADD", ALTER },
{ "AFTER", "TK_AFTER", TRIGGER },
{ "ALL", "TK_ALL", ALWAYS },
{ "ALTER", "TK_ALTER", ALTER },
{ "ANALYZE", "TK_ANALYZE", ANALYZE },
{ "AND", "TK_AND", ALWAYS },
{ "AS", "TK_AS", ALWAYS },
{ "ASC", "TK_ASC", ALWAYS },
{ "ATTACH", "TK_ATTACH", ATTACH },
{ "AUTOINCREMENT", "TK_AUTOINCR", AUTOINCR },
{ "BEFORE", "TK_BEFORE", TRIGGER },
{ "BEGIN", "TK_BEGIN", ALWAYS },
{ "BETWEEN", "TK_BETWEEN", ALWAYS },
{ "BY", "TK_BY", ALWAYS },
{ "CASCADE", "TK_CASCADE", FKEY },
{ "CASE", "TK_CASE", ALWAYS },
{ "CAST", "TK_CAST", CAST },
{ "CHECK", "TK_CHECK", ALWAYS },
{ "COLLATE", "TK_COLLATE", ALWAYS },
{ "COLUMN", "TK_COLUMNKW", ALTER },
{ "COMMIT", "TK_COMMIT", ALWAYS },
{ "CONFLICT", "TK_CONFLICT", CONFLICT },
{ "CONSTRAINT", "TK_CONSTRAINT", ALWAYS },
{ "CREATE", "TK_CREATE", ALWAYS },
{ "CROSS", "TK_JOIN_KW", ALWAYS },
{ "CURRENT_DATE", "TK_CTIME_KW", ALWAYS },
{ "CURRENT_TIME", "TK_CTIME_KW", ALWAYS },
{ "CURRENT_TIMESTAMP","TK_CTIME_KW", ALWAYS },
{ "DATABASE", "TK_DATABASE", ATTACH },
{ "DEFAULT", "TK_DEFAULT", ALWAYS },
{ "DEFERRED", "TK_DEFERRED", ALWAYS },
{ "DEFERRABLE", "TK_DEFERRABLE", FKEY },
{ "DELETE", "TK_DELETE", ALWAYS },
{ "DESC", "TK_DESC", ALWAYS },
{ "DETACH", "TK_DETACH", ATTACH },
{ "DISTINCT", "TK_DISTINCT", ALWAYS },
{ "DROP", "TK_DROP", ALWAYS },
{ "END", "TK_END", ALWAYS },
{ "EACH", "TK_EACH", TRIGGER },
{ "ELSE", "TK_ELSE", ALWAYS },
{ "ESCAPE", "TK_ESCAPE", ALWAYS },
{ "EXCEPT", "TK_EXCEPT", COMPOUND },
{ "EXCLUSIVE", "TK_EXCLUSIVE", ALWAYS },
{ "EXISTS", "TK_EXISTS", ALWAYS },
{ "EXPLAIN", "TK_EXPLAIN", EXPLAIN },
{ "FAIL", "TK_FAIL", CONFLICT|TRIGGER },
{ "FOR", "TK_FOR", TRIGGER },
{ "FOREIGN", "TK_FOREIGN", FKEY },
{ "FROM", "TK_FROM", ALWAYS },
{ "FULL", "TK_JOIN_KW", ALWAYS },
{ "GLOB", "TK_LIKE_KW", ALWAYS },
{ "GROUP", "TK_GROUP", ALWAYS },
{ "HAVING", "TK_HAVING", ALWAYS },
{ "IF", "TK_IF", ALWAYS },
{ "IGNORE", "TK_IGNORE", CONFLICT|TRIGGER },
{ "IMMEDIATE", "TK_IMMEDIATE", ALWAYS },
{ "IN", "TK_IN", ALWAYS },
{ "INDEX", "TK_INDEX", ALWAYS },
{ "INITIALLY", "TK_INITIALLY", FKEY },
{ "INNER", "TK_JOIN_KW", ALWAYS },
{ "INSERT", "TK_INSERT", ALWAYS },
{ "INSTEAD", "TK_INSTEAD", TRIGGER },
{ "INTERSECT", "TK_INTERSECT", COMPOUND },
{ "INTO", "TK_INTO", ALWAYS },
{ "IS", "TK_IS", ALWAYS },
{ "ISNULL", "TK_ISNULL", ALWAYS },
{ "JOIN", "TK_JOIN", ALWAYS },
{ "KEY", "TK_KEY", ALWAYS },
{ "LEFT", "TK_JOIN_KW", ALWAYS },
{ "LIKE", "TK_LIKE_KW", ALWAYS },
{ "LIMIT", "TK_LIMIT", ALWAYS },
{ "MATCH", "TK_MATCH", ALWAYS },
{ "NATURAL", "TK_JOIN_KW", ALWAYS },
{ "NOT", "TK_NOT", ALWAYS },
{ "NOTNULL", "TK_NOTNULL", ALWAYS },
{ "NULL", "TK_NULL", ALWAYS },
{ "OF", "TK_OF", ALWAYS },
{ "OFFSET", "TK_OFFSET", ALWAYS },
{ "ON", "TK_ON", ALWAYS },
{ "OR", "TK_OR", ALWAYS },
{ "ORDER", "TK_ORDER", ALWAYS },
{ "OUTER", "TK_JOIN_KW", ALWAYS },
{ "PLAN", "TK_PLAN", EXPLAIN },
{ "PRAGMA", "TK_PRAGMA", PRAGMA },
{ "PRIMARY", "TK_PRIMARY", ALWAYS },
{ "QUERY", "TK_QUERY", EXPLAIN },
{ "RAISE", "TK_RAISE", TRIGGER },
{ "REFERENCES", "TK_REFERENCES", FKEY },
{ "REGEXP", "TK_LIKE_KW", ALWAYS },
{ "REINDEX", "TK_REINDEX", REINDEX },
{ "RENAME", "TK_RENAME", ALTER },
{ "REPLACE", "TK_REPLACE", CONFLICT },
{ "RESTRICT", "TK_RESTRICT", FKEY },
{ "RIGHT", "TK_JOIN_KW", ALWAYS },
{ "ROLLBACK", "TK_ROLLBACK", ALWAYS },
{ "ROW", "TK_ROW", TRIGGER },
{ "SELECT", "TK_SELECT", ALWAYS },
{ "SET", "TK_SET", ALWAYS },
{ "STATEMENT", "TK_STATEMENT", TRIGGER },
{ "TABLE", "TK_TABLE", ALWAYS },
{ "TEMP", "TK_TEMP", ALWAYS },
{ "TEMPORARY", "TK_TEMP", ALWAYS },
{ "THEN", "TK_THEN", ALWAYS },
{ "TO", "TK_TO", ALTER },
{ "TRANSACTION", "TK_TRANSACTION", ALWAYS },
{ "TRIGGER", "TK_TRIGGER", TRIGGER },
{ "UNION", "TK_UNION", COMPOUND },
{ "UNIQUE", "TK_UNIQUE", ALWAYS },
{ "UPDATE", "TK_UPDATE", ALWAYS },
{ "USING", "TK_USING", ALWAYS },
{ "VACUUM", "TK_VACUUM", VACUUM },
{ "VALUES", "TK_VALUES", ALWAYS },
{ "VIEW", "TK_VIEW", VIEW },
{ "VIRTUAL", "TK_VIRTUAL", VTAB },
{ "WHEN", "TK_WHEN", ALWAYS },
{ "WHERE", "TK_WHERE", ALWAYS },
};
/* Number of keywords */
static int NKEYWORD = (sizeof(aKeywordTable)/sizeof(aKeywordTable[0]));
/* An array to map all upper-case characters into their corresponding
** lower-case character.
*/
const unsigned char sqlite3UpperToLower[] = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 97, 98, 99,100,101,102,103,
104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,
122, 91, 92, 93, 94, 95, 96, 97, 98, 99,100,101,102,103,104,105,106,107,
108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,
126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,
144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,
162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,
180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,
198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,
216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,
234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,
252,253,254,255
};
#define UpperToLower sqlite3UpperToLower
/*
** Comparision function for two Keyword records
*/
static int keywordCompare1(const void *a, const void *b){
const Keyword *pA = (Keyword*)a;
const Keyword *pB = (Keyword*)b;
int n = pA->len - pB->len;
if( n==0 ){
n = strcmp(pA->zName, pB->zName);
}
return n;
}
static int keywordCompare2(const void *a, const void *b){
const Keyword *pA = (Keyword*)a;
const Keyword *pB = (Keyword*)b;
int n = strcmp(pA->zName, pB->zName);
return n;
}
static int keywordCompare3(const void *a, const void *b){
const Keyword *pA = (Keyword*)a;
const Keyword *pB = (Keyword*)b;
int n = pA->offset - pB->offset;
return n;
}
/*
** Return a KeywordTable entry with the given id
*/
static Keyword *findById(int id){
int i;
for(i=0; i<NKEYWORD; i++){
if( aKeywordTable[i].id==id ) break;
}
return &aKeywordTable[i];
}
/*
** This routine does the work. The generated code is printed on standard
** output.
*/
int main(int argc, char **argv){
int i, j, k, h;
int bestSize, bestCount;
int count;
int nChar;
int aHash[1000]; /* 1000 is much bigger than NKEYWORD */
/* Remove entries from the list of keywords that have mask==0 */
for(i=j=0; i<NKEYWORD; i++){
if( aKeywordTable[i].mask==0 ) continue;
if( j<i ){
aKeywordTable[j] = aKeywordTable[i];
}
j++;
}
NKEYWORD = j;
/* Fill in the lengths of strings and hashes for all entries. */
for(i=0; i<NKEYWORD; i++){
Keyword *p = &aKeywordTable[i];
p->len = strlen(p->zName);
p->hash = (UpperToLower[p->zName[0]]*4) ^
(UpperToLower[p->zName[p->len-1]]*3) ^ p->len;
p->id = i+1;
}
/* Sort the table from shortest to longest keyword */
qsort(aKeywordTable, NKEYWORD, sizeof(aKeywordTable[0]), keywordCompare1);
/* Look for short keywords embedded in longer keywords */
for(i=NKEYWORD-2; i>=0; i--){
Keyword *p = &aKeywordTable[i];
for(j=NKEYWORD-1; j>i && p->substrId==0; j--){
Keyword *pOther = &aKeywordTable[j];
if( pOther->substrId ) continue;
if( pOther->len<=p->len ) continue;
for(k=0; k<=pOther->len-p->len; k++){
if( memcmp(p->zName, &pOther->zName[k], p->len)==0 ){
p->substrId = pOther->id;
p->substrOffset = k;
break;
}
}
}
}
/* Sort the table into alphabetical order */
qsort(aKeywordTable, NKEYWORD, sizeof(aKeywordTable[0]), keywordCompare2);
/* Fill in the offset for all entries */
nChar = 0;
for(i=0; i<NKEYWORD; i++){
Keyword *p = &aKeywordTable[i];
if( p->offset>0 || p->substrId ) continue;
p->offset = nChar;
nChar += p->len;
for(k=p->len-1; k>=1; k--){
for(j=i+1; j<NKEYWORD; j++){
Keyword *pOther = &aKeywordTable[j];
if( pOther->offset>0 || pOther->substrId ) continue;
if( pOther->len<=k ) continue;
if( memcmp(&p->zName[p->len-k], pOther->zName, k)==0 ){
p = pOther;
p->offset = nChar - k;
nChar = p->offset + p->len;
p->zName += k;
p->len -= k;
p->prefix = k;
j = i;
k = p->len;
}
}
}
}
for(i=0; i<NKEYWORD; i++){
Keyword *p = &aKeywordTable[i];
if( p->substrId ){
p->offset = findById(p->substrId)->offset + p->substrOffset;
}
}
/* Sort the table by offset */
qsort(aKeywordTable, NKEYWORD, sizeof(aKeywordTable[0]), keywordCompare3);
/* Figure out how big to make the hash table in order to minimize the
** number of collisions */
bestSize = NKEYWORD;
bestCount = NKEYWORD*NKEYWORD;
for(i=NKEYWORD/2; i<=2*NKEYWORD; i++){
for(j=0; j<i; j++) aHash[j] = 0;
for(j=0; j<NKEYWORD; j++){
h = aKeywordTable[j].hash % i;
aHash[h] *= 2;
aHash[h]++;
}
for(j=count=0; j<i; j++) count += aHash[j];
if( count<bestCount ){
bestCount = count;
bestSize = i;
}
}
/* Compute the hash */
for(i=0; i<bestSize; i++) aHash[i] = 0;
for(i=0; i<NKEYWORD; i++){
h = aKeywordTable[i].hash % bestSize;
aKeywordTable[i].iNext = aHash[h];
aHash[h] = i+1;
}
/* Begin generating code */
printf("/* Hash score: %d */\n", bestCount);
printf("static int keywordCode(const char *z, int n){\n");
printf(" static const char zText[%d] =\n", nChar+1);
for(i=j=0; i<NKEYWORD; i++){
Keyword *p = &aKeywordTable[i];
if( p->substrId ) continue;
if( j==0 ) printf(" \"");
printf("%s", p->zName);
j += p->len;
if( j>60 ){
printf("\"\n");
j = 0;
}
}
printf("%s;\n", j>0 ? "\"" : " ");
printf(" static const unsigned char aHash[%d] = {\n", bestSize);
for(i=j=0; i<bestSize; i++){
if( j==0 ) printf(" ");
printf(" %3d,", aHash[i]);
j++;
if( j>12 ){
printf("\n");
j = 0;
}
}
printf("%s };\n", j==0 ? "" : "\n");
printf(" static const unsigned char aNext[%d] = {\n", NKEYWORD);
for(i=j=0; i<NKEYWORD; i++){
if( j==0 ) printf(" ");
printf(" %3d,", aKeywordTable[i].iNext);
j++;
if( j>12 ){
printf("\n");
j = 0;
}
}
printf("%s };\n", j==0 ? "" : "\n");
printf(" static const unsigned char aLen[%d] = {\n", NKEYWORD);
for(i=j=0; i<NKEYWORD; i++){
if( j==0 ) printf(" ");
printf(" %3d,", aKeywordTable[i].len+aKeywordTable[i].prefix);
j++;
if( j>12 ){
printf("\n");
j = 0;
}
}
printf("%s };\n", j==0 ? "" : "\n");
printf(" static const unsigned short int aOffset[%d] = {\n", NKEYWORD);
for(i=j=0; i<NKEYWORD; i++){
if( j==0 ) printf(" ");
printf(" %3d,", aKeywordTable[i].offset);
j++;
if( j>12 ){
printf("\n");
j = 0;
}
}
printf("%s };\n", j==0 ? "" : "\n");
printf(" static const unsigned char aCode[%d] = {\n", NKEYWORD);
for(i=j=0; i<NKEYWORD; i++){
char *zToken = aKeywordTable[i].zTokenType;
if( j==0 ) printf(" ");
printf("%s,%*s", zToken, (int)(14-strlen(zToken)), "");
j++;
if( j>=5 ){
printf("\n");
j = 0;
}
}
printf("%s };\n", j==0 ? "" : "\n");
printf(" int h, i;\n");
printf(" if( n<2 ) return TK_ID;\n");
printf(" h = ((charMap(z[0])*4) ^\n"
" (charMap(z[n-1])*3) ^\n"
" n) %% %d;\n", bestSize);
printf(" for(i=((int)aHash[h])-1; i>=0; i=((int)aNext[i])-1){\n");
printf(" if( aLen[i]==n &&"
" sqlite3StrNICmp(&zText[aOffset[i]],z,n)==0 ){\n");
printf(" return aCode[i];\n");
printf(" }\n");
printf(" }\n");
printf(" return TK_ID;\n");
printf("}\n");
printf("int sqlite3KeywordCode(const unsigned char *z, int n){\n");
printf(" return keywordCode((char*)z, n);\n");
printf("}\n");
return 0;
}
+51
View File
@@ -0,0 +1,51 @@
#!/usr/bin/tclsh
#
# This script is used to generate the array of strings and the enum
# that appear at the beginning of the C code implementation of a
# a TCL command and that define the available subcommands for that
# TCL command.
set prefix {}
while {![eof stdin]} {
set line [gets stdin]
if {$line==""} continue
regsub -all "\[ \t\n,\]+" [string trim $line] { } line
foreach token [split $line { }] {
if {![regexp {(([a-zA-Z]+)_)?([_a-zA-Z]+)} $token all px p2 name]} continue
lappend namelist [string tolower $name]
if {$px!=""} {set prefix $p2}
}
}
puts " static const char *${prefix}_strs\[\] = \173"
set col 0
proc put_item x {
global col
if {$col==0} {puts -nonewline " "}
if {$col<2} {
puts -nonewline [format " %-21s" $x]
incr col
} else {
puts $x
set col 0
}
}
proc finalize {} {
global col
if {$col>0} {puts {}}
set col 0
}
foreach name [lsort $namelist] {
put_item \"$name\",
}
put_item 0
finalize
puts " \175;"
puts " enum ${prefix}_enum \173"
foreach name [lsort $namelist] {
regsub -all {@} $name {} name
put_item ${prefix}_[string toupper $name],
}
finalize
puts " \175;"
+175
View File
@@ -0,0 +1,175 @@
set rcsid {$Id: omittest.tcl,v 1.2 2006/06/20 11:01:09 danielk1977 Exp $}
# Documentation for this script. This may be output to stderr
# if the script is invoked incorrectly.
set ::USAGE_MESSAGE {
This Tcl script is used to test the various compile time options
available for omitting code (the SQLITE_OMIT_xxx options). It
should be invoked as follows:
<script> ?-makefile PATH-TO-MAKEFILE?
The default value for ::MAKEFILE is "../Makefile.linux.gcc".
This script builds the testfixture program and runs the SQLite test suite
once with each SQLITE_OMIT_ option defined and then once with all options
defined together. Each run is performed in a seperate directory created
as a sub-directory of the current directory by the script. The output
of the build is saved in <sub-directory>/build.log. The output of the
test-suite is saved in <sub-directory>/test.log.
Almost any SQLite makefile (except those generated by configure - see below)
should work. The following properties are required:
* The makefile should support the "testfixture" target.
* The makefile should support the "test" target.
* The makefile should support the variable "OPTS" as a way to pass
options from the make command line to lemon and the C compiler.
More precisely, the following two invocations must be supported:
make -f $::MAKEFILE testfixture OPTS="-DSQLITE_OMIT_ALTERTABLE=1"
make -f $::MAKEFILE test
Makefiles generated by the sqlite configure program cannot be used as
they do not respect the OPTS variable.
}
# Build a testfixture executable and run quick.test using it. The first
# parameter is the name of the directory to create and use to run the
# test in. The second parameter is a list of OMIT symbols to define
# when doing so. For example:
#
# run_quick_test /tmp/testdir {SQLITE_OMIT_TRIGGER SQLITE_OMIT_VIEW}
#
#
proc run_quick_test {dir omit_symbol_list} {
# Compile the value of the OPTS Makefile variable.
set opts "-DSQLITE_MEMDEBUG=2 -DSQLITE_DEBUG -DOS_UNIX"
foreach sym $omit_symbol_list {
append opts " -D${sym}=1"
}
# Create the directory and do the build. If an error occurs return
# early without attempting to run the test suite.
file mkdir $dir
puts -nonewline "Building $dir..."
flush stdout
set rc [catch {
exec make -C $dir -f $::MAKEFILE testfixture OPTS=$opts >& $dir/build.log
}]
if {$rc} {
puts "No good. See $dir/build.log."
return
} else {
puts "Ok"
}
# Create an empty file "$dir/sqlite3". This is to trick the makefile out
# of trying to build the sqlite shell. The sqlite shell won't build
# with some of the OMIT options (i.e OMIT_COMPLETE).
if {![file exists $dir/sqlite3]} {
set wr [open $dir/sqlite3 w]
puts $wr "dummy"
close $wr
}
# Run the test suite.
puts -nonewline "Testing $dir..."
flush stdout
set rc [catch {
exec make -C $dir -f $::MAKEFILE test OPTS=$opts >& $dir/test.log
}]
if {$rc} {
puts "No good. See $dir/test.log."
} else {
puts "Ok"
}
}
# This proc processes the command line options passed to this script.
# Currently the only option supported is "-makefile", default
# "../Makefile.linux-gcc". Set the ::MAKEFILE variable to the value of this
# option.
#
proc process_options {argv} {
set ::MAKEFILE ../Makefile.linux-gcc ;# Default value
for {set i 0} {$i < [llength $argv]} {incr i} {
switch -- [lindex $argv $i] {
-makefile {
incr i
set ::MAKEFILE [lindex $argv $i]
}
default {
puts stderr [string trim $::USAGE_MESSAGE]
exit -1
}
}
set ::MAKEFILE [file normalize $::MAKEFILE]
}
}
# Main routine.
#
proc main {argv} {
# List of SQLITE_OMIT_XXX symbols supported by SQLite.
set ::SYMBOLS [list \
SQLITE_OMIT_VIEW \
SQLITE_OMIT_VIRTUALTABLE \
SQLITE_OMIT_ALTERTABLE \
SQLITE_OMIT_EXPLAIN \
SQLITE_OMIT_FLOATING_POINT \
SQLITE_OMIT_FOREIGN_KEY \
SQLITE_OMIT_INTEGRITY_CHECK \
SQLITE_OMIT_MEMORYDB \
SQLITE_OMIT_PAGER_PRAGMAS \
SQLITE_OMIT_PRAGMA \
SQLITE_OMIT_PROGRESS_CALLBACK \
SQLITE_OMIT_REINDEX \
SQLITE_OMIT_SCHEMA_PRAGMAS \
SQLITE_OMIT_SCHEMA_VERSION_PRAGMAS \
SQLITE_OMIT_DATETIME_FUNCS \
SQLITE_OMIT_SUBQUERY \
SQLITE_OMIT_TCL_VARIABLE \
SQLITE_OMIT_TRIGGER \
SQLITE_OMIT_UTF16 \
SQLITE_OMIT_VACUUM \
SQLITE_OMIT_COMPLETE \
SQLITE_OMIT_AUTOVACUUM \
SQLITE_OMIT_AUTHORIZATION \
SQLITE_OMIT_AUTOINCREMENT \
SQLITE_OMIT_BLOB_LITERAL \
SQLITE_OMIT_COMPOUND_SELECT \
SQLITE_OMIT_CONFLICT_CLAUSE \
]
# Process any command line options.
process_options $argv
# First try a test with all OMIT symbols except SQLITE_OMIT_FLOATING_POINT
# and SQLITE_OMIT_PRAGMA defined. The former doesn't work (causes segfaults)
# and the latter is currently incompatible with the test suite (this should
# be fixed, but it will be a lot of work).
set allsyms [list]
foreach s $::SYMBOLS {
if {$s!="SQLITE_OMIT_FLOATING_POINT" && $s!="SQLITE_OMIT_PRAGMA"} {
lappend allsyms $s
}
}
run_quick_test test_OMIT_EVERYTHING $allsyms
# Now try one quick.test with each of the OMIT symbols defined. Included
# are the OMIT_FLOATING_POINT and OMIT_PRAGMA symbols, even though we
# know they will fail. It's good to be reminded of this from time to time.
foreach sym $::SYMBOLS {
set dirname "test_[string range $sym 7 end]"
run_quick_test $dirname $sym
}
}
main $argv
+23
View File
@@ -0,0 +1,23 @@
#
# Extract opcode documentation for sqliteVdbe.c and generate HTML
#
BEGIN {
print "<html><body bgcolor=white>"
print "<h1>SQLite Virtual Database Engine Opcodes</h1>"
print "<table>"
}
/ Opcode: /,/\*\// {
if( $2=="Opcode:" ){
printf "<tr><td>%s&nbsp;%s&nbsp;%s&nbsp;%s</td>\n<td>\n", $3, $4, $5, $6
}else if( $1=="*/" ){
printf "</td></tr>\n"
}else if( NF>1 ){
sub(/^ *\*\* /,"")
gsub(/</,"&lt;")
gsub(/&/,"&amp;")
print
}
}
END {
print "</table></body></html>"
}
+66
View File
@@ -0,0 +1,66 @@
The SQL database used for ACD contains 113 tables and indices implemented
in GDBM. The following are statistics on the sizes of keys and data
within these tables and indices.
Entries: 962080
Size: 45573853
Avg Size: 48
Key Size: 11045299
Avg Key Size: 12
Max Key Size: 99
Size of key Cummulative
and data Instances Percentage
------------ ---------- -----------
0..8 266 0%
9..12 5485 0%
13..16 73633 8%
17..24 180918 27%
25..32 209823 48%
33..40 148995 64%
41..48 76304 72%
49..56 14346 73%
57..64 15725 75%
65..80 44916 80%
81..96 127815 93%
97..112 34769 96%
113..128 13314 98%
129..144 8098 99%
145..160 3355 99%
161..176 1159 99%
177..192 629 99%
193..208 221 99%
209..224 210 99%
225..240 129 99%
241..256 57 99%
257..288 496 99%
289..320 60 99%
321..352 37 99%
353..384 46 99%
385..416 22 99%
417..448 24 99%
449..480 26 99%
481..512 27 99%
513..1024 471 99%
1025..2048 389 99%
2049..4096 182 99%
4097..8192 74 99%
8193..16384 34 99%
16385..32768 17 99%
32769..65536 5 99%
65537..131073 3 100%
This information is gathered to help design the new built-in
backend for sqlite 2.0. Note in particular that 99% of all
database entries have a combined key and data size of less than
144 bytes. So if a leaf node in the new database is able to
store 144 bytes of combined key and data, only 1% of the leaves
will require overflow pages. Furthermore, note that no key
is larger than 99 bytes, so if the key will never be on an
overflow page.
The average combined size of key+data is 48. Add in 16 bytes of
overhead for a total of 64. That means that a 1K page will
store (on average) about 16 entries.
+86
View File
@@ -0,0 +1,86 @@
/*
** A utility for printing all or part of an SQLite database file.
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
static int pagesize = 1024;
static int db = -1;
static int mxPage = 0;
static int perLine = 32;
static void out_of_memory(void){
fprintf(stderr,"Out of memory...\n");
exit(1);
}
static print_page(int iPg){
unsigned char *aData;
int i, j;
aData = malloc(pagesize);
if( aData==0 ) out_of_memory();
lseek(db, (iPg-1)*pagesize, SEEK_SET);
read(db, aData, pagesize);
fprintf(stdout, "Page %d:\n", iPg);
for(i=0; i<pagesize; i += perLine){
fprintf(stdout, " %03x: ",i);
for(j=0; j<perLine; j++){
fprintf(stdout,"%02x ", aData[i+j]);
}
for(j=0; j<perLine; j++){
fprintf(stdout,"%c", isprint(aData[i+j]) ? aData[i+j] : '.');
}
fprintf(stdout,"\n");
}
free(aData);
}
int main(int argc, char **argv){
struct stat sbuf;
if( argc<2 ){
fprintf(stderr,"Usage: %s FILENAME ?PAGE? ...\n", argv[0]);
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);
}
fstat(db, &sbuf);
mxPage = sbuf.st_size/pagesize + 1;
if( argc==2 ){
int i;
for(i=1; i<=mxPage; i++) print_page(i);
}else{
int i;
for(i=2; i<argc; i++){
int iStart, iEnd;
char *zLeft;
iStart = strtol(argv[i], &zLeft, 0);
if( zLeft && strcmp(zLeft,"..end")==0 ){
iEnd = mxPage;
}else if( zLeft && zLeft[0]=='.' && zLeft[1]=='.' ){
iEnd = strtol(&zLeft[2], 0, 0);
}else{
iEnd = iStart;
}
if( iStart<1 || iEnd<iStart || iEnd>mxPage ){
fprintf(stderr,
"Page argument should be LOWER?..UPPER?. Range 1 to %d\n",
mxPage);
exit(1);
}
while( iStart<=iEnd ){
print_page(iStart);
iStart++;
}
}
}
close(db);
}
+76
View File
@@ -0,0 +1,76 @@
/*
** A utility for printing an SQLite database journal.
*/
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
static int pagesize = 1024;
static int db = -1;
static int mxPage = 0;
static void out_of_memory(void){
fprintf(stderr,"Out of memory...\n");
exit(1);
}
static print_page(int iPg){
unsigned char *aData;
int i, j;
aData = malloc(pagesize);
if( aData==0 ) out_of_memory();
read(db, aData, pagesize);
fprintf(stdout, "Page %d:\n", iPg);
for(i=0; i<pagesize; i += 16){
fprintf(stdout, " %03x: ",i);
for(j=0; j<16; j++){
fprintf(stdout,"%02x ", aData[i+j]);
}
for(j=0; j<16; j++){
fprintf(stdout,"%c", isprint(aData[i+j]) ? aData[i+j] : '.');
}
fprintf(stdout,"\n");
}
free(aData);
}
int main(int argc, char **argv){
struct stat sbuf;
unsigned int u;
int rc;
unsigned char zBuf[10];
unsigned char zBuf2[sizeof(u)];
if( argc!=2 ){
fprintf(stderr,"Usage: %s FILENAME\n", argv[0]);
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);
}
read(db, zBuf, 8);
if( zBuf[7]==0xd6 ){
read(db, &u, sizeof(u));
printf("Records in Journal: %u\n", u);
read(db, &u, sizeof(u));
printf("Magic Number: 0x%08x\n", u);
}
read(db, zBuf2, sizeof(zBuf2));
u = zBuf2[0]<<24 | zBuf2[1]<<16 | zBuf2[2]<<8 | zBuf2[3];
printf("Database Size: %u\n", u);
while( read(db, zBuf2, sizeof(zBuf2))==sizeof(zBuf2) ){
u = zBuf2[0]<<24 | zBuf2[1]<<16 | zBuf2[2]<<8 | zBuf2[3];
print_page(u);
if( zBuf[7]==0xd6 ){
read(db, &u, sizeof(u));
printf("Checksum: 0x%08x\n", u);
}
}
close(db);
}
+111
View File
@@ -0,0 +1,111 @@
# Run this TCL script using "testfixture" in order get a report that shows
# how much disk space is used by a particular data to actually store data
# versus how much space is unused.
#
# Get the name of the database to analyze
#
if {[llength $argv]!=1} {
puts stderr "Usage: $argv0 database-name"
exit 1
}
set file_to_analyze [lindex $argv 0]
# Open the database
#
sqlite db [lindex $argv 0]
set DB [btree_open [lindex $argv 0]]
# Output the schema for the generated report
#
puts \
{BEGIN;
CREATE TABLE space_used(
name clob, -- Name of a table or index in the database file
is_index boolean, -- TRUE if it is an index, false for a table
payload int, -- Total amount of data stored in this table or index
pri_pages int, -- Number of primary pages used
ovfl_pages int, -- Number of overflow pages used
pri_unused int, -- Number of unused bytes on primary pages
ovfl_unused int -- Number of unused bytes on overflow pages
);}
# This query will be used to find the root page number for every index and
# table in the database.
#
set sql {
SELECT name, type, rootpage FROM sqlite_master
UNION ALL
SELECT 'sqlite_master', 'table', 2
ORDER BY 1
}
# Initialize variables used for summary statistics.
#
set total_size 0
set total_primary 0
set total_overflow 0
set total_unused_primary 0
set total_unused_ovfl 0
# Analyze every table in the database, one at a time.
#
foreach {name type rootpage} [db eval $sql] {
set cursor [btree_cursor $DB $rootpage 0]
set go [btree_first $cursor]
set size 0
catch {unset pg_used}
set unused_ovfl 0
set n_overflow 0
while {$go==0} {
set payload [btree_payload_size $cursor]
incr size $payload
set stat [btree_cursor_dump $cursor]
set pgno [lindex $stat 0]
set freebytes [lindex $stat 4]
set pg_used($pgno) $freebytes
if {$payload>238} {
set n [expr {($payload-238+1019)/1020}]
incr n_overflow $n
incr unused_ovfl [expr {$n*1020+238-$payload}]
}
set go [btree_next $cursor]
}
btree_close_cursor $cursor
set n_primary [llength [array names pg_used]]
set unused_primary 0
foreach x [array names pg_used] {incr unused_primary $pg_used($x)}
regsub -all ' $name '' name
puts -nonewline "INSERT INTO space_used VALUES('$name'"
puts -nonewline ",[expr {$type=="index"}]"
puts ",$size,$n_primary,$n_overflow,$unused_primary,$unused_ovfl);"
incr total_size $size
incr total_primary $n_primary
incr total_overflow $n_overflow
incr total_unused_primary $unused_primary
incr total_unused_ovfl $unused_ovfl
}
# Output summary statistics:
#
puts "-- Total payload size: $total_size"
puts "-- Total pages used: $total_primary primary and $total_overflow overflow"
set file_pgcnt [expr {[file size [lindex $argv 0]]/1024}]
puts -nonewline "-- Total unused bytes on primary pages: $total_unused_primary"
if {$total_primary>0} {
set upp [expr {$total_unused_primary/$total_primary}]
puts " (avg $upp bytes/page)"
} else {
puts ""
}
puts -nonewline "-- Total unused bytes on overflow pages: $total_unused_ovfl"
if {$total_overflow>0} {
set upp [expr {$total_unused_ovfl/$total_overflow}]
puts " (avg $upp bytes/page)"
} else {
puts ""
}
set n_free [expr {$file_pgcnt-$total_primary-$total_overflow}]
if {$n_free>0} {incr n_free -1}
puts "-- Total pages on freelist: $n_free"
puts "COMMIT;"
+813
View File
@@ -0,0 +1,813 @@
# Run this TCL script using "testfixture" in order get a report that shows
# how much disk space is used by a particular data to actually store data
# versus how much space is unused.
#
if {[catch {
# Get the name of the database to analyze
#
#set argv $argv0
if {[llength $argv]!=1} {
puts stderr "Usage: $argv0 database-name"
exit 1
}
set file_to_analyze [lindex $argv 0]
if {![file exists $file_to_analyze]} {
puts stderr "No such file: $file_to_analyze"
exit 1
}
if {![file readable $file_to_analyze]} {
puts stderr "File is not readable: $file_to_analyze"
exit 1
}
if {[file size $file_to_analyze]<512} {
puts stderr "Empty or malformed database: $file_to_analyze"
exit 1
}
# Open the database
#
sqlite3 db [lindex $argv 0]
set DB [btree_open [lindex $argv 0] 1000 0]
# In-memory database for collecting statistics. This script loops through
# the tables and indices in the database being analyzed, adding a row for each
# to an in-memory database (for which the schema is shown below). It then
# queries the in-memory db to produce the space-analysis report.
#
sqlite3 mem :memory:
set tabledef\
{CREATE TABLE space_used(
name clob, -- Name of a table or index in the database file
tblname clob, -- Name of associated table
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
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
mx_payload int, -- Maximum payload size
int_pages int, -- Number of interior pages used
leaf_pages int, -- Number of leaf pages used
ovfl_pages int, -- Number of overflow pages used
int_unused int, -- Number of unused bytes on interior pages
leaf_unused int, -- Number of unused bytes on primary pages
ovfl_unused int -- Number of unused bytes on overflow pages
);}
mem eval $tabledef
proc integerify {real} {
return [expr int($real)]
}
mem function int integerify
# Quote a string for use in an SQL query. Examples:
#
# [quote {hello world}] == {'hello world'}
# [quote {hello world's}] == {'hello world''s'}
#
proc quote {txt} {
regsub -all ' $txt '' q
return '$q'
}
# This proc is a wrapper around the btree_cursor_info command. The
# second argument is an open btree cursor returned by [btree_cursor].
# The first argument is the name of an array variable that exists in
# the scope of the caller. If the third argument is non-zero, then
# info is returned for the page that lies $up entries upwards in the
# tree-structure. (i.e. $up==1 returns the parent page, $up==2 the
# grandparent etc.)
#
# The following entries in that array are filled in with information retrieved
# using [btree_cursor_info]:
#
# $arrayvar(page_no) = The page number
# $arrayvar(entry_no) = The entry number
# $arrayvar(page_entries) = Total number of entries on this page
# $arrayvar(cell_size) = Cell size (local payload + header)
# $arrayvar(page_freebytes) = Number of free bytes on this page
# $arrayvar(page_freeblocks) = Number of free blocks on the page
# $arrayvar(payload_bytes) = Total payload size (local + overflow)
# $arrayvar(header_bytes) = Header size in bytes
# $arrayvar(local_payload_bytes) = Local payload size
# $arrayvar(parent) = Parent page number
#
proc cursor_info {arrayvar csr {up 0}} {
upvar $arrayvar a
foreach [list a(page_no) \
a(entry_no) \
a(page_entries) \
a(cell_size) \
a(page_freebytes) \
a(page_freeblocks) \
a(payload_bytes) \
a(header_bytes) \
a(local_payload_bytes) \
a(parent) ] [btree_cursor_info $csr $up] {}
}
# Determine the page-size of the database. This global variable is used
# throughout the script.
#
set pageSize [db eval {PRAGMA page_size}]
# Analyze every table in the database, one at a time.
#
# The following query returns the name and root-page of each table in the
# database, including the sqlite_master table.
#
set sql {
SELECT name, rootpage FROM sqlite_master
WHERE type='table' AND rootpage>0
UNION ALL
SELECT 'sqlite_master', 1
ORDER BY 1
}
set wideZero [expr {10000000000 - 10000000000}]
foreach {name rootpage} [db eval $sql] {
puts stderr "Analyzing table $name..."
# Code below traverses the table being analyzed (table name $name), using the
# btree cursor $cursor. Statistics related to table $name are accumulated in
# the following variables:
#
set total_payload $wideZero ;# Payload space used by all entries
set total_ovfl $wideZero ;# Payload space on overflow pages
set unused_int $wideZero ;# Unused space on interior nodes
set unused_leaf $wideZero ;# Unused space on leaf nodes
set unused_ovfl $wideZero ;# Unused space on overflow pages
set cnt_ovfl $wideZero ;# Number of entries that use overflows
set cnt_leaf_entry $wideZero ;# Number of leaf entries
set cnt_int_entry $wideZero ;# Number of interor entries
set mx_payload $wideZero ;# Maximum payload size
set ovfl_pages $wideZero ;# Number of overflow pages used
set leaf_pages $wideZero ;# Number of leaf pages
set int_pages $wideZero ;# Number of interior pages
# As the btree is traversed, the array variable $seen($pgno) is set to 1
# the first time page $pgno is encountered.
#
catch {unset seen}
# The following loop runs once for each entry in table $name. The table
# is traversed using the btree cursor stored in variable $csr
#
set csr [btree_cursor $DB $rootpage 0]
for {btree_first $csr} {![btree_eof $csr]} {btree_next $csr} {
incr cnt_leaf_entry
# Retrieve information about the entry the btree-cursor points to into
# the array variable $ci (cursor info).
#
cursor_info ci $csr
# Check if the payload of this entry is greater than the current
# $mx_payload statistic for the table. Also increase the $total_payload
# statistic.
#
if {$ci(payload_bytes)>$mx_payload} {set mx_payload $ci(payload_bytes)}
incr total_payload $ci(payload_bytes)
# If this entry uses overflow pages, then update the $cnt_ovfl,
# $total_ovfl, $ovfl_pages and $unused_ovfl statistics.
#
set ovfl [expr {$ci(payload_bytes)-$ci(local_payload_bytes)}]
if {$ovfl} {
incr cnt_ovfl
incr total_ovfl $ovfl
set n [expr {int(ceil($ovfl/($pageSize-4.0)))}]
incr ovfl_pages $n
incr unused_ovfl [expr {$n*($pageSize-4) - $ovfl}]
}
# If this is the first table entry analyzed for the page, then update
# the page-related statistics $leaf_pages and $unused_leaf. Also, if
# this page has a parent page that has not been analyzed, retrieve
# info for the parent and update statistics for it too.
#
if {![info exists seen($ci(page_no))]} {
set seen($ci(page_no)) 1
incr leaf_pages
incr unused_leaf $ci(page_freebytes)
# Now check if the page has a parent that has not been analyzed. If
# so, update the $int_pages, $cnt_int_entry and $unused_int statistics
# accordingly. Then check if the parent page has a parent that has
# not yet been analyzed etc.
#
# set parent $ci(parent_page_no)
for {set up 1} \
{$ci(parent)!=0 && ![info exists seen($ci(parent))]} {incr up} \
{
# Mark the parent as seen.
#
set seen($ci(parent)) 1
# Retrieve info for the parent and update statistics.
cursor_info ci $csr $up
incr int_pages
incr cnt_int_entry $ci(page_entries)
incr unused_int $ci(page_freebytes)
}
}
}
btree_close_cursor $csr
# Handle the special case where a table contains no data. In this case
# all statistics are zero, except for the number of leaf pages (1) and
# the unused bytes on leaf pages ($pageSize - 8).
#
# An exception to the above is the sqlite_master table. If it is empty
# then all statistics are zero except for the number of leaf pages (1),
# and the number of unused bytes on leaf pages ($pageSize - 112).
#
if {[llength [array names seen]]==0} {
set leaf_pages 1
if {$rootpage==1} {
set unused_leaf [expr {$pageSize-112}]
} else {
set unused_leaf [expr {$pageSize-8}]
}
}
# Insert the statistics for the table analyzed into the in-memory database.
#
set sql "INSERT INTO space_used VALUES("
append sql [quote $name]
append sql ",[quote $name]"
append sql ",0"
append sql ",[expr {$cnt_leaf_entry+$cnt_int_entry}]"
append sql ",$cnt_leaf_entry"
append sql ",$total_payload"
append sql ",$total_ovfl"
append sql ",$cnt_ovfl"
append sql ",$mx_payload"
append sql ",$int_pages"
append sql ",$leaf_pages"
append sql ",$ovfl_pages"
append sql ",$unused_int"
append sql ",$unused_leaf"
append sql ",$unused_ovfl"
append sql );
mem eval $sql
}
# Analyze every index in the database, one at a time.
#
# The query below returns the name, associated table and root-page number
# for every index in the database.
#
set sql {
SELECT name, tbl_name, rootpage FROM sqlite_master WHERE type='index'
ORDER BY 2, 1
}
foreach {name tbl_name rootpage} [db eval $sql] {
puts stderr "Analyzing index $name of table $tbl_name..."
# Code below traverses the index being analyzed (index name $name), using the
# btree cursor $cursor. Statistics related to index $name are accumulated in
# the following variables:
#
set total_payload $wideZero ;# Payload space used by all entries
set total_ovfl $wideZero ;# Payload space on overflow pages
set unused_leaf $wideZero ;# Unused space on leaf nodes
set unused_ovfl $wideZero ;# Unused space on overflow pages
set cnt_ovfl $wideZero ;# Number of entries that use overflows
set cnt_leaf_entry $wideZero ;# Number of leaf entries
set mx_payload $wideZero ;# Maximum payload size
set ovfl_pages $wideZero ;# Number of overflow pages used
set leaf_pages $wideZero ;# Number of leaf pages
# As the btree is traversed, the array variable $seen($pgno) is set to 1
# the first time page $pgno is encountered.
#
catch {unset seen}
# The following loop runs once for each entry in index $name. The index
# is traversed using the btree cursor stored in variable $csr
#
set csr [btree_cursor $DB $rootpage 0]
for {btree_first $csr} {![btree_eof $csr]} {btree_next $csr} {
incr cnt_leaf_entry
# Retrieve information about the entry the btree-cursor points to into
# the array variable $ci (cursor info).
#
cursor_info ci $csr
# Check if the payload of this entry is greater than the current
# $mx_payload statistic for the table. Also increase the $total_payload
# statistic.
#
set payload [btree_keysize $csr]
if {$payload>$mx_payload} {set mx_payload $payload}
incr total_payload $payload
# If this entry uses overflow pages, then update the $cnt_ovfl,
# $total_ovfl, $ovfl_pages and $unused_ovfl statistics.
#
set ovfl [expr {$payload-$ci(local_payload_bytes)}]
if {$ovfl} {
incr cnt_ovfl
incr total_ovfl $ovfl
set n [expr {int(ceil($ovfl/($pageSize-4.0)))}]
incr ovfl_pages $n
incr unused_ovfl [expr {$n*($pageSize-4) - $ovfl}]
}
# If this is the first table entry analyzed for the page, then update
# the page-related statistics $leaf_pages and $unused_leaf.
#
if {![info exists seen($ci(page_no))]} {
set seen($ci(page_no)) 1
incr leaf_pages
incr unused_leaf $ci(page_freebytes)
}
}
btree_close_cursor $csr
# Handle the special case where a index contains no data. In this case
# all statistics are zero, except for the number of leaf pages (1) and
# the unused bytes on leaf pages ($pageSize - 8).
#
if {[llength [array names seen]]==0} {
set leaf_pages 1
set unused_leaf [expr {$pageSize-8}]
}
# Insert the statistics for the index analyzed into the in-memory database.
#
set sql "INSERT INTO space_used VALUES("
append sql [quote $name]
append sql ",[quote $tbl_name]"
append sql ",1"
append sql ",$cnt_leaf_entry"
append sql ",$cnt_leaf_entry"
append sql ",$total_payload"
append sql ",$total_ovfl"
append sql ",$cnt_ovfl"
append sql ",$mx_payload"
append sql ",0"
append sql ",$leaf_pages"
append sql ",$ovfl_pages"
append sql ",0"
append sql ",$unused_leaf"
append sql ",$unused_ovfl"
append sql );
mem eval $sql
}
# Generate a single line of output in the statistics section of the
# report.
#
proc statline {title value {extra {}}} {
set len [string length $title]
set dots [string range {......................................} $len end]
set len [string length $value]
set sp2 [string range { } $len end]
if {$extra ne ""} {
set extra " $extra"
}
puts "$title$dots $value$sp2$extra"
}
# Generate a formatted percentage value for $num/$denom
#
proc percent {num denom {of {}}} {
if {$denom==0.0} {return ""}
set v [expr {$num*100.0/$denom}]
set of {}
if {$v==100.0 || $v<0.001 || ($v>1.0 && $v<99.0)} {
return [format {%5.1f%% %s} $v $of]
} elseif {$v<0.1 || $v>99.9} {
return [format {%7.3f%% %s} $v $of]
} else {
return [format {%6.2f%% %s} $v $of]
}
}
proc divide {num denom} {
if {$denom==0} {return 0.0}
return [format %.2f [expr double($num)/double($denom)]]
}
# Generate a subreport that covers some subset of the database.
# the $where clause determines which subset to analyze.
#
proc subreport {title where} {
global pageSize file_pgcnt
# Query the in-memory database for the sum of various statistics
# for the subset of tables/indices identified by the WHERE clause in
# $where. Note that even if the WHERE clause matches no rows, the
# following query returns exactly one row (because it is an aggregate).
#
# The results of the query are stored directly by SQLite into local
# variables (i.e. $nentry, $nleaf etc.).
#
mem eval "
SELECT
int(sum(nentry)) AS nentry,
int(sum(leaf_entries)) AS nleaf,
int(sum(payload)) AS payload,
int(sum(ovfl_payload)) AS ovfl_payload,
max(mx_payload) AS mx_payload,
int(sum(ovfl_cnt)) as ovfl_cnt,
int(sum(leaf_pages)) AS leaf_pages,
int(sum(int_pages)) AS int_pages,
int(sum(ovfl_pages)) AS ovfl_pages,
int(sum(leaf_unused)) AS leaf_unused,
int(sum(int_unused)) AS int_unused,
int(sum(ovfl_unused)) AS ovfl_unused
FROM space_used WHERE $where" {} {}
# Output the sub-report title, nicely decorated with * characters.
#
puts ""
set len [string length $title]
set stars [string repeat * [expr 65-$len]]
puts "*** $title $stars"
puts ""
# Calculate statistics and store the results in TCL variables, as follows:
#
# total_pages: Database pages consumed.
# total_pages_percent: Pages consumed as a percentage of the file.
# storage: Bytes consumed.
# payload_percent: Payload bytes used as a percentage of $storage.
# total_unused: Unused bytes on pages.
# avg_payload: Average payload per btree entry.
# avg_fanout: Average fanout for internal pages.
# avg_unused: Average unused bytes per btree entry.
# ovfl_cnt_percent: Percentage of btree entries that use overflow pages.
#
set total_pages [expr {$leaf_pages+$int_pages+$ovfl_pages}]
set total_pages_percent [percent $total_pages $file_pgcnt]
set storage [expr {$total_pages*$pageSize}]
set payload_percent [percent $payload $storage {of storage consumed}]
set total_unused [expr {$ovfl_unused+$int_unused+$leaf_unused}]
set avg_payload [divide $payload $nleaf]
set avg_unused [divide $total_unused $nleaf]
if {$int_pages>0} {
# TODO: Is this formula correct?
set nTab [mem eval "
SELECT count(*) FROM (
SELECT DISTINCT tblname FROM space_used WHERE $where AND is_index=0
)
"]
set avg_fanout [mem eval "
SELECT (sum(leaf_pages+int_pages)-$nTab)/sum(int_pages) FROM space_used
WHERE $where AND is_index = 0
"]
set avg_fanout [format %.2f $avg_fanout]
}
set ovfl_cnt_percent [percent $ovfl_cnt $nleaf {of all entries}]
# Print out the sub-report statistics.
#
statline {Percentage of total database} $total_pages_percent
statline {Number of entries} $nleaf
statline {Bytes of storage consumed} $storage
statline {Bytes of payload} $payload $payload_percent
statline {Average payload per entry} $avg_payload
statline {Average unused bytes per entry} $avg_unused
if {[info exists avg_fanout]} {
statline {Average fanout} $avg_fanout
}
statline {Maximum payload per entry} $mx_payload
statline {Entries that use overflow} $ovfl_cnt $ovfl_cnt_percent
if {$int_pages>0} {
statline {Index pages used} $int_pages
}
statline {Primary pages used} $leaf_pages
statline {Overflow pages used} $ovfl_pages
statline {Total pages used} $total_pages
if {$int_unused>0} {
set int_unused_percent \
[percent $int_unused [expr {$int_pages*$pageSize}] {of index space}]
statline "Unused bytes on index pages" $int_unused $int_unused_percent
}
statline "Unused bytes on primary pages" $leaf_unused \
[percent $leaf_unused [expr {$leaf_pages*$pageSize}] {of primary space}]
statline "Unused bytes on overflow pages" $ovfl_unused \
[percent $ovfl_unused [expr {$ovfl_pages*$pageSize}] {of overflow space}]
statline "Unused bytes on all pages" $total_unused \
[percent $total_unused $storage {of all space}]
return 1
}
# Calculate the overhead in pages caused by auto-vacuum.
#
# This procedure calculates and returns the number of pages used by the
# auto-vacuum 'pointer-map'. If the database does not support auto-vacuum,
# then 0 is returned. The two arguments are the size of the database file in
# pages and the page size used by the database (in bytes).
proc autovacuum_overhead {filePages pageSize} {
# Read the value of meta 4. If non-zero, then the database supports
# auto-vacuum. It would be possible to use "PRAGMA auto_vacuum" instead,
# but that would not work if the SQLITE_OMIT_PRAGMA macro was defined
# when the library was built.
set meta4 [lindex [btree_get_meta $::DB] 4]
# If the database is not an auto-vacuum database or the file consists
# of one page only then there is no overhead for auto-vacuum. Return zero.
if {0==$meta4 || $filePages==1} {
return 0
}
# The number of entries on each pointer map page. The layout of the
# database file is one pointer-map page, followed by $ptrsPerPage other
# pages, followed by a pointer-map page etc. The first pointer-map page
# is the second page of the file overall.
set ptrsPerPage [expr double($pageSize/5)]
# Return the number of pointer map pages in the database.
return [expr int(ceil( ($filePages-1.0)/($ptrsPerPage+1.0) ))]
}
# Calculate the summary statistics for the database and store the results
# in TCL variables. They are output below. Variables are as follows:
#
# pageSize: Size of each page in bytes.
# file_bytes: File size in bytes.
# file_pgcnt: Number of pages in the file.
# file_pgcnt2: Number of pages in the file (calculated).
# av_pgcnt: Pages consumed by the auto-vacuum pointer-map.
# av_percent: Percentage of the file consumed by auto-vacuum pointer-map.
# inuse_pgcnt: Data pages in the file.
# inuse_percent: Percentage of pages used to store data.
# free_pgcnt: Free pages calculated as (<total pages> - <in-use pages>)
# free_pgcnt2: Free pages in the file according to the file header.
# free_percent: Percentage of file consumed by free pages (calculated).
# free_percent2: Percentage of file consumed by free pages (header).
# ntable: Number of tables in the db.
# nindex: Number of indices in the db.
# nautoindex: Number of indices created automatically.
# nmanindex: Number of indices created manually.
# user_payload: Number of bytes of payload in table btrees
# (not including sqlite_master)
# user_percent: $user_payload as a percentage of total file size.
set file_bytes [file size $file_to_analyze]
set file_pgcnt [expr {$file_bytes/$pageSize}]
set av_pgcnt [autovacuum_overhead $file_pgcnt $pageSize]
set av_percent [percent $av_pgcnt $file_pgcnt]
set sql {SELECT sum(leaf_pages+int_pages+ovfl_pages) FROM space_used}
set inuse_pgcnt [expr int([mem eval $sql])]
set inuse_percent [percent $inuse_pgcnt $file_pgcnt]
set free_pgcnt [expr $file_pgcnt-$inuse_pgcnt-$av_pgcnt]
set free_percent [percent $free_pgcnt $file_pgcnt]
set free_pgcnt2 [lindex [btree_get_meta $DB] 0]
set free_percent2 [percent $free_pgcnt2 $file_pgcnt]
set file_pgcnt2 [expr {$inuse_pgcnt+$free_pgcnt2+$av_pgcnt}]
set ntable [db eval {SELECT count(*)+1 FROM sqlite_master WHERE type='table'}]
set nindex [db eval {SELECT count(*) FROM sqlite_master WHERE type='index'}]
set sql {SELECT count(*) FROM sqlite_master WHERE name LIKE 'sqlite_autoindex%'}
set nautoindex [db eval $sql]
set nmanindex [expr {$nindex-$nautoindex}]
# set total_payload [mem eval "SELECT sum(payload) FROM space_used"]
set user_payload [mem one {SELECT int(sum(payload)) FROM space_used
WHERE NOT is_index AND name NOT LIKE 'sqlite_master'}]
set user_percent [percent $user_payload $file_bytes]
# Output the summary statistics calculated above.
#
puts "/** Disk-Space Utilization Report For $file_to_analyze"
catch {
puts "*** As of [clock format [clock seconds] -format {%Y-%b-%d %H:%M:%S}]"
}
puts ""
statline {Page size in bytes} $pageSize
statline {Pages in the whole file (measured)} $file_pgcnt
statline {Pages in the whole file (calculated)} $file_pgcnt2
statline {Pages that store data} $inuse_pgcnt $inuse_percent
statline {Pages on the freelist (per header)} $free_pgcnt2 $free_percent2
statline {Pages on the freelist (calculated)} $free_pgcnt $free_percent
statline {Pages of auto-vacuum overhead} $av_pgcnt $av_percent
statline {Number of tables in the database} $ntable
statline {Number of indices} $nindex
statline {Number of named indices} $nmanindex
statline {Automatically generated indices} $nautoindex
statline {Size of the file in bytes} $file_bytes
statline {Bytes of user payload stored} $user_payload $user_percent
# Output table rankings
#
puts ""
puts "*** Page counts for all tables with their indices ********************"
puts ""
mem eval {SELECT tblname, count(*) AS cnt,
int(sum(int_pages+leaf_pages+ovfl_pages)) AS size
FROM space_used GROUP BY tblname ORDER BY size+0 DESC, tblname} {} {
statline [string toupper $tblname] $size [percent $size $file_pgcnt]
}
# Output subreports
#
if {$nindex>0} {
subreport {All tables and indices} 1
}
subreport {All tables} {NOT is_index}
if {$nindex>0} {
subreport {All indices} {is_index}
}
foreach tbl [mem eval {SELECT name FROM space_used WHERE NOT is_index
ORDER BY name}] {
regsub ' $tbl '' qn
set name [string toupper $tbl]
set n [mem eval "SELECT count(*) FROM space_used WHERE tblname='$qn'"]
if {$n>1} {
subreport "Table $name and all its indices" "tblname='$qn'"
subreport "Table $name w/o any indices" "name='$qn'"
subreport "Indices of table $name" "tblname='$qn' AND is_index"
} else {
subreport "Table $name" "name='$qn'"
}
}
# Output instructions on what the numbers above mean.
#
puts {
*** Definitions ******************************************************
Page size in bytes
The number of bytes in a single page of the database file.
Usually 1024.
Number of pages in the whole file
}
puts \
" The number of $pageSize-byte pages that go into forming the complete
database"
puts \
{
Pages that store data
The number of pages that store data, either as primary B*Tree pages or
as overflow pages. The number at the right is the data pages divided by
the total number of pages in the file.
Pages on the freelist
The number of pages that are not currently in use but are reserved for
future use. The percentage at the right is the number of freelist pages
divided by the total number of pages in the file.
Pages of auto-vacuum overhead
The number of pages that store data used by the database to facilitate
auto-vacuum. This is zero for databases that do not support auto-vacuum.
Number of tables in the database
The number of tables in the database, including the SQLITE_MASTER table
used to store schema information.
Number of indices
The total number of indices in the database.
Number of named indices
The number of indices created using an explicit CREATE INDEX statement.
Automatically generated indices
The number of indices used to implement PRIMARY KEY or UNIQUE constraints
on tables.
Size of the file in bytes
The total amount of disk space used by the entire database files.
Bytes of user payload stored
The total number of bytes of user payload stored in the database. The
schema information in the SQLITE_MASTER table is not counted when
computing this number. The percentage at the right shows the payload
divided by the total file size.
Percentage of total database
The amount of the complete database file that is devoted to storing
information described by this category.
Number of entries
The total number of B-Tree key/value pairs stored under this category.
Bytes of storage consumed
The total amount of disk space required to store all B-Tree entries
under this category. The is the total number of pages used times
the pages size.
Bytes of payload
The amount of payload stored under this category. Payload is the data
part of table entries and the key part of index entries. The percentage
at the right is the bytes of payload divided by the bytes of storage
consumed.
Average payload per entry
The average amount of payload on each entry. This is just the bytes of
payload divided by the number of entries.
Average unused bytes per entry
The average amount of free space remaining on all pages under this
category on a per-entry basis. This is the number of unused bytes on
all pages divided by the number of entries.
Maximum payload per entry
The largest payload size of any entry.
Entries that use overflow
The number of entries that user one or more overflow pages.
Total pages used
This is the number of pages used to hold all information in the current
category. This is the sum of index, primary, and overflow pages.
Index pages used
This is the number of pages in a table B-tree that hold only key (rowid)
information and no data.
Primary pages used
This is the number of B-tree pages that hold both key and data.
Overflow pages used
The total number of overflow pages used for this category.
Unused bytes on index pages
The total number of bytes of unused space on all index pages. The
percentage at the right is the number of unused bytes divided by the
total number of bytes on index pages.
Unused bytes on primary pages
The total number of bytes of unused space on all primary pages. The
percentage at the right is the number of unused bytes divided by the
total number of bytes on primary pages.
Unused bytes on overflow pages
The total number of bytes of unused space on all overflow pages. The
percentage at the right is the number of unused bytes divided by the
total number of bytes on overflow pages.
Unused bytes on all pages
The total number of bytes of unused space on all primary and overflow
pages. The percentage at the right is the number of unused bytes
divided by the total number of bytes.
}
# Output a dump of the in-memory database. This can be used for more
# complex offline analysis.
#
puts "**********************************************************************"
puts "The entire text of this report can be sourced into any SQL database"
puts "engine for further analysis. All of the text above is an SQL comment."
puts "The data used to generate this report follows:"
puts "*/"
puts "BEGIN;"
puts $tabledef
unset -nocomplain x
mem eval {SELECT * FROM space_used} x {
puts -nonewline "INSERT INTO space_used VALUES"
set sep (
foreach col $x(*) {
set v $x($col)
if {$v=="" || ![string is double $v]} {set v [quote $v]}
puts -nonewline $sep$v
set sep ,
}
puts ");"
}
puts "COMMIT;"
} err]} {
puts "ERROR: $err"
puts $errorInfo
exit 1
}
+275
View File
@@ -0,0 +1,275 @@
#!/usr/bin/tclsh
#
# Run this script using TCLSH to do a speed comparison between
# various versions of SQLite and PostgreSQL and MySQL
#
# Run a test
#
set cnt 1
proc runtest {title} {
global cnt
set sqlfile test$cnt.sql
puts "<h2>Test $cnt: $title</h2>"
incr cnt
set fd [open $sqlfile r]
set sql [string trim [read $fd [file size $sqlfile]]]
close $fd
set sx [split $sql \n]
set n [llength $sx]
if {$n>8} {
set sql {}
for {set i 0} {$i<3} {incr i} {append sql [lindex $sx $i]<br>\n}
append sql "<i>... [expr {$n-6}] lines omitted</i><br>\n"
for {set i [expr {$n-3}]} {$i<$n} {incr i} {
append sql [lindex $sx $i]<br>\n
}
} else {
regsub -all \n [string trim $sql] <br> sql
}
puts "<blockquote>"
puts "$sql"
puts "</blockquote><table border=0 cellpadding=0 cellspacing=0>"
set format {<tr><td>%s</td><td align="right">&nbsp;&nbsp;&nbsp;%.3f</td></tr>}
set delay 1000
# exec sync; after $delay;
# set t [time "exec psql drh <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format PostgreSQL: $t]
exec sync; after $delay;
set t [time "exec mysql -f drh <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format MySQL: $t]
# set t [time "exec ./sqlite232 s232.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.3.2:} $t]
# set t [time "exec ./sqlite-100 s100.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.4 (cache=100):} $t]
exec sync; after $delay;
set t [time "exec ./sqlite248 s2k.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4.8:} $t]
exec sync; after $delay;
set t [time "exec ./sqlite248 sns.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4.8 (nosync):} $t]
exec sync; after $delay;
set t [time "exec ./sqlite2412 s2kb.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4.12:} $t]
exec sync; after $delay;
set t [time "exec ./sqlite2412 snsb.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4.12 (nosync):} $t]
# set t [time "exec ./sqlite-t1 st1.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.4 (test):} $t]
puts "</table>"
}
# Initialize the environment
#
expr srand(1)
catch {exec /bin/sh -c {rm -f s*.db}}
set fd [open clear.sql w]
puts $fd {
drop table t1;
drop table t2;
}
close $fd
catch {exec psql drh <clear.sql}
catch {exec mysql drh <clear.sql}
set fd [open 2kinit.sql w]
puts $fd {
PRAGMA default_cache_size=2000;
PRAGMA default_synchronous=on;
}
close $fd
exec ./sqlite248 s2k.db <2kinit.sql
exec ./sqlite2412 s2kb.db <2kinit.sql
set fd [open nosync-init.sql w]
puts $fd {
PRAGMA default_cache_size=2000;
PRAGMA default_synchronous=off;
}
close $fd
exec ./sqlite248 sns.db <nosync-init.sql
exec ./sqlite2412 snsb.db <nosync-init.sql
set ones {zero one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen sixteen seventeen
eighteen nineteen}
set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
proc number_name {n} {
if {$n>=1000} {
set txt "[number_name [expr {$n/1000}]] thousand"
set n [expr {$n%1000}]
} else {
set txt {}
}
if {$n>=100} {
append txt " [lindex $::ones [expr {$n/100}]] hundred"
set n [expr {$n%100}]
}
if {$n>=20} {
append txt " [lindex $::tens [expr {$n/10}]]"
set n [expr {$n%10}]
}
if {$n>0} {
append txt " [lindex $::ones $n]"
}
set txt [string trim $txt]
if {$txt==""} {set txt zero}
return $txt
}
set fd [open test$cnt.sql w]
puts $fd "CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100));"
for {set i 1} {$i<=1000} {incr i} {
set r [expr {int(rand()*100000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
close $fd
runtest {1000 INSERTs}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
puts $fd "CREATE TABLE t2(a INTEGER, b INTEGER, c VARCHAR(100));"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t2 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
for {set i 0} {$i<100} {incr i} {
set lwr [expr {$i*100}]
set upr [expr {($i+10)*100}]
puts $fd "SELECT count(*), avg(b) FROM t2 WHERE b>=$lwr AND b<$upr;"
}
close $fd
runtest {100 SELECTs without an index}
set fd [open test$cnt.sql w]
for {set i 1} {$i<=100} {incr i} {
puts $fd "SELECT count(*), avg(b) FROM t2 WHERE c LIKE '%[number_name $i]%';"
}
close $fd
runtest {100 SELECTs on a string comparison}
set fd [open test$cnt.sql w]
puts $fd {CREATE INDEX i2a ON t2(a);}
puts $fd {CREATE INDEX i2b ON t2(b);}
close $fd
runtest {Creating an index}
set fd [open test$cnt.sql w]
for {set i 0} {$i<5000} {incr i} {
set lwr [expr {$i*100}]
set upr [expr {($i+1)*100}]
puts $fd "SELECT count(*), avg(b) FROM t2 WHERE b>=$lwr AND b<$upr;"
}
close $fd
runtest {5000 SELECTs with an index}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 0} {$i<1000} {incr i} {
set lwr [expr {$i*10}]
set upr [expr {($i+1)*10}]
puts $fd "UPDATE t1 SET b=b*2 WHERE a>=$lwr AND a<$upr;"
}
puts $fd "COMMIT;"
close $fd
runtest {1000 UPDATEs without an index}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "UPDATE t2 SET b=$r WHERE a=$i;"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 UPDATEs with an index}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "UPDATE t2 SET c='[number_name $r]' WHERE a=$i;"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 text UPDATEs with an index}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
puts $fd "INSERT INTO t1 SELECT * FROM t2;"
puts $fd "INSERT INTO t2 SELECT * FROM t1;"
puts $fd "COMMIT;"
close $fd
runtest {INSERTs from a SELECT}
set fd [open test$cnt.sql w]
puts $fd {DELETE FROM t2 WHERE c LIKE '%fifty%';}
close $fd
runtest {DELETE without an index}
set fd [open test$cnt.sql w]
puts $fd {DELETE FROM t2 WHERE a>10 AND a<20000;}
close $fd
runtest {DELETE with an index}
set fd [open test$cnt.sql w]
puts $fd {INSERT INTO t2 SELECT * FROM t1;}
close $fd
runtest {A big INSERT after a big DELETE}
set fd [open test$cnt.sql w]
puts $fd {BEGIN;}
puts $fd {DELETE FROM t1;}
for {set i 1} {$i<=3000} {incr i} {
set r [expr {int(rand()*100000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd {COMMIT;}
close $fd
runtest {A big DELETE followed by many small INSERTs}
set fd [open test$cnt.sql w]
puts $fd {DROP TABLE t1;}
puts $fd {DROP TABLE t2;}
close $fd
runtest {DROP TABLE}
+207
View File
@@ -0,0 +1,207 @@
#!/usr/bin/tclsh
#
# Run this script using TCLSH to do a speed comparison between
# various versions of SQLite and PostgreSQL and MySQL
#
# Run a test
#
set cnt 1
proc runtest {title} {
global cnt
set sqlfile test$cnt.sql
puts "<h2>Test $cnt: $title</h2>"
incr cnt
set fd [open $sqlfile r]
set sql [string trim [read $fd [file size $sqlfile]]]
close $fd
set sx [split $sql \n]
set n [llength $sx]
if {$n>8} {
set sql {}
for {set i 0} {$i<3} {incr i} {append sql [lindex $sx $i]<br>\n}
append sql "<i>... [expr {$n-6}] lines omitted</i><br>\n"
for {set i [expr {$n-3}]} {$i<$n} {incr i} {
append sql [lindex $sx $i]<br>\n
}
} else {
regsub -all \n [string trim $sql] <br> sql
}
puts "<blockquote>"
puts "$sql"
puts "</blockquote><table border=0 cellpadding=0 cellspacing=0>"
set format {<tr><td>%s</td><td align="right">&nbsp;&nbsp;&nbsp;%.3f</td></tr>}
set delay 1000
exec sync; after $delay;
set t [time "exec psql drh <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format PostgreSQL: $t]
exec sync; after $delay;
set t [time "exec mysql -f drh <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format MySQL: $t]
# set t [time "exec ./sqlite232 s232.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.3.2:} $t]
# set t [time "exec ./sqlite-100 s100.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.4 (cache=100):} $t]
exec sync; after $delay;
set t [time "exec ./sqlite240 s2k.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4:} $t]
exec sync; after $delay;
set t [time "exec ./sqlite240 sns.db <$sqlfile" 1]
set t [expr {[lindex $t 0]/1000000.0}]
puts [format $format {SQLite 2.4 (nosync):} $t]
# set t [time "exec ./sqlite-t1 st1.db <$sqlfile" 1]
# set t [expr {[lindex $t 0]/1000000.0}]
# puts [format $format {SQLite 2.4 (test):} $t]
puts "</table>"
}
# Initialize the environment
#
expr srand(1)
catch {exec /bin/sh -c {rm -f s*.db}}
set fd [open clear.sql w]
puts $fd {
drop table t1;
drop table t2;
}
close $fd
catch {exec psql drh <clear.sql}
catch {exec mysql drh <clear.sql}
set fd [open 2kinit.sql w]
puts $fd {
PRAGMA default_cache_size=2000;
PRAGMA default_synchronous=on;
}
close $fd
exec ./sqlite240 s2k.db <2kinit.sql
exec ./sqlite-t1 st1.db <2kinit.sql
set fd [open nosync-init.sql w]
puts $fd {
PRAGMA default_cache_size=2000;
PRAGMA default_synchronous=off;
}
close $fd
exec ./sqlite240 sns.db <nosync-init.sql
set ones {zero one two three four five six seven eight nine
ten eleven twelve thirteen fourteen fifteen sixteen seventeen
eighteen nineteen}
set tens {{} ten twenty thirty forty fifty sixty seventy eighty ninety}
proc number_name {n} {
if {$n>=1000} {
set txt "[number_name [expr {$n/1000}]] thousand"
set n [expr {$n%1000}]
} else {
set txt {}
}
if {$n>=100} {
append txt " [lindex $::ones [expr {$n/100}]] hundred"
set n [expr {$n%100}]
}
if {$n>=20} {
append txt " [lindex $::tens [expr {$n/10}]]"
set n [expr {$n%10}]
}
if {$n>0} {
append txt " [lindex $::ones $n]"
}
set txt [string trim $txt]
if {$txt==""} {set txt zero}
return $txt
}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
puts $fd "CREATE TABLE t1(a INTEGER, b INTEGER, c VARCHAR(100));"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
puts $fd "DELETE FROM t1;"
close $fd
runtest {DELETE everything}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
puts $fd "DELETE FROM t1;"
close $fd
runtest {DELETE everything}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
puts $fd "DELETE FROM t1;"
close $fd
runtest {DELETE everything}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
puts $fd "DELETE FROM t1;"
close $fd
runtest {DELETE everything}
set fd [open test$cnt.sql w]
puts $fd "BEGIN;"
for {set i 1} {$i<=25000} {incr i} {
set r [expr {int(rand()*500000)}]
puts $fd "INSERT INTO t1 VALUES($i,$r,'[number_name $r]');"
}
puts $fd "COMMIT;"
close $fd
runtest {25000 INSERTs in a transaction}
set fd [open test$cnt.sql w]
puts $fd "DELETE FROM t1;"
close $fd
runtest {DELETE everything}
set fd [open test$cnt.sql w]
puts $fd {DROP TABLE t1;}
close $fd
runtest {DROP TABLE}