506 changes: 506 additions & 0 deletions src/providers/spatialite/qgsspatialiteconnection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,506 @@
#include "qgsspatialiteconnection.h"

#include <QFileInfo>
#include <QSettings>
#include <stdlib.h> // atoi

QStringList QgsSpatiaLiteConnection::connectionList()
{
QSettings settings;
settings.beginGroup( "/SpatiaLite/connections" );
return settings.childGroups();
}

// -------

QgsSpatiaLiteConnection::QgsSpatiaLiteConnection( QString name )
{
// "name" can be either a saved connection or a path to database

QSettings settings;
mPath = settings.value( QString( "/SpatiaLite/connections/%1/sqlitepath" ).arg( name ) ).toString();
if ( mPath.isNull() )
mPath = name; // not found in settings - probably it's a path
}

QgsSpatiaLiteConnection::Error QgsSpatiaLiteConnection::fetchTables( bool loadGeometrylessTables )
{
mErrorMsg = QString();

QFileInfo fi( mPath );
if ( !fi.exists() )
{
return NotExists;
}

sqlite3* handle = openSpatiaLiteDb( fi.canonicalFilePath() );
if ( handle == NULL )
{
return FailedToOpen;
}

bool isSpatiaLite = checkHasMetadataTables( handle );
if ( !mErrorMsg.isNull() )
{
// unexpected error; invalid SpatiaLite DB
return FailedToCheckMetadata;
}

if ( !getTableInfo( handle, loadGeometrylessTables ) )
{
return FailedToGetTables;
}
closeSpatiaLiteDb( handle );

return NoError;
}


sqlite3 *QgsSpatiaLiteConnection::openSpatiaLiteDb( QString path )
{
sqlite3 *handle = NULL;
int ret;
// trying to open the SQLite DB
ret = sqlite3_open_v2( path.toUtf8().constData(), &handle, SQLITE_OPEN_READWRITE, NULL );
if ( ret )
{
// failure
mErrorMsg = sqlite3_errmsg( handle );
return NULL;
}
return handle;
}

void QgsSpatiaLiteConnection::closeSpatiaLiteDb( sqlite3 * handle )
{
if ( handle )
sqlite3_close( handle );
}

bool QgsSpatiaLiteConnection::checkHasMetadataTables( sqlite3* handle )
{
bool gcSpatiaLite = false;
bool rsSpatiaLite = false;
bool tableName = false;
bool geomColumn = false;
bool coordDims = false;
bool gcSrid = false;
bool type = false;
bool spatialIndex = false;
bool srsSrid = false;
bool authName = false;
bool authSrid = false;
bool refSysName = false;
bool proj4text = false;
int ret;
const char *name;
int i;
char **results;
int rows;
int columns;
char *errMsg = NULL;

// checking if table GEOMETRY_COLUMNS exists and has the expected layout
ret = sqlite3_get_table( handle, "PRAGMA table_info(geometry_columns)", &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
name = results[( i * columns ) + 1];
if ( strcasecmp( name, "f_table_name" ) == 0 )
tableName = true;
if ( strcasecmp( name, "f_geometry_column" ) == 0 )
geomColumn = true;
if ( strcasecmp( name, "coord_dimension" ) == 0 )
coordDims = true;
if ( strcasecmp( name, "srid" ) == 0 )
gcSrid = true;
if ( strcasecmp( name, "type" ) == 0 )
type = true;
if ( strcasecmp( name, "spatial_index_enabled" ) == 0 )
spatialIndex = true;
}
}
sqlite3_free_table( results );
if ( tableName && geomColumn && type && coordDims && gcSrid && spatialIndex )
gcSpatiaLite = true;

// checking if table SPATIAL_REF_SYS exists and has the expected layout
ret = sqlite3_get_table( handle, "PRAGMA table_info(spatial_ref_sys)", &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
name = results[( i * columns ) + 1];
if ( strcasecmp( name, "srid" ) == 0 )
srsSrid = true;
if ( strcasecmp( name, "auth_name" ) == 0 )
authName = true;
if ( strcasecmp( name, "auth_srid" ) == 0 )
authSrid = true;
if ( strcasecmp( name, "ref_sys_name" ) == 0 )
refSysName = true;
if ( strcasecmp( name, "proj4text" ) == 0 )
proj4text = true;
}
}
sqlite3_free_table( results );
if ( srsSrid && authName && authSrid && refSysName && proj4text )
rsSpatiaLite = true;

// OK, this one seems to be a valid SpatiaLite DB
if ( gcSpatiaLite && rsSpatiaLite )
return true;

// this seems to be a valid SQLite DB, but not a SpatiaLite's one
return false;

error:
// unexpected IO error
mErrorMsg = tr( "unknown error cause" );
if ( errMsg != NULL )
{
mErrorMsg = errMsg;
sqlite3_free( errMsg );
}
return false;
}

bool QgsSpatiaLiteConnection::getTableInfo( sqlite3 * handle, bool loadGeometrylessTables )
{
int ret;
int i;
char **results;
int rows;
int columns;
char *errMsg = NULL;
bool ok = false;
QString sql;

// the following query return the tables containing a Geometry column
sql = "SELECT f_table_name, f_geometry_column, type "
"FROM geometry_columns";
ret = sqlite3_get_table( handle, sql.toUtf8(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( isRasterlite1Datasource( handle, results[( i * columns ) + 0] ) )
continue;
QString tableName = QString::fromUtf8( results[( i * columns ) + 0] );
QString column = QString::fromUtf8( results[( i * columns ) + 1] );
QString type = results[( i * columns ) + 2];
if ( isDeclaredHidden( handle, tableName, column ) )
continue;

mTables.append( TableEntry( tableName, column, type ) );
}
ok = true;
}
sqlite3_free_table( results );

if ( checkViewsGeometryColumns( handle ) )
{
// the following query return the views supporting a Geometry column
sql = "SELECT view_name, view_geometry, type "
"FROM views_geometry_columns "
"JOIN geometry_columns USING (f_table_name, f_geometry_column)";
ret = sqlite3_get_table( handle, sql.toUtf8(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
QString tableName = QString::fromUtf8( results[( i * columns ) + 0] );
QString column = QString::fromUtf8( results[( i * columns ) + 1] );
QString type = results[( i * columns ) + 2];
if ( isDeclaredHidden( handle, tableName, column ) )
continue;

mTables.append( TableEntry( tableName, column, type ) );
}
ok = true;
}
sqlite3_free_table( results );
}

if ( checkVirtsGeometryColumns( handle ) )
{
// the following query return the VirtualShapefiles
sql = "SELECT virt_name, virt_geometry, type "
"FROM virts_geometry_columns";
ret = sqlite3_get_table( handle, sql.toUtf8(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
QString tableName = QString::fromUtf8( results[( i * columns ) + 0] );
QString column = QString::fromUtf8( results[( i * columns ) + 1] );
QString type = results[( i * columns ) + 2];
if ( isDeclaredHidden( handle, tableName, column ) )
continue;

mTables.append( TableEntry( tableName, column, type ) );
}
ok = true;
}
sqlite3_free_table( results );
}

if ( loadGeometrylessTables )
{
// get all tables
sql = "SELECT name "
"FROM sqlite_master "
"WHERE type in ('table', 'view')";
ret = sqlite3_get_table( handle, sql.toUtf8(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
QString tableName = QString::fromUtf8( results[( i * columns ) + 0] );
mTables.append( TableEntry( tableName, QString(), "qgis_table" ) );
}
ok = true;
}
sqlite3_free_table( results );
}

return ok;

error:
// unexpected IO error
mErrorMsg = tr( "unknown error cause" );
if ( errMsg != NULL )
{
mErrorMsg = errMsg;
sqlite3_free( errMsg );
}
return false;
}

QString QgsSpatiaLiteConnection::quotedValue( QString value ) const
{
if ( value.isNull() )
return "NULL";

value.replace( "'", "''" );
return value.prepend( "'" ).append( "'" );
}

bool QgsSpatiaLiteConnection::checkGeometryColumnsAuth( sqlite3 * handle )
{
int ret;
int i;
char **results;
int rows;
int columns;
bool exists = false;

// checking the metadata tables
QString sql = QString( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'geometry_columns_auth'" );

ret = sqlite3_get_table( handle, sql.toUtf8().constData(), &results, &rows, &columns, NULL );
if ( ret != SQLITE_OK )
return false;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( results[( i * columns ) + 0] != NULL )
{
const char *name = results[( i * columns ) + 0];
if ( name )
exists = true;
}
}
}
sqlite3_free_table( results );
return exists;
}


bool QgsSpatiaLiteConnection::checkViewsGeometryColumns( sqlite3 * handle )
{
int ret;
int i;
char **results;
int rows;
int columns;
bool exists = false;

// checking the metadata tables
QString sql = QString( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'views_geometry_columns'" );

ret = sqlite3_get_table( handle, sql.toUtf8().constData(), &results, &rows, &columns, NULL );
if ( ret != SQLITE_OK )
return false;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( results[( i * columns ) + 0] != NULL )
{
const char *name = results[( i * columns ) + 0];
if ( name )
exists = true;
}
}
}
sqlite3_free_table( results );
return exists;
}

bool QgsSpatiaLiteConnection::checkVirtsGeometryColumns( sqlite3 * handle )
{
int ret;
int i;
char **results;
int rows;
int columns;
bool exists = false;

// checking the metadata tables
QString sql = QString( "SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'virts_geometry_columns'" );

ret = sqlite3_get_table( handle, sql.toUtf8().constData(), &results, &rows, &columns, NULL );
if ( ret != SQLITE_OK )
return false;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( results[( i * columns ) + 0] != NULL )
{
const char *name = results[( i * columns ) + 0];
if ( name )
exists = true;
}
}
}
sqlite3_free_table( results );
return exists;
}

bool QgsSpatiaLiteConnection::isRasterlite1Datasource( sqlite3 * handle, const char *table )
{
// testing for RasterLite-1 datasources
int ret;
int i;
char **results;
int rows;
int columns;
bool exists = false;
int len;
char table_raster[4192];
char sql[4192];

strcpy( table_raster, table );
len = strlen( table_raster );
if ( strlen( table_raster ) < 9 )
return false;
if ( strcmp( table_raster + len - 9, "_metadata" ) != 0 )
return false;
// ok, possible candidate
strcpy( table_raster + len - 9, "_rasters" );

// checking if the related "_RASTERS table exists
sprintf( sql, "SELECT name FROM sqlite_master WHERE type = 'table' AND name = '%s'", table_raster );

ret = sqlite3_get_table( handle, sql, &results, &rows, &columns, NULL );
if ( ret != SQLITE_OK )
return false;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( results[( i * columns ) + 0] != NULL )
{
const char *name = results[( i * columns ) + 0];
if ( name )
exists = true;
}
}
}
sqlite3_free_table( results );
return exists;
}

bool QgsSpatiaLiteConnection::isDeclaredHidden( sqlite3 * handle, QString table, QString geom )
{
int ret;
int i;
char **results;
int rows;
int columns;
char *errMsg = NULL;
bool isHidden = false;

if ( !checkGeometryColumnsAuth( handle ) )
return false;
// checking if some Layer has been declared as HIDDEN
QString sql = QString( "SELECT hidden FROM geometry_columns_auth"
" WHERE f_table_name=%1 and f_geometry_column=%2" ).arg( quotedValue( table ) ).
arg( quotedValue( geom ) );

ret = sqlite3_get_table( handle, sql.toUtf8().constData(), &results, &rows, &columns, &errMsg );
if ( ret != SQLITE_OK )
goto error;
if ( rows < 1 )
;
else
{
for ( i = 1; i <= rows; i++ )
{
if ( results[( i * columns ) + 0] != NULL )
{
if ( atoi( results[( i * columns ) + 0] ) != 0 )
isHidden = true;
}
}
}
sqlite3_free_table( results );

return isHidden;

error:
// unexpected IO error
mErrorMsg = tr( "unknown error cause" );
if ( errMsg != NULL )
{
mErrorMsg = errMsg;
sqlite3_free( errMsg );
}
return false;
}
83 changes: 83 additions & 0 deletions src/providers/spatialite/qgsspatialiteconnection.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#ifndef QGSSPATIALITECONNECTION_H
#define QGSSPATIALITECONNECTION_H

#include <QStringList>
#include <QObject>

extern "C"
{
#include <sqlite3.h>
}

class QgsSpatiaLiteConnection : public QObject
{
public:
/** construct a connection. Name can be either stored connection name or a path to the database file */
QgsSpatiaLiteConnection( QString name );

QString path() { return mPath; }

static QStringList connectionList();

typedef struct TableEntry
{
TableEntry( QString _tableName, QString _column, QString _type )
: tableName( _tableName ), column( _column ), type( _type ) {}
QString tableName;
QString column;
QString type;
} TableEntry;

enum Error
{
NoError,
NotExists,
FailedToOpen,
FailedToCheckMetadata,
FailedToGetTables,
};

Error fetchTables( bool loadGeometrylessTables );

/** return list of tables. fetchTables() function has to be called before */
QList<TableEntry> tables() { return mTables; }

/** return additional error message (if an error occurred before) */
QString errorMessage() { return mErrorMsg; }

protected:
// SpatiaLite DB open / close
sqlite3 *openSpatiaLiteDb( QString path );
void closeSpatiaLiteDb( sqlite3 * handle );

/**Checks if geometry_columns and spatial_ref_sys exist and have expected layout*/
bool checkHasMetadataTables( sqlite3* handle );

/**Inserts information about the spatial tables into mTables*/
bool getTableInfo( sqlite3 * handle, bool loadGeometrylessTables );

/**cleaning well-formatted SQL strings*/
QString quotedValue( QString value ) const;

/**Checks if geometry_columns_auth table exists*/
bool checkGeometryColumnsAuth( sqlite3 * handle );

/**Checks if views_geometry_columns table exists*/
bool checkViewsGeometryColumns( sqlite3 * handle );

/**Checks if virts_geometry_columns table exists*/
bool checkVirtsGeometryColumns( sqlite3 * handle );

/**Checks if this layer has been declared HIDDEN*/
bool isDeclaredHidden( sqlite3 * handle, QString table, QString geom );

/**Checks if this layer is a RasterLite-1 datasource*/
bool isRasterlite1Datasource( sqlite3 * handle, const char * table );

QString mErrorMsg;
QString mPath; // full path to the database

QList<TableEntry> mTables;
};

#endif // QGSSPATIALITECONNECTION_H
200 changes: 200 additions & 0 deletions src/providers/spatialite/qgsspatialitedataitems.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
#include "qgsspatialitedataitems.h"

#include "qgsspatialiteprovider.h"
#include "qgsspatialiteconnection.h"

#include "qgslogger.h"

#include <QAction>


QgsSLConnectionItem::QgsSLConnectionItem( QgsDataItem* parent, QString name, QString path )
: QgsDataCollectionItem( parent, name, path )
{
}

QgsSLConnectionItem::~QgsSLConnectionItem()
{
}

static QgsLayerItem::LayerType _layerTypeFromDb( QString dbType )
{
if ( dbType == "POINT" || dbType == "MULTIPOINT" )
{
return QgsLayerItem::Point;
}
else if ( dbType == "LINESTRING" || dbType == "MULTILINESTRING" )
{
return QgsLayerItem::Line;
}
else if ( dbType == "POLYGON" || dbType == "MULTIPOLYGON" )
{
return QgsLayerItem::Polygon;
}
else if ( dbType == "qgis_table" )
{
return QgsLayerItem::Table;
}
else
{
return QgsLayerItem::NoType;
}
}

QVector<QgsDataItem*> QgsSLConnectionItem::createChildren()
{
QgsDebugMsg( "Entered" );
QVector<QgsDataItem*> children;
QgsSpatiaLiteConnection connection( mName );

QgsSpatiaLiteConnection::Error err = connection.fetchTables( false ); // TODO: allow geometryless tables
if ( err != QgsSpatiaLiteConnection::NoError )
{
QString msg;
switch ( err )
{
case QgsSpatiaLiteConnection::NotExists: msg = tr( "Database does not exist" ); break;
case QgsSpatiaLiteConnection::FailedToOpen: msg = tr( "Failed to open database" ); break;
case QgsSpatiaLiteConnection::FailedToCheckMetadata: msg = tr( "Failed to check metadata" ); break;
case QgsSpatiaLiteConnection::FailedToGetTables: msg = tr( "Failed to get list of tables" ); break;
default: msg = tr( "Unknown error" ); break;
}
QString msgDetails = connection.errorMessage();
if ( !msgDetails.isEmpty() )
msg = QString( "%1 (%2)" ).arg( msg ).arg( msgDetails );
children.append( new QgsErrorItem( this, msg, mPath + "/error" ) );
return children;
}

QString connectionInfo = QString( "dbname='%1'" ).arg( QString( connection.path() ).replace( "'", "\\'" ) );
QgsDataSourceURI uri( connectionInfo );

foreach( const QgsSpatiaLiteConnection::TableEntry& entry, connection.tables() )
{
uri.setDataSource( QString(), entry.tableName, entry.column, QString(), QString() );
QgsSLLayerItem * layer = new QgsSLLayerItem( this, entry.tableName, mPath + "/" + entry.tableName, uri.uri(), _layerTypeFromDb( entry.type ) );
children.append( layer );
}
return children;
}

bool QgsSLConnectionItem::equal( const QgsDataItem *other )
{
if ( type() != other->type() )
{
return false;
}
const QgsSLConnectionItem *o = dynamic_cast<const QgsSLConnectionItem *>( other );
return ( mPath == o->mPath && mName == o->mName );
}

QList<QAction*> QgsSLConnectionItem::actions()
{
QList<QAction*> lst;

QAction* actionEdit = new QAction( tr( "Edit..." ), this );
connect( actionEdit, SIGNAL( triggered() ), this, SLOT( editConnection() ) );
lst.append( actionEdit );

QAction* actionDelete = new QAction( tr( "Delete" ), this );
connect( actionDelete, SIGNAL( triggered() ), this, SLOT( deleteConnection() ) );
lst.append( actionDelete );

return lst;
}

void QgsSLConnectionItem::editConnection()
{
/*
QgsPgNewConnection nc( NULL, mName );
if ( nc.exec() )
{
// the parent should be updated
mParent->refresh();
}
*/
}

void QgsSLConnectionItem::deleteConnection()
{
/*
QgsPgSourceSelect::deleteConnection( mName );
// the parent should be updated
mParent->refresh();
*/
}


// ---------------------------------------------------------------------------

QgsSLRootItem::QgsSLRootItem( QgsDataItem* parent, QString name, QString path )
: QgsDataCollectionItem( parent, name, path )
{
mIcon = QIcon( getThemePixmap( "mIconSpatialite.png" ) );
populate();
}

QgsSLRootItem::~QgsSLRootItem()
{
}

QVector<QgsDataItem*> QgsSLRootItem::createChildren()
{
QVector<QgsDataItem*> connections;
foreach( QString connName, QgsSpatiaLiteConnection::connectionList() )
{
QgsDataItem * conn = new QgsSLConnectionItem( this, connName, mPath + "/" + connName );
connections.push_back( conn );
}
return connections;
}

QList<QAction*> QgsSLRootItem::actions()
{
QList<QAction*> lst;

QAction* actionNew = new QAction( tr( "New..." ), this );
connect( actionNew, SIGNAL( triggered() ), this, SLOT( newConnection() ) );
lst.append( actionNew );

return lst;
}

QWidget * QgsSLRootItem::paramWidget()
{
/*
QgsSLSourceSelect *select = new QgsSLSourceSelect( 0, 0, true, true );
connect( select, SIGNAL( connectionsChanged() ), this, SLOT( connectionsChanged() ) );
return select;
*/
return NULL;
}

void QgsSLRootItem::connectionsChanged()
{
refresh();
}

void QgsSLRootItem::newConnection()
{
/*
QgsSLNewConnection nc( NULL );
if ( nc.exec() )
{
refresh();
}
*/
}

// ---------------------------------------------------------------------------

QGISEXTERN int dataCapabilities()
{
return QgsDataProvider::Database;
}

QGISEXTERN QgsDataItem * dataItem( QString thePath, QgsDataItem* parentItem )
{
Q_UNUSED( thePath );
return new QgsSLRootItem( parentItem, "SpatiaLite", "spatialite:" );
}
55 changes: 55 additions & 0 deletions src/providers/spatialite/qgsspatialitedataitems.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#ifndef QGSSPATIALITEDATAITEMS_H
#define QGSSPATIALITEDATAITEMS_H

#include "qgsdataitem.h"

class QgsSLLayerItem : public QgsLayerItem
{
public:
QgsSLLayerItem( QgsDataItem* parent, QString name, QString path, QString uri, LayerType layerType )
: QgsLayerItem( parent, name, path, uri, layerType, "spatialite" )
{
mPopulated = true; // no children are expected
}
};

class QgsSLConnectionItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsSLConnectionItem( QgsDataItem* parent, QString name, QString path );
~QgsSLConnectionItem();

QVector<QgsDataItem*> createChildren();
virtual bool equal( const QgsDataItem *other );

virtual QList<QAction*> actions();

public slots:
void editConnection();
void deleteConnection();

protected:
QString mDbPath;
};

class QgsSLRootItem : public QgsDataCollectionItem
{
Q_OBJECT
public:
QgsSLRootItem( QgsDataItem* parent, QString name, QString path );
~QgsSLRootItem();

QVector<QgsDataItem*> createChildren();

virtual QWidget * paramWidget();

virtual QList<QAction*> actions();

public slots:
void connectionsChanged();
void newConnection();
};


#endif // QGSSPATIALITEDATAITEMS_H