diff options
Diffstat (limited to 'kexi/kexidb/cursor.cpp')
| -rw-r--r-- | kexi/kexidb/cursor.cpp | 571 |
1 files changed, 571 insertions, 0 deletions
diff --git a/kexi/kexidb/cursor.cpp b/kexi/kexidb/cursor.cpp new file mode 100644 index 000000000..4b9cdea31 --- /dev/null +++ b/kexi/kexidb/cursor.cpp @@ -0,0 +1,571 @@ +/* This file is part of the KDE project + Copyright (C) 2003-2006 Jaroslaw Staniek <js@iidea.pl> + + 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 <kexidb/cursor.h> + +#include <kexidb/driver.h> +#include <kexidb/driver_p.h> +#include <kexidb/error.h> +#include <kexidb/roweditbuffer.h> +#include <kexiutils/utils.h> + +#include <kdebug.h> +#include <klocale.h> + +#include <assert.h> +#include <stdlib.h> + +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 + //<members related to buffering> +// 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() == "<<ret<<endl; + return ret; +} + +bool Cursor::reopen() +{ + if (!m_opened) + return open(); + return close() && open(); +} + +bool Cursor::moveFirst() +{ + if (!m_opened) + return false; +// if (!m_beforeFirst) { //cursor isn't @ first record now: reopen + if (!m_readAhead) { + if (m_options & Buffered) { + if (m_records_in_buf==0 && m_buffering_completed) { + //eof and bof should now return true: + m_afterLast = true; + m_at = 0; + return false; //buffering completed and there is no records! + } + if (m_records_in_buf>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() ===="<<endl; + drv_getNextRecord(); + } + if ((FetchResult) m_result != FetchOK) {//there is no record + m_buffering_completed = true; //no more records for buffer +// KexiDBDbg<<"m_result != FetchOK ********"<<endl; + m_validRecord = false; + m_afterLast = true; +//js m_at = m_records_in_buf; + m_at = -1; //position is invalid now and will not be used + if ((FetchResult) m_result == FetchEnd) { + return false; + } + setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record.")); + return false; + } + //we have a record: store this record's values in the buffer + drv_appendCurrentRecordToBuffer(); + m_records_in_buf++; + } + else //we have a record that was read ahead: eat this + m_readAhead = false; + } + } + 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 +// KexiDBDbg<<"==== no prefetched record ===="<<endl; + drv_getNextRecord(); + if ((FetchResult)m_result != FetchOK) {//there is no record +// KexiDBDbg<<"m_result != FetchOK ********"<<endl; + m_validRecord = false; + m_afterLast = true; + m_at = -1; + if ((FetchResult) m_result == FetchEnd) { + return false; + } + setError(ERR_CURSOR_RECORD_FETCHING, i18n("Cannot fetch next record.")); + return false; + } + } + else //we have a record that was read ahead: eat this + m_readAhead = false; + } + + m_at++; + +// if (m_data->curr_colname && m_data->curr_coldata) +// for (int i=0;i<m_data->curr_cols;i++) { +// KexiDBDbg<<i<<": "<< m_data->curr_colname[i]<<" == "<< m_data->curr_coldata[i]<<endl; +// } +// KexiDBDbg<<"m_at == "<<(long)m_at<<endl; + + m_validRecord = true; + return true; +} + +bool Cursor::updateRow(RowData& data, RowEditBuffer& buf, bool useROWID) +{ +//! @todo doesn't update cursor's buffer YET! + clearError(); + if (!m_query) + return false; + return m_conn->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<QVariant> Cursor::queryParameters() const +{ + return m_queryParameters ? *m_queryParameters : QValueList<QVariant>(); +} + +void Cursor::setQueryParameters(const QValueList<QVariant>& params) +{ + if (!m_queryParameters) + m_queryParameters = new QValueList<QVariant>(params); + else + *m_queryParameters = params; +} + +#include "cursor.moc" |
