Skip to content

Commit

Permalink
Add PDO parameter types for national character set strings
Browse files Browse the repository at this point in the history
  • Loading branch information
adambaratz committed Mar 20, 2017
1 parent 3817cba commit 4afce8e
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 9 deletions.
1 change: 1 addition & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ PHP NEWS

- 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).
Expand Down
4 changes: 4 additions & 0 deletions ext/pdo/pdo_dbh.c
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,9 @@ void pdo_dbh_init(void)
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);
Expand Down Expand Up @@ -1462,6 +1465,7 @@ void pdo_dbh_init(void)
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);
Expand Down
13 changes: 11 additions & 2 deletions ext/pdo/php_pdo_driver.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64);
# define FALSE 0
#endif

#define PDO_DRIVER_API 20161020
#define PDO_DRIVER_API 20170320

enum pdo_param_type {
PDO_PARAM_NULL,
Expand Down Expand Up @@ -77,7 +77,15 @@ enum pdo_param_type {
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
Expand Down Expand Up @@ -140,6 +148,7 @@ enum pdo_attribute_type {
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
Expand Down
36 changes: 34 additions & 2 deletions ext/pdo_dblib/dblib_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,18 +151,37 @@ static zend_long dblib_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_l

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

*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++) {
Expand Down Expand Up @@ -256,12 +275,17 @@ char *dblib_handle_last_id(pdo_dbh_t *dbh, const char *name, size_t *len)

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;
Expand All @@ -270,14 +294,20 @@ static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)

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:
Expand Down Expand Up @@ -355,6 +385,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
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) {
Expand All @@ -376,6 +407,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
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);
}

Expand Down
1 change: 1 addition & 0 deletions ext/pdo_dblib/php_pdo_dblib_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ typedef struct {
DBPROCESS *link;

pdo_dblib_err err;
unsigned assume_national_character_set_strings:1;
unsigned stringify_uniqueidentifier:1;
} pdo_dblib_db_handle;

Expand Down
27 changes: 27 additions & 0 deletions ext/pdo_dblib/tests/pdo_dblib_param_str_natl.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--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
22 changes: 21 additions & 1 deletion ext/pdo_dblib/tests/pdo_dblib_quote.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,20 @@ var_dump($db->quote(42, PDO::PARAM_INT));
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'"
Expand All @@ -23,4 +36,11 @@ string(4) "'42'"
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)
47 changes: 43 additions & 4 deletions ext/pdo_mysql/mysql_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -300,12 +300,35 @@ static char *pdo_mysql_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t *
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);
Expand Down Expand Up @@ -369,7 +392,7 @@ static inline int mysql_handle_autocommit(pdo_dbh_t *dbh)
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);
Expand All @@ -382,18 +405,25 @@ static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
}
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) {
Expand Down Expand Up @@ -450,10 +480,15 @@ static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_
}
}
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;
Expand Down Expand Up @@ -597,6 +632,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
H->max_buffer_size = 1024*1024;
#endif

H->assume_national_character_set_strings = 0;
H->buffered = H->emulate_prepare = 1;

/* handle MySQL options */
Expand All @@ -616,6 +652,9 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_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
Expand Down
1 change: 1 addition & 0 deletions ext/pdo_mysql/php_pdo_mysql_int.h
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ typedef struct {
typedef struct {
MYSQL *server;

unsigned assume_national_character_set_strings:1;
unsigned attached:1;
unsigned buffered:1;
unsigned emulate_prepare:1;
Expand Down
44 changes: 44 additions & 0 deletions ext/pdo_mysql/tests/pdo_mysql_param_str_natl.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
--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
)
Loading

0 comments on commit 4afce8e

Please sign in to comment.