summaryrefslogtreecommitdiffstats
path: root/kexi/kexidb/cursor.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kexi/kexidb/cursor.cpp')
-rw-r--r--kexi/kexidb/cursor.cpp571
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"