/ Check-in [5dd98317]
Login

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

Overview
Comment:Changes to allow the code in sqlite3expert.c to be tested directly (via the API in sqlite3expert.h) instead of by invoking the sqlite3_expert application. Fix memory leaks and other problems.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | schemalint
Files: files | file ages | folders
SHA3-256: 5dd9831721b70a89a26728adcd49c7f6103ef8266891a79c2db34d913702709e
User & Date: dan 2017-04-10 16:13:20
Context
2017-04-10
20:00
Add ext/expert/README.md. check-in: 9318f1b9 user: dan tags: schemalint
16:13
Changes to allow the code in sqlite3expert.c to be tested directly (via the API in sqlite3expert.h) instead of by invoking the sqlite3_expert application. Fix memory leaks and other problems. check-in: 5dd98317 user: dan tags: schemalint
2017-04-09
08:38
Fix the -file option on the sqlite3_expert program. check-in: 0857c48e user: dan tags: schemalint
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Name change from test/expert1.test to ext/expert/expert1.test.

    22     22   set testprefix expert1
    23     23   
    24     24   if {$tcl_platform(platform)=="windows"} {
    25     25     set CMD "sqlite3_expert.exe"
    26     26   } else {
    27     27     set CMD ".././sqlite3_expert"
    28     28   }
    29         -if {![file executable $CMD]} {
    30         -  finish_test
    31         -  return
    32         -}
    33         -
    34     29   
    35     30   proc squish {txt} {
    36     31     regsub -all {[[:space:]]+} $txt { }
    37     32   }
    38     33   
    39         -proc do_rec_test {tn sql res} {
    40         -  set res [squish [string trim $res]]
    41         -  set tst [subst -nocommands { 
    42         -    squish [string trim [exec $::CMD -verbose 0 -sql {$sql;} test.db]]
    43         -  }]
    44         -  uplevel [list do_test $tn $tst $res]
    45         -}
    46         -
    47     34   proc do_setup_rec_test {tn setup sql res} {
    48     35     reset_db
    49     36     db eval $setup
    50     37     uplevel [list do_rec_test $tn $sql $res]
    51     38   }
    52     39   
           40  +foreach {tn setup} {
           41  +  1 {
           42  +    if {![file executable $CMD]} { continue }
    53     43   
    54         -do_setup_rec_test 1.1 { CREATE TABLE t1(a, b, c) } {
           44  +    proc do_rec_test {tn sql res} {
           45  +      set res [squish [string trim $res]]
           46  +      set tst [subst -nocommands { 
           47  +        squish [string trim [exec $::CMD -verbose 0 -sql {$sql;} test.db]]
           48  +      }]
           49  +      uplevel [list do_test $tn $tst $res]
           50  +    }
           51  +  }
           52  +  2 {
           53  +    if {[info commands sqlite3_expert_new]==""} { continue }
           54  +
           55  +    proc do_rec_test {tn sql res} {
           56  +      set expert [sqlite3_expert_new db]
           57  +      $expert sql $sql
           58  +      $expert analyze
           59  +
           60  +      set result [list]
           61  +      for {set i 0} {$i < [$expert count]} {incr i} {
           62  +        lappend result [string trim [$expert report $i indexes]]
           63  +        lappend result [string trim [$expert report $i plan]]
           64  +      }
           65  +
           66  +      $expert destroy
           67  +
           68  +      set tst [subst -nocommands {set {} [squish [join {$result}]]}]
           69  +      uplevel [list do_test $tn $tst [string trim [squish $res]]]
           70  +    }
           71  +  }
           72  +} {
           73  +
           74  +  eval $setup
           75  +
           76  +
           77  +do_setup_rec_test $tn.1.1 { CREATE TABLE t1(a, b, c) } {
    55     78     SELECT * FROM t1
    56     79   } {
    57     80     (no new indexes)
    58     81     0|0|0|SCAN TABLE t1
    59     82   }
    60     83   
    61         -do_setup_rec_test 1.2 {
           84  +do_setup_rec_test $tn.1.2 {
    62     85     CREATE TABLE t1(a, b, c);
    63     86   } {
    64     87     SELECT * FROM t1 WHERE b>?;
    65     88   } {
    66     89     CREATE INDEX t1_idx_00000062 ON t1(b);
    67     90     0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_00000062 (b>?)
    68     91   }
    69     92   
    70         -do_setup_rec_test 1.3 {
           93  +do_setup_rec_test $tn.1.3 {
    71     94     CREATE TABLE t1(a, b, c);
    72     95   } {
    73     96     SELECT * FROM t1 WHERE b COLLATE nocase BETWEEN ? AND ?
    74     97   } {
    75     98     CREATE INDEX t1_idx_3e094c27 ON t1(b COLLATE NOCASE);
    76     99     0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_3e094c27 (b>? AND b<?)
    77    100   }
    78    101   
    79         -do_setup_rec_test 1.4 {
          102  +do_setup_rec_test $tn.1.4 {
    80    103     CREATE TABLE t1(a, b, c);
    81    104   } {
    82    105     SELECT a FROM t1 ORDER BY b;
    83    106   } {
    84    107     CREATE INDEX t1_idx_00000062 ON t1(b);
    85    108     0|0|0|SCAN TABLE t1 USING INDEX t1_idx_00000062
    86    109   }
    87    110   
    88         -do_setup_rec_test 1.5 {
          111  +do_setup_rec_test $tn.1.5 {
    89    112     CREATE TABLE t1(a, b, c);
    90    113   } {
    91    114     SELECT a FROM t1 WHERE a=? ORDER BY b;
    92    115   } {
    93    116     CREATE INDEX t1_idx_000123a7 ON t1(a, b);
    94    117     0|0|0|SEARCH TABLE t1 USING COVERING INDEX t1_idx_000123a7 (a=?)
    95    118   }
    96    119   
    97         -do_setup_rec_test 1.6 {
          120  +do_setup_rec_test $tn.1.6 {
    98    121     CREATE TABLE t1(a, b, c);
    99    122   } {
   100    123     SELECT min(a) FROM t1
   101    124   } {
   102    125     CREATE INDEX t1_idx_00000061 ON t1(a);
   103    126     0|0|0|SEARCH TABLE t1 USING COVERING INDEX t1_idx_00000061
   104    127   }
   105    128   
   106         -do_setup_rec_test 1.7 {
          129  +do_setup_rec_test $tn.1.7 {
   107    130     CREATE TABLE t1(a, b, c);
   108    131   } {
   109    132     SELECT * FROM t1 ORDER BY a, b, c;
   110    133   } {
   111    134     CREATE INDEX t1_idx_033e95fe ON t1(a, b, c);
   112    135     0|0|0|SCAN TABLE t1 USING COVERING INDEX t1_idx_033e95fe
   113    136   }
   114    137   
   115         -do_setup_rec_test 1.8 {
          138  +do_setup_rec_test $tn.1.8 {
   116    139     CREATE TABLE t1(a, b, c);
   117    140   } {
   118    141     SELECT * FROM t1 ORDER BY a ASC, b COLLATE nocase DESC, c ASC;
   119    142   } {
   120    143     CREATE INDEX t1_idx_5be6e222 ON t1(a, b COLLATE NOCASE DESC, c);
   121    144     0|0|0|SCAN TABLE t1 USING COVERING INDEX t1_idx_5be6e222
   122    145   }
   123    146   
   124         -do_setup_rec_test 1.9 {
          147  +do_setup_rec_test $tn.1.9 {
   125    148     CREATE TABLE t1(a COLLATE NOCase, b, c);
   126    149   } {
   127    150     SELECT * FROM t1 WHERE a=?
   128    151   } {
   129    152     CREATE INDEX t1_idx_00000061 ON t1(a);
   130    153     0|0|0|SEARCH TABLE t1 USING INDEX t1_idx_00000061 (a=?)
   131    154   }
   132    155   
   133    156   
   134    157   # Tables with names that require quotes.
   135    158   #
   136         -do_setup_rec_test 8.1 {
          159  +do_setup_rec_test $tn.8.1 {
   137    160     CREATE TABLE "t t"(a, b, c);
   138    161   } {
   139    162     SELECT * FROM "t t" WHERE a=?
   140    163   } {
   141    164     CREATE INDEX 't t_idx_00000061' ON 't t'(a);
   142    165     0|0|0|SEARCH TABLE t t USING INDEX t t_idx_00000061 (a=?) 
   143    166   }
   144    167   
   145         -do_setup_rec_test 8.2 {
          168  +do_setup_rec_test $tn.8.2 {
   146    169     CREATE TABLE "t t"(a, b, c);
   147    170   } {
   148    171     SELECT * FROM "t t" WHERE b BETWEEN ? AND ?
   149    172   } {
   150    173     CREATE INDEX 't t_idx_00000062' ON 't t'(b);
   151    174     0|0|0|SEARCH TABLE t t USING INDEX t t_idx_00000062 (b>? AND b<?)
   152    175   }
   153    176   
   154    177   # Columns with names that require quotes.
   155    178   #
   156         -do_setup_rec_test 9.1 {
          179  +do_setup_rec_test $tn.9.1 {
   157    180     CREATE TABLE t3(a, "b b", c);
   158    181   } {
   159    182     SELECT * FROM t3 WHERE "b b" = ?
   160    183   } {
   161    184     CREATE INDEX t3_idx_00050c52 ON t3('b b');
   162    185     0|0|0|SEARCH TABLE t3 USING INDEX t3_idx_00050c52 (b b=?)
   163    186   }
   164    187   
   165         -do_setup_rec_test 9.2 {
          188  +do_setup_rec_test $tn.9.2 {
   166    189     CREATE TABLE t3(a, "b b", c);
   167    190   } {
   168    191     SELECT * FROM t3 ORDER BY "b b"
   169    192   } {
   170    193     CREATE INDEX t3_idx_00050c52 ON t3('b b');
   171    194     0|0|0|SCAN TABLE t3 USING INDEX t3_idx_00050c52
   172    195   }
   173    196   
   174    197   # Transitive constraints
   175    198   #
   176         -do_setup_rec_test 10.1 {
          199  +do_setup_rec_test $tn.10.1 {
   177    200     CREATE TABLE t5(a, b);
   178    201     CREATE TABLE t6(c, d);
   179    202   } {
   180    203     SELECT * FROM t5, t6 WHERE a=? AND b=c AND c=?
   181    204   } {
   182    205     CREATE INDEX t5_idx_000123a7 ON t5(a, b);
   183    206     CREATE INDEX t6_idx_00000063 ON t6(c);
   184    207     0|0|1|SEARCH TABLE t6 USING INDEX t6_idx_00000063 (c=?) 
   185    208     0|1|0|SEARCH TABLE t5 USING COVERING INDEX t5_idx_000123a7 (a=? AND b=?)
   186    209   }
   187    210   
   188    211   # OR terms.
   189    212   #
   190         -do_setup_rec_test 11.1 {
          213  +do_setup_rec_test $tn.11.1 {
   191    214     CREATE TABLE t7(a, b);
   192    215   } {
   193    216     SELECT * FROM t7 WHERE a=? OR b=?
   194    217   } {
   195    218     CREATE INDEX t7_idx_00000062 ON t7(b);
   196    219     CREATE INDEX t7_idx_00000061 ON t7(a);
   197    220     0|0|0|SEARCH TABLE t7 USING INDEX t7_idx_00000061 (a=?) 
   198    221     0|0|0|SEARCH TABLE t7 USING INDEX t7_idx_00000062 (b=?)
          222  +}
          223  +
   199    224   }
   200    225   
   201    226   finish_test
   202    227   

Changes to ext/expert/sqlite3expert.c.

     7      7   **    May you do good and not evil.
     8      8   **    May you find forgiveness for yourself and forgive others.
     9      9   **    May you share freely, never taking more than you give.
    10     10   **
    11     11   *************************************************************************
    12     12   */
    13     13   
           14  +#if !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHEREINFO_HOOK)
           15  +
    14     16   #include "sqlite3expert.h"
    15     17   #include <assert.h>
    16     18   #include <string.h>
    17     19   #include <stdio.h>
    18     20   
    19     21   typedef sqlite3_int64 i64;
    20     22   typedef sqlite3_uint64 u64;
    21     23   
    22     24   typedef struct IdxConstraint IdxConstraint;
    23         -typedef struct IdxContext IdxContext;
    24     25   typedef struct IdxScan IdxScan;
    25     26   typedef struct IdxStatement IdxStatement;
    26     27   typedef struct IdxWhere IdxWhere;
    27     28   
    28     29   typedef struct IdxColumn IdxColumn;
    29     30   typedef struct IdxTable IdxTable;
    30     31   
................................................................................
    85     86     int iPk;
    86     87   };
    87     88   struct IdxTable {
    88     89     int nCol;
    89     90     IdxColumn *aCol;
    90     91   };
    91     92   
    92         -/*
    93         -** Context object passed to idxWhereInfo() and other functions.
    94         -*/
    95         -struct IdxContext {
    96         -  char **pzErrmsg;
    97         -  IdxWhere *pCurrent;             /* Current where clause */
    98         -  int rc;                         /* Error code (if error has occurred) */
    99         -  IdxScan *pScan;                 /* List of scan objects */
   100         -  sqlite3 *dbm;                   /* In-memory db for this analysis */
   101         -  sqlite3 *db;                    /* User database under analysis */
   102         -  sqlite3_stmt *pInsertMask;      /* To write to aux.depmask */
   103         -};
   104         -
   105     93   struct IdxStatement {
   106     94     int iId;                        /* Statement number */
   107     95     char *zSql;                     /* SQL statement */
   108     96     char *zIdx;                     /* Indexes */
   109     97     char *zEQP;                     /* Plan */
   110     98     IdxStatement *pNext;
   111     99   };
................................................................................
   297    285     return pNew;
   298    286   }
   299    287   
   300    288   /*
   301    289   ** sqlite3_whereinfo_hook() callback.
   302    290   */
   303    291   static void idxWhereInfo(
   304         -  void *pCtx,                     /* Pointer to IdxContext structure */
          292  +  void *pCtx,                     /* Pointer to sqlite3expert structure */
   305    293     int eOp, 
   306    294     const char *zVal, 
   307    295     int iVal, 
   308    296     u64 mask
   309    297   ){
   310    298     sqlite3expert *p = (sqlite3expert*)pCtx;
   311    299   
................................................................................
   789    777       }
   790    778   
   791    779       idxHash64Clear(&hMask);
   792    780     }
   793    781   
   794    782     return rc;
   795    783   }
          784  +
          785  +static void idxConstraintFree(IdxConstraint *pConstraint){
          786  +  IdxConstraint *pNext;
          787  +  IdxConstraint *p;
          788  +
          789  +  for(p=pConstraint; p; p=pNext){
          790  +    pNext = p->pNext;
          791  +    sqlite3_free(p);
          792  +  }
          793  +}
   796    794   
   797    795   /*
   798    796   ** Free all elements of the linked list starting from pScan up until pLast
   799    797   ** (pLast is not freed).
   800    798   */
   801    799   static void idxScanFree(IdxScan *pScan, IdxScan *pLast){
   802         -  /* TODO! */
          800  +  IdxScan *p;
          801  +  IdxScan *pNext;
          802  +  for(p=pScan; p!=pLast; p=pNext){
          803  +    pNext = p->pNextScan;
          804  +    idxConstraintFree(p->pOrder);
          805  +    idxConstraintFree(p->where.pEq);
          806  +    idxConstraintFree(p->where.pRange);
          807  +    sqlite3_free(p->pTable);
          808  +    sqlite3_free(p);
          809  +  }
   803    810   }
   804    811   
   805    812   /*
   806    813   ** Free all elements of the linked list starting from pStatement up 
   807    814   ** until pLast (pLast is not freed).
   808    815   */
   809    816   static void idxStatementFree(IdxStatement *pStatement, IdxStatement *pLast){
   810         -  /* TODO! */
          817  +  IdxStatement *p;
          818  +  IdxStatement *pNext;
          819  +  for(p=pStatement; p!=pLast; p=pNext){
          820  +    pNext = p->pNext;
          821  +    sqlite3_free(p->zEQP);
          822  +    sqlite3_free(p->zIdx);
          823  +    sqlite3_free(p);
          824  +  }
   811    825   }
   812    826   
   813    827   
   814    828   int idxFindIndexes(
   815    829     sqlite3expert *p,
   816    830     char **pzErr                         /* OUT: Error message (sqlite3_malloc) */
   817    831   ){
................................................................................
   871    885         pStmt->zIdx = idxAppendText(&rc, 0, "(no new indexes)\n");
   872    886       }
   873    887   
   874    888       idxFinalize(&rc, pExplain);
   875    889     }
   876    890   
   877    891    find_indexes_out:
          892  +  idxHashClear(&hIdx);
   878    893     return rc;
   879    894   }
   880    895   
   881    896   /*
   882    897   ** Allocate a new sqlite3expert object.
   883    898   */
   884    899   sqlite3expert *sqlite3_expert_new(sqlite3 *db, char **pzErrmsg){
................................................................................
   970    985     IdxScan *pIter;
   971    986   
   972    987     /* Load IdxTable objects */
   973    988     for(pIter=p->pScan; pIter && rc==SQLITE_OK; pIter=pIter->pNextScan){
   974    989       rc = idxGetTableInfo(p->dbm, pIter, pzErr);
   975    990     }
   976    991   
   977         -
   978    992     /* Create candidate indexes within the in-memory database file */
   979    993     if( rc==SQLITE_OK ){
   980    994       rc = idxCreateCandidates(p, pzErr);
   981    995     }
   982    996   
   983    997     /* Figure out which of the candidate indexes are preferred by the query
   984    998     ** planner and report the results to the user.  */
................................................................................
   988   1002   
   989   1003     if( rc==SQLITE_OK ){
   990   1004       p->bRun = 1;
   991   1005     }
   992   1006     return rc;
   993   1007   }
   994   1008   
         1009  +/*
         1010  +** Return the total number of statements that have been added to this
         1011  +** sqlite3expert using sqlite3_expert_sql().
         1012  +*/
   995   1013   int sqlite3_expert_count(sqlite3expert *p){
   996   1014     int nRet = 0;
   997   1015     if( p->pStatement ) nRet = p->pStatement->iId+1;
   998   1016     return nRet;
   999   1017   }
  1000   1018   
         1019  +/*
         1020  +** Return a component of the report.
         1021  +*/
  1001   1022   const char *sqlite3_expert_report(sqlite3expert *p, int iStmt, int eReport){
  1002   1023     const char *zRet = 0;
  1003   1024     IdxStatement *pStmt;
  1004   1025   
  1005   1026     if( p->bRun==0 ) return 0;
  1006   1027     for(pStmt=p->pStatement; pStmt && pStmt->iId!=iStmt; pStmt=pStmt->pNext);
  1007   1028     if( pStmt ){
................................................................................
  1023   1044   /*
  1024   1045   ** Free an sqlite3expert object.
  1025   1046   */
  1026   1047   void sqlite3_expert_destroy(sqlite3expert *p){
  1027   1048     sqlite3_close(p->dbm);
  1028   1049     idxScanFree(p->pScan, 0);
  1029   1050     idxStatementFree(p->pStatement, 0);
         1051  +  idxHashClear(&p->hIdx);
  1030   1052     sqlite3_free(p);
  1031   1053   }
         1054  +
         1055  +#endif /* !defined(SQLITE_TEST) || defined(SQLITE_ENABLE_WHEREINFO_HOOK) */
  1032   1056   

Added ext/expert/test_expert.c.

            1  +/*
            2  +** 2017 April 07
            3  +**
            4  +** The author disclaims copyright to this source code.  In place of
            5  +** a legal notice, here is a blessing:
            6  +**
            7  +**    May you do good and not evil.
            8  +**    May you find forgiveness for yourself and forgive others.
            9  +**    May you share freely, never taking more than you give.
           10  +**
           11  +*************************************************************************
           12  +*/
           13  +
           14  +#if defined(SQLITE_TEST) && defined(SQLITE_ENABLE_WHEREINFO_HOOK)
           15  +
           16  +#include "sqlite3expert.h"
           17  +#include <assert.h>
           18  +#include <string.h>
           19  +
           20  +#if defined(INCLUDE_SQLITE_TCL_H)
           21  +#  include "sqlite_tcl.h"
           22  +#else
           23  +#  include "tcl.h"
           24  +#  ifndef SQLITE_TCLAPI
           25  +#    define SQLITE_TCLAPI
           26  +#  endif
           27  +#endif
           28  +
           29  +/*
           30  +** Extract an sqlite3* db handle from the object passed as the second
           31  +** argument. If successful, set *pDb to point to the db handle and return
           32  +** TCL_OK. Otherwise, return TCL_ERROR.
           33  +*/
           34  +static int dbHandleFromObj(Tcl_Interp *interp, Tcl_Obj *pObj, sqlite3 **pDb){
           35  +  Tcl_CmdInfo info;
           36  +  if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(pObj), &info) ){
           37  +    Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(pObj), 0);
           38  +    return TCL_ERROR;
           39  +  }
           40  +
           41  +  *pDb = *(sqlite3 **)info.objClientData;
           42  +  return TCL_OK;
           43  +}
           44  +
           45  +
           46  +/*
           47  +** Tclcmd:  $expert sql SQL
           48  +**          $expert analyze
           49  +**          $expert count
           50  +**          $expert report STMT EREPORT
           51  +**          $expert destroy
           52  +*/
           53  +static int SQLITE_TCLAPI testExpertCmd(
           54  +  void *clientData,
           55  +  Tcl_Interp *interp,
           56  +  int objc,
           57  +  Tcl_Obj *CONST objv[]
           58  +){
           59  +  sqlite3expert *pExpert = (sqlite3expert*)clientData;
           60  +  struct Subcmd {
           61  +    const char *zSub;
           62  +    int nArg;
           63  +    const char *zMsg;
           64  +  } aSub[] = {
           65  +    { "sql",       1, "TABLE",        }, /* 0 */
           66  +    { "analyze",   0, "",             }, /* 1 */
           67  +    { "count",     0, "",             }, /* 2 */
           68  +    { "report",    2, "STMT EREPORT", }, /* 3 */
           69  +    { "destroy",   0, "",             }, /* 4 */
           70  +    { 0 }
           71  +  };
           72  +  int iSub;
           73  +  int rc = TCL_OK;
           74  +  char *zErr = 0;
           75  +
           76  +  if( objc<2 ){
           77  +    Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
           78  +    return TCL_ERROR;
           79  +  }
           80  +  rc = Tcl_GetIndexFromObjStruct(interp, 
           81  +      objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
           82  +  );
           83  +  if( rc!=TCL_OK ) return rc;
           84  +  if( objc!=2+aSub[iSub].nArg ){
           85  +    Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
           86  +    return TCL_ERROR;
           87  +  }
           88  +
           89  +  switch( iSub ){
           90  +    case 0: {      /* sql */
           91  +      char *zArg = Tcl_GetString(objv[2]);
           92  +      rc = sqlite3_expert_sql(pExpert, zArg, &zErr);
           93  +      break;
           94  +    }
           95  +
           96  +    case 1: {      /* analyze */
           97  +      rc = sqlite3_expert_analyze(pExpert, &zErr);
           98  +      break;
           99  +    }
          100  +
          101  +    case 2: {      /* count */
          102  +      int n = sqlite3_expert_count(pExpert);
          103  +      Tcl_SetObjResult(interp, Tcl_NewIntObj(n));
          104  +      break;
          105  +    }
          106  +
          107  +    case 3: {      /* report */
          108  +      const char *aEnum[] = {
          109  +        "sql", "indexes", "plan", 0
          110  +      };
          111  +      int iEnum;
          112  +      int iStmt;
          113  +      const char *zReport;
          114  +
          115  +      if( Tcl_GetIntFromObj(interp, objv[2], &iStmt) 
          116  +       || Tcl_GetIndexFromObj(interp, objv[3], aEnum, "report", 0, &iEnum)
          117  +      ){
          118  +        return TCL_ERROR;
          119  +      }
          120  +
          121  +      assert( EXPERT_REPORT_SQL==1 );
          122  +      assert( EXPERT_REPORT_INDEXES==2 );
          123  +      assert( EXPERT_REPORT_PLAN==3 );
          124  +      zReport = sqlite3_expert_report(pExpert, iStmt, 1+iEnum);
          125  +      Tcl_SetObjResult(interp, Tcl_NewStringObj(zReport, -1));
          126  +      break;
          127  +    }
          128  +
          129  +    default:       /* destroy */
          130  +      assert( iSub==4 );     
          131  +      Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
          132  +      break;
          133  +  }
          134  +
          135  +  if( rc!=TCL_OK ){
          136  +    if( zErr ){
          137  +      Tcl_SetObjResult(interp, Tcl_NewStringObj(zErr, -1));
          138  +    }else{
          139  +      extern const char *sqlite3ErrName(int);
          140  +      Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
          141  +    }
          142  +  }
          143  +  sqlite3_free(zErr);
          144  +  return rc;
          145  +}
          146  +
          147  +static void SQLITE_TCLAPI testExpertDel(void *clientData){
          148  +  sqlite3expert *pExpert = (sqlite3expert*)clientData;
          149  +  sqlite3_expert_destroy(pExpert);
          150  +}
          151  +
          152  +/*
          153  +** sqlite3_expert_new DB
          154  +*/
          155  +static int SQLITE_TCLAPI test_sqlite3_expert_new(
          156  +  void * clientData,
          157  +  Tcl_Interp *interp,
          158  +  int objc,
          159  +  Tcl_Obj *CONST objv[]
          160  +){
          161  +  static int iCmd = 0;
          162  +  sqlite3 *db;
          163  +  char *zCmd = 0;
          164  +  char *zErr = 0;
          165  +  sqlite3expert *pExpert;
          166  +  int rc = TCL_OK;
          167  +
          168  +  if( objc!=2 ){
          169  +    Tcl_WrongNumArgs(interp, 1, objv, "DB");
          170  +    return TCL_ERROR;
          171  +  }
          172  +  if( dbHandleFromObj(interp, objv[1], &db) ){
          173  +    return TCL_ERROR;
          174  +  }
          175  +
          176  +  zCmd = sqlite3_mprintf("sqlite3expert%d", ++iCmd);
          177  +  if( zCmd==0 ){
          178  +    Tcl_AppendResult(interp, "out of memory", (char*)0);
          179  +    return TCL_ERROR;
          180  +  }
          181  +
          182  +  pExpert = sqlite3_expert_new(db, &zErr);
          183  +  if( pExpert==0 ){
          184  +    Tcl_AppendResult(interp, zErr, (char*)0);
          185  +    rc = TCL_ERROR;
          186  +  }else{
          187  +    void *p = (void*)pExpert;
          188  +    Tcl_CreateObjCommand(interp, zCmd, testExpertCmd, p, testExpertDel);
          189  +    Tcl_SetObjResult(interp, Tcl_NewStringObj(zCmd, -1));
          190  +  }
          191  +
          192  +  sqlite3_free(zCmd);
          193  +  sqlite3_free(zErr);
          194  +  return rc;
          195  +}
          196  +
          197  +int TestExpert_Init(Tcl_Interp *interp){
          198  +  struct Cmd {
          199  +    const char *zCmd;
          200  +    Tcl_ObjCmdProc *xProc;
          201  +  } aCmd[] = {
          202  +    { "sqlite3_expert_new", test_sqlite3_expert_new },
          203  +  };
          204  +  int i;
          205  +
          206  +  for(i=0; i<sizeof(aCmd)/sizeof(struct Cmd); i++){
          207  +    struct Cmd *p = &aCmd[i];
          208  +    Tcl_CreateObjCommand(interp, p->zCmd, p->xProc, 0, 0);
          209  +  }
          210  +
          211  +  return TCL_OK;
          212  +}
          213  +
          214  +#else /* defined(SQLITE_TEST) && defined(SQLITE_ENABLE_WHEREINFO_HOOK) */
          215  +int TestExpert_Init(Tcl_Interp *interp){
          216  +  return TCL_OK;
          217  +}
          218  +#endif

Changes to main.mk.

   270    270     parse.h \
   271    271     sqlite3.h
   272    272   
   273    273   
   274    274   # Source code to the test files.
   275    275   #
   276    276   TESTSRC = \
          277  +  $(TOP)/ext/expert/sqlite3expert.c \
          278  +  $(TOP)/ext/expert/test_expert.c \
   277    279     $(TOP)/ext/fts3/fts3_term.c \
   278    280     $(TOP)/ext/fts3/fts3_test.c \
   279    281     $(TOP)/ext/rbu/test_rbu.c \
   280    282     $(TOP)/src/test1.c \
   281    283     $(TOP)/src/test2.c \
   282    284     $(TOP)/src/test3.c \
   283    285     $(TOP)/src/test4.c \

Changes to src/tclsqlite.c.

  4125   4125       extern int Sqlitequota_Init(Tcl_Interp*);
  4126   4126       extern int Sqlitemultiplex_Init(Tcl_Interp*);
  4127   4127       extern int SqliteSuperlock_Init(Tcl_Interp*);
  4128   4128       extern int SqlitetestSyscall_Init(Tcl_Interp*);
  4129   4129   #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
  4130   4130       extern int TestSession_Init(Tcl_Interp*);
  4131   4131   #endif
         4132  +    extern int TestExpert_Init(Tcl_Interp*);
  4132   4133       extern int Fts5tcl_Init(Tcl_Interp *);
  4133   4134       extern int SqliteRbu_Init(Tcl_Interp*);
  4134   4135       extern int Sqlitetesttcl_Init(Tcl_Interp*);
  4135   4136   #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
  4136   4137       extern int Sqlitetestfts3_Init(Tcl_Interp *interp);
  4137   4138   #endif
  4138   4139   
................................................................................
  4173   4174       Sqlitequota_Init(interp);
  4174   4175       Sqlitemultiplex_Init(interp);
  4175   4176       SqliteSuperlock_Init(interp);
  4176   4177       SqlitetestSyscall_Init(interp);
  4177   4178   #if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
  4178   4179       TestSession_Init(interp);
  4179   4180   #endif
         4181  +    TestExpert_Init(interp);
  4180   4182       Fts5tcl_Init(interp);
  4181   4183       SqliteRbu_Init(interp);
  4182   4184       Sqlitetesttcl_Init(interp);
  4183   4185   
  4184   4186   #if defined(SQLITE_ENABLE_FTS3) || defined(SQLITE_ENABLE_FTS4)
  4185   4187       Sqlitetestfts3_Init(interp);
  4186   4188   #endif

Changes to test/permutations.test.

    85     85   #   $allquicktests
    86     86   #
    87     87   set alltests [list]
    88     88   foreach f [glob $testdir/*.test] { lappend alltests [file tail $f] }
    89     89   foreach f [glob -nocomplain       \
    90     90       $testdir/../ext/rtree/*.test  \
    91     91       $testdir/../ext/fts5/test/*.test   \
           92  +    $testdir/../ext/expert/*.test   \
    92     93   ] { 
    93     94     lappend alltests $f 
    94     95   }
    95     96   foreach f [glob -nocomplain $testdir/../ext/session/*.test] { 
    96     97     lappend alltests $f 
    97     98   }
    98     99