Add the experimental create_collation_x() api. (CVS 3934)

FossilOrigin-Name: ff49d48f2f025898a0f4ace1fc227e1d367ea89f
diff --git a/src/callback.c b/src/callback.c
index 87f5b24..f3d4188 100644
--- a/src/callback.c
+++ b/src/callback.c
@@ -13,7 +13,7 @@
 ** This file contains functions used to access the internal hash tables
 ** of user defined functions and collation sequences.
 **
-** $Id: callback.c,v 1.17 2007/04/16 15:06:25 danielk1977 Exp $
+** $Id: callback.c,v 1.18 2007/05/07 09:32:45 danielk1977 Exp $
 */
 
 #include "sqliteInt.h"
@@ -63,6 +63,7 @@
     pColl2 = sqlite3FindCollSeq(db, aEnc[i], z, n, 0);
     if( pColl2->xCmp!=0 ){
       memcpy(pColl, pColl2, sizeof(CollSeq));
+      pColl->xDel = 0;         /* Do not copy the destructor */
       return SQLITE_OK;
     }
   }
diff --git a/src/main.c b/src/main.c
index 38aa70b..bd72bdd 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.371 2007/05/06 16:04:12 danielk1977 Exp $
+** $Id: main.c,v 1.372 2007/05/07 09:32:45 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -182,6 +182,12 @@
 
   for(i=sqliteHashFirst(&db->aCollSeq); i; i=sqliteHashNext(i)){
     CollSeq *pColl = (CollSeq *)sqliteHashData(i);
+    /* Invoke any destructors registered for collation sequence user data. */
+    for(j=0; j<3; j++){
+      if( pColl[j].xDel ){
+        pColl[j].xDel(pColl[j].pUser);
+      }
+    }
     sqliteFree(pColl);
   }
   sqlite3HashClear(&db->aCollSeq);
@@ -825,7 +831,8 @@
   const char *zName, 
   int enc, 
   void* pCtx,
-  int(*xCompare)(void*,int,const void*,int,const void*)
+  int(*xCompare)(void*,int,const void*,int,const void*),
+  void(*xDel)(void*)
 ){
   CollSeq *pColl;
   int enc2;
@@ -860,12 +867,33 @@
       return SQLITE_BUSY;
     }
     sqlite3ExpirePreparedStatements(db);
+
+    /* If collation sequence pColl was created directly by a call to
+    ** sqlite3_create_collation, and not generated by synthCollSeq(),
+    ** then any copies made by synthCollSeq() need to be invalidated.
+    ** Also, collation destructor - CollSeq.xDel() - function may need
+    ** to be called.
+    */ 
+    if( (pColl->enc & ~SQLITE_UTF16_ALIGNED)==enc2 ){
+      CollSeq *aColl = sqlite3HashFind(&db->aCollSeq, zName, strlen(zName));
+      int j;
+      for(j=0; j<3; j++){
+        CollSeq *p = &aColl[j];
+        if( p->enc==pColl->enc ){
+          if( p->xDel ){
+            p->xDel(p->pUser);
+          }
+          p->xCmp = 0;
+        }
+      }
+    }
   }
 
   pColl = sqlite3FindCollSeq(db, (u8)enc2, zName, strlen(zName), 1);
   if( pColl ){
     pColl->xCmp = xCompare;
     pColl->pUser = pCtx;
+    pColl->xDel = xDel;
     pColl->enc = enc2 | (enc & SQLITE_UTF16_ALIGNED);
   }
   sqlite3Error(db, SQLITE_OK, 0);
@@ -915,9 +943,9 @@
   ** and UTF-16, so add a version for each to avoid any unnecessary
   ** conversions. The only error that can occur here is a malloc() failure.
   */
-  if( createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc) ||
-      createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc) ||
-      createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc) ||
+  if( createCollation(db, "BINARY", SQLITE_UTF8, 0, binCollFunc, 0) ||
+      createCollation(db, "BINARY", SQLITE_UTF16BE, 0, binCollFunc, 0) ||
+      createCollation(db, "BINARY", SQLITE_UTF16LE, 0, binCollFunc, 0) ||
       (db->pDfltColl = sqlite3FindCollSeq(db, SQLITE_UTF8, "BINARY", 6, 0))==0 
   ){
     assert( sqlite3MallocFailed() );
@@ -926,7 +954,7 @@
   }
 
   /* Also add a UTF-8 case-insensitive collation sequence. */
-  createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc);
+  createCollation(db, "NOCASE", SQLITE_UTF8, 0, nocaseCollatingFunc, 0);
 
   /* Set flags on the built-in collating sequences */
   db->pDfltColl->type = SQLITE_COLL_BINARY;
@@ -986,7 +1014,7 @@
 #endif
 
 #ifdef SQLITE_ENABLE_ICU
-  {
+  if( !sqlite3MallocFailed() ){
     extern int sqlite3IcuInit(sqlite3*);
     sqlite3IcuInit(db);
   }
@@ -1106,7 +1134,24 @@
 ){
   int rc;
   assert( !sqlite3MallocFailed() );
-  rc = createCollation(db, zName, enc, pCtx, xCompare);
+  rc = createCollation(db, zName, enc, pCtx, xCompare, 0);
+  return sqlite3ApiExit(db, rc);
+}
+
+/*
+** Register a new collation sequence with the database handle db.
+*/
+int sqlite3_create_collation_x(
+  sqlite3* db, 
+  const char *zName, 
+  int enc, 
+  void* pCtx,
+  int(*xCompare)(void*,int,const void*,int,const void*),
+  void(*xDel)(void*)
+){
+  int rc;
+  assert( !sqlite3MallocFailed() );
+  rc = createCollation(db, zName, enc, pCtx, xCompare, xDel);
   return sqlite3ApiExit(db, rc);
 }
 
@@ -1126,7 +1171,7 @@
   assert( !sqlite3MallocFailed() );
   zName8 = sqlite3utf16to8(zName, -1);
   if( zName8 ){
-    rc = createCollation(db, zName8, enc, pCtx, xCompare);
+    rc = createCollation(db, zName8, enc, pCtx, xCompare, 0);
     sqliteFree(zName8);
   }
   return sqlite3ApiExit(db, rc);
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 4af11e2..f84128c 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.204 2007/05/03 16:31:26 danielk1977 Exp $
+** @(#) $Id: sqlite.h.in,v 1.205 2007/05/07 09:32:45 danielk1977 Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -1280,6 +1280,18 @@
 );
 
 /*
+****** EXPERIMENTAL - subject to change without notice **************
+*/
+int sqlite3_create_collation_x(
+  sqlite3*, 
+  const char *zName, 
+  int eTextRep, 
+  void*,
+  int(*xCompare)(void*,int,const void*,int,const void*),
+  void(*xDel)(void*)
+);
+
+/*
 ** To avoid having to register all collation sequences before a database
 ** can be used, a single callback function may be registered with the
 ** database handle to be called whenever an undefined collation sequence is
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index c5917c6..374f1eb 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.557 2007/05/05 11:48:54 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.558 2007/05/07 09:32:45 danielk1977 Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -646,11 +646,12 @@
 ** collating sequence may not be read or written.
 */
 struct CollSeq {
-  char *zName;         /* Name of the collating sequence, UTF-8 encoded */
-  u8 enc;              /* Text encoding handled by xCmp() */
-  u8 type;             /* One of the SQLITE_COLL_... values below */
-  void *pUser;         /* First argument to xCmp() */
+  char *zName;          /* Name of the collating sequence, UTF-8 encoded */
+  u8 enc;               /* Text encoding handled by xCmp() */
+  u8 type;              /* One of the SQLITE_COLL_... values below */
+  void *pUser;          /* First argument to xCmp() */
   int (*xCmp)(void*,int, const void*, int, const void*);
+  void (*xDel)(void*);  /* Destructor for pUser */
 };
 
 /*
diff --git a/src/test1.c b/src/test1.c
index e4221b1..479390d 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.247 2007/05/06 16:04:12 danielk1977 Exp $
+** $Id: test1.c,v 1.248 2007/05/07 09:32:45 danielk1977 Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -1508,6 +1508,18 @@
 
 /*
 ** sqlite3_blob_read  CHANNEL OFFSET N
+**
+**   This command is used to test the sqlite3_blob_read() in ways that
+**   the Tcl channel interface does not. The first argument should
+**   be the name of a valid channel created by the [incrblob] method
+**   of a database handle. This function calls sqlite3_blob_read()
+**   to read N bytes from offset OFFSET from the underlying SQLite
+**   blob handle.
+**
+**   On success, a byte-array object containing the read data is 
+**   returned. On failure, the interpreter result is set to the
+**   text representation of the returned error code (i.e. "SQLITE_NOMEM")
+**   and a Tcl exception is thrown.
 */
 static int test_blob_read(
   ClientData clientData, /* Not used */
@@ -1555,6 +1567,17 @@
 
 /*
 ** sqlite3_blob_write CHANNEL OFFSET DATA
+**
+**   This command is used to test the sqlite3_blob_write() in ways that
+**   the Tcl channel interface does not. The first argument should
+**   be the name of a valid channel created by the [incrblob] method
+**   of a database handle. This function calls sqlite3_blob_write()
+**   to write the DATA byte-array to the underlying SQLite blob handle.
+**   at offset OFFSET.
+**
+**   On success, an empty string is returned. On failure, the interpreter
+**   result is set to the text representation of the returned error code 
+**   (i.e. "SQLITE_NOMEM") and a Tcl exception is thrown.
 */
 static int test_blob_write(
   ClientData clientData, /* Not used */
@@ -1599,6 +1622,82 @@
 #endif
 
 /*
+** Usage: sqlite3_create_collation_x DB-HANDLE NAME CMP-PROC DEL-PROC
+**
+**   This Tcl proc is used for testing the experimental
+**   sqlite3_create_collation_x() interface.
+*/
+struct TestCollationX {
+  Tcl_Interp *interp;
+  Tcl_Obj *pCmp;
+  Tcl_Obj *pDel;
+};
+typedef struct TestCollationX TestCollationX;
+static void testCreateCollationDel(void *pCtx){
+  TestCollationX *p = (TestCollationX *)pCtx;
+
+  int rc = Tcl_EvalObjEx(p->interp, p->pDel, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL);
+  if( rc!=TCL_OK ){
+    Tcl_BackgroundError(p->interp);
+  }
+
+  Tcl_DecrRefCount(p->pCmp);
+  Tcl_DecrRefCount(p->pDel);
+  sqlite3_free((void *)p);
+}
+static int testCreateCollationCmp(
+  void *pCtx,
+  int nLeft,
+  const void *zLeft,
+  int nRight,
+  const void *zRight
+){
+  TestCollationX *p = (TestCollationX *)pCtx;
+  Tcl_Obj *pScript = Tcl_DuplicateObj(p->pCmp);
+  int iRes = 0;
+
+  Tcl_IncrRefCount(pScript);
+  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj((char *)zLeft, nLeft));
+  Tcl_ListObjAppendElement(0, pScript, Tcl_NewStringObj((char *)zRight,nRight));
+
+  if( TCL_OK!=Tcl_EvalObjEx(p->interp, pScript, TCL_EVAL_DIRECT|TCL_EVAL_GLOBAL)
+   || TCL_OK!=Tcl_GetIntFromObj(p->interp, Tcl_GetObjResult(p->interp), &iRes)
+  ){
+    Tcl_BackgroundError(p->interp);
+  }
+  Tcl_DecrRefCount(pScript);
+
+  return iRes;
+}
+static int test_create_collation_x(
+  ClientData clientData, /* Not used */
+  Tcl_Interp *interp,    /* The TCL interpreter that invoked this command */
+  int objc,              /* Number of arguments */
+  Tcl_Obj *CONST objv[]  /* Command arguments */
+){
+  TestCollationX *p;
+  sqlite3 *db;
+
+  if( objc!=5 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "DB-HANDLE NAME CMP-PROC DEL-PROC");
+    return TCL_ERROR;
+  }
+  if( getDbPointer(interp, Tcl_GetString(objv[1]), &db) ) return TCL_ERROR;
+
+  p = (TestCollationX *)sqlite3_malloc(sizeof(TestCollationX));
+  p->pCmp = objv[3];
+  p->pDel = objv[4];
+  p->interp = interp;
+  Tcl_IncrRefCount(p->pCmp);
+  Tcl_IncrRefCount(p->pDel);
+
+  sqlite3_create_collation_x(db, Tcl_GetString(objv[2]), SQLITE_UTF8, 
+      (void *)p, testCreateCollationCmp, testCreateCollationDel
+  );
+  return TCL_OK;
+}
+
+/*
 ** Usage: sqlite3_load_extension DB-HANDLE FILE ?PROC?
 */
 static int test_load_extension(
@@ -4617,8 +4716,9 @@
 {"sqlite3_column_origin_name16", test_stmt_utf16, sqlite3_column_origin_name16},
 #endif
 #endif
-     { "sqlite3_global_recover",    test_global_recover, 0   },
-     { "working_64bit_int",         working_64bit_int,   0   },
+     { "sqlite3_create_collation_x", test_create_collation_x, 0 },
+     { "sqlite3_global_recover",     test_global_recover, 0   },
+     { "working_64bit_int",          working_64bit_int,   0   },
 
      /* Functions from os.h */
 #ifndef SQLITE_OMIT_DISKIO