From cf932a929abd9ad4f9413e58bf9d85b75e42d88b Mon Sep 17 00:00:00 2001 From: Starsky Torchia Date: Thu, 18 Jan 2024 17:46:55 +0000 Subject: [PATCH 1/9] Add fetch_bignumeric attribute Add in optional bignumeric attribute which converts sql bigints to php ints and sql decimal and numeric to php float Add basic tests for string vs int/float returns --- .gitignore | 1 + source/pdo_sqlsrv/pdo_dbh.cpp | 17 +++ source/pdo_sqlsrv/pdo_init.cpp | 1 + source/pdo_sqlsrv/pdo_stmt.cpp | 33 ++++- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 5 + .../pdo_fetch_bignumeric_as_int.phpt | 72 +++++++++ .../pdo_fetch_bignumeric_nulls.phpt | 139 ++++++++++++++++++ .../pdo_fetch_decimal_numeric_as_float.phpt | 75 ++++++++++ 8 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_as_int.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt create mode 100644 test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt diff --git a/.gitignore b/.gitignore index db797324b..f6e358133 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .vs .vscode +.DS_Store __pycache__ *.diff *.exp diff --git a/source/pdo_sqlsrv/pdo_dbh.cpp b/source/pdo_sqlsrv/pdo_dbh.cpp index 8f39e72fd..877563049 100644 --- a/source/pdo_sqlsrv/pdo_dbh.cpp +++ b/source/pdo_sqlsrv/pdo_dbh.cpp @@ -87,6 +87,7 @@ enum PDO_STMT_OPTIONS { PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, PDO_STMT_OPTION_EMULATE_PREPARES, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, + PDO_STMT_OPTION_FETCHES_BIGNUMERIC_TYPE, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, PDO_STMT_OPTION_FORMAT_DECIMALS, PDO_STMT_OPTION_DECIMAL_PLACES, @@ -104,6 +105,7 @@ const stmt_option PDO_STMT_OPTS[] = { { NULL, 0, PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE, std::unique_ptr( new stmt_option_buffered_query_limit ) }, { NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr( new stmt_option_emulate_prepares ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_numeric ) }, + { NULL, 0, PDO_STMT_OPTION_FETCHES_BIGNUMERIC_TYPE, std::unique_ptr( new stmt_option_fetch_bignumeric ) }, { NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr( new stmt_option_fetch_datetime ) }, { NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr( new stmt_option_format_decimals ) }, { NULL, 0, PDO_STMT_OPTION_DECIMAL_PLACES, std::unique_ptr( new stmt_option_decimal_places ) }, @@ -592,6 +594,7 @@ pdo_sqlsrv_dbh::pdo_sqlsrv_dbh( _In_ SQLHANDLE h, _In_ error_callback e, _In_ vo query_timeout( QUERY_TIMEOUT_INVALID ), client_buffer_max_size( PDO_SQLSRV_G( client_buffer_max_size )), fetch_numeric( false ), + fetch_bignumeric( false ), fetch_datetime( false ), format_decimals( false ), decimal_places( NO_CHANGE_DECIMAL_PLACES ), @@ -1273,6 +1276,10 @@ bool pdo_sqlsrv_dbh_set_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout driver_dbh->fetch_numeric = zend_is_true(val); break; + case SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE: + driver_dbh->fetch_bignumeric = zend_is_true(val); + break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: driver_dbh->fetch_datetime = zend_is_true(val); break; @@ -1504,6 +1511,12 @@ int pdo_sqlsrv_dbh_get_attr(_Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout_ break; } + case SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_dbh->fetch_bignumeric ); + break; + } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: { ZVAL_BOOL( return_value, driver_dbh->fetch_datetime ); @@ -1959,6 +1972,10 @@ void add_stmt_option_key(_Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_ H option_key = PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE; break; + case SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE: + option_key = PDO_STMT_OPTION_FETCHES_BIGNUMERIC_TYPE; + break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE; break; diff --git a/source/pdo_sqlsrv/pdo_init.cpp b/source/pdo_sqlsrv/pdo_init.cpp index 31c3d8fa9..ab82ebcd2 100644 --- a/source/pdo_sqlsrv/pdo_init.cpp +++ b/source/pdo_sqlsrv/pdo_init.cpp @@ -300,6 +300,7 @@ namespace { { "SQLSRV_ATTR_CURSOR_SCROLL_TYPE" , SQLSRV_ATTR_CURSOR_SCROLL_TYPE }, { "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE }, { "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE }, + { "SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE", SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE }, { "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE }, { "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS }, { "SQLSRV_ATTR_DECIMAL_PLACES" , SQLSRV_ATTR_DECIMAL_PLACES }, diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 30f9177fc..222332dfa 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -340,6 +340,12 @@ void stmt_option_fetch_numeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_opt pdo_stmt->fetch_numeric = zend_is_true(value_z); } +void stmt_option_fetch_bignumeric:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) +{ + pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); + pdo_stmt->fetch_bignumeric = zend_is_true(value_z); +} + void stmt_option_fetch_datetime:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ) { pdo_sqlsrv_stmt *pdo_stmt = static_cast( stmt ); @@ -930,6 +936,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In driver_stmt->fetch_numeric = zend_is_true(val); break; + case SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE: + driver_stmt->fetch_bignumeric = zend_is_true(val); + break; + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: driver_stmt->fetch_datetime = zend_is_true(val); break; @@ -1027,6 +1037,12 @@ int pdo_sqlsrv_stmt_get_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In break; } + case SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE: + { + ZVAL_BOOL( return_value, driver_stmt->fetch_bignumeric ); + break; + } + case SQLSRV_ATTR_FETCHES_DATETIME_TYPE: { ZVAL_BOOL( return_value, driver_stmt->fetch_datetime ); @@ -1539,10 +1555,23 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, } break; case SQL_BIGINT: + if ( this->fetch_bignumeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } + break; case SQL_DECIMAL: case SQL_NUMERIC: - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; - sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + if ( this->fetch_bignumeric ) { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT; + } + else { + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; + sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR; + } break; case SQL_CHAR: case SQL_GUID: diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index a0a6dd89b..ba7261985 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -87,6 +87,7 @@ enum PDO_SQLSRV_ATTR { SQLSRV_ATTR_CURSOR_SCROLL_TYPE, SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE, SQLSRV_ATTR_FETCHES_NUMERIC_TYPE, + SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, SQLSRV_ATTR_FETCHES_DATETIME_TYPE, SQLSRV_ATTR_FORMAT_DECIMALS, SQLSRV_ATTR_DECIMAL_PLACES, @@ -187,6 +188,7 @@ struct pdo_sqlsrv_dbh : public sqlsrv_conn { long query_timeout; zend_long client_buffer_max_size; bool fetch_numeric; + bool fetch_bignumeric; bool fetch_datetime; bool format_decimals; short decimal_places; @@ -241,11 +243,13 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { placeholders(NULL), bound_column_param_types( NULL ), fetch_numeric( false ), + fetch_bignumeric( false ), fetch_datetime( false ) { pdo_sqlsrv_dbh* db = static_cast( c ); direct_query = db->direct_query; fetch_numeric = db->fetch_numeric; + fetch_bignumeric = db->fetch_bignumeric; fetch_datetime = db->fetch_datetime; format_decimals = db->format_decimals; decimal_places = db->decimal_places; @@ -265,6 +269,7 @@ struct pdo_sqlsrv_stmt : public sqlsrv_stmt { pdo_param_type* bound_column_param_types; bool fetch_numeric; + bool fetch_bignumeric; bool fetch_datetime; }; diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_as_int.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_as_int.phpt new file mode 100644 index 000000000..5e00bbe14 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_as_int.phpt @@ -0,0 +1,72 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for bigint columns +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for bigint columns. +The input value is a bigint bigger than the max int value in mssql. +Note that the existing attribute ATTR_STRINGIFY_FETCHES should have no effect on data retrieval. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, FALSE); + Test(); + + // Set PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE = true + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, TRUE); + Test(); + + // Close connection + unset($stmt); + unset($conn); + + print "Done"; +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +// Generic test starts here +function Test() +{ + global $conn, $tableName; + + // Drop table if exists + createTable($conn, $tableName, array("c1" => "bigint")); + + // Insert data using bind values + $sql = "INSERT INTO $tableName VALUES (32147483647)"; + $stmt = $conn->prepare($sql); + $stmt->execute(); + + // Get data + $sql = "SELECT * FROM $tableName"; + $stmt = $conn->query($sql); + $row = $stmt->fetchAll(PDO::FETCH_NUM); + + // Print out + for ($i=0; $i<$stmt->rowCount(); $i++) { + var_dump($row[$i][0]); + } + + // clean up + dropTable( $conn, $tableName ); + unset( $stmt ); +} +?> + +--EXPECT-- +string(11) "32147483647" +string(11) "32147483647" +int(32147483647) +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt new file mode 100644 index 000000000..8e6bc6a73 --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt @@ -0,0 +1,139 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for bigint, decimal and numeric types with null values +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for different bigint, decimal and numeric +types with null values. Whether retrieved as strings, ints or floats should return NULLs. +--SKIPIF-- + +--FILE-- + PDO::CURSOR_SCROLL, + PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED); + } + + // fetch_bignumeric off + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNull($row, $columns, PDO::FETCH_NUM); + + // fetch_bignumeric on + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNull($row, $columns, PDO::FETCH_ASSOC); + + // conn attribute fetch_bignumeric on, but statement attribute fetch_bignumeric off -- + // expected strings to be returned because statement attribute overrides the + // connection attribute + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, true); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNull($row, $columns, PDO::FETCH_NUM); + + // conn attribute fetch_bignumeric unchanged, but statement attribute fetch_bignumeric on -- + // expected datetime objects to be returned (this time no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + checkNull($row, $columns, PDO::FETCH_ASSOC); + + // likewise, conn attribute fetch_bignumeric off, but statement attribute + // fetch_bignumeric on -- expected int/float objects to be returned + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, false); + $stmt = $conn->prepare($query, $options); + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, true); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_BOTH); + checkNull($row, $columns, PDO::FETCH_BOTH); + + // conn attribute fetch_bignumeric unchanged, but statement attribute fetch_bignumeric off -- + // expected strings to be returned (again no need to prepare the statement) + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, false); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_NUM); + checkNull($row, $columns, PDO::FETCH_NUM); + + // last test: set statement attribute fetch_datetime on with no change to + // prepared statement -- expected int/float objects to be returned + $stmt->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, true); + $i = 0; + do { + $stmt->execute(); + $dtObj = $stmt->fetchColumn($i); + if (!is_null($dtObj)) { + echo "Expected NULL for column " . ($i + 1) . " but got: "; + var_dump($dtObj); + } + } while (++$i < count($columns)); +} + +try { + $conn = connect(); + + // create a test table + $tableName = "TestNullDateTime"; + $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); + $colMeta = array(new ColumnMeta('bigint', $columns[0]), + new ColumnMeta('bigint', $columns[1])); + new ColumnMeta('decimal(5,2)', $columns[2]), + new ColumnMeta('numeric(5,2)', $columns[3]), + new ColumnMeta('decimal(38,4)', $columns[4]), + new ColumnMeta('numeric(38,4)', $columns[5]), + createTable($conn, $tableName, $colMeta); + + $value = null; + $query = "INSERT INTO $tableName VALUES(?, ?, ?, ?, ?, ?)"; + $stmt = $conn->prepare($query); + for ($i = 0; $i < count($columns); $i++) { + $stmt->bindParam($i+1, $value, PDO::PARAM_NULL); + } + $stmt->execute(); + + $query = "SELECT * FROM $tableName"; + + runTest($conn, $query, $columns); + runTest($conn, $query, $columns, true); + + dropTable($conn, $tableName); + + echo "Done\n"; + + unset($stmt); + unset($conn); +} catch (PDOException $e) { + var_dump($e->errorInfo); +} +?> +--EXPECT-- +Done diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt new file mode 100644 index 000000000..bbe67625a --- /dev/null +++ b/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt @@ -0,0 +1,75 @@ +--TEST-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for decimal and numeric columns +--DESCRIPTION-- +Test attribute PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE for decimal and numeric columns. +The input values are random and they are retrieved either as strings or floats. +Note that the existing attribute ATTR_STRINGIFY_FETCHES should have no effect on data retrieval. +--SKIPIF-- + +--FILE-- +setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, FALSE); + Test(); + + // Set PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE = true + $conn->setAttribute(PDO::SQLSRV_ATTR_FETCHES_BIGNUMERIC_TYPE, TRUE); + Test(); + + // Close connection + unset($stmt); + unset($conn); + + print "Done"; +} catch (PDOException $e) { + var_dump($e->errorInfo); +} + +// Generic test starts here +function Test() +{ + global $conn, $tableName; + + // Drop table if exists + createTable($conn, $tableName, array("c1" => "decimal(5,2)", "c2" => "numeric(5,2)")); + + // Insert data using bind values + $sql = "INSERT INTO $tableName VALUES (123.45, 123.45)"; + $stmt = $conn->prepare($sql); + $stmt->execute(); + + // Get data + $sql = "SELECT * FROM $tableName"; + $stmt = $conn->query($sql); + $row = $stmt->fetchAll(PDO::FETCH_NUM); + + // Print out + for ($i=0; $i<$stmt->rowCount(); $i++) { + var_dump($row[$i][0]); + } + + // clean up + dropTable( $conn, $tableName ); + unset( $stmt ); +} +?> + +--EXPECT-- +string(6) "123.45" +string(6) "123.45" +string(6) "123.45" +string(6) "123.45" +float(123.45) +float(123.45) +Done From f33321016722f9f150123702580f683f706cfd30 Mon Sep 17 00:00:00 2001 From: Starsky Torchia Date: Sat, 20 Jan 2024 13:45:54 +0000 Subject: [PATCH 2/9] Remove DS_Store from gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index f6e358133..db797324b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .vs .vscode -.DS_Store __pycache__ *.diff *.exp From b4eecdfaa9b8f4a9634243199044ac4bac834bbc Mon Sep 17 00:00:00 2001 From: Starsky Torchia Date: Sun, 21 Jan 2024 10:27:03 +0000 Subject: [PATCH 3/9] Add missing structure --- source/pdo_sqlsrv/php_pdo_sqlsrv_int.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h index ba7261985..d1f5f0fac 100644 --- a/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h +++ b/source/pdo_sqlsrv/php_pdo_sqlsrv_int.h @@ -227,6 +227,10 @@ struct stmt_option_fetch_numeric : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; +struct stmt_option_fetch_bignumeric : public stmt_option_functor { + virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); +}; + struct stmt_option_fetch_datetime : public stmt_option_functor { virtual void operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z ); }; From 3e758ab6c0d4b695f9126ccf5b7c4410831ed928 Mon Sep 17 00:00:00 2001 From: Starsky Torchia Date: Mon, 22 Jan 2024 09:52:07 +0000 Subject: [PATCH 4/9] Fix broken bigint tests --- test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt | 4 ++-- .../pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt index 8e6bc6a73..f2415468d 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_bignumeric_nulls.phpt @@ -105,11 +105,11 @@ try { $tableName = "TestNullDateTime"; $columns = array('c1', 'c2', 'c3', 'c4', 'c5', 'c6'); $colMeta = array(new ColumnMeta('bigint', $columns[0]), - new ColumnMeta('bigint', $columns[1])); + new ColumnMeta('bigint', $columns[1]), new ColumnMeta('decimal(5,2)', $columns[2]), new ColumnMeta('numeric(5,2)', $columns[3]), new ColumnMeta('decimal(38,4)', $columns[4]), - new ColumnMeta('numeric(38,4)', $columns[5]), + new ColumnMeta('numeric(38,4)', $columns[5])); createTable($conn, $tableName, $colMeta); $value = null; diff --git a/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt b/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt index bbe67625a..6375a77d8 100644 --- a/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt +++ b/test/functional/pdo_sqlsrv/pdo_fetch_decimal_numeric_as_float.phpt @@ -55,9 +55,8 @@ function Test() $row = $stmt->fetchAll(PDO::FETCH_NUM); // Print out - for ($i=0; $i<$stmt->rowCount(); $i++) { - var_dump($row[$i][0]); - } + var_dump($row[0][0]); + var_dump($row[0][1]); // clean up dropTable( $conn, $tableName ); From 4fb8d38a6f773ba298da5318cc9de677d8644ca2 Mon Sep 17 00:00:00 2001 From: Yogesh Vaishnav Date: Mon, 24 Jun 2024 19:43:33 +0530 Subject: [PATCH 5/9] fix: type cast SQL_BIGINT to SQLSRV_PHPTYPE_LONG instead of SQLSRV_PHPTYPE_INT --- source/pdo_sqlsrv/pdo_stmt.cpp | 2 +- source/shared/core_results.cpp | 22 ++++++++++++++++++++++ source/shared/core_sqlsrv.h | 3 +++ source/shared/core_stmt.cpp | 27 +++++++++++++++++++++++++++ 4 files changed, 53 insertions(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 222332dfa..19e73a877 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -1556,7 +1556,7 @@ sqlsrv_phptype pdo_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, break; case SQL_BIGINT: if ( this->fetch_bignumeric ) { - sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT; + sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_LONG; } else { sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING; diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 0360e6ce2..5bab5362a 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -763,6 +763,7 @@ SQLRETURN sqlsrv_buffered_result_set::get_data( _In_ SQLUSMALLINT field_index, _ case SQL_C_BINARY: return sqlsrv_buffered_result_set::to_binary_string(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_DOUBLE: return sqlsrv_buffered_result_set::string_to_double(field_index, buffer, buffer_length, out_buffer_length); case SQL_C_LONG: return sqlsrv_buffered_result_set::string_to_long(field_index, buffer, buffer_length, out_buffer_length); + case SQL_C_SBIGINT: return sqlsrv_buffered_result_set::string_to_long_long(field_index, buffer, buffer_length, out_buffer_length); default: break; } @@ -1123,6 +1124,27 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_ind return SQL_SUCCESS; } +SQLRETURN sqlsrv_buffered_result_set::string_to_long_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, + _Inout_ SQLLEN* out_buffer_length ) +{ + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONGLONG ), "Buffer needs to be big enough to hold a long" ); + + unsigned char* row = get_row(); + char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); + + LONGLONG* number_data = reinterpret_cast(buffer); + try { + *number_data = std::stol(std::string(string_data)); + } catch (const std::logic_error& ) { + last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); + return SQL_ERROR; + } + + *out_buffer_length = sizeof(LONGLONG); + return SQL_SUCCESS; +} + SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { diff --git a/source/shared/core_sqlsrv.h b/source/shared/core_sqlsrv.h index 74c0b32e7..85796f83b 100644 --- a/source/shared/core_sqlsrv.h +++ b/source/shared/core_sqlsrv.h @@ -198,6 +198,7 @@ enum SQLSRV_PHPTYPE { MIN_SQLSRV_PHPTYPE = 1, // lowest value for a php type SQLSRV_PHPTYPE_NULL = 1, SQLSRV_PHPTYPE_INT, + SQLSRV_PHPTYPE_LONG, SQLSRV_PHPTYPE_FLOAT, SQLSRV_PHPTYPE_STRING, SQLSRV_PHPTYPE_DATETIME, @@ -1926,6 +1927,8 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set { _Inout_ SQLLEN* out_buffer_length ); SQLRETURN string_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); + SQLRETURN string_to_long_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, + _Inout_ SQLLEN* out_buffer_length ); SQLRETURN wstring_to_double( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ); SQLRETURN wstring_to_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 3adf0e7b7..8262cb33c 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1425,6 +1425,33 @@ void core_get_field_common( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_i break; } + case SQLSRV_PHPTYPE_LONG: + { + sqlsrv_malloc_auto_ptr field_value_temp; + field_value_temp = static_cast( sqlsrv_malloc( sizeof( SQLLEN ))); + *field_value_temp = 0; + + SQLRETURN r = stmt->current_results->get_data( field_index + 1, SQL_C_SBIGINT, field_value_temp, sizeof( SQLLEN ), + field_len, true /*handle_warning*/ ); + + CHECK_SQL_ERROR_OR_WARNING( r, stmt, NULL ) { + throw core::CoreException(); + } + + CHECK_CUSTOM_ERROR(( r == SQL_NO_DATA ), stmt, SQLSRV_ERROR_NO_DATA, field_index, NULL) { + throw core::CoreException(); + } + + if( *field_len == SQL_NULL_DATA ) { + field_value = NULL; + break; + } + + field_value = field_value_temp; + field_value_temp.transferred(); + break; + } + case SQLSRV_PHPTYPE_FLOAT: { sqlsrv_malloc_auto_ptr field_value_temp; From fbd74c4b1a572281fd739b113292d627e1950648 Mon Sep 17 00:00:00 2001 From: Yogesh Vaishnav Date: Mon, 24 Jun 2024 20:15:30 +0530 Subject: [PATCH 6/9] fix: convert c_char string to long long using stoll() --- source/shared/core_results.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/shared/core_results.cpp b/source/shared/core_results.cpp index 5bab5362a..84c0294ef 100644 --- a/source/shared/core_results.cpp +++ b/source/shared/core_results.cpp @@ -1127,15 +1127,15 @@ SQLRETURN sqlsrv_buffered_result_set::string_to_long( _In_ SQLSMALLINT field_ind SQLRETURN sqlsrv_buffered_result_set::string_to_long_long( _In_ SQLSMALLINT field_index, _Out_writes_bytes_(*out_buffer_length) void* buffer, _In_ SQLLEN buffer_length, _Inout_ SQLLEN* out_buffer_length ) { - SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to long" ); - SQLSRV_ASSERT( buffer_length >= sizeof( LONGLONG ), "Buffer needs to be big enough to hold a long" ); + SQLSRV_ASSERT( meta[field_index].c_type == SQL_C_CHAR, "Invalid conversion from string to long long" ); + SQLSRV_ASSERT( buffer_length >= sizeof( LONGLONG ), "Buffer needs to be big enough to hold a long long" ); unsigned char* row = get_row(); char* string_data = reinterpret_cast( &row[meta[field_index].offset] ) + sizeof( SQLULEN ); LONGLONG* number_data = reinterpret_cast(buffer); try { - *number_data = std::stol(std::string(string_data)); + *number_data = std::stoll(std::string(string_data)); } catch (const std::logic_error& ) { last_error = new (sqlsrv_malloc(sizeof(sqlsrv_error))) sqlsrv_error((SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103); return SQL_ERROR; From 090e8cdd55fe61cd0c26a26f920debe44870c4ac Mon Sep 17 00:00:00 2001 From: Yogesh Vaishnav Date: Mon, 24 Jun 2024 20:50:52 +0530 Subject: [PATCH 7/9] fix: add SQLSRV_PHPTYPE_LONG as a valid phptype --- source/shared/core_stmt.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/source/shared/core_stmt.cpp b/source/shared/core_stmt.cpp index 11211b99f..7fe424b0d 100644 --- a/source/shared/core_stmt.cpp +++ b/source/shared/core_stmt.cpp @@ -1902,6 +1902,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type ) case SQLSRV_PHPTYPE_NULL: case SQLSRV_PHPTYPE_INT: + case SQLSRV_PHPTYPE_LONG: case SQLSRV_PHPTYPE_FLOAT: case SQLSRV_PHPTYPE_DATETIME: case SQLSRV_PHPTYPE_TABLE: From c1afd250b4deb03c898fcb1f34144c29c1d4d8a5 Mon Sep 17 00:00:00 2001 From: Yogesh Vaishnav Date: Mon, 24 Jun 2024 23:37:47 +0530 Subject: [PATCH 8/9] fix: add support for SQLSRV_PHPTYPE_LONG in convert_to_zval() --- source/pdo_sqlsrv/pdo_stmt.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index 19e73a877..c995b0100 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -220,13 +220,14 @@ zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_t case SQLSRV_PHPTYPE_INT: case SQLSRV_PHPTYPE_FLOAT: + case SQLSRV_PHPTYPE_LONG: { if (*in_val == NULL) { ZVAL_NULL(&out_zval); } else { - if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { + if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT || sqlsrv_php_type == SQLSRV_PHPTYPE_LONG) { ZVAL_LONG(&out_zval, **(reinterpret_cast(in_val))); } else { From 774f1adb3452276fee76acf8911e6bac1f1138ea Mon Sep 17 00:00:00 2001 From: Yogesh Vaishnav Date: Tue, 25 Jun 2024 00:09:09 +0530 Subject: [PATCH 9/9] fix: change pointer type to long long** instead of int** for SQLSRV_PHPTYPE_LONG --- source/pdo_sqlsrv/pdo_stmt.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/source/pdo_sqlsrv/pdo_stmt.cpp b/source/pdo_sqlsrv/pdo_stmt.cpp index c995b0100..bb9fa1486 100644 --- a/source/pdo_sqlsrv/pdo_stmt.cpp +++ b/source/pdo_sqlsrv/pdo_stmt.cpp @@ -226,8 +226,10 @@ zval convert_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_t ZVAL_NULL(&out_zval); } else { - - if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT || sqlsrv_php_type == SQLSRV_PHPTYPE_LONG) { + if (sqlsrv_php_type == SQLSRV_PHPTYPE_LONG) { + ZVAL_LONG(&out_zval, **(reinterpret_cast(in_val))); + } + else if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) { ZVAL_LONG(&out_zval, **(reinterpret_cast(in_val))); } else {