aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2014-08-14 09:37:06 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2014-11-11 10:29:48 +0200
commit7684cb0fa99f1a20130870ce2d092f3d2df18dcf (patch)
tree7860c32b60f76708b3d377996502b4d0d0766419
parent05ff680189f689d5bf82cd514e9d650c59aaf3da (diff)
Draft implementation for INSERT
-rw-r--r--odb/mssql/binding.hxx21
-rw-r--r--odb/mssql/error.cxx89
-rw-r--r--odb/mssql/error.hxx9
-rw-r--r--odb/mssql/exceptions.cxx18
-rw-r--r--odb/mssql/exceptions.hxx9
-rw-r--r--odb/mssql/simple-object-statements.hxx18
-rw-r--r--odb/mssql/simple-object-statements.txx17
-rw-r--r--odb/mssql/statement.cxx516
-rw-r--r--odb/mssql/statement.hxx45
-rw-r--r--odb/mssql/statement.ixx20
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 <string>
+#include <iostream> // @@ TMP
#include <odb/mssql/mssql.hxx>
#include <odb/mssql/error.hxx>
@@ -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<SQLLEN> (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<SQL_HANDLE_STMT>& h)
+ const auto_handle<SQL_HANDLE_STMT>& 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 <odb/pre.hxx>
+#include <cstddef> // std::size_t
#include <odb/mssql/mssql-fwd.hxx>
#include <odb/mssql/version.hxx>
-#include <odb/mssql/forward.hxx> // connection
+#include <odb/mssql/forward.hxx> // connection, multiple_exceptions
#include <odb/mssql/auto-handle.hxx>
#include <odb/mssql/details/export.hxx>
@@ -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<SQL_HANDLE_STMT>&);
+ const auto_handle<SQL_HANDLE_STMT>&,
+ 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_type, image_type>
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 <cstring> // std::strlen, std::strstr
#include <cassert>
+#include <iostream>
#include <odb/tracer.hxx>
@@ -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<long_callback*> (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<long_callback*> (cbp));
+ long_callback cb (*static_cast<long_callback*> (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 ? &param : 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 ? &param : 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<SQLLEN> (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 <cstddef> // std::size_t
#include <odb/statement.hxx>
+#include <odb/exceptions.hxx>
#include <odb/mssql/version.hxx>
#include <odb/mssql/forward.hxx>
@@ -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<SQL_HANDLE_STMT> 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<unsigned char*> (&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 ()
{