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.
570 lines
16 KiB
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, TQt::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, TQt::ISODate );
|
|
}
|
|
case Field::Boolean:
|
|
return TQVariant(sqliteStringToBool(GET_sqlite3_column_text));
|
|
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 );
|
|
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)) );
|
|
break;
|
|
case Field::Date:
|
|
data[i] = TQDate::fromString( TQString::fromLatin1(*col), TQt::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, TQt::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;
|
|
}
|
|
|