/* This file is part of the KDE project Copyright (C) 2003-2006 Jaroslaw Staniek This program 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 program 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 program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include using namespace KexiDB; #ifdef KEXI_DEBUG_GUI #endif Cursor::Cursor(Connection* conn, const QString& statement, uint options) : QObject() , m_conn(conn) , m_query(0) , m_rawStatement(statement) , m_options(options) { #ifdef KEXI_DEBUG_GUI KexiUtils::addKexiDBDebug(QString("Create cursor: ")+statement); #endif init(); } Cursor::Cursor(Connection* conn, QuerySchema& query, uint options ) : QObject() , m_conn(conn) , m_query(&query) , m_options(options) { #ifdef KEXI_DEBUG_GUI KexiUtils::addKexiDBDebug(QString("Create cursor for query \"%1\": ").arg(query.name())+query.debugString()); #endif init(); } void Cursor::init() { assert(m_conn); m_conn->m_cursors.insert(this,this); m_opened = false; // , m_atFirst(false) // , m_atLast(false) // , m_beforeFirst(false) m_atLast = false; m_afterLast = false; m_readAhead = false; m_at = 0; //js:todo: if (m_query) // m_fieldCount = m_query->fieldsCount(); // m_fieldCount = m_query ? m_query->fieldCount() : 0; //do not know // // m_cols_pointers_mem_size = 0; m_records_in_buf = 0; m_buffering_completed = false; m_at_buffer = false; m_result = -1; m_containsROWIDInfo = (m_query && m_query->masterTable()) && m_conn->driver()->beh->ROW_ID_FIELD_RETURNS_LAST_AUTOINCREMENTED_VALUE == false; if (m_query) { //get list of all fields m_fieldsExpanded = new QueryColumnInfo::Vector(); *m_fieldsExpanded = m_query->fieldsExpanded( m_containsROWIDInfo ? QuerySchema::WithInternalFieldsAndRowID : QuerySchema::WithInternalFields); m_logicalFieldCount = m_fieldsExpanded->count() - m_query->internalFields().count() - (m_containsROWIDInfo?1:0); m_fieldCount = m_fieldsExpanded->count(); } else { m_fieldsExpanded = 0; m_logicalFieldCount = 0; m_fieldCount = 0; } m_orderByColumnList = 0; m_queryParameters = 0; } Cursor::~Cursor() { #ifdef KEXI_DEBUG_GUI if (m_query) KexiUtils::addKexiDBDebug(QString("~ Delete cursor for query")); else KexiUtils::addKexiDBDebug(QString("~ Delete cursor: ")+m_rawStatement); #endif /* if (!m_query) KexiDBDbg << "Cursor::~Cursor() '" << m_rawStatement.latin1() << "'" << endl; else KexiDBDbg << "Cursor::~Cursor() " << endl;*/ //take me if delete was if (!m_conn->m_destructor_started) m_conn->m_cursors.take(this); else { KexiDBDbg << "Cursor::~Cursor() can be destroyed with Conenction::deleteCursor(), not with delete operator !"<< endl; exit(1); } delete m_fieldsExpanded; delete m_queryParameters; } bool Cursor::open() { if (m_opened) { if (!close()) return false; } if (!m_rawStatement.isEmpty()) m_conn->m_sql = m_rawStatement; else { if (!m_query) { KexiDBDbg << "Cursor::open(): no query statement (or schema) defined!" << endl; setError(ERR_SQL_EXECUTION_ERROR, i18n("No query statement or schema defined.")); return false; } Connection::SelectStatementOptions options; options.alsoRetrieveROWID = m_containsROWIDInfo; /*get ROWID if needed*/ m_conn->m_sql = m_queryParameters ? m_conn->selectStatement( *m_query, *m_queryParameters, options ) : m_conn->selectStatement( *m_query, options ); if (m_conn->m_sql.isEmpty()) { KexiDBDbg << "Cursor::open(): empty statement!" << endl; setError(ERR_SQL_EXECUTION_ERROR, i18n("Query statement is empty.")); return false; } } m_sql = m_conn->m_sql; m_opened = drv_open(); // m_beforeFirst = true; m_afterLast = false; //we are not @ the end m_at = 0; //we are before 1st rec if (!m_opened) { setError(ERR_SQL_EXECUTION_ERROR, i18n("Error opening database cursor.")); return false; } m_validRecord = false; //luci: WHAT_EXACTLY_SHOULD_THAT_BE? // if (!m_readAhead) // jowenn: to ensure before first state, without cluttering implementation code if (m_conn->driver()->beh->_1ST_ROW_READ_AHEAD_REQUIRED_TO_KNOW_IF_THE_RESULT_IS_EMPTY) { // KexiDBDbg << "READ AHEAD:" << endl; m_readAhead = getNextRecord(); //true if any record in this query // KexiDBDbg << "READ AHEAD = " << m_readAhead << endl; } m_at = 0; //we are still before 1st rec return !error(); } bool Cursor::close() { if (!m_opened) return true; bool ret = drv_close(); clearBuffer(); m_opened = false; // m_beforeFirst = false; m_afterLast = false; m_readAhead = false; m_fieldCount = 0; m_logicalFieldCount = 0; m_at = -1; // KexiDBDbg<<"Cursor::close() == "<0) { //set state as we would be before first rec: m_at_buffer = false; m_at = 0; //..and move to next, ie. 1st record // m_afterLast = m_afterLast = !getNextRecord(); m_afterLast = !getNextRecord(); return !m_afterLast; } } if (m_afterLast && m_at==0) //failure if already no records return false; if (!reopen()) //try reopen return false; if (m_afterLast) //eof return false; } else { //we have a record already read-ahead: we now point @ that: m_at = 1; } // if (!m_atFirst) { //cursor isn't @ first record now: reopen // reopen(); // } // if (m_validRecord) { // return true; //there is already valid record retrieved // } //get first record // if (drv_moveFirst() && drv_getRecord()) { // m_beforeFirst = false; m_afterLast = false; m_readAhead = false; //1st record had been read // } return m_validRecord; } bool Cursor::moveLast() { if (!m_opened) return false; if (m_afterLast || m_atLast) { return m_validRecord; //we already have valid last record retrieved } if (!getNextRecord()) { //at least next record must be retrieved // m_beforeFirst = false; m_afterLast = true; m_validRecord = false; m_atLast = false; return false; //no records } while (getNextRecord()) //move after last rec. ; // m_beforeFirst = false; m_afterLast = false; //cursor shows last record data m_atLast = true; // m_validRecord = true; /* //we are before or @ last record: // if (m_atLast && m_validRecord) //we're already @ last rec. // return true; if (m_validRecord) { if (drv_getRecord()) } if (!m_validRecord) { if (drv_getRecord() && m_validRecord) return true; reopen(); } */ return true; } bool Cursor::moveNext() { if (!m_opened || m_afterLast) return false; if (getNextRecord()) { // m_validRecord = true; return true; } return false; } bool Cursor::movePrev() { if (!m_opened /*|| m_beforeFirst*/ || !(m_options & Buffered)) return false; //we're after last record and there are records in the buffer //--let's move to last record if (m_afterLast && (m_records_in_buf>0)) { drv_bufferMovePointerTo(m_records_in_buf-1); m_at=m_records_in_buf; m_at_buffer = true; //now current record is stored in the buffer m_validRecord=true; m_afterLast=false; return true; } //we're at first record: go BOF if ((m_at <= 1) || (m_records_in_buf <= 1/*sanity*/)) { m_at=0; m_at_buffer = false; m_validRecord=false; return false; } m_at--; if (m_at_buffer) {//we already have got a pointer to buffer drv_bufferMovePointerPrev(); //just move to prev record in the buffer } else {//we have no pointer //compute a place in the buffer that contain next record's data drv_bufferMovePointerTo(m_at-1); m_at_buffer = true; //now current record is stored in the buffer } m_validRecord=true; m_afterLast=false; return true; } bool Cursor::eof() const { return m_afterLast; } bool Cursor::bof() const { return m_at==0; } Q_LLONG Cursor::at() const { if (m_readAhead) return 0; return m_at - 1; } bool Cursor::isBuffered() const { return m_options & Buffered; } void Cursor::setBuffered(bool buffered) { if (!m_opened) return; if (isBuffered()==buffered) return; m_options ^= Buffered; } void Cursor::clearBuffer() { if ( !isBuffered() || m_fieldCount==0) return; drv_clearBuffer(); m_records_in_buf=0; m_at_buffer=false; } bool Cursor::getNextRecord() { m_result = -1; //by default: invalid result of row fetching if ((m_options & Buffered)) {//this cursor is buffered: // KexiDBDbg << "m_at < m_records_in_buf :: " << (long)m_at << " < " << m_records_in_buf << endl; //js if (m_at==-1) m_at=0; if (m_at < m_records_in_buf) {//we have next record already buffered: /// if (m_at < (m_records_in_buf-1)) {//we have next record already buffered: //js if (m_at_buffer && (m_at!=0)) {//we already have got a pointer to buffer if (m_at_buffer) {//we already have got a pointer to buffer drv_bufferMovePointerNext(); //just move to next record in the buffer } else {//we have no pointer //compute a place in the buffer that contain next record's data drv_bufferMovePointerTo(m_at-1+1); // drv_bufferMovePointerTo(m_at+1); m_at_buffer = true; //now current record is stored in the buffer } } else {//we are after last retrieved record: we need to physically fetch next record: if (!m_readAhead) {//we have no record that was read ahead if (!m_buffering_completed) { //retrieve record only if we are not after //the last buffer's item (i.e. when buffer is not fully filled): // KexiDBDbg<<"==== buffering: drv_getNextRecord() ===="<curr_colname && m_data->curr_coldata) // for (int i=0;icurr_cols;i++) { // KexiDBDbg<curr_colname[i]<<" == "<< m_data->curr_coldata[i]<updateRow(*m_query, data, buf, useROWID); } bool Cursor::insertRow(RowData& data, RowEditBuffer& buf, bool getROWID) { //! @todo doesn't update cursor's buffer YET! clearError(); if (!m_query) return false; return m_conn->insertRow(*m_query, data, buf, getROWID); } bool Cursor::deleteRow(RowData& data, bool useROWID) { //! @todo doesn't update cursor's buffer YET! clearError(); if (!m_query) return false; return m_conn->deleteRow(*m_query, data, useROWID); } bool Cursor::deleteAllRows() { //! @todo doesn't update cursor's buffer YET! clearError(); if (!m_query) return false; return m_conn->deleteAllRows(*m_query); } QString Cursor::debugString() const { QString dbg = "CURSOR( "; if (!m_query) { dbg += "RAW STATEMENT: '"; dbg += m_rawStatement; dbg += "'\n"; } else { dbg += "QuerySchema: '"; dbg += m_conn->selectStatement( *m_query ); dbg += "'\n"; } if (isOpened()) dbg += " OPENED"; else dbg += " NOT_OPENED"; if (isBuffered()) dbg += " BUFFERED"; else dbg += " NOT_BUFFERED"; dbg += " AT="; dbg += QString::number((unsigned long)at()); dbg += " )"; return dbg; } void Cursor::debug() const { KexiDBDbg << debugString() << endl; } void Cursor::setOrderByColumnList(const QStringList& columnNames) { Q_UNUSED(columnNames); //! @todo implement this: // all field names should be fooun, exit otherwise .......... // OK //TODO if (!m_orderByColumnList) //TODO } /*! Convenience method, similar to setOrderBy(const QStringList&). */ void Cursor::setOrderByColumnList(const QString& column1, const QString& column2, const QString& column3, const QString& column4, const QString& column5) { Q_UNUSED(column1); Q_UNUSED(column2); Q_UNUSED(column3); Q_UNUSED(column4); Q_UNUSED(column5); //! @todo implement this, like above //! @todo add ORDER BY info to debugString() } QueryColumnInfo::Vector Cursor::orderByColumnList() const { return m_orderByColumnList ? *m_orderByColumnList: QueryColumnInfo::Vector(); } QValueList Cursor::queryParameters() const { return m_queryParameters ? *m_queryParameters : QValueList(); } void Cursor::setQueryParameters(const QValueList& params) { if (!m_queryParameters) m_queryParameters = new QValueList(params); else *m_queryParameters = params; } #include "cursor.moc"