From 284aca85ce4528fb48c79e82ae9e1d05a6501a01 Mon Sep 17 00:00:00 2001 From: Jenny Tam Date: Fri, 7 Aug 2020 18:33:12 -0700 Subject: [PATCH] Updated checks to distinguish smalldatetime, datetime and datetime2 fields (#1175) --- source/shared/core_stmt.cpp | 35 +++++++++++++++---- ...indColumn_pdoparam_datetime_precision.phpt | 14 +++++++- .../pdo_ae_insert_datetime_encrypted.phpt | 15 +++++--- ...ae_insert_pdoparam_datetime_precision.phpt | 20 ++++++++++- .../sqlsrv_ae_insert_datetime_encrypted.phpt | 20 ++++++----- 5 files changed, 82 insertions(+), 22 deletions(-) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index f88f6fc7..1b2ec22e 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -746,19 +746,40 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_ ind_ptr = SQL_NULL_DATA; } - core::SQLBindParameter( stmt, param_num + 1, direction, - c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); - // When calling SQLDescribeParam() on a parameter targeting a Datetime column, the return values for ParameterType, ColumnSize and DecimalDigits are SQL_TYPE_TIMESTAMP, 23, and 3 respectively. // For a parameter targeting a SmallDatetime column, the return values are SQL_TYPE_TIMESTAMP, 16, and 0. Inputting these values into SQLBindParameter() results in Operand type clash error. // This is because SQL_TYPE_TIMESTAMP corresponds to Datetime2 by default, and conversion of Datetime2 to Datetime and conversion of Datetime2 to SmallDatatime is not allowed with encrypted columns. // To fix the conversion problem, set the SQL_CA_SS_SERVER_TYPE field of the parameter to SQL_SS_TYPE_DATETIME and SQL_SS_TYPE_SMALLDATETIME respectively for a Datetime and Smalldatetime column. + // Check SQL_DESC_OCTET_LENGTH of the implementation parameter descriptor (IPD) to distinguish Smalldatetime or Datetime fields from Datetime2(0) or Datetime2(3) fields, as described in + // https://docs.microsoft.com/sql/relational-databases/native-client-odbc-date-time/metadata-parameter-and-result + // This has to be done before SQLBindParameter() if (stmt->conn->ce_option.enabled && sql_type == SQL_TYPE_TIMESTAMP) { - if (decimal_digits == 3) - core::SQLSetDescField(stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); - else if (decimal_digits == 0) - core::SQLSetDescField( stmt, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER ); + if (decimal_digits == 0 || decimal_digits == 3) { + SQLHDESC hIpd = NULL; + core::SQLGetStmtAttr(stmt, SQL_ATTR_IMP_PARAM_DESC, &hIpd, 0, 0); + if (hIpd != NULL) { + SQLULEN octetLength = 0; + SQLINTEGER dummy = 0; + + SQLRETURN r = ::SQLGetDescField(hIpd, param_num + 1, SQL_DESC_OCTET_LENGTH, (SQLPOINTER)&octetLength, 0, &dummy); + if (SQL_SUCCEEDED(r)) { + // The octet length for datetime2 is 16 but no action required + if (octetLength == 8) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_DATETIME, SQL_IS_INTEGER); + } else if (octetLength == 4) { + r = ::SQLSetDescField(hIpd, param_num + 1, SQL_CA_SS_SERVER_TYPE, (SQLPOINTER)SQL_SS_TYPE_SMALLDATETIME, SQL_IS_INTEGER); + } + } + CHECK_SQL_ERROR_OR_WARNING(r, stmt) { + throw core::CoreException(); + } + } + } } + + core::SQLBindParameter( stmt, param_num + 1, direction, + c_type, sql_type, column_size, decimal_digits, buffer, buffer_len, &ind_ptr ); + } catch( core::CoreException& e ){ stmt->free_param_data(); diff --git a/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt b/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt index 9a68d7a9..cb92c950 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_bindColumn_pdoparam_datetime_precision.phpt @@ -31,7 +31,7 @@ function compareDate($dtout, $dtin, $dataType) { } $dataTypes = array("datetime2", "datetimeoffset", "time"); -$precisions = array(/*0,*/ 1, 2, 4, 7); +$precisions = array(0, 1, 2, 4, 7); $inputValuesInit = array("datetime2" => array("0001-01-01 00:00:00", "9999-12-31 23:59:59"), "datetimeoffset" => array("0001-01-01 00:00:00 -14:00", "9999-12-31 23:59:59 +14:00"), "time" => array("00:00:00", "23:59:59")); @@ -101,6 +101,10 @@ try { } ?> --EXPECT-- +Testing datetime2(0): +****Retrieving datetime2(0) as PDO::PARAM_STR is supported**** +****Retrieving datetime2(0) as PDO::PARAM_LOB is supported**** + Testing datetime2(1): ****Retrieving datetime2(1) as PDO::PARAM_STR is supported**** ****Retrieving datetime2(1) as PDO::PARAM_LOB is supported**** @@ -117,6 +121,10 @@ Testing datetime2(7): ****Retrieving datetime2(7) as PDO::PARAM_STR is supported**** ****Retrieving datetime2(7) as PDO::PARAM_LOB is supported**** +Testing datetimeoffset(0): +****Retrieving datetimeoffset(0) as PDO::PARAM_STR is supported**** +****Retrieving datetimeoffset(0) as PDO::PARAM_LOB is supported**** + Testing datetimeoffset(1): ****Retrieving datetimeoffset(1) as PDO::PARAM_STR is supported**** ****Retrieving datetimeoffset(1) as PDO::PARAM_LOB is supported**** @@ -133,6 +141,10 @@ Testing datetimeoffset(7): ****Retrieving datetimeoffset(7) as PDO::PARAM_STR is supported**** ****Retrieving datetimeoffset(7) as PDO::PARAM_LOB is supported**** +Testing time(0): +****Retrieving time(0) as PDO::PARAM_STR is supported**** +****Retrieving time(0) as PDO::PARAM_LOB is supported**** + Testing time(1): ****Retrieving time(1) as PDO::PARAM_STR is supported**** ****Retrieving time(1) as PDO::PARAM_LOB is supported**** diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt index 5d84a1d6..1e1a9157 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_datetime_encrypted.phpt @@ -63,7 +63,7 @@ try { dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -75,14 +75,16 @@ try { // Insert values that cause errors $val1 = '9999-12-31 23:59:59'; $val2 = null; - $val3 = '9999-12-31 23:59:59.9999'; + $val3 = null; + $val4 = '9999-12-31 23:59:59.999'; - $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; + $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; $stmt = $conn->prepare($tsql); $stmt->bindParam(1, $val1); $stmt->bindParam(2, $val2); $stmt->bindParam(3, $val3); + $stmt->bindParam(4, $val4); try { $stmt->execute(); @@ -97,6 +99,7 @@ try { // These values should work $val1 = '2021-11-03 11:49:00'; $val2 = '2015-10-23 07:03:00.000'; + $val3 = '0001-01-01 01:01:01'; try { $stmt->execute(); @@ -128,12 +131,14 @@ echo "Done\n"; ?> --EXPECT-- -array(3) { +array(4) { [0]=> string(19) "2021-11-03 11:49:00" [1]=> string(23) "2015-10-23 07:03:00.000" [2]=> - string(24) "9999-12-31 23:59:59.9999" + string(19) "0001-01-01 01:01:01" + [3]=> + string(23) "9999-12-31 23:59:59.999" } Done \ No newline at end of file diff --git a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt index a3db5c7f..0d7cd155 100644 --- a/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt +++ b/test/functional/pdo_sqlsrv/pdo_ae_insert_pdoparam_datetime_precision.phpt @@ -29,7 +29,7 @@ function compareDate($dtout, $dtin, $dataType) { } $dataTypes = array("datetime2", "datetimeoffset", "time"); -$precisions = array(/*0,*/ 1, 2, 4, 7); +$precisions = array(0, 1, 2, 4, 7); $inputValuesInit = array("datetime2" => array("0001-01-01 00:00:00", "9999-12-31 23:59:59"), "datetimeoffset" => array("0001-01-01 00:00:00 -14:00", "9999-12-31 23:59:59 +14:00"), "time" => array("00:00:00", "23:59:59")); @@ -102,6 +102,12 @@ try { } ?> --EXPECT-- +Testing datetime2(0): +****Conversion from PDO::PARAM_BOOL to datetime2(0) is supported**** +****Conversion from PDO::PARAM_INT to datetime2(0) is supported**** +****Conversion from PDO::PARAM_STR to datetime2(0) is supported**** +****Conversion from PDO::PARAM_LOB to datetime2(0) is supported**** + Testing datetime2(1): ****Conversion from PDO::PARAM_BOOL to datetime2(1) is supported**** ****Conversion from PDO::PARAM_INT to datetime2(1) is supported**** @@ -126,6 +132,12 @@ Testing datetime2(7): ****Conversion from PDO::PARAM_STR to datetime2(7) is supported**** ****Conversion from PDO::PARAM_LOB to datetime2(7) is supported**** +Testing datetimeoffset(0): +****Conversion from PDO::PARAM_BOOL to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_INT to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_STR to datetimeoffset(0) is supported**** +****Conversion from PDO::PARAM_LOB to datetimeoffset(0) is supported**** + Testing datetimeoffset(1): ****Conversion from PDO::PARAM_BOOL to datetimeoffset(1) is supported**** ****Conversion from PDO::PARAM_INT to datetimeoffset(1) is supported**** @@ -150,6 +162,12 @@ Testing datetimeoffset(7): ****Conversion from PDO::PARAM_STR to datetimeoffset(7) is supported**** ****Conversion from PDO::PARAM_LOB to datetimeoffset(7) is supported**** +Testing time(0): +****Conversion from PDO::PARAM_BOOL to time(0) is supported**** +****Conversion from PDO::PARAM_INT to time(0) is supported**** +****Conversion from PDO::PARAM_STR to time(0) is supported**** +****Conversion from PDO::PARAM_LOB to time(0) is supported**** + Testing time(1): ****Conversion from PDO::PARAM_BOOL to time(1) is supported**** ****Conversion from PDO::PARAM_INT to time(1) is supported**** diff --git a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt index 8527d861..68ed4f58 100644 --- a/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt +++ b/test/functional/sqlsrv/sqlsrv_ae_insert_datetime_encrypted.phpt @@ -65,7 +65,7 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov dropTable($conn, $tableName); // Define the column definitions - $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(4)'); + $columns = array('c1' => 'smalldatetime', 'c2' => 'datetime', 'c3' => 'datetime2(0)', 'c4' => 'datetime2(3)'); if ($qualified) { $tsql = createTableEncryptedQuery($conn, $tableName, $columns); @@ -81,10 +81,11 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov // Insert values that cause errors $val1 = '9999-12-31 23:59:59'; $val2 = null; - $val3 = '9999-12-31 23:59:59.9999'; + $val3 = null; + $val4 = '9999-12-31 23:59:59.999'; - $tsql = "INSERT INTO $tableName (c1, c2, c3) VALUES (?,?,?)"; - $params = array($val1, $val2, $val3); + $tsql = "INSERT INTO $tableName (c1, c2, c3, c4) VALUES (?,?,?,?)"; + $params = array($val1, $val2, $val3, $val4); $stmt = sqlsrv_prepare($conn, $tsql, $params); if (!$stmt) { @@ -105,8 +106,9 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov // These values should work $val1 = '2021-11-03 11:49:00'; $val2 = '2015-10-23 07:03:00.000'; - - $params = array($val1, $val2, $val3); + $val3 = '0001-01-01 01:01:01'; + + $params = array($val1, $val2, $val3, $val4); $stmt = sqlsrv_prepare($conn, $tsql, $params); if (!$stmt) { fatalError("Failed to prepare insert statement"); @@ -138,12 +140,14 @@ Verify that inserting into smalldatetime column might trigger "Datetime field ov ?> --EXPECT-- -array(3) { +array(4) { ["c1"]=> string(19) "2021-11-03 11:49:00" ["c2"]=> string(23) "2015-10-23 07:03:00.000" ["c3"]=> - string(24) "9999-12-31 23:59:59.9999" + string(19) "0001-01-01 01:01:01" + ["c4"]=> + string(23) "9999-12-31 23:59:59.999" } Done