[FEATURE] Binary blob support for OGR provider

Instead of converting binary fields to truncated strings, we instead
store their contents as QByteArray values, allowing the original binary
content to be retrieved.

This allows for plugins and scripts to utilise binary fields,
such as extracting their contents.
This commit is contained in:
Nyall Dawson 2018-11-09 14:26:49 +10:00
parent 8ae1880d38
commit 5c27b7da50
56 changed files with 59 additions and 1 deletions

View File

@ -259,6 +259,10 @@ QString QgsField::displayString( const QVariant &v ) const
QJsonDocument doc = QJsonDocument::fromVariant( v );
return QString::fromUtf8( doc.toJson().data() );
}
else if ( d->type == QVariant::ByteArray )
{
return QObject::tr( "BLOB" );
}
// Fallback if special rules do not apply
return v.toString();
}

View File

@ -221,6 +221,17 @@ QVariant QgsOgrUtils::getOgrFeatureAttribute( OGRFeatureH ogrFet, const QgsField
value = QDateTime( QDate( year, month, day ), QTime( hour, minute, second ) );
}
break;
case QVariant::ByteArray:
{
int size = 0;
const GByte *b = OGR_F_GetFieldAsBinary( ogrFet, attIndex, &size );
QByteArray ba = QByteArray::fromRawData( reinterpret_cast<const char *>( b ), size );
ba.detach();
value = ba;
break;
}
default:
Q_ASSERT_X( false, "QgsOgrUtils::getOgrFeatureAttribute", "unsupported field type" );
if ( ok )

View File

@ -1028,6 +1028,11 @@ void QgsOgrProvider::loadFields()
case OFTDateTime:
varType = QVariant::DateTime;
break;
case OFTBinary:
varType = QVariant::ByteArray;
break;
case OFTString:
default:
varType = QVariant::String; // other unsupported, leave it as a string
@ -1504,6 +1509,13 @@ bool QgsOgrProvider::addFeaturePrivate( QgsFeature &f, Flags flags )
OGR_F_SetFieldString( feature.get(), ogrAttId, textEncoding()->fromUnicode( attrVal.toString() ).constData() );
break;
case OFTBinary:
{
const QByteArray ba = attrVal.toByteArray();
OGR_F_SetFieldBinary( feature.get(), ogrAttId, ba.size(), const_cast< GByte * >( reinterpret_cast< const GByte * >( ba.data() ) ) );
break;
}
default:
QgsMessageLog::logMessage( tr( "type %1 for attribute %2 not found" ).arg( type ).arg( qgisAttId ), tr( "OGR" ) );
break;
@ -2059,6 +2071,14 @@ bool QgsOgrProvider::changeAttributeValues( const QgsChangedAttributesMap &attr_
case OFTString:
OGR_F_SetFieldString( of.get(), f, textEncoding()->fromUnicode( it2->toString() ).constData() );
break;
case OFTBinary:
{
const QByteArray ba = it2->toByteArray();
OGR_F_SetFieldBinary( of.get(), f, ba.size(), const_cast< GByte * >( reinterpret_cast< const GByte * >( ba.data() ) ) );
break;
}
default:
pushError( tr( "Type %1 of attribute %2 of feature %3 unknown." ).arg( type ).arg( fid ).arg( f ) );
break;

View File

@ -384,6 +384,11 @@ void TestQgsField::displayString()
QCOMPARE( longField.displayString( 5 ), QString( "5" ) );
QCOMPARE( longField.displayString( 599999898999LL ), QString( "599999898999" ) );
// binary field
QgsField binaryField( QStringLiteral( "binary" ), QVariant::ByteArray, QStringLiteral( "Binary" ) );
QString testBAString( QStringLiteral( "test string" ) );
QByteArray testBA( testBAString.toLocal8Bit() );
QCOMPARE( binaryField.displayString( testBA ), QStringLiteral( "BLOB" ) );
}
void TestQgsField::convertCompatible()

View File

@ -16,9 +16,10 @@ import os
import shutil
import sys
import tempfile
import hashlib
from osgeo import gdal, ogr # NOQA
from qgis.PyQt.QtCore import QVariant
from qgis.PyQt.QtCore import QVariant, QByteArray
from qgis.core import (QgsApplication,
QgsRectangle,
QgsProviderRegistry,
@ -489,6 +490,23 @@ class PyQgsOGRProvider(unittest.TestCase):
self.assertEqual(f.id(), 8)
del it
def testBinaryField(self):
source = os.path.join(TEST_DATA_DIR, 'attachments.gdb')
vl = QgsVectorLayer(source + "|layername=points__ATTACH")
self.assertTrue(vl.isValid())
fields = vl.fields()
data_field = fields[fields.lookupField('DATA')]
self.assertEqual(data_field.type(), QVariant.ByteArray)
self.assertEqual(data_field.typeName(), 'Binary')
features = {f['ATTACHMENTID']: f for f in vl.getFeatures()}
self.assertEqual(len(features), 2)
self.assertIsInstance(features[1]['DATA'], QByteArray)
self.assertEqual(hashlib.md5(features[1]['DATA'].data()).hexdigest(), 'ef3dbc530cc39a545832a6c82aac57b6')
self.assertIsInstance(features[2]['DATA'], QByteArray)
self.assertEqual(hashlib.md5(features[2]['DATA'].data()).hexdigest(), '4b952b80e4288ca5111be2f6dd5d6809')
if __name__ == '__main__':
unittest.main()

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
tests/testdata/attachments.gdb/gdb vendored Normal file

Binary file not shown.

Binary file not shown.