Files
soci/src/core/statement.cpp
2009-04-30 11:50:31 +02:00

716 lines
17 KiB
C++

//
// Copyright (C) 2004-2008 Maciej Sobczak, Stephen Hutton
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
#define SOCI_SOURCE
#include "statement.h"
#include "session.h"
#include "into-type.h"
#include "use-type.h"
#include "values.h"
#include <ctime>
#ifdef _MSC_VER
#pragma warning(disable:4355)
#endif
using namespace soci;
using namespace soci::details;
void statement::exchange(into_type_ptr const & i)
{
impl_->exchange(i);
}
void statement::exchange(use_type_ptr const & u)
{
impl_->exchange(u);
}
statement_impl::statement_impl(session & s)
: session_(s), refCount_(1), row_(0),
fetchSize_(1), initialFetchSize_(1),
alreadyDescribed_(false)
{
backEnd_ = s.make_statement_backend();
}
statement_impl::statement_impl(prepare_temp_type const & prep)
: session_(prep.get_prepare_info()->session_),
refCount_(1), row_(0), fetchSize_(1), alreadyDescribed_(false)
{
backEnd_ = session_.make_statement_backend();
ref_counted_prepare_info * prepInfo = prep.get_prepare_info();
// take all bind/define info
intos_.swap(prepInfo->intos_);
uses_.swap(prepInfo->uses_);
// allocate handle
alloc();
// prepare the statement
query_ = prepInfo->get_query();
prepare(query_);
define_and_bind();
}
statement_impl::~statement_impl()
{
clean_up();
}
void statement_impl::alloc()
{
backEnd_->alloc();
}
void statement_impl::bind(values & values)
{
std::size_t cnt = 0;
try
{
for (std::vector<details::standard_use_type *>::iterator it =
values.uses_.begin(); it != values.uses_.end(); ++it)
{
// only bind those variables which are:
// - either named and actually referenced in the statement,
// - or positional
std::string const & useName = (*it)->get_name();
if (useName.empty())
{
// positional use element
int position = static_cast<int>(uses_.size());
(*it)->bind(*this, position);
uses_.push_back(*it);
indicators_.push_back(values.indicators_[cnt]);
}
else
{
// named use element - check if it is used
const std::string placeholder = ":" + useName;
std::size_t pos = query_.find(placeholder);
if (pos != std::string::npos)
{
const char nextChar = query_[pos + placeholder.size()];
if (nextChar == ' ' || nextChar == ',' ||
nextChar == '\0' || nextChar == ')')
{
int position = static_cast<int>(uses_.size());
(*it)->bind(*this, position);
uses_.push_back(*it);
indicators_.push_back(values.indicators_[cnt]);
}
else
{
values.add_unused(*it, values.indicators_[cnt]);
}
}
else
{
values.add_unused(*it, values.indicators_[cnt]);
}
}
cnt++;
}
}
catch (...)
{
for (std::size_t i = ++cnt; i != values.uses_.size(); ++i)
{
values.add_unused(values.uses_[i], values.indicators_[i]);
}
throw;
}
}
void statement_impl::exchange(into_type_ptr const & i)
{
intos_.push_back(i.get());
i.release();
}
void statement_impl::exchange_for_row(into_type_ptr const & i)
{
intosForRow_.push_back(i.get());
i.release();
}
void statement_impl::exchange_for_rowset(into_type_ptr const & i)
{
if (intos_.empty() == false)
{
throw soci_error("Explicit into elements not allowed with rowset.");
}
into_type_base * p = i.get();
intos_.push_back(p);
i.release();
int definePosition = 1;
p->define(*this, definePosition);
definePositionForRow_ = definePosition;
}
void statement_impl::exchange(use_type_ptr const & u)
{
uses_.push_back(u.get());
u.release();
}
void statement_impl::clean_up()
{
// deallocate all bind and define objects
std::size_t const isize = intos_.size();
for (std::size_t i = isize; i != 0; --i)
{
intos_[i - 1]->clean_up();
delete intos_[i - 1];
intos_.resize(i - 1);
}
std::size_t const ifrsize = intosForRow_.size();
for (std::size_t i = ifrsize; i != 0; --i)
{
intosForRow_[i - 1]->clean_up();
delete intosForRow_[i - 1];
intosForRow_.resize(i - 1);
}
std::size_t const usize = uses_.size();
for (std::size_t i = usize; i != 0; --i)
{
uses_[i - 1]->clean_up();
delete uses_[i - 1];
uses_.resize(i - 1);
}
std::size_t const indsize = indicators_.size();
for (std::size_t i = 0; i != indsize; ++i)
{
delete indicators_[i];
indicators_[i] = NULL;
}
if (backEnd_ != NULL)
{
backEnd_->clean_up();
delete backEnd_;
backEnd_ = NULL;
}
}
void statement_impl::prepare(std::string const & query,
statement_type eType)
{
query_ = query;
session_.log_query(query);
backEnd_->prepare(query, eType);
}
void statement_impl::define_and_bind()
{
int definePosition = 1;
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intos_[i]->define(*this, definePosition);
}
// if there are some implicite into elements
// injected by the row description process,
// they should be defined in the later phase,
// starting at the position where the above loop finished
definePositionForRow_ = definePosition;
int bindPosition = 1;
std::size_t const usize = uses_.size();
for (std::size_t i = 0; i != usize; ++i)
{
uses_[i]->bind(*this, bindPosition);
}
}
void statement_impl::define_for_row()
{
std::size_t const isize = intosForRow_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intosForRow_[i]->define(*this, definePositionForRow_);
}
}
void statement_impl::undefine_and_bind()
{
std::size_t const isize = intos_.size();
for (std::size_t i = isize; i != 0; --i)
{
intos_[i - 1]->clean_up();
}
std::size_t const ifrsize = intosForRow_.size();
for (std::size_t i = ifrsize; i != 0; --i)
{
intosForRow_[i - 1]->clean_up();
}
std::size_t const usize = uses_.size();
for (std::size_t i = usize; i != 0; --i)
{
uses_[i - 1]->clean_up();
}
}
bool statement_impl::execute(bool withDataExchange)
{
initialFetchSize_ = intos_size();
if (intos_.empty() == false && initialFetchSize_ == 0)
{
// this can happen only with into-vectors elements
// and is not allowed when calling execute
throw soci_error("Vectors of size 0 are not allowed.");
}
fetchSize_ = initialFetchSize_;
std::size_t bindSize = uses_size();
if (bindSize > 1 && fetchSize_ > 1)
{
throw soci_error(
"Bulk insert/update and bulk select not allowed in same query");
}
pre_use();
// looks like a hack and it is - row description should happen
// *after* the use elements were completely prepared
// and *before* the into elements are touched, so that the row
// description process can inject more into elements for
// implicit data exchange
if (row_ != NULL && alreadyDescribed_ == false)
{
describe();
define_for_row();
}
int num = 0;
if (withDataExchange)
{
num = 1;
pre_fetch();
if (static_cast<int>(fetchSize_) > num)
{
num = static_cast<int>(fetchSize_);
}
if (static_cast<int>(bindSize) > num)
{
num = static_cast<int>(bindSize);
}
}
statement_backend::exec_fetch_result res = backEnd_->execute(num);
bool gotData = false;
if (res == statement_backend::ef_success)
{
// the "success" means that the statement executed correctly
// and for select statement this also means that some rows were read
if (num > 0)
{
gotData = true;
// ensure into vectors have correct size
resize_intos(static_cast<std::size_t>(num));
}
}
else // res == ef_no_data
{
// the "no data" means that the end-of-rowset condition was hit
// but still some rows might have been read (the last bunch of rows)
// it can also mean that the statement did not produce any results
gotData = fetchSize_ > 1 ? resize_intos() : false;
}
if (num > 0)
{
post_fetch(gotData, false);
}
post_use(gotData);
session_.set_got_data(gotData);
return gotData;
}
bool statement_impl::fetch()
{
if (fetchSize_ == 0)
{
truncate_intos();
session_.set_got_data(false);
return false;
}
bool gotData = false;
// vectors might have been resized between fetches
std::size_t newFetchSize = intos_size();
if (newFetchSize > initialFetchSize_)
{
// this is not allowed, because most likely caused reallocation
// of the vector - this would require complete re-bind
throw soci_error(
"Increasing the size of the output vector is not supported.");
}
else if (newFetchSize == 0)
{
session_.set_got_data(false);
return false;
}
else
{
// the output vector was downsized or remains the same as before
fetchSize_ = newFetchSize;
}
statement_backend::exec_fetch_result res =
backEnd_->fetch(static_cast<int>(fetchSize_));
if (res == statement_backend::ef_success)
{
// the "success" means that some number of rows was read
// and that it is not yet the end-of-rowset (there are more rows)
gotData = true;
// ensure into vectors have correct size
resize_intos(fetchSize_);
}
else // res == ef_no_data
{
// end-of-rowset condition
if (fetchSize_ > 1)
{
// but still the last bunch of rows might have been read
gotData = resize_intos();
fetchSize_ = 0;
}
else
{
truncate_intos();
gotData = false;
}
}
post_fetch(gotData, true);
session_.set_got_data(gotData);
return gotData;
}
std::size_t statement_impl::intos_size()
{
// this function does not need to take into account intosForRow_ elements,
// since their sizes are always 1 (which is the same and the primary
// into(row) element, which has injected them)
std::size_t intos_size = 0;
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
if (i==0)
{
intos_size = intos_[i]->size();
}
else if (intos_size != intos_[i]->size())
{
std::ostringstream msg;
msg << "Bind variable size mismatch (into["
<< static_cast<unsigned long>(i) << "] has size "
<< static_cast<unsigned long>(intos_[i]->size())
<< ", into[0] has size "
<< static_cast<unsigned long>(intos_size);
throw soci_error(msg.str());
}
}
return intos_size;
}
std::size_t statement_impl::uses_size()
{
std::size_t usesSize = 0;
std::size_t const usize = uses_.size();
for (std::size_t i = 0; i != usize; ++i)
{
if (i==0)
{
usesSize = uses_[i]->size();
if (usesSize == 0)
{
// this can happen only for vectors
throw soci_error("Vectors of size 0 are not allowed.");
}
}
else if (usesSize != uses_[i]->size())
{
std::ostringstream msg;
msg << "Bind variable size mismatch (use["
<< static_cast<unsigned long>(i) << "] has size "
<< static_cast<unsigned long>(uses_[i]->size())
<< ", use[0] has size "
<< static_cast<unsigned long>(usesSize);
throw soci_error(msg.str());
}
}
return usesSize;
}
bool statement_impl::resize_intos(std::size_t upperBound)
{
// this function does not need to take into account the intosForRow_
// elements, since they are never used for bulk operations
std::size_t rows = backEnd_->get_number_of_rows();
if (upperBound != 0 && upperBound < rows)
{
rows = upperBound;
}
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intos_[i]->resize(rows);
}
return rows > 0 ? true : false;
}
void statement_impl::truncate_intos()
{
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intos_[i]->resize(0);
}
}
void statement_impl::pre_fetch()
{
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intos_[i]->pre_fetch();
}
std::size_t const ifrsize = intosForRow_.size();
for (std::size_t i = 0; i != ifrsize; ++i)
{
intosForRow_[i]->pre_fetch();
}
}
void statement_impl::pre_use()
{
std::size_t const usize = uses_.size();
for (std::size_t i = 0; i != usize; ++i)
{
uses_[i]->pre_use();
}
}
void statement_impl::post_fetch(bool gotData, bool calledFromFetch)
{
// first iterate over intosForRow_ elements, since the Row element
// (which is among the intos_ elements) might depend on the
// values of those implicitly injected elements
std::size_t const ifrsize = intosForRow_.size();
for (std::size_t i = 0; i != ifrsize; ++i)
{
intosForRow_[i]->post_fetch(gotData, calledFromFetch);
}
std::size_t const isize = intos_.size();
for (std::size_t i = 0; i != isize; ++i)
{
intos_[i]->post_fetch(gotData, calledFromFetch);
}
}
void statement_impl::post_use(bool gotData)
{
// iterate in reverse order here in case the first item
// is an UseType<Values> (since it depends on the other UseTypes)
for (std::size_t i = uses_.size(); i != 0; --i)
{
uses_[i-1]->post_use(gotData);
}
}
namespace soci
{
namespace details
{
// Map data_types to stock types for dynamic result set support
template<>
void statement_impl::bind_into<dt_string>()
{
into_row<std::string>();
}
template<>
void statement_impl::bind_into<dt_double>()
{
into_row<double>();
}
template<>
void statement_impl::bind_into<dt_integer>()
{
into_row<int>();
}
template<>
void statement_impl::bind_into<dt_unsigned_long>()
{
into_row<unsigned long>();
}
template<>
void statement_impl::bind_into<dt_long_long>()
{
into_row<long long>();
}
template<>
void statement_impl::bind_into<dt_date>()
{
into_row<std::tm>();
}
void statement_impl::describe()
{
row_->clean_up();
int numcols = backEnd_->prepare_for_describe();
for (int i = 1; i <= numcols; ++i)
{
data_type dtype;
std::string columnName;
backEnd_->describe_column(i, dtype, columnName);
column_properties props;
props.set_name(columnName);
props.set_data_type(dtype);
switch (dtype)
{
case dt_string:
bind_into<dt_string>();
break;
case dt_double:
bind_into<dt_double>();
break;
case dt_integer:
bind_into<dt_integer>();
break;
case dt_unsigned_long:
bind_into<dt_unsigned_long>();
break;
case dt_long_long:
bind_into<dt_long_long>();
break;
case dt_date:
bind_into<dt_date>();
break;
default:
std::ostringstream msg;
msg << "db column type " << dtype
<<" not supported for dynamic selects"<<std::endl;
throw soci_error(msg.str());
}
row_->add_properties(props);
}
alreadyDescribed_ = true;
}
} // namespace details
} // namespace soci
void statement_impl::set_row(row * r)
{
if (row_ != NULL)
{
throw soci_error(
"Only one Row element allowed in a single statement.");
}
row_ = r;
row_->uppercase_column_names(session_.get_uppercase_column_names());
}
std::string statement_impl::rewrite_for_procedure_call(std::string const & query)
{
return backEnd_->rewrite_for_procedure_call(query);
}
void statement_impl::inc_ref()
{
++refCount_;
}
void statement_impl::dec_ref()
{
if (--refCount_ == 0)
{
delete this;
}
}
standard_into_type_backend *
statement_impl::make_into_type_backend()
{
return backEnd_->make_into_type_backend();
}
standard_use_type_backend *
statement_impl::make_use_type_backend()
{
return backEnd_->make_use_type_backend();
}
vector_into_type_backend *
statement_impl::make_vector_into_type_backend()
{
return backEnd_->make_vector_into_type_backend();
}
vector_use_type_backend *
statement_impl::make_vector_use_type_backend()
{
return backEnd_->make_vector_use_type_backend();
}