- PDO:
. Add "Sent SQL" to debug dump for emulated prepares. (Adam Baratz)
+ . Add parameter types for national character set strings. (Adam Baratz)
- PDO_DBlib:
. Fixed bug #73234 (Emulated statements let value dictate parameter type).
REGISTER_PDO_CLASS_CONST_LONG("PARAM_LOB", (zend_long)PDO_PARAM_LOB);
REGISTER_PDO_CLASS_CONST_LONG("PARAM_STMT", (zend_long)PDO_PARAM_STMT);
REGISTER_PDO_CLASS_CONST_LONG("PARAM_INPUT_OUTPUT", (zend_long)PDO_PARAM_INPUT_OUTPUT);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_NATL", (zend_long)PDO_PARAM_STR_NATL);
+ REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_CHAR", (zend_long)PDO_PARAM_STR_CHAR);
+
REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_ALLOC", (zend_long)PDO_PARAM_EVT_ALLOC);
REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FREE", (zend_long)PDO_PARAM_EVT_FREE);
REGISTER_PDO_CLASS_CONST_LONG("ATTR_MAX_COLUMN_LEN", (zend_long)PDO_ATTR_MAX_COLUMN_LEN);
REGISTER_PDO_CLASS_CONST_LONG("ATTR_EMULATE_PREPARES", (zend_long)PDO_ATTR_EMULATE_PREPARES);
REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_FETCH_MODE", (zend_long)PDO_ATTR_DEFAULT_FETCH_MODE);
+ REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_STR_PARAM", (zend_long)PDO_ATTR_DEFAULT_STR_PARAM);
REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_SILENT", (zend_long)PDO_ERRMODE_SILENT);
REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_WARNING", (zend_long)PDO_ERRMODE_WARNING);
# define FALSE 0
#endif
-#define PDO_DRIVER_API 20161020
+#define PDO_DRIVER_API 20170320
enum pdo_param_type {
PDO_PARAM_NULL,
PDO_PARAM_ZVAL,
/* magic flag to denote a parameter as being input/output */
- PDO_PARAM_INPUT_OUTPUT = 0x80000000
+ PDO_PARAM_INPUT_OUTPUT = 0x80000000,
+
+ /* magic flag to denote a string that uses the national character set
+ see section 4.2.1 of SQL-92: http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
+ */
+ PDO_PARAM_STR_NATL = 0x40000000,
+
+ /* magic flag to denote a string that uses the regular character set */
+ PDO_PARAM_STR_CHAR = 0x20000000,
};
#define PDO_PARAM_FLAGS 0xFFFF0000
PDO_ATTR_MAX_COLUMN_LEN, /* make database calculate maximum length of data found in a column */
PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */
PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */
+ PDO_ATTR_DEFAULT_STR_PARAM, /* set the default string parameter type (see the PDO::PARAM_STR_* magic flags) */
/* this defines the start of the range for driver specific options.
* Drivers should define their own attribute constants beginning with this
static int dblib_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype)
{
+ pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
+ zend_bool use_national_character_set = 0;
+
size_t i;
char * q;
*quotedlen = 0;
+ if (H->assume_national_character_set_strings) {
+ use_national_character_set = 1;
+ }
+ if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
+ use_national_character_set = 1;
+ }
+ if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
+ use_national_character_set = 0;
+ }
+
/* Detect quoted length, adding extra char for doubled single quotes */
for (i = 0; i < unquotedlen; i++) {
if (unquoted[i] == '\'') ++*quotedlen;
}
*quotedlen += 2; /* +2 for opening, closing quotes */
+ if (use_national_character_set) {
+ ++*quotedlen; /* N prefix */
+ }
q = *quoted = emalloc(*quotedlen + 1); /* Add byte for terminal null */
+ if (use_national_character_set) {
+ *q++ = 'N';
+ }
*q++ = '\'';
for (i = 0; i < unquotedlen; i++) {
static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
{
+ pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
+
switch(attr) {
+ case PDO_ATTR_DEFAULT_STR_PARAM:
+ H->assume_national_character_set_strings = zval_get_long(val) == PDO_PARAM_STR_NATL ? 1 : 0;
+ return 1;
case PDO_ATTR_TIMEOUT:
case PDO_DBLIB_ATTR_QUERY_TIMEOUT:
return SUCCEED == dbsettime(zval_get_long(val)) ? 1 : 0;
case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER:
- ((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier = zval_get_long(val);
+ H->stringify_uniqueidentifier = zval_get_long(val);
return 1;
default:
return 0;
static int dblib_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value)
{
+ pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
+
switch (attr) {
+ case PDO_ATTR_DEFAULT_STR_PARAM:
+ ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR);
+ break;
+
case PDO_ATTR_EMULATE_PREPARES:
/* this is the only option available, but expose it so common tests and whatever else can introspect */
ZVAL_TRUE(return_value);
break;
case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER:
- ZVAL_BOOL(return_value, ((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier);
+ ZVAL_BOOL(return_value, H->stringify_uniqueidentifier);
break;
case PDO_DBLIB_ATTR_VERSION:
H = pecalloc(1, sizeof(*H), dbh->is_persistent);
H->login = dblogin();
H->err.sqlstate = dbh->error_code;
+ H->assume_national_character_set_strings = 0;
H->stringify_uniqueidentifier = 0;
if (!H->login) {
dbsetlogintime(connect_timeout); /* Connection/Login Timeout */
dbsettime(query_timeout); /* Statement Timeout */
+ H->assume_national_character_set_strings = pdo_attr_lval(driver_options, PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0;
H->stringify_uniqueidentifier = pdo_attr_lval(driver_options, PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER, 0);
}
DBPROCESS *link;
pdo_dblib_err err;
+ unsigned assume_national_character_set_strings:1;
unsigned stringify_uniqueidentifier:1;
} pdo_dblib_db_handle;
--- /dev/null
+--TEST--
+PDO_DBLIB: national character set values are quoted correctly in queries
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo_dblib')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+
+$stmt = $db->prepare('SELECT :value');
+$stmt->bindValue(':value', 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL);
+$stmt->execute();
+
+var_dump($stmt->debugDumpParams());
+?>
+--EXPECT--
+SQL: [13] SELECT :value
+Sent SQL: [13] SELECT N'foo'
+Params: 1
+Key: Name: [6] :value
+paramno=-1
+name=[6] ":value"
+is_param=1
+param_type=1073741826
+NULL
var_dump($db->quote(null, PDO::PARAM_NULL));
var_dump($db->quote('\'', PDO::PARAM_STR));
var_dump($db->quote('foo', PDO::PARAM_STR));
+var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
+var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
+
+var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR);
+$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
+var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
+
+var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
var_dump($db->quote('über', PDO::PARAM_STR));
+var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
+
+$db = new PDO($dsn, $user, $pass, [PDO::ATTR_DEFAULT_STR_PARAM => PDO::PARAM_STR_NATL]);
+var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
+
?>
--EXPECT--
string(3) "'1'"
string(2) "''"
string(4) "''''"
string(5) "'foo'"
-string(7) "'über'"
+string(5) "'foo'"
+string(8) "N'über'"
+bool(true)
+bool(true)
+string(5) "'foo'"
+string(8) "N'über'"
+string(8) "N'über'"
+bool(true)
static int mysql_handle_quoter(pdo_dbh_t *dbh, const char *unquoted, size_t unquotedlen, char **quoted, size_t *quotedlen, enum pdo_param_type paramtype )
{
pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
+ zend_bool use_national_character_set = 0;
+
+ if (H->assume_national_character_set_strings) {
+ use_national_character_set = 1;
+ }
+ if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
+ use_national_character_set = 1;
+ }
+ if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
+ use_national_character_set = 0;
+ }
+
PDO_DBG_ENTER("mysql_handle_quoter");
PDO_DBG_INF_FMT("dbh=%p", dbh);
PDO_DBG_INF_FMT("unquoted=%.*s", (int)unquotedlen, unquoted);
- *quoted = safe_emalloc(2, unquotedlen, 3);
- *quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen);
- (*quoted)[0] =(*quoted)[++*quotedlen] = '\'';
+ *quoted = safe_emalloc(2, unquotedlen, 3 + (use_national_character_set ? 1 : 0));
+
+ if (use_national_character_set) {
+ *quotedlen = mysql_real_escape_string(H->server, *quoted + 2, unquoted, unquotedlen);
+ (*quoted)[0] = 'N';
+ (*quoted)[1] = '\'';
+
+ ++*quotedlen; /* N prefix */
+ } else {
+ *quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen);
+ (*quoted)[0] = '\'';
+ }
+
+ (*quoted)[++*quotedlen] = '\'';
(*quoted)[++*quotedlen] = '\0';
PDO_DBG_INF_FMT("quoted=%.*s", (int)*quotedlen, *quoted);
PDO_DBG_RETURN(1);
static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
{
zend_long lval = zval_get_long(val);
- zend_bool bval = lval? 1 : 0;
+ zend_bool bval = lval ? 1 : 0;
PDO_DBG_ENTER("pdo_mysql_set_attribute");
PDO_DBG_INF_FMT("dbh=%p", dbh);
PDO_DBG_INF_FMT("attr=%l", attr);
}
PDO_DBG_RETURN(1);
+ case PDO_ATTR_DEFAULT_STR_PARAM:
+ ((pdo_mysql_db_handle *)dbh->driver_data)->assume_national_character_set_strings = lval == PDO_PARAM_STR_NATL ? 1 : 0;
+ PDO_DBG_RETURN(1);
+
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
/* ignore if the new value equals the old one */
((pdo_mysql_db_handle *)dbh->driver_data)->buffered = bval;
PDO_DBG_RETURN(1);
+
case PDO_MYSQL_ATTR_DIRECT_QUERY:
case PDO_ATTR_EMULATE_PREPARES:
/* ignore if the new value equals the old one */
((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = bval;
PDO_DBG_RETURN(1);
+
case PDO_ATTR_FETCH_TABLE_NAMES:
((pdo_mysql_db_handle *)dbh->driver_data)->fetch_table_names = bval;
PDO_DBG_RETURN(1);
+
#ifndef PDO_USE_MYSQLND
case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
if (lval < 0) {
}
}
break;
+
case PDO_ATTR_AUTOCOMMIT:
ZVAL_LONG(return_value, dbh->auto_commit);
break;
+ case PDO_ATTR_DEFAULT_STR_PARAM:
+ ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR);
+ break;
+
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
ZVAL_LONG(return_value, H->buffered);
break;
H->max_buffer_size = 1024*1024;
#endif
+ H->assume_national_character_set_strings = 0;
H->buffered = H->emulate_prepare = 1;
/* handle MySQL options */
H->emulate_prepare = pdo_attr_lval(driver_options,
PDO_ATTR_EMULATE_PREPARES, H->emulate_prepare);
+ H->assume_national_character_set_strings = pdo_attr_lval(driver_options,
+ PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0;
+
#ifndef PDO_USE_MYSQLND
H->max_buffer_size = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, H->max_buffer_size);
#endif
typedef struct {
MYSQL *server;
+ unsigned assume_national_character_set_strings:1;
unsigned attached:1;
unsigned buffered:1;
unsigned emulate_prepare:1;
--- /dev/null
+--TEST--
+PDO MySQL national character set parameters don't affect true prepared statements
+--SKIPIF--
+<?php
+if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+PDOTest::skip();
+?>
+--FILE--
+<?php
+require dirname(__FILE__) . '/config.inc';
+require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
+$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
+
+$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
+
+$db->exec('CREATE TABLE test (bar NCHAR(4) NOT NULL)');
+
+$stmt = $db->prepare('INSERT INTO test VALUES(?)');
+$stmt->bindValue(1, 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL);
+$stmt->execute();
+
+var_dump($db->query('SELECT * from test'));
+foreach ($db->query('SELECT * from test') as $row) {
+ print_r($row);
+}
+
+?>
+--CLEAN--
+<?php
+require dirname(__FILE__) . '/mysql_pdo_test.inc';
+MySQLPDOTest::dropTestTable();
+?>
+--EXPECTF--
+object(PDOStatement)#%d (1) {
+ ["queryString"]=>
+ string(18) "SELECT * from test"
+}
+Array
+(
+ [bar] => foo
+ [0] => foo
+)
--- /dev/null
+--TEST--
+MySQL ensure quote function returns expected results
+--SKIPIF--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'skipif.inc');
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+MySQLPDOTest::skip();
+?>
+--FILE--
+<?php
+require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'mysql_pdo_test.inc');
+$db = MySQLPDOTest::factory();
+
+var_dump($db->quote('foo', PDO::PARAM_STR));
+var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
+var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
+
+var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR);
+$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
+var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
+
+var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
+var_dump($db->quote('über', PDO::PARAM_STR));
+var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
+?>
+--EXPECT--
+string(5) "'foo'"
+string(5) "'foo'"
+string(8) "N'über'"
+bool(true)
+bool(true)
+string(5) "'foo'"
+string(8) "N'über'"
+string(8) "N'über'"