Add support for arrays in PostgresQL

Fix parsing of PostgresQL hstore. Had problems when the key or values were
containing comas.
This commit is contained in:
Patrick Valsecchi 2016-09-06 15:03:16 +02:00 committed by Patrick Valsecchi
parent 93afbe12e0
commit abc55f4c42
19 changed files with 420 additions and 71 deletions

View File

@ -24,13 +24,15 @@ class QgsField
* @param prec Field precision. Usually decimal places but may also be
* used in conjunction with other fields types (eg. variable character fields)
* @param comment Comment for the field
* @param subType If the field is a collection, its element's type.
*/
QgsField( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() );
const QString& comment = QString(),
QVariant::Type subType = QVariant::Invalid );
/** Copy constructor
*/
@ -59,6 +61,12 @@ class QgsField
//! Gets variant type of the field as it will be retrieved from data source
QVariant::Type type() const;
/**
* If the field is a collection, gets its element's type
* @note added in QGIS 3.0
*/
QVariant::Type subType() const;
/**
* Gets the field type. Field types vary depending on the data source. Examples
* are char, int, double, blob, geometry, etc. The type is stored exactly as
@ -103,6 +111,12 @@ class QgsField
*/
void setType( QVariant::Type type );
/**
* If the field is a collection, set its element's type.
* @note added in QGIS 3.0
*/
void setSubType( QVariant::Type subType );
/**
* Set the field type.
* @param typeName Field type

View File

@ -5064,6 +5064,22 @@ QString QgsExpression::formatPreviewString( const QVariant& value )
}
return tr( "<i>&lt;map: %1&gt;</i>" ).arg( mapStr );
}
else if ( value.type() == QVariant::List || value.type() == QVariant::StringList )
{
QString listStr;
const QVariantList list = value.toList();
for ( QVariantList::const_iterator it = list.constBegin(); it != list.constEnd(); ++it )
{
if ( !listStr.isEmpty() ) listStr.append( ", " );
listStr.append( formatPreviewString( *it ) );
if ( listStr.length() > MAX_PREVIEW + 3 )
{
listStr = QString( tr( "%1..." ) ).arg( listStr.left( MAX_PREVIEW ) );
break;
}
}
return tr( "<i>&lt;list: %1&gt;</i>" ).arg( listStr );
}
else
{
return value.toString();

View File

@ -54,6 +54,7 @@ void QgsExpressionFieldBuffer::writeXml( QDomNode& layerNode, QDomDocument& docu
fldElem.setAttribute( "comment", fld.field.comment() );
fldElem.setAttribute( "length", fld.field.length() );
fldElem.setAttribute( "type", fld.field.type() );
fldElem.setAttribute( "subType", fld.field.subType() );
fldElem.setAttribute( "typeName", fld.field.typeName() );
expressionFieldsElem.appendChild( fldElem );
@ -79,9 +80,10 @@ void QgsExpressionFieldBuffer::readXml( const QDomNode& layerNode )
int precision = field.attribute( "precision" ).toInt();
int length = field.attribute( "length" ).toInt();
QVariant::Type type = static_cast< QVariant::Type >( field.attribute( "type" ).toInt() );
QVariant::Type subType = static_cast< QVariant::Type >( field.attribute( "subType", 0 ).toInt() );
QString typeName = field.attribute( "typeName" );
mExpressions.append( ExpressionField( exp, QgsField( name, type, typeName, length, precision, comment ) ) );
mExpressions.append( ExpressionField( exp, QgsField( name, type, typeName, length, precision, comment, subType ) ) );
}
}
}

View File

@ -44,9 +44,10 @@ QgsField::QgsField( QString nam, QString typ, int len, int prec, bool num,
}
#endif
QgsField::QgsField( const QString& name, QVariant::Type type,
const QString& typeName, int len, int prec, const QString& comment )
const QString& typeName, int len, int prec, const QString& comment,
QVariant::Type subType )
{
d = new QgsFieldPrivate( name, type, typeName, len, prec, comment );
d = new QgsFieldPrivate( name, type, subType, typeName, len, prec, comment );
}
QgsField::QgsField( const QgsField &other )
@ -99,6 +100,11 @@ QVariant::Type QgsField::type() const
return d->type;
}
QVariant::Type QgsField::subType() const
{
return d->subType;
}
QString QgsField::typeName() const
{
return d->typeName;
@ -140,6 +146,11 @@ void QgsField::setType( QVariant::Type type )
d->type = type;
}
void QgsField::setSubType( QVariant::Type subType )
{
d->subType = subType;
}
void QgsField::setTypeName( const QString& typeName )
{
d->typeName = typeName;
@ -292,14 +303,15 @@ QDataStream& operator<<( QDataStream& out, const QgsField& field )
out << field.comment();
out << field.alias();
out << field.defaultValueExpression();
out << static_cast< quint32 >( field.subType() );
return out;
}
QDataStream& operator>>( QDataStream& in, QgsField& field )
{
quint32 type, length, precision;
quint32 type, subType, length, precision;
QString name, typeName, comment, alias, defaultValueExpression;
in >> name >> type >> typeName >> length >> precision >> comment >> alias >> defaultValueExpression;
in >> name >> type >> typeName >> length >> precision >> comment >> alias >> defaultValueExpression >> subType;
field.setName( name );
field.setType( static_cast< QVariant::Type >( type ) );
field.setTypeName( typeName );
@ -308,6 +320,7 @@ QDataStream& operator>>( QDataStream& in, QgsField& field )
field.setComment( comment );
field.setAlias( alias );
field.setDefaultValueExpression( defaultValueExpression );
field.setSubType( static_cast< QVariant::Type >( subType ) );
return in;
}

View File

@ -66,13 +66,15 @@ class CORE_EXPORT QgsField
* @param prec Field precision. Usually decimal places but may also be
* used in conjunction with other fields types (eg. variable character fields)
* @param comment Comment for the field
* @param subType If the field is a collection, its element's type
*/
QgsField( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() );
const QString& comment = QString(),
QVariant::Type subType = QVariant::Invalid );
/** Copy constructor
*/
@ -105,6 +107,12 @@ class CORE_EXPORT QgsField
//! Gets variant type of the field as it will be retrieved from data source
QVariant::Type type() const;
/**
* If the field is a collection, gets its element's type
* @note added in QGIS 3.0
*/
QVariant::Type subType() const;
/**
* Gets the field type. Field types vary depending on the data source. Examples
* are char, int, double, blob, geometry, etc. The type is stored exactly as
@ -149,6 +157,12 @@ class CORE_EXPORT QgsField
*/
void setType( QVariant::Type type );
/**
* If the field is a collection, set its element's type.
* @note added in QGIS 3.0
*/
void setSubType( QVariant::Type subType );
/**
* Set the field type.
* @param typeName Field type

View File

@ -44,12 +44,14 @@ class QgsFieldPrivate : public QSharedData
QgsFieldPrivate( const QString& name = QString(),
QVariant::Type type = QVariant::Invalid,
QVariant::Type subType = QVariant::Invalid,
const QString& typeName = QString(),
int len = 0,
int prec = 0,
const QString& comment = QString() )
: name( name )
, type( type )
, subType( subType )
, typeName( typeName )
, length( len )
, precision( prec )
@ -61,6 +63,7 @@ class QgsFieldPrivate : public QSharedData
: QSharedData( other )
, name( other.name )
, type( other.type )
, subType( other.subType )
, typeName( other.typeName )
, length( other.length )
, precision( other.precision )
@ -74,7 +77,7 @@ class QgsFieldPrivate : public QSharedData
bool operator==( const QgsFieldPrivate& other ) const
{
return (( name == other.name ) && ( type == other.type )
return (( name == other.name ) && ( type == other.type ) && ( subType == other.subType )
&& ( length == other.length ) && ( precision == other.precision )
&& ( alias == other.alias ) && ( defaultValueExpression == other.defaultValueExpression ) );
}
@ -85,6 +88,9 @@ class QgsFieldPrivate : public QSharedData
//! Variant type
QVariant::Type type;
//! If the variant is a collection, its element's type
QVariant::Type subType;
//! Type name from provider
QString typeName;

View File

@ -961,6 +961,20 @@ static QString quotedMap( const QVariantMap& map )
return "E'" + ret + "'::hstore";
}
static QString quotedList( const QVariantList& list )
{
QString ret;
for ( QVariantList::const_iterator i = list.constBegin(); i != list.constEnd(); ++i )
{
if ( !ret.isEmpty() )
{
ret += ",";
}
ret.append( doubleQuotedMapValue( i->toString() ) );
}
return "E'{" + ret + "}'";
}
QString QgsPostgresConn::quotedValue( const QVariant& value )
{
if ( value.isNull() )
@ -979,6 +993,10 @@ QString QgsPostgresConn::quotedValue( const QVariant& value )
case QVariant::Map:
return quotedMap( value.toMap() );
case QVariant::StringList:
case QVariant::List:
return quotedList( value.toList() );
case QVariant::String:
default:
return quotedString( value.toString() );

View File

@ -739,7 +739,7 @@ bool QgsPostgresFeatureIterator::getFeature( QgsPostgresResult &queryResult, int
{
QgsField fld = mSource->mFields.at( idx );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), queryResult.PQgetvalue( row, col ) );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ) );
primaryKeyVals << v;
if ( !subsetOfAttributes || fetchAttributes.contains( idx ) )
@ -781,7 +781,8 @@ void QgsPostgresFeatureIterator::getFeatureAttribute( int idx, QgsPostgresResult
if ( mSource->mPrimaryKeyAttrs.contains( idx ) )
return;
QVariant v = QgsPostgresProvider::convertValue( mSource->mFields.at( idx ).type(), queryResult.PQgetvalue( row, col ) );
const QgsField fld = mSource->mFields.at( idx );
QVariant v = QgsPostgresProvider::convertValue( fld.type(), fld.subType(), queryResult.PQgetvalue( row, col ) );
feature.setAttribute( idx, v );
col++;

View File

@ -356,9 +356,10 @@ static bool operator<( const QVariant &a, const QVariant &b )
return a.toLongLong() < b.toLongLong();
case QVariant::List:
case QVariant::StringList:
{
const QList<QVariant> &al = a.toList();
const QList<QVariant> &bl = b.toList();
const QList<QVariant> al = a.toList();
const QList<QVariant> bl = b.toList();
int i, n = qMin( al.size(), bl.size() );
for ( i = 0; i < n && al[i].type() == bl[i].type() && al[i].isNull() == bl[i].isNull() && al[i] == bl[i]; i++ )
@ -370,21 +371,6 @@ static bool operator<( const QVariant &a, const QVariant &b )
return al[i] < bl[i];
}
case QVariant::StringList:
{
const QStringList &al = a.toStringList();
const QStringList &bl = b.toStringList();
int i, n = qMin( al.size(), bl.size() );
for ( i = 0; i < n && al[i] == bl[i]; i++ )
;
if ( i == n )
return al.size() < bl.size();
else
return al[i] < bl[i];
}
case QVariant::Map:
return a.toMap() < b.toMap();
@ -884,6 +870,7 @@ bool QgsPostgresProvider::loadFields()
QString fieldComment = descrMap[tableoid][attnum];
QVariant::Type fieldType;
QVariant::Type fieldSubType = QVariant::Invalid;
if ( fieldTType == "b" )
{
@ -1025,6 +1012,7 @@ bool QgsPostgresProvider::loadFields()
else if ( fieldTypeName == "hstore" )
{
fieldType = QVariant::Map;
fieldSubType = QVariant::String;
fieldSize = -1;
}
else
@ -1036,7 +1024,8 @@ bool QgsPostgresProvider::loadFields()
if ( isArray )
{
fieldTypeName = '_' + fieldTypeName;
fieldType = QVariant::String;
fieldSubType = fieldType;
fieldType = ( fieldType == QVariant::String ? QVariant::StringList : QVariant::List );
fieldSize = -1;
}
}
@ -1068,7 +1057,7 @@ bool QgsPostgresProvider::loadFields()
mAttrPalIndexName.insert( i, fieldName );
mDefaultValues.insert( mAttributeFields.size(), defValMap[tableoid][attnum] );
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment ) );
mAttributeFields.append( QgsField( fieldName, fieldType, fieldTypeName, fieldSize, fieldPrec, fieldComment, fieldSubType ) );
}
setEditorWidgets();
@ -1570,7 +1559,7 @@ QVariant QgsPostgresProvider::minimumValue( int index ) const
sql = QString( "SELECT %1 FROM (%2) foo" ).arg( connectionRO()->fieldExpression( fld ), sql );
QgsPostgresResult rmin( connectionRO()->PQexec( sql ) );
return convertValue( fld.type(), rmin.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), rmin.PQgetvalue( 0, 0 ) );
}
catch ( PGFieldNotFound )
{
@ -1609,7 +1598,7 @@ void QgsPostgresProvider::uniqueValues( int index, QList<QVariant> &uniqueValues
if ( res.PQresultStatus() == PGRES_TUPLES_OK )
{
for ( int i = 0; i < res.PQntuples(); i++ )
uniqueValues.append( convertValue( fld.type(), res.PQgetvalue( i, 0 ) ) );
uniqueValues.append( convertValue( fld.type(), fld.subType(), res.PQgetvalue( i, 0 ) ) );
}
}
catch ( PGFieldNotFound )
@ -1746,7 +1735,7 @@ QVariant QgsPostgresProvider::maximumValue( int index ) const
QgsPostgresResult rmax( connectionRO()->PQexec( sql ) );
return convertValue( fld.type(), rmax.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), rmax.PQgetvalue( 0, 0 ) );
}
catch ( PGFieldNotFound )
{
@ -1770,7 +1759,7 @@ QVariant QgsPostgresProvider::defaultValue( int fieldId ) const
QgsPostgresResult res( connectionRO()->PQexec( QString( "SELECT %1" ).arg( defVal.toString() ) ) );
return convertValue( fld.type(), res.PQgetvalue( 0, 0 ) );
return convertValue( fld.type(), fld.subType(), res.PQgetvalue( 0, 0 ) );
}
return defVal;
@ -2070,7 +2059,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
{
QgsField fld = field( attrIdx );
v = paramValue( defaultValues[ i ], defaultValues[ i ] );
features->setAttribute( attrIdx, convertValue( fld.type(), v ) );
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v ) );
}
else
{
@ -2079,7 +2068,7 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
if ( v != value.toString() )
{
QgsField fld = field( attrIdx );
features->setAttribute( attrIdx, convertValue( fld.type(), v ) );
features->setAttribute( attrIdx, convertValue( fld.type(), fld.subType(), v ) );
}
}
@ -2092,8 +2081,9 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
{
for ( int i = 0; i < mPrimaryKeyAttrs.size(); ++i )
{
int idx = mPrimaryKeyAttrs.at( i );
features->setAttribute( idx, convertValue( mAttributeFields.at( idx ).type(), result.PQgetvalue( 0, i ) ) );
const int idx = mPrimaryKeyAttrs.at( i );
const QgsField fld = mAttributeFields.at( idx );
features->setAttribute( idx, convertValue( fld.type(), fld.subType(), result.PQgetvalue( 0, i ) ) );
}
}
else if ( result.PQresultStatus() != PGRES_COMMAND_OK )
@ -3485,6 +3475,20 @@ bool QgsPostgresProvider::convertField( QgsField &field, const QMap<QString, QVa
fieldPrec = -1;
break;
case QVariant::StringList:
fieldType = "_text";
fieldPrec = -1;
break;
case QVariant::List:
{
QgsField sub( "", field.subType(), "", fieldSize, fieldPrec );
if ( !convertField( sub, nullptr ) ) return false;
fieldType = "_" + sub.typeName();
fieldPrec = -1;
break;
}
case QVariant::Double:
if ( fieldPrec > 0 )
{
@ -3847,42 +3851,130 @@ QString QgsPostgresProvider::description() const
return tr( "PostgreSQL/PostGIS provider\n%1\nPostGIS %2" ).arg( pgVersion, postgisVersion );
} // QgsPostgresProvider::description()
static QVariant parseHstore( const QString& value )
static void jumpSpace( const QString& txt, int& i )
{
QRegExp recordSep( "\\s*,\\s*" );
QRegExp valueExtractor( "^(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))\\s*=>\\s*(?:\"((?:\\.|.)*)\"|((?:\\.|.)*))$" );
QVariantMap result;
Q_FOREACH ( QString record, value.split( recordSep ) )
while ( i < txt.length() && txt.at( i ).isSpace() ) ++i;
}
static QString getNextString( const QString& txt, int& i, const QString& sep )
{
jumpSpace( txt, i );
QString cur = txt.mid( i );
if ( cur.startsWith( '"' ) )
{
if ( valueExtractor.exactMatch( record ) )
QRegExp stringRe( "^\"((?:\\\\.|[^\"\\\\])*)\".*" );
if ( !stringRe.exactMatch( cur ) )
{
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 );
QgsLogger::warning( "Cannot find end of double quoted string: " + txt );
return QString::null;
}
else
i += stringRe.cap( 1 ).length() + 2;
jumpSpace( txt, i );
if ( !txt.mid( i ).startsWith( sep ) && i < txt.length() )
{
QgsLogger::warning( "Error parsing hstore record: " + record );
QgsLogger::warning( "Cannot find separator: " + txt.mid( i ) );
return QString::null;
}
i += sep.length();
return stringRe.cap( 1 ).replace( "\\\"", "\"" ).replace( "\\\\", "\\" );
}
else
{
QString ret;
int sepPos = cur.indexOf( sep );
if ( sepPos < 0 )
{
i += cur.length();
return cur.trimmed();
}
i += sepPos + sep.length();
return cur.left( sepPos ).trimmed();
}
}
static QVariant parseHstore( const QString& txt )
{
QVariantMap result;
int i = 0;
while ( i < txt.length() )
{
QString key = getNextString( txt, i, "=>" );
QString value = getNextString( txt, i, "," );
if ( key.isNull() || value.isNull() )
{
QgsLogger::warning( "Error parsing hstore: " + txt );
break;
}
result.insert( key, value );
}
return result;
}
static QVariant parseOtherArray( const QString& txt, QVariant::Type subType )
{
int i = 0;
QVariantList result;
while ( i < txt.length() )
{
const QString value = getNextString( txt, i, "," );
if ( value.isNull() )
{
QgsLogger::warning( "Error parsing array: " + txt );
break;
}
result.append( QgsPostgresProvider::convertValue( subType, QVariant::Invalid, value ) );
}
return result;
}
QVariant QgsPostgresProvider::convertValue( QVariant::Type type, const QString& value )
static QVariant parseStringArray( const QString& txt )
{
if ( type == QVariant::Map )
int i = 0;
QStringList result;
while ( i < txt.length() )
{
return parseHstore( value );
const QString value = getNextString( txt, i, "," );
if ( value.isNull() )
{
QgsLogger::warning( "Error parsing array: " + txt );
break;
}
result.append( value );
}
QVariant v( value );
return result;
}
if ( !v.convert( type ) || value.isNull() )
v = QVariant( type );
static QVariant parseArray( const QString& txt, QVariant::Type type, QVariant::Type subType )
{
if ( !txt.startsWith( '{' ) || !txt.endsWith( '}' ) )
{
QgsLogger::warning( "Error parsing array, missing curly braces: " + txt );
return QVariant( type );
}
QString inner = txt.mid( 1, txt.length() - 2 );
if ( type == QVariant::StringList )
return parseStringArray( inner );
else
return parseOtherArray( inner, subType );
}
return v;
QVariant QgsPostgresProvider::convertValue( QVariant::Type type, QVariant::Type subType, const QString& value )
{
switch ( type )
{
case QVariant::Map:
return parseHstore( value );
case QVariant::StringList:
case QVariant::List:
return parseArray( value, type, subType );
default:
{
QVariant v( value );
if ( !v.convert( type ) || value.isNull() ) return QVariant( type );
return v;
}
}
}
/**

View File

@ -252,10 +252,11 @@ class QgsPostgresProvider : public QgsVectorDataProvider
/**
* Convert the postgres string representation into the given QVariant type.
* @param type the wanted type
* @param subType if type is a collection, the wanted element 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 );
static QVariant convertValue( QVariant::Type type, QVariant::Type subType, const QString& value );
signals:
/**

View File

@ -542,7 +542,8 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
tablePrefix = QgsWFSUtils::removeNamespacePrefix( tablePrefix );
fieldName = tablePrefix + "." + fieldName;
}
QgsField field( fieldName, srcField.type(), srcField.typeName() );
QgsField field( srcField );
field.setName( fieldName );
if ( mapFieldNameToSrcLayerNameFieldName.contains( fieldName ) )
{
errorMsg = tr( "Field '%1': a field with the same name already exists" ).arg( field.name() );
@ -572,7 +573,8 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
tablePrefix = QgsWFSUtils::removeNamespacePrefix( tablePrefix );
fieldName = tablePrefix + "." + fieldName;
}
QgsField field( fieldName, srcField.type(), srcField.typeName() );
QgsField field( srcField );
field.setName( fieldName );
mapFieldNameToSrcLayerNameFieldName[ field.name()] =
QPair<QString, QString>( typeName, srcField.name() );
mShared->mFields.append( field );
@ -618,9 +620,11 @@ bool QgsWFSProvider::processSQL( const QString& sqlString, QString& errorMsg, QS
return false;
}
QgsField field( fieldName, tableFields.at( idx ).type(), tableFields.at( idx ).typeName() );
QgsField orig = tableFields.at( idx );
QgsField field( orig );
field.setName( fieldName );
mapFieldNameToSrcLayerNameFieldName[ field.name()] =
QPair<QString, QString>( columnTableTypename, tableFields.at( idx ).name() );
QPair<QString, QString>( columnTableTypename, orig.name() );
mShared->mFields.append( field );
}
}

View File

@ -2342,6 +2342,15 @@ class TestQgsExpression: public QObject
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>" ) );
QVariantList list;
list << 1 << 2 << 3;
QCOMPARE( QgsExpression::formatPreviewString( QVariant( list ) ), QString( "<i>&lt;list: 1, 2, 3&gt;</i>" ) );
QStringList stringList;
stringList << "One" << "Two" << "A very long string that is going to be truncated";
QCOMPARE( QgsExpression::formatPreviewString( QVariant( stringList ) ),
QString( "<i>&lt;list: 'One', 'Two', 'A very long string that is going to be trunca...&gt;</i>" ) );
}
};

View File

@ -42,6 +42,7 @@ class TestQgsField: public QObject
void dataStream();
void displayName();
void editorWidgetSetup();
void collection();
private:
};
@ -411,5 +412,16 @@ void TestQgsField::editorWidgetSetup()
QCOMPARE( field.editorWidgetSetup().config(), setup.config() );
}
void TestQgsField::collection()
{
QgsField field( "collection", QVariant::List, "_int32", 0, 0, QString(), QVariant::Int );
QCOMPARE( field.subType(), QVariant::Int );
field.setSubType( QVariant::Double );
QCOMPARE( field.subType(), QVariant::Double );
QVariant str( "hello" );
QVERIFY( !field.convertCompatible( str ) );
}
QTEST_MAIN( TestQgsField )
#include "testqgsfield.moc"

View File

@ -24,6 +24,22 @@ class TestQgsPostgresConn: public QObject
QCOMPARE( QgsPostgresConn::quotedValue( "b \"c' \\x" ), QString( "E'b \"c'' \\\\x'" ) );
}
void quotedValueStringArray()
{
QStringList list;
list << "a" << "b \"c' \\x";
const QString actual = QgsPostgresConn::quotedValue( list );
QCOMPARE( actual, QString( "E'{\"a\",\"b \\\\\"c\\' \\\\\\\\x\"}'" ) );
}
void quotedValueIntArray()
{
QVariantList list;
list << 1 << -5;
const QString actual = QgsPostgresConn::quotedValue( list );
QCOMPARE( actual, QString( "E'{\"1\",\"-5\"}'" ) );
}
};
QTEST_MAIN( TestQgsPostgresConn )

View File

@ -9,27 +9,62 @@ class TestQgsPostgresProvider: public QObject
private slots:
void decodeHstore()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, "\"1\"=>\"2\", \"a\"=>\"b \\\"c'\"" );
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, QVariant::String, "\"1\"=>\"2\", \"a\"=>\"b, \\\"c'\", \"backslash\"=>\"\\\\\"" );
QCOMPARE( decoded.type(), QVariant::Map );
QVariantMap expected;
expected["1"] = "2";
expected["a"] = "b \"c'";
expected["a"] = "b, \"c'";
expected["backslash"] = "\\";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toMap(), expected );
}
void decodeHstoreNoQuote()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, "1=>2, a=>b" );
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::Map, QVariant::String, "1=>2, a=>b c" );
QCOMPARE( decoded.type(), QVariant::Map );
QVariantMap expected;
expected["1"] = "2";
expected["a"] = "b";
expected["a"] = "b c";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toMap(), expected );
}
void decodeArray2StringList()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{\"1\",\"2\", \"a\\\\1\" , \"\\\\\",\"b, \\\"c'\"}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QStringList expected;
expected << "1" << "2" << "a\\1" << "\\" << "b, \"c'";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toStringList(), expected );
}
void decodeArray2StringListNoQuote()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{1,2, a ,b, c}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QStringList expected;
expected << "1" << "2" << "a" << "b" << "c";
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toStringList(), expected );
}
void decodeArray2IntList()
{
const QVariant decoded = QgsPostgresProvider::convertValue( QVariant::StringList, QVariant::String, "{1, 2, 3,-5,10}" );
QCOMPARE( decoded.type(), QVariant::StringList );
QVariantList expected;
expected << QVariant( 1 ) << QVariant( 2 ) << QVariant( 3 ) << QVariant( -5 ) << QVariant( 10 );
qDebug() << "actual: " << decoded;
QCOMPARE( decoded.toList(), expected );
}
};
QTEST_MAIN( TestQgsPostgresProvider )

View File

@ -143,7 +143,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
res = provider.addAttributes([QgsField("name", QVariant.String, ),
res = provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
assert res, "Failed to add attributes"
@ -193,7 +193,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
provider.addAttributes([QgsField("name", QVariant.String, ),
provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
myMessage = ('Expected: %s\nGot: %s\n' %
@ -267,7 +267,7 @@ class TestPyQgsMemoryProvider(unittest.TestCase, ProviderTestCase):
layer = QgsVectorLayer("Point", "test", "memory")
provider = layer.dataProvider()
res = provider.addAttributes([QgsField("name", QVariant.String, ),
res = provider.addAttributes([QgsField("name", QVariant.String),
QgsField("age", QVariant.Int),
QgsField("size", QVariant.Double)])
layer.updateFields()

View File

@ -350,7 +350,6 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
new_f = QgsFeature(vl.fields())
new_f['pk'] = NULL
#new_f['value'] = {'x': 'a\'s "y" \\', 'z': 'end'}
new_f['value'] = {'simple': '1', 'doubleQuote': '"y"', 'quote': "'q'", 'backslash': '\\'}
r, fs = vl.dataProvider().addFeatures([new_f])
self.assertTrue(r)
@ -366,6 +365,65 @@ class TestPyQgsPostgresProvider(unittest.TestCase, ProviderTestCase):
self.assertTrue(vl.deleteFeatures([new_pk]))
self.assertTrue(vl.commitChanges())
def testStringArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."string_array" sql=' % (self.dbconn), "teststringarray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.StringList)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.String)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], ['a', 'b', 'c'])
new_f = QgsFeature(vl.fields())
new_f['pk'] = NULL
new_f['value'] = ['simple', '"doubleQuote"', "'quote'", 'back\\slash']
r, fs = vl.dataProvider().addFeatures([new_f])
self.assertTrue(r)
new_pk = fs[0]['pk']
self.assertNotEqual(new_pk, NULL, fs[0].attributes())
try:
read_back = vl.getFeature(new_pk)
self.assertEqual(read_back['pk'], new_pk)
self.assertEqual(read_back['value'], new_f['value'])
finally:
self.assertTrue(vl.startEditing())
self.assertTrue(vl.deleteFeatures([new_pk]))
self.assertTrue(vl.commitChanges())
def testIntArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."int_array" sql=' % (self.dbconn), "testintarray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.List)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.Int)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], [1, 2, -5])
def testDoubleArray(self):
vl = QgsVectorLayer('%s table="qgis_test"."double_array" sql=' % (self.dbconn), "testdoublearray", "postgres")
self.assertTrue(vl.isValid())
fields = vl.dataProvider().fields()
self.assertEqual(fields.at(fields.indexFromName('value')).type(), QVariant.List)
self.assertEqual(fields.at(fields.indexFromName('value')).subType(), QVariant.Double)
f = next(vl.getFeatures(QgsFeatureRequest()))
value_idx = vl.fieldNameIndex('value')
self.assertTrue(isinstance(f.attributes()[value_idx], list))
self.assertEqual(f.attributes()[value_idx], [1.1, 2, -5.12345])
class TestPyQgsPostgresProviderCompoundKey(unittest.TestCase, ProviderTestCase):

View File

@ -5,6 +5,7 @@ SCRIPTS="
tests/testdata/provider/testdata_pg_reltests.sql
tests/testdata/provider/testdata_pg_vectorjoin.sql
tests/testdata/provider/testdata_pg_hstore.sql
tests/testdata/provider/testdata_pg_array.sql
"
dropdb qgis_test 2> /dev/null || true

View File

@ -0,0 +1,37 @@
DROP TABLE IF EXISTS qgis_test.string_array;
CREATE TABLE qgis_test.string_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value text[]
);
INSERT INTO qgis_test.string_array(value)
VALUES
('{a,b,c}');
DROP TABLE IF EXISTS qgis_test.int_array;
CREATE TABLE qgis_test.int_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value int4[]
);
INSERT INTO qgis_test.int_array(value)
VALUES
('{1,2,-5}');
DROP TABLE IF EXISTS qgis_test.double_array;
CREATE TABLE qgis_test.double_array
(
pk SERIAL NOT NULL PRIMARY KEY,
value float8[]
);
INSERT INTO qgis_test.double_array(value)
VALUES
('{1.1,2,-5.12345}');