From: drh Date: Sun, 8 Oct 2000 22:20:57 +0000 (+0000) Subject: Added the _printf() interface. (CVS 150) X-Git-Tag: version-3.6.10~5938 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=a18c5681e9ad68e8eee3b077b24309b1f0df2872;p=thirdparty%2Fsqlite.git Added the _printf() interface. (CVS 150) FossilOrigin-Name: f9372072a6d6caa15fa14ec722523944470fe155 --- diff --git a/Makefile.in b/Makefile.in index 37c7dab7a6..08bf85c7ba 100644 --- a/Makefile.in +++ b/Makefile.in @@ -48,7 +48,7 @@ LIBREADLINE = @TARGET_READLINE_LIBS@ # Object files for the SQLite library. # LIBOBJ = build.o dbbe.o delete.o expr.o insert.o \ - main.o parse.o select.o table.o tokenize.o update.o \ + main.o parse.o printf.o select.o table.o tokenize.o update.o \ util.o vdbe.o where.o tclsqlite.o # All of the source code files. @@ -62,6 +62,7 @@ SRC = \ $(TOP)/src/insert.c \ $(TOP)/src/main.c \ $(TOP)/src/parse.y \ + $(TOP)/src/printf.c \ $(TOP)/src/select.c \ $(TOP)/src/shell.c \ $(TOP)/src/sqlite.h.in \ @@ -164,6 +165,9 @@ update.o: $(TOP)/src/update.c $(HDR) tclsqlite.o: $(TOP)/src/tclsqlite.c $(HDR) $(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/tclsqlite.c +printf.o: $(TOP)/src/printf.c $(HDR) + $(TCC) $(GDBM_FLAGS) $(TCL_FLAGS) -c $(TOP)/src/printf.c + gdbmdump: $(TOP)/tool/gdbmdump.c $(TCC) $(GDBM_FLAGS) -o gdbmdump $(TOP)/tool/gdbmdump.c $(LIBGDBM) diff --git a/VERSION b/VERSION index b0f3d96f87..66c4c2263e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.8 +1.0.9 diff --git a/manifest b/manifest index e51b544bb5..1c703f59aa 100644 --- a/manifest +++ b/manifest @@ -1,9 +1,9 @@ -C Version\s1.0.8\s(CVS\s494) -D 2000-09-30T23:00:00 +C Added\sthe\s_printf()\sinterface.\s(CVS\s150) +D 2000-10-08T22:20:57 F COPYRIGHT 74a8a6531a42e124df07ab5599aad63870fa0bd4 -F Makefile.in 2fe404a488607712e569d69974d7929bae06ad1e +F Makefile.in f0b70aaa6717f9454c787dc74e5504c98ae7ea18 F README 51f6a4e7408b34afa5bc1c0485f61b6a4efb6958 -F VERSION c2e64b60cb74d152b5b5a0822f0e8cb673c928da +F VERSION 1fe894c9b80001e9dbc3b40ef1f4f984c9b624f3 F configure 3dc1edb9dcf60215e31ff72b447935ab62211442 x F configure.in d892ca33db7e88a055519ce2f36dcb11020e8fff F doc/lemon.html e233a3e97a779c7a87e1bc4528c664a58e49dd47 @@ -15,10 +15,11 @@ F src/expr.c e8e350d7baa33bd9ed8701c159eaba5e912e0adb F src/insert.c f146f149ad2422a1dc3bfa7a1651a25940f98958 F src/main.c 9a89579b40e498920f86e89878f52185457b9c2c F src/parse.y 5d199034de5d29ebedb42c1c51f34db4df40cbe5 +F src/printf.c 534954b942046761e0860c30a9031fa9d803ca3c F src/select.c d382e96c2221d08367cc87976f2b574537c9de97 -F src/shell.c 3d25f6709c2794cd910733a48a14f105eed6d7f3 +F src/shell.c ef5d12129c824cb98238763e9e86ca1847e0c7bd F src/shell.tcl 27ecbd63dd88396ad16d81ab44f73e6c0ea9d20e -F src/sqlite.h.in 1e0e4495172f752935ad534871ff726ae509d2f0 +F src/sqlite.h.in 8d87c93de1b43c3cce984a4204d8de84e0a626b6 F src/sqliteInt.h b65fdecac7281aafb4c9ff3e79ea1b5546478385 F src/table.c 12f0165b47178b54a675d25ed373ee7e798d6ff0 F src/tclsqlite.c 7ccccae67fb36ed60ec98282953bf5dad0f9c16f @@ -62,8 +63,8 @@ F tool/renumberOps.awk 6d067177ad5f8d711b79577b462da9b3634bd0a9 F www/arch.fig 4f246003b7da23bd63b8b0af0618afb4ee3055c8 F www/arch.png 8dae0766d42ed3de9ed013c1341a5792bcf633e6 F www/arch.tcl a40380c1fe0080c43e6cc5c20ed70731511b06be -F www/c_interface.tcl 73b5c1354e250a12ceaaccc376611351c867146a -F www/changes.tcl d62039b5387cc0871ec1bd3b15065a7c7d421cb1 +F www/c_interface.tcl 47d6b9b66510be10d3b482ce978a317a183477e5 +F www/changes.tcl c4f4584325dd55f7f4146bb9f5c5f9f7ce385302 F www/crosscompile.tcl 19734ce7f18b16ff2ed8479412abf8aca56e1dcc F www/fileformat.tcl cfb7fba80b7275555281ba2f256c00734bcdd1c9 F www/index.tcl b19418d506f90968deef972bf1b427d98bdf13e0 @@ -71,9 +72,9 @@ F www/lang.tcl 9192e114b19987e630a41e879585b87006eb84a1 F www/mingw.tcl fc5f4ba9d336b6e8c97347cc6496d6162461ef60 F www/opcode.tcl cb3a1abf8b7b9be9f3a228d097d6bf8b742c2b6f F www/sqlite.tcl cb0d23d8f061a80543928755ec7775da6e4f362f -F www/tclsqlite.tcl 21ecd82eaea3ce3d08593a9a2d2bfdb3c1f7b547 +F www/tclsqlite.tcl ae101d5f7c07dcc59770e2a84aae09025fab2dad F www/vdbe.tcl bcbfc33bcdd0ebad95eab31286adb9e1bc289520 -P 7e0bacedf928095b29e7166eacd2356e8169d6dd -R 3e751dc5aea8f130e00211562c95fd7d +P 384909e50f4ae2e84534f9a6505cfb5cc9f4d6ca +R 20b739a1a421bb63bda38b88ca775180 U drh -Z 96e782ab6ce077a32cb4132dcba512bf +Z 26c391a6fcfb2f70f6b3966c96a404f9 diff --git a/manifest.uuid b/manifest.uuid index 4313275861..52b6b1b042 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -384909e50f4ae2e84534f9a6505cfb5cc9f4d6ca \ No newline at end of file +f9372072a6d6caa15fa14ec722523944470fe155 \ No newline at end of file diff --git a/src/printf.c b/src/printf.c new file mode 100644 index 0000000000..ed3854e44f --- /dev/null +++ b/src/printf.c @@ -0,0 +1,788 @@ +/* +** The "printf" code that follows dates from the 1980's. It is in +** the public domain. The original comments are included here for +** completeness. They are slightly out-of-date. +** +** The following modules is an enhanced replacement for the "printf" programs +** found in the standard library. The following enhancements are +** supported: +** +** + Additional functions. The standard set of "printf" functions +** includes printf, fprintf, sprintf, vprintf, vfprintf, and +** vsprintf. This module adds the following: +** +** * snprintf -- Works like sprintf, but has an extra argument +** which is the size of the buffer written to. +** +** * mprintf -- Similar to sprintf. Writes output to memory +** obtained from malloc. +** +** * xprintf -- Calls a function to dispose of output. +** +** * nprintf -- No output, but returns the number of characters +** that would have been output by printf. +** +** * A v- version (ex: vsnprintf) of every function is also +** supplied. +** +** + A few extensions to the formatting notation are supported: +** +** * The "=" flag (similar to "-") causes the output to be +** be centered in the appropriately sized field. +** +** * The %b field outputs an integer in binary notation. +** +** * The %c field now accepts a precision. The character output +** is repeated by the number of times the precision specifies. +** +** * The %' field works like %c, but takes as its character the +** next character of the format string, instead of the next +** argument. For example, printf("%.78'-") prints 78 minus +** signs, the same as printf("%.78c",'-'). +** +** + When compiled using GCC on a SPARC, this version of printf is +** faster than the library printf for SUN OS 4.1. +** +** + All functions are fully reentrant. +** +*/ +#include "sqliteInt.h" +/* +** Undefine COMPATIBILITY to make some slight changes in the way things +** work. I think the changes are an improvement, but they are not +** backwards compatible. +*/ +/* #define COMPATIBILITY / * Compatible with SUN OS 4.1 */ + +/* +** Conversion types fall into various categories as defined by the +** following enumeration. +*/ +enum et_type { /* The type of the format field */ + etRADIX, /* Integer types. %d, %x, %o, and so forth */ + etFLOAT, /* Floating point. %f */ + etEXP, /* Exponentional notation. %e and %E */ + etGENERIC, /* Floating or exponential, depending on exponent. %g */ + etSIZE, /* Return number of characters processed so far. %n */ + etSTRING, /* Strings. %s */ + etPERCENT, /* Percent symbol. %% */ + etCHARX, /* Characters. %c */ + etERROR, /* Used to indicate no such conversion type */ +/* The rest are extensions, not normally found in printf() */ + etCHARLIT, /* Literal characters. %' */ + etSQLESCAPE, /* Strings with '\'' doubled. %q */ + etORDINAL /* 1st, 2nd, 3rd and so forth */ +}; + +/* +** Each builtin conversion character (ex: the 'd' in "%d") is described +** by an instance of the following structure +*/ +typedef struct et_info { /* Information about each format field */ + int fmttype; /* The format field code letter */ + int base; /* The base for radix conversion */ + char *charset; /* The character set for conversion */ + int flag_signed; /* Is the quantity signed? */ + char *prefix; /* Prefix on non-zero values in alt format */ + enum et_type type; /* Conversion paradigm */ +} et_info; + +/* +** The following table is searched linearly, so it is good to put the +** most frequently used conversion types first. +*/ +static et_info fmtinfo[] = { + { 'd', 10, "0123456789", 1, 0, etRADIX, }, + { 's', 0, 0, 0, 0, etSTRING, }, + { 'q', 0, 0, 0, 0, etSQLESCAPE, }, + { 'c', 0, 0, 0, 0, etCHARX, }, + { 'o', 8, "01234567", 0, "0", etRADIX, }, + { 'u', 10, "0123456789", 0, 0, etRADIX, }, + { 'x', 16, "0123456789abcdef", 0, "x0", etRADIX, }, + { 'X', 16, "0123456789ABCDEF", 0, "X0", etRADIX, }, + { 'r', 10, "0123456789", 0, 0, etORDINAL, }, + { 'f', 0, 0, 1, 0, etFLOAT, }, + { 'e', 0, "e", 1, 0, etEXP, }, + { 'E', 0, "E", 1, 0, etEXP, }, + { 'g', 0, "e", 1, 0, etGENERIC, }, + { 'G', 0, "E", 1, 0, etGENERIC, }, + { 'i', 10, "0123456789", 1, 0, etRADIX, }, + { 'n', 0, 0, 0, 0, etSIZE, }, + { '%', 0, 0, 0, 0, etPERCENT, }, + { 'b', 2, "01", 0, "b0", etRADIX, }, /* Binary */ + { 'p', 10, "0123456789", 0, 0, etRADIX, }, /* Pointers */ + { '\'', 0, 0, 0, 0, etCHARLIT, }, /* Literal char */ +}; +#define etNINFO (sizeof(fmtinfo)/sizeof(fmtinfo[0])) + +/* +** If NOFLOATINGPOINT is defined, then none of the floating point +** conversions will work. +*/ +#ifndef etNOFLOATINGPOINT +/* +** "*val" is a double such that 0.1 <= *val < 10.0 +** Return the ascii code for the leading digit of *val, then +** multiply "*val" by 10.0 to renormalize. +** +** Example: +** input: *val = 3.14159 +** output: *val = 1.4159 function return = '3' +** +** The counter *cnt is incremented each time. After counter exceeds +** 16 (the number of significant digits in a 64-bit float) '0' is +** always returned. +*/ +static int et_getdigit(double *val, int *cnt){ + int digit; + double d; + if( (*cnt)++ >= 16 ) return '0'; + digit = (int)*val; + d = digit; + digit += '0'; + *val = (*val - d)*10.0; + return digit; +} +#endif + +#define etBUFSIZE 1000 /* Size of the output buffer */ + +/* +** The root program. All variations call this core. +** +** INPUTS: +** func This is a pointer to a function taking three arguments +** 1. A pointer to anything. Same as the "arg" parameter. +** 2. A pointer to the list of characters to be output +** (Note, this list is NOT null terminated.) +** 3. An integer number of characters to be output. +** (Note: This number might be zero.) +** +** arg This is the pointer to anything which will be passed as the +** first argument to "func". Use it for whatever you like. +** +** fmt This is the format string, as in the usual print. +** +** ap This is a pointer to a list of arguments. Same as in +** vfprint. +** +** OUTPUTS: +** The return value is the total number of characters sent to +** the function "func". Returns -1 on a error. +** +** Note that the order in which automatic variables are declared below +** seems to make a big difference in determining how fast this beast +** will run. +*/ +static int vxprintf( + void (*func)(void*,char*,int), + void *arg, + const char *format, + va_list ap +){ + register const char *fmt; /* The format string. */ + register int c; /* Next character in the format string */ + register char *bufpt; /* Pointer to the conversion buffer */ + register int precision; /* Precision of the current field */ + register int length; /* Length of the field */ + register int idx; /* A general purpose loop counter */ + int count; /* Total number of characters output */ + int width; /* Width of the current field */ + int flag_leftjustify; /* True if "-" flag is present */ + int flag_plussign; /* True if "+" flag is present */ + int flag_blanksign; /* True if " " flag is present */ + int flag_alternateform; /* True if "#" flag is present */ + int flag_zeropad; /* True if field width constant starts with zero */ + int flag_long; /* True if "l" flag is present */ + int flag_center; /* True if "=" flag is present */ + unsigned long longvalue; /* Value for integer types */ + double realvalue; /* Value for real types */ + et_info *infop; /* Pointer to the appropriate info structure */ + char buf[etBUFSIZE]; /* Conversion buffer */ + char prefix; /* Prefix character. "+" or "-" or " " or '\0'. */ + int errorflag = 0; /* True if an error is encountered */ + enum et_type xtype; /* Conversion paradigm */ + char *zMem; /* String to be freed */ + char *zExtra; /* Extra memory used for etTCLESCAPE conversions */ + static char spaces[] = " " + " "; +#define etSPACESIZE (sizeof(spaces)-1) +#ifndef etNOFLOATINGPOINT + int exp; /* exponent of real numbers */ + double rounder; /* Used for rounding floating point values */ + int flag_dp; /* True if decimal point should be shown */ + int flag_rtz; /* True if trailing zeros should be removed */ + int flag_exp; /* True to force display of the exponent */ + int nsd; /* Number of significant digits returned */ +#endif + + fmt = format; /* Put in a register for speed */ + count = length = 0; + bufpt = 0; + for(; (c=(*fmt))!=0; ++fmt){ + if( c!='%' ){ + register int amt; + bufpt = (char *)fmt; + amt = 1; + while( (c=(*++fmt))!='%' && c!=0 ) amt++; + (*func)(arg,bufpt,amt); + count += amt; + if( c==0 ) break; + } + if( (c=(*++fmt))==0 ){ + errorflag = 1; + (*func)(arg,"%",1); + count++; + break; + } + /* Find out what flags are present */ + flag_leftjustify = flag_plussign = flag_blanksign = + flag_alternateform = flag_zeropad = flag_center = 0; + do{ + switch( c ){ + case '-': flag_leftjustify = 1; c = 0; break; + case '+': flag_plussign = 1; c = 0; break; + case ' ': flag_blanksign = 1; c = 0; break; + case '#': flag_alternateform = 1; c = 0; break; + case '0': flag_zeropad = 1; c = 0; break; + case '=': flag_center = 1; c = 0; break; + default: break; + } + }while( c==0 && (c=(*++fmt))!=0 ); + if( flag_center ) flag_leftjustify = 0; + /* Get the field width */ + width = 0; + if( c=='*' ){ + width = va_arg(ap,int); + if( width<0 ){ + flag_leftjustify = 1; + width = -width; + } + c = *++fmt; + }else{ + while( isdigit(c) ){ + width = width*10 + c - '0'; + c = *++fmt; + } + } + if( width > etBUFSIZE-10 ){ + width = etBUFSIZE-10; + } + /* Get the precision */ + if( c=='.' ){ + precision = 0; + c = *++fmt; + if( c=='*' ){ + precision = va_arg(ap,int); +#ifndef etCOMPATIBILITY + /* This is sensible, but SUN OS 4.1 doesn't do it. */ + if( precision<0 ) precision = -precision; +#endif + c = *++fmt; + }else{ + while( isdigit(c) ){ + precision = precision*10 + c - '0'; + c = *++fmt; + } + } + /* Limit the precision to prevent overflowing buf[] during conversion */ + if( precision>etBUFSIZE-40 ) precision = etBUFSIZE-40; + }else{ + precision = -1; + } + /* Get the conversion type modifier */ + if( c=='l' ){ + flag_long = 1; + c = *++fmt; + }else{ + flag_long = 0; + } + /* Fetch the info entry for the field */ + infop = 0; + for(idx=0; idxtype; + } + zExtra = 0; + + /* + ** At this point, variables are initialized as follows: + ** + ** flag_alternateform TRUE if a '#' is present. + ** flag_plussign TRUE if a '+' is present. + ** flag_leftjustify TRUE if a '-' is present or if the + ** field width was negative. + ** flag_zeropad TRUE if the width began with 0. + ** flag_long TRUE if the letter 'l' (ell) prefixed + ** the conversion character. + ** flag_blanksign TRUE if a ' ' is present. + ** width The specified field width. This is + ** always non-negative. Zero is the default. + ** precision The specified precision. The default + ** is -1. + ** xtype The class of the conversion. + ** infop Pointer to the appropriate info struct. + */ + switch( xtype ){ + case etORDINAL: + case etRADIX: + if( flag_long ) longvalue = va_arg(ap,long); + else longvalue = va_arg(ap,int); +#ifdef etCOMPATIBILITY + /* For the format %#x, the value zero is printed "0" not "0x0". + ** I think this is stupid. */ + if( longvalue==0 ) flag_alternateform = 0; +#else + /* More sensible: turn off the prefix for octal (to prevent "00"), + ** but leave the prefix for hex. */ + if( longvalue==0 && infop->base==8 ) flag_alternateform = 0; +#endif + if( infop->flag_signed ){ + if( *(long*)&longvalue<0 ){ + longvalue = -*(long*)&longvalue; + prefix = '-'; + }else if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + }else prefix = 0; + if( flag_zeropad && precision3 || (b>10 && b<14) ){ + bufpt[0] = 't'; + bufpt[1] = 'h'; + }else if( a==1 ){ + bufpt[0] = 's'; + bufpt[1] = 't'; + }else if( a==2 ){ + bufpt[0] = 'n'; + bufpt[1] = 'd'; + }else if( a==3 ){ + bufpt[0] = 'r'; + bufpt[1] = 'd'; + } + } + { + register char *cset; /* Use registers for speed */ + register int base; + cset = infop->charset; + base = infop->base; + do{ /* Convert to ascii */ + *(--bufpt) = cset[longvalue%base]; + longvalue = longvalue/base; + }while( longvalue>0 ); + } + length = (long)&buf[etBUFSIZE]-(long)bufpt; + for(idx=precision-length; idx>0; idx--){ + *(--bufpt) = '0'; /* Zero pad */ + } + if( prefix ) *(--bufpt) = prefix; /* Add sign */ + if( flag_alternateform && infop->prefix ){ /* Add "0" or "0x" */ + char *pre, x; + pre = infop->prefix; + if( *bufpt!=pre[0] ){ + for(pre=infop->prefix; (x=(*pre))!=0; pre++) *(--bufpt) = x; + } + } + length = (long)&buf[etBUFSIZE]-(long)bufpt; + break; + case etFLOAT: + case etEXP: + case etGENERIC: + realvalue = va_arg(ap,double); +#ifndef etNOFLOATINGPOINT + if( precision<0 ) precision = 6; /* Set default precision */ + if( precision>etBUFSIZE-10 ) precision = etBUFSIZE-10; + if( realvalue<0.0 ){ + realvalue = -realvalue; + prefix = '-'; + }else{ + if( flag_plussign ) prefix = '+'; + else if( flag_blanksign ) prefix = ' '; + else prefix = 0; + } + if( infop->type==etGENERIC && precision>0 ) precision--; + rounder = 0.0; +#ifdef COMPATIBILITY + /* Rounding works like BSD when the constant 0.4999 is used. Wierd! */ + for(idx=precision, rounder=0.4999; idx>0; idx--, rounder*=0.1); +#else + /* It makes more sense to use 0.5 */ + for(idx=precision, rounder=0.5; idx>0; idx--, rounder*=0.1); +#endif + if( infop->type==etFLOAT ) realvalue += rounder; + /* Normalize realvalue to within 10.0 > realvalue >= 1.0 */ + exp = 0; + if( realvalue>0.0 ){ + int k = 0; + while( realvalue>=1e8 && k++<100 ){ realvalue *= 1e-8; exp+=8; } + while( realvalue>=10.0 && k++<100 ){ realvalue *= 0.1; exp++; } + while( realvalue<1e-8 && k++<100 ){ realvalue *= 1e8; exp-=8; } + while( realvalue<1.0 && k++<100 ){ realvalue *= 10.0; exp--; } + if( k>=100 ){ + bufpt = "NaN"; + length = 3; + break; + } + } + bufpt = buf; + /* + ** If the field type is etGENERIC, then convert to either etEXP + ** or etFLOAT, as appropriate. + */ + flag_exp = xtype==etEXP; + if( xtype!=etFLOAT ){ + realvalue += rounder; + if( realvalue>=10.0 ){ realvalue *= 0.1; exp++; } + } + if( xtype==etGENERIC ){ + flag_rtz = !flag_alternateform; + if( exp<-4 || exp>precision ){ + xtype = etEXP; + }else{ + precision = precision - exp; + xtype = etFLOAT; + } + }else{ + flag_rtz = 0; + } + /* + ** The "exp+precision" test causes output to be of type etEXP if + ** the precision is too large to fit in buf[]. + */ + nsd = 0; + if( xtype==etFLOAT && exp+precision0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + if( exp<0 ) *(bufpt++) = '0'; /* Digits before "." */ + else for(; exp>=0; exp--) *(bufpt++) = et_getdigit(&realvalue,&nsd); + if( flag_dp ) *(bufpt++) = '.'; /* The decimal point */ + for(exp++; exp<0 && precision>0; precision--, exp++){ + *(bufpt++) = '0'; + } + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + *(bufpt--) = 0; /* Null terminate */ + if( flag_rtz && flag_dp ){ /* Remove trailing zeros and "." */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + }else{ /* etEXP or etGENERIC */ + flag_dp = (precision>0 || flag_alternateform); + if( prefix ) *(bufpt++) = prefix; /* Sign */ + *(bufpt++) = et_getdigit(&realvalue,&nsd); /* First digit */ + if( flag_dp ) *(bufpt++) = '.'; /* Decimal point */ + while( (precision--)>0 ) *(bufpt++) = et_getdigit(&realvalue,&nsd); + bufpt--; /* point to last digit */ + if( flag_rtz && flag_dp ){ /* Remove tail zeros */ + while( bufpt>=buf && *bufpt=='0' ) *(bufpt--) = 0; + if( bufpt>=buf && *bufpt=='.' ) *(bufpt--) = 0; + } + bufpt++; /* point to next free slot */ + if( exp || flag_exp ){ + *(bufpt++) = infop->charset[0]; + if( exp<0 ){ *(bufpt++) = '-'; exp = -exp; } /* sign of exp */ + else { *(bufpt++) = '+'; } + if( exp>=100 ){ + *(bufpt++) = (exp/100)+'0'; /* 100's digit */ + exp %= 100; + } + *(bufpt++) = exp/10+'0'; /* 10's digit */ + *(bufpt++) = exp%10+'0'; /* 1's digit */ + } + } + /* The converted number is in buf[] and zero terminated. Output it. + ** Note that the number is in the usual order, not reversed as with + ** integer conversions. */ + length = (long)bufpt-(long)buf; + bufpt = buf; + + /* Special case: Add leading zeros if the flag_zeropad flag is + ** set and we are not left justified */ + if( flag_zeropad && !flag_leftjustify && length < width){ + int i; + int nPad = width - length; + for(i=width; i>=nPad; i--){ + bufpt[i] = bufpt[i-nPad]; + } + i = prefix!=0; + while( nPad-- ) bufpt[i++] = '0'; + length = width; + } +#endif + break; + case etSIZE: + *(va_arg(ap,int*)) = count; + length = width = 0; + break; + case etPERCENT: + buf[0] = '%'; + bufpt = buf; + length = 1; + break; + case etCHARLIT: + case etCHARX: + c = buf[0] = (xtype==etCHARX ? va_arg(ap,int) : *++fmt); + if( precision>=0 ){ + for(idx=1; idx=0 && precisionetBUFSIZE ){ + bufpt = zExtra = sqliteMalloc( n ); + }else{ + bufpt = buf; + } + for(i=j=0; (c=arg[i])!=0; i++){ + bufpt[j++] = c; + if( c=='\'' ) bufpt[j++] = c; + } + bufpt[j] = 0; + length = j; + if( precision>=0 && precision0 ){ + if( flag_center ){ + nspace = nspace/2; + width -= nspace; + flag_leftjustify = 1; + } + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( length>0 ){ + (*func)(arg,bufpt,length); + count += length; + } + if( flag_leftjustify ){ + register int nspace; + nspace = width-length; + if( nspace>0 ){ + count += nspace; + while( nspace>=etSPACESIZE ){ + (*func)(arg,spaces,etSPACESIZE); + nspace -= etSPACESIZE; + } + if( nspace>0 ) (*func)(arg,spaces,nspace); + } + } + if( zExtra ){ + sqliteFree(zExtra); + } + }/* End for loop over the format string */ + return errorflag ? -1 : count; +} /* End of function */ + + +/* This structure is used to store state information about the +** write to memory that is currently in progress. +*/ +struct sgMprintf { + char *zBase; /* A base allocation */ + char *zText; /* The string collected so far */ + int nChar; /* Length of the string so far */ + int nAlloc; /* Amount of space allocated in zText */ +}; + +/* +** This function implements the callback from vxprintf. +** +** This routine add nNewChar characters of text in zNewText to +** the sgMprintf structure pointed to by "arg". +*/ +static void mout(void *arg, char *zNewText, int nNewChar){ + struct sgMprintf *pM = (struct sgMprintf*)arg; + if( pM->nChar + nNewChar + 1 > pM->nAlloc ){ + pM->nAlloc = pM->nChar + nNewChar*2 + 1; + if( pM->zText==pM->zBase ){ + pM->zText = sqliteMalloc(pM->nAlloc); + if( pM->zText && pM->nChar ) memcpy(pM->zText,pM->zBase,pM->nChar); + }else{ + pM->zText = sqliteRealloc(pM->zText, pM->nAlloc); + } + } + if( pM->zText ){ + memcpy(&pM->zText[pM->nChar], zNewText, nNewChar); + pM->nChar += nNewChar; + pM->zText[pM->nChar] = 0; + } +} + +/* +** sqlite_mprintf() works like printf(), but allocations memory to hold the +** resulting string and returns a pointer to the allocated memory. Use +** sqliteFree() to release the memory allocated. +*/ +char *sqlite_mprintf(const char *zFormat, ...){ + va_list ap; + struct sgMprintf sMprintf; + char *zNew; + char zBuf[200]; + + sMprintf.nChar = 0; + sMprintf.nAlloc = sizeof(zBuf); + sMprintf.zText = zBuf; + sMprintf.zBase = zBuf; + va_start(ap,zFormat); + vxprintf(mout,&sMprintf,zFormat,ap); + va_end(ap); + sMprintf.zText[sMprintf.nChar] = 0; + if( sMprintf.zText==sMprintf.zBase ){ + zNew = sqliteMalloc( sMprintf.nChar+1 ); + if( zNew ) strcpy(zNew,zBuf); + }else{ + zNew = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1); + } + return zNew; +} + +/* This is the varargs version of sqlite_mprintf. +*/ +char *sqlite_vmprintf(const char *zFormat, va_list ap){ + struct sgMprintf sMprintf; + char zBuf[200]; + sMprintf.nChar = 0; + sMprintf.zText = zBuf; + sMprintf.nAlloc = sizeof(zBuf); + sMprintf.zBase = zBuf; + vxprintf(mout,&sMprintf,zFormat,ap); + sMprintf.zText[sMprintf.nChar] = 0; + if( sMprintf.zText==sMprintf.zBase ){ + sMprintf.zText = sqliteMalloc( strlen(zBuf)+1 ); + if( sMprintf.zText ) strcpy(sMprintf.zText,zBuf); + }else{ + sMprintf.zText = sqliteRealloc(sMprintf.zText,sMprintf.nChar+1); + } + return sMprintf.zText; +} + +/* +** The following four routines implement the varargs versions of the +** sqlite_exec() and sqlite_get_table() interfaces. See the sqlite.h +** header files for a more detailed description of how these interfaces +** work. +** +** These routines are all just simple wrappers. +*/ +int sqlite_exec_printf( + sqlite *db, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback xCallback, /* Callback function */ + void *pArg, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string. */ +){ + va_list ap; + int rc; + + va_start(ap, errmsg); + rc = sqlite_exec_vprintf(db, sqlFormat, xCallback, pArg, errmsg, ap); + va_end(ap); + return rc; +} +int sqlite_exec_vprintf( + sqlite *db, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback xCallback, /* Callback function */ + void *pArg, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string. */ +){ + char *zSql; + int rc; + + zSql = sqlite_vmprintf(sqlFormat, ap); + rc = sqlite_exec(db, zSql, xCallback, pArg, errmsg); + sqliteFree(zSql); + return rc; +} +int sqlite_get_table_printf( + sqlite *db, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncol, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string */ +){ + va_list ap; + int rc; + + va_start(ap, errmsg); + rc = sqlite_get_table_vprintf(db, sqlFormat, resultp, nrow, ncol, errmsg, ap); + va_end(ap); + return rc; +} +int sqlite_get_table_vprintf( + sqlite *db, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string */ +){ + char *zSql; + int rc; + + zSql = sqlite_vmprintf(sqlFormat, ap); + rc = sqlite_get_table(db, zSql, resultp, nrow, ncolumn, errmsg); + sqliteFree(zSql); + return rc; +} diff --git a/src/shell.c b/src/shell.c index 9ef5896337..0fedfebf60 100644 --- a/src/shell.c +++ b/src/shell.c @@ -24,7 +24,7 @@ ** This file contains code to implement the "sqlite" command line ** utility for accessing SQLite databases. ** -** $Id: shell.c,v 1.25 2000/09/29 13:30:55 drh Exp $ +** $Id: shell.c,v 1.26 2000/10/08 22:20:58 drh Exp $ */ #include #include @@ -377,14 +377,15 @@ static int dump_callback(void *pArg, int nArg, char **azArg, char **azCol){ fprintf(pData->out, "%s;\n", azArg[2]); if( strcmp(azArg[1],"table")==0 ){ struct callback_data d2; - char zSql[1000]; d2 = *pData; d2.mode = MODE_List; d2.escape = '\t'; strcpy(d2.separator,"\t"); fprintf(pData->out, "COPY '%s' FROM STDIN;\n", azArg[0]); - sprintf(zSql, "SELECT * FROM '%s'", azArg[0]); - sqlite_exec(pData->db, zSql, callback, &d2, 0); + sqlite_exec_printf(pData->db, + "SELECT * FROM '%q'", + callback, &d2, 0, azArg[0] + ); fprintf(pData->out, "\\.\n"); } fprintf(pData->out, "VACUUM '%s';\n", azArg[0]); @@ -449,19 +450,22 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){ if( c=='d' && strncmp(azArg[0], "dump", n)==0 ){ char *zErrMsg = 0; - char zSql[1000]; if( nArg==1 ){ - sprintf(zSql, "SELECT name, type, sql FROM sqlite_master " - "WHERE type!='meta' " - "ORDER BY tbl_name, type DESC, name"); - sqlite_exec(db, zSql, dump_callback, p, &zErrMsg); + sqlite_exec(db, + "SELECT name, type, sql FROM sqlite_master " + "WHERE type!='meta' " + "ORDER BY tbl_name, type DESC, name", + dump_callback, p, &zErrMsg + ); }else{ int i; for(i=1; i1 ){ struct callback_data data; char *zErrMsg = 0; - char zSql[1000]; memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_List; - sprintf(zSql, "SELECT name FROM sqlite_master " - "WHERE type='index' AND tbl_name LIKE '%.800s' " - "ORDER BY name", azArg[1]); - sqlite_exec(db, zSql, callback, &data, &zErrMsg); + sqlite_exec_printf(db, + "SELECT name FROM sqlite_master " + "WHERE type='index' AND tbl_name LIKE '%q' " + "ORDER BY name", + callback, &data, &zErrMsg, azArg[1] + ); if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); free(zErrMsg); @@ -559,21 +564,37 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){ if( c=='s' && strncmp(azArg[0], "schema", n)==0 ){ struct callback_data data; char *zErrMsg = 0; - char zSql[1000]; memcpy(&data, p, sizeof(data)); data.showHeader = 0; data.mode = MODE_Semi; if( nArg>1 ){ - sprintf(zSql, "SELECT sql FROM sqlite_master " - "WHERE tbl_name LIKE '%.800s' AND type!='meta'" - "ORDER BY type DESC, name", - azArg[1]); + if( sqliteStrICmp(azArg[1],"sqlite_master")==0 ){ + char *new_argv[2], *new_colv[2]; + new_argv[0] = "CREATE TABLE sqlite_master (\n" + " type text,\n" + " name text,\n" + " tbl_name text,\n" + " sql text\n" + ")"; + new_argv[1] = 0; + new_colv[0] = "sql"; + new_colv[1] = 0; + callback(&data, 1, new_argv, new_colv); + }else{ + sqlite_exec_printf(db, + "SELECT sql FROM sqlite_master " + "WHERE tbl_name LIKE '%q' AND type!='meta'" + "ORDER BY type DESC, name", + callback, &data, &zErrMsg, azArg[1]); + } }else{ - sprintf(zSql, "SELECT sql FROM sqlite_master " + sqlite_exec(db, + "SELECT sql FROM sqlite_master " "WHERE type!='meta' " - "ORDER BY tbl_name, type DESC, name"); + "ORDER BY tbl_name, type DESC, name", + callback, &data, &zErrMsg + ); } - sqlite_exec(db, zSql, callback, &data, &zErrMsg); if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); free(zErrMsg); @@ -588,19 +609,21 @@ static void do_meta_command(char *zLine, sqlite *db, struct callback_data *p){ char **azResult; int nRow, rc; char *zErrMsg; - char zSql[1000]; if( nArg==1 ){ - sprintf(zSql, + rc = sqlite_get_table(db, "SELECT name FROM sqlite_master " "WHERE type='table' " - "ORDER BY name"); + "ORDER BY name", + &azResult, &nRow, 0, &zErrMsg + ); }else{ - sprintf(zSql, + rc = sqlite_get_table_printf(db, "SELECT name FROM sqlite_master " - "WHERE type='table' AND name LIKE '%%%.100s%%' " - "ORDER BY name", azArg[1]); + "WHERE type='table' AND name LIKE '%%%q%%' " + "ORDER BY name", + &azResult, &nRow, 0, &zErrMsg, azArg[1] + ); } - rc = sqlite_get_table(db, zSql, &azResult, &nRow, 0, &zErrMsg); if( zErrMsg ){ fprintf(stderr,"Error: %s\n", zErrMsg); free(zErrMsg); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 9b06b21525..3612f11e5f 100644 --- a/src/sqlite.h.in +++ b/src/sqlite.h.in @@ -24,10 +24,11 @@ ** This header file defines the interface that the sqlite library ** presents to client programs. ** -** @(#) $Id: sqlite.h.in,v 1.3 2000/09/29 13:30:55 drh Exp $ +** @(#) $Id: sqlite.h.in,v 1.4 2000/10/08 22:20:58 drh Exp $ */ #ifndef _SQLITE_H_ #define _SQLITE_H_ +#include /* Needed for the definition of va_list */ /* ** The version of the SQLite library. @@ -187,12 +188,39 @@ void sqlite_busy_timeout(sqlite*, int ms); ** Instead of invoking a user-supplied callback for each row of the ** result, this routine remembers each row of the result in memory ** obtained from malloc(), then returns all of the result after the -** query has finished. After the calling function has finished using -** the result, it should pass the result data pointer to -** sqlite_free_table() in order to release the memory that was malloc-ed. -** Because of the way the malloc() happens, the calling function must -** not try to call malloc() directly. Only sqlite_free_table() is able -** to release the memory properly and safely. +** query has finished. +** +** As an example, suppose the query result where this table: +** +** Name | Age +** ----------------------- +** Alice | 43 +** Bob | 28 +** Cindy | 21 +** +** If the 3rd argument were &azResult then after the function returns +** azResult would contain the following data: +** +** azResult[0] = "Name"; +** azResult[1] = "Age"; +** azResult[2] = "Alice"; +** azResult[3] = "43"; +** azResult[4] = "Bob"; +** azResult[5] = "28"; +** azResult[6] = "Cindy"; +** azResult[7] = "21"; +** +** Notice that there is an extra row of data containing the column +** headers. But the *nrow return value is still 3. *ncolumn is +** set to 2. In general, the number of values inserted into azResult +** will be ((*nrow) + 1)*(*ncolumn). +** +** After the calling function has finished using the result, it should +** pass the result data pointer to sqlite_free_table() in order to +** release the memory that was malloc-ed. Because of the way the +** malloc() happens, the calling function must not try to call +** malloc() directly. Only sqlite_free_table() is able to release +** the memory properly and safely. ** ** The return value of this routine is the same as from sqlite_exec(). */ @@ -210,5 +238,78 @@ int sqlite_get_table( */ void sqlite_free_table(char **result); +/* +** The following routines are wrappers around sqlite_exec() and +** sqlite_get_table(). The only difference between the routine that +** follow and the originals is that the second argument to the +** routines that follow is really a printf()-style format +** string describing the SQL to be executed. Arguments to the format +** string appear at the end of the argument list. +** +** All of the usual printf formatting options apply. In addition, there +** is a "%q" option. %q works like %s in that it substitutes a null-terminated +** string from the argument list. But %q also double every '\'' character. +** %q is designed for use inside a string literal. By doubling each '\'' +** character is escapes that character and allows it to be inserted into +** the string. +** +** For example, so some string variable contains text as follows: +** +** char *zText = "It's a happy day!"; +** +** We can use this text in an SQL statement as follows: +** +** sqlite_exec_printf(db, "INSERT INTO table VALUES('%q')", +** callback1, 0, 0, zText); +** +** Because the %q format string is used, the '\'' character in zText +** is escaped and the SQL generated is as follows: +** +** INSERT INTO table1 VALUES('It''s a happy day!') +** +** This is correct. Had we used %s instead of %q, the generated SQL +** would have looked like this: +** +** INSERT INTO table1 VALUES('It's a happy day!'); +** +** This second example is an SQL syntax error. As a general rule you +** should always use %q instead of %s when inserting text into a string +** literal. +*/ +int sqlite_exec_printf( + sqlite*, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string. */ +); +int sqlite_exec_vprintf( + sqlite*, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + sqlite_callback, /* Callback function */ + void *, /* 1st argument to callback function */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string. */ +); +int sqlite_get_table_printf( + sqlite*, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + ... /* Arguments to the format string */ +); +int sqlite_get_table_vprintf( + sqlite*, /* An open database */ + char *sqlFormat, /* printf-style format string for the SQL */ + char ***resultp, /* Result written to a char *[] that this points to */ + int *nrow, /* Number of result rows written here */ + int *ncolumn, /* Number of result columns written here */ + char **errmsg, /* Error msg written here */ + va_list ap /* Arguments to the format string */ +); + #endif /* _SQLITE_H_ */ diff --git a/www/c_interface.tcl b/www/c_interface.tcl index 108214cdc4..a33bded4c3 100644 --- a/www/c_interface.tcl +++ b/www/c_interface.tcl @@ -1,7 +1,7 @@ # # Run this Tcl script to generate the sqlite.html file. # -set rcsid {$Id: c_interface.tcl,v 1.8 2000/09/29 13:30:55 drh Exp $} +set rcsid {$Id: c_interface.tcl,v 1.9 2000/10/08 22:20:58 drh Exp $} puts { @@ -22,10 +22,9 @@ programming interface.

The API

-

The interface to the SQLite library consists of eight functions -(only the first three of which are required), -one opaque data structure, and some constants used as return -values from sqlite_exec():

+

The interface to the SQLite library consists of three core functions, +one opaque data structure, and some constants used as return values. +The core interface is as follows:

 typedef struct sqlite sqlite;
@@ -42,6 +41,22 @@ int sqlite_exec(
   char **errmsg
 );
 
+#define SQLITE_OK        0    /* Successful result */
+#define SQLITE_INTERNAL  1    /* An internal logic error in SQLite */
+#define SQLITE_ERROR     2    /* SQL error or missing database */
+#define SQLITE_PERM      3    /* Access permission denied */
+#define SQLITE_ABORT     4    /* Callback routine requested an abort */
+#define SQLITE_BUSY      5    /* One or more database files are locked */
+#define SQLITE_NOMEM     6    /* A malloc() failed */
+#define SQLITE_READONLY  7    /* Attempt to write a readonly database */
+
+ +

Only the three core routines shown above are required to use +SQLite. But there are many other functions that provide +useful interfaces. These extended routines are as follows: +

+ +
 int sqlite_get_table(
   sqlite*,
   char *sql,
@@ -61,14 +76,44 @@ void sqlite_busy_timeout(sqlite*, int ms);
 
 const char sqlite_version[];
 
-#define SQLITE_OK        0    /* Successful result */
-#define SQLITE_INTERNAL  1    /* An internal logic error in SQLite */
-#define SQLITE_ERROR     2    /* SQL error or missing database */
-#define SQLITE_PERM      3    /* Access permission denied */
-#define SQLITE_ABORT     4    /* Callback routine requested an abort */
-#define SQLITE_BUSY      5    /* One or more database files are locked */
-#define SQLITE_NOMEM     6    /* A malloc() failed */
-#define SQLITE_READONLY  7    /* Attempt to write a readonly database */
+int sqlite_exec_printf(
+  sqlite*,
+  char *sql,
+  int (*)(void*,int,char**,char**),
+  void*,
+  char **errmsg,
+  ...
+);
+
+int sqlite_exec_vprintf(
+  sqlite*,
+  char *sql,
+  int (*)(void*,int,char**,char**),
+  void*,
+  char **errmsg,
+  va_list
+);
+
+int sqlite_get_table_printf(
+  sqlite*,
+  char *sql,
+  char ***result,
+  int *nrow,
+  int *ncolumn,
+  char **errmsg,
+  ...
+);
+
+int sqlite_get_table_vprintf(
+  sqlite*,
+  char *sql,
+  char ***result,
+  int *nrow,
+  int *ncolumn,
+  char **errmsg,
+  va_list
+);
+
 

All of the above definitions are included in the "sqlite.h" @@ -304,7 +349,7 @@ the SQLITE_VERSION macro against the sqlite_version string constant to verify that the version number of the header file and the library match.

-

Changing the libraries reponse to locked files

+

Changing the libraries response to locked files

The GDBM library supports database locks at the file level. If a GDBM database file is opened for reading, then that same @@ -345,6 +390,85 @@ will wait for the lock to clear for at least the number of milliseconds specified before it returns SQLITE_BUSY. Specifying zero milliseconds for the timeout restores the default behavior.

+

Using the _printf() wrapper functions

+ +

The four utility functions

+ +

+

    +
  • sqlite_exec_printf()
  • +
  • sqlite_exec_vprintf()
  • +
  • sqlite_get_table_printf()
  • +
  • sqlite_get_table_vprintf()
  • +
+

+ +

implement the same query functionality as sqlite_exec() +and sqlite_get_table(). But instead of taking a complete +SQL statement as their second argument, the four _printf +routines take a printf-style format string. The SQL statement to +be executed is generated from this format string and from whatever +additional arguments are attached to the end of the function call.

+ +

There are two advantages to using the SQLite printf +functions instead of sprintf(). First of all, with the +SQLite printf routines, there is never a danger of overflowing a +static buffer as there is with sprintf(). The SQLite +printf routines automatically allocate (and later free) +as much memory as is +necessary to hold the SQL statements generated.

+ +

The second advantage the SQLite printf routines have over +sprintf() is a new formatting option specifically designed +to support string literals in SQL. Within the format string, +the %q formatting option works very much like %s in that it +reads a null-terminated string from the argument list and inserts +it into the result. But %q translates the inserted string by +making two copies of every single-quote (') character in the +substituted string. This has the effect of escaping the end-of-string +meaning of single-quote within a string literal. +

+ +

Consider an example. Suppose you are trying to insert a string +values into a database table where the string value was obtained from +user input. Suppose the string to be inserted is stored in a variable +named zString. The code to insert this string might look like this:

+ +
+sqlite_exec_printf(db,
+  "INSERT INTO table1 VALUES('%s')",
+  0, 0, 0, zString);
+
+ +

If the zString variable holds text like "Hello", then this statement +will work just fine. But suppose the user enters a string like +"Hi y'all!". The SQL statement generated reads as follows: + +

+INSERT INTO table1 VALUES('Hi y'all')
+
+ +

This is not valid SQL because of the apostrophy in the word "y'all". +But if the %q formatting option is used instead of %s, like this:

+ +
+sqlite_exec_printf(db,
+  "INSERT INTO table1 VALUES('%q')",
+  0, 0, 0, zString);
+
+ +

Then the generated SQL will look like the following:

+ +
+INSERT INTO table1 VALUES('Hi y''all')
+
+ +

Here the apostrophy has been escaped and the SQL statement is well-formed. +When generating SQL on-the-fly from data that might contain a +single-quote character ('), it is always a good idea to use the +SQLite printf routines and the %q formatting option instead of sprintf. +

+

Usage Examples

For examples of how the SQLite C/C++ interface can be used, diff --git a/www/changes.tcl b/www/changes.tcl index f2028309e9..bd1b0860d7 100644 --- a/www/changes.tcl +++ b/www/changes.tcl @@ -17,6 +17,14 @@ proc chng {date desc} { puts "

    $desc

" } +chng {2000 Oct 8 (1.0.9)} { +
  • Added the sqlite_..._printf() interface routines.
  • +
  • Modified the sqlite shell program to use the new interface + routines.
  • +
  • Modified the sqlite shell program to print the schema for + the built-in SQLITE_MASTER table, if explicitly requested.
  • +} + chng {2000 Sep 30 (1.0.8)} {
  • Begin writing documentation on the TCL interface.
  • } diff --git a/www/tclsqlite.tcl b/www/tclsqlite.tcl index 5c575b48f9..e3c7c2a1c3 100644 --- a/www/tclsqlite.tcl +++ b/www/tclsqlite.tcl @@ -1,7 +1,7 @@ # # Run this Tcl script to generate the tclsqlite.html file. # -set rcsid {$Id: tclsqlite.tcl,v 1.1 2000/09/30 22:46:07 drh Exp $} +set rcsid {$Id: tclsqlite.tcl,v 1.2 2000/10/08 22:20:58 drh Exp $} puts { @@ -108,7 +108,7 @@ db1 eval {CREATE TABLE t1(a int, b text)}

    The above code creates a new table named t1 with columns -a and b. What could be simplier?

    +a and b. What could be simpler?

    Query results are returned as a list of column values. If a query requests 2 columns and there are 3 rows matching the query, @@ -186,14 +186,58 @@ a=3 b=howdy!

    The "complete" method

    -TBD + +

    +The "complete" method takes a string of supposed SQL as its only argument. +It returns TRUE if the string is a complete statement of SQL and FALSE if +there is more to be entered.

    + +

    The "complete" method is useful when building interactive applications +in order to know when the user has finished entering a line of SQL code. +This is really just an interface to the sqlite_complete() C +function. Refer to the C/C++ interface +specification for additional information.

    The "timeout" method

    -TBD + +

    The "timeout" method is used to control how long the SQLite library +will wait for locks to clear before giving up on a database transaction. +The default timeout is 0 millisecond. (In other words, the default behavior +is not to wait at all.)

    + +

    The GDBM library the underlies SQLite allows multiple simultaneous +readers or a single writer but not both. If any process is writing to +the database no other process is allows to read or write. If any process +is reading the database other processes are allowed to read but not write. +Each GDBM file is locked separately. Because each SQL table is stored as +a separate file, it is possible for different processes to write to different +database tables at the same time, just not the same table.

    + +

    When SQLite tries to open a GDBM file and finds that it is locked, it +can optionally delay for a short while and try to open the file again. +This process repeats until the query times out and SQLite returns a +failure. The timeout is adjustable. It is set to 0 by default so that +if a GDBM file is locked, the SQL statement fails immediately. But you +can use the "timeout" method to change the timeout value to a positive +number. For example:

    + +
    db1 timeout 2000
    + +

    The argument to the timeout method is the maximum number of milliseconds +to wait for the lock to clear. So in the example above, the maximum delay +would be 2 seconds.

    The "busy" method

    -TBD +

    The "busy" method, like "timeout", only comes into play when a GDBM +file is locked. But the "busy" method gives the programmer much more +control over what action to take. The "busy" method specifies a callback +Tcl procedure that is invoked whenever SQLite tries to open a locked +GDBM file. This callback can do whatever is desired. Presumably, the +callback will do some other useful work for a short while then return +so that the lock can be tried again. The callback procedure should +return "0" if it wants SQLite to try again to open the GDBM file and +should return "1" if it wants SQLite to abandon the current operation. }