diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index 4cb6c14eae0a..596029524dea 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -36,6 +36,15 @@ class Sqlite extends \PDO /** @cvalue PDO_SQLITE_ATTR_BUSY_STATEMENT */ public const int ATTR_BUSY_STATEMENT = UNKNOWN; + /** @cvalue PDO_SQLITE_ATTR_EXPLAIN_STATEMENT */ + public const int ATTR_EXPLAIN_STATEMENT = UNKNOWN; + +#if SQLITE_VERSION_NUMBER >= 3041000 + public const int EXPLAIN_MODE_PREPARED = 0; + public const int EXPLAIN_MODE_EXPLAIN = 1; + public const int EXPLAIN_MODE_EXPLAIN_QUERY_PLAN = 2; +#endif + /** @cvalue SQLITE_OK */ public const int OK = UNKNOWN; diff --git a/ext/pdo_sqlite/pdo_sqlite_arginfo.h b/ext/pdo_sqlite/pdo_sqlite_arginfo.h index ec826bc4bbc5..b81790fa1df1 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 the .stub.php file instead. - * Stub hash: ae1e62d72c3c8290c9f39f21b583e980ea9b8eb2 */ + * Stub hash: fa489a46c586ae935036f76a992163aeb67246d3 */ 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) @@ -116,6 +116,36 @@ static zend_class_entry *register_class_Pdo_Sqlite(zend_class_entry *class_entry zend_declare_typed_class_constant(class_entry, const_ATTR_BUSY_STATEMENT_name, &const_ATTR_BUSY_STATEMENT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release(const_ATTR_BUSY_STATEMENT_name); + zval const_ATTR_EXPLAIN_STATEMENT_value; + ZVAL_LONG(&const_ATTR_EXPLAIN_STATEMENT_value, PDO_SQLITE_ATTR_EXPLAIN_STATEMENT); + zend_string *const_ATTR_EXPLAIN_STATEMENT_name = zend_string_init_interned("ATTR_EXPLAIN_STATEMENT", sizeof("ATTR_EXPLAIN_STATEMENT") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_ATTR_EXPLAIN_STATEMENT_name, &const_ATTR_EXPLAIN_STATEMENT_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_ATTR_EXPLAIN_STATEMENT_name); +#if SQLITE_VERSION_NUMBER >= 3041000 + + zval const_EXPLAIN_MODE_PREPARED_value; + ZVAL_LONG(&const_EXPLAIN_MODE_PREPARED_value, 0); + zend_string *const_EXPLAIN_MODE_PREPARED_name = zend_string_init_interned("EXPLAIN_MODE_PREPARED", sizeof("EXPLAIN_MODE_PREPARED") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_PREPARED_name, &const_EXPLAIN_MODE_PREPARED_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_EXPLAIN_MODE_PREPARED_name); +#endif +#if SQLITE_VERSION_NUMBER >= 3041000 + + zval const_EXPLAIN_MODE_EXPLAIN_value; + ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_value, 1); + zend_string *const_EXPLAIN_MODE_EXPLAIN_name = zend_string_init_interned("EXPLAIN_MODE_EXPLAIN", sizeof("EXPLAIN_MODE_EXPLAIN") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_EXPLAIN_name, &const_EXPLAIN_MODE_EXPLAIN_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_EXPLAIN_MODE_EXPLAIN_name); +#endif +#if SQLITE_VERSION_NUMBER >= 3041000 + + zval const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value; + ZVAL_LONG(&const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value, 2); + zend_string *const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name = zend_string_init_interned("EXPLAIN_MODE_EXPLAIN_QUERY_PLAN", sizeof("EXPLAIN_MODE_EXPLAIN_QUERY_PLAN") - 1, 1); + zend_declare_typed_class_constant(class_entry, const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name, &const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release(const_EXPLAIN_MODE_EXPLAIN_QUERY_PLAN_name); +#endif + zval const_OK_value; ZVAL_LONG(&const_OK_value, SQLITE_OK); zend_string *const_OK_name = zend_string_init_interned("OK", sizeof("OK") - 1, 1); diff --git a/ext/pdo_sqlite/php_pdo_sqlite_int.h b/ext/pdo_sqlite/php_pdo_sqlite_int.h index 8acb95015e79..69ac003356b8 100644 --- a/ext/pdo_sqlite/php_pdo_sqlite_int.h +++ b/ext/pdo_sqlite/php_pdo_sqlite_int.h @@ -74,7 +74,8 @@ enum { PDO_SQLITE_ATTR_OPEN_FLAGS = PDO_ATTR_DRIVER_SPECIFIC, PDO_SQLITE_ATTR_READONLY_STATEMENT, PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES, - PDO_SQLITE_ATTR_BUSY_STATEMENT + PDO_SQLITE_ATTR_BUSY_STATEMENT, + PDO_SQLITE_ATTR_EXPLAIN_STATEMENT }; typedef int pdo_sqlite_create_collation_callback(void*, int, const void*, int, const void*); diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index 64c8c8a86dd9..e683cc5ed51b 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -26,6 +26,11 @@ #include "php_pdo_sqlite.h" #include "php_pdo_sqlite_int.h" +#if defined(__APPLE__) +// If more than one usage, a Zend macro could be created +// around this runtime check +#include +#endif static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) { @@ -387,6 +392,23 @@ static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval ZVAL_TRUE(val); } break; + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3041000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + ZVAL_LONG(val, (zend_long)sqlite3_stmt_isexplain(S->stmt)); + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif default: return 0; } @@ -394,6 +416,46 @@ static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval return 1; } +static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval *zval) +{ + pdo_sqlite_stmt *S = (pdo_sqlite_stmt*)stmt->driver_data; + + switch (attr) { + case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: +#if SQLITE_VERSION_NUMBER >= 3041000 +#if defined(__APPLE__) + if (__builtin_available(macOS 14.2, *)) { +#endif + if (Z_TYPE_P(zval) != IS_LONG) { + zend_type_error("explain mode must be of type int, %s given", zend_zval_value_name(zval)); + return 0; + } + if (Z_LVAL_P(zval) < 0 || Z_LVAL_P(zval) > 2) { + zend_value_error("explain mode must be one of the EXPLAIN_MODE_* constants"); + return 0; + } + if (sqlite3_stmt_explain(S->stmt, (int)Z_LVAL_P(zval)) != SQLITE_OK) { + return 0; + } + + return 1; +#if defined(__APPLE__) + } else { + zend_value_error("explain statement unsupported"); + return 0; + } +#endif +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif + default: + return 0; + } + + return 1; +} + const struct pdo_stmt_methods sqlite_stmt_methods = { pdo_sqlite_stmt_dtor, pdo_sqlite_stmt_execute, @@ -401,7 +463,7 @@ const struct pdo_stmt_methods sqlite_stmt_methods = { pdo_sqlite_stmt_describe, pdo_sqlite_stmt_get_col, pdo_sqlite_stmt_param_hook, - NULL, /* set_attr */ + pdo_sqlite_stmt_set_attribute, /* set_attr */ pdo_sqlite_stmt_get_attribute, /* get_attr */ pdo_sqlite_stmt_col_meta, NULL, /* next_rowset */ diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt new file mode 100644 index 000000000000..c91fb892477b --- /dev/null +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt @@ -0,0 +1,400 @@ +--TEST-- +Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT usage +--EXTENSIONS-- +pdo_sqlite +--SKIPIF-- + +--FILE-- +query('CREATE TABLE test_explain (a string);'); +$stmt = $db->prepare('INSERT INTO test_explain VALUES ("first insert"), ("second_insert")'); +$stmt->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, Pdo\Sqlite::EXPLAIN_MODE_EXPLAIN); +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT) == Pdo\Sqlite::EXPLAIN_MODE_EXPLAIN); +$r = $stmt->execute(); +var_dump($stmt->fetchAll(PDO::FETCH_ASSOC)); +$stmts = $db->prepare('SELECT * FROM test_explain'); +$stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, Pdo\Sqlite::EXPLAIN_MODE_EXPLAIN_QUERY_PLAN); +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT) == Pdo\Sqlite::EXPLAIN_MODE_EXPLAIN_QUERY_PLAN); +$r = $stmts->execute(); +var_dump($stmts->fetchAll(PDO::FETCH_ASSOC)); + +$stmt = $db->prepare('INSERT INTO test_explain VALUES ("first insert"), ("second_insert")'); +$stmt->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, Pdo\Sqlite::EXPLAIN_MODE_PREPARED); +$stmt->execute(); +$stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, Pdo\Sqlite::EXPLAIN_MODE_PREPARED); +$r = $stmts->execute(); +var_dump($stmts->fetchAll(PDO::FETCH_ASSOC)); + +class Duh {} + +try { + $stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, "EXPLAIN"); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, new Duh()); +} catch (\TypeError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, -1); +} catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +try { + $stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, 256); +} catch (\ValueError $e) { + echo $e->getMessage(), PHP_EOL; +} + +var_dump($stmts->getAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT) == Pdo\Sqlite::EXPLAIN_MODE_PREPARED); +?> +--EXPECT-- +bool(true) +array(16) { + [0]=> + array(8) { + ["addr"]=> + int(0) + ["opcode"]=> + string(4) "Init" + ["p1"]=> + int(0) + ["p2"]=> + int(14) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [1]=> + array(8) { + ["addr"]=> + int(1) + ["opcode"]=> + string(13) "InitCoroutine" + ["p1"]=> + int(3) + ["p2"]=> + int(7) + ["p3"]=> + int(2) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [2]=> + array(8) { + ["addr"]=> + int(2) + ["opcode"]=> + string(7) "String8" + ["p1"]=> + int(0) + ["p2"]=> + int(2) + ["p3"]=> + int(0) + ["p4"]=> + string(12) "first insert" + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [3]=> + array(8) { + ["addr"]=> + int(3) + ["opcode"]=> + string(5) "Yield" + ["p1"]=> + int(3) + ["p2"]=> + int(0) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [4]=> + array(8) { + ["addr"]=> + int(4) + ["opcode"]=> + string(7) "String8" + ["p1"]=> + int(0) + ["p2"]=> + int(2) + ["p3"]=> + int(0) + ["p4"]=> + string(13) "second_insert" + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [5]=> + array(8) { + ["addr"]=> + int(5) + ["opcode"]=> + string(5) "Yield" + ["p1"]=> + int(3) + ["p2"]=> + int(0) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [6]=> + array(8) { + ["addr"]=> + int(6) + ["opcode"]=> + string(12) "EndCoroutine" + ["p1"]=> + int(3) + ["p2"]=> + int(0) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [7]=> + array(8) { + ["addr"]=> + int(7) + ["opcode"]=> + string(9) "OpenWrite" + ["p1"]=> + int(0) + ["p2"]=> + int(2) + ["p3"]=> + int(0) + ["p4"]=> + string(1) "1" + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [8]=> + array(8) { + ["addr"]=> + int(8) + ["opcode"]=> + string(5) "Yield" + ["p1"]=> + int(3) + ["p2"]=> + int(13) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [9]=> + array(8) { + ["addr"]=> + int(9) + ["opcode"]=> + string(8) "NewRowid" + ["p1"]=> + int(0) + ["p2"]=> + int(1) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [10]=> + array(8) { + ["addr"]=> + int(10) + ["opcode"]=> + string(10) "MakeRecord" + ["p1"]=> + int(2) + ["p2"]=> + int(1) + ["p3"]=> + int(4) + ["p4"]=> + string(1) "C" + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [11]=> + array(8) { + ["addr"]=> + int(11) + ["opcode"]=> + string(6) "Insert" + ["p1"]=> + int(0) + ["p2"]=> + int(4) + ["p3"]=> + int(1) + ["p4"]=> + string(12) "test_explain" + ["p5"]=> + int(57) + ["comment"]=> + NULL + } + [12]=> + array(8) { + ["addr"]=> + int(12) + ["opcode"]=> + string(4) "Goto" + ["p1"]=> + int(0) + ["p2"]=> + int(8) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [13]=> + array(8) { + ["addr"]=> + int(13) + ["opcode"]=> + string(4) "Halt" + ["p1"]=> + int(0) + ["p2"]=> + int(0) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } + [14]=> + array(8) { + ["addr"]=> + int(14) + ["opcode"]=> + string(11) "Transaction" + ["p1"]=> + int(0) + ["p2"]=> + int(1) + ["p3"]=> + int(1) + ["p4"]=> + string(1) "0" + ["p5"]=> + int(1) + ["comment"]=> + NULL + } + [15]=> + array(8) { + ["addr"]=> + int(15) + ["opcode"]=> + string(4) "Goto" + ["p1"]=> + int(0) + ["p2"]=> + int(1) + ["p3"]=> + int(0) + ["p4"]=> + NULL + ["p5"]=> + int(0) + ["comment"]=> + NULL + } +} +bool(false) +array(1) { + [0]=> + array(4) { + ["id"]=> + int(2) + ["parent"]=> + int(0) + ["notused"]=> + int(0) + ["detail"]=> + string(17) "SCAN test_explain" + } +} +array(2) { + [0]=> + array(1) { + ["a"]=> + string(12) "first insert" + } + [1]=> + array(1) { + ["a"]=> + string(13) "second_insert" + } +} +explain mode must be of type int, string given +explain mode must be of type int, Duh given +explain mode must be one of the EXPLAIN_MODE_* constants +explain mode must be one of the EXPLAIN_MODE_* constants +bool(true)