diff --git a/.gitignore b/.gitignore index 608cb86294e..6ab337b2e4c 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/ext/pdo/pdo_sql_parser.h b/ext/pdo/pdo_sql_parser.h new file mode 100644 index 00000000000..f8c303473e7 --- /dev/null +++ b/ext/pdo/pdo_sql_parser.h @@ -0,0 +1,32 @@ +/* + +----------------------------------------------------------------------+ + | 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 | + +----------------------------------------------------------------------+ +*/ + +#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); } diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re index 6bb0837fb31..e04ebe73d88 100644 --- a/ext/pdo/pdo_sql_parser.re +++ b/ext/pdo/pdo_sql_parser.re @@ -17,29 +17,9 @@ #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; @@ -47,18 +27,16 @@ static int scan(Scanner *s) /*!re2c BINDCHR = [:][a-zA-Z0-9_]+; QUESTION = [?]; - ESCQUESTION = [?][?]; COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*); - SPECIALS = [:?"'-/]; - MULTICHAR = [:]{2,}; + 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); } @@ -81,7 +59,7 @@ static void free_param_name(zval *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; @@ -91,6 +69,9 @@ 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); + + scan = stmt->dbh->methods->scanner ? stmt->dbh->methods->scanner : default_scanner; s.cur = ZSTR_VAL(inquery); s.end = s.cur + ZSTR_LEN(inquery) + 1; diff --git a/ext/pdo/php_pdo_driver.h b/ext/pdo/php_pdo_driver.h index 262a908f976..0f700c6818d 100644 --- a/ext/pdo/php_pdo_driver.h +++ b/ext/pdo/php_pdo_driver.h @@ -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 @@ -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 { @@ -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 @@ -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; }; /* }}} */ @@ -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); diff --git a/ext/pdo_dblib/dblib_driver.c b/ext/pdo_dblib/dblib_driver.c index cfce8c50461..17036d2b002 100644 --- a/ext/pdo_dblib/dblib_driver.c +++ b/ext/pdo_dblib/dblib_driver.c @@ -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) diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index 8d62936bb37..82df4ae40ff 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -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 */ }; /* }}} */ diff --git a/ext/pdo_mysql/Makefile.frag b/ext/pdo_mysql/Makefile.frag new file mode 100644 index 00000000000..8349f47a669 --- /dev/null +++ b/ext/pdo_mysql/Makefile.frag @@ -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) diff --git a/ext/pdo_mysql/Makefile.frag.w32 b/ext/pdo_mysql/Makefile.frag.w32 new file mode 100644 index 00000000000..02da654de36 --- /dev/null +++ b/ext/pdo_mysql/Makefile.frag.w32 @@ -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 diff --git a/ext/pdo_mysql/config.m4 b/ext/pdo_mysql/config.m4 index 122376bdc02..9465d61496b 100644 --- a/ext/pdo_mysql/config.m4 +++ b/ext/pdo_mysql/config.m4 @@ -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) diff --git a/ext/pdo_mysql/config.w32 b/ext/pdo_mysql/config.w32 index 48e47f78718..ce5584386e3 100644 --- a/ext/pdo_mysql/config.w32 +++ b/ext/pdo_mysql/config.w32 @@ -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"); } diff --git a/ext/pdo_mysql/mysql_driver.c b/ext/pdo_mysql/mysql_driver.c index 95d54500467..16e3620bb80 100644 --- a/ext/pdo_mysql/mysql_driver.c +++ b/ext/pdo_mysql/mysql_driver.c @@ -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 }; /* }}} */ diff --git a/ext/pdo_mysql/mysql_sql_parser.re b/ext/pdo_mysql/mysql_sql_parser.re new file mode 100644 index 00000000000..6e2fd72709e --- /dev/null +++ b/ext/pdo_mysql/mysql_sql_parser.re @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | 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: | + | https://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: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_mysql_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + /*!re2c + BINDCHR = [:][a-zA-Z0-9_]+; + QUESTION = [?]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|("--"|[#])[^\r\n]*); + 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); } + MULTICHAR { RET(PDO_PARSER_TEXT); } + BINDCHR { RET(PDO_PARSER_BIND); } + QUESTION { RET(PDO_PARSER_BIND_POS); } + SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_mysql/php_pdo_mysql_int.h b/ext/pdo_mysql/php_pdo_mysql_int.h index adde9a4ebc0..b2e15e3080d 100644 --- a/ext/pdo_mysql/php_pdo_mysql_int.h +++ b/ext/pdo_mysql/php_pdo_mysql_int.h @@ -147,6 +147,8 @@ typedef struct { extern const pdo_driver_t pdo_mysql_driver; +extern int pdo_mysql_scanner(pdo_scanner_t *s); + extern int _pdo_mysql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); #define pdo_mysql_error(s) _pdo_mysql_error(s, NULL, __FILE__, __LINE__) #define pdo_mysql_error_stmt(s) _pdo_mysql_error(stmt->dbh, stmt, __FILE__, __LINE__) diff --git a/ext/pdo_mysql/tests/pdo_mysql_parser.phpt b/ext/pdo_mysql/tests/pdo_mysql_parser.phpt new file mode 100644 index 00000000000..6db495654ca --- /dev/null +++ b/ext/pdo_mysql/tests/pdo_mysql_parser.phpt @@ -0,0 +1,58 @@ +--TEST-- +MySQL PDO Parser custom syntax +--EXTENSIONS-- +pdo_mysql +--SKIPIF-- + +--FILE-- +exec("DROP TABLE IF EXISTS {$table}"); +$db->exec("CREATE TABLE {$table} (`a``?` int NOT NULL)"); +$db->exec("INSERT INTO {$table} VALUES (1)"); + +// No parameters +$queries = [ + "SELECT * FROM {$table}", + "SELECT * FROM {$table} -- ?", + "SELECT * FROM {$table} # ?", + "SELECT * FROM {$table} /* ? */", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute(); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +// One parameter +$queries = [ + "SELECT * FROM {$table} WHERE 1 = ?", + "SELECT * FROM {$table} WHERE \"?\" IN (?, '?')", + "SELECT * FROM {$table} WHERE `a``?` = ?", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute([1]); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +$db->exec("DROP TABLE pdo_mysql_parser"); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/pdo_odbc/odbc_driver.c b/ext/pdo_odbc/odbc_driver.c index d991b8dc5fe..0e89e2b21d4 100644 --- a/ext/pdo_odbc/odbc_driver.c +++ b/ext/pdo_odbc/odbc_driver.c @@ -461,7 +461,8 @@ static const struct pdo_dbh_methods odbc_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_odbc_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ diff --git a/ext/pdo_pgsql/Makefile.frag b/ext/pdo_pgsql/Makefile.frag new file mode 100644 index 00000000000..33abcfc79ef --- /dev/null +++ b/ext/pdo_pgsql/Makefile.frag @@ -0,0 +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) --no-generation-date -o pgsql_sql_parser.c pgsql_sql_parser.re; \ + else \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -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 new file mode 100644 index 00000000000..fb7cab96a44 --- /dev/null +++ b/ext/pdo_pgsql/Makefile.frag.w32 @@ -0,0 +1,3 @@ +ext\pdo_pgsql\pgsql_sql_parser.c: ext\pdo_pgsql\pgsql_sql_parser.re + cd $(PHP_SRC_DIR) + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_pgsql/pgsql_sql_parser.c ext/pdo_pgsql/pgsql_sql_parser.re diff --git a/ext/pdo_pgsql/config.m4 b/ext/pdo_pgsql/config.m4 index 492a2e8b66a..436841417a6 100644 --- a/ext/pdo_pgsql/config.m4 +++ b/ext/pdo_pgsql/config.m4 @@ -79,6 +79,7 @@ if test "$PHP_PDO_PGSQL" != "no"; then PHP_CHECK_PDO_INCLUDES - PHP_NEW_EXTENSION(pdo_pgsql, pdo_pgsql.c pgsql_driver.c pgsql_statement.c, $ext_shared) + PHP_NEW_EXTENSION(pdo_pgsql, pdo_pgsql.c pgsql_driver.c pgsql_statement.c pgsql_sql_parser.c, $ext_shared) PHP_ADD_EXTENSION_DEP(pdo_pgsql, pdo) + PHP_ADD_MAKEFILE_FRAGMENT fi diff --git a/ext/pdo_pgsql/config.w32 b/ext/pdo_pgsql/config.w32 index cda62a64dba..d6be4487c58 100644 --- a/ext/pdo_pgsql/config.w32 +++ b/ext/pdo_pgsql/config.w32 @@ -5,7 +5,7 @@ ARG_WITH("pdo-pgsql", "PostgreSQL support for PDO", "no"); if (PHP_PDO_PGSQL != "no") { if (CHECK_LIB("libpq.lib", "pdo_pgsql", PHP_PDO_PGSQL) && CHECK_HEADER_ADD_INCLUDE("libpq-fe.h", "CFLAGS_PDO_PGSQL", PHP_PDO_PGSQL + "\\include;" + PHP_PHP_BUILD + "\\include\\pgsql;" + PHP_PHP_BUILD + "\\include\\libpq;")) { - EXTENSION("pdo_pgsql", "pdo_pgsql.c pgsql_driver.c pgsql_statement.c"); + EXTENSION("pdo_pgsql", "pdo_pgsql.c pgsql_driver.c pgsql_statement.c pgsql_sql_parser.c"); if (X64) { ADD_FLAG('CFLAGS_PDO_PGSQL', "/D HAVE_PG_LO64=1"); @@ -14,6 +14,7 @@ if (PHP_PDO_PGSQL != "no") { AC_DEFINE('HAVE_PDO_PGSQL', 1, 'Have PostgreSQL library'); ADD_EXTENSION_DEP('pdo_pgsql', 'pdo'); + ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_pgsql not enabled; libraries and headers not found"); } diff --git a/ext/pdo_pgsql/pgsql_driver.c b/ext/pdo_pgsql/pgsql_driver.c index a8d4f7efbd6..3171653eab0 100644 --- a/ext/pdo_pgsql/pgsql_driver.c +++ b/ext/pdo_pgsql/pgsql_driver.c @@ -1321,7 +1321,8 @@ static const struct pdo_dbh_methods pgsql_methods = { pdo_pgsql_get_driver_methods, /* get_driver_methods */ NULL, pgsql_handle_in_transaction, - NULL /* get_gc */ + NULL, /* get_gc */ + pdo_pgsql_scanner }; static int pdo_pgsql_handle_factory(pdo_dbh_t *dbh, zval *driver_options) /* {{{ */ diff --git a/ext/pdo_pgsql/pgsql_sql_parser.re b/ext/pdo_pgsql/pgsql_sql_parser.re new file mode 100644 index 00000000000..1d20221c37f --- /dev/null +++ b/ext/pdo_pgsql/pgsql_sql_parser.re @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | 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: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_pgsql_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,}; + ANYNOEOF = [\001-\377]; + */ + + /*!re2c + (["]((["]["])|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); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_pgsql/php_pdo_pgsql_int.h b/ext/pdo_pgsql/php_pdo_pgsql_int.h index 5b3e194f23d..3cd0b005fcc 100644 --- a/ext/pdo_pgsql/php_pdo_pgsql_int.h +++ b/ext/pdo_pgsql/php_pdo_pgsql_int.h @@ -74,6 +74,8 @@ typedef struct { extern const pdo_driver_t pdo_pgsql_driver; +extern int pdo_pgsql_scanner(pdo_scanner_t *s); + extern int _pdo_pgsql_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, int errcode, const char *sqlstate, const char *msg, const char *file, int line); #define pdo_pgsql_error(d,e,z) _pdo_pgsql_error(d, NULL, e, z, NULL, __FILE__, __LINE__) #define pdo_pgsql_error_msg(d,e,m) _pdo_pgsql_error(d, NULL, e, NULL, m, __FILE__, __LINE__) diff --git a/ext/pdo_sqlite/Makefile.frag b/ext/pdo_sqlite/Makefile.frag new file mode 100644 index 00000000000..c439367799d --- /dev/null +++ b/ext/pdo_sqlite/Makefile.frag @@ -0,0 +1,7 @@ +$(srcdir)/sqlite_sql_parser.c: $(srcdir)/sqlite_sql_parser.re + @(cd $(top_srcdir); \ + if test -f ./sqlite_sql_parser.re; then \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o sqlite_sql_parser.c sqlite_sql_parser.re; \ + else \ + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_sqlite/sqlite_sql_parser.c ext/pdo_sqlite/sqlite_sql_parser.re; \ + fi) diff --git a/ext/pdo_sqlite/Makefile.frag.w32 b/ext/pdo_sqlite/Makefile.frag.w32 new file mode 100644 index 00000000000..a7763758bb5 --- /dev/null +++ b/ext/pdo_sqlite/Makefile.frag.w32 @@ -0,0 +1,3 @@ +ext\pdo_sqlite\sqlite_sql_parser.c: ext\pdo_sqlite\sqlite_sql_parser.re + cd $(PHP_SRC_DIR) + $(RE2C) $(RE2C_FLAGS) --no-generation-date -o ext/pdo_sqlite/sqlite_sql_parser.c ext/pdo_sqlite/sqlite_sql_parser.re diff --git a/ext/pdo_sqlite/config.m4 b/ext/pdo_sqlite/config.m4 index 62c738f01e8..17b2fca8a1e 100644 --- a/ext/pdo_sqlite/config.m4 +++ b/ext/pdo_sqlite/config.m4 @@ -29,8 +29,9 @@ if test "$PHP_PDO_SQLITE" != "no"; then ) PHP_SUBST(PDO_SQLITE_SHARED_LIBADD) - PHP_NEW_EXTENSION(pdo_sqlite, pdo_sqlite.c sqlite_driver.c sqlite_statement.c, + PHP_NEW_EXTENSION(pdo_sqlite, pdo_sqlite.c sqlite_driver.c sqlite_statement.c sqlite_sql_parser.c, $ext_shared) PHP_ADD_EXTENSION_DEP(pdo_sqlite, pdo) + PHP_ADD_MAKEFILE_FRAGMENT fi diff --git a/ext/pdo_sqlite/config.w32 b/ext/pdo_sqlite/config.w32 index 1ad3b74e8c3..59496f4ca52 100644 --- a/ext/pdo_sqlite/config.w32 +++ b/ext/pdo_sqlite/config.w32 @@ -4,11 +4,12 @@ ARG_WITH("pdo-sqlite", "for pdo_sqlite support", "no"); if (PHP_PDO_SQLITE != "no") { if (SETUP_SQLITE3("pdo_sqlite", PHP_PDO_SQLITE, PHP_PDO_SQLITE_SHARED)) { - EXTENSION("pdo_sqlite", "pdo_sqlite.c sqlite_driver.c sqlite_statement.c"); + EXTENSION("pdo_sqlite", "pdo_sqlite.c sqlite_driver.c sqlite_statement.c sqlite_sql_parser.c"); ADD_EXTENSION_DEP('pdo_sqlite', 'pdo'); AC_DEFINE("HAVE_SQLITE3_COLUMN_TABLE_NAME", 1, "have sqlite3_column_table_name"); AC_DEFINE("HAVE_SQLITE3_CLOSE_V2", 1, "have sqlite3_close_v2"); + ADD_MAKEFILE_FRAGMENT(); } else { WARNING("pdo_sqlite not enabled; libraries and/or headers not found"); } diff --git a/ext/pdo_sqlite/php_pdo_sqlite_int.h b/ext/pdo_sqlite/php_pdo_sqlite_int.h index 054e1fbae26..43a07345ed6 100644 --- a/ext/pdo_sqlite/php_pdo_sqlite_int.h +++ b/ext/pdo_sqlite/php_pdo_sqlite_int.h @@ -61,6 +61,8 @@ typedef struct { extern const pdo_driver_t pdo_sqlite_driver; +extern int pdo_sqlite_scanner(pdo_scanner_t *s); + extern int _pdo_sqlite_error(pdo_dbh_t *dbh, pdo_stmt_t *stmt, const char *file, int line); #define pdo_sqlite_error(s) _pdo_sqlite_error(s, NULL, __FILE__, __LINE__) #define pdo_sqlite_error_stmt(s) _pdo_sqlite_error(stmt->dbh, stmt, __FILE__, __LINE__) diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c index 76e82cc2e61..8d0c034fee4 100644 --- a/ext/pdo_sqlite/sqlite_driver.c +++ b/ext/pdo_sqlite/sqlite_driver.c @@ -743,7 +743,8 @@ static const struct pdo_dbh_methods sqlite_methods = { get_driver_methods, pdo_sqlite_request_shutdown, pdo_sqlite_in_transaction, - pdo_sqlite_get_gc + pdo_sqlite_get_gc, + pdo_sqlite_scanner }; static char *make_filename_safe(const char *filename) diff --git a/ext/pdo_sqlite/sqlite_sql_parser.re b/ext/pdo_sqlite/sqlite_sql_parser.re new file mode 100644 index 00000000000..de64487f628 --- /dev/null +++ b/ext/pdo_sqlite/sqlite_sql_parser.re @@ -0,0 +1,48 @@ +/* + +----------------------------------------------------------------------+ + | 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: | + | https://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: Matteo Beccati | + +----------------------------------------------------------------------+ +*/ + + +#include "php.h" +#include "ext/pdo/php_pdo_driver.h" +#include "ext/pdo/php_pdo_int.h" +#include "ext/pdo/pdo_sql_parser.h" + +int pdo_sqlite_scanner(pdo_scanner_t *s) +{ + const char *cursor = s->cur; + + s->tok = cursor; + /*!re2c + BINDCHR = [:][a-zA-Z0-9_]+; + QUESTION = [?]; + COMMENTS = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--"[^\r\n]*); + SPECIALS = [:?"'`/-]; + MULTICHAR = ([:]{2,}|[?]{2,}); + ANYNOEOF = [\001-\377]; + */ + + /*!re2c + (["]((["]["])|ANYNOEOF)*["]) { RET(PDO_PARSER_TEXT); } + (['](([']['])|ANYNOEOF)*[']) { RET(PDO_PARSER_TEXT); } + ([`](([`][`])|ANYNOEOF)*[`]) { RET(PDO_PARSER_TEXT); } + MULTICHAR { RET(PDO_PARSER_TEXT); } + BINDCHR { RET(PDO_PARSER_BIND); } + QUESTION { RET(PDO_PARSER_BIND_POS); } + SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); } + COMMENTS { RET(PDO_PARSER_TEXT); } + (ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); } + */ +} diff --git a/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt b/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt new file mode 100644 index 00000000000..12d6aed5137 --- /dev/null +++ b/ext/pdo_sqlite/tests/pdo_sqlite_parser.phpt @@ -0,0 +1,58 @@ +--TEST-- +PDO_sqlite: Parser custom syntax +--EXTENSIONS-- +pdo_sqlite +--FILE-- + PDO::ERRMODE_EXCEPTION]); + +$table = 'pdo_sqlite_parser'; + +$db->exec("CREATE TABLE {$table} (`a``?` int NOT NULL)"); +$db->exec("INSERT INTO {$table} VALUES (1)"); + +// No parameters +$queries = [ + "SELECT * FROM {$table}", + "SELECT * FROM {$table} -- ?", + "SELECT * FROM {$table} /* ? */", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute(); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +// One parameter +$queries = [ + "SELECT * FROM {$table} WHERE '1' = ?", + "SELECT * FROM {$table} WHERE \"?\" IN (?, '?')", + "SELECT * FROM {$table} WHERE `a``?` = ?", +]; + +foreach ($queries as $k => $query) { + $stmt = $db->prepare($query); + $stmt->execute([1]); + var_dump($stmt->fetch(PDO::FETCH_NUM) === [0 => 1]); +} + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/scripts/dev/genfiles b/scripts/dev/genfiles index 2121596fbf1..0e3f8a09489 100755 --- a/scripts/dev/genfiles +++ b/scripts/dev/genfiles @@ -125,6 +125,21 @@ $MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo builddir=ext/pdo top_ -f ext/pdo/Makefile.frag \ ext/pdo/pdo_sql_parser.c +echo "genfiles: Generating PDO_mysql lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_mysql builddir=ext/pdo_mysql top_srcdir=. \ + -f ext/pdo_mysql/Makefile.frag \ + ext/pdo_mysql/mysql_sql_parser.c + +echo "genfiles: Generating PDO_pgsql lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_pgsql builddir=ext/pdo_pgsql top_srcdir=. \ + -f ext/pdo_pgsql/Makefile.frag \ + ext/pdo_pgsql/pgsql_sql_parser.c + +echo "genfiles: Generating PDO_sqlite lexer file" +$MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/pdo_sqlite builddir=ext/pdo_sqlite top_srcdir=. \ + -f ext/pdo_sqlite/Makefile.frag \ + ext/pdo_sqlite/sqlite_sql_parser.c + echo "genfiles: Generating standard extension lexer files" $MAKE RE2C="$RE2C" RE2C_FLAGS="$RE2C_FLAGS" srcdir=ext/standard builddir=ext/standard top_srcdir=. \ -f ext/standard/Makefile.frag \