#
#***********************************************************************
#
-# Test OTA with virtual tables
+# Test OTA with virtual tables. And tables with no PRIMARY KEY declarations.
#
if {![info exists testdir]} {
do_execsql_test 1.2.3 { INSERT INTO f1(f1) VALUES('integrity-check') }
integrity_check 1.2.4
+#-------------------------------------------------------------------------
+# Tables with no PK declaration.
+#
+
+# Run the OTA in file $ota on target database $target until completion.
+#
+proc run_ota {target ota} {
+ sqlite3ota ota $target $ota
+ while { [ota step]=="SQLITE_OK" } {}
+ ota close
+}
+
+foreach {tn idx} {
+ 1 { }
+ 2 {
+ CREATE INDEX i1 ON t1(a);
+ }
+ 3 {
+ CREATE INDEX i1 ON t1(b, c);
+ CREATE INDEX i2 ON t1(c, b);
+ CREATE INDEX i3 ON t1(a, a, a, b, b, b, c, c, c);
+ }
+} {
+
+ reset_db
+ do_execsql_test 2.$tn.1 {
+ CREATE TABLE t1(a, b, c);
+ INSERT INTO t1 VALUES(1, 2, 3);
+ INSERT INTO t1 VALUES(4, 5, 6);
+ INSERT INTO t1(rowid, a, b, c) VALUES(-1, 'a', 'b', 'c');
+ INSERT INTO t1(rowid, a, b, c) VALUES(-2, 'd', 'e', 'f');
+ }
+
+ db eval $idx
+
+ do_test 2.$tn.2 {
+ forcedelete ota.db
+ sqlite3 db2 ota.db
+ db2 eval {
+ CREATE TABLE data_t1(ota_rowid, a, b, c, ota_control);
+ INSERT INTO data_t1 VALUES(3, 'x', 'y', 'z', 0);
+ INSERT INTO data_t1 VALUES(NULL, 'X', 'Y', 'Z', 0);
+ INSERT INTO data_t1 VALUES('1', NULL, NULL, NULL, 1);
+ INSERT INTO data_t1 VALUES(-2, NULL, NULL, 'fff', '..x');
+ }
+ db2 close
+ } {}
+
+ run_ota test.db ota.db
+
+ do_execsql_test 2.$tn.3 {
+ SELECT rowid, a, b, c FROM t1 ORDER BY rowid;
+ } {
+ -2 d e fff
+ -1 a b c
+ 2 4 5 6
+ 3 x y z
+ 4 X Y Z
+ }
+
+ integrity_check 2.$tn.4
+}
finish_test
int nTblCol; /* Size of azTblCol[] array */
char **azTblCol; /* Array of quoted column names */
unsigned char *abTblPk; /* Array of flags - true for PK columns */
+ int eType;
+#if 0
unsigned char bRowid; /* True for implicit IPK tables */
unsigned char bVtab; /* True for a virtual table */
+#endif
/* Output variables. zTbl==0 implies EOF. */
int bCleanup; /* True in "cleanup" state */
sqlite3_stmt *pUpdate; /* Last update statement (or NULL) */
};
+/*
+** Values for OtaObjIter.eType
+*/
+#define OTA_PK_REAL 1 /* Table has a real primary key */
+#define OTA_PK_EXTERNAL 2 /* Table has an external primary key index */
+#define OTA_PK_NONE 3 /* Table has no PK (use rowid) */
+#define OTA_PK_VTAB 4 /* Table is a virtual table (use rowid) */
+
/*
** OTA handle.
*/
pIter->nTblCol = 0;
sqlite3_free(pIter->zMask);
pIter->zMask = 0;
- pIter->bRowid = 0;
- pIter->bVtab = 0;
+ pIter->eType = 0; /* Invalid value */
}
/*
if( pIter->azTblCol==0 ){
sqlite3_stmt *pStmt = 0;
int nCol = 0;
- int bSeenPk = 0;
int i; /* for() loop iterator variable */
int rc2; /* sqlite3_finalize() return value */
+ int bOtaRowid = 0; /* If input table has column "ota_rowid" */
- assert( pIter->bRowid==0 && pIter->bVtab==0 );
+ assert( pIter->eType==0 );
/* Populate the azTblCol[] and nTblCol variables based on the columns
** of the input table. Ignore any input table columns that begin with
pIter->azTblCol[pIter->nTblCol++] = zCopy;
if( zCopy==0 ) p->rc = SQLITE_NOMEM;
}
+ else if( 0==sqlite3_stricmp("ota_rowid", zName) ){
+ bOtaRowid = 1;
+ }
}
sqlite3_finalize(pStmt);
pStmt = 0;
}else{
int iPk = sqlite3_column_int(pStmt, 5);
pIter->abTblPk[i] = (iPk!=0);
- if( iPk ) bSeenPk = 1;
- if( iPk<0 ) pIter->bRowid = 1;
+ if( iPk ){
+ pIter->eType = (iPk<0) ? OTA_PK_EXTERNAL : OTA_PK_REAL;
+ }
}
}
rc2 = sqlite3_finalize(pStmt);
if( p->rc==SQLITE_OK ) p->rc = rc2;
- if( p->rc==SQLITE_OK && bSeenPk==0 ){
- const char *zTab = pIter->zTbl;
- if( otaIsVtab(p, zTab) ){
- pIter->bVtab = 1;
- }else{
- p->zErrmsg = sqlite3_mprintf("table %s has no PRIMARY KEY", zTab);
+ if( p->rc==SQLITE_OK ){
+ if( pIter->eType==0 ){
+ /* This must either be a virtual table, or a regular table with no
+ ** PRIMARY KEY declaration whatsoever. */
+ if( bOtaRowid==0 ){
+ p->rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf(
+ "table data_%q requires ota_rowid column", pIter->zTbl
+ );
+ }else if( otaIsVtab(p, pIter->zTbl) ){
+ pIter->eType = OTA_PK_VTAB;
+ }else{
+ pIter->eType = OTA_PK_NONE;
+ }
+ }else if( bOtaRowid ){
p->rc = SQLITE_ERROR;
+ p->zErrmsg = sqlite3_mprintf(
+ "table data_%q may not have ota_rowid column", pIter->zTbl
+ );
}
}
}
return zList;
}
+/*
+** Assuming the current table columns are "a", "b" and "c", and the zObj
+** paramter is passed "old", return a string of the form:
+**
+** "old.a, old.b, old.b"
+**
+** With the column names escaped.
+**
+** For tables with implicit rowids - OTA_PK_EXTERNAL and OTA_PK_NONE, append
+** the text ", old._rowid_" to the returned value.
+*/
static char *otaObjIterGetOldlist(
sqlite3ota *p,
OtaObjIter *pIter,
}
/* For a table with implicit rowids, append "old._rowid_" to the list. */
- if( pIter->bRowid ){
+ if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){
zList = sqlite3_mprintf("%z, %s._rowid_", zList, zObj);
}
}
return zList;
}
+/*
+** Return an expression that can be used in a WHERE clause to match the
+** primary key of the current table. For example, if the table is:
+**
+** CREATE TABLE t1(a, b, c, PRIMARY KEY(b, c));
+**
+** Return the string:
+**
+** "b = ?1 AND c = ?2"
+*/
static char *otaObjIterGetWhere(
sqlite3ota *p,
OtaObjIter *pIter
){
char *zList = 0;
if( p->rc==SQLITE_OK ){
- if( pIter->bVtab ){
- zList = otaMPrintfAndCollectError(p, "rowid = ?%d", pIter->nTblCol+1);
+ if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){
+ zList = otaMPrintfAndCollectError(p, "_rowid_ = ?%d", pIter->nTblCol+1);
}else{
const char *zSep = "";
int i;
int *aiCol; /* Column map */
const char **azColl; /* Collation sequences */
- assert( pIter->bVtab==0 );
+ assert( pIter->eType!=OTA_PK_VTAB );
/* Create the index writers */
if( p->rc==SQLITE_OK ){
zCollist = otaObjIterGetCollist(p, pIter, pIter->nCol, aiCol, azColl);
if( p->rc==SQLITE_OK ){
char *zSql;
- if( pIter->bRowid ){
+ if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){
zSql = sqlite3_mprintf(
"SELECT %s, ota_control FROM ota.'ota_tmp_%q' ORDER BY %s%s",
- zCollist, pIter->zTbl,
+ zCollist, pIter->zTbl,
zCollist, zLimit
);
}else{
p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz, zSql);
}
}else{
+ int bOtaRowid = (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE);
const char *zTbl = pIter->zTbl;
- char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol+pIter->bVtab);
char *zWhere = otaObjIterGetWhere(p, pIter);
char *zOldlist = otaObjIterGetOldlist(p, pIter, "old");
char *zNewlist = otaObjIterGetOldlist(p, pIter, "new");
+ char *zBindings = otaObjIterGetBindlist(p, pIter->nTblCol + bOtaRowid);
+
zCollist = otaObjIterGetCollist(p, pIter, pIter->nTblCol, 0, 0);
pIter->nCol = pIter->nTblCol;
p->rc = prepareFreeAndCollectError(p->db, &pIter->pSelect, pz,
sqlite3_mprintf(
"SELECT %s, ota_control%s FROM ota.'data_%q'%s",
- zCollist, (pIter->bVtab ? ", ota_rowid" : ""), zTbl, zLimit
+ zCollist, (bOtaRowid ? ", ota_rowid" : ""), zTbl, zLimit
)
);
}
p->rc = prepareFreeAndCollectError(p->db, &pIter->pInsert, pz,
sqlite3_mprintf(
"INSERT INTO main.%Q(%s%s) VALUES(%s)",
- zTbl, zCollist, (pIter->bVtab ? ", rowid" : ""), zBindings
+ zTbl, zCollist, (bOtaRowid ? ", _rowid_" : ""), zBindings
)
);
}
);
}
- if( pIter->bVtab==0 ){
- const char *zOtaRowid = (pIter->bRowid ? ", ota_rowid" : "");
+ if( pIter->eType!=OTA_PK_VTAB ){
+ const char *zOtaRowid = "";
+ if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){
+ zOtaRowid = ", ota_rowid";
+ }
/* Create the ota_tmp_xxx table and the triggers to populate it. */
otaMPrintfExec(p,
"BEGIN "
" INSERT INTO 'ota_tmp_%q'(ota_control, %s%s) VALUES(3, %s);"
"END;"
- , zTbl, (pIter->bRowid ? ", 0 AS ota_rowid" : ""), zTbl,
+ , zTbl, (pIter->eType==OTA_PK_EXTERNAL ? ", 0 AS ota_rowid" : "")
+ , zTbl,
zTbl, zTbl, zTbl, zCollist, zOtaRowid, zOldlist,
zTbl, zTbl, zTbl, zCollist, zOtaRowid, zOldlist,
zTbl, zTbl, zTbl, zCollist, zOtaRowid, zNewlist
);
- if( pIter->bRowid ){
+ if( pIter->eType==OTA_PK_EXTERNAL || pIter->eType==OTA_PK_NONE ){
otaMPrintfExec(p,
"CREATE TEMP TRIGGER ota_insert_%q AFTER INSERT ON main.%Q "
"BEGIN "
return res;
}
+#ifdef SQLITE_DEBUG
+static void assertColumnName(sqlite3_stmt *pStmt, int iCol, const char *zName){
+ const char *zCol = sqlite3_column_name(pStmt, iCol);
+ assert( 0==sqlite3_stricmp(zName, zCol) );
+}
+#else
+# define assertColumnName(x,y,z)
+#endif
+
/*
** This function does the work for an sqlite3ota_step() call.
**
pVal = sqlite3_column_value(pIter->pSelect, i);
sqlite3_bind_value(pWriter, i+1, pVal);
}
- if( pIter->bVtab ){
- /* For a virtual table, the SELECT statement is:
+ if( pIter->zIdx==0
+ && (pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE)
+ ){
+ /* For a virtual table, or a table with no primary key, the
+ ** SELECT statement is:
**
** SELECT <cols>, ota_control, ota_rowid FROM ....
**
** Hence column_value(pIter->nCol+1).
*/
+ assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
sqlite3_bind_value(pWriter, pIter->nCol+1, pVal);
}
pVal = sqlite3_column_value(pIter->pSelect, i);
sqlite3_bind_value(pUpdate, i+1, pVal);
}
- if( pIter->bVtab ){
+ if( pIter->eType==OTA_PK_VTAB || pIter->eType==OTA_PK_NONE ){
+ /* Bind the ota_rowid value to column _rowid_ */
+ assertColumnName(pIter->pSelect, pIter->nCol+1, "ota_rowid");
pVal = sqlite3_column_value(pIter->pSelect, pIter->nCol+1);
sqlite3_bind_value(pUpdate, pIter->nCol+1, pVal);
}
/* Clean up the ota_tmp_xxx table for the previous table. It
** cannot be dropped as there are currently active SQL statements.
** But the contents can be deleted. */
- if( pIter->bVtab==0 ){
+ if( pIter->eType!=OTA_PK_VTAB ){
otaMPrintfExec(p, "DELETE FROM ota.'ota_tmp_%q'", pIter->zTbl);
}
}else{
-C Update\sota\sso\sthat\sthe\shidden\scolumns\sof\svirtual\stables\smay\sbe\swritten.
-D 2014-11-27T18:09:46.630
+C Allow\sthe\sota\sextension\sto\swrite\sto\stables\swith\sno\sPRIMARY\sKEY\sdeclaration.
+D 2014-12-06T19:30:41.673
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
F Makefile.in a226317fdf3f4c895fb3cfedc355b4d0868ce1fb
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
F ext/misc/wholenumber.c 784b12543d60702ebdd47da936e278aa03076212
F ext/ota/README.txt 78d4a9f78f567d4bf826cf0f02df6254902562ca
F ext/ota/ota.c c11a85af71dccc45976622fe7a51169a481caa91
-F ext/ota/ota1.test a8f9d89c9b2d381a663bcedaa5dd5952cdbd1231
+F ext/ota/ota1.test 64770d76d3dc00c24f9a78ac69e4448708bde985
F ext/ota/ota10.test ab815dff9cef7248c504f06b888627d236f25e9c
F ext/ota/ota2.test 4568c2671d19dbde789fb9091d727a2e94880128
F ext/ota/ota3.test 71bd8cc0cf8d7e7d9bb11a1fcc238320a5a9d8c8
F ext/ota/ota6.test 82f1f757ec9b2ad07d6de4060b8e3ba8e44dfdd3
F ext/ota/ota7.test 1fe2c5761705374530e29f70c39693076028221a
F ext/ota/ota8.test cd70e63a0c29c45c0906692827deafa34638feda
-F ext/ota/ota9.test d9ad30ccb4e08f878e382876fe67752309538af9
+F ext/ota/ota9.test d3eee95dd836824d07a22e5efcdb7bf6e869358b
F ext/ota/otafault.test be02466863015a583cc0ceb6aca871a5e6f7a71b
-F ext/ota/sqlite3ota.c f1e930690ec2bcb72138301ebf05ea2ffd4450de
+F ext/ota/sqlite3ota.c 84cab0f965144772068ec0183252ae5e5278f0be
F ext/ota/sqlite3ota.h 8dc9c812e61f47f497336a45f820bee88b33a2c5
F ext/rtree/README 6315c0d73ebf0ec40dedb5aa0e942bc8b54e3761
F ext/rtree/rtree.c 57bec53e1a677ab74217fe1f20a58c3a47261d6b
F tool/warnings-clang.sh f6aa929dc20ef1f856af04a730772f59283631d4
F tool/warnings.sh 0abfd78ceb09b7f7c27c688c8e3fe93268a13b32
F tool/win/sqlite.vsix deb315d026cc8400325c5863eef847784a219a2f
-P 600cefdd4d29c1de4d107fa7ddeb76a18edce4f5
-R 19cf180a791a327f1f4fb68723a586ef
+P ccee999649d0fa1d48e53847542f4cbe05e3d694
+R 23b9af0814440584d410f9d611aafb5a
U dan
-Z 7646a22500cce7c7ef211b057e34ff27
+Z b3bc69908e00e34016dd9eb2dbbe05c2