Skip to content

Commit

Permalink
Add hstore support to the postgres provider
Browse files Browse the repository at this point in the history
  • Loading branch information
Patrick Valsecchi committed Sep 8, 2016
1 parent 25191e1 commit 3d6e7c8
Show file tree
Hide file tree
Showing 15 changed files with 283 additions and 18 deletions.
22 changes: 20 additions & 2 deletions src/core/qgsexpression.cpp
Expand Up @@ -4978,6 +4978,8 @@ QString QgsExpression::group( const QString& name )

QString QgsExpression::formatPreviewString( const QVariant& value )
{
static const int MAX_PREVIEW = 60;

if ( value.canConvert<QgsGeometry>() )
{
//result is a geometry
Expand Down Expand Up @@ -5017,15 +5019,31 @@ QString QgsExpression::formatPreviewString( const QVariant& value )
else if ( value.type() == QVariant::String )
{
QString previewString = value.toString();
if ( previewString.length() > 63 )
if ( previewString.length() > MAX_PREVIEW + 3 )
{
return QString( tr( "'%1...'" ) ).arg( previewString.left( 60 ) );
return QString( tr( "'%1...'" ) ).arg( previewString.left( MAX_PREVIEW ) );
}
else
{
return previewString.prepend( '\'' ).append( '\'' );
}
}
else if ( value.type() == QVariant::Map )
{
QString mapStr;
const QVariantMap map = value.toMap();
for ( QVariantMap::const_iterator it = map.constBegin(); it != map.constEnd(); ++it )
{
if ( !mapStr.isEmpty() ) mapStr.append( ", " );
mapStr.append( it.key() ).append( ": " ).append( formatPreviewString( it.value() ) );
if ( mapStr.length() > MAX_PREVIEW + 3 )
{
mapStr = QString( tr( "%1..." ) ).arg( mapStr.left( MAX_PREVIEW ) );
break;
}
}
return tr( "<i>&lt;map: %1&gt;</i>" ).arg( mapStr );
}
else
{
return value.toString();
Expand Down
3 changes: 3 additions & 0 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -2233,7 +2233,10 @@ bool QgsVectorLayer::deleteFeature( QgsFeatureId fid )
bool QgsVectorLayer::deleteFeatures( const QgsFeatureIds& fids )
{
if ( !mEditBuffer )
{
QgsDebugMsg( "Cannot delete features (mEditBuffer==NULL)" );
return false;
}

bool res = mEditBuffer->deleteFeatures( fids );

Expand Down
18 changes: 15 additions & 3 deletions src/core/qgsvectorlayereditbuffer.cpp
Expand Up @@ -20,7 +20,6 @@
#include "qgsvectordataprovider.h"
#include "qgsvectorlayer.h"


//! populate two lists (ks, vs) from map - in reverse order
template <class Key, class T> void mapToReversedLists( const QMap< Key, T >& map, QList<Key>& ks, QList<T>& vs )
{
Expand Down Expand Up @@ -150,17 +149,26 @@ bool QgsVectorLayerEditBuffer::addFeatures( QgsFeatureList& features )
bool QgsVectorLayerEditBuffer::deleteFeature( QgsFeatureId fid )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
QgsDebugMsg( "Cannot delete features (missing DeleteFeature capability)" );
return false;
}

if ( FID_IS_NEW( fid ) )
{
if ( !mAddedFeatures.contains( fid ) )
{
QgsDebugMsg( "Cannot delete features (in the list of added features)" );
return false;
}
}
else // existing feature
{
if ( mDeletedFeatureIds.contains( fid ) )
{
QgsDebugMsg( "Cannot delete features (in the list of deleted features)" );
return false;
}
}

L->undoStack()->push( new QgsVectorLayerUndoCommandDeleteFeature( this, fid ) );
Expand All @@ -170,12 +178,16 @@ bool QgsVectorLayerEditBuffer::deleteFeature( QgsFeatureId fid )
bool QgsVectorLayerEditBuffer::deleteFeatures( const QgsFeatureIds& fids )
{
if ( !( L->dataProvider()->capabilities() & QgsVectorDataProvider::DeleteFeatures ) )
{
QgsDebugMsg( "Cannot delete features (missing DeleteFeatures capability)" );
return false;
}

bool ok = true;
Q_FOREACH ( QgsFeatureId fid, fids )
deleteFeature( fid );
ok = deleteFeature( fid ) && ok;

return true;
return ok;
}


Expand Down
6 changes: 6 additions & 0 deletions src/providers/postgres/CMakeLists.txt
Expand Up @@ -56,8 +56,14 @@ INCLUDE_DIRECTORIES(
${CMAKE_CURRENT_BINARY_DIR}/../../ui
)

ADD_LIBRARY (postgresprovider_a STATIC ${PG_SRCS} ${PG_HDRS} ${PG_MOC_SRCS})
ADD_LIBRARY (postgresprovider MODULE ${PG_SRCS} ${PG_HDRS} ${PG_MOC_SRCS})

TARGET_LINK_LIBRARIES (postgresprovider_a
${POSTGRES_LIBRARY}
qgis_core
qgis_gui
)
TARGET_LINK_LIBRARIES (postgresprovider
${POSTGRES_LIBRARY}
qgis_core
Expand Down
51 changes: 40 additions & 11 deletions src/providers/postgres/qgspostgresconn.cpp
Expand Up @@ -923,11 +923,42 @@ QString QgsPostgresConn::postgisVersion()
return mPostgisVersionInfo;
}

QString QgsPostgresConn::quotedIdentifier( QString ident )
QString QgsPostgresConn::quotedIdentifier( const QString& ident )
{
ident.replace( '"', "\"\"" );
ident = ident.prepend( '\"' ).append( '\"' );
return ident;
QString result = ident;
result.replace( '"', "\"\"" );
return result.prepend( '\"' ).append( '\"' );
}

static QString quotedString( const QString& v )
{
QString result = v;
result.replace( '\'', "''" );
if ( result.contains( '\\' ) )
return result.replace( '\\', "\\\\" ).prepend( "E'" ).append( '\'' );
else
return result.prepend( '\'' ).append( '\'' );
}

static QString doubleQuotedMapValue( const QString& v )
{
QString result = v;
return "\"" + result.replace( '\\', "\\\\\\\\" ).replace( '\"', "\\\\\"" ).replace( '\'', "\\'" ) + "\"";
}

static QString quotedMap( const QVariantMap& map )
{
QString ret;
for ( QVariantMap::const_iterator i = map.constBegin(); i != map.constEnd(); ++i )
{
if ( !ret.isEmpty() )
{
ret += ",";
}
ret.append( doubleQuotedMapValue( i.key() ) + "=>" +
doubleQuotedMapValue( i.value().toString() ) );
}
return "E'" + ret + "'::hstore";
}

QString QgsPostgresConn::quotedValue( const QVariant& value )
Expand All @@ -945,14 +976,12 @@ QString QgsPostgresConn::quotedValue( const QVariant& value )
case QVariant::Bool:
return value.toBool() ? "TRUE" : "FALSE";

default:
case QVariant::Map:
return quotedMap( value.toMap() );

case QVariant::String:
QString v = value.toString();
v.replace( '\'', "''" );
if ( v.contains( '\\' ) )
return v.replace( '\\', "\\\\" ).prepend( "E'" ).append( '\'' );
else
return v.prepend( '\'' ).append( '\'' );
default:
return quotedString( value.toString() );
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/providers/postgres/qgspostgresconn.h
Expand Up @@ -263,7 +263,7 @@ class QgsPostgresConn : public QObject

/** Double quote a PostgreSQL identifier for placement in a SQL string.
*/
static QString quotedIdentifier( QString ident );
static QString quotedIdentifier( const QString& ident );

/** Quote a value for placement in a SQL string.
*/
Expand Down
58 changes: 57 additions & 1 deletion src/providers/postgres/qgspostgresprovider.cpp
Expand Up @@ -218,6 +218,9 @@ QgsPostgresProvider::QgsPostgresProvider( QString const & uri )
<< QgsVectorDataProvider::NativeType( tr( "Date" ), "date", QVariant::Date, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Time" ), "time", QVariant::Time, -1, -1, -1, -1 )
<< QgsVectorDataProvider::NativeType( tr( "Date & Time" ), "timestamp without time zone", QVariant::DateTime, -1, -1, -1, -1 )

// complex types
<< QgsVectorDataProvider::NativeType( tr( "Map" ), "hstore", QVariant::Map, -1, -1, -1, -1 )
;

QString key;
Expand Down Expand Up @@ -382,6 +385,9 @@ static bool operator<( const QVariant &a, const QVariant &b )
return al[i] < bl[i];
}

case QVariant::Map:
return a.toMap() < b.toMap();

case QVariant::Date:
return a.toDate() < b.toDate();

Expand Down Expand Up @@ -966,7 +972,6 @@ bool QgsPostgresProvider::loadFields()
else if ( fieldTypeName == "text" ||
fieldTypeName == "bool" ||
fieldTypeName == "geometry" ||
fieldTypeName == "hstore" ||
fieldTypeName == "inet" ||
fieldTypeName == "money" ||
fieldTypeName == "ltree" ||
Expand Down Expand Up @@ -1017,6 +1022,11 @@ bool QgsPostgresProvider::loadFields()
fieldPrec = -1;
}
}
else if ( fieldTypeName == "hstore" )
{
fieldType = QVariant::Map;
fieldSize = -1;
}
else
{
QgsMessageLog::logMessage( tr( "Field %1 ignored, because of unsupported type %2" ).arg( fieldName, fieldTypeName ), tr( "PostGIS" ) );
Expand Down Expand Up @@ -2149,7 +2159,10 @@ bool QgsPostgresProvider::deleteFeatures( const QgsFeatureIds & id )
bool returnvalue = true;

if ( mIsQuery )
{
QgsDebugMsg( "Cannot delete features (is a query)" );
return false;
}

QgsPostgresConn* conn = connectionRW();
if ( !conn )
Expand Down Expand Up @@ -3467,6 +3480,11 @@ bool QgsPostgresProvider::convertField( QgsField &field, const QMap<QString, QVa
fieldPrec = 0;
break;

case QVariant::Map:
fieldType = "hstore";
fieldPrec = -1;
break;

case QVariant::Double:
if ( fieldPrec > 0 )
{
Expand Down Expand Up @@ -3829,6 +3847,44 @@ QString QgsPostgresProvider::description() const
return tr( "PostgreSQL/PostGIS provider\n%1\nPostGIS %2" ).arg( pgVersion, postgisVersion );
} // QgsPostgresProvider::description()


static QVariant parseHstore( const QString& value )
{
QRegExp recordSep( "\\s*,\\s*" );
QRegExp valueExtractor( "^(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))\\s*=>\\s*(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))$" );
QVariantMap result;
Q_FOREACH ( QString record, value.split( recordSep ) )
{
if ( valueExtractor.exactMatch( record ) )
{
QString key = valueExtractor.cap( 1 ) + valueExtractor.cap( 2 );
key.replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
QString value = valueExtractor.cap( 3 ) + valueExtractor.cap( 4 );
value.replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
result.insert( key, value );
}
else
{
QgsLogger::warning( "Error parsing hstore record: " + record );
}
}
return result;
}

QVariant QgsPostgresProvider::convertValue( QVariant::Type type, const QString& value )
{
if ( type == QVariant::Map )
{
return parseHstore( value );
}
QVariant v( value );

if ( !v.convert( type ) || value.isNull() )
v = QVariant( type );

return v;
}

/**
* Class factory to return a pointer to a newly created
* QgsPostgresProvider object
Expand Down
8 changes: 8 additions & 0 deletions src/providers/postgres/qgspostgresprovider.h
Expand Up @@ -249,6 +249,14 @@ class QgsPostgresProvider : public QgsVectorDataProvider
*/
virtual QgsTransaction* transaction() const override;

/**
* Convert the postgres string representation into the given QVariant type.
* @param type the wanted type
* @param value the value to convert
* @return a QVariant of the given type or a null QVariant
*/
static QVariant convertValue( QVariant::Type type, const QString& value );

signals:
/**
* This is emitted whenever the worker thread has fully calculated the
Expand Down
13 changes: 13 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Expand Up @@ -2331,6 +2331,19 @@ class TestQgsExpression: public QObject
QCOMPARE( result.toString(), QString( "f2" ) );
}

void test_formatPreviewString()
{
QCOMPARE( QgsExpression::formatPreviewString( QVariant( "hello" ) ), QString( "'hello'" ) );
QCOMPARE( QgsExpression::formatPreviewString( QVariant( QVariantMap() ) ), QString( "<i>&lt;map: &gt;</i>" ) );

QVariantMap map;
map["1"] = "One";
map["2"] = "Two";
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i>&lt;map: 1: 'One', 2: 'Two'&gt;</i>" ) );
map["3"] = "A very long string that is going to be truncated";
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i>&lt;map: 1: 'One', 2: 'Two', 3: 'A very long string that is going to ...&gt;</i>" ) );
}

};

QTEST_MAIN( TestQgsExpression )
Expand Down
8 changes: 8 additions & 0 deletions tests/src/providers/CMakeLists.txt
Expand Up @@ -10,13 +10,15 @@ INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/src/core/geometry
${CMAKE_SOURCE_DIR}/src/core/raster
${CMAKE_SOURCE_DIR}/src/providers/wms
${CMAKE_SOURCE_DIR}/src/providers/postgres
)
INCLUDE_DIRECTORIES(SYSTEM
${QT_INCLUDE_DIR}
${GDAL_INCLUDE_DIR}
${PROJ_INCLUDE_DIR}
${GEOS_INCLUDE_DIR}
${QCA_INCLUDE_DIR}
${POSTGRES_INCLUDE_DIR}
)

#############################################################
Expand Down Expand Up @@ -93,6 +95,12 @@ ADD_QGIS_TEST(wmsprovidertest
testqgswmsprovider.cpp)
TARGET_LINK_LIBRARIES(qgis_wmsprovidertest wmsprovider_a)

ADD_QGIS_TEST(postgresprovidertest testqgspostgresprovider.cpp)
TARGET_LINK_LIBRARIES(qgis_postgresprovidertest postgresprovider_a)

ADD_QGIS_TEST(postgresconntest testqgspostgresconn.cpp)
TARGET_LINK_LIBRARIES(qgis_postgresconntest postgresprovider_a)

#############################################################
# WCS public servers test:
# No need to test on all platforms
Expand Down

0 comments on commit 3d6e7c8

Please sign in to comment.