Skip to content

Commit fbe317b

Browse files
committed
Merge remote-tracking branch 'mbeccati/pdo_driver_specific_parser'
2 parents f4588d5 + 329dfa7 commit fbe317b

35 files changed

+594
-45
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ php
137137
# ------------------------------------------------------------------------------
138138
/ext/json/json_scanner.c
139139
/ext/json/php_json_scanner_defs.h
140-
/ext/pdo/pdo_sql_parser.c
140+
/ext/pdo*/*_sql_parser.c
141141
/ext/phar/phar_path_check.c
142142
/ext/standard/url_scanner_ex.c
143143
/ext/standard/var_unserializer.c

NEWS

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ PHP NEWS
171171
- PDO:
172172
. Fixed setAttribute and getAttribute. (SakiTakamachi)
173173
. Implemented PDO driver-specific subclasses RFC. (danack, kocsismate)
174+
. Added support for PDO driver-specific SQL parsers. (Matteo Beccati)
174175

175176
- PDO_DBLIB:
176177
. Fixed setAttribute and getAttribute. (SakiTakamachi)
@@ -185,6 +186,7 @@ PHP NEWS
185186
- PDO_MYSQL:
186187
. Fixed setAttribute and getAttribute. (SakiTakamachi)
187188
. Added class Pdo\Mysql. (danack, kocsismate)
189+
. Added custom SQL parser. (Matteo Beccati)
188190

189191
- PDO_ODBC:
190192
. Added class Pdo\Odbc. (danack, kocsismate)
@@ -197,11 +199,13 @@ PHP NEWS
197199
. Retrieve the memory usage of the query result resource. (KentarouTakeda)
198200
. Added Pdo\Pgsql::setNoticeCallBack method to receive DB notices.
199201
(outtersg)
202+
. Added custom SQL parser. (Matteo Beccati)
200203

201204
- PDO_SQLITE:
202205
. Added class Pdo\Sqlite. (danack, kocsismate)
203206
. Fixed bug #81227 (PDO::inTransaction reports false when in transaction).
204207
(nielsdos)
208+
. Added custom SQL parser. (Matteo Beccati)
205209

206210
- PGSQL:
207211
. Added the possibility to have no conditions for pg_select. (OmarEmaraDev)

UPGRADING

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,32 @@ PHP 8.4 UPGRADE NOTES
240240
openssl_pkey_get_details as well as openssl_sign and openssl_verify were
241241
extended to support those keys.
242242

243+
- PDO:
244+
. Added support for driver specific SQL parsers. The default parser supports:
245+
- single and double quoted literals, with doubling as escaping mechanism.
246+
- two-dashes and non-nested C-style comments.
247+
248+
- PDO_MYSQL:
249+
. Added custom parser supporting:
250+
- single and double-quoted literals, with doubling and backslash as escaping
251+
mechanism
252+
- backtick literal identifiers and with doubling as escaping mechanism
253+
- two dashes followed by at least 1 whitespace, non-nested C-style comments,
254+
and hash-comments
255+
256+
- PDO_PGSQL:
257+
. Added custom parser supporting:
258+
- single and double quoted literals, with doubling as escaping mechanism
259+
- C-style "escape" string literals (E'string')
260+
- dollar-quoted string literals
261+
- two-dashes and C-style comments (non-nested)
262+
- support for "??" as escape sequence for the "?" operator
263+
264+
- PDO_SQLITE:
265+
. Added custom parser supporting:
266+
- single, double quoted, and backtick literals, with doubling as escaping mechanism
267+
- square brackets quoting for identifiers
268+
- two-dashes and C-style comments (non-nested)
243269

244270
- Phar:
245271
. Added support for the unix timestamp extension for zip archives.
@@ -355,6 +381,11 @@ PHP 8.4 UPGRADE NOTES
355381
. Calling ldap_exop() with more than 4 arguments is deprecated. Use
356382
ldap_exop_sync() instead.
357383

384+
- PDO_PGSQL:
385+
. Using escaped question marks (??) inside dollar-quoted strings is deprecated.
386+
Since PDO_PGSQL has its own SQL parser with dollar-quoted strings support, it
387+
is no longer necessary to escape question marks inside them.
388+
358389
- PgSQL:
359390
. Calling pgsql_fetch_result() with 2 arguments is deprecated. Use the
360391
3-parameter signature with a null $row parameter instead.

ext/pdo/pdo_sql_parser.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
+----------------------------------------------------------------------+
3+
| Copyright (c) The PHP Group |
4+
+----------------------------------------------------------------------+
5+
| This source file is subject to version 3.01 of the PHP license, |
6+
| that is bundled with this package in the file LICENSE, and is |
7+
| available through the world-wide-web at the following url: |
8+
| http://www.php.net/license/3_01.txt |
9+
| If you did not receive a copy of the PHP license and are unable to |
10+
| obtain it through the world-wide-web, please send a note to |
11+
| license@php.net so we can mail you a copy immediately. |
12+
+----------------------------------------------------------------------+
13+
| Author: George Schlossnagle <george@omniti.com> |
14+
+----------------------------------------------------------------------+
15+
*/
16+
17+
#define PDO_PARSER_TEXT 1
18+
#define PDO_PARSER_BIND 2
19+
#define PDO_PARSER_BIND_POS 3
20+
#define PDO_PARSER_ESCAPED_QUESTION 4
21+
#define PDO_PARSER_CUSTOM_QUOTE 5
22+
#define PDO_PARSER_EOI 6
23+
24+
#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1
25+
26+
#define RET(i) {s->cur = cursor; return i; }
27+
#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
28+
29+
#define YYCTYPE unsigned char
30+
#define YYCURSOR cursor
31+
#define YYLIMIT s->end
32+
#define YYMARKER s->ptr
33+
#define YYFILL(n) { if (YYLIMIT - 1 <= YYCURSOR) RET(PDO_PARSER_EOI); }

ext/pdo/pdo_sql_parser.re

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,26 @@
1717
#include "php.h"
1818
#include "php_pdo_driver.h"
1919
#include "php_pdo_int.h"
20+
#include "pdo_sql_parser.h"
2021

21-
#define PDO_PARSER_TEXT 1
22-
#define PDO_PARSER_BIND 2
23-
#define PDO_PARSER_BIND_POS 3
24-
#define PDO_PARSER_ESCAPED_QUESTION 4
25-
#define PDO_PARSER_EOI 5
26-
27-
#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1
28-
29-
#define RET(i) {s->cur = cursor; return i; }
30-
#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }
31-
32-
#define YYCTYPE unsigned char
33-
#define YYCURSOR cursor
34-
#define YYLIMIT s->end
35-
#define YYMARKER s->ptr
36-
#define YYFILL(n) { RET(PDO_PARSER_EOI); }
37-
38-
typedef struct Scanner {
39-
const char *ptr, *cur, *tok, *end;
40-
} Scanner;
41-
42-
static int scan(Scanner *s)
22+
static int default_scanner(pdo_scanner_t *s)
4323
{
4424
const char *cursor = s->cur;
4525

4626
s->tok = cursor;
4727
/*!re2c
4828
BINDCHR = [:][a-zA-Z0-9_]+;
4929
QUESTION = [?];
50-
ESCQUESTION = [?][?];
51-
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*);
52-
SPECIALS = [:?"'-/];
53-
MULTICHAR = [:]{2,};
30+
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*);
31+
SPECIALS = [:?"'/-];
32+
MULTICHAR = ([:]{2,}|[?]{2,});
5433
ANYNOEOF = [\001-\377];
5534
*/
5635
5736
/*!re2c
58-
(["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); }
59-
(['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); }
37+
(["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); }
38+
(['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); }
6039
MULTICHAR { RET(PDO_PARSER_TEXT); }
61-
ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); }
6240
BINDCHR { RET(PDO_PARSER_BIND); }
6341
QUESTION { RET(PDO_PARSER_BIND_POS); }
6442
SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); }
@@ -75,13 +53,18 @@ struct placeholder {
7553
struct placeholder *next;
7654
};
7755

56+
struct custom_quote {
57+
const char *pos;
58+
size_t len;
59+
};
60+
7861
static void free_param_name(zval *el) {
7962
zend_string_release(Z_PTR_P(el));
8063
}
8164

8265
PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string **outquery)
8366
{
84-
Scanner s;
67+
pdo_scanner_t s;
8568
char *newbuffer;
8669
ptrdiff_t t;
8770
uint32_t bindno = 0;
@@ -91,12 +74,42 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
9174
struct pdo_bound_param_data *param;
9275
int query_type = PDO_PLACEHOLDER_NONE;
9376
struct placeholder *placeholders = NULL, *placetail = NULL, *plc = NULL;
77+
int (*scan)(pdo_scanner_t *s);
78+
struct custom_quote custom_quote = {NULL, 0};
79+
80+
scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner;
9481

9582
s.cur = ZSTR_VAL(inquery);
9683
s.end = s.cur + ZSTR_LEN(inquery) + 1;
9784

9885
/* phase 1: look for args */
9986
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+
} else if (t == PDO_PARSER_ESCAPED_QUESTION) {
94+
/* An escaped question mark has been used inside a dollar quoted string, most likely as a workaround
95+
* as a single "?" would have been parsed as placeholder, due to the lack of support for dollar quoted
96+
* strings. For now, we emit a deprecation notice, but still process it */
97+
php_error_docref(NULL, E_DEPRECATED, "Escaping question marks inside dollar quoted strings is not required anymore and is deprecated");
98+
99+
goto placeholder;
100+
}
101+
102+
continue;
103+
}
104+
105+
if (t == PDO_PARSER_CUSTOM_QUOTE) {
106+
/* Start of a custom quote, keep a reference to search for the matching closing quote */
107+
custom_quote.pos = s.tok;
108+
custom_quote.len = s.cur - s.tok;
109+
110+
continue;
111+
}
112+
100113
if (t == PDO_PARSER_BIND || t == PDO_PARSER_BIND_POS || t == PDO_PARSER_ESCAPED_QUESTION) {
101114
if (t == PDO_PARSER_ESCAPED_QUESTION && stmt->supports_placeholders == PDO_PLACEHOLDER_POSITIONAL) {
102115
/* escaped question marks unsupported, treat as text */
@@ -113,6 +126,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
113126
query_type |= PDO_PLACEHOLDER_POSITIONAL;
114127
}
115128

129+
placeholder:
116130
plc = emalloc(sizeof(*plc));
117131
memset(plc, 0, sizeof(*plc));
118132
plc->next = NULL;

ext/pdo/php_pdo_driver.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ typedef struct _pdo_dbh_t pdo_dbh_t;
2424
typedef struct _pdo_dbh_object_t pdo_dbh_object_t;
2525
typedef struct _pdo_stmt_t pdo_stmt_t;
2626
typedef struct _pdo_row_t pdo_row_t;
27+
typedef struct _pdo_scanner_t pdo_scanner_t;
2728
struct pdo_bound_param_data;
2829

2930
#ifndef TRUE
@@ -33,7 +34,7 @@ struct pdo_bound_param_data;
3334
# define FALSE 0
3435
#endif
3536

36-
#define PDO_DRIVER_API 20170320
37+
#define PDO_DRIVER_API 20240423
3738

3839
/* Doctrine hardcodes these constants, avoid changing their values. */
3940
enum pdo_param_type {
@@ -275,6 +276,9 @@ typedef void (*pdo_dbh_request_shutdown)(pdo_dbh_t *dbh);
275276
* with any zvals in the driver_data that would be freed if the handle is destroyed. */
276277
typedef void (*pdo_dbh_get_gc_func)(pdo_dbh_t *dbh, zend_get_gc_buffer *buffer);
277278

279+
/* driver specific re2s sql parser, overrides the default one if present */
280+
typedef int (*pdo_dbh_sql_scanner)(pdo_scanner_t *s);
281+
278282
/* for adding methods to the dbh or stmt objects
279283
pointer to a list of driver specific functions. The convention is
280284
to prefix the function names using the PDO driver name; this will
@@ -307,6 +311,7 @@ struct pdo_dbh_methods {
307311
/* if defined to NULL, PDO will use its internal transaction tracking state */
308312
pdo_dbh_txn_func in_transaction;
309313
pdo_dbh_get_gc_func get_gc;
314+
pdo_dbh_sql_scanner scanner;
310315
};
311316

312317
/* }}} */
@@ -647,6 +652,10 @@ struct _pdo_row_t {
647652
pdo_stmt_t *stmt;
648653
};
649654

655+
struct _pdo_scanner_t {
656+
const char *ptr, *cur, *tok, *end;
657+
};
658+
650659
/* Call this in MINIT to register the PDO driver.
651660
* Registering the driver might fail and should be reported accordingly in MINIT. */
652661
PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver);

ext/pdo_dblib/dblib_driver.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -436,7 +436,8 @@ static const struct pdo_dbh_methods dblib_methods = {
436436
NULL, /* get driver methods */
437437
NULL, /* request shutdown */
438438
NULL, /* in transaction, use PDO's internal tracking mechanism */
439-
NULL /* get gc */
439+
NULL, /* get gc */
440+
NULL /* scanner */
440441
};
441442

442443
static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)

ext/pdo_firebird/firebird_driver.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,7 +1274,8 @@ static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
12741274
NULL, /* get driver methods */
12751275
NULL, /* request shutdown */
12761276
pdo_firebird_in_manually_transaction,
1277-
NULL /* get gc */
1277+
NULL, /* get gc */
1278+
NULL /* scanner */
12781279
};
12791280
/* }}} */
12801281

ext/pdo_mysql/Makefile.frag

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
$(srcdir)/mysql_sql_parser.c: $(srcdir)/mysql_sql_parser.re
2+
@(cd $(top_srcdir); \
3+
if test -f ./mysql_sql_parser.re; then \
4+
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o mysql_sql_parser.c mysql_sql_parser.re; \
5+
else \
6+
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re; \
7+
fi)

ext/pdo_mysql/Makefile.frag.w32

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ext\pdo_mysql\mysql_sql_parser.c: ext\pdo_mysql\mysql_sql_parser.re
2+
cd $(PHP_SRC_DIR)
3+
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re

ext/pdo_mysql/config.m4

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,10 @@ if test "$PHP_PDO_MYSQL" != "no"; then
8585
AC_DEFINE_UNQUOTED(PDO_MYSQL_UNIX_ADDR, "$PDO_MYSQL_SOCKET", [ ])
8686
fi
8787

88-
PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
88+
PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
8989

9090
PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo)
91+
PHP_ADD_MAKEFILE_FRAGMENT
9192

9293
if test "$PHP_PDO_MYSQL" = "yes" || test "$PHP_PDO_MYSQL" = "mysqlnd"; then
9394
PHP_ADD_EXTENSION_DEP(pdo_mysql, mysqlnd)

ext/pdo_mysql/config.w32

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ if (PHP_PDO_MYSQL != "no") {
66
if (PHP_PDO_MYSQL == "yes" || PHP_PDO_MYSQL == "mysqlnd") {
77
AC_DEFINE('PDO_USE_MYSQLND', 1, 'Using MySQL native driver');
88
STDOUT.WriteLine("INFO: mysqlnd build");
9-
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c");
9+
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c");
1010
ADD_EXTENSION_DEP('pdo_mysql', 'pdo');
11+
ADD_MAKEFILE_FRAGMENT();
1112
} else {
1213
if (CHECK_LIB("libmysql.lib", "pdo_mysql", PHP_PDO_MYSQL) &&
1314
CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_PDO_MYSQL",
1415
PHP_PDO_MYSQL + "\\include;" +
1516
PHP_PHP_BUILD + "\\include\\mysql;" +
1617
PHP_PDO_MYSQL)) {
17-
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
18+
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
19+
ADD_MAKEFILE_FRAGMENT();
1820
} else {
1921
WARNING("pdo_mysql not enabled; libraries and headers not found");
2022
}

ext/pdo_mysql/mysql_driver.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,8 @@ static const struct pdo_dbh_methods mysql_methods = {
650650
NULL,
651651
pdo_mysql_request_shutdown,
652652
pdo_mysql_in_transaction,
653-
NULL /* get_gc */
653+
NULL, /* get_gc */
654+
pdo_mysql_scanner
654655
};
655656
/* }}} */
656657

0 commit comments

Comments
 (0)