Navigation Menu

Skip to content

Commit

Permalink
DBManager: fix importing a new layer in a GeoPackage (#16295)
Browse files Browse the repository at this point in the history
  • Loading branch information
rouault committed Apr 24, 2017
1 parent cd521d6 commit 965350b
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 39 deletions.
13 changes: 11 additions & 2 deletions python/plugins/db_manager/dlg_import_vector.py
Expand Up @@ -319,9 +319,16 @@ def accept(self):

# get output params, update output URI
self.outUri.setDataSource(schema, table, geom, "", pk)
uri = self.outUri.uri(False)

typeName = self.db.dbplugin().typeName()
providerName = self.db.dbplugin().providerName()
if typeName == 'gpkg':
uri = self.outUri.database()
options['update'] = True
options['driverName'] = 'GPKG'
options['layerName'] = table
else:
uri = self.outUri.uri(False)

if self.chkDropTable.isChecked():
options['overwrite'] = True

Expand Down Expand Up @@ -369,6 +376,8 @@ def accept(self):
if self.chkSpatialIndex.isEnabled() and self.chkSpatialIndex.isChecked():
self.db.connector.createSpatialIndex((schema, table), geom)

self.db.connection().reconnect()
self.db.refresh()
QMessageBox.information(self, self.tr("Import to database"), self.tr("Import was successful."))
return QDialog.accept(self)

Expand Down
61 changes: 31 additions & 30 deletions src/core/qgsvectorfilewriter.h
Expand Up @@ -427,6 +427,37 @@ class CORE_EXPORT QgsVectorFileWriter
SymbologyExport symbologyExport = NoSymbology
);

/** Create a new vector file writer.
* \param vectorFileName file name to write to
* \param fileEncoding encoding to use
* \param fields fields to write
* \param geometryType geometry type of output file
* \param srs spatial reference system of output file
* \param driverName OGR driver to use
* \param datasourceOptions list of OGR data source creation options
* \param layerOptions list of OGR layer creation options
* \param newFilename potentially modified file name (output parameter)
* \param symbologyExport symbology to export
* \param fieldValueConverter field value converter (added in QGIS 2.16)
* \param layerName layer name. If let empty, it will be derived from the filename (added in QGIS 3.0)
* \param action action on existing file (added in QGIS 3.0)
* \note not available in Python bindings
*/
QgsVectorFileWriter( const QString &vectorFileName,
const QString &fileEncoding,
const QgsFields &fields,
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &srs,
const QString &driverName,
const QStringList &datasourceOptions,
const QStringList &layerOptions,
QString *newFilename,
SymbologyExport symbologyExport,
FieldValueConverter *fieldValueConverter,
const QString &layerName,
ActionOnExistingFile action
);

//! QgsVectorFileWriter cannot be copied.
QgsVectorFileWriter( const QgsVectorFileWriter &rh ) = delete;
//! QgsVectorFileWriter cannot be copied.
Expand Down Expand Up @@ -569,36 +600,6 @@ class CORE_EXPORT QgsVectorFileWriter

private:

/** Create a new vector file writer.
* \param vectorFileName file name to write to
* \param fileEncoding encoding to use
* \param fields fields to write
* \param geometryType geometry type of output file
* \param srs spatial reference system of output file
* \param driverName OGR driver to use
* \param datasourceOptions list of OGR data source creation options
* \param layerOptions list of OGR layer creation options
* \param newFilename potentially modified file name (output parameter)
* \param symbologyExport symbology to export
* \param fieldValueConverter field value converter (added in QGIS 2.16)
* \param layerName layer name. If let empty, it will be derived from the filename (added in QGIS 3.0)
* \param action action on existing file (added in QGIS 3.0)
*/
QgsVectorFileWriter( const QString &vectorFileName,
const QString &fileEncoding,
const QgsFields &fields,
QgsWkbTypes::Type geometryType,
const QgsCoordinateReferenceSystem &srs,
const QString &driverName,
const QStringList &datasourceOptions,
const QStringList &layerOptions,
QString *newFilename,
SymbologyExport symbologyExport,
FieldValueConverter *fieldValueConverter,
const QString &layerName,
ActionOnExistingFile action
);

void init( QString vectorFileName, QString fileEncoding, const QgsFields &fields,
QgsWkbTypes::Type geometryType, QgsCoordinateReferenceSystem srs,
const QString &driverName, QStringList datasourceOptions,
Expand Down
15 changes: 14 additions & 1 deletion src/core/qgsvectorlayerimport.cpp
Expand Up @@ -98,7 +98,20 @@ QgsVectorLayerImport::QgsVectorLayerImport( const QString &uri,

QgsDebugMsg( "Created empty layer" );

QgsVectorDataProvider *vectorProvider = dynamic_cast< QgsVectorDataProvider * >( pReg->provider( providerKey, uri ) );
QString uriUpdated( uri );
// HACK sorry...
if ( providerKey == "ogr" )
{
QString layerName;
if ( options->contains( QStringLiteral( "layerName" ) ) )
layerName = options->value( QStringLiteral( "layerName" ) ).toString();
if ( !layerName.isEmpty() )
{
uriUpdated += "|layername=";
uriUpdated += layerName;
}
}
QgsVectorDataProvider *vectorProvider = dynamic_cast< QgsVectorDataProvider * >( pReg->provider( providerKey, uriUpdated ) );
if ( !vectorProvider || !vectorProvider->isValid() || ( vectorProvider->capabilities() & QgsVectorDataProvider::AddFeatures ) == 0 )
{
mError = ErrInvalidLayer;
Expand Down
65 changes: 60 additions & 5 deletions src/providers/ogr/qgsogrprovider.cpp
Expand Up @@ -228,6 +228,7 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer( const QStrin
QString encoding;
QString driverName = QStringLiteral( "ESRI Shapefile" );
QStringList dsOptions, layerOptions;
QString layerName;
if ( options )
{
Expand All @@ -242,14 +243,45 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer( const QStrin
if ( options->contains( QStringLiteral( "layerOptions" ) ) )
layerOptions << options->value( QStringLiteral( "layerOptions" ) ).toStringList();
if ( options->contains( QStringLiteral( "layerName" ) ) )
layerName = options->value( QStringLiteral( "layerName" ) ).toString();
}
if ( oldToNewAttrIdxMap )
oldToNewAttrIdxMap->clear();
if ( errorMessage )
errorMessage->clear();
if ( !overwrite )
QgsVectorFileWriter::ActionOnExistingFile action( QgsVectorFileWriter::CreateOrOverwriteFile );
bool update = false;
if ( options->contains( QStringLiteral( "update" ) ) )
{
update = options->value( QStringLiteral( "update" ) ).toBool();
if ( update )
{
if ( !overwrite && !layerName.isEmpty() )
{
OGRDataSourceH hDS = OGROpen( uri.toUtf8().constData(), TRUE, nullptr );
if ( hDS )
{
if ( OGR_DS_GetLayerByName( hDS, layerName.toUtf8().constData() ) )
{
OGR_DS_Destroy( hDS );
if ( errorMessage )
*errorMessage += QObject::tr( "Layer %2 of %1 exists and overwrite flag is false." )
.arg( uri ).arg( layerName );
return QgsVectorLayerImport::ErrCreateDataSource;
}
OGR_DS_Destroy( hDS );
}
}
action = QgsVectorFileWriter::CreateOrOverwriteLayer;
}
}
if ( !overwrite && !update )
{
QFileInfo fi( uri );
if ( fi.exists() )
Expand All @@ -263,7 +295,9 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer( const QStrin
QgsVectorFileWriter *writer = new QgsVectorFileWriter(
uri, encoding, fields, wkbType,
srs, driverName, dsOptions, layerOptions );
srs, driverName, dsOptions, layerOptions, nullptr,
QgsVectorFileWriter::NoSymbology, nullptr,
layerName, action );
QgsVectorFileWriter::WriterError error = writer->hasError();
if ( error )
Expand All @@ -275,16 +309,37 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer( const QStrin
return ( QgsVectorLayerImport::ImportError ) error;
}
QMap<int, int> attrIdxMap = writer->attrIdxToOgrIdx();
delete writer;
if ( oldToNewAttrIdxMap )
{
QMap<int, int> attrIdxMap = writer->attrIdxToOgrIdx();
bool firstFieldIsFid = false;
if ( !layerName.isEmpty() )
{
OGRDataSourceH hDS = OGROpen( uri.toUtf8().constData(), TRUE, nullptr );
if ( hDS )
{
OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, layerName.toUtf8().constData() );
if ( hLayer )
{
// Expose the OGR FID if it comes from a "real" column (typically GPKG)
// and make sure that this FID column is not exposed as a regular OGR field (shouldn't happen normally)
firstFieldIsFid = !( EQUAL( OGR_L_GetFIDColumn( hLayer ), "" ) ) &&
OGR_FD_GetFieldIndex( OGR_L_GetLayerDefn( hLayer ), OGR_L_GetFIDColumn( hLayer ) ) < 0 &&
fields.indexFromName( OGR_L_GetFIDColumn( hLayer ) ) < 0;
}
OGR_DS_Destroy( hDS );
}
}
for ( QMap<int, int>::const_iterator attrIt = attrIdxMap.begin(); attrIt != attrIdxMap.end(); ++attrIt )
{
oldToNewAttrIdxMap->insert( attrIt.key(), *attrIt );
oldToNewAttrIdxMap->insert( attrIt.key(), *attrIt + ( firstFieldIsFid ? 1 : 0 ) );
}
}
delete writer;
return QgsVectorLayerImport::NoError;
}
Expand Down
58 changes: 57 additions & 1 deletion tests/src/python/test_provider_ogr_gpkg.py
Expand Up @@ -19,7 +19,7 @@
import shutil
from osgeo import gdal, ogr

from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsRectangle, QgsSettings
from qgis.core import QgsVectorLayer, QgsVectorLayerImport, QgsFeature, QgsGeometry, QgsRectangle, QgsSettings
from qgis.PyQt.QtCore import QCoreApplication
from qgis.testing import start_app, unittest

Expand Down Expand Up @@ -395,6 +395,62 @@ def testDisablewalForSqlite3(self):

QgsSettings().setValue("/qgis/walForSqlite3", None)

def testSimulatedDBManagerImport(self):
uri = 'point?field=f1:int'
uri += '&field=f2:double(6,4)'
uri += '&field=f3:string(20)'
lyr = QgsVectorLayer(uri, "x", "memory")
self.assertTrue(lyr.isValid())
f = QgsFeature(lyr.fields())
f['f1'] = 1
f['f2'] = 123.456
f['f3'] = '12345678.90123456789'
f2 = QgsFeature(lyr.fields())
f2['f1'] = 2
lyr.dataProvider().addFeatures([f, f2])

tmpfile = os.path.join(self.basetestpath, 'testSimulatedDBManagerImport.gpkg')
ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
ds = None
options = {}
options['update'] = True
options['driverName'] = 'GPKG'
options['layerName'] = 'my_out_table'
err = QgsVectorLayerImport.importLayer(lyr, tmpfile, "ogr", lyr.crs(), False, False, options)
self.assertEqual(err[0], QgsVectorLayerImport.NoError,
'unexpected import error {0}'.format(err))
lyr = QgsVectorLayer(tmpfile + "|layername=my_out_table", "y", "ogr")
self.assertTrue(lyr.isValid())
features = lyr.getFeatures()
f = next(features)
self.assertEqual(f['f1'], 1)
self.assertEqual(f['f2'], 123.456)
self.assertEqual(f['f3'], '12345678.90123456789')
f = next(features)
self.assertEqual(f['f1'], 2)
features = None

# Test overwriting without overwrite option
err = QgsVectorLayerImport.importLayer(lyr, tmpfile, "ogr", lyr.crs(), False, False, options)
self.assertEqual(err[0], QgsVectorLayerImport.ErrCreateDataSource)

# Test overwriting
lyr = QgsVectorLayer(uri, "x", "memory")
self.assertTrue(lyr.isValid())
f = QgsFeature(lyr.fields())
f['f1'] = 3
lyr.dataProvider().addFeatures([f])
options['overwrite'] = True
err = QgsVectorLayerImport.importLayer(lyr, tmpfile, "ogr", lyr.crs(), False, False, options)
self.assertEqual(err[0], QgsVectorLayerImport.NoError,
'unexpected import error {0}'.format(err))
lyr = QgsVectorLayer(tmpfile + "|layername=my_out_table", "y", "ogr")
self.assertTrue(lyr.isValid())
features = lyr.getFeatures()
f = next(features)
self.assertEqual(f['f1'], 3)
features = None


if __name__ == '__main__':
unittest.main()

0 comments on commit 965350b

Please sign in to comment.