/ Check-in [3134b326]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Performance optimizations on the CSV virtual table. Disallow WITHOUT ROWID virtual tables that have an xUpdate method, for now.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | without-rowid-vtab
Files: files | file ages | folders
SHA1: 3134b3266c36c9d018e8d365ef46ef638c0792f4
User & Date: drh 2016-06-03 01:01:57
Context
2016-06-03
17:27
Disallow access to the rowid column on WITHOUT ROWID virtual tables. check-in: d31c2597 user: drh tags: without-rowid-vtab
01:01
Performance optimizations on the CSV virtual table. Disallow WITHOUT ROWID virtual tables that have an xUpdate method, for now. check-in: 3134b326 user: drh tags: without-rowid-vtab
2016-06-02
23:13
Add the CSV extension to the test fixture. Fix a memory leak in the CSV extension. Add test cases for the CSV extension, including one that uses a WITHOUT ROWID virtual table participating in the OR optimization. check-in: 95f483e8 user: drh tags: without-rowid-vtab
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ext/misc/csv.c.

23
24
25
26
27
28
29











30
31
32
33
34
35
36
...
198
199
200
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233

234
235
236
237
238
239
240
241
242
243
244
245
246
247
...
283
284
285
286
287
288
289

290
291
292
293
294
295
296
...
406
407
408
409
410
411
412



413
414
415
416
417
418
419
...
424
425
426
427
428
429
430

431

432
433
434
435
436
437
438
...
465
466
467
468
469
470
471

472
473
474

475
476
477
478
479
480
481
...
506
507
508
509
510
511
512

513

514
515
516
517
518
519
520
...
553
554
555
556
557
558
559

560
561
562
563
564
565
566
...
587
588
589
590
591
592
593


594
595
596
597

598
599
600
601
602
603
604
...
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623


624
625
626
627
628







629

630


631
632
633
634
635
636
637
638
639
...
703
704
705
706
707
708
709
710
711
712
713

714
715
716
717
718
719
720
721
722
723
724
725
726
727


728
729
730
731
732
733
734
735
736
737
738
739
740


741
742
743
744
745
746
747
...
760
761
762
763
764
765
766



































767
768
769
770
771
772
773
...
774
775
776
777
778
779
780

781
782



783



** application can define its own CREATE TABLE statement as an additional
** parameter.  For example:
**
**    CREATE VIRTUAL TABLE temp.csv2 USING csv(
**       filename = "../http.log",
**       schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
**    );











*/
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
................................................................................
  if( c==EOF ){
    p->cTerm = EOF;
    return "";
  }
  if( c=='"' ){
    int pc, ppc;
    int startLine = p->nLine;
    int cQuote = c;
    pc = ppc = 0;
    while( 1 ){
      c = csv_getc(p);

      if( c=='\n' ) p->nLine++;
      if( c==cQuote ){
        if( pc==cQuote ){
          pc = 0;
          continue;
        }
      }
      if( (c==',' && pc==cQuote)
       || (c=='\n' && pc==cQuote)
       || (c=='\n' && pc=='\r' && ppc==cQuote)
       || (c==EOF && pc==cQuote)
      ){
        do{ p->n--; }while( p->z[p->n]!=cQuote );
        p->cTerm = c;
        break;
      }
      if( pc==cQuote && c!='\r' ){
        csv_errmsg(p, "line %d: unescaped %c character", p->nLine, cQuote);
        break;
      }
      if( c==EOF ){
        csv_errmsg(p, "line %d: unterminated %c-quoted field\n",
                   startLine, cQuote);
        p->cTerm = c;
        break;

      }
      if( csv_append(p, (char)c) ) return 0;
      ppc = pc;
      pc = c;
    }
  }else{
    while( c!=EOF && c!=',' && c!='\n' ){
      if( csv_append(p, (char)c) ) return 0;
      c = csv_getc(p);
    }
    if( c=='\n' ){
      p->nLine++;
      if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
    }
................................................................................
#define CSVTEST_FIDX  0x0001      /* Pretend that constrained searchs cost less*/

/* A cursor for the CSV virtual table */
typedef struct CsvCursor {
  sqlite3_vtab_cursor base;       /* Base class.  Must be first */
  CsvReader rdr;                  /* The CsvReader object */
  char **azVal;                   /* Value of the current row */

  sqlite3_int64 iRowid;           /* The current rowid.  Negative for EOF */
} CsvCursor;

/* Transfer error message text from a reader into a CsvTable */
static void csv_xfer_error(CsvTable *pTab, CsvReader *pRdr){
  sqlite3_free(pTab->base.zErrMsg);
  pTab->base.zErrMsg = sqlite3_mprintf("%s", pRdr->zErr);
................................................................................
** Parameters:
**    filename=FILENAME          Name of file containing CSV content
**    data=TEXT                  Direct CSV content.
**    schema=SCHEMA              Alternative CSV schema.
**    header=YES|NO              First row of CSV defines the names of
**                               columns if "yes".  Default "no".
**    columns=N                  Assume the CSV file contains N columns.



**    testflags=N                Bitmask of test flags.  Optional
**
** If schema= is omitted, then the columns are named "c0", "c1", "c2",
** and so forth.  If columns=N is omitted, then the file is opened and
** the number of columns in the first row is counted to determine the
** column count.  If header=YES, then the first row is skipped.
*/
................................................................................
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  CsvTable *pNew = 0;        /* The CsvTable object to construct */
  int bHeader = -1;          /* header= flags.  -1 means not seen yet */
  int rc = SQLITE_OK;        /* Result code from this routine */
  int i, j;                  /* Loop counters */

  int tstFlags = 0;          /* Value for testflags=N parameter */

  int nCol = -99;            /* Value of the columns= parameter */
  CsvReader sRdr;            /* A CSV file reader used to store an error
                             ** message and/or to count the number of columns */
  static const char *azParam[] = {
     "filename", "data", "schema", 
  };
  char *azPValue[3];         /* Parameter values */
................................................................................
      }else if( x==0 ){
        bHeader = 0;
      }else{
        csv_errmsg(&sRdr, "unrecognized argument to 'header': %s", zValue);
        goto csvtab_connect_error;
      }
    }else

    if( (zValue = csv_parameter("testflags",9,z))!=0 ){
      tstFlags = (unsigned int)atoi(zValue);
    }else

    if( (zValue = csv_parameter("columns",7,z))!=0 ){
      if( nCol>0 ){
        csv_errmsg(&sRdr, "more than one 'columns' parameter");
        goto csvtab_connect_error;
      }
      nCol = atoi(zValue);
      if( nCol<=0 ){
................................................................................
      const char *z = csv_read_one_field(&sRdr);
      if( z==0 ) goto csvtab_connect_oom;
      pNew->nCol++;
    }while( sRdr.cTerm==',' );
  }
  pNew->zFilename = CSV_FILENAME;  CSV_FILENAME = 0;
  pNew->zData = CSV_DATA;          CSV_DATA = 0;

  pNew->tstFlags = tstFlags;

  pNew->iStart = bHeader==1 ? ftell(sRdr.in) : 0;
  csv_reader_reset(&sRdr);
  if( CSV_SCHEMA==0 ){
    char *zSep = "";
    CSV_SCHEMA = sqlite3_mprintf("CREATE TABLE x(");
    if( CSV_SCHEMA==0 ) goto csvtab_connect_oom;
    for(i=0; i<pNew->nCol; i++){
................................................................................
*/
static void csvtabCursorRowReset(CsvCursor *pCur){
  CsvTable *pTab = (CsvTable*)pCur->base.pVtab;
  int i;
  for(i=0; i<pTab->nCol; i++){
    sqlite3_free(pCur->azVal[i]);
    pCur->azVal[i] = 0;

  }
}

/*
** The xConnect and xCreate methods do the same thing, but they must be
** different so that the virtual table is not an eponymous virtual table.
*/
................................................................................

/*
** Constructor for a new CsvTable cursor object.
*/
static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
  CsvTable *pTab = (CsvTable*)p;
  CsvCursor *pCur;


  pCur = sqlite3_malloc( sizeof(*pCur) * sizeof(char*)*pTab->nCol );
  if( pCur==0 ) return SQLITE_NOMEM;
  memset(pCur, 0, sizeof(*pCur) + sizeof(char*)*pTab->nCol );
  pCur->azVal = (char**)&pCur[1];

  *ppCursor = &pCur->base;
  if( csv_reader_open(&pCur->rdr, pTab->zFilename, pTab->zData) ){
    csv_xfer_error(pTab, &pCur->rdr);
    return SQLITE_ERROR;
  }
  return SQLITE_OK;
}
................................................................................
** Set the EOF marker if we reach the end of input.
*/
static int csvtabNext(sqlite3_vtab_cursor *cur){
  CsvCursor *pCur = (CsvCursor*)cur;
  CsvTable *pTab = (CsvTable*)cur->pVtab;
  int i = 0;
  char *z;
  csvtabCursorRowReset(pCur);
  do{
    z = csv_read_one_field(&pCur->rdr);
    if( z==0 ){
      csv_xfer_error(pTab, &pCur->rdr);
      break;
    }
    z = sqlite3_mprintf("%s", z);


    if( z==0 ){
      csv_errmsg(&pCur->rdr, "out of memory");
      csv_xfer_error(pTab, &pCur->rdr);
      break;
    }







    if( i<pTab->nCol ){

      pCur->azVal[i++] = z;


    }
  }while( z!=0 && pCur->rdr.cTerm==',' );
  if( z==0 || pCur->rdr.cTerm==EOF ){
    pCur->iRowid = -1;
  }else{
    pCur->iRowid++;
  }
  return SQLITE_OK;
}
................................................................................
** constraints lowers the estimated cost, which is fiction, but is useful
** for testing certain kinds of virtual table behavior.
*/
static int csvtabBestIndex(
  sqlite3_vtab *tab,
  sqlite3_index_info *pIdxInfo
){
  CsvTable *pTab = (CsvTable*)tab;
  int i;
  int nConst = 0;
  pIdxInfo->estimatedCost = 1000000;

  if( (pTab->tstFlags & CSVTEST_FIDX)==0 ){
    return SQLITE_OK;
  }
  /* The usual (and sensible) case is to take the "return SQLITE_OK" above.
  ** The code below only runs when testflags=1.  The code below
  ** generates an artifical and unrealistic plan which is useful
  ** for testing virtual table logic but is not helpfulto real applications.
  **
  ** Any ==, LIKE, or GLOB constraint is marked as usable by the virtual
  ** table (even though it is not) and the cost of running the virtual table
  ** is reduced from 1 million to just 10.  The constraints are *not* marked
  ** as omittable, however, so the query planner should still generate a
  ** plan that gives a correct answer, even if they plan is not optimal.
  */


  for(i=0; i<pIdxInfo->nConstraint; i++){
    unsigned char op;
    if( pIdxInfo->aConstraint[i].usable==0 ) continue;
    op = pIdxInfo->aConstraint[i].op;
    if( op==SQLITE_INDEX_CONSTRAINT_EQ 
     || op==SQLITE_INDEX_CONSTRAINT_LIKE
     || op==SQLITE_INDEX_CONSTRAINT_GLOB
    ){
      pIdxInfo->estimatedCost = 10;
      pIdxInfo->aConstraintUsage[nConst].argvIndex = nConst+1;
      nConst++;
    }
  }


  return SQLITE_OK;
}


static sqlite3_module CsvModule = {
  0,                       /* iVersion */
  csvtabCreate,            /* xCreate */
................................................................................
  0,                       /* xBegin */
  0,                       /* xSync */
  0,                       /* xCommit */
  0,                       /* xRollback */
  0,                       /* xFindMethod */
  0,                       /* xRename */
};




































#ifdef _WIN32
__declspec(dllexport)
#endif
/* 
** This routine is called when the extension is loaded.  The new
** CSV virtual table module is registered with the calling database
................................................................................
** connection.
*/
int sqlite3_csv_init(
  sqlite3 *db, 
  char **pzErrMsg, 
  const sqlite3_api_routines *pApi
){

  SQLITE_EXTENSION_INIT2(pApi);
  return sqlite3_create_module(db, "csv", &CsvModule, 0);



}










>
>
>
>
>
>
>
>
>
>
>







 







<



>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>






|







 







>







 







>
>
>







 







>

>







 







>



>







 







>

>







 







>







 







>
>
|

|

>







 







<






|
>
>
|
|
|
|
|
>
>
>
>
>
>
>
|
>
|
>
>
|
<







 







<
<
<

>
|
<
<
|
|
|
|
|
|
|
|
|
|
|
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>

|
>
>
>
|
>
>
>
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
...
209
210
211
212
213
214
215

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
...
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
...
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
...
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
...
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
...
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
...
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
...
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
...
635
636
637
638
639
640
641

642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668

669
670
671
672
673
674
675
...
739
740
741
742
743
744
745



746
747
748


749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
...
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
...
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
** application can define its own CREATE TABLE statement as an additional
** parameter.  For example:
**
**    CREATE VIRTUAL TABLE temp.csv2 USING csv(
**       filename = "../http.log",
**       schema = "CREATE TABLE x(date,ipaddr,url,referrer,userAgent)"
**    );
**
** Instead of specifying a file, the text of the CSV can be loaded using
** the data= parameter.
**
** If the columns=N parameter is supplied, then the CSV file is assumed to have
** N columns.  If the columns parameter is omitted, the CSV file is opened
** as soon as the virtual table is constructed and the first row of the CSV
** is read in order to count the tables.
**
** Some extra debugging features (used for testing virtual tables) are available
** if this module is compiled with -DSQLITE_TEST.
*/
#include <sqlite3ext.h>
SQLITE_EXTENSION_INIT1
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <stdarg.h>
................................................................................
  if( c==EOF ){
    p->cTerm = EOF;
    return "";
  }
  if( c=='"' ){
    int pc, ppc;
    int startLine = p->nLine;

    pc = ppc = 0;
    while( 1 ){
      c = csv_getc(p);
      if( c<='"' || pc=='"' ){
        if( c=='\n' ) p->nLine++;
        if( c=='"' ){
          if( pc=='"' ){
            pc = 0;
            continue;
          }
        }
        if( (c==',' && pc=='"')
         || (c=='\n' && pc=='"')
         || (c=='\n' && pc=='\r' && ppc=='"')
         || (c==EOF && pc=='"')
        ){
          do{ p->n--; }while( p->z[p->n]!='"' );
          p->cTerm = c;
          break;
        }
        if( pc=='"' && c!='\r' ){
          csv_errmsg(p, "line %d: unescaped %c character", p->nLine, '"');
          break;
        }
        if( c==EOF ){
          csv_errmsg(p, "line %d: unterminated %c-quoted field\n",
                     startLine, '"');
          p->cTerm = c;
          break;
        }
      }
      if( csv_append(p, (char)c) ) return 0;
      ppc = pc;
      pc = c;
    }
  }else{
    while( c>',' || (c!=EOF && c!=',' && c!='\n') ){
      if( csv_append(p, (char)c) ) return 0;
      c = csv_getc(p);
    }
    if( c=='\n' ){
      p->nLine++;
      if( p->n>0 && p->z[p->n-1]=='\r' ) p->n--;
    }
................................................................................
#define CSVTEST_FIDX  0x0001      /* Pretend that constrained searchs cost less*/

/* A cursor for the CSV virtual table */
typedef struct CsvCursor {
  sqlite3_vtab_cursor base;       /* Base class.  Must be first */
  CsvReader rdr;                  /* The CsvReader object */
  char **azVal;                   /* Value of the current row */
  int *aLen;                      /* Length of each entry */
  sqlite3_int64 iRowid;           /* The current rowid.  Negative for EOF */
} CsvCursor;

/* Transfer error message text from a reader into a CsvTable */
static void csv_xfer_error(CsvTable *pTab, CsvReader *pRdr){
  sqlite3_free(pTab->base.zErrMsg);
  pTab->base.zErrMsg = sqlite3_mprintf("%s", pRdr->zErr);
................................................................................
** Parameters:
**    filename=FILENAME          Name of file containing CSV content
**    data=TEXT                  Direct CSV content.
**    schema=SCHEMA              Alternative CSV schema.
**    header=YES|NO              First row of CSV defines the names of
**                               columns if "yes".  Default "no".
**    columns=N                  Assume the CSV file contains N columns.
**
** Only available if compiled with SQLITE_TEST:
**    
**    testflags=N                Bitmask of test flags.  Optional
**
** If schema= is omitted, then the columns are named "c0", "c1", "c2",
** and so forth.  If columns=N is omitted, then the file is opened and
** the number of columns in the first row is counted to determine the
** column count.  If header=YES, then the first row is skipped.
*/
................................................................................
  sqlite3_vtab **ppVtab,
  char **pzErr
){
  CsvTable *pNew = 0;        /* The CsvTable object to construct */
  int bHeader = -1;          /* header= flags.  -1 means not seen yet */
  int rc = SQLITE_OK;        /* Result code from this routine */
  int i, j;                  /* Loop counters */
#ifdef SQLITE_TEST
  int tstFlags = 0;          /* Value for testflags=N parameter */
#endif
  int nCol = -99;            /* Value of the columns= parameter */
  CsvReader sRdr;            /* A CSV file reader used to store an error
                             ** message and/or to count the number of columns */
  static const char *azParam[] = {
     "filename", "data", "schema", 
  };
  char *azPValue[3];         /* Parameter values */
................................................................................
      }else if( x==0 ){
        bHeader = 0;
      }else{
        csv_errmsg(&sRdr, "unrecognized argument to 'header': %s", zValue);
        goto csvtab_connect_error;
      }
    }else
#ifdef SQLITE_TEST
    if( (zValue = csv_parameter("testflags",9,z))!=0 ){
      tstFlags = (unsigned int)atoi(zValue);
    }else
#endif
    if( (zValue = csv_parameter("columns",7,z))!=0 ){
      if( nCol>0 ){
        csv_errmsg(&sRdr, "more than one 'columns' parameter");
        goto csvtab_connect_error;
      }
      nCol = atoi(zValue);
      if( nCol<=0 ){
................................................................................
      const char *z = csv_read_one_field(&sRdr);
      if( z==0 ) goto csvtab_connect_oom;
      pNew->nCol++;
    }while( sRdr.cTerm==',' );
  }
  pNew->zFilename = CSV_FILENAME;  CSV_FILENAME = 0;
  pNew->zData = CSV_DATA;          CSV_DATA = 0;
#ifdef SQLITE_TEST
  pNew->tstFlags = tstFlags;
#endif
  pNew->iStart = bHeader==1 ? ftell(sRdr.in) : 0;
  csv_reader_reset(&sRdr);
  if( CSV_SCHEMA==0 ){
    char *zSep = "";
    CSV_SCHEMA = sqlite3_mprintf("CREATE TABLE x(");
    if( CSV_SCHEMA==0 ) goto csvtab_connect_oom;
    for(i=0; i<pNew->nCol; i++){
................................................................................
*/
static void csvtabCursorRowReset(CsvCursor *pCur){
  CsvTable *pTab = (CsvTable*)pCur->base.pVtab;
  int i;
  for(i=0; i<pTab->nCol; i++){
    sqlite3_free(pCur->azVal[i]);
    pCur->azVal[i] = 0;
    pCur->aLen[i] = 0;
  }
}

/*
** The xConnect and xCreate methods do the same thing, but they must be
** different so that the virtual table is not an eponymous virtual table.
*/
................................................................................

/*
** Constructor for a new CsvTable cursor object.
*/
static int csvtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
  CsvTable *pTab = (CsvTable*)p;
  CsvCursor *pCur;
  size_t nByte;
  nByte = sizeof(*pCur) + (sizeof(char*)+sizeof(int))*pTab->nCol;
  pCur = sqlite3_malloc( nByte );
  if( pCur==0 ) return SQLITE_NOMEM;
  memset(pCur, 0, nByte);
  pCur->azVal = (char**)&pCur[1];
  pCur->aLen = (int*)&pCur->azVal[pTab->nCol];
  *ppCursor = &pCur->base;
  if( csv_reader_open(&pCur->rdr, pTab->zFilename, pTab->zData) ){
    csv_xfer_error(pTab, &pCur->rdr);
    return SQLITE_ERROR;
  }
  return SQLITE_OK;
}
................................................................................
** Set the EOF marker if we reach the end of input.
*/
static int csvtabNext(sqlite3_vtab_cursor *cur){
  CsvCursor *pCur = (CsvCursor*)cur;
  CsvTable *pTab = (CsvTable*)cur->pVtab;
  int i = 0;
  char *z;

  do{
    z = csv_read_one_field(&pCur->rdr);
    if( z==0 ){
      csv_xfer_error(pTab, &pCur->rdr);
      break;
    }
    if( i<pTab->nCol ){
      if( pCur->aLen[i] < pCur->rdr.n+1 ){
        char *zNew = sqlite3_realloc(pCur->azVal[i], pCur->rdr.n+1);
        if( zNew==0 ){
          csv_errmsg(&pCur->rdr, "out of memory");
          csv_xfer_error(pTab, &pCur->rdr);
          break;
        }
        pCur->azVal[i] = zNew;
        pCur->aLen[i] = pCur->rdr.n+1;
      }
      memcpy(pCur->azVal[i], z, pCur->rdr.n+1);
      i++;
    }
  }while( pCur->rdr.cTerm==',' );
  while( i<pTab->nCol ){
    sqlite3_free(pCur->azVal[i]);
    pCur->azVal[i] = 0;
    pCur->aLen[i] = 0;
    i++;
  }

  if( z==0 || pCur->rdr.cTerm==EOF ){
    pCur->iRowid = -1;
  }else{
    pCur->iRowid++;
  }
  return SQLITE_OK;
}
................................................................................
** constraints lowers the estimated cost, which is fiction, but is useful
** for testing certain kinds of virtual table behavior.
*/
static int csvtabBestIndex(
  sqlite3_vtab *tab,
  sqlite3_index_info *pIdxInfo
){



  pIdxInfo->estimatedCost = 1000000;
#ifdef SQLITE_TEST
  if( (((CsvTable*)tab)->tstFlags & CSVTEST_FIDX)!=0 ){


    /* The usual (and sensible) case is to always do a full table scan.
    ** The code in this branch only runs when testflags=1.  This code
    ** generates an artifical and unrealistic plan which is useful
    ** for testing virtual table logic but is not helpful to real applications.
    **
    ** Any ==, LIKE, or GLOB constraint is marked as usable by the virtual
    ** table (even though it is not) and the cost of running the virtual table
    ** is reduced from 1 million to just 10.  The constraints are *not* marked
    ** as omittable, however, so the query planner should still generate a
    ** plan that gives a correct answer, even if they plan is not optimal.
    */
    int i;
    int nConst = 0;
    for(i=0; i<pIdxInfo->nConstraint; i++){
      unsigned char op;
      if( pIdxInfo->aConstraint[i].usable==0 ) continue;
      op = pIdxInfo->aConstraint[i].op;
      if( op==SQLITE_INDEX_CONSTRAINT_EQ 
       || op==SQLITE_INDEX_CONSTRAINT_LIKE
       || op==SQLITE_INDEX_CONSTRAINT_GLOB
      ){
        pIdxInfo->estimatedCost = 10;
        pIdxInfo->aConstraintUsage[nConst].argvIndex = nConst+1;
        nConst++;
      }
    }
  }
#endif
  return SQLITE_OK;
}


static sqlite3_module CsvModule = {
  0,                       /* iVersion */
  csvtabCreate,            /* xCreate */
................................................................................
  0,                       /* xBegin */
  0,                       /* xSync */
  0,                       /* xCommit */
  0,                       /* xRollback */
  0,                       /* xFindMethod */
  0,                       /* xRename */
};

#ifdef SQLITE_TEST
/*
** For virtual table testing, make a version of the CSV virtual table
** available that has an xUpdate function.  But the xUpdate always returns
** SQLITE_READONLY since the CSV file is not really writable.
*/
static int csvtabUpdate(sqlite3_vtab *p,int n,sqlite3_value**v,sqlite3_int64*x){
  return SQLITE_READONLY;
}
static sqlite3_module CsvModuleFauxWrite = {
  0,                       /* iVersion */
  csvtabCreate,            /* xCreate */
  csvtabConnect,           /* xConnect */
  csvtabBestIndex,         /* xBestIndex */
  csvtabDisconnect,        /* xDisconnect */
  csvtabDisconnect,        /* xDestroy */
  csvtabOpen,              /* xOpen - open a cursor */
  csvtabClose,             /* xClose - close a cursor */
  csvtabFilter,            /* xFilter - configure scan constraints */
  csvtabNext,              /* xNext - advance a cursor */
  csvtabEof,               /* xEof - check for end of scan */
  csvtabColumn,            /* xColumn - read data */
  csvtabRowid,             /* xRowid - read data */
  csvtabUpdate,            /* xUpdate */
  0,                       /* xBegin */
  0,                       /* xSync */
  0,                       /* xCommit */
  0,                       /* xRollback */
  0,                       /* xFindMethod */
  0,                       /* xRename */
};
#endif /* SQLITE_TEST */



#ifdef _WIN32
__declspec(dllexport)
#endif
/* 
** This routine is called when the extension is loaded.  The new
** CSV virtual table module is registered with the calling database
................................................................................
** connection.
*/
int sqlite3_csv_init(
  sqlite3 *db, 
  char **pzErrMsg, 
  const sqlite3_api_routines *pApi
){
  int rc;
  SQLITE_EXTENSION_INIT2(pApi);
  rc = sqlite3_create_module(db, "csv", &CsvModule, 0);
#ifdef SQLITE_TEST
  if( rc==SQLITE_OK ){
    rc = sqlite3_create_module(db, "csv_wr", &CsvModuleFauxWrite, 0);
  }
#endif
  return rc;
}

Changes to src/vtab.c.

758
759
760
761
762
763
764



765
766
767
768
769
770
771
        Index *pIdx;
        pTab->aCol = pNew->aCol;
        pTab->nCol = pNew->nCol;
        pTab->tabFlags |= pNew->tabFlags & TF_WithoutRowid;
        pNew->nCol = 0;
        pNew->aCol = 0;
        assert( pTab->pIndex==0 );



        pIdx = pNew->pIndex;
        if( pIdx ){
          assert( pIdx->pNext==0 );
          pTab->pIndex = pIdx;
          pNew->pIndex = 0;
          pIdx->pTable = pTab;
        }







>
>
>







758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
        Index *pIdx;
        pTab->aCol = pNew->aCol;
        pTab->nCol = pNew->nCol;
        pTab->tabFlags |= pNew->tabFlags & TF_WithoutRowid;
        pNew->nCol = 0;
        pNew->aCol = 0;
        assert( pTab->pIndex==0 );
        if( !HasRowid(pNew) && pCtx->pVTable->pMod->pModule->xUpdate!=0 ){
          rc = SQLITE_ERROR;
        }
        pIdx = pNew->pIndex;
        if( pIdx ){
          assert( pIdx->pNext==0 );
          pTab->pIndex = pIdx;
          pNew->pIndex = 0;
          pIdx->pTable = pTab;
        }

Changes to test/csv01.test.

84
85
86
87
88
89
90
















91
  );
  SELECT a FROM t3 WHERE b=6 OR c=7 OR d=12 ORDER BY +a;
} {5 9}
do_execsql_test 3.1 {
  SELECT a FROM t3 WHERE +b=6 OR c=7 OR d=12 ORDER BY +a;
} {5 9}

















finish_test







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
  );
  SELECT a FROM t3 WHERE b=6 OR c=7 OR d=12 ORDER BY +a;
} {5 9}
do_execsql_test 3.1 {
  SELECT a FROM t3 WHERE +b=6 OR c=7 OR d=12 ORDER BY +a;
} {5 9}

do_catchsql_test 4.0 {
  DROP TABLE t3;
  CREATE VIRTUAL TABLE temp.t4 USING csv_wr(
    data=
'1,2,3,4
5,6,7,8
9,10,11,12
13,14,15,16
',
    columns=4,
    schema=
      'CREATE TABLE t3(a PRIMARY KEY,b TEXT,c TEXT,d TEXT) WITHOUT ROWID',
    testflags=1
  );
} {1 {vtable constructor failed: t4}}

finish_test