mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-17 00:09:36 -04:00
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:
parent
93afbe12e0
commit
abc55f4c42
@ -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
|
||||
|
@ -5064,6 +5064,22 @@ QString QgsExpression::formatPreviewString( const QVariant& value )
|
||||
}
|
||||
return tr( "<i><map: %1></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><list: %1></i>" ).arg( listStr );
|
||||
}
|
||||
else
|
||||
{
|
||||
return value.toString();
|
||||
|
@ -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 ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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() );
|
||||
|
@ -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++;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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:
|
||||
/**
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
@ -2342,6 +2342,15 @@ class TestQgsExpression: public QObject
|
||||
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i><map: 1: 'One', 2: 'Two'></i>" ) );
|
||||
map["3"] = "A very long string that is going to be truncated";
|
||||
QCOMPARE( QgsExpression::formatPreviewString( QVariant( map ) ), QString( "<i><map: 1: 'One', 2: 'Two', 3: 'A very long string that is going to ...></i>" ) );
|
||||
|
||||
QVariantList list;
|
||||
list << 1 << 2 << 3;
|
||||
QCOMPARE( QgsExpression::formatPreviewString( QVariant( list ) ), QString( "<i><list: 1, 2, 3></i>" ) );
|
||||
|
||||
QStringList stringList;
|
||||
stringList << "One" << "Two" << "A very long string that is going to be truncated";
|
||||
QCOMPARE( QgsExpression::formatPreviewString( QVariant( stringList ) ),
|
||||
QString( "<i><list: 'One', 'Two', 'A very long string that is going to be trunca...></i>" ) );
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -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"
|
||||
|
@ -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 )
|
||||
|
@ -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 )
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
||||
|
1
tests/testdata/provider/testdata_pg.sh
vendored
1
tests/testdata/provider/testdata_pg.sh
vendored
@ -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
|
||||
|
37
tests/testdata/provider/testdata_pg_array.sql
vendored
Normal file
37
tests/testdata/provider/testdata_pg_array.sql
vendored
Normal 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}');
|
||||
|
Loading…
x
Reference in New Issue
Block a user