зеркало из https://github.com/microsoft/msphpsql.git
2217 строки
78 KiB
C++
2217 строки
78 KiB
C++
//---------------------------------------------------------------------------------------------------------------------------------
|
|
// File: stmt.cpp
|
|
//
|
|
// Contents: Routines that use statement handles
|
|
//
|
|
// Microsoft Drivers 5.12 for PHP for SQL Server
|
|
// Copyright(c) Microsoft Corporation
|
|
// All rights reserved.
|
|
// MIT License
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files(the ""Software""),
|
|
// to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
// and / or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions :
|
|
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
// THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
// IN THE SOFTWARE.
|
|
//---------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
// *** header files ***
|
|
extern "C" {
|
|
#include "php_sqlsrv.h"
|
|
}
|
|
|
|
#include "php_sqlsrv_int.h"
|
|
|
|
#ifdef _WIN32
|
|
#include <sal.h>
|
|
#endif // _WIN32
|
|
|
|
//
|
|
// *** internal variables and constants ***
|
|
//
|
|
// our resource descriptor assigned in minit
|
|
int ss_sqlsrv_stmt::descriptor = 0;
|
|
const char* ss_sqlsrv_stmt::resource_name = "ss_sqlsrv_stmt";
|
|
|
|
namespace {
|
|
|
|
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
|
|
unsigned int current_log_subsystem = LOG_STMT;
|
|
|
|
// constants used as invalid types for type errors
|
|
const zend_uchar PHPTYPE_INVALID = SQLSRV_PHPTYPE_INVALID;
|
|
const int SQLSRV_INVALID_PRECISION = -1;
|
|
const SQLUINTEGER SQLSRV_INVALID_SIZE = (~1U);
|
|
const int SQLSRV_INVALID_SCALE = -1;
|
|
const int SQLSRV_SIZE_MAX_TYPE = -1;
|
|
|
|
// constants for maximums in SQL Server
|
|
const int SQL_SERVER_MAX_FIELD_SIZE = 8000;
|
|
const int SQL_SERVER_MAX_PRECISION = 38;
|
|
|
|
// default class used when no class is specified by sqlsrv_fetch_object
|
|
const char STDCLASS_NAME[] = "stdclass";
|
|
const char STDCLASS_NAME_LEN = sizeof( STDCLASS_NAME ) - 1;
|
|
|
|
// map a Zend PHP type constant to our constant type
|
|
enum SQLSRV_PHPTYPE zend_to_sqlsrv_phptype[] = {
|
|
SQLSRV_PHPTYPE_INVALID,
|
|
SQLSRV_PHPTYPE_NULL,
|
|
SQLSRV_PHPTYPE_INVALID,
|
|
SQLSRV_PHPTYPE_INVALID,
|
|
SQLSRV_PHPTYPE_INT,
|
|
SQLSRV_PHPTYPE_FLOAT,
|
|
SQLSRV_PHPTYPE_STRING,
|
|
SQLSRV_PHPTYPE_TABLE,
|
|
SQLSRV_PHPTYPE_DATETIME,
|
|
SQLSRV_PHPTYPE_STREAM,
|
|
SQLSRV_PHPTYPE_INVALID,
|
|
SQLSRV_PHPTYPE_INVALID,
|
|
SQLSRV_PHPTYPE_INVALID
|
|
};
|
|
|
|
// constant strings used for the field metadata results
|
|
// (char to avoid having to cast them where they are used)
|
|
namespace FieldMetaData {
|
|
|
|
const char* NAME = "Name";
|
|
const char* TYPE = "Type";
|
|
const char* SIZE = "Size";
|
|
const char* PREC = "Precision";
|
|
const char* SCALE = "Scale";
|
|
const char* NULLABLE = "Nullable";
|
|
|
|
}
|
|
|
|
/* internal functions */
|
|
|
|
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval );
|
|
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt* stmt);
|
|
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names );
|
|
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
|
|
_Out_ SQLSMALLINT* decimal_digits );
|
|
sqlsrv_phptype determine_sqlsrv_php_type( sqlsrv_stmt const* stmt, SQLINTEGER sql_type, SQLUINTEGER size, bool prefer_string );
|
|
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt );
|
|
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type );
|
|
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype type );
|
|
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
|
|
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
|
|
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type );
|
|
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding );
|
|
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index,
|
|
_Out_ SQLSMALLINT& direction, _Out_ SQLSRV_PHPTYPE& php_out_type,
|
|
_Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
|
|
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits);
|
|
|
|
}
|
|
|
|
// query options for cursor types
|
|
namespace SSCursorTypes {
|
|
|
|
const char QUERY_OPTION_SCROLLABLE_STATIC[] = "static";
|
|
const char QUERY_OPTION_SCROLLABLE_DYNAMIC[] = "dynamic";
|
|
const char QUERY_OPTION_SCROLLABLE_KEYSET[] = "keyset";
|
|
const char QUERY_OPTION_SCROLLABLE_FORWARD[] = "forward";
|
|
const char QUERY_OPTION_SCROLLABLE_BUFFERED[] = "buffered";
|
|
}
|
|
|
|
ss_sqlsrv_stmt::ss_sqlsrv_stmt( _In_ sqlsrv_conn* c, _In_ SQLHANDLE handle, _In_ error_callback e, _In_ void* drv ) :
|
|
sqlsrv_stmt( c, handle, e, drv ),
|
|
prepared( false ),
|
|
conn_index( -1 ),
|
|
params_z( NULL ),
|
|
fetch_field_names( NULL ),
|
|
fetch_fields_count ( 0 )
|
|
{
|
|
core_sqlsrv_set_buffered_query_limit( this, SQLSRV_G( buffered_query_limit ) );
|
|
|
|
// inherit other values based on the corresponding connection options
|
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
|
date_as_string = ss_conn->date_as_string;
|
|
format_decimals = ss_conn->format_decimals;
|
|
decimal_places = ss_conn->decimal_places;
|
|
}
|
|
|
|
ss_sqlsrv_stmt::~ss_sqlsrv_stmt( void )
|
|
{
|
|
if( fetch_field_names != NULL ) {
|
|
|
|
for( int i=0; i < fetch_fields_count; ++i ) {
|
|
|
|
sqlsrv_free( fetch_field_names[i].name );
|
|
}
|
|
sqlsrv_free( fetch_field_names );
|
|
}
|
|
if( params_z ) {
|
|
zval_ptr_dtor( params_z );
|
|
sqlsrv_free(params_z);
|
|
}
|
|
}
|
|
|
|
// to be called whenever a new result set is created, such as after an
|
|
// execute or next_result. Resets the state variables and calls the subclass.
|
|
void ss_sqlsrv_stmt::new_result_set( void )
|
|
{
|
|
if( fetch_field_names != NULL ) {
|
|
|
|
for( int i=0; i < fetch_fields_count; ++i ) {
|
|
|
|
sqlsrv_free( fetch_field_names[i].name );
|
|
}
|
|
sqlsrv_free( fetch_field_names );
|
|
}
|
|
|
|
fetch_field_names = NULL;
|
|
fetch_fields_count = 0;
|
|
sqlsrv_stmt::new_result_set();
|
|
}
|
|
|
|
// Returns a php type for a given sql type. Also sets the encoding wherever applicable.
|
|
sqlsrv_phptype ss_sqlsrv_stmt::sql_type_to_php_type( _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string_to_stream )
|
|
{
|
|
sqlsrv_phptype ss_phptype;
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
|
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
|
|
|
|
switch( sql_type ) {
|
|
|
|
case SQL_BIGINT:
|
|
case SQL_CHAR:
|
|
case SQL_DECIMAL:
|
|
case SQL_GUID:
|
|
case SQL_NUMERIC:
|
|
case SQL_WCHAR:
|
|
case SQL_SS_VARIANT:
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
|
break;
|
|
|
|
case SQL_VARCHAR:
|
|
case SQL_WVARCHAR:
|
|
case SQL_LONGVARCHAR:
|
|
case SQL_WLONGVARCHAR:
|
|
case SQL_SS_XML:
|
|
if( prefer_string_to_stream || size != SQL_SS_LENGTH_UNLIMITED ) {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
|
}
|
|
else {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
|
|
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
|
}
|
|
break;
|
|
|
|
case SQL_BIT:
|
|
case SQL_INTEGER:
|
|
case SQL_SMALLINT:
|
|
case SQL_TINYINT:
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
|
|
break;
|
|
|
|
case SQL_BINARY:
|
|
case SQL_LONGVARBINARY:
|
|
case SQL_VARBINARY:
|
|
case SQL_SS_UDT:
|
|
if( prefer_string_to_stream ) {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
|
|
}
|
|
else {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
|
|
ss_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
|
|
}
|
|
break;
|
|
|
|
case SQL_FLOAT:
|
|
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:
|
|
case SQL_TYPE_TIMESTAMP:
|
|
if (this->date_as_string) {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
ss_phptype.typeinfo.encoding = this->conn->encoding();
|
|
}
|
|
else {
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ss_phptype.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
|
break;
|
|
}
|
|
|
|
return ss_phptype;
|
|
}
|
|
|
|
// statement specific parameter proccessing. Uses the generic function specialised to return a statement
|
|
// resource.
|
|
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
|
|
rsrc = process_params<ss_sqlsrv_stmt>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ## __VA_ARGS__ );\
|
|
if( rsrc == NULL ) { \
|
|
RETURN_FALSE; \
|
|
}
|
|
|
|
// sqlsrv_execute( resource $stmt )
|
|
//
|
|
// Executes a previously prepared statement. See sqlsrv_prepare for information
|
|
// on preparing a statement for execution.
|
|
//
|
|
// This function is ideal for executing a prepared statement multiple times with
|
|
// different parameter values. See the MSDN documentation
|
|
//
|
|
// Parameters
|
|
// $stmt: A resource specifying the statement to be executed. For more
|
|
// information about how to create a statement resource, see sqlsrv_prepare.
|
|
//
|
|
// Return Value
|
|
// A Boolean value: true if the statement was successfully executed. Otherwise, false.
|
|
|
|
PHP_FUNCTION( sqlsrv_execute )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_execute" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
|
|
try {
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
CHECK_CUSTOM_ERROR(( !stmt->prepared ), stmt, SS_SQLSRV_ERROR_STATEMENT_NOT_PREPARED ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// prepare for the next execution by flushing anything remaining in the result set
|
|
if( stmt->executed ) {
|
|
|
|
// to prepare to execute the next statement, we skip any remaining results (and skip parameter finalization too)
|
|
while( stmt->past_next_result_end == false ) {
|
|
|
|
core_sqlsrv_next_result( stmt, false, false );
|
|
}
|
|
}
|
|
|
|
// bind parameters before executing
|
|
bind_params( stmt );
|
|
|
|
core_sqlsrv_execute( stmt );
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_execute: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_fetch( resource $stmt )
|
|
//
|
|
// Makes the next row of a result set available for reading. Use
|
|
// sqlsrv_get_field to read fields of the row.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement. A
|
|
// statement must be executed before results can be retrieved. For information
|
|
// on executing a statement, see sqlsrv_query and sqlsrv_execute.
|
|
//
|
|
// Return Value
|
|
// If the next row of the result set was successfully retrieved, true is
|
|
// returned. If there are no more results in the result set, null is
|
|
// returned. If an error occured, false is returned
|
|
|
|
PHP_FUNCTION( sqlsrv_fetch )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_fetch" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
// NOTE: zend_parse_parameter expect zend_long when the type spec is 'l',and core_sqlsrv_fetch expect short int
|
|
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
|
|
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
|
|
|
|
// take only the statement resource
|
|
PROCESS_PARAMS( stmt, "r|ll", _FN_, 2, &fetch_style, &fetch_offset, NULL );
|
|
|
|
try {
|
|
|
|
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
|
|
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
|
|
if( !result ) {
|
|
RETURN_NULL();
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_fetch: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_fetch_array( resource $stmt [, int $fetchType] )
|
|
//
|
|
// Retrieves the next row of data as an array.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement.
|
|
// $fetchType [OPTIONAL]: A predefined constant. See SQLSRV_FETCH_TYPE in php_sqlsrv.h
|
|
//
|
|
// Return Value
|
|
// If a row of data is retrieved, an array is returned. If there are no more
|
|
// rows to retrieve, null is returned. If an error occurs, false is returned.
|
|
// Based on the value of the $fetchType parameter, the returned array can be a
|
|
// numerically indexed array, an associative array, or both. By default, an
|
|
// array with both numeric and associative keys is returned. The data type of a
|
|
// value in the returned array will be the default PHP data type. For
|
|
// information about default PHP data types, see Default PHP Data Types.
|
|
|
|
PHP_FUNCTION( sqlsrv_fetch_array )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_fetch_array" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
zend_long fetch_type = SQLSRV_FETCH_BOTH; // default value for parameter if one isn't supplied
|
|
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
|
|
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
|
|
|
|
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
|
|
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
|
|
PROCESS_PARAMS( stmt, "r|lll", _FN_, 3, &fetch_type, &fetch_style, &fetch_offset, NULL );
|
|
|
|
try {
|
|
|
|
CHECK_CUSTOM_ERROR(( fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH ), stmt,
|
|
SS_SQLSRV_ERROR_INVALID_FETCH_TYPE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
|
|
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
|
|
if( !result ) {
|
|
RETURN_NULL();
|
|
}
|
|
zval fields;
|
|
ZVAL_UNDEF( &fields );
|
|
fetch_fields_common( stmt, fetch_type, fields, true /*allow_empty_field_names*/ );
|
|
RETURN_ARR( Z_ARRVAL( fields ));
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_fetch_array: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_field_metadata( resource $stmt )
|
|
//
|
|
// Retrieves metadata for the fields of a prepared statement. For information
|
|
// about preparing a statement, see sqlsrv_query or sqlsrv_prepare. Note that
|
|
// sqlsrv_field_metadata can be called on any prepared statement, pre- or
|
|
// post-execution.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource for which field metadata is sought.
|
|
//
|
|
// Return Value
|
|
// retrieve an array of metadata for the current result set on a statement. Each element of the
|
|
// array is a sub-array containing 5 elements accessed by key:
|
|
// name - name of the field.
|
|
// type - integer of the type. Can be compared against the SQLSRV_SQLTYPE constants.
|
|
// size - length of the field. null if the field uses precision and scale instead.
|
|
// precision - number of digits in a numeric field. null if the field uses size.
|
|
// scale - number of decimal digits in a numeric field. null if the field uses sizes.
|
|
// is_nullable - if the field may contain a NULL instead of a value
|
|
// false is returned if an error occurs retrieving the metadata
|
|
|
|
PHP_FUNCTION( sqlsrv_field_metadata )
|
|
{
|
|
sqlsrv_stmt* stmt = NULL;
|
|
|
|
LOG_FUNCTION( "sqlsrv_field_metadata" );
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// get the number of fields in the resultset and its metadata if not exists
|
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
|
|
|
if (stmt->data_classification) {
|
|
core_sqlsrv_sensitivity_metadata(stmt);
|
|
}
|
|
|
|
zval result_meta_data;
|
|
ZVAL_UNDEF(&result_meta_data);
|
|
array_init(&result_meta_data);
|
|
|
|
for( SQLSMALLINT f = 0; f < num_cols; ++f ) {
|
|
field_meta_data* core_meta_data = stmt->current_meta_data[f];
|
|
|
|
// initialize the array
|
|
zval field_array;
|
|
ZVAL_UNDEF( &field_array );
|
|
array_init(&field_array );
|
|
|
|
// add the field name to the associative array but keep a copy
|
|
add_assoc_string(&field_array, FieldMetaData::NAME, reinterpret_cast<char*>(core_meta_data->field_name.get()));
|
|
|
|
//core::sqlsrv_add_assoc_long( *stmt, &field_array, FieldMetaData::TYPE, core_meta_data->field_type );
|
|
add_assoc_long(&field_array, FieldMetaData::TYPE, core_meta_data->field_type);
|
|
|
|
switch( core_meta_data->field_type ) {
|
|
case SQL_DECIMAL:
|
|
case SQL_NUMERIC:
|
|
case SQL_TYPE_TIMESTAMP:
|
|
case SQL_TYPE_DATE:
|
|
case SQL_SS_TIME2:
|
|
case SQL_SS_TIMESTAMPOFFSET:
|
|
add_assoc_null(&field_array, FieldMetaData::SIZE);
|
|
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
|
|
add_assoc_long(&field_array, FieldMetaData::SCALE, core_meta_data->field_scale);
|
|
break;
|
|
case SQL_BIT:
|
|
case SQL_TINYINT:
|
|
case SQL_SMALLINT:
|
|
case SQL_INTEGER:
|
|
case SQL_BIGINT:
|
|
case SQL_REAL:
|
|
case SQL_FLOAT:
|
|
case SQL_DOUBLE:
|
|
add_assoc_null(&field_array, FieldMetaData::SIZE);
|
|
add_assoc_long(&field_array, FieldMetaData::PREC, core_meta_data->field_precision);
|
|
add_assoc_null(&field_array, FieldMetaData::SCALE);
|
|
break;
|
|
default:
|
|
add_assoc_long(&field_array, FieldMetaData::SIZE, core_meta_data->field_size);
|
|
add_assoc_null(&field_array, FieldMetaData::PREC);
|
|
add_assoc_null(&field_array, FieldMetaData::SCALE);
|
|
break;
|
|
}
|
|
|
|
// add the nullability to the array
|
|
add_assoc_long(&field_array, FieldMetaData::NULLABLE, core_meta_data->field_is_nullable);
|
|
|
|
if (stmt->data_classification) {
|
|
data_classification::fill_column_sensitivity_array(stmt, f, &field_array);
|
|
}
|
|
|
|
// add this field's meta data to the result set meta data
|
|
add_next_index_zval(&result_meta_data, &field_array);
|
|
}
|
|
|
|
// return our built collection and transfer ownership
|
|
RETURN_ZVAL(&result_meta_data, 1, 1);
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_field_metadata: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_next_result( resource $stmt )
|
|
//
|
|
// Makes the next result (result set, row count, or output parameter) of the
|
|
// specified statement active. The first (or only) result returned by a batch
|
|
// query or stored procedure is active without a call to sqlsrv_next_result.
|
|
// Any output parameters bound are only available after sqlsrv_next_result returns
|
|
// null as per ODBC Driver 11 for SQL Server specs: http://msdn.microsoft.com/en-us/library/ms403283.aspx
|
|
//
|
|
// Parameters
|
|
// $stmt: The executed statement on which the next result is made active.
|
|
//
|
|
// Return Value
|
|
// If the next result was successfully made active, the Boolean value true is
|
|
// returned. If an error occurred in making the next result active, false is
|
|
// returned. If no more results are available, null is returned.
|
|
|
|
PHP_FUNCTION( sqlsrv_next_result )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_next_result" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
core_sqlsrv_next_result( stmt, true );
|
|
|
|
// clear the current meta data since the new result will generate new meta data
|
|
stmt->clean_up_results_metadata();
|
|
|
|
if( stmt->past_next_result_end ) {
|
|
|
|
RETURN_NULL();
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_next_result: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_rows_affected( resource $stmt )
|
|
//
|
|
// Returns the number of rows modified by the last statement executed. This
|
|
// function does not return the number of rows returned by a SELECT statement.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement.
|
|
//
|
|
// Return Value
|
|
// An integer indicating the number of rows modified by the last executed
|
|
// statement. If no rows were modified, zero (0) is returned. If no information
|
|
// about the number of modified rows is available, negative one (-1) is
|
|
// returned. If an error occurred in retrieving the number of modified rows,
|
|
// false is returned. See SQLRowCount in the MSDN ODBC documentation.
|
|
|
|
PHP_FUNCTION( sqlsrv_rows_affected )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_rows_affected" );
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
SQLLEN rows = -1;
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// make sure that the statement has already been executed.
|
|
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// make sure it is not scrollable. This function should only work for inserts, updates, and deletes,
|
|
// but this is the best we can do to enforce that.
|
|
CHECK_CUSTOM_ERROR( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY, stmt, SS_SQLSRV_ERROR_STATEMENT_SCROLLABLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
rows = stmt->current_results->row_count();
|
|
RETURN_LONG( rows );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_rows_affected: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_num_rows( resource $stmt )
|
|
//
|
|
// Retrieves the number of rows in an active result set. The statement must
|
|
// have been created with the Scrollable attribute set to 'static'.
|
|
//
|
|
// Parameters
|
|
// $stmt: The statement on which the targeted result set is active.
|
|
//
|
|
// Return Value
|
|
// An integer value that represents the number of rows in the active result
|
|
// set. If an error occurs, the boolean value false is returned.
|
|
|
|
PHP_FUNCTION( sqlsrv_num_rows )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_num_rows" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
SQLLEN rows = -1;
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// make sure that the statement has already been executed.
|
|
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// make sure that the statement is scrollable and the cursor is not dynamic.
|
|
// if the cursor is dynamic, then the number of rows returned is always -1.
|
|
CHECK_CUSTOM_ERROR( stmt->cursor_type == SQL_CURSOR_FORWARD_ONLY || stmt->cursor_type == SQL_CURSOR_DYNAMIC, stmt,
|
|
SS_SQLSRV_ERROR_STATEMENT_NOT_SCROLLABLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
rows = stmt->current_results->row_count();
|
|
RETURN_LONG( rows );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_num_rows: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_num_fields( resource $stmt )
|
|
//
|
|
// Retrieves the number of fields in an active result set. Note that
|
|
// sqlsrv_num_fields can be called on any prepared statement, before or after
|
|
// execution.
|
|
//
|
|
// Parameters
|
|
// $stmt: The statement on which the targeted result set is active.
|
|
//
|
|
// Return Value
|
|
// An integer value that represents the number of fields in the active result
|
|
// set. If an error occurs, the boolean value false is returned.
|
|
|
|
PHP_FUNCTION( sqlsrv_num_fields )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_num_fields" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
SQLSMALLINT fields = -1;
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// retrieve the number of columns from ODBC
|
|
fields = core::SQLNumResultCols( stmt );
|
|
|
|
RETURN_LONG( fields );
|
|
}
|
|
|
|
catch( ss::SSException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_num_fields: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_fetch_object( resource $stmt [, string $className [, array $ctorParams]])
|
|
//
|
|
// Retrieves the next row of data as a PHP object.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement.
|
|
//
|
|
// $className [OPTIONAL]: A string specifying the name of the class to
|
|
// instantiate. If a value for the $className parameter is not specified, an
|
|
// instance of the PHP stdClass is instantiated.
|
|
//
|
|
// $ctorParams [OPTIONAL]: An array that contains values passed to the
|
|
// constructor of the class specified with the $className parameter. If the
|
|
// constructor of the specified class accepts parameter values, the $ctorParams
|
|
// parameter must be used when calling sqlsrv_fetch_object.
|
|
//
|
|
// Return Value
|
|
// A PHP object with properties that correspond to result set field
|
|
// names. Property values are populated with the corresponding result set field
|
|
// values. If the class specified with the optional $className parameter does
|
|
// not exist or if there is no active result set associated with the specified
|
|
// statement, false is returned.
|
|
// The data type of a value in the returned object will be the default PHP data
|
|
// type. For information on default PHP data types, see Default PHP Data Types.
|
|
//
|
|
// Remarks
|
|
// If a class name is specified with the optional $className parameter, an
|
|
// object of this class type is instantiated. If the class has properties whose
|
|
// names match the result set field names, the corresponding result set values
|
|
// are applied to the properties. If a result set field name does not match a
|
|
// class property, a property with the result set field name is added to the
|
|
// object and the result set value is applied to the property. For more
|
|
// information about calling sqlsrv_fetch_object with the $className parameter,
|
|
// see How to: Retrieve Data as an Object (Microsoft Drivers for PHP for SQL Server).
|
|
//
|
|
// If a field with no name is returned, sqlsrv_fetch_object will discard the
|
|
// field value and issue a warning.
|
|
|
|
PHP_FUNCTION( sqlsrv_fetch_object )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_fetch_object" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
zval* class_name_z = NULL;
|
|
zval* ctor_params_z = NULL;
|
|
zend_long fetch_style = SQL_FETCH_NEXT; // default value for parameter if one isn't supplied
|
|
zend_long fetch_offset = 0; // default value for parameter if one isn't supplied
|
|
|
|
// stdClass is the name of the system's default base class in PHP
|
|
char* class_name = const_cast<char*>( STDCLASS_NAME );
|
|
std::size_t class_name_len = STDCLASS_NAME_LEN;
|
|
HashTable* properties_ht = NULL;
|
|
zval retval_z;
|
|
ZVAL_UNDEF( &retval_z );
|
|
|
|
// retrieve the statement resource and optional fetch type (see enum SQLSRV_FETCH_TYPE),
|
|
// fetch style (see SQLSRV_SCROLL_* constants) and fetch offset
|
|
// we also use z! instead of s and a so that null may be passed in as valid values for
|
|
// the class name and ctor params
|
|
PROCESS_PARAMS( stmt, "r|z!z!ll", _FN_, 4, &class_name_z, &ctor_params_z, &fetch_style, &fetch_offset, NULL );
|
|
|
|
try {
|
|
|
|
CHECK_CUSTOM_ERROR(( fetch_style < SQL_FETCH_NEXT || fetch_style > SQL_FETCH_RELATIVE ), stmt,
|
|
SS_SQLSRV_ERROR_INVALID_FETCH_STYLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
if( class_name_z ) {
|
|
|
|
CHECK_CUSTOM_ERROR(( Z_TYPE_P( class_name_z ) != IS_STRING ), stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
class_name = Z_STRVAL( *class_name_z );
|
|
class_name_len = Z_STRLEN( *class_name_z );
|
|
}
|
|
|
|
if( ctor_params_z && Z_TYPE_P( ctor_params_z ) != IS_ARRAY ) {
|
|
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL );
|
|
}
|
|
|
|
// fetch the data
|
|
bool result = core_sqlsrv_fetch( stmt, static_cast<SQLSMALLINT>(fetch_style), fetch_offset );
|
|
if( !result ) {
|
|
RETURN_NULL();
|
|
}
|
|
|
|
fetch_fields_common( stmt, SQLSRV_FETCH_ASSOC, retval_z, false /*allow_empty_field_names*/ );
|
|
properties_ht = Z_ARRVAL( retval_z );
|
|
|
|
// find the zend_class_entry of the class the user requested (stdClass by default) for use below
|
|
zend_class_entry* class_entry = NULL;
|
|
zend_string* class_name_str_z = zend_string_init( class_name, class_name_len, 0 );
|
|
int zr = ( NULL != ( class_entry = zend_lookup_class( class_name_str_z ))) ? SUCCESS : FAILURE;
|
|
zend_string_release( class_name_str_z );
|
|
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_BAD_CLASS, class_name, NULL ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// create an instance of the object with its default properties
|
|
// we pass NULL for the properties so that the object will be populated by its default properties
|
|
zr = object_and_properties_init( &retval_z, class_entry, NULL /*properties*/ );
|
|
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name, NULL ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// merge in the "properties" (associative array) returned from the fetch doing this vice versa
|
|
// since putting properties_ht into object_and_properties_init and merging the default properties
|
|
// causes duplicate properties when the visibilities are different and also references the
|
|
// default parameters directly in the object, meaning the default property value is changed when
|
|
// the object's property is changed.
|
|
zend_merge_properties( &retval_z, properties_ht );
|
|
zend_hash_destroy( properties_ht );
|
|
FREE_HASHTABLE( properties_ht );
|
|
|
|
// find and call the object's constructor
|
|
|
|
// The header files (zend.h and zend_API.h) declare
|
|
// these functions and structures, so by working with those, we were able to
|
|
// develop this as a suitable snippet for calling constructors. Some observations:
|
|
// params must be an array of zval**, not a zval** to an array as we originally
|
|
// thought. Also, a constructor doesn't show up in the function table, but
|
|
// is put into the "magic methods" section of the class entry.
|
|
//
|
|
// The default values of the fci and fcic structures were determined by
|
|
// calling zend_fcall_info_init with a test callable.
|
|
|
|
// if there is a constructor (e.g., stdClass doesn't have one)
|
|
if( class_entry->constructor ) {
|
|
|
|
// take the parameters given as our last argument and put them into a sequential array
|
|
sqlsrv_malloc_auto_ptr<zval> params_m;
|
|
zval ctor_retval_z;
|
|
ZVAL_UNDEF( &ctor_retval_z );
|
|
int num_params = 0;
|
|
|
|
if ( ctor_params_z ) {
|
|
HashTable* ctor_params_ht = Z_ARRVAL( *ctor_params_z );
|
|
num_params = zend_hash_num_elements( ctor_params_ht );
|
|
params_m = reinterpret_cast<zval*>( sqlsrv_malloc( num_params * sizeof( zval ) ));
|
|
|
|
int i = 0;
|
|
zval* value_z = NULL;
|
|
ZEND_HASH_FOREACH_VAL( ctor_params_ht, value_z ) {
|
|
zr = ( value_z ) ? SUCCESS : FAILURE;
|
|
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name, NULL ) {
|
|
throw ss::SSException();
|
|
}
|
|
ZVAL_COPY_VALUE(¶ms_m[i], value_z);
|
|
i++;
|
|
} ZEND_HASH_FOREACH_END();
|
|
} //if( !Z_ISUNDEF( ctor_params_z ))
|
|
|
|
// call the constructor function itself.
|
|
zend_fcall_info fci;
|
|
zend_fcall_info_cache fcic;
|
|
|
|
memset( &fci, 0, sizeof( fci ));
|
|
fci.size = sizeof( fci );
|
|
#if PHP_VERSION_ID < 70100
|
|
fci.function_table = &( class_entry )->function_table;
|
|
#endif
|
|
ZVAL_UNDEF( &( fci.function_name ) );
|
|
fci.retval = &ctor_retval_z;
|
|
fci.param_count = num_params;
|
|
fci.params = params_m; // purposefully not transferred since ownership isn't actually transferred.
|
|
|
|
fci.object = Z_OBJ_P( &retval_z );
|
|
|
|
memset( &fcic, 0, sizeof( fcic ));
|
|
#if PHP_VERSION_ID < 70300
|
|
fcic.initialized = 1;
|
|
#endif
|
|
fcic.function_handler = class_entry->constructor;
|
|
fcic.calling_scope = class_entry;
|
|
|
|
fcic.object = Z_OBJ_P( &retval_z );
|
|
|
|
zr = zend_call_function( &fci, &fcic );
|
|
CHECK_ZEND_ERROR( zr, stmt, SS_SQLSRV_ERROR_ZEND_OBJECT_FAILED, class_name, NULL ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
} //if( class_entry->constructor )
|
|
RETURN_ZVAL( &retval_z, 1, 1 );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
|
|
if( properties_ht != NULL ) {
|
|
|
|
zend_hash_destroy( properties_ht );
|
|
FREE_HASHTABLE( properties_ht );
|
|
}
|
|
else if ( Z_TYPE( retval_z ) == IS_ARRAY ) {
|
|
zend_hash_destroy( Z_ARRVAL( retval_z ));
|
|
FREE_HASHTABLE( Z_ARRVAL( retval_z ));
|
|
}
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_fetch_object: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_has_rows( resource $stmt )
|
|
//
|
|
// Parameters
|
|
// $stmt: The statement on which the targeted result set is active.
|
|
//
|
|
// Return Value
|
|
// Returns whether or not there are rows waiting to be processed. There are two scenarios
|
|
// for using a function like this:
|
|
// 1) To know if there are any actual rows, not just a result set (empty or not). Use sqlsrv_has_rows to determine this.
|
|
// The guarantee is that if sqlsrv_has_rows returns true immediately after a query, that sqlsrv_fetch_* will return at least
|
|
// one row of data.
|
|
// 2) To know if there is any sort of result set, empty or not, that has to be bypassed to get to something else, such as
|
|
// output parameters being returned. Use sqlsrv_num_fields > 0 to check if there is any result set that must be bypassed
|
|
// until sqlsrv_fetch returns NULL.
|
|
// The last caveat is that this function can still return FALSE if there is an error, which is fine since an error
|
|
// most likely means that there is no result data anyways.
|
|
// If this functions returs true one time, then it will return true even after the result set is exhausted
|
|
// (sqlsrv_fetch returns null)
|
|
|
|
PHP_FUNCTION( sqlsrv_has_rows )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_has_rows" );
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
|
|
try {
|
|
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
CHECK_CUSTOM_ERROR( !stmt->executed, stmt, SQLSRV_ERROR_STATEMENT_NOT_EXECUTED ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
if( !stmt->has_rows && !stmt->fetch_called ) {
|
|
|
|
determine_stmt_has_rows( stmt );
|
|
}
|
|
|
|
if( stmt->has_rows ) {
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
}
|
|
catch( core::CoreException& ) {
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_has_rows: Unknown exception caught." );
|
|
}
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
|
|
// sqlsrv_send_stream_data( resource $stmt )
|
|
//
|
|
// Sends data from parameter streams to the server. Up to eight kilobytes (8K)
|
|
// of data is sent with each call to sqlsrv_send_stream_data.
|
|
// By default, all stream data is sent to the server when a query is
|
|
// executed. If this default behavior is not changed, you do not have to use
|
|
// sqlsrv_send_stream_data to send stream data to the server. For information
|
|
// about changing the default behavior, see the Parameters section of
|
|
// sqlsrv_query or sqlsrv_prepare.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement.
|
|
//
|
|
// Return Value
|
|
// true if there is more data to be sent. null, if all the data has been sent,
|
|
// and false if an error occurred
|
|
|
|
PHP_FUNCTION( sqlsrv_send_stream_data )
|
|
{
|
|
sqlsrv_stmt* stmt = NULL;
|
|
|
|
LOG_FUNCTION( "sqlsrv_send_stream_data" );
|
|
|
|
// get the statement resource that we've bound streams to
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// if everything was sent at execute time, just return that there is nothing more to send.
|
|
if( stmt->send_streams_at_exec ) {
|
|
RETURN_NULL();
|
|
}
|
|
|
|
// send the next packet
|
|
bool more = core_sqlsrv_send_stream_packet( stmt );
|
|
|
|
// if more to send, return true
|
|
if( more ) {
|
|
RETURN_TRUE;
|
|
}
|
|
// otherwise we're done, so return null
|
|
else {
|
|
RETURN_NULL();
|
|
}
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
// return false if an error occurred
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_send_stream_data: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_get_field( resource $stmt, int $fieldIndex [, int $getAsType] )
|
|
//
|
|
// Retrieves data from the specified field of the current row. Field data must
|
|
// be accessed in order. For example, data from the first field cannot be
|
|
// accessed after data from the second field has been accessed.
|
|
//
|
|
// Parameters
|
|
// $stmt: A statement resource corresponding to an executed statement.
|
|
// $fieldIndex: The index of the field to be retrieved. Indexes begin at zero.
|
|
// $getAsType [OPTIONAL]: A SQLSRV constant (SQLSRV_PHPTYPE) that determines
|
|
// the PHP data type for the returned data. For information about supported data
|
|
// types, see SQLSRV Constants (Microsoft Drivers for PHP for SQL Server). If no return
|
|
// type is specified, a default PHP type will be returned. For information about
|
|
// default PHP types, see Default PHP Data Types. For information about
|
|
// specifying PHP data types, see How to: Specify PHP Data Types.
|
|
//
|
|
// Return Value
|
|
// The field data. You can specify the PHP data type of the returned data by
|
|
// using the $getAsType parameter. If no return data type is specified, the
|
|
// default PHP data type will be returned. For information about default PHP
|
|
// types, see Default PHP Data Types. For information about specifying PHP data
|
|
// types, see How to: Specify PHP Data Types.
|
|
|
|
PHP_FUNCTION( sqlsrv_get_field )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_get_field" );
|
|
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
sqlsrv_phptype sqlsrv_php_type;
|
|
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
|
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
|
|
void* field_value = NULL;
|
|
zend_long field_index = -1;
|
|
SQLLEN field_len = -1;
|
|
zval retval_z;
|
|
ZVAL_UNDEF(&retval_z);
|
|
|
|
// get the statement, the field index and the optional type
|
|
PROCESS_PARAMS( stmt, "rl|l", _FN_, 2, &field_index, &sqlsrv_php_type, NULL );
|
|
|
|
try {
|
|
|
|
// validate that the field index is within range
|
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
|
|
|
if( field_index < 0 || field_index >= num_cols ) {
|
|
THROW_SS_ERROR( stmt, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL );
|
|
}
|
|
|
|
core_sqlsrv_get_field( stmt, static_cast<SQLUSMALLINT>( field_index ), sqlsrv_php_type, false, field_value, &field_len, false/*cache_field*/,
|
|
&sqlsrv_php_type_out );
|
|
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, retval_z );
|
|
sqlsrv_free( field_value );
|
|
RETURN_ZVAL( &retval_z, 1, 1 );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_get_field: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// ** type functions. **
|
|
// When specifying PHP and SQL Server types that take parameters, such as VARCHAR(2000), we use functions
|
|
// to match that notation and return a specially encoded integer that tells us what type and size/precision
|
|
// are. For PHP types specifically we munge the type and encoding into the integer.
|
|
// As is easily seen, since they are so similar, we delegate the actual encoding to helper methods defined
|
|
// below.
|
|
|
|
// takes an encoding of the stream
|
|
PHP_FUNCTION( SQLSRV_PHPTYPE_STREAM )
|
|
{
|
|
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STREAM );
|
|
}
|
|
|
|
// takes an encoding of the string
|
|
PHP_FUNCTION( SQLSRV_PHPTYPE_STRING )
|
|
{
|
|
type_and_encoding( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQLSRV_PHPTYPE_STRING );
|
|
}
|
|
|
|
// takes the size of the binary field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_BINARY)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_BINARY );
|
|
}
|
|
|
|
// takes the size of the char field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_CHAR)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_CHAR );
|
|
}
|
|
|
|
// takes the precision and scale of the decimal field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_DECIMAL)
|
|
{
|
|
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_DECIMAL );
|
|
}
|
|
|
|
// takes the size of the nchar field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_NCHAR)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WCHAR );
|
|
}
|
|
|
|
// takes the precision and scale of the numeric field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_NUMERIC)
|
|
{
|
|
type_and_precision_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_NUMERIC );
|
|
}
|
|
|
|
// takes the size (in characters, not bytes) of the nvarchar field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_NVARCHAR)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_WVARCHAR );
|
|
}
|
|
|
|
// takes the size of the varbinary field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_VARBINARY)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARBINARY );
|
|
}
|
|
|
|
// takes the size of the varchar field
|
|
PHP_FUNCTION(SQLSRV_SQLTYPE_VARCHAR)
|
|
{
|
|
type_and_size_calc( INTERNAL_FUNCTION_PARAM_PASSTHRU, SQL_VARCHAR );
|
|
}
|
|
|
|
void bind_params( _Inout_ ss_sqlsrv_stmt* stmt )
|
|
{
|
|
// if there's nothing to do, just return
|
|
if( stmt->params_z == NULL ) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
|
|
stmt->executed = false;
|
|
|
|
zval* params_z = stmt->params_z;
|
|
|
|
HashTable* params_ht = Z_ARRVAL_P( params_z );
|
|
|
|
zend_ulong index = -1;
|
|
zend_string *key = NULL;
|
|
zval* param_z = NULL;
|
|
|
|
ZEND_HASH_FOREACH_KEY_VAL( params_ht, index, key, param_z ) {
|
|
// make sure it's an integer index
|
|
int type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
|
CHECK_CUSTOM_ERROR(type != HASH_KEY_IS_LONG, stmt, SS_SQLSRV_ERROR_PARAM_INVALID_INDEX) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
zval* value_z = NULL;
|
|
SQLSMALLINT direction = SQL_PARAM_INPUT;
|
|
SQLSRV_ENCODING encoding = stmt->encoding();
|
|
if( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) {
|
|
encoding = stmt->conn->encoding();
|
|
}
|
|
SQLSMALLINT sql_type = SQL_UNKNOWN_TYPE;
|
|
SQLULEN column_size = SQLSRV_UNKNOWN_SIZE;
|
|
SQLSMALLINT decimal_digits = 0;
|
|
SQLSRV_PHPTYPE php_out_type = SQLSRV_PHPTYPE_INVALID;
|
|
|
|
// if it's a parameter array
|
|
if (Z_TYPE_P(param_z) == IS_ARRAY) {
|
|
try {
|
|
HashTable* param_ht = Z_ARRVAL_P(param_z);
|
|
// Check the number of elements in the array
|
|
int num_elems = zend_hash_num_elements(param_ht);
|
|
if (num_elems > 1) {
|
|
value_z = parse_param_array(stmt, param_ht, index, direction, php_out_type, encoding, sql_type, column_size, decimal_digits);
|
|
} else {
|
|
// Simply get the first variable and use the defaults
|
|
value_z = zend_hash_index_find(param_ht, 0);
|
|
if (value_z == NULL) {
|
|
THROW_SS_ERROR(stmt, SS_SQLSRV_ERROR_VAR_REQUIRED, index + 1, NULL);
|
|
}
|
|
}
|
|
} catch (core::CoreException&) {
|
|
SQLFreeStmt(stmt->handle(), SQL_RESET_PARAMS);
|
|
throw;
|
|
}
|
|
}
|
|
else {
|
|
CHECK_CUSTOM_ERROR(!stmt->prepared && stmt->conn->ce_option.enabled, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
|
|
throw ss::SSException();
|
|
}
|
|
value_z = param_z;
|
|
}
|
|
|
|
// If the user specifies a certain type for an output parameter, we have to convert the zval
|
|
// to that type so that when the buffer is filled, the type is correct. But first,
|
|
// should check if a LOB type is specified.
|
|
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && (sql_type == SQL_LONGVARCHAR
|
|
|| sql_type == SQL_WLONGVARCHAR || sql_type == SQL_LONGVARBINARY),
|
|
stmt, SQLSRV_ERROR_OUTPUT_PARAM_TYPES_NOT_SUPPORTED) {
|
|
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
|
|
core_sqlsrv_bind_param( stmt, static_cast<SQLUSMALLINT>( index ), direction, value_z, php_out_type, encoding, sql_type, column_size,
|
|
decimal_digits );
|
|
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
catch( core::CoreException& ) {
|
|
stmt->free_param_data();
|
|
|
|
SQLFreeStmt( stmt->handle(), SQL_RESET_PARAMS );
|
|
zval_ptr_dtor( stmt->params_z );
|
|
sqlsrv_free( stmt->params_z );
|
|
stmt->params_z = NULL;
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// sqlsrv_cancel( resource $stmt )
|
|
//
|
|
// Cancels a statement. This means that any pending results for the statement
|
|
// are discarded. After this function is called, the statement can be
|
|
// re-executed if it was prepared with sqlsrv_prepare. Calling this function is
|
|
// not necessary if all the results associated with the statement have been
|
|
// consumed.
|
|
//
|
|
// Parameters
|
|
// $stmt: The statement to be canceled.
|
|
//
|
|
// Return Value
|
|
// A Boolean value: true if the operation was successful. Otherwise, false.
|
|
|
|
PHP_FUNCTION( sqlsrv_cancel )
|
|
{
|
|
|
|
LOG_FUNCTION( "sqlsrv_cancel" );
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
PROCESS_PARAMS( stmt, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
// close the stream to release the resource
|
|
close_active_stream( stmt );
|
|
|
|
SQLRETURN r = SQLCancel( stmt->handle() );
|
|
CHECK_SQL_ERROR_OR_WARNING( r, stmt, NULL ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_cancel: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
void __cdecl sqlsrv_stmt_dtor( _Inout_ zend_resource *rsrc )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_stmt_dtor" );
|
|
|
|
// get the structure
|
|
ss_sqlsrv_stmt *stmt = static_cast<ss_sqlsrv_stmt*>( rsrc->ptr );
|
|
if( stmt->conn ) {
|
|
int zr = zend_hash_index_del( static_cast<ss_sqlsrv_conn*>( stmt->conn )->stmts, stmt->conn_index );
|
|
if( zr == FAILURE ) {
|
|
LOG( SEV_ERROR, "Failed to remove statement reference from the connection" );
|
|
}
|
|
}
|
|
|
|
stmt->~ss_sqlsrv_stmt();
|
|
sqlsrv_free( stmt );
|
|
rsrc->ptr = NULL;
|
|
}
|
|
|
|
// sqlsrv_free_stmt( resource $stmt )
|
|
//
|
|
// Frees all resources associated with the specified statement. The statement
|
|
// cannot be used again after this function has been called.
|
|
//
|
|
// Parameters
|
|
// $stmt: The statement to be closed.
|
|
//
|
|
// Return Value
|
|
// The Boolean value true unless the function is called with an invalid
|
|
// parameter. If the function is called with an invalid parameter, false is
|
|
// returned.
|
|
//
|
|
// Null is a valid parameter for this function. This allows the function to be
|
|
// called multiple times in a script. For example, if you free a statement in an
|
|
// error condition and free it again at the end of the script, the second call
|
|
// to sqlsrv_free_stmt will return true because the first call to
|
|
// sqlsrv_free_stmt (in the error condition) sets the statement resource to
|
|
// null.
|
|
|
|
PHP_FUNCTION( sqlsrv_free_stmt )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_free_stmt" );
|
|
|
|
zval* stmt_r = NULL;
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
sqlsrv_context_auto_ptr error_ctx;
|
|
|
|
reset_errors();
|
|
|
|
try {
|
|
|
|
// dummy context to pass to the error handler
|
|
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
|
|
error_ctx->set_func(_FN_);
|
|
|
|
// take only the statement resource
|
|
if( zend_parse_parameters( ZEND_NUM_ARGS(), "r", &stmt_r ) == FAILURE ) {
|
|
|
|
// Check if it was a zval
|
|
int zr = zend_parse_parameters( ZEND_NUM_ARGS(), "z", &stmt_r );
|
|
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
if( Z_TYPE_P( stmt_r ) == IS_NULL ) {
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
else {
|
|
|
|
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL );
|
|
}
|
|
}
|
|
|
|
// verify the resource so we know we're deleting a statement
|
|
stmt = static_cast<ss_sqlsrv_stmt*>(zend_fetch_resource_ex(stmt_r, ss_sqlsrv_stmt::resource_name, ss_sqlsrv_stmt::descriptor));
|
|
|
|
// if sqlsrv_free_stmt was called on an already closed statment then we just return success.
|
|
// zend_list_close sets the type of the closed statment to -1.
|
|
SQLSRV_ASSERT( stmt_r != NULL, "sqlsrv_free_stmt: stmt_r is null." );
|
|
if ( Z_RES_TYPE_P( stmt_r ) == RSRC_INVALID_TYPE ) {
|
|
RETURN_TRUE;
|
|
}
|
|
|
|
if( stmt == NULL ) {
|
|
|
|
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_, NULL );
|
|
}
|
|
|
|
// delete the resource from Zend's master list, which will trigger the statement's destructor
|
|
#if PHP_VERSION_ID < 80000
|
|
if (zend_list_close(Z_RES_P(stmt_r)) == FAILURE) {
|
|
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_P(stmt_r)->handle);
|
|
}
|
|
#else
|
|
zend_list_close(Z_RES_P(stmt_r));
|
|
#endif
|
|
|
|
// when stmt_r is first parsed in zend_parse_parameters, stmt_r becomes a zval that points to a zend_resource with a refcount of 2
|
|
// need to DELREF here so the refcount becomes 1 and stmt_r can be appropriate destroyed by the garbage collector when it goes out of scope
|
|
// zend_list_close only destroy the resource pointed to by Z_RES_P( stmt_r ), not the zend_resource itself
|
|
Z_TRY_DELREF_P(stmt_r);
|
|
ZVAL_NULL( stmt_r );
|
|
|
|
RETURN_TRUE;
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_free_stmt: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
void stmt_option_ss_scrollable:: operator()( _Inout_ sqlsrv_stmt* stmt, stmt_option const* /*opt*/, _In_ zval* value_z )
|
|
{
|
|
CHECK_CUSTOM_ERROR(( Z_TYPE_P( value_z ) != IS_STRING ), stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
const char* scroll_type = Z_STRVAL_P( value_z );
|
|
unsigned long cursor_type = -1;
|
|
|
|
// find which cursor type they would like and set the ODBC statement attribute as such
|
|
if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_STATIC )) {
|
|
|
|
cursor_type = SQL_CURSOR_STATIC;
|
|
}
|
|
|
|
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_DYNAMIC )) {
|
|
|
|
cursor_type = SQL_CURSOR_DYNAMIC;
|
|
}
|
|
|
|
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_KEYSET )) {
|
|
|
|
cursor_type = SQL_CURSOR_KEYSET_DRIVEN;
|
|
}
|
|
|
|
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_FORWARD )) {
|
|
|
|
cursor_type = SQL_CURSOR_FORWARD_ONLY;
|
|
}
|
|
|
|
else if( !stricmp( scroll_type, SSCursorTypes::QUERY_OPTION_SCROLLABLE_BUFFERED )) {
|
|
|
|
cursor_type = SQLSRV_CURSOR_BUFFERED;
|
|
}
|
|
|
|
else {
|
|
|
|
THROW_SS_ERROR( stmt, SQLSRV_ERROR_INVALID_OPTION_SCROLLABLE );
|
|
}
|
|
|
|
core_sqlsrv_set_scrollable( stmt, cursor_type );
|
|
|
|
}
|
|
|
|
namespace {
|
|
|
|
void convert_to_zval( _Inout_ sqlsrv_stmt* stmt, _In_ SQLSRV_PHPTYPE sqlsrv_php_type, _In_opt_ void* in_val, _In_ SQLLEN field_len, _Inout_ zval& out_zval)
|
|
{
|
|
if ( in_val == NULL ) {
|
|
ZVAL_NULL( &out_zval);
|
|
return;
|
|
}
|
|
|
|
switch (sqlsrv_php_type) {
|
|
|
|
case SQLSRV_PHPTYPE_INT:
|
|
case SQLSRV_PHPTYPE_FLOAT:
|
|
{
|
|
if (sqlsrv_php_type == SQLSRV_PHPTYPE_INT) {
|
|
ZVAL_LONG( &out_zval, *(static_cast<int*>( in_val )));
|
|
}
|
|
else {
|
|
ZVAL_DOUBLE( &out_zval, *(static_cast<double*>( in_val )));
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SQLSRV_PHPTYPE_STRING:
|
|
{
|
|
ZVAL_STRINGL( &out_zval, static_cast<const char*>( in_val ), field_len);
|
|
break;
|
|
}
|
|
|
|
case SQLSRV_PHPTYPE_STREAM:
|
|
{
|
|
out_zval = *( static_cast<zval*>( in_val ));
|
|
stmt->active_stream = out_zval;
|
|
//addref here because deleting out_zval later will decrement the refcount
|
|
Z_TRY_ADDREF( out_zval );
|
|
break;
|
|
}
|
|
case SQLSRV_PHPTYPE_DATETIME:
|
|
{
|
|
convert_datetime_string_to_zval(stmt, static_cast<char*>(in_val), field_len, out_zval);
|
|
break;
|
|
}
|
|
|
|
case SQLSRV_PHPTYPE_NULL:
|
|
ZVAL_NULL(&out_zval);
|
|
break;
|
|
|
|
default:
|
|
DIE("Unknown php type");
|
|
break;
|
|
}
|
|
return;
|
|
}
|
|
|
|
|
|
// put in the column size and scale/decimal digits of the sql server type
|
|
// these values are taken from the MSDN page at http://msdn2.microsoft.com/en-us/library/ms711786(VS.85).aspx
|
|
// for SQL_VARBINARY, SQL_VARCHAR, and SQL_WLONGVARCHAR types, see https://msdn.microsoft.com/en-CA/library/ms187993.aspx
|
|
bool determine_column_size_or_precision( sqlsrv_stmt const* stmt, _In_ sqlsrv_sqltype sqlsrv_type, _Inout_ SQLULEN* column_size,
|
|
_Out_ SQLSMALLINT* decimal_digits )
|
|
{
|
|
*decimal_digits = 0;
|
|
|
|
switch( sqlsrv_type.typeinfo.type ) {
|
|
case SQL_BIGINT:
|
|
*column_size = 19;
|
|
break;
|
|
case SQL_BIT:
|
|
*column_size = 1;
|
|
break;
|
|
case SQL_INTEGER:
|
|
*column_size = 10;
|
|
break;
|
|
case SQL_SMALLINT:
|
|
*column_size = 5;
|
|
break;
|
|
case SQL_TINYINT:
|
|
*column_size = 3;
|
|
break;
|
|
case SQL_GUID:
|
|
*column_size = 36;
|
|
break;
|
|
case SQL_FLOAT:
|
|
*column_size = 53;
|
|
break;
|
|
case SQL_REAL:
|
|
*column_size = 24;
|
|
break;
|
|
case SQL_LONGVARBINARY:
|
|
case SQL_LONGVARCHAR:
|
|
*column_size = INT_MAX;
|
|
break;
|
|
case SQL_WLONGVARCHAR:
|
|
*column_size = INT_MAX >> 1;
|
|
break;
|
|
case SQL_SS_XML:
|
|
case SQL_SS_TABLE:
|
|
*column_size = SQL_SS_LENGTH_UNLIMITED;
|
|
break;
|
|
case SQL_BINARY:
|
|
case SQL_CHAR:
|
|
case SQL_VARBINARY:
|
|
case SQL_VARCHAR:
|
|
case SQL_SS_VARIANT:
|
|
*column_size = sqlsrv_type.typeinfo.size;
|
|
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
|
|
*column_size = SQL_SS_LENGTH_UNLIMITED;
|
|
}
|
|
else if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
|
|
*column_size = SQLSRV_INVALID_SIZE;
|
|
return false;
|
|
}
|
|
break;
|
|
case SQL_WCHAR:
|
|
case SQL_WVARCHAR:
|
|
*column_size = sqlsrv_type.typeinfo.size;
|
|
if( *column_size == SQLSRV_SIZE_MAX_TYPE ) {
|
|
*column_size = SQL_SS_LENGTH_UNLIMITED;
|
|
break;
|
|
}
|
|
if( *column_size > SQL_SERVER_MAX_FIELD_SIZE || *column_size == SQLSRV_INVALID_SIZE ) {
|
|
*column_size = SQLSRV_INVALID_SIZE;
|
|
return false;
|
|
}
|
|
break;
|
|
case SQL_DECIMAL:
|
|
case SQL_NUMERIC:
|
|
*column_size = sqlsrv_type.typeinfo.size;
|
|
*decimal_digits = sqlsrv_type.typeinfo.scale;
|
|
// if there was something wrong with the values given on type_and_precision_calc, these are set to invalid precision
|
|
if( *column_size == SQLSRV_INVALID_PRECISION || *decimal_digits == SQLSRV_INVALID_PRECISION ) {
|
|
*column_size = SQLSRV_INVALID_SIZE;
|
|
return false;
|
|
}
|
|
break;
|
|
// this can represent one of three data types: smalldatetime, datetime, and datetime2
|
|
// we present the largest for the version and let SQL Server downsize it
|
|
case SQL_TYPE_TIMESTAMP:
|
|
*column_size = sqlsrv_type.typeinfo.size;
|
|
*decimal_digits = sqlsrv_type.typeinfo.scale;
|
|
break;
|
|
case SQL_SS_TIMESTAMPOFFSET:
|
|
*column_size = 34;
|
|
*decimal_digits = 7;
|
|
break;
|
|
case SQL_TYPE_DATE:
|
|
*column_size = 10;
|
|
*decimal_digits = 0;
|
|
break;
|
|
case SQL_SS_TIME2:
|
|
*column_size = 16;
|
|
*decimal_digits = 7;
|
|
break;
|
|
default:
|
|
// an invalid sql type should have already been dealt with, so we assert here.
|
|
DIE( "Trying to determine column size for an invalid type. Type should have already been verified." );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// given a SQL Server type, return a sqlsrv php type
|
|
sqlsrv_phptype determine_sqlsrv_php_type( _In_ ss_sqlsrv_stmt const* stmt, _In_ SQLINTEGER sql_type, _In_ SQLUINTEGER size, _In_ bool prefer_string )
|
|
{
|
|
sqlsrv_phptype sqlsrv_phptype;
|
|
sqlsrv_phptype.typeinfo.type = PHPTYPE_INVALID;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
|
|
|
|
switch( sql_type ) {
|
|
case SQL_BIGINT:
|
|
case SQL_DECIMAL:
|
|
case SQL_NUMERIC:
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
|
break;
|
|
case SQL_CHAR:
|
|
case SQL_GUID:
|
|
case SQL_WCHAR:
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
break;
|
|
case SQL_VARCHAR:
|
|
case SQL_WVARCHAR:
|
|
case SQL_SS_VARIANT:
|
|
if( prefer_string || size != SQL_SS_LENGTH_UNLIMITED ) {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
}
|
|
else {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
}
|
|
break;
|
|
case SQL_BIT:
|
|
case SQL_INTEGER:
|
|
case SQL_SMALLINT:
|
|
case SQL_TINYINT:
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_INT;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
|
break;
|
|
case SQL_BINARY:
|
|
case SQL_LONGVARBINARY:
|
|
case SQL_VARBINARY:
|
|
case SQL_SS_UDT:
|
|
if( prefer_string ) {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
|
|
}
|
|
else {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_BINARY;
|
|
}
|
|
break;
|
|
case SQL_LONGVARCHAR:
|
|
case SQL_WLONGVARCHAR:
|
|
case SQL_SS_XML:
|
|
if( prefer_string ) {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
}
|
|
else {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STREAM;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
}
|
|
break;
|
|
case SQL_FLOAT:
|
|
case SQL_REAL:
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_FLOAT;
|
|
sqlsrv_phptype.typeinfo.encoding = SQLSRV_ENCODING_CHAR;
|
|
break;
|
|
case SQL_TYPE_DATE:
|
|
case SQL_SS_TIMESTAMPOFFSET:
|
|
case SQL_SS_TIME2:
|
|
case SQL_TYPE_TIMESTAMP:
|
|
{
|
|
if (stmt->date_as_string) {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_STRING;
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->encoding();
|
|
}
|
|
else {
|
|
sqlsrv_phptype.typeinfo.type = SQLSRV_PHPTYPE_DATETIME;
|
|
}
|
|
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;
|
|
SQLSRV_ASSERT(false, "An invalid php type was returned with (supposedly) validated sql type and column_size");
|
|
break;
|
|
}
|
|
|
|
// if an encoding hasn't been set for the statement, then use the connection's encoding
|
|
if( sqlsrv_phptype.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
|
sqlsrv_phptype.typeinfo.encoding = stmt->conn->encoding();
|
|
}
|
|
|
|
return sqlsrv_phptype;
|
|
}
|
|
|
|
|
|
// determine if a query returned any rows of data. It does this by actually fetching the first row
|
|
// (though not retrieving the data) and setting the has_rows flag in the stmt the fetch was successful.
|
|
// The return value simply states whether or not if an error occurred during the determination.
|
|
// (All errors are posted here before returning.)
|
|
|
|
void determine_stmt_has_rows( _Inout_ ss_sqlsrv_stmt* stmt )
|
|
{
|
|
SQLRETURN r = SQL_SUCCESS;
|
|
|
|
if( stmt->fetch_called ) {
|
|
|
|
return;
|
|
}
|
|
|
|
// default condition
|
|
stmt->has_rows = false;
|
|
|
|
// if there are no columns then there are no rows
|
|
if( core::SQLNumResultCols( stmt ) == 0 ) {
|
|
|
|
return;
|
|
}
|
|
|
|
// if the statement is scrollable, our work is easier though less performant. We simply
|
|
// fetch the first row, and then roll the cursor back to be prior to the first row
|
|
if( stmt->cursor_type != SQL_CURSOR_FORWARD_ONLY ) {
|
|
|
|
r = stmt->current_results->fetch( SQL_FETCH_FIRST, 0 );
|
|
if( SQL_SUCCEEDED( r )) {
|
|
|
|
stmt->has_rows = true;
|
|
CHECK_SQL_WARNING( r, stmt, NULL );
|
|
// restore the cursor to its original position.
|
|
r = stmt->current_results->fetch( SQL_FETCH_ABSOLUTE, 0 );
|
|
SQLSRV_ASSERT(( r == SQL_NO_DATA ), "core_sqlsrv_has_rows: Should have scrolled the cursor to the beginning "
|
|
"of the result set." );
|
|
}
|
|
}
|
|
else {
|
|
|
|
// otherwise, we fetch the first row, but record that we did. sqlsrv_fetch checks this
|
|
// flag and simply skips the first fetch, knowing it was already done. It records its own
|
|
// flags to know if it should fetch on subsequent calls.
|
|
|
|
r = core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 );
|
|
if( SQL_SUCCEEDED( r )) {
|
|
|
|
stmt->has_rows = true;
|
|
CHECK_SQL_WARNING( r, stmt, NULL );
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
SQLSMALLINT get_resultset_meta_data(_Inout_ sqlsrv_stmt * stmt)
|
|
{
|
|
// get the numer of columns in the result set
|
|
SQLSMALLINT num_cols = -1;
|
|
|
|
num_cols = stmt->current_meta_data.size();
|
|
bool getMetaData = false;
|
|
|
|
if (num_cols == 0) {
|
|
getMetaData = true;
|
|
if (stmt->column_count == ACTIVE_NUM_COLS_INVALID) {
|
|
num_cols = core::SQLNumResultCols(stmt);
|
|
stmt->column_count = num_cols;
|
|
} else {
|
|
num_cols = stmt->column_count;
|
|
}
|
|
}
|
|
|
|
try {
|
|
if (getMetaData) {
|
|
for (int i = 0; i < num_cols; i++) {
|
|
sqlsrv_malloc_auto_ptr<field_meta_data> core_meta_data;
|
|
core_meta_data = core_sqlsrv_field_metadata(stmt, i);
|
|
stmt->current_meta_data.push_back(core_meta_data.get());
|
|
core_meta_data.transferred();
|
|
}
|
|
}
|
|
} catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
|
|
SQLSRV_ASSERT(stmt->current_meta_data.size() == num_cols, "Meta data vector out of sync" );
|
|
|
|
return num_cols;
|
|
}
|
|
|
|
void fetch_fields_common( _Inout_ ss_sqlsrv_stmt* stmt, _In_ zend_long fetch_type, _Out_ zval& fields, _In_ bool allow_empty_field_names )
|
|
{
|
|
void* field_value = NULL;
|
|
sqlsrv_phptype sqlsrv_php_type;
|
|
sqlsrv_php_type.typeinfo.type = SQLSRV_PHPTYPE_INVALID;
|
|
SQLSRV_PHPTYPE sqlsrv_php_type_out = SQLSRV_PHPTYPE_INVALID;
|
|
|
|
// make sure that the fetch type is legal
|
|
CHECK_CUSTOM_ERROR((fetch_type < MIN_SQLSRV_FETCH || fetch_type > MAX_SQLSRV_FETCH), stmt, SS_SQLSRV_ERROR_INVALID_FETCH_TYPE, stmt->func(), NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// get the numer of columns in the result set and its metadata if not exists
|
|
SQLSMALLINT num_cols = get_resultset_meta_data(stmt);
|
|
|
|
// if this is the first fetch in a new result set, then get the field names and
|
|
// store them off for successive fetches.
|
|
if ((fetch_type & SQLSRV_FETCH_ASSOC) && stmt->fetch_field_names == NULL) {
|
|
|
|
SQLLEN field_name_len = 0;
|
|
sqlsrv_malloc_auto_ptr<sqlsrv_fetch_field_name> field_names;
|
|
field_names = static_cast<sqlsrv_fetch_field_name*>(sqlsrv_malloc(num_cols * sizeof(sqlsrv_fetch_field_name)));
|
|
for (int i = 0; i < num_cols; ++i) {
|
|
// The meta data field name is already null-terminated, and the field name len is correct.
|
|
field_name_len = stmt->current_meta_data[i]->field_name_len;
|
|
field_names[i].name = static_cast<char*>(sqlsrv_malloc(field_name_len, sizeof(char), 1));
|
|
memcpy_s((void*)field_names[i].name, (field_name_len * sizeof(char)), (void*)stmt->current_meta_data[i]->field_name, field_name_len);
|
|
field_names[i].name[field_name_len] = '\0'; // null terminate the field name after the memcpy
|
|
field_names[i].len = field_name_len; // field_name_len should not need to include the null char
|
|
}
|
|
|
|
stmt->fetch_field_names = field_names;
|
|
stmt->fetch_fields_count = num_cols;
|
|
field_names.transferred();
|
|
}
|
|
|
|
int zr = SUCCESS;
|
|
array_init(&fields);
|
|
|
|
for( int i = 0; i < num_cols; ++i ) {
|
|
SQLLEN field_len = -1;
|
|
|
|
core_sqlsrv_get_field( stmt, i, sqlsrv_php_type, true /*prefer string*/,
|
|
field_value, &field_len, false /*cache_field*/, &sqlsrv_php_type_out );
|
|
|
|
zval field;
|
|
ZVAL_UNDEF( &field );
|
|
convert_to_zval( stmt, sqlsrv_php_type_out, field_value, field_len, field );
|
|
sqlsrv_free( field_value );
|
|
if( fetch_type & SQLSRV_FETCH_NUMERIC ) {
|
|
|
|
zr = add_next_index_zval( &fields, &field );
|
|
CHECK_ZEND_ERROR( zr, stmt, SQLSRV_ERROR_ZEND_HASH ) {
|
|
throw ss::SSException();
|
|
}
|
|
}
|
|
|
|
if( fetch_type & SQLSRV_FETCH_ASSOC ) {
|
|
|
|
CHECK_CUSTOM_WARNING_AS_ERROR(( stmt->fetch_field_names[i].len == 0 && !allow_empty_field_names ), stmt,
|
|
SS_SQLSRV_WARNING_FIELD_NAME_EMPTY, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
if( stmt->fetch_field_names[i].len > 0 || allow_empty_field_names ) {
|
|
|
|
add_assoc_zval(&fields, stmt->fetch_field_names[i].name, &field);
|
|
}
|
|
}
|
|
//only addref when the fetch_type is BOTH because this is the only case when fields(hashtable)
|
|
//has 2 elements pointing to field. Do not addref if the type is NUMERIC or ASSOC because
|
|
//fields now only has 1 element pointing to field and we want the ref count to be only 1
|
|
if (fetch_type == SQLSRV_FETCH_BOTH) {
|
|
Z_TRY_ADDREF(field);
|
|
}
|
|
} //for loop
|
|
|
|
}
|
|
|
|
zval* parse_param_array(_Inout_ ss_sqlsrv_stmt* stmt, _Inout_ HashTable* param_ht, zend_ulong index, _Out_ SQLSMALLINT& direction,
|
|
_Out_ SQLSRV_PHPTYPE& php_out_type, _Out_ SQLSRV_ENCODING& encoding, _Out_ SQLSMALLINT& sql_type,
|
|
_Out_ SQLULEN& column_size, _Out_ SQLSMALLINT& decimal_digits)
|
|
{
|
|
zval* var_or_val = zend_hash_index_find(param_ht, 0);
|
|
bool php_type_param_is_null = true;
|
|
bool sql_type_param_is_null = true;
|
|
|
|
// Assumption: there are more than only the variable, parse the rest of the array
|
|
zval* dir = zend_hash_index_find(param_ht, 1);
|
|
if(!dir) { throw ss::SSException(); }
|
|
if (Z_TYPE_P(dir) != IS_NULL) {
|
|
// if param direction is specified, make sure it's valid
|
|
CHECK_CUSTOM_ERROR(Z_TYPE_P(dir) != IS_LONG, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
direction = static_cast<SQLSMALLINT>(Z_LVAL_P(dir));
|
|
|
|
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && direction != SQL_PARAM_OUTPUT && direction != SQL_PARAM_INPUT_OUTPUT,
|
|
stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_DIRECTION, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
CHECK_CUSTOM_ERROR(direction != SQL_PARAM_INPUT && !Z_ISREF_P(var_or_val), stmt, SS_SQLSRV_ERROR_PARAM_VAR_NOT_REF, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
}
|
|
|
|
// Check if the user provides php type or sql type or both
|
|
zval* phptype_z = zend_hash_index_find(param_ht, 2);
|
|
zval* sqltype_z = zend_hash_index_find(param_ht, 3);
|
|
|
|
php_type_param_is_null = (phptype_z == NULL || Z_TYPE_P(phptype_z) == IS_NULL);
|
|
sql_type_param_is_null = (sqltype_z == NULL || Z_TYPE_P(sqltype_z) == IS_NULL);
|
|
|
|
if (php_type_param_is_null) {
|
|
// so set default for php type based on the variable
|
|
if (Z_ISREF_P(var_or_val)) {
|
|
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(Z_REFVAL_P(var_or_val))];
|
|
} else {
|
|
php_out_type = zend_to_sqlsrv_phptype[Z_TYPE_P(var_or_val)];
|
|
}
|
|
} else {
|
|
CHECK_CUSTOM_ERROR(Z_TYPE_P(phptype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
sqlsrv_phptype srv_phptype;
|
|
srv_phptype.value = Z_LVAL_P(phptype_z);
|
|
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_phptype(srv_phptype), stmt, SQLSRV_ERROR_INVALID_PARAMETER_PHPTYPE, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
|
|
encoding = (SQLSRV_ENCODING)srv_phptype.typeinfo.encoding;
|
|
// if the call has a SQLSRV_PHPTYPE_STRING/STREAM('default'), then the stream is in the encoding established
|
|
// by the connection
|
|
if (encoding == SQLSRV_ENCODING_DEFAULT) {
|
|
encoding = stmt->conn->encoding();
|
|
}
|
|
}
|
|
|
|
if (sql_type_param_is_null) {
|
|
// the sql type is not specified, which is required for always encrypted for non-prepared statements
|
|
CHECK_CUSTOM_ERROR(stmt->conn->ce_option.enabled && !stmt->prepared, stmt, SS_SQLSRV_ERROR_AE_QUERY_SQLTYPE_REQUIRED) {
|
|
throw ss::SSException();
|
|
}
|
|
} else {
|
|
CHECK_CUSTOM_ERROR(Z_TYPE_P(sqltype_z) != IS_LONG, stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// since the user supplied this type, make sure it's valid
|
|
sqlsrv_sqltype sqlsrv_sql_type;
|
|
sqlsrv_sql_type.value = Z_LVAL_P(sqltype_z);
|
|
CHECK_CUSTOM_ERROR(!is_valid_sqlsrv_sqltype(sqlsrv_sql_type), stmt, SQLSRV_ERROR_INVALID_PARAMETER_SQLTYPE, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
bool size_okay = determine_column_size_or_precision(stmt, sqlsrv_sql_type, &column_size, &decimal_digits);
|
|
CHECK_CUSTOM_ERROR(!size_okay, stmt, SS_SQLSRV_ERROR_INVALID_PARAMETER_PRECISION, index + 1, NULL) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
sql_type = sqlsrv_sql_type.typeinfo.type;
|
|
|
|
if (direction != SQL_PARAM_INPUT && php_type_param_is_null) {
|
|
sqlsrv_phptype srv_phptype;
|
|
srv_phptype = determine_sqlsrv_php_type(stmt, sql_type, (SQLUINTEGER)column_size, true);
|
|
|
|
php_out_type = static_cast<SQLSRV_PHPTYPE>(srv_phptype.typeinfo.type);
|
|
encoding = static_cast<SQLSRV_ENCODING>(srv_phptype.typeinfo.encoding);
|
|
}
|
|
}
|
|
|
|
if (direction == SQL_PARAM_OUTPUT) {
|
|
if (php_out_type == SQLSRV_PHPTYPE_NULL || php_out_type == SQLSRV_PHPTYPE_DATETIME || php_out_type == SQLSRV_PHPTYPE_STREAM) {
|
|
THROW_CORE_ERROR(stmt, SS_SQLSRV_ERROR_INVALID_OUTPUT_PARAM_TYPE);
|
|
}
|
|
}
|
|
|
|
return var_or_val;
|
|
}
|
|
|
|
bool is_valid_sqlsrv_phptype( _In_ sqlsrv_phptype type )
|
|
{
|
|
switch( type.typeinfo.type ) {
|
|
|
|
case SQLSRV_PHPTYPE_NULL:
|
|
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:
|
|
{
|
|
if( type.typeinfo.encoding == SQLSRV_ENCODING_BINARY || type.typeinfo.encoding == SQLSRV_ENCODING_CHAR
|
|
|| type.typeinfo.encoding == CP_UTF8 || type.typeinfo.encoding == SQLSRV_ENCODING_DEFAULT ) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// return if the type is a valid sql server type not including
|
|
// size, precision or scale. Use determine_precision_and_scale for that.
|
|
bool is_valid_sqlsrv_sqltype( _In_ sqlsrv_sqltype sql_type )
|
|
{
|
|
switch( sql_type.typeinfo.type ) {
|
|
case SQL_BIGINT:
|
|
case SQL_BIT:
|
|
case SQL_INTEGER:
|
|
case SQL_SMALLINT:
|
|
case SQL_TINYINT:
|
|
case SQL_GUID:
|
|
case SQL_FLOAT:
|
|
case SQL_REAL:
|
|
case SQL_LONGVARBINARY:
|
|
case SQL_LONGVARCHAR:
|
|
case SQL_WLONGVARCHAR:
|
|
case SQL_SS_XML:
|
|
case SQL_BINARY:
|
|
case SQL_CHAR:
|
|
case SQL_WCHAR:
|
|
case SQL_WVARCHAR:
|
|
case SQL_VARBINARY:
|
|
case SQL_VARCHAR:
|
|
case SQL_DECIMAL:
|
|
case SQL_NUMERIC:
|
|
case SQL_TYPE_TIMESTAMP:
|
|
case SQL_TYPE_DATE:
|
|
case SQL_SS_TIME2:
|
|
case SQL_SS_TIMESTAMPOFFSET:
|
|
case SQL_SS_TABLE:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// verify an encoding given to type_and_encoding by looking through the list
|
|
// of standard encodings created at module initialization time
|
|
bool verify_and_set_encoding( _In_ const char* encoding_string, _Inout_ sqlsrv_phptype& phptype_encoding )
|
|
{
|
|
void* encoding_temp = NULL;
|
|
zend_ulong index = -1;
|
|
zend_string* key = NULL;
|
|
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, encoding_temp ) {
|
|
if (encoding_temp) {
|
|
sqlsrv_encoding* encoding = reinterpret_cast<sqlsrv_encoding*>(encoding_temp);
|
|
encoding_temp = NULL;
|
|
if (!stricmp(encoding_string, encoding->iana)) {
|
|
phptype_encoding.typeinfo.encoding = encoding->code_page;
|
|
return true;
|
|
}
|
|
}
|
|
else {
|
|
DIE("Fatal: Error retrieving encoding from encoding hash table.");
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
return false;
|
|
}
|
|
|
|
// called when one of the SQLSRV_SQLTYPE type functions is called. Encodes the type and size
|
|
// into a sqlsrv_sqltype bit fields (see php_sqlsrv.h).
|
|
void type_and_size_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
|
|
{
|
|
char* size_p = NULL;
|
|
size_t size_len = 0;
|
|
int size = 0;
|
|
|
|
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &size_p, &size_len ) == FAILURE ) {
|
|
return;
|
|
}
|
|
if (size_p) {
|
|
if (!strnicmp("max", size_p, sizeof("max") / sizeof(char))) {
|
|
size = SQLSRV_SIZE_MAX_TYPE;
|
|
}
|
|
else {
|
|
#ifndef _WIN32
|
|
errno = 0;
|
|
#else
|
|
_set_errno(0); // reset errno for atol
|
|
#endif // !_WIN32
|
|
size = atol(size_p);
|
|
if (errno != 0) {
|
|
size = SQLSRV_INVALID_SIZE;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
DIE("type_and_size_calc: size_p is null.");
|
|
}
|
|
|
|
int max_size = SQL_SERVER_MAX_FIELD_SIZE;
|
|
// size is actually the number of characters, not the number of bytes, so if they ask for a
|
|
// 2 byte per character type, then we half the maximum size allowed.
|
|
if( type == SQL_WVARCHAR || type == SQL_WCHAR ) {
|
|
max_size >>= 1;
|
|
}
|
|
|
|
if( size > max_size || size < SQLSRV_SIZE_MAX_TYPE || size == 0 ) {
|
|
LOG( SEV_ERROR, "invalid size. size must be > 0 and <= %1!d! characters or 'max'", max_size );
|
|
size = SQLSRV_INVALID_SIZE;
|
|
}
|
|
|
|
sqlsrv_sqltype sql_type;
|
|
sql_type.typeinfo.type = type;
|
|
sql_type.typeinfo.size = size;
|
|
sql_type.typeinfo.scale = SQLSRV_INVALID_SCALE;
|
|
|
|
ZVAL_LONG( return_value, sql_type.value );
|
|
}
|
|
|
|
// called when the user gives SQLSRV_SQLTYPE_DECIMAL or SQLSRV_SQLTYPE_NUMERIC sql types as the type of the
|
|
// field. encodes these into a sqlsrv_sqltype structure (see php_sqlsrv.h)
|
|
void type_and_precision_calc( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
|
|
{
|
|
zend_long prec = SQLSRV_INVALID_PRECISION;
|
|
zend_long scale = SQLSRV_INVALID_SCALE;
|
|
|
|
if( zend_parse_parameters( ZEND_NUM_ARGS(), "|ll", &prec, &scale ) == FAILURE ) {
|
|
return;
|
|
}
|
|
|
|
if( prec > SQL_SERVER_MAX_PRECISION ) {
|
|
LOG( SEV_ERROR, "Invalid precision. Precision can't be > 38" );
|
|
prec = SQLSRV_INVALID_PRECISION;
|
|
}
|
|
|
|
if( prec < 0 ) {
|
|
LOG( SEV_ERROR, "Invalid precision. Precision can't be negative" );
|
|
prec = SQLSRV_INVALID_PRECISION;
|
|
}
|
|
|
|
if( scale > prec ) {
|
|
LOG( SEV_ERROR, "Invalid scale. Scale can't be > precision" );
|
|
scale = SQLSRV_INVALID_SCALE;
|
|
}
|
|
|
|
sqlsrv_sqltype sql_type;
|
|
sql_type.typeinfo.type = type;
|
|
sql_type.typeinfo.size = prec;
|
|
sql_type.typeinfo.scale = scale;
|
|
|
|
ZVAL_LONG( return_value, sql_type.value );
|
|
}
|
|
|
|
// common code for SQLSRV_PHPTYPE_STREAM and SQLSRV_PHPTYPE_STRING php types given as parameters.
|
|
// encodes the type and encoding into a sqlsrv_phptype structure (see php_sqlsrv.h)
|
|
void type_and_encoding( INTERNAL_FUNCTION_PARAMETERS, _In_ int type )
|
|
{
|
|
|
|
SQLSRV_ASSERT(( type == SQLSRV_PHPTYPE_STREAM || type == SQLSRV_PHPTYPE_STRING ), "type_and_encoding: Invalid type passed." );
|
|
|
|
char* encoding_param;
|
|
size_t encoding_param_len = 0;
|
|
|
|
// set the default encoding values to invalid so that
|
|
// if the encoding isn't validated, it will return the invalid setting.
|
|
sqlsrv_phptype sqlsrv_php_type;
|
|
sqlsrv_php_type.typeinfo.type = type;
|
|
sqlsrv_php_type.typeinfo.encoding = SQLSRV_ENCODING_INVALID;
|
|
|
|
if( zend_parse_parameters( ZEND_NUM_ARGS(), "s", &encoding_param, &encoding_param_len ) == FAILURE ) {
|
|
ZVAL_LONG( return_value, sqlsrv_php_type.value );
|
|
}
|
|
|
|
if( !verify_and_set_encoding( encoding_param, sqlsrv_php_type )) {
|
|
LOG( SEV_ERROR, "Invalid encoding for php type." );
|
|
}
|
|
|
|
ZVAL_LONG( return_value, sqlsrv_php_type.value );
|
|
}
|
|
|
|
}
|