diff --git a/ext/pdo/pdo_stmt.c b/ext/pdo/pdo_stmt.c index 73d4530e8f657..8ea8bac02447f 100644 --- a/ext/pdo/pdo_stmt.c +++ b/ext/pdo/pdo_stmt.c @@ -1628,6 +1628,10 @@ bool pdo_stmt_setup_fetch_mode(pdo_stmt_t *stmt, zend_long mode, uint32_t mode_a stmt->default_fetch_type = PDO_FETCH_BOTH; + if ((mode & ~PDO_FETCH_FLAGS) == PDO_FETCH_USE_DEFAULT) { + mode = stmt->dbh->default_fetch_type; + } + flags = mode & PDO_FETCH_FLAGS; if (!pdo_verify_fetch_mode(stmt->default_fetch_type, mode, mode_arg_num, false)) { diff --git a/ext/pdo_sqlite/config.m4 b/ext/pdo_sqlite/config.m4 index 7b7700481a30d..9a037d58903e6 100644 --- a/ext/pdo_sqlite/config.m4 +++ b/ext/pdo_sqlite/config.m4 @@ -17,6 +17,13 @@ if test "$PHP_PDO_SQLITE" != "no"; then [], [$PDO_SQLITE_SHARED_LIBADD]) + PHP_CHECK_LIBRARY([sqlite3], [sqlite3_expanded_sql], + [AC_DEFINE([HAVE_SQLITE3_EXPANDED_SQL], [1], + [Define to 1 if SQLite library has the 'sqlite3_expanded_sql' + function.])], + [], + [$PDO_SQLITE_SHARED_LIBADD]) + PHP_CHECK_LIBRARY([sqlite3], [sqlite3_load_extension], [], [AC_DEFINE([PDO_SQLITE_OMIT_LOAD_EXTENSION], [1], diff --git a/ext/pdo_sqlite/config.w32 b/ext/pdo_sqlite/config.w32 index d6162278abedf..76ee5598fa3ef 100644 --- a/ext/pdo_sqlite/config.w32 +++ b/ext/pdo_sqlite/config.w32 @@ -8,6 +8,7 @@ if (PHP_PDO_SQLITE != "no") { ADD_EXTENSION_DEP('pdo_sqlite', 'pdo'); AC_DEFINE("HAVE_SQLITE3_COLUMN_TABLE_NAME", 1, "Define to 1 if SQLite library was compiled with the SQLITE_ENABLE_COLUMN_METADATA and has the 'sqlite3_column_table_name' function."); + AC_DEFINE("HAVE_SQLITE3_EXPANDED_SQL", 1, "Define to 1 if SQLite library has the 'sqlite3_expanded_sql' function."); ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_sqlite not enabled; libraries and/or headers not found"); diff --git a/ext/pdo_sqlite/pdo_sqlite.stub.php b/ext/pdo_sqlite/pdo_sqlite.stub.php index 53f1ceba427b0..4cbb758d1e43e 100644 --- a/ext/pdo_sqlite/pdo_sqlite.stub.php +++ b/ext/pdo_sqlite/pdo_sqlite.stub.php @@ -42,6 +42,14 @@ class Sqlite extends \PDO /** @cvalue PDO_SQLITE_ATTR_TRANSACTION_MODE */ public const int ATTR_TRANSACTION_MODE = UNKNOWN; + /** @cvalue PDO_SQLITE_ATTR_SQL */ + public const int ATTR_SQL = UNKNOWN; + +#ifdef HAVE_SQLITE3_EXPANDED_SQL + /** @cvalue PDO_SQLITE_ATTR_EXPANDED_SQL */ + public const int ATTR_EXPANDED_SQL = UNKNOWN; +#endif + public const int TRANSACTION_MODE_DEFERRED = 0; public const int TRANSACTION_MODE_IMMEDIATE = 1; public const int TRANSACTION_MODE_EXCLUSIVE = 2; diff --git a/ext/pdo_sqlite/pdo_sqlite_arginfo.h b/ext/pdo_sqlite/pdo_sqlite_arginfo.h index 73a9158301c0a..f9cadf4faa5e6 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: c16b75eca0adbce4f98b304b1264dd62e37267ff */ 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) @@ -128,6 +128,20 @@ static zend_class_entry *register_class_Pdo_Sqlite(zend_class_entry *class_entry zend_declare_typed_class_constant(class_entry, const_ATTR_TRANSACTION_MODE_name, &const_ATTR_TRANSACTION_MODE_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); zend_string_release_ex(const_ATTR_TRANSACTION_MODE_name, true); + zval const_ATTR_SQL_value; + ZVAL_LONG(&const_ATTR_SQL_value, PDO_SQLITE_ATTR_SQL); + zend_string *const_ATTR_SQL_name = zend_string_init_interned("ATTR_SQL", sizeof("ATTR_SQL") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_SQL_name, &const_ATTR_SQL_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_SQL_name, true); +#if defined(HAVE_SQLITE3_EXPANDED_SQL) + + zval const_ATTR_EXPANDED_SQL_value; + ZVAL_LONG(&const_ATTR_EXPANDED_SQL_value, PDO_SQLITE_ATTR_EXPANDED_SQL); + zend_string *const_ATTR_EXPANDED_SQL_name = zend_string_init_interned("ATTR_EXPANDED_SQL", sizeof("ATTR_EXPANDED_SQL") - 1, true); + zend_declare_typed_class_constant(class_entry, const_ATTR_EXPANDED_SQL_name, &const_ATTR_EXPANDED_SQL_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG)); + zend_string_release_ex(const_ATTR_EXPANDED_SQL_name, true); +#endif + zval const_TRANSACTION_MODE_DEFERRED_value; ZVAL_LONG(&const_TRANSACTION_MODE_DEFERRED_value, 0); zend_string *const_TRANSACTION_MODE_DEFERRED_name = zend_string_init_interned("TRANSACTION_MODE_DEFERRED", sizeof("TRANSACTION_MODE_DEFERRED") - 1, true); diff --git a/ext/pdo_sqlite/php_pdo_sqlite_int.h b/ext/pdo_sqlite/php_pdo_sqlite_int.h index 0cb09cfaa4fb4..b23eb62534d48 100644 --- a/ext/pdo_sqlite/php_pdo_sqlite_int.h +++ b/ext/pdo_sqlite/php_pdo_sqlite_int.h @@ -77,7 +77,9 @@ enum { PDO_SQLITE_ATTR_EXTENDED_RESULT_CODES, PDO_SQLITE_ATTR_BUSY_STATEMENT, PDO_SQLITE_ATTR_EXPLAIN_STATEMENT, - PDO_SQLITE_ATTR_TRANSACTION_MODE + PDO_SQLITE_ATTR_TRANSACTION_MODE, + PDO_SQLITE_ATTR_SQL, + PDO_SQLITE_ATTR_EXPANDED_SQL }; 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 ffb8c1ad4b3c8..8eff6db0f7f0f 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -408,6 +408,32 @@ static int pdo_sqlite_stmt_get_attribute(pdo_stmt_t *stmt, zend_long attr, zval zend_value_error("explain statement unsupported"); return 0; #endif + case PDO_SQLITE_ATTR_SQL: { + const char *sql = sqlite3_sql(S->stmt); + if (sql) { + ZVAL_STRING(val, sql); + } else { + ZVAL_NULL(val); + } + return 1; + } + + case PDO_SQLITE_ATTR_EXPANDED_SQL: { +#ifdef HAVE_SQLITE3_EXPANDED_SQL + char *sql = sqlite3_expanded_sql(S->stmt); + if (sql) { + ZVAL_STRING(val, sql); + sqlite3_free(sql); + } else { + ZVAL_NULL(val); + } + return 1; +#else + zend_value_error("expanded sql unsupported"); + return -1; +#endif + } + default: return 0; } diff --git a/ext/pdo_sqlite/tests/gh20214.phpt b/ext/pdo_sqlite/tests/gh20214.phpt new file mode 100644 index 0000000000000..29adc50b0b747 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh20214.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-20214 (PDO::FETCH_DEFAULT unexpected behavior with PDOStatement::setFetchMode) +--EXTENSIONS-- +pdo_sqlite +--FILE-- +setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_OBJ); + +// setFetchMode with FETCH_DEFAULT should use connection default (FETCH_OBJ) +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt->setFetchMode(PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump($row instanceof stdClass); +var_dump($row->c1); + +// fetch with FETCH_DEFAULT should also use connection default +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$row = $stmt->fetch(PDO::FETCH_DEFAULT); +var_dump($row instanceof stdClass); + +// fetchAll with FETCH_DEFAULT should also use connection default +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$rows = $stmt->fetchAll(PDO::FETCH_DEFAULT); +var_dump($rows[0] instanceof stdClass); + +// setFetchMode then fetch without argument +$stmt = $db->query("SELECT 'v1' AS c1, 'v2' AS c2"); +$stmt->setFetchMode(PDO::FETCH_DEFAULT); +$row = $stmt->fetch(); +var_dump($row instanceof stdClass); +?> +--EXPECT-- +bool(true) +string(2) "v1" +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_constants.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_constants.phpt index d1db58b1323eb..0abe6f65d6acb 100644 --- a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_constants.phpt +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_constants.phpt @@ -14,6 +14,7 @@ var_dump(Pdo\Sqlite::OPEN_CREATE); var_dump(Pdo\Sqlite::ATTR_READONLY_STATEMENT); var_dump(Pdo\Sqlite::ATTR_EXTENDED_RESULT_CODES); var_dump(Pdo\Sqlite::ATTR_BUSY_STATEMENT); +var_dump(Pdo\Sqlite::ATTR_SQL); ?> --EXPECTF-- @@ -26,3 +27,4 @@ int(%d) int(%d) int(%d) int(%d) +int(%d) diff --git a/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getattr_sql.phpt b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getattr_sql.phpt new file mode 100644 index 0000000000000..885a523fed49d --- /dev/null +++ b/ext/pdo_sqlite/tests/subclasses/pdo_sqlite_getattr_sql.phpt @@ -0,0 +1,29 @@ +--TEST-- +Pdo\Sqlite::ATTR_SQL and Pdo\Sqlite::ATTR_EXPANDED_SQL usage +--EXTENSIONS-- +pdo_sqlite +--SKIPIF-- + +--FILE-- +prepare('SELECT :name AS greeting, :num AS number'); +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_SQL)); +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_EXPANDED_SQL)); + +$stmt->bindValue(':name', 'hello world'); +$stmt->bindValue(':num', 42, PDO::PARAM_INT); +$stmt->execute(); + +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_SQL)); +var_dump($stmt->getAttribute(Pdo\Sqlite::ATTR_EXPANDED_SQL)); +?> +--EXPECT-- +string(40) "SELECT :name AS greeting, :num AS number" +string(39) "SELECT NULL AS greeting, NULL AS number" +string(40) "SELECT :name AS greeting, :num AS number" +string(46) "SELECT 'hello world' AS greeting, 42 AS number"