sqlcipher_export from attached database to main (2nd parameter specifies source)

This commit is contained in:
Stephen Lombardo 2018-09-27 13:06:59 -04:00
parent 867d0dafd5
commit c566ead72a
3 changed files with 101 additions and 18 deletions

View File

@ -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) {

View File

@ -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 */

View File

@ -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