mirror of
https://github.com/signalwire/freeswitch.git
synced 2026-07-24 21:22:09 +00:00
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:
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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;"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Executable
+51
@@ -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;"
|
||||
@@ -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
|
||||
@@ -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 %s %s %s</td>\n<td>\n", $3, $4, $5, $6
|
||||
}else if( $1=="*/" ){
|
||||
printf "</td></tr>\n"
|
||||
}else if( NF>1 ){
|
||||
sub(/^ *\*\* /,"")
|
||||
gsub(/</,"<")
|
||||
gsub(/&/,"&")
|
||||
print
|
||||
}
|
||||
}
|
||||
END {
|
||||
print "</table></body></html>"
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"> %.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}
|
||||
@@ -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"> %.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}
|
||||
Reference in New Issue
Block a user