]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
First approximation of incremental blob IO API. (CVS 3892)
authordanielk1977 <danielk1977@noemail.net>
Tue, 1 May 2007 17:49:49 +0000 (17:49 +0000)
committerdanielk1977 <danielk1977@noemail.net>
Tue, 1 May 2007 17:49:49 +0000 (17:49 +0000)
FossilOrigin-Name: c444836e7b690c16dd6acff571c613a23beb42dc

main.mk
manifest
manifest.uuid
src/btree.c
src/btree.h
src/sqlite.h.in
src/tclsqlite.c
src/vdbeblob.c [new file with mode: 0644]
test/incrblob.test [new file with mode: 0644]
test/tclsqlite.test

diff --git a/main.mk b/main.mk
index eab250a4f8d66d7473bfabf3b543653ee22481d8..44cb08296c6da22645b877a892f097a898ab287c 100644 (file)
--- a/main.mk
+++ b/main.mk
@@ -62,7 +62,7 @@ LIBOBJ+= alter.o analyze.o attach.o auth.o btree.o build.o \
          pager.o parse.o pragma.o prepare.o printf.o random.o \
          select.o table.o tclsqlite.o tokenize.o trigger.o \
          update.o util.o vacuum.o \
-         vdbe.o vdbeapi.o vdbeaux.o vdbefifo.o vdbemem.o \
+         vdbe.o vdbeapi.o vdbeaux.o vdbeblob.o vdbefifo.o vdbemem.o \
          where.o utf.o legacy.o vtab.o
 
 # All of the source code files.
@@ -115,6 +115,7 @@ SRC = \
   $(TOP)/src/vdbe.h \
   $(TOP)/src/vdbeapi.c \
   $(TOP)/src/vdbeaux.c \
+  $(TOP)/src/vdbeblob.c \
   $(TOP)/src/vdbefifo.c \
   $(TOP)/src/vdbemem.c \
   $(TOP)/src/vdbeInt.h \
@@ -410,6 +411,9 @@ vdbeapi.o:  $(TOP)/src/vdbeapi.c $(VDBEHDR) $(HDR)
 vdbeaux.o:     $(TOP)/src/vdbeaux.c $(VDBEHDR) $(HDR)
        $(TCCX) -c $(TOP)/src/vdbeaux.c
 
+vdbeblob.o:    $(TOP)/src/vdbeblob.c $(VDBEHDR) $(HDR)
+       $(TCCX) -c $(TOP)/src/vdbeblob.c
+
 vdbefifo.o:    $(TOP)/src/vdbefifo.c $(VDBEHDR) $(HDR)
        $(TCCX) -c $(TOP)/src/vdbefifo.c
 
index a0407afcdad1b2404d118b7e22e5d3565c9d7f4c..d715b33c0bb8befe204e1c9a691a4486b3b7ca6f 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Modify\sloadSegmentLeavesInt()\sto\scorrectly\shandle\sprefix\ssearching.\nThe\snew\sfunction\sdocListUnion()\sis\sused\sto\saccumulate\sa\sunion\sof\sthe\nhits\sfor\sthe\smatching\sterms,\swhich\swill\sbe\smerged\sacross\ssegments\nusing\sdocListMerge().\s(CVS\s3891)
-D 2007-05-01T17:14:59
+C First\sapproximation\sof\sincremental\sblob\sIO\sAPI.\s(CVS\s3892)
+D 2007-05-01T17:49:49
 F Makefile.in 8cab54f7c9f5af8f22fd97ddf1ecfd1e1860de62
 F Makefile.linux-gcc 2d8574d1ba75f129aba2019f0b959db380a90935
 F README 9c4e2d6706bdcc3efdd773ce752a8cdab4f90028
@@ -44,7 +44,7 @@ F ext/fts2/fts2_tokenizer1.c 5c979fe8815f95396beb22b627571da895a025af
 F ext/fts2/mkfts2amal.tcl 2a9ec76b0760fe7f3669dca5bc0d60728bc1c977
 F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
 F ltmain.sh 56abb507100ed2d4261f6dd1653dec3cf4066387
-F main.mk dd14eeac33ac5e6c062f43e79876948f1d64908b
+F main.mk 2bc462dba2a8332a8a831f5ddd718b4ad7aac6c8
 F mkdll.sh ed62756baf44babf562a7843588790c02fee2106
 F mkopcodec.awk bd46ad001c98dfbab07b1713cb8e692fa0e5415d
 F mkopcodeh.awk cde995d269aa06c94adbf6455bea0acedb913fa5
@@ -59,8 +59,8 @@ F src/alter.c 2c79ec40f65e33deaf90ca493422c74586e481a3
 F src/analyze.c 4bbf5ddf9680587c6d4917e02e378b6037be3651
 F src/attach.c a16ada4a4654a0d126b8223ec9494ebb81bc5c3c
 F src/auth.c 902f4722661c796b97f007d9606bd7529c02597f
-F src/btree.c abdbd0261d2ab4fed65332cc93f959906c185d22
-F src/btree.h 4c0b5855cef3e4e6627358aa69541d21a2015947
+F src/btree.c 80814622b3e3d6b71e53a9286e1a8a2ea486da11
+F src/btree.h 9a219f01b732c8be4b3ccd8143881204da63e80f
 F src/build.c 02e01ec7907c7d947ab3041fda0e81eaed05db42
 F src/callback.c 6414ed32d55859d0f65067aa5b88d2da27b3af9e
 F src/complete.c 7d1a44be8f37de125fcafd3d3a018690b3799675
@@ -97,11 +97,11 @@ F src/random.c 6119474a6f6917f708c1dee25b9a8e519a620e88
 F src/select.c b914abca0ba28893e7fb7c7fb97a05e240e2ce8b
 F src/server.c 087b92a39d883e3fa113cae259d64e4c7438bc96
 F src/shell.c 3ae4654560e91220a95738a73d135d91d937cda1
-F src/sqlite.h.in e429f66f9245c7f8675db24b230c950b8672ad1c
+F src/sqlite.h.in 2dd7d439a1b991043388c7c37eb7db957d7276ff
 F src/sqlite3ext.h 7d0d363ea7327e817ef0dfe1b7eee1f171b72890
 F src/sqliteInt.h 0b14d0eae083aafca0562d2261a404e5e5abc5f0
 F src/table.c 6d0da66dde26ee75614ed8f584a1996467088d06
-F src/tclsqlite.c ec69eb9ad56d03fbf7570ca1ca5ea947d1ec4b6f
+F src/tclsqlite.c 82f7be1e8015ef224e2a9410a8f98dd6f61d64e9
 F src/test1.c f1271d41719d05348e6dc39722260e17b8d7ddc1
 F src/test2.c 24458b17ab2f3c90cbc1c8446bd7ffe69be62f88
 F src/test3.c 65f92247cf8592854e9bf5115b3fb711f8b33280
@@ -130,6 +130,7 @@ F src/vdbe.h 0025259af1939fb264a545816c69e4b5b8d52691
 F src/vdbeInt.h 4b19fd8febad3fd14c4c97adaefc06754d323132
 F src/vdbeapi.c 37fc2818bec64b361af73f3935699107bab0e625
 F src/vdbeaux.c ef59545f53f90394283f2fd003375d3ebbf0bd6e
+F src/vdbeblob.c 6d3128c71d5a6b8db627ea3052ed5aaaaf26e672
 F src/vdbefifo.c 3ca8049c561d5d67cbcb94dc909ae9bb68c0bf8f
 F src/vdbemem.c 981a113405bd9b80aeb71fe246a2f01708e8a8f7
 F src/vtab.c 89a0d5f39c1beba65a77fdb4d507b831fc5e6baf
@@ -238,6 +239,7 @@ F test/fts2m.test 4b30142ead6f3ed076e880a2a464064c5ad58c51
 F test/func.test 8a3bc8e8365dc0053c826923c0f738645f50f2f5
 F test/hook.test 7e7645fd9a033f79cce8fdff151e32715e7ec50a
 F test/in.test 369cb2aa1eab02296b4ec470732fe8c131260b1d
+F test/incrblob.test 86708ae039f564535e4200ac0e61dab5a6d6f18e
 F test/incrvacuum.test a4c9022d7b26b10495616cc5a255f11afb683be8
 F test/incrvacuum_ioerr.test 0ebc382bcc2036ec58cf49cc5ffada45f75d907b
 F test/index.test e65df12bed94b2903ee89987115e1578687e9266
@@ -329,7 +331,7 @@ F test/subselect.test 974e87f8fc91c5f00dd565316d396a5a6c3106c4
 F test/sync.test d05397b8f89f423dd6dba528692019ab036bc1c3
 F test/table.test feea6a3eb08cf166f570255eea5447e42ef82498
 F test/tableapi.test 036575a98dcce7c92e9f39056839bbad8a715412
-F test/tclsqlite.test 51334389283c74bcbe28645a73159b17e239e9f3
+F test/tclsqlite.test 726c301d35a2c1f4181fb772a607f785dd9e284e
 F test/temptable.test c36f3e5a94507abb64f7ba23deeb4e1a8a8c3821
 F test/tester.tcl dcebe3c5bf15f3b4ba015b4b2237030c1e384941
 F test/thread1.test 776c9e459b75ba905193b351926ac4019b049f35
@@ -466,7 +468,7 @@ F www/tclsqlite.tcl bb0d1357328a42b1993d78573e587c6dcbc964b9
 F www/vdbe.tcl 87a31ace769f20d3627a64fa1fade7fed47b90d0
 F www/version3.tcl 890248cf7b70e60c383b0e84d77d5132b3ead42b
 F www/whentouse.tcl fc46eae081251c3c181bd79c5faef8195d7991a5
-P e5e6af55ccc5c1a8a9206b42f1dd7bf547cb97ca
-R 2c25fe5630cee9d287b5318018624ff2
-U shess
-Z bcafc47e1cdd98b2861c3ec5b23e7e20
+P 72c796307338c2751a91c30f6fb16989afbf3816
+R 30bd64cea77836fbaf98d3678ccdaa09
+U danielk1977
+Z 5cdc2c295a9fcd37c90d369f1b123096
index c65caabc21c5d928f1c23c3b39405499df9e5e21..9801a2e4d066955e199febe55b5cbd5aa3ec6723 100644 (file)
@@ -1 +1 @@
-72c796307338c2751a91c30f6fb16989afbf3816
\ No newline at end of file
+c444836e7b690c16dd6acff571c613a23beb42dc
\ No newline at end of file
index 085d4e8085ef0b30b3f7f7bfeee4615ff56b21cb..b253395d971780e9d6d19c8502275071597b57c8 100644 (file)
@@ -9,7 +9,7 @@
 **    May you share freely, never taking more than you give.
 **
 *************************************************************************
-** $Id: btree.c,v 1.362 2007/04/30 16:55:01 danielk1977 Exp $
+** $Id: btree.c,v 1.363 2007/05/01 17:49:49 danielk1977 Exp $
 **
 ** This file implements a external (disk-based) database using BTrees.
 ** For a detailed discussion of BTrees, refer to
@@ -6810,6 +6810,61 @@ int sqlite3BtreeLockTable(Btree *p, int iTab, u8 isWriteLock){
 }
 #endif
 
+#ifndef SQLITE_OMIT_INCRBLOB
+/*
+** Argument pCsr must be a cursor opened for writing on an 
+** INTKEY table currently pointing at a valid table entry. 
+** This function modifies the data stored as part of that entry.
+** Only the data content may only be modified, it is not possible
+** to change the length of the data stored.
+*/
+int sqlite3BtreePutData(BtCursor *pCsr, u32 offset, u32 amt, const void *z){
+  /* TODO: The following is only a stop-gap implementation. It needs
+  ** to be made efficient using the optimistic overflow page trick. 
+  ** Similar changes need to be made to sqlite3BtreeData().
+  */
+  i64 iKey;
+  int rc;
+
+  int nCopy;
+  u32 nData;
+  char *zData;
+
+  rc = sqlite3BtreeKeySize(pCsr, &iKey);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+
+  rc = sqlite3BtreeDataSize(pCsr, &nData);
+  if( rc!=SQLITE_OK ){
+    return rc;
+  }
+
+  zData = sqliteMalloc(nData);
+  if( !zData ){
+    return SQLITE_NOMEM;
+  }
+
+  rc = sqlite3BtreeData(pCsr, 0, nData, (void *)zData);
+  if( rc!=SQLITE_OK ){
+    sqliteFree(zData);
+    return rc;
+  }
+
+  nCopy = amt;
+  if( nCopy>(nData-offset) ){
+    nCopy = nData-offset;
+  }
+  if( nCopy>0 ){
+    memcpy(&zData[offset], z, amt);
+    rc = sqlite3BtreeInsert(pCsr, 0, iKey, zData, nData, 0);
+  }
+
+  sqliteFree(zData);
+  return rc;
+}
+#endif
+
 /*
 ** The following debugging interface has to be in this file (rather
 ** than in, for example, test1.c) so that it can get access to
index 0d591ef46d57f504a85b28bc10bd3cd8e50ce956..40a885f51bedb686966460433c90f905966f42df 100644 (file)
@@ -13,7 +13,7 @@
 ** subsystem.  See comments in the source code for a detailed description
 ** of what each interface routine does.
 **
-** @(#) $Id: btree.h,v 1.75 2007/04/26 14:42:36 danielk1977 Exp $
+** @(#) $Id: btree.h,v 1.76 2007/05/01 17:49:49 danielk1977 Exp $
 */
 #ifndef _BTREE_H_
 #define _BTREE_H_
@@ -141,6 +141,7 @@ int sqlite3BtreeData(BtCursor*, u32 offset, u32 amt, void*);
 char *sqlite3BtreeIntegrityCheck(Btree*, int *aRoot, int nRoot, int, int*);
 struct Pager *sqlite3BtreePager(Btree*);
 
+int sqlite3BtreePutData(BtCursor*, u32 offset, u32 amt, const void*);
 
 #ifdef SQLITE_TEST
 int sqlite3BtreeCursorInfo(BtCursor*, int*, int);
index 9e16db17cd1ed01a831dfb797befecd711560d9f..c229a10434960f0ef12f868bc009f695d02eaf8a 100644 (file)
@@ -12,7 +12,7 @@
 ** This header file defines the interface that the SQLite library
 ** presents to client programs.
 **
-** @(#) $Id: sqlite.h.in,v 1.201 2007/03/30 20:43:42 drh Exp $
+** @(#) $Id: sqlite.h.in,v 1.202 2007/05/01 17:49:49 danielk1977 Exp $
 */
 #ifndef _SQLITE3_H_
 #define _SQLITE3_H_
@@ -1871,6 +1871,24 @@ int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg);
 ****** EXPERIMENTAL - subject to change without notice **************
 */
 
+typedef struct sqlite3_blob sqlite3_blob;
+
+int sqlite3_blob_open(
+  sqlite3*,
+  const char *zDb,
+  const char *zTable,
+  const char *zColumn,
+  sqlite_int64 iRow,
+  int flags,
+  sqlite3_blob **ppBlob
+);
+
+int sqlite3_blob_close(sqlite3_blob *);
+
+int sqlite3_blob_read(sqlite3_blob *, void *z, int n, int iOffset);
+int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
+int sqlite3_blob_bytes(sqlite3_blob *);
+
 /*
 ** Undo the hack that converts floating point types to integer for
 ** builds on processors without floating point support.
index 50b563f8df36f4494a3edefd76b3014dc1f50ed4..9364061831c3d2bb6bb3846dbe6bd8e803bbb010 100644 (file)
 ** A TCL Interface to SQLite.  Append this file to sqlite3.c and
 ** compile the whole thing to build a TCL-enabled version of SQLite.
 **
-** $Id: tclsqlite.c,v 1.179 2007/04/06 15:02:14 drh Exp $
+** $Id: tclsqlite.c,v 1.180 2007/05/01 17:49:49 danielk1977 Exp $
 */
 #include "tcl.h"
+#include <errno.h>
 
 /*
 ** Some additional include files are needed if this file is not
@@ -115,6 +116,178 @@ struct SqliteDb {
   int nStmt;                 /* Number of statements in stmtList */
 };
 
+typedef struct IncrblobChannel IncrblobChannel;
+struct IncrblobChannel {
+  sqlite3_blob *pBlob;
+  int iSeek;              /* Current seek offset */
+};
+
+/*
+** Close an incremental blob channel.
+*/
+static int incrblobClose(ClientData instanceData, Tcl_Interp *interp){
+  IncrblobChannel *p = (IncrblobChannel *)instanceData;
+  sqlite3_blob_close(p->pBlob);
+  Tcl_Free((char *)p);
+  return TCL_OK;
+}
+
+/*
+** Read data from an incremental blob channel.
+*/
+static int incrblobInput(
+  ClientData instanceData, 
+  char *buf, 
+  int bufSize,
+  int *errorCodePtr
+){
+  IncrblobChannel *p = (IncrblobChannel *)instanceData;
+  int nRead = bufSize;         /* Number of bytes to read */
+  int nBlob;                   /* Total size of the blob */
+  int rc;                      /* sqlite error code */
+
+  nBlob = sqlite3_blob_bytes(p->pBlob);
+  if( (p->iSeek+nRead)>nBlob ){
+    nRead = nBlob-p->iSeek;
+  }
+  if( nRead<=0 ){
+    return 0;
+  }
+
+  rc = sqlite3_blob_read(p->pBlob, (void *)buf, nRead, p->iSeek);
+  if( rc!=SQLITE_OK ){
+    *errorCodePtr = rc;
+    return -1;
+  }
+
+  p->iSeek += nRead;
+  return nRead;
+}
+
+static int incrblobOutput(
+  ClientData instanceData, 
+  CONST char *buf, 
+  int toWrite,
+  int *errorCodePtr
+){
+  IncrblobChannel *p = (IncrblobChannel *)instanceData;
+  int nWrite = toWrite;        /* Number of bytes to write */
+  int nBlob;                   /* Total size of the blob */
+  int rc;                      /* sqlite error code */
+
+  nBlob = sqlite3_blob_bytes(p->pBlob);
+  if( (p->iSeek+nWrite)>nBlob ){
+    *errorCodePtr = EINVAL;
+    return -1;
+  }
+  if( nWrite<=0 ){
+    return 0;
+  }
+
+  rc = sqlite3_blob_write(p->pBlob, (void *)buf, nWrite, p->iSeek);
+  if( rc!=SQLITE_OK ){
+    *errorCodePtr = EIO;
+    return -1;
+  }
+
+  p->iSeek += nWrite;
+  return nWrite;
+}
+
+/*
+** Seek an incremental blob channel.
+*/
+static int incrblobSeek(
+  ClientData instanceData, 
+  long offset,
+  int seekMode,
+  int *errorCodePtr
+){
+  IncrblobChannel *p = (IncrblobChannel *)instanceData;
+
+  switch( seekMode ){
+    case SEEK_SET:
+      p->iSeek = offset;
+      break;
+    case SEEK_CUR:
+      p->iSeek += offset;
+      break;
+    case SEEK_END:
+      p->iSeek = sqlite3_blob_bytes(p->pBlob) + offset;
+      break;
+
+    default: assert(!"Bad seekMode");
+  }
+
+  return p->iSeek;
+}
+
+
+static void incrblobWatch(ClientData instanceData, int mode){ 
+  /* NO-OP */ 
+}
+static int incrblobHandle(ClientData instanceData, int dir, ClientData *hPtr){
+  return TCL_ERROR;
+}
+
+static Tcl_ChannelType IncrblobChannelType = {
+  "incrblob",                        /* typeName                             */
+  TCL_CHANNEL_VERSION_2,             /* version                              */
+  incrblobClose,                     /* closeProc                            */
+  incrblobInput,                     /* inputProc                            */
+  incrblobOutput,                    /* outputProc                           */
+  incrblobSeek,                      /* seekProc                             */
+  0,                                 /* setOptionProc                        */
+  0,                                 /* getOptionProc                        */
+  incrblobWatch,                     /* watchProc (this is a no-op)          */
+  incrblobHandle,                    /* getHandleProc (always returns error) */
+  0,                                 /* close2Proc                           */
+  0,                                 /* blockModeProc                        */
+  0,                                 /* flushProc                            */
+  0,                                 /* handlerProc                          */
+  0,                                 /* wideSeekProc                         */
+  0,                                 /* threadActionProc                     */
+};
+
+/*
+** Create a new incrblob channel.
+*/
+static int createIncrblobChannel(
+  Tcl_Interp *interp, 
+  SqliteDb *pDb, 
+  const char *zDb,
+  const char *zTable, 
+  const char *zColumn, 
+  sqlite_int64 iRow
+){
+  IncrblobChannel *p;
+  sqlite3_blob *pBlob;
+  int rc;
+  Tcl_Channel channel;
+  int flags = TCL_READABLE|TCL_WRITABLE;
+
+  /* This variable is used to name the channels: "incrblob_[incr count]" */
+  static int count = 0;
+  char zChannel[64];
+
+  rc = sqlite3_blob_open(pDb->db, zDb, zTable, zColumn, iRow, 1, &pBlob);
+  if( rc!=SQLITE_OK ){
+    Tcl_SetResult(interp, (char *)sqlite3_errmsg(pDb->db), TCL_VOLATILE);
+    return TCL_ERROR;
+  }
+
+  p = (IncrblobChannel *)Tcl_Alloc(sizeof(IncrblobChannel));
+  p->iSeek = 0;
+  p->pBlob = pBlob;
+
+  sprintf(zChannel, "incrblob_%d", ++count);
+  channel = Tcl_CreateChannel(&IncrblobChannelType, zChannel, p, flags);
+  Tcl_RegisterChannel(interp, channel);
+
+  Tcl_SetResult(interp, (char *)Tcl_GetChannelName(channel), TCL_VOLATILE);
+  return TCL_OK;
+}
+
 /*
 ** Look at the script prefix in pCmd.  We will be executing this script
 ** after first appending one or more arguments.  This routine analyzes
@@ -676,6 +849,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     "collation_needed",   "commit_hook",       "complete",
     "copy",               "enable_load_extension","errorcode",
     "eval",               "exists",            "function",
+    "incrblob",
     "interrupt",          "last_insert_rowid", "nullvalue",
     "onecolumn",          "profile",           "progress",
     "rekey",              "rollback_hook",     "timeout",
@@ -688,6 +862,7 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     DB_COLLATION_NEEDED,  DB_COMMIT_HOOK,      DB_COMPLETE,
     DB_COPY,              DB_ENABLE_LOAD_EXTENSION,DB_ERRORCODE,
     DB_EVAL,              DB_EXISTS,           DB_FUNCTION,
+    DB_INCRBLOB,
     DB_INTERRUPT,         DB_LAST_INSERT_ROWID,DB_NULLVALUE,
     DB_ONECOLUMN,         DB_PROFILE,          DB_PROGRESS,
     DB_REKEY,             DB_ROLLBACK_HOOK,    DB_TIMEOUT,
@@ -1623,6 +1798,33 @@ static int DbObjCmd(void *cd, Tcl_Interp *interp, int objc,Tcl_Obj *const*objv){
     break;
   }
 
+  /*
+  **     $db incrblob ?DB? TABLE COLUMN ROWID
+  */
+  case DB_INCRBLOB: {
+    const char *zDb = "main";
+    const char *zTable;
+    const char *zColumn;
+    sqlite_int64 iRow;
+
+    if( objc!=5 && objc!=6 ){
+      Tcl_WrongNumArgs(interp, 2, objv, "?DB? TABLE ROWID");
+      return TCL_ERROR;
+    }
+
+    if( objc==6 ){
+      zDb = Tcl_GetString(objv[2]);
+    }
+    zTable = Tcl_GetString(objv[objc-3]);
+    zColumn = Tcl_GetString(objv[objc-2]);
+    rc = Tcl_GetWideIntFromObj(interp, objv[objc-1], &iRow);
+
+    if( rc==TCL_OK ){
+      rc = createIncrblobChannel(interp, pDb, zDb, zTable, zColumn, iRow);
+    }
+    break;
+  }
+
   /*
   **     $db interrupt
   **
diff --git a/src/vdbeblob.c b/src/vdbeblob.c
new file mode 100644 (file)
index 0000000..ddc81b6
--- /dev/null
@@ -0,0 +1,235 @@
+/*
+** 2007 May 1
+**
+** The author disclaims copyright to this source code.  In place of
+** a legal notice, here is a blessing:
+**
+**    May you do good and not evil.
+**    May you find forgiveness for yourself and forgive others.
+**    May you share freely, never taking more than you give.
+**
+*************************************************************************
+**
+** $Id: vdbeblob.c,v 1.1 2007/05/01 17:49:49 danielk1977 Exp $
+*/
+
+#include "sqliteInt.h"
+#include "vdbeInt.h"
+
+#ifndef SQLITE_OMIT_INCRBLOB
+
+/*
+** Valid sqlite3_blob* handles point to Incrblob structures.
+*/
+typedef struct Incrblob Incrblob;
+
+struct Incrblob {
+  int flags;              /* Copy of "flags" passed to sqlite3_blob_open() */
+  int nByte;              /* Size of open blob, in bytes */
+  int iOffset;            /* Byte offset of blob in cursor data */
+  BtCursor *pCsr;         /* Cursor pointing at blob row */
+  sqlite3_stmt *pStmt;    /* Statement holding cursor open */
+};
+
+/*
+** Open a blob handle.
+*/
+int sqlite3_blob_open(
+  sqlite3* db,
+  const char *zDb,
+  const char *zTable,
+  const char *zColumn,
+  sqlite_int64 iRow,
+  int flags,              /* True -> read/write access, false -> read-only */
+  sqlite3_blob **ppBlob
+){
+  int rc = SQLITE_OK;
+  int nAttempt = 0;
+  int iCol;               /* Index of zColumn in row-record */
+
+  /* This VDBE program seeks a btree cursor to the identified 
+  ** db/table/row entry. The reason for using a vdbe program instead
+  ** of writing code to use the b-tree layer directly is that the
+  ** vdbe program will take advantage of the various transaction,
+  ** locking and error handling infrastructure built into the vdbe.
+  **
+  ** After seeking the cursor, the vdbe executes an OP_Callback.
+  ** Code external to the Vdbe then "borrows" the b-tree cursor and
+  ** uses it to implement the blob_read(), blob_write() and 
+  ** blob_bytes() functions.
+  **
+  ** The sqlite3_blob_close() function finalizes the vdbe program,
+  ** which closes the b-tree cursor and (possibly) commits the 
+  ** transaction.
+  */
+  static const VdbeOpList openBlob[] = {
+    {OP_Transaction, 0, 0, 0},     /* 0: Start a transaction */
+    {OP_VerifyCookie, 0, 0, 0},    /* 1: Check the schema cookie */
+    {OP_Integer, 0, 0, 0},         /* 2: Database number */
+
+    /* One of the following two instructions is replaced by an
+    ** OP_Noop before exection.
+    */
+    {OP_OpenRead, 0, 0, 0},        /* 3: Open cursor 0 for reading */
+    {OP_OpenWrite, 0, 0, 0},       /* 4: Open cursor 0 for read/write */
+    {OP_SetNumColumns, 0, 0, 0},   /* 5: Num cols for cursor */
+
+    {OP_Variable, 1, 0, 0},        /* 6: Push the rowid to the stack */
+    {OP_NotExists, 0, 10, 0},      /* 7: Seek the cursor */
+    {OP_Column, 0, 0, 0},          /* 8  */
+    {OP_Callback, 0, 0, 0},        /* 9  */
+    {OP_Close, 0, 0, 0},           /* 10 */
+    {OP_Halt, 0, 0, 0},            /* 11 */
+  };
+
+  Vdbe *v = 0;
+
+  do {
+    Parse sParse;
+    Table *pTab;
+
+    memset(&sParse, 0, sizeof(Parse));
+    sParse.db = db;
+
+    pTab = sqlite3LocateTable(&sParse, zTable, zDb);
+    if( !pTab ){
+      sqlite3Error(db, sParse.rc, "%s", sParse.zErrMsg);
+      sqliteFree(sParse.zErrMsg);
+      rc = sParse.rc;
+      goto blob_open_out;
+    }
+
+    /* Now search pTab for the exact column. */
+    for(iCol=0; iCol < pTab->nCol; iCol++) {
+      if( sqlite3StrICmp(pTab->aCol[iCol].zName, zColumn)==0 ){
+        break;
+      }
+    }
+    if( iCol==pTab->nCol ){
+      sqlite3Error(db, SQLITE_ERROR, "no such column: %s", zColumn);
+      sqliteFree(sParse.zErrMsg);
+      rc = SQLITE_ERROR;
+      goto blob_open_out;
+    }
+
+    v = sqlite3VdbeCreate(db);
+    if( v ){
+      int iDb = sqlite3SchemaToIndex(db, pTab->pSchema);
+      sqlite3VdbeAddOpList(v, sizeof(openBlob)/sizeof(VdbeOpList), openBlob);
+
+      /* Configure the OP_Transaction */
+      sqlite3VdbeChangeP1(v, 0, iDb);
+      sqlite3VdbeChangeP2(v, 0, (flags ? 1 : 0));
+
+      /* Configure the OP_VerifyCookie */
+      sqlite3VdbeChangeP1(v, 1, iDb);
+      sqlite3VdbeChangeP2(v, 1, pTab->pSchema->schema_cookie);
+
+      /* Configure the db number pushed onto the stack */
+      sqlite3VdbeChangeP1(v, 2, iDb);
+
+      /* Remove either the OP_OpenWrite or OpenRead. Set the P2 parameter
+      ** of the other to pTab->tnum. 
+      */
+      sqlite3VdbeChangeToNoop(v, (flags ? 3 : 4), 1);
+      sqlite3VdbeChangeP2(v, (flags ? 4 : 3), pTab->tnum);
+
+      /* Configure the OP_SetNumColumns. Configure the cursor to
+      ** think that the table has one more column than it really
+      ** does. An OP_Column to retrieve this imaginary column will
+      ** always return an SQL NULL. This is useful because it means
+      ** we can invoke OP_Column to fill in the vdbe cursors type 
+      ** and offset cache without causing any IO.
+      */
+      sqlite3VdbeChangeP2(v, 5, pTab->nCol+1);
+      sqlite3VdbeMakeReady(v, 1, 0, 1, 0);
+    }
+
+    sqlite3_bind_int64((sqlite3_stmt *)v, 1, iRow);
+    rc = sqlite3_step((sqlite3_stmt *)v);
+    if( rc!=SQLITE_ROW ){
+      nAttempt++;
+      rc = sqlite3_finalize((sqlite3_stmt *)v);
+      v = 0;
+    }
+  } while( nAttempt<5 && rc==SQLITE_SCHEMA );
+
+  if( rc==SQLITE_ROW ){
+    /* The row-record has been opened successfully. Check that the
+    ** column in question contains text or a blob. If it contains
+    ** text, it is up to the caller to get the encoding right.
+    */
+    Incrblob *pBlob;
+    u32 type = v->apCsr[0]->aType[iCol];
+
+    if( type<12 ){
+      rc = SQLITE_ERROR;
+      goto blob_open_out;
+    }
+    pBlob = (Incrblob *)sqliteMalloc(sizeof(Incrblob));
+    if( sqlite3MallocFailed() ){
+      sqliteFree(pBlob);
+      goto blob_open_out;
+    }
+    pBlob->flags = flags;
+    pBlob->pCsr =  v->apCsr[0]->pCursor;
+    pBlob->pStmt = (sqlite3_stmt *)v;
+    pBlob->iOffset = v->apCsr[0]->aOffset[iCol];
+    pBlob->nByte = sqlite3VdbeSerialTypeLen(type);
+    *ppBlob = (sqlite3_blob *)pBlob;
+    rc = SQLITE_OK;
+  }else{
+    if( rc==SQLITE_DONE ){
+      rc = SQLITE_ERROR;
+    }
+  }
+
+blob_open_out:
+  if( rc!=SQLITE_OK || sqlite3MallocFailed() ){
+    sqlite3_finalize((sqlite3_stmt *)v);
+  }
+  sqlite3Error(db, rc, "");
+  return sqlite3ApiExit(db, rc);
+}
+
+/*
+** Close a blob handle.
+*/
+int sqlite3_blob_close(sqlite3_blob *pBlob){
+  Incrblob *p = (Incrblob *)pBlob;
+  sqlite3_finalize(p->pStmt);
+  sqliteFree(p);
+  return SQLITE_OK;
+}
+
+/*
+** Read data from a blob handle.
+*/
+int sqlite3_blob_read(sqlite3_blob *pBlob, void *z, int n, int iOffset){
+  Incrblob *p = (Incrblob *)pBlob;
+  if( (iOffset+n)>p->nByte ){
+    return SQLITE_ERROR;
+  }
+  return sqlite3BtreeData(p->pCsr, iOffset+p->iOffset, n, z);
+}
+
+/*
+** Write data to a blob handle.
+*/
+int sqlite3_blob_write(sqlite3_blob *pBlob, const void *z, int n, int iOffset){
+  Incrblob *p = (Incrblob *)pBlob;
+  if( (iOffset+n)>p->nByte ){
+    return SQLITE_ERROR;
+  }
+  return sqlite3BtreePutData(p->pCsr, iOffset+p->iOffset, n, z);
+}
+
+/*
+** Query a blob handle for the size of the data.
+*/
+int sqlite3_blob_bytes(sqlite3_blob *pBlob){
+  Incrblob *p = (Incrblob *)pBlob;
+  return p->nByte;
+}
+
+#endif /* #ifndef SQLITE_OMIT_INCRBLOB */
diff --git a/test/incrblob.test b/test/incrblob.test
new file mode 100644 (file)
index 0000000..3470f03
--- /dev/null
@@ -0,0 +1,53 @@
+# 2007 May 1
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#***********************************************************************
+#
+# $Id: incrblob.test,v 1.1 2007/05/01 17:49:49 danielk1977 Exp $
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+
+do_test incrblob-1.1 {
+  execsql {
+    CREATE TABLE blobs(k PRIMARY KEY, v BLOB);
+    INSERT INTO blobs VALUES('one', X'0102030405060708090A');
+    INSERT INTO blobs VALUES('two', X'0A090807060504030201');
+  }
+} {}
+
+do_test incrblob-1.2.1 {
+  set ::blob [db incrblob blobs v 1]
+} {incrblob_1}
+do_test incrblob-1.2.2 {
+  binary scan [read $::blob] c* data
+  set data
+} {1 2 3 4 5 6 7 8 9 10}
+do_test incrblob-1.2.3 {
+  seek $::blob 0
+  puts -nonewline $::blob "1234567890"
+  flush $::blob
+} {}
+do_test incrblob-1.2.4 {
+  seek $::blob 0
+  binary scan [read $::blob] c* data
+  set data
+} {49 50 51 52 53 54 55 56 57 48}
+do_test incrblob-1.2.5 {
+  close $::blob
+} {}
+do_test incrblob-1.2.6 {
+  execsql {
+    SELECT v FROM blobs WHERE rowid = 1;
+  }
+} {1234567890}
+
+finish_test
+
+
index cc5ce412a91e2922e7c52dd7f9d92bcf4e991fd8..bbeec53b126cdcc14368afb321c092c98072b76b 100644 (file)
@@ -15,7 +15,7 @@
 # interface is pretty well tested.  This file contains some addition
 # tests for fringe issues that the main test suite does not cover.
 #
-# $Id: tclsqlite.test,v 1.56 2006/09/01 15:49:06 drh Exp $
+# $Id: tclsqlite.test,v 1.57 2007/05/01 17:49:49 danielk1977 Exp $
 
 set testdir [file dirname $argv0]
 source $testdir/tester.tcl
@@ -34,7 +34,7 @@ do_test tcl-1.1 {
 do_test tcl-1.2 {
   set v [catch {db bogus} msg]
   lappend v $msg
-} {1 {bad option "bogus": must be authorizer, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, rollback_hook, timeout, total_changes, trace, transaction, update_hook, or version}}
+} {1 {bad option "bogus": must be authorizer, busy, cache, changes, close, collate, collation_needed, commit_hook, complete, copy, enable_load_extension, errorcode, eval, exists, function, incrblob, interrupt, last_insert_rowid, nullvalue, onecolumn, profile, progress, rekey, rollback_hook, timeout, total_changes, trace, transaction, update_hook, or version}}
 do_test tcl-1.3 {
   execsql {CREATE TABLE t1(a int, b int)}
   execsql {INSERT INTO t1 VALUES(10,20)}