657 lines
14 KiB
C++
657 lines
14 KiB
C++
/***********************************************************************
|
|
query.cpp - Implements the Query class.
|
|
|
|
Copyright (c) 1998 by Kevin Atkinson, (c) 1999, 2000 and 2001 by
|
|
MySQL AB, and (c) 2004-2007 by Educational Technology Resources, Inc.
|
|
Others may also hold copyrights on code in this file. See the CREDITS
|
|
file in the top directory of the distribution for details.
|
|
|
|
This file is part of MySQL++.
|
|
|
|
MySQL++ is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published
|
|
by the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
MySQL++ is distributed in the hope that it will be useful, but WITHOUT
|
|
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
|
|
License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
License along with MySQL++; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
|
|
USA
|
|
***********************************************************************/
|
|
|
|
#include "query.h"
|
|
|
|
#include "autoflag.h"
|
|
#include "connection.h"
|
|
|
|
namespace mysqlpp {
|
|
|
|
Query::Query(Connection* c, bool te) :
|
|
#if defined(_MSC_VER) && !defined(_STLP_VERSION) && !defined(_STLP_VERSION_STR)
|
|
// prevents a double-init memory leak in native VC++ RTL (not STLport!)
|
|
std::ostream(std::_Noinit),
|
|
#else
|
|
std::ostream(0),
|
|
#endif
|
|
OptionalExceptions(te),
|
|
Lockable(false),
|
|
def(this),
|
|
conn_(c),
|
|
success_(false)
|
|
{
|
|
init(&sbuffer_);
|
|
success_ = true;
|
|
}
|
|
|
|
Query::Query(const Query& q) :
|
|
#if defined(_MSC_VER) && !defined(_STLP_VERSION) && !defined(_STLP_VERSION_STR)
|
|
// ditto above
|
|
std::ostream(std::_Noinit),
|
|
#else
|
|
std::ostream(0),
|
|
#endif
|
|
OptionalExceptions(q.throw_exceptions()),
|
|
Lockable(q.locked()),
|
|
def(q.def),
|
|
conn_(q.conn_),
|
|
success_(q.success_)
|
|
{
|
|
init(&sbuffer_);
|
|
}
|
|
|
|
|
|
Query&
|
|
Query::operator=(const Query& rhs)
|
|
{
|
|
set_exceptions(rhs.throw_exceptions());
|
|
set_lock(rhs.locked());
|
|
def = rhs.def;
|
|
conn_ = rhs.conn_;
|
|
success_ = rhs.success_;
|
|
|
|
return *this;
|
|
}
|
|
|
|
|
|
my_ulonglong
|
|
Query::affected_rows() const
|
|
{
|
|
return conn_->affected_rows();
|
|
}
|
|
|
|
|
|
std::string
|
|
Query::error()
|
|
{
|
|
return conn_->error();
|
|
}
|
|
|
|
|
|
bool
|
|
Query::exec(const std::string& str)
|
|
{
|
|
success_ = !mysql_real_query(&conn_->mysql_, str.data(),
|
|
static_cast<unsigned long>(str.length()));
|
|
if (!success_ && throw_exceptions()) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
return success_;
|
|
}
|
|
}
|
|
|
|
|
|
ResNSel
|
|
Query::execute(const SQLString& str)
|
|
{
|
|
if ((parse_elems_.size() == 2) && !def.processing_) {
|
|
// We're a template query and we haven't gone through this path
|
|
// before, so take str to be a lone parameter for the query.
|
|
// We will come back through this function with a completed
|
|
// query, but the processing_ flag will be reset, allowing us to
|
|
// take the 'else' path, avoiding an infinite loop.
|
|
AutoFlag<> af(def.processing_);
|
|
return execute(SQLQueryParms() << str);
|
|
}
|
|
else {
|
|
// Take str to be the entire query string
|
|
return execute(str.data(), str.length());
|
|
}
|
|
}
|
|
|
|
|
|
ResNSel
|
|
Query::execute(const char* str)
|
|
{
|
|
return execute(SQLString(str));
|
|
}
|
|
|
|
|
|
ResNSel
|
|
Query::execute(const char* str, size_t len)
|
|
{
|
|
if (lock()) {
|
|
success_ = false;
|
|
if (throw_exceptions()) {
|
|
throw LockFailed();
|
|
}
|
|
else {
|
|
return ResNSel();
|
|
}
|
|
}
|
|
|
|
success_ = !mysql_real_query(&conn_->mysql_, str, len);
|
|
|
|
unlock();
|
|
if (success_) {
|
|
return ResNSel(conn_);
|
|
}
|
|
else if (throw_exceptions()) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
return ResNSel();
|
|
}
|
|
}
|
|
|
|
|
|
#if !defined(DOXYGEN_IGNORE)
|
|
// Doxygen will not generate documentation for this section.
|
|
|
|
ResNSel
|
|
Query::execute(SQLQueryParms& p)
|
|
{
|
|
return execute(str(p, parse_elems_.size() ? DONT_RESET : RESET_QUERY));
|
|
}
|
|
|
|
#endif // !defined(DOXYGEN_IGNORE)
|
|
|
|
|
|
std::string
|
|
Query::info()
|
|
{
|
|
return conn_->info();
|
|
}
|
|
|
|
|
|
my_ulonglong
|
|
Query::insert_id()
|
|
{
|
|
return conn_->insert_id();
|
|
}
|
|
|
|
|
|
bool
|
|
Query::lock()
|
|
{
|
|
return conn_->lock();
|
|
}
|
|
|
|
|
|
bool
|
|
Query::more_results()
|
|
{
|
|
#if MYSQL_VERSION_ID > 41000 // only in MySQL v4.1 +
|
|
return mysql_more_results(&conn_->mysql_);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
void
|
|
Query::parse()
|
|
{
|
|
std::string str = "";
|
|
char num[4];
|
|
std::string name;
|
|
char *s, *s0;
|
|
s0 = s = preview_char();
|
|
while (*s) {
|
|
if (*s == '%') {
|
|
// Following might be a template parameter declaration...
|
|
s++;
|
|
if (*s == '%') {
|
|
// Doubled percent sign, so insert literal percent sign.
|
|
str += *s++;
|
|
}
|
|
else if (isdigit(*s)) {
|
|
// Number following percent sign, so it signifies a
|
|
// positional parameter. First step: find position
|
|
// value, up to 3 digits long.
|
|
num[0] = *s;
|
|
s++;
|
|
if (isdigit(*s)) {
|
|
num[1] = *s;
|
|
num[2] = 0;
|
|
s++;
|
|
if (isdigit(*s)) {
|
|
num[2] = *s;
|
|
num[3] = 0;
|
|
s++;
|
|
}
|
|
else {
|
|
num[2] = 0;
|
|
}
|
|
}
|
|
else {
|
|
num[1] = 0;
|
|
}
|
|
signed char n = atoi(num);
|
|
|
|
// Look for option character following position value.
|
|
char option = ' ';
|
|
if (*s == 'q' || *s == 'Q' || *s == 'r' || *s == 'R') {
|
|
option = *s++;
|
|
}
|
|
|
|
// Is it a named parameter?
|
|
if (*s == ':') {
|
|
// Save all alphanumeric and underscore characters
|
|
// following colon as parameter name.
|
|
s++;
|
|
for (/* */; isalnum(*s) || *s == '_'; ++s) {
|
|
name += *s;
|
|
}
|
|
|
|
// Eat trailing colon, if it's present.
|
|
if (*s == ':') {
|
|
s++;
|
|
}
|
|
|
|
// Update maps that translate parameter name to
|
|
// number and vice versa.
|
|
if (n >= static_cast<short>(parsed_names_.size())) {
|
|
parsed_names_.insert(parsed_names_.end(),
|
|
static_cast<std::vector<std::string>::size_type>(
|
|
n + 1) - parsed_names_.size(),
|
|
std::string());
|
|
}
|
|
parsed_names_[n] = name;
|
|
parsed_nums_[name] = n;
|
|
}
|
|
|
|
// Finished parsing parameter; save it.
|
|
parse_elems_.push_back(SQLParseElement(str, option, n));
|
|
str = "";
|
|
name = "";
|
|
}
|
|
else {
|
|
// Insert literal percent sign, because sign didn't
|
|
// precede a valid parameter string; this allows users
|
|
// to play a little fast and loose with the rules,
|
|
// avoiding a double percent sign here.
|
|
str += '%';
|
|
}
|
|
}
|
|
else {
|
|
// Regular character, so just copy it.
|
|
str += *s++;
|
|
}
|
|
}
|
|
|
|
parse_elems_.push_back(SQLParseElement(str, ' ', -1));
|
|
delete[] s0;
|
|
}
|
|
|
|
|
|
SQLString*
|
|
Query::pprepare(char option, SQLString& S, bool replace)
|
|
{
|
|
if (S.processed) {
|
|
return &S;
|
|
}
|
|
|
|
if (option == 'r' || (option == 'q' && S.is_string)) {
|
|
char *s = new char[S.size() * 2 + 1];
|
|
mysql_real_escape_string(&conn_->mysql_, s, S.data(),
|
|
static_cast<unsigned long>(S.size()));
|
|
SQLString *ss = new SQLString("'");
|
|
*ss += s;
|
|
*ss += "'";
|
|
delete[] s;
|
|
|
|
if (replace) {
|
|
S = *ss;
|
|
S.processed = true;
|
|
delete ss;
|
|
return &S;
|
|
}
|
|
else {
|
|
return ss;
|
|
}
|
|
}
|
|
else if (option == 'R' || (option == 'Q' && S.is_string)) {
|
|
SQLString *ss = new SQLString("'" + S + "'");
|
|
|
|
if (replace) {
|
|
S = *ss;
|
|
S.processed = true;
|
|
delete ss;
|
|
return &S;
|
|
}
|
|
else {
|
|
return ss;
|
|
}
|
|
}
|
|
else {
|
|
if (replace) {
|
|
S.processed = true;
|
|
}
|
|
return &S;
|
|
}
|
|
}
|
|
|
|
|
|
char*
|
|
Query::preview_char()
|
|
{
|
|
const std::string& str(sbuffer_.str());
|
|
char* s = new char[str.size() + 1];
|
|
memcpy(s, str.data(), str.size());
|
|
s[str.size()] = '\0';
|
|
return s;
|
|
}
|
|
|
|
|
|
void
|
|
Query::proc(SQLQueryParms& p)
|
|
{
|
|
sbuffer_.str("");
|
|
|
|
for (std::vector<SQLParseElement>::iterator i = parse_elems_.begin();
|
|
i != parse_elems_.end(); ++i) {
|
|
MYSQLPP_QUERY_THISPTR << i->before;
|
|
int num = i->num;
|
|
if (num >= 0) {
|
|
SQLQueryParms* c;
|
|
if (size_t(num) < p.size()) {
|
|
c = &p;
|
|
}
|
|
else if (size_t(num) < def.size()) {
|
|
c = &def;
|
|
}
|
|
else {
|
|
*this << " ERROR";
|
|
throw BadParamCount(
|
|
"Not enough parameters to fill the template.");
|
|
}
|
|
|
|
SQLString& param = (*c)[num];
|
|
SQLString* ss = pprepare(i->option, param, c->bound());
|
|
MYSQLPP_QUERY_THISPTR << *ss;
|
|
if (ss != ¶m) {
|
|
// pprepare() returned a new string object instead of
|
|
// updating param in place, so we need to delete it.
|
|
delete ss;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
Query::reset()
|
|
{
|
|
seekp(0);
|
|
clear();
|
|
sbuffer_.str("");
|
|
|
|
parse_elems_.clear();
|
|
def.clear();
|
|
}
|
|
|
|
|
|
Result
|
|
Query::store(const SQLString& str)
|
|
{
|
|
if ((parse_elems_.size() == 2) && !def.processing_) {
|
|
// We're a template query and we haven't gone through this path
|
|
// before, so take str to be a lone parameter for the query.
|
|
// We will come back through this function with a completed
|
|
// query, but the processing_ flag will be reset, allowing us to
|
|
// take the 'else' path, avoiding an infinite loop.
|
|
AutoFlag<> af(def.processing_);
|
|
return store(SQLQueryParms() << str);
|
|
}
|
|
else {
|
|
// Take str to be the entire query string
|
|
return store(str.data(), str.length());
|
|
}
|
|
}
|
|
|
|
|
|
Result
|
|
Query::store(const char* str)
|
|
{
|
|
return store(SQLString(str));
|
|
}
|
|
|
|
|
|
Result
|
|
Query::store(const char* str, size_t len)
|
|
{
|
|
if (lock()) {
|
|
success_ = false;
|
|
if (throw_exceptions()) {
|
|
throw LockFailed();
|
|
}
|
|
else {
|
|
return Result();
|
|
}
|
|
}
|
|
|
|
|
|
if (success_ = !mysql_real_query(&conn_->mysql_, str, len)) {
|
|
MYSQL_RES* res = mysql_store_result(&conn_->mysql_);
|
|
if (res) {
|
|
unlock();
|
|
return Result(res, throw_exceptions());
|
|
}
|
|
else {
|
|
success_ = false;
|
|
}
|
|
}
|
|
unlock();
|
|
|
|
// One of the MySQL API calls failed, but it's not an error if we
|
|
// just get an empty result set. It happens when store()ing a query
|
|
// that doesn't always return results. While it's better to use
|
|
// exec*() in that situation, it's legal to call store() instead,
|
|
// and sometimes you have no choice. For example, if the SQL comes
|
|
// from outside the program so you can't predict whether there will
|
|
// be results.
|
|
if (conn_->errnum() && throw_exceptions()) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
return Result();
|
|
}
|
|
}
|
|
|
|
|
|
#if !defined(DOXYGEN_IGNORE)
|
|
// Doxygen will not generate documentation for this section.
|
|
|
|
Result
|
|
Query::store(SQLQueryParms& p)
|
|
{
|
|
return store(str(p, parse_elems_.size() ? DONT_RESET : RESET_QUERY));
|
|
}
|
|
|
|
#endif // !defined(DOXYGEN_IGNORE)
|
|
|
|
|
|
Result
|
|
Query::store_next()
|
|
{
|
|
#if MYSQL_VERSION_ID > 41000 // only in MySQL v4.1 +
|
|
if (lock()) {
|
|
if (throw_exceptions()) {
|
|
throw LockFailed();
|
|
}
|
|
else {
|
|
return Result();
|
|
}
|
|
}
|
|
|
|
int ret;
|
|
if ((ret = mysql_next_result(&conn_->mysql_)) == 0) {
|
|
// There are more results, so return next result set.
|
|
MYSQL_RES* res = mysql_store_result(&conn_->mysql_);
|
|
unlock();
|
|
if (res) {
|
|
return Result(res, throw_exceptions());
|
|
}
|
|
else {
|
|
// Result set is null, but throw an exception only i it is
|
|
// null because of some error. If not, it's just an empty
|
|
// result set, which is harmless. We return an empty result
|
|
// set if exceptions are disabled, as well.
|
|
if (conn_->errnum() && throw_exceptions()) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
return Result();
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// No more results, or some other error occurred.
|
|
unlock();
|
|
if (throw_exceptions()) {
|
|
if (ret > 0) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
throw EndOfResultSets();
|
|
}
|
|
}
|
|
else {
|
|
return Result();
|
|
}
|
|
}
|
|
#else
|
|
return store();
|
|
#endif // MySQL v4.1+
|
|
}
|
|
|
|
|
|
std::string
|
|
Query::str(SQLQueryParms& p)
|
|
{
|
|
if (!parse_elems_.empty()) {
|
|
proc(p);
|
|
}
|
|
|
|
return sbuffer_.str();
|
|
}
|
|
|
|
|
|
std::string
|
|
Query::str(SQLQueryParms& p, query_reset r)
|
|
{
|
|
std::string tmp = str(p);
|
|
if (r == RESET_QUERY) {
|
|
reset();
|
|
}
|
|
return tmp;
|
|
}
|
|
|
|
|
|
bool
|
|
Query::success()
|
|
{
|
|
return success_ && conn_->success();
|
|
}
|
|
|
|
|
|
void
|
|
Query::unlock()
|
|
{
|
|
conn_->unlock();
|
|
}
|
|
|
|
|
|
ResUse
|
|
Query::use(const SQLString& str)
|
|
{
|
|
if ((parse_elems_.size() == 2) && !def.processing_) {
|
|
// We're a template query and we haven't gone through this path
|
|
// before, so take str to be a lone parameter for the query.
|
|
// We will come back through this function with a completed
|
|
// query, but the processing_ flag will be reset, allowing us to
|
|
// take the 'else' path, avoiding an infinite loop.
|
|
AutoFlag<> af(def.processing_);
|
|
return use(SQLQueryParms() << str);
|
|
}
|
|
else {
|
|
// Take str to be the entire query string
|
|
return use(str.data(), str.length());
|
|
}
|
|
}
|
|
|
|
|
|
ResUse
|
|
Query::use(const char* str)
|
|
{
|
|
return use(SQLString(str));
|
|
}
|
|
|
|
|
|
ResUse
|
|
Query::use(const char* str, size_t len)
|
|
{
|
|
if (lock()) {
|
|
success_ = false;
|
|
if (throw_exceptions()) {
|
|
throw LockFailed();
|
|
}
|
|
else {
|
|
return ResUse();
|
|
}
|
|
}
|
|
|
|
if (success_ = !mysql_real_query(&conn_->mysql_, str, len)) {
|
|
MYSQL_RES* res = mysql_use_result(&conn_->mysql_);
|
|
if (res) {
|
|
unlock();
|
|
return ResUse(res, conn_, throw_exceptions());
|
|
}
|
|
}
|
|
unlock();
|
|
|
|
// One of the MySQL API calls failed, but it's not an error if we
|
|
// just get an empty result set. It happens when use()ing a query
|
|
// that doesn't always return results. While it's better to use
|
|
// exec*() in that situation, it's legal to call use() instead, and
|
|
// sometimes you have no choice. For example, if the SQL comes
|
|
// from outside the program so you can't predict whether there will
|
|
// be results.
|
|
if (conn_->errnum() && throw_exceptions()) {
|
|
throw BadQuery(error());
|
|
}
|
|
else {
|
|
return ResUse();
|
|
}
|
|
}
|
|
|
|
|
|
#if !defined(DOXYGEN_IGNORE)
|
|
// Doxygen will not generate documentation for this section.
|
|
|
|
ResUse
|
|
Query::use(SQLQueryParms& p)
|
|
{
|
|
return use(str(p, parse_elems_.size() ? DONT_RESET : RESET_QUERY));
|
|
}
|
|
|
|
#endif // !defined(DOXYGEN_IGNORE)
|
|
|
|
} // end namespace mysqlpp
|
|
|