diff options
author | Boris Kolpackov <boris@codesynthesis.com> | 2014-11-13 13:54:29 +0200 |
---|---|---|
committer | Boris Kolpackov <boris@codesynthesis.com> | 2014-11-13 13:54:29 +0200 |
commit | effe0f97e4b869dd2b333fe7f213d55f3363cb82 (patch) | |
tree | 600de990f186255bbe6c5e4a000d912ced509c59 /odb/mssql/statement.cxx | |
parent | 09808a6118977c0a40a40fe1265ebe03c8d9e5fe (diff) |
Bulk update implementation
Diffstat (limited to 'odb/mssql/statement.cxx')
-rw-r--r-- | odb/mssql/statement.cxx | 284 |
1 files changed, 172 insertions, 112 deletions
diff --git a/odb/mssql/statement.cxx b/odb/mssql/statement.cxx index e49627c..22b1de9 100644 --- a/odb/mssql/statement.cxx +++ b/odb/mssql/statement.cxx @@ -783,12 +783,12 @@ namespace odb bool ok (SQL_SUCCEEDED (r) || r == SQL_NO_DATA); // If the statement failed as a whole, assume no parameter sets - // were attempted. Otherwise, the documentation says that the - // native client driver keeps processing remaining sets even - // in case of an error. + // were attempted in case of a batch. Otherwise, the documentation + // says that the native client driver keeps processing remaining + // sets even in case of an error. // i_ = 0; - n_ = (ok ? n : 0); + n_ = (ok ? n : (status_ == 0 ? 1 : 0)); if (mex_ != 0) { @@ -828,6 +828,73 @@ namespace odb return r; } + size_t bulk_statement:: + extract_errors () + { + size_t e (0); + + for (size_t i (0); i != n_; ++i) + { + if (status_[i] != SQL_PARAM_SUCCESS && + status_[i] != SQL_PARAM_SUCCESS_WITH_INFO) + { + translate_error (SQL_ERROR, conn_, stmt_, i, mex_); + e++; + } + } + + return e; + } + + unsigned long long bulk_statement:: + affected (SQLRETURN r, size_t errors, bool unique) + { + unsigned long long rows (0); + + // SQL_NO_DATA indicates that the statement hasn't affected any rows. + // + if (r != SQL_NO_DATA) + { + SQLLEN n; + r = SQLRowCount (stmt_, &n); + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + + // If all the parameter sets failed, then the returned count is -1, + // which means "not available" according to the documentation. + // + rows = (n != -1 ? static_cast<unsigned long long> (n) : 0); + } + + cerr << "total: " << rows << endl; + + if (n_ > 1) // Batch. + { + if (rows != 0) // Some rows did get affected. + { + // Subtract the parameter sets that failed since they haven't + // affected any rows. + // + size_t p (n_ - errors); + + if (p > 1) // True batch. + { + if (unique) // Each can affect 0 or 1 row. + { + rows = (p == static_cast<size_t> (rows) + ? 1 + : result_unknown); + } + else + rows = result_unknown; + } + } + } + + return rows; + } + // // select_statement // @@ -1085,10 +1152,29 @@ namespace odb // (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. // + // The OUTPUT case can be handled in two different ways: we can + // "execute" (with SQLMoreResults()) each set as the user moves + // from one result to the next (result() call). The advantage of + // this approach is that the returned data ends up in the right + // place automatically. The potential drawback is that the total + // affected row count will only be available at the end. As a + // result, this approach probably won't work if we need to handle, + // say, UPDATE with OUTPUT (SQLRowCount() does not return an + // intermediate total, at least not for INSERT). + // + // The alternative implementation would call SQLMoreResults() + // inside execute() until all the parameter sets are executed. + // In this case we will have to copy the extracted data into + // the right place in the bindings (or update the binding before + // each call to SQLMoreResults()). It is also not clear whether + // the diagnostic records for the failed sets would accumulate. + // If not, those will have to be stashed into mex on each + // iteration. + // SQLRETURN r (bulk_statement::execute (n, mex)); // Statement failed as a whole, assume no parameter sets were - // attempted. + // attempted in case of a batch. // if (!SQL_SUCCEEDED (r)) { @@ -1096,8 +1182,8 @@ namespace odb return n_; } - if (n == 1) // Note: not n_; n and n_ not be the same, see command above. - fetch (SQL_SUCCESS); // Non-batch case. + if (status_ == 0) // Non-batch case. + fetch (SQL_SUCCESS); else fetch (status_[i_] == SQL_PARAM_SUCCESS || status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO @@ -1209,7 +1295,7 @@ namespace odb } } - // Fetch the row containing the id/version if this statement if + // Fetch the row containing the id/version if this statement is // returning. // if (result_ && (returning_id_ || returning_version_)) @@ -1313,12 +1399,15 @@ namespace odb update_statement:: update_statement (connection_type& conn, const string& text, + bool unique, bool process, binding& param, bool returning_version) - : statement (conn, - text, statement_update, - (process ? ¶m : 0), false), + : bulk_statement (conn, + text, statement_update, + (process ? ¶m : 0), false, + param.batch, param.skip, param.status), + unique_ (unique), returning_version_ (returning_version) { if (!empty ()) @@ -1333,14 +1422,17 @@ namespace odb update_statement:: update_statement (connection_type& conn, const char* text, + bool unique, bool process, binding& param, bool returning_version, bool copy_text) - : statement (conn, - text, statement_update, - (process ? ¶m : 0), false, - copy_text), + : bulk_statement (conn, + text, statement_update, + (process ? ¶m : 0), false, + param.batch, param.skip, param.status, + copy_text), + unique_ (unique), returning_version_ (returning_version) { if (!empty ()) @@ -1367,53 +1459,77 @@ namespace odb translate_error (r, conn_, stmt_); } - unsigned long long update_statement:: - execute () + size_t update_statement:: + execute (size_t n, multiple_exceptions* mex) { - SQLRETURN r (statement::execute ()); - - // SQL_NO_DATA indicates that the statement hasn't affected any rows. + // In batch UPDATE without the OUTPUT clause (which is the + // only kind we currently support) all the parameter sets + // are processed inside SQLExecute() and the total count of + // affected rows is available after it returns. // - if (r == SQL_NO_DATA) - return 0; + assert (!returning_version_ || n == 1); - if (!SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_); + SQLRETURN r (bulk_statement::execute (n, mex)); - // Get the number of affected rows. + // Statement failed as a whole, assume no parameter sets were + // attempted in case of a batch. // - SQLLEN rows; - r = SQLRowCount (stmt_, &rows); - - if (!SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_); + if (!(SQL_SUCCEEDED (r) || r == SQL_NO_DATA)) + { + translate_error (r, conn_, stmt_, 0, mex_); + return n_; + } - // Fetch the row containing the version if this statement is - // returning. We still need to close the cursor even if we - // haven't updated any rows. + // Extract error information for failed parameter sets. If we do + // this after calling SQLRowCount(), all the diagnostics records + // that we need will be gone. // - if (returning_version_) - { - r = SQLFetch (stmt_); + size_t errors (n_ > 1 ? extract_errors () : 0); - if (r != SQL_NO_DATA && !SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_); + // Figure out the affected row count. + // + result_ = affected (r, errors, unique_); + if (status_ == 0) // Non-batch case. + { + // Fetch the row containing the version if this statement is + // returning. We still need to close the cursor even if we + // haven't updated any rows. + // + if (returning_version_) { - SQLRETURN r (SQLCloseCursor (stmt_)); // Don't overwrite r. + r = SQLFetch (stmt_); - if (!SQL_SUCCEEDED (r)) + if (r != SQL_NO_DATA && !SQL_SUCCEEDED (r)) translate_error (r, conn_, stmt_); - } - if (rows != 0 && r == SQL_NO_DATA) - throw database_exception ( - 0, - "?????", - "result set expected from a statement with the OUTPUT clause"); + { + SQLRETURN r (SQLCloseCursor (stmt_)); // Don't overwrite r. + + if (!SQL_SUCCEEDED (r)) + translate_error (r, conn_, stmt_); + } + + if (result_ != 0 && r == SQL_NO_DATA) + throw database_exception ( + 0, + "?????", + "result set expected from a statement with the OUTPUT clause"); + } } - return static_cast<unsigned long long> (rows); + return n_; + } + + unsigned long long update_statement:: + result (size_t i) + { + assert ((i_ == i || i_ + 1 == i) && i < n_); + + if (i != i_) + mex_->current (++i_); // mex cannot be NULL since this is a batch. + + return result_; } // @@ -1467,91 +1583,35 @@ namespace odb SQLRETURN r (bulk_statement::execute (n, mex)); // Statement failed as a whole, assume no parameter sets were - // attempted. + // attempted in case of a batch. // if (!(SQL_SUCCEEDED (r) || r == SQL_NO_DATA)) { - fetch (r); + translate_error (r, conn_, stmt_, 0, mex_); return n_; } - // Figure out the affected row count. SQL_NO_DATA indicates that - // the statement hasn't affected any rows. + // Extract error information for failed parameter sets. If we do + // this after calling SQLRowCount(), all the diagnostics records + // that we need will be gone. // - if (r == SQL_NO_DATA) - result_ = 0; - else - { - SQLLEN rows; - r = SQLRowCount (stmt_, &rows); + size_t errors (n_ > 1 ? extract_errors () : 0); - if (!SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_); - - result_ = static_cast<unsigned long long> (rows); - } - - cerr << "total: " << result_ << endl; - - if (n_ > 1) // Batch. - { - if (result_ != 0) // Some rows did get affected. - { - size_t p (n_); - - // Subtract the parameter sets that failed since they haven't - // affected any rows. - // - for (size_t i (0); i != n_; ++i) - if (status_[i] != SQL_PARAM_SUCCESS && - status_[i] != SQL_PARAM_SUCCESS_WITH_INFO) - p--; - - if (p > 1) // True batch. - { - if (unique_) // Each can affect 0 or 1 row. - { - result_ = (p == static_cast<size_t> (result_) - ? 1 - : result_unknown); - } - else - result_ = result_unknown; - } - } - } - - if (n == 1) // n and n_ are really the same here. - fetch (SQL_SUCCESS); // Non-batch case. - else - fetch (status_[i_] == SQL_PARAM_SUCCESS || - status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO - ? SQL_SUCCESS : SQL_ERROR); + // Figure out the affected row count. + // + result_ = affected (r, errors, unique_); return n_; } - void delete_statement:: - fetch (SQLRETURN r) - { - if (!SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_, i_, mex_); // Can return. - } - unsigned long long delete_statement:: result (size_t i) { assert ((i_ == i || i_ + 1 == i) && i < n_); if (i != i_) - { mex_->current (++i_); // mex cannot be NULL since this is a batch. - fetch (status_[i_] == SQL_PARAM_SUCCESS || - status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO - ? SQL_SUCCESS : SQL_ERROR); - } - return result_; } } |