diff --git a/main.mk b/main.mk index eab250a4f8..44cb08296c 100644 --- 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 diff --git a/manifest b/manifest index a0407afcda..d715b33c0b 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index c65caabc21..9801a2e4d0 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -72c796307338c2751a91c30f6fb16989afbf3816 \ No newline at end of file +c444836e7b690c16dd6acff571c613a23beb42dc \ No newline at end of file diff --git a/src/btree.c b/src/btree.c index 085d4e8085..b253395d97 100644 --- a/src/btree.c +++ b/src/btree.c @@ -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 diff --git a/src/btree.h b/src/btree.h index 0d591ef46d..40a885f51b 100644 --- a/src/btree.h +++ b/src/btree.h @@ -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); diff --git a/src/sqlite.h.in b/src/sqlite.h.in index 9e16db17cd..c229a10434 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.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. diff --git a/src/tclsqlite.c b/src/tclsqlite.c index 50b563f8df..9364061831 100644 --- a/src/tclsqlite.c +++ b/src/tclsqlite.c @@ -12,9 +12,10 @@ ** 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 /* ** 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 index 0000000000..ddc81b669f --- /dev/null +++ b/src/vdbeblob.c @@ -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 index 0000000000..3470f03afa --- /dev/null +++ b/test/incrblob.test @@ -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 + + diff --git a/test/tclsqlite.test b/test/tclsqlite.test index cc5ce412a9..bbeec53b12 100644 --- a/test/tclsqlite.test +++ b/test/tclsqlite.test @@ -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)}