diff --git a/src/crypto.c b/src/crypto.c index 8343b98..92977e6 100644 --- a/src/crypto.c +++ b/src/crypto.c @@ -794,7 +794,8 @@ static int sqlcipher_execExecSql(sqlite3 *db, char **pzErrMsg, const char *zSql) */ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **argv) { sqlite3 *db = sqlite3_context_db_handle(context); - const char* attachedDb = (const char*) sqlite3_value_text(argv[0]); + const char* targetDb, *sourceDb; + int saved_flags; /* Saved value of the db->flags */ int saved_nChange; /* Saved value of db->nChange */ int saved_nTotalChange; /* Saved value of db->nTotalChange */ @@ -803,7 +804,16 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar int rc = SQLITE_OK; /* Return code from service routines */ char *zSql = NULL; /* SQL statements */ char *pzErrMsg = NULL; - + + if(argc != 1 && argc != 2) { + rc = SQLITE_ERROR; + pzErrMsg = sqlite3_mprintf("invalid number of arguments (%d) passed to sqlcipher_export", argc); + goto end_of_export; + } + + targetDb = (const char*) sqlite3_value_text(argv[0]); + sourceDb = (argc == 2) ? (char *) sqlite3_value_text(argv[1]) : "main"; + saved_flags = db->flags; saved_nChange = db->nChange; saved_nTotalChange = db->nTotalChange; @@ -820,25 +830,25 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar */ zSql = sqlite3_mprintf( "SELECT 'CREATE TABLE %s.' || substr(sql,14) " - " FROM sqlite_master WHERE type='table' AND name!='sqlite_sequence'" + " FROM %s.sqlite_master WHERE type='table' AND name!='sqlite_sequence'" " AND rootpage>0" - , attachedDb); + , targetDb, sourceDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); zSql = sqlite3_mprintf( "SELECT 'CREATE INDEX %s.' || substr(sql,14)" - " FROM sqlite_master WHERE sql LIKE 'CREATE INDEX %%' " - , attachedDb); + " FROM %s.sqlite_master WHERE sql LIKE 'CREATE INDEX %%' " + , targetDb, sourceDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); zSql = sqlite3_mprintf( "SELECT 'CREATE UNIQUE INDEX %s.' || substr(sql,21) " - " FROM sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'" - , attachedDb); + " FROM %s.sqlite_master WHERE sql LIKE 'CREATE UNIQUE INDEX %%'" + , targetDb, sourceDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); @@ -849,11 +859,11 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar */ zSql = sqlite3_mprintf( "SELECT 'INSERT INTO %s.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';'" - "FROM main.sqlite_master " + "|| ' SELECT * FROM %s.' || quote(name) || ';'" + "FROM %s.sqlite_master " "WHERE type = 'table' AND name!='sqlite_sequence' " " AND rootpage>0" - , attachedDb); + , targetDb, sourceDb, sourceDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); @@ -863,16 +873,16 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar zSql = sqlite3_mprintf( "SELECT 'DELETE FROM %s.' || quote(name) || ';' " "FROM %s.sqlite_master WHERE name='sqlite_sequence' " - , attachedDb, attachedDb); + , targetDb, targetDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); zSql = sqlite3_mprintf( "SELECT 'INSERT INTO %s.' || quote(name) " - "|| ' SELECT * FROM main.' || quote(name) || ';' " + "|| ' SELECT * FROM %s.' || quote(name) || ';' " "FROM %s.sqlite_master WHERE name=='sqlite_sequence';" - , attachedDb, attachedDb); + , targetDb, sourceDb, targetDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execExecSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); @@ -885,10 +895,10 @@ void sqlcipher_exportFunc(sqlite3_context *context, int argc, sqlite3_value **ar zSql = sqlite3_mprintf( "INSERT INTO %s.sqlite_master " " SELECT type, name, tbl_name, rootpage, sql" - " FROM main.sqlite_master" + " FROM %s.sqlite_master" " WHERE type='view' OR type='trigger'" " OR (type='table' AND rootpage=0)" - , attachedDb); + , targetDb, sourceDb); rc = (zSql == NULL) ? SQLITE_NOMEM : sqlcipher_execSql(db, &pzErrMsg, zSql); if( rc!=SQLITE_OK ) goto end_of_export; sqlite3_free(zSql); @@ -901,7 +911,7 @@ end_of_export: db->xTrace = saved_xTrace; db->mTrace = saved_mTrace; - sqlite3_free(zSql); + if(zSql) sqlite3_free(zSql); if(rc) { if(pzErrMsg != NULL) { diff --git a/src/func.c b/src/func.c index 166c416..c7e7902 100644 --- a/src/func.c +++ b/src/func.c @@ -1801,7 +1801,7 @@ void sqlite3RegisterPerConnectionBuiltinFunctions(sqlite3 *db){ /* BEGIN SQLCIPHER */ #ifdef SQLITE_HAS_CODEC #ifndef OMIT_EXPORT - sqlite3CreateFunc(db, "sqlcipher_export", 1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0, 0, 0); + sqlite3CreateFunc(db, "sqlcipher_export", -1, SQLITE_TEXT, 0, sqlcipher_exportFunc, 0, 0, 0, 0, 0); #endif #endif /* END SQLCIPHER */ diff --git a/test/crypto.test b/test/crypto.test index 4c108b5..1cbf16a 100644 --- a/test/crypto.test +++ b/test/crypto.test @@ -1096,6 +1096,79 @@ db close file delete -force test.db file delete -force test2.db +# use the sqlcipher_export function +# to copy a complicated attached database to the main database +do_test export-attached-database { + sqlite_orig db test.db + + execsql { + PRAGMA key = 'testkey'; + CREATE TABLE t1(a INTEGER PRIMARY KEY AUTOINCREMENT, b, c); + CREATE UNIQUE INDEX b_idx ON t1(b); + CREATE INDEX c_idx ON t1(c); + + CREATE TABLE t2(b,c); + CREATE TRIGGER t2_after_insert AFTER INSERT ON t2 + BEGIN + INSERT INTO t1(b,c) VALUES (new.b, new.c); + END; + + CREATE VIEW v1 AS + SELECT c FROM t1; + + CREATE VIRTUAL TABLE fts USING fts5(a,b); + + BEGIN; + -- start with one known value + INSERT INTO t2 VALUES(1000000,'value 1000000'); + } + + for {set i 1} {$i<=999} {incr i} { + set r [expr {int(rand()*500000)}] + execsql "INSERT INTO t2 VALUES($i,'value $r');" + } + + execsql { + INSERT INTO fts SELECT b,c FROM t1; + COMMIT; + } + db close + + sqlite_orig db test2.db + execsql { + PRAGMA key = 'testkey2'; + + CREATE TABLE t3(a INTEGER PRIMARY KEY AUTOINCREMENT, b, c); + CREATE UNIQUE INDEX d_idx ON t3(b); + INSERT INTO t3(b,c) VALUES ('one', 'two'); + + ATTACH DATABASE 'test.db' AS db KEY 'testkey'; + + SELECT sqlcipher_export('main', 'db'); + + DETACH DATABASE db; + INSERT INTO t3(b,c) VALUES ('three', 'four'); + } + db close + + sqlite_orig db test2.db + execsql { + PRAGMA key = 'testkey2'; + SELECT count(*) FROM t1; + SELECT count(*) FROM v1; + SELECT count(*) FROM sqlite_sequence; + SELECT seq FROM sqlite_sequence WHERE name = 't1'; + INSERT INTO t2 VALUES(10001, 'value 938383'); + SELECT count(*) FROM t1; -- verify the trigger worked + SELECT seq FROM sqlite_sequence WHERE name = 't1'; -- verify that autoincrement worked + SELECT a FROM fts WHERE b MATCH '1000000'; + SELECT count(*) FROM t3; + } +} {1000 1000 1 1000 1001 1001 1000000 2} +db close +file delete -force test.db +file delete -force test2.db + # 1. create a database with WAL journal mode # 2. create table and insert operations should work # 3. close database, open it again