314 changes: 314 additions & 0 deletions src/providers/oracle/qgsoracletablecache.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/***************************************************************************
qgsoraclestablecache.cpp
-------------------
begin : April 2014
copyright : (C) 2014 by Martin Dobias
email : wonder.sk at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsoracletablecache.h"

#include <sqlite3.h>

#include "qgsapplication.h"

#include <QDir>



static bool _executeSqliteStatement( sqlite3* db, const QString& sql )
{
sqlite3_stmt* stmt;
if ( sqlite3_prepare_v2( db, sql.toUtf8().data(), -1, &stmt, NULL ) != SQLITE_OK )
return false;

return sqlite3_step( stmt ) == SQLITE_DONE;
}

static bool _removeFromCache( sqlite3* db, const QString& connName )
{
QString tblName = "oracle_" + connName;

QString sqlDeleteFromMeta = QString( "DELETE FROM meta_oracle WHERE conn = %1" ).arg( QgsOracleConn::quotedValue( connName ) );
bool res1 = _executeSqliteStatement( db, sqlDeleteFromMeta );

QString sqlDropTable = QString( "DROP TABLE IF EXISTS %1" ).arg( QgsOracleConn::quotedIdentifier( tblName ) );
bool res2 = _executeSqliteStatement( db, sqlDropTable );

return res1 && res2;
}


static sqlite3* _openCacheDatabase()
{
sqlite3* database;
if ( sqlite3_open_v2( QgsOracleTableCache::cacheDatabaseFilename().toUtf8().data(), &database, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0 ) != SQLITE_OK )
return 0;

if ( !_executeSqliteStatement( database, "CREATE TABLE IF NOT EXISTS meta_oracle(conn text primary_key, flags int)" ) )
{
sqlite3_close( database );
return 0;
}

return database;
}


static bool _hasCache( sqlite3* db, const QString& connName, int flags = -1 ) // flags == -1 implies any flags
{
QString sqlCacheForConn = QString( "SELECT * FROM meta_oracle WHERE conn = %1" ).arg( QgsOracleConn::quotedValue( connName ) );
if ( flags >= 0 )
sqlCacheForConn.append( QString( " AND flags = %1" ).arg( flags ) );

char **results;
int rows, columns;
char *errMsg = NULL;
bool res = sqlite3_get_table( db, sqlCacheForConn.toUtf8(), &results, &rows, &columns, &errMsg ) == SQLITE_OK;
bool hasCache = ( res && rows == 1 );
sqlite3_free_table( results );

return hasCache;
}


static bool _renameConnectionInCache( sqlite3* db, const QString& oldName, const QString& newName )
{
if ( !_hasCache( db, oldName ) )
return true;

QString sql1 = QString( "ALTER TABLE %1 RENAME TO %2" ).arg( QgsOracleConn::quotedIdentifier( "oracle_" + oldName ) ).arg( QgsOracleConn::quotedIdentifier( "oracle_" + newName ) );
bool res1 = _executeSqliteStatement( db, sql1 );

QString sql2 = QString( "UPDATE meta_oracle SET conn = %1 WHERE conn = %2" ).arg( QgsOracleConn::quotedIdentifier( newName ) ).arg( QgsOracleConn::quotedIdentifier( oldName ) );
bool res2 = _executeSqliteStatement( db, sql2 );

return res1 && res2;
}



QString QgsOracleTableCache::cacheDatabaseFilename()
{
return QgsApplication::qgisSettingsDirPath() + QDir::separator() + "data_sources_cache.db";
}

bool QgsOracleTableCache::hasCache( const QString& connName, CacheFlags flags )
{
sqlite3* db = _openCacheDatabase();
if ( !db )
return false;

bool hasCache = _hasCache( db, connName, ( int ) flags );

sqlite3_close( db );
return hasCache;
}


bool QgsOracleTableCache::saveToCache( const QString& connName, CacheFlags flags, const QVector<QgsOracleLayerProperty>& layers )
{
sqlite3* db = _openCacheDatabase();
if ( !db )
return false;

QString tblNameRaw = "oracle_" + connName;
QString tblName = QgsOracleConn::quotedIdentifier( tblNameRaw );

// recreate the cache table

if ( !_removeFromCache( db, connName ) )
{
sqlite3_close( db );
return false;
}

QString sqlCreateTable = QString( "CREATE TABLE %1 (ownername text, tablename text, geometrycolname text, isview int, sql text, pkcols text, geomtypes text, geomsrids text)" ).arg( tblName );
QString sqlInsertToMeta = QString( "INSERT INTO meta_oracle VALUES (%1, %2)" ).arg( QgsOracleConn::quotedValue( connName ) ).arg(( int ) flags );

bool res1 = _executeSqliteStatement( db, sqlCreateTable );
bool res2 = _executeSqliteStatement( db, sqlInsertToMeta );
if ( !res1 || !res2 )
{
sqlite3_close( db );
return false;
}

// insert data

_executeSqliteStatement( db, "BEGIN" );

QString sqlInsert = QString( "INSERT INTO %1 VALUES(?,?,?,?,?,?,?,?)" ).arg( tblName );
sqlite3_stmt* stmtInsert;
if ( sqlite3_prepare_v2( db, sqlInsert.toUtf8().data(), -1, &stmtInsert, 0 ) != SQLITE_OK )
{
sqlite3_close( db );
return false;
}

bool insertOk = true;
foreach ( const QgsOracleLayerProperty& item, layers )
{
sqlite3_bind_text( stmtInsert, 1, item.ownerName.toUtf8().data(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmtInsert, 2, item.tableName.toUtf8().data(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmtInsert, 3, item.geometryColName.toUtf8().data(), -1, SQLITE_TRANSIENT );
sqlite3_bind_int( stmtInsert, 4, item.isView );
sqlite3_bind_text( stmtInsert, 5, item.sql.toUtf8().data(), -1, SQLITE_TRANSIENT );

sqlite3_bind_text( stmtInsert, 6, item.pkCols.join( "," ).toUtf8().data(), -1, SQLITE_TRANSIENT );

QStringList geomTypes;
foreach ( QGis::WkbType geomType, item.types )
geomTypes.append( QString::number( static_cast<ulong>( geomType ) ) );
sqlite3_bind_text( stmtInsert, 7, geomTypes.join( "," ).toUtf8().data(), -1, SQLITE_TRANSIENT );

QStringList geomSrids;
foreach ( int geomSrid, item.srids )
geomSrids.append( QString::number( geomSrid ) );
sqlite3_bind_text( stmtInsert, 8, geomSrids.join( "," ).toUtf8().data(), -1, SQLITE_TRANSIENT );

if ( sqlite3_step( stmtInsert ) != SQLITE_DONE )
insertOk = false;

sqlite3_reset( stmtInsert );
}

sqlite3_finalize( stmtInsert );

_executeSqliteStatement( db, "COMMIT" );

sqlite3_close( db );
return insertOk;
}


bool QgsOracleTableCache::loadFromCache( const QString& connName, CacheFlags flags, QVector<QgsOracleLayerProperty>& layers )
{
sqlite3* db = _openCacheDatabase();
if ( !db )
return false;

if ( !_hasCache( db, connName, ( int ) flags ) )
return false;

sqlite3_stmt* stmt;
QString sql = QString( "SELECT * FROM %1" ).arg( QgsOracleConn::quotedIdentifier( "oracle_" + connName ) );
if ( sqlite3_prepare_v2( db, sql.toUtf8().data(), -1, &stmt, NULL ) != SQLITE_OK )
{
sqlite3_close( db );
return false;
}

while ( sqlite3_step( stmt ) == SQLITE_ROW )
{
QgsOracleLayerProperty layer;
layer.ownerName = QString::fromUtf8(( const char * ) sqlite3_column_text( stmt, 0 ) );
layer.tableName = QString::fromUtf8(( const char * ) sqlite3_column_text( stmt, 1 ) );
layer.geometryColName = QString::fromUtf8(( const char * ) sqlite3_column_text( stmt, 2 ) );
layer.isView = sqlite3_column_int( stmt, 3 );
layer.sql = QString::fromUtf8(( const char* ) sqlite3_column_text( stmt, 4 ) );

QString pkCols = QString::fromUtf8(( const char* ) sqlite3_column_text( stmt, 5 ) );
layer.pkCols = pkCols.split( ",", QString::SkipEmptyParts );

QString geomTypes = QString::fromUtf8(( const char* ) sqlite3_column_text( stmt, 6 ) );
foreach ( QString geomType, geomTypes.split( ",", QString::SkipEmptyParts ) )
layer.types.append( static_cast<QGis::WkbType>( geomType.toInt() ) );

QString geomSrids = QString::fromUtf8(( const char* ) sqlite3_column_text( stmt, 7 ) );
foreach ( QString geomSrid, geomSrids.split( ",", QString::SkipEmptyParts ) )
layer.srids.append( geomSrid.toInt() );

layers.append( layer );
}

sqlite3_finalize( stmt );

sqlite3_close( db );
return true;
}


bool QgsOracleTableCache::removeFromCache( const QString& connName )
{
sqlite3* db = _openCacheDatabase();
if ( !db )
return false;

bool res = _removeFromCache( db, connName );

sqlite3_close( db );
return res;
}


bool QgsOracleTableCache::renameConnectionInCache( const QString& oldName, const QString& newName )
{
sqlite3* db = _openCacheDatabase();
if ( !db )
return false;

bool res = _renameConnectionInCache( db, oldName, newName );

sqlite3_close( db );
return res;
}


#if 0
// testing routine - ideally it should be a unit test
void _testTableCache()
{
QString connName = "local";
QVector<QgsOracleLayerProperty> layers;

// fetch

QgsDataSourceURI uri = QgsOracleConn::connUri( connName );
QgsOracleConn* c = QgsOracleConn::connectDb( uri.connectionInfo() );
if ( !c )
return;

c->supportedLayers( layers, true );

bool useEstimated = true;
bool onlyExisting = QgsOracleConn::onlyExistingTypes( connName );

for ( QVector<QgsOracleLayerProperty>::iterator it = layers.begin(), end = layers.end(); it != end; ++it )
{
QgsOracleLayerProperty &layerProperty = *it;
c->retrieveLayerTypes( layerProperty, useEstimated, onlyExisting );
}

c->disconnect();

// save

QgsOracleTableCache::CacheFlags flags = QgsOracleTableCache::UseEstimatedTableMetadata | QgsOracleTableCache::OnlyExistingGeometryTypes;
QgsOracleTableCache::saveToCache( connName, flags, layers );

// load

QVector<QgsOracleLayerProperty> layersLoaded;
QgsOracleTableCache::loadFromCache( connName, flags, layersLoaded );

// compare

foreach ( const QgsOracleLayerProperty& item, layers )
qDebug( "== %s %s", item.tableName.toAscii().data(), item.geometryColName.toAscii().data() );

foreach ( const QgsOracleLayerProperty& item, layersLoaded )
qDebug( "++ %s %s", item.tableName.toAscii().data(), item.geometryColName.toAscii().data() );

Q_ASSERT( layers == layersLoaded );
}
#endif
73 changes: 73 additions & 0 deletions src/providers/oracle/qgsoracletablecache.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/***************************************************************************
qgsoraclestablecache.h
-------------------
begin : April 2014
copyright : (C) 2014 by Martin Dobias
email : wonder.sk at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* 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 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSORACLETABLECACHE_H
#define QGSORACLETABLECACHE_H

#include <sqlite3.h>

#include <QFlags>

#include "qgsoracleconn.h"

/**
* This class contains routines for local caching of listing of layers, so the add Oracle
* layer dialog does not need to discover the tables every time the user wants to add a layer.
*
* The cached entries are stored in SQLite database in QGIS user directory (usually ~/.qgis2).
* The database can be used for other data sources, too. Each saved connection's list is stored
* in one table "oracle_xyz" (where xyz is the name of the connection). There is one meta table
* "meta_oracle" which has a list of cached connections and the combination of flags used for
* the list. The cached entries are used only in case the flags are exactly the same.
*
*/
class QgsOracleTableCache
{
public:

enum CacheFlag
{
OnlyLookIntoMetadataTable = 0x01,
OnlyLookForUserTables = 0x02,
UseEstimatedTableMetadata = 0x04,
OnlyExistingGeometryTypes = 0x08,
AllowGeometrylessTables = 0x10
};
Q_DECLARE_FLAGS( CacheFlags, CacheFlag )

//! Return name of the file used for the cached entries
static QString cacheDatabaseFilename();

//! check whether the given connection is cached (with equal flags)
static bool hasCache( const QString& connName, CacheFlags flags );

//! Store the given list of entries (layers) into the cache. Returns true on success.
static bool saveToCache( const QString& connName, CacheFlags flags, const QVector<QgsOracleLayerProperty>& layers );

//! Try to load cached entries for the given connection and its flags. Returns true on success.
static bool loadFromCache( const QString& connName, CacheFlags flags, QVector<QgsOracleLayerProperty>& layers );

//! Remove cached entries for given connection. Returns true on success.
static bool removeFromCache( const QString& connName );

//! Rename cached connection (useful when an existing connection gets renamed). Returns true on success.
static bool renameConnectionInCache( const QString& oldName, const QString& newName );
};

Q_DECLARE_OPERATORS_FOR_FLAGS( QgsOracleTableCache::CacheFlags )

#endif // QGSORACLETABLECACHE_H