From fa6bc0000fc5b52903cbcb36bacc78833d320e4e Mon Sep 17 00:00:00 2001
From: drh <drh@noemail.net>
Date: Tue, 7 Sep 2004 16:19:52 +0000
Subject: [PATCH] Wildcards with the same name map into the same variable
 number.  New api sqlite3_bind_parameter_index() added to map wildcard names
 into wildcard index numbers.  Support for "?nnn" wildcards. (CVS 1945)

FossilOrigin-Name: 435b3f301fbb6953adc974c7f03589b06e9114c3
---
 manifest         |  28 ++++++-------
 manifest.uuid    |   2 +-
 src/expr.c       |  71 +++++++++++++++++++++++++++++++-
 src/parse.y      |   6 +--
 src/sqlite.h.in  |  11 ++++-
 src/sqliteInt.h  |  11 ++++-
 src/test1.c      |  30 +++++++++++++-
 src/tokenize.c   |  24 ++++++-----
 src/vdbeapi.c    |  47 +++++++++++++++++----
 test/bind.test   | 104 ++++++++++++++++++++++++++++++++++++++++++++++-
 www/capi3ref.tcl |  13 +++++-
 11 files changed, 299 insertions(+), 48 deletions(-)

diff --git a/manifest b/manifest
index 9e4b1c1fd7..f584ab9a63 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Fix\sthe\sonecolumn\smethod\sin\sthe\sTCL\sinterface\sso\sthat\sit\sworks\sthe\ssame\nas\sthe\seval\smethod\sin\sall\sways\sexcept\sfor\sreturning\sjust\sthe\sfirst\svalue\nin\sthe\sresult\sset.\s(CVS\s1944)
-D 2004-09-07T13:20:35
+C Wildcards\swith\sthe\ssame\sname\smap\sinto\sthe\ssame\svariable\snumber.\s\sNew\napi\ssqlite3_bind_parameter_index()\sadded\sto\smap\swildcard\snames\sinto\nwildcard\sindex\snumbers.\s\sSupport\sfor\s"?nnn"\swildcards.\s(CVS\s1945)
+D 2004-09-07T16:19:53
 F Makefile.in 65a7c43fcaf9a710d62f120b11b6e435eeb4a450
 F Makefile.linux-gcc a9e5a0d309fa7c38e7c14d3ecf7690879d3a5457
 F README f1de682fbbd94899d50aca13d387d1b3fd3be2dd
@@ -34,7 +34,7 @@ F src/build.c c6940e4a663fa6b7dc3dff34f7dddbc45d331d48
 F src/date.c eb8d5fa1a6d5cfc09031c8852d10ff742a94b15b
 F src/delete.c e887f44aae1e33da1643df58abe86cd9cde45ad1
 F src/encode.c a876af473d1d636faa3dca51c7571f2e007eea37
-F src/expr.c 5b6881a229e49869c348825aa1f1af6bd6b4bc76
+F src/expr.c 9130794d8c86af2cbf2b8cdc66f2158167fd15b1
 F src/func.c 14bf20710a10fe66266e16da4364ca2dd8c4c36d
 F src/hash.c a97721a55440b7bea31ffe471bb2f6b4123cddd5
 F src/hash.h 1b0c445e1c89ff2aaad9b4605ba61375af001e84
@@ -54,22 +54,22 @@ F src/os_win.c 9e2887825b1a32f0ceb1b73b93ffe29a112cd86f
 F src/os_win.h babd4e912967c6b09088cfe38a45e8005a07ba44
 F src/pager.c ae06c85de0db43f61a7a3e5eacad3fd5615daf59
 F src/pager.h 67739fe649f33be55dba522ca8a9cc4e42d14f71
-F src/parse.y 581a2ce014b843506805b2470c02b7865ad034d5
+F src/parse.y 9389af67bd49b8e5c9d2968b3315a30565b4a200
 F src/pragma.c 6385059dfd77eee9fe7e53c0469776315f136ae0
 F src/printf.c 17b28a1eedfe8129b05de981719306c18c3f1327
 F src/random.c eff68e3f257e05e81eae6c4d50a51eb88beb4ff3
 F src/select.c 6e3ec12a01c6d5b51459d6ddaca36040d9e7730e
 F src/shell.c 4f1a2760ced81c829defb47b0a3b61ffec61b604
-F src/sqlite.h.in 8bdf3fc4c76040f939cb1831eb97babf6a2fa858
-F src/sqliteInt.h 0840e651db8e16f88f2b8a2393ac98dfdbf01df0
+F src/sqlite.h.in e29a526593b806f148017ed8bada760ada84cf2f
+F src/sqliteInt.h abbb66373b410e242b617af0364def4f7af084fc
 F src/table.c 8168c6e824009f8485bff79fc60ea8fea6829b10
 F src/tclsqlite.c 9225350a3144b3c0dd07a3cc88d2c219d57e2f0d
-F src/test1.c 0a7ae23d822177ecf3e8b577d026f0c8a39fe5c3
+F src/test1.c e7df5556002c50e917420f6f0edcc63047567659
 F src/test2.c f4c2f3928f1998fd8cb75a81e33a60e025ea85d4
 F src/test3.c 94d0a2a90bccd85802488cb42c69ec8afd2e4646
 F src/test4.c 7c6b9fc33dd1f3f93c7f1ee6e5e6d016afa6c1df
 F src/test5.c b001fa7f1b9e2dc5c2331de62fc641b5ab2bd7a1
-F src/tokenize.c 566ca7d1354dcb990475a52990056fc387d49df1
+F src/tokenize.c 60525d9966d90f548f44cc37116378379424c2a1
 F src/trigger.c 98dd620bebd9f3dadaacf0db3958b916cf1e7b7f
 F src/update.c 0e00300763d2ce0dbd6a0598882a5039580b225e
 F src/utf.c 328890099db492dda5620ee5f924e244c6e57ff7
@@ -78,7 +78,7 @@ F src/vacuum.c 819a3f411cb8d2d714e55f0805e8c23a642dd7ba
 F src/vdbe.c b19de04c57b4136a8e0203d5e3b76dd82bded1b5
 F src/vdbe.h 067ca8d6750ba4f69a50284765e5883dee860181
 F src/vdbeInt.h e09362d6323a725de3c30b0cc381a691e86ed697
-F src/vdbeapi.c e3fa5b775161bc8337c20f2e46a68bb4746b2094
+F src/vdbeapi.c 20bf8901592c7f38e8aabb448a913327ab19f0a7
 F src/vdbeaux.c 4080a6162d96818f875a95ead4f67cb9ca8ecf15
 F src/vdbemem.c ef9ac7d32acfe4bce5c5b408b1294c8d9e0cdb56
 F src/where.c 12e214870c84546858ddb9f121165a1fbfce6811
@@ -89,7 +89,7 @@ F test/attach3.test 6d060986ff004ebb89e1876a331d96c6bb62269e
 F test/auth.test e74b015545f608c06d5b84d17acdf7146eb818af
 F test/bigfile.test 62722ac4b420dfbcdceb137b8634e2cf2865fe27
 F test/bigrow.test f0aeb7573dcb8caaafea76454be3ade29b7fc747
-F test/bind.test 7968edd61eb83b6e09165e4ba38e327dace6fb35
+F test/bind.test d7e7f44817a6dbe0faedbb699ad2b559e5a3a7bb
 F test/blob.test 8727a7b46b2073a369cfc9bcb6f54dd366b9d884
 F test/btree.test 97b563e1ab999bf8764b129e8c4b4be0a116a52a
 F test/btree2.test aa4a6d05b1ea90b1acaf83ba89039dd302a88635
@@ -217,7 +217,7 @@ F www/arch2b.fig d22a2c9642d584b89d4088b1e51e2bb0f7c04bed
 F www/audit.tcl 90e09d580f79c7efec0c7d6f447b7ec5c2dce5c0
 F www/c_interface.tcl 83b39203e1ded4c2dab97f42edf31279a308efcb
 F www/capi3.tcl 5c1cb163f4d2a54e2d0e22dcc399dd71245c8b89
-F www/capi3ref.tcl bead38516efb0227ffa6ac76beb3f7ea405bb389
+F www/capi3ref.tcl aa6ea82ea34ff71357300b8f1ab9fd8232a9eec8
 F www/changes.tcl 3641bc28b86b40c82d546727da45ea0f0aa9a9f4
 F www/common.tcl f786e6be86fb2627ceb30e770e9efa83b9c67a3a
 F www/conflict.tcl fb8a2ba83746c7fdfd9e52fa7f6aaf5c422b8246
@@ -248,7 +248,7 @@ F www/tclsqlite.tcl 560ecd6a916b320e59f2917317398f3d59b7cc25
 F www/vdbe.tcl 59288db1ac5c0616296b26dce071c36cb611dfe9
 F www/version3.tcl 092a01f5ef430d2c4acc0ae558d74c4bb89638a0
 F www/whentouse.tcl a8335bce47cc2fddb07f19052cb0cb4d9129a8e4
-P d53047cbbc4e618c7bb5161b6f82876bb113db25
-R 303fc0394eb00108b3dd869bec641bd9
+P f323e4f86a08fe6448cbd4ff7cab459e8039d9f1
+R b14b673cf74913bc0aa77288120014ba
 U drh
-Z 3769d67a37ed8f4ac0e4d151ebaba45a
+Z abec07c7d353cf60af4ed426f1ba4bbf
diff --git a/manifest.uuid b/manifest.uuid
index 4d5283f9dd..ddef491877 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-f323e4f86a08fe6448cbd4ff7cab459e8039d9f1
\ No newline at end of file
+435b3f301fbb6953adc974c7f03589b06e9114c3
\ No newline at end of file
diff --git a/src/expr.c b/src/expr.c
index b72474a618..86cb54ad39 100644
--- a/src/expr.c
+++ b/src/expr.c
@@ -12,7 +12,7 @@
 ** This file contains routines used for analyzing expressions and
 ** for generating VDBE code that evaluates expressions in SQLite.
 **
-** $Id: expr.c,v 1.160 2004/09/06 17:24:13 drh Exp $
+** $Id: expr.c,v 1.161 2004/09/07 16:19:53 drh Exp $
 */
 #include "sqliteInt.h"
 #include <ctype.h>
@@ -273,6 +273,75 @@ Expr *sqlite3ExprFunction(ExprList *pList, Token *pToken){
   return pNew;
 }
 
+/*
+** Assign a variable number to an expression that encodes a wildcard
+** in the original SQL statement.  
+**
+** Wildcards consisting of a single "?" are assigned the next sequential
+** variable number.
+**
+** Wildcards of the form "?nnn" are assigned the number "nnn".  We make
+** sure "nnn" is not too be to avoid a denial of service attack when
+** the SQL statement comes from an external source.
+**
+** Wildcards of the form ":aaa" or "$aaa" are assigned the same number
+** as the previous instance of the same wildcard.  Or if this is the first
+** instance of the wildcard, the next sequenial variable number is
+** assigned.
+*/
+void sqlite3ExprAssignVarNumber(Parse *pParse, Expr *pExpr){
+  Token *pToken;
+  if( pExpr==0 ) return;
+  pToken = &pExpr->token;
+  assert( pToken->n>=1 );
+  assert( pToken->z!=0 );
+  assert( pToken->z[0]!=0 );
+  if( pToken->n==1 ){
+    /* Wildcard of the form "?".  Assign the next variable number */
+    pExpr->iTable = ++pParse->nVar;
+  }else if( pToken->z[0]=='?' ){
+    /* Wildcard of the form "?nnn".  Convert "nnn" to an integer and
+    ** use it as the variable number */
+    int i;
+    pExpr->iTable = i = atoi(&pToken->z[1]);
+    if( i<1 || i>SQLITE_MAX_VARIABLE_NUMBER ){
+      sqlite3ErrorMsg(pParse, "variable number must be between ?1 and ?%d",
+          SQLITE_MAX_VARIABLE_NUMBER);
+    }
+    if( i>pParse->nVar ){
+      pParse->nVar = i;
+    }
+  }else{
+    /* Wildcards of the form ":aaa" or "$aaa".  Reuse the same variable
+    ** number as the prior appearance of the same name, or if the name
+    ** has never appeared before, reuse the same variable number
+    */
+    int i, n;
+    n = pToken->n;
+    for(i=0; i<pParse->nVarExpr; i++){
+      Expr *pE;
+      if( (pE = pParse->apVarExpr[i])!=0
+          && pE->token.n==n
+          && memcmp(pE->token.z, pToken->z, n)==0 ){
+        pExpr->iTable = pE->iTable;
+        break;
+      }
+    }
+    if( i>=pParse->nVarExpr ){
+      pExpr->iTable = ++pParse->nVar;
+      if( pParse->nVarExpr>=pParse->nVarExprAlloc-1 ){
+        pParse->nVarExprAlloc += pParse->nVarExprAlloc + 10;
+        pParse->apVarExpr = sqliteRealloc(pParse->apVarExpr,
+                       pParse->nVarExprAlloc*sizeof(pParse->apVarExpr[0]) );
+      }
+      if( !sqlite3_malloc_failed ){
+        assert( pParse->apVarExpr!=0 );
+        pParse->apVarExpr[pParse->nVarExpr++] = pExpr;
+      }
+    }
+  } 
+}
+
 /*
 ** Recursively delete an expression tree.
 */
diff --git a/src/parse.y b/src/parse.y
index a22f9e5d14..37a864aac7 100644
--- a/src/parse.y
+++ b/src/parse.y
@@ -14,7 +14,7 @@
 ** the parser.  Lemon will also generate a header file containing
 ** numeric codes for all of the tokens.
 **
-** @(#) $Id: parse.y,v 1.135 2004/08/25 04:07:02 drh Exp $
+** @(#) $Id: parse.y,v 1.136 2004/09/07 16:19:54 drh Exp $
 */
 %token_prefix TK_
 %token_type {Token}
@@ -560,9 +560,7 @@ expr(A) ::= BLOB(X).         {A = sqlite3Expr(@X, 0, 0, &X);}
 expr(A) ::= VARIABLE(X).     {
   Token *pToken = &X;
   Expr *pExpr = A = sqlite3Expr(TK_VARIABLE, 0, 0, pToken);
-  if( pExpr ){
-    pExpr->iTable = ++pParse->nVar;
-  }
+  sqlite3ExprAssignVarNumber(pParse, pExpr);
 }
 expr(A) ::= ID(X) LP exprlist(Y) RP(E). {
   A = sqlite3ExprFunction(Y, &X);
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index ae4ce19789..218a91cd47 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.118 2004/09/06 17:34:13 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.119 2004/09/07 16:19:54 drh Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -640,12 +640,19 @@ int sqlite3_bind_parameter_count(sqlite3_stmt*);
 
 /*
 ** Return the name of the i-th parameter.  Ordinary wildcards "?" are
-** nameless and a NULL is returned.  For wildcards of the form :N: or
+** nameless and a NULL is returned.  For wildcards of the form :N or
 ** $vvvv the complete text of the wildcard is returned.
 ** NULL is returned if the index is out of range.
 */
 const char *sqlite3_bind_parameter_name(sqlite3_stmt*, int);
 
+/*
+** Return the index of a parameter with the given name.  The name
+** must match exactly.  If no parameter with the given name is found,
+** return 0.
+*/
+int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+
 /*
 ** Return the number of columns in the result set returned by the compiled
 ** SQL statement. This routine returns 0 if pStmt is an SQL statement
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index 28f9f36c18..0fd1029fa5 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -11,7 +11,7 @@
 *************************************************************************
 ** Internal interface definitions for SQLite.
 **
-** @(#) $Id: sqliteInt.h,v 1.319 2004/09/06 17:24:13 drh Exp $
+** @(#) $Id: sqliteInt.h,v 1.320 2004/09/07 16:19:54 drh Exp $
 */
 #ifndef _SQLITEINT_H_
 #define _SQLITEINT_H_
@@ -67,6 +67,11 @@
 */
 #define MAX_ATTACHED 10
 
+/*
+** The maximum value of a ?nnn wildcard that the parser will accept.
+*/
+#define SQLITE_MAX_VARIABLE_NUMBER 999
+
 /*
 ** When building SQLite for embedded systems where memory is scarce,
 ** you can define one or more of the following macros to omit extra
@@ -990,6 +995,9 @@ struct Parse {
   int nSet;            /* Number of sets used so far */
   int nAgg;            /* Number of aggregate expressions */
   int nVar;            /* Number of '?' variables seen in the SQL so far */
+  int nVarExpr;        /* Number of used slots in apVarExpr[] */
+  int nVarExprAlloc;   /* Number of allocated slots in apVarExpr[] */
+  Expr **apVarExpr;    /* Pointers to :aaa and $aaaa wildcard expressions */
   AggExpr *aAgg;       /* An array of aggregate expressions */
   const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
   Trigger *pNewTrigger;     /* Trigger under construct by a CREATE TRIGGER */
@@ -1209,6 +1217,7 @@ Expr *sqlite3Expr(int, Expr*, Expr*, Token*);
 Expr *sqlite3ExprAnd(Expr*, Expr*);
 void sqlite3ExprSpan(Expr*,Token*,Token*);
 Expr *sqlite3ExprFunction(ExprList*, Token*);
+void sqlite3ExprAssignVarNumber(Parse*, Expr*);
 void sqlite3ExprDelete(Expr*);
 ExprList *sqlite3ExprListAppend(ExprList*,Expr*,Token*);
 void sqlite3ExprListDelete(ExprList*);
diff --git a/src/test1.c b/src/test1.c
index cbd3e7372d..a8a8b17e0e 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.101 2004/09/06 17:24:13 drh Exp $
+** $Id: test1.c,v 1.102 2004/09/07 16:19:54 drh Exp $
 */
 #include "sqliteInt.h"
 #include "tcl.h"
@@ -1645,6 +1645,33 @@ static int test_bind_parameter_name(
   return TCL_OK;
 }
 
+/*
+** Usage:   sqlite3_bind_parameter_index  STMT  NAME
+**
+** Return the index of the wildcard called NAME.  Return 0 if there is
+** no such wildcard.
+*/
+static int test_bind_parameter_index(
+  void * clientData,
+  Tcl_Interp *interp,
+  int objc,
+  Tcl_Obj *CONST objv[]
+){
+  sqlite3_stmt *pStmt;
+
+  if( objc!=3 ){
+    Tcl_WrongNumArgs(interp, 1, objv, "STMT NAME");
+    return TCL_ERROR;
+  }
+  if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+  Tcl_SetObjResult(interp, 
+     Tcl_NewIntObj(
+       sqlite3_bind_parameter_index(pStmt,Tcl_GetString(objv[2]))
+     )
+  );
+  return TCL_OK;
+}
+
 /*
 ** Usage: sqlite3_errcode DB
 **
@@ -2463,6 +2490,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
      { "sqlite3_bind_blob",             test_bind_blob     ,0 },
      { "sqlite3_bind_parameter_count",  test_bind_parameter_count, 0},
      { "sqlite3_bind_parameter_name",   test_bind_parameter_name,  0},
+     { "sqlite3_bind_parameter_index",  test_bind_parameter_index, 0},
      { "sqlite3_errcode",               test_errcode       ,0 },
      { "sqlite3_errmsg",                test_errmsg        ,0 },
      { "sqlite3_errmsg16",              test_errmsg16      ,0 },
diff --git a/src/tokenize.c b/src/tokenize.c
index f3bda60144..7601cf34e8 100644
--- a/src/tokenize.c
+++ b/src/tokenize.c
@@ -15,7 +15,7 @@
 ** individual tokens and sends those tokens one-by-one over to the
 ** parser for analysis.
 **
-** $Id: tokenize.c,v 1.85 2004/09/06 17:24:13 drh Exp $
+** $Id: tokenize.c,v 1.86 2004/09/07 16:19:54 drh Exp $
 */
 #include "sqliteInt.h"
 #include "os.h"
@@ -371,7 +371,8 @@ static int sqliteGetToken(const unsigned char *z, int *tokenType){
     }
     case '?': {
       *tokenType = TK_VARIABLE;
-      return 1;
+      for(i=1; isdigit(z[i]); i++){}
+      return i;
     }
     case ':': {
       for(i=1; (z[i]&0x80)!=0 || isIdChar[z[i]]; i++){}
@@ -474,7 +475,13 @@ int sqlite3RunParser(Parse *pParse, const char *zSql, char **pzErrMsg){
     sqlite3SetString(pzErrMsg, "out of memory", (char*)0);
     return 1;
   }
-  pParse->sLastToken.dyn = 0;
+  assert( pParse->sLastToken.dyn==0 );
+  assert( pParse->pNewTable==0 );
+  assert( pParse->pNewTrigger==0 );
+  assert( pParse->nVar==0 );
+  assert( pParse->nVarExpr==0 );
+  assert( pParse->nVarExprAlloc==0 );
+  assert( pParse->apVarExpr==0 );
   pParse->zTail = pParse->zSql = zSql;
   while( sqlite3_malloc_failed==0 && zSql[i]!=0 ){
     assert( i>=0 );
@@ -541,14 +548,9 @@ abort_parse:
     sqlite3VdbeDelete(pParse->pVdbe);
     pParse->pVdbe = 0;
   }
-  if( pParse->pNewTable ){
-    sqlite3DeleteTable(pParse->db, pParse->pNewTable);
-    pParse->pNewTable = 0;
-  }
-  if( pParse->pNewTrigger ){
-    sqlite3DeleteTrigger(pParse->pNewTrigger);
-    pParse->pNewTrigger = 0;
-  }
+  sqlite3DeleteTable(pParse->db, pParse->pNewTable);
+  sqlite3DeleteTrigger(pParse->pNewTrigger);
+  sqliteFree(pParse->apVarExpr);
   if( nErr>0 && (pParse->rc==SQLITE_OK || pParse->rc==SQLITE_DONE) ){
     pParse->rc = SQLITE_ERROR;
   }
diff --git a/src/vdbeapi.c b/src/vdbeapi.c
index b238c7fe79..f5abe0ee66 100644
--- a/src/vdbeapi.c
+++ b/src/vdbeapi.c
@@ -525,16 +525,11 @@ int sqlite3_bind_parameter_count(sqlite3_stmt *pStmt){
 }
 
 /*
-** Return the name of a wildcard parameter.  Return NULL if the index
-** is out of range or if the wildcard is unnamed.
-**
-** The result is always UTF-8.
+** Create a mapping from variable numbers to variable names
+** in the Vdbe.azVar[] array, if such a mapping does not already
+** exist.
 */
-const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
-  Vdbe *p = (Vdbe*)pStmt;
-  if( p==0 || i<1 || i>p->nVar ){
-    return 0;
-  }
+static void createVarMap(Vdbe *p){
   if( !p->okVar ){
     int j;
     Op *pOp;
@@ -546,5 +541,39 @@ const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
     }
     p->okVar = 1;
   }
+}
+
+/*
+** Return the name of a wildcard parameter.  Return NULL if the index
+** is out of range or if the wildcard is unnamed.
+**
+** The result is always UTF-8.
+*/
+const char *sqlite3_bind_parameter_name(sqlite3_stmt *pStmt, int i){
+  Vdbe *p = (Vdbe*)pStmt;
+  if( p==0 || i<1 || i>p->nVar ){
+    return 0;
+  }
+  createVarMap(p);
   return p->azVar[i-1];
 }
+
+/*
+** Given a wildcard parameter name, return the index of the variable
+** with that name.  If there is no variable with the given name,
+** return 0.
+*/
+int sqlite3_bind_parameter_index(sqlite3_stmt *pStmt, const char *zName){
+  Vdbe *p = (Vdbe*)pStmt;
+  int i;
+  if( p==0 ){
+    return 0;
+  }
+  createVarMap(p); 
+  for(i=0; i<p->nVar; i++){
+    if( strcmp(p->azVar[i],zName)==0 ){
+      return i+1;
+    }
+  }
+  return 0;
+}
diff --git a/test/bind.test b/test/bind.test
index d5ea33f67c..f1c4d4c6d0 100644
--- a/test/bind.test
+++ b/test/bind.test
@@ -11,7 +11,7 @@
 # This file implements regression tests for SQLite library.  The
 # focus of this script testing the sqlite_bind API.
 #
-# $Id: bind.test,v 1.18 2004/08/25 04:07:03 drh Exp $
+# $Id: bind.test,v 1.19 2004/09/07 16:19:54 drh Exp $
 #
 
 set testdir [file dirname $argv0]
@@ -115,6 +115,18 @@ do_test bind-2.1.3 {
 do_test bind-2.1.4 {
   sqlite3_bind_parameter_name $VM 3
 } {${x}}
+do_test bind-2.1.5 {
+  sqlite3_bind_parameter_index $VM {$one}
+} 1
+do_test bind-2.1.6 {
+  sqlite3_bind_parameter_index $VM {$::two}
+} 2
+do_test bind-2.1.7 {
+  sqlite3_bind_parameter_index $VM {${x}}
+} 3
+do_test bind-2.1.8 {
+  sqlite3_bind_parameter_index $VM {:hi}
+} 0
 
 # 32 bit Integers
 do_test bind-2.2 {
@@ -280,10 +292,98 @@ do_test bind-8.15 {
   catch { sqlite3_bind_double $VM 4 6.0 }
 } {1}
 
-do_test bind-9.99 {
+do_test bind-8.99 {
   sqlite3_finalize $VM
 } SQLITE_OK
 
+do_test bind-9.1 {
+  execsql {
+    CREATE TABLE t2(a,b,c,d,e,f);
+  }
+  set rc [catch {
+    sqlite3_prepare $DB {
+      INSERT INTO t2(a) VALUES(?0)
+    } -1 TAIL
+  } msg]
+  lappend rc $msg
+} {1 {(1) variable number must be between ?1 and ?999}}
+do_test bind-9.2 {
+  set rc [catch {
+    sqlite3_prepare $DB {
+      INSERT INTO t2(a) VALUES(?1000)
+    } -1 TAIL
+  } msg]
+  lappend rc $msg
+} {1 {(1) variable number must be between ?1 and ?999}}
+do_test bind-9.3 {
+  set VM [
+    sqlite3_prepare $DB {
+      INSERT INTO t2(a,b) VALUES(?1,?999)
+    } -1 TAIL
+  ]
+  sqlite3_bind_parameter_count $VM
+} {999}
+catch {sqlite3_finalize $VM}
+do_test bind-9.4 {
+  set VM [
+    sqlite3_prepare $DB {
+      INSERT INTO t2(a,b,c,d) VALUES(?1,?999,?,?)
+    } -1 TAIL
+  ]
+  sqlite3_bind_parameter_count $VM
+} {1001}
+do_test bind-9.5 {
+  sqlite3_bind_int $VM 1 1
+  sqlite3_bind_int $VM 999 999
+  sqlite3_bind_int $VM 1000 1000
+  sqlite3_bind_int $VM 1001 1001
+  sqlite3_step $VM
+} SQLITE_DONE
+do_test bind-9.6 {
+  sqlite3_finalize $VM
+} SQLITE_OK
+do_test bind-9.7 {
+  execsql {SELECT * FROM t2}
+} {1 999 1000 1001 {} {}}
 
+do_test bind-10.1 {
+  catch {sqlite3_finalize $VM}
+  set VM [
+    sqlite3_prepare $DB {
+      INSERT INTO t2(a,b,c,d,e,f) VALUES(:abc,$abc,:abc,$ab,$abc,:abc)
+    } -1 TAIL
+  ]
+  sqlite3_bind_parameter_count $VM
+} 3
+do_test bind-10.2 {
+  sqlite3_bind_parameter_index $VM :abc
+} 1
+do_test bind-10.3 {
+  sqlite3_bind_parameter_index $VM {$abc}
+} 2
+do_test bind-10.4 {
+  sqlite3_bind_parameter_index $VM {$ab}
+} 3
+do_test bind-10.5 {
+  sqlite3_bind_parameter_name $VM 1
+} :abc
+do_test bind-10.6 {
+  sqlite3_bind_parameter_name $VM 2
+} {$abc}
+do_test bind-10.7 {
+  sqlite3_bind_parameter_name $VM 3
+} {$ab}
+do_test bind-10.8 {
+  sqlite3_bind_int $VM 1 1
+  sqlite3_bind_int $VM 2 2
+  sqlite3_bind_int $VM 3 3
+  sqlite3_step $VM
+} SQLITE_DONE
+do_test bind-10.9 {
+  sqlite3_finalize $VM
+} SQLITE_OK
+do_test bind-10.10 {
+  execsql {SELECT * FROM t2}
+} {1 999 1000 1001 {} {} 1 2 1 3 2 1}
 
 finish_test
diff --git a/www/capi3ref.tcl b/www/capi3ref.tcl
index 34ad945e21..df36e807c9 100644
--- a/www/capi3ref.tcl
+++ b/www/capi3ref.tcl
@@ -1,4 +1,4 @@
-set rcsid {$Id: capi3ref.tcl,v 1.10 2004/08/28 16:19:01 drh Exp $}
+set rcsid {$Id: capi3ref.tcl,v 1.11 2004/09/07 16:19:54 drh Exp $}
 source common.tcl
 header {C/C++ Interface For SQLite Version 3}
 puts {
@@ -124,13 +124,22 @@ api {} {
 } {
   Return the name of the n-th wildcard in the precompiled statement.
   Wildcards of the form ":AAA" have a name which is the string ":AAA".
-  Wildcards of the form "?" have no name.
+  Wildcards of the form "?" or "?NNN" have no name.
 
   If the value n is out of range or if the n-th wildcard is nameless,
   then NULL is returned.  The returned string is always in the
   UTF-8 encoding.
 }
 
+api {} {
+  int sqlite3_bind_parameter_index(sqlite3_stmt*, const char *zName);
+} {
+  Return the index of the wildcard with the given name.
+  The name must match exactly.
+  If there is no wildcard with the given name, return 0.
+  The string zName is always in the UTF-8 encoding.
+}
+
 api {} {
   int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
 } {