/*
sqlite3xx - sqlite3 C++ layer, following ideas of libpqxx
Copyright (C) 2009 Andreas Baumann
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
#include "port/string.h"
#include "sqlite3xx/result.hpp"
#include
#include
#include
#include
#include
#include "sqlite3xx/except.hpp"
#include "port/unused.h"
using namespace std;
namespace sqlite3xx {
/* field */
ostream& operator<<( ostream& o, const result::field& f ) {
int type = f._t.column_type( f._c );
switch( type ) {
case SQLITE_INTEGER: {
int value = f._t.GetValueInt( f._c );
o << value;
}
break;
case SQLITE3_TEXT: {
const unsigned char* value = f._t.GetValueText( f._c );
o << value;
}
break;
case SQLITE_FLOAT: {
double value = f._t.GetValueDouble( f._c );
o << value;
}
break;
case SQLITE_NULL:
o << "(NULL)";
break;
default:
o << "(Unknown type " << type << "!)";
}
return o;
}
/* result */
void result::Step( ) {
int rc;
rc = sqlite3_step( _stmt );
switch( rc ) {
case SQLITE_DONE:
if( _crow == 0 ) {
/* never had a result, will never read-ahead and buffer */
_status = st_nodata;
} else {
/* last row */
_status = st_lastrow;
_crow++;
}
break;
case SQLITE_ROW:
_status = st_hasdata;
_crow++;
break;
/* don't fail if the sqlite file is locked by another writer, try again later */
case SQLITE_BUSY:
throw database_locked( "database locked in step" );
default: {
ostringstream s;
s << "Illegal state after sqlite3_step (sqlite code: " << rc << ")";
string str = s.str( );
string msg = sqlite3_errmsg( sqlite3_db_handle( _stmt ) );
throw sql_error( str, msg );
}
}
}
void result::BufferData( ) {
const char *tmp = NULL;
_cache.resize( columns( ) + 1 );
for( size_type i = 0; i < columns( ); i++ ) {
CachedValue &value = _cache.at( i );
value.type = column_type( i );
switch( value.type ) {
case SQLITE_INTEGER:
value.value.i = sqlite3_column_int( _stmt, i );
break;
case SQLITE3_TEXT:
/* KLUDGE: the documentation is not specific about returning NULL here
* I get the feeling this is undefined behaviour, so people should not
* relly on it from outside.
*/
tmp = (const char *)sqlite3_column_text( _stmt, i );
if( tmp != NULL ) {
if( value.value.s != NULL ) free( value.value.s );
value.value.s = (unsigned char *)strdup( tmp );
} else {
value.type = SQLITE_NULL;
value.value.s = NULL;
}
break;
case SQLITE_FLOAT:
value.value.d = sqlite3_column_double( _stmt, i );
break;
case SQLITE_NULL:
value.value.s = NULL;
break;
default: {
ostringstream s;
s << "Illegal type " << value.type << " when buffering row " << _row;
string msg = s.str( );
throw logic_error( msg );
}
}
}
}
void result::FillColNameMap( ) {
size_type i;
const char* colname;
for( i = 0; i < columns( ); i++ ) {
colname = sqlite3_column_name( _stmt, i );
_colmap.insert( make_pair( string( colname ), i ) );
}
}
void result::FillColTypeMap( ) {
size_type i;
_coltype.resize( columns( ) + 1 );
for( i = 0; i < columns( ); i++ ) {
_coltype.at( i ) = sqlite3_column_type( _stmt, i );
}
}
result::result( const result& r ) {
_stmt = r._stmt;
_status = r._status;
_colmap = r._colmap;
_coltype = r._coltype;
_cache = r._cache;
_row = r._row;
_crow = r._crow;
}
result::result( sqlite3_stmt* stmt ) {
_stmt = stmt;
_status = st_nascent;
_row = 0;
_crow = 0;
Step( );
if( _status == st_hasdata || _status == st_lastrow ) {
FillColNameMap( );
FillColTypeMap( );
BufferData( );
Step( );
}
}
result::~result( ) {
switch( _status ) {
case st_nascent: /* created no result fetched, weird, but ok */
break;
case st_nodata:
/* a command without result data, just reset the prepared statement
* so it can be used again for the next set of parameters
*/
break;
case st_lastrow:
break;
case st_hasdata:
case st_nomoredata:
/* TODO: free row table data */
break;
default:
throw logic_error( "Illegal state in destructor" );
}
}
result& result::operator=( const result& r ) {
if( this != &r ) {
_stmt = r._stmt;
_status = r._status;
_colmap = r._colmap;
_coltype = r._coltype;
_cache = r._cache;
_row = r._row;
_crow = r._crow;
}
return *this;
}
result::size_type result::affected_rows( ) const {
switch( _status ) {
case st_nascent:
throw logic_error( "Called affected_rows() before sqlite3_step!" );
case st_nodata:
return sqlite3_changes( sqlite3_db_handle( _stmt ) );
case st_hasdata:
case st_lastrow:
case st_nomoredata:
throw logic_error( "Affected rows doesn't make sence in a query!" );
default:
throw logic_error( "Illegal state in affected_rows()" );
}
}
result::size_type result::size( ) const {
/* TODO: can we get this value with sqlite3_step, I doubt!
* at least we know from the state wheter it's 0 or 1 and more
*/
switch( _status ) {
case st_nascent:
throw logic_error( "Called size() before sqlite3_step!" );
case st_nodata:
return 0;
case st_hasdata:
/* ..but we know that the result must be at least _row */
return _row + 2;
case st_nomoredata:
return _row + 1;
case st_lastrow:
return _row + 1;
default:
throw logic_error( "Illegal state in size()" );
}
}
result::size_type result::columns( ) const {
return sqlite3_column_count( _stmt );
}
const result::tuple result::operator[]( result::size_type i ) throw( ) {
if( _row == i ) {
return tuple( this, i );
} else if( i == _row + 1 ) {
_row++;
BufferData( );
Step( );
return tuple( this, i );
} else {
throw logic_error( "Illegal access outside scope of cursor!" );
}
}
result::size_type result::column_number( string name ) const {
ColMap::const_iterator it = _colmap.find( name );
if( it == _colmap.end( ) ) {
throw invalid_argument( "Unknown column '" + name + "' requested" );
}
return it->second;
}
int result::column_type( size_type i ) const {
if( _status != st_hasdata && _status != st_lastrow ) {
throw logic_error( "Fetching row after sqlite returned no data!" );
}
return _coltype[i];
}
/* value caching */
result::CachedValue::CachedValue( const CachedValue& v ) {
type = v.type;
value = v.value;
if( type == SQLITE3_TEXT ) {
value.s = (unsigned char *)strdup( (const char *)v.value.s );
}
}
result::CachedValue::~CachedValue( ) {
if( type == SQLITE3_TEXT && value.s != NULL ) {
free( value.s );
value.s = NULL;
}
}
result::CachedValue& result::CachedValue::operator= ( const result::CachedValue& v ) {
if( this != &v ) {
type = v.type;
value = v.value;
if( type == SQLITE3_TEXT ) {
value.s = (unsigned char *)strdup( (const char *)v.value.s );
}
}
return *this;
}
/* const iterator for tuples */
result::const_iterator result::const_iterator::operator++( int incr ) {
SQLITEXX_UNUSED( incr );
const_iterator old( *this );
// idx++;
return old;
}
bool result::const_iterator::operator<( const result::const_iterator& i ) const {
SQLITEXX_UNUSED( i.dummy );
// idx < i.idx
return false;
}
} /* namespace sqlite3xx */