Skip to content

Commit b49cb51

Browse files
authored
Simplified parse_param_array in sqlsrv (#1262)
1 parent 0da75f5 commit b49cb51

File tree

5 files changed

+214
-218
lines changed

5 files changed

+214
-218
lines changed

source/sqlsrv/stmt.cpp

Lines changed: 101 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,14 @@ sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sq
9696
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt );
9797
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
9898
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type );
99-
void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction,
100-
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
101-
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits );
10299
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
103100
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
104101
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
105102
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding );
103+
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index,
104+
_Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type,
105+
_Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
106+
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits);
106107

107108
}
108109

@@ -1194,44 +1195,50 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
11941195

11951196
HashTable* params_ht = Z_ARRVAL_P( params_z );
11961197

1197-
zend_ulong index = -1;
1198-
zend_string *key = NULL;
1199-
zval* param_z = NULL;
1200-
1201-
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
1202-
zval* value_z = NULL;
1203-
SQLSMALLINT direction = SQL_PARAM_INPUT;
1204-
SQLSRV_ENCODING encoding = stmt->encoding();
1205-
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
1206-
encoding = stmt->conn->encoding();
1207-
}
1208-
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
1209-
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
1210-
SQLSMALLINT decimal_digits = 0;
1211-
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
1212-
1213-
// make sure it's an integer index
1214-
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
1215-
CHECK_CUSTOM_ERROR( type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX ) {
1216-
throw ss::SSException();
1217-
}
1198+
zend_ulong index = -1;
1199+
zend_string *key = NULL;
1200+
zval* param_z = NULL;
12181201

1219-
// if it's a parameter array
1220-
if( Z_TYPE_P( param_z ) == IS_ARRAY ) {
1202+
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
1203+
// make sure it's an integer index
1204+
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
1205+
CHECK_CUSTOM_ERROR(type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX) {
1206+
throw ss::SSException();
1207+
}
12211208

1222-
zval* var = NULL;
1223-
int zr = ( NULL != ( var = zend_hash_index_find( Z_ARRVAL_P( param_z ), 0 ))) ? SUCCESS : FAILURE;
1224-
CHECK_CUSTOM_ERROR( zr == FAILURE, stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 ) {
1225-
throw ss::SSException();
1226-
}
1209+
zval* value_z = NULL;
1210+
SQLSMALLINT direction = SQL_PARAM_INPUT;
1211+
SQLSRV_ENCODING encoding = stmt->encoding();
1212+
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
1213+
encoding = stmt->conn->encoding();
1214+
}
1215+
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
1216+
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
1217+
SQLSMALLINT decimal_digits = 0;
1218+
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
12271219

1228-
// parse the parameter array that the user gave
1229-
parse_param_array( stmt, param_z, index, direction, php_out_type, encoding, sql_type, column_size,
1230-
decimal_digits );
1231-
value_z = var;
1220+
// if it's a parameter array
1221+
if (Z_TYPE_P(param_z) == IS_ARRAY) {
1222+
try {
1223+
HashTable* param_ht = Z_ARRVAL_P(param_z);
1224+
// Check the number of elements in the array
1225+
int num_elems = zend_hash_num_elements(param_ht);
1226+
if (num_elems > 1) {
1227+
value_z = parse_param_array(stmt, param_ht, index, direction, php_out_type, encoding, sql_type, column_size, decimal_digits);
1228+
} else {
1229+
// Simply get the first variable and use the defaults
1230+
value_z = zend_hash_index_find(param_ht, 0);
1231+
if (value_z == NULL) {
1232+
THROW_SS_ERROR(stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1);
1233+
}
1234+
}
1235+
} catch (core::CoreException&) {
1236+
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
1237+
throw;
1238+
}
12321239
}
12331240
else {
1234-
CHECK_CUSTOM_ERROR( !stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED ) {
1241+
CHECK_CUSTOM_ERROR(!stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
12351242
throw ss::SSException();
12361243
}
12371244
value_z = param_z;
@@ -1252,7 +1259,6 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
12521259
}
12531260

12541261
// bind the parameter
1255-
SQLSRV_ASSERT( value_z != NULL, "bind_params: value_z is null." );
12561262
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
12571263
decimal_digits );
12581264

@@ -1724,6 +1730,7 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
17241730
break;
17251731
default:
17261732
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
1733+
SQLSRV_ASSERT(false, "An invalid php type was returned with (supposedly) validated sql type and column_size");
17271734
break;
17281735
}
17291736

@@ -1905,179 +1912,106 @@ void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_typ
19051912

19061913
}
19071914

1908-
void parse_param_array( _Inout_ ss_sqlsrv_stmt* stmt, _Inout_ zval* param_array, zend_ulong index, _Out_ SQLSMALLINT& direction,
1915+
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index, _Out_ SQLSMALLINT& direction,
19091916
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
1910-
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits )
1917+
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits)
19111918
{
1912-
zval* var_or_val = NULL;
1913-
zval* temp = NULL;
1914-
HashTable* param_ht = Z_ARRVAL_P( param_array );
1915-
sqlsrv_sqltype sqlsrv_sql_type;
1916-
HashPosition pos;
1917-
1918-
try {
1919-
1920-
bool php_type_param_was_null = true;
1921-
bool sql_type_param_was_null = true;
1922-
1923-
php_out_type = SQLSRV_PHPTYPE_INVALID;
1924-
encoding = SQLSRV_ENCODING_INVALID;
1925-
1926-
// handle the array parameters that contain the value/var, direction, php_type, sql_type
1927-
zend_hash_internal_pointer_reset_ex( param_ht, &pos );
1928-
if( zend_hash_has_more_elements_ex( param_ht, &pos ) == FAILURE ||
1929-
(var_or_val = zend_hash_get_current_data_ex(param_ht, &pos)) == NULL) {
1930-
1931-
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1 );
1932-
}
1933-
1934-
// if the direction is included, then use what they gave, otherwise INPUT is assumed
1935-
if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != NULL &&
1936-
Z_TYPE_P( temp ) != IS_NULL ) {
1937-
1938-
CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) {
1939-
1919+
zval* var_or_val = zend_hash_index_find(param_ht, 0);
1920+
bool php_type_param_is_null = true;
1921+
bool sql_type_param_is_null = true;
1922+
1923+
// Assumption: there are more than only the variable, parse the rest of the array
1924+
zval* dir = zend_hash_index_find(param_ht, 1);
1925+
if (Z_TYPE_P(dir) != IS_NULL) {
1926+
// if param direction is specified, make sure it's valid
1927+
CHECK_CUSTOM_ERROR(Z_TYPE_P(dir) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
19401928
throw ss::SSException();
19411929
}
1942-
direction = static_cast<SQLSMALLINT>( Z_LVAL_P( temp ));
1943-
CHECK_CUSTOM_ERROR( direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
1944-
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1 ) {
1930+
direction = static_cast<SQLSMALLINT>(Z_LVAL_P(dir));
1931+
1932+
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
1933+
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1) {
19451934
throw ss::SSException();
19461935
}
1947-
1948-
CHECK_CUSTOM_ERROR( !Z_ISREF_P( var_or_val ) && ( direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT ), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1 ) {
1936+
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && !Z_ISREF_P(var_or_val), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1) {
19491937
throw ss::SSException();
19501938
}
1951-
1952-
}
1953-
else {
1954-
direction = SQL_PARAM_INPUT;
19551939
}
19561940

1957-
// extract the php type and encoding from the 3rd parameter
1958-
if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != NULL &&
1959-
Z_TYPE_P( temp ) != IS_NULL ) {
1960-
1961-
php_type_param_was_null = false;
1962-
sqlsrv_phptype sqlsrv_phptype;
1941+
// Check if the user provides php type or sql type or both
1942+
zval* phptype_z = zend_hash_index_find(param_ht, 2);
1943+
zval* sqltype_z = zend_hash_index_find(param_ht, 3);
19631944

1964-
CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1 ) {
1945+
php_type_param_is_null = (phptype_z == NULL || Z_TYPE_P(phptype_z) == IS_NULL);
1946+
sql_type_param_is_null = (sqltype_z == NULL || Z_TYPE_P(sqltype_z) == IS_NULL);
19651947

1948+
if (php_type_param_is_null) {
1949+
// so set default for php type based on the variable
1950+
if (Z_ISREF_P(var_or_val)) {
1951+
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))];
1952+
} else {
1953+
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)];
1954+
}
1955+
} else {
1956+
CHECK_CUSTOM_ERROR(Z_TYPE_P(phptype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
19661957
throw ss::SSException();
19671958
}
19681959

1969-
sqlsrv_phptype.value = Z_LVAL_P( temp );
1970-
1971-
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_phptype( sqlsrv_phptype ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE,
1972-
index + 1 ) {
1973-
1960+
sqlsrv_phptype srv_phptype;
1961+
srv_phptype.value = Z_LVAL_P(phptype_z);
1962+
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(srv_phptype), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1) {
19741963
throw ss::SSException();
19751964
}
19761965

1977-
php_out_type = static_cast<SQLSRV_PHPTYPE>( sqlsrv_phptype.typeinfo.type );
1978-
encoding = ( SQLSRV_ENCODING ) sqlsrv_phptype.typeinfo.encoding;
1966+
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
1967+
encoding = (SQLSRV_ENCODING)srv_phptype.typeinfo.encoding;
19791968
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
19801969
// by the connection
1981-
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
1970+
if (encoding == SQLSRV_ENCODING_DEFAULT) {
19821971
encoding = stmt->conn->encoding();
19831972
}
19841973
}
1985-
// set default for php type and encoding if not supplied
1986-
else {
1987-
1988-
php_type_param_was_null = true;
19891974

1990-
if ( Z_ISREF_P( var_or_val )){
1991-
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P( Z_REFVAL_P( var_or_val ))];
1992-
}
1993-
else{
1994-
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P( var_or_val )];
1995-
}
1996-
encoding = stmt->encoding();
1997-
if( encoding == SQLSRV_ENCODING_DEFAULT ) {
1998-
encoding = stmt->conn->encoding();
1975+
if (sql_type_param_is_null) {
1976+
// the sql type is not specified, which is required for always encrypted for non-prepared statements
1977+
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled && !stmt->prepared, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
1978+
throw ss::SSException();
19991979
}
2000-
}
2001-
2002-
// get the server type, column size/precision and the decimal digits if provided
2003-
if ( zend_hash_move_forward_ex( param_ht, &pos ) == SUCCESS && ( temp = zend_hash_get_current_data_ex( param_ht, &pos )) != NULL &&
2004-
Z_TYPE_P( temp ) != IS_NULL ) {
2005-
2006-
sql_type_param_was_null = false;
2007-
2008-
CHECK_CUSTOM_ERROR( Z_TYPE_P( temp ) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1 ) {
2009-
1980+
} else {
1981+
CHECK_CUSTOM_ERROR(Z_TYPE_P(sqltype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
20101982
throw ss::SSException();
20111983
}
20121984

2013-
sqlsrv_sql_type.value = Z_LVAL_P( temp );
2014-
20151985
// since the user supplied this type, make sure it's valid
2016-
CHECK_CUSTOM_ERROR( !is_valid_sqlsrv_sqltype( sqlsrv_sql_type ), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE,
2017-
index + 1 ) {
2018-
1986+
sqlsrv_sqltype sqlsrv_sql_type;
1987+
sqlsrv_sql_type.value = Z_LVAL_P(sqltype_z);
1988+
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_sqltype(sqlsrv_sql_type), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1) {
20191989
throw ss::SSException();
20201990
}
20211991

2022-
bool size_okay = determine_column_size_or_precision( stmt, sqlsrv_sql_type, &column_size, &decimal_digits );
2023-
2024-
CHECK_CUSTOM_ERROR( !size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1 ) {
2025-
1992+
bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits);
1993+
CHECK_CUSTOM_ERROR(!size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1) {
20261994
throw ss::SSException();
20271995
}
20281996

20291997
sql_type = sqlsrv_sql_type.typeinfo.type;
2030-
}
2031-
// else the sql type and size are unknown, so tell the core layer to use its defaults
2032-
else {
2033-
CHECK_CUSTOM_ERROR( !stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED ) {
2034-
throw ss::SSException();
2035-
}
2036-
sql_type_param_was_null = true;
2037-
2038-
sql_type = SQL_UNKNOWN_TYPE;
2039-
column_size = SQLSRV_UNKNOWN_SIZE;
2040-
decimal_digits = 0;
2041-
}
2042-
2043-
// if the user for some reason provides an inout / output parameter with a null phptype and a specified
2044-
// sql server type, infer the php type from the sql server type.
2045-
if( direction != SQL_PARAM_INPUT && php_type_param_was_null && !sql_type_param_was_null ) {
2046-
2047-
sqlsrv_phptype sqlsrv_phptype;
2048-
2049-
sqlsrv_phptype = determine_sqlsrv_php_type( stmt, sql_type, (SQLUINTEGER)column_size, true );
20501998

2051-
// we DIE here since everything should have been validated already and to return the user an error
2052-
// for our own logic error would be confusing/misleading.
2053-
SQLSRV_ASSERT( sqlsrv_phptype.typeinfo.type != PHPTYPE_INVALID, "An invalid php type was returned with (supposed) "
2054-
"validated sql type and column_size" );
1999+
if (direction != SQL_PARAM_INPUT && php_type_param_is_null) {
2000+
sqlsrv_phptype srv_phptype;
2001+
srv_phptype = determine_sqlsrv_php_type(stmt, sql_type, (SQLUINTEGER)column_size, true);
20552002

2056-
php_out_type = static_cast<SQLSRV_PHPTYPE>( sqlsrv_phptype.typeinfo.type );
2057-
encoding = static_cast<SQLSRV_ENCODING>( sqlsrv_phptype.typeinfo.encoding );
2058-
}
2059-
2060-
// verify that the parameter is a valid output param type
2061-
if( direction == SQL_PARAM_OUTPUT ) {
2062-
2063-
switch( php_out_type ) {
2064-
case SQLSRV_PHPTYPE_NULL:
2065-
case SQLSRV_PHPTYPE_DATETIME:
2066-
case SQLSRV_PHPTYPE_STREAM:
2067-
THROW_CORE_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE );
2068-
break;
2069-
default:
2070-
break;
2003+
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
2004+
encoding = static_cast<SQLSRV_ENCODING>(srv_phptype.typeinfo.encoding);
20712005
}
2072-
20732006
}
20742007

2008+
if (direction == SQL_PARAM_OUTPUT) {
2009+
if (php_out_type == SQLSRV_PHPTYPE_NULL || php_out_type == SQLSRV_PHPTYPE_DATETIME || php_out_type == SQLSRV_PHPTYPE_STREAM) {
2010+
THROW_CORE_ERROR(stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
2011+
}
20752012
}
2076-
catch( core::CoreException& ) {
20772013

2078-
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
2079-
throw;
2080-
}
2014+
return var_or_val;
20812015
}
20822016

20832017
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )

0 commit comments

Comments
 (0)