--- /dev/null
+# 2016 March 7
+#
+# 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.
+#
+#***********************************************************************
+# Tests for RBU focused on the REPLACE operation (rbu_control column
+# contains integer value 2).
+#
+
+source [file join [file dirname [info script]] rbu_common.tcl]
+set ::testprefix rbuC
+
+#-------------------------------------------------------------------------
+# This test is actually of an UPDATE directive. Just to establish that
+# these work with UNIQUE indexes before preceding to REPLACE.
+#
+do_execsql_test 1.0 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
+ INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
+}
+
+forcedelete rbu.db
+do_execsql_test 1.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 'a', 'b', 'c', '.xxx');
+}
+
+do_test 1.2 {
+ step_rbu test.db rbu.db
+} {SQLITE_DONE}
+
+do_execsql_test 1.3 {
+ SELECT * FROM t1
+} {
+ 1 a b c
+}
+
+#-------------------------------------------------------------------------
+#
+foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(i INTEGER PRIMARY KEY, a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+ 2 {
+ CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+ 3 {
+ CREATE TABLE t1(i PRIMARY KEY, a, b, c UNIQUE) WITHOUT ROWID;
+ CREATE INDEX t1a ON t1(a);
+ }
+} {
+ reset_db
+ forcedelete rbu.db
+ execsql $schema
+
+ do_execsql_test 2.$tn.0 {
+ INSERT INTO t1 VALUES(1, 'a', 'b', 'c');
+ INSERT INTO t1 VALUES(2, 'b', 'c', 'd');
+ INSERT INTO t1 VALUES(3, 'c', 'd', 'e');
+ }
+
+ do_execsql_test 2.$tn.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(i, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
+ INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
+ INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
+ }
+
+ do_test 2.$tn.2 {
+ step_rbu test.db rbu.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 2.$tn.3 {
+ SELECT * FROM t1 ORDER BY i
+ } {
+ 1 1 2 3
+ 2 b c d
+ 3 c d e
+ 4 d e f
+ }
+
+ integrity_check 2.$tn.4
+}
+
+foreach {tn schema} {
+ 1 {
+ CREATE TABLE t1(a, b, c UNIQUE);
+ CREATE INDEX t1a ON t1(a);
+ }
+
+ 2 {
+ CREATE VIRTUAL TABLE t1 USING fts5(a, b, c);
+ }
+} {
+ if {$tn==2} { ifcapable !fts5 break }
+ reset_db
+ forcedelete rbu.db
+ execsql $schema
+
+ do_execsql_test 3.$tn.0 {
+ INSERT INTO t1 VALUES('a', 'b', 'c');
+ INSERT INTO t1 VALUES('b', 'c', 'd');
+ INSERT INTO t1 VALUES('c', 'd', 'e');
+ }
+
+ do_execsql_test 3.$tn.1 {
+ ATTACH 'rbu.db' AS rbu;
+ CREATE TABLE rbu.data_t1(rbu_rowid, a, b, c, rbu_control);
+ INSERT INTO data_t1 VALUES(1, 1, 2, 3, 2);
+ INSERT INTO data_t1 VALUES(3, 'c', 'd', 'e', 2);
+ INSERT INTO data_t1 VALUES(4, 'd', 'e', 'f', 2);
+ }
+
+ do_test 3.$tn.2 {
+ step_rbu test.db rbu.db
+ } {SQLITE_DONE}
+
+ do_execsql_test 3.$tn.3 {
+ SELECT rowid, * FROM t1 ORDER BY 1
+ } {
+ 1 1 2 3
+ 2 b c d
+ 3 c d e
+ 4 d e f
+ }
+
+ integrity_check 3.$tn.4
+}
+
+
+
+finish_test
+
*/
#define RBU_INSERT 1 /* Insert on a main table b-tree */
#define RBU_DELETE 2 /* Delete a row from a main table b-tree */
-#define RBU_IDX_DELETE 3 /* Delete a row from an aux. index b-tree */
-#define RBU_IDX_INSERT 4 /* Insert on an aux. index b-tree */
-#define RBU_UPDATE 5 /* Update a row in a main table b-tree */
+#define RBU_REPLACE 3 /* Delete and then insert a row */
+#define RBU_IDX_DELETE 4 /* Delete a row from an aux. index b-tree */
+#define RBU_IDX_INSERT 5 /* Insert on an aux. index b-tree */
+#define RBU_UPDATE 6 /* Update a row in a main table b-tree */
/*
** A single step of an incremental checkpoint - frame iWalFrame of the wal
);
}else{
zSql = sqlite3_mprintf(
+ "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
+ "UNION ALL "
"SELECT %s, rbu_control FROM '%q' "
"WHERE typeof(rbu_control)='integer' AND rbu_control!=1 "
- "UNION ALL "
- "SELECT %s, rbu_control FROM %s.'rbu_tmp_%q' "
"ORDER BY %s%s",
- zCollist, pIter->zDataTbl,
zCollist, p->zStateDb, pIter->zDataTbl,
+ zCollist, pIter->zDataTbl,
zCollist, zLimit
);
}
rbuMPrintfExec(p, p->dbMain,
"CREATE TEMP TRIGGER rbu_delete_tr BEFORE DELETE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(2, %s);"
+ " SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update1_tr BEFORE UPDATE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(2, %s);"
+ " SELECT rbu_tmp_insert(3, %s);"
"END;"
"CREATE TEMP TRIGGER rbu_update2_tr AFTER UPDATE ON \"%s%w\" "
"BEGIN "
- " SELECT rbu_tmp_insert(3, %s);"
+ " SELECT rbu_tmp_insert(4, %s);"
"END;",
zWrite, zTbl, zOldlist,
zWrite, zTbl, zOldlist,
switch( sqlite3_column_type(p->objiter.pSelect, iCol) ){
case SQLITE_INTEGER: {
int iVal = sqlite3_column_int(p->objiter.pSelect, iCol);
- if( iVal==0 ){
- res = RBU_INSERT;
- }else if( iVal==1 ){
- res = RBU_DELETE;
- }else if( iVal==2 ){
- res = RBU_IDX_DELETE;
- }else if( iVal==3 ){
- res = RBU_IDX_INSERT;
+ switch( iVal ){
+ case 0: res = RBU_INSERT; break;
+ case 1: res = RBU_DELETE; break;
+ case 2: res = RBU_REPLACE; break;
+ case 3: res = RBU_IDX_DELETE; break;
+ case 4: res = RBU_IDX_INSERT; break;
}
break;
}
# define assertColumnName(x,y,z)
#endif
+/*
+** Argument eType must be one of RBU_INSERT, RBU_DELETE, RBU_IDX_INSERT or
+** RBU_IDX_DELETE. This function performs the work of a single
+** sqlite3rbu_step() call for the type of operation specified by eType.
+*/
+static void rbuStepOneOp(sqlite3rbu *p, int eType){
+ RbuObjIter *pIter = &p->objiter;
+ sqlite3_value *pVal;
+ sqlite3_stmt *pWriter;
+ int i;
+
+ assert( p->rc==SQLITE_OK );
+ assert( eType!=RBU_DELETE || pIter->zIdx==0 );
+
+ if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
+ pWriter = pIter->pDelete;
+ }else{
+ pWriter = pIter->pInsert;
+ }
+
+ for(i=0; i<pIter->nCol; i++){
+ /* If this is an INSERT into a table b-tree and the table has an
+ ** explicit INTEGER PRIMARY KEY, check that this is not an attempt
+ ** to write a NULL into the IPK column. That is not permitted. */
+ if( eType==RBU_INSERT
+ && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
+ && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
+ ){
+ p->rc = SQLITE_MISMATCH;
+ p->zErrmsg = sqlite3_mprintf("datatype mismatch");
+ return;
+ }
+
+ if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
+ continue;
+ }
+
+ pVal = sqlite3_column_value(pIter->pSelect, i);
+ p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
+ if( p->rc ) return;
+ }
+ if( pIter->zIdx==0
+ && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
+ ){
+ /* For a virtual table, or a table with no primary key, the
+ ** SELECT statement is:
+ **
+ ** SELECT <cols>, rbu_control, rbu_rowid FROM ....
+ **
+ ** Hence column_value(pIter->nCol+1).
+ */
+ assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
+ pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
+ p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
+ }
+ if( p->rc==SQLITE_OK ){
+ sqlite3_step(pWriter);
+ p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
+ }
+}
+
/*
** This function does the work for an sqlite3rbu_step() call.
**
static int rbuStep(sqlite3rbu *p){
RbuObjIter *pIter = &p->objiter;
const char *zMask = 0;
- int i;
int eType = rbuStepType(p, &zMask);
if( eType ){
+ assert( eType==RBU_INSERT || eType==RBU_DELETE
+ || eType==RBU_REPLACE || eType==RBU_IDX_DELETE
+ || eType==RBU_IDX_INSERT || eType==RBU_UPDATE
+ );
assert( eType!=RBU_UPDATE || pIter->zIdx==0 );
if( pIter->zIdx==0 && eType==RBU_IDX_DELETE ){
rbuBadControlError(p);
}
- else if(
- eType==RBU_INSERT
- || eType==RBU_DELETE
- || eType==RBU_IDX_DELETE
- || eType==RBU_IDX_INSERT
- ){
- sqlite3_value *pVal;
- sqlite3_stmt *pWriter;
-
- assert( eType!=RBU_UPDATE );
- assert( eType!=RBU_DELETE || pIter->zIdx==0 );
-
- if( eType==RBU_IDX_DELETE || eType==RBU_DELETE ){
- pWriter = pIter->pDelete;
- }else{
- pWriter = pIter->pInsert;
- }
-
- for(i=0; i<pIter->nCol; i++){
- /* If this is an INSERT into a table b-tree and the table has an
- ** explicit INTEGER PRIMARY KEY, check that this is not an attempt
- ** to write a NULL into the IPK column. That is not permitted. */
- if( eType==RBU_INSERT
- && pIter->zIdx==0 && pIter->eType==RBU_PK_IPK && pIter->abTblPk[i]
- && sqlite3_column_type(pIter->pSelect, i)==SQLITE_NULL
- ){
- p->rc = SQLITE_MISMATCH;
- p->zErrmsg = sqlite3_mprintf("datatype mismatch");
- goto step_out;
- }
-
- if( eType==RBU_DELETE && pIter->abTblPk[i]==0 ){
- continue;
- }
-
- pVal = sqlite3_column_value(pIter->pSelect, i);
- p->rc = sqlite3_bind_value(pWriter, i+1, pVal);
- if( p->rc ) goto step_out;
- }
- if( pIter->zIdx==0
- && (pIter->eType==RBU_PK_VTAB || pIter->eType==RBU_PK_NONE)
- ){
- /* For a virtual table, or a table with no primary key, the
- ** SELECT statement is:
- **
- ** SELECT <cols>, rbu_control, rbu_rowid FROM ....
- **
- ** Hence column_value(pIter->nCol+1).
- */
- assertColumnName(pIter->pSelect, pIter->nCol+1, "rbu_rowid");
- pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
- p->rc = sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
- }
- if( p->rc==SQLITE_OK ){
- sqlite3_step(pWriter);
- p->rc = resetAndCollectError(pWriter, &p->zErrmsg);
- }
- }else{
+ else if( eType==RBU_REPLACE ){
+ if( pIter->zIdx==0 ) rbuStepOneOp(p, RBU_DELETE);
+ if( p->rc==SQLITE_OK ) rbuStepOneOp(p, RBU_INSERT);
+ }
+ else if( eType!=RBU_UPDATE ){
+ rbuStepOneOp(p, eType);
+ }
+ else{
sqlite3_value *pVal;
sqlite3_stmt *pUpdate = 0;
assert( eType==RBU_UPDATE );
rbuGetUpdateStmt(p, pIter, zMask, &pUpdate);
if( pUpdate ){
+ int i;
for(i=0; p->rc==SQLITE_OK && i<pIter->nCol; i++){
char c = zMask[pIter->aiSrcOrder[i]];
pVal = sqlite3_column_value(pIter->pSelect, i);
-C Add\sthe\sSQLITE_CONFIG_STMTJRNL_SPILL\soption\sfor\ssqlite3_config().
-D 2016-03-07T17:37:37.281
+C Add\sa\snew\srow\stype\sto\sRBU\s(a\speer\sof\sinsert,\supdate\sand\sdelete)\s-\s"delete\sthen\sinsert".
+D 2016-03-07T17:39:30.317
F Makefile.in f53429fb2f313c099283659d0df6f20f932c861f
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc df0bf9ff7f8b3f4dd9fb4cc43f92fe58f6ec5c66
F ext/rbu/rbu9.test 0806d1772c9f4981774ff028de6656e4183082af
F ext/rbu/rbuA.test c1a7b3e2d926b8f8448bb3b4ae787e314ee4b2b3
F ext/rbu/rbuB.test c25bc325b8072a766e56bb76c001866b405925c2
+F ext/rbu/rbuC.test efe47db508a0269b683cb2a1913a425ffd39a831
F ext/rbu/rbu_common.tcl 0398545fed614f807d5f0ba55a85a51f08ba8f1a
F ext/rbu/rbucrash.test 8d2ed5d4b05fef6c00c2a6b5f7ead71fa172a695
F ext/rbu/rbudiff.test 6cc806dc36389292f2a8f5842d0103721df4a07d
F ext/rbu/rbufault2.test 9a7f19edd6ea35c4c9f807d8a3db0a03a5670c06
F ext/rbu/rbufts.test 828cd689da825f0a7b7c53ffc1f6f7fdb6fa5bda
F ext/rbu/rbusave.test 0f43b6686084f426ddd040b878426452fd2c2f48
-F ext/rbu/sqlite3rbu.c 371e8bf06cfb3f691adac47eb15ab1073ed92dcf
+F ext/rbu/sqlite3rbu.c 0d901d773bf4c9e7d101daaf545263044e7e6615
F ext/rbu/sqlite3rbu.h 0bdeb3be211aaba7d85445fa36f4701a25a3dbde
F ext/rbu/test_rbu.c 4a4cdcef4ef9379fc2a21f008805c80b27bcf573
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P d8d89d69a490a708b83147945f74134ae0e4b387
-R bbf6d9fea009c5097a1f1af27ba30392
-U drh
-Z 638bda7b36ac0921edfae65baff4e139
+P b6c4202432dc96f8f1740f52d0bf872116357fcc
+R 56e1f15e83703ebf857ebd55a98bbcfe
+T *branch * rbu-delete-then-insert
+T *sym-rbu-delete-then-insert *
+T -sym-trunk *
+U dan
+Z 681ea677d41a7fd6408ffd5219102c46