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.
koffice/kexi/kexidb/drivers/sqlite/sqlitecursor.cpp

570 lines
16 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 "sqlitecursor.h"
#include "sqliteconnection.h"
#include "sqliteconnection_p.h"
#include <kexidb/error.h>
#include <kexidb/driver.h>
#include <kexiutils/utils.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tqptrvector.h>
#include <tqdatetime.h>
using namespace KexiDB;
//! safer interpretations of boolean values for SQLite
static bool sqliteStringToBool(const TQString& s)
{
return s.lower()=="yes" || (s.lower()!="no" && s!="0");
}
//----------------------------------------------------
class KexiDB::SQLiteCursorData : public SQLiteConnectionInternal
{
public:
SQLiteCursorData(Connection* conn)
:
SQLiteConnectionInternal(conn)
// : curr_cols(0)
// errmsg_p(0)
// , res(SQLITE_OK)
, curr_coldata(0)
, curr_colname(0)
, cols_pointers_mem_size(0)
// , rec_stored(false)
/* MOVED TO Cursor:
, cols_pointers_mem_size(0)
, records_in_buf(0)
, buffering_completed(false)
, at_buffer(false)*/
//#ifdef SQLITE3
// , rowDataReadyToFetch(false)
//#endif
{
data_owned = false;
}
/*#ifdef SQLITE3
void fetchRowDataIfNeeded()
{
if (!rowDataReadyToFetch)
return true;
rowDataReadyToFetch = false;
m_fieldCount = sqlite3_data_count(data);
for (int i=0; i<m_fieldCount; i++) {
}
}
#endif*/
TQCString st;
//for sqlite:
// sqlite_struct *data; //! taken from SQLiteConnection
#ifdef SQLITE2
sqlite_vm *prepared_st_handle; //vm
#else //SQLITE3
sqlite3_stmt *prepared_st_handle;
#endif
char *utail;
// TQString errmsg; //<! server-specific message of last operation
// char *errmsg_p; //<! temporary: server-specific message of last operation
// int res; //<! result code of last operation on server
// int curr_cols;
const char **curr_coldata;
const char **curr_colname;
int next_cols;
// const char **next_coldata;
// const char **next_colname;
// bool rec_stored : 1; //! true, current record is stored in next_coldata
/* MOVED TO Cursor:
uint cols_pointers_mem_size; //! size of record's array of pointers to values
int records_in_buf; //! number of records currently stored in the buffer
bool buffering_completed; //! true if we have already all records stored in the buffer
TQPtrVector<const char*> records; //buffer data
bool at_buffer; //! true if we already point to the buffer with curr_coldata
*/
/* int prev_cols;
const char **prev_coldata;
const char **prev_colname;*/
uint cols_pointers_mem_size; //! size of record's array of pointers to values
TQPtrVector<const char*> records;//! buffer data
//#ifdef SQLITE3
// bool rowDataReadyToFetch : 1;
//#endif
#ifdef SQLITE3
inline TQVariant getValue(Field *f, int i)
{
int type = sqlite3_column_type(prepared_st_handle, i);
if (type==SQLITE_NULL) {
return TQVariant();
}
else if (!f || type==SQLITE_TEXT) {
//TODO: support for UTF-16
#define GET_sqlite3_column_text TQString::fromUtf8( (const char*)sqlite3_column_text(prepared_st_handle, i) )
if (!f || f->isTextType())
return GET_sqlite3_column_text;
else {
switch (f->type()) {
case Field::Date:
return TQDate::fromString( GET_sqlite3_column_text, Qt::ISODate );
case Field::Time:
//TQDateTime - a hack needed because TQVariant(TQTime) has broken isNull()
return KexiUtils::stringToHackedTQTime(GET_sqlite3_column_text);
case Field::DateTime: {
TQString tmp( GET_sqlite3_column_text );
tmp[10] = 'T'; //for ISODate compatibility
return TQDateTime::fromString( tmp, Qt::ISODate );
}
case Field::Boolean:
return TQVariant(sqliteStringToBool(GET_sqlite3_column_text), 1);
default:
return TQVariant(); //TODO
}
}
}
else if (type==SQLITE_INTEGER) {
switch (f->type()) {
case Field::Byte:
case Field::ShortInteger:
case Field::Integer:
return TQVariant( sqlite3_column_int(prepared_st_handle, i) );
case Field::BigInteger:
return TQVariant( (TQ_LLONG)sqlite3_column_int64(prepared_st_handle, i) );
case Field::Boolean:
return TQVariant( sqlite3_column_int(prepared_st_handle, i)!=0, 1 );
default:;
}
if (f->isFPNumericType()) //WEIRD, YEAH?
return TQVariant( (double)sqlite3_column_int(prepared_st_handle, i) );
else
return TQVariant(); //TODO
}
else if (type==SQLITE_FLOAT) {
if (f && f->isFPNumericType())
return TQVariant( sqlite3_column_double(prepared_st_handle, i) );
else if (!f || f->isIntegerType())
return TQVariant( (double)sqlite3_column_double(prepared_st_handle, i) );
else
return TQVariant(); //TODO
}
else if (type==SQLITE_BLOB) {
if (f && f->type()==Field::BLOB) {
TQByteArray ba;
//! @todo efficient enough?
ba.duplicate((const char*)sqlite3_column_blob(prepared_st_handle, i),
sqlite3_column_bytes(prepared_st_handle, i));
return ba;
} else
return TQVariant(); //TODO
}
return TQVariant();
}
#endif //SQLITE3
};
SQLiteCursor::SQLiteCursor(Connection* conn, const TQString& statement, uint options)
: Cursor( conn, statement, options )
, d( new SQLiteCursorData(conn) )
{
d->data = static_cast<SQLiteConnection*>(conn)->d->data;
}
SQLiteCursor::SQLiteCursor(Connection* conn, QuerySchema& query, uint options )
: Cursor( conn, query, options )
, d( new SQLiteCursorData(conn) )
{
d->data = static_cast<SQLiteConnection*>(conn)->d->data;
}
SQLiteCursor::~SQLiteCursor()
{
close();
delete d;
}
bool SQLiteCursor::drv_open()
{
// d->st.resize(statement.length()*2);
//TODO: decode
// d->st = statement.local8Bit();
// d->st = m_conn->driver()->escapeString( statement.local8Bit() );
if(! d->data) {
// this may as example be the case if SQLiteConnection::drv_useDatabase()
// wasn't called before. Normaly sqlite_compile/sqlite3_prepare
// should handle it, but it crashes in in sqlite3SafetyOn at util.c:786
kdWarning() << "SQLiteCursor::drv_open(): Database handle undefined." << endl;
return false;
}
#ifdef SQLITE2
d->st = m_sql.local8Bit();
d->res = sqlite_compile(
d->data,
d->st.data(),
(const char**)&d->utail,
&d->prepared_st_handle,
&d->errmsg_p );
#else //SQLITE3
d->st = m_sql.utf8();
d->res = sqlite3_prepare(
d->data, /* Database handle */
d->st.data(), /* SQL statement, UTF-8 encoded */
d->st.length(), /* Length of zSql in bytes. */
&d->prepared_st_handle, /* OUT: Statement handle */
0/*const char **pzTail*/ /* OUT: Pointer to unused portion of zSql */
);
#endif
if (d->res!=SQLITE_OK) {
d->storeResult();
return false;
}
//cursor is automatically @ first record
// m_beforeFirst = true;
if (isBuffered()) {
d->records.resize(128); //TODO: manage size dynamically
}
return true;
}
/*bool SQLiteCursor::drv_getFirstRecord()
{
bool ok = drv_getNextRecord();*/
/* if ((m_options & Buffered) && ok) { //1st record is there:
//compute parameters for cursor's buffer:
//-size of record's array of pointer to values
d->cols_pointers_mem_size = d->curr_cols * sizeof(char*);
d->records_in_buf = 1;
}*/
/*return ok;
}*/
bool SQLiteCursor::drv_close()
{
#ifdef SQLITE2
d->res = sqlite_finalize( d->prepared_st_handle, &d->errmsg_p );
#else //SQLITE3
d->res = sqlite3_finalize( d->prepared_st_handle );
#endif
if (d->res!=SQLITE_OK) {
d->storeResult();
return false;
}
return true;
}
void SQLiteCursor::drv_getNextRecord()
{
#ifdef SQLITE2
static int _fieldCount;
d->res = sqlite_step(
d->prepared_st_handle,
&_fieldCount,
&d->curr_coldata,
&d->curr_colname);
#else //SQLITE3
d->res = sqlite3_step( d->prepared_st_handle );
#endif
if (d->res == SQLITE_ROW) {
m_result = FetchOK;
#ifdef SQLITE2
m_fieldCount = (uint)_fieldCount;
#else
m_fieldCount = sqlite3_data_count(d->prepared_st_handle);
//#else //for SQLITE3 data fetching is delayed. Now we even do not take field count information
// // -- just set a flag that we've a data not fetched but available
// d->rowDataReadyToFetch = true;
#endif
//(m_logicalFieldCount introduced) m_fieldCount -= (m_containsROWIDInfo ? 1 : 0);
} else {
//#ifdef SQLITE3
// d->rowDataReadyToFetch = false;
//#endif
if (d->res==SQLITE_DONE)
m_result = FetchEnd;
else
m_result = FetchError;
}
//debug
/* if (((int)m_result == (int)FetchOK) && d->curr_coldata) {
for (uint i=0;i<m_fieldCount;i++) {
KexiDBDrvDbg<<"col."<< i<<": "<< d->curr_colname[i]<<" "<< d->curr_colname[m_fieldCount+i]
<< " = " << (d->curr_coldata[i] ? TQString::fromLocal8Bit(d->curr_coldata[i]) : "(NULL)") <<endl;
}
KexiDBDrvDbg << "SQLiteCursor::drv_getNextRecord(): "<<m_fieldCount<<" col(s) fetched"<<endl;
}*/
}
void SQLiteCursor::drv_appendCurrentRecordToBuffer()
{
// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer():" <<endl;
if (!d->curr_coldata)
return;
if (!d->cols_pointers_mem_size)
d->cols_pointers_mem_size = m_fieldCount * sizeof(char*);
const char **record = (const char**)malloc(d->cols_pointers_mem_size);
const char **src_col = d->curr_coldata;
const char **dest_col = record;
for (uint i=0; i<m_fieldCount; i++,src_col++,dest_col++) {
// KexiDBDrvDbg << i <<": '" << *src_col << "'" <<endl;
// KexiDBDrvDbg << "src_col: " << src_col << endl;
*dest_col = *src_col ? strdup(*src_col) : 0;
}
d->records.insert(m_records_in_buf,record);
// KexiDBDrvDbg << "SQLiteCursor::drv_appendCurrentRecordToBuffer() ok." <<endl;
}
void SQLiteCursor::drv_bufferMovePointerNext()
{
d->curr_coldata++; //move to next record in the buffer
}
void SQLiteCursor::drv_bufferMovePointerPrev()
{
d->curr_coldata--; //move to prev record in the buffer
}
//compute a place in the buffer that contain next record's data
//and move internal buffer pointer to that place
void SQLiteCursor::drv_bufferMovePointerTo(TQ_LLONG at)
{
d->curr_coldata = d->records.at(at);
}
void SQLiteCursor::drv_clearBuffer()
{
if (d->cols_pointers_mem_size>0) {
const uint records_in_buf = m_records_in_buf;
const char ***r_ptr = d->records.data();
for (uint i=0; i<records_in_buf; i++, r_ptr++) {
// const char **record = m_records.at(i);
const char **field_data = *r_ptr;
// for (int col=0; col<d->curr_cols; col++, field_data++) {
for (uint col=0; col<m_fieldCount; col++, field_data++) {
free((void*)*field_data); //free field memory
}
free(*r_ptr); //free pointers to fields array
}
}
// d->curr_cols=0;
// m_fieldCount=0;
m_records_in_buf=0;
d->cols_pointers_mem_size=0;
// m_at_buffer=false;
d->records.clear();
}
/*
void SQLiteCursor::drv_storeCurrentRecord()
{
#if 0
assert(!m_data->rec_stored);
m_data->rec_stored = true;
m_data->next_cols = m_data->curr_cols;
for (int i=0;i<m_data->curr_cols;i++) {
KexiDBDrvDbg<<"[COPY] "<<i<<": "<< m_data->curr_coldata[i]<<endl;
if (m_data->curr_coldata[i])
m_data->next_coldata[i] = strdup( m_data->curr_coldata[i] );
else
m_data->next_coldata[i] = 0;
}
#endif
}
*/
/*TODO
const char *** SQLiteCursor::bufferData()
{
if (!isBuffered())
return 0;
return m_records.data();
}*/
const char ** SQLiteCursor::rowData() const
{
return d->curr_coldata;
}
void SQLiteCursor::storeCurrentRow(RowData &data) const
{
#ifdef SQLITE2
const char **col = d->curr_coldata;
#endif
//const uint realCount = m_fieldCount + (m_containsROWIDInfo ? 1 : 0);
data.resize(m_fieldCount);
if (!m_fieldsExpanded) {//simple version: without types
for( uint i=0; i<m_fieldCount; i++ ) {
#ifdef SQLITE2
data[i] = TQVariant( *col );
col++;
#else //SQLITE3
data[i] = TQString::fromUtf8( (const char*)sqlite3_column_text(d->prepared_st_handle, i) );
#endif
}
return;
}
//const uint fieldsExpandedCount = m_fieldsExpanded->count();
const uint maxCount = TQMIN(m_fieldCount, m_fieldsExpanded->count());
// i - visible field's index, j - physical index
for( uint i=0, j=0; i<m_fieldCount; i++, j++ ) {
// while (j < m_detailedVisibility.count() && !m_detailedVisibility[j]) //!m_query->isColumnVisible(j))
// j++;
while (j < maxCount && !m_fieldsExpanded->at(j)->visible)
j++;
if (j >= (maxCount /*+(m_containsROWIDInfo ? 1 : 0)*/)) {
//ERR!
break;
}
//(m_logicalFieldCount introduced) Field *f = (m_containsROWIDInfo && i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field;
Field *f = (i>=m_fieldCount) ? 0 : m_fieldsExpanded->at(j)->field;
// KexiDBDrvDbg << "SQLiteCursor::storeCurrentRow(): col=" << (col ? *col : 0) << endl;
#ifdef SQLITE2
if (!*col)
data[i] = TQVariant();
else if (f && f->isTextType())
# ifdef SQLITE_UTF8
data[i] = TQString::fromUtf8( *col );
# else
data[i] = TQVariant( *col ); //only latin1
# endif
else if (f && f->isFPNumericType())
data[i] = TQVariant( TQCString(*col).toDouble() );
else {
switch (f ? f->type() : Field::Integer/*ROWINFO*/) {
//todo: use short, etc.
case Field::Byte:
case Field::ShortInteger:
case Field::Integer:
data[i] = TQVariant( TQCString(*col).toInt() );
case Field::BigInteger:
data[i] = TQVariant( TQString::fromLatin1(*col).toLongLong() );
case Field::Boolean:
data[i] = TQVariant( sqliteStringToBool(TQString::fromLatin1(*col)), 1 );
break;
case Field::Date:
data[i] = TQDate::fromString( TQString::fromLatin1(*col), Qt::ISODate );
break;
case Field::Time:
//TQDateTime - a hack needed because TQVariant(TQTime) has broken isNull()
data[i] = KexiUtils::stringToHackedTQTime(TQString::fromLatin1(*col));
break;
case Field::DateTime: {
TQString tmp( TQString::fromLatin1(*col) );
tmp[10] = 'T';
data[i] = TQDateTime::fromString( tmp, Qt::ISODate );
break;
}
default:
data[i] = TQVariant( *col );
}
}
col++;
#else //SQLITE3
data[i] = d->getValue(f, i); //, !f /*!f means ROWID*/);
#endif
}
}
TQVariant SQLiteCursor::value(uint i)
{
// if (i > (m_fieldCount-1+(m_containsROWIDInfo?1:0))) //range checking
if (i > (m_fieldCount-1)) //range checking
return TQVariant();
//TODO: allow disable range checking! - performance reasons
// const KexiDB::Field *f = m_query ? m_query->field(i) : 0;
KexiDB::Field *f = (m_fieldsExpanded && i<m_fieldsExpanded->count())
? m_fieldsExpanded->at(i)->field : 0;
#ifdef SQLITE2
//from most to least frequently used types:
//(m_logicalFieldCount introduced) if (i==m_fieldCount || f && f->isIntegerType())
if (!f || f->isIntegerType())
return TQVariant( TQCString(d->curr_coldata[i]).toInt() );
else if (!f || f->isTextType())
return TQVariant( d->curr_coldata[i] );
else if (f->isFPNumericType())
return TQVariant( TQCString(d->curr_coldata[i]).toDouble() );
return TQVariant( d->curr_coldata[i] ); //default
#else
return d->getValue(f, i); //, i==m_logicalFieldCount/*ROWID*/);
#endif
}
/*! Stores string value taken from field number \a i to \a str.
\return false when range checking failed.
bool SQLiteCursor::storeStringValue(uint i, TQString &str)
{
if (i > (m_fieldCount-1)) //range checking
return false;
str = d->curr_coldata[i];
return true;
}*/
int SQLiteCursor::serverResult()
{
return d->res;
}
TQString SQLiteCursor::serverResultName()
{
#ifdef SQLITE2
return TQString::fromLatin1( sqlite_error_string(d->res) );
#else //SQLITE3
return TQString::fromLatin1( d->result_name );
#endif
}
TQString SQLiteCursor::serverErrorMsg()
{
return d->errmsg;
}
void SQLiteCursor::drv_clearServerResult()
{
d->res = SQLITE_OK;
d->errmsg_p = 0;
}