From: shaneh Date: Thu, 5 Nov 2009 02:34:42 +0000 (+0000) Subject: Initial implementation of a virtual table for CSV files. X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=520bcc1170f41dcbc80119503ceef50f1b07cac6;p=thirdparty%2Fsqlite.git Initial implementation of a virtual table for CSV files. FossilOrigin-Name: 90e63b7d845bacc9a1a1db13c1e9a406e5306faa --- diff --git a/ext/csv/csv.c b/ext/csv/csv.c new file mode 100644 index 0000000000..9244922d84 --- /dev/null +++ b/ext/csv/csv.c @@ -0,0 +1,623 @@ +/* +** 2009 March 26 +** +** 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 code for implementations of the CSV +** algorithms packaged as an SQLite virtual table module. +*/ +#if defined(_WIN32) || defined(WIN32) +/* This needs to come before any includes for MSVC compiler */ +#define _CRT_SECURE_NO_WARNINGS +#endif + +#if !defined(SQLITE_CORE) || defined(SQLITE_ENABLE_CSV) + + +#include +#include +#include + + +#ifndef SQLITE_CORE + #include "sqlite3ext.h" + SQLITE_EXTENSION_INIT1 +#else + #include "sqlite3.h" +#endif + + +#define UNUSED_PARAMETER(x) (void)(x) + + +/* +** The CSV virtual-table types. +*/ +typedef struct CSV CSV; +typedef struct CSVCursor CSVCursor; + + +/* +** An CSV virtual-table object. +*/ +struct CSV { + sqlite3_vtab base; /* Must be first */ + sqlite3 *db; /* Host database connection */ + char *zDb; /* Name of database containing CSV table */ + char *zName; /* Name of CSV table */ + char *zFile; /* Name of CSV file */ + int nBusy; /* Current number of users of this structure */ + FILE *f; /* File pointer for source CSV file */ + long offsetFirstRow; /* ftell position of first row */ + int eof; /* True when at end of file */ + char zRow[4096]; /* Buffer for current CSV row */ + char cDelim; /* Character to use for delimiting columns */ + int nCol; /* Number of columns in current row */ + int maxCol; /* Size of aCols array */ + char **aCols; /* Array of parsed columns */ +}; + + +/* +** An CSV cursor object. +*/ +struct CSVCursor { + sqlite3_vtab_cursor base; /* Must be first */ + long csvpos; /* ftell position of current zRow */ +}; + + +/* +** Forward declarations. +*/ +static int csvNext( sqlite3_vtab_cursor* pVtabCursor ); +static int csvInit( + sqlite3 *db, /* Database connection */ + void *pAux, /* Unused */ + int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */ + sqlite3_vtab **ppVtab, /* OUT: New virtual table */ + char **pzErr, /* OUT: Error message, if any */ + int isCreate /* True for xCreate, false for xConnect */ +); +static void csvReference( CSV *pCSV ); +static int csvRelease( CSV *pCSV ); + + +/* +** Abstract out file io routines for porting +*/ +static FILE *csv_open( CSV *pCSV ){ + return fopen( pCSV->zFile, "rb" ); +} +static void csv_close( CSV *pCSV ){ + if( pCSV->f ) fclose( pCSV->f ); +} +static int csv_seek( CSV *pCSV, long pos ){ + return fseek( pCSV->f, pos, SEEK_SET ); +} +static long csv_tell( CSV *pCSV ){ + return ftell( pCSV->f ); +} +static char *csv_gets( CSV *pCSV ){ + return fgets( pCSV->zRow, sizeof(pCSV->zRow), pCSV->f ); +} + + +/* +** CSV virtual table module xCreate method. +*/ +static int csvCreate( + sqlite3* db, + void *pAux, + int argc, + const char *const *argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return csvInit( db, pAux, argc, argv, ppVtab, pzErr, 1 ); +} + + +/* +** CSV virtual table module xConnect method. +*/ +static int csvConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + return csvInit(db, pAux, argc, argv, ppVtab, pzErr, 0); +} + + +/* +** CSV virtual table module xBestIndex method. +*/ +static int csvBestIndex( sqlite3_vtab *pVtab, sqlite3_index_info* info ) +{ + UNUSED_PARAMETER(pVtab); + UNUSED_PARAMETER(info); + + /* TBD */ + + return SQLITE_OK; +} + + +/* +** CSV virtual table module xDisconnect method. +*/ +static int csvDisconnect( sqlite3_vtab *pVtab ){ + return csvRelease( (CSV *)pVtab ); +} + + +/* +** CSV virtual table module xDestroy method. +*/ +static int csvDestroy( sqlite3_vtab *pVtab ){ + return csvDisconnect( pVtab ); +} + + +/* +** CSV virtual table module xOpen method. +*/ +static int csvOpen( sqlite3_vtab *pVtab, sqlite3_vtab_cursor **ppVtabCursor ){ + int rc = SQLITE_NOMEM; + CSVCursor *pCsr; + + /* create a new cursor object */ + pCsr = (CSVCursor *)sqlite3_malloc(sizeof(CSVCursor)); + if( pCsr ){ + memset(pCsr, 0, sizeof(CSVCursor)); + pCsr->base.pVtab = pVtab; + rc = SQLITE_OK; + } + *ppVtabCursor = (sqlite3_vtab_cursor *)pCsr; + + return rc; +} + + +/* +** CSV virtual table module xClose method. +*/ +static int csvClose( sqlite3_vtab_cursor *pVtabCursor ){ + CSVCursor *pCsr = (CSVCursor *)pVtabCursor; + + sqlite3_free(pCsr); + + return SQLITE_OK; +} + + +/* +** CSV virtual table module xFilter method. +*/ +static int csvFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv +){ + CSV *pCSV = (CSV *)pVtabCursor->pVtab; + int rc; + + UNUSED_PARAMETER(idxNum); + UNUSED_PARAMETER(idxStr); + UNUSED_PARAMETER(argc); + UNUSED_PARAMETER(argv); + + csvReference( pCSV ); + + /* seek back to start of first zRow */ + pCSV->eof = 0; + csv_seek( pCSV, pCSV->offsetFirstRow ); + /* read and parse next line */ + rc = csvNext( pVtabCursor ); + + csvRelease( pCSV ); + + return rc; +} + + +/* +** CSV virtual table module xNext method. +*/ +static int csvNext( sqlite3_vtab_cursor* pVtabCursor ){ + CSV *pCSV = (CSV *)pVtabCursor->pVtab; + CSVCursor *pCsr = (CSVCursor *)pVtabCursor; + int nCol = 0; + char *s; + char zDelims[4] = ",\r\n"; + char cDelim; /* char that delimited current col */ + + if( pCSV->eof ){ + return SQLITE_ERROR; + } + + /* update the cursor */ + pCsr->csvpos = csv_tell( pCSV ); + + /* read the next row of data */ + s = csv_gets( pCSV ); + if( !s ){ + /* and error or eof occured */ + pCSV->eof = -1; + return SQLITE_OK; + } + + /* allocate initial space for the column pointers */ + if( pCSV->maxCol < 1 ){ + /* take a guess */ + pCSV->maxCol = (int)(strlen(pCSV->zRow) / 5 + 1); + if( pCSV->aCols ) sqlite3_free( pCSV->aCols ); + pCSV->aCols = (char **)sqlite3_malloc( sizeof(char*) * pCSV->maxCol ); + if( !pCSV->aCols ){ + /* out of memory */ + return SQLITE_NOMEM; + } + } + + /* add custom delim character */ + zDelims[0] = pCSV->cDelim; + + /* parse the zRow into individual columns */ + do{ + /* if it begins with a quote, assume it's a quoted col */ + if( *s=='\"' ){ + s++; /* skip quote */ + pCSV->aCols[nCol] = s; /* save pointer for this col */ + /* find closing quote */ +#if 1 + s = strchr(s, '\"'); + if( !s ){ + /* no closing quote */ + pCSV->eof = -1; + return SQLITE_ERROR; + } + *s = '\0'; /* null terminate this col */ + /* fall through and look for following ",\n\r" */ + s++; +#else + /* TBD: handle escaped quotes "" */ + while( s[0] ){ + if( s[0]=='\"' ){ + if( s[1]=='\"' ){ + } + break; + } + s++; + } +#endif + }else{ + pCSV->aCols[nCol] = s; /* save pointer for this col */ + } + s = strpbrk(s, zDelims); + if( !s ){ + /* no col delimiter */ + pCSV->eof = -1; + return SQLITE_ERROR; + } + cDelim = *s; + /* null terminate the column by overwriting the delimiter */ + *s = '\0'; + nCol++; + /* if end of zRow, stop parsing cols */ + if( (cDelim == '\n') || (cDelim == '\r') ) break; + /* move to start of next col */ + s++; /* skip delimiter */ + + if(nCol >= pCSV->maxCol ){ + /* we need to grow our col pointer array */ + char **p = (char **)sqlite3_realloc( pCSV->aCols, sizeof(char*) * (nCol+5) ); + if( !p ){ + /* out of memory */ + return SQLITE_ERROR; + } + pCSV->maxCol = nCol + 5; + pCSV->aCols = p; + } + + }while( *s ); + + pCSV->nCol = nCol; + return SQLITE_OK; +} + + +/* +** CSV virtual table module xEof method. +** +** Return non-zero if the cursor does not currently point to a valid +** record (i.e if the scan has finished), or zero otherwise. +*/ +static int csvEof( sqlite3_vtab_cursor *pVtabCursor ) +{ + CSV *pCSV = (CSV *)pVtabCursor->pVtab; + + return pCSV->eof; +} + + +/* +** CSV virtual table module xColumn method. +*/ +static int csvColumn(sqlite3_vtab_cursor *pVtabCursor, sqlite3_context *ctx, int i){ + CSV *pCSV = (CSV *)pVtabCursor->pVtab; + + if( i<0 || i>=pCSV->nCol ){ + sqlite3_result_null( ctx ); + }else{ + char *col = pCSV->aCols[i]; + if( !col ){ + sqlite3_result_null( ctx ); + }else{ + sqlite3_result_text( ctx, col, -1, SQLITE_TRANSIENT ); + } + } + + return SQLITE_OK; +} + + +/* +** CSV virtual table module xRowid method. +** We probably should store a hidden table +** mapping rowid's to csvpos. +*/ +static int csvRowid( sqlite3_vtab_cursor* pVtabCursor, sqlite3_int64 *pRowid ){ + CSVCursor *pCsr = (CSVCursor *)pVtabCursor; + + *pRowid = pCsr->csvpos; + + return SQLITE_OK; +} + + +static sqlite3_module csvModule = { + 0, /* iVersion */ + csvCreate, /* xCreate - create a table */ + csvConnect, /* xConnect - connect to an existing table */ + csvBestIndex, /* xBestIndex - Determine search strategy */ + csvDisconnect, /* xDisconnect - Disconnect from a table */ + csvDestroy, /* xDestroy - Drop a table */ + csvOpen, /* xOpen - open a cursor */ + csvClose, /* xClose - close a cursor */ + csvFilter, /* xFilter - configure scan constraints */ + csvNext, /* xNext - advance a cursor */ + csvEof, /* xEof */ + csvColumn, /* xColumn - read data */ + csvRowid, /* xRowid - read data */ + 0, /* xUpdate - write data */ + 0, /* xBegin - begin transaction */ + 0, /* xSync - sync transaction */ + 0, /* xCommit - commit transaction */ + 0, /* xRollback - rollback transaction */ + 0, /* xFindFunction - function overloading */ + 0 /* xRename - rename the table */ +}; + + +/* +** Increment the CSV reference count. +*/ +static void csvReference( CSV *pCSV ){ + pCSV->nBusy++; +} + + +/* +** Decrement the CSV reference count. When the reference count reaches +** zero the structure is deleted. +*/ +static int csvRelease( CSV *pCSV ){ + pCSV->nBusy--; + if( pCSV->nBusy<1 ){ + + /* finalize any prepared statements here */ + + csv_close( pCSV ); + if( pCSV->aCols ) sqlite3_free( pCSV->aCols ); + sqlite3_free( pCSV ); + } + return 0; +} + + +/* +** This function is the implementation of both the xConnect and xCreate +** methods of the CSV virtual table. +** +** argv[0] -> module name +** argv[1] -> database name +** argv[2] -> table name +** argv[3] -> csv file name +** argv[4] -> custom delimiter +** argv[5] -> optional: use header row for column names +*/ +static int csvInit( + sqlite3 *db, /* Database connection */ + void *pAux, /* Unused */ + int argc, const char *const*argv, /* Parameters to CREATE TABLE statement */ + sqlite3_vtab **ppVtab, /* OUT: New virtual table */ + char **pzErr, /* OUT: Error message, if any */ + int isCreate /* True for xCreate, false for xConnect */ +){ + int rc = SQLITE_OK; + int i; + CSV *pCSV; + char *zSql; + char cDelim = ','; /* Default col delimiter */ + int bUseHeaderRow = 0; /* Default to not use zRow headers */ + size_t nDb; /* Length of string argv[1] */ + size_t nName; /* Length of string argv[2] */ + size_t nFile; /* Length of string argv[3] */ + CSVCursor csvCsr; /* Used for calling csvNext */ + + const char *aErrMsg[] = { + 0, /* 0 */ + "No CSV file specified", /* 1 */ + "Error opening CSV file: '%s'", /* 2 */ + "No columns found", /* 3 */ + "No column name found", /* 4 */ + "Out of memory", /* 5 */ + }; + + UNUSED_PARAMETER(pAux); + UNUSED_PARAMETER(isCreate); + + if( argc < 4 ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[1]); + return SQLITE_ERROR; + } + + /* allocate space for the virtual table object */ + nDb = strlen(argv[1]); + nName = strlen(argv[2]); + nFile = strlen(argv[3]); + pCSV = (CSV *)sqlite3_malloc( (int)(sizeof(CSV)+nDb+nName+nFile+3) ); + if( !pCSV ){ + /* out of memory */ + *pzErr = sqlite3_mprintf("%s", aErrMsg[5]); + return SQLITE_NOMEM; + } + + /* intialize virtual table object */ + memset(pCSV, 0, sizeof(CSV)+nDb+nName+nFile+3); + pCSV->nBusy = 1; + pCSV->base.pModule = &csvModule; + pCSV->cDelim = cDelim; + pCSV->zDb = (char *)&pCSV[1]; + pCSV->zName = &pCSV->zDb[nDb+1]; + pCSV->zFile = &pCSV->zName[nName+1]; + memcpy(pCSV->zDb, argv[1], nDb); + memcpy(pCSV->zName, argv[2], nName); + + /* pull out name of csv file (remove quotes) */ + if( argv[3][0] == '\'' ){ + memcpy( pCSV->zFile, argv[3]+1, nFile-2 ); + pCSV->zFile[nFile-2] = '\0'; + }else{ + memcpy( pCSV->zFile, argv[3], nFile ); + } + + /* if a custom delimiter specified, pull it out */ + if( argc > 4 ){ + if( argv[4][0] == '\'' ){ + pCSV->cDelim = argv[4][1]; + }else{ + pCSV->cDelim = argv[4][0]; + } + } + + /* should the header zRow be used */ + if( argc > 5 ){ + if( !strcmp(argv[5], "USE_HEADER_ROW") ){ + bUseHeaderRow = -1; + } + } + + /* open the source csv file */ + pCSV->f = csv_open( pCSV ); + if( !pCSV->f ){ + *pzErr = sqlite3_mprintf(aErrMsg[2], pCSV->zFile); + csvRelease( pCSV ); + return SQLITE_ERROR; + } + + /* Read first zRow to obtain column names/number */ + csvCsr.base.pVtab = (sqlite3_vtab *)pCSV; + rc = csvNext( (sqlite3_vtab_cursor *)&csvCsr ); + if( (SQLITE_OK!=rc) || (pCSV->nCol<=0) ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[3]); + csvRelease( pCSV ); + return SQLITE_ERROR; + } + if( bUseHeaderRow ){ + pCSV->offsetFirstRow = csv_tell( pCSV ); + } + + /* Create the underlying relational database schema. If + ** that is successful, call sqlite3_declare_vtab() to configure + ** the csv table schema. + */ + zSql = sqlite3_mprintf("CREATE TABLE x("); + for(i=0; zSql && inCol; i++){ + const char *zTail = (i+1nCol) ? ", " : ");"; + char *zTmp = zSql; + if( bUseHeaderRow ){ + const char *zCol = pCSV->aCols[i]; + if( !zCol ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[4]); + sqlite3_free(zSql); + csvRelease( pCSV ); + return SQLITE_ERROR; + } + zSql = sqlite3_mprintf("%s%s%s", zTmp, zCol, zTail); + }else{ + zSql = sqlite3_mprintf("%scol%d%s", zTmp, i+1, zTail); + } + sqlite3_free(zTmp); + } + if( !zSql ){ + *pzErr = sqlite3_mprintf("%s", aErrMsg[5]); + csvRelease( pCSV ); + return SQLITE_NOMEM; + } + + rc = sqlite3_declare_vtab( db, zSql ); + sqlite3_free(zSql); + if( SQLITE_OK != rc ){ + *pzErr = sqlite3_mprintf("%s", sqlite3_errmsg(db)); + csvRelease( pCSV ); + return SQLITE_ERROR; + } + + *ppVtab = (sqlite3_vtab *)pCSV; + *pzErr = NULL; + return SQLITE_OK; +} + + +/* +** Register the CSV module with database handle db. This creates the +** virtual table module "csv". +*/ +int sqlite3CsvInit(sqlite3 *db){ + int rc = SQLITE_OK; + + if( rc==SQLITE_OK ){ + void *c = (void *)NULL; + rc = sqlite3_create_module_v2(db, "csv", &csvModule, c, 0); + } + + return rc; +} + + +#if !SQLITE_CORE +/* +** Support auto-extension loading. +*/ +int sqlite3_extension_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + SQLITE_EXTENSION_INIT2(pApi) + return sqlite3CsvInit(db); +} +#endif + + +#endif diff --git a/ext/csv/csv.h b/ext/csv/csv.h new file mode 100644 index 0000000000..1545b1a379 --- /dev/null +++ b/ext/csv/csv.h @@ -0,0 +1,28 @@ +/* +** 2009 March 26 +** +** 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 header file is used by programs that want to link against the +** CSV Virtual Table extention. +** +** All it does is declare the sqlite3CsvInit() interface. +*/ +#include "sqlite3.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +int sqlite3CsvInit(sqlite3 *db); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ diff --git a/ext/csv/csv1.test b/ext/csv/csv1.test new file mode 100644 index 0000000000..dac3982a6f --- /dev/null +++ b/ext/csv/csv1.test @@ -0,0 +1,253 @@ +# 2009 Nov 11 +# +# 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. +# +#*********************************************************************** +# +# The focus of this file is testing the csv extension. +# +# $Id: csv1.test,v 1.7 2009/07/17 16:54:48 shaneh Exp $ +# + +if {![info exists testdir]} { + set testdir [file join [file dirname $argv0] .. .. test] +} +source $testdir/tester.tcl + +# Test plan: +# +# csv-1.*: Creating/destroying csv tables. +# csv-2.*: Linear scans of csv data. +# csv-3.*: Test renaming an csv table. +# csv-4.*: CREATE errors +# + +ifcapable !csv { + finish_test + return +} + +# This file is delimited by ',' and has quoted fields. +set test1csv [file join [file dirname [info script]] test1.csv] +# This file is delimited by '|' and has quoted fields. +set test2csv [file join [file dirname [info script]] test2.csv] +# This file is delimited by '|'. It does NOT have quoted fields. +set test3csv [file join [file dirname [info script]] test3.csv] + +#---------------------------------------------------------------------------- +# Test cases csv-1.* test CREATE and DROP table statements. +# + +# Test creating and dropping an csv table with a header row. +# +do_test csv-1.1.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test1csv', ',', USE_HEADER_ROW) " +} {} +do_test csv-1.1.2 { + execsql { SELECT name FROM sqlite_master ORDER BY name } +} {t1} +do_test csv-1.1.3 { + execsql { + DROP TABLE t1; + SELECT name FROM sqlite_master ORDER BY name; + } +} {} + +# Test creating and dropping an csv table without a header row. +# +do_test csv-1.2.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test1csv', ',') " +} {} +do_test csv-1.2.2 { + execsql { SELECT name FROM sqlite_master ORDER BY name } +} {t1} +do_test csv-1.2.3 { + execsql { + DROP TABLE t1; + SELECT name FROM sqlite_master ORDER BY name; + } +} {} + +# Test creating and dropping an csv table without a header row +# and with the default delimiter ','. +# +do_test csv-1.3.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test1csv') " +} {} +do_test csv-1.3.2 { + execsql { SELECT name FROM sqlite_master ORDER BY name } +} {t1} +do_test csv-1.3.3 { + execsql { + DROP TABLE t1; + SELECT name FROM sqlite_master ORDER BY name; + } +} {} + +# Test creating and dropping an csv table without a header row +# and with the custom delimiter '|'. +# +do_test csv-1.4.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test2csv', '|') " +} {} +do_test csv-1.4.2 { + execsql { SELECT name FROM sqlite_master ORDER BY name } +} {t1} +do_test csv-1.4.3 { + execsql { + DROP TABLE t1; + SELECT name FROM sqlite_master ORDER BY name; + } +} {} + +#---------------------------------------------------------------------------- +# Test cases csv-2.* test linear scans of csv table data. +# +do_test csv-2.1.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test1csv', ',', USE_HEADER_ROW) " +} {} +do_test csv-2.1.2 { + execsql { + SELECT * FROM t1; + } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-2.1.3 { + execsql { + SELECT * FROM t1 WHERE colA='a'; + } +} {a b c a b c a b {c .. z} a b c,d} + +do_test csv-2.2.1 { + execsql " CREATE VIRTUAL TABLE t2 USING csv('$test1csv', ',') " +} {} +do_test csv-2.2.2 { + execsql { + SELECT * FROM t2; + } +} {colA colB colC 1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-2.2.3 { + execsql { + SELECT * FROM t2 WHERE col1='a'; + } +} {a b c a b c a b {c .. z} a b c,d} + +# Test scanning with the custom delimiter '|'. +# +do_test csv-2.3.1 { + execsql " CREATE VIRTUAL TABLE t3 USING csv('$test2csv', '|') " +} {} +do_test csv-2.3.2 { + execsql { + SELECT * FROM t3; + } +} {colA colB colC 1 2 3 a b c a b c a b {c .. z} a b c|d} +do_test csv-2.3.3 { + execsql { + SELECT * FROM t3 WHERE col1='a'; + } +} {a b c a b c a b {c .. z} a b c|d} + +# Test scanning with the custom delimiter ';'. The test file +# uses | for a delimiter, so everything should be treated as +# a single column. +# +do_test csv-2.4.1 { + execsql " CREATE VIRTUAL TABLE t4 USING csv('$test3csv', ';') " +} {} +do_test csv-2.4.2 { + execsql { + SELECT * FROM t4; + } +} {colA|colB|colC 1|2|3 a|b|c {a|b|c .. z} a|b|c|d} +do_test csv-2.4.3 { + execsql { + SELECT * FROM t4 WHERE col1 LIKE 'a%'; + } +} {a|b|c {a|b|c .. z} a|b|c|d} + +# Test rowid column. +# +do_test csv-2.5.1 { + execsql { + SELECT rowid FROM t1; + } +} {21 27 33 41 58} +do_test csv-2.5.2 { + execsql { + SELECT rowid FROM t1 WHERE colA='a'; + } +} {27 33 41 58} + +# Clean-up. +# +do_test csv-2.6.1 { + execsql { + DROP TABLE t1; + DROP TABLE t2; + DROP TABLE t3; + DROP TABLE t4; + } +} {} + +#---------------------------------------------------------------------------- +# Test cases csv-3.* test rename operations. +# +do_test csv-3.1.1 { + execsql " CREATE VIRTUAL TABLE t1 USING csv('$test1csv', ',', USE_HEADER_ROW) " + execsql " CREATE VIRTUAL TABLE t2 USING csv('$test1csv', ',', USE_HEADER_ROW) " +} {} +do_test csv-3.1.2 { + catchsql { ALTER TABLE t2 RENAME TO t1 } +} {1 {there is already another table or index with this name: t1}} +do_test csv-3.1.3 { + execsql { + DROP TABLE t1; + ALTER TABLE t2 RENAME TO t1 + } +} {} +do_test csv-3.1.4 { + execsql { ALTER TABLE t1 RENAME TO t5 } + execsql { SELECT * FROM t5 } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-3.1.5 { + db close + sqlite3 db test.db + execsql { SELECT * FROM t5 } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-3.1.6 { + execsql { ALTER TABLE t5 RENAME TO 'raisara "one"'''} + execsql { SELECT * FROM "raisara ""one""'" } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-3.1.7 { + execsql { SELECT * FROM 'raisara "one"''' } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-3.1.8 { + execsql { ALTER TABLE "raisara ""one""'" RENAME TO "abc 123" } + execsql { SELECT * FROM "abc 123" } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} +do_test csv-3.1.9 { + db close + sqlite3 db test.db + execsql { SELECT * FROM "abc 123" } +} {1 2 3 a b c a b c a b {c .. z} a b c,d} + +#---------------------------------------------------------------------------- +# Test cases csv-4.* test CREATE errors +# + +# Test creating and dropping an csv table with a header row. +# +do_test csv-4.1.1 { + catchsql " CREATE VIRTUAL TABLE t1 USING csv() " +} {1 {No CSV file specified}} +do_test csv-4.1.2 { + catchsql " CREATE VIRTUAL TABLE t1 USING csv('foo') " +} {1 {Error opening CSV file: 'foo'}} +do_test csv-4.1.3 { + catchsql " CREATE VIRTUAL TABLE t1 USING csv(foo foo) " +} {1 {Error opening CSV file: 'foo foo'}} diff --git a/ext/csv/test1.csv b/ext/csv/test1.csv new file mode 100644 index 0000000000..708f93f195 --- /dev/null +++ b/ext/csv/test1.csv @@ -0,0 +1,6 @@ +"colA","colB","colC" +1,2,3 +a,b,c +a,"b",c +"a","b","c .. z" +"a","b","c,d" diff --git a/ext/csv/test2.csv b/ext/csv/test2.csv new file mode 100644 index 0000000000..4766b9083d --- /dev/null +++ b/ext/csv/test2.csv @@ -0,0 +1,6 @@ +"colA"|"colB"|"colC" +1|2|3 +a|b|c +a|"b"|c +"a"|"b"|"c .. z" +"a"|"b"|"c|d" diff --git a/ext/csv/test3.csv b/ext/csv/test3.csv new file mode 100644 index 0000000000..eb7b6c8808 --- /dev/null +++ b/ext/csv/test3.csv @@ -0,0 +1,5 @@ +colA|colB|colC +1|2|3 +a|b|c +a|b|c .. z +a|b|c|d diff --git a/manifest b/manifest index eb2afb9b77..ec48b249c4 100644 --- a/manifest +++ b/manifest @@ -1,8 +1,5 @@ ------BEGIN PGP SIGNED MESSAGE----- -Hash: SHA1 - -C Remove\s"const"\sfrom\sparameter\sof\ssqlite3BtreeFactory()\sto\savoid\sa\scompiler\nwarning. -D 2009-11-04T13:30:02 +C Initial\simplementation\sof\sa\svirtual\stable\sfor\sCSV\sfiles. +D 2009-11-05T02:34:43 F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0 F Makefile.in a77dfde96ad86aafd3f71651a4333a104debe86a F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654 @@ -30,6 +27,12 @@ F ext/README.txt 913a7bd3f4837ab14d7e063304181787658b14e1 F ext/async/README.txt 0c541f418b14b415212264cbaaf51c924ec62e5b F ext/async/sqlite3async.c 3d5396cd69851f5633ef29c7491ca9249eac903a F ext/async/sqlite3async.h a21e1252deb14a2c211f0e165c4b9122a8f1f344 +F ext/csv/csv.c 7df206eadb1faf0d02cd06df76e37917643f9813 +F ext/csv/csv.h 9d200dd0511a41f22561d3100cd3cc924818c04a +F ext/csv/csv1.test 822780d05036796c3f2ff4432b5451fe47ae3355 +F ext/csv/test1.csv 888555e2a197fecaf9d0b90133551ebb8cbac8ce +F ext/csv/test2.csv 04c9f79ff9edd3f9fa4a332524d7b651b968bf8b +F ext/csv/test3.csv 4d0a88e552374c2d3f57bec1c4fe3b0fbd37a362 F ext/fts1/README.txt 20ac73b006a70bcfd80069bdaf59214b6cf1db5e F ext/fts1/ft_hash.c 3927bd880e65329bdc6f506555b228b28924921b F ext/fts1/ft_hash.h 1a35e654a235c2c662d3ca0dfc3138ad60b8b7d5 @@ -130,7 +133,7 @@ F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0 F src/legacy.c 303b4ffcf1ae652fcf5ef635846c563c254564f6 F src/lempar.c 7f026423f4d71d989e719a743f98a1cbd4e6d99e F src/loadext.c 0e88a335665db0b2fb4cece3e49dcb65d832635a -F src/main.c aae32d5af35b88faff0664e0f937ee7133d77c8d +F src/main.c 3f99627e8e3014eb38d95ca7e00e56a2dd95f687 F src/malloc.c 685561d2f1602042e349aea5d7a88c3f10a63454 F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c F src/mem1.c e6d5c23941288df8191b8a98c28e3f57771e2270 @@ -185,7 +188,7 @@ F src/test_async.c 731d23f953ece5bf40ce87810cfb7607218953c5 F src/test_autoext.c f53b0cdf7bf5f08100009572a5d65cdb540bd0ad F src/test_backup.c 1384a18985a5a2d275c2662e48473bf1542ebd08 F src/test_btree.c 5adbba9b138988a3cf4d3b5424dbc7c85651da02 -F src/test_config.c 4ac1e6257dcf926a71b7934410b71c5c326e68f2 +F src/test_config.c cc5c8cfdb2ce128ff61c8546db51c0169b45dd50 F src/test_devsym.c 9f4bc2551e267ce7aeda195f3897d0f30c5228f4 F src/test_func.c c6e9d7cfbd7bb0bd7c392a10d76adab4b48e813b F src/test_hexio.c 2f1122aa3f012fa0142ee3c36ce5c902a70cd12f @@ -764,14 +767,11 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224 F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f -P 24a4d520d540d92b611abc4eb57dc6da9be4eac6 -R a2178242a921307d19fbc8b7d2eec4f0 -U drh -Z a8d4aa9309aed4b69860cd88128a4708 ------BEGIN PGP SIGNATURE----- -Version: GnuPG v1.4.6 (GNU/Linux) - -iD8DBQFK8YHdoxKgR168RlERAs0DAJ447A8mNmnCD0zy/gACLgqIHsmF8QCfYMHu -do95dSeuDXCMLnSZQtGGeQE= -=FnZg ------END PGP SIGNATURE----- +P eb7a544fe49d1626bacecfe53ddc03fe082e3243 +R 27eb092351aa8c08879582048eb7304c +T *bgcolor * orange +T *branch * csv_ext +T *sym-csv_ext * +T -sym-trunk * +U shaneh +Z c880cba03cce7d6961f66416059a9bd1 diff --git a/manifest.uuid b/manifest.uuid index d60694a03f..37e351a30b 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -eb7a544fe49d1626bacecfe53ddc03fe082e3243 \ No newline at end of file +90e63b7d845bacc9a1a1db13c1e9a406e5306faa \ No newline at end of file diff --git a/src/main.c b/src/main.c index a675427889..c77a420032 100644 --- a/src/main.c +++ b/src/main.c @@ -25,6 +25,9 @@ #ifdef SQLITE_ENABLE_ICU # include "sqliteicu.h" #endif +#ifdef SQLITE_ENABLE_CSV +# include "csv.h" +#endif /* ** The version of the library @@ -1715,6 +1718,12 @@ static int openDatabase( } #endif +#ifdef SQLITE_ENABLE_CSV + if( !db->mallocFailed && rc==SQLITE_OK){ + rc = sqlite3CsvInit(db); + } +#endif + sqlite3Error(db, rc, 0); /* -DSQLITE_DEFAULT_LOCKING_MODE=1 makes EXCLUSIVE the default locking diff --git a/src/test_config.c b/src/test_config.c index 6340b5e269..16cf7c4d2e 100644 --- a/src/test_config.c +++ b/src/test_config.c @@ -384,6 +384,12 @@ Tcl_SetVar2(interp, "sqlite_options", "long_double", Tcl_SetVar2(interp, "sqlite_options", "rtree", "0", TCL_GLOBAL_ONLY); #endif +#ifdef SQLITE_ENABLE_CSV + Tcl_SetVar2(interp, "sqlite_options", "csv", "1", TCL_GLOBAL_ONLY); +#else + Tcl_SetVar2(interp, "sqlite_options", "csv", "0", TCL_GLOBAL_ONLY); +#endif + #ifdef SQLITE_OMIT_SCHEMA_PRAGMAS Tcl_SetVar2(interp, "sqlite_options", "schema_pragmas", "0", TCL_GLOBAL_ONLY); #else