aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2014-11-11 10:19:22 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2014-11-11 10:29:48 +0200
commit09808a6118977c0a40a40fe1265ebe03c8d9e5fe (patch)
tree20154c2a09f3c5d7b483ec839e9a7106a98ef510
parenta6df72a09ef63f4b03bb7f7fb8866640040b4fef (diff)
Complete initial bulk erase() implementation
-rw-r--r--odb/mssql/simple-object-statements.hxx2
-rw-r--r--odb/mssql/statement.cxx190
-rw-r--r--odb/mssql/statement.hxx38
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_;
};
}