diff --git a/ext/pdo/pdo_sql_parser.h b/ext/pdo/pdo_sql_parser.h index f8c303473e7..fc6f5a65eb3 100644 --- a/ext/pdo/pdo_sql_parser.h +++ b/ext/pdo/pdo_sql_parser.h @@ -18,7 +18,8 @@ #define PDO_PARSER_BIND 2 #define PDO_PARSER_BIND_POS 3 #define PDO_PARSER_ESCAPED_QUESTION 4 -#define PDO_PARSER_EOI 5 +#define PDO_PARSER_CUSTOM_QUOTE 5 +#define PDO_PARSER_EOI 6 #define PDO_PARSER_BINDNO_ESCAPED_CHAR -1 diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re index e04ebe73d88..a1e82a3a181 100644 --- a/ext/pdo/pdo_sql_parser.re +++ b/ext/pdo/pdo_sql_parser.re @@ -53,6 +53,11 @@ struct placeholder { struct placeholder *next; }; +struct custom_quote { + const char *pos; + size_t len; +}; + static void free_param_name(zval *el) { zend_string_release(Z_PTR_P(el)); } @@ -70,6 +75,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string int query_type = PDO_PLACEHOLDER_NONE; struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL; int (*scan)(pdo_scanner_t *s); + struct custom_quote custom_quote = {NULL, 0}; scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner; @@ -78,6 +84,25 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string /* phase 1: look for args */ while((t = scan(&s)) != PDO_PARSER_EOI) { + if (custom_quote.pos) { + /* Inside a custom quote */ + if (t == PDO_PARSER_CUSTOM_QUOTE && custom_quote.len == s.cur - s.tok && !strncmp(s.tok, custom_quote.pos, custom_quote.len)) { + /* Matching closing quote found, end custom quoting */ + custom_quote.pos = NULL; + custom_quote.len = 0; + } + + continue; + } + + if (t == PDO_PARSER_CUSTOM_QUOTE) { + /* Start of a custom quote, keep a reference to search for the matching closing quote */ + custom_quote.pos = s.tok; + custom_quote.len = s.cur - s.tok; + + continue; + } + if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) { if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) { /* escaped question marks unsupported, treat as text */ diff --git a/ext/pdo_pgsql/pgsql_sql_parser.re b/ext/pdo_pgsql/pgsql_sql_parser.re index 524197585f9..13db8e6ffc8 100644 --- a/ext/pdo_pgsql/pgsql_sql_parser.re +++ b/ext/pdo_pgsql/pgsql_sql_parser.re @@ -29,8 +29,10 @@ int pdo_pgsql_scanner(pdo_scanner_t *s) BINDCHR = [:][a-zA-Z0-9_]+; QUESTION = [?]; ESCQUESTION = [?][?]; - COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*); - SPECIALS = [eE:?"'/-]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*); + DOLQ_START = [A-Za-z\200-\377_]; + DOLQ_CONT = [A-Za-z\200-\377_0-9]; + SPECIALS = [$eE:?"'/-]; MULTICHAR = [:]{2,}; ANYNOEOF = [\001-\377]; */ @@ -39,6 +41,7 @@ int pdo_pgsql_scanner(pdo_scanner_t *s) [eE](['](([']['])|([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); } (["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); } (['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); } + [$](DOLQ_START DOLQ_CONT*)?[$] { RET(PDO_PARSER_CUSTOM_QUOTE); } MULTICHAR { RET(PDO_PARSER_TEXT); } ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); } BINDCHR { RET(PDO_PARSER_BIND); } diff --git a/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt b/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt index ff4de460854..d4a4130c6fa 100644 --- a/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt +++ b/ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt @@ -17,20 +17,49 @@ require_once __DIR__ . "/config.inc"; $db = new PdoPgsql($config['ENV']['PDOTEST_DSN']); $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -$query = <<prepare($query); -$stmt->execute(['World', 'World']); +$stmt->execute(['World', 'World', 'base', 'Yes']); while ($row = $stmt->fetchColumn()) { var_dump($row); } +// Nested comments are incompatible: PDO parses the "?" inside the comment, Postgres doesn't +$query = <<<'EOF' +SELECT 'Hello' || ? /* is this a /* nested */ 'comment' ? */ +EOF; + +$stmt = $db->prepare($query); + +try { + $stmt->execute(['X', 'Y', 'Z']); +} catch (PDOException $e) { + // PDO error: 3 parameters vs 2 expected + var_dump('HY093' === $e->getCode()); +} + +try { + $stmt->execute(['X', 'Y']); +} catch (PDOException $e) { + // PgSQL error: Indeterminate datatype (1 extra parameter) + var_dump('42P18' === $e->getCode()); +} + ?> --EXPECT-- -string(13) "He'll'o World" -string(13) "He'll'o\World" +string(14) "He'll'o? World" +string(14) "He'll'o?\World" +string(10) "data? base" +string(36) "Is this a $$dollar$$ 'escaping'? Yes" +bool(true) +bool(true)