Index: src/sqlite.h.in
==================================================================
--- src/sqlite.h.in
+++ src/sqlite.h.in
@@ -6136,29 +6136,41 @@
** interface fixed, support it indefinitely, and remove this comment.
*/
/*
** CAPI3REF: A Handle To An Open BLOB
-** KEYWORDS: {BLOB handle} {BLOB handles}
+** KEYWORDS: {BLOB handle} {BLOB handles} {sqlite3_blob object}
**
-** An instance of this object represents an open BLOB on which
-** [sqlite3_blob_open | incremental BLOB I/O] can be performed.
+** An instance of this object represents a connection to a single
+** column in a single row of a table that holds either a BLOB or string
+** and on which [sqlite3_blob_open | incremental BLOB I/O] can be performed.
+**
** ^Objects of this type are created by [sqlite3_blob_open()]
** and destroyed by [sqlite3_blob_close()].
** ^The [sqlite3_blob_read()] and [sqlite3_blob_write()] interfaces
** can be used to read or write small subsections of the BLOB.
** ^The [sqlite3_blob_bytes()] interface returns the size of the BLOB in bytes.
+**
+** An sqlite3_blob object can be in two states: ACTIVE and RESET. When in
+** the ACTIVE state, the object is pointing to a specific entry and is
+** ready to do I/O. When RESET, the sqlite3_blob object is in standby mode,
+** is not associated with any particular row of its table,
+** and is not available for I/O.
+**
+** The sqlite3_blob object does not contain a mutex and so a single
+** sqlite3_blob object may not be safely used by multiple threads
+** concurrently.
*/
typedef struct sqlite3_blob sqlite3_blob;
/*
** CAPI3REF: Open A BLOB For Incremental I/O
** METHOD: sqlite3
** CONSTRUCTOR: sqlite3_blob
**
-** ^(This interfaces opens a [BLOB handle | handle] to the BLOB located
-** in row iRow, column zColumn, table zTable in database zDb;
+** ^(This interfaces creates an sqlite3_blob object pointing to the string
+** or BLOB located in row iRow, column zColumn, table zTable in database zDb;
** in other words, the same BLOB that would be selected by:
**
**
** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow;
**
)^
@@ -6171,14 +6183,15 @@
**
** ^If the flags parameter is non-zero, then the BLOB is opened for read
** and write access. ^If the flags parameter is zero, the BLOB is opened for
** read-only access.
**
-** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored
-** in *ppBlob. Otherwise an [error code] is returned and, unless the error
-** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided
-** the API is not misused, it is always safe to call [sqlite3_blob_close()]
+** ^(On success, [SQLITE_OK] is returned and the new [sqlite3_blob object]
+** is stored in *ppBlob. Otherwise an [error code] is returned and
+** *ppBlob is set to NULL.)^ ^Because *ppBlob is always set to either a
+** valid sqlite3_blob object or NULL
+** it is always safe to call [sqlite3_blob_close()]
** on *ppBlob after this function it returns.
**
** This function fails with SQLITE_ERROR if any of the following are true:
**
** - ^(Database zDb does not exist)^,
@@ -6206,11 +6219,12 @@
** interface. However, the column, table, or database of a [BLOB handle]
** cannot be changed after the [BLOB handle] is opened.
**
** ^(If the row that a BLOB handle points to is modified by an
** [UPDATE], [DELETE], or by [ON CONFLICT] side-effects
-** then the BLOB handle is marked as "expired".
+** then the BLOB handle is marked as "expired" and cannot be used
+** successfully with first being [sqlite3_blob_reopen|reopened].
** This is true if any column of the row is changed, even a column
** other than the one the BLOB handle is open on.)^
** ^Calls to [sqlite3_blob_read()] and [sqlite3_blob_write()] for
** an expired BLOB handle fail with a return code of [SQLITE_ABORT].
** ^(Changes written into a BLOB prior to the BLOB expiring are not
@@ -6218,11 +6232,12 @@
** commit if the transaction continues to completion.)^
**
** ^Use the [sqlite3_blob_bytes()] interface to determine the size of
** the opened blob. ^The size of a blob may not be changed by this
** interface. Use the [UPDATE] SQL command to change the size of a
-** blob.
+** blob. The [sqlite3_blob_bytes()] interface returns an arbitrary
+** number when used on an expired sqlite3_blob object.
**
** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces
** and the built-in [zeroblob] SQL function may be used to create a
** zero-filled blob to read or write using the incremental-blob interface.
**
@@ -6229,11 +6244,12 @@
** To avoid a resource leak, every open [BLOB handle] should eventually
** be released by a call to [sqlite3_blob_close()].
**
** See also: [sqlite3_blob_close()],
** [sqlite3_blob_reopen()], [sqlite3_blob_read()],
-** [sqlite3_blob_bytes()], [sqlite3_blob_write()].
+** [sqlite3_blob_bytes()], [sqlite3_blob_write()],
+** [sqlite3_blob_reset()].
*/
int sqlite3_blob_open(
sqlite3*,
const char *zDb,
const char *zTable,
@@ -6242,67 +6258,80 @@
int flags,
sqlite3_blob **ppBlob
);
/*
-** CAPI3REF: Move a BLOB Handle to a New Row
+** CAPI3REF: Move an sqlite3_blob object to a New Row
** METHOD: sqlite3_blob
**
-** ^This function is used to move an existing [BLOB handle] so that it points
+** ^This function updates an existing [sqlite3_blob object] so that it points
** to a different row of the same database table. ^The new row is identified
** by the rowid value passed as the second argument. Only the row can be
** changed. ^The database, table and column on which the blob handle is open
-** remain the same. Moving an existing [BLOB handle] to a new row is
+** remain the same. Moving an existing [sqlite3_blob object] to a new row is
** faster than closing the existing handle and opening a new one.
**
** ^(The new row must meet the same criteria as for [sqlite3_blob_open()] -
** it must exist and there must be either a blob or text value stored in
** the nominated column.)^ ^If the new row is not present in the table, or if
** it does not contain a blob or text value, or if another error occurs, an
-** SQLite error code is returned and the blob handle is considered aborted.
-** ^All subsequent calls to [sqlite3_blob_read()], [sqlite3_blob_write()] or
-** [sqlite3_blob_reopen()] on an aborted blob handle immediately return
-** SQLITE_ABORT. ^Calling [sqlite3_blob_bytes()] on an aborted blob handle
+** SQLite error code is returned and the [sqlite3_blob object] is changed
+** to the RESET state.
+** ^Calling [sqlite3_blob_bytes()] on an aborted blob handle
** always returns zero.
**
** ^This function sets the database handle error code and message.
*/
int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64);
/*
-** CAPI3REF: Close A BLOB Handle
+** CAPI3REF: Close an sqlite3_blob object
** DESTRUCTOR: sqlite3_blob
**
-** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed
+** ^This function is the destructor for an [sqlite3_blob object].
+** ^(The [sqlite3_blob object] is closed
** unconditionally. Even if this routine returns an error code, the
-** handle is still closed.)^
+** object is still closed.)^
**
-** ^If the blob handle being closed was opened for read-write access, and if
-** the database is in auto-commit mode and there are no other open read-write
+** ^If the sqlite3_blob object being closed was opened for read-write access,
+** and is in the ACTIVE state,
+** and if the database is in auto-commit mode and there are no other open
+** read-write
** blob handles or active write statements, the current transaction is
** committed. ^If an error occurs while committing the transaction, an error
** code is returned and the transaction rolled back.
**
-** Calling this function with an argument that is not a NULL pointer or an
-** open blob handle results in undefined behaviour. ^Calling this routine
+** Calling this function with an argument that is not a NULL pointer or a
+** valid sqlite3_blob object pointer results in undefined behaviour.
+** ^Calling this routine
** with a null pointer (such as would be returned by a failed call to
** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function
-** is passed a valid open blob handle, the values returned by the
+** is passed a valid sqlite3_blob objecct pointer, the values returned by the
** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning.
*/
int sqlite3_blob_close(sqlite3_blob *);
/*
-** CAPI3REF: Return The Size Of An Open BLOB
+** CAPI3REF: Reset an sqlite3_blob object
+** METHOD: sqlite3_blob
+**
+** ^This function changes an [sqlite3_blob object] to the RESET state.
+** ^If the [sqlite3_blob object] was already in the RESET state, then this
+** routine is a harmless no-op.
+*/
+int sqlite3_blob_reset(sqlite3_blob *);
+
+/*
+** CAPI3REF: Return The Size Of A BLOB
** METHOD: sqlite3_blob
**
-** ^Returns the size in bytes of the BLOB accessible via the
-** successfully opened [BLOB handle] in its only argument. ^The
+** ^Returns the size in bytes of the BLOB or string accessible via the
+** ACTIVE [sqlite3_blob object] in its only argument. ^The
** incremental blob I/O routines can only read or overwriting existing
-** blob content; they cannot change the size of a blob.
+** content; they cannot change the size of a blob or string.
**
-** This routine only works on a [BLOB handle] which has been created
+** This routine only works on an [sqlite3_blob object] that has been created
** by a prior successful call to [sqlite3_blob_open()] and which has not
** been closed by [sqlite3_blob_close()]. Passing any other pointer in
** to this routine results in undefined and probably undesirable behavior.
*/
int sqlite3_blob_bytes(sqlite3_blob *);
@@ -6309,42 +6338,40 @@
/*
** CAPI3REF: Read Data From A BLOB Incrementally
** METHOD: sqlite3_blob
**
-** ^(This function is used to read data from an open [BLOB handle] into a
-** caller-supplied buffer. N bytes of data are copied into buffer Z
-** from the open BLOB, starting at offset iOffset.)^
+** ^(This function is used to read data from string or BLOB that an
+** [sqlite3_blob object] is pointing to into a caller-supplied buffer.
+** N bytes of data are copied into buffer Z starting at offset iOffset.)^
**
** ^If offset iOffset is less than N bytes from the end of the BLOB,
** [SQLITE_ERROR] is returned and no data is read. ^If N or iOffset is
** less than zero, [SQLITE_ERROR] is returned and no data is read.
** ^The size of the blob (and hence the maximum value of N+iOffset)
** can be determined using the [sqlite3_blob_bytes()] interface.
**
-** ^An attempt to read from an expired [BLOB handle] fails with an
+** ^An attempt to read from an [sqlite3_blob object] that is in the RESET
+** state, or from a database row that has changed since the most recent
+** call to [sqlite3_blob_open()] or [sqlite3_blob_reopen()] fails with an
** error code of [SQLITE_ABORT].
**
** ^(On success, sqlite3_blob_read() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
**
-** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
-** to this routine results in undefined and probably undesirable behavior.
-**
** See also: [sqlite3_blob_write()].
*/
int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset);
/*
** CAPI3REF: Write Data Into A BLOB Incrementally
** METHOD: sqlite3_blob
**
-** ^(This function is used to write data into an open [BLOB handle] from a
-** caller-supplied buffer. N bytes of data are copied from the buffer Z
-** into the open BLOB, starting at offset iOffset.)^
+** ^(This function is used to write data from a caller-supplied buffer
+** into a BLOB or string pointed to by an [sqlite3_blob object].
+** N bytes of data are copied from the buffer Z
+** into the BLOB or string, starting at offset iOffset.)^
**
** ^(On success, sqlite3_blob_write() returns SQLITE_OK.
** Otherwise, an [error code] or an [extended error code] is returned.)^
** ^Unless SQLITE_MISUSE is returned, this function sets the
** [database connection] error code and message accessible via
@@ -6360,21 +6387,14 @@
** [SQLITE_ERROR] is returned and no data is written. The size of the
** BLOB (and hence the maximum value of N+iOffset) can be determined
** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less
** than zero [SQLITE_ERROR] is returned and no data is written.
**
-** ^An attempt to write to an expired [BLOB handle] fails with an
-** error code of [SQLITE_ABORT]. ^Writes to the BLOB that occurred
-** before the [BLOB handle] expired are not rolled back by the
-** expiration of the handle, though of course those changes might
-** have been overwritten by the statement that expired the BLOB handle
-** or by other independent statements.
-**
-** This routine only works on a [BLOB handle] which has been created
-** by a prior successful call to [sqlite3_blob_open()] and which has not
-** been closed by [sqlite3_blob_close()]. Passing any other pointer in
-** to this routine results in undefined and probably undesirable behavior.
+** ^An attempt to write into an [sqlite3_blob object] that is in the RESET
+** state, or into a database row that has changed since the most recent
+** call to [sqlite3_blob_open()] or [sqlite3_blob_reopen()] fails with an
+** error code of [SQLITE_ABORT].
**
** See also: [sqlite3_blob_read()].
*/
int sqlite3_blob_write(sqlite3_blob *, const void *z, int n, int iOffset);
Index: src/vdbeblob.c
==================================================================
--- src/vdbeblob.c
+++ src/vdbeblob.c
@@ -21,20 +21,19 @@
/*
** Valid sqlite3_blob* handles point to Incrblob structures.
*/
typedef struct Incrblob Incrblob;
struct Incrblob {
- int nByte; /* Size of open blob, in bytes */
+ int nByte; /* Size of open blob if ACTIVE. -1 if RESET */
int iOffset; /* Byte offset of blob in cursor data */
u16 iCol; /* Table column this handle is open on */
BtCursor *pCsr; /* Cursor pointing at blob row */
sqlite3_stmt *pStmt; /* Statement holding cursor open */
sqlite3 *db; /* The associated database */
char *zDb; /* Database name */
Table *pTab; /* Table object */
};
-
/*
** This function is used by both blob_open() and blob_reopen(). It seeks
** the b-tree cursor associated with blob handle p to point to row iRow.
** If successful, SQLITE_OK is returned and subsequent calls to
@@ -80,12 +79,12 @@
if( type<12 ){
zErr = sqlite3MPrintf(p->db, "cannot open value of type %s",
type==0?"null": type==7?"real": "integer"
);
rc = SQLITE_ERROR;
- sqlite3_finalize(p->pStmt);
- p->pStmt = 0;
+ sqlite3_reset(p->pStmt);
+ p->nByte = -1;
}else{
p->iOffset = pC->aType[p->iCol + pC->nField];
p->nByte = sqlite3VdbeSerialTypeLen(type);
p->pCsr = pC->uc.pCursor;
sqlite3BtreeIncrblobCursor(p->pCsr);
@@ -92,13 +91,13 @@
}
}
if( rc==SQLITE_ROW ){
rc = SQLITE_OK;
- }else if( p->pStmt ){
- rc = sqlite3_finalize(p->pStmt);
- p->pStmt = 0;
+ }else if( p->nByte>=0 ){
+ rc = sqlite3_reset(p->pStmt);
+ p->nByte = -1;
if( rc==SQLITE_OK ){
zErr = sqlite3MPrintf(p->db, "no such rowid: %lld", iRow);
rc = SQLITE_ERROR;
}else{
zErr = sqlite3MPrintf(p->db, "%s", sqlite3_errmsg(p->db));
@@ -155,10 +154,11 @@
do {
memset(pParse, 0, sizeof(Parse));
pParse->db = db;
sqlite3DbFree(db, zErr);
zErr = 0;
+ sqlite3_finalize(pBlob->pStmt);
sqlite3BtreeEnterAll(db);
pTab = sqlite3LocateTable(pParse, 0, zTable, zDb);
if( pTab && IsVirtual(pTab) ){
pTab = 0;
@@ -322,10 +322,11 @@
}
}
pBlob->iCol = iCol;
pBlob->db = db;
+ pBlob->nByte = 0;
sqlite3BtreeLeaveAll(db);
if( db->mallocFailed ){
goto blob_open_out;
}
rc = blobSeekToRow(pBlob, iRow, &zErr);
@@ -357,11 +358,11 @@
sqlite3 *db;
if( p ){
db = p->db;
sqlite3_mutex_enter(db->mutex);
- rc = sqlite3_finalize(p->pStmt);
+ rc = sqlite3VdbeFinalize((Vdbe*)p->pStmt);
sqlite3DbFree(db, p);
sqlite3_mutex_leave(db->mutex);
}else{
rc = SQLITE_OK;
}
@@ -382,22 +383,21 @@
Incrblob *p = (Incrblob *)pBlob;
Vdbe *v;
sqlite3 *db;
if( p==0 ) return SQLITE_MISUSE_BKPT;
+ if( p->nByte<0 ){
+ /* The blob handle is in the RESET state. Always return SQLITE_ABORT. */
+ return SQLITE_ABORT;
+ }
db = p->db;
sqlite3_mutex_enter(db->mutex);
v = (Vdbe*)p->pStmt;
if( n<0 || iOffset<0 || ((sqlite3_int64)iOffset+n)>p->nByte ){
/* Request is out of range. Return a transient error. */
rc = SQLITE_ERROR;
- }else if( v==0 ){
- /* If there is no statement handle, then the blob-handle has
- ** already been invalidated. Return SQLITE_ABORT in this case.
- */
- rc = SQLITE_ABORT;
}else{
/* Call either BtreeData() or BtreePutData(). If SQLITE_ABORT is
** returned, clean-up the statement handle.
*/
assert( db == v->db );
@@ -427,12 +427,12 @@
#endif
rc = xCall(p->pCsr, iOffset+p->iOffset, n, z);
sqlite3BtreeLeaveCursor(p->pCsr);
if( rc==SQLITE_ABORT ){
- sqlite3VdbeFinalize(v);
- p->pStmt = 0;
+ sqlite3_reset(p->pStmt);
+ p->nByte = -1;
}else{
v->rc = rc;
}
}
sqlite3Error(db, rc);
@@ -456,18 +456,40 @@
}
/*
** Query a blob handle for the size of the data.
**
-** The Incrblob.nByte field is fixed for the lifetime of the Incrblob
-** so no mutex is required for access.
+** The Incrblob.nByte field is fixed for as long as the sqlite3_blob
+** object is pointing to the same row, so no mutex is required for access.
+**
+** When the sqlite3_blob interface was first designed in 2007, we specified
+** that sqlite3_blob_bytes() returned 0 when in the RESET state. It would
+** have been better to return -1 in order to distinguish a RESET blob handle
+** from an ACTIVE blob handle pointing to a zero-length string or blob.
+** But, sadly, we cannot change that now without breaking compatibility.
+** So 0 is returned for RESET blob handles and for ACTIVE blob handles
+** pointing to zero-length blobs.
*/
int sqlite3_blob_bytes(sqlite3_blob *pBlob){
Incrblob *p = (Incrblob *)pBlob;
- return (p && p->pStmt) ? p->nByte : 0;
+ return (p && p->pStmt && p->nByte>0) ? p->nByte : 0;
}
+/*
+** Move the sqlite3_blob object to the RESET state. This releases
+** any locks and gets the object out of the way of commits and
+** rollbacks.
+*/
+int sqlite3_blob_reset(sqlite3_blob *pBlob){
+ Incrblob *p = (Incrblob *)pBlob;
+ if( p->nByte>=0 ){
+ sqlite3_reset(p->pStmt);
+ p->nByte = -1;
+ }
+ return SQLITE_OK;
+}
+
/*
** Move an existing blob handle to point to a different row of the same
** database table.
**
** If an error occurs, or if the specified row does not exist or does not
@@ -478,32 +500,24 @@
*/
int sqlite3_blob_reopen(sqlite3_blob *pBlob, sqlite3_int64 iRow){
int rc;
Incrblob *p = (Incrblob *)pBlob;
sqlite3 *db;
+ char *zErr;
if( p==0 ) return SQLITE_MISUSE_BKPT;
db = p->db;
sqlite3_mutex_enter(db->mutex);
- if( p->pStmt==0 ){
- /* If there is no statement handle, then the blob-handle has
- ** already been invalidated. Return SQLITE_ABORT in this case.
- */
- rc = SQLITE_ABORT;
- }else{
- char *zErr;
- rc = blobSeekToRow(p, iRow, &zErr);
- if( rc!=SQLITE_OK ){
- sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
- sqlite3DbFree(db, zErr);
- }
- assert( rc!=SQLITE_SCHEMA );
+ rc = blobSeekToRow(p, iRow, &zErr);
+ if( rc!=SQLITE_OK ){
+ sqlite3ErrorWithMsg(db, rc, (zErr ? "%s" : 0), zErr);
+ sqlite3DbFree(db, zErr);
}
rc = sqlite3ApiExit(db, rc);
- assert( rc==SQLITE_OK || p->pStmt==0 );
+ assert( rc==SQLITE_OK || p->nByte<0 );
sqlite3_mutex_leave(db->mutex);
return rc;
}
#endif /* #ifndef SQLITE_OMIT_INCRBLOB */
Index: test/e_blobwrite.test
==================================================================
--- test/e_blobwrite.test
+++ test/e_blobwrite.test
@@ -139,12 +139,12 @@
SQLITE_ABORT {callback requested query abort}
do_test 2.3.2 {
execsql { SELECT 1, 2, 3 }
sqlite3_errcode db
} {SQLITE_OK}
-blob_write_error_test 2.3.3 $B 5 $blob 5 \
- SQLITE_ABORT {callback requested query abort}
+#blob_write_error_test 2.3.3 $B 5 $blob 5 \
+# SQLITE_OK {not an error}
sqlite3_blob_close $B
# EVIDENCE-OF: R-08382-59936 Writes to the BLOB that occurred before the
# BLOB handle expired are not rolled back by the expiration of the
# handle, though of course those changes might have been overwritten by
Index: test/incrblob3.test
==================================================================
--- test/incrblob3.test
+++ test/incrblob3.test
@@ -60,11 +60,11 @@
do_test incrblob3-2.1.2 {
list [sqlite3_errcode db] [sqlite3_errmsg db]
} {SQLITE_ERROR {no such rowid: 3}}
do_test incrblob3-2.1.3 {
list [catch {sqlite3_blob_reopen $::blob 1} msg] $msg
-} {1 SQLITE_ABORT}
+} {0 {}}
do_test incrblob3-2.1.4 { close $::blob } {}
do_execsql_test incrblob3-2.2.1 {
INSERT INTO blobs VALUES(3, 42);
INSERT INTO blobs VALUES(4, 54.4);
@@ -83,22 +83,25 @@
list [sqlite3_errcode db] [sqlite3_errmsg db]
} "SQLITE_ERROR {cannot open value of type $type}"
do_test incrblob3-2.2.$tn.3 {
list [catch {sqlite3_blob_reopen $::blob 1} msg] $msg
- } {1 SQLITE_ABORT}
+ } {0 {}}
do_test incrblob3-2.2.$tn.4 {
list [catch {sqlite3_blob_read $::blob 0 10} msg] $msg
- } {1 SQLITE_ABORT}
+ } {0 {hello worl}}
do_test incrblob3-2.2.$tn.5 {
list [catch {sqlite3_blob_write $::blob 0 "abcd"} msg] $msg
- } {1 SQLITE_ABORT}
+ } {0 {}}
do_test incrblob3-2.2.$tn.6 {
sqlite3_blob_bytes $::blob
- } {0}
+ } {100}
+ do_test incrblob3-2.2.$tn.7 {
+ list [catch {sqlite3_blob_write $::blob 0 "hello"} msg] $msg
+ } {0 {}}
- do_test incrblob3-2.2.$tn.7 { close $::blob } {}
+ do_test incrblob3-2.2.$tn.8 { close $::blob } {}
}
# Test that passing NULL to sqlite3_blob_XXX() APIs returns SQLITE_MISUSE.
#
# incrblob3-3.1: sqlite3_blob_reopen()