Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5e08d0fcb6 | |||
| d66c4f809d | |||
| a7c90c42ea | |||
| f94fdd832c | |||
| 30e3fdf0ee | |||
| cc97173810 | |||
| f0755fc85e | |||
| a4680a8b19 | |||
| 0b2c140a4d | |||
| 7edcf627af | |||
| c576b27580 | |||
| ac9c3d2c18 | |||
| 35db31b24b | |||
| adcba64df4 | |||
| 273bfe9f20 | |||
| 82a1c0e5bb | |||
| 9f6e686f59 | |||
| 62340f8498 | |||
| 1fc1a0f280 | |||
| abfd272b59 | |||
| 724b18966d | |||
| 0df5024e83 | |||
| dbd2dcbd5e | |||
| 66bf810467 | |||
| c1502e2f4f | |||
| 444c445ff8 | |||
| 00dae0a3ca | |||
| 3c40ae7d3a |
@@ -417,6 +417,7 @@ TESTSRC = \
|
||||
TESTSRC += \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
$(TOP)/ext/misc/csv.c \
|
||||
$(TOP)/ext/misc/eval.c \
|
||||
$(TOP)/ext/misc/fileio.c \
|
||||
$(TOP)/ext/misc/fuzzer.c \
|
||||
|
||||
@@ -1293,6 +1293,7 @@ TESTSRC = \
|
||||
TESTEXT = \
|
||||
$(TOP)\ext\misc\amatch.c \
|
||||
$(TOP)\ext\misc\closure.c \
|
||||
$(TOP)\ext\misc\csv.c \
|
||||
$(TOP)\ext\misc\eval.c \
|
||||
$(TOP)\ext\misc\fileio.c \
|
||||
$(TOP)\ext\misc\fuzzer.c \
|
||||
|
||||
+43
-31
@@ -30,40 +30,52 @@ AC_FUNC_STRERROR_R
|
||||
AC_CONFIG_FILES([Makefile sqlite3.pc])
|
||||
AC_SUBST(BUILD_CFLAGS)
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
#-------------------------------------------------------------------------
|
||||
# Two options to enable readline compatible libraries:
|
||||
#
|
||||
# --enable-editline
|
||||
# --enable-readline
|
||||
#
|
||||
AC_ARG_ENABLE(editline, [AS_HELP_STRING(
|
||||
[--enable-editline],
|
||||
[use BSD libedit])],
|
||||
[], [enable_editline=yes])
|
||||
AC_ARG_ENABLE(readline, [AS_HELP_STRING(
|
||||
[--enable-readline],
|
||||
[use readline])],
|
||||
[], [enable_readline=yes])
|
||||
if test x"$enable_editline" != xno ; then
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS([readline],[edit],[enable_readline=no],[enable_editline=no])
|
||||
READLINE_LIBS=$LIBS
|
||||
if test x"$LIBS" != "x"; then
|
||||
AC_DEFINE([HAVE_EDITLINE],1,Define to use BSD editline)
|
||||
enable_readline=no
|
||||
else
|
||||
unset ac_cv_search_readline
|
||||
fi
|
||||
LIBS=$sLIBS
|
||||
fi
|
||||
if test x"$enable_readline" != xno ; then
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS(tgetent, curses ncurses ncursesw, [], [])
|
||||
AC_SEARCH_LIBS(readline, readline, [], [enable_readline=no])
|
||||
AC_CHECK_FUNCS(readline, [], [])
|
||||
READLINE_LIBS=$LIBS
|
||||
LIBS=$sLIBS
|
||||
fi
|
||||
# Both are enabled by default. If, after command line processing both are
|
||||
# still enabled, the script searches for editline first and automatically
|
||||
# disables readline if it is found. So, to use readline explicitly, the
|
||||
# user must pass "--disable-editline". To disable command line editing
|
||||
# support altogether, "--disable-editline --disable-readline".
|
||||
#
|
||||
# When searching for either library, check for headers before libraries
|
||||
# as some distros supply packages that contain libraries but not header
|
||||
# files, which come as a separate development package.
|
||||
#
|
||||
AC_ARG_ENABLE(editline, [AS_HELP_STRING([--enable-editline],[use BSD libedit])])
|
||||
AC_ARG_ENABLE(readline, [AS_HELP_STRING([--enable-readline],[use readline])])
|
||||
|
||||
AS_IF([ test x"$enable_editline" != xno ],[
|
||||
AC_CHECK_HEADERS([editline/readline.h],[
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS([readline],[edit],[
|
||||
AC_DEFINE([HAVE_EDITLINE],1,Define to use BSD editline)
|
||||
READLINE_LIBS=$LIBS
|
||||
enable_readline=no
|
||||
])
|
||||
AS_UNSET(ac_cv_search_readline)
|
||||
LIBS=$sLIBS
|
||||
])
|
||||
])
|
||||
|
||||
AS_IF([ test x"$enable_readline" != xno ],[
|
||||
AC_CHECK_HEADERS([readline/readline.h],[
|
||||
sLIBS=$LIBS
|
||||
LIBS=""
|
||||
AC_SEARCH_LIBS(tgetent, termcap curses ncurses ncursesw, [], [])
|
||||
AC_SEARCH_LIBS(readline,[readline edit], [
|
||||
AC_DEFINE([HAVE_READLINE],1,Define to use readline or wrapper)
|
||||
READLINE_LIBS=$LIBS
|
||||
])
|
||||
LIBS=$sLIBS
|
||||
])
|
||||
])
|
||||
|
||||
AC_SUBST(READLINE_LIBS)
|
||||
#-----------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -686,7 +686,6 @@ int sqlite3Fts5ExprPopulatePoslists(
|
||||
Fts5Config*, Fts5Expr*, Fts5PoslistPopulator*, int, const char*, int
|
||||
);
|
||||
void sqlite3Fts5ExprCheckPoslists(Fts5Expr*, i64);
|
||||
void sqlite3Fts5ExprClearEof(Fts5Expr*);
|
||||
|
||||
int sqlite3Fts5ExprClonePhrase(Fts5Expr*, int, Fts5Expr**);
|
||||
|
||||
|
||||
@@ -2617,17 +2617,6 @@ void sqlite3Fts5ExprCheckPoslists(Fts5Expr *pExpr, i64 iRowid){
|
||||
fts5ExprCheckPoslists(pExpr->pRoot, iRowid);
|
||||
}
|
||||
|
||||
static void fts5ExprClearEof(Fts5ExprNode *pNode){
|
||||
int i;
|
||||
for(i=0; i<pNode->nChild; i++){
|
||||
fts5ExprClearEof(pNode->apChild[i]);
|
||||
}
|
||||
pNode->bEof = 0;
|
||||
}
|
||||
void sqlite3Fts5ExprClearEof(Fts5Expr *pExpr){
|
||||
fts5ExprClearEof(pExpr->pRoot);
|
||||
}
|
||||
|
||||
/*
|
||||
** This function is only called for detail=columns tables.
|
||||
*/
|
||||
|
||||
@@ -1186,7 +1186,6 @@ static int fts5FilterMethod(
|
||||
pCsr->ePlan = FTS5_PLAN_SOURCE;
|
||||
pCsr->pExpr = pTab->pSortCsr->pExpr;
|
||||
rc = fts5CursorFirst(pTab, pCsr, bDesc);
|
||||
sqlite3Fts5ExprClearEof(pCsr->pExpr);
|
||||
}else if( pMatch ){
|
||||
const char *zExpr = (const char*)sqlite3_value_text(apVal[0]);
|
||||
if( zExpr==0 ) zExpr = "";
|
||||
|
||||
@@ -91,9 +91,38 @@ do_test 2.7 {
|
||||
} {1 3 2}
|
||||
|
||||
|
||||
#--------------------------------------------------------------------------
|
||||
# At one point there was a problem with queries such as:
|
||||
#
|
||||
# ... MATCH 'x OR y' ORDER BY rank;
|
||||
#
|
||||
# if there were zero occurrences of token 'y' in the dataset. The
|
||||
# following tests verify that that problem has been addressed.
|
||||
#
|
||||
foreach_detail_mode $::testprefix {
|
||||
do_execsql_test 3.0 {
|
||||
CREATE VIRTUAL TABLE y1 USING fts5(z, detail=%DETAIL%);
|
||||
INSERT INTO y1 VALUES('test xyz');
|
||||
INSERT INTO y1 VALUES('test test xyz test');
|
||||
INSERT INTO y1 VALUES('test test xyz');
|
||||
}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
SELECT rowid FROM y1('test OR tset');
|
||||
} {1 2 3}
|
||||
|
||||
do_execsql_test 3.2 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY bm25(y1)
|
||||
} {2 3 1}
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY +rank
|
||||
} {2 3 1}
|
||||
|
||||
do_execsql_test 3.4 {
|
||||
SELECT rowid FROM y1('test OR tset') ORDER BY rank
|
||||
} {2 3 1}
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
+861
@@ -0,0 +1,861 @@
|
||||
/*
|
||||
** 2016-05-28
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file contains the implementation of an SQLite virtual table for
|
||||
** reading CSV files.
|
||||
**
|
||||
** Usage:
|
||||
**
|
||||
** .load ./csv
|
||||
** CREATE VIRTUAL TABLE temp.csv USING csv(filename=FILENAME);
|
||||
** SELECT * FROM csv;
|
||||
**
|
||||
** The columns are named "c1", "c2", "c3", ... by default. But the
|
||||
** application can define its own CREATE TABLE statement as an additional
|
||||
** parameter. For example:
|
||||
**
|
||||
** CREATE VIRTUAL TABLE temp.csv2 USING csv(
|
||||
** filename = "../http.log",
|
||||
** schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
|
||||
** );
|
||||
**
|
||||
** Instead of specifying a file, the text of the CSV can be loaded using
|
||||
** the data= parameter.
|
||||
**
|
||||
** If the columns=N parameter is supplied, then the CSV file is assumed to have
|
||||
** N columns. If the columns parameter is omitted, the CSV file is opened
|
||||
** as soon as the virtual table is constructed and the first row of the CSV
|
||||
** is read in order to count the tables.
|
||||
**
|
||||
** Some extra debugging features (used for testing virtual tables) are available
|
||||
** if this module is compiled with -DSQLITE_TEST.
|
||||
*/
|
||||
#include <sqlite3ext.h>
|
||||
SQLITE_EXTENSION_INIT1
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdarg.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
/*
|
||||
** A macro to hint to the compiler that a function should not be
|
||||
** inlined.
|
||||
*/
|
||||
#if defined(__GNUC__)
|
||||
# define CSV_NOINLINE __attribute__((noinline))
|
||||
#elif defined(_MSC_VER) && _MSC_VER>=1310
|
||||
# define CSV_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
# define CSV_NOINLINE
|
||||
#endif
|
||||
|
||||
|
||||
/* Max size of the error message in a CsvReader */
|
||||
#define CSV_MXERR 200
|
||||
|
||||
/* Size of the CsvReader input buffer */
|
||||
#define CSV_INBUFSZ 1024
|
||||
|
||||
/* A context object used when read a CSV file. */
|
||||
typedef struct CsvReader CsvReader;
|
||||
struct CsvReader {
|
||||
FILE *in; /* Read the CSV text from this input stream */
|
||||
char *z; /* Accumulated text for a field */
|
||||
int n; /* Number of bytes in z */
|
||||
int nAlloc; /* Space allocated for z[] */
|
||||
int nLine; /* Current line number */
|
||||
char cTerm; /* Character that terminated the most recent field */
|
||||
size_t iIn; /* Next unread character in the input buffer */
|
||||
size_t nIn; /* Number of characters in the input buffer */
|
||||
char *zIn; /* The input buffer */
|
||||
char zErr[CSV_MXERR]; /* Error message */
|
||||
};
|
||||
|
||||
/* Initialize a CsvReader object */
|
||||
static void csv_reader_init(CsvReader *p){
|
||||
p->in = 0;
|
||||
p->z = 0;
|
||||
p->n = 0;
|
||||
p->nAlloc = 0;
|
||||
p->nLine = 0;
|
||||
p->nIn = 0;
|
||||
p->zIn = 0;
|
||||
p->zErr[0] = 0;
|
||||
}
|
||||
|
||||
/* Close and reset a CsvReader object */
|
||||
static void csv_reader_reset(CsvReader *p){
|
||||
if( p->in ){
|
||||
fclose(p->in);
|
||||
sqlite3_free(p->zIn);
|
||||
}
|
||||
sqlite3_free(p->z);
|
||||
csv_reader_init(p);
|
||||
}
|
||||
|
||||
/* Report an error on a CsvReader */
|
||||
static void csv_errmsg(CsvReader *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
va_start(ap, zFormat);
|
||||
sqlite3_vsnprintf(CSV_MXERR, p->zErr, zFormat, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
/* Open the file associated with a CsvReader
|
||||
** Return the number of errors.
|
||||
*/
|
||||
static int csv_reader_open(
|
||||
CsvReader *p, /* The reader to open */
|
||||
const char *zFilename, /* Read from this filename */
|
||||
const char *zData /* ... or use this data */
|
||||
){
|
||||
if( zFilename ){
|
||||
p->zIn = sqlite3_malloc( CSV_INBUFSZ );
|
||||
if( p->zIn==0 ){
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
p->in = fopen(zFilename, "rb");
|
||||
if( p->in==0 ){
|
||||
csv_reader_reset(p);
|
||||
csv_errmsg(p, "cannot open '%s' for reading", zFilename);
|
||||
return 1;
|
||||
}
|
||||
}else{
|
||||
assert( p->in==0 );
|
||||
p->zIn = (char*)zData;
|
||||
p->nIn = strlen(zData);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* The input buffer has overflowed. Refill the input buffer, then
|
||||
** return the next character
|
||||
*/
|
||||
static CSV_NOINLINE int csv_getc_refill(CsvReader *p){
|
||||
size_t got;
|
||||
|
||||
assert( p->iIn>=p->nIn ); /* Only called on an empty input buffer */
|
||||
assert( p->in!=0 ); /* Only called if reading froma file */
|
||||
|
||||
got = fread(p->zIn, 1, CSV_INBUFSZ, p->in);
|
||||
if( got==0 ) return EOF;
|
||||
p->nIn = got;
|
||||
p->iIn = 1;
|
||||
return p->zIn[0];
|
||||
}
|
||||
|
||||
/* Return the next character of input. Return EOF at end of input. */
|
||||
static int csv_getc(CsvReader *p){
|
||||
if( p->iIn >= p->nIn ){
|
||||
if( p->in!=0 ) return csv_getc_refill(p);
|
||||
return EOF;
|
||||
}
|
||||
return p->zIn[p->iIn++];
|
||||
}
|
||||
|
||||
/* Increase the size of p->z and append character c to the end.
|
||||
** Return 0 on success and non-zero if there is an OOM error */
|
||||
static CSV_NOINLINE int csv_resize_and_append(CsvReader *p, char c){
|
||||
char *zNew;
|
||||
int nNew = p->nAlloc*2 + 100;
|
||||
zNew = sqlite3_realloc64(p->z, nNew);
|
||||
if( zNew ){
|
||||
p->z = zNew;
|
||||
p->nAlloc = nNew;
|
||||
p->z[p->n++] = c;
|
||||
return 0;
|
||||
}else{
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Append a single character to the CsvReader.z[] array.
|
||||
** Return 0 on success and non-zero if there is an OOM error */
|
||||
static int csv_append(CsvReader *p, char c){
|
||||
if( p->n>=p->nAlloc-1 ) return csv_resize_and_append(p, c);
|
||||
p->z[p->n++] = c;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read a single field of CSV text. Compatible with rfc4180 and extended
|
||||
** with the option of having a separator other than ",".
|
||||
**
|
||||
** + Input comes from p->in.
|
||||
** + Store results in p->z of length p->n. Space to hold p->z comes
|
||||
** from sqlite3_malloc64().
|
||||
** + Keep track of the line number in p->nLine.
|
||||
** + Store the character that terminates the field in p->cTerm. Store
|
||||
** EOF on end-of-file.
|
||||
**
|
||||
** Return "" at EOF. Return 0 on an OOM error.
|
||||
*/
|
||||
static char *csv_read_one_field(CsvReader *p){
|
||||
int c;
|
||||
p->n = 0;
|
||||
c = csv_getc(p);
|
||||
if( c==EOF ){
|
||||
p->cTerm = EOF;
|
||||
return "";
|
||||
}
|
||||
if( c=='"' ){
|
||||
int pc, ppc;
|
||||
int startLine = p->nLine;
|
||||
pc = ppc = 0;
|
||||
while( 1 ){
|
||||
c = csv_getc(p);
|
||||
if( c<='"' || pc=='"' ){
|
||||
if( c=='\n' ) p->nLine++;
|
||||
if( c=='"' ){
|
||||
if( pc=='"' ){
|
||||
pc = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if( (c==',' && pc=='"')
|
||||
|| (c=='\n' && pc=='"')
|
||||
|| (c=='\n' && pc=='\r' && ppc=='"')
|
||||
|| (c==EOF && pc=='"')
|
||||
){
|
||||
do{ p->n--; }while( p->z[p->n]!='"' );
|
||||
p->cTerm = c;
|
||||
break;
|
||||
}
|
||||
if( pc=='"' && c!='\r' ){
|
||||
csv_errmsg(p, "line %d: unescaped %c character", p->nLine, '"');
|
||||
break;
|
||||
}
|
||||
if( c==EOF ){
|
||||
csv_errmsg(p, "line %d: unterminated %c-quoted field\n",
|
||||
startLine, '"');
|
||||
p->cTerm = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( csv_append(p, (char)c) ) return 0;
|
||||
ppc = pc;
|
||||
pc = c;
|
||||
}
|
||||
}else{
|
||||
while( c>',' || (c!=EOF && c!=',' && c!='\n') ){
|
||||
if( csv_append(p, (char)c) ) return 0;
|
||||
c = csv_getc(p);
|
||||
}
|
||||
if( c=='\n' ){
|
||||
p->nLine++;
|
||||
if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
|
||||
}
|
||||
p->cTerm = c;
|
||||
}
|
||||
if( p->z ) p->z[p->n] = 0;
|
||||
return p->z;
|
||||
}
|
||||
|
||||
|
||||
/* Forward references to the various virtual table methods implemented
|
||||
** in this file. */
|
||||
static int csvtabCreate(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int csvtabConnect(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int csvtabBestIndex(sqlite3_vtab*,sqlite3_index_info*);
|
||||
static int csvtabDisconnect(sqlite3_vtab*);
|
||||
static int csvtabOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
|
||||
static int csvtabClose(sqlite3_vtab_cursor*);
|
||||
static int csvtabFilter(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv);
|
||||
static int csvtabNext(sqlite3_vtab_cursor*);
|
||||
static int csvtabEof(sqlite3_vtab_cursor*);
|
||||
static int csvtabColumn(sqlite3_vtab_cursor*,sqlite3_context*,int);
|
||||
static int csvtabRowid(sqlite3_vtab_cursor*,sqlite3_int64*);
|
||||
|
||||
/* An instance of the CSV virtual table */
|
||||
typedef struct CsvTable {
|
||||
sqlite3_vtab base; /* Base class. Must be first */
|
||||
char *zFilename; /* Name of the CSV file */
|
||||
char *zData; /* Raw CSV data in lieu of zFilename */
|
||||
long iStart; /* Offset to start of data in zFilename */
|
||||
int nCol; /* Number of columns in the CSV file */
|
||||
unsigned int tstFlags; /* Bit values used for testing */
|
||||
} CsvTable;
|
||||
|
||||
/* Allowed values for tstFlags */
|
||||
#define CSVTEST_FIDX 0x0001 /* Pretend that constrained searchs cost less*/
|
||||
|
||||
/* A cursor for the CSV virtual table */
|
||||
typedef struct CsvCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
CsvReader rdr; /* The CsvReader object */
|
||||
char **azVal; /* Value of the current row */
|
||||
int *aLen; /* Length of each entry */
|
||||
sqlite3_int64 iRowid; /* The current rowid. Negative for EOF */
|
||||
} CsvCursor;
|
||||
|
||||
/* Transfer error message text from a reader into a CsvTable */
|
||||
static void csv_xfer_error(CsvTable *pTab, CsvReader *pRdr){
|
||||
sqlite3_free(pTab->base.zErrMsg);
|
||||
pTab->base.zErrMsg = sqlite3_mprintf("%s", pRdr->zErr);
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is the destructor fo a CsvTable object.
|
||||
*/
|
||||
static int csvtabDisconnect(sqlite3_vtab *pVtab){
|
||||
CsvTable *p = (CsvTable*)pVtab;
|
||||
sqlite3_free(p->zFilename);
|
||||
sqlite3_free(p->zData);
|
||||
sqlite3_free(p);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/* Skip leading whitespace. Return a pointer to the first non-whitespace
|
||||
** character, or to the zero terminator if the string has only whitespace */
|
||||
static const char *csv_skip_whitespace(const char *z){
|
||||
while( isspace((unsigned char)z[0]) ) z++;
|
||||
return z;
|
||||
}
|
||||
|
||||
/* Remove trailing whitespace from the end of string z[] */
|
||||
static void csv_trim_whitespace(char *z){
|
||||
size_t n = strlen(z);
|
||||
while( n>0 && isspace((unsigned char)z[n]) ) n--;
|
||||
z[n] = 0;
|
||||
}
|
||||
|
||||
/* Dequote the string */
|
||||
static void csv_dequote(char *z){
|
||||
int i, j;
|
||||
char cQuote = z[0];
|
||||
size_t n;
|
||||
|
||||
if( cQuote!='\'' && cQuote!='"' ) return;
|
||||
n = strlen(z);
|
||||
if( n<2 || z[n-1]!=z[0] ) return;
|
||||
for(i=1, j=0; i<n-1; i++){
|
||||
if( z[i]==cQuote && z[i+1]==cQuote ) i++;
|
||||
z[j++] = z[i];
|
||||
}
|
||||
z[j] = 0;
|
||||
}
|
||||
|
||||
/* Check to see if the string is of the form: "TAG = VALUE" with optional
|
||||
** whitespace before and around tokens. If it is, return a pointer to the
|
||||
** first character of VALUE. If it is not, return NULL.
|
||||
*/
|
||||
static const char *csv_parameter(const char *zTag, int nTag, const char *z){
|
||||
z = csv_skip_whitespace(z);
|
||||
if( strncmp(zTag, z, nTag)!=0 ) return 0;
|
||||
z = csv_skip_whitespace(z+nTag);
|
||||
if( z[0]!='=' ) return 0;
|
||||
return csv_skip_whitespace(z+1);
|
||||
}
|
||||
|
||||
/* Decode a parameter that requires a dequoted string.
|
||||
**
|
||||
** Return 1 if the parameter is seen, or 0 if not. 1 is returned
|
||||
** even if there is an error. If an error occurs, then an error message
|
||||
** is left in p->zErr. If there are no errors, p->zErr[0]==0.
|
||||
*/
|
||||
static int csv_string_parameter(
|
||||
CsvReader *p, /* Leave the error message here, if there is one */
|
||||
const char *zParam, /* Parameter we are checking for */
|
||||
const char *zArg, /* Raw text of the virtual table argment */
|
||||
char **pzVal /* Write the dequoted string value here */
|
||||
){
|
||||
const char *zValue;
|
||||
zValue = csv_parameter(zParam,strlen(zParam),zArg);
|
||||
if( zValue==0 ) return 0;
|
||||
p->zErr[0] = 0;
|
||||
if( *pzVal ){
|
||||
csv_errmsg(p, "more than one '%s' parameter", zParam);
|
||||
return 1;
|
||||
}
|
||||
*pzVal = sqlite3_mprintf("%s", zValue);
|
||||
if( *pzVal==0 ){
|
||||
csv_errmsg(p, "out of memory");
|
||||
return 1;
|
||||
}
|
||||
csv_trim_whitespace(*pzVal);
|
||||
csv_dequote(*pzVal);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* Return 0 if the argument is false and 1 if it is true. Return -1 if
|
||||
** we cannot really tell.
|
||||
*/
|
||||
static int csv_boolean(const char *z){
|
||||
if( sqlite3_stricmp("yes",z)==0
|
||||
|| sqlite3_stricmp("on",z)==0
|
||||
|| sqlite3_stricmp("true",z)==0
|
||||
|| (z[0]=='1' && z[0]==0)
|
||||
){
|
||||
return 1;
|
||||
}
|
||||
if( sqlite3_stricmp("no",z)==0
|
||||
|| sqlite3_stricmp("off",z)==0
|
||||
|| sqlite3_stricmp("false",z)==0
|
||||
|| (z[0]=='0' && z[1]==0)
|
||||
){
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Parameters:
|
||||
** filename=FILENAME Name of file containing CSV content
|
||||
** data=TEXT Direct CSV content.
|
||||
** schema=SCHEMA Alternative CSV schema.
|
||||
** header=YES|NO First row of CSV defines the names of
|
||||
** columns if "yes". Default "no".
|
||||
** columns=N Assume the CSV file contains N columns.
|
||||
**
|
||||
** Only available if compiled with SQLITE_TEST:
|
||||
**
|
||||
** testflags=N Bitmask of test flags. Optional
|
||||
**
|
||||
** If schema= is omitted, then the columns are named "c0", "c1", "c2",
|
||||
** and so forth. If columns=N is omitted, then the file is opened and
|
||||
** the number of columns in the first row is counted to determine the
|
||||
** column count. If header=YES, then the first row is skipped.
|
||||
*/
|
||||
static int csvtabConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
CsvTable *pNew = 0; /* The CsvTable object to construct */
|
||||
int bHeader = -1; /* header= flags. -1 means not seen yet */
|
||||
int rc = SQLITE_OK; /* Result code from this routine */
|
||||
int i, j; /* Loop counters */
|
||||
#ifdef SQLITE_TEST
|
||||
int tstFlags = 0; /* Value for testflags=N parameter */
|
||||
#endif
|
||||
int nCol = -99; /* Value of the columns= parameter */
|
||||
CsvReader sRdr; /* A CSV file reader used to store an error
|
||||
** message and/or to count the number of columns */
|
||||
static const char *azParam[] = {
|
||||
"filename", "data", "schema",
|
||||
};
|
||||
char *azPValue[3]; /* Parameter values */
|
||||
# define CSV_FILENAME (azPValue[0])
|
||||
# define CSV_DATA (azPValue[1])
|
||||
# define CSV_SCHEMA (azPValue[2])
|
||||
|
||||
|
||||
assert( sizeof(azPValue)==sizeof(azParam) );
|
||||
memset(&sRdr, 0, sizeof(sRdr));
|
||||
memset(azPValue, 0, sizeof(azPValue));
|
||||
for(i=3; i<argc; i++){
|
||||
const char *z = argv[i];
|
||||
const char *zValue;
|
||||
for(j=0; j<sizeof(azParam)/sizeof(azParam[0]); j++){
|
||||
if( csv_string_parameter(&sRdr, azParam[j], z, &azPValue[j]) ) break;
|
||||
}
|
||||
if( j<sizeof(azParam)/sizeof(azParam[0]) ){
|
||||
if( sRdr.zErr[0] ) goto csvtab_connect_error;
|
||||
}else
|
||||
if( (zValue = csv_parameter("header",6,z))!=0 ){
|
||||
int x;
|
||||
if( bHeader>=0 ){
|
||||
csv_errmsg(&sRdr, "more than one 'header' parameter");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
x = csv_boolean(zValue);
|
||||
if( x==1 ){
|
||||
bHeader = 1;
|
||||
}else if( x==0 ){
|
||||
bHeader = 0;
|
||||
}else{
|
||||
csv_errmsg(&sRdr, "unrecognized argument to 'header': %s", zValue);
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}else
|
||||
#ifdef SQLITE_TEST
|
||||
if( (zValue = csv_parameter("testflags",9,z))!=0 ){
|
||||
tstFlags = (unsigned int)atoi(zValue);
|
||||
}else
|
||||
#endif
|
||||
if( (zValue = csv_parameter("columns",7,z))!=0 ){
|
||||
if( nCol>0 ){
|
||||
csv_errmsg(&sRdr, "more than one 'columns' parameter");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
nCol = atoi(zValue);
|
||||
if( nCol<=0 ){
|
||||
csv_errmsg(&sRdr, "must have at least one column");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}else
|
||||
{
|
||||
csv_errmsg(&sRdr, "unrecognized parameter '%s'", z);
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
}
|
||||
if( (CSV_FILENAME==0)==(CSV_DATA==0) ){
|
||||
csv_errmsg(&sRdr, "must either filename= or data= but not both");
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
if( nCol<=0 && csv_reader_open(&sRdr, CSV_FILENAME, CSV_DATA) ){
|
||||
goto csvtab_connect_error;
|
||||
}
|
||||
pNew = sqlite3_malloc( sizeof(*pNew) );
|
||||
*ppVtab = (sqlite3_vtab*)pNew;
|
||||
if( pNew==0 ) goto csvtab_connect_oom;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
if( nCol>0 ){
|
||||
pNew->nCol = nCol;
|
||||
}else{
|
||||
do{
|
||||
const char *z = csv_read_one_field(&sRdr);
|
||||
if( z==0 ) goto csvtab_connect_oom;
|
||||
pNew->nCol++;
|
||||
}while( sRdr.cTerm==',' );
|
||||
}
|
||||
pNew->zFilename = CSV_FILENAME; CSV_FILENAME = 0;
|
||||
pNew->zData = CSV_DATA; CSV_DATA = 0;
|
||||
#ifdef SQLITE_TEST
|
||||
pNew->tstFlags = tstFlags;
|
||||
#endif
|
||||
pNew->iStart = bHeader==1 ? ftell(sRdr.in) : 0;
|
||||
csv_reader_reset(&sRdr);
|
||||
if( CSV_SCHEMA==0 ){
|
||||
char *zSep = "";
|
||||
CSV_SCHEMA = sqlite3_mprintf("CREATE TABLE x(");
|
||||
if( CSV_SCHEMA==0 ) goto csvtab_connect_oom;
|
||||
for(i=0; i<pNew->nCol; i++){
|
||||
CSV_SCHEMA = sqlite3_mprintf("%z%sc%d TEXT",CSV_SCHEMA, zSep, i);
|
||||
zSep = ",";
|
||||
}
|
||||
CSV_SCHEMA = sqlite3_mprintf("%z);", CSV_SCHEMA);
|
||||
}
|
||||
rc = sqlite3_declare_vtab(db, CSV_SCHEMA);
|
||||
if( rc ) goto csvtab_connect_error;
|
||||
for(i=0; i<sizeof(azPValue)/sizeof(azPValue[0]); i++){
|
||||
sqlite3_free(azPValue[i]);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
|
||||
csvtab_connect_oom:
|
||||
rc = SQLITE_NOMEM;
|
||||
csv_errmsg(&sRdr, "out of memory");
|
||||
|
||||
csvtab_connect_error:
|
||||
if( pNew ) csvtabDisconnect(&pNew->base);
|
||||
for(i=0; i<sizeof(azPValue)/sizeof(azPValue[0]); i++){
|
||||
sqlite3_free(azPValue[i]);
|
||||
}
|
||||
if( sRdr.zErr[0] ){
|
||||
sqlite3_free(*pzErr);
|
||||
*pzErr = sqlite3_mprintf("%s", sRdr.zErr);
|
||||
}
|
||||
csv_reader_reset(&sRdr);
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_ERROR;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Reset the current row content held by a CsvCursor.
|
||||
*/
|
||||
static void csvtabCursorRowReset(CsvCursor *pCur){
|
||||
CsvTable *pTab = (CsvTable*)pCur->base.pVtab;
|
||||
int i;
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
sqlite3_free(pCur->azVal[i]);
|
||||
pCur->azVal[i] = 0;
|
||||
pCur->aLen[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** The xConnect and xCreate methods do the same thing, but they must be
|
||||
** different so that the virtual table is not an eponymous virtual table.
|
||||
*/
|
||||
static int csvtabCreate(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
return csvtabConnect(db, pAux, argc, argv, ppVtab, pzErr);
|
||||
}
|
||||
|
||||
/*
|
||||
** Destructor for a CsvCursor.
|
||||
*/
|
||||
static int csvtabClose(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
csvtabCursorRowReset(pCur);
|
||||
csv_reader_reset(&pCur->rdr);
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Constructor for a new CsvTable cursor object.
|
||||
*/
|
||||
static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
CsvTable *pTab = (CsvTable*)p;
|
||||
CsvCursor *pCur;
|
||||
size_t nByte;
|
||||
nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(int))*pTab->nCol;
|
||||
pCur = sqlite3_malloc( nByte );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, nByte);
|
||||
pCur->azVal = (char**)&pCur[1];
|
||||
pCur->aLen = (int*)&pCur->azVal[pTab->nCol];
|
||||
*ppCursor = &pCur->base;
|
||||
if( csv_reader_open(&pCur->rdr, pTab->zFilename, pTab->zData) ){
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a CsvCursor to its next row of input.
|
||||
** Set the EOF marker if we reach the end of input.
|
||||
*/
|
||||
static int csvtabNext(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
CsvTable *pTab = (CsvTable*)cur->pVtab;
|
||||
int i = 0;
|
||||
char *z;
|
||||
do{
|
||||
z = csv_read_one_field(&pCur->rdr);
|
||||
if( z==0 ){
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
break;
|
||||
}
|
||||
if( i<pTab->nCol ){
|
||||
if( pCur->aLen[i] < pCur->rdr.n+1 ){
|
||||
char *zNew = sqlite3_realloc(pCur->azVal[i], pCur->rdr.n+1);
|
||||
if( zNew==0 ){
|
||||
csv_errmsg(&pCur->rdr, "out of memory");
|
||||
csv_xfer_error(pTab, &pCur->rdr);
|
||||
break;
|
||||
}
|
||||
pCur->azVal[i] = zNew;
|
||||
pCur->aLen[i] = pCur->rdr.n+1;
|
||||
}
|
||||
memcpy(pCur->azVal[i], z, pCur->rdr.n+1);
|
||||
i++;
|
||||
}
|
||||
}while( pCur->rdr.cTerm==',' );
|
||||
while( i<pTab->nCol ){
|
||||
sqlite3_free(pCur->azVal[i]);
|
||||
pCur->azVal[i] = 0;
|
||||
pCur->aLen[i] = 0;
|
||||
i++;
|
||||
}
|
||||
if( z==0 || pCur->rdr.cTerm==EOF ){
|
||||
pCur->iRowid = -1;
|
||||
}else{
|
||||
pCur->iRowid++;
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return values of columns for the row at which the CsvCursor
|
||||
** is currently pointing.
|
||||
*/
|
||||
static int csvtabColumn(
|
||||
sqlite3_vtab_cursor *cur, /* The cursor */
|
||||
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
||||
int i /* Which column to return */
|
||||
){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
CsvTable *pTab = (CsvTable*)cur->pVtab;
|
||||
if( i>=0 && i<pTab->nCol && pCur->azVal[i]!=0 ){
|
||||
sqlite3_result_text(ctx, pCur->azVal[i], -1, SQLITE_STATIC);
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row.
|
||||
*/
|
||||
static int csvtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
*pRowid = pCur->iRowid;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the cursor has been moved off of the last
|
||||
** row of output.
|
||||
*/
|
||||
static int csvtabEof(sqlite3_vtab_cursor *cur){
|
||||
CsvCursor *pCur = (CsvCursor*)cur;
|
||||
return pCur->iRowid<0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a full table scan is supported. So xFilter simply rewinds to
|
||||
** the beginning.
|
||||
*/
|
||||
static int csvtabFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
CsvCursor *pCur = (CsvCursor*)pVtabCursor;
|
||||
CsvTable *pTab = (CsvTable*)pVtabCursor->pVtab;
|
||||
pCur->iRowid = 0;
|
||||
if( pCur->rdr.in==0 ){
|
||||
assert( pCur->rdr.zIn==pTab->zData );
|
||||
assert( pTab->iStart<=pCur->rdr.nIn );
|
||||
pCur->rdr.iIn = pTab->iStart;
|
||||
}else{
|
||||
fseek(pCur->rdr.in, pTab->iStart, SEEK_SET);
|
||||
pCur->rdr.iIn = 0;
|
||||
pCur->rdr.nIn = 0;
|
||||
}
|
||||
return csvtabNext(pVtabCursor);
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a forward full table scan is supported. xBestIndex is mostly
|
||||
** a no-op. If CSVTEST_FIDX is set, then the presence of equality
|
||||
** constraints lowers the estimated cost, which is fiction, but is useful
|
||||
** for testing certain kinds of virtual table behavior.
|
||||
*/
|
||||
static int csvtabBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
pIdxInfo->estimatedCost = 1000000;
|
||||
#ifdef SQLITE_TEST
|
||||
if( (((CsvTable*)tab)->tstFlags & CSVTEST_FIDX)!=0 ){
|
||||
/* The usual (and sensible) case is to always do a full table scan.
|
||||
** The code in this branch only runs when testflags=1. This code
|
||||
** generates an artifical and unrealistic plan which is useful
|
||||
** for testing virtual table logic but is not helpful to real applications.
|
||||
**
|
||||
** Any ==, LIKE, or GLOB constraint is marked as usable by the virtual
|
||||
** table (even though it is not) and the cost of running the virtual table
|
||||
** is reduced from 1 million to just 10. The constraints are *not* marked
|
||||
** as omittable, however, so the query planner should still generate a
|
||||
** plan that gives a correct answer, even if they plan is not optimal.
|
||||
*/
|
||||
int i;
|
||||
int nConst = 0;
|
||||
for(i=0; i<pIdxInfo->nConstraint; i++){
|
||||
unsigned char op;
|
||||
if( pIdxInfo->aConstraint[i].usable==0 ) continue;
|
||||
op = pIdxInfo->aConstraint[i].op;
|
||||
if( op==SQLITE_INDEX_CONSTRAINT_EQ
|
||||
|| op==SQLITE_INDEX_CONSTRAINT_LIKE
|
||||
|| op==SQLITE_INDEX_CONSTRAINT_GLOB
|
||||
){
|
||||
pIdxInfo->estimatedCost = 10;
|
||||
pIdxInfo->aConstraintUsage[nConst].argvIndex = nConst+1;
|
||||
nConst++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
static sqlite3_module CsvModule = {
|
||||
0, /* iVersion */
|
||||
csvtabCreate, /* xCreate */
|
||||
csvtabConnect, /* xConnect */
|
||||
csvtabBestIndex, /* xBestIndex */
|
||||
csvtabDisconnect, /* xDisconnect */
|
||||
csvtabDisconnect, /* xDestroy */
|
||||
csvtabOpen, /* xOpen - open a cursor */
|
||||
csvtabClose, /* xClose - close a cursor */
|
||||
csvtabFilter, /* xFilter - configure scan constraints */
|
||||
csvtabNext, /* xNext - advance a cursor */
|
||||
csvtabEof, /* xEof - check for end of scan */
|
||||
csvtabColumn, /* xColumn - read data */
|
||||
csvtabRowid, /* xRowid - read data */
|
||||
0, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
#ifdef SQLITE_TEST
|
||||
/*
|
||||
** For virtual table testing, make a version of the CSV virtual table
|
||||
** available that has an xUpdate function. But the xUpdate always returns
|
||||
** SQLITE_READONLY since the CSV file is not really writable.
|
||||
*/
|
||||
static int csvtabUpdate(sqlite3_vtab *p,int n,sqlite3_value**v,sqlite3_int64*x){
|
||||
return SQLITE_READONLY;
|
||||
}
|
||||
static sqlite3_module CsvModuleFauxWrite = {
|
||||
0, /* iVersion */
|
||||
csvtabCreate, /* xCreate */
|
||||
csvtabConnect, /* xConnect */
|
||||
csvtabBestIndex, /* xBestIndex */
|
||||
csvtabDisconnect, /* xDisconnect */
|
||||
csvtabDisconnect, /* xDestroy */
|
||||
csvtabOpen, /* xOpen - open a cursor */
|
||||
csvtabClose, /* xClose - close a cursor */
|
||||
csvtabFilter, /* xFilter - configure scan constraints */
|
||||
csvtabNext, /* xNext - advance a cursor */
|
||||
csvtabEof, /* xEof - check for end of scan */
|
||||
csvtabColumn, /* xColumn - read data */
|
||||
csvtabRowid, /* xRowid - read data */
|
||||
csvtabUpdate, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
#endif /* SQLITE_TEST */
|
||||
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
/*
|
||||
** This routine is called when the extension is loaded. The new
|
||||
** CSV virtual table module is registered with the calling database
|
||||
** connection.
|
||||
*/
|
||||
int sqlite3_csv_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
rc = sqlite3_create_module(db, "csv", &CsvModule, 0);
|
||||
#ifdef SQLITE_TEST
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_create_module(db, "csv_wr", &CsvModuleFauxWrite, 0);
|
||||
}
|
||||
#endif
|
||||
return rc;
|
||||
}
|
||||
@@ -0,0 +1,816 @@
|
||||
/*
|
||||
** 2016-05-27
|
||||
**
|
||||
** 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.
|
||||
**
|
||||
******************************************************************************
|
||||
**
|
||||
** This file contains the implementation of an SQLite vfs shim that
|
||||
** tracks I/O. Access to the accumulated status counts is provided using
|
||||
** an eponymous virtual table.
|
||||
*/
|
||||
#include <sqlite3ext.h>
|
||||
SQLITE_EXTENSION_INIT1
|
||||
|
||||
/*
|
||||
** This module contains code for a wrapper VFS that cause stats for
|
||||
** most VFS calls to be recorded.
|
||||
**
|
||||
** To use this module, first compile it as a loadable extension. See
|
||||
** https://www.sqlite.org/loadext.html#build for compilations instructions.
|
||||
**
|
||||
** After compliing, load this extension, then open database connections to be
|
||||
** measured. Query usages status using the vfsstat virtual table:
|
||||
**
|
||||
** SELECT * FROM vfsstat;
|
||||
**
|
||||
** Reset counters using UPDATE statements against vfsstat:
|
||||
**
|
||||
** UPDATE vfsstat SET count=0;
|
||||
**
|
||||
** EXAMPLE SCRIPT:
|
||||
**
|
||||
** .load ./vfsstat
|
||||
** .open test.db
|
||||
** DROP TABLE IF EXISTS t1;
|
||||
** CREATE TABLE t1(x,y);
|
||||
** INSERT INTO t1 VALUES(123, randomblob(5000));
|
||||
** CREATE INDEX t1x ON t1(x);
|
||||
** DROP TABLE t1;
|
||||
** VACUUM;
|
||||
** SELECT * FROM vfsstat WHERE count>0;
|
||||
**
|
||||
** LIMITATIONS:
|
||||
**
|
||||
** This module increments counters without using mutex protection. So if
|
||||
** two or more threads try to use this module at the same time, race conditions
|
||||
** may occur which mess up the counts. This is harmless, other than giving
|
||||
** incorrect statistics.
|
||||
*/
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
|
||||
/*
|
||||
** File types
|
||||
*/
|
||||
#define VFSSTAT_MAIN 0 /* Main database file */
|
||||
#define VFSSTAT_JOURNAL 1 /* Rollback journal */
|
||||
#define VFSSTAT_WAL 2 /* Write-ahead log file */
|
||||
#define VFSSTAT_MASTERJRNL 3 /* Master journal */
|
||||
#define VFSSTAT_SUBJRNL 4 /* Subjournal */
|
||||
#define VFSSTAT_TEMPDB 5 /* TEMP database */
|
||||
#define VFSSTAT_TEMPJRNL 6 /* Journal for TEMP database */
|
||||
#define VFSSTAT_TRANSIENT 7 /* Transient database */
|
||||
#define VFSSTAT_ANY 8 /* Unspecified file type */
|
||||
#define VFSSTAT_nFile 9 /* This many file types */
|
||||
|
||||
/* Names of the file types. These are allowed values for the
|
||||
** first column of the vfsstat virtual table.
|
||||
*/
|
||||
static const char *azFile[] = {
|
||||
"database", "journal", "wal", "master-journal", "sub-journal",
|
||||
"temp-database", "temp-journal", "transient-db", "*"
|
||||
};
|
||||
|
||||
/*
|
||||
** Stat types
|
||||
*/
|
||||
#define VFSSTAT_BYTESIN 0 /* Bytes read in */
|
||||
#define VFSSTAT_BYTESOUT 1 /* Bytes written out */
|
||||
#define VFSSTAT_READ 2 /* Read requests */
|
||||
#define VFSSTAT_WRITE 3 /* Write requests */
|
||||
#define VFSSTAT_SYNC 4 /* Syncs */
|
||||
#define VFSSTAT_OPEN 5 /* File opens */
|
||||
#define VFSSTAT_LOCK 6 /* Lock requests */
|
||||
#define VFSSTAT_ACCESS 0 /* xAccess calls. filetype==ANY only */
|
||||
#define VFSSTAT_DELETE 1 /* xDelete calls. filetype==ANY only */
|
||||
#define VFSSTAT_FULLPATH 2 /* xFullPathname calls. ANY only */
|
||||
#define VFSSTAT_RANDOM 3 /* xRandomness calls. ANY only */
|
||||
#define VFSSTAT_SLEEP 4 /* xSleep calls. ANY only */
|
||||
#define VFSSTAT_CURTIME 5 /* xCurrentTime calls. ANY only */
|
||||
#define VFSSTAT_nStat 7 /* This many stat types */
|
||||
|
||||
|
||||
/* Names for the second column of the vfsstat virtual table for all
|
||||
** cases except when the first column is "*" or VFSSTAT_ANY. */
|
||||
static const char *azStat[] = {
|
||||
"bytes-in", "bytes-out", "read", "write", "sync", "open", "lock",
|
||||
};
|
||||
static const char *azStatAny[] = {
|
||||
"access", "delete", "fullpathname", "randomness", "sleep", "currenttimestamp",
|
||||
"not-used"
|
||||
};
|
||||
|
||||
/* Total number of counters */
|
||||
#define VFSSTAT_MXCNT (VFSSTAT_nStat*VFSSTAT_nFile)
|
||||
|
||||
/*
|
||||
** Performance stats are collected in an instance of the following
|
||||
** global array.
|
||||
*/
|
||||
static sqlite3_uint64 aVfsCnt[VFSSTAT_MXCNT];
|
||||
|
||||
/*
|
||||
** Access to a specific counter
|
||||
*/
|
||||
#define STATCNT(filetype,stat) (aVfsCnt[(filetype)*VFSSTAT_nStat+(stat)])
|
||||
|
||||
/*
|
||||
** Forward declaration of objects used by this utility
|
||||
*/
|
||||
typedef struct VStatVfs VStatVfs;
|
||||
typedef struct VStatFile VStatFile;
|
||||
|
||||
/* An instance of the VFS */
|
||||
struct VStatVfs {
|
||||
sqlite3_vfs base; /* VFS methods */
|
||||
sqlite3_vfs *pVfs; /* Parent VFS */
|
||||
};
|
||||
|
||||
/* An open file */
|
||||
struct VStatFile {
|
||||
sqlite3_file base; /* IO methods */
|
||||
sqlite3_file *pReal; /* Underlying file handle */
|
||||
unsigned char eFiletype; /* What type of file is this */
|
||||
};
|
||||
|
||||
#define REALVFS(p) (((VStatVfs*)(p))->pVfs)
|
||||
|
||||
/*
|
||||
** Methods for VStatFile
|
||||
*/
|
||||
static int vstatClose(sqlite3_file*);
|
||||
static int vstatRead(sqlite3_file*, void*, int iAmt, sqlite3_int64 iOfst);
|
||||
static int vstatWrite(sqlite3_file*,const void*,int iAmt, sqlite3_int64 iOfst);
|
||||
static int vstatTruncate(sqlite3_file*, sqlite3_int64 size);
|
||||
static int vstatSync(sqlite3_file*, int flags);
|
||||
static int vstatFileSize(sqlite3_file*, sqlite3_int64 *pSize);
|
||||
static int vstatLock(sqlite3_file*, int);
|
||||
static int vstatUnlock(sqlite3_file*, int);
|
||||
static int vstatCheckReservedLock(sqlite3_file*, int *pResOut);
|
||||
static int vstatFileControl(sqlite3_file*, int op, void *pArg);
|
||||
static int vstatSectorSize(sqlite3_file*);
|
||||
static int vstatDeviceCharacteristics(sqlite3_file*);
|
||||
static int vstatShmMap(sqlite3_file*, int iPg, int pgsz, int, void volatile**);
|
||||
static int vstatShmLock(sqlite3_file*, int offset, int n, int flags);
|
||||
static void vstatShmBarrier(sqlite3_file*);
|
||||
static int vstatShmUnmap(sqlite3_file*, int deleteFlag);
|
||||
static int vstatFetch(sqlite3_file*, sqlite3_int64 iOfst, int iAmt, void **pp);
|
||||
static int vstatUnfetch(sqlite3_file*, sqlite3_int64 iOfst, void *p);
|
||||
|
||||
/*
|
||||
** Methods for VStatVfs
|
||||
*/
|
||||
static int vstatOpen(sqlite3_vfs*, const char *, sqlite3_file*, int , int *);
|
||||
static int vstatDelete(sqlite3_vfs*, const char *zName, int syncDir);
|
||||
static int vstatAccess(sqlite3_vfs*, const char *zName, int flags, int *);
|
||||
static int vstatFullPathname(sqlite3_vfs*, const char *zName, int, char *zOut);
|
||||
static void *vstatDlOpen(sqlite3_vfs*, const char *zFilename);
|
||||
static void vstatDlError(sqlite3_vfs*, int nByte, char *zErrMsg);
|
||||
static void (*vstatDlSym(sqlite3_vfs *pVfs, void *p, const char*zSym))(void);
|
||||
static void vstatDlClose(sqlite3_vfs*, void*);
|
||||
static int vstatRandomness(sqlite3_vfs*, int nByte, char *zOut);
|
||||
static int vstatSleep(sqlite3_vfs*, int microseconds);
|
||||
static int vstatCurrentTime(sqlite3_vfs*, double*);
|
||||
static int vstatGetLastError(sqlite3_vfs*, int, char *);
|
||||
static int vstatCurrentTimeInt64(sqlite3_vfs*, sqlite3_int64*);
|
||||
|
||||
static VStatVfs vstat_vfs = {
|
||||
{
|
||||
2, /* iVersion */
|
||||
0, /* szOsFile (set by register_vstat()) */
|
||||
1024, /* mxPathname */
|
||||
0, /* pNext */
|
||||
"vfslog", /* zName */
|
||||
0, /* pAppData */
|
||||
vstatOpen, /* xOpen */
|
||||
vstatDelete, /* xDelete */
|
||||
vstatAccess, /* xAccess */
|
||||
vstatFullPathname, /* xFullPathname */
|
||||
vstatDlOpen, /* xDlOpen */
|
||||
vstatDlError, /* xDlError */
|
||||
vstatDlSym, /* xDlSym */
|
||||
vstatDlClose, /* xDlClose */
|
||||
vstatRandomness, /* xRandomness */
|
||||
vstatSleep, /* xSleep */
|
||||
vstatCurrentTime, /* xCurrentTime */
|
||||
vstatGetLastError, /* xGetLastError */
|
||||
vstatCurrentTimeInt64 /* xCurrentTimeInt64 */
|
||||
},
|
||||
0
|
||||
};
|
||||
|
||||
static const sqlite3_io_methods vstat_io_methods = {
|
||||
3, /* iVersion */
|
||||
vstatClose, /* xClose */
|
||||
vstatRead, /* xRead */
|
||||
vstatWrite, /* xWrite */
|
||||
vstatTruncate, /* xTruncate */
|
||||
vstatSync, /* xSync */
|
||||
vstatFileSize, /* xFileSize */
|
||||
vstatLock, /* xLock */
|
||||
vstatUnlock, /* xUnlock */
|
||||
vstatCheckReservedLock, /* xCheckReservedLock */
|
||||
vstatFileControl, /* xFileControl */
|
||||
vstatSectorSize, /* xSectorSize */
|
||||
vstatDeviceCharacteristics, /* xDeviceCharacteristics */
|
||||
vstatShmMap, /* xShmMap */
|
||||
vstatShmLock, /* xShmLock */
|
||||
vstatShmBarrier, /* xShmBarrier */
|
||||
vstatShmUnmap, /* xShmUnmap */
|
||||
vstatFetch, /* xFetch */
|
||||
vstatUnfetch /* xUnfetch */
|
||||
};
|
||||
|
||||
|
||||
|
||||
/*
|
||||
** Close an vstat-file.
|
||||
*/
|
||||
static int vstatClose(sqlite3_file *pFile){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
if( p->pReal->pMethods ){
|
||||
rc = p->pReal->pMethods->xClose(p->pReal);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Read data from an vstat-file.
|
||||
*/
|
||||
static int vstatRead(
|
||||
sqlite3_file *pFile,
|
||||
void *zBuf,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
|
||||
rc = p->pReal->pMethods->xRead(p->pReal, zBuf, iAmt, iOfst);
|
||||
STATCNT(p->eFiletype,VFSSTAT_READ)++;
|
||||
if( rc==SQLITE_OK ){
|
||||
STATCNT(p->eFiletype,VFSSTAT_BYTESIN) += iAmt;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Write data to an vstat-file.
|
||||
*/
|
||||
static int vstatWrite(
|
||||
sqlite3_file *pFile,
|
||||
const void *z,
|
||||
int iAmt,
|
||||
sqlite_int64 iOfst
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
|
||||
rc = p->pReal->pMethods->xWrite(p->pReal, z, iAmt, iOfst);
|
||||
STATCNT(p->eFiletype,VFSSTAT_WRITE)++;
|
||||
if( rc==SQLITE_OK ){
|
||||
STATCNT(p->eFiletype,VFSSTAT_BYTESOUT) += iAmt;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Truncate an vstat-file.
|
||||
*/
|
||||
static int vstatTruncate(sqlite3_file *pFile, sqlite_int64 size){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xTruncate(p->pReal, size);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Sync an vstat-file.
|
||||
*/
|
||||
static int vstatSync(sqlite3_file *pFile, int flags){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xSync(p->pReal, flags);
|
||||
STATCNT(p->eFiletype,VFSSTAT_SYNC)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current file-size of an vstat-file.
|
||||
*/
|
||||
static int vstatFileSize(sqlite3_file *pFile, sqlite_int64 *pSize){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xFileSize(p->pReal, pSize);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Lock an vstat-file.
|
||||
*/
|
||||
static int vstatLock(sqlite3_file *pFile, int eLock){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xLock(p->pReal, eLock);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Unlock an vstat-file.
|
||||
*/
|
||||
static int vstatUnlock(sqlite3_file *pFile, int eLock){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xUnlock(p->pReal, eLock);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Check if another file-handle holds a RESERVED lock on an vstat-file.
|
||||
*/
|
||||
static int vstatCheckReservedLock(sqlite3_file *pFile, int *pResOut){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xCheckReservedLock(p->pReal, pResOut);
|
||||
STATCNT(p->eFiletype,VFSSTAT_LOCK)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** File control method. For custom operations on an vstat-file.
|
||||
*/
|
||||
static int vstatFileControl(sqlite3_file *pFile, int op, void *pArg){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
int rc;
|
||||
rc = p->pReal->pMethods->xFileControl(p->pReal, op, pArg);
|
||||
if( op==SQLITE_FCNTL_VFSNAME && rc==SQLITE_OK ){
|
||||
*(char**)pArg = sqlite3_mprintf("vstat/%z", *(char**)pArg);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the sector-size in bytes for an vstat-file.
|
||||
*/
|
||||
static int vstatSectorSize(sqlite3_file *pFile){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xSectorSize(p->pReal);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the device characteristic flags supported by an vstat-file.
|
||||
*/
|
||||
static int vstatDeviceCharacteristics(sqlite3_file *pFile){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
rc = p->pReal->pMethods->xDeviceCharacteristics(p->pReal);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Create a shared memory file mapping */
|
||||
static int vstatShmMap(
|
||||
sqlite3_file *pFile,
|
||||
int iPg,
|
||||
int pgsz,
|
||||
int bExtend,
|
||||
void volatile **pp
|
||||
){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmMap(p->pReal, iPg, pgsz, bExtend, pp);
|
||||
}
|
||||
|
||||
/* Perform locking on a shared-memory segment */
|
||||
static int vstatShmLock(sqlite3_file *pFile, int offset, int n, int flags){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmLock(p->pReal, offset, n, flags);
|
||||
}
|
||||
|
||||
/* Memory barrier operation on shared memory */
|
||||
static void vstatShmBarrier(sqlite3_file *pFile){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
p->pReal->pMethods->xShmBarrier(p->pReal);
|
||||
}
|
||||
|
||||
/* Unmap a shared memory segment */
|
||||
static int vstatShmUnmap(sqlite3_file *pFile, int deleteFlag){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xShmUnmap(p->pReal, deleteFlag);
|
||||
}
|
||||
|
||||
/* Fetch a page of a memory-mapped file */
|
||||
static int vstatFetch(
|
||||
sqlite3_file *pFile,
|
||||
sqlite3_int64 iOfst,
|
||||
int iAmt,
|
||||
void **pp
|
||||
){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xFetch(p->pReal, iOfst, iAmt, pp);
|
||||
}
|
||||
|
||||
/* Release a memory-mapped page */
|
||||
static int vstatUnfetch(sqlite3_file *pFile, sqlite3_int64 iOfst, void *pPage){
|
||||
VStatFile *p = (VStatFile *)pFile;
|
||||
return p->pReal->pMethods->xUnfetch(p->pReal, iOfst, pPage);
|
||||
}
|
||||
|
||||
/*
|
||||
** Open an vstat file handle.
|
||||
*/
|
||||
static int vstatOpen(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zName,
|
||||
sqlite3_file *pFile,
|
||||
int flags,
|
||||
int *pOutFlags
|
||||
){
|
||||
int rc;
|
||||
VStatFile *p = (VStatFile*)pFile;
|
||||
|
||||
p->pReal = (sqlite3_file*)&p[1];
|
||||
rc = REALVFS(pVfs)->xOpen(REALVFS(pVfs), zName, p->pReal, flags, pOutFlags);
|
||||
if( flags & SQLITE_OPEN_MAIN_DB ){
|
||||
p->eFiletype = VFSSTAT_MAIN;
|
||||
}else if( flags & SQLITE_OPEN_MAIN_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_JOURNAL;
|
||||
}else if( flags & SQLITE_OPEN_WAL ){
|
||||
p->eFiletype = VFSSTAT_WAL;
|
||||
}else if( flags & SQLITE_OPEN_MASTER_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_MASTERJRNL;
|
||||
}else if( flags & SQLITE_OPEN_SUBJOURNAL ){
|
||||
p->eFiletype = VFSSTAT_SUBJRNL;
|
||||
}else if( flags & SQLITE_OPEN_TEMP_DB ){
|
||||
p->eFiletype = VFSSTAT_TEMPDB;
|
||||
}else if( flags & SQLITE_OPEN_TEMP_JOURNAL ){
|
||||
p->eFiletype = VFSSTAT_TEMPJRNL;
|
||||
}else{
|
||||
p->eFiletype = VFSSTAT_TRANSIENT;
|
||||
}
|
||||
STATCNT(p->eFiletype,VFSSTAT_OPEN)++;
|
||||
pFile->pMethods = rc ? 0 : &vstat_io_methods;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete the file located at zPath. If the dirSync argument is true,
|
||||
** ensure the file-system modifications are synced to disk before
|
||||
** returning.
|
||||
*/
|
||||
static int vstatDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync){
|
||||
int rc;
|
||||
rc = REALVFS(pVfs)->xDelete(REALVFS(pVfs), zPath, dirSync);
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_DELETE)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Test for access permissions. Return true if the requested permission
|
||||
** is available, or false otherwise.
|
||||
*/
|
||||
static int vstatAccess(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int flags,
|
||||
int *pResOut
|
||||
){
|
||||
int rc;
|
||||
rc = REALVFS(pVfs)->xAccess(REALVFS(pVfs), zPath, flags, pResOut);
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_ACCESS)++;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate buffer zOut with the full canonical pathname corresponding
|
||||
** to the pathname in zPath. zOut is guaranteed to point to a buffer
|
||||
** of at least (INST_MAX_PATHNAME+1) bytes.
|
||||
*/
|
||||
static int vstatFullPathname(
|
||||
sqlite3_vfs *pVfs,
|
||||
const char *zPath,
|
||||
int nOut,
|
||||
char *zOut
|
||||
){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_FULLPATH)++;
|
||||
return REALVFS(pVfs)->xFullPathname(REALVFS(pVfs), zPath, nOut, zOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Open the dynamic library located at zPath and return a handle.
|
||||
*/
|
||||
static void *vstatDlOpen(sqlite3_vfs *pVfs, const char *zPath){
|
||||
return REALVFS(pVfs)->xDlOpen(REALVFS(pVfs), zPath);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer zErrMsg (size nByte bytes) with a human readable
|
||||
** utf-8 string describing the most recent error encountered associated
|
||||
** with dynamic libraries.
|
||||
*/
|
||||
static void vstatDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg){
|
||||
REALVFS(pVfs)->xDlError(REALVFS(pVfs), nByte, zErrMsg);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to the symbol zSymbol in the dynamic library pHandle.
|
||||
*/
|
||||
static void (*vstatDlSym(sqlite3_vfs *pVfs, void *p, const char *zSym))(void){
|
||||
return REALVFS(pVfs)->xDlSym(REALVFS(pVfs), p, zSym);
|
||||
}
|
||||
|
||||
/*
|
||||
** Close the dynamic library handle pHandle.
|
||||
*/
|
||||
static void vstatDlClose(sqlite3_vfs *pVfs, void *pHandle){
|
||||
REALVFS(pVfs)->xDlClose(REALVFS(pVfs), pHandle);
|
||||
}
|
||||
|
||||
/*
|
||||
** Populate the buffer pointed to by zBufOut with nByte bytes of
|
||||
** random data.
|
||||
*/
|
||||
static int vstatRandomness(sqlite3_vfs *pVfs, int nByte, char *zBufOut){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_RANDOM)++;
|
||||
return REALVFS(pVfs)->xRandomness(REALVFS(pVfs), nByte, zBufOut);
|
||||
}
|
||||
|
||||
/*
|
||||
** Sleep for nMicro microseconds. Return the number of microseconds
|
||||
** actually slept.
|
||||
*/
|
||||
static int vstatSleep(sqlite3_vfs *pVfs, int nMicro){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_SLEEP)++;
|
||||
return REALVFS(pVfs)->xSleep(REALVFS(pVfs), nMicro);
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the current time as a Julian Day number in *pTimeOut.
|
||||
*/
|
||||
static int vstatCurrentTime(sqlite3_vfs *pVfs, double *pTimeOut){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_CURTIME)++;
|
||||
return REALVFS(pVfs)->xCurrentTime(REALVFS(pVfs), pTimeOut);
|
||||
}
|
||||
|
||||
static int vstatGetLastError(sqlite3_vfs *pVfs, int a, char *b){
|
||||
return REALVFS(pVfs)->xGetLastError(REALVFS(pVfs), a, b);
|
||||
}
|
||||
static int vstatCurrentTimeInt64(sqlite3_vfs *pVfs, sqlite3_int64 *p){
|
||||
STATCNT(VFSSTAT_ANY,VFSSTAT_CURTIME)++;
|
||||
return REALVFS(pVfs)->xCurrentTimeInt64(REALVFS(pVfs), p);
|
||||
}
|
||||
|
||||
/*
|
||||
** A virtual table for accessing the stats collected by this VFS shim
|
||||
*/
|
||||
static int vstattabConnect(sqlite3*, void*, int, const char*const*,
|
||||
sqlite3_vtab**,char**);
|
||||
static int vstattabBestIndex(sqlite3_vtab*,sqlite3_index_info*);
|
||||
static int vstattabDisconnect(sqlite3_vtab*);
|
||||
static int vstattabOpen(sqlite3_vtab*, sqlite3_vtab_cursor**);
|
||||
static int vstattabClose(sqlite3_vtab_cursor*);
|
||||
static int vstattabFilter(sqlite3_vtab_cursor*, int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv);
|
||||
static int vstattabNext(sqlite3_vtab_cursor*);
|
||||
static int vstattabEof(sqlite3_vtab_cursor*);
|
||||
static int vstattabColumn(sqlite3_vtab_cursor*,sqlite3_context*,int);
|
||||
static int vstattabRowid(sqlite3_vtab_cursor*,sqlite3_int64*);
|
||||
static int vstattabUpdate(sqlite3_vtab*,int,sqlite3_value**,sqlite3_int64*);
|
||||
|
||||
/* A cursor for the vfsstat virtual table */
|
||||
typedef struct VfsStatCursor {
|
||||
sqlite3_vtab_cursor base; /* Base class. Must be first */
|
||||
int i; /* Pointing to this aVfsCnt[] value */
|
||||
} VfsStatCursor;
|
||||
|
||||
|
||||
static int vstattabConnect(
|
||||
sqlite3 *db,
|
||||
void *pAux,
|
||||
int argc, const char *const*argv,
|
||||
sqlite3_vtab **ppVtab,
|
||||
char **pzErr
|
||||
){
|
||||
sqlite3_vtab *pNew;
|
||||
int rc;
|
||||
|
||||
/* Column numbers */
|
||||
#define VSTAT_COLUMN_FILE 0
|
||||
#define VSTAT_COLUMN_STAT 1
|
||||
#define VSTAT_COLUMN_COUNT 2
|
||||
|
||||
rc = sqlite3_declare_vtab(db,"CREATE TABLE x(file,stat,count)");
|
||||
if( rc==SQLITE_OK ){
|
||||
pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) );
|
||||
if( pNew==0 ) return SQLITE_NOMEM;
|
||||
memset(pNew, 0, sizeof(*pNew));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** This method is the destructor for vstat table object.
|
||||
*/
|
||||
static int vstattabDisconnect(sqlite3_vtab *pVtab){
|
||||
sqlite3_free(pVtab);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Constructor for a new vstat table cursor object.
|
||||
*/
|
||||
static int vstattabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
|
||||
VfsStatCursor *pCur;
|
||||
pCur = sqlite3_malloc( sizeof(*pCur) );
|
||||
if( pCur==0 ) return SQLITE_NOMEM;
|
||||
memset(pCur, 0, sizeof(*pCur));
|
||||
*ppCursor = &pCur->base;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Destructor for a VfsStatCursor.
|
||||
*/
|
||||
static int vstattabClose(sqlite3_vtab_cursor *cur){
|
||||
sqlite3_free(cur);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Advance a VfsStatCursor to its next row of output.
|
||||
*/
|
||||
static int vstattabNext(sqlite3_vtab_cursor *cur){
|
||||
((VfsStatCursor*)cur)->i++;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return values of columns for the row at which the VfsStatCursor
|
||||
** is currently pointing.
|
||||
*/
|
||||
static int vstattabColumn(
|
||||
sqlite3_vtab_cursor *cur, /* The cursor */
|
||||
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
|
||||
int i /* Which column to return */
|
||||
){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
switch( i ){
|
||||
case VSTAT_COLUMN_FILE: {
|
||||
sqlite3_result_text(ctx, azFile[pCur->i/VFSSTAT_nStat], -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case VSTAT_COLUMN_STAT: {
|
||||
const char **az;
|
||||
az = (pCur->i/VFSSTAT_nStat)==VFSSTAT_ANY ? azStatAny : azStat;
|
||||
sqlite3_result_text(ctx, az[pCur->i%VFSSTAT_nStat], -1, SQLITE_STATIC);
|
||||
break;
|
||||
}
|
||||
case VSTAT_COLUMN_COUNT: {
|
||||
sqlite3_result_int64(ctx, aVfsCnt[pCur->i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return the rowid for the current row.
|
||||
*/
|
||||
static int vstattabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
*pRowid = pCur->i;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return TRUE if the cursor has been moved off of the last
|
||||
** row of output.
|
||||
*/
|
||||
static int vstattabEof(sqlite3_vtab_cursor *cur){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)cur;
|
||||
return pCur->i >= VFSSTAT_MXCNT;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a full table scan is supported. So xFilter simply rewinds to
|
||||
** the beginning.
|
||||
*/
|
||||
static int vstattabFilter(
|
||||
sqlite3_vtab_cursor *pVtabCursor,
|
||||
int idxNum, const char *idxStr,
|
||||
int argc, sqlite3_value **argv
|
||||
){
|
||||
VfsStatCursor *pCur = (VfsStatCursor*)pVtabCursor;
|
||||
pCur->i = 0;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Only a forwards full table scan is supported. xBestIndex is a no-op.
|
||||
*/
|
||||
static int vstattabBestIndex(
|
||||
sqlite3_vtab *tab,
|
||||
sqlite3_index_info *pIdxInfo
|
||||
){
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
** Any VSTAT_COLUMN_COUNT can be changed to a positive integer.
|
||||
** No deletions or insertions are allowed. No changes to other
|
||||
** columns are allowed.
|
||||
*/
|
||||
static int vstattabUpdate(
|
||||
sqlite3_vtab *tab,
|
||||
int argc, sqlite3_value **argv,
|
||||
sqlite3_int64 *pRowid
|
||||
){
|
||||
sqlite3_int64 iRowid, x;
|
||||
if( argc==1 ) return SQLITE_ERROR;
|
||||
if( sqlite3_value_type(argv[0])!=SQLITE_INTEGER ) return SQLITE_ERROR;
|
||||
iRowid = sqlite3_value_int64(argv[0]);
|
||||
if( iRowid!=sqlite3_value_int64(argv[1]) ) return SQLITE_ERROR;
|
||||
if( iRowid<0 || iRowid>=VFSSTAT_MXCNT ) return SQLITE_ERROR;
|
||||
if( sqlite3_value_type(argv[VSTAT_COLUMN_COUNT+2])!=SQLITE_INTEGER ){
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
x = sqlite3_value_int64(argv[VSTAT_COLUMN_COUNT+2]);
|
||||
if( x<0 ) return SQLITE_ERROR;
|
||||
aVfsCnt[iRowid] = x;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
static sqlite3_module VfsStatModule = {
|
||||
0, /* iVersion */
|
||||
0, /* xCreate */
|
||||
vstattabConnect, /* xConnect */
|
||||
vstattabBestIndex, /* xBestIndex */
|
||||
vstattabDisconnect, /* xDisconnect */
|
||||
0, /* xDestroy */
|
||||
vstattabOpen, /* xOpen - open a cursor */
|
||||
vstattabClose, /* xClose - close a cursor */
|
||||
vstattabFilter, /* xFilter - configure scan constraints */
|
||||
vstattabNext, /* xNext - advance a cursor */
|
||||
vstattabEof, /* xEof - check for end of scan */
|
||||
vstattabColumn, /* xColumn - read data */
|
||||
vstattabRowid, /* xRowid - read data */
|
||||
vstattabUpdate, /* xUpdate */
|
||||
0, /* xBegin */
|
||||
0, /* xSync */
|
||||
0, /* xCommit */
|
||||
0, /* xRollback */
|
||||
0, /* xFindMethod */
|
||||
0, /* xRename */
|
||||
};
|
||||
|
||||
/*
|
||||
** This routine is an sqlite3_auto_extension() callback, invoked to register
|
||||
** the vfsstat virtual table for all new database connections.
|
||||
*/
|
||||
static int vstatRegister(
|
||||
sqlite3 *db,
|
||||
const char **pzErrMsg,
|
||||
const struct sqlite3_api_routines *pThunk
|
||||
){
|
||||
return sqlite3_create_module(db, "vfsstat", &VfsStatModule, 0);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
__declspec(dllexport)
|
||||
#endif
|
||||
/*
|
||||
** This routine is called when the extension is loaded.
|
||||
**
|
||||
** Register the new VFS. Make arrangement to register the virtual table
|
||||
** for each new database connection.
|
||||
*/
|
||||
int sqlite3_vfsstat_init(
|
||||
sqlite3 *db,
|
||||
char **pzErrMsg,
|
||||
const sqlite3_api_routines *pApi
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
SQLITE_EXTENSION_INIT2(pApi);
|
||||
vstat_vfs.pVfs = sqlite3_vfs_find(0);
|
||||
vstat_vfs.base.szOsFile = sizeof(VStatFile) + vstat_vfs.pVfs->szOsFile;
|
||||
rc = sqlite3_vfs_register(&vstat_vfs.base, 1);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_auto_extension((void(*)(void))vstatRegister);
|
||||
}
|
||||
if( rc==SQLITE_OK ) rc = SQLITE_OK_LOAD_PERMANENTLY;
|
||||
return rc;
|
||||
}
|
||||
@@ -36,3 +36,20 @@ proc step_rbu {target rbu} {
|
||||
set rc
|
||||
}
|
||||
|
||||
proc do_rbu_vacuum_test {tn step} {
|
||||
uplevel [list do_test $tn.1 {
|
||||
if {$step==0} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
while 1 {
|
||||
if {$step==1} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
set rc [rbu step]
|
||||
if {$rc!="SQLITE_OK"} break
|
||||
if {$step==1} { rbu close }
|
||||
}
|
||||
rbu close
|
||||
} {SQLITE_DONE}]
|
||||
|
||||
uplevel [list do_execsql_test $tn.2 {
|
||||
PRAGMA integrity_check
|
||||
} ok]
|
||||
}
|
||||
|
||||
|
||||
@@ -17,23 +17,6 @@
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
set ::testprefix rbuvacuum
|
||||
|
||||
proc do_rbu_vacuum_test {tn step} {
|
||||
uplevel [list do_test $tn.1 {
|
||||
if {$step==0} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
while 1 {
|
||||
if {$step==1} { sqlite3rbu_vacuum rbu test.db state.db }
|
||||
set rc [rbu step]
|
||||
if {$rc!="SQLITE_OK"} break
|
||||
if {$step==1} { rbu close }
|
||||
}
|
||||
rbu close
|
||||
} {SQLITE_DONE}]
|
||||
|
||||
uplevel [list do_execsql_test $tn.2 {
|
||||
PRAGMA integrity_check
|
||||
} ok]
|
||||
}
|
||||
|
||||
foreach step {0 1} {
|
||||
|
||||
set ::testprefix rbuvacuum-step=$step
|
||||
@@ -404,7 +387,6 @@ do_test 3.5 {
|
||||
list [catch { rbu close } msg] $msg
|
||||
} {0 SQLITE_DONE}
|
||||
|
||||
|
||||
catch { db close }
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
# 2016 June 1
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# This file contains tests for the RBU module. More specifically, it
|
||||
# contains tests to ensure that the sqlite3rbu_vacuum() API works as
|
||||
# expected.
|
||||
#
|
||||
|
||||
source [file join [file dirname [info script]] rbu_common.tcl]
|
||||
|
||||
foreach step {0 1} {
|
||||
set ::testprefix rbuvacuum2-$step
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains fts3 tables can be vacuumed.
|
||||
#
|
||||
ifcapable fts3 {
|
||||
reset_db
|
||||
do_execsql_test 1.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts3(z, y);
|
||||
INSERT INTO t1 VALUES('fix this issue', 'at some point');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 1.2 $step
|
||||
|
||||
do_execsql_test 1.3 {
|
||||
SELECT * FROM t1;
|
||||
} {{fix this issue} {at some point}}
|
||||
|
||||
do_execsql_test 1.4 {
|
||||
SELECT rowid FROM t1 WHERE t1 MATCH 'fix';
|
||||
} {1}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
INSERT INTO t1 VALUES('a b c', 'd e f');
|
||||
INSERT INTO t1 VALUES('l h i', 'd e f');
|
||||
DELETE FROM t1 WHERE docid = 2;
|
||||
INSERT INTO t1 VALUES('a b c', 'x y z');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 1.6 $step
|
||||
do_execsql_test 1.7 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
SELECT * FROM t1;
|
||||
} {
|
||||
{fix this issue} {at some point}
|
||||
{l h i} {d e f}
|
||||
{a b c} {x y z}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains fts5 tables can be vacuumed.
|
||||
#
|
||||
ifcapable fts5 {
|
||||
reset_db
|
||||
do_execsql_test 2.1 {
|
||||
CREATE VIRTUAL TABLE t1 USING fts5(z, y);
|
||||
INSERT INTO t1 VALUES('fix this issue', 'at some point');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 2.2 $step
|
||||
|
||||
do_execsql_test 2.3 {
|
||||
SELECT * FROM t1;
|
||||
} {{fix this issue} {at some point}}
|
||||
|
||||
do_execsql_test 2.4 {
|
||||
SELECT rowid FROM t1 ('fix');
|
||||
} {1}
|
||||
|
||||
do_execsql_test 2.5 {
|
||||
INSERT INTO t1 VALUES('a b c', 'd e f');
|
||||
INSERT INTO t1 VALUES('l h i', 'd e f');
|
||||
DELETE FROM t1 WHERE rowid = 2;
|
||||
INSERT INTO t1 VALUES('a b c', 'x y z');
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 2.6 $step
|
||||
do_execsql_test 2.7 {
|
||||
INSERT INTO t1(t1) VALUES('integrity-check');
|
||||
SELECT * FROM t1;
|
||||
} {
|
||||
{fix this issue} {at some point}
|
||||
{l h i} {d e f}
|
||||
{a b c} {x y z}
|
||||
}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that a database that contains an rtree table can be vacuumed.
|
||||
#
|
||||
ifcapable rtree {
|
||||
reset_db
|
||||
do_execsql_test 3.1 {
|
||||
CREATE VIRTUAL TABLE rt USING rtree(id, x1, x2);
|
||||
INSERT INTO rt VALUES(1, 45, 55);
|
||||
INSERT INTO rt VALUES(2, 50, 60);
|
||||
INSERT INTO rt VALUES(3, 55, 65);
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 3.2 $step
|
||||
|
||||
do_execsql_test 3.3 {
|
||||
SELECT * FROM rt;
|
||||
} {1 45.0 55.0 2 50.0 60.0 3 55.0 65.0}
|
||||
|
||||
do_execsql_test 3.4.1 {
|
||||
SELECT rowid FROM rt WHERE x2>51 AND x1 < 51
|
||||
} {1 2}
|
||||
do_execsql_test 3.4.2 {
|
||||
SELECT rowid FROM rt WHERE x2>59 AND x1 < 59
|
||||
} {2 3}
|
||||
|
||||
do_rbu_vacuum_test 3.5 $step
|
||||
|
||||
do_execsql_test 3.6.1 {
|
||||
SELECT rowid FROM rt WHERE x2>51 AND x1 < 51
|
||||
} {1 2}
|
||||
do_execsql_test 3.6.2 {
|
||||
SELECT rowid FROM rt WHERE x2>59 AND x1 < 59
|
||||
} {2 3}
|
||||
}
|
||||
|
||||
ifcapable trigger {
|
||||
reset_db
|
||||
do_execsql_test 4.1 {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
CREATE VIEW v1 AS SELECT * FROM t1;
|
||||
CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END;
|
||||
}
|
||||
|
||||
do_execsql_test 4.2 {
|
||||
SELECT * FROM sqlite_master;
|
||||
} {
|
||||
table t1 t1 2 {CREATE TABLE t1(a, b, c)}
|
||||
view v1 v1 0 {CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
trigger tr1 t1 0 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END}
|
||||
}
|
||||
|
||||
do_rbu_vacuum_test 4.3 $step
|
||||
do_execsql_test 4.4 {
|
||||
SELECT * FROM sqlite_master;
|
||||
} {
|
||||
table t1 t1 2 {CREATE TABLE t1(a, b, c)}
|
||||
view v1 v1 0 {CREATE VIEW v1 AS SELECT * FROM t1}
|
||||
trigger tr1 t1 0 {CREATE TRIGGER tr1 AFTER INSERT ON t1 BEGIN SELECT 1; END}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
finish_test
|
||||
@@ -921,12 +921,14 @@ static int rbuObjIterFirst(sqlite3rbu *p, RbuObjIter *pIter){
|
||||
int rc;
|
||||
memset(pIter, 0, sizeof(RbuObjIter));
|
||||
|
||||
rc = prepareAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg,
|
||||
rc = prepareFreeAndCollectError(p->dbRbu, &pIter->pTblIter, &p->zErrmsg,
|
||||
sqlite3_mprintf(
|
||||
"SELECT rbu_target_name(name, type='view') AS target, name "
|
||||
"FROM sqlite_master "
|
||||
"WHERE type IN ('table', 'view') AND target IS NOT NULL "
|
||||
" %s "
|
||||
"ORDER BY name"
|
||||
);
|
||||
, rbuIsVacuum(p) ? "AND rootpage!=0 AND rootpage IS NOT NULL" : ""));
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = prepareAndCollectError(p->dbMain, &pIter->pIdxIter, &p->zErrmsg,
|
||||
|
||||
@@ -325,6 +325,7 @@ TESTSRC = \
|
||||
TESTSRC += \
|
||||
$(TOP)/ext/misc/amatch.c \
|
||||
$(TOP)/ext/misc/closure.c \
|
||||
$(TOP)/ext/misc/csv.c \
|
||||
$(TOP)/ext/misc/eval.c \
|
||||
$(TOP)/ext/misc/fileio.c \
|
||||
$(TOP)/ext/misc/fuzzer.c \
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
C Clang\scan\sdefine\s_MSC_VER\sin\ssome\scircumstances;\stherefore,\scheck\sfor\sClang\sfirst.
|
||||
D 2016-05-28T00:13:21.036
|
||||
F Makefile.in f59e0763ff448719fc1bd25513882b0567286317
|
||||
C Improved\scomment\son\scursorOwnsBtShared().\s\sNo\schanges\sto\scode.
|
||||
D 2016-06-04T21:05:54.667
|
||||
F Makefile.in 7321ef0b584224781ec7731408857fa8962c32cc
|
||||
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
|
||||
F Makefile.msc 306d73e854b1a92ea06e5d1e637faa5c44de53c7
|
||||
F Makefile.msc 831503fc4e988f571590af1405645fff121b5f1e
|
||||
F README.md 8ecc12493ff9f820cdea6520a9016001cb2e59b7
|
||||
F VERSION cb29eb11e493dd85b3eeec4053c03949bf98478e
|
||||
F aclocal.m4 a5c22d164aff7ed549d53a90fa56d56955281f50
|
||||
@@ -14,7 +14,7 @@ F autoconf/Makefile.am 1a47d071e3d5435f8f7ebff7eb6703848bbd65d4
|
||||
F autoconf/Makefile.msc 0eca137c12542bd76c253a2d24380f29ade59b95
|
||||
F autoconf/README.first 6c4f34fe115ff55d4e8dbfa3cecf04a0188292f7
|
||||
F autoconf/README.txt 4f04b0819303aabaa35fff5f7b257fb0c1ef95f1
|
||||
F autoconf/configure.ac 7754404e955900cfdeba08862cdfa802eca5b0af
|
||||
F autoconf/configure.ac cacf2616abf6e4a569bde2ef365c143caeec40bc
|
||||
F autoconf/tea/Makefile.in b438a7020446c8a8156e8d97c8914a04833da6fd
|
||||
F autoconf/tea/README 3e9a3c060f29a44344ab50aec506f4db903fb873
|
||||
F autoconf/tea/aclocal.m4 52c47aac44ce0ddb1f918b6993e8beb8eee88f43
|
||||
@@ -98,14 +98,14 @@ F ext/fts3/unicode/mkunicode.tcl 2debed3f582d77b3fdd0b8830880250021571fd8
|
||||
F ext/fts3/unicode/parseunicode.tcl da577d1384810fb4e2b209bf3313074353193e95
|
||||
F ext/fts5/extract_api_docs.tcl a36e54ec777172ddd3f9a88daf593b00848368e0
|
||||
F ext/fts5/fts5.h b770c5e0a8d2ee071ddffc7ab722dbf3474a8abe
|
||||
F ext/fts5/fts5Int.h 3677076aecbf645a7f2a019115c6a4ec3272dd78
|
||||
F ext/fts5/fts5Int.h 9bd0c7c64285b5b368eca0ac63613185c5ad24ba
|
||||
F ext/fts5/fts5_aux.c daa57fb45216491814520bbb587e97bf81ced458
|
||||
F ext/fts5/fts5_buffer.c 4c1502d4c956cd092c89ce4480867f9d8bf325cd
|
||||
F ext/fts5/fts5_config.c 5af9c360e99669d29f06492c370892394aba0857
|
||||
F ext/fts5/fts5_expr.c da2b33c2aac91e96641c0a7cf2bbaa36eb2667f7
|
||||
F ext/fts5/fts5_expr.c bcb238ee4ac1164302ab528487520488516bd030
|
||||
F ext/fts5/fts5_hash.c 880998e596b60f078348d48732ca4ad9a90caad2
|
||||
F ext/fts5/fts5_index.c b429e23fabb57506f71e406997cc46b89190dc97
|
||||
F ext/fts5/fts5_main.c b4a0fc5bf17f2f1f056ee76cdd7d2af08b360f55
|
||||
F ext/fts5/fts5_main.c f85281445dcf8be32d18841c93a6f90fe27dbfe2
|
||||
F ext/fts5/fts5_storage.c 3309c6a8e34b974513016fd1ef47c83f5898f94c
|
||||
F ext/fts5/fts5_tcl.c f8731e0508299bd43f1a2eff7dbeaac870768966
|
||||
F ext/fts5/fts5_test_mi.c 783b86697ebf773c18fc109992426c0173a055bc
|
||||
@@ -176,7 +176,7 @@ F ext/fts5/test/fts5porter.test 7cdc07bef301d70eebbfa75dcaf45c3680e1d0e1
|
||||
F ext/fts5/test/fts5porter2.test 2e65633d58a1c525d5af0f6c01e5a59155bb3487
|
||||
F ext/fts5/test/fts5prefix.test efd42e00bb8e8a36383f25c838185508681c093f
|
||||
F ext/fts5/test/fts5query.test f5ec25f5f2fbb70033424113cdffc101b1985a40
|
||||
F ext/fts5/test/fts5rank.test 7e9e64eac7245637f6f2033aec4b292aaf611aab
|
||||
F ext/fts5/test/fts5rank.test 3e55e7eeb4c98728e4a3171c9e994e1a2f24eb99
|
||||
F ext/fts5/test/fts5rebuild.test 03935f617ace91ed23a6099c7c74d905227ff29b
|
||||
F ext/fts5/test/fts5restart.test c17728fdea26e7d0f617d22ad5b4b2862b994c17
|
||||
F ext/fts5/test/fts5rowid.test 16908a99d6efc9ba21081b4f2b86b3fc699839a6
|
||||
@@ -206,6 +206,7 @@ F ext/icu/sqliteicu.h 728867a802baa5a96de7495e9689a8e01715ef37
|
||||
F ext/misc/amatch.c 211108e201105e4bb0c076527b8cfd34330fc234
|
||||
F ext/misc/closure.c 0d2a038df8fbae7f19de42e7c7d71f2e4dc88704
|
||||
F ext/misc/compress.c 122faa92d25033d6c3f07c39231de074ab3d2e83
|
||||
F ext/misc/csv.c f01126ba170fd4ef7c752b156568a80c912d4441
|
||||
F ext/misc/eval.c f971962e92ebb8b0a4e6b62949463ee454d88fa2
|
||||
F ext/misc/fileio.c d4171c815d6543a9edef8308aab2951413cd8d0f
|
||||
F ext/misc/fuzzer.c 7c64b8197bb77b7d64eff7cac7848870235d4c25
|
||||
@@ -221,6 +222,7 @@ F ext/misc/showauth.c 732578f0fe4ce42d577e1c86dc89dd14a006ab52
|
||||
F ext/misc/spellfix.c bf1b922c2750698e9a3d4c50cce6974adb7e93be
|
||||
F ext/misc/totype.c 4a167594e791abeed95e0a8db028822b5e8fe512
|
||||
F ext/misc/vfslog.c fe40fab5c077a40477f7e5eba994309ecac6cc95
|
||||
F ext/misc/vfsstat.c 6110aeeaab2f1df17a923c8a8acef3c74f6dc515
|
||||
F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
|
||||
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
|
||||
F ext/rbu/rbu.c b2c0b5e6ae1a89affc0edfc127ebfa5f637a0ce4
|
||||
@@ -239,7 +241,7 @@ F ext/rbu/rbu9.test 0806d1772c9f4981774ff028de6656e4183082af
|
||||
F ext/rbu/rbuA.test c1a7b3e2d926b8f8448bb3b4ae787e314ee4b2b3
|
||||
F ext/rbu/rbuB.test c25bc325b8072a766e56bb76c001866b405925c2
|
||||
F ext/rbu/rbuC.test efe47db508a0269b683cb2a1913a425ffd39a831
|
||||
F ext/rbu/rbu_common.tcl 0398545fed614f807d5f0ba55a85a51f08ba8f1a
|
||||
F ext/rbu/rbu_common.tcl 3a4b916b6f5dca9c9da9a30863e272fe5ea4414f
|
||||
F ext/rbu/rbucrash.test 8d2ed5d4b05fef6c00c2a6b5f7ead71fa172a695
|
||||
F ext/rbu/rbudiff.test 2df0a8a7d998ecf81764c21eeda3cde5611c5091
|
||||
F ext/rbu/rbufault.test cc0be8d5d392d98b0c2d6a51be377ea989250a89
|
||||
@@ -248,8 +250,9 @@ F ext/rbu/rbufault3.test 54a399888ac4af44c68f9f58afbed23149428bca
|
||||
F ext/rbu/rbufts.test 828cd689da825f0a7b7c53ffc1f6f7fdb6fa5bda
|
||||
F ext/rbu/rbuprogress.test 2023a7df2c523e3df1cb532eff811cda385a789a
|
||||
F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48
|
||||
F ext/rbu/rbuvacuum.test 66e02cf299836770e718e95c36686be0b26dbda3
|
||||
F ext/rbu/sqlite3rbu.c bf36625990c6865ecf08bd844d8097ed2d0a6958
|
||||
F ext/rbu/rbuvacuum.test 4a977447c15c2581ab668781d9ef4294382530e0
|
||||
F ext/rbu/rbuvacuum2.test 45009e127c3fb385e5c0fd5a8a63fb922a79d0ab
|
||||
F ext/rbu/sqlite3rbu.c a37a7dfb225c497171aa60120e81b884954361c7
|
||||
F ext/rbu/sqlite3rbu.h 2acd0a6344a6079de15c8dc9d84d3df83a665930
|
||||
F ext/rbu/test_rbu.c 9bbdf6bd8efd58fbc4f192698df50569598fbb9e
|
||||
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
|
||||
@@ -304,7 +307,7 @@ F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
|
||||
F main.mk eab7db27ac2a32b1172a85aa850c0b9c3a00524c
|
||||
F main.mk 2b90646ca027cc21dbae209a0fee68dfedfe0e83
|
||||
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
|
||||
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
|
||||
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
|
||||
@@ -323,19 +326,19 @@ F src/auth.c b56c78ebe40a2110fd361379f7e8162d23f92240
|
||||
F src/backup.c 6df65fdd569c901a418887a1a76f82ec35044556
|
||||
F src/bitvec.c 3ee4c8b2c94ed3a7377256e18199e6ff5cf33f63
|
||||
F src/btmutex.c bc87dd3b062cc26edfe79918de2200ccb8d41e73
|
||||
F src/btree.c 2128172fc1c420a6fa6878827fa595407795069a
|
||||
F src/btree.h 1342a9b2cc2089e3534d3ef00204786783f6aea6
|
||||
F src/btree.c b8875cff2d98d662a7e25d73483bccc2374d8bdb
|
||||
F src/btree.h 2107a2630e02c8cba58bb12ce14e731e734ea29c
|
||||
F src/btreeInt.h c18b7d2a3494695133e4e60ee36061d37f45d9a5
|
||||
F src/build.c 785fa789319d93c6ae20efbd01d4da9ce8f8a793
|
||||
F src/build.c e827e57e4a29c00e8429c5fd4d9d4572cb1b32a4
|
||||
F src/callback.c 2e76147783386374bf01b227f752c81ec872d730
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 999a828425b35b8092a8cde25690e71c20906344
|
||||
F src/date.c 1cc9fb516ec9932c6fd4d2a0d2f8bc4480145c39
|
||||
F src/dbstat.c c845548d4346e606e2f2b7d2e714ace2b8a7dd1b
|
||||
F src/delete.c 4aba4214a377ce8ddde2d2e609777bcc8235200f
|
||||
F src/expr.c 81bd7d87985746313770211183e900ed1ad28381
|
||||
F src/expr.c 798146ea4c87d723e8a157d88450ac9c43256998
|
||||
F src/fault.c 160a0c015b6c2629d3899ed2daf63d75754a32bb
|
||||
F src/fkey.c 4c0bd09e602b8ae8d36d81e31e4872d0b53c87bb
|
||||
F src/fkey.c bc4145347595b7770f9a598cff1c848302cf5413
|
||||
F src/func.c ef4c18c8a66143413ce41a58d582d2c14ddf78e1
|
||||
F src/global.c c45ea22aff29334f6a9ec549235ac3357c970015
|
||||
F src/hash.c 55b5fb474100cee0b901edaf203e26c970940f36
|
||||
@@ -343,7 +346,7 @@ F src/hash.h c8f3c31722cf3277d03713909761e152a5b81094
|
||||
F src/hwtime.h d32741c8f4df852c7d959236615444e2b1063b08
|
||||
F src/insert.c 8f4e9fcbd8e95e85f15647ba8b413b18d556ec2b
|
||||
F src/legacy.c 75d3023be8f0d2b99d60f905090341a03358c58e
|
||||
F src/loadext.c 8b3a73f0624c5f7cadbd5cb89940783bee1d39a6
|
||||
F src/loadext.c 4237fd37ca589f1d90b3ea925dd9df20da8e0c84
|
||||
F src/main.c 405d13e3a4f7c5add9fb27702ae70ed0a6e32cca
|
||||
F src/malloc.c 1443d1ad95d67c21d77af7ae3f44678252f0efec
|
||||
F src/mem0.c 6a55ebe57c46ca1a7d98da93aaa07f99f1059645
|
||||
@@ -368,7 +371,7 @@ F src/os_win.c 852fc2ff6084296348ed3739c548b2cf32df394e
|
||||
F src/os_win.h eb7a47aa17b26b77eb97e4823f20a00b8bda12ca
|
||||
F src/pager.c c368634b888b1c8740aea83b36bfd266f2443e60
|
||||
F src/pager.h 8ab6b6feeee4bc0439bfde7ee59ba99df98b9bc3
|
||||
F src/parse.y 10eb2f3fb62341291528c7984498054731f9d31e
|
||||
F src/parse.y 01b9f37c4c7009ab56fda98bc7db4c42643cecfe
|
||||
F src/pcache.c 5583c8ade4b05075a60ba953ef471d1c1a9c05df
|
||||
F src/pcache.h 2cedcd8407eb23017d92790b112186886e179490
|
||||
F src/pcache1.c 7f51d2b541aab57596adf62db2c4bb025d34f04d
|
||||
@@ -379,19 +382,19 @@ F src/printf.c a5f0ca08ddede803c241266abb46356ec748ded1
|
||||
F src/random.c ba2679f80ec82c4190062d756f22d0c358180696
|
||||
F src/resolve.c cca3aa77b95706df5d635a2141a4d1de60ae6598
|
||||
F src/rowset.c 7b7e7e479212e65b723bf40128c7b36dc5afdfac
|
||||
F src/select.c a0c4abf54bc6bd3a9c77a36ef3d1676045706cb2
|
||||
F src/select.c f3c6e9065fb34f6a23af27ec7f1f717ffbfc2ee4
|
||||
F src/shell.c 14ff7f660530a52b117d110ba3390b7b2eb719b6
|
||||
F src/sqlite.h.in 9984129d86243424b765fcb3f147c697bd20bb54
|
||||
F src/sqlite.h.in 5f8113dbec74c6c093ead9930afb8c9fbd9f643d
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 98f72cbfe00169c39089115427d06ea05fe4b4a2
|
||||
F src/sqliteInt.h 09621b4b7dba808b24262c2480ea75b045001853
|
||||
F src/sqliteInt.h 801e2a569ea79b09b87d045cd6f00ec88205f1f6
|
||||
F src/sqliteLimit.h c0373387c287c8d0932510b5547ecde31b5da247
|
||||
F src/status.c 70912d7be68e9e2dbc4010c93d344af61d4c59ba
|
||||
F src/table.c 5226df15ab9179b9ed558d89575ea0ce37b03fc9
|
||||
F src/tclsqlite.c 9c4c4589d078de37813ded708d8838b338ffb060
|
||||
F src/test1.c c0e5b69f99e95a2c9f55fdb6e46b44f1a15292d8
|
||||
F src/test1.c 43b37ab2b7338fd3313e74902f0d6c821eae843b
|
||||
F src/test2.c 5586f43fcd9a1be0830793cf9d354082c261b25b
|
||||
F src/test3.c d2c9efd2985ff8f5502ffd3253156984778d77d8
|
||||
F src/test3.c c75c8af0eadb335236c9e61b51044c58a8f7dd59
|
||||
F src/test4.c d168f83cc78d02e8d35567bb5630e40dcd85ac1e
|
||||
F src/test5.c 5a34feec76d9b3a86aab30fd4f6cc9c48cbab4c1
|
||||
F src/test6.c a684b7abd01352ab50cb79c0bf727e6b3f381a3d
|
||||
@@ -445,16 +448,16 @@ F src/update.c 4f05ea8cddfa367d045e03589756c02199e8f9bd
|
||||
F src/utf.c 699001c79f28e48e9bcdf8a463da029ea660540c
|
||||
F src/util.c 810ec3f22e2d1b62e66c30fe3621ebdedd23584d
|
||||
F src/vacuum.c feb1eabb20987983d9350cad98299b21fa811f52
|
||||
F src/vdbe.c 45e4da739187028281e342f79fae0f4145055bec
|
||||
F src/vdbe.c e23a15d352e63b51cf08af8052e678e67c54ec09
|
||||
F src/vdbe.h 5591b5add447096e31288b5a0a78ec5d7b5c5170
|
||||
F src/vdbeInt.h ddb157974436d87652de7dc641f7191496d9a8cd
|
||||
F src/vdbeapi.c ba85b78fe08dc4a9ce747e62c89a2b4a4547e74c
|
||||
F src/vdbeaux.c 1d6b9a979d1036db7bc39990e9e683f19520bc5c
|
||||
F src/vdbeblob.c c9f2f494b911c6fa34efd9803f0a10807da80f77
|
||||
F src/vdbemem.c 5cfef60e60e19cab6275d1b975bf4c791d575beb
|
||||
F src/vdbeapi.c 02bcbc2ca5d2004b029088b05b468b394881e103
|
||||
F src/vdbeaux.c c90275b0e55a2b32c03dc09314194fe46f2429d8
|
||||
F src/vdbeblob.c 83d2d266383157b02e2b809350bb197e89d7895b
|
||||
F src/vdbemem.c 1ecaa5ee0caff07255f25d04e8dc88befb6f88d1
|
||||
F src/vdbesort.c 91fda3909326860382b0ca8aa251e609c6a9d62c
|
||||
F src/vdbetrace.c f75c5455d8cf389ef86a8bfdfd3177e0e3692484
|
||||
F src/vtab.c ce0f2ebb589b459b32c640b33af64bfa5b29aaf8
|
||||
F src/vtab.c 948d2d4984219eee37a7bf427d6667e21e6eb92e
|
||||
F src/vxworks.h d2988f4e5a61a4dfe82c6524dd3d6e4f2ce3cdb9
|
||||
F src/wal.c 02eeecc265f6ffd0597378f5d8ae9070b62a406a
|
||||
F src/wal.h 2f7c831cf3b071fa548bf2d5cac640846a7ff19c
|
||||
@@ -462,7 +465,7 @@ F src/walker.c 0f142b5bd3ed2041fc52d773880748b212e63354
|
||||
F src/where.c b9f5b0ddb14c3827e70b5379e659cf4cfd524c4d
|
||||
F src/whereInt.h e5b939701a7ceffc5a3a8188a37f9746416ebcd0
|
||||
F src/wherecode.c ba71a4e4bada29aa9842200e6299714bf18c812c
|
||||
F src/whereexpr.c eacc0e60d029a082b4fc0cc42ea98544add1319e
|
||||
F src/whereexpr.c c32d47085dbaca0b8fd013210f56693c7d220d48
|
||||
F test/8_3_names.test ebbb5cd36741350040fd28b432ceadf495be25b2
|
||||
F test/affinity2.test a6d901b436328bd67a79b41bb0ac2663918fe3bd
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
@@ -525,6 +528,7 @@ F test/badutf2.test f5bc7f2d280670ecd79b9cf4f0f1760c607fe51f
|
||||
F test/bc_common.tcl b5e42d80305be95697e6370e015af571e5333a1c
|
||||
F test/bestindex1.test 0cf1bd2d7b97d3a3a8c10736125274f64765c4ee
|
||||
F test/bestindex2.test 4a06b8922ab2fd09434870da8d1cdf525aaf7060
|
||||
F test/bestindex3.test b80da904d23581d233a7ceee7d6bbad2b23a8133
|
||||
F test/between.test 34d375fb5ce1ae283ffe82b6b233e9f38e84fc6c
|
||||
F test/bigfile.test aa74f4e5db51c8e54a1d9de9fa65d01d1eb20b59
|
||||
F test/bigfile2.test 1b489a3a39ae90c7f027b79110d6b4e1dbc71bfc
|
||||
@@ -610,6 +614,7 @@ F test/crashM.test d95f59046fa749b0d0822edf18a717788c8f318d
|
||||
F test/crashtest1.c 09c1c7d728ccf4feb9e481671e29dda5669bbcc2
|
||||
F test/createtab.test b5de160630b209c4b8925bdcbbaf48cc90b67fe8
|
||||
F test/cse.test 277350a26264495e86b1785f34d2d0c8600e021c
|
||||
F test/csv01.test 0929a9ce47021519512be92861f29e32d2538e5f
|
||||
F test/ctime.test 7bd009071e242aac4f18521581536b652b789a47
|
||||
F test/cursorhint.test 7bc346788390475e77a345da2b92270d04d35856
|
||||
F test/date.test 984ac1e3e5e031386866f034006148d3972b4a65
|
||||
@@ -1088,7 +1093,7 @@ F test/speed3.test d32043614c08c53eafdc80f33191d5bd9b920523
|
||||
F test/speed4.test abc0ad3399dcf9703abed2fff8705e4f8e416715
|
||||
F test/speed4p.explain 6b5f104ebeb34a038b2f714150f51d01143e59aa
|
||||
F test/speed4p.test 0e51908951677de5a969b723e03a27a1c45db38b
|
||||
F test/speedtest1.c 4476f7030775507f060d27b789e1f4123c5a5866
|
||||
F test/speedtest1.c 28221f433d358dd3fcf2ca504ed51409aa66ffaf
|
||||
F test/spellfix.test f9c1f431e2c096c8775fec032952320c0e4700db
|
||||
F test/spellfix2.test dfc8f519a3fc204cb2dfa8b4f29821ae90f6f8c3
|
||||
F test/spellfix3.test 0f9efaaa502a0e0a09848028518a6fb096c8ad33
|
||||
@@ -1495,7 +1500,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 7c2cd4b05f88a759bdf72d3a249db5984fd7105d
|
||||
R 49f2ff6c03dd42bcf8b96859a75667c2
|
||||
U mistachkin
|
||||
Z 7fd0da57a78dfad092dddeef636c580c
|
||||
P aa53a36ea2eb90cc4939e37e6ad320b4211692fd
|
||||
R 31679baf828d6d3e0ee835dac82e6874
|
||||
U drh
|
||||
Z bd564a0c26801286c1f95fad1a35ec86
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3f710bc3617691fa7432a24f1410d4651ca181b6
|
||||
5e269c2d2667df65592a32d6bc2fa388fd7eb181
|
||||
+32
-41
@@ -450,6 +450,15 @@ static void releasePage(MemPage *pPage); /* Forward reference */
|
||||
static int cursorHoldsMutex(BtCursor *p){
|
||||
return sqlite3_mutex_held(p->pBt->mutex);
|
||||
}
|
||||
|
||||
/* Verify that the cursor and the BtShared agree about what is the current
|
||||
** database connetion. This is important in shared-cache mode. If the database
|
||||
** connection pointers get out-of-sync, it is possible for routines like
|
||||
** btreeInitPage() to reference an stale connection pointer that references a
|
||||
** a connection that has already closed. This routine is used inside assert()
|
||||
** statements only and for the purpose of double-checking that the btree code
|
||||
** does keep the database connection pointers up-to-date.
|
||||
*/
|
||||
static int cursorOwnsBtShared(BtCursor *p){
|
||||
assert( cursorHoldsMutex(p) );
|
||||
return (p->pBtree->db==p->pBt->db);
|
||||
@@ -609,21 +618,19 @@ static void btreeReleaseAllCursorPages(BtCursor *pCur){
|
||||
** the key.
|
||||
*/
|
||||
static int saveCursorKey(BtCursor *pCur){
|
||||
int rc;
|
||||
int rc = SQLITE_OK;
|
||||
assert( CURSOR_VALID==pCur->eState );
|
||||
assert( 0==pCur->pKey );
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
|
||||
rc = sqlite3BtreeKeySize(pCur, &pCur->nKey);
|
||||
assert( rc==SQLITE_OK ); /* KeySize() cannot fail */
|
||||
|
||||
/* If this is an intKey table, then the above call to BtreeKeySize()
|
||||
** stores the integer key in pCur->nKey. In this case this value is
|
||||
** all that is required. Otherwise, if pCur is not open on an intKey
|
||||
** table, then malloc space for and store the pCur->nKey bytes of key
|
||||
** data. */
|
||||
if( 0==pCur->curIntKey ){
|
||||
void *pKey = sqlite3Malloc( pCur->nKey );
|
||||
if( pCur->curIntKey ){
|
||||
/* Only the rowid is required for a table btree */
|
||||
pCur->nKey = sqlite3BtreeIntegerKey(pCur);
|
||||
}else{
|
||||
/* For an index btree, save the complete key content */
|
||||
void *pKey;
|
||||
pCur->nKey = sqlite3BtreePayloadSize(pCur);
|
||||
pKey = sqlite3Malloc( pCur->nKey );
|
||||
if( pKey ){
|
||||
rc = sqlite3BtreeKey(pCur, 0, (int)pCur->nKey, pKey);
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -4262,46 +4269,33 @@ int sqlite3BtreeCursorIsValid(BtCursor *pCur){
|
||||
#endif /* NDEBUG */
|
||||
|
||||
/*
|
||||
** Set *pSize to the size of the buffer needed to hold the value of
|
||||
** the key for the current entry. If the cursor is not pointing
|
||||
** to a valid entry, *pSize is set to 0.
|
||||
**
|
||||
** For a table with the INTKEY flag set, this routine returns the key
|
||||
** itself, not the number of bytes in the key.
|
||||
**
|
||||
** The caller must position the cursor prior to invoking this routine.
|
||||
**
|
||||
** This routine cannot fail. It always returns SQLITE_OK.
|
||||
** Return the value of the integer key or "rowid" for a table btree.
|
||||
** This routine is only valid for a cursor that is pointing into a
|
||||
** ordinary table btree. If the cursor points to an index btree or
|
||||
** is invalid, the result of this routine is undefined.
|
||||
*/
|
||||
int sqlite3BtreeKeySize(BtCursor *pCur, i64 *pSize){
|
||||
i64 sqlite3BtreeIntegerKey(BtCursor *pCur){
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
assert( pCur->eState==CURSOR_VALID );
|
||||
assert( pCur->curIntKey );
|
||||
getCellInfo(pCur);
|
||||
*pSize = pCur->info.nKey;
|
||||
return SQLITE_OK;
|
||||
return pCur->info.nKey;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set *pSize to the number of bytes of data in the entry the
|
||||
** cursor currently points to.
|
||||
** Return the number of bytes of payload for the entry that pCur is
|
||||
** currently pointing to. For table btrees, this will be the amount
|
||||
** of data. For index btrees, this will be the size of the key.
|
||||
**
|
||||
** The caller must guarantee that the cursor is pointing to a non-NULL
|
||||
** valid entry. In other words, the calling procedure must guarantee
|
||||
** that the cursor has Cursor.eState==CURSOR_VALID.
|
||||
**
|
||||
** Failure is not possible. This function always returns SQLITE_OK.
|
||||
** It might just as well be a procedure (returning void) but we continue
|
||||
** to return an integer result code for historical reasons.
|
||||
*/
|
||||
int sqlite3BtreeDataSize(BtCursor *pCur, u32 *pSize){
|
||||
assert( cursorOwnsBtShared(pCur) );
|
||||
u32 sqlite3BtreePayloadSize(BtCursor *pCur){
|
||||
assert( cursorHoldsMutex(pCur) );
|
||||
assert( pCur->eState==CURSOR_VALID );
|
||||
assert( pCur->iPage>=0 );
|
||||
assert( pCur->iPage<BTCURSOR_MAX_DEPTH );
|
||||
assert( pCur->apPage[pCur->iPage]->intKeyLeaf==1 );
|
||||
getCellInfo(pCur);
|
||||
*pSize = pCur->info.nPayload;
|
||||
return SQLITE_OK;
|
||||
return pCur->info.nPayload;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4743,10 +4737,7 @@ static const void *fetchPayload(
|
||||
** These routines is used to get quick access to key and data
|
||||
** in the common case where no overflow pages are used.
|
||||
*/
|
||||
const void *sqlite3BtreeKeyFetch(BtCursor *pCur, u32 *pAmt){
|
||||
return fetchPayload(pCur, pAmt);
|
||||
}
|
||||
const void *sqlite3BtreeDataFetch(BtCursor *pCur, u32 *pAmt){
|
||||
const void *sqlite3BtreePayloadFetch(BtCursor *pCur, u32 *pAmt){
|
||||
return fetchPayload(pCur, pAmt);
|
||||
}
|
||||
|
||||
|
||||
+3
-4
@@ -284,11 +284,10 @@ int sqlite3BtreeLast(BtCursor*, int *pRes);
|
||||
int sqlite3BtreeNext(BtCursor*, int *pRes);
|
||||
int sqlite3BtreeEof(BtCursor*);
|
||||
int sqlite3BtreePrevious(BtCursor*, int *pRes);
|
||||
int sqlite3BtreeKeySize(BtCursor*, i64 *pSize);
|
||||
i64 sqlite3BtreeIntegerKey(BtCursor*);
|
||||
int sqlite3BtreeKey(BtCursor*, u32 offset, u32 amt, void*);
|
||||
const void *sqlite3BtreeKeyFetch(BtCursor*, u32 *pAmt);
|
||||
const void *sqlite3BtreeDataFetch(BtCursor*, u32 *pAmt);
|
||||
int sqlite3BtreeDataSize(BtCursor*, u32 *pSize);
|
||||
const void *sqlite3BtreePayloadFetch(BtCursor*, u32 *pAmt);
|
||||
u32 sqlite3BtreePayloadSize(BtCursor*);
|
||||
int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*);
|
||||
|
||||
char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*);
|
||||
|
||||
+42
-42
@@ -605,8 +605,9 @@ static void SQLITE_NOINLINE deleteTable(sqlite3 *db, Table *pTable){
|
||||
/* Delete all indices associated with this table. */
|
||||
for(pIndex = pTable->pIndex; pIndex; pIndex=pNext){
|
||||
pNext = pIndex->pNext;
|
||||
assert( pIndex->pSchema==pTable->pSchema );
|
||||
if( !db || db->pnBytesFreed==0 ){
|
||||
assert( pIndex->pSchema==pTable->pSchema
|
||||
|| (IsVirtual(pTable) && pIndex->idxType!=SQLITE_IDXTYPE_APPDEF) );
|
||||
if( (db==0 || db->pnBytesFreed==0) && !IsVirtual(pTable) ){
|
||||
char *zName = pIndex->zName;
|
||||
TESTONLY ( Index *pOld = ) sqlite3HashInsert(
|
||||
&pIndex->pSchema->idxHash, zName, 0
|
||||
@@ -1288,7 +1289,7 @@ void sqlite3AddPrimaryKey(
|
||||
Column *pCol = 0;
|
||||
int iCol = -1, i;
|
||||
int nTerm;
|
||||
if( pTab==0 || IN_DECLARE_VTAB ) goto primary_key_exit;
|
||||
if( pTab==0 ) goto primary_key_exit;
|
||||
if( pTab->tabFlags & TF_HasPrimaryKey ){
|
||||
sqlite3ErrorMsg(pParse,
|
||||
"table \"%s\" has more than one primary key", pTab->zName);
|
||||
@@ -1334,12 +1335,8 @@ void sqlite3AddPrimaryKey(
|
||||
"INTEGER PRIMARY KEY");
|
||||
#endif
|
||||
}else{
|
||||
Index *p;
|
||||
p = sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0,
|
||||
0, sortOrder, 0);
|
||||
if( p ){
|
||||
p->idxType = SQLITE_IDXTYPE_PRIMARYKEY;
|
||||
}
|
||||
sqlite3CreateIndex(pParse, 0, 0, 0, pList, onError, 0,
|
||||
0, sortOrder, 0, SQLITE_IDXTYPE_PRIMARYKEY);
|
||||
pList = 0;
|
||||
}
|
||||
|
||||
@@ -1656,21 +1653,23 @@ static int hasColumn(const i16 *aiCol, int nCol, int x){
|
||||
** are appropriate for a WITHOUT ROWID table instead of a rowid table.
|
||||
** Changes include:
|
||||
**
|
||||
** (1) Convert the OP_CreateTable into an OP_CreateIndex. There is
|
||||
** (1) Set all columns of the PRIMARY KEY schema object to be NOT NULL.
|
||||
** (2) Convert the OP_CreateTable into an OP_CreateIndex. There is
|
||||
** no rowid btree for a WITHOUT ROWID. Instead, the canonical
|
||||
** data storage is a covering index btree.
|
||||
** (2) Bypass the creation of the sqlite_master table entry
|
||||
** (3) Bypass the creation of the sqlite_master table entry
|
||||
** for the PRIMARY KEY as the primary key index is now
|
||||
** identified by the sqlite_master table entry of the table itself.
|
||||
** (3) Set the Index.tnum of the PRIMARY KEY Index object in the
|
||||
** (4) Set the Index.tnum of the PRIMARY KEY Index object in the
|
||||
** schema to the rootpage from the main table.
|
||||
** (4) Set all columns of the PRIMARY KEY schema object to be NOT NULL.
|
||||
** (5) Add all table columns to the PRIMARY KEY Index object
|
||||
** so that the PRIMARY KEY is a covering index. The surplus
|
||||
** columns are part of KeyInfo.nXField and are not used for
|
||||
** sorting or lookup or uniqueness checks.
|
||||
** (6) Replace the rowid tail on all automatically generated UNIQUE
|
||||
** indices with the PRIMARY KEY columns.
|
||||
**
|
||||
** For virtual tables, only (1) is performed.
|
||||
*/
|
||||
static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
|
||||
Index *pIdx;
|
||||
@@ -1680,6 +1679,20 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
|
||||
sqlite3 *db = pParse->db;
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
|
||||
/* Mark every PRIMARY KEY column as NOT NULL (except for imposter tables)
|
||||
*/
|
||||
if( !db->init.imposterTable ){
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( (pTab->aCol[i].colFlags & COLFLAG_PRIMKEY)!=0 ){
|
||||
pTab->aCol[i].notNull = OE_Abort;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* The remaining transformations only apply to b-tree tables, not to
|
||||
** virtual tables */
|
||||
if( IN_DECLARE_VTAB ) return;
|
||||
|
||||
/* Convert the OP_CreateTable opcode that would normally create the
|
||||
** root-page for the table into an OP_CreateIndex opcode. The index
|
||||
** created will become the PRIMARY KEY index.
|
||||
@@ -1701,9 +1714,10 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
|
||||
if( pList==0 ) return;
|
||||
pList->a[0].sortOrder = pParse->iPkSortOrder;
|
||||
assert( pParse->pNewTable==pTab );
|
||||
pPk = sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0);
|
||||
if( pPk==0 ) return;
|
||||
pPk->idxType = SQLITE_IDXTYPE_PRIMARYKEY;
|
||||
sqlite3CreateIndex(pParse, 0, 0, 0, pList, pTab->keyConf, 0, 0, 0, 0,
|
||||
SQLITE_IDXTYPE_PRIMARYKEY);
|
||||
if( db->mallocFailed ) return;
|
||||
pPk = sqlite3PrimaryKeyIndex(pTab);
|
||||
pTab->iPKey = -1;
|
||||
}else{
|
||||
pPk = sqlite3PrimaryKeyIndex(pTab);
|
||||
@@ -1731,19 +1745,11 @@ static void convertToWithoutRowidTable(Parse *pParse, Table *pTab){
|
||||
}
|
||||
pPk->nKeyCol = j;
|
||||
}
|
||||
pPk->isCovering = 1;
|
||||
assert( pPk!=0 );
|
||||
pPk->isCovering = 1;
|
||||
if( !db->init.imposterTable ) pPk->uniqNotNull = 1;
|
||||
nPk = pPk->nKeyCol;
|
||||
|
||||
/* Make sure every column of the PRIMARY KEY is NOT NULL. (Except,
|
||||
** do not enforce this for imposter tables.) */
|
||||
if( !db->init.imposterTable ){
|
||||
for(i=0; i<nPk; i++){
|
||||
pTab->aCol[pPk->aiColumn[i]].notNull = OE_Abort;
|
||||
}
|
||||
pPk->uniqNotNull = 1;
|
||||
}
|
||||
|
||||
/* The root page of the PRIMARY KEY is the table root page */
|
||||
pPk->tnum = pTab->tnum;
|
||||
|
||||
@@ -2868,12 +2874,8 @@ Index *sqlite3AllocateIndexObject(
|
||||
** pList is a list of columns to be indexed. pList will be NULL if this
|
||||
** is a primary key or unique-constraint on the most recent column added
|
||||
** to the table currently under construction.
|
||||
**
|
||||
** If the index is created successfully, return a pointer to the new Index
|
||||
** structure. This is used by sqlite3AddPrimaryKey() to mark the index
|
||||
** as the tables primary key (Index.idxType==SQLITE_IDXTYPE_PRIMARYKEY)
|
||||
*/
|
||||
Index *sqlite3CreateIndex(
|
||||
void sqlite3CreateIndex(
|
||||
Parse *pParse, /* All information about this parse */
|
||||
Token *pName1, /* First part of index name. May be NULL */
|
||||
Token *pName2, /* Second part of index name. May be NULL */
|
||||
@@ -2883,9 +2885,9 @@ Index *sqlite3CreateIndex(
|
||||
Token *pStart, /* The CREATE token that begins this statement */
|
||||
Expr *pPIWhere, /* WHERE clause for partial indices */
|
||||
int sortOrder, /* Sort order of primary key when pList==NULL */
|
||||
int ifNotExist /* Omit error if index already exists */
|
||||
int ifNotExist, /* Omit error if index already exists */
|
||||
u8 idxType /* The index type */
|
||||
){
|
||||
Index *pRet = 0; /* Pointer to return */
|
||||
Table *pTab = 0; /* Table to be indexed */
|
||||
Index *pIndex = 0; /* The index to be created */
|
||||
char *zName = 0; /* Name of the index */
|
||||
@@ -2903,7 +2905,10 @@ Index *sqlite3CreateIndex(
|
||||
char *zExtra = 0; /* Extra space after the Index object */
|
||||
Index *pPk = 0; /* PRIMARY KEY index for WITHOUT ROWID tables */
|
||||
|
||||
if( db->mallocFailed || IN_DECLARE_VTAB || pParse->nErr>0 ){
|
||||
if( db->mallocFailed || pParse->nErr>0 ){
|
||||
goto exit_create_index;
|
||||
}
|
||||
if( IN_DECLARE_VTAB && idxType!=SQLITE_IDXTYPE_PRIMARYKEY ){
|
||||
goto exit_create_index;
|
||||
}
|
||||
if( SQLITE_OK!=sqlite3ReadSchema(pParse) ){
|
||||
@@ -3092,7 +3097,7 @@ Index *sqlite3CreateIndex(
|
||||
pIndex->pTable = pTab;
|
||||
pIndex->onError = (u8)onError;
|
||||
pIndex->uniqNotNull = onError!=OE_None;
|
||||
pIndex->idxType = pName ? SQLITE_IDXTYPE_APPDEF : SQLITE_IDXTYPE_UNIQUE;
|
||||
pIndex->idxType = idxType;
|
||||
pIndex->pSchema = db->aDb[iDb].pSchema;
|
||||
pIndex->nKeyCol = pList->nExpr;
|
||||
if( pPIWhere ){
|
||||
@@ -3272,7 +3277,7 @@ Index *sqlite3CreateIndex(
|
||||
pIdx->onError = pIndex->onError;
|
||||
}
|
||||
}
|
||||
pRet = pIdx;
|
||||
if( idxType==SQLITE_IDXTYPE_PRIMARYKEY ) pIdx->idxType = idxType;
|
||||
goto exit_create_index;
|
||||
}
|
||||
}
|
||||
@@ -3284,6 +3289,7 @@ Index *sqlite3CreateIndex(
|
||||
assert( pParse->nErr==0 );
|
||||
if( db->init.busy ){
|
||||
Index *p;
|
||||
assert( !IN_DECLARE_VTAB );
|
||||
assert( sqlite3SchemaMutexHeld(db, 0, pIndex->pSchema) );
|
||||
p = sqlite3HashInsert(&pIndex->pSchema->idxHash,
|
||||
pIndex->zName, pIndex);
|
||||
@@ -3390,7 +3396,6 @@ Index *sqlite3CreateIndex(
|
||||
pIndex->pNext = pOther->pNext;
|
||||
pOther->pNext = pIndex;
|
||||
}
|
||||
pRet = pIndex;
|
||||
pIndex = 0;
|
||||
}
|
||||
|
||||
@@ -3401,7 +3406,6 @@ exit_create_index:
|
||||
sqlite3ExprListDelete(db, pList);
|
||||
sqlite3SrcListDelete(db, pTblName);
|
||||
sqlite3DbFree(db, zName);
|
||||
return pRet;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -4315,10 +4319,6 @@ void sqlite3Reindex(Parse *pParse, Token *pName1, Token *pName2){
|
||||
/*
|
||||
** Return a KeyInfo structure that is appropriate for the given Index.
|
||||
**
|
||||
** The KeyInfo structure for an index is cached in the Index object.
|
||||
** So there might be multiple references to the returned pointer. The
|
||||
** caller should not try to modify the KeyInfo object.
|
||||
**
|
||||
** The caller should invoke sqlite3KeyInfoUnref() on the returned object
|
||||
** when it has finished using it.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -2535,7 +2535,7 @@ void sqlite3ExprCodeGetColumnOfTable(
|
||||
}else{
|
||||
int op = IsVirtual(pTab) ? OP_VColumn : OP_Column;
|
||||
int x = iCol;
|
||||
if( !HasRowid(pTab) ){
|
||||
if( !HasRowid(pTab) && !IsVirtual(pTab) ){
|
||||
x = sqlite3ColumnOfIndex(sqlite3PrimaryKeyIndex(pTab), iCol);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, op, iTabCur, x, regOut);
|
||||
|
||||
+2
-1
@@ -1372,7 +1372,8 @@ void sqlite3FkDelete(sqlite3 *db, Table *pTab){
|
||||
FKey *pFKey; /* Iterator variable */
|
||||
FKey *pNext; /* Copy of pFKey->pNextFrom */
|
||||
|
||||
assert( db==0 || sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
|
||||
assert( db==0 || IsVirtual(pTab)
|
||||
|| sqlite3SchemaMutexHeld(db, 0, pTab->pSchema) );
|
||||
for(pFKey=pTab->pFKey; pFKey; pFKey=pNext){
|
||||
|
||||
/* Remove the FK from the fkeyHash hash table. */
|
||||
|
||||
+4
-1
@@ -446,6 +446,7 @@ static int sqlite3LoadExtension(
|
||||
void **aHandle;
|
||||
u64 nMsg = 300 + sqlite3Strlen30(zFile);
|
||||
int ii;
|
||||
int rc;
|
||||
|
||||
/* Shared library endings to try if zFile cannot be loaded as written */
|
||||
static const char *azEndings[] = {
|
||||
@@ -548,7 +549,9 @@ static int sqlite3LoadExtension(
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
sqlite3_free(zAltEntry);
|
||||
if( xInit(db, &zErrmsg, &sqlite3Apis) ){
|
||||
rc = xInit(db, &zErrmsg, &sqlite3Apis);
|
||||
if( rc ){
|
||||
if( rc==SQLITE_OK_LOAD_PERMANENTLY ) return SQLITE_OK;
|
||||
if( pzErrMsg ){
|
||||
*pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg);
|
||||
}
|
||||
|
||||
+5
-3
@@ -300,7 +300,8 @@ ccons ::= NULL onconf.
|
||||
ccons ::= NOT NULL onconf(R). {sqlite3AddNotNull(pParse, R);}
|
||||
ccons ::= PRIMARY KEY sortorder(Z) onconf(R) autoinc(I).
|
||||
{sqlite3AddPrimaryKey(pParse,0,R,I,Z);}
|
||||
ccons ::= UNIQUE onconf(R). {sqlite3CreateIndex(pParse,0,0,0,0,R,0,0,0,0);}
|
||||
ccons ::= UNIQUE onconf(R). {sqlite3CreateIndex(pParse,0,0,0,0,R,0,0,0,0,
|
||||
SQLITE_IDXTYPE_UNIQUE);}
|
||||
ccons ::= CHECK LP expr(X) RP. {sqlite3AddCheckConstraint(pParse,X.pExpr);}
|
||||
ccons ::= REFERENCES nm(T) eidlist_opt(TA) refargs(R).
|
||||
{sqlite3CreateForeignKey(pParse,0,&T,TA,R);}
|
||||
@@ -349,7 +350,8 @@ tcons ::= CONSTRAINT nm(X). {pParse->constraintName = X;}
|
||||
tcons ::= PRIMARY KEY LP sortlist(X) autoinc(I) RP onconf(R).
|
||||
{sqlite3AddPrimaryKey(pParse,X,R,I,0);}
|
||||
tcons ::= UNIQUE LP sortlist(X) RP onconf(R).
|
||||
{sqlite3CreateIndex(pParse,0,0,0,X,R,0,0,0,0);}
|
||||
{sqlite3CreateIndex(pParse,0,0,0,X,R,0,0,0,0,
|
||||
SQLITE_IDXTYPE_UNIQUE);}
|
||||
tcons ::= CHECK LP expr(E) RP onconf.
|
||||
{sqlite3AddCheckConstraint(pParse,E.pExpr);}
|
||||
tcons ::= FOREIGN KEY LP eidlist(FA) RP
|
||||
@@ -1198,7 +1200,7 @@ cmd ::= createkw(S) uniqueflag(U) INDEX ifnotexists(NE) nm(X) dbnm(D)
|
||||
ON nm(Y) LP sortlist(Z) RP where_opt(W). {
|
||||
sqlite3CreateIndex(pParse, &X, &D,
|
||||
sqlite3SrcListAppend(pParse->db,0,&Y,0), Z, U,
|
||||
&S, W, SQLITE_SO_ASC, NE);
|
||||
&S, W, SQLITE_SO_ASC, NE, SQLITE_IDXTYPE_APPDEF);
|
||||
}
|
||||
|
||||
%type uniqueflag {int}
|
||||
|
||||
+2
-2
@@ -1028,7 +1028,7 @@ static void selectInnerLoop(
|
||||
*/
|
||||
KeyInfo *sqlite3KeyInfoAlloc(sqlite3 *db, int N, int X){
|
||||
int nExtra = (N+X)*(sizeof(CollSeq*)+1);
|
||||
KeyInfo *p = sqlite3Malloc(sizeof(KeyInfo) + nExtra);
|
||||
KeyInfo *p = sqlite3DbMallocRaw(db, sizeof(KeyInfo) + nExtra);
|
||||
if( p ){
|
||||
p->aSortOrder = (u8*)&p->aColl[N+X];
|
||||
p->nField = (u16)N;
|
||||
@@ -1050,7 +1050,7 @@ void sqlite3KeyInfoUnref(KeyInfo *p){
|
||||
if( p ){
|
||||
assert( p->nRef>0 );
|
||||
p->nRef--;
|
||||
if( p->nRef==0 ) sqlite3DbFree(0, p);
|
||||
if( p->nRef==0 ) sqlite3DbFree(p->db, p);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -506,6 +506,7 @@ int sqlite3_exec(
|
||||
#define SQLITE_NOTICE_RECOVER_ROLLBACK (SQLITE_NOTICE | (2<<8))
|
||||
#define SQLITE_WARNING_AUTOINDEX (SQLITE_WARNING | (1<<8))
|
||||
#define SQLITE_AUTH_USER (SQLITE_AUTH | (1<<8))
|
||||
#define SQLITE_OK_LOAD_PERMANENTLY (SQLITE_OK | (1<<8))
|
||||
|
||||
/*
|
||||
** CAPI3REF: Flags For File Open Operations
|
||||
|
||||
+2
-2
@@ -3617,8 +3617,8 @@ void sqlite3SrcListAssignCursors(Parse*, SrcList*);
|
||||
void sqlite3IdListDelete(sqlite3*, IdList*);
|
||||
void sqlite3SrcListDelete(sqlite3*, SrcList*);
|
||||
Index *sqlite3AllocateIndexObject(sqlite3*,i16,int,char**);
|
||||
Index *sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
|
||||
Expr*, int, int);
|
||||
void sqlite3CreateIndex(Parse*,Token*,Token*,SrcList*,ExprList*,int,Token*,
|
||||
Expr*, int, int, u8);
|
||||
void sqlite3DropIndex(Parse*, SrcList*, int);
|
||||
int sqlite3Select(Parse*, Select*, SelectDest*);
|
||||
Select *sqlite3SelectNew(Parse*,ExprList*,SrcList*,Expr*,ExprList*,
|
||||
|
||||
@@ -6584,6 +6584,7 @@ static int tclLoadStaticExtensionCmd(
|
||||
){
|
||||
extern int sqlite3_amatch_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_closure_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_csv_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_eval_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_fileio_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
extern int sqlite3_fuzzer_init(sqlite3*,char**,const sqlite3_api_routines*);
|
||||
@@ -6601,6 +6602,7 @@ static int tclLoadStaticExtensionCmd(
|
||||
} aExtension[] = {
|
||||
{ "amatch", sqlite3_amatch_init },
|
||||
{ "closure", sqlite3_closure_init },
|
||||
{ "csv", sqlite3_csv_init },
|
||||
{ "eval", sqlite3_eval_init },
|
||||
{ "fileio", sqlite3_fileio_init },
|
||||
{ "fuzzer", sqlite3_fuzzer_init },
|
||||
|
||||
+3
-12
@@ -385,8 +385,7 @@ static int btree_payload_size(
|
||||
const char **argv /* Text of each argument */
|
||||
){
|
||||
BtCursor *pCur;
|
||||
int n2;
|
||||
u64 n1;
|
||||
u32 n;
|
||||
char zBuf[50];
|
||||
|
||||
if( argc!=2 ){
|
||||
@@ -396,17 +395,9 @@ static int btree_payload_size(
|
||||
}
|
||||
pCur = sqlite3TestTextToPtr(argv[1]);
|
||||
sqlite3BtreeEnter(pCur->pBtree);
|
||||
|
||||
/* The cursor may be in "require-seek" state. If this is the case, the
|
||||
** call to BtreeDataSize() will fix it. */
|
||||
sqlite3BtreeDataSize(pCur, (u32*)&n2);
|
||||
if( pCur->apPage[pCur->iPage]->intKey ){
|
||||
n1 = 0;
|
||||
}else{
|
||||
sqlite3BtreeKeySize(pCur, (i64*)&n1);
|
||||
}
|
||||
n = sqlite3BtreePayloadSize(pCur);
|
||||
sqlite3BtreeLeave(pCur->pBtree);
|
||||
sqlite3_snprintf(sizeof(zBuf),zBuf, "%d", (int)(n1+n2));
|
||||
sqlite3_snprintf(sizeof(zBuf),zBuf, "%u", n);
|
||||
Tcl_AppendResult(interp, zBuf, 0);
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
+10
-39
@@ -2380,7 +2380,6 @@ case OP_NotNull: { /* same as TK_NOTNULL, jump, in1 */
|
||||
** skipped for length() and all content loading can be skipped for typeof().
|
||||
*/
|
||||
case OP_Column: {
|
||||
i64 payloadSize64; /* Number of bytes in the record */
|
||||
int p2; /* column number to retrieve */
|
||||
VdbeCursor *pC; /* The VDBE cursor */
|
||||
BtCursor *pCrsr; /* The BTree cursor */
|
||||
@@ -2433,22 +2432,9 @@ case OP_Column: {
|
||||
}else{
|
||||
assert( pC->eCurType==CURTYPE_BTREE );
|
||||
assert( pCrsr );
|
||||
if( pC->isTable==0 ){
|
||||
assert( sqlite3BtreeCursorIsValid(pCrsr) );
|
||||
VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &payloadSize64);
|
||||
assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */
|
||||
/* sqlite3BtreeParseCellPtr() uses getVarint32() to extract the
|
||||
** payload size, so it is impossible for payloadSize64 to be
|
||||
** larger than 32 bits. */
|
||||
assert( (payloadSize64 & SQLITE_MAX_U32)==(u64)payloadSize64 );
|
||||
pC->aRow = sqlite3BtreeKeyFetch(pCrsr, &avail);
|
||||
pC->payloadSize = (u32)payloadSize64;
|
||||
}else{
|
||||
assert( sqlite3BtreeCursorIsValid(pCrsr) );
|
||||
VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &pC->payloadSize);
|
||||
assert( rc==SQLITE_OK ); /* DataSize() cannot fail */
|
||||
pC->aRow = sqlite3BtreeDataFetch(pCrsr, &avail);
|
||||
}
|
||||
assert( sqlite3BtreeCursorIsValid(pCrsr) );
|
||||
pC->payloadSize = sqlite3BtreePayloadSize(pCrsr);
|
||||
pC->aRow = sqlite3BtreePayloadFetch(pCrsr, &avail);
|
||||
assert( avail<=65536 ); /* Maximum page size is 64KiB */
|
||||
if( pC->payloadSize <= (u32)avail ){
|
||||
pC->szRow = pC->payloadSize;
|
||||
@@ -4201,8 +4187,7 @@ case OP_NewRowid: { /* out2 */
|
||||
v = 1; /* IMP: R-61914-48074 */
|
||||
}else{
|
||||
assert( sqlite3BtreeCursorIsValid(pC->uc.pCursor) );
|
||||
rc = sqlite3BtreeKeySize(pC->uc.pCursor, &v);
|
||||
assert( rc==SQLITE_OK ); /* Cannot fail following BtreeLast() */
|
||||
v = sqlite3BtreeIntegerKey(pC->uc.pCursor);
|
||||
if( v>=MAX_ROWID ){
|
||||
pC->useRandomRowid = 1;
|
||||
}else{
|
||||
@@ -4459,8 +4444,7 @@ case OP_Delete: {
|
||||
/* If p5 is zero, the seek operation that positioned the cursor prior to
|
||||
** OP_Delete will have also set the pC->movetoTarget field to the rowid of
|
||||
** the row that is being deleted */
|
||||
i64 iKey = 0;
|
||||
sqlite3BtreeKeySize(pC->uc.pCursor, &iKey);
|
||||
i64 iKey = sqlite3BtreeIntegerKey(pC->uc.pCursor);
|
||||
assert( pC->movetoTarget==iKey );
|
||||
}
|
||||
#endif
|
||||
@@ -4476,7 +4460,7 @@ case OP_Delete: {
|
||||
zDb = db->aDb[pC->iDb].zName;
|
||||
pTab = pOp->p4.pTab;
|
||||
if( (pOp->p5 & OPFLAG_SAVEPOSITION)!=0 && pC->isTable ){
|
||||
sqlite3BtreeKeySize(pC->uc.pCursor, &pC->movetoTarget);
|
||||
pC->movetoTarget = sqlite3BtreeIntegerKey(pC->uc.pCursor);
|
||||
}
|
||||
}else{
|
||||
zDb = 0; /* Not needed. Silence a compiler warning. */
|
||||
@@ -4630,7 +4614,6 @@ case OP_RowData: {
|
||||
VdbeCursor *pC;
|
||||
BtCursor *pCrsr;
|
||||
u32 n;
|
||||
i64 n64;
|
||||
|
||||
pOut = &aMem[pOp->p2];
|
||||
memAboutToChange(p, pOut);
|
||||
@@ -4662,20 +4645,9 @@ case OP_RowData: {
|
||||
if( rc!=SQLITE_OK ) goto abort_due_to_error;
|
||||
#endif
|
||||
|
||||
if( pC->isTable==0 ){
|
||||
assert( !pC->isTable );
|
||||
VVA_ONLY(rc =) sqlite3BtreeKeySize(pCrsr, &n64);
|
||||
assert( rc==SQLITE_OK ); /* True because of CursorMoveto() call above */
|
||||
if( n64>db->aLimit[SQLITE_LIMIT_LENGTH] ){
|
||||
goto too_big;
|
||||
}
|
||||
n = (u32)n64;
|
||||
}else{
|
||||
VVA_ONLY(rc =) sqlite3BtreeDataSize(pCrsr, &n);
|
||||
assert( rc==SQLITE_OK ); /* DataSize() cannot fail */
|
||||
if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
|
||||
goto too_big;
|
||||
}
|
||||
n = sqlite3BtreePayloadSize(pCrsr);
|
||||
if( n>(u32)db->aLimit[SQLITE_LIMIT_LENGTH] ){
|
||||
goto too_big;
|
||||
}
|
||||
testcase( n==0 );
|
||||
if( sqlite3VdbeMemClearAndResize(pOut, MAX(n,32)) ){
|
||||
@@ -4740,8 +4712,7 @@ case OP_Rowid: { /* out2 */
|
||||
pOut->flags = MEM_Null;
|
||||
break;
|
||||
}
|
||||
rc = sqlite3BtreeKeySize(pC->uc.pCursor, &v);
|
||||
assert( rc==SQLITE_OK ); /* Always so because of CursorRestore() above */
|
||||
v = sqlite3BtreeIntegerKey(pC->uc.pCursor);
|
||||
}
|
||||
pOut->u.i = v;
|
||||
break;
|
||||
|
||||
+1
-2
@@ -1650,8 +1650,7 @@ int sqlite3_preupdate_old(sqlite3 *db, int iIdx, sqlite3_value **ppValue){
|
||||
u32 nRec;
|
||||
u8 *aRec;
|
||||
|
||||
rc = sqlite3BtreeDataSize(p->pCsr->uc.pCursor, &nRec);
|
||||
if( rc!=SQLITE_OK ) goto preupdate_old_out;
|
||||
nRec = sqlite3BtreePayloadSize(p->pCsr->uc.pCursor);
|
||||
aRec = sqlite3DbMallocRaw(db, nRec);
|
||||
if( !aRec ) goto preupdate_old_out;
|
||||
rc = sqlite3BtreeData(p->pCsr->uc.pCursor, 0, nRec, aRec);
|
||||
|
||||
+2
-4
@@ -4315,8 +4315,7 @@ int sqlite3VdbeIdxRowid(sqlite3 *db, BtCursor *pCur, i64 *rowid){
|
||||
** this code can safely assume that nCellKey is 32-bits
|
||||
*/
|
||||
assert( sqlite3BtreeCursorIsValid(pCur) );
|
||||
VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey);
|
||||
assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */
|
||||
nCellKey = sqlite3BtreePayloadSize(pCur);
|
||||
assert( (nCellKey & SQLITE_MAX_U32)==(u64)nCellKey );
|
||||
|
||||
/* Read in the complete content of the index entry */
|
||||
@@ -4393,8 +4392,7 @@ int sqlite3VdbeIdxKeyCompare(
|
||||
assert( pC->eCurType==CURTYPE_BTREE );
|
||||
pCur = pC->uc.pCursor;
|
||||
assert( sqlite3BtreeCursorIsValid(pCur) );
|
||||
VVA_ONLY(rc =) sqlite3BtreeKeySize(pCur, &nCellKey);
|
||||
assert( rc==SQLITE_OK ); /* pCur is always valid so KeySize cannot fail */
|
||||
nCellKey = sqlite3BtreePayloadSize(pCur);
|
||||
/* nCellKey will always be between 0 and 0xffffffff because of the way
|
||||
** that btreeParseCellPtr() and sqlite3GetVarint32() are implemented */
|
||||
if( nCellKey<=0 || nCellKey>0x7fffffff ){
|
||||
|
||||
+1
-1
@@ -415,7 +415,7 @@ static int blobReadWrite(
|
||||
** anyhow.
|
||||
*/
|
||||
sqlite3_int64 iKey;
|
||||
sqlite3BtreeKeySize(p->pCsr, &iKey);
|
||||
iKey = sqlite3BtreeIntegerKey(p->pCsr);
|
||||
sqlite3VdbePreUpdateHook(
|
||||
v, v->apCsr[0], SQLITE_DELETE, p->zDb, p->pTab, iKey, -1
|
||||
);
|
||||
|
||||
+1
-5
@@ -991,11 +991,7 @@ int sqlite3VdbeMemFromBtree(
|
||||
/* Note: the calls to BtreeKeyFetch() and DataFetch() below assert()
|
||||
** that both the BtShared and database handle mutexes are held. */
|
||||
assert( (pMem->flags & MEM_RowSet)==0 );
|
||||
if( key ){
|
||||
zData = (char *)sqlite3BtreeKeyFetch(pCur, &available);
|
||||
}else{
|
||||
zData = (char *)sqlite3BtreeDataFetch(pCur, &available);
|
||||
}
|
||||
zData = (char *)sqlite3BtreePayloadFetch(pCur, &available);
|
||||
assert( zData!=0 );
|
||||
|
||||
if( offset+amt<=available ){
|
||||
|
||||
+30
-13
@@ -754,10 +754,24 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
|
||||
&& (pParse->pNewTable->tabFlags & TF_Virtual)==0
|
||||
){
|
||||
if( !pTab->aCol ){
|
||||
pTab->aCol = pParse->pNewTable->aCol;
|
||||
pTab->nCol = pParse->pNewTable->nCol;
|
||||
pParse->pNewTable->nCol = 0;
|
||||
pParse->pNewTable->aCol = 0;
|
||||
Table *pNew = pParse->pNewTable;
|
||||
Index *pIdx;
|
||||
pTab->aCol = pNew->aCol;
|
||||
pTab->nCol = pNew->nCol;
|
||||
pTab->tabFlags |= pNew->tabFlags & (TF_WithoutRowid|TF_NoVisibleRowid);
|
||||
pNew->nCol = 0;
|
||||
pNew->aCol = 0;
|
||||
assert( pTab->pIndex==0 );
|
||||
if( !HasRowid(pNew) && pCtx->pVTable->pMod->pModule->xUpdate!=0 ){
|
||||
rc = SQLITE_ERROR;
|
||||
}
|
||||
pIdx = pNew->pIndex;
|
||||
if( pIdx ){
|
||||
assert( pIdx->pNext==0 );
|
||||
pTab->pIndex = pIdx;
|
||||
pNew->pIndex = 0;
|
||||
pIdx->pTable = pTab;
|
||||
}
|
||||
}
|
||||
pCtx->bDeclared = 1;
|
||||
}else{
|
||||
@@ -1087,7 +1101,7 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
|
||||
}
|
||||
|
||||
/*
|
||||
** Check to see if virtual tale module pMod can be have an eponymous
|
||||
** Check to see if virtual table module pMod can be have an eponymous
|
||||
** virtual table instance. If it can, create one if one does not already
|
||||
** exist. Return non-zero if the eponymous virtual table instance exists
|
||||
** when this routine returns, and return zero if it does not exist.
|
||||
@@ -1104,17 +1118,18 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
|
||||
const sqlite3_module *pModule = pMod->pModule;
|
||||
Table *pTab;
|
||||
char *zErr = 0;
|
||||
int nName;
|
||||
int rc;
|
||||
sqlite3 *db = pParse->db;
|
||||
if( pMod->pEpoTab ) return 1;
|
||||
if( pModule->xCreate!=0 && pModule->xCreate!=pModule->xConnect ) return 0;
|
||||
nName = sqlite3Strlen30(pMod->zName) + 1;
|
||||
pTab = sqlite3DbMallocZero(db, sizeof(Table) + nName);
|
||||
pTab = sqlite3DbMallocZero(db, sizeof(Table));
|
||||
if( pTab==0 ) return 0;
|
||||
pTab->zName = sqlite3DbStrDup(db, pMod->zName);
|
||||
if( pTab->zName==0 ){
|
||||
sqlite3DbFree(db, pTab);
|
||||
return 0;
|
||||
}
|
||||
pMod->pEpoTab = pTab;
|
||||
pTab->zName = (char*)&pTab[1];
|
||||
memcpy(pTab->zName, pMod->zName, nName);
|
||||
pTab->nRef = 1;
|
||||
pTab->pSchema = db->aDb[0].pSchema;
|
||||
pTab->tabFlags |= TF_Virtual;
|
||||
@@ -1140,9 +1155,11 @@ int sqlite3VtabEponymousTableInit(Parse *pParse, Module *pMod){
|
||||
void sqlite3VtabEponymousTableClear(sqlite3 *db, Module *pMod){
|
||||
Table *pTab = pMod->pEpoTab;
|
||||
if( pTab!=0 ){
|
||||
sqlite3DeleteColumnNames(db, pTab);
|
||||
sqlite3VtabClear(db, pTab);
|
||||
sqlite3DbFree(db, pTab);
|
||||
/* Mark the table as Ephemeral prior to deleting it, so that the
|
||||
** sqlite3DeleteTable() routine will know that it is not stored in
|
||||
** the schema. */
|
||||
pTab->tabFlags |= TF_Ephemeral;
|
||||
sqlite3DeleteTable(db, pTab);
|
||||
pMod->pEpoTab = 0;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-2
@@ -568,7 +568,9 @@ static void exprAnalyzeOrTerm(
|
||||
if( !db->mallocFailed ){
|
||||
for(j=0, pAndTerm=pAndWC->a; j<pAndWC->nTerm; j++, pAndTerm++){
|
||||
assert( pAndTerm->pExpr );
|
||||
if( allowedOp(pAndTerm->pExpr->op) ){
|
||||
if( allowedOp(pAndTerm->pExpr->op)
|
||||
|| pAndTerm->eOperator==WO_MATCH
|
||||
){
|
||||
b |= sqlite3WhereGetMask(&pWInfo->sMaskSet, pAndTerm->leftCursor);
|
||||
}
|
||||
}
|
||||
@@ -1122,7 +1124,7 @@ static void exprAnalyze(
|
||||
** virtual tables. The native query optimizer does not attempt
|
||||
** to do anything with MATCH functions.
|
||||
*/
|
||||
if( isMatchOfColumn(pExpr, &eOp2) ){
|
||||
if( pWC->op==TK_AND && isMatchOfColumn(pExpr, &eOp2) ){
|
||||
int idxNew;
|
||||
Expr *pRight, *pLeft;
|
||||
WhereTerm *pNewTerm;
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
# 2016 May 29
|
||||
#
|
||||
# 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 testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix bestindex3
|
||||
|
||||
ifcapable !vtab {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Virtual table callback for a virtual table named $tbl.
|
||||
#
|
||||
# The table created is:
|
||||
#
|
||||
# "CREATE TABLE t1 (a, b, c)"
|
||||
#
|
||||
# This virtual table supports both LIKE and = operators on all columns.
|
||||
#
|
||||
proc vtab_cmd {bOmit method args} {
|
||||
switch -- $method {
|
||||
xConnect {
|
||||
return "CREATE TABLE t1(a, b, c)"
|
||||
}
|
||||
|
||||
xBestIndex {
|
||||
foreach {clist orderby mask} $args {}
|
||||
|
||||
set ret [list]
|
||||
set use use
|
||||
if {$bOmit} {set use omit}
|
||||
|
||||
for {set i 0} {$i < [llength $clist]} {incr i} {
|
||||
array unset C
|
||||
array set C [lindex $clist $i]
|
||||
if {$C(usable) && ($C(op)=="like" || $C(op)=="eq")} {
|
||||
lappend ret $use $i
|
||||
lappend ret idxstr
|
||||
lappend ret "[lindex {a b c} $C(column)] [string toupper $C(op)] ?"
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if {$ret==""} {
|
||||
lappend ret cost 1000000 rows 1000000
|
||||
} else {
|
||||
lappend ret cost 100 rows 10
|
||||
}
|
||||
return $ret
|
||||
}
|
||||
|
||||
xFilter {
|
||||
foreach {idxnum idxstr param} $args {}
|
||||
set where ""
|
||||
if {$bOmit && $idxstr != ""} {
|
||||
set where " WHERE [string map [list ? '$param' EQ =] $idxstr]"
|
||||
}
|
||||
return [list sql "SELECT rowid, * FROM ttt$where"]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
register_tcl_module db
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE t1 USING tcl("vtab_cmd 0");
|
||||
}
|
||||
|
||||
do_eqp_test 1.1 {
|
||||
SELECT * FROM t1 WHERE a LIKE 'abc';
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?}
|
||||
}
|
||||
|
||||
do_eqp_test 1.2 {
|
||||
SELECT * FROM t1 WHERE a = 'abc';
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?}
|
||||
}
|
||||
|
||||
do_eqp_test 1.3 {
|
||||
SELECT * FROM t1 WHERE a = 'abc' OR b = 'def';
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a EQ ?}
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?}
|
||||
}
|
||||
|
||||
do_eqp_test 1.4 {
|
||||
SELECT * FROM t1 WHERE a LIKE 'abc%' OR b = 'def';
|
||||
} {
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:a LIKE ?}
|
||||
0 0 0 {SCAN TABLE t1 VIRTUAL TABLE INDEX 0:b EQ ?}
|
||||
}
|
||||
|
||||
do_execsql_test 1.5 {
|
||||
CREATE TABLE ttt(a, b, c);
|
||||
|
||||
INSERT INTO ttt VALUES(1, 'two', 'three');
|
||||
INSERT INTO ttt VALUES(2, 'one', 'two');
|
||||
INSERT INTO ttt VALUES(3, 'three', 'one');
|
||||
INSERT INTO ttt VALUES(4, 'y', 'one');
|
||||
INSERT INTO ttt VALUES(5, 'x', 'two');
|
||||
INSERT INTO ttt VALUES(6, 'y', 'three');
|
||||
}
|
||||
|
||||
foreach omit {0 1} {
|
||||
do_execsql_test 1.6.$omit.0 "
|
||||
DROP TABLE t1;
|
||||
CREATE VIRTUAL TABLE t1 USING tcl('vtab_cmd $omit');
|
||||
"
|
||||
do_execsql_test 1.6.$omit.1 {
|
||||
SELECT rowid FROM t1 WHERE c LIKE 'o%'
|
||||
} {3 4}
|
||||
|
||||
do_execsql_test 1.6.$omit.2 {
|
||||
SELECT rowid FROM t1 WHERE c LIKE 'o%' OR b='y'
|
||||
} {3 4 6}
|
||||
|
||||
do_execsql_test 1.6.$omit.3 {
|
||||
SELECT rowid FROM t1 WHERE c = 'three' OR c LIKE 'o%'
|
||||
} {1 6 3 4}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the same pattern works with ordinary tables.
|
||||
#
|
||||
do_execsql_test 2.1 {
|
||||
CREATE TABLE t2(x TEXT COLLATE nocase, y TEXT);
|
||||
CREATE INDEX t2x ON t2(x COLLATE nocase);
|
||||
CREATE INDEX t2y ON t2(y);
|
||||
}
|
||||
|
||||
do_eqp_test 2.2 {
|
||||
SELECT * FROM t2 WHERE x LIKE 'abc%' OR y = 'def'
|
||||
} {
|
||||
0 0 0 {SEARCH TABLE t2 USING INDEX t2x (x>? AND x<?)}
|
||||
0 0 0 {SEARCH TABLE t2 USING INDEX t2y (y=?)}
|
||||
}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test that any PRIMARY KEY within a sqlite3_decl_vtab() CREATE TABLE
|
||||
# statement is currently ignored.
|
||||
#
|
||||
proc vvv_command {method args} {
|
||||
switch -- $method {
|
||||
xConnect { return "CREATE TABLE t1(a PRIMARY KEY, b, c)" }
|
||||
}
|
||||
}
|
||||
proc yyy_command {method args} {
|
||||
switch -- $method {
|
||||
xConnect { return "CREATE TABLE t1(a, b, c, PRIMARY KEY(a, b))" }
|
||||
}
|
||||
}
|
||||
|
||||
do_execsql_test 3.1 { CREATE VIRTUAL TABLE t3 USING tcl('vvv_command') }
|
||||
do_execsql_test 3.2 { CREATE VIRTUAL TABLE t4 USING tcl('yyy_command') }
|
||||
|
||||
finish_test
|
||||
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
# 2016-06-02
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
# Test cases for CSV virtual table.
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix closure01
|
||||
|
||||
ifcapable !vtab||!cte { finish_test ; return }
|
||||
|
||||
load_static_extension db csv
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE VIRTUAL TABLE temp.t1 USING csv(
|
||||
data=
|
||||
'1,2,3,4
|
||||
5,6,7,8
|
||||
9,10,11,12
|
||||
13,14,15,16
|
||||
',
|
||||
columns=4
|
||||
);
|
||||
SELECT * FROM t1 WHERE c1=10;
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 1.1 {
|
||||
SELECT * FROM t1 WHERE c1='10';
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 1.2 {
|
||||
SELECT rowid FROM t1;
|
||||
} {1 2 3 4}
|
||||
|
||||
do_execsql_test 2.0 {
|
||||
DROP TABLE t1;
|
||||
CREATE VIRTUAL TABLE temp.t2 USING csv(
|
||||
data=
|
||||
'1,2,3,4
|
||||
5,6,7,8
|
||||
9,10,11,12
|
||||
13,14,15,16
|
||||
',
|
||||
columns=4,
|
||||
schema='CREATE TABLE t2(a INT, b TEXT, c REAL, d BLOB)'
|
||||
);
|
||||
SELECT * FROM t2 WHERE a=9;
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 2.1 {
|
||||
SELECT * FROM t2 WHERE b=10;
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 2.2 {
|
||||
SELECT * FROM t2 WHERE c=11;
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 2.3 {
|
||||
SELECT * FROM t2 WHERE d=12;
|
||||
} {}
|
||||
do_execsql_test 2.4 {
|
||||
SELECT * FROM t2 WHERE d='12';
|
||||
} {9 10 11 12}
|
||||
do_execsql_test 2.5 {
|
||||
SELECT * FROM t2 WHERE a='9';
|
||||
} {9 10 11 12}
|
||||
|
||||
do_execsql_test 3.0 {
|
||||
DROP TABLE t2;
|
||||
CREATE VIRTUAL TABLE temp.t3 USING csv(
|
||||
data=
|
||||
'1,2,3,4
|
||||
5,6,7,8
|
||||
9,10,11,12
|
||||
13,14,15,16
|
||||
',
|
||||
columns=4,
|
||||
schema=
|
||||
'CREATE TABLE t3(a PRIMARY KEY,b TEXT,c TEXT,d TEXT) WITHOUT ROWID',
|
||||
testflags=1
|
||||
);
|
||||
SELECT a FROM t3 WHERE b=6 OR c=7 OR d=12 ORDER BY +a;
|
||||
} {5 9}
|
||||
do_execsql_test 3.1 {
|
||||
SELECT a FROM t3 WHERE +b=6 OR c=7 OR d=12 ORDER BY +a;
|
||||
} {5 9}
|
||||
|
||||
# The rowid column is not visible on a WITHOUT ROWID virtual table
|
||||
do_catchsql_test 3.2 {
|
||||
SELECT rowid, a FROM t3;
|
||||
} {1 {no such column: rowid}}
|
||||
|
||||
do_catchsql_test 4.0 {
|
||||
DROP TABLE t3;
|
||||
CREATE VIRTUAL TABLE temp.t4 USING csv_wr(
|
||||
data=
|
||||
'1,2,3,4
|
||||
5,6,7,8
|
||||
9,10,11,12
|
||||
13,14,15,16
|
||||
',
|
||||
columns=4,
|
||||
schema=
|
||||
'CREATE TABLE t3(a PRIMARY KEY,b TEXT,c TEXT,d TEXT) WITHOUT ROWID',
|
||||
testflags=1
|
||||
);
|
||||
} {1 {vtable constructor failed: t4}}
|
||||
|
||||
finish_test
|
||||
@@ -1216,6 +1216,10 @@ static void displayLinuxIoStats(FILE *out){
|
||||
}
|
||||
#endif
|
||||
|
||||
#if SQLITE_VERSION_NUMBER<3006018
|
||||
# define sqlite3_sourceid(X) "(before 3.6.18)"
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv){
|
||||
int doAutovac = 0; /* True for --autovacuum */
|
||||
int cacheSize = 0; /* Desired cache size. 0 means default */
|
||||
|
||||
Reference in New Issue
Block a user