Increase resolution of time-of-day on unix.  Add an experimental
sqlite3_profile() API. (CVS 2639)

FossilOrigin-Name: ed2ca0873fa89d6cfd123541d5d1c6b92c72b6ab
diff --git a/src/build.c b/src/build.c
index 50ad526..d79db7e 100644
--- a/src/build.c
+++ b/src/build.c
@@ -22,7 +22,7 @@
 **     COMMIT
 **     ROLLBACK
 **
-** $Id: build.c,v 1.342 2005/08/20 03:03:04 drh Exp $
+** $Id: build.c,v 1.343 2005/08/29 23:00:04 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -85,6 +85,7 @@
       sqlite3VdbeAddOp(v, OP_Goto, 0, pParse->cookieGoto);
     }
 
+#ifndef SQLITE_OMIT_TRACE
     /* Add a No-op that contains the complete text of the compiled SQL
     ** statement as its P3 argument.  This does not change the functionality
     ** of the program. 
@@ -92,6 +93,7 @@
     ** This is used to implement sqlite3_trace().
     */
     sqlite3VdbeOp3(v, OP_Noop, 0, 0, pParse->zSql, pParse->zTail-pParse->zSql);
+#endif /* SQLITE_OMIT_TRACE */
   }
 
 
diff --git a/src/main.c b/src/main.c
index 41d1045..bfc2bd5 100644
--- a/src/main.c
+++ b/src/main.c
@@ -14,7 +14,7 @@
 ** other files are for internal use by SQLite and should not be
 ** accessed by users of the library.
 **
-** $Id: main.c,v 1.299 2005/08/28 17:00:23 drh Exp $
+** $Id: main.c,v 1.300 2005/08/29 23:00:04 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -509,13 +509,14 @@
 }
 #endif
 
+#ifndef SQLITE_OMIT_TRACE
 /*
 ** Register a trace function.  The pArg from the previously registered trace
 ** is returned.  
 **
 ** A NULL trace function means that no tracing is executes.  A non-NULL
 ** trace is a pointer to a function that is invoked at the start of each
-** sqlite3_exec().
+** SQL statement.
 */
 void *sqlite3_trace(sqlite3 *db, void (*xTrace)(void*,const char*), void *pArg){
   void *pOld = db->pTraceArg;
@@ -523,6 +524,25 @@
   db->pTraceArg = pArg;
   return pOld;
 }
+/*
+** Register a profile function.  The pArg from the previously registered 
+** profile function is returned.  
+**
+** A NULL profile function means that no profiling is executes.  A non-NULL
+** profile is a pointer to a function that is invoked at the conclusion of
+** each SQL statement that is run.
+*/
+void *sqlite3_profile(
+  sqlite3 *db,
+  void (*xProfile)(void*,const char*,sqlite_uint64),
+  void *pArg
+){
+  void *pOld = db->pProfileArg;
+  db->xProfile = xProfile;
+  db->pProfileArg = pArg;
+  return pOld;
+}
+#endif /* SQLITE_OMIT_TRACE */
 
 /*** EXPERIMENTAL ***
 **
diff --git a/src/os_unix.c b/src/os_unix.c
index 2ea9aa8..836d01f 100644
--- a/src/os_unix.c
+++ b/src/os_unix.c
@@ -18,6 +18,7 @@
 
 
 #include <time.h>
+#include <sys/time.h>
 #include <errno.h>
 #include <unistd.h>
 
@@ -1420,9 +1421,16 @@
 ** return 0.  Return 1 if the time and date cannot be found.
 */
 int sqlite3OsCurrentTime(double *prNow){
+#ifdef NO_GETTOD
   time_t t;
   time(&t);
   *prNow = t/86400.0 + 2440587.5;
+#else
+  struct timeval sNow;
+  struct timezone sTz;  /* Not used */
+  gettimeofday(&sNow, &sTz);
+  *prNow = 2440587.5 + sNow.tv_sec/86400.0 + sNow.tv_usec/86400000000.0;
+#endif
 #ifdef SQLITE_TEST
   if( sqlite3_current_time ){
     *prNow = sqlite3_current_time/86400.0 + 2440587.5;
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 3f18f88..30533e4 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.139 2005/08/11 02:10:19 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.140 2005/08/29 23:00:04 drh Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -464,11 +464,18 @@
 #define SQLITE_IGNORE 2   /* Don't allow access, but don't generate an error */
 
 /*
-** Register a function that is called at every invocation of sqlite3_exec()
-** or sqlite3_prepare().  This function can be used (for example) to generate
-** a log file of all SQL executed against a database.
+** Register a function for tracing SQL command evaluation.  The function
+** registered by sqlite3_trace() is invoked at the first sqlite3_step()
+** for the evaluation of an SQL statement.  The function registered by
+** sqlite3_profile() runs at the end of each SQL statement and includes
+** information on how long that statement ran.
+**
+** The sqlite3_profile() API is currently considered experimental and
+** is subject to change.
 */
 void *sqlite3_trace(sqlite3*, void(*xTrace)(void*,const char*), void*);
+void *sqlite3_profile(sqlite3*,
+   void(*xProfile)(void*,const char*,sqlite_uint64), void*);
 
 /*
 ** This routine configures a callback function - the progress callback - that
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index dba03e1..8042ae0 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.404 2005/08/28 17:00:23 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.405 2005/08/29 23:00:04 drh Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -420,8 +420,10 @@
   } init;
   struct Vdbe *pVdbe;           /* List of active virtual machines */
   int activeVdbeCnt;            /* Number of vdbes currently executing */
-  void (*xTrace)(void*,const char*);     /* Trace function */
-  void *pTraceArg;                       /* Argument to the trace function */
+  void (*xTrace)(void*,const char*);        /* Trace function */
+  void *pTraceArg;                          /* Argument to the trace function */
+  void (*xProfile)(void*,const char*,u64);  /* Profiling function */
+  void *pProfileArg;                        /* Argument to profile function */
   void *pCommitArg;             /* Argument to xCommitCallback() */   
   int (*xCommitCallback)(void*);/* Invoked at every commit. */
   void(*xCollNeeded)(void*,sqlite3*,int eTextRep,const char*);
diff --git a/src/tclsqlite.c b/src/tclsqlite.c
index 6a36437..5e5e626 100644
--- a/src/tclsqlite.c
+++ b/src/tclsqlite.c
@@ -11,7 +11,7 @@
 *************************************************************************
 ** A TCL Interface to SQLite
 **
-** $Id: tclsqlite.c,v 1.131 2005/08/16 11:11:35 drh Exp $
+** $Id: tclsqlite.c,v 1.132 2005/08/29 23:00:04 drh Exp $
 */
 #ifndef NO_TCL     /* Omit this whole file if TCL is unavailable */
 
@@ -84,6 +84,7 @@
   char *zBusy;               /* The busy callback routine */
   char *zCommit;             /* The commit hook callback routine */
   char *zTrace;              /* The trace callback routine */
+  char *zProfile;            /* The profile callback routine */
   char *zProgress;           /* The progress callback routine */
   char *zAuth;               /* The authorization callback routine */
   char *zNull;               /* Text to substitute for an SQL NULL value */
@@ -190,6 +191,9 @@
   if( pDb->zTrace ){
     Tcl_Free(pDb->zTrace);
   }
+  if( pDb->zProfile ){
+    Tcl_Free(pDb->zProfile);
+  }
   if( pDb->zAuth ){
     Tcl_Free(pDb->zAuth);
   }
@@ -248,6 +252,25 @@
 }
 
 /*
+** This routine is called by the SQLite profile handler after a statement
+** SQL has executed.  The TCL script in pDb->zProfile is evaluated.
+*/
+static void DbProfileHandler(void *cd, const char *zSql, sqlite_uint64 tm){
+  SqliteDb *pDb = (SqliteDb*)cd;
+  Tcl_DString str;
+  char zTm[100];
+
+  sqlite3_snprintf(sizeof(zTm)-1, zTm, "%lld", tm);
+  Tcl_DStringInit(&str);
+  Tcl_DStringAppend(&str, pDb->zProfile, -1);
+  Tcl_DStringAppendElement(&str, zSql);
+  Tcl_DStringAppendElement(&str, zTm);
+  Tcl_Eval(pDb->interp, Tcl_DStringValue(&str));
+  Tcl_DStringFree(&str);
+  Tcl_ResetResult(pDb->interp);
+}
+
+/*
 ** This routine is called when a transaction is committed.  The
 ** TCL script in pDb->zCommit is executed.  If it returns non-zero or
 ** if it throws an exception, the transaction is rolled back instead
@@ -589,9 +612,10 @@
     "collation_needed",   "commit_hook",       "complete",
     "copy",               "errorcode",         "eval",
     "function",           "last_insert_rowid", "nullvalue",
-    "onecolumn",          "progress",          "rekey",
-    "timeout",            "total_changes",     "trace",
-    "transaction",        "version",           0
+    "onecolumn",          "profile",           "progress",
+    "rekey",              "timeout",           "total_changes",
+    "trace",              "transaction",       "version",
+    0                    
   };
   enum DB_enum {
     DB_AUTHORIZER,        DB_BUSY,             DB_CACHE,
@@ -599,9 +623,9 @@
     DB_COLLATION_NEEDED,  DB_COMMIT_HOOK,      DB_COMPLETE,
     DB_COPY,              DB_ERRORCODE,        DB_EVAL,
     DB_FUNCTION,          DB_LAST_INSERT_ROWID,DB_NULLVALUE,
-    DB_ONECOLUMN,         DB_PROGRESS,         DB_REKEY,
-    DB_TIMEOUT,           DB_TOTAL_CHANGES,    DB_TRACE,
-    DB_TRANSACTION,       DB_VERSION,          
+    DB_ONECOLUMN,         DB_PROFILE,          DB_PROGRESS,
+    DB_REKEY,             DB_TIMEOUT,          DB_TOTAL_CHANGES,
+    DB_TRACE,             DB_TRANSACTION,      DB_VERSION
   };
   /* don't leave trailing commas on DB_enum, it confuses the AIX xlc compiler */
 
@@ -780,44 +804,6 @@
     break;
   }
 
-  /*    $db commit_hook ?CALLBACK?
-  **
-  ** Invoke the given callback just before committing every SQL transaction.
-  ** If the callback throws an exception or returns non-zero, then the
-  ** transaction is aborted.  If CALLBACK is an empty string, the callback
-  ** is disabled.
-  */
-  case DB_COMMIT_HOOK: {
-    if( objc>3 ){
-      Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
-      return TCL_ERROR;
-    }else if( objc==2 ){
-      if( pDb->zCommit ){
-        Tcl_AppendResult(interp, pDb->zCommit, 0);
-      }
-    }else{
-      char *zCommit;
-      int len;
-      if( pDb->zCommit ){
-        Tcl_Free(pDb->zCommit);
-      }
-      zCommit = Tcl_GetStringFromObj(objv[2], &len);
-      if( zCommit && len>0 ){
-        pDb->zCommit = Tcl_Alloc( len + 1 );
-        strcpy(pDb->zCommit, zCommit);
-      }else{
-        pDb->zCommit = 0;
-      }
-      if( pDb->zCommit ){
-        pDb->interp = interp;
-        sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb);
-      }else{
-        sqlite3_commit_hook(pDb->db, 0, 0);
-      }
-    }
-    break;
-  }
-
   /*
   **     $db collate NAME SCRIPT
   **
@@ -870,6 +856,44 @@
     break;
   }
 
+  /*    $db commit_hook ?CALLBACK?
+  **
+  ** Invoke the given callback just before committing every SQL transaction.
+  ** If the callback throws an exception or returns non-zero, then the
+  ** transaction is aborted.  If CALLBACK is an empty string, the callback
+  ** is disabled.
+  */
+  case DB_COMMIT_HOOK: {
+    if( objc>3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+      return TCL_ERROR;
+    }else if( objc==2 ){
+      if( pDb->zCommit ){
+        Tcl_AppendResult(interp, pDb->zCommit, 0);
+      }
+    }else{
+      char *zCommit;
+      int len;
+      if( pDb->zCommit ){
+        Tcl_Free(pDb->zCommit);
+      }
+      zCommit = Tcl_GetStringFromObj(objv[2], &len);
+      if( zCommit && len>0 ){
+        pDb->zCommit = Tcl_Alloc( len + 1 );
+        strcpy(pDb->zCommit, zCommit);
+      }else{
+        pDb->zCommit = 0;
+      }
+      if( pDb->zCommit ){
+        pDb->interp = interp;
+        sqlite3_commit_hook(pDb->db, DbCommitHandler, pDb);
+      }else{
+        sqlite3_commit_hook(pDb->db, 0, 0);
+      }
+    }
+    break;
+  }
+
   /*    $db complete SQL
   **
   ** Return TRUE if SQL is a complete SQL statement.  Return FALSE if
@@ -891,6 +915,192 @@
     break;
   }
 
+  /*    $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR?
+  **
+  ** Copy data into table from filename, optionally using SEPARATOR
+  ** as column separators.  If a column contains a null string, or the
+  ** value of NULLINDICATOR, a NULL is inserted for the column.
+  ** conflict-algorithm is one of the sqlite conflict algorithms:
+  **    rollback, abort, fail, ignore, replace
+  ** On success, return the number of lines processed, not necessarily same
+  ** as 'db changes' due to conflict-algorithm selected.
+  **
+  ** This code is basically an implementation/enhancement of
+  ** the sqlite3 shell.c ".import" command.
+  **
+  ** This command usage is equivalent to the sqlite2.x COPY statement,
+  ** which imports file data into a table using the PostgreSQL COPY file format:
+  **   $db copy $conflit_algo $table_name $filename \t \\N
+  */
+  case DB_COPY: {
+    char *zTable;               /* Insert data into this table */
+    char *zFile;                /* The file from which to extract data */
+    char *zConflict;            /* The conflict algorithm to use */
+    sqlite3_stmt *pStmt;        /* A statement */
+    int rc;                     /* Result code */
+    int nCol;                   /* Number of columns in the table */
+    int nByte;                  /* Number of bytes in an SQL string */
+    int i, j;                   /* Loop counters */
+    int nSep;                   /* Number of bytes in zSep[] */
+    int nNull;                  /* Number of bytes in zNull[] */
+    char *zSql;                 /* An SQL statement */
+    char *zLine;                /* A single line of input from the file */
+    char **azCol;               /* zLine[] broken up into columns */
+    char *zCommit;              /* How to commit changes */
+    FILE *in;                   /* The input file */
+    int lineno = 0;             /* Line number of input file */
+    char zLineNum[80];          /* Line number print buffer */
+    Tcl_Obj *pResult;           /* interp result */
+
+    char *zSep;
+    char *zNull;
+    if( objc<5 || objc>7 ){
+      Tcl_WrongNumArgs(interp, 2, objv, 
+         "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?");
+      return TCL_ERROR;
+    }
+    if( objc>=6 ){
+      zSep = Tcl_GetStringFromObj(objv[5], 0);
+    }else{
+      zSep = "\t";
+    }
+    if( objc>=7 ){
+      zNull = Tcl_GetStringFromObj(objv[6], 0);
+    }else{
+      zNull = "";
+    }
+    zConflict = Tcl_GetStringFromObj(objv[2], 0);
+    zTable = Tcl_GetStringFromObj(objv[3], 0);
+    zFile = Tcl_GetStringFromObj(objv[4], 0);
+    nSep = strlen(zSep);
+    nNull = strlen(zNull);
+    if( nSep==0 ){
+      Tcl_AppendResult(interp, "Error: non-null separator required for copy", 0);
+      return TCL_ERROR;
+    }
+    if(sqlite3StrICmp(zConflict, "rollback") != 0 &&
+       sqlite3StrICmp(zConflict, "abort"   ) != 0 &&
+       sqlite3StrICmp(zConflict, "fail"    ) != 0 &&
+       sqlite3StrICmp(zConflict, "ignore"  ) != 0 &&
+       sqlite3StrICmp(zConflict, "replace" ) != 0 ) {
+      Tcl_AppendResult(interp, "Error: \"", zConflict, 
+            "\", conflict-algorithm must be one of: rollback, "
+            "abort, fail, ignore, or replace", 0);
+      return TCL_ERROR;
+    }
+    zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
+    if( zSql==0 ){
+      Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0);
+      return TCL_ERROR;
+    }
+    nByte = strlen(zSql);
+    rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
+    sqlite3_free(zSql);
+    if( rc ){
+      Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
+      nCol = 0;
+    }else{
+      nCol = sqlite3_column_count(pStmt);
+    }
+    sqlite3_finalize(pStmt);
+    if( nCol==0 ) {
+      return TCL_ERROR;
+    }
+    zSql = malloc( nByte + 50 + nCol*2 );
+    if( zSql==0 ) {
+      Tcl_AppendResult(interp, "Error: can't malloc()", 0);
+      return TCL_ERROR;
+    }
+    sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?",
+         zConflict, zTable);
+    j = strlen(zSql);
+    for(i=1; i<nCol; i++){
+      zSql[j++] = ',';
+      zSql[j++] = '?';
+    }
+    zSql[j++] = ')';
+    zSql[j] = 0;
+    rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
+    free(zSql);
+    if( rc ){
+      Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
+      sqlite3_finalize(pStmt);
+      return TCL_ERROR;
+    }
+    in = fopen(zFile, "rb");
+    if( in==0 ){
+      Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, NULL);
+      sqlite3_finalize(pStmt);
+      return TCL_ERROR;
+    }
+    azCol = malloc( sizeof(azCol[0])*(nCol+1) );
+    if( azCol==0 ) {
+      Tcl_AppendResult(interp, "Error: can't malloc()", 0);
+      return TCL_ERROR;
+    }
+    sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0);
+    zCommit = "COMMIT";
+    while( (zLine = local_getline(0, in))!=0 ){
+      char *z;
+      i = 0;
+      lineno++;
+      azCol[0] = zLine;
+      for(i=0, z=zLine; *z; z++){
+        if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){
+          *z = 0;
+          i++;
+          if( i<nCol ){
+            azCol[i] = &z[nSep];
+            z += nSep-1;
+          }
+        }
+      }
+      if( i+1!=nCol ){
+        char *zErr;
+        zErr = malloc(200 + strlen(zFile));
+        sprintf(zErr,"Error: %s line %d: expected %d columns of data but found %d",
+           zFile, lineno, nCol, i+1);
+        Tcl_AppendResult(interp, zErr, 0);
+        free(zErr);
+        zCommit = "ROLLBACK";
+        break;
+      }
+      for(i=0; i<nCol; i++){
+        /* check for null data, if so, bind as null */
+        if ((nNull>0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) {
+          sqlite3_bind_null(pStmt, i+1);
+        }else{
+          sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
+        }
+      }
+      sqlite3_step(pStmt);
+      rc = sqlite3_reset(pStmt);
+      free(zLine);
+      if( rc!=SQLITE_OK ){
+        Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), 0);
+        zCommit = "ROLLBACK";
+        break;
+      }
+    }
+    free(azCol);
+    fclose(in);
+    sqlite3_finalize(pStmt);
+    sqlite3_exec(pDb->db, zCommit, 0, 0, 0);
+
+    if( zCommit[0] == 'C' ){
+      /* success, set result as number of lines processed */
+      pResult = Tcl_GetObjResult(interp);
+      Tcl_SetIntObj(pResult, lineno);
+      rc = TCL_OK;
+    }else{
+      /* failure, append lineno where failed */
+      sprintf(zLineNum,"%d",lineno);
+      Tcl_AppendResult(interp,", failed while processing line: ",zLineNum,0);
+      rc = TCL_ERROR;
+    }
+    break;
+  }
+
   /*
   **    $db errorcode
   **
@@ -1302,6 +1512,37 @@
   }
 
   /*
+  **     $db nullvalue ?STRING?
+  **
+  ** Change text used when a NULL comes back from the database. If ?STRING?
+  ** is not present, then the current string used for NULL is returned.
+  ** If STRING is present, then STRING is returned.
+  **
+  */
+  case DB_NULLVALUE: {
+    if( objc!=2 && objc!=3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE");
+      return TCL_ERROR;
+    }
+    if( objc==3 ){
+      int len;
+      char *zNull = Tcl_GetStringFromObj(objv[2], &len);
+      if( pDb->zNull ){
+        Tcl_Free(pDb->zNull);
+      }
+      if( zNull && len>0 ){
+        pDb->zNull = Tcl_Alloc( len + 1 );
+        strncpy(pDb->zNull, zNull, len);
+        pDb->zNull[len] = '\0';
+      }else{
+        pDb->zNull = 0;
+      }
+    }
+    Tcl_SetObjResult(interp, dbTextToObj(pDb->zNull));
+    break;
+  }
+
+  /*
   **     $db last_insert_rowid 
   **
   ** Return an integer which is the ROWID for the most recent insert.
@@ -1365,6 +1606,45 @@
     break;
   }
 
+  /*    $db profile ?CALLBACK?
+  **
+  ** Make arrangements to invoke the CALLBACK routine after each SQL statement
+  ** that has run.  The text of the SQL and the amount of elapse time are
+  ** appended to CALLBACK before the script is run.
+  */
+  case DB_PROFILE: {
+    if( objc>3 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?CALLBACK?");
+      return TCL_ERROR;
+    }else if( objc==2 ){
+      if( pDb->zProfile ){
+        Tcl_AppendResult(interp, pDb->zProfile, 0);
+      }
+    }else{
+      char *zProfile;
+      int len;
+      if( pDb->zProfile ){
+        Tcl_Free(pDb->zProfile);
+      }
+      zProfile = Tcl_GetStringFromObj(objv[2], &len);
+      if( zProfile && len>0 ){
+        pDb->zProfile = Tcl_Alloc( len + 1 );
+        strcpy(pDb->zProfile, zProfile);
+      }else{
+        pDb->zProfile = 0;
+      }
+#ifndef SQLITE_OMIT_TRACE
+      if( pDb->zProfile ){
+        pDb->interp = interp;
+        sqlite3_profile(pDb->db, DbProfileHandler, pDb);
+      }else{
+        sqlite3_profile(pDb->db, 0, 0);
+      }
+#endif
+    }
+    break;
+  }
+
   /*
   **     $db rekey KEY
   **
@@ -1403,37 +1683,6 @@
     sqlite3_busy_timeout(pDb->db, ms);
     break;
   }
-
-  /*
-  **     $db nullvalue ?STRING?
-  **
-  ** Change text used when a NULL comes back from the database. If ?STRING?
-  ** is not present, then the current string used for NULL is returned.
-  ** If STRING is present, then STRING is returned.
-  **
-  */
-  case DB_NULLVALUE: {
-    if( objc!=2 && objc!=3 ){
-      Tcl_WrongNumArgs(interp, 2, objv, "NULLVALUE");
-      return TCL_ERROR;
-    }
-    if( objc==3 ){
-      int len;
-      char *zNull = Tcl_GetStringFromObj(objv[2], &len);
-      if( pDb->zNull ){
-        Tcl_Free(pDb->zNull);
-      }
-      if( zNull && len>0 ){
-        pDb->zNull = Tcl_Alloc( len + 1 );
-        strncpy(pDb->zNull, zNull, len);
-        pDb->zNull[len] = '\0';
-      }else{
-        pDb->zNull = 0;
-      }
-    }
-    Tcl_SetObjResult(interp, dbTextToObj(pDb->zNull));
-    break;
-  }
   
   /*
   **     $db total_changes
@@ -1479,12 +1728,14 @@
       }else{
         pDb->zTrace = 0;
       }
+#ifndef SQLITE_OMIT_TRACE
       if( pDb->zTrace ){
         pDb->interp = interp;
         sqlite3_trace(pDb->db, DbTraceHandler, pDb);
       }else{
         sqlite3_trace(pDb->db, 0, 0);
       }
+#endif
     }
     break;
   }
@@ -1546,192 +1797,6 @@
     break;
   }
 
-  /*    $db copy conflict-algorithm table filename ?SEPARATOR? ?NULLINDICATOR?
-  **
-  ** Copy data into table from filename, optionally using SEPARATOR
-  ** as column separators.  If a column contains a null string, or the
-  ** value of NULLINDICATOR, a NULL is inserted for the column.
-  ** conflict-algorithm is one of the sqlite conflict algorithms:
-  **    rollback, abort, fail, ignore, replace
-  ** On success, return the number of lines processed, not necessarily same
-  ** as 'db changes' due to conflict-algorithm selected.
-  **
-  ** This code is basically an implementation/enhancement of
-  ** the sqlite3 shell.c ".import" command.
-  **
-  ** This command usage is equivalent to the sqlite2.x COPY statement,
-  ** which imports file data into a table using the PostgreSQL COPY file format:
-  **   $db copy $conflit_algo $table_name $filename \t \\N
-  */
-  case DB_COPY: {
-    char *zTable;               /* Insert data into this table */
-    char *zFile;                /* The file from which to extract data */
-    char *zConflict;            /* The conflict algorithm to use */
-    sqlite3_stmt *pStmt;        /* A statement */
-    int rc;                     /* Result code */
-    int nCol;                   /* Number of columns in the table */
-    int nByte;                  /* Number of bytes in an SQL string */
-    int i, j;                   /* Loop counters */
-    int nSep;                   /* Number of bytes in zSep[] */
-    int nNull;                  /* Number of bytes in zNull[] */
-    char *zSql;                 /* An SQL statement */
-    char *zLine;                /* A single line of input from the file */
-    char **azCol;               /* zLine[] broken up into columns */
-    char *zCommit;              /* How to commit changes */
-    FILE *in;                   /* The input file */
-    int lineno = 0;             /* Line number of input file */
-    char zLineNum[80];          /* Line number print buffer */
-    Tcl_Obj *pResult;           /* interp result */
-
-    char *zSep;
-    char *zNull;
-    if( objc<5 || objc>7 ){
-      Tcl_WrongNumArgs(interp, 2, objv, 
-         "CONFLICT-ALGORITHM TABLE FILENAME ?SEPARATOR? ?NULLINDICATOR?");
-      return TCL_ERROR;
-    }
-    if( objc>=6 ){
-      zSep = Tcl_GetStringFromObj(objv[5], 0);
-    }else{
-      zSep = "\t";
-    }
-    if( objc>=7 ){
-      zNull = Tcl_GetStringFromObj(objv[6], 0);
-    }else{
-      zNull = "";
-    }
-    zConflict = Tcl_GetStringFromObj(objv[2], 0);
-    zTable = Tcl_GetStringFromObj(objv[3], 0);
-    zFile = Tcl_GetStringFromObj(objv[4], 0);
-    nSep = strlen(zSep);
-    nNull = strlen(zNull);
-    if( nSep==0 ){
-      Tcl_AppendResult(interp, "Error: non-null separator required for copy", 0);
-      return TCL_ERROR;
-    }
-    if(sqlite3StrICmp(zConflict, "rollback") != 0 &&
-       sqlite3StrICmp(zConflict, "abort"   ) != 0 &&
-       sqlite3StrICmp(zConflict, "fail"    ) != 0 &&
-       sqlite3StrICmp(zConflict, "ignore"  ) != 0 &&
-       sqlite3StrICmp(zConflict, "replace" ) != 0 ) {
-      Tcl_AppendResult(interp, "Error: \"", zConflict, 
-            "\", conflict-algorithm must be one of: rollback, "
-            "abort, fail, ignore, or replace", 0);
-      return TCL_ERROR;
-    }
-    zSql = sqlite3_mprintf("SELECT * FROM '%q'", zTable);
-    if( zSql==0 ){
-      Tcl_AppendResult(interp, "Error: no such table: ", zTable, 0);
-      return TCL_ERROR;
-    }
-    nByte = strlen(zSql);
-    rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
-    sqlite3_free(zSql);
-    if( rc ){
-      Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
-      nCol = 0;
-    }else{
-      nCol = sqlite3_column_count(pStmt);
-    }
-    sqlite3_finalize(pStmt);
-    if( nCol==0 ) {
-      return TCL_ERROR;
-    }
-    zSql = malloc( nByte + 50 + nCol*2 );
-    if( zSql==0 ) {
-      Tcl_AppendResult(interp, "Error: can't malloc()", 0);
-      return TCL_ERROR;
-    }
-    sqlite3_snprintf(nByte+50, zSql, "INSERT OR %q INTO '%q' VALUES(?",
-         zConflict, zTable);
-    j = strlen(zSql);
-    for(i=1; i<nCol; i++){
-      zSql[j++] = ',';
-      zSql[j++] = '?';
-    }
-    zSql[j++] = ')';
-    zSql[j] = 0;
-    rc = sqlite3_prepare(pDb->db, zSql, 0, &pStmt, 0);
-    free(zSql);
-    if( rc ){
-      Tcl_AppendResult(interp, "Error: ", sqlite3_errmsg(pDb->db), 0);
-      sqlite3_finalize(pStmt);
-      return TCL_ERROR;
-    }
-    in = fopen(zFile, "rb");
-    if( in==0 ){
-      Tcl_AppendResult(interp, "Error: cannot open file: ", zFile, NULL);
-      sqlite3_finalize(pStmt);
-      return TCL_ERROR;
-    }
-    azCol = malloc( sizeof(azCol[0])*(nCol+1) );
-    if( azCol==0 ) {
-      Tcl_AppendResult(interp, "Error: can't malloc()", 0);
-      return TCL_ERROR;
-    }
-    sqlite3_exec(pDb->db, "BEGIN", 0, 0, 0);
-    zCommit = "COMMIT";
-    while( (zLine = local_getline(0, in))!=0 ){
-      char *z;
-      i = 0;
-      lineno++;
-      azCol[0] = zLine;
-      for(i=0, z=zLine; *z; z++){
-        if( *z==zSep[0] && strncmp(z, zSep, nSep)==0 ){
-          *z = 0;
-          i++;
-          if( i<nCol ){
-            azCol[i] = &z[nSep];
-            z += nSep-1;
-          }
-        }
-      }
-      if( i+1!=nCol ){
-        char *zErr;
-        zErr = malloc(200 + strlen(zFile));
-        sprintf(zErr,"Error: %s line %d: expected %d columns of data but found %d",
-           zFile, lineno, nCol, i+1);
-        Tcl_AppendResult(interp, zErr, 0);
-        free(zErr);
-        zCommit = "ROLLBACK";
-        break;
-      }
-      for(i=0; i<nCol; i++){
-        /* check for null data, if so, bind as null */
-        if ((nNull>0 && strcmp(azCol[i], zNull)==0) || strlen(azCol[i])==0) {
-          sqlite3_bind_null(pStmt, i+1);
-        }else{
-          sqlite3_bind_text(pStmt, i+1, azCol[i], -1, SQLITE_STATIC);
-        }
-      }
-      sqlite3_step(pStmt);
-      rc = sqlite3_reset(pStmt);
-      free(zLine);
-      if( rc!=SQLITE_OK ){
-        Tcl_AppendResult(interp,"Error: ", sqlite3_errmsg(pDb->db), 0);
-        zCommit = "ROLLBACK";
-        break;
-      }
-    }
-    free(azCol);
-    fclose(in);
-    sqlite3_finalize(pStmt);
-    sqlite3_exec(pDb->db, zCommit, 0, 0, 0);
-
-    if( zCommit[0] == 'C' ){
-      /* success, set result as number of lines processed */
-      pResult = Tcl_GetObjResult(interp);
-      Tcl_SetIntObj(pResult, lineno);
-      rc = TCL_OK;
-    }else{
-      /* failure, append lineno where failed */
-      sprintf(zLineNum,"%d",lineno);
-      Tcl_AppendResult(interp,", failed while processing line: ",zLineNum,0);
-      rc = TCL_ERROR;
-    }
-    break;
-  }
-
   /*    $db version
   **
   ** Return the version string for this database.
diff --git a/src/test1.c b/src/test1.c
index e181f09..cd0c655 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -13,7 +13,7 @@
 ** is not included in the SQLite library.  It is used for automated
 ** testing of the SQLite library.
 **
-** $Id: test1.c,v 1.155 2005/08/19 00:14:42 drh Exp $
+** $Id: test1.c,v 1.156 2005/08/29 23:00:05 drh Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -2954,6 +2954,12 @@
   Tcl_SetVar2(interp, "sqlite_options", "threadsafe", "0", TCL_GLOBAL_ONLY);
 #endif
 
+#ifdef SQLITE_OMIT_TRACE
+  Tcl_SetVar2(interp, "sqlite_options", "trace", "0", TCL_GLOBAL_ONLY);
+#else
+  Tcl_SetVar2(interp, "sqlite_options", "trace", "1", TCL_GLOBAL_ONLY);
+#endif
+
 #ifdef SQLITE_OMIT_TRIGGER
   Tcl_SetVar2(interp, "sqlite_options", "trigger", "0", TCL_GLOBAL_ONLY);
 #else
diff --git a/src/vdbeInt.h b/src/vdbeInt.h
index 7bf2a0e..34e8d23 100644
--- a/src/vdbeInt.h
+++ b/src/vdbeInt.h
@@ -358,6 +358,7 @@
   u8 aborted;             /* True if ROLLBACK in another VM causes an abort */
   u8 expired;             /* True if the VM needs to be recompiled */
   int nChange;            /* Number of db changes made since last reset */
+  u64 startTime;          /* Time when query started - used for profiling */
 };
 
 /*
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index dfe8f12..75af8a9 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -15,6 +15,7 @@
 */
 #include "sqliteInt.h"
 #include "vdbeInt.h"
+#include "os.h"
 
 /*
 ** Return TRUE (non-zero) of the statement supplied as an argument needs
@@ -173,9 +174,10 @@
     return SQLITE_MISUSE;
   }
   if( p->pc<0 ){
+#ifndef SQLITE_OMIT_TRACE
     /* Invoke the trace callback if there is one
     */
-    if( (db = p->db)->xTrace && !db->init.busy ){
+    if( db->xTrace && !db->init.busy ){
       assert( p->nOp>0 );
       assert( p->aOp[p->nOp-1].opcode==OP_Noop );
       assert( p->aOp[p->nOp-1].p3!=0 );
@@ -187,6 +189,12 @@
         return SQLITE_MISUSE;
       }
     }
+    if( db->xProfile && !db->init.busy ){
+      double rNow;
+      sqlite3OsCurrentTime(&rNow);
+      p->startTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0;
+    }
+#endif
 
     /* Print a copy of SQL as it is executed if the SQL_TRACE pragma is turned
     ** on in debugging mode.
@@ -213,6 +221,23 @@
     rc = SQLITE_MISUSE;
   }
 
+#ifndef SQLITE_OMIT_TRACE
+  /* Invoke the profile callback if there is one
+  */
+  if( rc!=SQLITE_ROW && db->xProfile && !db->init.busy ){
+    double rNow;
+    u64 elapseTime;
+
+    sqlite3OsCurrentTime(&rNow);
+    elapseTime = (rNow - (int)rNow)*3600.0*24.0*1000000000.0 - p->startTime;
+    assert( p->nOp>0 );
+    assert( p->aOp[p->nOp-1].opcode==OP_Noop );
+    assert( p->aOp[p->nOp-1].p3!=0 );
+    assert( p->aOp[p->nOp-1].p3type==P3_DYNAMIC );
+    db->xProfile(db->pProfileArg, p->aOp[p->nOp-1].p3, elapseTime);
+  }
+#endif
+
   sqlite3Error(p->db, rc, p->zErrMsg ? "%s" : 0, p->zErrMsg);
   return rc;
 }