UNIQUE fields detector for sqlite based providers

This is a temporary solution for OGR because we expect
to use the native GDAL implementation in GDAL 3.2.

For now a new method QgsSqliteUtils::uniqueFields
used by spatialite and OGR/GPKG is used to detect
UNIQUE constraints on single fields.
This commit is contained in:
Alessandro Pasotti 2020-05-29 08:19:40 +02:00
parent bb47d7f745
commit fa7177a056
5 changed files with 39 additions and 24 deletions

View File

@ -52,6 +52,21 @@ Returns a string list of SQLite (and spatialite) system tables
.. versionadded:: 3.8
%End
static QSet<QString> uniqueFields( sqlite3 *connection, const QString &tableName, QString &errorMessage );
%Docstring
Returns a list of field names for ``connection`` and ``tableName`` having a UNIQUE constraint,
fields that are part of a UNIQUE constraint that spans over multiple fields
are not returned.
.. note::
the implementation is the same of GDAL but the test coverage is much
better in GDAL.
.. versionadded:: 3.14
%End
};
/************************************************************************

View File

@ -1105,13 +1105,15 @@ void QgsOgrProvider::loadFields()
// This is a temporary solution until GDAL Unique support is available
QSet<QString> uniqueFieldNames;
if ( mGDALDriverName == QLatin1String( "GPKG" ) )
{
sqlite3_database_unique_ptr dsPtr;
if ( dsPtr.open( mFilePath ) == SQLITE_OK )
if ( dsPtr.open_v2( mFilePath, SQLITE_OPEN_READONLY, nullptr ) == SQLITE_OK )
{
QString errMsg;
uniqueFieldNames = dsPtr.uniqueFields( mOgrLayer->name(), errMsg );
uniqueFieldNames = QgsSqliteUtils::uniqueFields( dsPtr.get(), mOgrLayer->name(), errMsg );
if ( ! errMsg.isEmpty() )
{
QgsMessageLog::logMessage( tr( "GPKG error searching for unique constraints on fields for table %1" ).arg( QString( mOgrLayer->name() ) ), tr( "OGR" ) );

View File

@ -120,23 +120,23 @@ int sqlite3_database_unique_ptr::exec( const QString &sql, QString &errorMessage
return ret;
}
QSet<QString> sqlite3_database_unique_ptr::uniqueFields( const QString &tableName, QString &errorMessage )
QSet<QString> QgsSqliteUtils::uniqueFields( sqlite3 *connection, const QString &tableName, QString &errorMessage )
{
QSet<QString> uniqueFieldsResults;
char *zErrMsg = 0;
std::vector<std::string> rows;
QString sql = sqlite3_mprintf( "select sql from sqlite_master where type='table' and name=%q", QgsSqliteUtils::quotedIdentifier( tableName ).toStdString().c_str() );
QString sql = sqlite3_mprintf( "select sql from sqlite_master where type='table' and name=%q", quotedIdentifier( tableName ).toStdString().c_str() );
auto cb = [ ](
void *data /* Data provided in the 4th argument of sqlite3_exec() */,
int /* The number of columns in row */,
char **argv /* An array of strings representing fields in the row */,
char **/* An array of strings representing column names */ ) -> int
char ** /* An array of strings representing column names */ ) -> int
{
static_cast<std::vector<std::string>*>( data )->push_back( argv[0] );
return 0;
};
int rc = sqlite3_exec( get(), sql.toUtf8(), cb, ( void * )&rows, &zErrMsg );
int rc = sqlite3_exec( connection, sql.toUtf8(), cb, ( void * )&rows, &zErrMsg );
if ( rc != SQLITE_OK )
{
errorMessage = zErrMsg;
@ -172,7 +172,7 @@ QSet<QString> sqlite3_database_unique_ptr::uniqueFields( const QString &tableNam
// Search indexes:
sql = sqlite3_mprintf( "SELECT sql FROM sqlite_master WHERE type='index' AND"
" tbl_name='%q' AND sql LIKE 'CREATE UNIQUE INDEX%%'" );
rc = sqlite3_exec( get(), sql.toUtf8(), cb, ( void * )&rows, &zErrMsg );
rc = sqlite3_exec( connection, sql.toUtf8(), cb, ( void * )&rows, &zErrMsg );
if ( rc != SQLITE_OK )
{
errorMessage = zErrMsg;

View File

@ -154,15 +154,6 @@ class CORE_EXPORT sqlite3_database_unique_ptr : public std::unique_ptr< sqlite3,
*/
int exec( const QString &sql, QString &errorMessage SIP_OUT ) const;
/**
* Returns a list of field names for \a tableName having a UNIQUE constraint,
* fields that are part of a UNIQUE constraint that spans over multiple fields
* are not returned.
* \note the implementation is the same of GDAL but the test coverage is much
* better in GDAL.
* \since QGIS 3.14
*/
QSet<QString> uniqueFields( const QString &tableName, QString &errorMessage );
};
/**
@ -210,6 +201,17 @@ class CORE_EXPORT QgsSqliteUtils
* \since QGIS 3.8
*/
static QStringList systemTables();
/**
* Returns a list of field names for \a connection and \a tableName having a UNIQUE constraint,
* fields that are part of a UNIQUE constraint that spans over multiple fields
* are not returned.
* \note the implementation is the same of GDAL but the test coverage is much
* better in GDAL.
* \since QGIS 3.14
*/
static QSet<QString> uniqueFields( sqlite3 *connection, const QString &tableName, QString &errorMessage );
};
#endif // QGSSQLITEUTILS_H

View File

@ -913,15 +913,11 @@ void QgsSpatiaLiteProvider::fetchConstraints()
// Use the same logic implemented in GDAL for GPKG
QSet<QString> uniqueFieldNames;
{
sqlite3_database_unique_ptr dsPtr;
if ( dsPtr.open( mSqlitePath ) == SQLITE_OK )
QString errMsg;
uniqueFieldNames = QgsSqliteUtils::uniqueFields( mSqliteHandle, mTableName, errMsg );
if ( ! errMsg.isEmpty() )
{
QString errMsg;
uniqueFieldNames = dsPtr.uniqueFields( mTableName, errMsg );
if ( ! errMsg.isEmpty() )
{
QgsMessageLog::logMessage( tr( "Error searching for unique constraints on fields for table %1" ).arg( mTableName ), tr( "spatialite" ) );
}
QgsMessageLog::logMessage( tr( "Error searching for unique constraints on fields for table %1" ).arg( mTableName ), tr( "spatialite" ) );
}
}