Compare commits
3 Commits
threadtest4
...
csv_ext
| Author | SHA1 | Date | |
|---|---|---|---|
| e2db2b1978 | |||
| 7e57ba9016 | |||
| 520bcc1170 |
+670
@@ -0,0 +1,670 @@
|
||||
/*
|
||||
** 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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
#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 */
|
||||
int maxRow; /* Size of zRow buffer */
|
||||
char *zRow; /* 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 );
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This routine reads a line of text from FILE in, stores
|
||||
** the text in memory obtained from malloc() and returns a pointer
|
||||
** to the text. NULL is returned at end of file, or if malloc()
|
||||
** fails.
|
||||
**
|
||||
** The interface is like "readline" but no command-line editing
|
||||
** is done.
|
||||
**
|
||||
** This code was modified from existing code in shell.c of the sqlite3 CLI.
|
||||
*/
|
||||
static char *csv_getline( CSV *pCSV ){
|
||||
int n = 0;
|
||||
int bEol = 0;
|
||||
int bShrink = 0;
|
||||
|
||||
/* allocate initial row buffer */
|
||||
if( pCSV->maxRow < 1 ){
|
||||
pCSV->zRow = sqlite3_malloc( 100 );
|
||||
if( pCSV->zRow ){
|
||||
pCSV->maxRow = 100;
|
||||
}
|
||||
}
|
||||
if( !pCSV->zRow ) return 0;
|
||||
|
||||
/* read until eol */
|
||||
while( !bEol ){
|
||||
/* grow row buffer as needed */
|
||||
if( n+100>pCSV->maxRow ){
|
||||
int newSize = pCSV->maxRow*2 + 100;
|
||||
char *p = sqlite3_realloc(pCSV->zRow, newSize);
|
||||
if( !p ) return 0;
|
||||
pCSV->maxRow = newSize;
|
||||
pCSV->zRow = p;
|
||||
bShrink = -1;
|
||||
}
|
||||
if( fgets(&pCSV->zRow[n], pCSV->maxRow-n, pCSV->f)==0 ){
|
||||
if( n==0 ){
|
||||
break;
|
||||
}
|
||||
pCSV->zRow[n] = '\0';
|
||||
bEol = -1;
|
||||
break;
|
||||
}
|
||||
/* look for line delimiter */
|
||||
while( pCSV->zRow[n] ){ n++; }
|
||||
if( (n>0) && ((pCSV->zRow[n-1]=='\n') || (pCSV->zRow[n-1]=='\r')) ){
|
||||
pCSV->zRow[n-1] = '\n'; /* uniform line ending */
|
||||
pCSV->zRow[n] = '\0';
|
||||
bEol = -1;
|
||||
}
|
||||
}
|
||||
if( bShrink ){
|
||||
pCSV->zRow = realloc( pCSV->zRow, n+1 );
|
||||
pCSV->maxRow = n+1;
|
||||
}
|
||||
return bEol ? pCSV->zRow : 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** 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[3] = ",\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_getline( 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 */
|
||||
int maxCol = (int)(strlen(pCSV->zRow) / 5 + 1);
|
||||
pCSV->aCols = (char **)sqlite3_malloc( sizeof(char*) * maxCol );
|
||||
if( pCSV->aCols ){
|
||||
pCSV->maxCol = maxCol;
|
||||
}
|
||||
}
|
||||
if( !pCSV->aCols ) 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 */
|
||||
/* TBD: handle escaped quotes "" */
|
||||
/* find closing quote */
|
||||
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" */
|
||||
s++;
|
||||
}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' ) 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->zRow ) sqlite3_free( pCSV->zRow );
|
||||
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 && i<pCSV->nCol; i++){
|
||||
const char *zTail = (i+1<pCSV->nCol) ? ", " : ");";
|
||||
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
|
||||
@@ -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 */
|
||||
@@ -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'}}
|
||||
@@ -0,0 +1,6 @@
|
||||
"colA","colB","colC"
|
||||
1,2,3
|
||||
a,b,c
|
||||
a,"b",c
|
||||
"a","b","c .. z"
|
||||
"a","b","c,d"
|
||||
|
@@ -0,0 +1,6 @@
|
||||
"colA"|"colB"|"colC"
|
||||
1|2|3
|
||||
a|b|c
|
||||
a|"b"|c
|
||||
"a"|"b"|"c .. z"
|
||||
"a"|"b"|"c|d"
|
||||
|
@@ -0,0 +1,5 @@
|
||||
colA|colB|colC
|
||||
1|2|3
|
||||
a|b|c
|
||||
a|b|c .. z
|
||||
a|b|c|d
|
||||
|
@@ -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 Update\smaxRow\ssize\safter\sshrinking\sthe\srow\sbuffer\s(CSV).
|
||||
D 2009-11-05T04:14:30
|
||||
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 c2b193b98394970c6e5fc8b2a9dd3d80114c5703
|
||||
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,7 @@ 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 ec08b15f77741efd38dadd6aff492ccbb841471e
|
||||
R bbd4a60a692322be9ab3321f8859c300
|
||||
U shaneh
|
||||
Z 68d17772bddd2ba999f9cce39f7958ee
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
eb7a544fe49d1626bacecfe53ddc03fe082e3243
|
||||
d474195a997b9d94bea8976738c942662f7c4751
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user