From 91c962e4615101e14be4c720fc386878ddb598a4 Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 28 Nov 2011 15:08:56 +0200 Subject: Implement statements; add support for tracing --- odb/mssql/auto-handle.hxx | 2 +- odb/mssql/binding.hxx | 45 +++ odb/mssql/connection.cxx | 21 +- odb/mssql/connection.hxx | 15 +- odb/mssql/database.hxx | 8 +- odb/mssql/error.cxx | 3 +- odb/mssql/error.hxx | 2 +- odb/mssql/exceptions.hxx | 2 +- odb/mssql/makefile | 2 + odb/mssql/mssql-fwd.hxx | 73 +++- odb/mssql/mssql-types.hxx | 133 +++++++ odb/mssql/mssql.hxx | 32 +- odb/mssql/statement.cxx | 805 +++++++++++++++++++++++++++++++++++++++++ odb/mssql/statement.hxx | 228 ++++++++++++ odb/mssql/tracer.cxx | 62 ++++ odb/mssql/tracer.hxx | 63 ++++ odb/mssql/transaction-impl.cxx | 9 - odb/mssql/transaction.hxx | 6 +- 18 files changed, 1465 insertions(+), 46 deletions(-) create mode 100644 odb/mssql/binding.hxx create mode 100644 odb/mssql/mssql-types.hxx create mode 100644 odb/mssql/statement.cxx create mode 100644 odb/mssql/statement.hxx create mode 100644 odb/mssql/tracer.cxx create mode 100644 odb/mssql/tracer.hxx diff --git a/odb/mssql/auto-handle.hxx b/odb/mssql/auto-handle.hxx index d545629..56f2fc2 100644 --- a/odb/mssql/auto-handle.hxx +++ b/odb/mssql/auto-handle.hxx @@ -8,8 +8,8 @@ #include -#include #include +#include #include diff --git a/odb/mssql/binding.hxx b/odb/mssql/binding.hxx new file mode 100644 index 0000000..f8f4fc4 --- /dev/null +++ b/odb/mssql/binding.hxx @@ -0,0 +1,45 @@ +// file : odb/mssql/binding.hxx +// author : Constantin Michael +// copyright : Copyright (c) 2005-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#ifndef ODB_MSSQL_BINDING_HXX +#define ODB_MSSQL_BINDING_HXX + +#include + +#include // std::size_t + +#include +#include + +#include + +namespace odb +{ + namespace mssql + { + class LIBODB_MSSQL_EXPORT binding + { + public: + typedef mssql::bind bind_type; + + binding (bind_type* b, std::size_t n) + : bind (b), count (n), version (0) + { + } + + bind_type* bind; + std::size_t count; + std::size_t version; + + private: + binding (const binding&); + binding& operator= (const binding&); + }; + } +} + +#include + +#endif // ODB_MSSQL_BINDING_HXX diff --git a/odb/mssql/connection.cxx b/odb/mssql/connection.cxx index 3ba10ba..1609b80 100644 --- a/odb/mssql/connection.cxx +++ b/odb/mssql/connection.cxx @@ -25,8 +25,9 @@ namespace odb connection (database_type& db) : odb::connection (db), db_ (db), - state_ (state_disconnected) + state_ (state_disconnected), // statement_cache_ (new statement_cache_type (*this)) + long_buffer_ (0) { SQLRETURN r; @@ -46,7 +47,7 @@ namespace odb // r = SQLSetConnectAttr (handle_, SQL_ATTR_AUTOCOMMIT, - SQL_AUTOCOMMIT_OFF, + (SQLPOINTER) SQL_AUTOCOMMIT_OFF, 0); if (!SQL_SUCCEEDED (r)) @@ -55,6 +56,16 @@ namespace odb // translate_error (r, handle_, SQL_HANDLE_DBC); + // Enable Multiple Active Result Sets (MARS). + // + r = SQLSetConnectAttr (handle_, + SQL_COPT_SS_MARS_ENABLED, + (SQLPOINTER) SQL_MARS_ENABLED_YES, + SQL_IS_UINTEGER); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, handle_, SQL_HANDLE_DBC); + // Connect. // { @@ -84,8 +95,9 @@ namespace odb : odb::connection (db), db_ (db), handle_ (handle), - state_ (state_connected) + state_ (state_connected), // statement_cache_ (new statement_cache_type (*this)) + long_buffer_ (0) { } @@ -109,8 +121,6 @@ namespace odb unsigned long long connection:: execute (const char* s, std::size_t n) { - /* - @@ { odb::tracer* t; if ((t = transaction_tracer ()) || @@ -121,7 +131,6 @@ namespace odb t->execute (*this, str.c_str ()); } } - */ SQLRETURN r; diff --git a/odb/mssql/connection.hxx b/odb/mssql/connection.hxx index 1a6cd24..c38692b 100644 --- a/odb/mssql/connection.hxx +++ b/odb/mssql/connection.hxx @@ -15,14 +15,15 @@ #include #include +#include #include +#include #include #include -//#include +#include #include #include -#include #include @@ -66,7 +67,6 @@ namespace odb // SQL statement tracing. // public: - /* typedef mssql::tracer tracer_type; void @@ -82,7 +82,6 @@ namespace odb } using odb::connection::tracer; - */ public: bool @@ -112,6 +111,12 @@ namespace odb } */ + details::buffer& + long_buffer () + { + return long_buffer_; + } + private: connection (const connection&); connection& operator= (const connection&); @@ -133,6 +138,8 @@ namespace odb auto_handle direct_stmt_; //std::auto_ptr statement_cache_; + + details::buffer long_buffer_; }; } } diff --git a/odb/mssql/database.hxx b/odb/mssql/database.hxx index cabdbb4..5bb5e4f 100644 --- a/odb/mssql/database.hxx +++ b/odb/mssql/database.hxx @@ -6,8 +6,6 @@ #ifndef ODB_MSSQL_DATABASE_HXX #define ODB_MSSQL_DATABASE_HXX -//@@ disabled functionality - #include #include @@ -16,13 +14,13 @@ #include +#include #include #include -//#include +#include #include #include #include -#include #include @@ -211,7 +209,6 @@ namespace odb // SQL statement tracing. // public: - /* typedef mssql::tracer tracer_type; void @@ -227,7 +224,6 @@ namespace odb } using odb::database::tracer; - */ public: virtual diff --git a/odb/mssql/error.cxx b/odb/mssql/error.cxx index dffe4cf..613f346 100644 --- a/odb/mssql/error.cxx +++ b/odb/mssql/error.cxx @@ -87,13 +87,12 @@ namespace odb 0, &msg_size); - string s (sqlstate); - if (r == SQL_NO_DATA) break; else if (SQL_SUCCEEDED (r)) { code nc; + string s (sqlstate); if (s == "40001") // Serialization failure (native code 1205). nc = code_deadlock; diff --git a/odb/mssql/error.hxx b/odb/mssql/error.hxx index 126f7d9..fd7cfb6 100644 --- a/odb/mssql/error.hxx +++ b/odb/mssql/error.hxx @@ -8,9 +8,9 @@ #include +#include #include #include // connection -#include #include #include diff --git a/odb/mssql/exceptions.hxx b/odb/mssql/exceptions.hxx index 321a41d..3e7a845 100644 --- a/odb/mssql/exceptions.hxx +++ b/odb/mssql/exceptions.hxx @@ -13,8 +13,8 @@ #include -#include #include +#include #include namespace odb diff --git a/odb/mssql/makefile b/odb/mssql/makefile index 63aa888..4758abf 100644 --- a/odb/mssql/makefile +++ b/odb/mssql/makefile @@ -13,6 +13,8 @@ connection-factory.cxx \ database.cxx \ error.cxx \ exceptions.cxx \ +statement.cxx \ +tracer.cxx \ transaction.cxx \ transaction-impl.cxx diff --git a/odb/mssql/mssql-fwd.hxx b/odb/mssql/mssql-fwd.hxx index b4afbb6..3579403 100644 --- a/odb/mssql/mssql-fwd.hxx +++ b/odb/mssql/mssql-fwd.hxx @@ -8,25 +8,78 @@ #include +#include // std::size_t + // Forward declaration for some of the types defined in sqltypes.h or // sqlncli.h. This allows us to avoid having to include these files // in public headers. // #ifdef _WIN32 -typedef long SQLINTEGER; -typedef unsigned long SQLUINTEGER; + +// Keep consistent with Windows ODBC headers. +// + +typedef long SQLINTEGER; +typedef unsigned long SQLUINTEGER; + +#ifdef _WIN64 +typedef __int64 SQLLEN; +typedef unsigned __int64 SQLULEN; +#else +#ifndef SQLLEN +typedef SQLINTEGER SQLLEN; +typedef SQLUINTEGER SQLULEN; +#endif +#endif + +#else // _WIN32 + +// Keep consistent with unixODBC headers. +// + +template +struct odbc_types; + +template <> +struct odbc_types<4> +{ + typedef long integer; + typedef unsigned long uinteger; + + typedef integer len; + typedef uinteger ulen; +}; + +template <> +struct odbc_types<8> +{ + typedef int integer; + typedef unsigned int uinteger; + + typedef long len; + typedef unsigned long ulen; +}; + +typedef odbc_types::integer SQLINTEGER; +typedef odbc_types::uinteger SQLUINTEGER; + +#ifndef SQLLEN +typedef odbc_types::len SQLLEN; +typedef odbc_types::ulen SQLULEN; #endif -typedef short SQLSMALLINT; -typedef unsigned short SQLUSMALLINT; +#endif // _WIN32 + +typedef short SQLSMALLINT; +typedef unsigned short SQLUSMALLINT; -typedef SQLSMALLINT SQLRETURN; +typedef SQLSMALLINT SQLRETURN; -typedef void* SQLHANDLE; -typedef SQLHANDLE SQLHENV; -typedef SQLHANDLE SQLHDBC; -typedef SQLHANDLE SQLHSTMT; -typedef SQLHANDLE SQLHDESC; +typedef void* SQLHANDLE; +typedef SQLHANDLE SQLHENV; +typedef SQLHANDLE SQLHDBC; +typedef SQLHANDLE SQLHSTMT; +typedef SQLHANDLE SQLHDESC; // If you get a redefinition error or warning for one of these macros, // then that means you included this header (or one that includes it), diff --git a/odb/mssql/mssql-types.hxx b/odb/mssql/mssql-types.hxx new file mode 100644 index 0000000..2ce7940 --- /dev/null +++ b/odb/mssql/mssql-types.hxx @@ -0,0 +1,133 @@ +// file : odb/mssql/mssql-types.hxx +// author : Constantin Michael +// copyright : Copyright (c) 2005-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#ifndef ODB_MSSQL_MSSQL_TYPES_HXX +#define ODB_MSSQL_MSSQL_TYPES_HXX + +#include + +#include // std::size_t + +#include +#include + +namespace odb +{ + namespace mssql + { + enum chunk_type + { + first_chunk, + next_chunk, + last_chunk, + one_chunk, + null_chunk + }; + + typedef void (*param_callback_type) ( + const void* context, // User context. + std::size_t* position, // Position context. Am implementation is free + // to use this to track position information. It + // is initialized to zero before the first call. + const void** buffer, // [out] Buffer contaning the data. + std::size_t* size, // [out] Data size. + chunk_type*, // [out] The position of this chunk of data. + void* temp_buffer, // A temporary buffer that may be used by the + // implementation. + std::size_t capacity); // Capacity of the temporary buffer. + + typedef void (*result_callback_type) ( + void* context, // User context. + std::size_t* position, // Position context. Am implementation is free + // to use this to track position information. It + // is initialized to zero before the first call. + void** buffer, // [out] Buffer to copy the data to. + std::size_t* size, // [in/out] In: amount of data copied into the + // buffer after the previous call. Out: capacity + // of the buffer. + chunk_type, // The position of this chunk; first_chunk means + // this is the first call, last_chunk means there + // is no more data, null_chunk means this value is + // NULL, and one_chunk means the value is empty + // (in this case *size is 0). + std::size_t size_left, // Contains the amount of data left or 0 if this + // information is not available. + void* temp_buffer, // A temporary buffer that may be used by the + // implementation. + std::size_t capacity); // Capacity of the temporary buffer. + + struct long_callback + { + union + { + param_callback_type param; + result_callback_type result; + } callback; + + union + { + const void* param; + void* result; + } context; + }; + + struct bind + { + // This enumeration identifies the possible buffer types that can be + // bound to a parameter or result. In most cases, these map directly + // to SQL_XXX/SQL_C_XXX codes. + // + enum buffer_type + { + bit, // Buffer is a 1-byte integer. + tinyint, // Buffer is a 1-byte integer. + smallint, // Buffer is a 2-byte integer. + int_, // Buffer is a 4-byte integer. + bigint, // Buffer is an 8-byte integer. + + /* + numeric, // Buffer is an SQL_NUMERIC_STRUCT. + + smallmoney, // Buffer is a 4-byte integer (*10,000 value). + money, // Buffer is an 8-byte integer (*10,000 value). + */ + + float4, // Buffer is a float. + float8, // Buffer is a double. + + string, // Buffer is a char array. + long_string, // Buffer is a long_callback. + + nstring, // Buffer is a wchar_t (2-byte) array. + long_nstring, // Buffer is a long_callback. + + binary, // Buffer is a byte array. + long_binary, // Buffer is a long_callback. + + /* + date, // Buffer is an SQL_DATE_STRUCT. + time, // Buffer is an SQL_SS_TIME2_STRUCT. + datetime, // Buffer is an SQL_TIMESTAMP_STRUCT. + datetimeoffset, // Buffer is an SQL_SS_TIMESTAMPOFFSET_STRUCT. + + uuid, // Buffer is a 16-byte array (or SQLGUID). + rowversion, // Buffer is an 8-byte array. + */ + + last // Used as an end of list marker. + }; + + buffer_type type; // The buffer type. + void* buffer; // The buffer. For long data this is a long_callback. + SQLLEN* size_ind; // Pointer to the size/inidicator variable. + SQLLEN capacity; // Buffer capacity. For string/binary parameters + // this value is also used as maximum column size. + }; + } +} + +#include + +#endif // ODB_MSSQL_MSSQL_TYPES_HXX diff --git a/odb/mssql/mssql.hxx b/odb/mssql/mssql.hxx index 80cff65..8c455ba 100644 --- a/odb/mssql/mssql.hxx +++ b/odb/mssql/mssql.hxx @@ -24,7 +24,37 @@ #include // Standard ODBC. //#define _SQLNCLI_ODBC_ -//#include // SQL Server native client driver specifics. +//#include // SQL Server Native Client driver specifics. + +// Instead of having a dependency on (which, BTW, is not +// currently available for the Linux version of the Native Client), +// we are going to provide the few definitions that we need ourselves. +// +#ifndef SQL_SS_LENGTH_UNLIMITED +# define SQL_SS_LENGTH_UNLIMITED 0 +#endif + +#ifndef SQL_COPT_SS_BASE +# define SQL_COPT_SS_BASE 1200 +#endif + +#ifndef SQL_COPT_SS_MARS_ENABLED +# define SQL_COPT_SS_MARS_ENABLED (SQL_COPT_SS_BASE + 24) +#endif + +#ifndef SQL_MARS_ENABLED_NO +# define SQL_MARS_ENABLED_NO 0L +# define SQL_MARS_ENABLED_YES 1L +#endif + +// unixODBC doesn't define SQL_PARAM_DATA_AVAILABLE even though it +// claims ODBC version 3.80. +// +#if ODBCVER >= 0x0380 +# ifndef SQL_PARAM_DATA_AVAILABLE +# define SQL_PARAM_DATA_AVAILABLE 101 +# endif +#endif #include diff --git a/odb/mssql/statement.cxx b/odb/mssql/statement.cxx new file mode 100644 index 0000000..8e61d82 --- /dev/null +++ b/odb/mssql/statement.cxx @@ -0,0 +1,805 @@ +// file : odb/mssql/statement.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2005-2011 Code Synthesis Tools CC +// license : GNU GPL v2; see accompanying LICENSE file + +#include // std::strlen +#include + +#include + +#include +#include +#include +#include +#include +#include + +using namespace std; + +namespace odb +{ + namespace mssql + { + // Mapping of bind::buffer_type to SQL_* SQL types. + // + static const SQLSMALLINT sql_type_lookup [bind::last] = + { + SQL_BIT, // bind::bit + SQL_TINYINT, // bind::tinyint + SQL_SMALLINT, // bind::smallint + SQL_INTEGER, // bind::integer + SQL_BIGINT, // bind::bigint + + SQL_REAL, // bind::float4 + SQL_DOUBLE, // bind::float8 + + SQL_VARCHAR, // bind::string + SQL_VARCHAR, // bind::long_string + + SQL_WVARCHAR, // bind::nstring + SQL_WVARCHAR, // bind::long_nstring + + SQL_VARBINARY, // bind::binary + SQL_VARBINARY // bind::long_binary + }; + + // Mapping of bind::buffer_type to SQL_C_* C types. + // + static const SQLSMALLINT c_type_lookup [bind::last] = + { + SQL_C_BIT, // bind::bit + SQL_C_UTINYINT, // bind::tinyint + SQL_C_SSHORT, // bind::smallint + SQL_C_SLONG, // bind::integer + SQL_C_SBIGINT, // bind::bigint + + SQL_C_FLOAT, // bind::float4 + SQL_C_DOUBLE, // bind::float8 + + SQL_C_CHAR, // bind::string + SQL_C_CHAR, // bind::long_string + + SQL_C_WCHAR, // bind::nstring + SQL_C_WCHAR, // bind::long_nstring + + SQL_C_BINARY, // bind::binary + SQL_C_BINARY // bind::long_binary + }; + + // + // statement + // + + statement:: + statement (connection& conn, const string& text) + : conn_ (conn), text_copy_ (text), text_ (text_copy_.c_str ()) + { + init (text_copy_.size ()); + } + + statement:: + statement (connection& conn, const char* text, bool copy) + : conn_ (conn) + { + size_t n; + + if (copy) + { + text_copy_ = text; + text_ = text_copy_.c_str (); + n = text_copy_.size (); + } + else + { + text_ = text; + n = strlen (text_); + } + + init (n); + } + + void statement:: + init (size_t text_size) + { + SQLRETURN r; + + // Allocate the handle. + // + { + SQLHANDLE h; + r = SQLAllocHandle (SQL_HANDLE_STMT, conn_.handle (), &h); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_); + + stmt_.reset (h); + } + + // Disable escape sequences. + // + r = SQLSetStmtAttr (stmt_, + SQL_ATTR_NOSCAN, + (SQLPOINTER) SQL_NOSCAN_OFF, + 0); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + // Prepare the statement. + // + r = SQLPrepare (stmt_, (SQLCHAR*) text_, (SQLINTEGER) text_size); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->prepare (conn_, *this); + } + } + + statement:: + ~statement () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->deallocate (conn_, *this); + } + } + + const char* statement:: + text () const + { + return text_; + } + + void statement:: + bind_param (bind* b, size_t n) + { + SQLRETURN r; + + n++; // Parameters are counted from 1. + + for (size_t i (1); i < n; ++i, ++b) + { + SQLULEN col_size; + SQLPOINTER buf; + + switch (b->type) + { + case bind::long_string: + case bind::long_binary: + { + buf = (SQLPOINTER) b; + col_size = b->capacity != 0 + ? (SQLULEN) b->capacity + : SQL_SS_LENGTH_UNLIMITED; + break; + } + case bind::long_nstring: + { + buf = (SQLPOINTER) b; + col_size = b->capacity != 0 + ? (SQLULEN) b->capacity / 2 // In characters, not bytes. + : SQL_SS_LENGTH_UNLIMITED; + break; + } + case bind::string: + case bind::binary: + { + buf = (SQLPOINTER) b->buffer; + col_size = (SQLULEN) b->capacity; + break; + } + case bind::nstring: + { + buf = (SQLPOINTER) b->buffer; + col_size = (SQLULEN) b->capacity / 2; // In characters, not bytes. + break; + } + default: + { + buf = (SQLPOINTER) b->buffer; + break; + } + } + + r = SQLBindParameter ( + stmt_, + (SQLUSMALLINT) i, + SQL_PARAM_INPUT, + c_type_lookup[b->type], + sql_type_lookup[b->type], + col_size, + 0, // decimal digits + buf, + 0, // buffer capacity (shouldn't be needed for input parameters) + b->size_ind); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + } + + size_t statement:: + bind_result (bind* b, size_t n) + { + SQLRETURN r; + + size_t i (0); + for (bind* end (b + n); b != end; ++b) + { + switch (b->type) + { + case bind::long_string: + case bind::long_nstring: + case bind::long_binary: + { + // Long data is not bound. + // + continue; + } + default: + { + break; + } + } + + r = SQLBindCol (stmt_, + (SQLUSMALLINT) (i + 1), // Results are counted from 1. + c_type_lookup[b->type], + (SQLPOINTER) b->buffer, + b->capacity, + b->size_ind); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + ++i; + } + + return i; + } + + SQLRETURN statement:: + execute () + { + { + odb::tracer* t; + if ((t = conn_.transaction_tracer ()) || + (t = conn_.tracer ()) || + (t = conn_.database ().tracer ())) + t->execute (conn_, *this); + } + + SQLRETURN r (SQLExecute (stmt_)); + + if (r == SQL_NEED_DATA) + { + details::buffer& tmp_buf (conn_.long_buffer ()); + + if (tmp_buf.capacity () == 0) + tmp_buf.capacity (4096); + + bind* b; + for (;;) + { + r = SQLParamData (stmt_, (SQLPOINTER*) &b); + + // If we get anything other than SQL_NEED_DATA, then this is + // the return code of SQLExecute(). + // + if (r != SQL_NEED_DATA) + break; + + // See if we have a NULL value. + // + if (*b->size_ind == SQL_NULL_DATA) + { + r = SQLPutData (stmt_, 0, SQL_NULL_DATA); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + else + { + long_callback& cb (*static_cast (b->buffer)); + + size_t position (0); + for (;;) + { + size_t n; + const void* buf; + chunk_type chunk; + + cb.callback.param ( + cb.context.param, + &position, + &buf, + &n, + &chunk, + tmp_buf.data (), + tmp_buf.capacity ()); + + r = SQLPutData ( + stmt_, + (SQLPOINTER) buf, + chunk != null_chunk ? (SQLLEN) n : SQL_NULL_DATA); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + if (chunk == one_chunk || + chunk == last_chunk || + chunk == null_chunk) + break; + } + } + } + } + + return r; + } + + void statement:: + stream_result (bind* b, size_t i, size_t n) + { + details::buffer& tmp_buf (conn_.long_buffer ()); + + if (tmp_buf.capacity () == 0) + tmp_buf.capacity (4096); + + SQLRETURN r; + + for (bind* end (b + n); b != end; ++b) + { + bool char_data; + + switch (b->type) + { + case bind::long_string: + case bind::long_nstring: + { + char_data = true; + break; + } + case bind::long_binary: + { + char_data = false; + break; + } + default: + { + continue; // Not long data. + } + } + + long_callback& cb (*static_cast (b->buffer)); + + // First determine if the value is NULL as well as try to + // get the total data size. + // + SQLLEN si; + r = SQLGetData (stmt_, + (SQLUSMALLINT) (i + 1), + c_type_lookup[b->type], + tmp_buf.data (), // Dummy value. + 0, + &si); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + void* buf; + size_t size (0); + size_t position (0); + size_t size_left (si == SQL_NO_TOTAL ? 0 : static_cast (si)); + + chunk_type c (si == SQL_NULL_DATA + ? null_chunk + : (si == 0 ? one_chunk : first_chunk)); + + for (;;) + { + cb.callback.result ( + cb.context.result, + &position, + &buf, + &size, + c, + size_left, + tmp_buf.data (), + tmp_buf.capacity ()); + + if (c == last_chunk || c == one_chunk || c == null_chunk) + break; + + // SQLGetData() can keep returning SQL_SUCCESS_WITH_INFO (truncated) + // with SQL_NO_TOTAL for all the calls except the last one. For the + // last call we should get SQL_SUCCESS and the size_indicator should + // contain a valid value. + // + r = SQLGetData (stmt_, + (SQLUSMALLINT) (i + 1), + c_type_lookup[b->type], + (SQLPOINTER) buf, + (SQLLEN) size, + &si); + + if (r == SQL_SUCCESS) + { + assert (si != SQL_NO_TOTAL); + + // Actual amount of data copied to the buffer (appears not to + // include the NULL teminator). + // + size = static_cast (si); + c = last_chunk; + } + else if (r == SQL_SUCCESS_WITH_INFO) + { + if (char_data) + size--; // NULL terminator. + + c = next_chunk; + } + else + translate_error (r, conn_, stmt_); + + // Update the total. + // + if (size_left != 0) + size_left -= size; + } + + ++i; + } + } + + // + // select_statement + // + + select_statement:: + select_statement (connection& conn, + const string& t, + binding& param, + binding& result) + : statement (conn, t), result_ (result), executed_ (false) + { + bind_param (param.bind, param.count); + first_long_ = bind_result (result.bind, result.count); + } + + select_statement:: + select_statement (connection& conn, + const char* t, + binding& param, + binding& result, + bool ct) + : statement (conn, t, ct), result_ (result), executed_ (false) + { + bind_param (param.bind, param.count); + first_long_ = bind_result (result.bind, result.count); + } + + select_statement:: + select_statement (connection& conn, const string& t, binding& result) + : statement (conn, t), result_ (result), executed_ (false) + { + first_long_ = bind_result (result.bind, result.count); + } + + select_statement:: + select_statement (connection& conn, + const char* t, + binding& result, + bool ct) + : statement (conn, t, ct), result_ (result), executed_ (false) + { + first_long_ = bind_result (result.bind, result.count); + } + + select_statement:: + ~select_statement () + { + if (executed_) + { + try + { + free_result (); + } + catch (...) + { + } + } + } + + void select_statement:: + execute () + { + if (executed_) + free_result (); + + SQLRETURN r (statement::execute ()); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + executed_ = true; + } + + select_statement::result select_statement:: + fetch () + { + SQLRETURN r (SQLFetch (stmt_)); + + if (r == SQL_NO_DATA) + return no_data; + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + return success; + } + + void select_statement:: + free_result () + { + if (executed_) + { + // If we cannot close the cursor, there is no point in trying again. + // + executed_ = false; + + SQLRETURN r (SQLCloseCursor (stmt_)); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + } + + // + // insert_statement + // + + insert_statement:: + ~insert_statement () + { + } + + insert_statement:: + insert_statement (connection& conn, + const string& t, + binding& param, + bool returning) + : statement (conn, t), returning_ (returning) + { + bind_param (param.bind, param.count); + + if (returning) + init_result (); + } + + insert_statement:: + insert_statement (connection& conn, + const char* t, + binding& param, + bool returning, + bool ct) + : statement (conn, t, ct), returning_ (returning) + { + bind_param (param.bind, param.count); + + if (returning) + init_result (); + } + + void insert_statement:: + init_result () + { + SQLRETURN r ( + SQLBindCol (stmt_, + 1, + SQL_C_SBIGINT, + (SQLPOINTER) &id_, + sizeof (id_), + &id_size_ind_)); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + + bool insert_statement:: + execute () + { + SQLRETURN r (statement::execute ()); + + if (!SQL_SUCCEEDED (r)) + { + // Translate the integrity contraint violation (SQLSTATE 23000) + // to the flase return value. This code is similar to that found + // in translate_error(). + // + char sqlstate[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER native_code; + SQLSMALLINT msg_size; + + bool cv (false); + + for (SQLSMALLINT i (1);; ++i) + { + SQLRETURN r (SQLGetDiagRec (SQL_HANDLE_STMT, + stmt_, + i, + (SQLCHAR*) sqlstate, + &native_code, + 0, + 0, + &msg_size)); + + if (r == SQL_NO_DATA) + break; + else if (SQL_SUCCEEDED (r)) + { + string s (sqlstate); + + if (s == "23000") // Integrity contraint violation. + cv = true; + else if (s != "01000") // General warning. + { + // Some other code. + // + cv = false; + break; + } + } + else + { + // SQLGetDiagRec() failure. + // + cv = false; + break; + } + } + + if (cv) + return false; + else + translate_error (r, conn_, stmt_); + } + + // Fetch the row containing the id if this statement is returning. + // + if (returning_) + { + r = SQLFetch (stmt_); + + if (r != SQL_NO_DATA && !SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + { + SQLRETURN r (SQLCloseCursor (stmt_)); // Don't overwrite r. + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + + if (r == SQL_NO_DATA) + throw database_exception ( + 0, + "?????", + "result set expected from a statement with the OUTPUT clause"); + } + + return true; + } + + // + // update_statement + // + + update_statement:: + ~update_statement () + { + } + + update_statement:: + update_statement (connection& conn, const string& t, binding& param) + : statement (conn, t) + { + bind_param (param.bind, param.count); + } + + update_statement:: + update_statement (connection& conn, + const char* t, + binding& param, + bool ct) + : statement (conn, t, ct) + { + bind_param (param.bind, param.count); + } + + unsigned long long update_statement:: + execute () + { + SQLRETURN r (statement::execute ()); + + // SQL_NO_DATA indicates that the statement hasn't affected any rows. + // + if (r == SQL_NO_DATA) + return 0; + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + // Get the number of affected rows. + // + SQLLEN rows; + r = SQLRowCount (stmt_, &rows); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + return static_cast (rows); + } + + // + // delete_statement + // + + delete_statement:: + ~delete_statement () + { + } + + delete_statement:: + delete_statement (connection& conn, const string& t, binding& param) + : statement (conn, t) + { + bind_param (param.bind, param.count); + } + + delete_statement:: + delete_statement (connection& conn, + const char* t, + binding& param, + bool ct) + : statement (conn, t, ct) + { + bind_param (param.bind, param.count); + } + + unsigned long long delete_statement:: + execute () + { + SQLRETURN r (statement::execute ()); + + // SQL_NO_DATA indicates that the statement hasn't affected any rows. + // + if (r == SQL_NO_DATA) + return 0; + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + // Get the number of affected rows. + // + SQLLEN rows; + r = SQLRowCount (stmt_, &rows); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + return static_cast (rows); + } + } +} diff --git a/odb/mssql/statement.hxx b/odb/mssql/statement.hxx new file mode 100644 index 0000000..15e64a0 --- /dev/null +++ b/odb/mssql/statement.hxx @@ -0,0 +1,228 @@ +// file : odb/mssql/statement.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2005-2011 Code Synthesis Tools CC +// license : GNU GPL v2; see accompanying LICENSE file + +#ifndef ODB_MSSQL_STATEMENT_HXX +#define ODB_MSSQL_STATEMENT_HXX + +#include + +#include +#include // std::size_t + +#include +#include + +#include +#include +#include +#include + +#include + +namespace odb +{ + namespace mssql + { + class connection; + + class LIBODB_MSSQL_EXPORT statement: public odb::statement + { + public: + virtual + ~statement () = 0; + + SQLHSTMT + handle () const + { + return stmt_; + } + + virtual const char* + text () const; + + protected: + statement (connection&, const std::string& text); + statement (connection&, const char* text, bool copy_text); + + private: + void + init (std::size_t text_size); + + protected: + void + bind_param (bind*, std::size_t count); + + std::size_t + bind_result (bind*, std::size_t count); + + SQLRETURN + execute (); + + void + stream_result (bind*, std::size_t start, std::size_t count); + + protected: + connection& conn_; + std::string text_copy_; + const char* text_; + auto_handle stmt_; + }; + + class LIBODB_MSSQL_EXPORT select_statement: public statement + { + public: + virtual + ~select_statement (); + + // While the long data columns can appear in any order in the + // result binding, they should appear last in the statement + // text. + // + select_statement (connection& conn, + const std::string& text, + binding& param, + binding& result); + + select_statement (connection& conn, + const char* text, + binding& param, + binding& result, + bool copy_text = true); + + select_statement (connection& conn, + const std::string& text, + binding& result); + + select_statement (connection& conn, + const char* text, + binding& result, + bool copy_text = true); + + enum result + { + success, + no_data + }; + + void + execute (); + + result + fetch (); + + void + stream_result () + { + if (first_long_ != result_.count) + statement::stream_result (result_.bind, first_long_, result_.count); + } + + void + free_result (); + + private: + select_statement (const select_statement&); + select_statement& operator= (const select_statement&); + + private: + binding& result_; + std::size_t first_long_; // First long data column. + bool executed_; + }; + + class LIBODB_MSSQL_EXPORT insert_statement: public statement + { + public: + virtual + ~insert_statement (); + + insert_statement (connection& conn, + const std::string& text, + binding& param, + bool returning); + + insert_statement (connection& conn, + const char* text, + binding& param, + bool returning, + bool copy_text = true); + + // Return true if successful and false if the row is a duplicate. + // All other errors are reported by throwing exceptions. + // + bool + execute (); + + unsigned long long + id () + { + return id_; + } + + private: + insert_statement (const insert_statement&); + insert_statement& operator= (const insert_statement&); + + private: + void + init_result (); + + private: + bool returning_; + unsigned long long id_; + SQLLEN id_size_ind_; + }; + + class LIBODB_MSSQL_EXPORT update_statement: public statement + { + public: + virtual + ~update_statement (); + + update_statement (connection& conn, + const std::string& text, + binding& param); + + update_statement (connection& conn, + const char* text, + binding& param, + bool copy_text = true); + + unsigned long long + execute (); + + private: + update_statement (const update_statement&); + update_statement& operator= (const update_statement&); + }; + + class LIBODB_MSSQL_EXPORT delete_statement: public statement + { + public: + virtual + ~delete_statement (); + + delete_statement (connection& conn, + const std::string& text, + binding& param); + + delete_statement (connection& conn, + const char* text, + binding& param, + bool copy_text = true); + + unsigned long long + execute (); + + private: + delete_statement (const delete_statement&); + delete_statement& operator= (const delete_statement&); + }; + } +} + +#include + +#endif // ODB_MSSQL_STATEMENT_HXX diff --git a/odb/mssql/tracer.cxx b/odb/mssql/tracer.cxx new file mode 100644 index 0000000..023d192 --- /dev/null +++ b/odb/mssql/tracer.cxx @@ -0,0 +1,62 @@ +// file : odb/mssql/tracer.cxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#include +#include +#include + +namespace odb +{ + namespace mssql + { + tracer:: + ~tracer () + { + } + + void tracer:: + prepare (connection&, const statement&) + { + } + + void tracer:: + execute (connection& c, const statement& s) + { + execute (c, s.text ()); + } + + void tracer:: + deallocate (connection&, const statement&) + { + } + + void tracer:: + prepare (odb::connection& c, const odb::statement& s) + { + prepare (static_cast (c), + static_cast (s)); + } + + void tracer:: + execute (odb::connection& c, const odb::statement& s) + { + execute (static_cast (c), + static_cast (s)); + } + + void tracer:: + execute (odb::connection& c, const char* s) + { + execute (static_cast (c), s); + } + + void tracer:: + deallocate (odb::connection& c, const odb::statement& s) + { + deallocate (static_cast (c), + static_cast (s)); + } + } +} diff --git a/odb/mssql/tracer.hxx b/odb/mssql/tracer.hxx new file mode 100644 index 0000000..46c7c48 --- /dev/null +++ b/odb/mssql/tracer.hxx @@ -0,0 +1,63 @@ +// file : odb/mssql/tracer.hxx +// author : Boris Kolpackov +// copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC +// license : ODB NCUEL; see accompanying LICENSE file + +#ifndef ODB_MSSQL_TRACER_HXX +#define ODB_MSSQL_TRACER_HXX + +#include + +#include + +#include +#include +#include + +namespace odb +{ + namespace mssql + { + class LIBODB_MSSQL_EXPORT tracer: private odb::tracer + { + public: + virtual + ~tracer (); + + virtual void + prepare (connection&, const statement&); + + virtual void + execute (connection&, const statement&); + + virtual void + execute (connection&, const char* statement) = 0; + + virtual void + deallocate (connection&, const statement&); + + private: + // Allow these classes to convert mssql::tracer to odb::tracer. + // + friend class database; + friend class connection; + friend class transaction; + + virtual void + prepare (odb::connection&, const odb::statement&); + + virtual void + execute (odb::connection&, const odb::statement&); + + virtual void + execute (odb::connection&, const char* statement); + + virtual void + deallocate (odb::connection&, const odb::statement&); + }; + } +} + +#include + +#endif // ODB_MSSQL_TRACER_HXX diff --git a/odb/mssql/transaction-impl.cxx b/odb/mssql/transaction-impl.cxx index 308f078..68fee22 100644 --- a/odb/mssql/transaction-impl.cxx +++ b/odb/mssql/transaction-impl.cxx @@ -43,14 +43,11 @@ namespace odb odb::transaction_impl::connection_ = connection_.get (); } - /* - @@ TODO { odb::tracer* t; if ((t = connection_->tracer ()) || (t = database_.tracer ())) t->execute (*connection_, "BEGIN"); } - */ // In ODBC a transaction is started automatically before the first // statement is executed. @@ -60,14 +57,11 @@ namespace odb void transaction_impl:: commit () { - /* - @@ TODO { odb::tracer* t; if ((t = connection_->tracer ()) || (t = database_.tracer ())) t->execute (*connection_, "COMMIT"); } - */ SQLRETURN r ( SQLEndTran (SQL_HANDLE_DBC, connection_->handle (), SQL_COMMIT)); @@ -84,14 +78,11 @@ namespace odb void transaction_impl:: rollback () { - /* - @@ TODO { odb::tracer* t; if ((t = connection_->tracer ()) || (t = database_.tracer ())) t->execute (*connection_, "ROLLBACK"); } - */ SQLRETURN r ( SQLEndTran (SQL_HANDLE_DBC, connection_->handle (), SQL_ROLLBACK)); diff --git a/odb/mssql/transaction.hxx b/odb/mssql/transaction.hxx index 57806b8..9837401 100644 --- a/odb/mssql/transaction.hxx +++ b/odb/mssql/transaction.hxx @@ -3,8 +3,6 @@ // copyright : Copyright (c) 2009-2011 Code Synthesis Tools CC // license : ODB NCUEL; see accompanying LICENSE file -//@@ disabled functionality - #ifndef ODB_MSSQL_TRANSACTION_HXX #define ODB_MSSQL_TRANSACTION_HXX @@ -14,7 +12,7 @@ #include #include -//#include +#include #include @@ -54,7 +52,6 @@ namespace odb static void current (transaction&); - /* // SQL statement tracing. // public: @@ -73,7 +70,6 @@ namespace odb } using odb::transaction::tracer; - */ public: transaction_impl& -- cgit v1.1