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 }