You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
572 lines
15 KiB
572 lines
15 KiB
/* 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"
|