diff --git a/SQLite.xs b/SQLite.xs index 2712ac6..e18e1cc 100644 --- a/SQLite.xs +++ b/SQLite.xs @@ -259,6 +259,28 @@ backup_to_file(dbh, filename) OUTPUT: RETVAL +static int +backup_from_dbh(dbh, from) + SV *dbh + SV *from + ALIAS: + DBD::SQLite::db::sqlite_backup_from_dbh = 1 + CODE: + RETVAL = sqlite_db_backup_from_dbh(aTHX_ dbh, from); + OUTPUT: + RETVAL + +static int +backup_to_dbh(dbh, to) + SV *dbh + SV *to + ALIAS: + DBD::SQLite::db::sqlite_backup_to_dbh = 1 + CODE: + RETVAL = sqlite_db_backup_to_dbh(aTHX_ dbh, to); + OUTPUT: + RETVAL + HV* table_column_metadata(dbh, dbname, tablename, columnname) SV* dbh diff --git a/dbdimp.c b/dbdimp.c index 37dc294..b6669ac 100644 --- a/dbdimp.c +++ b/dbdimp.c @@ -2615,6 +2615,49 @@ sqlite_db_backup_from_file(pTHX_ SV *dbh, char *filename) #endif } +int +sqlite_db_backup_from_dbh(pTHX_ SV *dbh, SV *from) +{ + D_imp_dbh(dbh); + +#if SQLITE_VERSION_NUMBER >= 3006011 + int rc; + sqlite3_backup *pBackup; + + imp_dbh_t *imp_dbh_from = (imp_dbh_t *)DBIh_COM(from); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to backup from file on inactive database handle"); + return FALSE; + } + + if (!DBIc_ACTIVE(imp_dbh_from)) { + sqlite_error(dbh, -2, "attempt to backup from inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + /* COMPAT: sqlite3_backup_* are only available for 3006011 or newer */ + pBackup = sqlite3_backup_init(imp_dbh->db, "main", imp_dbh_from->db, "main"); + if (pBackup) { + (void)sqlite3_backup_step(pBackup, -1); + (void)sqlite3_backup_finish(pBackup); + } + rc = sqlite3_errcode(imp_dbh->db); + + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_backup_from_file failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + + return TRUE; +#else + sqlite_error(dbh, SQLITE_ERROR, form("backup feature requires SQLite 3.6.11 and newer")); + return FALSE; +#endif +} + /* Accesses the SQLite Online Backup API, and copies the currently loaded * database into the passed filename. * Usual usage of this would be when you're operating on the :memory: @@ -2663,6 +2706,49 @@ sqlite_db_backup_to_file(pTHX_ SV *dbh, char *filename) #endif } +int +sqlite_db_backup_to_dbh(pTHX_ SV *dbh, SV *to) +{ + D_imp_dbh(dbh); + +#if SQLITE_VERSION_NUMBER >= 3006011 + int rc; + sqlite3_backup *pBackup; + + imp_dbh_t *imp_dbh_to = (imp_dbh_t *)DBIh_COM(to); + + if (!DBIc_ACTIVE(imp_dbh)) { + sqlite_error(dbh, -2, "attempt to backup to file on inactive database handle"); + return FALSE; + } + + if (!DBIc_ACTIVE(imp_dbh_to)) { + sqlite_error(dbh, -2, "attempt to backup to inactive database handle"); + return FALSE; + } + + croak_if_db_is_null(); + + /* COMPAT: sqlite3_backup_* are only available for 3006011 or newer */ + pBackup = sqlite3_backup_init(imp_dbh_to->db, "main", imp_dbh->db, "main"); + if (pBackup) { + (void)sqlite3_backup_step(pBackup, -1); + (void)sqlite3_backup_finish(pBackup); + } + rc = sqlite3_errcode(imp_dbh_to->db); + + if ( rc != SQLITE_OK ) { + sqlite_error(dbh, rc, form("sqlite_backup_to_file failed with error %s", sqlite3_errmsg(imp_dbh->db))); + return FALSE; + } + + return TRUE; +#else + sqlite_error(dbh, SQLITE_ERROR, form("backup feature requires SQLite 3.6.11 and newer")); + return FALSE; +#endif +} + int sqlite_db_limit(pTHX_ SV *dbh, int id, int new_value) { diff --git a/dbdimp.h b/dbdimp.h index efe6828..09b8883 100644 --- a/dbdimp.h +++ b/dbdimp.h @@ -117,6 +117,8 @@ int sqlite_bind_col( SV *sth, imp_sth_t *imp_sth, SV *col, SV *ref, IV sql_type, int sqlite_db_busy_timeout (pTHX_ SV *dbh, SV *timeout ); int sqlite_db_backup_from_file(pTHX_ SV *dbh, char *filename); int sqlite_db_backup_to_file(pTHX_ SV *dbh, char *filename); +int sqlite_db_backup_from_dbh(pTHX_ SV *dbh, SV *from); +int sqlite_db_backup_to_dbh(pTHX_ SV *dbh, SV *to); void sqlite_db_collation_needed(pTHX_ SV *dbh, SV *callback ); SV* sqlite_db_commit_hook( pTHX_ SV *dbh, SV *hook ); SV* sqlite_db_rollback_hook( pTHX_ SV *dbh, SV *hook ); diff --git a/lib/DBD/SQLite.pm b/lib/DBD/SQLite.pm index be28915..2e4f0d7 100644 --- a/lib/DBD/SQLite.pm +++ b/lib/DBD/SQLite.pm @@ -47,6 +47,8 @@ sub driver { DBD::SQLite::db->install_method('sqlite_set_authorizer'); DBD::SQLite::db->install_method('sqlite_backup_from_file'); DBD::SQLite::db->install_method('sqlite_backup_to_file'); + DBD::SQLite::db->install_method('sqlite_backup_from_dbh'); + DBD::SQLite::db->install_method('sqlite_backup_to_dbh'); DBD::SQLite::db->install_method('sqlite_enable_load_extension'); DBD::SQLite::db->install_method('sqlite_load_extension'); DBD::SQLite::db->install_method('sqlite_register_fts3_perl_tokenizer'); @@ -2206,6 +2208,19 @@ special :memory: database, and you wish to populate it from an existing DB. This method accesses the SQLite Online Backup API, and will take a backup of the currently connected database, and write it out to the named file. +=head2 $dbh->sqlite_backup_from_dbh( $another_dbh ) + +This method accesses the SQLite Online Backup API, and will take a backup of +the database for the passed handle, copying it to, and overwriting, your current database +connection. This can be particularly handy if your current connection is to the +special :memory: database, and you wish to populate it from an existing DB. +You can use this to backup from an in-memory database to another in-memory database. + +=head2 $dbh->sqlite_backup_to_dbh( $another_dbh ) + +This method accesses the SQLite Online Backup API, and will take a backup of +the currently connected database, and write it out to the passed database handle. + =head2 $dbh->sqlite_enable_load_extension( $bool ) Calling this method with a true value enables loading (external) diff --git a/t/34_online_backup.t b/t/34_online_backup.t index 1ee67d6..5b817ba 100644 --- a/t/34_online_backup.t +++ b/t/34_online_backup.t @@ -12,7 +12,7 @@ BEGIN { requires_sqlite('3.6.11') } use Test::NoWarnings; use DBI; -plan tests => 6 * @CALL_FUNCS + 1; +plan tests => 11 * @CALL_FUNCS + 1; foreach my $call_func (@CALL_FUNCS) { # Connect to the test db and add some stuff: @@ -69,3 +69,45 @@ foreach my $call_func (@CALL_FUNCS) { unlink $dbfile; } + +foreach my $call_func (@CALL_FUNCS) { + # Connect to the test db and add some stuff: + my $foo = connect_ok( dbfile => ':memory:', RaiseError => 1 ); + $foo->do( + 'CREATE TABLE online_backup_test( id INTEGER PRIMARY KEY, foo INTEGER )' + ); + $foo->do("INSERT INTO online_backup_test (foo) VALUES ($$)"); + + my $dbh = DBI->connect( + 'dbi:SQLite:dbname=:memory:', + undef, undef, + { RaiseError => 1 } + ); + + ok($dbh->$call_func($foo, 'backup_from_dbh')); + + { + my ($count) = $dbh->selectrow_array( + "SELECT count(foo) FROM online_backup_test WHERE foo=$$" + ); + is($count, 1, "Found our process ID in backed-up table"); + } + + # Add more data then attempt to copy it back to file: + $dbh->do( + 'CREATE TABLE online_backup_test2 ( id INTEGER PRIMARY KEY, foo INTEGER )' + ); + $dbh->do("INSERT INTO online_backup_test2 (foo) VALUES ($$)"); + + # backup to dbh (foo): + ok($dbh->$call_func($foo, 'backup_to_dbh')); + + $dbh->disconnect; + + my ($count) = $foo->selectrow_array( + "SELECT count(foo) FROM online_backup_test2 WHERE foo=$$" + ); + is($count, 1, "Found our process ID in table back on disk"); + + $foo->disconnect; +}