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

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 <cctype>
0012 #include <sstream>
0013 #include <iostream>
0014 
0015 using namespace soci;
0016 using namespace soci::details;
0017 using namespace soci::details::firebird;
0018 
0019 firebird_statement_backend::firebird_statement_backend(firebird_session_backend &session)
0020     : session_(session), stmtp_(0), sqldap_(NULL), sqlda2p_(NULL),
0021         boundByName_(false), boundByPos_(false), rowsFetched_(0), endOfRowSet_(false), rowsAffectedBulk_(-1LL),
0022             intoType_(eStandard), useType_(eStandard), procedure_(false)
0023 {}
0024 
0025 void firebird_statement_backend::prepareSQLDA(XSQLDA ** sqldap, short size)
0026 {
0027     if (*sqldap != NULL)
0028     {
0029         *sqldap = reinterpret_cast<XSQLDA*>(realloc(*sqldap, XSQLDA_LENGTH(size)));
0030     }
0031     else
0032     {
0033         *sqldap = reinterpret_cast<XSQLDA*>(malloc(XSQLDA_LENGTH(size)));
0034     }
0035 
0036     (*sqldap)->sqln = size;
0037     (*sqldap)->version = 1;
0038 }
0039 
0040 void firebird_statement_backend::alloc()
0041 {
0042     ISC_STATUS stat[stat_size];
0043 
0044     if (isc_dsql_allocate_statement(stat, &session_.dbhp_, &stmtp_))
0045     {
0046         throw_iscerror(stat);
0047     }
0048 }
0049 
0050 void firebird_statement_backend::clean_up()
0051 {
0052     rowsAffectedBulk_ = -1LL;
0053 
0054     ISC_STATUS stat[stat_size];
0055 
0056     if (stmtp_ != 0)
0057     {
0058         if (isc_dsql_free_statement(stat, &stmtp_, DSQL_drop))
0059         {
0060             throw_iscerror(stat);
0061         }
0062         stmtp_ = 0;
0063     }
0064 
0065     if (sqldap_ != NULL)
0066     {
0067         free(sqldap_);
0068         sqldap_ = NULL;
0069     }
0070 
0071     if (sqlda2p_ != NULL)
0072     {
0073         free(sqlda2p_);
0074         sqlda2p_ = NULL;
0075     }
0076 }
0077 
0078 void firebird_statement_backend::rewriteParameters(
0079     std::string const & src, std::vector<char> & dst)
0080 {
0081     std::vector<char>::iterator dst_it = dst.begin();
0082 
0083     // rewrite the query by transforming all named parameters into
0084     // the Firebird question marks (:abc -> ?, etc.)
0085 
0086     enum { eNormal, eInQuotes, eInName } state = eNormal;
0087 
0088     std::string name;
0089     int position = 0;
0090 
0091     for (std::string::const_iterator it = src.begin(), end = src.end();
0092         it != end; ++it)
0093     {
0094         switch (state)
0095         {
0096         case eNormal:
0097             if (*it == '\'')
0098             {
0099                 *dst_it++ = *it;
0100                 state = eInQuotes;
0101             }
0102             else if (*it == ':')
0103             {
0104                 state = eInName;
0105             }
0106             else // regular character, stay in the same state
0107             {
0108                 *dst_it++ = *it;
0109             }
0110             break;
0111         case eInQuotes:
0112             if (*it == '\'')
0113             {
0114                 *dst_it++ = *it;
0115                 state = eNormal;
0116             }
0117             else // regular quoted character
0118             {
0119                 *dst_it++ = *it;
0120             }
0121             break;
0122         case eInName:
0123             if (std::isalnum(*it) || *it == '_')
0124             {
0125                 name += *it;
0126             }
0127             else // end of name
0128             {
0129                 names_.insert(std::pair<std::string, int>(name, position++));
0130                 name.clear();
0131                 *dst_it++ = '?';
0132                 *dst_it++ = *it;
0133                 state = eNormal;
0134             }
0135             break;
0136         }
0137     }
0138 
0139     if (state == eInName)
0140     {
0141         names_.insert(std::pair<std::string, int>(name, position++));
0142         *dst_it++ = '?';
0143     }
0144 
0145     *dst_it = '\0';
0146 }
0147 
0148 namespace
0149 {
0150     int statementType(isc_stmt_handle stmt)
0151     {
0152         int stype;
0153         int length;
0154         char type_item[] = {isc_info_sql_stmt_type};
0155         char res_buffer[8];
0156 
0157         ISC_STATUS stat[stat_size];
0158 
0159         if (isc_dsql_sql_info(stat, &stmt, sizeof(type_item),
0160             type_item, sizeof(res_buffer), res_buffer))
0161         {
0162             throw_iscerror(stat);
0163         }
0164 
0165         if (res_buffer[0] == isc_info_sql_stmt_type)
0166         {
0167             length = isc_vax_integer(res_buffer+1, 2);
0168             stype = isc_vax_integer(res_buffer+3, static_cast<short>(length));
0169         }
0170         else
0171         {
0172             throw soci_error("Can't determine statement type.");
0173         }
0174 
0175         return stype;
0176     }
0177 }
0178 
0179 void firebird_statement_backend::rewriteQuery(
0180     std::string const &query, std::vector<char> &buffer)
0181 {
0182     // buffer for temporary query
0183     std::vector<char> tmpQuery;
0184     std::vector<char>::iterator qItr;
0185 
0186     // buffer for query with named parameters changed to standard ones
0187     std::vector<char> rewQuery(query.size() + 1);
0188 
0189     // take care of named parameters in original query
0190     rewriteParameters(query, rewQuery);
0191 
0192     std::string const prefix("execute procedure ");
0193     std::string const prefix2("select * from ");
0194 
0195     // for procedures, we are preparing statement to determine
0196     // type of procedure.
0197     if (procedure_)
0198     {
0199         tmpQuery.resize(prefix.size() + rewQuery.size());
0200         qItr = tmpQuery.begin();
0201         std::copy(prefix.begin(), prefix.end(), qItr);
0202         qItr += prefix.size();
0203     }
0204     else
0205     {
0206         tmpQuery.resize(rewQuery.size());
0207         qItr = tmpQuery.begin();
0208     }
0209 
0210     // prepare temporary query
0211     std::copy(rewQuery.begin(), rewQuery.end(), qItr);
0212 
0213     // preparing buffers for output parameters
0214     if (sqldap_ == NULL)
0215     {
0216         prepareSQLDA(&sqldap_);
0217     }
0218 
0219     ISC_STATUS stat[stat_size];
0220     isc_stmt_handle tmpStmtp = 0;
0221 
0222     // allocate temporary statement to determine its type
0223     if (isc_dsql_allocate_statement(stat, &session_.dbhp_, &tmpStmtp))
0224     {
0225         throw_iscerror(stat);
0226     }
0227 
0228     // prepare temporary statement
0229     if (isc_dsql_prepare(stat, session_.current_transaction(), &tmpStmtp, 0,
0230         &tmpQuery[0], SQL_DIALECT_V6, sqldap_))
0231     {
0232         throw_iscerror(stat);
0233     }
0234 
0235     // get statement type
0236     int stType = statementType(tmpStmtp);
0237 
0238     // free temporary prepared statement
0239     if (isc_dsql_free_statement(stat, &tmpStmtp, DSQL_drop))
0240     {
0241         throw_iscerror(stat);
0242     }
0243 
0244     // take care of special cases
0245     if (procedure_)
0246     {
0247         // for procedures that return values, we need to use correct syntax
0248         if (sqldap_->sqld != 0)
0249         {
0250             // this is "select" procedure, so we have to change syntax
0251             buffer.resize(prefix2.size() + rewQuery.size());
0252             qItr = buffer.begin();
0253             std::copy(prefix2.begin(), prefix2.end(), qItr);
0254             qItr += prefix2.size();
0255             std::copy(rewQuery.begin(), rewQuery.end(), qItr);
0256 
0257             // that won't be needed anymore
0258             procedure_ = false;
0259 
0260             return;
0261         }
0262     }
0263     else
0264     {
0265         // this is not procedure, so syntax is ok except for named
0266         // parameters in ddl
0267         if (stType == isc_info_sql_stmt_ddl)
0268         {
0269             // this statement is a DDL - we can't rewrite named parameters
0270             // so, we will use original query
0271             buffer.resize(query.size() + 1);
0272             std::copy(query.begin(), query.end(), buffer.begin());
0273 
0274             // that won't be needed anymore
0275             procedure_ = false;
0276 
0277             return;
0278         }
0279     }
0280 
0281     // here we know, that temporary query is OK, so we leave it as is
0282     buffer.resize(tmpQuery.size());
0283     std::copy(tmpQuery.begin(), tmpQuery.end(), buffer.begin());
0284 
0285     // that won't be needed anymore
0286     procedure_ = false;
0287 }
0288 
0289 void firebird_statement_backend::prepare(std::string const & query,
0290                                          statement_type /* eType */)
0291 {
0292     //std::cerr << "prepare: query=" << query << std::endl;
0293     // clear named parametes
0294     names_.clear();
0295 
0296     std::vector<char> queryBuffer;
0297 
0298     // modify query's syntax and prepare buffer for use with
0299     // firebird's api
0300     rewriteQuery(query, queryBuffer);
0301 
0302     ISC_STATUS stat[stat_size];
0303 
0304     // prepare real statement
0305     if (isc_dsql_prepare(stat, session_.current_transaction(), &stmtp_, 0,
0306         &queryBuffer[0], SQL_DIALECT_V6, sqldap_))
0307     {
0308         throw_iscerror(stat);
0309     }
0310 
0311     if (sqldap_->sqln < sqldap_->sqld)
0312     {
0313         // sqlda is too small for all columns. it must be reallocated
0314         prepareSQLDA(&sqldap_, sqldap_->sqld);
0315 
0316         if (isc_dsql_describe(stat, &stmtp_, SQL_DIALECT_V6, sqldap_))
0317         {
0318             throw_iscerror(stat);
0319         }
0320     }
0321 
0322     // preparing input parameters
0323     if (sqlda2p_ == NULL)
0324     {
0325         prepareSQLDA(&sqlda2p_);
0326     }
0327 
0328     if (isc_dsql_describe_bind(stat, &stmtp_, SQL_DIALECT_V6, sqlda2p_))
0329     {
0330         throw_iscerror(stat);
0331     }
0332 
0333     if (sqlda2p_->sqln < sqlda2p_->sqld)
0334     {
0335         // sqlda is too small for all columns. it must be reallocated
0336         prepareSQLDA(&sqlda2p_, sqlda2p_->sqld);
0337 
0338         if (isc_dsql_describe_bind(stat, &stmtp_, SQL_DIALECT_V6, sqlda2p_))
0339         {
0340             throw_iscerror(stat);
0341         }
0342     }
0343 
0344     // prepare buffers for indicators
0345     inds_.clear();
0346     inds_.resize(sqldap_->sqld);
0347 
0348     // reset types of into buffers
0349     intoType_ = eStandard;
0350     intos_.resize(0);
0351 
0352     // reset types of use buffers
0353     useType_ = eStandard;
0354     uses_.resize(0);
0355 }
0356 
0357 
0358 namespace
0359 {
0360     void checkSize(std::size_t actual, std::size_t expected,
0361         std::string const & name)
0362     {
0363         if (actual != expected)
0364         {
0365             std::ostringstream msg;
0366             msg << "Incorrect number of " << name << " variables. "
0367                 << "Expected " << expected << ", got " << actual;
0368             throw soci_error(msg.str());
0369         }
0370     }
0371 }
0372 
0373 statement_backend::exec_fetch_result
0374 firebird_statement_backend::execute(int number)
0375 {
0376     ISC_STATUS stat[stat_size];
0377     XSQLDA *t = NULL;
0378 
0379     std::size_t usize = uses_.size();
0380 
0381     // do we have enough into variables ?
0382     checkSize(intos_.size(), sqldap_->sqld, "into");
0383     // do we have enough use variables ?
0384     checkSize(usize, sqlda2p_->sqld, "use");
0385 
0386     // do we have parameters ?
0387     if (sqlda2p_->sqld)
0388     {
0389         t = sqlda2p_;
0390 
0391         if (useType_ == eStandard)
0392         {
0393             for (std::size_t col=0; col<usize; ++col)
0394             {
0395                 static_cast<firebird_standard_use_type_backend*>(uses_[col])->exchangeData();
0396             }
0397         }
0398     }
0399 
0400     // make sure there is no active cursor
0401     if (isc_dsql_free_statement(stat, &stmtp_, DSQL_close))
0402     {
0403         // ignore attempt to close already closed cursor
0404         if (check_iscerror(stat, isc_dsql_cursor_close_err) == false)
0405         {
0406             throw_iscerror(stat);
0407         }
0408     }
0409 
0410     if (useType_ == eVector)
0411     {
0412         long long rowsAffectedBulkTemp = 0;
0413 
0414         // Here we have to explicitly loop to achieve the
0415         // effect of inserting or updating with vector use elements.
0416         std::size_t rows = static_cast<firebird_vector_use_type_backend*>(uses_[0])->size();
0417         for (std::size_t row=0; row < rows; ++row)
0418         {
0419             // first we have to prepare input parameters
0420             for (std::size_t col=0; col<usize; ++col)
0421             {
0422                 static_cast<firebird_vector_use_type_backend*>(uses_[col])->exchangeData(row);
0423             }
0424 
0425             // then execute query
0426             if (isc_dsql_execute(stat, session_.current_transaction(), &stmtp_, SQL_DIALECT_V6, t))
0427             {
0428                 // preserve the number of rows affected so far.
0429                 rowsAffectedBulk_ = rowsAffectedBulkTemp;
0430                 throw_iscerror(stat);
0431             }
0432             else
0433             {
0434                 rowsAffectedBulkTemp += get_affected_rows();
0435             }
0436             // soci does not allow bulk insert/update and bulk select operations
0437             // in same query. So here, we know that into elements are not
0438             // vectors. So, there is no need to fetch data here.
0439         }
0440         rowsAffectedBulk_ = rowsAffectedBulkTemp;
0441     }
0442     else
0443     {
0444         // use elements aren't vectors
0445         if (isc_dsql_execute(stat, session_.current_transaction(), &stmtp_, SQL_DIALECT_V6, t))
0446         {
0447             throw_iscerror(stat);
0448         }
0449     }
0450 
0451     // Successfully re-executing the statement must reset the "end of rowset"
0452     // flag, we might be able to fetch data again now.
0453     endOfRowSet_ = false;
0454 
0455     if (sqldap_->sqld)
0456     {
0457         // query may return some data
0458         if (number > 0)
0459         {
0460             // number contains size of input variables, so we may fetch() data here
0461             return fetch(number);
0462         }
0463         else
0464         {
0465             // execute(0) was meant to only perform the query
0466             return ef_success;
0467         }
0468     }
0469     else
0470     {
0471         // query can't return any data
0472         return ef_no_data;
0473     }
0474 }
0475 
0476 statement_backend::exec_fetch_result
0477 firebird_statement_backend::fetch(int number)
0478 {
0479     if (endOfRowSet_)
0480         return ef_no_data;
0481 
0482     ISC_STATUS stat[stat_size];
0483 
0484     for (size_t i = 0; i<static_cast<unsigned int>(sqldap_->sqld); ++i)
0485     {
0486         inds_[i].resize(number > 0 ? number : 1);
0487     }
0488 
0489     // Here we have to explicitly loop to achieve the effect of fetching
0490     // vector into elements. After each fetch, we have to exchange data
0491     // with into buffers.
0492     rowsFetched_ = 0;
0493     for (int i = 0; i < number; ++i)
0494     {
0495         ISC_STATUS const
0496             fetch_stat = isc_dsql_fetch(stat, &stmtp_, SQL_DIALECT_V6, sqldap_);
0497 
0498         // there is more data to read
0499         if (fetch_stat == 0)
0500         {
0501             ++rowsFetched_;
0502             exchangeData(true, i);
0503         }
0504         else if (fetch_stat == 100L)
0505         {
0506             endOfRowSet_ = true;
0507             return ef_no_data;
0508         }
0509         else
0510         {
0511             // error
0512             endOfRowSet_ = true;
0513             throw_iscerror(stat);
0514             return ef_no_data; // unreachable, for compiler only
0515         }
0516     } // for
0517 
0518     return ef_success;
0519 }
0520 
0521 // here we put data fetched from database into user buffers
0522 void firebird_statement_backend::exchangeData(bool gotData, int row)
0523 {
0524     if (gotData)
0525     {
0526         for (size_t i = 0; i < static_cast<unsigned int>(sqldap_->sqld); ++i)
0527         {
0528             // first save indicators
0529             if (((sqldap_->sqlvar+i)->sqltype & 1) == 0)
0530             {
0531                 // there is no indicator for this column
0532                 inds_[i][row] = i_ok;
0533             }
0534             else if (*((sqldap_->sqlvar+i)->sqlind) == 0)
0535             {
0536                 inds_[i][row] = i_ok;
0537             }
0538             else if (*((sqldap_->sqlvar+i)->sqlind) == -1)
0539             {
0540                 inds_[i][row] = i_null;
0541             }
0542             else
0543             {
0544                 throw soci_error("Unknown state in firebird_statement_backend::exchangeData()");
0545             }
0546 
0547             // then deal with data
0548             if (inds_[i][row] != i_null)
0549             {
0550                 if (intoType_ == eVector)
0551                 {
0552                     static_cast<firebird_vector_into_type_backend*>(
0553                         intos_[i])->exchangeData(row);
0554                 }
0555                 else
0556                 {
0557                     static_cast<firebird_standard_into_type_backend*>(
0558                         intos_[i])->exchangeData();
0559                 }
0560             }
0561         }
0562     }
0563 }
0564 
0565 long long firebird_statement_backend::get_affected_rows()
0566 {
0567     if (rowsAffectedBulk_ >= 0)
0568     {
0569         return rowsAffectedBulk_;
0570     }
0571 
0572     ISC_STATUS_ARRAY stat;
0573     char type_item[] = { isc_info_sql_records };
0574     char res_buffer[256];
0575 
0576     if (isc_dsql_sql_info(stat, &stmtp_, sizeof(type_item), type_item,
0577                           sizeof(res_buffer), res_buffer))
0578     {
0579         throw_iscerror(stat);
0580     }
0581 
0582     // We must get back a isc_info_sql_records block, that we parse below,
0583     // followed by isc_info_end.
0584     if (res_buffer[0] != isc_info_sql_records)
0585     {
0586         throw soci_error("Can't determine the number of affected rows");
0587     }
0588 
0589     char* sql_rec_buf = res_buffer + 1;
0590     const int length = isc_vax_integer(sql_rec_buf, 2);
0591     sql_rec_buf += 2;
0592 
0593     if (sql_rec_buf[length] != isc_info_end)
0594     {
0595         throw soci_error("Unexpected isc_info_sql_records return format");
0596     }
0597 
0598     // Examine the 4 sub-blocks each of which has a header indicating the block
0599     // type, its value length in bytes and the value itself.
0600     long long row_count = 0;
0601 
0602     for ( char* p = sql_rec_buf; !row_count && p < sql_rec_buf + length; )
0603     {
0604         switch (*p++)
0605         {
0606             case isc_info_req_select_count:
0607             case isc_info_req_insert_count:
0608             case isc_info_req_update_count:
0609             case isc_info_req_delete_count:
0610                 {
0611                     int len = isc_vax_integer(p, 2);
0612                     p += 2;
0613 
0614                     row_count += isc_vax_integer(p, static_cast<short>(len));
0615                     p += len;
0616                 }
0617                 break;
0618 
0619             case isc_info_end:
0620                 break;
0621 
0622             default:
0623                 throw soci_error("Unknown record counter");
0624         }
0625     }
0626 
0627     return row_count;
0628 }
0629 
0630 int firebird_statement_backend::get_number_of_rows()
0631 {
0632     return rowsFetched_;
0633 }
0634 
0635 std::string firebird_statement_backend::get_parameter_name(int index) const
0636 {
0637     for (std::map<std::string, int>::const_iterator i = names_.begin();
0638          i != names_.end();
0639          ++i)
0640     {
0641         if (i->second == index)
0642             return i->first;
0643     }
0644 
0645     return std::string();
0646 }
0647 
0648 std::string firebird_statement_backend::rewrite_for_procedure_call(
0649     std::string const &query)
0650 {
0651     procedure_ = true;
0652     return query;
0653 }
0654 
0655 int firebird_statement_backend::prepare_for_describe()
0656 {
0657     return static_cast<int>(sqldap_->sqld);
0658 }
0659 
0660 void firebird_statement_backend::describe_column(int colNum,
0661                                                 data_type & type, std::string & columnName)
0662 {
0663     XSQLVAR * var = sqldap_->sqlvar+(colNum-1);
0664 
0665     columnName.assign(var->aliasname, var->aliasname_length);
0666 
0667     switch (var->sqltype & ~1)
0668     {
0669     case SQL_TEXT:
0670     case SQL_VARYING:
0671         type = dt_string;
0672         break;
0673     case SQL_TYPE_DATE:
0674     case SQL_TYPE_TIME:
0675     case SQL_TIMESTAMP:
0676         type = dt_date;
0677         break;
0678     case SQL_FLOAT:
0679     case SQL_DOUBLE:
0680         type = dt_double;
0681         break;
0682     case SQL_SHORT:
0683     case SQL_LONG:
0684         if (var->sqlscale < 0)
0685         {
0686             if (session_.get_option_decimals_as_strings())
0687                 type = dt_string;
0688             else
0689                 type = dt_double;
0690         }
0691         else
0692         {
0693             type = dt_integer;
0694         }
0695         break;
0696     case SQL_INT64:
0697         if (var->sqlscale < 0)
0698         {
0699             if (session_.get_option_decimals_as_strings())
0700                 type = dt_string;
0701             else
0702                 type = dt_double;
0703         }
0704         else
0705         {
0706             type = dt_long_long;
0707         }
0708         break;
0709         /* case SQL_BLOB:
0710         case SQL_ARRAY:*/
0711     default:
0712         std::ostringstream msg;
0713         msg << "Type of column ["<< colNum << "] \"" << columnName
0714             << "\" is not supported for dynamic queries";
0715         throw soci_error(msg.str());
0716         break;
0717     }
0718 }
0719 
0720 firebird_standard_into_type_backend * firebird_statement_backend::make_into_type_backend()
0721 {
0722     return new firebird_standard_into_type_backend(*this);
0723 }
0724 
0725 firebird_standard_use_type_backend * firebird_statement_backend::make_use_type_backend()
0726 {
0727     return new firebird_standard_use_type_backend(*this);
0728 }
0729 
0730 firebird_vector_into_type_backend * firebird_statement_backend::make_vector_into_type_backend()
0731 {
0732     return new firebird_vector_into_type_backend(*this);
0733 }
0734 
0735 firebird_vector_use_type_backend * firebird_statement_backend::make_vector_use_type_backend()
0736 {
0737     return new firebird_vector_use_type_backend(*this);
0738 }