From 7684cb0fa99f1a20130870ce2d092f3d2df18dcf Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Thu, 14 Aug 2014 09:37:06 +0200 Subject: Draft implementation for INSERT --- odb/mssql/binding.hxx | 21 +- odb/mssql/error.cxx | 89 +++++- odb/mssql/error.hxx | 9 +- odb/mssql/exceptions.cxx | 18 ++ odb/mssql/exceptions.hxx | 9 + odb/mssql/simple-object-statements.hxx | 18 +- odb/mssql/simple-object-statements.txx | 17 +- odb/mssql/statement.cxx | 516 +++++++++++++++++++++++---------- odb/mssql/statement.hxx | 45 +-- odb/mssql/statement.ixx | 20 -- 10 files changed, 534 insertions(+), 228 deletions(-) diff --git a/odb/mssql/binding.hxx b/odb/mssql/binding.hxx index 76aa2b2..1ff6f50 100644 --- a/odb/mssql/binding.hxx +++ b/odb/mssql/binding.hxx @@ -24,10 +24,23 @@ namespace odb typedef mssql::bind bind_type; typedef mssql::change_callback change_callback_type; - binding (): bind (0), count (0), version (0), change_callback (0) {} + binding () + : bind (0), count (0), version (0), + batch (0), skip (0), status (0), + change_callback (0) {} binding (bind_type* b, std::size_t n) - : bind (b), count (n), version (0), change_callback (0) + : bind (b), count (n), version (0), + batch (1), skip (0), status (0), + change_callback (0) + { + } + + binding (bind_type* b, std::size_t n, + std::size_t bt, std::size_t s, SQLUSMALLINT* st) + : bind (b), count (n), version (0), + batch (bt), skip (s), status (st), + change_callback (0) { } @@ -35,6 +48,10 @@ namespace odb std::size_t count; std::size_t version; + std::size_t batch; + std::size_t skip; + SQLUSMALLINT* status; // Batch status array. + change_callback_type* change_callback; private: diff --git a/odb/mssql/error.cxx b/odb/mssql/error.cxx index f6be6ea..acc48dc 100644 --- a/odb/mssql/error.cxx +++ b/odb/mssql/error.cxx @@ -3,6 +3,7 @@ // license : ODB NCUEL; see accompanying LICENSE file #include +#include // @@ TMP #include #include @@ -20,7 +21,9 @@ namespace odb SQLHANDLE h, SQLSMALLINT htype, connection* conn, - bool end_tran) + bool end_tran, + size_t pos, + multiple_exceptions* mex) { // First see if we have one of the errors indicated via the // return error code. @@ -166,14 +169,41 @@ namespace odb for (SQLSMALLINT i (1);; ++i) { - r = SQLGetDiagRecA (htype, - h, - i, - (SQLCHAR*) sqlstate, - &native_code, - (SQLCHAR*) msg, - sizeof (msg), - &msg_size); + // If this is for a batch, filter out based on row association. + // Here we only ignore records that have the associated row + // number and this number doesn't match ours. In particular, + // this means that all the un-associated records which will be + // duplicated for all the failed rows, which seems like the + // correct thing to do. + // + if (mex != 0) + { + SQLLEN n; + r = SQLGetDiagField (htype, + h, + i, + SQL_DIAG_ROW_NUMBER, + &n, + 0, + 0); + + if (r == SQL_NO_DATA) + break; + else if (SQL_SUCCEEDED (r) && + n != SQL_NO_ROW_NUMBER && + n == SQL_ROW_NUMBER_UNKNOWN && + n != static_cast (pos + 1)) // 1-based + continue; + } + + r = SQLGetDiagRecA (htype, + h, + i, + (SQLCHAR*) sqlstate, + &native_code, + (SQLCHAR*) msg, + sizeof (msg), + &msg_size); if (r == SQL_NO_DATA) break; @@ -194,6 +224,28 @@ namespace odb } e.append (native_code, sqlstate, msg); + + //@@ TMP + if (htype == SQL_HANDLE_STMT) + { + SQLLEN n; + r = SQLGetDiagField (htype, + h, + i, + SQL_DIAG_ROW_NUMBER, + &n, + 0, + 0); + + // check error + if (n == SQL_NO_ROW_NUMBER) + cerr << "not associated with any row" << endl; + else if (n == SQL_ROW_NUMBER_UNKNOWN) + cerr << "unable to determine row association" << endl; + else + cerr << "associated with " << n << endl; + } + } else e.append (0, "?????", "unable to extract information for this " @@ -203,27 +255,36 @@ namespace odb if (e.size () == 0) e.append (0, "?????", "no diagnostic record (using wrong handle?)"); - throw e; + if (mex == 0) + throw e; + else + // It could be that some of these errors are fatal. I guess we + // will just have to learn from experience which ones are. The + // client code can always treat specific error codes as fatal. + // + mex->insert (pos, e); } void translate_error (SQLRETURN r, connection& c, bool end_tran) { - translate_error (r, c.handle (), SQL_HANDLE_DBC, &c, end_tran); + translate_error (r, c.handle (), SQL_HANDLE_DBC, &c, end_tran, 0, 0); } void translate_error (SQLRETURN r, connection& c, - const auto_handle& h) + const auto_handle& h, + size_t pos, + multiple_exceptions* mex) { - translate_error (r, h, SQL_HANDLE_STMT, &c, false); + translate_error (r, h, SQL_HANDLE_STMT, &c, false, pos, mex); } void translate_error (SQLRETURN r, SQLHANDLE h, SQLSMALLINT htype) { - translate_error (r, h, htype, 0, false); + translate_error (r, h, htype, 0, false, 0, 0); } } } diff --git a/odb/mssql/error.hxx b/odb/mssql/error.hxx index 69ff6d4..59ba3bf 100644 --- a/odb/mssql/error.hxx +++ b/odb/mssql/error.hxx @@ -6,10 +6,11 @@ #define ODB_MSSQL_ERROR_HXX #include +#include // std::size_t #include #include -#include // connection +#include // connection, multiple_exceptions #include #include @@ -18,13 +19,17 @@ namespace odb { namespace mssql { + // Translate ODBC error given a handle and throw (or return, in case + // multiple_exceptions is not NULL) an appropriate exception. + // LIBODB_MSSQL_EXPORT void translate_error (SQLRETURN, connection&, bool end_tran = false); LIBODB_MSSQL_EXPORT void translate_error (SQLRETURN, connection&, - const auto_handle&); + const auto_handle&, + std::size_t pos = 0, multiple_exceptions* = 0); LIBODB_MSSQL_EXPORT void translate_error (SQLRETURN, SQLHANDLE, SQLSMALLINT htype); diff --git a/odb/mssql/exceptions.cxx b/odb/mssql/exceptions.cxx index 33aaba4..44cdf2e 100644 --- a/odb/mssql/exceptions.cxx +++ b/odb/mssql/exceptions.cxx @@ -57,6 +57,12 @@ namespace odb return what_.c_str (); } + database_exception* database_exception:: + clone () const + { + return new database_exception (*this); + } + // // cli_exception // @@ -78,6 +84,12 @@ namespace odb return what_.c_str (); } + cli_exception* cli_exception:: + clone () const + { + return new cli_exception (*this); + } + // // long_data_reload // @@ -88,5 +100,11 @@ namespace odb return "attempt to re-load object or view with long data " "from query result"; } + + long_data_reload* long_data_reload:: + clone () const + { + return new long_data_reload (*this); + } } } diff --git a/odb/mssql/exceptions.hxx b/odb/mssql/exceptions.hxx index c503070..7d44a7b 100644 --- a/odb/mssql/exceptions.hxx +++ b/odb/mssql/exceptions.hxx @@ -79,6 +79,9 @@ namespace odb virtual const char* what () const throw (); + virtual database_exception* + clone () const; + public: ~database_exception () throw (); @@ -105,6 +108,9 @@ namespace odb virtual const char* what () const throw (); + virtual cli_exception* + clone () const; + private: std::string what_; }; @@ -113,6 +119,9 @@ namespace odb { virtual const char* what () const throw (); + + virtual long_data_reload* + clone () const; }; namespace core diff --git a/odb/mssql/simple-object-statements.hxx b/odb/mssql/simple-object-statements.hxx index ab43439..efd52f0 100644 --- a/odb/mssql/simple-object-statements.hxx +++ b/odb/mssql/simple-object-statements.hxx @@ -276,9 +276,9 @@ namespace odb // Object image. // image_type& - image () + image (std::size_t i = 0) { - return image_; + return image_[i]; } // Insert binding. @@ -323,7 +323,7 @@ namespace odb // Object id image and binding. // id_image_type& - id_image () {return id_image_;} + id_image (std::size_t i = 0) {return id_image_[i];} std::size_t id_image_version () const {return id_image_version_;} @@ -355,6 +355,9 @@ namespace odb insert_image_binding_, object_traits::auto_id, object_traits::rowversion, + (object_traits::auto_id || object_traits::rowversion + ? &id_image_binding_ + : 0), false)); return *persist_; @@ -479,7 +482,8 @@ namespace odb extra_statement_cache_ptr extra_statement_cache_; - image_type image_; + image_type image_[object_traits::batch]; + SQLUSMALLINT status_[object_traits::batch]; // Select binding. // @@ -508,10 +512,10 @@ namespace odb bind update_image_bind_[update_column_count + id_column_count + managed_optimistic_column_count]; - // Id image binding (only used as a parameter). Uses the suffix in - // the update bind. + // Id image binding (only used as a parameter or in OUTPUT for + // auto id and version). Uses the suffix in the update bind. // - id_image_type id_image_; + id_image_type id_image_[object_traits::batch]; std::size_t id_image_version_; binding id_image_binding_; diff --git a/odb/mssql/simple-object-statements.txx b/odb/mssql/simple-object-statements.txx index 84ed5db..9edd380 100644 --- a/odb/mssql/simple-object-statements.txx +++ b/odb/mssql/simple-object-statements.txx @@ -43,24 +43,31 @@ namespace odb object_statements (connection_type& conn) : object_statements_base (conn), select_image_binding_ (select_image_bind_, select_column_count), - insert_image_binding_ (insert_image_bind_, insert_column_count), + insert_image_binding_ (insert_image_bind_, + insert_column_count, + object_traits::batch, + sizeof (image_type), + status_), update_image_binding_ (update_image_bind_, update_column_count + id_column_count + managed_optimistic_column_count), id_image_binding_ (update_image_bind_ + update_column_count, - id_column_count), + id_column_count, + object_traits::batch, + sizeof (id_image_type), + 0), od_ (update_image_bind_ + update_column_count) { - image_.version = 0; + image_[0].version = 0; // @@ TODO [0] select_image_version_ = 0; insert_image_version_ = 0; update_image_version_ = 0; update_id_image_version_ = 0; - id_image_.version = 0; + id_image_[0].version = 0; // @@ TODO id_image_version_ = 0; - select_image_binding_.change_callback = image_.change_callback (); + select_image_binding_.change_callback = image_[0].change_callback (); std::memset (insert_image_bind_, 0, sizeof (insert_image_bind_)); std::memset (update_image_bind_, 0, sizeof (update_image_bind_)); diff --git a/odb/mssql/statement.cxx b/odb/mssql/statement.cxx index 15037a0..f3456fb 100644 --- a/odb/mssql/statement.cxx +++ b/odb/mssql/statement.cxx @@ -4,6 +4,7 @@ #include // std::strlen, std::strstr #include +#include #include @@ -27,7 +28,7 @@ namespace odb SQL_BIT, // bind::bit SQL_TINYINT, // bind::tinyint SQL_SMALLINT, // bind::smallint - SQL_INTEGER, // bind::integer + SQL_INTEGER, // bind::int_ SQL_BIGINT, // bind::bigint SQL_DECIMAL, // bind::decimal @@ -62,7 +63,7 @@ namespace odb SQL_C_BIT, // bind::bit SQL_C_UTINYINT, // bind::tinyint SQL_C_SSHORT, // bind::smallint - SQL_C_SLONG, // bind::integer + SQL_C_SLONG, // bind::int_ SQL_C_SBIGINT, // bind::bigint SQL_C_NUMERIC, // bind::decimal @@ -90,6 +91,41 @@ namespace odb SQL_C_BINARY // bind::rowversion }; + // Mapping of bind::buffer_type to fixed buffer capacity values. + // + static const SQLLEN capacity_lookup [bind::last] = + { + 1, // bind::bit + 1, // bind::tinyint + 2, // bind::smallint + 4, // bind::int_ + 8, // bind::bigint + + sizeof (decimal), // bind::decimal + 4, // bind::smallmoney + 8, // bind::money + + 4, // bind::float4 + 8, // bind::float8 + + 0, // bind::string + 0, // bind::long_string + + 0, // bind::nstring + 0, // bind::long_nstring + + 0, // bind::binary + 0, // bind::long_binary + + sizeof (date), // bind::date + sizeof (time), // bind::time + sizeof (datetime), // bind::datetime + sizeof (datetimeoffset), // bind::datetimeoffset + + 16, // bind::uniqueidentifier + 8 // bind::rowversion + }; + // // statement // @@ -240,10 +276,42 @@ namespace odb } void statement:: - bind_param (bind* b, size_t n) + bind_param (bind* b, size_t n, size_t skip, SQLUSMALLINT* status) { SQLRETURN r; + // Setup row-wise batch operation. We set the actual number of + // rows in the batch in execute(). + // + param_skip_ = skip; + + if (param_skip_ != 0) + { + r = SQLSetStmtAttr (stmt_, + SQL_ATTR_PARAM_BIND_TYPE, + (SQLPOINTER) param_skip_, + 0); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + r = SQLSetStmtAttr (stmt_, + SQL_ATTR_PARAMS_PROCESSED_PTR, + (SQLPOINTER) &processed_, + 0); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + r = SQLSetStmtAttr (stmt_, + SQL_ATTR_PARAM_STATUS_PTR, + (SQLPOINTER) status, + 0); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + SQLUSMALLINT i (0); for (bind* end (b + n); b != end; ++b) { @@ -289,7 +357,7 @@ namespace odb case bind::long_string: case bind::long_binary: { - buf = (SQLPOINTER) b; + buf = (SQLPOINTER) b->buffer; col_size = b->capacity != 0 ? (SQLULEN) b->capacity : SQL_SS_LENGTH_UNLIMITED; @@ -297,7 +365,7 @@ namespace odb } case bind::long_nstring: { - buf = (SQLPOINTER) b; + buf = (SQLPOINTER) b->buffer; col_size = b->capacity != 0 ? (SQLULEN) b->capacity / 2 // In characters, not bytes. : SQL_SS_LENGTH_UNLIMITED; @@ -429,56 +497,10 @@ namespace odb if (b->buffer == 0) // Skip NULL entries. continue; - SQLLEN cap (0); + SQLLEN cap (capacity_lookup[b->type]); switch (b->type) { - case bind::bit: - case bind::tinyint: - { - cap = 1; - break; - } - case bind::smallint: - { - cap = 2; - break; - } - case bind::int_: - { - cap = 4; - break; - } - case bind::bigint: - { - cap = 8; - break; - } - case bind::decimal: - { - cap = (SQLLEN) sizeof (decimal); - break; - } - case bind::smallmoney: - { - cap = 4; - break; - } - case bind::money: - { - cap = 8; - break; - } - case bind::float4: - { - cap = 4; - break; - } - case bind::float8: - { - cap = 8; - break; - } case bind::string: case bind::nstring: case bind::binary: @@ -495,41 +517,13 @@ namespace odb long_count++; continue; } - case bind::date: - { - cap = (SQLLEN) sizeof (date); - break; - } - case bind::time: - { - cap = (SQLLEN) sizeof (time); - break; - } - case bind::datetime: - { - cap = (SQLLEN) sizeof (datetime); - break; - } - case bind::datetimeoffset: - { - cap = (SQLLEN) sizeof (datetimeoffset); - break; - } - case bind::uniqueidentifier: - { - cap = 16; - break; - } - case bind::rowversion: - { - cap = 8; - break; - } case bind::last: { assert (false); break; } + default: + break; } r = SQLBindCol (stmt_, @@ -557,6 +551,8 @@ namespace odb t->execute (conn_, *this); } + processed_ = 0; + SQLRETURN r (SQLExecute (stmt_)); if (r == SQL_NEED_DATA) @@ -566,10 +562,13 @@ namespace odb if (tmp_buf.capacity () == 0) tmp_buf.capacity (4096); - bind* b; + long_callback* pcb; for (;;) { - r = SQLParamData (stmt_, (SQLPOINTER*) &b); + // ODBC seems to already offset the returned pointer for us + // in case of a batch. + // + r = SQLParamData (stmt_, (SQLPOINTER*) &pcb); // If we get anything other than SQL_NEED_DATA, then this is // the return code of SQLExecute(). @@ -577,12 +576,11 @@ namespace odb if (r != SQL_NEED_DATA) break; - long_callback& cb (*static_cast (b->buffer)); - // Store the pointer to the long_callback struct in buf on the // first call to the callback. This allows the callback to // redirect further calls to some other callback. // + long_callback cb (*pcb); const void* buf (&cb); size_t position (0); @@ -670,7 +668,7 @@ namespace odb cbp = nb + (p - ob); } - long_callback& cb (*static_cast (cbp)); + long_callback cb (*static_cast (cbp)); // First determine if the value is NULL as well as try to // get the total data size. @@ -907,17 +905,21 @@ namespace odb bool process, binding& param, bool returning_id, - bool returning_version) + bool returning_version, + binding* returning) : statement (conn, text, statement_insert, (process ? ¶m : 0), false), - returning_id_ (returning_id), - returning_version_ (returning_version) + returning_id_ (returning_id), returning_version_ (returning_version), + status_ (param.status) { - bind_param (param.bind, param.count); + //@@ Just pass binding already! And use batch size, not skip to + // decide if it is batch. + // + bind_param (param.bind, param.count, param.skip, status_); if (returning_id_ || returning_version_) - init_result (); + init_result (*returning); } insert_statement:: @@ -927,22 +929,23 @@ namespace odb binding& param, bool returning_id, bool returning_version, + binding* returning, bool copy_text) : statement (conn, text, statement_insert, (process ? ¶m : 0), false, copy_text), - returning_id_ (returning_id), - returning_version_ (returning_version) + returning_id_ (returning_id), returning_version_ (returning_version), + status_ (param.status) { - bind_param (param.bind, param.count); + bind_param (param.bind, param.count, param.skip, status_); if (returning_id_ || returning_version_) - init_result (); + init_result (*returning); } void insert_statement:: - init_result () + init_result (binding& ret) { // Figure out if we are using the OUTPUT clause or a batch of // INSERT and SELECT statements. The latter is used to work @@ -954,17 +957,19 @@ namespace odb batch_ = strstr (text_, "OUTPUT INSERTED.") == 0 && strstr (text_, "output inserted.") == 0; + SQLRETURN r; SQLUSMALLINT col (1); if (returning_id_) { - SQLRETURN r ( - SQLBindCol (stmt_, - col++, - SQL_C_SBIGINT, - (SQLPOINTER) &id_, - sizeof (id_), - &id_size_ind_)); + bind& b (ret.bind[0]); // Auto id is the first element. + + r = SQLBindCol (stmt_, + col++, + c_type_lookup[b.type], + (SQLPOINTER) b.buffer, + capacity_lookup[b.type], + b.size_ind); if (!SQL_SUCCEEDED (r)) translate_error (r, conn_, stmt_); @@ -972,82 +977,224 @@ namespace odb if (returning_version_) { - SQLRETURN r ( - SQLBindCol (stmt_, - col++, - SQL_C_BINARY, - (SQLPOINTER) &version_, - sizeof (version_), - &version_size_ind_)); + bind& b (ret.bind[ret.count - 1]); // Version is the last element. + + r = SQLBindCol (stmt_, + col++, + c_type_lookup[b.type], + (SQLPOINTER) b.buffer, + capacity_lookup[b.type], + b.size_ind); if (!SQL_SUCCEEDED (r)) translate_error (r, conn_, stmt_); } + + // It might seem logical to set up the array of results if this is a + // batch (i.e., the SQL_ATTR_ROW_BIND_TYPE, SQL_ATTR_ROW_ARRAY_SIZE). + // This won't work because what we are getting is multiple result + // sets (each containing a single row) and not multiple rows. As a + // result, the SQL Server ODBC driver will always store the data in + // the first element of our array. A bit counter-intuitive. } - bool insert_statement:: - execute () + size_t insert_statement:: + execute (size_t n, multiple_exceptions* mex) { + mex_ = mex; + + //@@ TODO Should move to statement, also don't call if no batch (will + // need a flag). + // + { + SQLRETURN r (SQLSetStmtAttr (stmt_, + SQL_ATTR_PARAMSET_SIZE, + (SQLPOINTER) n, + 0)); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + // Some SQL* functions would only update the status in case of + // an error. + // + memset (status_, 0, sizeof (status_[0]) * n); + } + SQLRETURN r (statement::execute ()); + // The batch INSERT works in two different ways, depending on + // whether we have the OUTPUT clause. If there is no OUTPUT, + // then all the rows are processed inside the SQLExecute() + // call. If, however, there is OUTPUT, then the rows are + // processed one at a time as we consume the results with + // the SQLMoreResults() call below. Thus we in effect have + // two counts: the "processed so far" as set by the API + // (SQL_ATTR_PARAMS_PROCESSED_PTR) and the "to be processed" + // (value in n_). Note that in the OUTPUT case if there is an + // error, the processed count seems to jump by 2 for some reason. + // + // If the statement failed as a whole, assume only the first row + // was attempted (and failed). Otherwise, the documentation says + // that the native client driver keeps processing remaining rows + // even in case of an error. + // + i_ = 0; + n_ = (SQL_SUCCEEDED (r) ? n : 1); + + if (mex_ != 0) + { + mex_->current (i_); + mex_->attempted (processed_); + } + 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; + if (mex_ != 0) + mex_->fatal (true); // An incomplete batch is always fatal. + + fetch (r); + return n_; + } - bool cv (false); + cerr << "processed: " << processed_ << endl; + + for (unsigned i (0); i != processed_; ++i) + { + cerr << "[" << i << "] " << status_[i] << " "; + + if (status_[i] == SQL_PARAM_ERROR) + cerr << "error "; + else if (status_[i] == SQL_PARAM_SUCCESS || + status_[i] == SQL_PARAM_SUCCESS_WITH_INFO) + cerr << "ok"; + else if (status_[i] == SQL_PARAM_UNUSED) + cerr << "unused"; + else if (status_[i] == SQL_PARAM_DIAG_UNAVAILABLE) + cerr << "unavailable"; + else + cerr << "?"; - for (SQLSMALLINT i (1);; ++i) + cerr << endl; + } + + if (n == 1) // Note: not n_! + fetch (SQL_SUCCESS); // Non-batch case. + else + fetch (status_[i_] == SQL_PARAM_SUCCESS || + status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO + ? SQL_SUCCESS : SQL_ERROR); + + return n_; + } + + void insert_statement:: + fetch (SQLRETURN r) + { + result_ = true; + + if (!SQL_SUCCEEDED (r)) + { + // An auto-assigned object id should never cause a duplicate primary + // key. + // + if (!returning_id_) { - SQLRETURN r (SQLGetDiagRecA (SQL_HANDLE_STMT, - stmt_, - i, - (SQLCHAR*) sqlstate, - &native_code, - 0, - 0, - &msg_size)); + // Translate the integrity contraint violation (SQLSTATE 23000) + // to the false result value. This code is similar to that found + // in translate_error(). + // + char sqlstate[SQL_SQLSTATE_SIZE + 1]; + SQLINTEGER native_code; + SQLSMALLINT msg_size; - if (r == SQL_NO_DATA) - break; - else if (SQL_SUCCEEDED (r)) + bool cv (false); + + for (SQLSMALLINT i (1);; ++i) { - string s (sqlstate); + SQLRETURN r; - if (s == "23000") // Integrity contraint violation. - cv = true; - else if (s != "01000") // General warning. + // Filter based on row association. + // + if (mex_ != 0) + { + SQLLEN n; + r = SQLGetDiagField (SQL_HANDLE_STMT, + stmt_, + i, + SQL_DIAG_ROW_NUMBER, + &n, + 0, + 0); + + if (r == SQL_NO_DATA) + break; + else if (!SQL_SUCCEEDED (r)) + continue; + + // check error + if (n == SQL_NO_ROW_NUMBER) + cerr << "not associated with any row" << endl; + else if (n == SQL_ROW_NUMBER_UNKNOWN) + cerr << "unable to determine row association" << endl; + else + cerr << "associated with " << n << endl; + + if (n == SQL_NO_ROW_NUMBER || + n == SQL_ROW_NUMBER_UNKNOWN || + n != static_cast (i_ + 1)) // 1-based + continue; + } + + r= SQLGetDiagRecA (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); + + cerr << s << endl; + + if (s == "23000") // Integrity contraint violation. + cv = true; + else if (s != "01000") // General warning. + { + // Some other code. + // + cv = false; + break; + } + } + else // SQLGetDiagRec() failure. { - // Some other code. - // cv = false; break; } } - else - { - // SQLGetDiagRec() failure. - // - cv = false; - break; - } + + if (cv) + result_ = false; } - if (cv) - return false; - else - translate_error (r, conn_, stmt_); + if (result_) + { + translate_error (r, conn_, stmt_, i_, mex_); // Can return. + result_ = false; // Prevent id/version extraction below. + } } - // Fetch the row containing the id/version if this statement is + // Fetch the row containing the id/version if this statement if // returning. // - if (returning_id_ || returning_version_) + if (result_ && (returning_id_ || returning_version_)) { if (batch_) { @@ -1069,12 +1216,7 @@ namespace odb 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_); - } + cerr << "fetch [" << i_ << "] " << status_[i_] << " " << endl; if (r == SQL_NO_DATA) throw database_exception ( @@ -1082,8 +1224,64 @@ namespace odb "?????", "result set expected from a statement with the OUTPUT clause"); } + } + + bool insert_statement:: + result (size_t i) + { + assert ((i_ == i || i_ + 1 == i) && i < n_); + + SQLRETURN r; + + // Get to the next result set if necessary. + // + if (i != i_) + { + // Only in case of the OUTPUT clause do we have multiple result sets. + // + if (returning_id_ || returning_version_) + { + r = SQLMoreResults (stmt_); + + cerr << "more [" << (i_ + 1) << "] " << status_[i_ + 1] << " " + << SQL_SUCCEEDED (r) << endl; + + cerr << "more processed: " << processed_ << endl; + + if (r == SQL_NO_DATA) + { + throw database_exception ( + 0, + "?????", + "multiple result sets expected from an array of parameters"); + } + + // The actually processed count could have changed (see execute()). + // Multiple exceptions cannot be NULL since this is a batch. + // + mex_->attempted (processed_); + } + + mex_->current (++i_); + + fetch (status_[i_] == SQL_PARAM_SUCCESS || + status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO + ? SQL_SUCCESS : SQL_ERROR); + } + + // Close the cursor if we are done. + // + if ((returning_id_ || returning_version_) && i_ + 1 == n_) + { + r = SQLCloseCursor (stmt_); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + + //cerr << "result for " << i << " " << result_; - return true; + return result_; } // diff --git a/odb/mssql/statement.hxx b/odb/mssql/statement.hxx index 1c012ce..69d144b 100644 --- a/odb/mssql/statement.hxx +++ b/odb/mssql/statement.hxx @@ -11,6 +11,7 @@ #include // std::size_t #include +#include #include #include @@ -96,7 +97,8 @@ namespace odb protected: void - bind_param (bind*, std::size_t count); + bind_param (bind*, std::size_t count, + std::size_t skip = 0, SQLUSMALLINT* status = 0); // Return the actual number of columns bound. // @@ -124,6 +126,10 @@ namespace odb std::string text_copy_; const char* text_; auto_handle stmt_; + + std::size_t param_skip_; // If 0, no batch is used. + SQLULEN processed_; // Number of batch rows processed. + // @@ TODO could be local var in execute()? Nop. }; class LIBODB_MSSQL_EXPORT select_statement: public statement @@ -247,7 +253,8 @@ namespace odb bool process_text, binding& param, bool returning_id, - bool returning_version); + bool returning_version, + binding* returning); insert_statement (connection_type& conn, const char* text, @@ -255,22 +262,19 @@ namespace odb binding& param, bool returning_id, bool returning_version, + binding* returning, bool copy_text = true); - // Return true if successful and false if the row is a duplicate. + // Return the number of rows (out of n) that were attempted. + // + std::size_t + execute (std::size_t n = 1, multiple_exceptions* = 0); + + // Return true if successful and false if this row is a duplicate. // All other errors are reported by throwing exceptions. // bool - execute (); - - unsigned long long - id () - { - return id_; - } - - unsigned long long - version (); + result (std::size_t i = 0); private: insert_statement (const insert_statement&); @@ -278,18 +282,21 @@ namespace odb private: void - init_result (); + init_result (binding&); + + void + fetch (SQLRETURN); private: bool returning_id_; bool returning_version_; bool batch_; - unsigned long long id_; - SQLLEN id_size_ind_; - - unsigned char version_[8]; - SQLLEN version_size_ind_; + multiple_exceptions* mex_; + std::size_t n_; // Batch size. + std::size_t i_; // Position in result. + SQLUSMALLINT* status_; // @@ TODO: move to statement? + bool result_; }; class LIBODB_MSSQL_EXPORT update_statement: public statement diff --git a/odb/mssql/statement.ixx b/odb/mssql/statement.ixx index 89be46f..b2c091e 100644 --- a/odb/mssql/statement.ixx +++ b/odb/mssql/statement.ixx @@ -6,26 +6,6 @@ namespace odb { namespace mssql { - inline unsigned long long insert_statement:: - version () - { - unsigned long long r; - - // The value is in the big-endian format. - // - unsigned char* p (reinterpret_cast (&r)); - p[0] = version_[7]; - p[1] = version_[6]; - p[2] = version_[5]; - p[3] = version_[4]; - p[4] = version_[3]; - p[5] = version_[2]; - p[6] = version_[1]; - p[7] = version_[0]; - - return r; - } - inline unsigned long long update_statement:: version () { -- cgit v1.1