1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

Merge remote-tracking branch 'mbeccati/pdo_driver_specific_parser'

This commit is contained in:
Matteo Beccati
2024-06-18 17:07:14 +02:00
35 changed files with 594 additions and 45 deletions

2
.gitignore vendored
View File

@@ -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

4
NEWS
View File

@@ -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)
@@ -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)
@@ -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)

View File

@@ -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.
@@ -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.

33
ext/pdo/pdo_sql_parser.h Normal file
View File

@@ -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); }

View File

@@ -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,};
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); }
@@ -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;
@@ -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 */
@@ -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;

View File

@@ -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);

View File

@@ -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)

View File

@@ -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 */
};
/* }}} */

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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");
}

View File

@@ -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
};
/* }}} */

View File

@@ -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 <mbeccati@php.net> |
+----------------------------------------------------------------------+
*/
#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 = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|(("--"[ \t\v\f\r])|[#]).*);
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); }
*/
}

View File

@@ -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__)

View File

@@ -0,0 +1,68 @@
--TEST--
MySQL PDO Parser custom syntax
--EXTENSIONS--
pdo_mysql
--SKIPIF--
<?php
require_once __DIR__ . '/inc/mysql_pdo_test.inc';
MySQLPDOTest::skip();
?>
--FILE--
<?php
require_once __DIR__ . '/inc/mysql_pdo_test.inc';
$db = MySQLPDOTest::factory();
$table = 'pdo_mysql_parser';
$db->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($query, $stmt->fetch(PDO::FETCH_NUM) === [0 => 1]);
}
// One parameter
$queries = [
"SELECT * FROM {$table} WHERE 1 = ?",
"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($query, $stmt->fetch(PDO::FETCH_NUM) === [0 => 1]);
}
$db->exec("DROP TABLE pdo_mysql_parser");
?>
--EXPECT--
string(30) "SELECT * FROM pdo_mysql_parser"
bool(true)
string(35) "SELECT * FROM pdo_mysql_parser -- ?"
bool(true)
string(34) "SELECT * FROM pdo_mysql_parser # ?"
bool(true)
string(38) "SELECT * FROM pdo_mysql_parser /* ? */"
bool(true)
string(42) "SELECT * FROM pdo_mysql_parser WHERE 1 = ?"
bool(true)
string(44) "SELECT * FROM pdo_mysql_parser WHERE 1 = --?"
bool(true)
string(52) "SELECT * FROM pdo_mysql_parser WHERE "?" IN (?, '?')"
bool(true)
string(47) "SELECT * FROM pdo_mysql_parser WHERE `a``?` = ?"
bool(true)

View File

@@ -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) /* {{{ */

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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) /* {{{ */

View File

@@ -0,0 +1,53 @@
/*
+----------------------------------------------------------------------+
| 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 <mbeccati@php.net> |
+----------------------------------------------------------------------+
*/
#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 = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*);
DOLQ_START = [A-Za-z\200-\377_];
DOLQ_CONT = [A-Za-z\200-\377_0-9];
SPECIALS = [$eE:?"'/-];
MULTICHAR = [:]{2,};
ANYNOEOF = [\001-\377];
*/
/*!re2c
[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); }
QUESTION { RET(PDO_PARSER_BIND_POS); }
SPECIALS { SKIP_ONE(PDO_PARSER_TEXT); }
COMMENTS { RET(PDO_PARSER_TEXT); }
(ANYNOEOF\SPECIALS)+ { RET(PDO_PARSER_TEXT); }
*/
}

View File

@@ -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__)

View File

@@ -0,0 +1,47 @@
--TEST--
PDO PgSQL Bug #14244 (Postgres sees parameters in a dollar-delimited string)
--EXTENSIONS--
pdo
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
echo "Test\n";
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
$pdo = PDOTest::test_factory(__DIR__ . '/common.phpt');
$pdo->setAttribute (\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute (\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
echo "Already working (see bug64953.phpt):\n";
$st = $pdo->prepare("SELECT '?' question");
$st->execute();
var_dump($st->fetch());
echo "Inside a dollar-quoted string:\n";
$st = $pdo->prepare("SELECT \$\$?\$\$ question");
$st->execute();
var_dump($st->fetch());
?>
Done
--EXPECT--
Test
Already working (see bug64953.phpt):
array(1) {
["question"]=>
string(1) "?"
}
Inside a dollar-quoted string:
array(1) {
["question"]=>
string(1) "?"
}
Done

View File

@@ -0,0 +1,67 @@
--TEST--
PgSQL PDO Parser custom syntax
--EXTENSIONS--
pdo
pdo_pgsql
--SKIPIF--
<?php
require __DIR__ . '/config.inc';
require dirname(__DIR__, 2) . '/pdo/tests/pdo_test.inc';
PDOTest::skip();
?>
--FILE--
<?php
require __DIR__ . '/../../../ext/pdo/tests/pdo_test.inc';
$db = PDOTest::test_factory(__DIR__ . '/common.phpt');
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$query = <<<'EOF'
SELECT e'He''ll\'o? ' || ? AS b -- '
UNION ALL
SELECT 'He''ll''o?\' || ? AS b -- '
UNION ALL
SELECT U&'d\0061t\+000061? ' || ? AS b /* :name */
UNION ALL
SELECT $__$Is this a $$dollar$$ 'escaping'? $__$ || ? AS b -- ?
UNION ALL
SELECT $$Escaped question mark here?? $$ || ? AS b -- ?
EOF;
$stmt = $db->prepare($query);
$stmt->execute(['World', 'World', 'base', 'Yes', '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());
}
?>
--EXPECTF--
Deprecated: PDO::prepare(): Escaping question marks inside dollar quoted strings is not required anymore and is deprecated in %s on line %d
string(14) "He'll'o? World"
string(14) "He'll'o?\World"
string(10) "data? base"
string(36) "Is this a $$dollar$$ 'escaping'? Yes"
string(31) "Escaped question mark here? Yes"
bool(true)
bool(true)

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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");
}

View File

@@ -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__)

View File

@@ -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)

View File

@@ -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: |
| 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 <mbeccati@php.net> |
+----------------------------------------------------------------------+
*/
#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 = ("/*"([^*]+|[*]+[^/*])*[*]*"*/"|"--".*);
SPECIALS = [:?"'`/[-];
MULTICHAR = ([:]{2,}|[?]{2,});
ANYNOEOF = [\001-\377];
*/
/*!re2c
(["]((["]["])|ANYNOEOF)*["]) { RET(PDO_PARSER_TEXT); }
(['](([']['])|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); }
*/
}

View File

@@ -0,0 +1,62 @@
--TEST--
PDO_sqlite: Parser custom syntax
--EXTENSIONS--
pdo_sqlite
--FILE--
<?php
$filename = __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_parser.db";
// Default open flag is read-write|create
$db = new PDO('sqlite:' . $filename, null, null, [PDO::ATTR_ERRMODE => 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``?` = ?",
"SELECT * FROM {$table} WHERE \"a`?\" = ?",
"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--
<?php
$filename = __DIR__ . DIRECTORY_SEPARATOR . "pdo_sqlite_parser.db";
if (file_exists($filename)) {
unlink($filename);
}
?>
--EXPECT--
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)
bool(true)

View File

@@ -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 \