Skip to content

Commit f4ad2ae

Browse files
authored
Feature request 415 for pdo_sqlsrv (#873)
1 parent 2a9398f commit f4ad2ae

File tree

11 files changed

+744
-66
lines changed

11 files changed

+744
-66
lines changed

source/pdo_sqlsrv/pdo_dbh.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,8 @@ enum PDO_STMT_OPTIONS {
8080
PDO_STMT_OPTION_CLIENT_BUFFER_MAX_KB_SIZE,
8181
PDO_STMT_OPTION_EMULATE_PREPARES,
8282
PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE,
83-
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE
83+
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
84+
PDO_STMT_OPTION_FORMAT_DECIMALS
8485
};
8586

8687
// List of all the statement options supported by this driver.
@@ -95,6 +96,7 @@ const stmt_option PDO_STMT_OPTS[] = {
9596
{ NULL, 0, PDO_STMT_OPTION_EMULATE_PREPARES, std::unique_ptr<stmt_option_emulate_prepares>( new stmt_option_emulate_prepares ) },
9697
{ NULL, 0, PDO_STMT_OPTION_FETCHES_NUMERIC_TYPE, std::unique_ptr<stmt_option_fetch_numeric>( new stmt_option_fetch_numeric ) },
9798
{ NULL, 0, PDO_STMT_OPTION_FETCHES_DATETIME_TYPE, std::unique_ptr<stmt_option_fetch_datetime>( new stmt_option_fetch_datetime ) },
99+
{ NULL, 0, PDO_STMT_OPTION_FORMAT_DECIMALS, std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals ) },
98100

99101
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
100102
};
@@ -1095,6 +1097,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
10951097
case PDO_ATTR_EMULATE_PREPARES:
10961098
case PDO_ATTR_CURSOR:
10971099
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
1100+
case SQLSRV_ATTR_FORMAT_DECIMALS:
10981101
{
10991102
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
11001103
}
@@ -1153,6 +1156,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
11531156
case PDO_ATTR_EMULATE_PREPARES:
11541157
case PDO_ATTR_CURSOR:
11551158
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
1159+
case SQLSRV_ATTR_FORMAT_DECIMALS:
11561160
{
11571161
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
11581162
}
@@ -1586,6 +1590,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_
15861590
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
15871591
break;
15881592

1593+
case SQLSRV_ATTR_FORMAT_DECIMALS:
1594+
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
1595+
break;
1596+
15891597
default:
15901598
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
15911599
throw core::CoreException();

source/pdo_sqlsrv/pdo_init.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,7 @@ namespace {
286286
{ "SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE", SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE },
287287
{ "SQLSRV_ATTR_FETCHES_NUMERIC_TYPE", SQLSRV_ATTR_FETCHES_NUMERIC_TYPE },
288288
{ "SQLSRV_ATTR_FETCHES_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
289+
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },
289290

290291
// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
291292
// PDO::PARAM_STR uses the size of the string in the variable

source/pdo_sqlsrv/pdo_stmt.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
882882
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
883883
break;
884884

885+
case SQLSRV_ATTR_FORMAT_DECIMALS:
886+
core_sqlsrv_set_format_decimals(driver_stmt, val TSRMLS_CC);
887+
break;
888+
885889
default:
886890
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
887891
break;

source/pdo_sqlsrv/pdo_util.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,14 @@ pdo_error PDO_ERRORS[] = {
437437
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
438438
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
439439
},
440+
{
441+
SQLSRV_ERROR_INVALID_FORMAT_DECIMALS,
442+
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
443+
},
444+
{
445+
SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE,
446+
{ IMSSP, (SQLCHAR*) "For formatting decimal data values, %1!d! is out of range. Expected an integer from 0 to 38, inclusive.", -93, true}
447+
},
440448

441449
{ UINT_MAX, {} }
442450
};

source/pdo_sqlsrv/php_pdo_sqlsrv.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,15 @@ extern "C" {
4141
// sqlsrv driver specific PDO attributes
4242
enum PDO_SQLSRV_ATTR {
4343

44-
// Currently there are only three custom attributes for this driver.
44+
// The custom attributes for this driver:
4545
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
4646
SQLSRV_ATTR_QUERY_TIMEOUT,
4747
SQLSRV_ATTR_DIRECT_QUERY,
4848
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
4949
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
5050
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
51-
SQLSRV_ATTR_FETCHES_DATETIME_TYPE
51+
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
52+
SQLSRV_ATTR_FORMAT_DECIMALS
5253
};
5354

5455
// valid set of values for TransactionIsolation connection option

source/shared/core_sqlsrv.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,7 +1527,7 @@ void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z
15271527
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
15281528
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
15291529
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC );
1530-
1530+
void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC);
15311531

15321532
//*********************************************************************************************************************************
15331533
// Result Set
@@ -1707,7 +1707,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {
17071707

17081708
// utility functions shared by multiple callers across files
17091709
bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
1710-
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len);
17111710
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
17121711
bool convert_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _In_reads_bytes_(cchInLen) const SQLWCHAR* inString, _In_ SQLINTEGER cchInLen, _Inout_updates_bytes_(cchOutLen) char** outString, _Out_ SQLLEN& cchOutLen );
17131712
SQLWCHAR* utf16_string_from_mbcs_string( _In_ SQLSRV_ENCODING php_encoding, _In_reads_bytes_(mbcs_len) const char* mbcs_string, _In_ unsigned int mbcs_len, _Out_ unsigned int* utf16_len );

source/shared/core_stmt.cpp

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,26 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout
12581258
}
12591259
}
12601260

1261+
void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC)
1262+
{
1263+
try {
1264+
// first check if the input is an integer
1265+
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
1266+
throw core::CoreException();
1267+
}
1268+
1269+
zend_long format_decimals = Z_LVAL_P(value_z);
1270+
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
1271+
throw core::CoreException();
1272+
}
1273+
1274+
stmt->num_decimals = static_cast<short>(format_decimals);
1275+
}
1276+
catch( core::CoreException& ) {
1277+
throw;
1278+
}
1279+
}
1280+
12611281
void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC )
12621282
{
12631283
TSRMLS_C;
@@ -1427,17 +1447,7 @@ void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op
14271447

14281448
void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
14291449
{
1430-
// first check if the input is an integer
1431-
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
1432-
throw core::CoreException();
1433-
}
1434-
1435-
zend_long format_decimals = Z_LVAL_P(value_z);
1436-
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
1437-
throw core::CoreException();
1438-
}
1439-
1440-
stmt->num_decimals = static_cast<short>(format_decimals);
1450+
core_sqlsrv_set_format_decimals(stmt, value_z TSRMLS_CC);
14411451
}
14421452

14431453
// internal function to release the active stream. Called by each main API function
@@ -2293,27 +2303,39 @@ void finalize_output_parameters( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC )
22932303
str_len = output_param->original_buffer_len - null_size;
22942304
}
22952305

2296-
// if it's not in the 8 bit encodings, then it's in UTF-16
2297-
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
2298-
bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len);
2299-
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
2300-
throw core::CoreException();
2301-
}
2302-
}
2303-
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
2306+
if (output_param->encoding == SQLSRV_ENCODING_BINARY) {
23042307
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
23052308
// so we do that here if the length of the returned data is less than the original allocation. The
23062309
// original allocation null terminates the buffer already.
2307-
str[str_len] = '\0';
2310+
if (str_len < output_param->original_buffer_len) {
2311+
str[str_len] = '\0';
2312+
}
23082313
core::sqlsrv_zval_stringl(value_z, str, str_len);
23092314
}
23102315
else {
23112316
SQLSMALLINT decimal_digits = output_param->getDecimalDigits();
2312-
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
2313-
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);
2317+
2318+
if (output_param->encoding != SQLSRV_ENCODING_CHAR) {
2319+
char* outString = NULL;
2320+
SQLLEN outLen = 0;
2321+
bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen );
2322+
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
2323+
throw core::CoreException();
2324+
}
2325+
2326+
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
2327+
format_decimal_numbers(stmt->num_decimals, decimal_digits, outString, &outLen);
2328+
}
2329+
core::sqlsrv_zval_stringl(value_z, outString, outLen);
2330+
sqlsrv_free(outString);
23142331
}
2332+
else {
2333+
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
2334+
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);
2335+
}
23152336

2316-
core::sqlsrv_zval_stringl(value_z, str, str_len);
2337+
core::sqlsrv_zval_stringl(value_z, str, str_len);
2338+
}
23172339
}
23182340
}
23192341
break;

source/shared/core_util.cpp

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,25 +91,6 @@ bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_up
9191
return result;
9292
}
9393

94-
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len)
95-
{
96-
char* string = Z_STRVAL_P(value_z);
97-
98-
if( validate_string(string, len)) {
99-
return true;
100-
}
101-
102-
char* outString = NULL;
103-
SQLLEN outLen = 0;
104-
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const SQLWCHAR*>(string), int(len / sizeof(SQLWCHAR)), &outString, outLen );
105-
if( result ) {
106-
core::sqlsrv_zval_stringl( value_z, outString, outLen );
107-
sqlsrv_free( outString );
108-
len = outLen;
109-
}
110-
return result;
111-
}
112-
11394
bool validate_string( _In_ char* string, _In_ SQLLEN& len )
11495
{
11596
SQLSRV_ASSERT(string != NULL, "String must be specified");

0 commit comments

Comments
 (0)