diff --git a/LICENSE b/LICENSE
new file mode 100644
index 00000000..a39c89d3
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,14 @@
+Copyright(c) 2015 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.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 00000000..be64993c
--- /dev/null
+++ b/README.md
@@ -0,0 +1,116 @@
+# Microsoft Drivers for PHP for SQL Server
+
+**Welcome to the Microsoft Drivers for PHP for SQL Server PHP 7 (Early Technical Preview)**
+
+The Microsoft Drivers for PHP for SQL Server are PHP extensions that allow for the reading and writing of SQL Server data from within PHP scripts. The SQLSRV extension provides a procedural interface while the PDO_SQLSRV extension implements PDO for accessing data in all editions of SQL Server 2005 and later (including Azure SQL DB). These drivers rely on the Microsoft ODBC Driver for SQL Server to handle the low-level communication with SQL Server.
+
+This preview contains the SQLSRV driver for 32-bit PHP 7 with limitations (see Limitations below for details). Upcoming release(s) will contain more functionality, bug fixes, the PDO_SQLSRV driver, 64-bit support and more (see Plans below for more details).
+
+The Microsoft Drivers for PHP for SQL Server Team
+
+## Build
+
+Note: if you prefer, you can use the pre-compiled binary found [HERE]
+
+####Prerequisites
+
+You must first be able to build PHP 7 without including these extensions. For help with doing this, see the [official PHP website][phpbuild] for building your own PHP on Windows.
+
+####Compile the SQLSRV driver
+
+1. Copy the sqlsrv source code directory from this repository into the ext subdirectory.
+
+2. Run `buildconf.bat` to rebuild the configure.js script to include the driver.
+
+3. Run `configure.bat --enable-sqlsrv=shared --with-odbcver=0x0380 [other options such as --disable-zts for the Non Thread Safe build]` to generate the makefile. You can run `configure.bat --help` to see what other options are available.
+
+4. Run `nmake`. It is suggested that you run the entire build. If you wish to do so, run `nmake clean` first.
+
+5. To install the resulting build, run `nmake install` or just copy php_sqlsrv.dll to your PHP extension directory.
+
+This software has been compiled and tested under PHP 7.0.2 using the Visual C++ 2015 compiler.
+
+## Install
+
+####Prerequisites
+
+- A Web server such as Internet Information Services (IIS) is required. Your Web server must be configured to run PHP
+- [Microsoft ODBC Driver 11][odbc]
+
+####Enable the SQLSRV driver
+
+1. Make sure that the driver is in your PHP extension directory (you can simply copy it there if you did not use nmake install).
+
+2. Enable it within your PHP installation's php.ini: `extension=php_sqlsrv.dll`. If necessary, specify the extension directory using extension_dir, for example: `extension_dir = "C:\PHP\ext"`
+
+3. Restart the Web server.
+
+## Sample Code
+For samples, please see the sample folder. For setup instructions, see [here] [phpazure]
+
+## Changes and Limitations
+
+This preview contains the 32-bit port for PHP 7 of the SQLSRV driver. The focus was on basic functionality. The following items are not supported:
+
+- PDO
+- Native 64 Bit
+- Backwards compatibility with PHP 5
+- Retrieving stream data and metadata
+- Retrieving some varchar, nvarchar, ntext, binary, varbinary, uniqueidentifier, datetime, smalldatetime, and timestamp fields
+- Handle UTF8 strings
+- Bind parameters with datetime types, streams, and arrays within arrays without references
+- Retrieve SQLTYPE_VARCHAR as an output parameter
+- Fetch a user defined object into a class
+
+And some aspects of the following items need improvement:
+- Memory management
+- Logging and error handling
+
+
+## Future Plans
+
+- PDO Support
+- Linux Version
+- Expand SQL 16 Feature Support (example: Always Encrypted)
+- Build Verification/Fundamental Tests
+- Bug Fixes
+
+##FAQs
+**Q:** Can we get dates for any of the Future Plans listed above?
+**A:** At this time, Microsoft is not able to announce dates. We are working extremely hard to release future versions of the driver. We will share future plans once they solidify over the next few weeks.
+
+**Q:** What's next?
+**A:** On Jan 29, 2016, we have released the early technical preview for our PHP Driver. Our next step will be a Community Tech Preview with completed functionally, PDO support, and more.
+
+**Q:** Is Microsoft taking pull requests for this project?
+**A:** We will not be seeking to take pull requests until GA, Build Verification, and Fundamental tests are released. At this point Microsoft will also begin actively developing using this GitHub project as the prime repository.
+
+
+
+## License
+
+The Microsoft Drivers for PHP for SQL Server are licensed under the MIT license. See the LICENSE file for more details.
+
+## Resources
+
+**Documentation**: [MSDN Online Documentation][phpdoc]. Please note that this documentation is not yet updated for PHP 7.
+**Team Blog**: Browse our blog for comments and announcements from the team in the [team blog][blog].
+**Known Issues**: Please visit the [project on Github][project] to view outstanding [issues][issues].
+
+[blog]: http://blogs.msdn.com/b/sqlphp/
+
+[project]: https://github.com/Azure/msphpsql
+
+[issues]: https://github.com/Azure/msphpsql/issues
+
+[phpweb]: http://php.net
+
+[phpbuild]: https://wiki.php.net/internals/windows/stepbystepbuild
+
+[phpdoc]: http://msdn.microsoft.com/en-us/library/dd903047%28SQL.11%29.aspx
+
+[odbc]: https://www.microsoft.com/en-us/download/details.aspx?id=36434
+
+[phpazure]: https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-php-simple-windows/
+
+
diff --git a/sample/sqlsrv_sample.php b/sample/sqlsrv_sample.php
new file mode 100644
index 00000000..c9e262a7
--- /dev/null
+++ b/sample/sqlsrv_sample.php
@@ -0,0 +1,69 @@
+"yourpassword", "Uid"=>"yourusername", "PWD"=>"yourpassword");
+
+ //Establishes the connection
+ $conn = sqlsrv_connect($serverName, $connectionOptions);
+ //Select Query
+ $tsql = "SELECT [CompanyName] FROM SalesLT.Customer";
+ //Executes the query
+ $getProducts = sqlsrv_query($conn, $tsql);
+ //Error handling
+ if ($getProducts == FALSE)
+ die(FormatErrors(sqlsrv_errors()));
+ $productCount = 0;
+ $ctr = 0;
+ ?>
+
First 10 results are :
+ 9)
+ break;
+ $ctr++;
+ echo($row['CompanyName']);
+ echo("
");
+ $productCount++;
+ }
+ sqlsrv_free_stmt($getProducts);
+
+ $tsql = "INSERT SalesLT.Product (Name, ProductNumber, StandardCost, ListPrice, SellStartDate) OUTPUT INSERTED.ProductID VALUES ('SQL New 1', 'SQL New 2', 0, 0, getdate())";
+ //Insert query
+ $insertReview = sqlsrv_query($conn, $tsql);
+ if($insertReview == FALSE)
+ die(FormatErrors( sqlsrv_errors()));
+ ?>
+ Product Key inserted is :
+ ";
+
+ foreach ( $errors as $error )
+ {
+ echo "SQLSTATE: ".$error['SQLSTATE']."
";
+ echo "Code: ".$error['code']."
";
+ echo "Message: ".$error['message']."
";
+ }
+ }
+
+?>
\ No newline at end of file
diff --git a/sqlsrv/CREDITS b/sqlsrv/CREDITS
new file mode 100644
index 00000000..7bb31c89
--- /dev/null
+++ b/sqlsrv/CREDITS
@@ -0,0 +1 @@
+Microsoft Drivers 4.0.0 for PHP for SQL Server (SQLSRV driver)
diff --git a/sqlsrv/config.w32 b/sqlsrv/config.w32
new file mode 100644
index 00000000..b9f41f07
--- /dev/null
+++ b/sqlsrv/config.w32
@@ -0,0 +1,39 @@
+//----------------------------------------------------------------------------------------------------------------------------------
+// File: config.w32
+//
+// Contents: JScript build configuration used by buildconf.bat
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+ARG_ENABLE("sqlsrv", "enable Microsoft Drivers for PHP for SQL Server (SQLSRV driver)", "no");
+
+if( PHP_SQLSRV != "no" ) {
+
+ sqlsrv_src = "conn.cpp init.cpp stmt.cpp util.cpp core_init.cpp core_conn.cpp core_stmt.cpp core_util.cpp core_stream.cpp core_results.cpp";
+
+ if (CHECK_LIB("odbc32.lib", "sqlsrv") && CHECK_LIB("odbccp32.lib", "sqlsrv") &&
+ CHECK_LIB("version.lib", "sqlsrv") && CHECK_LIB("psapi.lib", "sqlsrv")) {
+
+ EXTENSION("sqlsrv", sqlsrv_src, PHP_SQLSRV_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1")
+
+ CHECK_HEADER_ADD_INCLUDE('sql.h', 'CFLAGS_SQLSRV_ODBC');
+ CHECK_HEADER_ADD_INCLUDE('sqlext.h', 'CFLAGS_SQLSRV_ODBC');
+ ADD_FLAG( 'LDFLAGS_SQLSRV', '/NXCOMPAT /DYNAMICBASE /debug' );
+ ADD_FLAG( 'CFLAGS_SQLSRV', '/D ZEND_WIN32_FORCE_INLINE' );
+ ADD_FLAG( 'CFLAGS_SQLSRV', '/EHsc' );
+ ADD_FLAG( 'CFLAGS_SQLSRV', '/GS' );
+ ADD_FLAG( 'CFLAGS_SQLSRV', '/Zi' );
+ }
+}
diff --git a/sqlsrv/conn.cpp b/sqlsrv/conn.cpp
new file mode 100644
index 00000000..c9926ad9
--- /dev/null
+++ b/sqlsrv/conn.cpp
@@ -0,0 +1,1319 @@
+//---------------------------------------------------------------------------------------------------------------------------------
+// File: conn.cpp
+//
+// Contents: Routines that use connection handles
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+#include "php_sqlsrv.h"
+#include
+#include
+#include
+
+#include
+#include
+
+// *** 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*/, zval* value, 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( conn );
+
+ if( zend_is_true( value )) {
+ ss_conn->date_as_string = true;
+ }
+ else {
+ ss_conn->date_as_string = false;
+ }
+ }
+};
+
+struct conn_char_set_func {
+
+ static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
+ {
+ convert_to_string( value );
+ const char* encoding = Z_STRVAL_P( value );
+ unsigned int encoding_len = Z_STRLEN_P( value );
+
+ for( zend_hash_internal_pointer_reset( g_ss_encodings_ht );
+ zend_hash_has_more_elements( g_ss_encodings_ht ) == SUCCESS;
+ zend_hash_move_forward( g_ss_encodings_ht )) {
+
+ sqlsrv_encoding* ss_encoding = NULL;
+ core::sqlsrv_zend_hash_get_current_data_ptr( *conn, g_ss_encodings_ht,(void*&) ss_encoding TSRMLS_CC );
+
+ 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( ss_encoding->code_page ));
+ return;
+ }
+ }
+
+ THROW_SS_ERROR( conn, SS_SQLSRV_ERROR_CONNECT_ILLEGAL_ENCODING, encoding );
+ }
+};
+
+struct bool_conn_str_func {
+
+ static void func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, 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 += "};";
+ }
+};
+
+template
+struct int_conn_attr_func {
+
+ static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
+ {
+ try {
+
+ core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( Z_LVAL_P( value )),
+ SQL_IS_UINTEGER TSRMLS_CC );
+ }
+ catch( core::CoreException& ) {
+ throw;
+ }
+ }
+};
+
+template
+struct bool_conn_attr_func {
+
+ static void func( connection_option const* /*option*/, zval* value, sqlsrv_conn* conn, std::string& /*conn_str*/ TSRMLS_DC )
+ {
+ try {
+
+ core::SQLSetConnectAttr( conn, Attr, reinterpret_cast( zend_is_true( value )),
+ SQL_IS_UINTEGER TSRMLS_CC );
+ }
+ catch( core::CoreException& ) {
+ throw;
+ }
+
+ }
+};
+
+//// *** internal functions ***
+
+void sqlsrv_conn_close_stmts( ss_sqlsrv_conn* conn TSRMLS_DC );
+void validate_conn_options( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd,
+ __inout HashTable* ss_conn_options_ht TSRMLS_DC );
+void validate_stmt_options( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC );
+void add_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len,
+ HashTable* options_ht, zval* data TSRMLS_DC );
+void add_stmt_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, HashTable* options_ht, zval* data TSRMLS_DC );
+int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, zval const* value_z TSRMLS_DC );
+int get_stmt_option_key( zend_string* key, size_t key_len TSRMLS_DC );
+
+}
+
+// constants for parameters used by process_params function(s)
+int ss_sqlsrv_conn::descriptor;
+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( 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;
+}
+
+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 ApplicationIntent[] = "ApplicationIntent";
+const char AttachDBFileName[] = "AttachDbFileName";
+const char CharacterSet[] = "CharacterSet";
+const char ConnectionPooling[] = "ConnectionPooling";
+const char Database[] = "Database";
+const char DateAsString[] = "ReturnDatesAsStrings";
+const char Encrypt[] = "Encrypt";
+const char Failover_Partner[] = "Failover_Partner";
+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 UID[] = "UID";
+const char WSID[] = "WSID";
+
+}
+
+enum SS_CONN_OPTIONS {
+
+ SS_CONN_OPTION_DATE_AS_STRING = SQLSRV_CONN_OPTION_DRIVER_SPECIFIC,
+};
+
+//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,
+ new stmt_option_query_timeout
+ },
+ {
+ SSStmtOptionNames::SEND_STREAMS_AT_EXEC,
+ sizeof( SSStmtOptionNames::SEND_STREAMS_AT_EXEC ),
+ SQLSRV_STMT_OPTION_SEND_STREAMS_AT_EXEC,
+ new stmt_option_send_at_exec
+ },
+ {
+ SSStmtOptionNames::SCROLLABLE,
+ sizeof( SSStmtOptionNames::SCROLLABLE ),
+ SQLSRV_STMT_OPTION_SCROLLABLE,
+ new stmt_option_scrollable
+ },
+ {
+ SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE,
+ sizeof( SSStmtOptionNames::CLIENT_BUFFER_MAX_SIZE ),
+ SQLSRV_STMT_OPTION_CLIENT_BUFFER_MAX_SIZE,
+ new stmt_option_buffered_query_limit
+ },
+ { NULL, 0, SQLSRV_STMT_OPTION_INVALID, NULL },
+};
+
+
+// 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::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::ConnectionPooling,
+ sizeof( SSConnOptionNames::ConnectionPooling ),
+ SQLSRV_CONN_OPTION_CONN_POOLING,
+ ODBCConnOptions::ConnectionPooling,
+ sizeof( ODBCConnOptions::ConnectionPooling ),
+ CONN_ATTR_BOOL,
+ conn_null_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::LoginTimeout,
+ sizeof( SSConnOptionNames::LoginTimeout ),
+ SQLSRV_CONN_OPTION_LOGIN_TIMEOUT,
+ ODBCConnOptions::LoginTimeout,
+ sizeof( ODBCConnOptions::LoginTimeout ),
+ CONN_ATTR_INT,
+ int_conn_attr_func::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::func
+ },
+ {
+ SSConnOptionNames::TraceOn,
+ sizeof( SSConnOptionNames::TraceOn ),
+ SQLSRV_CONN_OPTION_TRACE_ON,
+ ODBCConnOptions::TraceOn,
+ sizeof( ODBCConnOptions::TraceOn ),
+ CONN_ATTR_BOOL,
+ bool_conn_attr_func::func
+ },
+ {
+ SSConnOptionNames::TransactionIsolation,
+ sizeof( SSConnOptionNames::TransactionIsolation ),
+ SQLSRV_CONN_OPTION_TRANS_ISOLATION,
+ ODBCConnOptions::TransactionIsolation,
+ sizeof( ODBCConnOptions::TransactionIsolation ),
+ CONN_ATTR_INT,
+ int_conn_attr_func::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::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
+ },
+ { 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_henv_cp );
+ SET_FUNCTION_NAME( *g_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_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_henv_cp, ss_conn_options_ht, 10 /* # of buckets */,
+ ZVAL_PTR_DTOR, 0 /*persistent*/ TSRMLS_CC );
+
+ // Either of g_henv_cp or g_henv_ncp can be used to propogate the error.
+ ::validate_conn_options( *g_henv_cp, options_z, &uid, &pwd, ss_conn_options_ht TSRMLS_CC );
+
+ // call the core connect function
+ conn = static_cast( core_sqlsrv_connect( *g_henv_cp, *g_henv_ncp, &core::allocate_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_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 than 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_ );
+ }
+ }
+
+ conn = static_cast( zend_fetch_resource_ex( conn_r TSRMLS_CC, NULL, ss_sqlsrv_conn::descriptor ));
+
+ 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.
+
+ int zr = zend_list_close(Z_RES_P(conn_r));
+
+ if( zr == FAILURE ) {
+
+ LOG( SEV_ERROR, "Failed to remove connection resource %1!d!", Z_RES_HANDLE_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( zend_resource *rsrc TSRMLS_DC )
+{
+ LOG_FUNCTION( "sqlsrv_conn_dtor" );
+
+ // get the structure
+ ss_sqlsrv_conn *conn = static_cast( 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
+ core::sqlsrv_add_assoc_string( *conn, return_value, "ExtensionVer", VER_FILEVERSION_STR, 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 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, 3 /* # 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( core_sqlsrv_create_stmt( conn, core::allocate_stmt,
+ ss_stmt_options_ht, SS_STMT_OPTS,
+ ss_error_handler, NULL TSRMLS_CC ) );
+
+ core_sqlsrv_prepare( stmt, sql, sql_len TSRMLS_CC );
+
+ //mark_params_by_reference( stmt, params_z TSRMLS_CC );
+ stmt->params_z = params_z;
+ if (params_z) {
+ zval_add_ref(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.
+ int 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 stmt;
+ char* sql = NULL;
+ hash_auto_ptr ss_stmt_options_ht;
+ zend_long 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, 3 /* # 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( core_sqlsrv_create_stmt( conn, core::allocate_stmt,
+ ss_stmt_options_ht, SS_STMT_OPTS,
+ ss_error_handler, NULL TSRMLS_CC ) );
+
+ stmt->params_z = params_z;
+ if( params_z ) {
+ zval_add_ref( params_z );
+ }
+
+ stmt->set_func( "sqlsrv_query" );
+
+ bind_params( stmt TSRMLS_CC );
+
+ // execute the statement
+ core_sqlsrv_execute( stmt TSRMLS_CC, sql, 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( zval* stmt_z TSRMLS_DC )
+{
+ int zr = zend_list_close(Z_RES_P(stmt_z));
+ if( zr == FAILURE ) {
+ 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( 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
+ for( zend_hash_internal_pointer_reset( conn->stmts );
+ zend_hash_has_more_elements( conn->stmts ) == SUCCESS;
+ zend_hash_move_forward( conn->stmts )) {
+
+ zval* rsrc_ptr = NULL;
+
+ try {
+ // get the resource id for the next statement created with this connection
+ core::sqlsrv_zend_hash_get_current_data( *conn, conn->stmts, rsrc_ptr TSRMLS_CC );
+ }
+ 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( 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;
+
+ try {
+ // this would call the destructor on the statement.
+ int zr = zend_list_close(Z_RES_P(rsrc_ptr));
+ }
+ catch( core::CoreException& ) {
+ LOG(SEV_ERROR, "Failed to remove statement resource %1!d! when closing the connection", Z_RES_HANDLE_P(rsrc_ptr));
+ }
+ }
+
+ zend_hash_destroy( conn->stmts );
+ FREE_HASHTABLE( conn->stmts );
+ conn->stmts = NULL;
+}
+
+int get_conn_option_key( sqlsrv_context& ctx, zend_string* key, size_t key_len, 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 );
+ int 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();
+ }
+ break;
+ }
+ }
+
+ return SS_CONN_OPTS[ i ].conn_option_key;
+ }
+ }
+ return SQLSRV_CONN_OPTION_INVALID;
+}
+
+int get_stmt_option_key( zend_string* key, 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( sqlsrv_context& ctx, zend_string* key, size_t key_len,
+ HashTable* options_ht, 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, key ) {
+
+ throw ss::SSException();
+ }
+
+ zval_add_ref( 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( sqlsrv_context& ctx, zend_string* key, size_t key_len,
+ HashTable* options_ht, 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, key ) {
+
+ throw ss::SSException();
+ }
+
+ zval_add_ref( 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( sqlsrv_context& ctx, zval* stmt_options, __inout HashTable* ss_stmt_options_ht TSRMLS_DC )
+{
+ try {
+
+ if( stmt_options ) {
+
+ HashTable* options_ht = Z_ARRVAL_P( stmt_options );
+
+ for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS;
+ zend_hash_move_forward( options_ht )) {
+
+ int type = HASH_KEY_NON_EXISTENT;
+ zend_string *key = NULL;
+ size_t key_len = 0;
+ zend_ulong int_key = -1;
+ zval* data = NULL;
+ zval* conn_opt = NULL;
+ int result = 0;
+
+ type = zend_hash_get_current_key(options_ht, &key, &int_key);
+ key_len = ZSTR_LEN(key) + 1;
+
+ if( type != HASH_KEY_IS_STRING ) {
+ std::ostringstream itoa;
+ itoa << int_key;
+ CHECK_CUSTOM_ERROR( true , ctx, SQLSRV_ERROR_INVALID_OPTION_KEY, itoa.str() ) {
+ throw core::CoreException();
+ }
+ }
+ core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, data TSRMLS_CC );
+ add_stmt_option_key( ctx, key, key_len, ss_stmt_options_ht, data TSRMLS_CC );
+ }
+ }
+ }
+ 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( sqlsrv_context& ctx, zval* user_options_z, __out char** uid, __out char** pwd, __inout HashTable* ss_conn_options_ht TSRMLS_DC )
+{
+ try {
+
+ if( user_options_z ) {
+
+ HashTable* options_ht = Z_ARRVAL_P( user_options_z );
+
+ for( zend_hash_internal_pointer_reset( options_ht ); zend_hash_has_more_elements( options_ht ) == SUCCESS;
+ zend_hash_move_forward( options_ht )) {
+
+ int type = HASH_KEY_NON_EXISTENT;
+ zend_string *key = NULL;
+ zend_ulong int_key = -1;
+ zval* data = NULL;
+
+ type = zend_hash_get_current_key(options_ht, &key, &int_key);
+
+ CHECK_CUSTOM_ERROR(( type != HASH_KEY_IS_STRING ), ctx, SS_SQLSRV_ERROR_INVALID_CONNECTION_KEY ) {
+ throw ss::SSException();
+ }
+
+ core::sqlsrv_zend_hash_get_current_data( ctx, options_ht, data TSRMLS_CC );
+
+ // 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 );
+ }
+ }
+ }
+ }
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+} // namespace
diff --git a/sqlsrv/core_conn.cpp b/sqlsrv/core_conn.cpp
new file mode 100644
index 00000000..7df522f1
--- /dev/null
+++ b/sqlsrv/core_conn.cpp
@@ -0,0 +1,764 @@
+//---------------------------------------------------------------------------------------------------------------------------------
+// File: core_conn.cpp
+//
+// Contents: Core routines that use connection handles shared between sqlsrv and pdo_sqlsrv
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+#include "core_sqlsrv.h"
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+// *** internal variables and constants ***
+
+namespace {
+
+// *** internal constants ***
+// an arbitrary figure that should be large enough for most connection strings.
+const int DEFAULT_CONN_STR_LEN = 2048;
+
+// length of buffer used to retrieve information for client and server info buffers
+const int INFO_BUFFER_LEN = 256;
+
+// processor architectures
+const char* PROCESSOR_ARCH[] = { "x86", "x64", "ia64" };
+
+// ODBC driver name.
+const char CONNECTION_STRING_DRIVER_NAME[] = "Driver={ODBC Driver 11 for SQL Server};";
+
+// default options if only the server is specified
+const char CONNECTION_STRING_DEFAULT_OPTIONS[] = "Mars_Connection={Yes}";
+
+// connection option appended when no user name or password is given
+const char CONNECTION_OPTION_NO_CREDENTIALS[] = "Trusted_Connection={Yes};";
+
+// connection option appended for MARS when MARS isn't explicitly mentioned
+const char CONNECTION_OPTION_MARS_ON[] = "MARS_Connection={Yes};";
+
+// *** internal function prototypes ***
+
+void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
+ HashTable* options_ht, const connection_option valid_conn_opts[],
+ void* driver,__inout std::string& connection_string TSRMLS_DC );
+void determine_server_version( sqlsrv_conn* conn TSRMLS_DC );
+const char* get_processor_arch( void );
+void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC );
+connection_option const* get_connection_option( sqlsrv_conn* conn, const char* key, unsigned int key_len TSRMLS_DC );
+void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC );
+
+}
+
+// core_sqlsrv_connect
+// opens a connection and returns a sqlsrv_conn structure.
+// Parameters:
+// henv_cp - connection pooled env context
+// henv_ncp - non connection pooled env context
+// server - name of the server we're connecting to
+// uid - username
+// pwd - password
+// options_ht - zend_hash list of options
+// err - error callback to put into the connection's context
+// valid_conn_opts[] - array of valid driver supported connection options.
+// driver - reference to caller
+// Return
+// A sqlsrv_conn structure. An exception is thrown if an error occurs
+
+sqlsrv_conn* core_sqlsrv_connect( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp, driver_conn_factory conn_factory,
+ const char* server, const char* uid, const char* pwd,
+ HashTable* options_ht, error_callback err, const connection_option valid_conn_opts[],
+ void* driver, const char* driver_func TSRMLS_DC )
+
+{
+ SQLRETURN r;
+ std::string conn_str;
+ conn_str.reserve( DEFAULT_CONN_STR_LEN );
+ sqlsrv_malloc_auto_ptr conn;
+ sqlsrv_malloc_auto_ptr wconn_string;
+ unsigned int wconn_len = 0;
+
+ try {
+
+ sqlsrv_context* henv = &henv_cp; // by default use the connection pooling henv
+
+ // check the connection pooling setting to determine which henv to use to allocate the connection handle
+ // we do this earlier because we have to allocate the connection handle prior to setting attributes on
+ // it in build_connection_string_and_set_conn_attr.
+
+ if( options_ht && zend_hash_num_elements( options_ht ) > 0 ) {
+
+ zval* option_z = NULL;
+ int zr = SUCCESS;
+
+ option_z = zend_hash_index_find(options_ht, SQLSRV_CONN_OPTION_CONN_POOLING);
+ if (option_z) {
+
+ // if the option was found and it's not true, then use the non pooled environment handle
+ if(( Z_TYPE_P( option_z ) == IS_STRING && !core_str_zval_is_true( option_z )) || !zend_is_true( option_z ) ) {
+
+ henv = &henv_ncp;
+ }
+ }
+ }
+
+ SQLHANDLE temp_conn_h;
+ core::SQLAllocHandle( SQL_HANDLE_DBC, *henv, &temp_conn_h TSRMLS_CC );
+
+ conn = conn_factory( temp_conn_h, err, driver TSRMLS_CC );
+ conn->set_func( driver_func );
+
+
+ build_connection_string_and_set_conn_attr( conn, server, uid, pwd, options_ht, valid_conn_opts, driver,
+ conn_str TSRMLS_CC );
+
+ // We only support UTF-8 encoding for connection string.
+ // Convert our UTF-8 connection string to UTF-16 before connecting with SQLDriverConnnectW
+ wconn_len = (conn_str.length() + 1) * sizeof( wchar_t );
+ wconn_string = utf16_string_from_mbcs_string( SQLSRV_ENCODING_UTF8, conn_str.c_str(), conn_str.length(), &wconn_len );
+ CHECK_CUSTOM_ERROR( wconn_string == NULL, conn, SQLSRV_ERROR_CONNECT_STRING_ENCODING_TRANSLATE, get_last_error_message() )
+ {
+ throw core::CoreException();
+ }
+
+ SQLSMALLINT output_conn_size;
+ r = SQLDriverConnectW( conn->handle(), NULL, reinterpret_cast( wconn_string.get() ),
+ static_cast( wconn_len ), NULL,
+ 0, &output_conn_size, SQL_DRIVER_NOPROMPT );
+ // clear the connection string from memory to remove sensitive data (such as a password).
+ memset( const_cast( conn_str.c_str()), 0, conn_str.size() );
+ memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
+ conn_str.clear();
+
+ if( !SQL_SUCCEEDED( r )) {
+ SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
+ SQLSMALLINT len;
+ SQLRETURN r = SQLGetDiagField( SQL_HANDLE_DBC, conn->handle(), 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len );
+ // if it's a IM002, meaning that the correct ODBC driver is not installed
+ CHECK_CUSTOM_ERROR( SQL_SUCCEEDED( r ) && state[0] == 'I' && state[1] == 'M' && state[2] == '0' && state[3] == '0' &&
+ state[4] == '2', conn, SQLSRV_ERROR_DRIVER_NOT_INSTALLED, get_processor_arch() ) {
+ throw core::CoreException();
+ }
+ }
+ CHECK_SQL_ERROR( r, conn ) {
+ throw core::CoreException();
+ }
+
+ CHECK_SQL_WARNING_AS_ERROR( r, conn ) {
+ throw core::CoreException();
+ }
+
+ // determine the version of the server we're connected to. The server version is left in the
+ // connection upon return.
+ determine_server_version( conn TSRMLS_CC );
+
+ }
+ catch( std::bad_alloc& ) {
+ memset( const_cast( conn_str.c_str()), 0, conn_str.size() );
+ memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
+ conn->invalidate();
+ DIE( "C++ memory allocation failure building the connection string." );
+ }
+ catch( std::out_of_range const& ex ) {
+ memset( const_cast( conn_str.c_str()), 0, conn_str.size() );
+ memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
+ LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
+ conn->invalidate();
+ throw;
+ }
+ catch( std::length_error const& ex ) {
+ memset( const_cast( conn_str.c_str()), 0, conn_str.size() );
+ memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
+ LOG( SEV_ERROR, "C++ exception returned: %1!s!", ex.what() );
+ conn->invalidate();
+ throw;
+ }
+ catch( core::CoreException& ) {
+ memset( const_cast( conn_str.c_str()), 0, conn_str.size() );
+ memset( wconn_string, 0, wconn_len * sizeof( wchar_t )); // wconn_len is the number of characters, not bytes
+ conn->invalidate();
+ throw;
+ }
+
+ sqlsrv_conn* return_conn = conn;
+ conn.transferred();
+
+ return return_conn;
+}
+
+
+
+// core_sqlsrv_begin_transaction
+// Begins a transaction on a specified connection. The current transaction
+// includes all statements on the specified connection that were executed after
+// the call to core_sqlsrv_begin_transaction and before any calls to
+// core_sqlsrv_rollback or core_sqlsrv_commit.
+// The default transaction mode is auto-commit. This means that all queries
+// are automatically committed upon success unless they have been designated
+// as part of an explicit transaction by using core_sqlsrv_begin_transaction.
+// Parameters:
+// sqlsrv_conn*: The connection with which the transaction is associated.
+
+void core_sqlsrv_begin_transaction( sqlsrv_conn* conn TSRMLS_DC )
+{
+ try {
+
+ DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_begin_transaction: connection object was null." );
+
+ core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_OFF ),
+ SQL_IS_UINTEGER TSRMLS_CC );
+ }
+ catch ( core::CoreException& ) {
+ throw;
+ }
+}
+
+// core_sqlsrv_commit
+// 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
+// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
+// core_sqlsrv_commit.
+// Parameters:
+// sqlsrv_conn*: The connection on which the transaction is active.
+
+void core_sqlsrv_commit( sqlsrv_conn* conn TSRMLS_DC )
+{
+ try {
+
+ DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_commit: connection object was null." );
+
+ core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_COMMIT TSRMLS_CC );
+
+ core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ),
+ SQL_IS_UINTEGER TSRMLS_CC );
+ }
+ catch ( core::CoreException& ) {
+ throw;
+ }
+}
+
+// core_sqlsrv_rollback
+// 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
+// core_sqlsrv_begin_transaction and before any calls to core_sqlsrv_rollback or
+// core_sqlsrv_commit.
+// Parameters:
+// sqlsrv_conn*: The connection on which the transaction is active.
+
+void core_sqlsrv_rollback( sqlsrv_conn* conn TSRMLS_DC )
+{
+ try {
+
+ DEBUG_SQLSRV_ASSERT( conn != NULL, "core_sqlsrv_rollback: connection object was null." );
+
+ core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
+
+ core::SQLSetConnectAttr( conn, SQL_ATTR_AUTOCOMMIT, reinterpret_cast( SQL_AUTOCOMMIT_ON ),
+ SQL_IS_UINTEGER TSRMLS_CC );
+
+ }
+ catch ( core::CoreException& ) {
+ throw;
+ }
+}
+
+// core_sqlsrv_close
+// Called when a connection resource is destroyed by the Zend engine.
+// Parameters:
+// conn - The current active connection.
+void core_sqlsrv_close( sqlsrv_conn* conn TSRMLS_DC )
+{
+ // if the connection wasn't successful, just return.
+ if( conn == NULL )
+ return;
+
+ try {
+
+ // rollback any transaction in progress (we don't care about the return result)
+ core::SQLEndTran( SQL_HANDLE_DBC, conn, SQL_ROLLBACK TSRMLS_CC );
+ }
+ catch( core::CoreException& ) {
+ LOG( SEV_ERROR, "Transaction rollback failed when closing the connection." );
+ }
+
+ // disconnect from the server
+ SQLRETURN r = SQLDisconnect( conn->handle() );
+ if( !SQL_SUCCEEDED( r )) {
+ LOG( SEV_ERROR, "Disconnect failed when closing the connection." );
+ }
+
+ // free the connection handle
+ conn->invalidate();
+
+ sqlsrv_free( conn );
+}
+
+// core_sqlsrv_prepare
+// Create a statement object and prepare the SQL query passed in for execution at a later time.
+// Parameters:
+// stmt - statement to be prepared
+// sql - T-SQL command to prepare
+// sql_len - length of the T-SQL string
+
+void core_sqlsrv_prepare( sqlsrv_stmt* stmt, const char* sql, long sql_len TSRMLS_DC )
+{
+ try {
+
+ // convert the string from its encoding to UTf-16
+ // if the string is empty, we initialize the fields and skip since an empty string is a
+ // failure case for utf16_string_from_mbcs_string
+ sqlsrv_malloc_auto_ptr wsql_string;
+ unsigned int wsql_len = 0;
+ if( sql_len == 0 || ( sql[0] == '\0' && sql_len == 1 )) {
+ wsql_string = reinterpret_cast( sqlsrv_malloc( sizeof( wchar_t )));
+ wsql_string[0] = L'\0';
+ wsql_len = 0;
+ }
+ else {
+ SQLSRV_ENCODING encoding = (( stmt->encoding() == SQLSRV_ENCODING_DEFAULT ) ? stmt->conn->encoding() :
+ stmt->encoding() );
+ wsql_string = utf16_string_from_mbcs_string( encoding, reinterpret_cast( sql ),
+ sql_len, &wsql_len );
+ CHECK_CUSTOM_ERROR( wsql_string == NULL, stmt, SQLSRV_ERROR_QUERY_STRING_ENCODING_TRANSLATE,
+ get_last_error_message() ) {
+ throw core::CoreException();
+ }
+ }
+
+ // prepare our wide char query string
+ core::SQLPrepareW( stmt, reinterpret_cast( wsql_string.get() ), wsql_len TSRMLS_CC );
+ }
+ catch( core::CoreException& ) {
+
+ throw;
+ }
+}
+
+// core_sqlsrv_get_server_version
+// Determines the vesrion of the SQL Server we are connected to. Calls a helper function
+// get_server_version to get the version of SQL Server.
+// Parameters:
+// conn - The connection resource by which the client and server are connected.
+// *server_version - zval for returning results.
+
+void core_sqlsrv_get_server_version( sqlsrv_conn* conn, __out zval *server_version TSRMLS_DC )
+{
+ try {
+
+ sqlsrv_malloc_auto_ptr buffer;
+ SQLSMALLINT buffer_len = 0;
+
+ get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
+ ZVAL_STRINGL( server_version, buffer, buffer_len);
+ if (NULL != buffer) {
+ sqlsrv_free( buffer );
+ }
+ buffer.transferred();
+ }
+
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+// core_sqlsrv_get_server_info
+// Returns the Database name, the name of the SQL Server we are connected to
+// and the version of the SQL Server.
+// Parameters:
+// conn - The connection resource by which the client and server are connected.
+// *server_info - zval for returning results.
+
+void core_sqlsrv_get_server_info( sqlsrv_conn* conn, __out zval *server_info TSRMLS_DC )
+{
+ try {
+
+ sqlsrv_malloc_auto_ptr buffer;
+ SQLSMALLINT buffer_len = 0;
+
+ // initialize the array
+ core::sqlsrv_array_init( *conn, server_info TSRMLS_CC );
+
+ // Get the database name
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_DATABASE_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, server_info, "CurrentDatabase", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+
+ // Get the server version
+ get_server_version( conn, &buffer, buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerVersion", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+
+ // Get the server name
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_SERVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, server_info, "SQLServerName", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+ }
+
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+// core_sqlsrv_get_client_info
+// Returns the ODBC driver's dll name, version and the ODBC version.
+// Parameters
+// conn - The connection resource by which the client and server are connected.
+// *client_info - zval for returning the results.
+
+void core_sqlsrv_get_client_info( sqlsrv_conn* conn, __out zval *client_info TSRMLS_DC )
+{
+ try {
+
+ sqlsrv_malloc_auto_ptr buffer;
+ SQLSMALLINT buffer_len = 0;
+
+ // initialize the array
+ core::sqlsrv_array_init( *conn, client_info TSRMLS_CC );
+
+ // Get the ODBC driver's dll name
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_DRIVER_NAME, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, client_info, "DriverDllName", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+
+ // Get the ODBC driver's ODBC version
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_DRIVER_ODBC_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, client_info, "DriverODBCVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+
+ // Get the OBDC driver's version
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_DRIVER_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ core::sqlsrv_add_assoc_string( *conn, client_info, "DriverVer", buffer, 0 /*duplicate*/ TSRMLS_CC );
+ buffer.transferred();
+
+ }
+
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+
+// core_is_conn_opt_value_escaped
+// determine if connection string value is properly escaped.
+// Properly escaped means that any '}' should be escaped by a prior '}'. It is assumed that
+// the value will be surrounded by { and } by the caller after it has been validated
+
+bool core_is_conn_opt_value_escaped( const char* value, int value_len )
+{
+ // if the value is already quoted, then only analyse the part inside the quotes and return it as
+ // unquoted since we quote it when adding it to the connection string.
+ if( value_len > 0 && value[0] == '{' && value[ value_len - 1 ] == '}' ) {
+ ++value;
+ value_len -= 2;
+ }
+ // check to make sure that all right braces are escaped
+ int i = 0;
+ while( ( value[i] != '}' || ( value[i] == '}' && value[i+1] == '}' )) && i < value_len ) {
+ // skip both braces
+ if( value[i] == '}' )
+ ++i;
+ ++i;
+ }
+ if( i < value_len && value[i] == '}' ) {
+ return false;
+ }
+
+ return true;
+}
+
+
+// *** internal connection functions and classes ***
+
+namespace {
+
+connection_option const* get_connection_option( sqlsrv_conn* conn, unsigned long key,
+ const connection_option conn_opts[] TSRMLS_DC )
+{
+ for( int opt_idx = 0; conn_opts[ opt_idx ].conn_option_key != SQLSRV_CONN_OPTION_INVALID; ++opt_idx ) {
+
+ if( key == conn_opts[ opt_idx ].conn_option_key ) {
+
+ return &conn_opts[ opt_idx ];
+ }
+ }
+
+ SQLSRV_ASSERT( false, "Invalid connection option, should have been validated by the driver layer." );
+ return NULL; // avoid a compiler warning
+}
+
+// says what it does, and does what it says
+// rather than have attributes and connection strings as ODBC does, we unify them into a hash table
+// passed to the connection, and then break them out ourselves and either set attributes or put the
+// option in the connection string.
+
+void build_connection_string_and_set_conn_attr( sqlsrv_conn* conn, const char* server, const char* uid, const char* pwd,
+ HashTable* options, const connection_option valid_conn_opts[],
+ void* driver,__inout std::string& connection_string TSRMLS_DC )
+{
+ bool credentials_mentioned = false;
+ bool mars_mentioned = false;
+ connection_option const* conn_opt;
+ int zr = SUCCESS;
+
+ try {
+
+ connection_string = CONNECTION_STRING_DRIVER_NAME;
+
+ // Add the server name
+ common_conn_str_append_func( ODBCConnOptions::SERVER, server, strlen( server ), connection_string TSRMLS_CC );
+
+ // if uid is not present then we use trusted connection.
+ if(uid == NULL || strlen( uid ) == 0 ) {
+
+ connection_string += "Trusted_Connection={Yes};";
+ }
+ else {
+
+ bool escaped = core_is_conn_opt_value_escaped( uid, strlen( uid ));
+ CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
+ throw core::CoreException();
+ }
+
+ common_conn_str_append_func( ODBCConnOptions::UID, uid, strlen( uid ), connection_string TSRMLS_CC );
+
+ // if no password was given, then don't add a password to the connection string. Perhaps the UID
+ // given doesn't have a password?
+ if( pwd != NULL ) {
+
+ escaped = core_is_conn_opt_value_escaped( pwd, strlen( pwd ));
+ CHECK_CUSTOM_ERROR( !escaped, conn, SQLSRV_ERROR_UID_PWD_BRACES_NOT_ESCAPED ) {
+
+ throw core::CoreException();
+ }
+
+ common_conn_str_append_func( ODBCConnOptions::PWD, pwd, strlen( pwd ), connection_string TSRMLS_CC );
+ }
+ }
+
+ // if no options were given, then we set MARS the defaults and return immediately.
+ if( options == NULL || zend_hash_num_elements( options ) == 0 ) {
+ connection_string += CONNECTION_STRING_DEFAULT_OPTIONS;
+ return;
+ }
+
+ // workaround for a bug in ODBC Driver Manager wherein the Driver Manager creates a 0 KB file
+ // if the TraceFile option is set, even if the "TraceOn" is not present or the "TraceOn"
+ // flag is set to false.
+ if( zend_hash_index_exists( options, SQLSRV_CONN_OPTION_TRACE_FILE )) {
+
+ zval* trace_value = NULL;
+ trace_value = zend_hash_index_find(options, SQLSRV_CONN_OPTION_TRACE_ON);
+
+ if (trace_value == NULL || !zend_is_true(trace_value)) {
+
+ zend_hash_index_del( options, SQLSRV_CONN_OPTION_TRACE_FILE );
+ }
+ }
+
+ for( zend_hash_internal_pointer_reset( options );
+ zend_hash_has_more_elements( options ) == SUCCESS;
+ zend_hash_move_forward( options )) {
+
+ int type = HASH_KEY_NON_EXISTENT;
+ zend_string *key = NULL;
+ zend_ulong index = -1;
+ zval* data = NULL;
+
+ type = zend_hash_get_current_key( options, &key, &index );
+
+ // The driver layer should ensure a valid key.
+ DEBUG_SQLSRV_ASSERT(( type == HASH_KEY_IS_LONG ), "build_connection_string_and_set_conn_attr: invalid connection option key type." );
+
+ core::sqlsrv_zend_hash_get_current_data( *conn, options, data TSRMLS_CC );
+
+ conn_opt = get_connection_option( conn, index, valid_conn_opts TSRMLS_CC );
+
+ if( index == SQLSRV_CONN_OPTION_MARS ) {
+ mars_mentioned = true;
+ }
+
+ conn_opt->func( conn_opt, data, conn, connection_string TSRMLS_CC );
+ }
+
+ // MARS on if not explicitly turned off
+ if( !mars_mentioned ) {
+ connection_string += CONNECTION_OPTION_MARS_ON;
+ }
+
+ }
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+
+// get_server_version
+// Helper function which returns the version of the SQL Server we are connected to.
+
+void get_server_version( sqlsrv_conn* conn, char** server_version, SQLSMALLINT& len TSRMLS_DC )
+{
+ try {
+
+ sqlsrv_malloc_auto_ptr buffer;
+ SQLSMALLINT buffer_len = 0;
+
+ buffer = static_cast( sqlsrv_malloc( INFO_BUFFER_LEN ));
+ core::SQLGetInfo( conn, SQL_DBMS_VER, buffer, INFO_BUFFER_LEN, &buffer_len TSRMLS_CC );
+ *server_version = buffer;
+ len = buffer_len;
+ buffer.transferred();
+ }
+
+ catch( core::CoreException& ) {
+ throw;
+ }
+}
+
+
+// get_processor_arch
+// Calls GetSystemInfo to verify the what architecture of the processor is supported
+// and return the string of the processor name.
+const char* get_processor_arch( void )
+{
+ SYSTEM_INFO sys_info;
+ GetSystemInfo( &sys_info);
+ switch( sys_info.wProcessorArchitecture ) {
+
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ return PROCESSOR_ARCH[0];
+
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ return PROCESSOR_ARCH[1];
+
+ case PROCESSOR_ARCHITECTURE_IA64:
+ return PROCESSOR_ARCH[2];
+
+ default:
+ DIE( "Unknown Windows processor architecture." );
+ return NULL;
+ }
+}
+
+
+// some features require a server of a certain version or later
+// this function determines the version of the server we're connected to
+// and stores it in the connection. Any errors are logged before return.
+// Exception is thrown when the server version is either undetermined
+// or is invalid (< 2000).
+
+void determine_server_version( sqlsrv_conn* conn TSRMLS_DC )
+{
+ SQLSMALLINT info_len;
+ char p[ INFO_BUFFER_LEN ];
+ core::SQLGetInfo( conn, SQL_DBMS_VER, p, INFO_BUFFER_LEN, &info_len TSRMLS_CC );
+
+ errno = 0;
+ char version_major_str[ 3 ];
+ SERVER_VERSION version_major;
+ memcpy( version_major_str, p, 2 );
+ version_major_str[ 2 ] = '\0';
+ version_major = static_cast( atoi( version_major_str ));
+
+ CHECK_CUSTOM_ERROR( version_major == 0 && ( errno == ERANGE || errno == EINVAL ), conn, SQLSRV_ERROR_UNKNOWN_SERVER_VERSION )
+ {
+ throw core::CoreException();
+ }
+
+ // SNAC won't connect to versions older than SQL Server 2000, so we know that the version is at least
+ // that high
+ conn->server_version = version_major;
+}
+
+void common_conn_str_append_func( const char* odbc_name, const char* val, int val_len, std::string& conn_str TSRMLS_DC )
+{
+ // wrap a connection option in a quote. It is presumed that any character that need to be escaped will
+ // be escaped, such as a closing }.
+ TSRMLS_C;
+
+ if( val_len > 0 && val[0] == '{' && val[ val_len - 1 ] == '}' ) {
+ ++val;
+ val_len -= 2;
+ }
+ conn_str += odbc_name;
+ conn_str += "={";
+ conn_str.append( val, val_len );
+ conn_str += "};";
+}
+
+} // namespace
+
+// simply add the parsed value to the connection string
+void conn_str_append_func::func( connection_option const* option, zval* value, sqlsrv_conn* /*conn*/, std::string& conn_str
+ TSRMLS_DC )
+{
+ const char* val_str = Z_STRVAL_P( value );
+ int val_len = Z_STRLEN_P( value );
+ common_conn_str_append_func( option->odbc_name, val_str, val_len, conn_str TSRMLS_CC );
+}
+
+// do nothing for connection pooling since we handled it earlier when
+// deciding which environment handle to use.
+void conn_null_func::func( connection_option const* /*option*/, zval* /*value*/, sqlsrv_conn* /*conn*/, std::string& /*conn_str*/
+ TSRMLS_DC )
+{
+ TSRMLS_C;
+}
+
+// helper function to evaluate whether a string value is true or false.
+// Values = ("true" or "1") are treated as true values. Everything else is treated as false.
+// Returns 1 for true and 0 for false.
+
+int core_str_zval_is_true( zval* value_z )
+{
+ SQLSRV_ASSERT( Z_TYPE_P( value_z ) == IS_STRING, "core_str_zval_is_true: This function only accepts zval of type string." );
+
+ char* value_in = Z_STRVAL_P( value_z );
+ int val_len = Z_STRLEN_P( value_z );
+
+ // strip any whitespace at the end (whitespace is the same value in ASCII and UTF-8)
+ int last_char = val_len - 1;
+ while( isspace( value_in[ last_char ] )) {
+ value_in[ last_char ] = '\0';
+ val_len = last_char;
+ --last_char;
+ }
+
+ // save adjustments to the value made by stripping whitespace at the end
+ // TODO - review value_in
+ ZVAL_STRINGL( value_z, value_in, val_len);
+
+ const char VALID_TRUE_VALUE_1[] = "true";
+ const char VALID_TRUE_VALUE_2[] = "1";
+
+ if(( val_len == ( sizeof( VALID_TRUE_VALUE_1 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_1, val_len )) ||
+ ( val_len == ( sizeof( VALID_TRUE_VALUE_2 ) - 1 ) && !strnicmp( value_in, VALID_TRUE_VALUE_2, val_len ))
+ ) {
+
+ return 1; // true
+ }
+
+ return 0; // false
+}
diff --git a/sqlsrv/core_init.cpp b/sqlsrv/core_init.cpp
new file mode 100644
index 00000000..9a091dab
--- /dev/null
+++ b/sqlsrv/core_init.cpp
@@ -0,0 +1,173 @@
+//---------------------------------------------------------------------------------------------------------------------------------
+// File: core_init.cpp
+//
+// Contents: common initialization routines shared by PDO and sqlsrv
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+#include "core_sqlsrv.h"
+
+
+// module global variables (initialized in minit and freed in mshutdown)
+HMODULE g_sqlsrv_hmodule = NULL;
+OSVERSIONINFO g_osversion;
+
+
+// core_sqlsrv_minit
+// Module initialization
+// This function is called once per execution by the driver layer's MINIT function.
+// The primary responsibility of this function is to allocate the two environment
+// handles used by core_sqlsrv_connect to allocate either a pooled or non pooled ODBC
+// connection handle.
+// Parameters:
+// henv_cp - Environment handle for pooled connection.
+// henv_ncp - Environment handle for non-pooled connection.
+// err - Driver specific error handler which handles any errors during initialization.
+void core_sqlsrv_minit( sqlsrv_context** henv_cp, sqlsrv_context** henv_ncp, error_callback err, const char* driver_func TSRMLS_DC )
+{
+ SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_sqltype ) == sizeof( long ));
+ SQLSRV_STATIC_ASSERT( sizeof( sqlsrv_phptype ) == sizeof( long ));
+
+ *henv_cp = *henv_ncp = SQL_NULL_HANDLE; // initialize return values to NULL
+
+ try {
+
+ // get the version of the OS we're running on. For now this governs certain flags used by
+ // WideCharToMultiByte. It might be relevant to other things in the future.
+ g_osversion.dwOSVersionInfoSize = sizeof( g_osversion );
+ BOOL ver_return = GetVersionEx( &g_osversion );
+ if( !ver_return ) {
+ LOG( SEV_ERROR, "Failed to retrieve Windows version information." );
+ throw core::CoreException();
+ }
+
+ SQLHANDLE henv = SQL_NULL_HANDLE;
+ SQLRETURN r;
+
+ // allocate the non pooled environment handle
+ // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
+ // we use the direct ODBC function.
+ r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
+ if( !SQL_SUCCEEDED( r )) {
+ throw core::CoreException();
+ }
+
+ *henv_ncp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
+ (*henv_ncp)->set_func( driver_func );
+
+ // set to ODBC 3
+ core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER
+ TSRMLS_CC );
+
+ // disable connection pooling
+ core::SQLSetEnvAttr( **henv_ncp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_OFF ),
+ SQL_IS_UINTEGER TSRMLS_CC );
+
+ // allocate the pooled envrionment handle
+ // we can't use the wrapper in core_sqlsrv.h since we don't have a context on which to base errors, so
+ // we use the direct ODBC function.
+ r = ::SQLAllocHandle( SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv );
+ if( !SQL_SUCCEEDED( r )) {
+ throw core::CoreException();
+ }
+
+ *henv_cp = new sqlsrv_context( henv, SQL_HANDLE_ENV, err, NULL );
+ (*henv_cp)->set_func( driver_func );
+
+ // set to ODBC 3
+ core::SQLSetEnvAttr( **henv_cp, SQL_ATTR_ODBC_VERSION, reinterpret_cast( SQL_OV_ODBC3 ), SQL_IS_INTEGER TSRMLS_CC);
+
+ // enable connection pooling
+ core:: SQLSetEnvAttr( **henv_cp, SQL_ATTR_CONNECTION_POOLING, reinterpret_cast( SQL_CP_ONE_PER_HENV ),
+ SQL_IS_UINTEGER TSRMLS_CC );
+
+ }
+ catch( core::CoreException& e ) {
+
+ LOG( SEV_ERROR, "core_sqlsrv_minit: Failed to allocate environment handles." );
+
+ if( *henv_ncp != NULL ) {
+ // free the ODBC env handle allocated just above
+ SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
+ delete *henv_ncp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
+ *henv_ncp = NULL;
+ }
+ if( *henv_cp != NULL ) {
+ // free the ODBC env handle allocated just above
+ SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
+ delete *henv_cp; // free the memory for the sqlsrv_context (it comes from the C heap, not PHP's heap)
+ *henv_cp = NULL;
+ }
+
+ throw e; // rethrow for the driver to catch
+ }
+ catch( std::bad_alloc& e ) {
+
+ LOG( SEV_ERROR, "core_sqlsrv_minit: Failed memory allocation for environment handles." );
+
+ if( *henv_ncp != NULL ) {
+ SQLFreeHandle( SQL_HANDLE_ENV, **henv_ncp );
+ delete *henv_ncp;
+ *henv_ncp = NULL;
+ }
+ if( *henv_cp ) {
+ SQLFreeHandle( SQL_HANDLE_ENV, **henv_cp );
+ delete *henv_cp;
+ *henv_cp = NULL;
+ }
+
+ throw e; // rethrow for the driver to catch
+ }
+}
+
+// core_sqlsrv_mshutdown
+// Module shutdown function
+// Free the environment handles allocated in MINIT and unregister our stream wrapper.
+// Resource types and constants are automatically released since we don't flag them as
+// persistent when they are registered.
+// Parameters:
+// henv_cp - Pooled environment handle.
+// henv_ncp - Non-pooled environment handle.
+void core_sqlsrv_mshutdown( sqlsrv_context& henv_cp, sqlsrv_context& henv_ncp )
+{
+ if( henv_ncp != SQL_NULL_HANDLE ) {
+
+ henv_ncp.invalidate();
+ }
+
+ if( henv_cp != SQL_NULL_HANDLE ) {
+
+ henv_cp.invalidate();
+ }
+
+ return;
+}
+
+
+// DllMain for the extension.
+
+BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD fdwReason, LPVOID )
+{
+ switch( fdwReason ) {
+ case DLL_PROCESS_ATTACH:
+ // store the module handle for use by client_info and server_info
+ g_sqlsrv_hmodule = hinstDLL;
+ break;
+ default:
+ break;
+ }
+
+ return TRUE;
+}
diff --git a/sqlsrv/core_results.cpp b/sqlsrv/core_results.cpp
new file mode 100644
index 00000000..bb008bc1
--- /dev/null
+++ b/sqlsrv/core_results.cpp
@@ -0,0 +1,1337 @@
+//---------------------------------------------------------------------------------------------------------------------------------
+// File: core_results.cpp
+//
+// Contents: Result sets
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+#include "core_sqlsrv.h"
+
+#include
+#include
+#include
+
+using namespace core;
+
+// conversion matrix
+// each entry holds a function that can perform the conversion or NULL which means the conversion isn't supported
+// this is initialized the first time the buffered result set is created.
+sqlsrv_buffered_result_set::conv_matrix_t sqlsrv_buffered_result_set::conv_matrix;
+
+namespace {
+
+// *** internal types ***
+
+#pragma warning(disable:4200)
+
+// *** internal constants ***
+
+const int INITIAL_FIELD_STRING_LEN = 256; // base allocation size when retrieving a string field
+
+// *** internal functions ***
+
+// return an integral type rounded up to a certain number
+template
+T align_to( T number )
+{
+ DEBUG_SQLSRV_ASSERT( (number + align) > number, "Number to align overflowed" );
+ return ((number % align) == 0) ? number : (number + align - (number % align));
+}
+
+// return a pointer address aligned to a certain address boundary
+template
+T* align_to( T* ptr )
+{
+ size_t p_value = (size_t) ptr;
+ return align_to( p_value );
+}
+
+// set the nth bit of the bitstream starting at ptr
+void set_bit( void* ptr, unsigned int bit )
+{
+ unsigned char* null_bits = reinterpret_cast( ptr );
+ null_bits += bit >> 3;
+ *null_bits |= 1 << ( 7 - ( bit & 0x7 ));
+}
+
+// retrieve the nth bit from the bitstream starting at ptr
+bool get_bit( void* ptr, unsigned int bit )
+{
+ unsigned char* null_bits = reinterpret_cast( ptr );
+ null_bits += bit >> 3;
+ return ((*null_bits & (1 << ( 7 - ( bit & 0x07 )))) != 0);
+}
+
+// read in LOB field during buffered result creation
+SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
+ unsigned long mem_used TSRMLS_DC );
+
+// dtor for each row in the cache
+void cache_row_dtor(zval* data);
+
+// convert a number to a string using locales
+// There is an extra copy here, but given the size is short (usually <20 bytes) and the complications of
+// subclassing a new streambuf just to avoid the copy, it's easier to do the copy
+template
+SQLRETURN number_to_string( Number* number_data, __out void* buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
+ sqlsrv_error_auto_ptr& last_error )
+{
+ std::basic_ostringstream os;
+ std::locale loc;
+ os.imbue( loc );
+ std::use_facet< std::num_put< Char > >( loc ).put( std::basic_ostream::_Iter( os.rdbuf() ), os, ' ', *number_data );
+ std::basic_string& str_num = os.str();
+
+ if( os.fail() ) {
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
+ (SQLCHAR*) "IMSSP", (SQLCHAR*) "Failed to convert number to string", -1 );
+ return SQL_ERROR;
+ }
+
+ if( str_num.size() * sizeof(Char) + sizeof(Char) > (size_t) buffer_length ) {
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
+ (SQLCHAR*) "HY090", (SQLCHAR*) "Buffer length too small to hold number as string", -1 );
+ return SQL_ERROR;
+ }
+
+ *out_buffer_length = str_num.size() * sizeof(Char) + sizeof(Char); // include NULL terminator
+ memcpy( buffer, str_num.c_str(), *out_buffer_length );
+
+ return SQL_SUCCESS;
+}
+
+template
+SQLRETURN string_to_number( Char* string_data, SQLLEN str_len, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length, sqlsrv_error_auto_ptr& last_error )
+{
+ Number* number_data = reinterpret_cast( buffer );
+ std::locale loc; // default locale should match system
+ std::basic_istringstream is;
+ is.str( string_data );
+ is.imbue( loc );
+ std::ios_base::iostate st = 0;
+
+ std::use_facet< std::num_get< Char > >( loc ).get( std::basic_istream::_Iter( is.rdbuf( ) ),
+ std::basic_istream::_Iter(0), is, st, *number_data );
+
+ if( st & std::ios_base::failbit ) {
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error(
+ (SQLCHAR*) "22003", (SQLCHAR*) "Numeric value out of range", 103 );
+ return SQL_ERROR;
+ }
+
+ *out_buffer_length = sizeof( Number );
+
+ return SQL_SUCCESS;
+}
+
+// "closure" for the hash table destructor
+struct row_dtor_closure {
+
+ sqlsrv_buffered_result_set* results;
+ BYTE* row_data;
+
+ row_dtor_closure( sqlsrv_buffered_result_set* st, BYTE* row ) :
+ results( st ), row_data( row )
+ {
+ }
+};
+
+sqlsrv_error* odbc_get_diag_rec( sqlsrv_stmt* odbc, SQLSMALLINT record_number )
+{
+ SQLWCHAR wsql_state[ SQL_SQLSTATE_BUFSIZE ];
+ SQLWCHAR wnative_message[ SQL_MAX_MESSAGE_LENGTH + 1 ];
+ SQLINTEGER native_code;
+ SQLSMALLINT wnative_message_len = 0;
+
+ SQLRETURN r = SQLGetDiagRecW( SQL_HANDLE_STMT, odbc->handle(), record_number, wsql_state, &native_code, wnative_message,
+ SQL_MAX_MESSAGE_LENGTH + 1, &wnative_message_len );
+ if( !SQL_SUCCEEDED( r ) || r == SQL_NO_DATA ) {
+ return NULL;
+ }
+
+ // convert the error into the encoding of the context
+ SQLSRV_ENCODING enc = odbc->encoding();
+ if( enc == SQLSRV_ENCODING_DEFAULT ) {
+ enc = odbc->conn->encoding();
+ }
+
+ // convert the error into the encoding of the context
+ sqlsrv_malloc_auto_ptr sql_state;
+ SQLINTEGER sql_state_len = 0;
+ if (!convert_string_from_utf16( enc, wsql_state, sizeof(wsql_state), (char**)&sql_state, sql_state_len )) {
+ return NULL;
+ }
+
+ sqlsrv_malloc_auto_ptr native_message;
+ SQLINTEGER native_message_len = 0;
+ if (!convert_string_from_utf16( enc, wnative_message, wnative_message_len, (char**)&native_message, native_message_len )) {
+ return NULL;
+ }
+
+ return new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) sql_state, (SQLCHAR*) native_message,
+ native_code );
+}
+
+} // namespace
+
+// base class result set
+
+sqlsrv_result_set::sqlsrv_result_set( sqlsrv_stmt* stmt ) :
+ odbc( stmt )
+{
+}
+
+
+// ODBC result set
+// This object simply wraps ODBC function calls
+
+sqlsrv_odbc_result_set::sqlsrv_odbc_result_set( sqlsrv_stmt* stmt ) :
+ sqlsrv_result_set( stmt )
+{
+}
+
+sqlsrv_odbc_result_set::~sqlsrv_odbc_result_set( void )
+{
+}
+
+SQLRETURN sqlsrv_odbc_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
+{
+ SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
+ return core::SQLFetchScroll( odbc, orientation, offset TSRMLS_CC );
+}
+
+SQLRETURN sqlsrv_odbc_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
+ __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
+ bool handle_warning TSRMLS_DC )
+{
+ SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
+ return core::SQLGetData( odbc, field_index, target_type, buffer, buffer_length, out_buffer_length, handle_warning TSRMLS_CC );
+}
+
+SQLRETURN sqlsrv_odbc_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
+ __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
+ __out SQLSMALLINT* out_buffer_length TSRMLS_DC )
+{
+ SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
+ return core::SQLGetDiagField( odbc, record_number, diag_identifier, diag_info_buffer, buffer_length,
+ out_buffer_length TSRMLS_CC );
+}
+
+sqlsrv_error* sqlsrv_odbc_result_set::get_diag_rec( SQLSMALLINT record_number )
+{
+ SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
+ return odbc_get_diag_rec( odbc, record_number );
+}
+
+SQLLEN sqlsrv_odbc_result_set::row_count( TSRMLS_D )
+{
+ SQLSRV_ASSERT( odbc != NULL, "Invalid statement handle" );
+ return core::SQLRowCount( odbc TSRMLS_CC );
+}
+
+
+// Buffered result set
+// This class holds a result set in memory
+
+sqlsrv_buffered_result_set::sqlsrv_buffered_result_set( sqlsrv_stmt* stmt TSRMLS_DC ) :
+ sqlsrv_result_set( stmt ),
+ cache(NULL),
+ col_count(0),
+ meta(NULL),
+ current(0),
+ last_field_index(-1),
+ read_so_far(0)
+{
+ // 10 is an arbitrary number for now for the initial size of the cache
+ ALLOC_HASHTABLE( cache );
+ core::sqlsrv_zend_hash_init( *stmt, cache, 10 /* # of buckets */, cache_row_dtor /*dtor*/, 0 /*persistent*/ TSRMLS_CC );
+ col_count = core::SQLNumResultCols( stmt TSRMLS_CC );
+ // there is no result set to buffer
+ if( col_count == 0 ) {
+ return;
+ }
+
+ SQLULEN null_bytes = ( col_count / 8 ) + 1; // number of bits to reserve at the beginning of each row for NULL flags
+ meta = static_cast( sqlsrv_malloc( col_count *
+ sizeof( sqlsrv_buffered_result_set::meta_data )));
+
+ // set up the conversion matrix if this is the first time we're called
+ if( conv_matrix.size() == 0 ) {
+
+ conv_matrix[ SQL_C_CHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::to_same_string;
+ conv_matrix[ SQL_C_CHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::system_to_wide_string;
+ conv_matrix[ SQL_C_CHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
+ conv_matrix[ SQL_C_CHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::string_to_double;
+ conv_matrix[ SQL_C_CHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::string_to_long;
+ conv_matrix[ SQL_C_WCHAR ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::to_same_string;
+ conv_matrix[ SQL_C_WCHAR ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_binary_string;
+ conv_matrix[ SQL_C_WCHAR ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::wide_to_system_string;
+ conv_matrix[ SQL_C_WCHAR ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::wstring_to_double;
+ conv_matrix[ SQL_C_WCHAR ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::wstring_to_long;
+ conv_matrix[ SQL_C_BINARY ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_same_string;
+ conv_matrix[ SQL_C_BINARY ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::binary_to_system_string;
+ conv_matrix[ SQL_C_BINARY ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::binary_to_wide_string;
+ conv_matrix[ SQL_C_LONG ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::long_to_double;
+ conv_matrix[ SQL_C_LONG ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::to_long;
+ conv_matrix[ SQL_C_LONG ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_long;
+ conv_matrix[ SQL_C_LONG ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::long_to_system_string;
+ conv_matrix[ SQL_C_LONG ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::long_to_wide_string;
+ conv_matrix[ SQL_C_DOUBLE ][ SQL_C_DOUBLE ] = &sqlsrv_buffered_result_set::to_double;
+ conv_matrix[ SQL_C_DOUBLE ][ SQL_C_BINARY ] = &sqlsrv_buffered_result_set::to_double;
+ conv_matrix[ SQL_C_DOUBLE ][ SQL_C_CHAR ] = &sqlsrv_buffered_result_set::double_to_system_string;
+ conv_matrix[ SQL_C_DOUBLE ][ SQL_C_LONG ] = &sqlsrv_buffered_result_set::double_to_long;
+ conv_matrix[ SQL_C_DOUBLE ][ SQL_C_WCHAR ] = &sqlsrv_buffered_result_set::double_to_wide_string;
+ }
+
+ // get the meta data and calculate the size of a row buffer
+ SQLULEN offset = null_bytes;
+ for( SQLSMALLINT i = 0; i < col_count; ++i ) {
+
+ core::SQLDescribeCol( stmt, i + 1, NULL, 0, NULL, &meta[i].type, &meta[i].length, &meta[i].scale, NULL TSRMLS_CC );
+
+ offset = align_to<4>( offset );
+ meta[i].offset = offset;
+
+ switch( meta[i].type ) {
+
+ // these types are the display size
+ case SQL_BIGINT:
+ case SQL_DECIMAL:
+ case SQL_GUID:
+ case SQL_NUMERIC:
+ core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
+ reinterpret_cast( &meta[i].length ) TSRMLS_CC );
+ meta[i].length += sizeof( char ) + sizeof( SQLULEN ); // null terminator space
+ offset += meta[i].length;
+ break;
+
+ // these types are the column size
+ case SQL_BINARY:
+ case SQL_CHAR:
+ case SQL_SS_UDT:
+ case SQL_VARBINARY:
+ case SQL_VARCHAR:
+ // var* field types are length prefixed
+ if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+ offset += sizeof( void* );
+ }
+ else {
+ meta[i].length += sizeof( SQLULEN ) + sizeof( char ); // length plus null terminator space
+ offset += meta[i].length;
+ }
+ break;
+
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+ offset += sizeof( void* );
+ }
+ else {
+ meta[i].length *= sizeof( WCHAR );
+ meta[i].length += sizeof( SQLULEN ) + sizeof( WCHAR ); // length plus null terminator space
+ offset += meta[i].length;
+ }
+ break;
+
+ // these types are LOBs
+ case SQL_LONGVARBINARY:
+ case SQL_LONGVARCHAR:
+ case SQL_WLONGVARCHAR:
+ case SQL_SS_XML:
+ meta[i].length = sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN;
+ offset += sizeof( void* );
+ break;
+
+ // these types are the ISO date size
+ case SQL_DATETIME:
+ case SQL_TYPE_DATE:
+ case SQL_SS_TIME2:
+ case SQL_SS_TIMESTAMPOFFSET:
+ case SQL_TYPE_TIMESTAMP:
+ core::SQLColAttribute( stmt, i + 1, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL,
+ reinterpret_cast( &meta[i].length ) TSRMLS_CC );
+ meta[i].length += sizeof(char) + sizeof( SQLULEN ); // null terminator space
+ offset += meta[i].length;
+ break;
+
+ // these types are the native size
+ case SQL_BIT:
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ meta[i].length = sizeof( long );
+ offset += meta[i].length;
+ break;
+
+ case SQL_REAL:
+ case SQL_FLOAT:
+ meta[i].length = sizeof( double );
+ offset += meta[i].length;
+ break;
+
+ default:
+ SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
+ break;
+ }
+
+ switch( meta[i].type ) {
+
+ case SQL_BIGINT:
+ case SQL_CHAR:
+ case SQL_DATETIME:
+ case SQL_DECIMAL:
+ case SQL_GUID:
+ case SQL_NUMERIC:
+ case SQL_LONGVARCHAR:
+ case SQL_TYPE_DATE:
+ case SQL_SS_TIME2:
+ case SQL_SS_TIMESTAMPOFFSET:
+ case SQL_SS_XML:
+ case SQL_TYPE_TIMESTAMP:
+ case SQL_VARCHAR:
+ meta[i].c_type = SQL_C_CHAR;
+ break;
+
+ case SQL_SS_UDT:
+ case SQL_LONGVARBINARY:
+ case SQL_BINARY:
+ case SQL_VARBINARY:
+ meta[i].c_type = SQL_C_BINARY;
+ break;
+
+ case SQL_WLONGVARCHAR:
+ case SQL_WCHAR:
+ case SQL_WVARCHAR:
+ meta[i].c_type = SQL_C_WCHAR;
+ break;
+
+ case SQL_BIT:
+ case SQL_INTEGER:
+ case SQL_SMALLINT:
+ case SQL_TINYINT:
+ meta[i].c_type = SQL_C_LONG;
+ break;
+
+ case SQL_REAL:
+ case SQL_FLOAT:
+ meta[i].c_type = SQL_C_DOUBLE;
+ break;
+
+ default:
+ SQLSRV_ASSERT( false, "Unknown type in sqlsrv_buffered_query::sqlsrv_buffered_query" );
+ break;
+ }
+
+ }
+
+ // read the data into the cache
+ // (offset from the above loop has the size of the row buffer necessary)
+ unsigned long mem_used = 0;
+ unsigned long row_count = 0;
+
+ while( core::SQLFetchScroll( stmt, SQL_FETCH_NEXT, 0 TSRMLS_CC ) != SQL_NO_DATA ) {
+
+ // allocate the row buffer
+ unsigned char* row = static_cast( sqlsrv_malloc( offset ));
+ memset( row, 0, offset );
+
+ // read the fields into the row buffer
+ for( SQLSMALLINT i = 0; i < col_count; ++i ) {
+
+ SQLLEN out_buffer_temp = SQL_NULL_DATA;
+ SQLPOINTER buffer;
+ SQLLEN* out_buffer_length = &out_buffer_temp;
+
+ switch( meta[i].c_type ) {
+
+ case SQL_C_CHAR:
+ case SQL_C_WCHAR:
+ case SQL_C_BINARY:
+ if( meta[i].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ out_buffer_length = &out_buffer_temp;
+ SQLPOINTER* lob_addr = reinterpret_cast( &row[ meta[i].offset ] );
+ *lob_addr = read_lob_field( stmt, i, meta[i], mem_used TSRMLS_CC );
+ // a NULL pointer means NULL field
+ if( *lob_addr == NULL ) {
+ *out_buffer_length = SQL_NULL_DATA;
+ }
+ else {
+ *out_buffer_length = **reinterpret_cast( lob_addr );
+ mem_used += *out_buffer_length;
+ }
+ }
+ else {
+
+ mem_used += meta[i].length;
+ CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
+ SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
+
+ throw core::CoreException();
+ }
+
+ buffer = row + meta[i].offset + sizeof( SQLULEN );
+ out_buffer_length = reinterpret_cast( row + meta[i].offset );
+ core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
+ false TSRMLS_CC );
+ }
+ break;
+
+ case SQL_C_LONG:
+ case SQL_C_DOUBLE:
+ {
+ mem_used += meta[i].length;
+ CHECK_CUSTOM_ERROR( mem_used > stmt->buffered_query_limit * 1024, stmt,
+ SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
+
+ throw core::CoreException();
+ }
+ buffer = row + meta[i].offset;
+ out_buffer_length = &out_buffer_temp;
+ core::SQLGetData( stmt, i + 1, meta[i].c_type, buffer, meta[i].length, out_buffer_length,
+ false TSRMLS_CC );
+ }
+ break;
+
+ default:
+ SQLSRV_ASSERT( false, "Unknown C type" );
+ break;
+ }
+
+ if( *out_buffer_length == SQL_NULL_DATA ) {
+ unsigned char* null_bits = reinterpret_cast( row );
+ set_bit( row, i );
+ }
+ }
+
+ SQLSRV_ASSERT( row_count < LONG_MAX, "Hard maximum of 2 billion rows exceeded in a buffered query" );
+
+ // add it to the cache
+ row_dtor_closure cl( this, row );
+ sqlsrv_zend_hash_next_index_insert_mem( *stmt, cache, &cl, sizeof(row_dtor_closure) TSRMLS_CC );
+ }
+
+}
+
+sqlsrv_buffered_result_set::~sqlsrv_buffered_result_set( void )
+{
+ // free the rows
+ if( cache ) {
+ zend_hash_destroy( cache );
+ FREE_HASHTABLE( cache );
+ cache = NULL;
+ }
+
+ // free the meta data
+ if( meta ) {
+ efree( meta );
+ meta = NULL;
+ }
+}
+
+SQLRETURN sqlsrv_buffered_result_set::fetch( SQLSMALLINT orientation, SQLLEN offset TSRMLS_DC )
+{
+ last_error = NULL;
+ last_field_index = -1;
+ read_so_far = 0;
+
+ switch( orientation ) {
+
+ case SQL_FETCH_NEXT:
+ offset = 1;
+ orientation = SQL_FETCH_RELATIVE;
+ break;
+ case SQL_FETCH_PRIOR:
+ offset = -1;
+ orientation = SQL_FETCH_RELATIVE;
+ break;
+ }
+
+ switch( orientation ) {
+
+ case SQL_FETCH_FIRST:
+ current = 1;
+ break;
+ case SQL_FETCH_LAST:
+ current = row_count( TSRMLS_C );
+ break;
+ case SQL_FETCH_ABSOLUTE:
+ current = offset;
+ break;
+ case SQL_FETCH_RELATIVE:
+ current += offset;
+ break;
+ default:
+ SQLSRV_ASSERT( false, "Invalid fetch orientation. Should have been caught before here." );
+ break;
+ }
+
+ // check validity of current row
+ // the cursor can never get further away than just before the first row
+ if( current <= 0 && ( offset < 0 || orientation != SQL_FETCH_RELATIVE )) {
+ current = 0;
+ return SQL_NO_DATA;
+ }
+
+ // the cursor can never get further away than just after the last row
+ if( current > row_count( TSRMLS_C ) || ( current <= 0 && offset > 0 ) /*overflow condition*/ ) {
+ current = row_count( TSRMLS_C ) + 1;
+ return SQL_NO_DATA;
+ }
+
+ return SQL_SUCCESS;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::get_data( SQLUSMALLINT field_index, SQLSMALLINT target_type,
+ __out SQLPOINTER buffer, SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
+ bool handle_warning TSRMLS_DC )
+{
+ last_error = NULL;
+ field_index--; // convert from 1 based to 0 based
+ SQLSRV_ASSERT( field_index < column_count(), "Invalid field index requested" );
+
+ if( field_index != last_field_index ) {
+ last_field_index = field_index;
+ read_so_far = 0;
+ }
+
+ unsigned char* row = get_row();
+
+ // if the field is null, then return SQL_NULL_DATA
+ if( get_bit( row, field_index )) {
+ *out_buffer_length = SQL_NULL_DATA;
+ return SQL_SUCCESS;
+ }
+
+ // check to make sure the conversion type is valid
+ if( conv_matrix.find( meta[ field_index ].c_type ) == conv_matrix.end() ||
+ conv_matrix.find( meta[ field_index ].c_type )->second.find( target_type ) ==
+ conv_matrix.find( meta[ field_index ].c_type )->second.end() ) {
+
+ last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "07006",
+ (SQLCHAR*) "Restricted data type attribute violation", 0 );
+ return SQL_ERROR;
+ }
+
+ return (( this )->*( conv_matrix[ meta[ field_index ].c_type ][ target_type ] ))( field_index, buffer, buffer_length,
+ out_buffer_length );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::get_diag_field( SQLSMALLINT record_number, SQLSMALLINT diag_identifier,
+ __out SQLPOINTER diag_info_buffer, SQLSMALLINT buffer_length,
+ __out SQLSMALLINT* out_buffer_length TSRMLS_DC )
+{
+ SQLSRV_ASSERT( record_number == 1, "Only record number 1 can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
+ SQLSRV_ASSERT( diag_identifier == SQL_DIAG_SQLSTATE,
+ "Only SQL_DIAG_SQLSTATE can be fetched by sqlsrv_buffered_result_set::get_diag_field" );
+ SQLSRV_ASSERT( buffer_length >= SQL_SQLSTATE_BUFSIZE,
+ "Buffer not big enough to return SQLSTATE in sqlsrv_buffered_result_set::get_diag_field" );
+
+ if( last_error == NULL ) {
+ return SQL_NO_DATA;
+ }
+
+ SQLSRV_ASSERT( last_error->sqlstate != NULL,
+ "Must have a SQLSTATE in a valid last_error in sqlsrv_buffered_result_set::get_diag_field" );
+
+ memcpy( diag_info_buffer, last_error->sqlstate, min( buffer_length, SQL_SQLSTATE_BUFSIZE ));
+
+ return SQL_SUCCESS;
+}
+
+unsigned char* sqlsrv_buffered_result_set::get_row( void )
+{
+ row_dtor_closure* cl_ptr;
+ cl_ptr = reinterpret_cast(zend_hash_index_find_ptr(cache, static_cast(current - 1)));
+ SQLSRV_ASSERT(cl_ptr != NULL, "Failed to find row %1!d! in the cache", current);
+ return cl_ptr->row_data;
+}
+
+sqlsrv_error* sqlsrv_buffered_result_set::get_diag_rec( SQLSMALLINT record_number )
+{
+ // we only hold a single error if there is one, otherwise return the ODBC error(s)
+ if( last_error == NULL ) {
+ return odbc_get_diag_rec( odbc, record_number );
+ }
+ if( record_number > 1 ) {
+ return NULL;
+ }
+
+ return new (sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( last_error->sqlstate, last_error->native_message, last_error->native_code );
+}
+
+SQLLEN sqlsrv_buffered_result_set::row_count( TSRMLS_D )
+{
+ last_error = NULL;
+
+ return zend_hash_num_elements( cache );
+}
+
+// private functions
+template
+SQLRETURN binary_to_string( SQLCHAR* field_data, SQLLEN& read_so_far, __out void* buffer,
+ SQLLEN buffer_length, __out SQLLEN* out_buffer_length,
+ sqlsrv_error_auto_ptr& out_error )
+{
+ // hex characters for the conversion loop below
+ static char hex_chars[] = "0123456789ABCDEF";
+
+ SQLSRV_ASSERT( out_error == NULL, "Pending error for sqlsrv_buffered_results_set::binary_to_string" );
+
+ SQLRETURN r = SQL_ERROR;
+
+ // Set the amount of space necessary for null characters at the end of the data.
+ SQLSMALLINT extra = sizeof(Char);
+
+ SQLSRV_ASSERT( ((buffer_length - extra) % (extra * 2)) == 0, "Must be multiple of 2 for binary to system string or "
+ "multiple of 4 for binary to wide string" );
+
+ // all fields will be treated as ODBC returns varchar(max) fields:
+ // the entire length of the string is returned the first
+ // call in out_buffer_len. Successive calls return how much is
+ // left minus how much has already been read by previous reads
+ // *2 is for each byte to hex conversion and * extra is for either system or wide string allocation
+ *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * 2 * extra;
+
+ // copy as much as we can into the buffer
+ SQLLEN to_copy;
+ if( buffer_length < *out_buffer_length + extra ) {
+ to_copy = (buffer_length - extra);
+ out_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
+ r = SQL_SUCCESS_WITH_INFO;
+ }
+ else {
+ r = SQL_SUCCESS;
+ to_copy = *out_buffer_length;
+ }
+
+ // if there are bytes to copy as hex
+ if( to_copy > 0 ) {
+ // quick hex conversion routine
+ Char* h = reinterpret_cast( buffer );
+ BYTE* b = reinterpret_cast( field_data );
+ // to_copy contains the number of bytes to copy, so we divide the number in half (or quarter)
+ // to get the number of hex digits we can copy
+ SQLLEN to_copy_hex = to_copy / (2 * extra);
+ for( int i = 0; i < to_copy_hex; ++i ) {
+ *h = hex_chars[ (*b & 0xf0) >> 4 ];
+ h++;
+ *h = hex_chars[ (*b++ & 0x0f) ];
+ h++;
+ }
+ read_so_far += to_copy_hex;
+ *h = static_cast( 0 );
+ }
+ else {
+ reinterpret_cast( buffer )[0] = '\0';
+ }
+
+ return r;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::binary_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLCHAR* row = get_row();
+ SQLCHAR* field_data = NULL;
+
+ if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
+ }
+ else {
+
+ field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
+ }
+
+ return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::binary_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLCHAR* row = get_row();
+ SQLCHAR* field_data = NULL;
+
+ if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
+ }
+ else {
+
+ field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
+ }
+
+ return binary_to_string( field_data, read_so_far, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+
+SQLRETURN sqlsrv_buffered_result_set::double_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to long" );
+ SQLSRV_ASSERT( buffer_length >= sizeof(LONG), "Buffer length must be able to find a long in "
+ "sqlsrv_buffered_result_set::double_to_long" );
+
+ unsigned char* row = get_row();
+ double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+ LONG* long_data = reinterpret_cast( buffer );
+
+ if( *double_data < double( LONG_MIN ) || *double_data > double( LONG_MAX )) {
+ last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "22003",
+ (SQLCHAR*) "Numeric value out of range", 0 );
+ return SQL_ERROR;
+ }
+
+ if( *double_data != floor( *double_data )) {
+ last_error = new (sqlsrv_malloc( sizeof( sqlsrv_error ))) sqlsrv_error( (SQLCHAR*) "01S07",
+ (SQLCHAR*) "Fractional truncation", 0 );
+ return SQL_SUCCESS_WITH_INFO;
+ }
+
+ *long_data = static_cast( *double_data );
+ *out_buffer_length = sizeof( LONG );
+
+ return SQL_SUCCESS;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::double_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to system string" );
+ SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_system_string" );
+
+ unsigned char* row = get_row();
+ double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::double_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invalid conversion to wide string" );
+ SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::double_to_wide_string" );
+
+ unsigned char* row = get_row();
+ double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ return number_to_string( double_data, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::long_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to long" );
+ SQLSRV_ASSERT( buffer_length >= sizeof(double), "Buffer length must be able to find a long in sqlsrv_buffered_result_set::double_to_long" );
+
+ unsigned char* row = get_row();
+ double* double_data = reinterpret_cast( buffer );
+ LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ *double_data = static_cast( *long_data );
+ *out_buffer_length = sizeof( double );
+
+ return SQL_SUCCESS;
+}
+SQLRETURN sqlsrv_buffered_result_set::long_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to system string" );
+ SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_system_string" );
+
+ unsigned char* row = get_row();
+ LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::long_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invalid conversion to wide string" );
+ SQLSRV_ASSERT( buffer_length > 0, "Buffer length must be > 0 in sqlsrv_buffered_result_set::long_to_wide_string" );
+
+ unsigned char* row = get_row();
+ LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ return number_to_string( long_data, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::string_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to double" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
+
+ unsigned char* row = get_row();
+ char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
+
+ return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::wstring_to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to double" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer needs to be big enough to hold a double" );
+
+ unsigned char* row = get_row();
+ SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
+
+ return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::string_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_CHAR, "Invalid conversion from string to long" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
+
+ unsigned char* row = get_row();
+ char* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
+
+ return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::wstring_to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_WCHAR, "Invalid conversion from wide string to long" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer needs to be big enough to hold a long" );
+
+ unsigned char* row = get_row();
+ SQLWCHAR* string_data = reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) / sizeof( SQLWCHAR );
+
+ return string_to_number( string_data, meta[ field_index ].length, buffer, buffer_length, out_buffer_length, last_error );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::system_to_wide_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::system_to_wide_string" );
+ SQLSRV_ASSERT( buffer_length % 2 == 0, "Odd buffer length passed to sqlsrv_buffered_result_set::system_to_wide_string" );
+
+ SQLRETURN r = SQL_ERROR;
+ unsigned char* row = get_row();
+
+ SQLCHAR* field_data = NULL;
+ SQLULEN field_len = NULL;
+
+ if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] );
+ field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
+ }
+ else {
+
+ field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] );
+ field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
+ }
+
+ // all fields will be treated as ODBC returns varchar(max) fields:
+ // the entire length of the string is returned the first
+ // call in out_buffer_len. Successive calls return how much is
+ // left minus how much has already been read by previous reads
+ *out_buffer_length = (*reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far) * sizeof(WCHAR);
+
+ // to_copy is the number of characters to copy, not including the null terminator
+ // supposedly it will never happen that a Windows MBCS will explode to UTF-16 surrogate pair.
+ SQLLEN to_copy;
+
+ if( (size_t) buffer_length < (field_len - read_so_far + sizeof(char)) * sizeof(WCHAR)) {
+
+ to_copy = (buffer_length - sizeof(WCHAR)) / sizeof(WCHAR); // to_copy is the number of characters
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
+ r = SQL_SUCCESS_WITH_INFO;
+ }
+ else {
+
+ r = SQL_SUCCESS;
+ to_copy = field_len - read_so_far;
+ }
+
+ if( to_copy > 0 ) {
+
+ bool tried_again = false;
+ do {
+ int ch_space = MultiByteToWideChar( CP_ACP, MB_ERR_INVALID_CHARS, (LPCSTR) field_data, to_copy,
+ (LPWSTR) buffer, to_copy );
+ if( ch_space == 0 ) {
+
+ switch( GetLastError() ) {
+
+ case ERROR_NO_UNICODE_TRANSLATION:
+ // the theory here is the conversion failed because the end of the buffer we provided contained only
+ // half a character at the end
+ if( !tried_again ) {
+ to_copy--;
+ tried_again = true;
+ continue;
+ }
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
+ break;
+ default:
+ SQLSRV_ASSERT( false, "Severe error translating Unicode" );
+ break;
+ }
+
+ return SQL_ERROR;
+ }
+
+ ((WCHAR*)buffer)[ to_copy ] = L'\0';
+ read_so_far += to_copy;
+ break;
+
+ } while( true );
+ }
+ else {
+ reinterpret_cast( buffer )[0] = L'\0';
+ }
+
+ return r;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::to_same_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::to_same_string" );
+
+ SQLRETURN r = SQL_ERROR;
+ unsigned char* row = get_row();
+
+ // Set the amount of space necessary for null characters at the end of the data.
+ SQLSMALLINT extra = 0;
+
+ switch( meta[ field_index ].c_type ) {
+ case SQL_C_WCHAR:
+ extra = sizeof( SQLWCHAR );
+ break;
+ case SQL_C_BINARY:
+ extra = 0;
+ break;
+ case SQL_C_CHAR:
+ extra = sizeof( SQLCHAR );
+ break;
+ default:
+ SQLSRV_ASSERT( false, "Invalid type in get_string_data" );
+ break;
+ }
+
+ SQLCHAR* field_data = NULL;
+
+ if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN );
+ }
+ else {
+
+ field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN );
+ }
+
+ // all fields will be treated as ODBC returns varchar(max) fields:
+ // the entire length of the string is returned the first
+ // call in out_buffer_len. Successive calls return how much is
+ // left minus how much has already been read by previous reads
+ *out_buffer_length = *reinterpret_cast( field_data - sizeof( SQLULEN )) - read_so_far;
+
+ // copy as much as we can into the buffer
+ SQLLEN to_copy;
+ if( buffer_length < *out_buffer_length + extra ) {
+ to_copy = buffer_length - extra;
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
+ r = SQL_SUCCESS_WITH_INFO;
+ }
+ else {
+ r = SQL_SUCCESS;
+ to_copy = *out_buffer_length;
+ }
+
+ SQLSRV_ASSERT( to_copy >= 0, "Negative field length calculated in buffered result set" );
+
+ if( to_copy > 0 ) {
+ memcpy( buffer, field_data + read_so_far, to_copy );
+ read_so_far += to_copy;
+ }
+ if( extra ) {
+ OACR_WARNING_SUPPRESS( 26001, "Buffer length verified above" );
+ memcpy( reinterpret_cast( buffer ) + to_copy, L"\0", extra );
+ }
+
+ return r;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::wide_to_system_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( last_error == NULL, "Pending error for sqlsrv_buffered_results_set::wide_to_system_string" );
+
+ SQLRETURN r = SQL_ERROR;
+ unsigned char* row = get_row();
+
+ SQLCHAR* field_data = NULL;
+ SQLULEN field_len = NULL;
+
+ // if this is the first time called for this field, just convert the entire string to system first then
+ // use that to read from instead of converting chunk by chunk. This is because it's impossible to know
+ // the total length of the string for output_buffer_length without doing the conversion and returning
+ // SQL_NO_TOTAL is not consistent with what our other conversion functions do (system_to_wide_string and
+ // to_same_string).
+
+ if( read_so_far == 0 ) {
+
+ if( meta[ field_index ].length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ field_len = **reinterpret_cast( &row[ meta[ field_index ].offset ] );
+ field_data = *reinterpret_cast( &row[ meta[ field_index ].offset ] ) + sizeof( SQLULEN ) + read_so_far;
+ }
+ else {
+
+ field_len = *reinterpret_cast( &row[ meta[ field_index ].offset ] );
+ field_data = &row[ meta[ field_index ].offset ] + sizeof( SQLULEN ) + read_so_far;
+ }
+
+ BOOL default_char_used = FALSE;
+ char default_char = '?';
+
+ // allocate enough to handle WC -> DBCS conversion if it happens
+ temp_string = reinterpret_cast( sqlsrv_malloc( field_len, sizeof( char ), sizeof(char)));
+ temp_length = WideCharToMultiByte( CP_ACP, 0, (LPCWSTR) field_data, field_len / sizeof(WCHAR),
+ (LPSTR) temp_string.get(), field_len, &default_char, &default_char_used );
+
+ if( temp_length == 0 ) {
+
+ switch( GetLastError() ) {
+
+ case ERROR_NO_UNICODE_TRANSLATION:
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "IMSSP", (SQLCHAR*) "Invalid Unicode translation", -1 );
+ break;
+ default:
+ SQLSRV_ASSERT( false, "Severe error translating Unicode" );
+ break;
+ }
+
+ return SQL_ERROR;
+ }
+ }
+
+ *out_buffer_length = (temp_length - read_so_far);
+
+ SQLLEN to_copy = 0;
+
+ if( (size_t) buffer_length < (temp_length - read_so_far + sizeof(char))) {
+
+ to_copy = buffer_length - sizeof(char);
+ last_error = new ( sqlsrv_malloc( sizeof( sqlsrv_error )))
+ sqlsrv_error( (SQLCHAR*) "01004", (SQLCHAR*) "String data, right truncated", -1 );
+ r = SQL_SUCCESS_WITH_INFO;
+ }
+ else {
+
+ to_copy = (temp_length - read_so_far);
+ r = SQL_SUCCESS;
+ }
+
+ if( to_copy > 0 ) {
+
+ memcpy( buffer, temp_string.get() + read_so_far, to_copy );
+ }
+ SQLSRV_ASSERT( to_copy >= 0, "Invalid field copy length" );
+ OACR_WARNING_SUPPRESS( BUFFER_UNDERFLOW, "Buffer length verified above" );
+ ((SQLCHAR*) buffer)[ to_copy ] = '\0';
+ read_so_far += to_copy;
+
+ return r;
+}
+
+
+SQLRETURN sqlsrv_buffered_result_set::to_binary_string( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ return to_same_string( field_index, buffer, buffer_length, out_buffer_length );
+}
+
+SQLRETURN sqlsrv_buffered_result_set::to_long( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_LONG, "Invlid conversion to long" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( LONG ), "Buffer too small for SQL_C_LONG" ); // technically should ignore this
+
+ unsigned char* row = get_row();
+ LONG* long_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ memcpy( buffer, long_data, sizeof( LONG ));
+ *out_buffer_length = sizeof( LONG );
+
+ return SQL_SUCCESS;
+}
+
+SQLRETURN sqlsrv_buffered_result_set::to_double( SQLSMALLINT field_index, __out void* buffer, SQLLEN buffer_length,
+ __out SQLLEN* out_buffer_length )
+{
+ SQLSRV_ASSERT( meta[ field_index ].c_type == SQL_C_DOUBLE, "Invlid conversion to double" );
+ SQLSRV_ASSERT( buffer_length >= sizeof( double ), "Buffer too small for SQL_C_DOUBLE" ); // technically should ignore this
+
+ unsigned char* row = get_row();
+ double* double_data = reinterpret_cast( &row[ meta[ field_index ].offset ] );
+
+ memcpy( buffer, double_data, sizeof( double ));
+ *out_buffer_length = sizeof( double );
+
+ return SQL_SUCCESS;
+}
+
+namespace {
+
+// called for each row in the cache when the cache is destroyed in the destructor
+void cache_row_dtor( zval* data )
+{
+ row_dtor_closure* cl = reinterpret_cast( Z_PTR_P(data) );
+ BYTE* row = cl->row_data;
+ // don't release this here, since this is called from the destructor of the result_set
+ sqlsrv_buffered_result_set* result_set = cl->results;
+
+ for( SQLSMALLINT i = 0; i < result_set->column_count(); ++i ) {
+
+ if( result_set->col_meta_data(i).length == sqlsrv_buffered_result_set::meta_data::SIZE_UNKNOWN ) {
+
+ void* out_of_row_data = *reinterpret_cast( &row[ result_set->col_meta_data(i).offset ] );
+ sqlsrv_free( out_of_row_data );
+ }
+ }
+
+ sqlsrv_free( row );
+}
+
+SQLPOINTER read_lob_field( sqlsrv_stmt* stmt, SQLUSMALLINT field_index, sqlsrv_buffered_result_set::meta_data& meta,
+ unsigned long mem_used TSRMLS_DC )
+{
+ SQLSMALLINT extra = 0;
+ SQLLEN* output_buffer_len = NULL;
+
+ // Set the amount of space necessary for null characters at the end of the data.
+ switch( meta.c_type ) {
+ case SQL_C_WCHAR:
+ extra = sizeof( SQLWCHAR );
+ break;
+ case SQL_C_BINARY:
+ extra = 0;
+ break;
+ case SQL_C_CHAR:
+ extra = sizeof( SQLCHAR );
+ break;
+ default:
+ SQLSRV_ASSERT( false, "Invalid type in read_lob_field" );
+ break;
+ }
+
+ SQLLEN already_read = 0;
+ SQLLEN to_read = INITIAL_FIELD_STRING_LEN;
+ sqlsrv_malloc_auto_ptr buffer;
+ buffer = static_cast( sqlsrv_malloc( INITIAL_FIELD_STRING_LEN + extra + sizeof( SQLULEN )));
+ SQLRETURN r = SQL_SUCCESS;
+ SQLCHAR state[ SQL_SQLSTATE_BUFSIZE ];
+ SQLLEN last_field_len = 0;
+ bool full_length_returned = false;
+
+ do {
+
+
+ output_buffer_len = reinterpret_cast( buffer.get() );
+ r = core::SQLGetData( stmt, field_index + 1, meta.c_type, buffer.get() + already_read + sizeof( SQLULEN ),
+ to_read - already_read + extra, &last_field_len, false /*handle_warning*/ TSRMLS_CC );
+
+ // if the field is NULL, then return a NULL pointer
+ if( last_field_len == SQL_NULL_DATA ) {
+ return NULL;
+ }
+
+ // if the last read was successful, we're done
+ if( r == SQL_SUCCESS ) {
+ // check to make sure we haven't overflown our memory limit
+ CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
+ SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
+
+ throw core::CoreException();
+ }
+ break;
+ }
+ // else if it wasn't the truncated warning (01004) then we're done
+ else if( r == SQL_SUCCESS_WITH_INFO ) {
+ SQLSMALLINT len;
+ core::SQLGetDiagField( stmt, 1, SQL_DIAG_SQLSTATE, state, SQL_SQLSTATE_BUFSIZE, &len
+ TSRMLS_CC );
+
+ if( !is_truncated_warning( state )) {
+ break;
+ }
+ }
+
+ SQLSRV_ASSERT( SQL_SUCCEEDED( r ), "Unknown SQL error not triggered" );
+
+ // if the type of the field returns the total to be read, we use that and preallocate the buffer
+ if( last_field_len != SQL_NO_TOTAL ) {
+
+ CHECK_CUSTOM_ERROR( mem_used + last_field_len > stmt->buffered_query_limit * 1024, stmt,
+ SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
+
+ throw core::CoreException();
+ }
+
+ already_read += to_read - already_read;
+ to_read = last_field_len;
+ buffer.resize( to_read + extra + sizeof( SQLULEN ));
+ output_buffer_len = reinterpret_cast( buffer.get() );
+ // record the size of the field since we have it available
+ *output_buffer_len = last_field_len;
+ full_length_returned = true;
+ }
+ // otherwise allocate another chunk of memory to read in
+ else {
+ already_read += to_read - already_read;
+ to_read *= 2;
+ CHECK_CUSTOM_ERROR( mem_used + to_read > stmt->buffered_query_limit * 1024, stmt,
+ SQLSRV_ERROR_BUFFER_LIMIT_EXCEEDED, stmt->buffered_query_limit ) {
+
+ throw core::CoreException();
+ }
+ buffer.resize( to_read + extra + sizeof( SQLULEN ));
+ output_buffer_len = reinterpret_cast( buffer.get() );
+ }
+
+ } while( true );
+
+ SQLSRV_ASSERT( output_buffer_len != NULL, "Output buffer not allocated properly" );
+
+ // most LOB field types return the total length in the last_field_len, but some field types such as XML
+ // only return the amount read on the last read
+ if( !full_length_returned ) {
+ *output_buffer_len = already_read + last_field_len;
+ }
+
+ char* return_buffer = buffer;
+ buffer.transferred();
+ return return_buffer;
+}
+
+}
diff --git a/sqlsrv/core_sqlsrv.h b/sqlsrv/core_sqlsrv.h
new file mode 100644
index 00000000..5e09d652
--- /dev/null
+++ b/sqlsrv/core_sqlsrv.h
@@ -0,0 +1,2223 @@
+#ifndef CORE_SQLSRV_H
+#define CORE_SQLSRV_H
+
+//---------------------------------------------------------------------------------------------------------------------------------
+// File: core_sqlsrv.h
+//
+// Contents: Core routines and constants shared by the Microsoft Drivers for PHP for SQL Server
+//
+// Microsoft Drivers 4.0 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.
+//---------------------------------------------------------------------------------------------------------------------------------
+
+//*********************************************************************************************************************************
+// Includes
+//*********************************************************************************************************************************
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef PHP_WIN32
+#define PHP_SQLSRV_API __declspec(dllexport)
+#else
+#define PHP_SQLSRV_API
+#endif
+
+// OACR is an internal Microsoft static code analysis tool
+#if defined(OACR)
+#include
+OACR_WARNING_PUSH
+OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW, "Third party code." )
+OACR_WARNING_DISABLE( INDEX_NEGATIVE, "Third party code." )
+OACR_WARNING_DISABLE( UNANNOTATED_BUFFER, "Third party code." )
+OACR_WARNING_DISABLE( INDEX_UNDERFLOW, "Third party code." )
+OACR_WARNING_DISABLE( REALLOCLEAK, "Third party code." )
+OACR_WARNING_DISABLE( ALLOC_SIZE_OVERFLOW_WITH_ACCESS, "Third party code." )
+#else
+// define to eliminate static analysis hints in the code
+#define OACR_WARNING_SUPPRESS( warning, msg )
+#endif
+
+extern "C" {
+
+#pragma warning(push)
+#pragma warning( disable: 4005 4100 4127 4142 4244 4505 4530 )
+
+#ifdef ZTS
+#include "TSRM.h"
+#endif
+
+#if _MSC_VER >= 1400
+// typedef and macro to prevent a conflict between php.h and ws2tcpip.h.
+// php.h defines this constant as unsigned int which causes a compile error
+// in ws2tcpip.h. Fortunately php.h allows an override by defining
+// HAVE_SOCKLEN_T. Since ws2tcpip.h isn't included until later, we define
+// socklen_t here and override the php.h version.
+typedef int socklen_t;
+#define HAVE_SOCKLEN_T
+#endif
+
+#include "php.h"
+#include "php_globals.h"
+#include "php_ini.h"
+#include "ext/standard/php_standard.h"
+#include "ext/standard/info.h"
+
+#pragma warning(pop)
+
+#if ZEND_DEBUG
+// debug build causes warning C4505 to pop up from the Zend header files
+#pragma warning( disable: 4505 )
+#endif
+
+} // extern "C"
+
+#if defined(OACR)
+OACR_WARNING_POP
+#endif
+
+#include
+#include
+
+#if !defined(WC_ERR_INVALID_CHARS)
+// imported from winnls.h as it isn't included by 5.3.0
+#define WC_ERR_INVALID_CHARS 0x00000080 // error for invalid chars
+#endif
+
+// PHP defines inline as __forceinline, which in debug mode causes a warning to be emitted when
+// we use std::copy, which causes compilation to fail since we compile with warnings as errors.
+#if defined(ZEND_DEBUG) && defined(inline)
+#undef inline
+#endif
+
+#include
+#include