Skip to content

Commit 01879ec

Browse files
committed
pdo_pgsql: add support for dollar-quotes
RFC: http://wiki.php.net/rfc/pdo_driver_specific_parsers
1 parent e82c486 commit 01879ec

File tree

4 files changed

+67
-9
lines changed

4 files changed

+67
-9
lines changed

ext/pdo/pdo_sql_parser.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
#define PDO_PARSER_BIND 2
1919
#define PDO_PARSER_BIND_POS 3
2020
#define PDO_PARSER_ESCAPED_QUESTION 4
21-
#define PDO_PARSER_EOI 5
21+
#define PDO_PARSER_CUSTOM_QUOTE 5
22+
#define PDO_PARSER_EOI 6
2223

2324
#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1
2425

ext/pdo/pdo_sql_parser.re

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ struct placeholder {
5353
struct placeholder *next;
5454
};
5555

56+
struct custom_quote {
57+
const char *pos;
58+
size_t len;
59+
};
60+
5661
static void free_param_name(zval *el) {
5762
zend_string_release(Z_PTR_P(el));
5863
}
@@ -70,6 +75,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
7075
int query_type = PDO_PLACEHOLDER_NONE;
7176
struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
7277
int (*scan)(pdo_scanner_t *s);
78+
struct custom_quote custom_quote = {NULL, 0};
7379

7480
scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner;
7581

@@ -78,6 +84,25 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
7884

7985
/* phase 1: look for args */
8086
while((t = scan(&s)) != PDO_PARSER_EOI) {
87+
if (custom_quote.pos) {
88+
/* Inside a custom quote */
89+
if (t == PDO_PARSER_CUSTOM_QUOTE && custom_quote.len == s.cur - s.tok && !strncmp(s.tok, custom_quote.pos, custom_quote.len)) {
90+
/* Matching closing quote found, end custom quoting */
91+
custom_quote.pos = NULL;
92+
custom_quote.len = 0;
93+
}
94+
95+
continue;
96+
}
97+
98+
if (t == PDO_PARSER_CUSTOM_QUOTE) {
99+
/* Start of a custom quote, keep a reference to search for the matching closing quote */
100+
custom_quote.pos = s.tok;
101+
custom_quote.len = s.cur - s.tok;
102+
103+
continue;
104+
}
105+
81106
if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) {
82107
if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) {
83108
/* escaped question marks unsupported, treat as text */

ext/pdo_pgsql/pgsql_sql_parser.re

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ int pdo_pgsql_scanner(pdo_scanner_t *s)
2929
BINDCHR = [:][a-zA-Z0-9_]+;
3030
QUESTION = [?];
3131
ESCQUESTION = [?][?];
32-
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*);
33-
SPECIALS = [eE:?"'/-];
32+
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*);
33+
DOLQ_START = [A-Za-z\200-\377_];
34+
DOLQ_CONT = [A-Za-z\200-\377_0-9];
35+
SPECIALS = [$eE:?"'/-];
3436
MULTICHAR = [:]{2,};
3537
ANYNOEOF = [\001-\377];
3638
*/
@@ -39,6 +41,7 @@ int pdo_pgsql_scanner(pdo_scanner_t *s)
3941
[eE](['](([']['])|([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); }
4042
(["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); }
4143
(['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); }
44+
[$](DOLQ_START DOLQ_CONT*)?[$] { RET(PDO_PARSER_CUSTOM_QUOTE); }
4245
MULTICHAR { RET(PDO_PARSER_TEXT); }
4346
ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); }
4447
BINDCHR { RET(PDO_PARSER_BIND); }

ext/pdo_pgsql/tests/pdo_pgsql_parser.phpt

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,49 @@ require_once __DIR__ . "/config.inc";
1717
$db = new PdoPgsql($config['ENV']['PDOTEST_DSN']);
1818
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
1919

20-
$query = <<<EOF
21-
SELECT e'He''ll\'o ' || ? AS b -- '
20+
$query = <<<'EOF'
21+
SELECT e'He''ll\'o? ' || ? AS b -- '
2222
UNION ALL
23-
SELECT 'He''ll''o\' || ? AS b -- '
23+
SELECT 'He''ll''o?\' || ? AS b -- '
24+
UNION ALL
25+
SELECT U&'d\0061t\+000061? ' || ? AS b /* :name */
26+
UNION ALL
27+
SELECT $__$Is this a $$dollar$$ 'escaping'? $__$ || ? AS b -- ?
2428
EOF;
2529

2630
$stmt = $db->prepare($query);
27-
$stmt->execute(['World', 'World']);
31+
$stmt->execute(['World', 'World', 'base', 'Yes']);
2832

2933
while ($row = $stmt->fetchColumn()) {
3034
var_dump($row);
3135
}
3236

37+
// Nested comments are incompatible: PDO parses the "?" inside the comment, Postgres doesn't
38+
$query = <<<'EOF'
39+
SELECT 'Hello' || ? /* is this a /* nested */ 'comment' ? */
40+
EOF;
41+
42+
$stmt = $db->prepare($query);
43+
44+
try {
45+
$stmt->execute(['X', 'Y', 'Z']);
46+
} catch (PDOException $e) {
47+
// PDO error: 3 parameters vs 2 expected
48+
var_dump('HY093' === $e->getCode());
49+
}
50+
51+
try {
52+
$stmt->execute(['X', 'Y']);
53+
} catch (PDOException $e) {
54+
// PgSQL error: Indeterminate datatype (1 extra parameter)
55+
var_dump('42P18' === $e->getCode());
56+
}
57+
3358
?>
3459
--EXPECT--
35-
string(13) "He'll'o World"
36-
string(13) "He'll'o\World"
60+
string(14) "He'll'o? World"
61+
string(14) "He'll'o?\World"
62+
string(10) "data? base"
63+
string(36) "Is this a $$dollar$$ 'escaping'? Yes"
64+
bool(true)
65+
bool(true)

0 commit comments

Comments
 (0)