Implemented table-valued parameter support (#1257)
This commit is contained in:
parent
5f2a14c8e9
commit
0da75f5b92
|
@ -1325,8 +1325,19 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
|
|||
switch( php_out_type ) {
|
||||
case SQLSRV_PHPTYPE_NULL:
|
||||
case SQLSRV_PHPTYPE_STREAM:
|
||||
THROW_PDO_ERROR( driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE );
|
||||
{
|
||||
zval *zv = ¶m->parameter;
|
||||
if (Z_ISREF_P(zv)) {
|
||||
ZVAL_DEREF(zv);
|
||||
}
|
||||
// Table-valued parameters are input-only
|
||||
CHECK_CUSTOM_ERROR(Z_TYPE_P(zv) == IS_ARRAY, driver_stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
|
||||
throw pdo::PDOException();
|
||||
}
|
||||
// For other types, simply throw the following error
|
||||
THROW_PDO_ERROR(driver_stmt, PDO_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
|
||||
break;
|
||||
}
|
||||
case SQLSRV_PHPTYPE_INT:
|
||||
column_size = SQLSRV_UNKNOWN_SIZE;
|
||||
break;
|
||||
|
@ -1391,7 +1402,7 @@ int pdo_sqlsrv_stmt_param_hook( _Inout_ pdo_stmt_t *stmt,
|
|||
|
||||
// and bind the parameter
|
||||
core_sqlsrv_bind_param( driver_stmt, static_cast<SQLUSMALLINT>( param->paramno ), direction, &(param->parameter) , php_out_type, encoding,
|
||||
sql_type, column_size, decimal_digits );
|
||||
sql_type, column_size, decimal_digits);
|
||||
}
|
||||
break;
|
||||
// undo any work done by the core layer after the statement is executed
|
||||
|
|
|
@ -461,6 +461,42 @@ pdo_error PDO_ERRORS[] = {
|
|||
PDO_SQLSRV_ERROR_EXTENDED_STRING_TYPE_INVALID,
|
||||
{ IMSSP, (SQLCHAR*) "Invalid extended string type specified. PDO_ATTR_DEFAULT_STR_PARAM can be either PDO_PARAM_STR_CHAR or PDO_PARAM_STR_NATL.", -97, false}
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
|
||||
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -98, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
|
||||
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -99, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_FETCH_METADATA,
|
||||
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -100, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_INPUTS,
|
||||
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -101, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
|
||||
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -102, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
|
||||
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -103, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_STRING_KEYS,
|
||||
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -104, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
|
||||
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -105, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
|
||||
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -106, false }
|
||||
},
|
||||
|
||||
{ UINT_MAX, {} }
|
||||
};
|
||||
|
|
|
@ -955,7 +955,6 @@ const char* get_processor_arch( void )
|
|||
#endif // !_WIN32
|
||||
}
|
||||
|
||||
|
||||
// some features require a server of a certain version or later
|
||||
// this function determines the version of the server we're connected to
|
||||
// and stores it in the connection. Any errors are logged before return.
|
||||
|
@ -1008,8 +1007,8 @@ void load_azure_key_vault(_Inout_ sqlsrv_conn* conn)
|
|||
|
||||
char *akv_id = conn->ce_option.akv_id.get();
|
||||
char *akv_secret = conn->ce_option.akv_secret.get();
|
||||
unsigned int id_len = strnlen_s(akv_id);
|
||||
unsigned int key_size = strnlen_s(akv_secret);
|
||||
size_t id_len = strnlen_s(akv_id);
|
||||
size_t key_size = strnlen_s(akv_secret);
|
||||
|
||||
configure_azure_key_vault(conn, AKV_CONFIG_FLAGS, conn->ce_option.akv_mode, 0);
|
||||
configure_azure_key_vault(conn, AKV_CONFIG_PRINCIPALID, akv_id, id_len);
|
||||
|
|
|
@ -207,6 +207,7 @@ enum SQLSRV_PHPTYPE {
|
|||
SQLSRV_PHPTYPE_STRING,
|
||||
SQLSRV_PHPTYPE_DATETIME,
|
||||
SQLSRV_PHPTYPE_STREAM,
|
||||
SQLSRV_PHPTYPE_TABLE,
|
||||
MAX_SQLSRV_PHPTYPE, // highest value for a php type
|
||||
SQLSRV_PHPTYPE_INVALID = MAX_SQLSRV_PHPTYPE // used to see if a type is invalid
|
||||
};
|
||||
|
@ -1427,11 +1428,11 @@ struct sqlsrv_param
|
|||
virtual void release_data();
|
||||
|
||||
bool derive_string_types_sizes(_In_ zval* param_z);
|
||||
void preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
bool preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
|
||||
// The following methods change the member placeholder_z
|
||||
void convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
void convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
// The following methods change the member placeholder_z, and both will return false if conversions fail
|
||||
bool convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
bool convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z);
|
||||
|
||||
virtual bool prepare_param(_In_ zval* param_ref, _Inout_ zval* param_z);
|
||||
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
|
||||
|
@ -1481,6 +1482,45 @@ struct sqlsrv_param_inout : public sqlsrv_param
|
|||
void finalize_output_string();
|
||||
};
|
||||
|
||||
// *** Table-valued parameter struct used for SQLBindParameter, inheriting sqlsrv_param
|
||||
// *** A sqlsrv_param_tvp can be representing a table-valued parameter itself or one of
|
||||
// *** its constituent columns. When it is a table-valued parameter, tvp_columns cannot
|
||||
// *** be empty. When it is a TVP column, parent_tvp points to its table-valued parameter
|
||||
// *** and tvp_columns must be empty. The member param_pos refers to the ordinal position
|
||||
// *** of this column in the corresponding table type.
|
||||
struct sqlsrv_param_tvp : public sqlsrv_param
|
||||
{
|
||||
sqlsrv_param_tvp* parent_tvp; // For a TVP column to reference to the table-valued parameter. NULL if this is the TVP itself.
|
||||
std::vector<sqlsrv_param_tvp*> tvp_columns; // The constituent columns of the table-valued parameter
|
||||
int num_rows; // The total number of rows
|
||||
int current_row; // A counter to keep track of which row is to be processed
|
||||
|
||||
sqlsrv_param_tvp(_In_ SQLUSMALLINT param_num, _In_ SQLSRV_ENCODING enc, _In_ SQLSMALLINT sql_type, _In_ SQLULEN col_size, _In_ SQLSMALLINT dec_digits, _In_ sqlsrv_param_tvp* tvp) :
|
||||
sqlsrv_param(param_num, SQL_PARAM_INPUT, enc, sql_type, col_size, dec_digits), num_rows(0), current_row(0), parent_tvp(tvp)
|
||||
{
|
||||
ZVAL_UNDEF(&placeholder_z);
|
||||
}
|
||||
virtual ~sqlsrv_param_tvp() { release_data(); }
|
||||
virtual void release_data();
|
||||
virtual void bind_param(_Inout_ sqlsrv_stmt* stmt);
|
||||
virtual void process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
|
||||
|
||||
// The following methods are used to supply data to the server post execution
|
||||
virtual void init_data_from_zval(_Inout_ sqlsrv_stmt* stmt) {}
|
||||
virtual bool send_data_packet(_Inout_ sqlsrv_stmt* stmt);
|
||||
|
||||
// Change the column encoding based on the sql data type
|
||||
static void sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding);
|
||||
|
||||
// The following methods are only applicable to a table-valued parameter or its individual columns
|
||||
int parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z);
|
||||
void get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ SQLCHAR* table_type_name);
|
||||
void process_param_column_value(_Inout_ sqlsrv_stmt* stmt);
|
||||
void process_null_param_value(_Inout_ sqlsrv_stmt* stmt);
|
||||
void populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal);
|
||||
void send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z);
|
||||
};
|
||||
|
||||
// *** a container of all parameters used for SQLBindParameter ***
|
||||
struct sqlsrv_params_container
|
||||
{
|
||||
|
@ -1717,7 +1757,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
|
|||
_In_opt_ const stmt_option valid_stmt_opts[], _In_ error_callback const err, _In_opt_ void* driver );
|
||||
void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
|
||||
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
|
||||
_Inout_ SQLSMALLINT decimal_digits );
|
||||
_Inout_ SQLSMALLINT decimal_digits);
|
||||
SQLRETURN core_sqlsrv_execute( _Inout_ sqlsrv_stmt* stmt, _In_reads_bytes_(sql_len) const char* sql = NULL, _In_ int sql_len = 0 );
|
||||
field_meta_data* core_sqlsrv_field_metadata( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT colno );
|
||||
bool core_sqlsrv_fetch( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSMALLINT fetch_orientation, _In_ SQLULEN fetch_offset );
|
||||
|
@ -1974,6 +2014,15 @@ enum SQLSRV_ERROR_CODES {
|
|||
SQLSRV_ERROR_DATA_CLASSIFICATION_PRE_EXECUTION,
|
||||
SQLSRV_ERROR_DATA_CLASSIFICATION_NOT_AVAILABLE,
|
||||
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
|
||||
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
|
||||
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
|
||||
SQLSRV_ERROR_TVP_FETCH_METADATA,
|
||||
SQLSRV_ERROR_TVP_INVALID_INPUTS,
|
||||
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
|
||||
SQLSRV_ERROR_TVP_STRING_KEYS,
|
||||
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
|
||||
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
|
||||
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
|
||||
|
||||
// Driver specific error codes starts from here.
|
||||
SQLSRV_ERROR_DRIVER_SPECIFIC = 1000,
|
||||
|
|
|
@ -109,6 +109,7 @@ stmt_option const* get_stmt_option( sqlsrv_conn const* conn, _In_ zend_ulong key
|
|||
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
|
||||
void adjustDecimalPrecision(_Inout_ zval* param_z, _In_ SQLSMALLINT decimal_digits);
|
||||
bool is_a_numeric_type(_In_ SQLSMALLINT sql_type);
|
||||
bool is_a_string_type(_In_ SQLSMALLINT sql_type);
|
||||
}
|
||||
|
||||
// constructor for sqlsrv_stmt. Here so that we can use functions declared earlier.
|
||||
|
@ -362,7 +363,7 @@ sqlsrv_stmt* core_sqlsrv_create_stmt( _Inout_ sqlsrv_conn* conn, _In_ driver_stm
|
|||
|
||||
void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_num, _In_ SQLSMALLINT direction, _Inout_ zval* param_z,
|
||||
_In_ SQLSRV_PHPTYPE php_out_type, _Inout_ SQLSRV_ENCODING encoding, _Inout_ SQLSMALLINT sql_type, _Inout_ SQLULEN column_size,
|
||||
_Inout_ SQLSMALLINT decimal_digits )
|
||||
_Inout_ SQLSMALLINT decimal_digits)
|
||||
{
|
||||
// check is only < because params are 0 based
|
||||
CHECK_CUSTOM_ERROR(param_num >= SQL_SERVER_MAX_PARAMS, stmt, SQLSRV_ERROR_MAX_PARAMS_EXCEEDED, param_num + 1) {
|
||||
|
@ -380,7 +381,12 @@ void core_sqlsrv_bind_param( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT param_
|
|||
if (param_ptr == NULL) {
|
||||
sqlsrv_malloc_auto_ptr<sqlsrv_param> new_param;
|
||||
if (direction == SQL_PARAM_INPUT) {
|
||||
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param))) sqlsrv_param(param_num, direction, encoding, sql_type, column_size, decimal_digits);
|
||||
// Check if it's a Table-Valued Parameter first
|
||||
if (Z_TYPE_P(param_z) == IS_ARRAY) {
|
||||
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(param_num, encoding, SQL_SS_TABLE, 0, 0, NULL);
|
||||
} else {
|
||||
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param))) sqlsrv_param(param_num, direction, encoding, sql_type, column_size, decimal_digits);
|
||||
}
|
||||
} else if (direction == SQL_PARAM_OUTPUT || direction == SQL_PARAM_INPUT_OUTPUT) {
|
||||
new_param = new (sqlsrv_malloc(sizeof(sqlsrv_param_inout))) sqlsrv_param_inout(param_num, direction, encoding, sql_type, column_size, decimal_digits, php_out_type);
|
||||
} else {
|
||||
|
@ -1234,6 +1240,28 @@ bool is_a_numeric_type(_In_ SQLSMALLINT sql_type)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool is_a_string_type(_In_ SQLSMALLINT sql_type)
|
||||
{
|
||||
switch (sql_type) {
|
||||
case SQL_BIGINT:
|
||||
case SQL_DECIMAL:
|
||||
case SQL_NUMERIC:
|
||||
case SQL_SS_VARIANT:
|
||||
case SQL_SS_UDT:
|
||||
case SQL_GUID:
|
||||
case SQL_SS_XML:
|
||||
case SQL_CHAR:
|
||||
case SQL_WCHAR:
|
||||
case SQL_VARCHAR:
|
||||
case SQL_WVARCHAR:
|
||||
case SQL_LONGVARCHAR:
|
||||
case SQL_WLONGVARCHAR:
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void calc_string_size( _Inout_ sqlsrv_stmt* stmt, _In_ SQLUSMALLINT field_index, _In_ SQLLEN sql_type, _Inout_ SQLLEN& size )
|
||||
{
|
||||
try {
|
||||
|
@ -1906,6 +1934,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
|
|||
case SQLSRV_PHPTYPE_INT:
|
||||
case SQLSRV_PHPTYPE_FLOAT:
|
||||
case SQLSRV_PHPTYPE_DATETIME:
|
||||
case SQLSRV_PHPTYPE_TABLE:
|
||||
return true;
|
||||
case SQLSRV_PHPTYPE_STRING:
|
||||
case SQLSRV_PHPTYPE_STREAM:
|
||||
|
@ -2326,7 +2355,7 @@ bool sqlsrv_param::derive_string_types_sizes(_In_ zval* param_z)
|
|||
return is_numeric;
|
||||
}
|
||||
|
||||
void sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
bool sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
{
|
||||
// This converts the string in param_z and stores the wide string in the member placeholder_z
|
||||
char* str = Z_STRVAL_P(param_z);
|
||||
|
@ -2337,8 +2366,8 @@ void sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
unsigned int wchar_size = 0;
|
||||
|
||||
wide_buffer = utf16_string_from_mbcs_string(encoding, reinterpret_cast<const char*>(str), static_cast<int>(str_length), &wchar_size, true);
|
||||
CHECK_CUSTOM_ERROR(wide_buffer == 0, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
if (wide_buffer == 0) {
|
||||
return false;
|
||||
}
|
||||
wide_buffer[wchar_size] = L'\0';
|
||||
core::sqlsrv_zval_stringl(&placeholder_z, reinterpret_cast<char*>(wide_buffer.get()), wchar_size * sizeof(SQLWCHAR));
|
||||
|
@ -2346,6 +2375,8 @@ void sqlsrv_param::convert_input_str_to_utf16(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
// If the string is empty, then nothing needs to be done
|
||||
core::sqlsrv_zval_stringl(&placeholder_z, "", 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sqlsrv_param::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
|
||||
|
@ -2367,7 +2398,10 @@ void sqlsrv_param::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
|
|||
throw core::CoreException();
|
||||
}
|
||||
// This changes the member placeholder_z to hold the wide string
|
||||
convert_input_str_to_utf16(stmt, param_z);
|
||||
bool converted = convert_input_str_to_utf16(stmt, param_z);
|
||||
CHECK_CUSTOM_ERROR(!converted, stmt, SQLSRV_ERROR_INPUT_PARAM_ENCODING_TRANSLATE, param_pos + 1, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Bind the wide string in placeholder_z
|
||||
buffer = Z_STRVAL(placeholder_z);
|
||||
|
@ -2426,7 +2460,7 @@ void sqlsrv_param::process_resource_param(_Inout_ zval* param_z)
|
|||
strlen_or_indptr = SQL_DATA_AT_EXEC;
|
||||
}
|
||||
|
||||
void sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
bool sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
{
|
||||
// This changes the member placeholder_z to hold the converted string of the datetime object
|
||||
zval function_z;
|
||||
|
@ -2440,23 +2474,23 @@ void sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
// meaning there is too much information in the character string. If the user specifies the 'datetimeoffset'
|
||||
// sql type, it lacks the timezone.
|
||||
if (sql_data_type == SQL_SS_TIMESTAMPOFFSET) {
|
||||
core::sqlsrv_zval_stringl(&format_z, const_cast<char*>(DateTime::DATETIMEOFFSET_FORMAT),
|
||||
DateTime::DATETIMEOFFSET_FORMAT_LEN);
|
||||
ZVAL_STRINGL(&format_z, DateTime::DATETIMEOFFSET_FORMAT, DateTime::DATETIMEOFFSET_FORMAT_LEN);
|
||||
} else if (sql_data_type == SQL_TYPE_DATE) {
|
||||
core::sqlsrv_zval_stringl(&format_z, const_cast<char*>(DateTime::DATE_FORMAT), DateTime::DATE_FORMAT_LEN);
|
||||
ZVAL_STRINGL(&format_z, DateTime::DATE_FORMAT, DateTime::DATE_FORMAT_LEN);
|
||||
} else {
|
||||
core::sqlsrv_zval_stringl(&format_z, const_cast<char*>(DateTime::DATETIME_FORMAT), DateTime::DATETIME_FORMAT_LEN);
|
||||
ZVAL_STRINGL(&format_z, DateTime::DATETIME_FORMAT, DateTime::DATETIME_FORMAT_LEN);
|
||||
}
|
||||
|
||||
// call the DateTime::format member function to convert the object to a string that SQL Server understands
|
||||
core::sqlsrv_zval_stringl(&function_z, "format", sizeof("format") - 1);
|
||||
ZVAL_STRINGL(&function_z, "format", sizeof("format") - 1);
|
||||
//core::sqlsrv_zval_stringl(&function_z, "format", sizeof("format") - 1);
|
||||
params[0] = format_z;
|
||||
|
||||
// If placeholder_z is a string, release it first before assigning a new string value
|
||||
if (Z_TYPE(placeholder_z) == IS_STRING && Z_STR(placeholder_z) != NULL) {
|
||||
zend_string_release(Z_STR(placeholder_z));
|
||||
}
|
||||
|
||||
|
||||
// This is equivalent to the PHP code: $param_z->format($format_z); where param_z is the
|
||||
// DateTime object and $format_z is the format string.
|
||||
int zr = call_user_function(EG(function_table), param_z, &function_z, &placeholder_z, 1, params);
|
||||
|
@ -2464,12 +2498,10 @@ void sqlsrv_param::convert_datetime_to_string(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
zend_string_release(Z_STR(format_z));
|
||||
zend_string_release(Z_STR(function_z));
|
||||
|
||||
CHECK_CUSTOM_ERROR(zr == FAILURE, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
return (zr != FAILURE);
|
||||
}
|
||||
|
||||
void sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
bool sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zval* param_z)
|
||||
{
|
||||
bool valid_class_name_found = false;
|
||||
zend_class_entry *class_entry = Z_OBJCE_P(param_z);
|
||||
|
@ -2486,8 +2518,8 @@ void sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
}
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR(!valid_class_name_found, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
if (!valid_class_name_found) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Derive the param SQL type only if it is unknown
|
||||
|
@ -2514,6 +2546,8 @@ void sqlsrv_param::preprocess_datetime_object(_Inout_ sqlsrv_stmt* stmt, _In_ zv
|
|||
decimal_digits = SQL_SERVER_2008_DEFAULT_DATETIME_SCALE;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void sqlsrv_param::process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
|
||||
|
@ -2521,8 +2555,13 @@ void sqlsrv_param::process_object_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval*
|
|||
// Assume the param refers to a DateTime object since it's the only type the drivers support.
|
||||
// Verification occurs in the calling function as the drivers convert the DateTime object
|
||||
// to a string before sending it to the server.
|
||||
preprocess_datetime_object(stmt, param_z);
|
||||
convert_datetime_to_string(stmt, param_z);
|
||||
bool succeeded = preprocess_datetime_object(stmt, param_z);
|
||||
if (succeeded) {
|
||||
succeeded = convert_datetime_to_string(stmt, param_z);
|
||||
}
|
||||
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
buffer = Z_STRVAL(placeholder_z);
|
||||
buffer_length = Z_STRLEN(placeholder_z) - 1;
|
||||
|
@ -2745,7 +2784,7 @@ void sqlsrv_param_inout::process_string_param(_Inout_ sqlsrv_stmt* stmt, _Inout_
|
|||
}
|
||||
|
||||
// If it's a UTF-8 input output parameter (signified by the C type being SQL_C_WCHAR)
|
||||
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXTSQL type,
|
||||
// or if the PHP type is a binary encoded string with a N(VAR)CHAR/NTEXT SQL type,
|
||||
// convert it to wchar first
|
||||
if (direction == SQL_PARAM_INPUT_OUTPUT &&
|
||||
(c_data_type == SQL_C_WCHAR ||
|
||||
|
@ -2890,7 +2929,7 @@ void sqlsrv_param_inout::finalize_output_string()
|
|||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
|
||||
// For ODBC 11+ see https://docs.microsoft.com/sql/relational-databases/native-client/features/odbc-driver-behavior-change-when-handling-character-conversions
|
||||
// A length value of SQL_NO_TOTAL for SQLBindParameter indicates that the buffer contains data up to the
|
||||
// original buffer_length and is NULL terminated.
|
||||
// The IF statement can be true when using connection pooling with unixODBC 2.3.4.
|
||||
|
@ -2959,8 +2998,8 @@ void sqlsrv_param_inout::resize_output_string_buffer(_Inout_ zval* param_z, _In_
|
|||
|
||||
// With AE enabled, column_size is already retrieved from SQLDescribeParam, but column_size
|
||||
// does not include the negative sign or decimal place for numeric values
|
||||
// Without AE, the same can happen as well, in particular with decimals and numerics
|
||||
// with precision/scale specified (See VSO Bug 2913 for details)
|
||||
// VSO Bug 2913: without AE, the same can happen as well, in particular to decimals
|
||||
// and numerics with precision/scale specified
|
||||
if (is_numeric_type) {
|
||||
// Include the possible negative sign
|
||||
field_size += elem_size;
|
||||
|
@ -3018,6 +3057,577 @@ void sqlsrv_param_inout::resize_output_string_buffer(_Inout_ zval* param_z, _In_
|
|||
}
|
||||
}
|
||||
|
||||
// Change the column encoding based on the sql data type
|
||||
/*static*/ void sqlsrv_param_tvp::sql_type_to_encoding(_In_ SQLSMALLINT sql_type, _Inout_ SQLSRV_ENCODING* encoding)
|
||||
{
|
||||
switch (sql_type) {
|
||||
case SQL_BIGINT:
|
||||
case SQL_DECIMAL:
|
||||
case SQL_NUMERIC:
|
||||
case SQL_BIT:
|
||||
case SQL_INTEGER:
|
||||
case SQL_SMALLINT:
|
||||
case SQL_TINYINT:
|
||||
case SQL_FLOAT:
|
||||
case SQL_REAL:
|
||||
*encoding = SQLSRV_ENCODING_CHAR;
|
||||
break;
|
||||
case SQL_BINARY:
|
||||
case SQL_LONGVARBINARY:
|
||||
case SQL_VARBINARY:
|
||||
case SQL_SS_UDT:
|
||||
*encoding = SQLSRV_ENCODING_BINARY;
|
||||
break;
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::get_tvp_metadata(_In_ sqlsrv_stmt* stmt, _In_ SQLCHAR* table_type_name)
|
||||
{
|
||||
SQLHANDLE chstmt = SQL_NULL_HANDLE;
|
||||
SQLRETURN rc;
|
||||
SQLSMALLINT data_type, dec_digits;
|
||||
SQLINTEGER col_size;
|
||||
SQLLEN cb_data_type, cb_col_size, cb_dec_digits;
|
||||
|
||||
core::SQLAllocHandle(SQL_HANDLE_STMT, *(stmt->conn), &chstmt);
|
||||
|
||||
rc = SQLSetStmtAttr(chstmt, SQL_SOPT_SS_NAME_SCOPE, (SQLPOINTER)SQL_SS_NAME_SCOPE_TABLE_TYPE, SQL_IS_UINTEGER);
|
||||
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Check table type name and see if the schema is specified. Otherwise, assume DBO
|
||||
std::string type_name(reinterpret_cast<char *>(table_type_name));
|
||||
std::size_t pos = type_name.find_first_of(".");
|
||||
if (pos != std::string::npos) {
|
||||
std::string str1 = type_name.substr(0, pos);
|
||||
std::string str2 = type_name.substr(pos + 1);
|
||||
|
||||
char schema[SS_MAXCOLNAMELEN] = { '\0' };
|
||||
char type[SS_MAXCOLNAMELEN] = { '\0' };
|
||||
|
||||
strcpy_s(schema, SS_MAXCOLNAMELEN, str1.c_str());
|
||||
strcpy_s(type, SS_MAXCOLNAMELEN, str2.c_str());
|
||||
|
||||
rc = SQLColumns(chstmt, NULL, 0, reinterpret_cast<SQLCHAR *>(schema), SQL_NTS, reinterpret_cast<SQLCHAR *>(type), SQL_NTS, NULL, 0);
|
||||
} else {
|
||||
rc = SQLColumns(chstmt, NULL, 0, NULL, 0, table_type_name, SQL_NTS, NULL, 0);
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR(!SQL_SUCCEEDED(rc), stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
SQLSRV_ENCODING stmt_encoding = (stmt->encoding() == SQLSRV_ENCODING_DEFAULT) ? stmt->conn->encoding() : stmt->encoding();
|
||||
|
||||
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO) {
|
||||
SQLBindCol(chstmt, 5, SQL_C_SSHORT, &data_type, 0, &cb_data_type);
|
||||
SQLBindCol(chstmt, 7, SQL_C_SLONG, &col_size, 0, &cb_col_size);
|
||||
SQLBindCol(chstmt, 9, SQL_C_SSHORT, &dec_digits, 0, &cb_dec_digits);
|
||||
|
||||
SQLUSMALLINT pos = 0;
|
||||
while (SQL_SUCCESS == rc) {
|
||||
rc = SQLFetch(chstmt);
|
||||
if (rc == SQL_NO_DATA) {
|
||||
CHECK_CUSTOM_ERROR(tvp_columns.size() == 0, stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
sqlsrv_malloc_auto_ptr<sqlsrv_param_tvp> param_ptr;
|
||||
|
||||
// The SQL data type is used to derive the column encoding
|
||||
SQLSRV_ENCODING column_encoding = stmt_encoding;
|
||||
sql_type_to_encoding(data_type, &column_encoding);
|
||||
|
||||
param_ptr = new (sqlsrv_malloc(sizeof(sqlsrv_param_tvp))) sqlsrv_param_tvp(pos, column_encoding, data_type, col_size, dec_digits, this);
|
||||
param_ptr->num_rows = this->num_rows; // Each column inherits the number of rows from the TVP
|
||||
|
||||
tvp_columns.push_back(param_ptr);
|
||||
param_ptr.transferred();
|
||||
|
||||
pos++;
|
||||
}
|
||||
} else {
|
||||
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_FETCH_METADATA, param_pos + 1);
|
||||
}
|
||||
|
||||
SQLCloseCursor(chstmt);
|
||||
SQLFreeHandle(SQL_HANDLE_STMT, chstmt);
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::release_data()
|
||||
{
|
||||
// Clean up tvp_columns as well
|
||||
for (int i = 0; i < tvp_columns.size(); i++) {
|
||||
tvp_columns[i]->release_data();
|
||||
sqlsrv_free(tvp_columns[i]);
|
||||
}
|
||||
|
||||
sqlsrv_param::release_data();
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::process_param(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
|
||||
{
|
||||
if (sql_data_type == SQL_SS_TABLE) {
|
||||
// This is a table-valued parameter
|
||||
param_php_type = IS_ARRAY;
|
||||
c_data_type = SQL_C_DEFAULT;
|
||||
|
||||
// The decimal_digits must be 0 for TVP
|
||||
decimal_digits = 0;
|
||||
|
||||
// The column_size for a TVP is the row array size
|
||||
// The following method will verify the input array and also derive num_rows
|
||||
this->num_rows = 0;
|
||||
int num_columns = parse_tv_param_arrays(stmt, param_z);
|
||||
column_size = num_rows;
|
||||
|
||||
buffer = NULL;
|
||||
buffer_length = NULL;
|
||||
strlen_or_indptr = (num_columns == 0)? SQL_DEFAULT_PARAM : SQL_DATA_AT_EXEC;
|
||||
} else {
|
||||
// This is one of the constituent columns of the table-valued parameter
|
||||
// The column value of the first row is already saved in member variable param_ptr_z
|
||||
process_param_column_value(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
int sqlsrv_param_tvp::parse_tv_param_arrays(_Inout_ sqlsrv_stmt* stmt, _Inout_ zval* param_z)
|
||||
{
|
||||
// If this is not a table-valued parameter, simply return
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This method verifies if the table-valued parameter (i.e. param_z) provided by the user is valid.
|
||||
// The number of columns in the given table-valued parameter is returned, which may be zero.
|
||||
HashTable* inputs_ht = Z_ARRVAL_P(param_z);
|
||||
zend_string *tvp_name = NULL;
|
||||
zval *tvp_data_z = NULL;
|
||||
HashPosition pos;
|
||||
|
||||
zend_hash_internal_pointer_reset_ex(inputs_ht, &pos);
|
||||
if (zend_hash_has_more_elements_ex(inputs_ht, &pos) == SUCCESS) {
|
||||
|
||||
zend_ulong num_index = -1;
|
||||
size_t key_len = 0;
|
||||
|
||||
int key_type = zend_hash_get_current_key(inputs_ht, &tvp_name, &num_index);
|
||||
if (key_type == HASH_KEY_IS_STRING) {
|
||||
key_len = ZSTR_LEN(tvp_name);
|
||||
tvp_data_z = zend_hash_get_current_data_ex(inputs_ht, &pos);
|
||||
}
|
||||
|
||||
CHECK_CUSTOM_ERROR((key_type == HASH_KEY_IS_LONG || key_len == 0), stmt, SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Find the docs page somewhere that says a TVP can not be null but it may have null columns??
|
||||
CHECK_CUSTOM_ERROR(tvp_data_z == NULL || Z_TYPE_P(tvp_data_z) == IS_NULL || Z_TYPE_P(tvp_data_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_INVALID_INPUTS, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Save the TVP multi-dim array data, which should be something like this
|
||||
// [
|
||||
// [r1c1, r1c2, r1c3],
|
||||
// [r2c1, r2c2, r2c3],
|
||||
// [r3c1, r3c2, r3c3]
|
||||
// ]
|
||||
param_ptr_z = tvp_data_z;
|
||||
HashTable* rows_ht = Z_ARRVAL_P(tvp_data_z);
|
||||
this->num_rows = zend_hash_num_elements(rows_ht);
|
||||
if (this->num_rows == 0) {
|
||||
// TVP has no data
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Given the table type name, get its column meta data next
|
||||
size_t total_num_columns = 0;
|
||||
get_tvp_metadata(stmt, reinterpret_cast<SQLCHAR*>(ZSTR_VAL(tvp_name)));
|
||||
total_num_columns = tvp_columns.size();
|
||||
|
||||
// (1) Is the array empty?
|
||||
// (2) Check individual rows and see if their sizes are consistent?
|
||||
zend_ulong id = -1;
|
||||
zend_string *key = NULL;
|
||||
zval* row_z = NULL;
|
||||
int num_columns = 0;
|
||||
int type = HASH_KEY_NON_EXISTENT;
|
||||
|
||||
// Loop through the rows to check the number of columns
|
||||
ZEND_HASH_FOREACH_KEY_VAL(rows_ht, id, key, row_z) {
|
||||
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
||||
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
}
|
||||
|
||||
// Individual row must be an array
|
||||
CHECK_CUSTOM_ERROR(Z_TYPE_P(row_z) != IS_ARRAY, stmt, SQLSRV_ERROR_TVP_ROW_NOT_ARRAY, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Are all the TVP's rows the same size
|
||||
num_columns = zend_hash_num_elements(Z_ARRVAL_P(row_z));
|
||||
CHECK_CUSTOM_ERROR(num_columns != total_num_columns, stmt, SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE, param_pos + 1, total_num_columns) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
// Return the number of columns
|
||||
return num_columns;
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::process_param_column_value(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
// This is one of the constituent columns of the table-valued parameter
|
||||
// The corresponding column value of the TVP's first row is already saved in
|
||||
// the member variable param_ptr_z, which may be a NULL value
|
||||
zval *data_z = param_ptr_z;
|
||||
param_php_type = is_a_string_type(sql_data_type) ? IS_STRING : Z_TYPE_P(data_z);
|
||||
|
||||
switch (param_php_type) {
|
||||
case IS_TRUE:
|
||||
case IS_FALSE:
|
||||
case IS_LONG:
|
||||
case IS_DOUBLE:
|
||||
sqlsrv_param::process_param(stmt, data_z);
|
||||
buffer = &placeholder_z.value; // use placeholder zval for binding later
|
||||
break;
|
||||
case IS_RESOURCE:
|
||||
sqlsrv_param::process_resource_param(data_z);
|
||||
break;
|
||||
case IS_STRING:
|
||||
case IS_OBJECT:
|
||||
if (param_php_type == IS_STRING) {
|
||||
derive_string_types_sizes(data_z);
|
||||
} else {
|
||||
// If preprocessing a datetime object fails, throw an error of invalid php type
|
||||
bool succeeded = preprocess_datetime_object(stmt, data_z);
|
||||
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
}
|
||||
buffer = reinterpret_cast<SQLPOINTER>(this);
|
||||
buffer_length = 0;
|
||||
strlen_or_indptr = SQL_DATA_AT_EXEC;
|
||||
break;
|
||||
case IS_NULL:
|
||||
process_null_param_value(stmt);
|
||||
break;
|
||||
default:
|
||||
THROW_CORE_ERROR(stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// Release the reference
|
||||
param_ptr_z = NULL;
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::process_null_param_value(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
// This is one of the constituent columns of the table-valued parameter
|
||||
// This method is called when the corresponding column value of the TVP's first row is NULL
|
||||
// So keep looking in the subsequent rows and find the first non-NULL value in the same column
|
||||
HashTable* rows_ht = Z_ARRVAL_P(parent_tvp->param_ptr_z);
|
||||
zval* row_z = NULL;
|
||||
zval* value_z = NULL;
|
||||
int php_type = IS_NULL;
|
||||
int row_id = 1; // Start from the second row
|
||||
|
||||
while ((row_z = zend_hash_index_find(rows_ht, row_id++)) != NULL) {
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
}
|
||||
|
||||
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
|
||||
php_type = Z_TYPE_P(value_z);
|
||||
if (php_type != IS_NULL) {
|
||||
// Save this non-NULL value before calling process_param_column_value()
|
||||
param_ptr_z = value_z;
|
||||
process_param_column_value(stmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (php_type == IS_NULL) {
|
||||
// This means that the entire column contains nothing but NULLs
|
||||
sqlsrv_param::process_null_param(param_ptr_z);
|
||||
}
|
||||
}
|
||||
|
||||
void sqlsrv_param_tvp::bind_param(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
core::SQLBindParameter(stmt, param_pos + 1, direction, c_data_type, sql_data_type, column_size, decimal_digits, buffer, buffer_length, &strlen_or_indptr);
|
||||
|
||||
// No need to continue if this is one of the constituent columns of the table-valued parameter
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (num_rows == 0) {
|
||||
// TVP has no data
|
||||
return;
|
||||
}
|
||||
|
||||
// Bind the TVP columns one by one
|
||||
// Register this object first using SQLSetDescField() for sending TVP data post execution
|
||||
SQLHDESC desc;
|
||||
core::SQLGetStmtAttr(stmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
|
||||
SQLRETURN r = ::SQLSetDescField(desc, param_pos + 1, SQL_DESC_DATA_PTR, reinterpret_cast<SQLPOINTER>(this), 0);
|
||||
CHECK_SQL_ERROR_OR_WARNING(r, stmt) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// First set focus on this parameter
|
||||
size_t ordinal = param_pos + 1;
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(ordinal), SQL_IS_INTEGER);
|
||||
|
||||
// Bind the TVP columns
|
||||
SQLSRV_ENCODING stmt_encoding = (stmt->encoding() == SQLSRV_ENCODING_DEFAULT) ? stmt->conn->encoding() : stmt->encoding();
|
||||
HashTable* rows_ht = Z_ARRVAL_P(param_ptr_z);
|
||||
zval* row_z = zend_hash_index_find(rows_ht, 0);
|
||||
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
}
|
||||
|
||||
HashTable* cols_ht = Z_ARRVAL_P(row_z);
|
||||
zend_ulong id = -1;
|
||||
zend_string *key = NULL;
|
||||
zval* data_z = NULL;
|
||||
int num_columns = 0;
|
||||
|
||||
// In case there are null values in the first row, have to loop
|
||||
// through the entire first row of column values using the Zend macros.
|
||||
ZEND_HASH_FOREACH_KEY_VAL(cols_ht, id, key, data_z) {
|
||||
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
||||
CHECK_CUSTOM_ERROR(type == HASH_KEY_IS_STRING, stmt, SQLSRV_ERROR_TVP_STRING_KEYS, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Assume the user has supplied data for all columns in the right order
|
||||
SQLUSMALLINT pos = static_cast<SQLUSMALLINT>(id);
|
||||
sqlsrv_param* column_param = tvp_columns[pos];
|
||||
SQLSRV_ASSERT(column_param != NULL, "sqlsrv_param_tvp::bind_param -- column param should not be null");
|
||||
|
||||
// If data_z is NULL, will need to keep looking in the subsequent rows of
|
||||
// the same column until a non-null value is found. Since Zend macros must be
|
||||
// used to traverse the array items, nesting Zend macros in different directions
|
||||
// does not work.
|
||||
// Therefore, save data_z for later processing and binding.
|
||||
column_param->param_ptr_z = data_z;
|
||||
num_columns++;
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
|
||||
// Process the columns and bind each of them using the saved data
|
||||
for (int i = 0; i < num_columns; i++) {
|
||||
sqlsrv_param* column_param = tvp_columns[i];
|
||||
|
||||
column_param->process_param(stmt, NULL);
|
||||
column_param->bind_param(stmt);
|
||||
}
|
||||
|
||||
// Reset focus
|
||||
core::SQLSetStmtAttr(stmt, SQL_SOPT_SS_PARAM_FOCUS, reinterpret_cast<SQLPOINTER>(0), SQL_IS_INTEGER);
|
||||
}
|
||||
|
||||
// For each of the constituent columns of the table-valued parameter, check its PHP type
|
||||
// For pure scalar types, map the cell value (based on current_row and ordinal) to the
|
||||
// member placeholder_z
|
||||
void sqlsrv_param_tvp::populate_cell_placeholder(_Inout_ sqlsrv_stmt* stmt, _In_ int ordinal)
|
||||
{
|
||||
if (sql_data_type == SQL_SS_TABLE || ordinal >= num_rows) {
|
||||
return;
|
||||
}
|
||||
|
||||
zval* row_z = NULL;
|
||||
HashTable* values_ht = NULL;
|
||||
zval* value_z = NULL;
|
||||
int type = IS_NULL;
|
||||
|
||||
switch (param_php_type) {
|
||||
case IS_TRUE:
|
||||
case IS_FALSE:
|
||||
case IS_LONG:
|
||||
case IS_DOUBLE:
|
||||
// Find the row from the TVP data based on ordinal
|
||||
row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), ordinal);
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
}
|
||||
// Now find the column value based on param_pos
|
||||
value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
|
||||
type = Z_TYPE_P(value_z);
|
||||
|
||||
// First check if value_z is NULL
|
||||
if (type == IS_NULL) {
|
||||
ZVAL_NULL(&placeholder_z);
|
||||
strlen_or_indptr = SQL_NULL_DATA;
|
||||
} else {
|
||||
// Once the placeholder is bound with the correct value from the array, update current_row
|
||||
if (param_php_type == IS_DOUBLE) {
|
||||
if (type != IS_DOUBLE) {
|
||||
// If value_z type is different from param_php_type convert first
|
||||
convert_to_double(value_z);
|
||||
}
|
||||
strlen_or_indptr = sizeof(Z_DVAL_P(value_z));
|
||||
ZVAL_DOUBLE(&placeholder_z, Z_DVAL_P(value_z));
|
||||
} else {
|
||||
if (type != IS_LONG) {
|
||||
// If value_z type is different from param_php_type convert first
|
||||
// Even for boolean values
|
||||
convert_to_long(value_z);
|
||||
}
|
||||
strlen_or_indptr = sizeof(Z_LVAL_P(value_z));
|
||||
ZVAL_LONG(&placeholder_z, Z_LVAL_P(value_z));
|
||||
}
|
||||
}
|
||||
current_row++;
|
||||
break;
|
||||
default:
|
||||
// Do nothing for non-scalar types
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is the table-valued parameter, loop through each parameter column
|
||||
// and populate the cell's placeholder_z.
|
||||
// If this is one of the constituent columns of the table-valued parameter,
|
||||
// call SQLPutData() to send the cell value to the server (based on current_row
|
||||
// and param_pos)
|
||||
bool sqlsrv_param_tvp::send_data_packet(_Inout_ sqlsrv_stmt* stmt)
|
||||
{
|
||||
if (sql_data_type != SQL_SS_TABLE) {
|
||||
// This is one of the constituent columns of the table-valued parameter
|
||||
// Check current_row first
|
||||
if (current_row >= num_rows) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Find the row from the TVP data based on current_row
|
||||
zval* row_z = zend_hash_index_find(Z_ARRVAL_P(parent_tvp->param_ptr_z), current_row);
|
||||
if (Z_ISREF_P(row_z)) {
|
||||
ZVAL_DEREF(row_z);
|
||||
}
|
||||
// Now find the column value based on param_pos
|
||||
zval* value_z = zend_hash_index_find(Z_ARRVAL_P(row_z), param_pos);
|
||||
|
||||
// First check if value_z is NULL
|
||||
if (Z_TYPE_P(value_z) == IS_NULL) {
|
||||
core::SQLPutData(stmt, NULL, SQL_NULL_DATA);
|
||||
current_row++;
|
||||
} else {
|
||||
switch (param_php_type) {
|
||||
case IS_RESOURCE:
|
||||
{
|
||||
num_bytes_read = 0;
|
||||
param_stream = NULL;
|
||||
|
||||
// Get the stream from the zval value
|
||||
core::sqlsrv_php_stream_from_zval_no_verify(*stmt, param_stream, value_z);
|
||||
// Keep sending the packets until EOF is reached
|
||||
while (sqlsrv_param::send_data_packet(stmt)) {
|
||||
}
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
case IS_OBJECT:
|
||||
{
|
||||
// This method updates placeholder_z as a string
|
||||
bool succeeded = convert_datetime_to_string(stmt, value_z);
|
||||
|
||||
// Conversion failed so assume the input was an invalid PHP type
|
||||
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE, parent_tvp->param_pos + 1, param_pos + 1) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
core::SQLPutData(stmt, Z_STRVAL(placeholder_z), SQL_NTS);
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
case IS_STRING:
|
||||
{
|
||||
int type = Z_TYPE_P(value_z);
|
||||
if (type != IS_STRING) {
|
||||
convert_to_string(value_z);
|
||||
}
|
||||
SQLLEN value_len = Z_STRLEN_P(value_z);
|
||||
if (value_len == 0) {
|
||||
// If it's an empty string
|
||||
core::SQLPutData(stmt, Z_STRVAL_P(value_z), 0);
|
||||
} else {
|
||||
if (encoding == CP_UTF8 && !is_a_numeric_type(sql_data_type)) {
|
||||
if (value_len > INT_MAX) {
|
||||
LOG(SEV_ERROR, "Convert input parameter to utf16: buffer length exceeded.");
|
||||
throw core::CoreException();
|
||||
}
|
||||
// This method would change the member placeholder_z
|
||||
bool succeeded = convert_input_str_to_utf16(stmt, value_z);
|
||||
CHECK_CUSTOM_ERROR(!succeeded, stmt, SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE, parent_tvp->param_pos + 1, param_pos + 1, get_last_error_message()) {
|
||||
throw core::CoreException();
|
||||
}
|
||||
|
||||
send_string_data_in_batches(stmt, &placeholder_z);
|
||||
} else {
|
||||
send_string_data_in_batches(stmt, value_z);
|
||||
}
|
||||
}
|
||||
current_row++;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Do nothing for basic types as they should be processed elsewhere
|
||||
break;
|
||||
}
|
||||
} // else not IS_NULL
|
||||
} else {
|
||||
// This is the table-valued parameter
|
||||
if (current_row < num_rows) {
|
||||
// Loop through the table parameter columns and populate each cell's placeholder whenever applicable
|
||||
for (size_t i = 0; i < tvp_columns.size(); i++) {
|
||||
tvp_columns[i]->populate_cell_placeholder(stmt, current_row);
|
||||
}
|
||||
|
||||
// This indicates a TVP row is available
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(1), 1);
|
||||
current_row++;
|
||||
} else {
|
||||
// This indicates there is no more TVP row
|
||||
core::SQLPutData(stmt, reinterpret_cast<SQLPOINTER>(0), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Return false to indicate that the current row has been sent
|
||||
return false;
|
||||
}
|
||||
|
||||
// A helper method for sending large string data in batches
|
||||
void sqlsrv_param_tvp::send_string_data_in_batches(_Inout_ sqlsrv_stmt* stmt, _In_ zval* value_z)
|
||||
{
|
||||
SQLLEN len = Z_STRLEN_P(value_z);
|
||||
SQLLEN batch = (encoding == CP_UTF8) ? PHP_STREAM_BUFFER_SIZE / sizeof(SQLWCHAR) : PHP_STREAM_BUFFER_SIZE;
|
||||
|
||||
char* p = Z_STRVAL_P(value_z);
|
||||
while (len > batch) {
|
||||
core::SQLPutData(stmt, p, batch);
|
||||
len -= batch;
|
||||
p += batch;
|
||||
}
|
||||
|
||||
// Put final batch
|
||||
core::SQLPutData(stmt, p, len);
|
||||
}
|
||||
|
||||
void sqlsrv_params_container::clean_up_param_data(_In_opt_ bool only_input/* = false*/)
|
||||
{
|
||||
current_param = NULL;
|
||||
|
@ -3051,7 +3661,7 @@ sqlsrv_param* sqlsrv_params_container::find_param(_In_ SQLUSMALLINT param_num, _
|
|||
} else {
|
||||
return output_params.at(param_num);
|
||||
}
|
||||
} catch (std::out_of_range& e) {
|
||||
} catch (std::out_of_range&) {
|
||||
// not found
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
@ -237,8 +237,8 @@ void convert_datetime_string_to_zval(_Inout_ sqlsrv_stmt* stmt, _In_opt_ char* i
|
|||
ZVAL_UNDEF(params);
|
||||
|
||||
// Convert the datetime string to a PHP DateTime object
|
||||
core::sqlsrv_zval_stringl(&value_temp_z, input, length);
|
||||
core::sqlsrv_zval_stringl(&function_z, "date_create", sizeof("date_create") - 1);
|
||||
ZVAL_STRINGL(&value_temp_z, input, length);
|
||||
ZVAL_STRINGL(&function_z, "date_create", sizeof("date_create") - 1);
|
||||
params[0] = value_temp_z;
|
||||
|
||||
if (call_user_function(EG(function_table), NULL, &function_z, &out_zval, 1,
|
||||
|
|
|
@ -77,6 +77,8 @@
|
|||
#define SQL_SOPT_SS_BASE 1225
|
||||
#define SQL_SOPT_SS_TEXTPTR_LOGGING (SQL_SOPT_SS_BASE+0) // Text pointer logging
|
||||
#define SQL_SOPT_SS_NOBROWSETABLE (SQL_SOPT_SS_BASE+3) // Set NOBROWSETABLE option
|
||||
#define SQL_SOPT_SS_PARAM_FOCUS (SQL_SOPT_SS_BASE+11)// Direct subsequent calls to parameter related methods to set properties on constituent columns/parameters of container types
|
||||
#define SQL_SOPT_SS_NAME_SCOPE (SQL_SOPT_SS_BASE+12)// Sets name scope for subsequent catalog function calls
|
||||
#define SQL_SOPT_SS_COLUMN_ENCRYPTION (SQL_SOPT_SS_BASE+13)// Sets the column encryption mode
|
||||
// Define old names
|
||||
#define SQL_TEXTPTR_LOGGING SQL_SOPT_SS_TEXTPTR_LOGGING
|
||||
|
@ -180,6 +182,10 @@
|
|||
#define SQL_COLUMN_ENCRYPTION_DEFAULT SQL_COLUMN_ENCRYPTION_DISABLE
|
||||
// Defines for use with SQL_COPT_SS_CEKCACHETTL
|
||||
#define SQL_CEKCACHETTL_DEFAULT 7200L // TTL value in seconds (2 hours)
|
||||
//SQL_SOPT_SS_NAME_SCOPE
|
||||
#define SQL_SS_NAME_SCOPE_TABLE 0L
|
||||
#define SQL_SS_NAME_SCOPE_TABLE_TYPE 1L
|
||||
#define SQL_SS_NAME_SCOPE_DEFAULT SQL_SS_NAME_SCOPE_TABLE
|
||||
// SQL_COPT_SS_ENCRYPT
|
||||
#define SQL_EN_OFF 0L
|
||||
#define SQL_EN_ON 1L
|
||||
|
|
|
@ -335,6 +335,7 @@ PHP_MINIT_FUNCTION(sqlsrv)
|
|||
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_INT", SQLSRV_PHPTYPE_INT, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_FLOAT", SQLSRV_PHPTYPE_FLOAT, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_DATETIME", SQLSRV_PHPTYPE_DATETIME, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_PHPTYPE_TABLE", SQLSRV_PHPTYPE_TABLE, CONST_PERSISTENT | CONST_CS);
|
||||
|
||||
std::string bin = "binary";
|
||||
std::string chr = "char";
|
||||
|
@ -377,6 +378,7 @@ PHP_MINIT_FUNCTION(sqlsrv)
|
|||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TIMESTAMP", constant_type.value, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TINYINT", SQL_TINYINT, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UDT", SQL_SS_UDT, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_TABLE", SQL_SS_TABLE, CONST_PERSISTENT | CONST_CS);
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_UNIQUEIDENTIFIER", SQL_GUID, CONST_PERSISTENT | CONST_CS );
|
||||
REGISTER_LONG_CONSTANT( "SQLSRV_SQLTYPE_XML", SQL_SS_XML, CONST_PERSISTENT | CONST_CS );
|
||||
constant_type.typeinfo.type = SQL_TYPE_DATE;
|
||||
|
|
|
@ -64,7 +64,7 @@ enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
|
|||
SQLSRV_PHPTYPE_INT,
|
||||
SQLSRV_PHPTYPE_FLOAT,
|
||||
SQLSRV_PHPTYPE_STRING,
|
||||
SQLSRV_PHPTYPE_INVALID,
|
||||
SQLSRV_PHPTYPE_TABLE,
|
||||
SQLSRV_PHPTYPE_DATETIME,
|
||||
SQLSRV_PHPTYPE_STREAM,
|
||||
SQLSRV_PHPTYPE_INVALID,
|
||||
|
@ -227,7 +227,9 @@ sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _
|
|||
case SQL_REAL:
|
||||
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
|
||||
break;
|
||||
|
||||
case SQL_SS_TABLE:
|
||||
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
|
||||
break;
|
||||
case SQL_TYPE_DATE:
|
||||
case SQL_SS_TIMESTAMPOFFSET:
|
||||
case SQL_SS_TIME2:
|
||||
|
@ -1244,6 +1246,11 @@ void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
|
|||
throw core::CoreException();
|
||||
}
|
||||
|
||||
// Table-valued parameters are input-only
|
||||
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_SS_TABLE || php_out_type == SQLSRV_PHPTYPE_TABLE), stmt, SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY) {
|
||||
throw ss::SSException();
|
||||
}
|
||||
|
||||
// bind the parameter
|
||||
SQLSRV_ASSERT( value_z != NULL, "bind_params: value_z is null." );
|
||||
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
|
||||
|
@ -1561,6 +1568,7 @@ bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sq
|
|||
*column_size = INT_MAX >> 1;
|
||||
break;
|
||||
case SQL_SS_XML:
|
||||
case SQL_SS_TABLE:
|
||||
*column_size = SQL_SS_LENGTH_UNLIMITED;
|
||||
break;
|
||||
case SQL_BINARY:
|
||||
|
@ -1710,6 +1718,10 @@ sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_
|
|||
}
|
||||
break;
|
||||
}
|
||||
case SQL_SS_TABLE:
|
||||
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_TABLE;
|
||||
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
||||
break;
|
||||
default:
|
||||
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
|
||||
break;
|
||||
|
@ -2076,6 +2088,7 @@ bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
|
|||
case SQLSRV_PHPTYPE_INT:
|
||||
case SQLSRV_PHPTYPE_FLOAT:
|
||||
case SQLSRV_PHPTYPE_DATETIME:
|
||||
case SQLSRV_PHPTYPE_TABLE:
|
||||
return true;
|
||||
case SQLSRV_PHPTYPE_STRING:
|
||||
case SQLSRV_PHPTYPE_STREAM:
|
||||
|
@ -2120,6 +2133,7 @@ bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type )
|
|||
case SQL_TYPE_DATE:
|
||||
case SQL_SS_TIME2:
|
||||
case SQL_SS_TIMESTAMPOFFSET:
|
||||
case SQL_SS_TABLE:
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
|
|
|
@ -445,6 +445,42 @@ ss_error SS_ERRORS[] = {
|
|||
SQLSRV_ERROR_DATA_CLASSIFICATION_FAILED,
|
||||
{ IMSSP, (SQLCHAR*) "Failed to retrieve Data Classification Sensitivity Metadata: %1!s!", -121, true}
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_STRING_ENCODING_TRANSLATE,
|
||||
{ IMSSP, (SQLCHAR*) "An error occurred translating a string for Table-Valued Param %1!d! Column %2!d! to UTF-16: %3!s!", -122, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_COLUMN_PHPTYPE,
|
||||
{ IMSSP, (SQLCHAR*) "An invalid type for Table-Valued Param %1!d! Column %2!d! was specified", -123, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_FETCH_METADATA,
|
||||
{ IMSSP, (SQLCHAR*) "Failed to get metadata for Table-Valued Param %1!d!", -124, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_INPUTS,
|
||||
{ IMSSP, (SQLCHAR*) "Invalid inputs for Table-Valued Param %1!d!", -125, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INVALID_TABLE_TYPE_NAME,
|
||||
{ IMSSP, (SQLCHAR*) "Expect a non-empty string for a Type Name for Table-Valued Param %1!d!", -126, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_ROWS_UNEXPECTED_SIZE,
|
||||
{ IMSSP, (SQLCHAR*) "For Table-Valued Param %1!d! the number of values in a row is expected to be %2!d!", -127, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_STRING_KEYS,
|
||||
{ IMSSP, (SQLCHAR*) "Associative arrays not allowed for Table-Valued Param %1!d!", -128, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_ROW_NOT_ARRAY,
|
||||
{ IMSSP, (SQLCHAR*) "Expect an array for each row for Table-Valued Param %1!d!", -129, true }
|
||||
},
|
||||
{
|
||||
SQLSRV_ERROR_TVP_INPUT_PARAM_ONLY,
|
||||
{ IMSSP, (SQLCHAR*) "You cannot return data in a table-valued parameter. Table-valued parameters are input-only.", -130, false }
|
||||
},
|
||||
|
||||
// terminate the list of errors/warnings
|
||||
{ UINT_MAX, {} }
|
||||
|
|
BIN
test/functional/inc/awc_tee_male_large.gif
Normal file
BIN
test/functional/inc/awc_tee_male_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
BIN
test/functional/inc/silver_chain_large.gif
Normal file
BIN
test/functional/inc/silver_chain_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
BIN
test/functional/inc/superlight_black_f_large.gif
Normal file
BIN
test/functional/inc/superlight_black_f_large.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
209
test/functional/inc/test_tvp_data.php
Normal file
209
test/functional/inc/test_tvp_data.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
$createTVPOrd = <<<ORD
|
||||
CREATE TABLE TVPOrd(
|
||||
OrdNo INTEGER IDENTITY(1,1),
|
||||
OrdDate DATETIME,
|
||||
CustID VARCHAR(10))
|
||||
ORD;
|
||||
|
||||
$createTVPItem = <<<ITEM
|
||||
CREATE TABLE TVPItem(
|
||||
OrdNo INTEGER,
|
||||
ItemNo INTEGER IDENTITY(1,1),
|
||||
ProductCode CHAR(10),
|
||||
OrderQty INTEGER,
|
||||
PackedOn DATE,
|
||||
Label NVARCHAR(30),
|
||||
Price DECIMAL(5,2),
|
||||
Photo VARBINARY(MAX))
|
||||
ITEM;
|
||||
|
||||
$createTVPParam = <<<TYPE
|
||||
CREATE TYPE TVPParam AS TABLE(
|
||||
ProductCode CHAR(10),
|
||||
OrderQty INTEGER,
|
||||
PackedOn DATE,
|
||||
Label NVARCHAR(30),
|
||||
Price DECIMAL(5,2),
|
||||
Photo VARBINARY(MAX))
|
||||
TYPE;
|
||||
|
||||
$createTVPOrderEntry = <<<PROC
|
||||
CREATE PROCEDURE TVPOrderEntry(
|
||||
@CustID VARCHAR(10),
|
||||
@Items TVPParam READONLY,
|
||||
@OrdNo INTEGER OUTPUT,
|
||||
@OrdDate DATETIME OUTPUT)
|
||||
AS
|
||||
BEGIN
|
||||
SET @OrdDate = GETDATE(); SET NOCOUNT ON;
|
||||
INSERT INTO TVPOrd (OrdDate, CustID) VALUES (@OrdDate, @CustID);
|
||||
SELECT @OrdNo = SCOPE_IDENTITY();
|
||||
INSERT INTO TVPItem (OrdNo, ProductCode, OrderQty, PackedOn, Label, Price, Photo)
|
||||
SELECT @OrdNo, ProductCode, OrderQty, PackedOn, Label, Price, Photo
|
||||
FROM @Items
|
||||
END;
|
||||
PROC;
|
||||
|
||||
$callTVPOrderEntry = "{call TVPOrderEntry(?, ?, ?, ?)}";
|
||||
$callTVPOrderEntryNamed = "{call TVPOrderEntry(:id, :tvp, :ordNo, :ordDate)}";
|
||||
|
||||
// The following gif files are some random product pictures
|
||||
// retrieved from the AdventureWorks sample database (their
|
||||
// sizes ranging from 12 KB to 26 KB)
|
||||
$gif1 = 'awc_tee_male_large.gif';
|
||||
$gif2 = 'superlight_black_f_large.gif';
|
||||
$gif3 = 'silver_chain_large.gif';
|
||||
|
||||
$items = [
|
||||
['0062836700', 367, "2009-03-12", 'AWC Tee Male Shirt', '20.75'],
|
||||
['1250153272', 256, "2017-11-07", 'Superlight Black Bicycle', '998.45'],
|
||||
['1328781505', 260, "2010-03-03", 'Silver Chain for Bikes', '88.98'],
|
||||
];
|
||||
|
||||
$selectTVPItemQuery = 'SELECT OrdNo, ItemNo, ProductCode, OrderQty, PackedOn, Label, Price FROM TVPItem ORDER BY ItemNo';
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
$createTestTVP = <<<TYPE1
|
||||
CREATE TYPE TestTVP AS TABLE(
|
||||
C01 VARCHAR(255),
|
||||
C02 VARCHAR(MAX),
|
||||
C03 BIT,
|
||||
C04 SMALLDATETIME,
|
||||
C05 DATETIME2(5),
|
||||
C06 UNIQUEIDENTIFIER,
|
||||
C07 BIGINT,
|
||||
C08 FLOAT,
|
||||
C09 NUMERIC(38, 24))
|
||||
TYPE1;
|
||||
|
||||
$createSelectTVP = <<<PROC1
|
||||
CREATE PROCEDURE SelectTVP (
|
||||
@TVP TestTVP READONLY)
|
||||
AS
|
||||
SELECT * FROM @TVP
|
||||
PROC1;
|
||||
|
||||
$callSelectTVP = "{call SelectTVP(?)}";
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
$createTestTVP2 = <<<TYPE2
|
||||
CREATE TYPE TestTVP2 AS TABLE(
|
||||
C01 NVARCHAR(50),
|
||||
C02 NVARCHAR(MAX),
|
||||
C03 INT,
|
||||
C04 REAL,
|
||||
C05 VARBINARY(10),
|
||||
C06 VARBINARY(MAX),
|
||||
C07 MONEY,
|
||||
C08 XML,
|
||||
C09 SQL_VARIANT)
|
||||
TYPE2;
|
||||
|
||||
$createSelectTVP2 = <<<PROC2
|
||||
CREATE PROCEDURE SelectTVP2 (
|
||||
@TVP TestTVP2 READONLY)
|
||||
AS
|
||||
SELECT * FROM @TVP
|
||||
PROC2;
|
||||
|
||||
$callSelectTVP2 = "{call SelectTVP2(?)}";
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Use schema other than DBO
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
$createSchema = 'CREATE SCHEMA [Sales DB]';
|
||||
$dropSchema = 'DROP SCHEMA IF EXISTS [Sales DB]';
|
||||
|
||||
$createTestTVP3 = <<<TYPE3
|
||||
CREATE TYPE [Sales DB].[TestTVP3] AS TABLE(
|
||||
Review VARCHAR(100) NOT NULL,
|
||||
SupplierId INT,
|
||||
SalesDate DATETIME2 NULL
|
||||
)
|
||||
TYPE3;
|
||||
|
||||
$createSelectTVP3 = <<<PROC3
|
||||
CREATE PROCEDURE [Sales DB].[SelectTVP3] (
|
||||
@TVP TestTVP3 READONLY )
|
||||
AS
|
||||
SELECT * FROM @TVP
|
||||
PROC3;
|
||||
|
||||
$callSelectTVP3 = "{call [Sales DB].[SelectTVP3](?)}";
|
||||
|
||||
$createSupplierType = <<<SUPP_TYPE
|
||||
CREATE TYPE [Sales DB].[SupplierType] AS TABLE(
|
||||
SupplierId INT,
|
||||
SupplierName NVARCHAR(50)
|
||||
)
|
||||
SUPP_TYPE;
|
||||
|
||||
$createAddReview = <<<SUPP_PROC
|
||||
CREATE PROCEDURE [Sales DB].[AddReview] (
|
||||
@suppType SupplierType READONLY,
|
||||
@reviewType TestTVP3 READONLY,
|
||||
@image VARBINARY(MAX))
|
||||
AS
|
||||
SELECT * FROM @suppType;
|
||||
SELECT SupplierId, SalesDate, Review FROM @reviewType;
|
||||
SELECT @image
|
||||
SUPP_PROC;
|
||||
|
||||
$callAddReview = "{call [Sales DB].[AddReview](?, ?, ?)}";
|
||||
|
||||
///////////////////////////////////////////////////////
|
||||
// Common functions
|
||||
///////////////////////////////////////////////////////
|
||||
|
||||
function dropProcSQL($conn, $procName)
|
||||
{
|
||||
return "IF OBJECT_ID('$procName', 'P') IS NOT NULL DROP PROCEDURE $procName";
|
||||
}
|
||||
|
||||
function dropTableTypeSQL($conn, $typeName, $schema = 'dbo')
|
||||
{
|
||||
return "IF EXISTS (SELECT * FROM sys.types WHERE is_table_type = 1 AND name = '$typeName') DROP TYPE [$schema].[$typeName]";
|
||||
}
|
||||
|
||||
function verifyBinaryData($fp, $data)
|
||||
{
|
||||
$size = 8192;
|
||||
$pos = 0;
|
||||
$matched = true;
|
||||
while (!feof($fp)) {
|
||||
$original = fread($fp, $size);
|
||||
$str = substr($data, $pos, $size);
|
||||
|
||||
if ($original !== $str) {
|
||||
$matched = false;
|
||||
break;
|
||||
}
|
||||
$pos += $size;
|
||||
}
|
||||
|
||||
return $matched;
|
||||
}
|
||||
|
||||
function verifyBinaryStream($fp, $stream)
|
||||
{
|
||||
$size = 8192;
|
||||
$matched = true;
|
||||
while (!feof($fp) && !feof($stream)) {
|
||||
$original = fread($fp, $size);
|
||||
$data = fread($stream, $size);
|
||||
|
||||
if ($original !== $data) {
|
||||
$matched = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $matched;
|
||||
}
|
||||
|
||||
?>
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
*/
|
||||
|
||||
$tvpIncPath = dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'inc'.DIRECTORY_SEPARATOR;
|
||||
|
||||
require_once($tvpIncPath. 'test_tvp_data.php');
|
||||
|
||||
//
|
||||
// looks like an additional file (in addition to pdo_test_base.inc) may be needed for these PHPTs
|
||||
// to be runnable from the MSSQL teams' internal proprietary test running system
|
||||
|
@ -1794,3 +1798,11 @@ function PhpVersionComponents(&$major, &$minor, &$sub)
|
|||
$minor = strtok(".");
|
||||
$sub = strtok(".");
|
||||
}
|
||||
|
||||
function getTodayDateAsString($conn)
|
||||
{
|
||||
$tsql = 'SELECT CONVERT (VARCHAR(20), GETDATE())';
|
||||
$stmt = $conn->query($tsql);
|
||||
$row = $stmt->fetch(PDO::FETCH_NUM);
|
||||
return $row[0];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using bindValue() and random null inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using bindValue() instead of bindParam() with random null values.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
try {
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
dropProc($conn, 'SelectTVP2');
|
||||
|
||||
$tvpType = 'TestTVP2';
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
// Create table type and a stored procedure
|
||||
$conn->exec($createTestTVP2);
|
||||
$conn->exec($createSelectTVP2);
|
||||
|
||||
// Create column arrays
|
||||
$str1 = "Šỡოē šâოрĺẻ ÅŚÇÏЇ-ťếхţ";
|
||||
$longStr1 = str_repeat($str1, 1500);
|
||||
$str2 = pack("H*", '49006427500048005000' ); // I'LOVE_SYMBOL'PHP
|
||||
$longStr2 = str_repeat($str2, 2000);
|
||||
|
||||
$bin1 = pack('H*', '0FD1CEFACE');
|
||||
$bin2 = pack('H*', '0001020304');
|
||||
$bin3 = hex2bin('616263646566676869'); // abcdefghi
|
||||
$bin4 = pack('H*', '7A61CC86C7BDCEB2F18FB3BF');
|
||||
|
||||
$xml = "<XmlTestData><Letters1>The quick brown fox jumps over the lazy dog</Letters1><Digits1>0123456789</Digits1></XmlTestData>";
|
||||
|
||||
$c01 = [null, $str1, $str2];
|
||||
$c02 = [null, $longStr1, $longStr2];
|
||||
$c03 = [null, null, 999];
|
||||
$c04 = [null, 3.1415927, null];
|
||||
$c05 = [$bin1, null, $bin2];
|
||||
$c06 = [null, $bin3, $bin4];
|
||||
$c07 = [null, '1234.56', '9876.54'];
|
||||
$c08 = [null, null, $xml];
|
||||
$c09 = [4.321, 'CF43B0B3-E645-48C4-9F25-1A2BB4CE581A', (0xCBAB)];
|
||||
|
||||
// Create a TVP input array
|
||||
$nrows = 3;
|
||||
$ncols = 8;
|
||||
$params = array();
|
||||
for ($i = 0; $i < $nrows; $i++) {
|
||||
$rowValues = array($c01[$i], $c02[$i], $c03[$i], $c04[$i], $c05[$i], $c06[$i], $c07[$i], $c08[$i], $c09[$i]);
|
||||
array_push($params, $rowValues);
|
||||
}
|
||||
|
||||
$tvpInput = array($tvpType => $params);
|
||||
|
||||
// Prepare to call the stored procedure
|
||||
$stmt = $conn->prepare($callSelectTVP2);
|
||||
|
||||
// Bind parameters for the stored procedure
|
||||
$stmt->bindValue(1, $tvpInput, PDO::PARAM_LOB);
|
||||
$stmt->execute();
|
||||
|
||||
// Verify the results
|
||||
$row = 0;
|
||||
while ($result = $stmt->fetch(PDO::FETCH_NUM)) {
|
||||
// Compare the values against the inputs
|
||||
for ($col = 0; $col < $ncols; $col++) {
|
||||
if ($result[$col] != $params[$row][$col]) {
|
||||
echo 'Unexpected data at row ' . ($row + 1) . ' and col ' . ($col + 1) . PHP_EOL;
|
||||
echo 'Expected: ' . $params[$row][$col] . PHP_EOL;
|
||||
echo 'Fetched: ' . $result[$col] . PHP_EOL;
|
||||
}
|
||||
}
|
||||
$row++;
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
dropProc($conn, 'SelectTVP2');
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Done
|
152
test/functional/pdo_sqlsrv/pdo_test_TVP_bind_params.phpt
Normal file
152
test/functional/pdo_sqlsrv/pdo_test_TVP_bind_params.phpt
Normal file
|
@ -0,0 +1,152 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using bindParam and no null values
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using bindParam and no null values. This test verifies the fetched results of the all columns.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
try {
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
// Create tables and a stored procedure
|
||||
$conn->exec($createTVPOrd);
|
||||
$conn->exec($createTVPItem);
|
||||
$conn->exec($createTVPParam);
|
||||
$conn->exec($createTVPOrderEntry);
|
||||
|
||||
$custCode = 'PDO_123';
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
$image1 = fopen($tvpIncPath. $gif1, 'rb');
|
||||
$image2 = fopen($tvpIncPath. $gif2, 'rb');
|
||||
$image3 = fopen($tvpIncPath. $gif3, 'rb');
|
||||
$images = [$image1, $image2, $image3];
|
||||
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], $images[$i]);
|
||||
}
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
// Prepare to call the stored procedure
|
||||
$stmt = $conn->prepare($callTVPOrderEntry);
|
||||
|
||||
// Bind parameters for the stored procedure
|
||||
$stmt->bindParam(1, $custCode);
|
||||
$stmt->bindParam(2, $tvpInput, PDO::PARAM_LOB);
|
||||
$stmt->bindParam(3, $ordNo, PDO::PARAM_INT, 10);
|
||||
$stmt->bindParam(4, $ordDate, PDO::PARAM_STR, 20);
|
||||
$stmt->execute();
|
||||
$stmt->closeCursor();
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch a random inserted image from the table and verify them
|
||||
$n = rand(10,100);
|
||||
$index = $n % count($images);
|
||||
|
||||
$tsql = 'SELECT Photo FROM TVPItem WHERE ItemNo = ' . ($index + 1);
|
||||
$stmt = $conn->query($tsql);
|
||||
$stmt->bindColumn('Photo', $photo, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||
if ($row = $stmt->fetch(PDO::FETCH_BOUND)) {
|
||||
if (!verifyBinaryData($images[$index], $photo)) {
|
||||
echo 'Image data corrupted for row '. ($index + 1) . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
echo 'Failed in calling bindColumn' . PHP_EOL;
|
||||
}
|
||||
unset($photo);
|
||||
|
||||
fclose($image1);
|
||||
fclose($image2);
|
||||
fclose($image3);
|
||||
|
||||
// Fetch CustID
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = $conn->query($tsql);
|
||||
$row = $stmt->fetch(PDO::FETCH_NUM);
|
||||
$id = $row[0];
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
|
||||
// Fetch other basic types
|
||||
$stmt = $conn->query($selectTVPItemQuery);
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
print_r($row);
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 1
|
||||
[ProductCode] => 0062836700
|
||||
[OrderQty] => 367
|
||||
[PackedOn] => 2009-03-12
|
||||
[Label] => AWC Tee Male Shirt
|
||||
[Price] => 20.75
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 2
|
||||
[ProductCode] => 1250153272
|
||||
[OrderQty] => 256
|
||||
[PackedOn] => 2017-11-07
|
||||
[Label] => Superlight Black Bicycle
|
||||
[Price] => 998.45
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 3
|
||||
[ProductCode] => 1328781505
|
||||
[OrderQty] => 260
|
||||
[PackedOn] => 2010-03-03
|
||||
[Label] => Silver Chain for Bikes
|
||||
[Price] => 88.98
|
||||
)
|
||||
Done
|
128
test/functional/pdo_sqlsrv/pdo_test_TVP_bind_values.phpt
Normal file
128
test/functional/pdo_sqlsrv/pdo_test_TVP_bind_values.phpt
Normal file
|
@ -0,0 +1,128 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using bindValue() and random null inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using bindValue() instead of bindParam() with random null values.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
try {
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
dropProc($conn, 'SelectTVP');
|
||||
|
||||
$tvpType = 'TestTVP';
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
// Create table type and a stored procedure
|
||||
$conn->exec($createTestTVP);
|
||||
$conn->exec($createSelectTVP);
|
||||
|
||||
// Create column arrays
|
||||
$str = '';
|
||||
for ($i = 0; $i < 255; $i++) {
|
||||
$str .= chr(($i % 95) + 32);
|
||||
}
|
||||
$longStr = str_repeat($str, 2000);
|
||||
|
||||
$c01 = ['abcde', '', $str];
|
||||
$c02 = ['Hello world!', 'ABCDEFGHIJKLMNOP', $longStr];
|
||||
$c03 = [1, 0, 1];
|
||||
$c04 = [null,
|
||||
null,
|
||||
date_create('1955-12-13 12:20:00')];
|
||||
$c05 = [date_create('2384-12-31 12:40:12.34565'), null, date_create('1074-12-31 23:59:59.01234')];
|
||||
$c06 = ['4CDBC69F-F0EE-4963-8F17-24DD47090126',
|
||||
'0F12A09D-D614-4998-AB1F-BD7CDBF6E3FE',
|
||||
null];
|
||||
$c07 = ['1234567', '-9223372036854775808', '9223372036854775807'];
|
||||
$c08 = [null, -1.79E+308, 1.79E+308];
|
||||
$c09 = ['31234567890123.141243449787580175325274',
|
||||
'0.000000000000000000000001',
|
||||
'99999999999999.999999999999999999999999'];
|
||||
|
||||
// Create a TVP input array
|
||||
$nrows = 3;
|
||||
$ncols = 9;
|
||||
$params = array();
|
||||
for ($i = 0; $i < $nrows; $i++) {
|
||||
$rowValues = array($c01[$i], $c02[$i], $c03[$i], $c04[$i], $c05[$i], $c06[$i], $c07[$i], $c08[$i], $c09[$i]);
|
||||
array_push($params, $rowValues);
|
||||
}
|
||||
|
||||
$tvpInput = array($tvpType => $params);
|
||||
|
||||
// Prepare to call the stored procedure
|
||||
$stmt = $conn->prepare($callSelectTVP);
|
||||
|
||||
// Bind parameters for the stored procedure
|
||||
$stmt->bindValue(1, $tvpInput, PDO::PARAM_LOB);
|
||||
$stmt->execute();
|
||||
|
||||
// Verify the results
|
||||
$row = 0;
|
||||
while ($result = $stmt->fetch(PDO::FETCH_NUM)) {
|
||||
// For strings, compare their values
|
||||
for ($col = 0; $col < 2; $col++) {
|
||||
if ($result[$col] != $params[$row][$col]) {
|
||||
echo 'Unexpected data at row ' . ($row + 1) . ' and col ' . ($col + 1) . PHP_EOL;
|
||||
echo 'Expected: ' . $params[$row][$col] . PHP_EOL;
|
||||
echo 'Fetched: ' . $result[$col] . PHP_EOL;
|
||||
}
|
||||
}
|
||||
// For other types, print them
|
||||
echo 'Row ' . ($row + 1) . ': from Col ' . ($col + 1) . ' to ' . $ncols . PHP_EOL;
|
||||
for ($col = 2; $col < $ncols; $col++) {
|
||||
var_dump($result[$col]);
|
||||
}
|
||||
echo PHP_EOL;
|
||||
$row++;
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
dropProc($conn, 'SelectTVP');
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Row 1: from Col 3 to 9
|
||||
string(1) "1"
|
||||
NULL
|
||||
string(25) "2384-12-31 12:40:12.34565"
|
||||
string(36) "4CDBC69F-F0EE-4963-8F17-24DD47090126"
|
||||
string(7) "1234567"
|
||||
NULL
|
||||
string(39) "31234567890123.141243449787580175325274"
|
||||
|
||||
Row 2: from Col 3 to 9
|
||||
string(1) "0"
|
||||
NULL
|
||||
NULL
|
||||
string(36) "0F12A09D-D614-4998-AB1F-BD7CDBF6E3FE"
|
||||
string(20) "-9223372036854775808"
|
||||
string(10) "-1.79E+308"
|
||||
string(25) ".000000000000000000000001"
|
||||
|
||||
Row 3: from Col 3 to 9
|
||||
string(1) "1"
|
||||
string(19) "1955-12-13 12:20:00"
|
||||
string(25) "1074-12-31 23:59:59.01234"
|
||||
NULL
|
||||
string(19) "9223372036854775807"
|
||||
string(9) "1.79E+308"
|
||||
string(39) "99999999999999.999999999999999999999999"
|
||||
|
||||
Done
|
131
test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt
Normal file
131
test/functional/pdo_sqlsrv/pdo_test_TVP_double_tvps.phpt
Normal file
|
@ -0,0 +1,131 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter with a stored procedure that takes two TVPs
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
function cleanup($conn, $schema)
|
||||
{
|
||||
global $dropSchema;
|
||||
|
||||
$dropProcedure = dropProcSQL($conn, "[$schema].[AddReview]");
|
||||
$conn->exec($dropProcedure);
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, "TestTVP3", $schema);
|
||||
$conn->exec($dropTableType);
|
||||
$dropTableType = dropTableTypeSQL($conn, "SupplierType", $schema);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
$conn->exec($dropSchema);
|
||||
}
|
||||
|
||||
try {
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Use a different schema instead of dbo
|
||||
$schema = 'Sales DB';
|
||||
cleanup($conn, $schema);
|
||||
|
||||
// Create the table type and stored procedure
|
||||
$conn->exec($createSchema);
|
||||
$conn->exec($createTestTVP3);
|
||||
$conn->exec($createSupplierType);
|
||||
$conn->exec($createAddReview);
|
||||
|
||||
// Create the TVP input arrays
|
||||
$inputs1 = [
|
||||
[12345, 'μεγάλο'],
|
||||
[67890, 'μεσαία'],
|
||||
[45678, 'μικρές'],
|
||||
];
|
||||
|
||||
$inputs2 = [
|
||||
['abcde', 12345, '2019-12-31 23:59:59.123456'],
|
||||
['fghij', 67890, '2000-07-15 12:30:30.5678'],
|
||||
['klmop', 45678, '2007-04-08 06:15:15.333'],
|
||||
];
|
||||
|
||||
$tvpType1 = "$schema.SupplierType";
|
||||
$tvpType2 = "$schema.TestTVP3";
|
||||
|
||||
$tvpInput1 = array($tvpType1 => $inputs1);
|
||||
$tvpInput2 = array($tvpType2 => $inputs2);
|
||||
|
||||
$image = fopen($tvpIncPath. 'superlight_black_f_large.gif', 'rb');
|
||||
|
||||
$stmt = $conn->prepare($callAddReview);
|
||||
$stmt->bindParam(1, $tvpInput1, PDO::PARAM_LOB);
|
||||
$stmt->bindParam(2, $tvpInput2, PDO::PARAM_LOB);
|
||||
$stmt->bindParam(3, $image, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||
$stmt->execute();
|
||||
|
||||
// Verify the results
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
print_r($row);
|
||||
}
|
||||
$stmt->nextRowset();
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
print_r($row);
|
||||
}
|
||||
$stmt->nextRowset();
|
||||
if ($row = $stmt->fetch(PDO::FETCH_NUM)) {
|
||||
if (!verifyBinaryData($image, $row[0])) {
|
||||
echo 'The image is corrupted' . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
echo 'Something went wrong reading the image' . PHP_EOL;
|
||||
}
|
||||
|
||||
fclose($image);
|
||||
unset($stmt);
|
||||
|
||||
cleanup($conn, $schema);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 12345
|
||||
[SupplierName] => μεγάλο
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 67890
|
||||
[SupplierName] => μεσαία
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 45678
|
||||
[SupplierName] => μικρές
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 12345
|
||||
[SalesDate] => 2019-12-31 23:59:59.1234560
|
||||
[Review] => abcde
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 67890
|
||||
[SalesDate] => 2000-07-15 12:30:30.5678000
|
||||
[Review] => fghij
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 45678
|
||||
[SalesDate] => 2007-04-08 06:15:15.3330000
|
||||
[Review] => klmop
|
||||
)
|
||||
Done
|
189
test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt
Normal file
189
test/functional/pdo_sqlsrv/pdo_test_TVP_error_cases.phpt
Normal file
|
@ -0,0 +1,189 @@
|
|||
--TEST--
|
||||
Test various error cases with invalid Table-valued parameter inputs
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
function invokeProc($conn, $proc, $tvpInput, $caseNo, $inputParam = true)
|
||||
{
|
||||
try {
|
||||
$stmt = $conn->prepare($proc);
|
||||
|
||||
// Bind TVP for the stored procedure
|
||||
if ($inputParam) {
|
||||
$stmt->bindValue(1, $tvpInput, PDO::PARAM_LOB);
|
||||
} else {
|
||||
$stmt->bindParam(1, $tvpInput, PDO::PARAM_LOB, 100);
|
||||
}
|
||||
$stmt->execute();
|
||||
} catch (PDOException $e) {
|
||||
echo "Error $caseNo: ";
|
||||
echo $e->getMessage();
|
||||
echo PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup($conn, $schema, $tvpType, $procName)
|
||||
{
|
||||
global $dropSchema;
|
||||
|
||||
$dropProcedure = dropProcSQL($conn, "[$schema].[$procName]");
|
||||
$conn->exec($dropProcedure);
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType, $schema);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
$conn->exec($dropSchema);
|
||||
}
|
||||
|
||||
try {
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
// Use a different schema instead of dbo
|
||||
$schema = 'Sales DB';
|
||||
$tvpType = 'TestTVP3';
|
||||
$procName = 'SelectTVP3';
|
||||
|
||||
cleanup($conn, $schema, $tvpType, $procName);
|
||||
|
||||
// Create the table type and stored procedure
|
||||
$conn->exec($createSchema);
|
||||
$conn->exec($createTestTVP3);
|
||||
$conn->exec($createSelectTVP3);
|
||||
|
||||
// Create a TVP input array
|
||||
$inputs = [
|
||||
['ABC', 12345, null],
|
||||
['DEF', 6789, null],
|
||||
['GHI', null],
|
||||
];
|
||||
$str = 'dummy';
|
||||
|
||||
// Case (1) - do not provide TVP type name
|
||||
$tvpInput = array($inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 1);
|
||||
|
||||
// Case (2) - use an empty string as TVP type name
|
||||
$tvpInput = array("" => array());
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 2);
|
||||
|
||||
// The TVP name should include the schema
|
||||
$tvpTypeName = "$schema.$tvpType";
|
||||
|
||||
// Case (3) - null inputs
|
||||
$tvpInput = array($tvpTypeName => null);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 3);
|
||||
|
||||
// Case (4) - not using array as inputs
|
||||
$tvpInput = array($tvpTypeName => 1);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 4);
|
||||
|
||||
// Case (5) - invalid TVP type name
|
||||
$tvpInput = array($str => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 5);
|
||||
|
||||
// Case (6) - input rows are not the same size
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 6);
|
||||
|
||||
// Case (7) - input row wrong size
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 12345, null, null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 7);
|
||||
|
||||
// Case (8) - use string keys
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['A' => null, null, null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 8);
|
||||
|
||||
// Case (9) - a row is not an array
|
||||
unset($inputs);
|
||||
$inputs = [null];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 9);
|
||||
|
||||
// Case (10) - a column value used a string key
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 12345, "key"=>null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 10);
|
||||
|
||||
// Case (11) - invalid input object for a TVP column
|
||||
class foo
|
||||
{
|
||||
function do_foo(){}
|
||||
}
|
||||
$bar = new foo;
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 1234, $bar],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 11);
|
||||
|
||||
// Case (12) - invalid input type for a TVP column
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', &$str, null],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 12);
|
||||
|
||||
// Case (13) - bind a TVP as an OUTPUT param
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 13, false);
|
||||
|
||||
// Case (14) - test UTF-8 invalid/corrupt string for a TVP column
|
||||
unset($inputs);
|
||||
$utf8 = str_repeat("41", 8188);
|
||||
$utf8 = $utf8 . "e38395e38395";
|
||||
$utf8 = substr_replace($utf8, "fe", 1000, 2);
|
||||
$utf8 = pack("H*", $utf8);
|
||||
|
||||
$inputs = [
|
||||
[$utf8, 1234, null],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 14);
|
||||
|
||||
cleanup($conn, $schema, $tvpType, $procName);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECTF--
|
||||
Error 1: SQLSTATE[IMSSP]: Expect a non-empty string for a Type Name for Table-Valued Param 1
|
||||
Error 2: SQLSTATE[IMSSP]: Expect a non-empty string for a Type Name for Table-Valued Param 1
|
||||
Error 3: SQLSTATE[IMSSP]: Invalid inputs for Table-Valued Param 1
|
||||
Error 4: SQLSTATE[IMSSP]: Invalid inputs for Table-Valued Param 1
|
||||
Error 5: SQLSTATE[IMSSP]: Failed to get metadata for Table-Valued Param 1
|
||||
Error 6: SQLSTATE[IMSSP]: For Table-Valued Param 1 the number of values in a row is expected to be 3
|
||||
Error 7: SQLSTATE[IMSSP]: For Table-Valued Param 1 the number of values in a row is expected to be 3
|
||||
Error 8: SQLSTATE[IMSSP]: Associative arrays not allowed for Table-Valued Param 1
|
||||
Error 9: SQLSTATE[IMSSP]: Expect an array for each row for Table-Valued Param 1
|
||||
Error 10: SQLSTATE[IMSSP]: Associative arrays not allowed for Table-Valued Param 1
|
||||
Error 11: SQLSTATE[IMSSP]: An invalid type for Table-Valued Param 1 Column 3 was specified
|
||||
Error 12: SQLSTATE[IMSSP]: An invalid type for Table-Valued Param 1 Column 2 was specified
|
||||
Error 13: SQLSTATE[IMSSP]: You cannot return data in a table-valued parameter. Table-valued parameters are input-only.
|
||||
Error 14: SQLSTATE[IMSSP]: An error occurred translating a string for Table-Valued Param 1 Column 1 to UTF-16: %a
|
||||
Done
|
149
test/functional/pdo_sqlsrv/pdo_test_TVP_nulls_buffered.phpt
Normal file
149
test/functional/pdo_sqlsrv/pdo_test_TVP_nulls_buffered.phpt
Normal file
|
@ -0,0 +1,149 @@
|
|||
--TEST--
|
||||
Table-valued parameter with bindParam and named parameters. The initial values of a column are NULLs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter with bindParam. The initial values of a column are NULLs. This test verifies the fetched results using client buffers.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
try {
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
// Create tables and a stored procedure
|
||||
$conn->exec($createTVPOrd);
|
||||
$conn->exec($createTVPItem);
|
||||
$conn->exec($createTVPParam);
|
||||
$conn->exec($createTVPOrderEntry);
|
||||
|
||||
// Bind parameters for call to TVPOrderEntry
|
||||
$custCode = 'PDO_789';
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
// TVP supports column-wise binding
|
||||
$image3 = fopen($tvpIncPath. $gif3, 'rb');
|
||||
$images = [null, null, $image3];
|
||||
|
||||
// Added images to $items
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], $images[$i]);
|
||||
}
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
// Prepare to call the stored procedure
|
||||
$stmt = $conn->prepare($callTVPOrderEntryNamed);
|
||||
|
||||
$stmt->bindParam(':id', $custCode);
|
||||
$stmt->bindParam(':tvp', $tvpInput, PDO::PARAM_LOB);
|
||||
$stmt->bindParam(':ordNo', $ordNo, PDO::PARAM_INT, 10);
|
||||
$stmt->bindParam(':ordDate', $ordDate, PDO::PARAM_STR, 20);
|
||||
|
||||
$stmt->execute();
|
||||
$stmt->closeCursor();
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch CustID
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = $conn->query($tsql);
|
||||
$row = $stmt->fetch(PDO::FETCH_NUM);
|
||||
$id = $row[0];
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
|
||||
// Fetch the only image from the table that is not NULL
|
||||
$tsql = 'SELECT ItemNo, Photo FROM TVPItem WHERE Photo IS NOT NULL ORDER BY ItemNo';
|
||||
$stmt = $conn->query($tsql);
|
||||
$index = 2;
|
||||
$stmt->bindColumn('Photo', $photo, PDO::PARAM_LOB, 0, PDO::SQLSRV_ENCODING_BINARY);
|
||||
if ($row = $stmt->fetch(PDO::FETCH_BOUND)) {
|
||||
if (!verifyBinaryData($images[$index], $photo)) {
|
||||
echo 'Image data corrupted for row '. ($index + 1) . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
echo 'Failed in calling bindColumn' . PHP_EOL;
|
||||
}
|
||||
unset($photo);
|
||||
fclose($image3);
|
||||
|
||||
// Fetch other basic types
|
||||
$stmt = $conn->prepare($selectTVPItemQuery, array(PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL, PDO::SQLSRV_ATTR_CURSOR_SCROLL_TYPE => PDO::SQLSRV_CURSOR_BUFFERED));
|
||||
$stmt->execute();
|
||||
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
|
||||
print_r($row);
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 1
|
||||
[ProductCode] => 0062836700
|
||||
[OrderQty] => 367
|
||||
[PackedOn] => 2009-03-12
|
||||
[Label] => AWC Tee Male Shirt
|
||||
[Price] => 20.75
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 2
|
||||
[ProductCode] => 1250153272
|
||||
[OrderQty] => 256
|
||||
[PackedOn] => 2017-11-07
|
||||
[Label] => Superlight Black Bicycle
|
||||
[Price] => 998.45
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 3
|
||||
[ProductCode] => 1328781505
|
||||
[OrderQty] => 260
|
||||
[PackedOn] => 2010-03-03
|
||||
[Label] => Silver Chain for Bikes
|
||||
[Price] => 88.98
|
||||
)
|
||||
Done
|
162
test/functional/pdo_sqlsrv/pdo_test_TVP_with_nulls.phpt
Normal file
162
test/functional/pdo_sqlsrv/pdo_test_TVP_with_nulls.phpt
Normal file
|
@ -0,0 +1,162 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using bindParam and some NULL inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using bindParam with some NULL input values. This test verifies the fetched results of all columns.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsSetup.inc');
|
||||
require_once('MsCommon_mid-refactor.inc');
|
||||
|
||||
try {
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
$conn = new PDO("sqlsrv:server = $server; database=$databaseName;", $uid, $pwd);
|
||||
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
// Create tables and a stored procedure
|
||||
$conn->exec($createTVPOrd);
|
||||
$conn->exec($createTVPItem);
|
||||
$conn->exec($createTVPParam);
|
||||
$conn->exec($createTVPOrderEntry);
|
||||
|
||||
// Bind parameters for call to TVPOrderEntry
|
||||
$custCode = 'PDO_456';
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
// Add null image to $items
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], null);
|
||||
}
|
||||
|
||||
// Randomly set some values to null
|
||||
$items[1][0] = null;
|
||||
$items[0][2] = null;
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
// Prepare to call the stored procedure
|
||||
$stmt = $conn->prepare($callTVPOrderEntryNamed);
|
||||
|
||||
$stmt->bindParam(':id', $custCode);
|
||||
$stmt->bindParam(':tvp', $tvpInput, PDO::PARAM_LOB);
|
||||
$stmt->bindParam(':ordNo', $ordNo, PDO::PARAM_INT, 10);
|
||||
$stmt->bindParam(':ordDate', $ordDate, PDO::PARAM_STR, 20);
|
||||
$stmt->execute();
|
||||
$stmt->closeCursor();
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch CustID
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = $conn->query($tsql);
|
||||
$row = $stmt->fetch(PDO::FETCH_NUM);
|
||||
$id = $row[0];
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
|
||||
// Fetch all columns
|
||||
$tsql = 'SELECT * FROM TVPItem ORDER BY ItemNo';
|
||||
$stmt = $conn->query($tsql);
|
||||
if ($row = $stmt->fetchall(PDO::FETCH_NUM)) {
|
||||
var_dump($row);
|
||||
}
|
||||
unset($stmt);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
$conn->exec($dropTableType);
|
||||
|
||||
unset($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
|
||||
} catch (PDOException $e) {
|
||||
var_dump($e->getMessage());
|
||||
}
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
array(3) {
|
||||
[0]=>
|
||||
array(8) {
|
||||
[0]=>
|
||||
string(1) "1"
|
||||
[1]=>
|
||||
string(1) "1"
|
||||
[2]=>
|
||||
string(10) "0062836700"
|
||||
[3]=>
|
||||
string(3) "367"
|
||||
[4]=>
|
||||
NULL
|
||||
[5]=>
|
||||
string(18) "AWC Tee Male Shirt"
|
||||
[6]=>
|
||||
string(5) "20.75"
|
||||
[7]=>
|
||||
NULL
|
||||
}
|
||||
[1]=>
|
||||
array(8) {
|
||||
[0]=>
|
||||
string(1) "1"
|
||||
[1]=>
|
||||
string(1) "2"
|
||||
[2]=>
|
||||
NULL
|
||||
[3]=>
|
||||
string(3) "256"
|
||||
[4]=>
|
||||
string(10) "2017-11-07"
|
||||
[5]=>
|
||||
string(24) "Superlight Black Bicycle"
|
||||
[6]=>
|
||||
string(6) "998.45"
|
||||
[7]=>
|
||||
NULL
|
||||
}
|
||||
[2]=>
|
||||
array(8) {
|
||||
[0]=>
|
||||
string(1) "1"
|
||||
[1]=>
|
||||
string(1) "3"
|
||||
[2]=>
|
||||
string(10) "1328781505"
|
||||
[3]=>
|
||||
string(3) "260"
|
||||
[4]=>
|
||||
string(10) "2010-03-03"
|
||||
[5]=>
|
||||
string(22) "Silver Chain for Bikes"
|
||||
[6]=>
|
||||
string(5) "88.98"
|
||||
[7]=>
|
||||
NULL
|
||||
}
|
||||
}
|
||||
Done
|
|
@ -11,6 +11,10 @@
|
|||
require_once('MsHelper.inc');
|
||||
require_once('MsSetup.inc');
|
||||
|
||||
$tvpIncPath = dirname(__FILE__).DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR.'inc'.DIRECTORY_SEPARATOR;
|
||||
|
||||
require_once($tvpIncPath. 'test_tvp_data.php');
|
||||
|
||||
$usingUTF8data = false;
|
||||
|
||||
function isWindows()
|
||||
|
@ -559,4 +563,19 @@ function verifyError($error, $state, $message)
|
|||
}
|
||||
}
|
||||
|
||||
function getTodayDateAsString($conn)
|
||||
{
|
||||
$tsql = 'SELECT CONVERT (VARCHAR(20), GETDATE())';
|
||||
$stmt = sqlsrv_query($conn, $tsql);
|
||||
$result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC);
|
||||
$today = '';
|
||||
if ($result) {
|
||||
$today = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
|
||||
} else {
|
||||
echo "Failed to get today's date as string: " . PHP_EOL;
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
return $today;
|
||||
}
|
||||
?>
|
||||
|
|
133
test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt
Normal file
133
test/functional/sqlsrv/sqlsrv_test_TVP_double_tvps.phpt
Normal file
|
@ -0,0 +1,133 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter with a stored procedure that takes two TVPs
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
function cleanup($conn, $schema)
|
||||
{
|
||||
global $dropSchema;
|
||||
|
||||
$dropProcedure = dropProcSQL($conn, "[$schema].[AddReview]");
|
||||
sqlsrv_query($conn, $dropProcedure);
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, "TestTVP3", $schema);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
$dropTableType = dropTableTypeSQL($conn, "SupplierType", $schema);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
sqlsrv_query($conn, $dropSchema);
|
||||
}
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('CharacterSet'=>'UTF-8', 'ReturnDatesAsStrings' => true));
|
||||
|
||||
// Use a different schema instead of dbo
|
||||
$schema = 'Sales DB';
|
||||
cleanup($conn, $schema);
|
||||
|
||||
// Create table types and stored procedures
|
||||
sqlsrv_query($conn, $createSchema);
|
||||
sqlsrv_query($conn, $createTestTVP3);
|
||||
sqlsrv_query($conn, $createSupplierType);
|
||||
sqlsrv_query($conn, $createAddReview);
|
||||
|
||||
// Create the TVP input arrays
|
||||
$inputs1 = [
|
||||
[12345, 'Large大'],
|
||||
[67890, 'Medium中'],
|
||||
[45678, 'Small小'],
|
||||
];
|
||||
|
||||
$inputs2 = [
|
||||
['ABCDE', 12345, '2019-12-31 23:59:59.123456'],
|
||||
['FGHIJ', 67890, '2000-07-15 12:30:30.5678'],
|
||||
['KLMOP', 45678, '2007-04-08 06:15:15.333'],
|
||||
];
|
||||
|
||||
$tvpType1 = "$schema.SupplierType";
|
||||
$tvpType2 = "$schema.TestTVP3";
|
||||
|
||||
$tvpInput1 = array($tvpType1 => $inputs1);
|
||||
$tvpInput2 = array($tvpType2 => $inputs2);
|
||||
|
||||
$image = fopen($tvpIncPath. 'awc_tee_male_large.gif', 'rb');
|
||||
|
||||
$params = array(array($tvpInput1, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE),
|
||||
array($tvpInput2, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE),
|
||||
array($image, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY), SQLSRV_SQLTYPE_VARBINARY('MAX')));
|
||||
|
||||
$stmt = sqlsrv_query($conn, $callAddReview, $params);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Verify the results
|
||||
$row = 0;
|
||||
while ($result = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
print_r($result);
|
||||
}
|
||||
sqlsrv_next_result($stmt);
|
||||
while ($result = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
print_r($result);
|
||||
}
|
||||
sqlsrv_next_result($stmt);
|
||||
if (sqlsrv_fetch($stmt)) {
|
||||
$photo = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
|
||||
if (!verifyBinaryStream($image, $photo)) {
|
||||
echo 'Image data is corrupted' . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
echo 'Something went wrong reading the image' . PHP_EOL;
|
||||
}
|
||||
|
||||
fclose($image);
|
||||
cleanup($conn, $schema);
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
sqlsrv_close($conn);
|
||||
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 12345
|
||||
[SupplierName] => Large大
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 67890
|
||||
[SupplierName] => Medium中
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 45678
|
||||
[SupplierName] => Small小
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 12345
|
||||
[SalesDate] => 2019-12-31 23:59:59.1234560
|
||||
[Review] => ABCDE
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 67890
|
||||
[SalesDate] => 2000-07-15 12:30:30.5678000
|
||||
[Review] => FGHIJ
|
||||
)
|
||||
Array
|
||||
(
|
||||
[SupplierId] => 45678
|
||||
[SalesDate] => 2007-04-08 06:15:15.3330000
|
||||
[Review] => KLMOP
|
||||
)
|
||||
Done
|
187
test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt
Normal file
187
test/functional/sqlsrv/sqlsrv_test_TVP_error_cases.phpt
Normal file
|
@ -0,0 +1,187 @@
|
|||
--TEST--
|
||||
Test various error cases with invalid Table-valued parameter inputs
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
function invokeProc($conn, $proc, $tvpInput, $caseNo, $dir = SQLSRV_PARAM_IN)
|
||||
{
|
||||
if ($dir == SQLSRV_PARAM_IN) {
|
||||
$params = array(array($tvpInput, $dir, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE));
|
||||
} else {
|
||||
$params = array(array(&$tvpInput, $dir, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE));
|
||||
}
|
||||
|
||||
$stmt = sqlsrv_query($conn, $proc, $params);
|
||||
if (!$stmt) {
|
||||
$errors = sqlsrv_errors(SQLSRV_ERR_ALL);
|
||||
if (!empty($errors)) {
|
||||
$count = count($errors);
|
||||
}
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
echo "Error $caseNo: ";
|
||||
echo $errors[$i]['message'] . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function cleanup($conn, $schema, $tvpType, $procName)
|
||||
{
|
||||
global $dropSchema;
|
||||
|
||||
$dropProcedure = dropProcSQL($conn, "[$schema].[$procName]");
|
||||
sqlsrv_query($conn, $dropProcedure);
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType, $schema);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
sqlsrv_query($conn, $dropSchema);
|
||||
}
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('CharacterSet'=>'UTF-8'));
|
||||
|
||||
// Use a different schema instead of dbo
|
||||
$schema = 'Sales DB';
|
||||
$tvpType = 'TestTVP3';
|
||||
$procName = 'SelectTVP3';
|
||||
|
||||
cleanup($conn, $schema, $tvpType, $procName);
|
||||
|
||||
// Create table type and a stored procedure
|
||||
sqlsrv_query($conn, $createSchema);
|
||||
sqlsrv_query($conn, $createTestTVP3);
|
||||
sqlsrv_query($conn, $createSelectTVP3);
|
||||
|
||||
// Create a TVP input array
|
||||
$inputs = [
|
||||
['ABC', 12345, null],
|
||||
['DEF', 6789, null],
|
||||
['GHI', null],
|
||||
];
|
||||
$str = 'dummy';
|
||||
|
||||
// Case (1) - do not provide TVP type name
|
||||
$tvpInput = array($inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 1);
|
||||
|
||||
// Case (2) - use an empty string as TVP type name
|
||||
$tvpInput = array("" => array());
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 2);
|
||||
|
||||
// The TVP name should include the schema
|
||||
$tvpTypeName = "$schema.$tvpType";
|
||||
|
||||
// Case (3) - null inputs
|
||||
$tvpInput = array($tvpTypeName => null);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 3);
|
||||
|
||||
// Case (4) - not using array as inputs
|
||||
$tvpInput = array($tvpTypeName => 1);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 4);
|
||||
|
||||
// Case (5) - invalid TVP type name
|
||||
$tvpInput = array($str => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 5);
|
||||
|
||||
// Case (6) - input rows are not the same size
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 6);
|
||||
|
||||
// Case (7) - input row wrong size
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 12345, null, null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 7);
|
||||
|
||||
// Case (8) - use string keys
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['A' => null, null, null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 8);
|
||||
|
||||
// Case (9) - a row is not an array
|
||||
unset($inputs);
|
||||
$inputs = [null];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 9);
|
||||
|
||||
// Case (10) - a column value used a string key
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 12345, "key"=>null]
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 10);
|
||||
|
||||
// Case (11) - invalid input object for a TVP column
|
||||
class foo
|
||||
{
|
||||
function do_foo(){}
|
||||
}
|
||||
$bar = new foo;
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', 1234, $bar],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 11);
|
||||
|
||||
// Case (12) - invalid input type for a TVP column
|
||||
unset($inputs);
|
||||
$inputs = [
|
||||
['ABC', &$str, null],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 12);
|
||||
|
||||
// Case (13) - bind a TVP as an OUTPUT param
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 13, SQLSRV_PARAM_OUT);
|
||||
|
||||
// Case (14) - test UTF-8 invalid/corrupt string for a TVP column
|
||||
unset($inputs);
|
||||
$utf8 = str_repeat("41", 8188);
|
||||
$utf8 = $utf8 . "e38395e38395";
|
||||
$utf8 = substr_replace($utf8, "fe", 1000, 2);
|
||||
$utf8 = pack("H*", $utf8);
|
||||
|
||||
$inputs = [
|
||||
[$utf8, 1234, null],
|
||||
['DEF', 6789, null],
|
||||
];
|
||||
$tvpInput = array($tvpTypeName => $inputs);
|
||||
invokeProc($conn, $callSelectTVP3, $tvpInput, 14);
|
||||
|
||||
cleanup($conn, $schema, $tvpType, $procName);
|
||||
|
||||
sqlsrv_close($conn);
|
||||
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECTF--
|
||||
Error 1: Expect a non-empty string for a Type Name for Table-Valued Param 1
|
||||
Error 2: Expect a non-empty string for a Type Name for Table-Valued Param 1
|
||||
Error 3: Invalid inputs for Table-Valued Param 1
|
||||
Error 4: Invalid inputs for Table-Valued Param 1
|
||||
Error 5: Failed to get metadata for Table-Valued Param 1
|
||||
Error 6: For Table-Valued Param 1 the number of values in a row is expected to be 3
|
||||
Error 7: For Table-Valued Param 1 the number of values in a row is expected to be 3
|
||||
Error 8: Associative arrays not allowed for Table-Valued Param 1
|
||||
Error 9: Expect an array for each row for Table-Valued Param 1
|
||||
Error 10: Associative arrays not allowed for Table-Valued Param 1
|
||||
Error 11: An invalid type for Table-Valued Param 1 Column 3 was specified
|
||||
Error 12: An invalid type for Table-Valued Param 1 Column 2 was specified
|
||||
Error 13: You cannot return data in a table-valued parameter. Table-valued parameters are input-only.
|
||||
Error 14: An error occurred translating a string for Table-Valued Param 1 Column 1 to UTF-16: %a
|
||||
Done
|
149
test/functional/sqlsrv/sqlsrv_test_TVP_prepare.phpt
Normal file
149
test/functional/sqlsrv/sqlsrv_test_TVP_prepare.phpt
Normal file
|
@ -0,0 +1,149 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using prepare/execute and sqlsrv_send_stream_data with one NULL column
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using prepare/execute and sqlsrv_send_stream_data with one column of NULL input values. This test verifies the fetched results of the basic data types.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
// Create tables
|
||||
sqlsrv_query($conn, $createTVPOrd);
|
||||
sqlsrv_query($conn, $createTVPItem);
|
||||
|
||||
// Create TABLE type for use as a TVP
|
||||
sqlsrv_query($conn, $createTVPParam);
|
||||
|
||||
// Create procedure with TVP parameters
|
||||
sqlsrv_query($conn, $createTVPOrderEntry);
|
||||
|
||||
// Bind parameters for call to TVPOrderEntry
|
||||
$custCode = 'SRV_000';
|
||||
|
||||
// 2 - Items TVP
|
||||
$images = [null, null, null];
|
||||
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], $images[$i]);
|
||||
}
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
$params = array($custCode,
|
||||
array($tvpInput, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE),
|
||||
array(&$ordNo, SQLSRV_PARAM_OUT),
|
||||
array(&$ordDate, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)));
|
||||
|
||||
$options = array("SendStreamParamsAtExec" => 0);
|
||||
$stmt = sqlsrv_prepare($conn, $callTVPOrderEntry, $params, $options);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
$res = sqlsrv_execute($stmt);
|
||||
if (!$res) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Now call sqlsrv_send_stream_data in a loop
|
||||
while (sqlsrv_send_stream_data($stmt)) {
|
||||
}
|
||||
|
||||
sqlsrv_next_result($stmt);
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch CustID
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = sqlsrv_query($conn, $tsql);
|
||||
|
||||
if ($result = sqlsrv_fetch( $stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
$id = sqlsrv_get_field($stmt, 0);
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
} else {
|
||||
echo "Failed in fetching from TVPOrd: " . PHP_EOL;
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
$stmt = sqlsrv_query($conn, 'SELECT * FROM TVPItem ORDER BY ItemNo');
|
||||
while ($row = sqlsrv_fetch_array( $stmt, SQLSRV_FETCH_ASSOC)) {
|
||||
print_r($row);
|
||||
}
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
sqlsrv_close($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 1
|
||||
[ProductCode] => 0062836700
|
||||
[OrderQty] => 367
|
||||
[PackedOn] => 2009-03-12
|
||||
[Label] => AWC Tee Male Shirt
|
||||
[Price] => 20.75
|
||||
[Photo] =>
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 2
|
||||
[ProductCode] => 1250153272
|
||||
[OrderQty] => 256
|
||||
[PackedOn] => 2017-11-07
|
||||
[Label] => Superlight Black Bicycle
|
||||
[Price] => 998.45
|
||||
[Photo] =>
|
||||
)
|
||||
Array
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 3
|
||||
[ProductCode] => 1328781505
|
||||
[OrderQty] => 260
|
||||
[PackedOn] => 2010-03-03
|
||||
[Label] => Silver Chain for Bikes
|
||||
[Price] => 88.98
|
||||
[Photo] =>
|
||||
)
|
||||
Done
|
175
test/functional/sqlsrv/sqlsrv_test_TVP_prepare_buffered.phpt
Normal file
175
test/functional/sqlsrv/sqlsrv_test_TVP_prepare_buffered.phpt
Normal file
|
@ -0,0 +1,175 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using prepare/execute and some NULL inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using prepare/execute and some NULL inputs. This test fetches results as objects using client buffers.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
// Create tables
|
||||
sqlsrv_query($conn, $createTVPOrd);
|
||||
sqlsrv_query($conn, $createTVPItem);
|
||||
|
||||
// Create TABLE type for use as a TVP
|
||||
sqlsrv_query($conn, $createTVPParam);
|
||||
|
||||
// Create procedure with TVP parameters
|
||||
sqlsrv_query($conn, $createTVPOrderEntry);
|
||||
|
||||
// Bind parameters for call to TVPOrderEntry
|
||||
$custCode = 'SRV_789';
|
||||
|
||||
// 2 - Items TVP
|
||||
$image3 = fopen($tvpIncPath. $gif3, 'rb');
|
||||
$images = [null, null, $image3];
|
||||
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], $images[$i]);
|
||||
}
|
||||
|
||||
// Randomly set some values to null
|
||||
$items[0][1] = null;
|
||||
$items[2][3] = null;
|
||||
$items[0][2] = null;
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
$params = array($custCode,
|
||||
array($tvpInput, SQLSRV_PARAM_IN, null, SQLSRV_SQLTYPE_TABLE),
|
||||
array(&$ordNo, SQLSRV_PARAM_OUT),
|
||||
array(&$ordDate, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)));
|
||||
|
||||
$stmt = sqlsrv_prepare($conn, $callTVPOrderEntry, $params);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
$res = sqlsrv_execute($stmt);
|
||||
if (!$res) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
sqlsrv_next_result($stmt);
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch the inserted data from the tables
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = sqlsrv_query($conn, $tsql);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
if ($result = sqlsrv_fetch( $stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
$id = sqlsrv_get_field($stmt, 0);
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
} else {
|
||||
echo "Failed in fetching from TVPOrd: " . PHP_EOL;
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Fetch the only image from the table that is not NULL
|
||||
$tsql = 'SELECT ItemNo, Photo FROM TVPItem WHERE Photo IS NOT NULL ORDER BY ItemNo';
|
||||
$stmt = sqlsrv_query($conn, $tsql, array(), array("Scrollable"=>"buffered"));
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Only the last image is not NULL
|
||||
$index = 2;
|
||||
while (sqlsrv_fetch($stmt)) {
|
||||
$itemNo = sqlsrv_get_field($stmt, 0);
|
||||
echo $itemNo . PHP_EOL;
|
||||
$photo = sqlsrv_get_field($stmt, 1, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
|
||||
if (!verifyBinaryStream($images[$index], $photo)) {
|
||||
echo "Stream data for image $index corrupted!" . PHP_EOL;
|
||||
}
|
||||
}
|
||||
sqlsrv_free_stmt($stmt);
|
||||
fclose($image3);
|
||||
|
||||
// Fetch the other columns next
|
||||
$stmt = sqlsrv_query($conn, $selectTVPItemQuery, array(), array("Scrollable"=>"buffered"));
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
while ($item = sqlsrv_fetch_object($stmt)) {
|
||||
print_r($item);
|
||||
}
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
sqlsrv_close($conn);
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
3
|
||||
stdClass Object
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 1
|
||||
[ProductCode] => 0062836700
|
||||
[OrderQty] =>
|
||||
[PackedOn] =>
|
||||
[Label] => AWC Tee Male Shirt
|
||||
[Price] => 20.75
|
||||
)
|
||||
stdClass Object
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 2
|
||||
[ProductCode] => 1250153272
|
||||
[OrderQty] => 256
|
||||
[PackedOn] => 2017-11-07
|
||||
[Label] => Superlight Black Bicycle
|
||||
[Price] => 998.45
|
||||
)
|
||||
stdClass Object
|
||||
(
|
||||
[OrdNo] => 1
|
||||
[ItemNo] => 3
|
||||
[ProductCode] => 1328781505
|
||||
[OrderQty] => 260
|
||||
[PackedOn] => 2010-03-03
|
||||
[Label] =>
|
||||
[Price] => 88.98
|
||||
)
|
||||
Done
|
166
test/functional/sqlsrv/sqlsrv_test_TVP_query.phpt
Normal file
166
test/functional/sqlsrv/sqlsrv_test_TVP_query.phpt
Normal file
|
@ -0,0 +1,166 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using direct queries and no null values
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using direct queries and no null values. This test verifies the fetched results of all types.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||
|
||||
$tvpType = 'TVPParam';
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
// Create tables
|
||||
sqlsrv_query($conn, $createTVPOrd);
|
||||
sqlsrv_query($conn, $createTVPItem);
|
||||
|
||||
// Create TABLE type for use as a TVP
|
||||
sqlsrv_query($conn, $createTVPParam);
|
||||
|
||||
// Create procedure with TVP parameters
|
||||
sqlsrv_query($conn, $createTVPOrderEntry);
|
||||
|
||||
// Bind parameters for call to TVPOrderEntry
|
||||
$custCode = 'SRV_123';
|
||||
|
||||
// 2 - Items TVP
|
||||
$image1 = fopen($tvpIncPath. $gif1, 'rb');
|
||||
$image2 = fopen($tvpIncPath. $gif2, 'rb');
|
||||
$image3 = fopen($tvpIncPath. $gif3, 'rb');
|
||||
$images = [$image1, $image2, $image3];
|
||||
|
||||
for ($i = 0; $i < count($items); $i++) {
|
||||
array_push($items[$i], $images[$i]);
|
||||
}
|
||||
|
||||
// Create a TVP input array
|
||||
$tvpInput = array($tvpType => $items);
|
||||
|
||||
$ordNo = 0;
|
||||
$ordDate = null;
|
||||
|
||||
$params = array($custCode,
|
||||
array($tvpInput, null, null, SQLSRV_SQLTYPE_TABLE),
|
||||
array(&$ordNo, SQLSRV_PARAM_OUT),
|
||||
array(&$ordDate, SQLSRV_PARAM_OUT, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR)));
|
||||
|
||||
$stmt = sqlsrv_query($conn, $callTVPOrderEntry, $params);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
sqlsrv_next_result($stmt);
|
||||
|
||||
// Verify the results
|
||||
echo "Order Number: $ordNo" . PHP_EOL;
|
||||
|
||||
$today = getTodayDateAsString($conn);
|
||||
if ($ordDate != $today) {
|
||||
echo "Order Date unexpected: ";
|
||||
var_dump($ordDate);
|
||||
}
|
||||
|
||||
// Fetch CustID
|
||||
$tsql = 'SELECT CustID FROM TVPOrd';
|
||||
$stmt = sqlsrv_query($conn, $tsql);
|
||||
|
||||
if ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
$id = sqlsrv_get_field($stmt, 0);
|
||||
if ($id != $custCode) {
|
||||
echo "Customer ID unexpected: " . PHP_EOL;
|
||||
var_dump($id);
|
||||
}
|
||||
} else {
|
||||
echo "Failed in fetching from TVPOrd: " . PHP_EOL;
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Fetch other basic types
|
||||
$stmt = sqlsrv_query($conn, $selectTVPItemQuery);
|
||||
while ($row = sqlsrv_fetch_array($stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
print_r($row);
|
||||
}
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
// Fetch the inserted images from the table and verify them
|
||||
$tsql = 'SELECT Photo FROM TVPItem ORDER BY ItemNo';
|
||||
$stmt = sqlsrv_query($conn, $tsql);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
$index = 0;
|
||||
while (sqlsrv_fetch($stmt)) {
|
||||
$photo = sqlsrv_get_field($stmt, 0, SQLSRV_PHPTYPE_STREAM(SQLSRV_ENC_BINARY));
|
||||
if (!verifyBinaryStream($images[$index], $photo)) {
|
||||
echo "Image data corrupted for row ". ($index + 1) . PHP_EOL;
|
||||
}
|
||||
$index++;
|
||||
}
|
||||
if ($index == 0) {
|
||||
echo 'Failed in fetching binary data' . PHP_EOL;
|
||||
}
|
||||
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
fclose($image1);
|
||||
fclose($image2);
|
||||
fclose($image3);
|
||||
|
||||
dropProc($conn, 'TVPOrderEntry');
|
||||
dropTable($conn, 'TVPOrd');
|
||||
dropTable($conn, 'TVPItem');
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
sqlsrv_close($conn);
|
||||
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Order Number: 1
|
||||
Array
|
||||
(
|
||||
[0] => 1
|
||||
[1] => 1
|
||||
[2] => 0062836700
|
||||
[3] => 367
|
||||
[4] => 2009-03-12
|
||||
[5] => AWC Tee Male Shirt
|
||||
[6] => 20.75
|
||||
)
|
||||
Array
|
||||
(
|
||||
[0] => 1
|
||||
[1] => 2
|
||||
[2] => 1250153272
|
||||
[3] => 256
|
||||
[4] => 2017-11-07
|
||||
[5] => Superlight Black Bicycle
|
||||
[6] => 998.45
|
||||
)
|
||||
Array
|
||||
(
|
||||
[0] => 1
|
||||
[1] => 3
|
||||
[2] => 1328781505
|
||||
[3] => 260
|
||||
[4] => 2010-03-03
|
||||
[5] => Silver Chain for Bikes
|
||||
[6] => 88.98
|
||||
)
|
||||
Done
|
123
test/functional/sqlsrv/sqlsrv_test_TVP_query_binary_fields.phpt
Normal file
123
test/functional/sqlsrv/sqlsrv_test_TVP_query_binary_fields.phpt
Normal file
|
@ -0,0 +1,123 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using direct queries and sqlsrv_send_stream_data with random null inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using direct queries and sqlsrv_send_stream_data with random null inputs. This test verifies the fetched results of all columns.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
$conn = connect(array('CharacterSet'=>'UTF-8'));
|
||||
|
||||
dropProc($conn, 'SelectTVP2');
|
||||
$tvpType = 'TestTVP2';
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
// Create table type and a stored procedure
|
||||
sqlsrv_query($conn, $createTestTVP2);
|
||||
sqlsrv_query($conn, $createSelectTVP2);
|
||||
|
||||
// Create column arrays
|
||||
$str1 = "Šỡოē šâოрĺẻ ÅŚÇÏЇ-ťếхţ";
|
||||
$longStr1 = str_repeat($str1, 1500);
|
||||
$str2 = pack("H*", '49006427500048005000' ); // I'LOVE_SYMBOL'PHP
|
||||
$longStr2 = str_repeat($str2, 2000);
|
||||
|
||||
$bin1 = pack('H*', '0FD1CEFACE');
|
||||
$bin2 = pack('H*', '0001020304');
|
||||
$bin3 = hex2bin('616263646566676869'); // abcdefghi
|
||||
$bin4 = pack('H*', '7A61CC86C7BDCEB2F18FB3BF');
|
||||
|
||||
$xml = "<XmlTestData><Letters1>The quick brown fox jumps over the lazy dog</Letters1><Digits1>0123456789</Digits1></XmlTestData>";
|
||||
|
||||
$c01 = [null, $str1, $str2];
|
||||
$c02 = [null, $longStr1, $longStr2];
|
||||
$c03 = [null, null, 999];
|
||||
$c04 = [null, 3.1415927, null];
|
||||
$c05 = [$bin1, null, $bin2];
|
||||
$c06 = [null, $bin3, $bin4];
|
||||
$c07 = [null, '1234.56', '9876.54'];
|
||||
$c08 = [null, null, $xml];
|
||||
$c09 = [9876, $str1, (0x0FAB)];
|
||||
|
||||
// Create a TVP input array
|
||||
$nrows = 3;
|
||||
$ncols = 9;
|
||||
$inputs = array();
|
||||
for ($i = 0; $i < $nrows; $i++) {
|
||||
$rowValues = array($c01[$i], $c02[$i], $c03[$i], $c04[$i], $c05[$i], $c06[$i], $c07[$i], $c08[$i], $c09[$i]);
|
||||
array_push($inputs, $rowValues);
|
||||
}
|
||||
|
||||
$tvpInput = array($tvpType => $inputs);
|
||||
$params = array(array($tvpInput, SQLSRV_PARAM_IN, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE));
|
||||
|
||||
$stmt = sqlsrv_query($conn, $callSelectTVP2, $params);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Verify the results
|
||||
$row = 0;
|
||||
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
// For strings, compare their values
|
||||
for ($col = 0; $col < 2; $col++) {
|
||||
$field = sqlsrv_get_field($stmt, $col, SQLSRV_PHPTYPE_STRING('UTF-8'));
|
||||
if ($field != $inputs[$row][$col]) {
|
||||
echo 'Unexpected data at row ' . ($row + 1) . ' and col ' . ($col + 1) . PHP_EOL;
|
||||
echo 'Expected: ' . $inputs[$row][$col] . PHP_EOL;
|
||||
echo 'Fetched: ' . $field . PHP_EOL;
|
||||
}
|
||||
}
|
||||
// For other types, print them
|
||||
echo 'Row ' . ($row + 1) . ': from Col ' . ($col + 1) . ' to ' . $ncols . PHP_EOL;
|
||||
for ($col = 2; $col < $ncols; $col++) {
|
||||
$field = sqlsrv_get_field($stmt, $col, SQLSRV_PHPTYPE_STRING('UTF-8'));
|
||||
var_dump($field);
|
||||
}
|
||||
echo PHP_EOL;
|
||||
$row++;
|
||||
}
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
dropProc($conn, 'SelectTVP2');
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
sqlsrv_close($conn);
|
||||
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Row 1: from Col 3 to 9
|
||||
NULL
|
||||
NULL
|
||||
string(10) "0FD1CEFACE"
|
||||
NULL
|
||||
NULL
|
||||
NULL
|
||||
string(4) "9876"
|
||||
|
||||
Row 2: from Col 3 to 9
|
||||
NULL
|
||||
string(9) "3.1415927"
|
||||
NULL
|
||||
string(18) "616263646566676869"
|
||||
string(9) "1234.5600"
|
||||
NULL
|
||||
string(46) "Šỡოē šâოрĺẻ ÅŚÇÏЇ-ťếхţ"
|
||||
|
||||
Row 3: from Col 3 to 9
|
||||
string(3) "999"
|
||||
NULL
|
||||
string(10) "0001020304"
|
||||
string(24) "7A61CC86C7BDCEB2F18FB3BF"
|
||||
string(9) "9876.5400"
|
||||
string(120) "<XmlTestData><Letters1>The quick brown fox jumps over the lazy dog</Letters1><Digits1>0123456789</Digits1></XmlTestData>"
|
||||
string(4) "4011"
|
||||
|
||||
Done
|
131
test/functional/sqlsrv/sqlsrv_test_TVP_query_with_nulls.phpt
Normal file
131
test/functional/sqlsrv/sqlsrv_test_TVP_query_with_nulls.phpt
Normal file
|
@ -0,0 +1,131 @@
|
|||
--TEST--
|
||||
Test Table-valued parameter using direct queries and sqlsrv_send_stream_data with random null inputs
|
||||
--DESCRIPTION--
|
||||
Test Table-valued parameter using direct queries and sqlsrv_send_stream_data with random null inputs. This test verifies the fetched results of all columns.
|
||||
--ENV--
|
||||
PHPT_EXEC=true
|
||||
--SKIPIF--
|
||||
<?php require('skipif.inc'); ?>
|
||||
--FILE--
|
||||
<?php
|
||||
require_once('MsCommon.inc');
|
||||
|
||||
date_default_timezone_set('America/Los_Angeles');
|
||||
|
||||
sqlsrv_configure('LogSeverity', SQLSRV_LOG_SEVERITY_ALL);
|
||||
|
||||
$conn = connect(array('ReturnDatesAsStrings' => true));
|
||||
|
||||
dropProc($conn, 'SelectTVP');
|
||||
|
||||
$tvpType = 'TestTVP';
|
||||
$dropTableType = dropTableTypeSQL($conn, $tvpType);
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
|
||||
// Create table type and a stored procedure
|
||||
sqlsrv_query($conn, $createTestTVP);
|
||||
sqlsrv_query($conn, $createSelectTVP);
|
||||
|
||||
// Create column arrays
|
||||
$str = '';
|
||||
for ($i = 0; $i < 255; $i++) {
|
||||
$str .= chr(($i % 95) + 32);
|
||||
}
|
||||
$longStr = str_repeat($str, 3000);
|
||||
|
||||
$c01 = [$str, 'ABCDE', ''];
|
||||
$c02 = ['abcdefghijklmnopqrstuvwxyz', null, $longStr];
|
||||
$c03 = [null, 0, 1];
|
||||
$c04 = [null,
|
||||
date_create('1997-02-13 12:43:10'),
|
||||
null];
|
||||
$c05 = ["2010-12-31 12:40:12.56679", null, "1965-02-18 23:59:59.43258"];
|
||||
$c06 = ['4CDBC69F-F0EE-4963-8F17-24DD47090126',
|
||||
'0F12A09D-D614-4998-AB1F-BD7CDBF6E3FE',
|
||||
null];
|
||||
$c07 = [null, '-9223372036854775808', '9223372036854775807'];
|
||||
$c08 = [null, -1.79E+308, 1.79E+308];
|
||||
$c09 = ['31234567890123.141243449787580175325274',
|
||||
'0.000000000000000000000001',
|
||||
'99999999999999.999999999999999999999999'];
|
||||
|
||||
// Create a TVP input array
|
||||
$nrows = 3;
|
||||
$ncols = 9;
|
||||
$inputs = array();
|
||||
for ($i = 0; $i < $nrows; $i++) {
|
||||
$rowValues = array($c01[$i], $c02[$i], $c03[$i], $c04[$i], $c05[$i], $c06[$i], $c07[$i], $c08[$i], $c09[$i]);
|
||||
array_push($inputs, $rowValues);
|
||||
}
|
||||
|
||||
$tvpInput = array($tvpType => $inputs);
|
||||
$params = array(array($tvpInput, null, SQLSRV_PHPTYPE_TABLE, SQLSRV_SQLTYPE_TABLE));
|
||||
|
||||
$options = array("SendStreamParamsAtExec" => 0);
|
||||
$stmt = sqlsrv_query($conn, $callSelectTVP, $params, $options);
|
||||
if (!$stmt) {
|
||||
print_r(sqlsrv_errors());
|
||||
}
|
||||
|
||||
// Now call sqlsrv_send_stream_data in a loop
|
||||
while (sqlsrv_send_stream_data($stmt)) {
|
||||
}
|
||||
|
||||
// Verify the results
|
||||
$row = 0;
|
||||
while ($result = sqlsrv_fetch($stmt, SQLSRV_FETCH_NUMERIC)) {
|
||||
// For strings, compare their values
|
||||
for ($col = 0; $col < 2; $col++) {
|
||||
$field = sqlsrv_get_field($stmt, $col, SQLSRV_PHPTYPE_STRING(SQLSRV_ENC_CHAR));
|
||||
if ($field != $inputs[$row][$col]) {
|
||||
echo 'Unexpected data at row ' . ($row + 1) . ' and col ' . ($col + 1) . PHP_EOL;
|
||||
echo 'Expected: ' . $inputs[$row][$col] . PHP_EOL;
|
||||
echo 'Fetched: ' . $field . PHP_EOL;
|
||||
}
|
||||
}
|
||||
// For other types, print them
|
||||
echo 'Row ' . ($row + 1) . ': from Col ' . ($col + 1) . ' to ' . $ncols . PHP_EOL;
|
||||
for ($col = 2; $col < $ncols; $col++) {
|
||||
$field = sqlsrv_get_field($stmt, $col);
|
||||
var_dump($field);
|
||||
}
|
||||
echo PHP_EOL;
|
||||
$row++;
|
||||
}
|
||||
sqlsrv_free_stmt($stmt);
|
||||
|
||||
dropProc($conn, 'SelectTVP');
|
||||
sqlsrv_query($conn, $dropTableType);
|
||||
sqlsrv_close($conn);
|
||||
|
||||
echo "Done" . PHP_EOL;
|
||||
?>
|
||||
--EXPECT--
|
||||
Row 1: from Col 3 to 9
|
||||
NULL
|
||||
NULL
|
||||
string(25) "2010-12-31 12:40:12.56679"
|
||||
string(36) "4CDBC69F-F0EE-4963-8F17-24DD47090126"
|
||||
NULL
|
||||
NULL
|
||||
string(39) "31234567890123.141243449787580175325274"
|
||||
|
||||
Row 2: from Col 3 to 9
|
||||
int(0)
|
||||
string(19) "1997-02-13 12:43:00"
|
||||
NULL
|
||||
string(36) "0F12A09D-D614-4998-AB1F-BD7CDBF6E3FE"
|
||||
string(20) "-9223372036854775808"
|
||||
float(-1.79E+308)
|
||||
string(25) ".000000000000000000000001"
|
||||
|
||||
Row 3: from Col 3 to 9
|
||||
int(1)
|
||||
NULL
|
||||
string(25) "1965-02-18 23:59:59.43258"
|
||||
NULL
|
||||
string(19) "9223372036854775807"
|
||||
float(1.79E+308)
|
||||
string(39) "99999999999999.999999999999999999999999"
|
||||
|
||||
Done
|
Loading…
Reference in a new issue