First approximation of incremental blob IO API. (CVS 3892)
FossilOrigin-Name: c444836e7b690c16dd6acff571c613a23beb42dc
This commit is contained in:
parent
cc3e986643
commit
b4e9af9ff9
6
main.mk
6
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
|
||||
|
||||
|
26
manifest
26
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
|
||||
|
@ -1 +1 @@
|
||||
72c796307338c2751a91c30f6fb16989afbf3816
|
||||
c444836e7b690c16dd6acff571c613a23beb42dc
|
57
src/btree.c
57
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
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
204
src/tclsqlite.c
204
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 <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
|
||||
**
|
||||
|
235
src/vdbeblob.c
Normal file
235
src/vdbeblob.c
Normal file
@ -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 */
|
53
test/incrblob.test
Normal file
53
test/incrblob.test
Normal file
@ -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
|
||||
|
||||
|
@ -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)}
|
||||
|
Loading…
x
Reference in New Issue
Block a user