File indexing completed on 2025-02-23 05:15:16

0001 //
0002 // Copyright (C) 2004-2006 Maciej Sobczak, Stephen Hutton, Rafal Bobrowski
0003 // Distributed under the Boost Software License, Version 1.0.
0004 // (See accompanying file LICENSE_1_0.txt or copy at
0005 // http://www.boost.org/LICENSE_1_0.txt)
0006 //
0007 
0008 #define SOCI_FIREBIRD_SOURCE
0009 #include "soci/firebird/soci-firebird.h"
0010 #include "firebird/error-firebird.h"
0011 #include "soci/session.h"
0012 #include <locale>
0013 #include <map>
0014 #include <sstream>
0015 #include <string>
0016 
0017 using namespace soci;
0018 using namespace soci::details::firebird;
0019 
0020 namespace
0021 {
0022 
0023 // Helpers of explodeISCConnectString() for reading words from a string. "Word"
0024 // here is defined very loosely as just a sequence of non-space characters.
0025 //
0026 // All these helper functions update the input iterator to point to the first
0027 // character not consumed by them.
0028 
0029 // Advance the input iterator until the first non-space character or end of the
0030 // string.
0031 void skipWhiteSpace(std::string::const_iterator& i, std::string::const_iterator const &end)
0032 {
0033     std::locale const loc;
0034     for (; i != end; ++i)
0035     {
0036         if (!std::isspace(*i, loc))
0037             break;
0038     }
0039 }
0040 
0041 // Return the string of all characters until the first space or the specified
0042 // delimiter.
0043 //
0044 // Throws if the first non-space character after the end of the word is not the
0045 // delimiter. However just returns en empty string, without throwing, if
0046 // nothing is left at all in the string except for white space.
0047 std::string
0048 getWordUntil(std::string const &s, std::string::const_iterator &i, char delim)
0049 {
0050     std::string::const_iterator const end = s.end();
0051     skipWhiteSpace(i, end);
0052 
0053     // We need to handle this case specially because it's not an error if
0054     // nothing at all remains in the string. But if anything does remain, then
0055     // we must have the delimiter.
0056     if (i == end)
0057         return std::string();
0058 
0059     // Simply put anything until the delimiter into the word, stopping at the
0060     // first white space character.
0061     std::string word;
0062     std::locale const loc;
0063     for (; i != end; ++i)
0064     {
0065         if (*i == delim)
0066             break;
0067 
0068         if (std::isspace(*i, loc))
0069         {
0070             skipWhiteSpace(i, end);
0071             if (i == end || *i != delim)
0072             {
0073                 std::ostringstream os;
0074                 os << "Expected '" << delim << "' at position "
0075                    << (i - s.begin() + 1)
0076                    << " in Firebird connection string \""
0077                    << s << "\".";
0078 
0079                 throw soci_error(os.str());
0080             }
0081 
0082             break;
0083         }
0084 
0085         word += *i;
0086     }
0087 
0088     if (i == end)
0089     {
0090         std::ostringstream os;
0091         os << "Expected '" << delim
0092            << "' not found before the end of the string "
0093            << "in Firebird connection string \""
0094            << s << "\".";
0095 
0096         throw soci_error(os.str());
0097     }
0098 
0099     ++i;    // Skip the delimiter itself.
0100 
0101     return word;
0102 }
0103 
0104 // Return a possibly quoted word, i.e. either just a sequence of non-space
0105 // characters or everything inside a double-quoted string.
0106 //
0107 // Throws if the word is quoted and the closing quote is not found. However
0108 // doesn't throw, just returns an empty string if there is nothing left.
0109 std::string
0110 getPossiblyQuotedWord(std::string const &s, std::string::const_iterator &i)
0111 {
0112     std::string::const_iterator const end = s.end();
0113     skipWhiteSpace(i, end);
0114 
0115     std::string word;
0116 
0117     if (i != end && *i == '"')
0118     {
0119         for (;;)
0120         {
0121             if (++i == end)
0122             {
0123                 std::ostringstream os;
0124                 os << "Expected '\"' not found before the end of the string "
0125                       "in Firebird connection string \""
0126                    << s << "\".";
0127 
0128                 throw soci_error(os.str());
0129             }
0130 
0131             if (*i == '"')
0132             {
0133                 ++i;
0134                 break;
0135             }
0136 
0137             word += *i;
0138         }
0139     }
0140     else // Not quoted.
0141     {
0142         std::locale const loc;
0143         for (; i != end; ++i)
0144         {
0145             if (std::isspace(*i, loc))
0146                 break;
0147 
0148             word += *i;
0149         }
0150     }
0151 
0152     return word;
0153 }
0154 
0155 // retrieves parameters from the uniform connect string which is supposed to be
0156 // in the form "key=value[ key2=value2 ...]" and the values may be quoted to
0157 // allow including spaces into them. Notice that currently there is no way to
0158 // include both a space and a double quote in a value.
0159 std::map<std::string, std::string>
0160 explodeISCConnectString(std::string const &connectString)
0161 {
0162     std::map<std::string, std::string> parameters;
0163 
0164     std::string key, value;
0165     for (std::string::const_iterator i = connectString.begin(); ; )
0166     {
0167         key = getWordUntil(connectString, i, '=');
0168         if (key.empty())
0169             break;
0170 
0171         value = getPossiblyQuotedWord(connectString, i);
0172 
0173         parameters.insert(std::pair<std::string, std::string>(key, value));
0174     }
0175 
0176     return parameters;
0177 }
0178 
0179 // extracts given parameter from map previusly build with explodeISCConnectString
0180 bool getISCConnectParameter(std::map<std::string, std::string> const & m, std::string const & key,
0181     std::string & value)
0182 {
0183     std::map <std::string, std::string> :: const_iterator i;
0184     value.clear();
0185 
0186     i = m.find(key);
0187 
0188     if (i != m.end())
0189     {
0190         value = i->second;
0191         return true;
0192     }
0193     else
0194     {
0195         return false;
0196     }
0197 }
0198 
0199 void setDPBOption(std::string& dpb, int const option, std::string const & value)
0200 {
0201 
0202     if (dpb.empty())
0203     {
0204         dpb.append(1, static_cast<char>(isc_dpb_version1));
0205     }
0206 
0207     // now we are adding new option
0208     dpb.append(1, static_cast<char>(option));
0209     dpb.append(1, static_cast<char>(value.size()));
0210     dpb.append(value);
0211 }
0212 
0213 } // namespace anonymous
0214 
0215 firebird_session_backend::firebird_session_backend(
0216     connection_parameters const & parameters) : dbhp_(0), trhp_(0)
0217                                          , decimals_as_strings_(false)
0218 {
0219     // extract connection parameters
0220     std::map<std::string, std::string>
0221         params(explodeISCConnectString(parameters.get_connect_string()));
0222 
0223     ISC_STATUS stat[stat_size];
0224     std::string param;
0225 
0226     // preparing connection options
0227     std::string dpb;
0228     if (getISCConnectParameter(params, "user", param))
0229     {
0230         setDPBOption(dpb, isc_dpb_user_name, param);
0231     }
0232 
0233     if (getISCConnectParameter(params, "password", param))
0234     {
0235         setDPBOption(dpb, isc_dpb_password, param);
0236     }
0237 
0238     if (getISCConnectParameter(params, "role", param))
0239     {
0240         setDPBOption(dpb, isc_dpb_sql_role_name, param);
0241     }
0242 
0243     if (getISCConnectParameter(params, "charset", param))
0244     {
0245         setDPBOption(dpb, isc_dpb_lc_ctype, param);
0246     }
0247 
0248     if (getISCConnectParameter(params, "service", param) == false)
0249     {
0250         throw soci_error("Service name not specified.");
0251     }
0252 
0253     // connecting data base
0254     if (isc_attach_database(stat, static_cast<short>(param.size()),
0255         const_cast<char*>(param.c_str()), &dbhp_,
0256         static_cast<short>(dpb.size()), const_cast<char*>(dpb.c_str())))
0257     {
0258         throw_iscerror(stat);
0259     }
0260 
0261     if (getISCConnectParameter(params, "decimals_as_strings", param))
0262     {
0263         decimals_as_strings_ = param == "1" || param == "Y" || param == "y";
0264     }
0265 }
0266 
0267 
0268 void firebird_session_backend::begin()
0269 {
0270     if (trhp_ == 0)
0271     {
0272         ISC_STATUS stat[stat_size];
0273         if (isc_start_transaction(stat, &trhp_, 1, &dbhp_, 0, NULL))
0274         {
0275             throw_iscerror(stat);
0276         }
0277     }
0278 }
0279 
0280 firebird_session_backend::~firebird_session_backend()
0281 {
0282     cleanUp();
0283 }
0284 
0285 bool firebird_session_backend::is_connected()
0286 {
0287     ISC_STATUS stat[stat_size];
0288     ISC_SCHAR req[] = { isc_info_ods_version, isc_info_end };
0289     ISC_SCHAR res[256];
0290 
0291     return isc_database_info(stat, &dbhp_, sizeof(req), req, sizeof(res), res) == 0;
0292 }
0293 
0294 void firebird_session_backend::commit()
0295 {
0296     ISC_STATUS stat[stat_size];
0297 
0298     if (trhp_ != 0)
0299     {
0300         if (isc_commit_transaction(stat, &trhp_))
0301         {
0302             throw_iscerror(stat);
0303         }
0304 
0305         trhp_ = 0;
0306     }
0307 }
0308 
0309 void firebird_session_backend::rollback()
0310 {
0311     ISC_STATUS stat[stat_size];
0312 
0313     if (trhp_ != 0)
0314     {
0315         if (isc_rollback_transaction(stat, &trhp_))
0316         {
0317             throw_iscerror(stat);
0318         }
0319 
0320         trhp_ = 0;
0321     }
0322 }
0323 
0324 isc_tr_handle* firebird_session_backend::current_transaction()
0325 {
0326     // It will do nothing if we're already inside a transaction.
0327     begin();
0328 
0329     return &trhp_;
0330 }
0331 
0332 void firebird_session_backend::cleanUp()
0333 {
0334     ISC_STATUS stat[stat_size];
0335 
0336     // at the end of session our transaction is finally commited.
0337     if (trhp_ != 0)
0338     {
0339         if (isc_commit_transaction(stat, &trhp_))
0340         {
0341             throw_iscerror(stat);
0342         }
0343 
0344         trhp_ = 0;
0345     }
0346 
0347     if (isc_detach_database(stat, &dbhp_))
0348     {
0349         throw_iscerror(stat);
0350     }
0351 
0352     dbhp_ = 0L;
0353 }
0354 
0355 bool firebird_session_backend::get_next_sequence_value(
0356     session & s, std::string const & sequence, long long & value)
0357 {
0358     // We could use isq_execute2() directly but this is even simpler.
0359     s << "select next value for " + sequence + " from rdb$database",
0360           into(value);
0361 
0362     return true;
0363 }
0364 
0365 firebird_statement_backend * firebird_session_backend::make_statement_backend()
0366 {
0367     return new firebird_statement_backend(*this);
0368 }
0369 
0370 details::rowid_backend* firebird_session_backend::make_rowid_backend()
0371 {
0372     throw soci_error("RowIDs are not supported");
0373 }
0374 
0375 firebird_blob_backend * firebird_session_backend::make_blob_backend()
0376 {
0377     return new firebird_blob_backend(*this);
0378 }