aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/insert.c1
-rw-r--r--src/sqlite.h.in69
-rw-r--r--src/sqliteInt.h7
-rw-r--r--src/test1.c28
-rw-r--r--src/update.c9
-rw-r--r--src/vdbe.c35
-rw-r--r--src/vdbeaux.c9
-rw-r--r--src/vtab.c115
8 files changed, 257 insertions, 16 deletions
diff --git a/src/insert.c b/src/insert.c
index 02456e776..452939db9 100644
--- a/src/insert.c
+++ b/src/insert.c
@@ -969,6 +969,7 @@ void sqlite3Insert(
const char *pVTab = (const char *)sqlite3GetVTable(db, pTab);
sqlite3VtabMakeWritable(pParse, pTab);
sqlite3VdbeAddOp4(v, OP_VUpdate, 1, pTab->nCol+2, regIns, pVTab, P4_VTAB);
+ sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
sqlite3MayAbort(pParse);
}else
#endif
diff --git a/src/sqlite.h.in b/src/sqlite.h.in
index 421da8e6b..c93922f9d 100644
--- a/src/sqlite.h.in
+++ b/src/sqlite.h.in
@@ -4607,6 +4607,11 @@ struct sqlite3_module {
void (**pxFunc)(sqlite3_context*,int,sqlite3_value**),
void **ppArg);
int (*xRename)(sqlite3_vtab *pVtab, const char *zNew);
+ /* The methods above are in version 0 of the sqlite_module object. Those
+ ** below are for version 1 and greater. */
+ int (*xSavepoint)(sqlite3_vtab *pVTab, int);
+ int (*xRelease)(sqlite3_vtab *pVTab, int);
+ int (*xRollbackTo)(sqlite3_vtab *pVTab, int);
};
/*
@@ -6384,11 +6389,75 @@ int sqlite3_wal_checkpoint_v2(
** [sqlite3_wal_checkpoint_v2()]. See the [sqlite3_wal_checkpoint_v2()]
** documentation for additional information about the meaning and use of
** each of these values.
+**
+** <dt>SQLITE_CONFIG_GETMUTEX</dt>
+** <dd> ^(This option takes a single argument which is a pointer to an
*/
#define SQLITE_CHECKPOINT_PASSIVE 0
#define SQLITE_CHECKPOINT_FULL 1
#define SQLITE_CHECKPOINT_RESTART 2
+/*
+** CAPI3REF: Virtual Table Interface Configuration
+**
+** This function is called by a virtual table implementation to configure
+** various facets of the virtual table interface. At present, there is only
+** one option that may be configured using this function. Further options
+** may be added in the future.
+**
+** <dl>
+** <dt>SQLITE_VTAB_CONSTRAINT_SUPPORT
+** <dd>If the second argument to sqlite3_vtab_config() is
+** SQLITE_VTAB_CONSTRAINT_SUPPORT, then SQLite expects this function to
+** have been called with three arguments, the third of which being of
+** type 'int'. If the third argument is zero, then the virtual table
+** is indicating that it does not support constraints. In this case if
+** a call to the xUpdate method returns SQLITE_CONSTRAINT, the entire
+** statement is rolled back as if [ON CONFLICT | OR ABORT] had been
+** specified as part of the users SQL statement, regardless of the actual
+** ON CONFLICT mode specified.
+**
+** If the third argument passed is non-zero, then the virtual table
+** implementation must guarantee that if xUpdate returns
+** SQLITE_CONSTRAINT, it does so before any modifications to internal
+** or persistent data structures have been made. If the [ON CONFLICT]
+** mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite is able to roll back
+** a statement or database transaction, and abandon or continue processing
+** the current SQL statement as appropriate. If the ON CONFLICT mode is
+** REPLACE and the xUpdate method returns SQLITE_CONSTRAINT, SQLite
+** handles this as if the ON CONFLICT mode had been ABORT.
+**
+** Virtual table implementations that are required to handle OR REPLACE
+** must do so within the xUpdate method. If a call to the
+** [sqlite3_vtab_on_conflict()] function indicates that the current ON
+** CONFLICT policy is REPLACE, the virtual table implementation should
+** silently replace the appropriate rows within the xUpdate callback and
+** return SQLITE_OK. Or, if this is not possible, it may return
+** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT
+** constraint handling.
+** </dl>
+**
+*/
+#define SQLITE_VTAB_CONSTRAINT_SUPPORT 1
+int sqlite3_vtab_config(sqlite3*, int op, ...);
+
+/*
+** CAPI3REF: Determine The Virtual Table Conflict Policy
+**
+** This function may only be called from within a call to the xUpdate method
+** of a virtual table implementation for an INSERT or UPDATE operation. The
+** value returned is one of SQLITE_ROLLBACK, SQLITE_IGNORE, SQLITE_FAIL,
+** SQLITE_ABORT or SQLITE_REPLACE, according to the [ON CONFLICT] mode of the
+** SQL statement that triggered the callback.
+*/
+#define SQLITE_ROLLBACK 1
+/* #define SQLITE_IGNORE 2 */
+#define SQLITE_FAIL 3
+/* #define SQLITE_ABORT 4 */
+#define SQLITE_REPLACE 5
+int sqlite3_vtab_on_conflict(sqlite3 *);
+
+
/*
** Undo the hack that converts floating point types to integer for
diff --git a/src/sqliteInt.h b/src/sqliteInt.h
index ea0925e41..9c1bda7a7 100644
--- a/src/sqliteInt.h
+++ b/src/sqliteInt.h
@@ -632,6 +632,7 @@ typedef struct TriggerPrg TriggerPrg;
typedef struct TriggerStep TriggerStep;
typedef struct UnpackedRecord UnpackedRecord;
typedef struct VTable VTable;
+typedef struct VtabCtx VtabCtx;
typedef struct Walker Walker;
typedef struct WherePlan WherePlan;
typedef struct WhereInfo WhereInfo;
@@ -811,6 +812,7 @@ struct sqlite3 {
u8 dfltLockMode; /* Default locking-mode for attached dbs */
signed char nextAutovac; /* Autovac setting after VACUUM if >=0 */
u8 suppressErr; /* Do not issue error messages if true */
+ u8 vtabOnConflict; /* Value to return for s3_vtab_on_conflict() */
int nextPagesize; /* Pagesize after VACUUM if >0 */
int nTable; /* Number of tables in the database */
CollSeq *pDfltColl; /* The default collating sequence (BINARY) */
@@ -869,7 +871,7 @@ struct sqlite3 {
#endif
#ifndef SQLITE_OMIT_VIRTUALTABLE
Hash aModule; /* populated by sqlite3_create_module() */
- Table *pVTab; /* vtab with active Connect/Create method */
+ VtabCtx *pVtabCtx; /* Context for active vtab connect/create */
VTable **aVTrans; /* Virtual tables with open transactions */
int nVTrans; /* Allocated size of aVTrans */
VTable *pDisconnect; /* Disconnect these in next sqlite3_prepare() */
@@ -1232,6 +1234,7 @@ struct VTable {
Module *pMod; /* Pointer to module implementation */
sqlite3_vtab *pVtab; /* Pointer to vtab instance */
int nRef; /* Number of pointers to this structure */
+ u8 bConstraint; /* True if constraints are supported */
VTable *pNext; /* Next in linked list (see above) */
};
@@ -3043,6 +3046,7 @@ void sqlite3AutoLoadExtensions(sqlite3*);
# define sqlite3VtabLock(X)
# define sqlite3VtabUnlock(X)
# define sqlite3VtabUnlockList(X)
+# define sqlite3VtabSavepoint(X, Y, Z) SQLITE_OK
#else
void sqlite3VtabClear(sqlite3 *db, Table*);
int sqlite3VtabSync(sqlite3 *db, char **);
@@ -3051,6 +3055,7 @@ void sqlite3AutoLoadExtensions(sqlite3*);
void sqlite3VtabLock(VTable *);
void sqlite3VtabUnlock(VTable *);
void sqlite3VtabUnlockList(sqlite3*);
+ int sqlite3VtabSavepoint(sqlite3 *, int, int);
# define sqlite3VtabInSync(db) ((db)->nVTrans>0 && (db)->aVTrans==0)
#endif
void sqlite3VtabMakeWritable(Parse*,Table*);
diff --git a/src/test1.c b/src/test1.c
index 8a0d09a71..b79bbd082 100644
--- a/src/test1.c
+++ b/src/test1.c
@@ -14,6 +14,7 @@
** testing of the SQLite library.
*/
#include "sqliteInt.h"
+#include "vdbeInt.h"
#include "tcl.h"
#include <stdlib.h>
#include <string.h>
@@ -2326,6 +2327,32 @@ static int test_stmt_readonly(
return TCL_OK;
}
+/*
+** Usage: uses_stmt_journal STMT
+**
+** Return true if STMT uses a statement journal.
+*/
+static int uses_stmt_journal(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3_stmt *pStmt;
+ int rc;
+
+ if( objc!=2 ){
+ Tcl_AppendResult(interp, "wrong # args: should be \"",
+ Tcl_GetStringFromObj(objv[0], 0), " STMT", 0);
+ return TCL_ERROR;
+ }
+
+ if( getStmtPointer(interp, Tcl_GetString(objv[1]), &pStmt) ) return TCL_ERROR;
+ rc = sqlite3_stmt_readonly(pStmt);
+ Tcl_SetObjResult(interp, Tcl_NewBooleanObj(((Vdbe *)pStmt)->usesStmtJournal));
+ return TCL_OK;
+}
+
/*
** Usage: sqlite3_reset STMT
@@ -5583,6 +5610,7 @@ int Sqlitetest1_Init(Tcl_Interp *interp){
{ "sqlite3_sql", test_sql ,0 },
{ "sqlite3_next_stmt", test_next_stmt ,0 },
{ "sqlite3_stmt_readonly", test_stmt_readonly ,0 },
+ { "uses_stmt_journal", uses_stmt_journal ,0 },
{ "sqlite3_release_memory", test_release_memory, 0},
{ "sqlite3_soft_heap_limit", test_soft_heap_limit, 0},
diff --git a/src/update.c b/src/update.c
index 315034d86..aecf75cf3 100644
--- a/src/update.c
+++ b/src/update.c
@@ -23,7 +23,8 @@ static void updateVirtualTable(
ExprList *pChanges, /* The columns to change in the UPDATE statement */
Expr *pRowidExpr, /* Expression used to recompute the rowid */
int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
- Expr *pWhere /* WHERE clause of the UPDATE statement */
+ Expr *pWhere, /* WHERE clause of the UPDATE statement */
+ int onError /* ON CONFLICT strategy */
);
#endif /* SQLITE_OMIT_VIRTUALTABLE */
@@ -267,7 +268,7 @@ void sqlite3Update(
/* Virtual tables must be handled separately */
if( IsVirtual(pTab) ){
updateVirtualTable(pParse, pTabList, pTab, pChanges, pRowidExpr, aXRef,
- pWhere);
+ pWhere, onError);
pWhere = 0;
pTabList = 0;
goto update_cleanup;
@@ -597,7 +598,8 @@ static void updateVirtualTable(
ExprList *pChanges, /* The columns to change in the UPDATE statement */
Expr *pRowid, /* Expression used to recompute the rowid */
int *aXRef, /* Mapping from columns of pTab to entries in pChanges */
- Expr *pWhere /* WHERE clause of the UPDATE statement */
+ Expr *pWhere, /* WHERE clause of the UPDATE statement */
+ int onError /* ON CONFLICT strategy */
){
Vdbe *v = pParse->pVdbe; /* Virtual machine under construction */
ExprList *pEList = 0; /* The result set of the SELECT statement */
@@ -654,6 +656,7 @@ static void updateVirtualTable(
}
sqlite3VtabMakeWritable(pParse, pTab);
sqlite3VdbeAddOp4(v, OP_VUpdate, 0, pTab->nCol+2, iReg, pVTab, P4_VTAB);
+ sqlite3VdbeChangeP5(v, onError==OE_Default ? OE_Abort : onError);
sqlite3MayAbort(pParse);
sqlite3VdbeAddOp2(v, OP_Next, ephemTab, addr+1);
sqlite3VdbeJumpHere(v, addr);
diff --git a/src/vdbe.c b/src/vdbe.c
index 5376b08a0..996c649f8 100644
--- a/src/vdbe.c
+++ b/src/vdbe.c
@@ -2580,6 +2580,14 @@ case OP_Savepoint: {
}else{
nName = sqlite3Strlen30(zName);
+ /* This call is Ok even if this savepoint is actually a transaction
+ ** savepoint (and therefore should not prompt xSavepoint()) callbacks.
+ ** If this is a transaction savepoint being opened, it is guaranteed
+ ** that the db->aVTrans[] array is empty. */
+ assert( db->autoCommit==0 || db->nVTrans==0 );
+ rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+
/* Create a new savepoint structure. */
pNew = sqlite3DbMallocRaw(db, sizeof(Savepoint)+nName+1);
if( pNew ){
@@ -2686,6 +2694,11 @@ case OP_Savepoint: {
}else{
db->nDeferredCons = pSavepoint->nDeferredCons;
}
+
+ if( !isTransaction ){
+ rc = sqlite3VtabSavepoint(db, p1, iSavepoint);
+ if( rc!=SQLITE_OK ) goto abort_due_to_error;
+ }
}
}
@@ -2821,7 +2834,11 @@ case OP_Transaction: {
db->nStatement++;
p->iStatement = db->nSavepoint + db->nStatement;
}
- rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
+
+ rc = sqlite3VtabSavepoint(db, SAVEPOINT_BEGIN, p->iStatement);
+ if( rc==SQLITE_OK ){
+ rc = sqlite3BtreeBeginStmt(pBt, p->iStatement);
+ }
/* Store the current value of the database handles deferred constraint
** counter. If the statement transaction needs to be rolled back,
@@ -5773,11 +5790,15 @@ case OP_VUpdate: {
Mem **apArg;
Mem *pX;
+ assert( pOp->p2==1 || pOp->p5==OE_Fail || pOp->p5==OE_Rollback
+ || pOp->p5==OE_Abort || pOp->p5==OE_Ignore || pOp->p5==OE_Replace
+ );
pVtab = pOp->p4.pVtab->pVtab;
pModule = (sqlite3_module *)pVtab->pModule;
nArg = pOp->p2;
assert( pOp->p4type==P4_VTAB );
if( ALWAYS(pModule->xUpdate) ){
+ u8 vtabOnConflict = db->vtabOnConflict;
apArg = p->apArg;
pX = &aMem[pOp->p3];
for(i=0; i<nArg; i++){
@@ -5787,13 +5808,23 @@ case OP_VUpdate: {
apArg[i] = pX;
pX++;
}
+ db->vtabOnConflict = pOp->p5;
rc = pModule->xUpdate(pVtab, nArg, apArg, &rowid);
+ db->vtabOnConflict = vtabOnConflict;
importVtabErrMsg(p, pVtab);
if( rc==SQLITE_OK && pOp->p1 ){
assert( nArg>1 && apArg[0] && (apArg[0]->flags&MEM_Null) );
db->lastRowid = rowid;
}
- p->nChange++;
+ if( rc==SQLITE_CONSTRAINT && pOp->p4.pVtab->bConstraint ){
+ if( pOp->p5==OE_Ignore ){
+ rc = SQLITE_OK;
+ }else{
+ p->errorAction = ((pOp->p5==OE_Replace) ? OE_Abort : pOp->p5);
+ }
+ }else{
+ p->nChange++;
+ }
}
break;
}
diff --git a/src/vdbeaux.c b/src/vdbeaux.c
index b594e1327..0c7b11f1e 100644
--- a/src/vdbeaux.c
+++ b/src/vdbeaux.c
@@ -2013,6 +2013,15 @@ int sqlite3VdbeCloseStatement(Vdbe *p, int eOp){
db->nStatement--;
p->iStatement = 0;
+ if( rc==SQLITE_OK ){
+ if( eOp==SAVEPOINT_ROLLBACK ){
+ rc = sqlite3VtabSavepoint(db, SAVEPOINT_ROLLBACK, iSavepoint);
+ }
+ if( rc==SQLITE_OK ){
+ rc = sqlite3VtabSavepoint(db, SAVEPOINT_RELEASE, iSavepoint);
+ }
+ }
+
/* If the statement transaction is being rolled back, also restore the
** database handles deferred constraint counter to the value it had when
** the statement transaction was opened. */
diff --git a/src/vtab.c b/src/vtab.c
index b052de23a..811d2e164 100644
--- a/src/vtab.c
+++ b/src/vtab.c
@@ -15,6 +15,18 @@
#include "sqliteInt.h"
/*
+** Before a virtual table xCreate() or xConnect() method is invoked, the
+** sqlite3.pVtabCtx member variable is set to point to an instance of
+** this struct allocated on the stack. It is used by the implementation of
+** the sqlite3_declare_vtab() and sqlite3_vtab_config() APIs, both of which
+** are invoked only from within xCreate and xConnect methods.
+*/
+struct VtabCtx {
+ Table *pTab;
+ VTable *pVTable;
+};
+
+/*
** The actual function that does the work of creating a new module.
** This function implements the sqlite3_create_module() and
** sqlite3_create_module_v2() interfaces.
@@ -434,6 +446,7 @@ static int vtabCallConstructor(
int (*xConstruct)(sqlite3*,void*,int,const char*const*,sqlite3_vtab**,char**),
char **pzErr
){
+ VtabCtx sCtx;
VTable *pVTable;
int rc;
const char *const*azArg = (const char *const*)pTab->azModuleArg;
@@ -453,12 +466,14 @@ static int vtabCallConstructor(
pVTable->db = db;
pVTable->pMod = pMod;
- assert( !db->pVTab );
- assert( xConstruct );
- db->pVTab = pTab;
-
/* Invoke the virtual table constructor */
+ assert( &db->pVtabCtx );
+ assert( xConstruct );
+ sCtx.pTab = pTab;
+ sCtx.pVTable = pVTable;
+ db->pVtabCtx = &sCtx;
rc = xConstruct(db, pMod->pAux, nArg, azArg, &pVTable->pVtab, &zErr);
+ db->pVtabCtx = 0;
if( rc==SQLITE_NOMEM ) db->mallocFailed = 1;
if( SQLITE_OK!=rc ){
@@ -474,7 +489,7 @@ static int vtabCallConstructor(
** the sqlite3_vtab object if successful. */
pVTable->pVtab->pModule = pMod->pModule;
pVTable->nRef = 1;
- if( db->pVTab ){
+ if( sCtx.pTab ){
const char *zFormat = "vtable constructor did not declare schema: %s";
*pzErr = sqlite3MPrintf(db, zFormat, pTab->zName);
sqlite3VtabUnlock(pVTable);
@@ -522,7 +537,6 @@ static int vtabCallConstructor(
}
sqlite3DbFree(db, zModuleName);
- db->pVTab = 0;
return rc;
}
@@ -642,8 +656,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
char *zErr = 0;
sqlite3_mutex_enter(db->mutex);
- pTab = db->pVTab;
- if( !pTab ){
+ if( !db->pVtabCtx || !(pTab = db->pVtabCtx->pTab) ){
sqlite3Error(db, SQLITE_MISUSE, 0);
sqlite3_mutex_leave(db->mutex);
return SQLITE_MISUSE_BKPT;
@@ -670,7 +683,7 @@ int sqlite3_declare_vtab(sqlite3 *db, const char *zCreateTable){
pParse->pNewTable->nCol = 0;
pParse->pNewTable->aCol = 0;
}
- db->pVTab = 0;
+ db->pVtabCtx->pTab = 0;
}else{
sqlite3Error(db, SQLITE_ERROR, (zErr ? "%s" : 0), zErr);
sqlite3DbFree(db, zErr);
@@ -822,7 +835,6 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
if( pModule->xBegin ){
int i;
-
/* If pVtab is already in the aVTrans array, return early */
for(i=0; i<db->nVTrans; i++){
if( db->aVTrans[i]==pVTab ){
@@ -840,6 +852,49 @@ int sqlite3VtabBegin(sqlite3 *db, VTable *pVTab){
}
/*
+** Invoke either the xSavepoint, xRollbackTo or xRelease method of all
+** virtual tables that currently have an open transaction. Pass iSavepoint
+** as the second argument to the virtual table method invoked.
+**
+** If op is SAVEPOINT_BEGIN, the xSavepoint method is invoked. If it is
+** SAVEPOINT_ROLLBACK, the xRollbackTo method. Otherwise, if op is
+** SAVEPOINT_RELEASE, then the xRelease method of each virtual table with
+** an open transaction is invoked.
+**
+** If any virtual table method returns an error code other than SQLITE_OK,
+** processing is abandoned and the error returned to the caller of this
+** function immediately. If all calls to virtual table methods are successful,
+** SQLITE_OK is returned.
+*/
+int sqlite3VtabSavepoint(sqlite3 *db, int op, int iSavepoint){
+ int rc = SQLITE_OK;
+
+ assert( op==SAVEPOINT_RELEASE||op==SAVEPOINT_ROLLBACK||op==SAVEPOINT_BEGIN );
+ if( db->aVTrans ){
+ int i;
+ for(i=0; rc==SQLITE_OK && i<db->nVTrans; i++){
+ const sqlite3_module *pMod = db->aVTrans[i]->pMod->pModule;
+ if( pMod->iVersion>=1 ){
+ int (*xMethod)(sqlite3_vtab *, int);
+ switch( op ){
+ case SAVEPOINT_BEGIN:
+ xMethod = pMod->xSavepoint;
+ break;
+ case SAVEPOINT_ROLLBACK:
+ xMethod = pMod->xRollbackTo;
+ break;
+ default:
+ xMethod = pMod->xRelease;
+ break;
+ }
+ if( xMethod ) rc = xMethod(db->aVTrans[i]->pVtab, iSavepoint);
+ }
+ }
+ }
+ return rc;
+}
+
+/*
** The first parameter (pDef) is a function implementation. The
** second parameter (pExpr) is the first argument to this function.
** If pExpr is a column in a virtual table, then let the virtual
@@ -937,4 +992,44 @@ void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
}
}
+int sqlite3_vtab_on_conflict(sqlite3 *db){
+ int aMap[] = {
+ SQLITE_ROLLBACK, SQLITE_IGNORE, SQLITE_ABORT, SQLITE_FAIL, SQLITE_REPLACE
+ };
+ assert( OE_Rollback==1 && OE_Abort==2 && OE_Fail==3 );
+ assert( OE_Ignore==4 && OE_Replace==5 );
+ assert( db->vtabOnConflict>=1 && db->vtabOnConflict<=5 );
+ return aMap[db->vtabOnConflict-1];
+}
+
+
+int sqlite3_vtab_config(sqlite3 *db, int op, ...){
+ va_list ap;
+ int rc = SQLITE_OK;
+
+ sqlite3_mutex_enter(db->mutex);
+
+ va_start(ap, op);
+ switch( op ){
+ case SQLITE_VTAB_CONSTRAINT_SUPPORT: {
+ VtabCtx *p = db->pVtabCtx;
+ if( !p ){
+ rc = SQLITE_MISUSE_BKPT;
+ }else{
+ assert( (p->pTab->tabFlags & TF_Virtual)!=0 );
+ p->pVTable->bConstraint = (u8)va_arg(ap, int);
+ }
+ break;
+ }
+ default:
+ rc = SQLITE_MISUSE_BKPT;
+ break;
+ }
+ va_end(ap);
+
+ if( rc!=SQLITE_OK ) sqlite3Error(db, rc, 0);
+ sqlite3_mutex_leave(db->mutex);
+ return rc;
+}
+
#endif /* SQLITE_OMIT_VIRTUALTABLE */