📄 resultset.cpp.svn-base
字号:
/* This file is part of libodbc++. Copyright (C) 1999-2000 Manush Dodunekov <manush@stendahls.net> This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library 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 Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING. If not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.*/#include <odbc++/resultset.h>#include <odbc++/statement.h>#include <odbc++/resultsetmetadata.h>#include "datahandler.h"#include "datastream.h"#include "driverinfo.h"#include "dtconv.h"#if defined(ODBCXX_QT)# include <qiodevice.h>#endifusing namespace odbc;using namespace std;// values for location_#define BEFORE_FIRST (-3)#define AFTER_LAST (-2)#define INSERT_ROW (-1)#define UNKNOWN (0)#define LOCATION_IS_VALID (location_>=0)/* Our current location in a result set is stored in location_. If location_>0, we know our position. If it's UNKNOWN we don't but it's still somewhere in the resultset. If location_<0 we're outside or on the insert row. We decide on which of the SQL*Fetch* functions to use in the following way: if (DM_IS_ODBC3) { if(forwardOnly) { SQLFetch(); } else { SQLFetchScroll(); } } else { if(forwardOnly && rowsetSize==1) { SQLFetch(); } else { SQLExtendedFetch(); } } For scrollable ResultSets, we add an extra row to rowset_. A call to moveToInsertRow() would put the rowset position on the last row. When using an ODBC2 driver, a call to insertRow simply does _applyPosition(SQL_ADD) (while on the insert row). With an ODBC3 driver, we have to re-bind all columns to point at the last row with rowset size set to 1 (stupid ODBC bind offsets only work with row-wise binding, and we use column-wise), and then call SQLBulkOperations. That binding is kept in effect until moveToCurrentRow() is called, for the case of the application wanting to insert more rows. When calling moveToCurrentRow() in this case, we have two possibilities: 1. bindPos_ = 0 -> app didn't call insertRow(), we can just set location_ to locBeforeInsert_ and the rowset pos to rowBeforeInsert_. 2. bindPos_ >0 -> app inserted one or more rows using SQLBulkOperations(). Per ODBC3 specs, the actual rowset position is undefined, and we do the following: 1. Restore the rowset size (SQL_ROW_ARRAY_SIZE) 2. If locBeforeInsert_ was valid, use an absolute fetch to get there and restore the rowset size. Otherwise, try to restore it by going after-last or before-first. With a driver that can't report the current row position (eg returns 0 for SQLGetStmtAttr(SQL_ATTR_ROW_NUMBER)), this breaks and leaves a ResultSet before it's first row after an insert. However, I haven't seen an ODBC3 driver that does this yet.*/namespace odbc { // a simple utility to save a value and restore it // at the end of the scope template <class T> class ValueSaver { private: T& orig_; T val_; public: ValueSaver(T& v) :orig_(v), val_(v) {} ~ValueSaver() { orig_=val_; } };};#define CHECK_INSERT_ROW \do { \ if(location_==INSERT_ROW) { \ throw SQLException \ (ODBCXX_STRING_CONST("[libodbc++]: Illegal operation while on insert row")); \ } \} while(false)#define CHECK_SCROLLABLE_CURSOR \do { \ if(this->getType()==TYPE_FORWARD_ONLY) { \ throw SQLException \ (ODBCXX_STRING_CONST("[libodbc++]: Operation not possible on a forward-only cursor")); \ } \} while(false)ResultSet::ResultSet(Statement* stmt, SQLHSTMT hstmt, bool ownStmt) :statement_(stmt), hstmt_(hstmt), ownStatement_(ownStmt), currentFetchSize_(stmt->getFetchSize()), newFetchSize_(currentFetchSize_), rowset_(NULL), rowStatus_(NULL), rowsInRowset_(0), colsBound_(false), streamedColsBound_(false), bindPos_(0), location_(BEFORE_FIRST){ metaData_=new ResultSetMetaData(this); //ODBC says GetData cannot be called on a forward-only cursor //with rowset size > 1 //so, if ResultSetMetaData saw a streamed column, //we 'adjust' the rowset size to 1 if(metaData_->needsGetData_==true && this->getType()==TYPE_FORWARD_ONLY) { currentFetchSize_=1; } newFetchSize_=currentFetchSize_;#if ODBCVER >= 0x0300 // with ODBC3, we call SQLFetchScroll, // and need to set the following statement_->_setPointerOption (SQL_ATTR_ROWS_FETCHED_PTR,(SQLPOINTER)&rowsInRowset_);#endif //this will do all the work - set the rowset size, bind and so on this->_applyFetchSize();}ResultSet::~ResultSet(){ if(colsBound_) { this->_unbindCols(); } // in case some exception caused us // to destruct, these can be bound if(streamedColsBound_) { this->_unbindStreamedCols(); }#if ODBCVER >= 0x0300 // we don't want anything to point to us statement_->_setPointerOption (SQL_ATTR_ROWS_FETCHED_PTR,(SQLPOINTER)NULL); statement_->_setPointerOption (SQL_ATTR_ROW_STATUS_PTR,(SQLPOINTER)NULL);#endif delete rowset_; delete[] rowStatus_; delete metaData_; statement_->_unregisterResultSet(this); //if we own the statement, nuke it if(ownStatement_) { delete statement_; }}//privatevoid ResultSet::_applyFetchSize(){ statement_->_setNumericOption (ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE), currentFetchSize_); // it is possible that the driver changes the // rowset size, so we do an extra check int driverFetchSize=statement_->_getNumericOption (ODBC3_C(SQL_ATTR_ROW_ARRAY_SIZE,SQL_ROWSET_SIZE)); if(driverFetchSize!=currentFetchSize_) { // save these values currentFetchSize_=driverFetchSize; newFetchSize_=driverFetchSize; } if(colsBound_) { this->_unbindCols(); } this->_resetRowset(); if(!colsBound_) { this->_bindCols(); }}//privatevoid ResultSet::_resetRowset(){ delete rowset_; delete[] rowStatus_; //Forward-only resultsets can't have insert rows int extraRows=(this->getType()==TYPE_FORWARD_ONLY?0:1); rowset_=new Rowset(currentFetchSize_+extraRows,//1 for the insert row ODBC3_DC(true,false)); // possibly use ODBC3 c-types rowStatus_=new SQLUSMALLINT[currentFetchSize_+extraRows]; //same here#if ODBCVER >= 0x0300 // since SQLFetchScroll doesn't take this argument, we // need to set it here. statement_->_setPointerOption (SQL_ATTR_ROW_STATUS_PTR,(SQLPOINTER)rowStatus_);#endif //add the datahandlers int nc=metaData_->getColumnCount(); for(int i=1; i<=nc; i++) { int realprec;#if ODBCVER < 0x0300 realprec=metaData_->getPrecision(i);#else if(this->_getDriverInfo()->getMajorVersion()>=3) { switch(metaData_->getColumnType(i)) { case Types::CHAR: case Types::VARCHAR:#if defined(ODBCXX_HAVE_SQLUCODE_H) case Types::WCHAR: case Types::WVARCHAR:#endif case Types::BINARY: case Types::VARBINARY: realprec=metaData_->colLengths_[i-1]; break; default: realprec=metaData_->getPrecision(i); break; } } else { realprec=metaData_->getPrecision(i); }#endif rowset_->addColumn(metaData_->getColumnType(i), realprec, metaData_->getScale(i)); }}//privatevoid ResultSet::_prepareForFetch(){ if(newFetchSize_!=currentFetchSize_) { currentFetchSize_=newFetchSize_; this->_applyFetchSize(); }}SQLRETURN ResultSet::_applyPosition(int mode){ if(this->getType() != TYPE_FORWARD_ONLY) { SQLRETURN r=SQLSetPos(hstmt_, (SQLUSMALLINT)rowset_->getCurrentRow()+1, (SQLUSMALLINT)mode, SQL_LOCK_NO_CHANGE); this->_checkStmtError(hstmt_,r,ODBCXX_STRING_CONST("SQLSetPos failed")); return r; } return SQL_SUCCESS;}void ResultSet::_bindCols(){ int nc=metaData_->getColumnCount(); SQLRETURN r; bindPos_=rowset_->getCurrentRow(); colsBound_=true; for(int i=1; i<=nc; i++) { DataHandler* dh=rowset_->getColumn(i); if(!dh->isStreamed_) { r=SQLBindCol(hstmt_, (SQLUSMALLINT)i, dh->cType_, (SQLPOINTER)dh->data(), dh->bufferSize_, &dh->dataStatus_[dh->currentRow_]); this->_checkStmtError(hstmt_,r,ODBCXX_STRING_CONST("Error binding column")); } }}//this we do before an update or insertvoid ResultSet::_bindStreamedCols(){ int nc=metaData_->getColumnCount(); unsigned int cr=rowset_->getCurrentRow(); for(int i=1; i<=nc; i++) { DataHandler* dh=rowset_->getColumn(i); if(dh->isStreamed_) { streamedColsBound_=true; SQLRETURN r=SQLBindCol(hstmt_, (SQLUSMALLINT)i, dh->cType_, (SQLPOINTER)i, //our column number (for SQLParamData) 0, &dh->dataStatus_[dh->currentRow_]); //dh->dataStatus_ should be SQL_LEN_DATA_AT_EXEC(len) by this point this->_checkStmtError(hstmt_,r,ODBCXX_STRING_CONST("Error binding column")); } }}void ResultSet::_unbindStreamedCols(){ int nc=metaData_->getColumnCount(); streamedColsBound_=false; for(int i=1; i<=nc; i++) { DataHandler* dh=rowset_->getColumn(i); if(dh->isStreamed_) { SQLRETURN r=SQLBindCol(hstmt_, (SQLUSMALLINT)i, dh->cType_, 0, //setting this to NULL means unbind this column 0, dh->dataStatus_); this->_checkStmtError(hstmt_,r,ODBCXX_STRING_CONST("Error unbinding column")); } }}//privatevoid ResultSet::_unbindCols(){ //we don't perform an error check here, since //this is called from the destructor; //egcs dislikes exceptions thrown from destructors. SQLFreeStmt(hstmt_,SQL_UNBIND); colsBound_=false;}//private//all actual fetching is done herevoid ResultSet::_doFetch(int fetchType, int rowNum){ SQLRETURN r; bool isfo=this->getType()==TYPE_FORWARD_ONLY; // if isfo is true fetchType can only be SQL_FETCH_NEXT if(isfo#if ODBCVER < 0x0300 && currentFetchSize_==1#endif ) { // the only way to get here assert(fetchType==SQL_FETCH_NEXT); // ODBC3 says SQLFetch can do rowset sizes > 1 r=SQLFetch(hstmt_);#if ODBCVER < 0x0300 // this won't get updated otherwise rowsInRowset_=1;#endif } else { // we have either a scrolling cursor or a // rowset size > 1 in ODBC2#if ODBCVER < 0x0300 r=SQLExtendedFetch(hstmt_, (SQLUSMALLINT)fetchType, (SQLINTEGER)rowNum, &rowsInRowset_, rowStatus_);#else r=SQLFetchScroll(hstmt_, (SQLUSMALLINT)fetchType, (SQLINTEGER)rowNum);#endif } this->_checkStmtError(hstmt_,r,ODBCXX_STRING_CONST("Error fetching data from datasource")); rowset_->setCurrentRow(0); // fixup for weird behaviour experienced with myodbc 2.50.28: // while on the first row of the result set, a call to SQLExtendedFetch(SQL_FETCH_PRIOR) // would set rowsInRowset_ to 0 but return SQL_SUCCESS_WITH_INFO. if(rowsInRowset_==0 && r!=ODBC3_C(SQL_NO_DATA,SQL_NO_DATA_FOUND)) { r=ODBC3_C(SQL_NO_DATA,SQL_NO_DATA_FOUND); } if(r==ODBC3_C(SQL_NO_DATA,SQL_NO_DATA_FOUND)) { rowsInRowset_=0; switch(fetchType) { case SQL_FETCH_RELATIVE: if(rowNum<0) { location_=BEFORE_FIRST; } else if(rowNum>0) { location_=AFTER_LAST; } break;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -