Skip to content

Commit

Permalink
Feature request 415 for pdo_sqlsrv (#873)
Browse files Browse the repository at this point in the history
  • Loading branch information
yitam authored Nov 2, 2018
1 parent 2a9398f commit f4ad2ae
Show file tree
Hide file tree
Showing 11 changed files with 744 additions and 66 deletions.
10 changes: 9 additions & 1 deletion source/pdo_sqlsrv/pdo_dbh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ 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_DATETIME_TYPE
PDO_STMT_OPTION_FETCHES_DATETIME_TYPE,
PDO_STMT_OPTION_FORMAT_DECIMALS
};

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

{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
};
Expand Down Expand Up @@ -1095,6 +1097,7 @@ int pdo_sqlsrv_dbh_set_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
case PDO_ATTR_EMULATE_PREPARES:
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
Expand Down Expand Up @@ -1153,6 +1156,7 @@ int pdo_sqlsrv_dbh_get_attr( _Inout_ pdo_dbh_t *dbh, _In_ zend_long attr, _Inout
case PDO_ATTR_EMULATE_PREPARES:
case PDO_ATTR_CURSOR:
case SQLSRV_ATTR_CURSOR_SCROLL_TYPE:
case SQLSRV_ATTR_FORMAT_DECIMALS:
{
THROW_PDO_ERROR( driver_dbh, PDO_SQLSRV_ERROR_STMT_LEVEL_ATTR );
}
Expand Down Expand Up @@ -1586,6 +1590,10 @@ void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ size_t key, _Inout_
option_key = PDO_STMT_OPTION_FETCHES_DATETIME_TYPE;
break;

case SQLSRV_ATTR_FORMAT_DECIMALS:
option_key = PDO_STMT_OPTION_FORMAT_DECIMALS;
break;

default:
CHECK_CUSTOM_ERROR( true, ctx, PDO_SQLSRV_ERROR_INVALID_STMT_OPTION ) {
throw core::CoreException();
Expand Down
1 change: 1 addition & 0 deletions source/pdo_sqlsrv/pdo_init.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ namespace {
{ "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_DATETIME_TYPE", SQLSRV_ATTR_FETCHES_DATETIME_TYPE },
{ "SQLSRV_ATTR_FORMAT_DECIMALS" , SQLSRV_ATTR_FORMAT_DECIMALS },

// used for the size for output parameters: PDO::PARAM_INT and PDO::PARAM_BOOL use the default size of int,
// PDO::PARAM_STR uses the size of the string in the variable
Expand Down
4 changes: 4 additions & 0 deletions source/pdo_sqlsrv/pdo_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,10 @@ int pdo_sqlsrv_stmt_set_attr( _Inout_ pdo_stmt_t *stmt, _In_ zend_long attr, _In
driver_stmt->fetch_datetime = ( zend_is_true( val )) ? true : false;
break;

case SQLSRV_ATTR_FORMAT_DECIMALS:
core_sqlsrv_set_format_decimals(driver_stmt, val TSRMLS_CC);
break;

default:
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_STMT_ATTR );
break;
Expand Down
8 changes: 8 additions & 0 deletions source/pdo_sqlsrv/pdo_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,14 @@ pdo_error PDO_ERRORS[] = {
SQLSRV_ERROR_EMPTY_ACCESS_TOKEN,
{ IMSSP, (SQLCHAR*) "The Azure AD Access Token is empty. Expected a byte string.", -91, false}
},
{
SQLSRV_ERROR_INVALID_FORMAT_DECIMALS,
{ IMSSP, (SQLCHAR*) "Expected an integer to specify number of decimals to format the output values of decimal data types.", -92, false}
},
{
SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE,
{ IMSSP, (SQLCHAR*) "For formatting decimal data values, %1!d! is out of range. Expected an integer from 0 to 38, inclusive.", -93, true}
},

{ UINT_MAX, {} }
};
Expand Down
5 changes: 3 additions & 2 deletions source/pdo_sqlsrv/php_pdo_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,15 @@ extern "C" {
// sqlsrv driver specific PDO attributes
enum PDO_SQLSRV_ATTR {

// Currently there are only three custom attributes for this driver.
// The custom attributes for this driver:
SQLSRV_ATTR_ENCODING = PDO_ATTR_DRIVER_SPECIFIC,
SQLSRV_ATTR_QUERY_TIMEOUT,
SQLSRV_ATTR_DIRECT_QUERY,
SQLSRV_ATTR_CURSOR_SCROLL_TYPE,
SQLSRV_ATTR_CLIENT_BUFFER_MAX_KB_SIZE,
SQLSRV_ATTR_FETCHES_NUMERIC_TYPE,
SQLSRV_ATTR_FETCHES_DATETIME_TYPE
SQLSRV_ATTR_FETCHES_DATETIME_TYPE,
SQLSRV_ATTR_FORMAT_DECIMALS
};

// valid set of values for TransactionIsolation connection option
Expand Down
3 changes: 1 addition & 2 deletions source/shared/core_sqlsrv.h
Original file line number Diff line number Diff line change
Expand Up @@ -1527,7 +1527,7 @@ void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z
bool core_sqlsrv_send_stream_packet( _Inout_ sqlsrv_stmt* stmt TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC );
void core_sqlsrv_set_buffered_query_limit( _Inout_ sqlsrv_stmt* stmt, _In_ SQLLEN limit TSRMLS_DC );

void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC);

//*********************************************************************************************************************************
// Result Set
Expand Down Expand Up @@ -1707,7 +1707,6 @@ struct sqlsrv_buffered_result_set : public sqlsrv_result_set {

// utility functions shared by multiple callers across files
bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_updates_z_(len) char** string, _Inout_ SQLLEN& len);
bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len);
bool validate_string( _In_ char* string, _In_ SQLLEN& len);
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 );
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 );
Expand Down
68 changes: 45 additions & 23 deletions source/shared/core_stmt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1258,6 +1258,26 @@ void core_sqlsrv_set_query_timeout( _Inout_ sqlsrv_stmt* stmt, _In_ long timeout
}
}

void core_sqlsrv_set_format_decimals(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC)
{
try {
// first check if the input is an integer
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
throw core::CoreException();
}

zend_long format_decimals = Z_LVAL_P(value_z);
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
throw core::CoreException();
}

stmt->num_decimals = static_cast<short>(format_decimals);
}
catch( core::CoreException& ) {
throw;
}
}

void core_sqlsrv_set_send_at_exec( _Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z TSRMLS_DC )
{
TSRMLS_C;
Expand Down Expand Up @@ -1427,17 +1447,7 @@ void stmt_option_date_as_string:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_op

void stmt_option_format_decimals:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /**/, _In_ zval* value_z TSRMLS_DC )
{
// first check if the input is an integer
CHECK_CUSTOM_ERROR(Z_TYPE_P(value_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_FORMAT_DECIMALS) {
throw core::CoreException();
}

zend_long format_decimals = Z_LVAL_P(value_z);
CHECK_CUSTOM_ERROR(format_decimals < 0 || format_decimals > SQL_SERVER_MAX_PRECISION, stmt, SQLSRV_ERROR_FORMAT_DECIMALS_OUT_OF_RANGE, format_decimals) {
throw core::CoreException();
}

stmt->num_decimals = static_cast<short>(format_decimals);
core_sqlsrv_set_format_decimals(stmt, value_z TSRMLS_CC);
}

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

// if it's not in the 8 bit encodings, then it's in UTF-16
if( output_param->encoding != SQLSRV_ENCODING_CHAR && output_param->encoding != SQLSRV_ENCODING_BINARY ) {
bool converted = convert_zval_string_from_utf16(output_param->encoding, value_z, str_len);
CHECK_CUSTOM_ERROR( !converted, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}
}
else if( output_param->encoding == SQLSRV_ENCODING_BINARY && str_len < output_param->original_buffer_len ) {
if (output_param->encoding == SQLSRV_ENCODING_BINARY) {
// ODBC doesn't null terminate binary encodings, but PHP complains if a string isn't null terminated
// so we do that here if the length of the returned data is less than the original allocation. The
// original allocation null terminates the buffer already.
str[str_len] = '\0';
if (str_len < output_param->original_buffer_len) {
str[str_len] = '\0';
}
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
else {
SQLSMALLINT decimal_digits = output_param->getDecimalDigits();
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);

if (output_param->encoding != SQLSRV_ENCODING_CHAR) {
char* outString = NULL;
SQLLEN outLen = 0;
bool result = convert_string_from_utf16(output_param->encoding, reinterpret_cast<const SQLWCHAR*>(str), int(str_len / sizeof(SQLWCHAR)), &outString, outLen );
CHECK_CUSTOM_ERROR(!result, stmt, SQLSRV_ERROR_OUTPUT_PARAM_ENCODING_TRANSLATE, get_last_error_message()) {
throw core::CoreException();
}

if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
format_decimal_numbers(stmt->num_decimals, decimal_digits, outString, &outLen);
}
core::sqlsrv_zval_stringl(value_z, outString, outLen);
sqlsrv_free(outString);
}
else {
if (stmt->num_decimals >= 0 && decimal_digits >= 0) {
format_decimal_numbers(stmt->num_decimals, decimal_digits, str, &str_len);
}

core::sqlsrv_zval_stringl(value_z, str, str_len);
core::sqlsrv_zval_stringl(value_z, str, str_len);
}
}
}
break;
Expand Down
19 changes: 0 additions & 19 deletions source/shared/core_util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,25 +91,6 @@ bool convert_string_from_utf16_inplace( _In_ SQLSRV_ENCODING encoding, _Inout_up
return result;
}

bool convert_zval_string_from_utf16( _In_ SQLSRV_ENCODING encoding, _Inout_ zval* value_z, _Inout_ SQLLEN& len)
{
char* string = Z_STRVAL_P(value_z);

if( validate_string(string, len)) {
return true;
}

char* outString = NULL;
SQLLEN outLen = 0;
bool result = convert_string_from_utf16( encoding, reinterpret_cast<const SQLWCHAR*>(string), int(len / sizeof(SQLWCHAR)), &outString, outLen );
if( result ) {
core::sqlsrv_zval_stringl( value_z, outString, outLen );
sqlsrv_free( outString );
len = outLen;
}
return result;
}

bool validate_string( _In_ char* string, _In_ SQLLEN& len )
{
SQLSRV_ASSERT(string != NULL, "String must be specified");
Expand Down
Loading

0 comments on commit f4ad2ae

Please sign in to comment.