Skip to content

Implemented PDO Driver specific SQL parsers #14035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ php
# ------------------------------------------------------------------------------
/ext/json/json_scanner.c
/ext/json/php_json_scanner_defs.h
/ext/pdo/pdo_sql_parser.c
/ext/pdo*/*_sql_parser.c
/ext/phar/phar_path_check.c
/ext/standard/url_scanner_ex.c
/ext/standard/var_unserializer.c
Expand Down
4 changes: 4 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ PHP NEWS
- PDO:
. Fixed setAttribute and getAttribute. (SakiTakamachi)
. Implemented PDO driver-specific subclasses RFC. (danack, kocsismate)
. Added support for PDO driver-specific SQL parsers. (Matteo Beccati)

- PDO_DBLIB:
. Fixed setAttribute and getAttribute. (SakiTakamachi)
Expand All @@ -185,6 +186,7 @@ PHP NEWS
- PDO_MYSQL:
. Fixed setAttribute and getAttribute. (SakiTakamachi)
. Added class Pdo\Mysql. (danack, kocsismate)
. Added custom SQL parser. (Matteo Beccati)

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

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

- PGSQL:
. Added the possibility to have no conditions for pg_select. (OmarEmaraDev)
Expand Down
31 changes: 31 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,32 @@ PHP 8.4 UPGRADE NOTES
openssl_pkey_get_details as well as openssl_sign and openssl_verify were
extended to support those keys.

- PDO:
. Added support for driver specific SQL parsers. The default parser supports:
- single and double quoted literals, with doubling as escaping mechanism.
- two-dashes and non-nested C-style comments.

- PDO_MYSQL:
. Added custom parser supporting:
- single and double-quoted literals, with doubling and backslash as escaping
mechanism
- backtick literal identifiers and with doubling as escaping mechanism
- two dashes followed by at least 1 whitespace, non-nested C-style comments,
and hash-comments

- PDO_PGSQL:
. Added custom parser supporting:
- single and double quoted literals, with doubling as escaping mechanism
- C-style "escape" string literals (E'string')
- dollar-quoted string literals
- two-dashes and C-style comments (non-nested)
- support for "??" as escape sequence for the "?" operator

- PDO_SQLITE:
. Added custom parser supporting:
- single, double quoted, and backtick literals, with doubling as escaping mechanism
- square brackets quoting for identifiers
- two-dashes and C-style comments (non-nested)

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

- PDO_PGSQL:
. Using escaped question marks (??) inside dollar-quoted strings is deprecated.
Since PDO_PGSQL has its own SQL parser with dollar-quoted strings support, it
is no longer necessary to escape question marks inside them.

- PgSQL:
. Calling pgsql_fetch_result() with 2 arguments is deprecated. Use the
3-parameter signature with a null $row parameter instead.
Expand Down
33 changes: 33 additions & 0 deletions ext/pdo/pdo_sql_parser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: George Schlossnagle <george@omniti.com> |
+----------------------------------------------------------------------+
*/

#define PDO_PARSER_TEXT 1
#define PDO_PARSER_BIND 2
#define PDO_PARSER_BIND_POS 3
#define PDO_PARSER_ESCAPED_QUESTION 4
#define PDO_PARSER_CUSTOM_QUOTE 5
#define PDO_PARSER_EOI 6

#define PDO_PARSER_BINDNO_ESCAPED_CHAR -1

#define RET(i) {s->cur = cursor; return i; }
#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }

#define YYCTYPE unsigned char
#define YYCURSOR cursor
#define YYLIMIT s->end
#define YYMARKER s->ptr
#define YYFILL(n) { if (YYLIMIT - 1 <= YYCURSOR) RET(PDO_PARSER_EOI); }
74 changes: 44 additions & 30 deletions ext/pdo/pdo_sql_parser.re
Original file line number Diff line number Diff line change
Expand Up @@ -17,48 +17,26 @@
#include "php.h"
#include "php_pdo_driver.h"
#include "php_pdo_int.h"
#include "pdo_sql_parser.h"

#define PDO_PARSER_TEXT 1
#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_BINDNO_ESCAPED_CHAR -1

#define RET(i) {s->cur = cursor; return i; }
#define SKIP_ONE(i) {s->cur = s->tok + 1; return i; }

#define YYCTYPE unsigned char
#define YYCURSOR cursor
#define YYLIMIT s->end
#define YYMARKER s->ptr
#define YYFILL(n) { RET(PDO_PARSER_EOI); }

typedef struct Scanner {
const char *ptr, *cur, *tok, *end;
} Scanner;

static int scan(Scanner *s)
static int default_scanner(pdo_scanner_t *s)
{
const char *cursor = s->cur;

s->tok = cursor;
/*!re2c
BINDCHR = [:][a-zA-Z0-9_]+;
QUESTION = [?];
ESCQUESTION = [?][?];
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*);
SPECIALS = [:?"'-/];
MULTICHAR = [:]{2,};
COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*);
SPECIALS = [:?"'/-];
MULTICHAR = ([:]{2,}|[?]{2,});
ANYNOEOF = [\001-\377];
*/

/*!re2c
(["](([\\]ANYNOEOF)|ANYNOEOF\["\\])*["]) { RET(PDO_PARSER_TEXT); }
(['](([\\]ANYNOEOF)|ANYNOEOF\['\\])*[']) { RET(PDO_PARSER_TEXT); }
(["]((["]["])|ANYNOEOF\["])*["]) { RET(PDO_PARSER_TEXT); }
(['](([']['])|ANYNOEOF\['])*[']) { RET(PDO_PARSER_TEXT); }
MULTICHAR { RET(PDO_PARSER_TEXT); }
ESCQUESTION { RET(PDO_PARSER_ESCAPED_QUESTION); }
BINDCHR { RET(PDO_PARSER_BIND); }
QUESTION { RET(PDO_PARSER_BIND_POS); }
SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); }
Expand All @@ -75,13 +53,18 @@ 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));
}

PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string **outquery)
{
Scanner s;
pdo_scanner_t s;
char *newbuffer;
ptrdiff_t t;
uint32_t bindno = 0;
Expand All @@ -91,12 +74,42 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
struct pdo_bound_param_data *param;
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;

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

/* 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;
} else if (t == PDO_PARSER_ESCAPED_QUESTION) {
/* An escaped question mark has been used inside a dollar quoted string, most likely as a workaround
* as a single "?" would have been parsed as placeholder, due to the lack of support for dollar quoted
* strings. For now, we emit a deprecation notice, but still process it */
php_error_docref(NULL, E_DEPRECATED, "Escaping question marks inside dollar quoted strings is not required anymore and is deprecated");

goto placeholder;
}

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 */
Expand All @@ -113,6 +126,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string
query_type |= PDO_PLACEHOLDER_POSITIONAL;
}

placeholder:
plc = emalloc(sizeof(*plc));
memset(plc, 0, sizeof(*plc));
plc->next = NULL;
Expand Down
11 changes: 10 additions & 1 deletion ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ typedef struct _pdo_dbh_t pdo_dbh_t;
typedef struct _pdo_dbh_object_t pdo_dbh_object_t;
typedef struct _pdo_stmt_t pdo_stmt_t;
typedef struct _pdo_row_t pdo_row_t;
typedef struct _pdo_scanner_t pdo_scanner_t;
struct pdo_bound_param_data;

#ifndef TRUE
Expand All @@ -33,7 +34,7 @@ struct pdo_bound_param_data;
# define FALSE 0
#endif

#define PDO_DRIVER_API 20170320
#define PDO_DRIVER_API 20240423

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

/* driver specific re2s sql parser, overrides the default one if present */
typedef int (*pdo_dbh_sql_scanner)(pdo_scanner_t *s);

/* for adding methods to the dbh or stmt objects
pointer to a list of driver specific functions. The convention is
to prefix the function names using the PDO driver name; this will
Expand Down Expand Up @@ -307,6 +311,7 @@ struct pdo_dbh_methods {
/* if defined to NULL, PDO will use its internal transaction tracking state */
pdo_dbh_txn_func in_transaction;
pdo_dbh_get_gc_func get_gc;
pdo_dbh_sql_scanner scanner;
};

/* }}} */
Expand Down Expand Up @@ -647,6 +652,10 @@ struct _pdo_row_t {
pdo_stmt_t *stmt;
};

struct _pdo_scanner_t {
const char *ptr, *cur, *tok, *end;
};

/* Call this in MINIT to register the PDO driver.
* Registering the driver might fail and should be reported accordingly in MINIT. */
PDO_API zend_result php_pdo_register_driver(const pdo_driver_t *driver);
Expand Down
3 changes: 2 additions & 1 deletion ext/pdo_dblib/dblib_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,8 @@ static const struct pdo_dbh_methods dblib_methods = {
NULL, /* get driver methods */
NULL, /* request shutdown */
NULL, /* in transaction, use PDO's internal tracking mechanism */
NULL /* get gc */
NULL, /* get gc */
NULL /* scanner */
};

static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
Expand Down
3 changes: 2 additions & 1 deletion ext/pdo_firebird/firebird_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -1274,7 +1274,8 @@ static const struct pdo_dbh_methods firebird_methods = { /* {{{ */
NULL, /* get driver methods */
NULL, /* request shutdown */
pdo_firebird_in_manually_transaction,
NULL /* get gc */
NULL, /* get gc */
NULL /* scanner */
};
/* }}} */

Expand Down
7 changes: 7 additions & 0 deletions ext/pdo_mysql/Makefile.frag
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
$(srcdir)/mysql_sql_parser.c: $(srcdir)/mysql_sql_parser.re
@(cd $(top_srcdir); \
if test -f ./mysql_sql_parser.re; then \
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o mysql_sql_parser.c mysql_sql_parser.re; \
else \
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re; \
fi)
3 changes: 3 additions & 0 deletions ext/pdo_mysql/Makefile.frag.w32
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ext\pdo_mysql\mysql_sql_parser.c: ext\pdo_mysql\mysql_sql_parser.re
cd $(PHP_SRC_DIR)
$(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_mysql/mysql_sql_parser.c ext/pdo_mysql/mysql_sql_parser.re
3 changes: 2 additions & 1 deletion ext/pdo_mysql/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,10 @@ if test "$PHP_PDO_MYSQL" != "no"; then
AC_DEFINE_UNQUOTED(PDO_MYSQL_UNIX_ADDR, "$PDO_MYSQL_SOCKET", [ ])
fi

PHP_NEW_EXTENSION(pdo_mysql, pdo_mysql.c mysql_driver.c mysql_statement.c, $ext_shared,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1)
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)

PHP_ADD_EXTENSION_DEP(pdo_mysql, pdo)
PHP_ADD_MAKEFILE_FRAGMENT

if test "$PHP_PDO_MYSQL" = "yes" || test "$PHP_PDO_MYSQL" = "mysqlnd"; then
PHP_ADD_EXTENSION_DEP(pdo_mysql, mysqlnd)
Expand Down
6 changes: 4 additions & 2 deletions ext/pdo_mysql/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,17 @@ if (PHP_PDO_MYSQL != "no") {
if (PHP_PDO_MYSQL == "yes" || PHP_PDO_MYSQL == "mysqlnd") {
AC_DEFINE('PDO_USE_MYSQLND', 1, 'Using MySQL native driver');
STDOUT.WriteLine("INFO: mysqlnd build");
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c");
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c");
ADD_EXTENSION_DEP('pdo_mysql', 'pdo');
ADD_MAKEFILE_FRAGMENT();
} else {
if (CHECK_LIB("libmysql.lib", "pdo_mysql", PHP_PDO_MYSQL) &&
CHECK_HEADER_ADD_INCLUDE("mysql.h", "CFLAGS_PDO_MYSQL",
PHP_PDO_MYSQL + "\\include;" +
PHP_PHP_BUILD + "\\include\\mysql;" +
PHP_PDO_MYSQL)) {
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
EXTENSION("pdo_mysql", "pdo_mysql.c mysql_driver.c mysql_statement.c mysql_sql_parser.c", null, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
ADD_MAKEFILE_FRAGMENT();
} else {
WARNING("pdo_mysql not enabled; libraries and headers not found");
}
Expand Down
3 changes: 2 additions & 1 deletion ext/pdo_mysql/mysql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,8 @@ static const struct pdo_dbh_methods mysql_methods = {
NULL,
pdo_mysql_request_shutdown,
pdo_mysql_in_transaction,
NULL /* get_gc */
NULL, /* get_gc */
pdo_mysql_scanner
};
/* }}} */

Expand Down
Loading