]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Initial implementation of a virtual table for CSV files.
authorshaneh <shaneh@noemail.net>
Thu, 5 Nov 2009 02:34:42 +0000 (02:34 +0000)
committershaneh <shaneh@noemail.net>
Thu, 5 Nov 2009 02:34:42 +0000 (02:34 +0000)
FossilOrigin-Name: 90e63b7d845bacc9a1a1db13c1e9a406e5306faa

ext/csv/csv.c [new file with mode: 0644]
ext/csv/csv.h [new file with mode: 0644]
ext/csv/csv1.test [new file with mode: 0644]
ext/csv/test1.csv [new file with mode: 0644]
ext/csv/test2.csv [new file with mode: 0644]
ext/csv/test3.csv [new file with mode: 0644]
manifest
manifest.uuid
src/main.c
src/test_config.c

diff --git a/ext/csv/csv.c b/ext/csv/csv.c
new file mode 100644 (file)
index 0000000..9244922
--- /dev/null
@@ -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 <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 */
+  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 && 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
diff --git a/ext/csv/csv.h b/ext/csv/csv.h
new file mode 100644 (file)
index 0000000..1545b1a
--- /dev/null
@@ -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 (file)
index 0000000..dac3982
--- /dev/null
@@ -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 (file)
index 0000000..708f93f
--- /dev/null
@@ -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 (file)
index 0000000..4766b90
--- /dev/null
@@ -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 (file)
index 0000000..eb7b6c8
--- /dev/null
@@ -0,0 +1,5 @@
+colA|colB|colC
+1|2|3
+a|b|c
+a|b|c .. z
+a|b|c|d
index eb2afb9b7773cc841e748578e39f1d9a0aa6baf0..ec48b249c4c33419988badac89392b2bbf8747e7 100644 (file)
--- 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
index d60694a03f97fed58b7ed40ac589eb8af45e6603..37e351a30bb7415c6b8bddee4c28abc57f5dacdd 100644 (file)
@@ -1 +1 @@
-eb7a544fe49d1626bacecfe53ddc03fe082e3243
\ No newline at end of file
+90e63b7d845bacc9a1a1db13c1e9a406e5306faa
\ No newline at end of file
index a675427889aa9fa96a63894e2c9a3ff7cc229db5..c77a4200322d4bd6a3a49a5be491e2ae5f5a8fcc 100644 (file)
@@ -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
index 6340b5e269c8b1099327a79dba604aa0700df926..16cf7c4d2ecb41a248ebb4d946266d1dea08b0ce 100644 (file)
@@ -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