Skip to content

Commit 4afce8e

Browse files
committed
Add PDO parameter types for national character set strings
1 parent 3817cba commit 4afce8e

11 files changed

+221
-9
lines changed

NEWS

+1
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ PHP NEWS
112112

113113
- PDO:
114114
. Add "Sent SQL" to debug dump for emulated prepares. (Adam Baratz)
115+
. Add parameter types for national character set strings. (Adam Baratz)
115116

116117
- PDO_DBlib:
117118
. Fixed bug #73234 (Emulated statements let value dictate parameter type).

ext/pdo/pdo_dbh.c

+4
Original file line numberDiff line numberDiff line change
@@ -1413,6 +1413,9 @@ void pdo_dbh_init(void)
14131413
REGISTER_PDO_CLASS_CONST_LONG("PARAM_LOB", (zend_long)PDO_PARAM_LOB);
14141414
REGISTER_PDO_CLASS_CONST_LONG("PARAM_STMT", (zend_long)PDO_PARAM_STMT);
14151415
REGISTER_PDO_CLASS_CONST_LONG("PARAM_INPUT_OUTPUT", (zend_long)PDO_PARAM_INPUT_OUTPUT);
1416+
REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_NATL", (zend_long)PDO_PARAM_STR_NATL);
1417+
REGISTER_PDO_CLASS_CONST_LONG("PARAM_STR_CHAR", (zend_long)PDO_PARAM_STR_CHAR);
1418+
14161419

14171420
REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_ALLOC", (zend_long)PDO_PARAM_EVT_ALLOC);
14181421
REGISTER_PDO_CLASS_CONST_LONG("PARAM_EVT_FREE", (zend_long)PDO_PARAM_EVT_FREE);
@@ -1462,6 +1465,7 @@ void pdo_dbh_init(void)
14621465
REGISTER_PDO_CLASS_CONST_LONG("ATTR_MAX_COLUMN_LEN", (zend_long)PDO_ATTR_MAX_COLUMN_LEN);
14631466
REGISTER_PDO_CLASS_CONST_LONG("ATTR_EMULATE_PREPARES", (zend_long)PDO_ATTR_EMULATE_PREPARES);
14641467
REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_FETCH_MODE", (zend_long)PDO_ATTR_DEFAULT_FETCH_MODE);
1468+
REGISTER_PDO_CLASS_CONST_LONG("ATTR_DEFAULT_STR_PARAM", (zend_long)PDO_ATTR_DEFAULT_STR_PARAM);
14651469

14661470
REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_SILENT", (zend_long)PDO_ERRMODE_SILENT);
14671471
REGISTER_PDO_CLASS_CONST_LONG("ERRMODE_WARNING", (zend_long)PDO_ERRMODE_WARNING);

ext/pdo/php_pdo_driver.h

+11-2
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ PDO_API char *php_pdo_int64_to_str(pdo_int64_t i64);
4646
# define FALSE 0
4747
#endif
4848

49-
#define PDO_DRIVER_API 20161020
49+
#define PDO_DRIVER_API 20170320
5050

5151
enum pdo_param_type {
5252
PDO_PARAM_NULL,
@@ -77,7 +77,15 @@ enum pdo_param_type {
7777
PDO_PARAM_ZVAL,
7878

7979
/* magic flag to denote a parameter as being input/output */
80-
PDO_PARAM_INPUT_OUTPUT = 0x80000000
80+
PDO_PARAM_INPUT_OUTPUT = 0x80000000,
81+
82+
/* magic flag to denote a string that uses the national character set
83+
see section 4.2.1 of SQL-92: http://www.contrib.andrew.cmu.edu/~shadow/sql/sql1992.txt
84+
*/
85+
PDO_PARAM_STR_NATL = 0x40000000,
86+
87+
/* magic flag to denote a string that uses the regular character set */
88+
PDO_PARAM_STR_CHAR = 0x20000000,
8189
};
8290

8391
#define PDO_PARAM_FLAGS 0xFFFF0000
@@ -140,6 +148,7 @@ enum pdo_attribute_type {
140148
PDO_ATTR_MAX_COLUMN_LEN, /* make database calculate maximum length of data found in a column */
141149
PDO_ATTR_DEFAULT_FETCH_MODE, /* Set the default fetch mode */
142150
PDO_ATTR_EMULATE_PREPARES, /* use query emulation rather than native */
151+
PDO_ATTR_DEFAULT_STR_PARAM, /* set the default string parameter type (see the PDO::PARAM_STR_* magic flags) */
143152

144153
/* this defines the start of the range for driver specific options.
145154
* Drivers should define their own attribute constants beginning with this

ext/pdo_dblib/dblib_driver.c

+34-2
Original file line numberDiff line numberDiff line change
@@ -151,18 +151,37 @@ static zend_long dblib_handle_doer(pdo_dbh_t *dbh, const char *sql, size_t sql_l
151151

152152
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)
153153
{
154+
pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
155+
zend_bool use_national_character_set = 0;
156+
154157
size_t i;
155158
char * q;
156159
*quotedlen = 0;
157160

161+
if (H->assume_national_character_set_strings) {
162+
use_national_character_set = 1;
163+
}
164+
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
165+
use_national_character_set = 1;
166+
}
167+
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
168+
use_national_character_set = 0;
169+
}
170+
158171
/* Detect quoted length, adding extra char for doubled single quotes */
159172
for (i = 0; i < unquotedlen; i++) {
160173
if (unquoted[i] == '\'') ++*quotedlen;
161174
++*quotedlen;
162175
}
163176

164177
*quotedlen += 2; /* +2 for opening, closing quotes */
178+
if (use_national_character_set) {
179+
++*quotedlen; /* N prefix */
180+
}
165181
q = *quoted = emalloc(*quotedlen + 1); /* Add byte for terminal null */
182+
if (use_national_character_set) {
183+
*q++ = 'N';
184+
}
166185
*q++ = '\'';
167186

168187
for (i = 0; i < unquotedlen; i++) {
@@ -256,12 +275,17 @@ char *dblib_handle_last_id(pdo_dbh_t *dbh, const char *name, size_t *len)
256275

257276
static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
258277
{
278+
pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
279+
259280
switch(attr) {
281+
case PDO_ATTR_DEFAULT_STR_PARAM:
282+
H->assume_national_character_set_strings = zval_get_long(val) == PDO_PARAM_STR_NATL ? 1 : 0;
283+
return 1;
260284
case PDO_ATTR_TIMEOUT:
261285
case PDO_DBLIB_ATTR_QUERY_TIMEOUT:
262286
return SUCCEED == dbsettime(zval_get_long(val)) ? 1 : 0;
263287
case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER:
264-
((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier = zval_get_long(val);
288+
H->stringify_uniqueidentifier = zval_get_long(val);
265289
return 1;
266290
default:
267291
return 0;
@@ -270,14 +294,20 @@ static int dblib_set_attr(pdo_dbh_t *dbh, zend_long attr, zval *val)
270294

271295
static int dblib_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_value)
272296
{
297+
pdo_dblib_db_handle *H = (pdo_dblib_db_handle *)dbh->driver_data;
298+
273299
switch (attr) {
300+
case PDO_ATTR_DEFAULT_STR_PARAM:
301+
ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR);
302+
break;
303+
274304
case PDO_ATTR_EMULATE_PREPARES:
275305
/* this is the only option available, but expose it so common tests and whatever else can introspect */
276306
ZVAL_TRUE(return_value);
277307
break;
278308

279309
case PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER:
280-
ZVAL_BOOL(return_value, ((pdo_dblib_db_handle *)dbh->driver_data)->stringify_uniqueidentifier);
310+
ZVAL_BOOL(return_value, H->stringify_uniqueidentifier);
281311
break;
282312

283313
case PDO_DBLIB_ATTR_VERSION:
@@ -355,6 +385,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
355385
H = pecalloc(1, sizeof(*H), dbh->is_persistent);
356386
H->login = dblogin();
357387
H->err.sqlstate = dbh->error_code;
388+
H->assume_national_character_set_strings = 0;
358389
H->stringify_uniqueidentifier = 0;
359390

360391
if (!H->login) {
@@ -376,6 +407,7 @@ static int pdo_dblib_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
376407
dbsetlogintime(connect_timeout); /* Connection/Login Timeout */
377408
dbsettime(query_timeout); /* Statement Timeout */
378409

410+
H->assume_national_character_set_strings = pdo_attr_lval(driver_options, PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0;
379411
H->stringify_uniqueidentifier = pdo_attr_lval(driver_options, PDO_DBLIB_ATTR_STRINGIFY_UNIQUEIDENTIFIER, 0);
380412
}
381413

ext/pdo_dblib/php_pdo_dblib_int.h

+1
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ typedef struct {
116116
DBPROCESS *link;
117117

118118
pdo_dblib_err err;
119+
unsigned assume_national_character_set_strings:1;
119120
unsigned stringify_uniqueidentifier:1;
120121
} pdo_dblib_db_handle;
121122

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
PDO_DBLIB: national character set values are quoted correctly in queries
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo_dblib')) die('skip not loaded');
6+
require dirname(__FILE__) . '/config.inc';
7+
?>
8+
--FILE--
9+
<?php
10+
require dirname(__FILE__) . '/config.inc';
11+
12+
$stmt = $db->prepare('SELECT :value');
13+
$stmt->bindValue(':value', 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL);
14+
$stmt->execute();
15+
16+
var_dump($stmt->debugDumpParams());
17+
?>
18+
--EXPECT--
19+
SQL: [13] SELECT :value
20+
Sent SQL: [13] SELECT N'foo'
21+
Params: 1
22+
Key: Name: [6] :value
23+
paramno=-1
24+
name=[6] ":value"
25+
is_param=1
26+
param_type=1073741826
27+
NULL

ext/pdo_dblib/tests/pdo_dblib_quote.phpt

+21-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,20 @@ var_dump($db->quote(42, PDO::PARAM_INT));
1414
var_dump($db->quote(null, PDO::PARAM_NULL));
1515
var_dump($db->quote('\'', PDO::PARAM_STR));
1616
var_dump($db->quote('foo', PDO::PARAM_STR));
17+
var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
18+
var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
19+
20+
var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_CHAR);
21+
$db->setAttribute(PDO::ATTR_DEFAULT_STR_PARAM, PDO::PARAM_STR_NATL);
22+
var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
23+
24+
var_dump($db->quote('foo', PDO::PARAM_STR | PDO::PARAM_STR_CHAR));
1725
var_dump($db->quote('über', PDO::PARAM_STR));
26+
var_dump($db->quote('über', PDO::PARAM_STR | PDO::PARAM_STR_NATL));
27+
28+
$db = new PDO($dsn, $user, $pass, [PDO::ATTR_DEFAULT_STR_PARAM => PDO::PARAM_STR_NATL]);
29+
var_dump($db->getAttribute(PDO::ATTR_DEFAULT_STR_PARAM) === PDO::PARAM_STR_NATL);
30+
1831
?>
1932
--EXPECT--
2033
string(3) "'1'"
@@ -23,4 +36,11 @@ string(4) "'42'"
2336
string(2) "''"
2437
string(4) "''''"
2538
string(5) "'foo'"
26-
string(7) "'über'"
39+
string(5) "'foo'"
40+
string(8) "N'über'"
41+
bool(true)
42+
bool(true)
43+
string(5) "'foo'"
44+
string(8) "N'über'"
45+
string(8) "N'über'"
46+
bool(true)

ext/pdo_mysql/mysql_driver.c

+43-4
Original file line numberDiff line numberDiff line change
@@ -300,12 +300,35 @@ static char *pdo_mysql_last_insert_id(pdo_dbh_t *dbh, const char *name, size_t *
300300
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 )
301301
{
302302
pdo_mysql_db_handle *H = (pdo_mysql_db_handle *)dbh->driver_data;
303+
zend_bool use_national_character_set = 0;
304+
305+
if (H->assume_national_character_set_strings) {
306+
use_national_character_set = 1;
307+
}
308+
if ((paramtype & PDO_PARAM_STR_NATL) == PDO_PARAM_STR_NATL) {
309+
use_national_character_set = 1;
310+
}
311+
if ((paramtype & PDO_PARAM_STR_CHAR) == PDO_PARAM_STR_CHAR) {
312+
use_national_character_set = 0;
313+
}
314+
303315
PDO_DBG_ENTER("mysql_handle_quoter");
304316
PDO_DBG_INF_FMT("dbh=%p", dbh);
305317
PDO_DBG_INF_FMT("unquoted=%.*s", (int)unquotedlen, unquoted);
306-
*quoted = safe_emalloc(2, unquotedlen, 3);
307-
*quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen);
308-
(*quoted)[0] =(*quoted)[++*quotedlen] = '\'';
318+
*quoted = safe_emalloc(2, unquotedlen, 3 + (use_national_character_set ? 1 : 0));
319+
320+
if (use_national_character_set) {
321+
*quotedlen = mysql_real_escape_string(H->server, *quoted + 2, unquoted, unquotedlen);
322+
(*quoted)[0] = 'N';
323+
(*quoted)[1] = '\'';
324+
325+
++*quotedlen; /* N prefix */
326+
} else {
327+
*quotedlen = mysql_real_escape_string(H->server, *quoted + 1, unquoted, unquotedlen);
328+
(*quoted)[0] = '\'';
329+
}
330+
331+
(*quoted)[++*quotedlen] = '\'';
309332
(*quoted)[++*quotedlen] = '\0';
310333
PDO_DBG_INF_FMT("quoted=%.*s", (int)*quotedlen, *quoted);
311334
PDO_DBG_RETURN(1);
@@ -369,7 +392,7 @@ static inline int mysql_handle_autocommit(pdo_dbh_t *dbh)
369392
static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
370393
{
371394
zend_long lval = zval_get_long(val);
372-
zend_bool bval = lval? 1 : 0;
395+
zend_bool bval = lval ? 1 : 0;
373396
PDO_DBG_ENTER("pdo_mysql_set_attribute");
374397
PDO_DBG_INF_FMT("dbh=%p", dbh);
375398
PDO_DBG_INF_FMT("attr=%l", attr);
@@ -382,18 +405,25 @@ static int pdo_mysql_set_attribute(pdo_dbh_t *dbh, zend_long attr, zval *val)
382405
}
383406
PDO_DBG_RETURN(1);
384407

408+
case PDO_ATTR_DEFAULT_STR_PARAM:
409+
((pdo_mysql_db_handle *)dbh->driver_data)->assume_national_character_set_strings = lval == PDO_PARAM_STR_NATL ? 1 : 0;
410+
PDO_DBG_RETURN(1);
411+
385412
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
386413
/* ignore if the new value equals the old one */
387414
((pdo_mysql_db_handle *)dbh->driver_data)->buffered = bval;
388415
PDO_DBG_RETURN(1);
416+
389417
case PDO_MYSQL_ATTR_DIRECT_QUERY:
390418
case PDO_ATTR_EMULATE_PREPARES:
391419
/* ignore if the new value equals the old one */
392420
((pdo_mysql_db_handle *)dbh->driver_data)->emulate_prepare = bval;
393421
PDO_DBG_RETURN(1);
422+
394423
case PDO_ATTR_FETCH_TABLE_NAMES:
395424
((pdo_mysql_db_handle *)dbh->driver_data)->fetch_table_names = bval;
396425
PDO_DBG_RETURN(1);
426+
397427
#ifndef PDO_USE_MYSQLND
398428
case PDO_MYSQL_ATTR_MAX_BUFFER_SIZE:
399429
if (lval < 0) {
@@ -450,10 +480,15 @@ static int pdo_mysql_get_attribute(pdo_dbh_t *dbh, zend_long attr, zval *return_
450480
}
451481
}
452482
break;
483+
453484
case PDO_ATTR_AUTOCOMMIT:
454485
ZVAL_LONG(return_value, dbh->auto_commit);
455486
break;
456487

488+
case PDO_ATTR_DEFAULT_STR_PARAM:
489+
ZVAL_LONG(return_value, H->assume_national_character_set_strings ? PDO_PARAM_STR_NATL : PDO_PARAM_STR_CHAR);
490+
break;
491+
457492
case PDO_MYSQL_ATTR_USE_BUFFERED_QUERY:
458493
ZVAL_LONG(return_value, H->buffered);
459494
break;
@@ -597,6 +632,7 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
597632
H->max_buffer_size = 1024*1024;
598633
#endif
599634

635+
H->assume_national_character_set_strings = 0;
600636
H->buffered = H->emulate_prepare = 1;
601637

602638
/* handle MySQL options */
@@ -616,6 +652,9 @@ static int pdo_mysql_handle_factory(pdo_dbh_t *dbh, zval *driver_options)
616652
H->emulate_prepare = pdo_attr_lval(driver_options,
617653
PDO_ATTR_EMULATE_PREPARES, H->emulate_prepare);
618654

655+
H->assume_national_character_set_strings = pdo_attr_lval(driver_options,
656+
PDO_ATTR_DEFAULT_STR_PARAM, 0) == PDO_PARAM_STR_NATL ? 1 : 0;
657+
619658
#ifndef PDO_USE_MYSQLND
620659
H->max_buffer_size = pdo_attr_lval(driver_options, PDO_MYSQL_ATTR_MAX_BUFFER_SIZE, H->max_buffer_size);
621660
#endif

ext/pdo_mysql/php_pdo_mysql_int.h

+1
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ typedef struct {
104104
typedef struct {
105105
MYSQL *server;
106106

107+
unsigned assume_national_character_set_strings:1;
107108
unsigned attached:1;
108109
unsigned buffered:1;
109110
unsigned emulate_prepare:1;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
PDO MySQL national character set parameters don't affect true prepared statements
3+
--SKIPIF--
4+
<?php
5+
if (!extension_loaded('pdo') || !extension_loaded('pdo_mysql')) die('skip not loaded');
6+
require dirname(__FILE__) . '/config.inc';
7+
require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
8+
PDOTest::skip();
9+
?>
10+
--FILE--
11+
<?php
12+
require dirname(__FILE__) . '/config.inc';
13+
require dirname(__FILE__) . '/../../../ext/pdo/tests/pdo_test.inc';
14+
$db = PDOTest::test_factory(dirname(__FILE__) . '/common.phpt');
15+
16+
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, 0);
17+
18+
$db->exec('CREATE TABLE test (bar NCHAR(4) NOT NULL)');
19+
20+
$stmt = $db->prepare('INSERT INTO test VALUES(?)');
21+
$stmt->bindValue(1, 'foo', PDO::PARAM_STR | PDO::PARAM_STR_NATL);
22+
$stmt->execute();
23+
24+
var_dump($db->query('SELECT * from test'));
25+
foreach ($db->query('SELECT * from test') as $row) {
26+
print_r($row);
27+
}
28+
29+
?>
30+
--CLEAN--
31+
<?php
32+
require dirname(__FILE__) . '/mysql_pdo_test.inc';
33+
MySQLPDOTest::dropTestTable();
34+
?>
35+
--EXPECTF--
36+
object(PDOStatement)#%d (1) {
37+
["queryString"]=>
38+
string(18) "SELECT * from test"
39+
}
40+
Array
41+
(
42+
[bar] => foo
43+
[0] => foo
44+
)

0 commit comments

Comments
 (0)