From ce22ccc77b1cfe3d922ebe485069afc61d784916 Mon Sep 17 00:00:00 2001 From: BohwaZ Date: Mon, 17 Jun 2019 23:28:30 +0200 Subject: [PATCH] Implement SQLite3 backup API --- NEWS | 3 ++ UPGRADING | 2 + ext/sqlite3/sqlite3.c | 68 ++++++++++++++++++++++++ ext/sqlite3/tests/sqlite3_38_backup.phpt | 58 ++++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 ext/sqlite3/tests/sqlite3_38_backup.phpt diff --git a/NEWS b/NEWS index 2e6ce32e97..4e8b6f234f 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,9 @@ PHP NEWS . Fixed bug #78106 (Path resolution fails if opcache disabled during request). (Nikita) +- SQLite3: + . Implement FR ##70950 (Make SQLite3 Online Backup API available). (BohwaZ) + 13 Jun 2019, PHP 7.4.0alpha1 - Core: diff --git a/UPGRADING b/UPGRADING index 1eaf8fc3d4..4acfa3e775 100644 --- a/UPGRADING +++ b/UPGRADING @@ -370,6 +370,8 @@ PHP 7.4 UPGRADE NOTES . Added SQLite3Stmt::getSQL() to retrieve the SQL of the statement. If TRUE is passed as parameter, query parameters will be replaced in the return value by their currently bound value, if libsqlite ≥ 3.14 is used. + . Added SQLite3::backup() to create database backups via the SQLite3 online + backup API. - Standard . bool sapi_windows_set_ctrl_handler(callable handler, [, bool add = true]) - diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 81ce1a0f00..61b032e900 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1294,6 +1294,63 @@ PHP_METHOD(sqlite3, enableExceptions) } /* }}} */ +#if SQLITE_VERSION_NUMBER >= 3006011 +/* {{{ proto bool SQLite3::backup(SQLite3 destination_db[, string source_dbname = "main"[, string destination_dbname = "main"]]) + Backups the current database to another one. */ +PHP_METHOD(sqlite3, backup) +{ + php_sqlite3_db_object *source_obj; + php_sqlite3_db_object *destination_obj; + char *source_dbname = "main", *destination_dbname = "main"; + size_t source_dbname_length, destination_dbname_length; + zval *source_zval = ZEND_THIS; + zval *destination_zval; + sqlite3_backup *dbBackup; + int rc; // Return code + + source_obj = Z_SQLITE3_DB_P(source_zval); + SQLITE3_CHECK_INITIALIZED(source_obj, source_obj->initialised, SQLite3) + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|ss", &destination_zval, php_sqlite3_sc_entry, &source_dbname, &source_dbname_length, &destination_dbname, &destination_dbname_length) == FAILURE) { + return; + } + + destination_obj = Z_SQLITE3_DB_P(destination_zval); + + SQLITE3_CHECK_INITIALIZED(destination_obj, destination_obj->initialised, SQLite3) + + dbBackup = sqlite3_backup_init(destination_obj->db, destination_dbname, source_obj->db, source_dbname); + + if (dbBackup) { + do { + rc = sqlite3_backup_step(dbBackup, -1); + } while (rc == SQLITE_OK); + + /* Release resources allocated by backup_init(). */ + rc = sqlite3_backup_finish(dbBackup); + } + else { + rc = sqlite3_errcode(source_obj->db); + } + + if (rc != SQLITE_OK) { + if (rc == SQLITE_BUSY) { + php_sqlite3_error(source_obj, "Backup failed: source database is busy"); + } + else if (rc == SQLITE_LOCKED) { + php_sqlite3_error(source_obj, "Backup failed: source database is locked"); + } + else { + php_sqlite3_error(source_obj, "Backup failed: %d, %s", rc, sqlite3_errmsg(source_obj->db)); + } + RETURN_FALSE; + } + + RETURN_TRUE; +} +/* }}} */ +#endif + /* {{{ proto int SQLite3Stmt::paramCount() Returns the number of parameters within the prepared statement. */ PHP_METHOD(sqlite3stmt, paramCount) @@ -2058,6 +2115,14 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_sqlite3_enableexceptions, 0, 0, 0) ZEND_ARG_INFO(0, enableExceptions) ZEND_END_ARG_INFO() +#if SQLITE_VERSION_NUMBER >= 3006011 +ZEND_BEGIN_ARG_INFO_EX(arginfo_sqlite3_backup, 0, 0, 1) + ZEND_ARG_INFO(0, destination_db) + ZEND_ARG_INFO(0, source_dbname) + ZEND_ARG_INFO(0, destination_dbname) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_INFO_EX(arginfo_sqlite3stmt_bindparam, 0, 0, 2) ZEND_ARG_INFO(0, param_number) ZEND_ARG_INFO(1, param) @@ -2117,6 +2182,9 @@ static const zend_function_entry php_sqlite3_class_methods[] = { PHP_ME(sqlite3, createCollation, arginfo_sqlite3_createcollation, ZEND_ACC_PUBLIC) PHP_ME(sqlite3, openBlob, arginfo_sqlite3_openblob, ZEND_ACC_PUBLIC) PHP_ME(sqlite3, enableExceptions, arginfo_sqlite3_enableexceptions, ZEND_ACC_PUBLIC) +#if SQLITE_VERSION_NUMBER >= 3006011 + PHP_ME(sqlite3, backup, arginfo_sqlite3_backup, ZEND_ACC_PUBLIC) +#endif /* Aliases */ PHP_MALIAS(sqlite3, __construct, open, arginfo_sqlite3_open, ZEND_ACC_PUBLIC) PHP_FE_END diff --git a/ext/sqlite3/tests/sqlite3_38_backup.phpt b/ext/sqlite3/tests/sqlite3_38_backup.phpt new file mode 100644 index 0000000000..8ff5189fdc --- /dev/null +++ b/ext/sqlite3/tests/sqlite3_38_backup.phpt @@ -0,0 +1,58 @@ +--TEST-- +SQLite3::backup test +--SKIPIF-- + +--FILE-- +exec('CREATE TABLE test (a, b);'); +$db->exec('INSERT INTO test VALUES (42, \'php\');'); + +echo "Checking if table has been created\n"; +var_dump($db->querySingle('SELECT COUNT(*) FROM sqlite_master;')); + +$db2 = new SQLite3(':memory:'); + +echo "Backup to DB2\n"; +var_dump($db->backup($db2)); + +echo "Checking if table has been copied\n"; +var_dump($db2->querySingle('SELECT COUNT(*) FROM sqlite_master;')); + +echo "Checking backup contents\n"; +var_dump($db2->querySingle('SELECT a FROM test;')); +var_dump($db2->querySingle('SELECT b FROM test;')); + +echo "Resetting DB2\n"; + +$db2->close(); +$db2 = new SQLite3(':memory:'); + +echo "Locking DB1\n"; +var_dump($db->exec('BEGIN EXCLUSIVE;')); + +echo "Backup to DB2 (should fail)\n"; +var_dump($db->backup($db2)); + +?> +--EXPECTF-- +Creating table +Checking if table has been created +int(1) +Backup to DB2 +bool(true) +Checking if table has been copied +int(1) +Checking backup contents +int(42) +string(3) "php" +Resetting DB2 +Locking DB1 +bool(true) +Backup to DB2 (should fail) + +Warning: SQLite3::backup(): Backup failed: source database is busy in %s on line %d +bool(false) \ No newline at end of file -- 2.40.0