diff options
-rw-r--r-- | odb/mssql/simple-object-statements.hxx | 2 | ||||
-rw-r--r-- | odb/mssql/statement.cxx | 190 | ||||
-rw-r--r-- | odb/mssql/statement.hxx | 38 |
3 files changed, 114 insertions, 116 deletions
diff --git a/odb/mssql/simple-object-statements.hxx b/odb/mssql/simple-object-statements.hxx index efd52f0..7c5c868 100644 --- a/odb/mssql/simple-object-statements.hxx +++ b/odb/mssql/simple-object-statements.hxx @@ -405,6 +405,7 @@ namespace odb conn_, object_traits::erase_statement, id_image_binding_, + true, // Unique (0 or 1 affected rows). false)); return *erase_; @@ -420,6 +421,7 @@ namespace odb conn_, object_traits::optimistic_erase_statement, od_.id_image_binding_, + true, // Unique (0 or 1 affected rows). false)); } diff --git a/odb/mssql/statement.cxx b/odb/mssql/statement.cxx index 12fc21c..e49627c 100644 --- a/odb/mssql/statement.cxx +++ b/odb/mssql/statement.cxx @@ -728,7 +728,7 @@ namespace odb init (size_t skip) { // Setup row-wise batch operation. We set the actual number of - // rows in the batch in execute(). + // parameter sets in the batch in execute(). // SQLRETURN r; @@ -780,14 +780,15 @@ namespace odb processed_ = 0; SQLRETURN r (statement::execute ()); + bool ok (SQL_SUCCEEDED (r) || r == SQL_NO_DATA); - // 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. + // 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. // i_ = 0; - n_ = (SQL_SUCCEEDED (r) ? n : 1); + n_ = (ok ? n : 0); if (mex_ != 0) { @@ -795,7 +796,7 @@ namespace odb mex_->attempted (processed_); } - if (!SQL_SUCCEEDED (r)) + if (!ok) { if (mex_ != 0) mex_->fatal (true); // An incomplete batch is always fatal. @@ -1074,9 +1075,9 @@ namespace odb execute (size_t n, multiple_exceptions* mex) { // 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 + // whether we have the OUTPUT clause. If there is no OUTPUT, then + // all the parameter sets are processed inside the SQLExecute() + // call. If, however, there is OUTPUT, then the sets 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 @@ -1086,7 +1087,8 @@ namespace odb // SQLRETURN r (bulk_statement::execute (n, mex)); - // Statement failed as a whole, assume only one row was attempted. + // Statement failed as a whole, assume no parameter sets were + // attempted. // if (!SQL_SUCCEEDED (r)) { @@ -1094,7 +1096,7 @@ namespace odb return n_; } - if (n == 1) // Note: not n_! + if (n == 1) // Note: not n_; n and n_ not be the same, see command above. fetch (SQL_SUCCESS); // Non-batch case. else fetch (status_[i_] == SQL_PARAM_SUCCESS || @@ -1253,9 +1255,7 @@ namespace odb // if (i != i_) { - // Multiple exceptions cannot be NULL since this is a batch. - // - mex_->current (++i_); + mex_->current (++i_); // mex cannot be NULL since this is a batch. // Only in case of the OUTPUT clause do we have multiple result sets. // @@ -1428,11 +1428,13 @@ namespace odb delete_statement:: delete_statement (connection_type& conn, const string& text, - binding& param) + binding& param, + bool unique) : bulk_statement (conn, text, statement_delete, 0, false, - param.batch, param.skip, param.status) + param.batch, param.skip, param.status), + unique_ (unique) { bind_param (param.bind, param.count); } @@ -1441,12 +1443,14 @@ namespace odb delete_statement (connection_type& conn, const char* text, binding& param, + bool unique, bool copy_text) : bulk_statement (conn, text, statement_delete, 0, false, param.batch, param.skip, param.status, - copy_text) + copy_text), + unique_ (unique) { bind_param (param.bind, param.count); } @@ -1454,139 +1458,101 @@ namespace odb size_t delete_statement:: execute (size_t n, multiple_exceptions* mex) { - /* - @@ ?? - - // 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. + // In batch DELETE 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. // - */ SQLRETURN r (bulk_statement::execute (n, mex)); - // Statement failed as a whole, assume only one row was attempted. + // Statement failed as a whole, assume no parameter sets were + // attempted. // - if (!SQL_SUCCEEDED (r)) + if (!(SQL_SUCCEEDED (r) || r == SQL_NO_DATA)) { fetch (r); return n_; } - 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 delete_statement:: - fetch (SQLRETURN r) - { - /* - @@ - - // SQL_NO_DATA indicates that the statement hasn't affected any rows. + // Figure out the affected row count. SQL_NO_DATA indicates that + // the statement hasn't affected any rows. // if (r == SQL_NO_DATA) - return 0; - - */ - - if (SQL_SUCCEEDED (r)) + result_ = 0; + else { - // Get the number of affected rows. - // SQLLEN rows; r = SQLRowCount (stmt_, &rows); if (!SQL_SUCCEEDED (r)) translate_error (r, conn_, stmt_); - cerr << "fetch: " << rows << endl; - result_ = static_cast<unsigned long long> (rows); } - else - 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_); - SQLRETURN r; + cerr << "total: " << result_ << endl; - // Get to the next result set if necessary. - // - if (i != i_) + if (n_ > 1) // Batch. { - // Multiple exceptions cannot be NULL since this is a batch. - // - mex_->current (++i_); - - r = SQLMoreResults (stmt_); - - // The actually processed count could have changed (see execute()). - // - mex_->attempted (processed_); - - cerr << "more [" << (i_) << "] " << status_[i_] << " " - << SQL_SUCCEEDED (r) << " " << (r == SQL_NO_DATA) << endl; + if (result_ != 0) // Some rows did get affected. + { + size_t p (n_); - cerr << "more processed: " << processed_ << endl; + // 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 (r == SQL_NO_DATA) - { - throw database_exception ( - 0, - "?????", - "multiple result sets expected from an array of parameters"); + 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); - } - return result_; + return n_; } - /* - unsigned long long delete_statement:: - execute () + void delete_statement:: + fetch (SQLRETURN r) { - 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_); + translate_error (r, conn_, stmt_, i_, mex_); // Can return. + } - // Get the number of affected rows. - // - SQLLEN rows; - r = SQLRowCount (stmt_, &rows); + unsigned long long delete_statement:: + result (size_t i) + { + assert ((i_ == i || i_ + 1 == i) && i < n_); - if (!SQL_SUCCEEDED (r)) - translate_error (r, conn_, stmt_); + if (i != i_) + { + mex_->current (++i_); // mex cannot be NULL since this is a batch. - return static_cast<unsigned long long> (rows); + fetch (status_[i_] == SQL_PARAM_SUCCESS || + status_[i_] == SQL_PARAM_SUCCESS_WITH_INFO + ? SQL_SUCCESS : SQL_ERROR); + } + + return result_; } - */ } } diff --git a/odb/mssql/statement.hxx b/odb/mssql/statement.hxx index 8da71c2..ea53a38 100644 --- a/odb/mssql/statement.hxx +++ b/odb/mssql/statement.hxx @@ -152,6 +152,11 @@ namespace odb SQLUSMALLINT* status, bool copy_text); + // Call SQLExecute() and set up the batch tracking variables (see + // below). Note that this function does not treat SQL_NO_DATA as + // an error since for DELETE and UPDATE statements this is a + // shortcut notation for zero rows affected. + // SQLRETURN execute (std::size_t n, multiple_exceptions*); @@ -300,7 +305,7 @@ namespace odb binding* returning, bool copy_text = true); - // Return the number of rows (out of n) that were attempted. + // Return the number of parameter sets (out of n) that were attempted. // std::size_t execute (std::size_t n = 1, multiple_exceptions* = 0); @@ -376,23 +381,47 @@ namespace odb virtual ~delete_statement (); + // SQL Server native client ODBC driver does not expose individual + // affected row counts for batch operations, even though it says it + // does (SQLGetInfo(SQL_PARAM_ARRAY_ROW_COUNTS) returns SQL_PARC_BATCH). + // Instead, it adds them all up and returns a single count. This is + // bad news for us. + // + // In case of deleting by primary key (the affected row count is + // either 1 or 0), we can recognize the presumably successful case + // where the total affected row count is equal to the batch size + // (we can also recognize the "all unsuccessful" case where the + // total affected row count is 0). The unique_hint argument in the + // constructors below indicates whether this is a "0 or 1" DELETE + // statement. + // + // In all other situations (provided this is a batch), the result() + // function below returns the special result_unknown value. + // delete_statement (connection_type& conn, const std::string& text, - binding& param); + binding& param, + bool unique_hint = false); delete_statement (connection_type& conn, const char* text, binding& param, + bool unique_hint = false, bool copy_text = true); - // Return the number of parameter rows (out of n) that were attempted. + // Return the number of parameter sets (out of n) that were attempted. // std::size_t execute (std::size_t n = 1, multiple_exceptions* = 0); // Return the number of rows affected (deleted) by the parameter - // row. Errors are reported by throwing exceptions. + // set. If this is a batch (n > 1 in execute() call above) and it + // is impossible to determine the affected row count for each + // parameter set, then this function returns result_unknown. All + // other errors are reported by throwing exceptions. // + static const unsigned long long result_unknown = ~0ULL; + unsigned long long result (std::size_t i = 0); @@ -405,6 +434,7 @@ namespace odb fetch (SQLRETURN); private: + bool unique_; unsigned long long result_; }; } |