From 44aa78603dc0c6a8faf554e56ba474c401dee030 Mon Sep 17 00:00:00 2001 From: ndossche <7771979+ndossche@users.noreply.github.com> Date: Sun, 1 Mar 2026 20:12:06 +0100 Subject: [PATCH] Add Pdo\Sqlite::backup() --- UPGRADING | 3 + ext/pdo_sqlite/pdo_sqlite.c | 51 ++++++++++++ ext/pdo_sqlite/pdo_sqlite.stub.php | 2 + ext/pdo_sqlite/pdo_sqlite_arginfo.h | 10 ++- .../tests/subclasses/pdo_sqlite_backup.phpt | 83 +++++++++++++++++++ 5 files changed, 148 insertions(+), 1 deletion(-) create mode 100644 ext/pdo_sqlite/tests/subclasses/pdo_sqlite_backup.phpt diff --git a/UPGRADING b/UPGRADING index 7e47d0ba4817c..7fdd7b7e4fae1 100644 --- a/UPGRADING +++ b/UPGRADING @@ -132,6 +132,9 @@ PHP 8.6 UPGRADE NOTES 6. New Functions ======================================== +- Pdo_Sqlite: + . Pdo\Sqlite::backup() analogous to Sqlite3::backup(). + - Reflection: . ReflectionConstant::inNamespace() . Added ReflectionProperty::isReadable() and ReflectionProperty::isWritable(). diff --git a/ext/pdo_sqlite/pdo_sqlite.c b/ext/pdo_sqlite/pdo_sqlite.c index 667948fea9f1f..d5c829e0ee1c0 100644 --- a/ext/pdo_sqlite/pdo_sqlite.c +++ b/ext/pdo_sqlite/pdo_sqlite.c @@ -362,6 +362,57 @@ PHP_METHOD(Pdo_Sqlite, setAuthorizer) RETURN_THROWS(); } +PHP_METHOD(Pdo_Sqlite, backup) +{ + zval *destination_object; + char *source_dbname = "main", *destination_dbname = "main"; + size_t source_dbname_len, destination_dbname_len; + int rc; + + ZEND_PARSE_PARAMETERS_START(1, 3) + Z_PARAM_OBJECT_OF_CLASS(destination_object, pdosqlite_ce) + Z_PARAM_OPTIONAL + Z_PARAM_PATH(source_dbname, source_dbname_len) + Z_PARAM_PATH(destination_dbname, destination_dbname_len) + ZEND_PARSE_PARAMETERS_END(); + + pdo_dbh_t *dbh = Z_PDO_DBH_P(destination_object); + PDO_CONSTRUCT_CHECK; + pdo_sqlite_db_handle *destination_db_handle = dbh->driver_data; + dbh = Z_PDO_DBH_P(ZEND_THIS); + PDO_CONSTRUCT_CHECK; + pdo_sqlite_db_handle *source_db_handle = dbh->driver_data; + + sqlite3_backup *dbBackup = sqlite3_backup_init(destination_db_handle->db, destination_dbname, source_db_handle->db, source_dbname); + + if (dbBackup) { + do { + rc = sqlite3_backup_step(dbBackup, -1); + } while (rc == SQLITE_OK); + + /* Release resources allocated by sqlite3_backup_init(). */ + rc = sqlite3_backup_finish(dbBackup); + } else { + rc = sqlite3_errcode(source_db_handle->db); + } + + if (rc != SQLITE_OK) { + if (rc == SQLITE_BUSY) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Backup failed: source database is busy"); + } else if (rc == SQLITE_LOCKED) { + pdo_raise_impl_error(dbh, NULL, "HY000", "Backup failed: source database is locked"); + } else { + char *message; + spprintf(&message, 0, "Backup failed: %s", sqlite3_errmsg(source_db_handle->db)); + pdo_raise_impl_error(dbh, NULL, "HY000", message); + efree(message); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} + static int php_sqlite_collation_callback(void *context, int string1_len, const void *string1, int string2_len, const void *string2) { diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index 53f1ceba427b0..d3a29c8362a34 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -94,4 +94,6 @@ public function openBlob( ) {} public function setAuthorizer(?callable $callback): void {} + + public function backup(Sqlite $destination, string $sourceDatabase = "main", string $destinationDatabase = "main"): bool {} } diff --git a/ext/pdo_sqlite/pdo_sqlite_arginfo.h b/ext/pdo_sqlite/pdo_sqlite_arginfo.h index 73a9158301c0a..b062bed73d499 100644 --- a/ext/pdo_sqlite/pdo_sqlite_arginfo.h +++ b/ext/pdo_sqlite/pdo_sqlite_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit pdo_sqlite.stub.php instead. - * Stub hash: 721c46905fa8fb1e18d7196ed85c37f56049ea33 */ + * Stub hash: 7f1470a568f9610eb7cd17e5acfa41ebfc19b2c5 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Sqlite_createAggregate, 0, 3, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0) @@ -38,6 +38,12 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Sqlite_setAuthorizer, ZEND_ARG_TYPE_INFO(0, callback, IS_CALLABLE, 1) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Pdo_Sqlite_backup, 0, 1, _IS_BOOL, 0) + ZEND_ARG_OBJ_INFO(0, destination, Pdo\\Sqlite, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, sourceDatabase, IS_STRING, 0, "\"main\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, destinationDatabase, IS_STRING, 0, "\"main\"") +ZEND_END_ARG_INFO() + ZEND_METHOD(Pdo_Sqlite, createAggregate); ZEND_METHOD(Pdo_Sqlite, createCollation); ZEND_METHOD(Pdo_Sqlite, createFunction); @@ -46,6 +52,7 @@ ZEND_METHOD(Pdo_Sqlite, loadExtension); #endif ZEND_METHOD(Pdo_Sqlite, openBlob); ZEND_METHOD(Pdo_Sqlite, setAuthorizer); +ZEND_METHOD(Pdo_Sqlite, backup); static const zend_function_entry class_Pdo_Sqlite_methods[] = { ZEND_ME(Pdo_Sqlite, createAggregate, arginfo_class_Pdo_Sqlite_createAggregate, ZEND_ACC_PUBLIC) @@ -56,6 +63,7 @@ static const zend_function_entry class_Pdo_Sqlite_methods[] = { #endif ZEND_ME(Pdo_Sqlite, openBlob, arginfo_class_Pdo_Sqlite_openBlob, ZEND_ACC_PUBLIC) ZEND_ME(Pdo_Sqlite, setAuthorizer, arginfo_class_Pdo_Sqlite_setAuthorizer, ZEND_ACC_PUBLIC) + ZEND_ME(Pdo_Sqlite, backup, arginfo_class_Pdo_Sqlite_backup, ZEND_ACC_PUBLIC) ZEND_FE_END }; diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_backup.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_backup.phpt new file mode 100644 index 0000000000000..d154164c50064 --- /dev/null +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_backup.phpt @@ -0,0 +1,83 @@ +--TEST-- +Pdo\Sqlite::backup test +--EXTENSIONS-- +pdo +pdo_sqlite +--FILE-- +exec('CREATE TABLE test (a, b);'); +$db->exec('INSERT INTO test VALUES (42, \'php\');'); + +$db2 = Pdo::connect('sqlite::memory:'); + +echo "Backup to DB2\n"; +var_dump($db->backup($db2)); + +echo "Checking if table has been copied\n"; +var_dump($db2->query('SELECT COUNT(*) FROM sqlite_master;')->fetchAll()); + +echo "Checking backup contents\n"; +var_dump($db2->query('SELECT a FROM test;')->fetchAll()); +var_dump($db2->query('SELECT b FROM test;')->fetchAll()); + +echo "Resetting DB2\n"; + +unset($db2); +$db2 = Pdo::connect('sqlite::memory:'); + +echo "Locking DB1\n"; +var_dump($db->exec('BEGIN EXCLUSIVE;')); + +echo "Backup to DB2 (should fail)\n"; +try { + $db->backup($db2); +} catch (PDOException $e) { + echo $e->getMessage(), "\n"; +} +$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING); +var_dump($db->backup($db2)); + +?> +--EXPECTF-- +Backup to DB2 +bool(true) +Checking if table has been copied +array(1) { + [0]=> + array(2) { + ["COUNT(*)"]=> + int(1) + [0]=> + int(1) + } +} +Checking backup contents +array(1) { + [0]=> + array(2) { + ["a"]=> + int(42) + [0]=> + int(42) + } +} +array(1) { + [0]=> + array(2) { + ["b"]=> + string(3) "php" + [0]=> + string(3) "php" + } +} +Resetting DB2 +Locking DB1 +int(1) +Backup to DB2 (should fail) +SQLSTATE[HY000]: General error: Backup failed: source database is busy + +Warning: Pdo\Sqlite::backup(): SQLSTATE[HY000]: General error: Backup failed: source database is busy in %s on line %d +bool(false)