1534 lines
56 KiB
C++
1534 lines
56 KiB
C++
//---------------------------------------------------------------------------------------------------------------------------------
|
|
// File: conn.cpp
|
|
//
|
|
// Contents: Routines that use connection handles
|
|
//
|
|
// Microsoft Drivers 5.6 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.
|
|
//---------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
extern "C" {
|
|
#include "php_sqlsrv.h"
|
|
}
|
|
|
|
#include "php_sqlsrv_int.h"
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
// *** internal variables and constants ***
|
|
|
|
namespace {
|
|
|
|
// current subsytem. defined for the CHECK_SQL_{ERROR|WARNING} macros
|
|
unsigned int current_log_subsystem = LOG_CONN;
|
|
|
|
struct date_as_string_func {
|
|
|
|
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
|
{
|
|
TSRMLS_C; // show as used to avoid a warning
|
|
|
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>( conn );
|
|
|
|
if( zend_is_true( value )) {
|
|
ss_conn->date_as_string = true;
|
|
}
|
|
else {
|
|
ss_conn->date_as_string = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct format_decimals_func
|
|
{
|
|
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC)
|
|
{
|
|
TSRMLS_C; // show as used to avoid a warning
|
|
|
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
|
|
|
if (zend_is_true(value)) {
|
|
ss_conn->format_decimals = true;
|
|
}
|
|
else {
|
|
ss_conn->format_decimals = false;
|
|
}
|
|
}
|
|
};
|
|
|
|
struct decimal_places_func
|
|
{
|
|
|
|
static void func(connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC)
|
|
{
|
|
TSRMLS_C; // show as used to avoid a warning
|
|
|
|
// first check if the input is an integer
|
|
if (Z_TYPE_P(value) != IS_LONG) {
|
|
THROW_SS_ERROR(conn, SQLSRV_ERROR_INVALID_DECIMAL_PLACES);
|
|
}
|
|
|
|
zend_long decimal_places = Z_LVAL_P(value);
|
|
if (decimal_places < 0 || decimal_places > SQL_SERVER_MAX_MONEY_SCALE) {
|
|
decimal_places = NO_CHANGE_DECIMAL_PLACES;
|
|
}
|
|
|
|
ss_sqlsrv_conn* ss_conn = static_cast<ss_sqlsrv_conn*>(conn);
|
|
ss_conn->decimal_places = static_cast<short>(decimal_places);
|
|
}
|
|
};
|
|
|
|
struct conn_char_set_func {
|
|
|
|
static void func( connection_option const* /*option*/, _Inout_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
|
{
|
|
convert_to_string( value );
|
|
const char* encoding = Z_STRVAL_P( value );
|
|
size_t encoding_len = Z_STRLEN_P( value );
|
|
|
|
zend_ulong index = -1;
|
|
zend_string* key = NULL;
|
|
void* ss_encoding_temp = NULL;
|
|
|
|
ZEND_HASH_FOREACH_KEY_PTR( g_ss_encodings_ht, index, key, ss_encoding_temp ) {
|
|
sqlsrv_encoding* ss_encoding = reinterpret_cast<sqlsrv_encoding*>( ss_encoding_temp );
|
|
ss_encoding_temp = NULL;
|
|
if (!strnicmp( encoding, ss_encoding->iana, encoding_len )) {
|
|
|
|
if ( ss_encoding->not_for_connection ) {
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
|
|
}
|
|
|
|
conn->set_encoding( static_cast<SQLSRV_ENCODING>(ss_encoding->code_page ));
|
|
return;
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
|
|
}
|
|
};
|
|
|
|
struct bool_conn_str_func {
|
|
|
|
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC )
|
|
{
|
|
TSRMLS_C;
|
|
char const* val_str;
|
|
if( zend_is_true( value )) {
|
|
val_str = "yes";
|
|
}
|
|
else {
|
|
val_str = "no";
|
|
}
|
|
conn_str += option->odbc_name;
|
|
conn_str += "={";
|
|
conn_str += val_str;
|
|
conn_str += "};";
|
|
}
|
|
};
|
|
|
|
struct int_conn_str_func {
|
|
|
|
static void func( _In_ connection_option const* option, _In_ zval* value, sqlsrv_conn* /*conn*/, _Out_ std::string& conn_str TSRMLS_DC )
|
|
{
|
|
TSRMLS_C;
|
|
SQLSRV_ASSERT( Z_TYPE_P( value ) == IS_LONG, "An integer is expected for this keyword" )
|
|
|
|
std::string val_str = std::to_string( Z_LVAL_P( value ));
|
|
|
|
conn_str += option->odbc_name;
|
|
conn_str += "={";
|
|
conn_str += val_str;
|
|
conn_str += "};";
|
|
}
|
|
};
|
|
|
|
template <unsigned int Attr>
|
|
struct int_conn_attr_func {
|
|
|
|
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
core::SQLSetConnectAttr( conn, Attr, reinterpret_cast<SQLPOINTER>( Z_LVAL_P( value )), SQL_IS_UINTEGER TSRMLS_CC );
|
|
}
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
};
|
|
|
|
template <unsigned int Attr>
|
|
struct bool_conn_attr_func {
|
|
|
|
static void func( connection_option const* /*option*/, _In_ zval* value, _Inout_ sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
|
|
{
|
|
try {
|
|
core::SQLSetConnectAttr(conn, Attr, reinterpret_cast<SQLPOINTER>((zend_long)zend_is_true(value)), SQL_IS_UINTEGER TSRMLS_CC);
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
|
|
}
|
|
};
|
|
|
|
//// *** internal functions ***
|
|
|
|
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC );
|
|
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd,
|
|
_Inout_ HashTable* ss_conn_options_ht TSRMLS_DC );
|
|
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC );
|
|
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
|
|
_Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC );
|
|
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC );
|
|
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC );
|
|
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC );
|
|
|
|
}
|
|
|
|
// constants for parameters used by process_params function(s)
|
|
int ss_sqlsrv_conn::descriptor;
|
|
const char* ss_sqlsrv_conn::resource_name = "ss_sqlsrv_conn";
|
|
|
|
// connection specific parameter proccessing. Use the generic function specialised to return a connection
|
|
// resource.
|
|
#define PROCESS_PARAMS( rsrc, param_spec, calling_func, param_count, ... ) \
|
|
rsrc = process_params<ss_sqlsrv_conn>( INTERNAL_FUNCTION_PARAM_PASSTHRU, param_spec, calling_func, param_count, ##__VA_ARGS__ );\
|
|
if( rsrc == NULL ) { \
|
|
RETURN_FALSE; \
|
|
}
|
|
|
|
namespace SSStmtOptionNames {
|
|
const char QUERY_TIMEOUT[]= "QueryTimeout";
|
|
const char SEND_STREAMS_AT_EXEC[] = "SendStreamParamsAtExec";
|
|
const char SCROLLABLE[] = "Scrollable";
|
|
const char CLIENT_BUFFER_MAX_SIZE[] = INI_BUFFERED_QUERY_LIMIT;
|
|
const char DATE_AS_STRING[] = "ReturnDatesAsStrings";
|
|
const char FORMAT_DECIMALS[] = "FormatDecimals";
|
|
const char DECIMAL_PLACES[] = "DecimalPlaces";
|
|
}
|
|
|
|
namespace SSConnOptionNames {
|
|
|
|
// most of these strings are the same for both the sqlsrv_connect connection option
|
|
// and the name put into the connection string. MARS is the only one that's different.
|
|
const char APP[] = "APP";
|
|
const char AccessToken[] = "AccessToken";
|
|
const char ApplicationIntent[] = "ApplicationIntent";
|
|
const char AttachDBFileName[] = "AttachDbFileName";
|
|
const char Authentication[] = "Authentication";
|
|
const char CharacterSet[] = "CharacterSet";
|
|
const char ColumnEncryption[] = "ColumnEncryption";
|
|
const char ConnectionPooling[] = "ConnectionPooling";
|
|
const char Language[] = "Language";
|
|
const char ConnectRetryCount[] = "ConnectRetryCount";
|
|
const char ConnectRetryInterval[] = "ConnectRetryInterval";
|
|
const char Database[] = "Database";
|
|
const char DecimalPlaces[] = "DecimalPlaces";
|
|
const char FormatDecimals[] = "FormatDecimals";
|
|
const char DateAsString[] = "ReturnDatesAsStrings";
|
|
const char Driver[] = "Driver";
|
|
const char Encrypt[] = "Encrypt";
|
|
const char Failover_Partner[] = "Failover_Partner";
|
|
const char KeyStoreAuthentication[] = "KeyStoreAuthentication";
|
|
const char KeyStorePrincipalId[] = "KeyStorePrincipalId";
|
|
const char KeyStoreSecret[] = "KeyStoreSecret";
|
|
const char LoginTimeout[] = "LoginTimeout";
|
|
const char MARS_Option[] = "MultipleActiveResultSets";
|
|
const char MultiSubnetFailover[] = "MultiSubnetFailover";
|
|
const char PWD[] = "PWD";
|
|
const char QuotedId[] = "QuotedId";
|
|
const char TraceFile[] = "TraceFile";
|
|
const char TraceOn[] = "TraceOn";
|
|
const char TrustServerCertificate[] = "TrustServerCertificate";
|
|
const char TransactionIsolation[] = "TransactionIsolation";
|
|
const char TransparentNetworkIPResolution[] = "TransparentNetworkIPResolution";
|
|
const char UID[] = "UID";
|
|
const char WSID[] = "WSID";
|
|
|
|
}
|
|
|
|
enum SS_CONN_OPTIONS {
|
|
|
|
SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
|
|
SS_CONN_OPTION_FORMAT_DECIMALS,
|
|
SS_CONN_OPTION_DECIMAL_PLACES,
|
|
};
|
|
|
|
//List of all statement options supported by this driver
|
|
const stmt_option SS_STMT_OPTS[] = {
|
|
{
|
|
SSStmtOptionNames::QUERY_TIMEOUT,
|
|
sizeof( SSStmtOptionNames::QUERY_TIMEOUT ),
|
|
SQLSRV_STMT_OPTION_QUERY_TIMEOUT,
|
|
std::unique_ptr<stmt_option_query_timeout>( new stmt_option_query_timeout )
|
|
},
|
|
{
|
|
SSStmtOptionNames::SEND_STREAMS_AT_EXEC,
|
|
sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ),
|
|
SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
|
|
std::unique_ptr<stmt_option_send_at_exec>( new stmt_option_send_at_exec )
|
|
},
|
|
{
|
|
SSStmtOptionNames::SCROLLABLE,
|
|
sizeof( SSStmtOptionNames::SCROLLABLE ),
|
|
SQLSRV_STMT_OPTION_SCROLLABLE,
|
|
std::unique_ptr<stmt_option_ss_scrollable>( new stmt_option_ss_scrollable )
|
|
},
|
|
{
|
|
SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE,
|
|
sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ),
|
|
SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
|
|
std::unique_ptr<stmt_option_buffered_query_limit>( new stmt_option_buffered_query_limit )
|
|
},
|
|
{
|
|
SSStmtOptionNames::DATE_AS_STRING,
|
|
sizeof( SSStmtOptionNames::DATE_AS_STRING ),
|
|
SQLSRV_STMT_OPTION_DATE_AS_STRING,
|
|
std::unique_ptr<stmt_option_date_as_string>( new stmt_option_date_as_string )
|
|
},
|
|
{
|
|
SSStmtOptionNames::FORMAT_DECIMALS,
|
|
sizeof( SSStmtOptionNames::FORMAT_DECIMALS ),
|
|
SQLSRV_STMT_OPTION_FORMAT_DECIMALS,
|
|
std::unique_ptr<stmt_option_format_decimals>( new stmt_option_format_decimals )
|
|
},
|
|
{
|
|
SSStmtOptionNames::DECIMAL_PLACES,
|
|
sizeof( SSStmtOptionNames::DECIMAL_PLACES),
|
|
SQLSRV_STMT_OPTION_DECIMAL_PLACES,
|
|
std::unique_ptr<stmt_option_decimal_places>( new stmt_option_decimal_places )
|
|
},
|
|
{ NULL, 0, SQLSRV_STMT_OPTION_INVALID, std::unique_ptr<stmt_option_functor>{} },
|
|
};
|
|
|
|
|
|
// List of all connection options supported by this driver.
|
|
const connection_option SS_CONN_OPTS[] = {
|
|
{
|
|
SSConnOptionNames::APP,
|
|
sizeof( SSConnOptionNames::APP ),
|
|
SQLSRV_CONN_OPTION_APP,
|
|
ODBCConnOptions::APP,
|
|
sizeof( ODBCConnOptions::APP ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::AccessToken,
|
|
sizeof( SSConnOptionNames::AccessToken ),
|
|
SQLSRV_CONN_OPTION_ACCESS_TOKEN,
|
|
ODBCConnOptions::AccessToken,
|
|
sizeof( ODBCConnOptions::AccessToken),
|
|
CONN_ATTR_STRING,
|
|
access_token_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::ApplicationIntent,
|
|
sizeof( SSConnOptionNames::ApplicationIntent ),
|
|
SQLSRV_CONN_OPTION_APPLICATION_INTENT,
|
|
ODBCConnOptions::ApplicationIntent,
|
|
sizeof( ODBCConnOptions::ApplicationIntent ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::AttachDBFileName,
|
|
sizeof( SSConnOptionNames::AttachDBFileName ),
|
|
SQLSRV_CONN_OPTION_ATTACHDBFILENAME,
|
|
ODBCConnOptions::AttachDBFileName,
|
|
sizeof( ODBCConnOptions::AttachDBFileName ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::CharacterSet,
|
|
sizeof( SSConnOptionNames::CharacterSet ),
|
|
SQLSRV_CONN_OPTION_CHARACTERSET,
|
|
ODBCConnOptions::CharacterSet,
|
|
sizeof( ODBCConnOptions::CharacterSet ),
|
|
CONN_ATTR_STRING,
|
|
conn_char_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Authentication,
|
|
sizeof( SSConnOptionNames::Authentication ),
|
|
SQLSRV_CONN_OPTION_AUTHENTICATION,
|
|
ODBCConnOptions::Authentication,
|
|
sizeof( ODBCConnOptions::Authentication ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::ConnectionPooling,
|
|
sizeof(SSConnOptionNames::ConnectionPooling),
|
|
SQLSRV_CONN_OPTION_CONN_POOLING,
|
|
ODBCConnOptions::ConnectionPooling,
|
|
sizeof(ODBCConnOptions::ConnectionPooling),
|
|
CONN_ATTR_BOOL,
|
|
conn_null_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Language,
|
|
sizeof(SSConnOptionNames::Language),
|
|
SQLSRV_CONN_OPTION_LANGUAGE,
|
|
ODBCConnOptions::Language,
|
|
sizeof(ODBCConnOptions::Language),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Driver,
|
|
sizeof(SSConnOptionNames::Driver),
|
|
SQLSRV_CONN_OPTION_DRIVER,
|
|
ODBCConnOptions::Driver,
|
|
sizeof(ODBCConnOptions::Driver),
|
|
CONN_ATTR_STRING,
|
|
driver_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::ColumnEncryption,
|
|
sizeof(SSConnOptionNames::ColumnEncryption),
|
|
SQLSRV_CONN_OPTION_COLUMNENCRYPTION,
|
|
ODBCConnOptions::ColumnEncryption,
|
|
sizeof(ODBCConnOptions::ColumnEncryption),
|
|
CONN_ATTR_STRING,
|
|
column_encryption_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::ConnectRetryCount,
|
|
sizeof( SSConnOptionNames::ConnectRetryCount ),
|
|
SQLSRV_CONN_OPTION_CONN_RETRY_COUNT,
|
|
ODBCConnOptions::ConnectRetryCount,
|
|
sizeof( ODBCConnOptions::ConnectRetryCount ),
|
|
CONN_ATTR_INT,
|
|
int_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::ConnectRetryInterval,
|
|
sizeof( SSConnOptionNames::ConnectRetryInterval ),
|
|
SQLSRV_CONN_OPTION_CONN_RETRY_INTERVAL,
|
|
ODBCConnOptions::ConnectRetryInterval,
|
|
sizeof( ODBCConnOptions::ConnectRetryInterval ),
|
|
CONN_ATTR_INT,
|
|
int_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Database,
|
|
sizeof( SSConnOptionNames::Database ),
|
|
SQLSRV_CONN_OPTION_DATABASE,
|
|
ODBCConnOptions::Database,
|
|
sizeof( ODBCConnOptions::Database ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Encrypt,
|
|
sizeof( SSConnOptionNames::Encrypt ),
|
|
SQLSRV_CONN_OPTION_ENCRYPT,
|
|
ODBCConnOptions::Encrypt,
|
|
sizeof( ODBCConnOptions::Encrypt ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::Failover_Partner,
|
|
sizeof( SSConnOptionNames::Failover_Partner ),
|
|
SQLSRV_CONN_OPTION_FAILOVER_PARTNER,
|
|
ODBCConnOptions::Failover_Partner,
|
|
sizeof( ODBCConnOptions::Failover_Partner ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::KeyStoreAuthentication,
|
|
sizeof( SSConnOptionNames::KeyStoreAuthentication ),
|
|
SQLSRV_CONN_OPTION_KEYSTORE_AUTHENTICATION,
|
|
ODBCConnOptions::KeyStoreAuthentication,
|
|
sizeof( ODBCConnOptions::KeyStoreAuthentication ),
|
|
CONN_ATTR_STRING,
|
|
ce_akv_str_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::KeyStorePrincipalId,
|
|
sizeof( SSConnOptionNames::KeyStorePrincipalId ),
|
|
SQLSRV_CONN_OPTION_KEYSTORE_PRINCIPAL_ID,
|
|
ODBCConnOptions::KeyStorePrincipalId,
|
|
sizeof( ODBCConnOptions::KeyStorePrincipalId ),
|
|
CONN_ATTR_STRING,
|
|
ce_akv_str_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::KeyStoreSecret,
|
|
sizeof( SSConnOptionNames::KeyStoreSecret ),
|
|
SQLSRV_CONN_OPTION_KEYSTORE_SECRET,
|
|
ODBCConnOptions::KeyStoreSecret,
|
|
sizeof( ODBCConnOptions::KeyStoreSecret ),
|
|
CONN_ATTR_STRING,
|
|
ce_akv_str_set_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::LoginTimeout,
|
|
sizeof( SSConnOptionNames::LoginTimeout ),
|
|
SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
|
|
ODBCConnOptions::LoginTimeout,
|
|
sizeof( ODBCConnOptions::LoginTimeout ),
|
|
CONN_ATTR_INT,
|
|
int_conn_attr_func<SQL_ATTR_LOGIN_TIMEOUT>::func
|
|
},
|
|
{
|
|
SSConnOptionNames::MARS_Option,
|
|
sizeof( SSConnOptionNames::MARS_Option ),
|
|
SQLSRV_CONN_OPTION_MARS,
|
|
ODBCConnOptions::MARS_ODBC,
|
|
sizeof( ODBCConnOptions::MARS_ODBC ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::MultiSubnetFailover,
|
|
sizeof( SSConnOptionNames::MultiSubnetFailover ),
|
|
SQLSRV_CONN_OPTION_MULTI_SUBNET_FAILOVER,
|
|
ODBCConnOptions::MultiSubnetFailover,
|
|
sizeof( ODBCConnOptions::MultiSubnetFailover ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::QuotedId,
|
|
sizeof( SSConnOptionNames::QuotedId ),
|
|
SQLSRV_CONN_OPTION_QUOTED_ID,
|
|
ODBCConnOptions::QuotedId,
|
|
sizeof( ODBCConnOptions::QuotedId ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::TraceFile,
|
|
sizeof( SSConnOptionNames::TraceFile ),
|
|
SQLSRV_CONN_OPTION_TRACE_FILE,
|
|
ODBCConnOptions::TraceFile,
|
|
sizeof( ODBCConnOptions::TraceFile ),
|
|
CONN_ATTR_STRING,
|
|
str_conn_attr_func<SQL_ATTR_TRACEFILE>::func
|
|
},
|
|
{
|
|
SSConnOptionNames::TraceOn,
|
|
sizeof( SSConnOptionNames::TraceOn ),
|
|
SQLSRV_CONN_OPTION_TRACE_ON,
|
|
ODBCConnOptions::TraceOn,
|
|
sizeof( ODBCConnOptions::TraceOn ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_attr_func<SQL_ATTR_TRACE>::func
|
|
},
|
|
{
|
|
SSConnOptionNames::TransactionIsolation,
|
|
sizeof( SSConnOptionNames::TransactionIsolation ),
|
|
SQLSRV_CONN_OPTION_TRANS_ISOLATION,
|
|
ODBCConnOptions::TransactionIsolation,
|
|
sizeof( ODBCConnOptions::TransactionIsolation ),
|
|
CONN_ATTR_INT,
|
|
int_conn_attr_func<SQL_COPT_SS_TXN_ISOLATION>::func
|
|
},
|
|
{
|
|
SSConnOptionNames::TrustServerCertificate,
|
|
sizeof( SSConnOptionNames::TrustServerCertificate ),
|
|
SQLSRV_CONN_OPTION_TRUST_SERVER_CERT,
|
|
ODBCConnOptions::TrustServerCertificate,
|
|
sizeof( ODBCConnOptions::TrustServerCertificate ),
|
|
CONN_ATTR_BOOL,
|
|
bool_conn_str_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::TransparentNetworkIPResolution,
|
|
sizeof(SSConnOptionNames::TransparentNetworkIPResolution),
|
|
SQLSRV_CONN_OPTION_TRANSPARENT_NETWORK_IP_RESOLUTION,
|
|
ODBCConnOptions::TransparentNetworkIPResolution,
|
|
sizeof(ODBCConnOptions::TransparentNetworkIPResolution),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::WSID,
|
|
sizeof( SSConnOptionNames::WSID ),
|
|
SQLSRV_CONN_OPTION_WSID,
|
|
ODBCConnOptions::WSID,
|
|
sizeof( ODBCConnOptions::WSID ),
|
|
CONN_ATTR_STRING,
|
|
conn_str_append_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::DateAsString,
|
|
sizeof( SSConnOptionNames::DateAsString ),
|
|
SS_CONN_OPTION_DATE_AS_STRING,
|
|
SSConnOptionNames::DateAsString,
|
|
sizeof( SSConnOptionNames::DateAsString ),
|
|
CONN_ATTR_BOOL,
|
|
date_as_string_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::FormatDecimals,
|
|
sizeof( SSConnOptionNames::FormatDecimals),
|
|
SS_CONN_OPTION_FORMAT_DECIMALS,
|
|
SSConnOptionNames::FormatDecimals,
|
|
sizeof( SSConnOptionNames::FormatDecimals),
|
|
CONN_ATTR_BOOL,
|
|
format_decimals_func::func
|
|
},
|
|
{
|
|
SSConnOptionNames::DecimalPlaces,
|
|
sizeof( SSConnOptionNames::DecimalPlaces),
|
|
SS_CONN_OPTION_DECIMAL_PLACES,
|
|
SSConnOptionNames::DecimalPlaces,
|
|
sizeof( SSConnOptionNames::DecimalPlaces),
|
|
CONN_ATTR_INT,
|
|
decimal_places_func::func
|
|
},
|
|
{ NULL, 0, SQLSRV_CONN_OPTION_INVALID, NULL, 0 , CONN_ATTR_INVALID, NULL }, //terminate the table
|
|
};
|
|
|
|
// sqlsrv_connect( string $serverName [, array $connectionInfo])
|
|
//
|
|
// Creates a connection resource and opens a connection. By default, the
|
|
// connection is attempted using Windows Authentication.
|
|
//
|
|
// Parameters
|
|
// $serverName: A string specifying the name of the server to which a connection
|
|
// is being established. An instance name (for example, "myServer\instanceName")
|
|
// or port number (for example, "myServer, 1521") can be included as part of
|
|
// this string. For a complete description of the options available for this
|
|
// parameter, see the Server keyword in the ODBC Driver Connection String
|
|
// Keywords section of Using Connection String Keywords with ODBC Driver 11 for SQL Server.
|
|
//
|
|
// $connectionInfo [OPTIONAL]: An associative array that contains connection
|
|
// attributes (for example, array("Database" => "AdventureWorks")).
|
|
//
|
|
// Return Value
|
|
// A PHP connection resource. If a connection cannot be successfully created and
|
|
// opened, false is returned
|
|
|
|
PHP_FUNCTION ( sqlsrv_connect )
|
|
{
|
|
|
|
LOG_FUNCTION( "sqlsrv_connect" );
|
|
SET_FUNCTION_NAME( *g_ss_henv_cp );
|
|
SET_FUNCTION_NAME( *g_ss_henv_ncp );
|
|
|
|
reset_errors( TSRMLS_C );
|
|
|
|
const char* server = NULL;
|
|
zval* options_z = NULL;
|
|
char* uid = NULL;
|
|
char* pwd = NULL;
|
|
size_t server_len = 0;
|
|
zval conn_z;
|
|
ZVAL_UNDEF(&conn_z);
|
|
// get the server name and connection options
|
|
int result = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "s|a", &server, &server_len, &options_z );
|
|
|
|
CHECK_CUSTOM_ERROR(( result == FAILURE ), *g_ss_henv_cp, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, "sqlsrv_connect" ) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
hash_auto_ptr ss_conn_options_ht;
|
|
hash_auto_ptr stmts;
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
|
|
try {
|
|
|
|
// Initialize the options array to be passed to the core layer
|
|
ALLOC_HASHTABLE( ss_conn_options_ht );
|
|
|
|
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, ss_conn_options_ht, 10 /* # of buckets */,
|
|
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
|
|
|
|
// Either of g_ss_henv_cp or g_ss_henv_ncp can be used to propagate the error.
|
|
::validate_conn_options( *g_ss_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC );
|
|
|
|
// call the core connect function
|
|
conn = static_cast<ss_sqlsrv_conn*>( core_sqlsrv_connect( *g_ss_henv_cp, *g_ss_henv_ncp, &core::allocate_conn<ss_sqlsrv_conn>,
|
|
server, uid, pwd, ss_conn_options_ht, ss_error_handler,
|
|
SS_CONN_OPTS, NULL, "sqlsrv_connect" TSRMLS_CC ));
|
|
|
|
SQLSRV_ASSERT( conn != NULL, "sqlsrv_connect: Invalid connection returned. Exception should have been thrown." );
|
|
|
|
// create a bunch of statements
|
|
ALLOC_HASHTABLE( stmts );
|
|
|
|
core::sqlsrv_zend_hash_init( *g_ss_henv_cp, stmts, 5, NULL /* dtor */, 0 /* persistent */ TSRMLS_CC );
|
|
|
|
// register the connection with the PHP runtime
|
|
|
|
ss::zend_register_resource(conn_z, conn, ss_sqlsrv_conn::descriptor, ss_sqlsrv_conn::resource_name TSRMLS_CC);
|
|
|
|
conn->stmts = stmts;
|
|
stmts.transferred();
|
|
RETURN_RES( Z_RES(conn_z) );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
|
|
if( conn != NULL ) {
|
|
|
|
conn->invalidate();
|
|
}
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
catch( ... ) {
|
|
|
|
DIE("sqlsrv_connect: Unknown exception caught.");
|
|
}
|
|
}
|
|
|
|
// sqlsrv_begin_transaction( resource $conn )
|
|
//
|
|
// Begins a transaction on a specified connection. The current transaction
|
|
// includes all statements on the specified connection that were executed after
|
|
// the call to sqlsrv_begin_transaction and before any calls to sqlsrv_rollback
|
|
// or sqlsrv_commit.
|
|
//
|
|
// The SQLSRV driver is in auto-commit mode by default. This means that all
|
|
// queries are automatically committed upon success unless they have been
|
|
// designated as part of an explicit transaction by using
|
|
// sqlsrv_begin_transaction.
|
|
//
|
|
// If sqlsrv_begin_transaction is called after a transaction has already been
|
|
// initiated on the connection but not completed by calling either sqlsrv_commit
|
|
// or sqlsrv_rollback, the call returns false and an Already in Transaction
|
|
// error is added to the error collection.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection with which the transaction is associated.
|
|
//
|
|
// Return Value
|
|
// A Boolean value: true if the transaction was successfully begun. Otherwise, false.
|
|
|
|
PHP_FUNCTION( sqlsrv_begin_transaction )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_begin_transaction" );
|
|
|
|
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
PROCESS_PARAMS( conn, "r", _FN_, 0 );
|
|
|
|
// Return false if already in transaction
|
|
CHECK_CUSTOM_ERROR(( conn->in_transaction == true ), *conn, SS_SQLSRV_ERROR_ALREADY_IN_TXN ) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
try {
|
|
|
|
core_sqlsrv_begin_transaction( conn TSRMLS_CC );
|
|
conn->in_transaction = true;
|
|
RETURN_TRUE;
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE("sqlsrv_begin_transaction: Unknown exception caught.");
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_close( resource $conn )
|
|
// Closes the specified connection and releases associated resources.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection to be closed. Null is a valid value parameter for this
|
|
// parameter. This allows the function to be called multiple times in a
|
|
// script. For example, if you close a connection in an error condition and
|
|
// close it again at the end of the script, the second call to sqlsrv_close will
|
|
// return true because the first call to sqlsrv_close (in the error condition)
|
|
// sets the connection resource to null.
|
|
//
|
|
// 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.
|
|
|
|
PHP_FUNCTION( sqlsrv_close )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_close" );
|
|
|
|
zval* conn_r = NULL;
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
sqlsrv_context_auto_ptr error_ctx;
|
|
|
|
reset_errors( TSRMLS_C );
|
|
|
|
try {
|
|
|
|
// dummy context to pass to the error handler
|
|
error_ctx = new (sqlsrv_malloc( sizeof( sqlsrv_context ))) sqlsrv_context( 0, ss_error_handler, NULL );
|
|
SET_FUNCTION_NAME( *error_ctx );
|
|
|
|
if( zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r", &conn_r) == FAILURE ) {
|
|
|
|
// Check if it was a zval
|
|
int zr = zend_parse_parameters( ZEND_NUM_ARGS() TSRMLS_CC, "z", &conn_r );
|
|
CHECK_CUSTOM_ERROR(( zr == FAILURE ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
// if sqlsrv_close was called on a non-existent connection then we just return success.
|
|
if( Z_TYPE_P( conn_r ) == IS_NULL ) {
|
|
RETURN_TRUE;
|
|
}
|
|
else {
|
|
THROW_CORE_ERROR( error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
|
}
|
|
}
|
|
SQLSRV_ASSERT( conn_r != NULL, "sqlsrv_close: conn_r was null" );
|
|
conn = static_cast<ss_sqlsrv_conn*>( zend_fetch_resource( Z_RES_P( conn_r ) TSRMLS_CC, ss_sqlsrv_conn::resource_name, ss_sqlsrv_conn::descriptor ));
|
|
|
|
// if sqlsrv_close was called on an already closed connection then we just return success.
|
|
if ( Z_RES_TYPE_P( conn_r ) == RSRC_INVALID_TYPE) {
|
|
RETURN_TRUE;
|
|
}
|
|
|
|
CHECK_CUSTOM_ERROR(( conn == NULL ), error_ctx, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
SET_FUNCTION_NAME( *conn );
|
|
|
|
// cause any variables still holding a reference to this to be invalid so they cause
|
|
// an error when passed to a sqlsrv function. There's nothing we can do if the
|
|
// removal fails, so we just log it and move on.
|
|
if( zend_list_close( Z_RES_P( conn_r ) ) == FAILURE ) {
|
|
LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_P( conn_r ));
|
|
}
|
|
|
|
// when conn_r is first parsed in zend_parse_parameters, conn_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 conn_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( conn_r ), not the zend_resource itself
|
|
Z_TRY_DELREF_P(conn_r);
|
|
ZVAL_NULL( conn_r );
|
|
|
|
RETURN_TRUE;
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_close: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
void __cdecl sqlsrv_conn_dtor( _Inout_ zend_resource *rsrc TSRMLS_DC )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_conn_dtor" );
|
|
|
|
// get the structure
|
|
ss_sqlsrv_conn *conn = static_cast<ss_sqlsrv_conn*>( rsrc->ptr );
|
|
SQLSRV_ASSERT( conn != NULL, "sqlsrv_conn_dtor: connection was null");
|
|
|
|
SET_FUNCTION_NAME( *conn );
|
|
|
|
// close all statements associated with the connection.
|
|
sqlsrv_conn_close_stmts( conn TSRMLS_CC );
|
|
|
|
// close the connection itself.
|
|
core_sqlsrv_close( conn TSRMLS_CC );
|
|
|
|
rsrc->ptr = NULL;
|
|
}
|
|
|
|
// sqlsrv_commit( resource $conn )
|
|
//
|
|
// Commits the current transaction on the specified connection and returns the
|
|
// connection to the auto-commit mode. The current transaction includes all
|
|
// statements on the specified connection that were executed after the call to
|
|
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
|
|
// sqlsrv_commit.
|
|
|
|
// The SQLSRV driver is in auto-commit mode by
|
|
// default. This means that all queries are automatically committed upon success
|
|
// unless they have been designated as part of an explicit transaction by using
|
|
// sqlsrv_begin_transaction. If sqlsrv_commit is called on a connection that is
|
|
// not in an active transaction and that was initiated with
|
|
// sqlsrv_begin_transaction, the call returns false and a Not in Transaction
|
|
// error is added to the error collection.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection on which the transaction is active.
|
|
//
|
|
// Return Value
|
|
// A Boolean value: true if the transaction was successfully committed. Otherwise, false.
|
|
|
|
PHP_FUNCTION( sqlsrv_commit )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_commit" );
|
|
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
|
|
PROCESS_PARAMS( conn, "r", _FN_, 0 );
|
|
|
|
// Return false if not in transaction
|
|
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
try {
|
|
|
|
conn->in_transaction = false;
|
|
core_sqlsrv_commit( conn TSRMLS_CC );
|
|
RETURN_TRUE;
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_commit: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_rollback( resource $conn )
|
|
//
|
|
// Rolls back the current transaction on the specified connection and returns
|
|
// the connection to the auto-commit mode. The current transaction includes all
|
|
// statements on the specified connection that were executed after the call to
|
|
// sqlsrv_begin_transaction and before any calls to sqlsrv_rollback or
|
|
// sqlsrv_commit.
|
|
//
|
|
// The SQLSRV driver is in auto-commit mode by default. This
|
|
// means that all queries are automatically committed upon success unless they
|
|
// have been designated as part of an explicit transaction by using
|
|
// sqlsrv_begin_transaction.
|
|
//
|
|
// If sqlsrv_rollback is called on a connection that is not in an active
|
|
// transaction that was initiated with sqlsrv_begin_transaction, the call
|
|
// returns false and a Not in Transaction error is added to the error
|
|
// collection.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection on which the transaction is active.
|
|
//
|
|
// Return Value
|
|
// A Boolean value: true if the transaction was successfully rolled back. Otherwise, false.
|
|
|
|
PHP_FUNCTION( sqlsrv_rollback )
|
|
{
|
|
LOG_FUNCTION( "sqlsrv_rollback" );
|
|
|
|
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
|
|
PROCESS_PARAMS( conn, "r", _FN_, 0 );
|
|
|
|
// Return false if not in transaction
|
|
CHECK_CUSTOM_ERROR(( conn->in_transaction == false ), *conn, SS_SQLSRV_ERROR_NOT_IN_TXN ) {
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
try {
|
|
|
|
conn->in_transaction = false;
|
|
core_sqlsrv_rollback( conn TSRMLS_CC );
|
|
RETURN_TRUE;
|
|
}
|
|
catch( core::CoreException& ){
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_rollback: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_client_info
|
|
// Returns the ODBC driver's dll name, version and the ODBC version. Also returns
|
|
// the version of this extension.
|
|
// Parameters:
|
|
// $conn - The connection resource by which the client and server are connected.
|
|
|
|
PHP_FUNCTION( sqlsrv_client_info )
|
|
{
|
|
|
|
LOG_FUNCTION( "sqlsrv_client_info" );
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
PROCESS_PARAMS( conn, "r", _FN_, 0 );
|
|
|
|
try {
|
|
|
|
core_sqlsrv_get_client_info( conn, return_value TSRMLS_CC );
|
|
|
|
// Add the sqlsrv driver's file version
|
|
//Declarations below eliminate compiler warnings about string constant to char* conversions
|
|
const char* extver = "ExtensionVer";
|
|
std::string filever = VER_FILEVERSION_STR;
|
|
core::sqlsrv_add_assoc_string( *conn, return_value, extver, &filever[0], 1 /*duplicate*/ TSRMLS_CC );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_client_info: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_server_info( resource $conn )
|
|
//
|
|
// Returns information about the server.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection resource by which the client and server are connected.
|
|
//
|
|
// Return Value
|
|
// An associative array with the following keys:
|
|
// CurrentDatabase
|
|
// The database currently being targeted.
|
|
// SQLServerVersion
|
|
// The version of SQL Server.
|
|
// SQLServerName
|
|
// The name of the server.
|
|
|
|
PHP_FUNCTION( sqlsrv_server_info )
|
|
{
|
|
try {
|
|
|
|
LOG_FUNCTION( "sqlsrv_server_info" );
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
PROCESS_PARAMS( conn, "r", _FN_, 0 );
|
|
|
|
core_sqlsrv_get_server_info( conn, return_value TSRMLS_CC );
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_server_info: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
|
|
// sqlsrv_prepare( resource $conn, string $tsql [, array $params [, array $options]])
|
|
//
|
|
// Creates a statement resource associated with the specified connection. A statement
|
|
// resource returned by sqlsrv_prepare may be executed multiple times by sqlsrv_execute.
|
|
// In between each execution, the values may be updated by changing the value of the
|
|
// variables bound. Output parameters cannot be relied upon to contain their results until
|
|
// all rows are processed.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection resource associated with the created statement.
|
|
//
|
|
// $tsql: The Transact-SQL expression that corresponds to the created statement.
|
|
//
|
|
// $params [OPTIONAL]: An array of values that correspond to parameters in a
|
|
// parameterized query. Each parameter may be specified as:
|
|
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
|
|
// When given just a $value, the direction is default input, and phptype is the value
|
|
// given, with the sql type inferred from the php type.
|
|
//
|
|
// $options [OPTIONAL]: An associative array that sets query properties. The
|
|
// table below lists the supported keys and corresponding values:
|
|
// QueryTimeout
|
|
// Sets the query timeout in seconds. By default, the driver will wait
|
|
// indefinitely for results.
|
|
// SendStreamParamsAtExec
|
|
// Configures the driver to send all stream data at execution (true), or to
|
|
// send stream data in chunks (false). By default, the value is set to
|
|
// true. For more information, see sqlsrv_send_stream_data.
|
|
//
|
|
// Return Value
|
|
// A statement resource. If the statement resource cannot be created, false is returned.
|
|
|
|
PHP_FUNCTION( sqlsrv_prepare )
|
|
{
|
|
|
|
LOG_FUNCTION( "sqlsrv_prepare" );
|
|
|
|
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
char *sql = NULL;
|
|
zend_long sql_len = 0;
|
|
zval* params_z = NULL;
|
|
zval* options_z = NULL;
|
|
hash_auto_ptr ss_stmt_options_ht;
|
|
zval stmt_z;
|
|
ZVAL_UNDEF(&stmt_z);
|
|
|
|
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z );
|
|
|
|
try {
|
|
|
|
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
|
|
|
|
// Initialize the options array to be passed to the core layer
|
|
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
|
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */,
|
|
ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
|
|
|
|
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
|
|
|
}
|
|
|
|
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
|
}
|
|
|
|
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
|
}
|
|
|
|
if( sql == NULL ) {
|
|
|
|
DIE( "sqlsrv_prepare: sql string was null." );
|
|
}
|
|
|
|
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
|
|
ss_stmt_options_ht, SS_STMT_OPTS,
|
|
ss_error_handler, NULL TSRMLS_CC ) );
|
|
|
|
core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC );
|
|
|
|
if (params_z) {
|
|
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
|
|
ZVAL_COPY(stmt->params_z, params_z);
|
|
}
|
|
|
|
stmt->prepared = true;
|
|
|
|
// register the statement with the PHP runtime
|
|
ss::zend_register_resource( stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC );
|
|
|
|
// store the resource id with the connection so the connection
|
|
// can release this statement when it closes.
|
|
zend_long next_index = zend_hash_next_free_element( conn->stmts );
|
|
|
|
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC);
|
|
|
|
stmt->conn_index = next_index;
|
|
|
|
// the statement is now registered with EG( regular_list )
|
|
stmt.transferred();
|
|
|
|
RETURN_RES(Z_RES(stmt_z));
|
|
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
if( stmt ) {
|
|
|
|
stmt->conn = NULL;
|
|
stmt->~ss_sqlsrv_stmt();
|
|
}
|
|
if (!Z_ISUNDEF(stmt_z)) {
|
|
free_stmt_resource(&stmt_z TSRMLS_CC);
|
|
}
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_prepare: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
// sqlsrv_query( resource $conn, string $tsql [, array $params [, array $options]])
|
|
//
|
|
// Creates a statement resource associated with the specified connection. The statement
|
|
// is immediately executed and may not be executed again using sqlsrv_execute.
|
|
//
|
|
// Parameters
|
|
// $conn: The connection resource associated with the created statement.
|
|
//
|
|
// $tsql: The Transact-SQL expression that corresponds to the created statement.
|
|
//
|
|
// $params [OPTIONAL]: An array of values that correspond to parameters in a
|
|
// parameterized query. Each parameter may be specified as:
|
|
// $value | array($value [, $direction [, $phpType [, $sqlType]]])
|
|
// When given just a $value, the direction is default input, and phptype is the value
|
|
// given, with the sql type inferred from the php type.
|
|
//
|
|
// $options [OPTIONAL]: An associative array that sets query properties. The
|
|
// table below lists the supported keys and corresponding values:
|
|
// QueryTimeout
|
|
// Sets the query timeout in seconds. By default, the driver will wait
|
|
// indefinitely for results.
|
|
// SendStreamParamsAtExec
|
|
// Configures the driver to send all stream data at execution (true), or to
|
|
// send stream data in chunks (false). By default, the value is set to
|
|
// true. For more information, see sqlsrv_send_stream_data.
|
|
//
|
|
// Return Value
|
|
// A statement resource. If the statement resource cannot be created, false is returned.
|
|
|
|
PHP_FUNCTION( sqlsrv_query )
|
|
{
|
|
|
|
LOG_FUNCTION( "sqlsrv_query" );
|
|
|
|
ss_sqlsrv_conn* conn = NULL;
|
|
sqlsrv_malloc_auto_ptr<ss_sqlsrv_stmt> stmt;
|
|
char* sql = NULL;
|
|
hash_auto_ptr ss_stmt_options_ht;
|
|
size_t sql_len = 0;
|
|
zval* options_z = NULL;
|
|
zval* params_z = NULL;
|
|
zval stmt_z;
|
|
ZVAL_UNDEF(&stmt_z);
|
|
|
|
PROCESS_PARAMS( conn, "rs|a!a!", _FN_, 4, &sql, &sql_len, ¶ms_z, &options_z );
|
|
|
|
try {
|
|
|
|
// check for statement options
|
|
if( options_z && zend_hash_num_elements( Z_ARRVAL_P( options_z )) > 0 ) {
|
|
|
|
// Initialize the options array to be passed to the core layer
|
|
ALLOC_HASHTABLE( ss_stmt_options_ht );
|
|
core::sqlsrv_zend_hash_init( *conn , ss_stmt_options_ht, 5 /* # of buckets */, ZVAL_PTR_DTOR,
|
|
0 /*persistent*/ TSRMLS_CC );
|
|
|
|
validate_stmt_options( *conn, options_z, ss_stmt_options_ht TSRMLS_CC );
|
|
}
|
|
|
|
if( params_z && Z_TYPE_P( params_z ) != IS_ARRAY ) {
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
|
}
|
|
|
|
if( options_z && Z_TYPE_P( options_z ) != IS_ARRAY ) {
|
|
THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_INVALID_FUNCTION_PARAMETER, _FN_ );
|
|
}
|
|
|
|
if( sql == NULL ) {
|
|
|
|
DIE( "sqlsrv_query: sql string was null." );
|
|
}
|
|
|
|
stmt = static_cast<ss_sqlsrv_stmt*>( core_sqlsrv_create_stmt( conn, core::allocate_stmt<ss_sqlsrv_stmt>,
|
|
ss_stmt_options_ht, SS_STMT_OPTS,
|
|
ss_error_handler, NULL TSRMLS_CC ) );
|
|
|
|
if( params_z ) {
|
|
stmt->params_z = (zval *)sqlsrv_malloc(sizeof(zval));
|
|
ZVAL_COPY(stmt->params_z, params_z);
|
|
}
|
|
|
|
stmt->set_func( "sqlsrv_query" );
|
|
|
|
bind_params( stmt TSRMLS_CC );
|
|
|
|
// execute the statement
|
|
core_sqlsrv_execute( stmt TSRMLS_CC, sql, static_cast<int>( sql_len ) );
|
|
|
|
// register the statement with the PHP runtime
|
|
ss::zend_register_resource(stmt_z, stmt, ss_sqlsrv_stmt::descriptor, ss_sqlsrv_stmt::resource_name TSRMLS_CC);
|
|
// store the resource id with the connection so the connection
|
|
// can release this statement when it closes.
|
|
zend_ulong next_index = zend_hash_next_free_element( conn->stmts );
|
|
|
|
core::sqlsrv_zend_hash_index_update(*conn, conn->stmts, next_index, &stmt_z TSRMLS_CC);
|
|
stmt->conn_index = next_index;
|
|
stmt.transferred();
|
|
|
|
RETURN_RES(Z_RES(stmt_z));
|
|
}
|
|
|
|
catch( core::CoreException& ) {
|
|
|
|
if( stmt ) {
|
|
|
|
stmt->conn = NULL; // tell the statement that it isn't part of the connection so it doesn't try to remove itself
|
|
stmt->~ss_sqlsrv_stmt();
|
|
}
|
|
if (!Z_ISUNDEF(stmt_z)) {
|
|
free_stmt_resource(&stmt_z TSRMLS_CC);
|
|
}
|
|
|
|
RETURN_FALSE;
|
|
}
|
|
catch( ... ) {
|
|
|
|
DIE( "sqlsrv_query: Unknown exception caught." );
|
|
}
|
|
}
|
|
|
|
void free_stmt_resource( _Inout_ zval* stmt_z TSRMLS_DC )
|
|
{
|
|
if( FAILURE == zend_list_close( Z_RES_P( stmt_z ))) {
|
|
LOG(SEV_ERROR, "Failed to remove stmt resource %1!d!", Z_RES_HANDLE_P(stmt_z));
|
|
}
|
|
ZVAL_NULL( stmt_z );
|
|
zval_ptr_dtor(stmt_z);
|
|
}
|
|
|
|
// internal connection functions
|
|
|
|
namespace {
|
|
|
|
// must close all statement handles opened by this connection before closing the connection
|
|
// no errors are returned, since close should always succeed
|
|
|
|
void sqlsrv_conn_close_stmts( _Inout_ ss_sqlsrv_conn* conn TSRMLS_DC )
|
|
{
|
|
//pre-condition check
|
|
SQLSRV_ASSERT(( conn->handle() != NULL ), "sqlsrv_conn_close_stmts: Connection handle is NULL. Trying to destroy an "
|
|
"already destroyed connection.");
|
|
SQLSRV_ASSERT(( conn->stmts ), "sqlsrv_conn_close_stmts: Connection doesn't contain a statement array." );
|
|
|
|
// loop through the stmts hash table and destroy each stmt resource so we can close the
|
|
// ODBC connection
|
|
|
|
zval* rsrc_ptr = NULL;
|
|
ZEND_HASH_FOREACH_VAL( conn->stmts, rsrc_ptr ) {
|
|
try {
|
|
int zr = ( rsrc_ptr ) != NULL ? SUCCESS : FAILURE;
|
|
CHECK_ZEND_ERROR( zr, *conn, SQLSRV_ERROR_ZEND_HASH ) {
|
|
throw core::CoreException();
|
|
}
|
|
}
|
|
catch( core::CoreException& ) {
|
|
DIE( "sqlsrv_conn_close_stmts: Failed to retrieve a statement resource from the connection" );
|
|
}
|
|
// see if the statement is still valid, and if not skip to the next one
|
|
// presumably this should never happen because if it's in the list, it should still be valid
|
|
// by virtue that a statement resource should remove itself from its connection when it is
|
|
// destroyed in sqlsrv_stmt_dtor. However, rather than die (assert), we simply skip this resource
|
|
// and move to the next one.
|
|
ss_sqlsrv_stmt* stmt = NULL;
|
|
stmt = static_cast<ss_sqlsrv_stmt*>( Z_RES_VAL_P( rsrc_ptr ));
|
|
if( stmt == NULL || Z_RES_TYPE_P( rsrc_ptr ) != ss_sqlsrv_stmt::descriptor ) {
|
|
LOG( SEV_ERROR, "Non existent statement found in connection. Statements should remove themselves"
|
|
" from the connection so this shouldn't be out of sync." );
|
|
continue;
|
|
}
|
|
// delete the statement by deleting it from Zend's resource list, which will force its destruction
|
|
stmt->conn = NULL;
|
|
|
|
// this would call the destructor on the statement.
|
|
// There's nothing we can do if the removal fails, so we just log it and move on.
|
|
if( zend_list_close( Z_RES_P(rsrc_ptr) ) == FAILURE ) {
|
|
LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr));
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
|
|
zend_hash_destroy( conn->stmts );
|
|
FREE_HASHTABLE( conn->stmts );
|
|
conn->stmts = NULL;
|
|
}
|
|
|
|
int get_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len, _Inout_ zval const* value_z TSRMLS_DC )
|
|
{
|
|
for( int i=0; SS_CONN_OPTS[i].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++i )
|
|
{
|
|
if( key_len == SS_CONN_OPTS[i].sqlsrv_len && !stricmp( ZSTR_VAL( key ), SS_CONN_OPTS[i].sqlsrv_name )) {
|
|
|
|
|
|
switch( SS_CONN_OPTS[i].value_type ) {
|
|
|
|
case CONN_ATTR_BOOL:
|
|
// bool attributes can be either strings to be appended to the connection string
|
|
// as yes or no or integral connection attributes. This will have to be reworked
|
|
// if we ever introduce a boolean connection option that maps to a string connection
|
|
// attribute.
|
|
break;
|
|
case CONN_ATTR_INT:
|
|
{
|
|
CHECK_CUSTOM_ERROR( (Z_TYPE_P( value_z ) != IS_LONG ), ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_INT,
|
|
SS_CONN_OPTS[i].sqlsrv_name )
|
|
{
|
|
throw ss::SSException();
|
|
}
|
|
break;
|
|
}
|
|
case CONN_ATTR_STRING:
|
|
{
|
|
CHECK_CUSTOM_ERROR( Z_TYPE_P( value_z ) != IS_STRING, ctx, SQLSRV_ERROR_INVALID_OPTION_TYPE_STRING,
|
|
SS_CONN_OPTS[i].sqlsrv_name ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
char* value = Z_STRVAL_P( value_z );
|
|
size_t value_len = Z_STRLEN_P( value_z );
|
|
bool escaped = core_is_conn_opt_value_escaped( value, value_len );
|
|
|
|
CHECK_CUSTOM_ERROR( !escaped, ctx, SS_SQLSRV_ERROR_CONNECT_BRACES_NOT_ESCAPED, SS_CONN_OPTS[i].sqlsrv_name ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
bool valid = true;
|
|
if( stricmp( SS_CONN_OPTS[i].sqlsrv_name, SSConnOptionNames::Authentication ) == 0 ) {
|
|
valid = core_is_authentication_option_valid( value, value_len );
|
|
}
|
|
|
|
CHECK_CUSTOM_ERROR( !valid, ctx, SS_SQLSRV_ERROR_INVALID_AUTHENTICATION_OPTION, SS_CONN_OPTS[i].sqlsrv_name ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return SS_CONN_OPTS[i].conn_option_key;
|
|
}
|
|
}
|
|
return SQLSRV_CONN_OPTION_INVALID;
|
|
}
|
|
|
|
int get_stmt_option_key( _In_ zend_string* key, _In_ size_t key_len TSRMLS_DC )
|
|
{
|
|
for( int i = 0; SS_STMT_OPTS[i].key != SQLSRV_STMT_OPTION_INVALID; ++i )
|
|
{
|
|
if( key_len == SS_STMT_OPTS[i].name_len && !stricmp( ZSTR_VAL( key ), SS_STMT_OPTS[i].name )) {
|
|
return SS_STMT_OPTS[i].key;
|
|
}
|
|
}
|
|
return SQLSRV_STMT_OPTION_INVALID;
|
|
}
|
|
|
|
void add_stmt_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
|
|
_Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC )
|
|
{
|
|
int option_key = ::get_stmt_option_key( key, key_len TSRMLS_CC );
|
|
|
|
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, ZSTR_VAL( key ) ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
|
|
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC );
|
|
}
|
|
|
|
void add_conn_option_key( _Inout_ sqlsrv_context& ctx, _In_ zend_string* key, _In_ size_t key_len,
|
|
_Inout_ HashTable* options_ht, _Inout_ zval* data TSRMLS_DC )
|
|
{
|
|
int option_key = ::get_conn_option_key( ctx, key, key_len, data TSRMLS_CC );
|
|
CHECK_CUSTOM_ERROR((option_key == SQLSRV_STMT_OPTION_INVALID ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, ZSTR_VAL( key ) ) {
|
|
|
|
throw ss::SSException();
|
|
}
|
|
|
|
Z_TRY_ADDREF_P(data); // inc the ref count since this is going into the options_ht too.
|
|
core::sqlsrv_zend_hash_index_update( ctx, options_ht, option_key, data TSRMLS_CC );
|
|
}
|
|
|
|
// Iterates through the list of statement options provided by the user and validates them
|
|
// against the list of supported statement options by this driver. After validation
|
|
// creates a Hashtable of statement options to be sent to the core layer for processing.
|
|
|
|
void validate_stmt_options( _Inout_ sqlsrv_context& ctx, _Inout_ zval* stmt_options, _Inout_ HashTable* ss_stmt_options_ht TSRMLS_DC )
|
|
{
|
|
try {
|
|
if( stmt_options ) {
|
|
|
|
HashTable* options_ht = Z_ARRVAL_P( stmt_options );
|
|
zend_ulong int_key = -1;
|
|
zend_string *key = NULL;
|
|
zval* data = NULL;
|
|
ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) {
|
|
int type = HASH_KEY_NON_EXISTENT;
|
|
size_t key_len = 0;
|
|
|
|
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
|
|
|
if (type != HASH_KEY_IS_STRING) {
|
|
CHECK_CUSTOM_ERROR(true, ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, std::to_string( int_key ).c_str() ) {
|
|
throw core::CoreException();
|
|
}
|
|
}
|
|
else if ( key != NULL ) {
|
|
key_len = ZSTR_LEN( key ) + 1;
|
|
add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC );
|
|
}
|
|
else {
|
|
DIE( "validate_stmt_options: key was null." );
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
}
|
|
catch( core::CoreException& ) {
|
|
|
|
throw;
|
|
}
|
|
}
|
|
|
|
// Iterates through the list of connection options provided by the user and validates them
|
|
// against the predefined list of supported connection options by this driver. After validation
|
|
// creates a Hashtable of connection options to be sent to the core layer for processing.
|
|
|
|
void validate_conn_options( _Inout_ sqlsrv_context& ctx, _In_ zval* user_options_z, _Inout_ char** uid, _Inout_ char** pwd, _Inout_ HashTable* ss_conn_options_ht TSRMLS_DC )
|
|
{
|
|
try {
|
|
|
|
if( user_options_z ) {
|
|
|
|
HashTable* options_ht = Z_ARRVAL_P( user_options_z );
|
|
zend_ulong int_key = -1;
|
|
zend_string *key = NULL;
|
|
zval* data = NULL;
|
|
ZEND_HASH_FOREACH_KEY_VAL( options_ht, int_key, key, data ) {
|
|
int type = HASH_KEY_NON_EXISTENT;
|
|
type = key ? HASH_KEY_IS_STRING : HASH_KEY_IS_LONG;
|
|
|
|
CHECK_CUSTOM_ERROR(( Z_TYPE_P( data ) == IS_NULL || Z_TYPE_P( data ) == IS_UNDEF ), ctx, SS_SQLSRV_ERROR_INVALID_OPTION, key) {
|
|
throw ss::SSException();
|
|
}
|
|
|
|
CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) {
|
|
throw ss::SSException();
|
|
}
|
|
if ( key != NULL ) {
|
|
// Length of the key string does not include the null terminator in PHP7, +1 has to be added
|
|
size_t key_len = ZSTR_LEN( key ) + 1;
|
|
if ( key_len == sizeof( SSConnOptionNames::UID ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::UID )) {
|
|
|
|
*uid = Z_STRVAL_P( data );
|
|
}
|
|
|
|
else if ( key_len == sizeof( SSConnOptionNames::PWD ) && !stricmp( ZSTR_VAL( key ), SSConnOptionNames::PWD )) {
|
|
|
|
*pwd = Z_STRVAL_P( data );
|
|
}
|
|
else {
|
|
|
|
::add_conn_option_key( ctx, key, key_len, ss_conn_options_ht, data TSRMLS_CC );
|
|
}
|
|
}
|
|
else {
|
|
DIE( "validate_conn_options: key was null." );
|
|
}
|
|
} ZEND_HASH_FOREACH_END();
|
|
}
|
|
}
|
|
catch( core::CoreException& ) {
|
|
throw;
|
|
}
|
|
}
|
|
|
|
} // namespace
|