From 12d404bb889e60971252f1377baeda4f94285a4a Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 10 Jun 2025 19:24:35 +0100 Subject: [PATCH 1/6] ext/pdo_sqlite: EXPLAIN mode support for SQL statements. available since 3.41.0 we can reprepare a statement in either explain, explain query plan or the usual prepared mode. --- ext/pdo_sqlite/pdo_sqlite.c | 1 + ext/pdo_sqlite/pdo_sqlite.stub.php | 7 +++++ ext/pdo_sqlite/pdo_sqlite_arginfo.h | 26 +++++++++++++++++- ext/pdo_sqlite/php_pdo_sqlite_int.h | 3 ++- ext/pdo_sqlite/sqlite_statement.c | 41 ++++++++++++++++++++++++++++- 5 files changed, 75 insertions(+), 3 deletions(-) diff --git a/ext/pdo_sqlite/pdo_sqlite.c b/ext/pdo_sqlite/pdo_sqlite.c index 023e35a2bc33c..8689d480bff84 100644 --- a/ext/pdo_sqlite/pdo_sqlite.c +++ b/ext/pdo_sqlite/pdo_sqlite.c @@ -26,6 +26,7 @@ #include "ext/pdo/php_pdo_driver.h" #include "php_pdo_sqlite.h" #include "php_pdo_sqlite_int.h" +#include "zend_enum.h" #include "zend_exceptions.h" #include "pdo_sqlite_arginfo.h" diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index 4cb6c14eae0a4..dfc293367ed0a 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -36,6 +36,13 @@ 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; + + public const int EXPLAIN_MODE_PREPARED = 0; + public const int EXPLAIN_MODE_EXPLAIN = 1; + public const int EXPLAIN_MODE_EXPLAIN_QUERY_PLAN = 2; + /** @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 ec826bc4bbc5a..0bea16f1bfefb 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: 739fdb0ab932e6bcb4f6c0113f9e641d695b764e */ 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,30 @@ 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); + + 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); + + 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); + + 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); + 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 8acb95015e79a..69ac003356b87 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 64c8c8a86dd9a..9f38fe8f69fa9 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -387,6 +387,14 @@ 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 + ZVAL_LONG(val, (zend_long)sqlite3_stmt_isexplain(S->stmt)); + return 1; +#else + zend_value_error("explain statement unsupported"); + return 0; +#endif default: return 0; } @@ -394,6 +402,37 @@ 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 (Z_TYPE_P(zval) != IS_LONG) { + zend_type_error("explain mode must be of type int"); + return 0; + } + if (Z_TYPE_P(zval) < 0 || Z_TYPE_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; +#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 +440,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 */ From 466ddc8877263be0908b44d40daf7f0f448fb045 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Tue, 10 Jun 2025 20:15:05 +0100 Subject: [PATCH 2/6] build fix attempt 2 --- ext/pdo_sqlite/pdo_sqlite.c | 1 - ext/pdo_sqlite/sqlite_statement.c | 47 ++++++++++++++++++++++--------- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/ext/pdo_sqlite/pdo_sqlite.c b/ext/pdo_sqlite/pdo_sqlite.c index 8689d480bff84..023e35a2bc33c 100644 --- a/ext/pdo_sqlite/pdo_sqlite.c +++ b/ext/pdo_sqlite/pdo_sqlite.c @@ -26,7 +26,6 @@ #include "ext/pdo/php_pdo_driver.h" #include "php_pdo_sqlite.h" #include "php_pdo_sqlite_int.h" -#include "zend_enum.h" #include "zend_exceptions.h" #include "pdo_sqlite_arginfo.h" diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index 9f38fe8f69fa9..e676301873cb9 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -26,6 +26,9 @@ #include "php_pdo_sqlite.h" #include "php_pdo_sqlite_int.h" +#if defined(__APPLE__) +#include +#endif static int pdo_sqlite_stmt_dtor(pdo_stmt_t *stmt) { @@ -389,8 +392,17 @@ static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval break; case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: #if SQLITE_VERSION_NUMBER >= 3041000 - ZVAL_LONG(val, (zend_long)sqlite3_stmt_isexplain(S->stmt)); - return 1; +#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; @@ -409,19 +421,28 @@ static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval switch (attr) { case PDO_SQLITE_ATTR_EXPLAIN_STATEMENT: #if SQLITE_VERSION_NUMBER >= 3041000 - if (Z_TYPE_P(zval) != IS_LONG) { - zend_type_error("explain mode must be of type int"); - return 0; - } - if (Z_TYPE_P(zval) < 0 || Z_TYPE_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) { +#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"); + return 0; + } + if (Z_TYPE_P(zval) < 0 || Z_TYPE_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; } - - return 1; +#endif #else zend_value_error("explain statement unsupported"); return 0; From 5f6eb45f3d79e288a8f735761abd9996ddecba25 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 11 Jun 2025 16:56:01 +0100 Subject: [PATCH 3/6] fix typos and attempt to add a test --- ext/pdo_sqlite/pdo_sqlite.stub.php | 2 + ext/pdo_sqlite/sqlite_statement.c | 4 +- .../pdo_sqlite_getsetattr_explain.phpt | 96 +++++++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index dfc293367ed0a..596029524dea0 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -39,9 +39,11 @@ class Sqlite extends \PDO /** @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/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index e676301873cb9..3b86571360d81 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -27,6 +27,8 @@ #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 @@ -428,7 +430,7 @@ static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval zend_type_error("explain mode must be of type int"); return 0; } - if (Z_TYPE_P(zval) < 0 || Z_TYPE_P(zval) > 2) { + 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; } 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 0000000000000..1d7be02efc9d8 --- /dev/null +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt @@ -0,0 +1,96 @@ +--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); +$r = $stmt->execute(); +var_dump($stmt->fetch(PDO::FETCH_ASSOC)); +$stmts = $db->prepare('SELECT * FROM test_explain'); +$stmts->setAttribute(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)); + +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, -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; +} +?> +--EXPECT-- +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 +} +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 +explain mode must be one of the EXPLAIN_MODE_* constants +explain mode must be one of the EXPLAIN_MODE_* constants From 947c75d2361d82014168756759516de3fac1a344 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 11 Jun 2025 18:48:51 +0100 Subject: [PATCH 4/6] forgotten stub regeneration --- ext/pdo_sqlite/pdo_sqlite_arginfo.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/pdo_sqlite/pdo_sqlite_arginfo.h b/ext/pdo_sqlite/pdo_sqlite_arginfo.h index 0bea16f1bfefb..b81790fa1df1d 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: 739fdb0ab932e6bcb4f6c0113f9e641d695b764e */ + * 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) @@ -121,24 +121,30 @@ static zend_class_entry *register_class_Pdo_Sqlite(zend_class_entry *class_entry 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); From b30ffe2d2f08616bc08dc5b3d961e91b9b32d965 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 11 Jun 2025 19:20:23 +0100 Subject: [PATCH 5/6] show case a little bit more in the test --- ext/pdo_sqlite/sqlite_statement.c | 2 +- .../pdo_sqlite_getsetattr_explain.phpt | 335 +++++++++++++++++- 2 files changed, 317 insertions(+), 20 deletions(-) diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index 3b86571360d81..e683cc5ed51bf 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -427,7 +427,7 @@ static int pdo_sqlite_stmt_set_attribute(pdo_stmt_t *stmt, zend_long attr, zval if (__builtin_available(macOS 14.2, *)) { #endif if (Z_TYPE_P(zval) != IS_LONG) { - zend_type_error("explain mode must be of type int"); + 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) { diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt index 1d7be02efc9d8..9282cd9456431 100644 --- a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt @@ -16,7 +16,7 @@ $db->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); $r = $stmt->execute(); -var_dump($stmt->fetch(PDO::FETCH_ASSOC)); +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); $r = $stmts->execute(); @@ -29,12 +29,20 @@ $stmts->setAttribute(Pdo\Sqlite::ATTR_EXPLAIN_STATEMENT, Pdo\Sqlite::EXPLAIN_MOD $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) { @@ -48,23 +56,311 @@ try { } ?> --EXPECT-- -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 +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 + } } array(1) { [0]=> @@ -91,6 +387,7 @@ array(2) { string(13) "second_insert" } } -explain mode must be of type int +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 From 26a258cf26c715f5ec625f16c58c132a9431ff80 Mon Sep 17 00:00:00 2001 From: David Carlier Date: Wed, 11 Jun 2025 21:05:14 +0100 Subject: [PATCH 6/6] getattribute test --- .../tests/subclasses/pdo_sqlite_getsetattr_explain.phpt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt index 9282cd9456431..c91fb892477b3 100644 --- a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getsetattr_explain.phpt @@ -15,10 +15,12 @@ $db = new Pdo\Sqlite('sqlite::memory:'); $db->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)); @@ -54,8 +56,11 @@ try { } 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) { @@ -362,6 +367,7 @@ array(16) { NULL } } +bool(false) array(1) { [0]=> array(4) { @@ -391,3 +397,4 @@ 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)