/mysql++/query.cpp
C++ | 696 lines | 494 code | 93 blank | 109 comment | 131 complexity | a24ac05f96c91025712734aa3b899624 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-3.0, LGPL-2.0, GPL-3.0
- /***********************************************************************
- query.cpp - Implements the Query class.
- Copyright (c) 1998 by Kevin Atkinson, (c) 1999-2001 by MySQL AB, and
- (c) 2004-2009 by Educational Technology Resources, Inc. Others may
- also hold copyrights on code in this file. See the CREDITS.txt 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 "dbdriver.h"
- #include "connection.h"
- namespace mysqlpp {
- // Force insertfrom() policy template instantiation. Required to make
- // VC++ happy.
- Query::RowCountInsertPolicy<Transaction> RowCountInsertPolicyI(0);
- Query::SizeThresholdInsertPolicy<Transaction> SizeThresholdInsertPolicyI(0);
- Query::MaxPacketInsertPolicy<Transaction> MaxPacketInsertPolicyI(0);
- Query::Query(Connection* c, bool te, const char* qstr) :
- #if defined(MYSQLPP_HAVE_STD__NOINIT)
- // prevents a double-init memory leak in native VC++ RTL (not STLport!)
- std::ostream(std::_Noinit),
- #else
- std::ostream(0),
- #endif
- OptionalExceptions(te),
- template_defaults(this),
- conn_(c),
- copacetic_(true)
- {
- // Set up our internal IOStreams string buffer
- init(&sbuffer_);
- // Insert passed query string into our string buffer, if given
- if (qstr) {
- sbuffer_.str(qstr);
- seekp(0, std::ios::end); // allow more insertions at end
- }
- // Override any global locale setting; we want to use the classic C
- // locale so we don't get weird things like thousands separators in
- // integers inserted into the query stream.
- imbue(std::locale::classic());
- }
- Query::Query(const Query& q) :
- #if defined(MYSQLPP_HAVE_STD__NOINIT)
- // ditto above
- std::ostream(std::_Noinit),
- #else
- std::ostream(0),
- #endif
- OptionalExceptions(q.throw_exceptions())
- {
- // Set up our internal IOStreams string buffer
- init(&sbuffer_);
- // See above for reason we override locale for Query streams.
- imbue(std::locale::classic());
- // Copy the other query as best we can
- operator =(q);
- }
- ulonglong
- Query::affected_rows()
- {
- return conn_->driver()->affected_rows();
- }
- int
- Query::errnum() const
- {
- return conn_->errnum();
- }
- const char*
- Query::error() const
- {
- return conn_->error();
- }
- size_t
- Query::escape_string(std::string* ps, const char* original,
- size_t length) const
- {
- if (conn_ && *conn_) {
- // Normal case
- return conn_->driver()->escape_string(ps, original, length);
- }
- else {
- // Should only happen in test/test_manip.cpp, since it doesn't
- // want to open a DB connection just to test the manipulators.
- return DBDriver::escape_string_no_conn(ps, original, length);
- }
- }
- size_t
- Query::escape_string(char* escaped, const char* original,
- size_t length) const
- {
- if (conn_ && *conn_) {
- // Normal case
- return conn_->driver()->escape_string(escaped, original, length);
- }
- else {
- // Should only happen in test/test_manip.cpp, since it doesn't
- // want to open a DB connection just to test the manipulators.
- return DBDriver::escape_string_no_conn(escaped, original, length);
- }
- }
- bool
- Query::exec(const std::string& str)
- {
- if ((copacetic_ = conn_->driver()->execute(str.data(),
- static_cast<unsigned long>(str.length()))) == true) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return true;
- }
- else if (throw_exceptions()) {
- throw BadQuery(error(), errnum());
- }
- else {
- return false;
- }
- }
- SimpleResult
- Query::execute()
- {
- AutoFlag<> af(template_defaults.processing_);
- return execute(str(template_defaults));
- }
- SimpleResult
- Query::execute(SQLQueryParms& p)
- {
- AutoFlag<> af(template_defaults.processing_);
- return execute(str(p));
- }
- SimpleResult
- Query::execute(const SQLTypeAdapter& s)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return execute(SQLQueryParms() << s);
- }
- else {
- // Take s to be the entire query string
- return execute(s.data(), s.length());
- }
- }
- SimpleResult
- Query::execute(const char* str, size_t len)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return execute(SQLQueryParms() << str << len );
- }
- if ((copacetic_ = conn_->driver()->execute(str, len)) == true) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return SimpleResult(conn_, insert_id(), affected_rows(), info());
- }
- else if (throw_exceptions()) {
- throw BadQuery(error(), errnum());
- }
- else {
- return SimpleResult();
- }
- }
- std::string
- Query::info()
- {
- return conn_->driver()->query_info();
- }
- ulonglong
- Query::insert_id()
- {
- return conn_->driver()->insert_id();
- }
- bool
- Query::more_results()
- {
- return conn_->driver()->more_results();
- }
- Query&
- Query::operator=(const Query& rhs)
- {
- set_exceptions(rhs.throw_exceptions());
- template_defaults = rhs.template_defaults;
- conn_ = rhs.conn_;
- copacetic_ = rhs.copacetic_;
- *this << rhs.sbuffer_.str();
- parse_elems_ = rhs.parse_elems_;
- parsed_names_ = rhs.parsed_names_;
- parsed_nums_ = rhs.parsed_nums_;
- return *this;
- }
- Query::operator void*() const
- {
- return *conn_ && copacetic_ ? const_cast<Query*>(this) : 0;
- }
- void
- Query::parse()
- {
- std::string str = "";
- char num[4];
- std::string name;
- char* s = new char[sbuffer_.str().size() + 1];
- memcpy(s, sbuffer_.str().data(), sbuffer_.str().size());
- s[sbuffer_.str().size()] = '\0';
- const char* s0 = s;
- 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') {
- 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;
- }
- SQLTypeAdapter*
- Query::pprepare(char option, SQLTypeAdapter& S, bool replace)
- {
- if (S.is_processed()) {
- return &S;
- }
- if (option == 'q') {
- std::string temp(S.quote_q() ? "'" : "", S.quote_q() ? 1 : 0);
- if (S.escape_q()) {
- char *escaped = new char[S.size() * 2 + 1];
- size_t len = conn_->driver()->escape_string(escaped,
- S.data(), static_cast<unsigned long>(S.size()));
- temp.append(escaped, len);
- delete[] escaped;
- }
- else {
- temp.append(S.data(), S.length());
- }
- if (S.quote_q()) temp.append("'", 1);
- SQLTypeAdapter* ss = new SQLTypeAdapter(temp);
- if (replace) {
- S = *ss;
- S.set_processed();
- delete ss;
- return &S;
- }
- else {
- return ss;
- }
- }
- else if (option == 'Q' && S.quote_q()) {
- std::string temp("'", 1);
- temp.append(S.data(), S.length());
- temp.append("'", 1);
- SQLTypeAdapter *ss = new SQLTypeAdapter(temp);
- if (replace) {
- S = *ss;
- S.set_processed();
- delete ss;
- return &S;
- }
- else {
- return ss;
- }
- }
- else {
- if (replace) {
- S.set_processed();
- }
- 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) < template_defaults.size()) {
- c = &template_defaults;
- }
- else {
- *this << " ERROR";
- throw BadParamCount(
- "Not enough parameters to fill the template.");
- }
- SQLTypeAdapter& param = (*c)[num];
- if (param.is_null()) {
- MYSQLPP_QUERY_THISPTR << "NULL";
- }
- else {
- SQLTypeAdapter* 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();
- template_defaults.clear();
- }
- StoreQueryResult
- Query::store()
- {
- AutoFlag<> af(template_defaults.processing_);
- return store(str(template_defaults));
- }
- StoreQueryResult
- Query::store(SQLQueryParms& p)
- {
- AutoFlag<> af(template_defaults.processing_);
- return store(str(p));
- }
- StoreQueryResult
- Query::store(const SQLTypeAdapter& s)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return store(SQLQueryParms() << s);
- }
- else {
- // Take s to be the entire query string
- return store(s.data(), s.length());
- }
- }
- StoreQueryResult
- Query::store(const char* str, size_t len)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return store(SQLQueryParms() << str << len );
- }
- MYSQL_RES* res = 0;
- if ((copacetic_ = conn_->driver()->execute(str, len)) == true) {
- res = conn_->driver()->store_result();
- }
- if (res) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return StoreQueryResult(res, conn_->driver(), throw_exceptions());
- }
- else {
- // Either result set is empty, or there was a problem executing
- // the query or storing its results. Since it's not an error to
- // use store() with queries that never return results (INSERT,
- // DELETE, CREATE, ALTER...) we need to figure out which case
- // this is. (You might use store() instead of execute() for
- // such queries when the query strings come from "outside".)
- copacetic_ = (conn_->errnum() == 0);
- if (copacetic_) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return StoreQueryResult();
- }
- else if (throw_exceptions()) {
- throw BadQuery(error(), errnum());
- }
- else {
- return StoreQueryResult();
- }
- }
- }
- StoreQueryResult
- Query::store_next()
- {
- #if MYSQL_VERSION_ID > 41000 // only in MySQL v4.1 +
- DBDriver::nr_code rc = conn_->driver()->next_result();
- if (rc == DBDriver::nr_more_results) {
- // There are more results, so return next result set.
- MYSQL_RES* res = conn_->driver()->store_result();
- if (res) {
- return StoreQueryResult(res, conn_->driver(),
- 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(), errnum());
- }
- else {
- return StoreQueryResult();
- }
- }
- }
- else if (throw_exceptions()) {
- if (rc == DBDriver::nr_error) {
- throw BadQuery(error(), errnum());
- }
- else if (conn_->errnum()) {
- throw BadQuery(error(), errnum());
- }
- else {
- return StoreQueryResult(); // normal end-of-result-sets case
- }
- }
- else {
- return StoreQueryResult();
- }
- #else
- return store();
- #endif // MySQL v4.1+
- }
- std::string
- Query::str(SQLQueryParms& p)
- {
- if (!parse_elems_.empty()) {
- proc(p);
- }
- return sbuffer_.str();
- }
- UseQueryResult
- Query::use()
- {
- AutoFlag<> af(template_defaults.processing_);
- return use(str(template_defaults));
- }
- UseQueryResult
- Query::use(SQLQueryParms& p)
- {
- AutoFlag<> af(template_defaults.processing_);
- return use(str(p));
- }
- UseQueryResult
- Query::use(const SQLTypeAdapter& s)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return use(SQLQueryParms() << s);
- }
- else {
- // Take s to be the entire query string
- return use(s.data(), s.length());
- }
- }
- UseQueryResult
- Query::use(const char* str, size_t len)
- {
- if (!parse_elems_.empty() && !template_defaults.processing_) {
- // We're a template query and this isn't a recursive call, so
- // take s to be a lone parameter for the query. We will come
- // back in here with a completed query, but the processing_
- // flag will be set, allowing us to avoid an infinite loop.
- AutoFlag<> af(template_defaults.processing_);
- return use(SQLQueryParms() << str << len );
- }
- MYSQL_RES* res = 0;
- if ((copacetic_ = conn_->driver()->execute(str, len)) == true) {
- res = conn_->driver()->use_result();
- }
- if (res) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return UseQueryResult(res, conn_->driver(), throw_exceptions());
- }
- else {
- // See comments in store() above for why we distinguish between
- // empty result sets and actual error returns here.
- copacetic_ = (conn_->errnum() == 0);
- if (copacetic_) {
- if (parse_elems_.size() == 0) {
- // Not a template query, so auto-reset
- reset();
- }
- return UseQueryResult();
- }
- else if (throw_exceptions()) {
- throw BadQuery(error(), errnum());
- }
- else {
- return UseQueryResult();
- }
- }
- }
- } // end namespace mysqlpp