diff --git a/UPGRADING b/UPGRADING index fd3efda9df7a..9442aad84504 100644 --- a/UPGRADING +++ b/UPGRADING @@ -134,3 +134,7 @@ PHP 8.7 UPGRADE NOTES - MBString: . Improved performance of mb_strlen for UTF-8 strings. + +- PDO_PGSQL: + . Improved performance of the SQL parser that handles placeholders in + PDO::prepare() and, with emulated prepares, PDOStatement::execute(). diff --git a/ext/pdo_pgsql/Makefile.frag b/ext/pdo_pgsql/Makefile.frag index 3aa2761369ab..2adabc51e698 100644 --- a/ext/pdo_pgsql/Makefile.frag +++ b/ext/pdo_pgsql/Makefile.frag @@ -1,7 +1,7 @@ $(srcdir)/pgsql_sql_parser.c: $(srcdir)/pgsql_sql_parser.re @(cd $(top_srcdir); \ if test -f ./pgsql_sql_parser.re; then \ - $(RE2C) $(RE2C_FLAGS) -o pgsql_sql_parser.c pgsql_sql_parser.re; \ + $(RE2C) $(RE2C_FLAGS) -b -o pgsql_sql_parser.c pgsql_sql_parser.re; \ else \ - $(RE2C) $(RE2C_FLAGS) -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re; \ + $(RE2C) $(RE2C_FLAGS) -b -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re; \ fi) diff --git a/ext/pdo_pgsql/Makefile.frag.w32 b/ext/pdo_pgsql/Makefile.frag.w32 index 0a1037d9afb6..5d8047decf69 100644 --- a/ext/pdo_pgsql/Makefile.frag.w32 +++ b/ext/pdo_pgsql/Makefile.frag.w32 @@ -1,3 +1,3 @@ ext\pdo_pgsql\pgsql_sql_parser.c: ext\pdo_pgsql\pgsql_sql_parser.re cd $(PHP_SRC_DIR) - $(RE2C) $(RE2C_FLAGS) -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re + $(RE2C) $(RE2C_FLAGS) -b -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re diff --git a/ext/pdo_pgsql/pgsql_sql_parser.re b/ext/pdo_pgsql/pgsql_sql_parser.re index 011fce56a1a0..ce89c32f5357 100644 --- a/ext/pdo_pgsql/pgsql_sql_parser.re +++ b/ext/pdo_pgsql/pgsql_sql_parser.re @@ -32,6 +32,7 @@ int pdo_pgsql_scanner(pdo_scanner_t *s) SPECIALS = [$eE:?"'/-]; MULTICHAR = [:]{2,}; ANYNOEOF = [\001-\377]; + TEXTCHR = ANYNOEOF\SPECIALS; */ /*!re2c @@ -45,6 +46,7 @@ int pdo_pgsql_scanner(pdo_scanner_t *s) QUESTION { RET(PDO_PARSER_BIND_POS); } SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } COMMENTS { RET(PDO_PARSER_TEXT); } - (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + /* Plain text may contain e/E, as long as it doesn't end with one, which could start an E'...' string */ + ([eE]* TEXTCHR)+ { RET(PDO_PARSER_TEXT); } */ } diff --git a/ext/pdo_pgsql/tests/pdo_pgsql_parser_e_strings.phpt b/ext/pdo_pgsql/tests/pdo_pgsql_parser_e_strings.phpt new file mode 100644 index 000000000000..359c6e5a59fe --- /dev/null +++ b/ext/pdo_pgsql/tests/pdo_pgsql_parser_e_strings.phpt @@ -0,0 +1,52 @@ +--TEST-- +PgSQL PDO Parser: E'' strings next to text containing e/E +--EXTENSIONS-- +pdo_pgsql +--SKIPIF-- + +--FILE-- +setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); +$db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC); + +$queries = [ + // The "?" and ":name" inside the E'' strings must not be treated as placeholders + ["SELECT E'here\\'s a ?' AS selected_e, ? AS value_e", ['one']], + ["SELECT 'x'||e'\\'?' AS concat_e, :name AS reference_e", ['name' => 'two']], + ["SELECT ('a%' LIKE 'a!%' ESCAPE'!')::text AS escape_e, E'\\\\:e' AS e, :e AS ee", ['e' => 'three']], + ["SELECT date'2026-10-14' AS date_e, ?::text AS where_else", ['four']], + ["SELECT ? AS e, ? AS E2 WHERE TRUE", ['five', 'six']], + ["SELECT /* e'? */ :value AS value_e -- e'?", ['value' => 'seven']], +]; + +foreach ([true, false] as $emulate) { + $db->setAttribute(PDO::ATTR_EMULATE_PREPARES, $emulate); + echo $emulate ? "emulated:\n" : "native:\n"; + foreach ($queries as [$sql, $params]) { + $stmt = $db->prepare($sql); + $stmt->execute($params); + echo json_encode($stmt->fetch()), "\n"; + } +} +?> +--EXPECT-- +emulated: +{"selected_e":"here's a ?","value_e":"one"} +{"concat_e":"x'?","reference_e":"two"} +{"escape_e":"true","e":"\\:e","ee":"three"} +{"date_e":"2026-10-14","where_else":"four"} +{"e":"five","e2":"six"} +{"value_e":"seven"} +native: +{"selected_e":"here's a ?","value_e":"one"} +{"concat_e":"x'?","reference_e":"two"} +{"escape_e":"true","e":"\\:e","ee":"three"} +{"date_e":"2026-10-14","where_else":"four"} +{"e":"five","e2":"six"} +{"value_e":"seven"}