PDO::ATTR_EMULATE_PREPARES at the connection level (#1324)

This commit is contained in:
Jenny Tam 2021-11-02 08:12:09 -07:00 committed by GitHub
parent e3042e1ed4
commit 3826f1a522
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 247 additions and 47 deletions

View file

@ -542,7 +542,8 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo
fetch_datetime( false ),
format_decimals( false ),
decimal_places( NO_CHANGE_DECIMAL_PLACES ),
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED)
use_national_characters(CHARSET_PREFERENCE_NOT_SPECIFIED),
emulate_prepare(false)
{
if( client_buffer_max_size < 0 ) {
client_buffer_max_size = sqlsrv_buffered_result_set::BUFFERED_QUERY_LIMIT_DEFAULT;
@ -734,7 +735,8 @@ bool pdo_sqlsrv_dbh_prepare(_Inout_ pdo_dbh_t *dbh, _In_ zend_string *sql_zstr,
// assign the methods for the statement object. This is necessary even if the
// statement fails so the user can retrieve the error information.
stmt->methods = &pdo_sqlsrv_stmt_methods;
stmt->supports_placeholders = PDO_PLACEHOLDER_POSITIONAL; // we support parameterized queries with ?, not names
// if not emulate_prepare, we support parameterized queries with ?, not names
stmt->supports_placeholders = (driver_dbh->emulate_prepare) ? PDO_PLACEHOLDER_NONE : PDO_PLACEHOLDER_POSITIONAL; // the statement options may override this later
// Initialize the options array to be passed to the core layer
ALLOC_HASHTABLE( pdo_stmt_options_ht );
@ -1288,8 +1290,15 @@ bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_READ_ONLY_DBH_ATTR );
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
{
driver_dbh->emulate_prepare = zend_is_true(val);
if (driver_dbh->emulate_prepare && driver_dbh->ce_option.enabled) {
THROW_PDO_ERROR(driver_dbh, PDO_SQLSRV_ERROR_CE_EMULATE_PREPARE_UNSUPPORTED);
}
}
break;
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:
@ -1362,8 +1371,13 @@ int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_
#endif
}
// Statement level only
case PDO_ATTR_EMULATE_PREPARES:
{
ZVAL_BOOL(return_value, driver_dbh->emulate_prepare);
break;
}
// Statement level only
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_DATA_CLASSIFICATION:

View file

@ -194,6 +194,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn {
bool format_decimals;
short decimal_places;
short use_national_characters;
bool emulate_prepare;
pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ void* driver );
};

View file

@ -1,49 +1,44 @@
--TEST--
Tests error returned when binding output parameter with emulate prepare
--DESCRIPTION--
The test shows that the option sets in prepared statements overrides the
connection setting of PDO::ATTR_EMULATE_PREPARES
--SKIPIF--
<?php require_once('skipif_mid-refactor.inc'); ?>
<?php require('skipif_azure_dw.inc'); ?>
--FILE--
<?php
require_once("MsCommon_mid-refactor.inc");
require_once("MsSetup.inc");
try {
$conn = connect();
// Do not connect with AE enabled because otherwise this would have thrown a different exception
$conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
$count = 0;
$query = "select ? = count(* ) from cd_info";
$stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
$stmt->bindParam(1, $count, PDO::PARAM_STR, 10);
$stmt->execute();
echo "Result: ".$count."\n";
$query = "select bigint_type, int_type, money_type from [test_types] where int_type < 0";
$stmt1 = $conn->prepare($query);
$stmt1->execute();
$row = $stmt1->fetch(PDO::FETCH_ASSOC);
print_r($row);
$int = 0;
$bigint = 100;
$query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0";
$stmt2 = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
$stmt2->bindparam(1, $bigint, PDO::PARAM_STR, 256);
$stmt2->bindParam(2, $int, PDO::PARAM_INT, 4);
$stmt2->bindParam(3, $money, PDO::PARAM_STR, 1024);
$stmt2->execute();
echo "Big integer: ".$bigint."\n";
echo "Integer: ".$int."\n";
echo "Money: ".$money."\n";
//free the statement and connection
unset($stmt);
unset($stmt1);
unset($stmt2);
unset($conn);
$stmt = $conn->prepare($query);
} catch (PDOException $e) {
print("Error: " . $e->getMessage() . "\n");
}
try {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$int = 0;
$bigint = 100;
$query = "select ? = bigint_type, ? = int_type, ? = money_type from [test_types] where int_type < 0";
$stmt = $conn->prepare($query, array(PDO::ATTR_EMULATE_PREPARES => true));
} catch (PDOException $e) {
print("Error: " . $e->getMessage() . "\n");
}
// free the statement and connection
unset($stmt);
unset($conn);
?>
--EXPECT--
Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters.
Error: SQLSTATE[IMSSP]: Statement with emulate prepare on does not support output or input_output parameters.

View file

@ -2,6 +2,7 @@
GitHub issue 1310 - bind null field as varchar(max) if not binary
--DESCRIPTION--
The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102.
Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries.
--ENV--
PHPT_EXEC=true
--SKIPIF--
@ -9,10 +10,9 @@ PHPT_EXEC=true
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
try {
$conn = connect();
$conn = new PDO("sqlsrv:server=$server; Database = $databaseName;", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// Issue 1310

View file

@ -0,0 +1,62 @@
--TEST--
GitHub issue 1320 - support PDO::ATTR_EMULATE_PREPARES at the connection level
--DESCRIPTION--
Supports PDO::ATTR_EMULATE_PREPARES at the connection level but setting it to true with column
encryption enabled will fail with an exception. Also, the options in the prepared statement will
override the connection setting.
--ENV--
PHPT_EXEC=true
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once("MsSetup.inc");
require_once("MsCommon_mid-refactor.inc");
try {
// Connection with column encryption enabled
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
echo "setAttribute should have failed because column encryption is enabled.\n\n";
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
unset($conn);
try {
// Connection with column encryption enabled
$options = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_EMULATE_PREPARES => true);
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd, $options);
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
unset($conn);
try {
// Connection with column encryption enabled - PDO::ATTR_EMULATE_PREPARES is false by default
$connectionInfo = "ColumnEncryption = Enabled;";
$conn = new PDO("sqlsrv:server = $server; database=$databaseName; $connectionInfo", $uid, $pwd);
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "Connected successfully with column encryption enabled.\n";
$enabled = $conn->getAttribute(PDO::ATTR_EMULATE_PREPARES);
echo "By default, the emulation of prepared statements is:\n";
var_dump($enabled);
} catch (PDOException $e) {
echo $e->getMessage() . "\n";
}
?>
--EXPECT--
SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.
SQLSTATE[IMSSP]: Parameterized statement with attribute PDO::ATTR_EMULATE_PREPARES is not supported in a Column Encryption enabled Connection.
Connected successfully with column encryption enabled.
By default, the emulation of prepared statements is:
bool(false)

View file

@ -0,0 +1,137 @@
--TEST--
Prepare with emulate prepare and binding uft8 characters
--DESCRIPTION--
This is the same as pdo_prepare_emulatePrepare_unicode.phpt except that
PDO::ATTR_EMULATE_PREPARES at the connection level
--SKIPIF--
<?php require('skipif_mid-refactor.inc'); ?>
--FILE--
<?php
require_once('MsCommon_mid-refactor.inc');
function prepareStmt($conn, $query, $prepareOptions = array(), $dataType = PDO::PARAM_STR, $length = 0, $driverOptions = null)
{
$name = "가각";
if (!isColEncrypted()) {
$stmt = $conn->prepare($query, $prepareOptions);
$stmt->bindParam(':name', $name, $dataType, $length, $driverOptions);
} else {
$status = 1;
$stmt = $conn->prepare($query, $prepareOptions);
$stmt->bindParam(':name', $name, $dataType, $length, $driverOptions);
$stmt->bindParam(':status', $status);
}
$stmt->execute();
return $stmt;
}
try {
$conn = connect("", array(), PDO::ERRMODE_SILENT);
$tableName = "users";
createTable($conn, $tableName, array("name" => "nvarchar(max)", "status" => "int", "age" => "int"));
if (!isColEncrypted()) {
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Belle', 1, 34)");
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'Абрам', 1, 40)");
$conn->exec("INSERT INTO [$tableName] (name, status, age) VALUES (N'가각', 1, 30)");
$query = "SELECT * FROM [$tableName] WHERE name = :name AND status = 1";
} else {
insertRow($conn, $tableName, array("name" => "Belle", "status" => 1, "age" => 34));
insertRow($conn, $tableName, array("name" => "Абрам", "status" => 1, "age" => 40));
insertRow($conn, $tableName, array("name" => "가각", "status" => 1, "age" => 30));
$query = "SELECT * FROM [$tableName] WHERE name = :name AND status = :status";
}
//without emulate prepare
print_r("Prepare without emulate prepare:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
if (!isAEConnected()) {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
} else {
$conn->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
}
//with emulate prepare and no bind param options
print_r("Prepare with emulate prepare and no bindParam options:\n");
// This test only makes sense without AE because the default encoding is PDO::SQLSRV_ENCODING_UTF8
if (!isAEConnected()) {
$stmt = prepareStmt($conn, $query, array());
$row = $stmt->fetch(PDO::FETCH_ASSOC);
if ($stmt->rowCount() != 0) {
print_r("Do not expect results for this query!\n");
print_r($row);
}
}
//with emulate prepare and SQLSRV_ENCODING_UTF8
print_r("Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_UTF8);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
//with emulate prepare and SQLSRV_ENCODING_SYSTEM
print_r("Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_SYSTEM);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
// The combination of Column Encryption and Unix platforms support SQLSRV_ENCODING_SYSTEM because:
// With Column Encryption enabled, binding parameters uses exact datatypes as the column definition
// the default encoding in Linux and Mac is UTF8
$success = true;
if (!(strtoupper( substr( php_uname( 's' ),0,3 ) ) === 'WIN') && isAEConnected()) {
if ($row['name'] != "가각" || $row['status'] != 1 || $row['age'] != 30) {
print_r("Incorrect results retrieved.\n");
$success = false;
}
} else {
// the default encoding in Windows is non-UTF8, thus binding UTF8 parameters does not work
if ($stmt->rowCount() != 0) {
print_r("Binding UTF8 data when encoding is SQLSRV_ENCODING_SYSTEM should not work.\n");
$success = false;
}
}
if ($success) {
print_r("Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully.\n");
}
//with emulate prepare and encoding SQLSRV_ENCODING_BINARY
print_r("Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY:\n");
$stmt = prepareStmt($conn, $query, array(), PDO::PARAM_STR, 0, PDO::SQLSRV_ENCODING_BINARY);
$row = $stmt->fetch(PDO::FETCH_ASSOC);
print_r($row);
if ($stmt->rowCount() == 0) {
print_r("No results for this query\n");
}
dropTable($conn, $tableName);
unset($stmt);
unset($conn);
} catch (PDOException $e) {
var_dump($e->errorInfo);
}
?>
--EXPECT--
Prepare without emulate prepare:
Array
(
[name] => 가각
[status] => 1
[age] => 30
)
Prepare with emulate prepare and no bindParam options:
Prepare with emulate prepare and SQLSRV_ENCODING_UTF8:
Array
(
[name] => 가각
[status] => 1
[age] => 30
)
Prepare with emulate prepare and and SQLSRV_ENCODING_SYSTEM:
Binding UTF8 data with SQLSRV_ENCODING_SYSTEM is tested successfully.
Prepare with emulate prepare and encoding SQLSRV_ENCODING_BINARY:
No results for this query

View file

@ -17,15 +17,6 @@ try {
$tableName = "pdo_test_table";
createTable($conn1, $tableName, array(new ColumnMeta("int", "ID", "NOT NULL PRIMARY KEY"), "Policy" => "varchar(2)", "Label" => "varchar(10)", "Budget" => "money"));
try {
$res = $conn1->setAttribute(PDO::ATTR_EMULATE_PREPARES, true);
if ($res) {
echo "setAttribute should have failed.\n\n";
}
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
try {
$query = "SELECT * FROM [$tableName]";
$stmt = $conn1->query($query);
@ -186,7 +177,6 @@ try {
?>
--EXPECTF--
SQLSTATE[IMSSP]: The given attribute is only supported on the PDOStatement object.
SQLSTATE[IMSSP]: An invalid attribute was designated on the PDOStatement object.
Start inserting data...
....Done....

View file

@ -2,6 +2,7 @@
GitHub issue 1310 - bind null field as varchar(max) if not binary
--DESCRIPTION--
The test shows null fields are no longer bound as char(1) if not binary such that it solves both issues 1310 and 1102.
Note that this test does not connect with AE enabled because SQLDescribeParam() does not work with these queries.
--ENV--
PHPT_EXEC=true
--SKIPIF--
@ -10,7 +11,7 @@ PHPT_EXEC=true
<?php
require_once('MsCommon.inc');
$conn = AE\connect();
$conn = connect();
// Issue 1310
$query = "SELECT CAST(ISNULL(?, -1) AS INT) AS K";