Allow the ota extension to write to tables with no PRIMARY KEY declaration.

FossilOrigin-Name: ba59a7e2ba97244492cbca9247456df0f3f19248
This commit is contained in:
dan 2014-12-06 19:30:41 +00:00
parent ca3d648f9c
commit 7ca1d07aff
5 changed files with 173 additions and 40 deletions

View File

@ -177,6 +177,9 @@ foreach {tn2 cmd} {1 run_ota 2 step_ota 3 step_ota_uri} {
# relaxed so that external PRIMARY KEYs on tables with automatic rowids
# are now allowed.
#
# UPDATE 2: Tables without any PRIMARY KEY declaration are now allowed.
# However the input table must feature an "ota_rowid" column.
#
reset_db
create_ota1 ota.db
do_execsql_test 2.1 { CREATE TABLE t1(a, b, c) }
@ -186,7 +189,7 @@ do_test 2.2 {
} {SQLITE_ERROR}
do_test 2.3 {
list [catch { ota close } msg] $msg
} {1 {SQLITE_ERROR - table t1 has no PRIMARY KEY}}
} {1 {SQLITE_ERROR - table data_t1 requires ota_rowid column}}
reset_db
do_execsql_test 2.4 { CREATE TABLE t1(a PRIMARY KEY, b, c) }
do_test 2.5 {

View File

@ -9,7 +9,7 @@
#
#***********************************************************************
#
# Test OTA with virtual tables
# Test OTA with virtual tables. And tables with no PRIMARY KEY declarations.
#
if {![info exists testdir]} {
@ -60,6 +60,68 @@ do_execsql_test 1.2.2 { SELECT rowid, * FROM f1 } {
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

View File

@ -104,8 +104,11 @@ struct OtaObjIter {
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 */
@ -124,6 +127,14 @@ struct OtaObjIter {
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.
*/
@ -228,8 +239,7 @@ static void otaObjIterFreeCols(OtaObjIter *pIter){
pIter->nTblCol = 0;
sqlite3_free(pIter->zMask);
pIter->zMask = 0;
pIter->bRowid = 0;
pIter->bVtab = 0;
pIter->eType = 0; /* Invalid value */
}
/*
@ -471,11 +481,11 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){
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
@ -494,6 +504,9 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){
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;
@ -519,20 +532,33 @@ static int otaObjIterGetCols(sqlite3ota *p, OtaObjIter *pIter){
}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
);
}
}
}
@ -618,6 +644,17 @@ static char *otaObjIterGetCollist(
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,
@ -637,21 +674,31 @@ static char *otaObjIterGetOldlist(
}
/* 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;
@ -759,7 +806,7 @@ static int otaObjIterPrepareAll(
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 ){
@ -777,10 +824,10 @@ static int otaObjIterPrepareAll(
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{
@ -798,11 +845,13 @@ static int otaObjIterPrepareAll(
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;
@ -811,7 +860,7 @@ static int otaObjIterPrepareAll(
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
)
);
}
@ -821,7 +870,7 @@ static int otaObjIterPrepareAll(
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
)
);
}
@ -835,8 +884,11 @@ static int otaObjIterPrepareAll(
);
}
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,
@ -858,12 +910,13 @@ static int otaObjIterPrepareAll(
"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 "
@ -1074,6 +1127,15 @@ static int otaStepType(sqlite3ota *p, const char **pzMask){
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.
**
@ -1122,13 +1184,17 @@ static int otaStep(sqlite3ota *p){
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);
}
@ -1143,7 +1209,9 @@ static int otaStep(sqlite3ota *p){
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);
}
@ -1195,7 +1263,7 @@ int sqlite3ota_step(sqlite3ota *p){
/* 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{

View File

@ -1,5 +1,5 @@
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
@ -125,7 +125,7 @@ F ext/misc/vtshim.c babb0dc2bf116029e3e7c9a618b8a1377045303e
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
@ -134,9 +134,9 @@ F ext/ota/ota5.test ad0799daf8923ddebffe75ae8c5504ca90b7fadb
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
@ -1237,7 +1237,7 @@ F tool/vdbe_profile.tcl 67746953071a9f8f2f668b73fe899074e2c6d8c1
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

View File

@ -1 +1 @@
ccee999649d0fa1d48e53847542f4cbe05e3d694
ba59a7e2ba97244492cbca9247456df0f3f19248