[qgis] 01/01: Imported Upstream version 2.18.1+dfsg

Bas Couwenberg sebastic at debian.org
Fri Nov 25 15:37:16 UTC 2016


This is an automated email from the git hooks/post-receive script.

sebastic pushed a commit to branch upstream
in repository qgis.

commit 417bf39e74bcdb343e96a885ed8607e2766c0601
Author: Bas Couwenberg <sebastic at xs4all.nl>
Date:   Fri Nov 25 15:55:20 2016 +0100

    Imported Upstream version 2.18.1+dfsg
---
 CMakeLists.txt                                     |   2 +-
 ChangeLog                                          | 541 +++++++++++++
 ci/travis/linux/qt4/script.sh                      |   2 +-
 ci/travis/linux/qt5/blacklist.txt                  |   3 +-
 ci/travis/linux/qt5/script.sh                      |   2 +-
 cmake/SIPMacros.cmake                              |   4 +
 debian/changelog                                   |  10 +-
 debian/rules                                       |   2 +-
 i18n/qgis_de.ts                                    |   4 +-
 ms-windows/osgeo4w/package.cmd                     |   1 -
 python/core/geometry/qgsabstractgeometryv2.sip     |   4 +-
 python/core/geometry/qgscircularstringv2.sip       |   4 +-
 python/core/geometry/qgscompoundcurvev2.sip        |   4 +-
 python/core/geometry/qgscurvepolygonv2.sip         |   5 +-
 python/core/geometry/qgscurvev2.sip                |   4 +-
 python/core/geometry/qgsgeometrycollectionv2.sip   |   5 +-
 python/core/geometry/qgslinestringv2.sip           |   1 +
 python/core/geometry/qgsmulticurvev2.sip           |   4 +-
 python/core/geometry/qgsmultilinestringv2.sip      |   4 +-
 python/core/geometry/qgsmultipointv2.sip           |   5 +-
 python/core/geometry/qgsmultipolygonv2.sip         |   4 +-
 python/core/geometry/qgsmultisurfacev2.sip         |   4 +-
 python/core/geometry/qgspointv2.sip                |   1 +
 python/core/geometry/qgspolygonv2.sip              |   4 +-
 python/core/geometry/qgssurfacev2.sip              |   2 +-
 python/core/layertree/qgslayertreelayer.sip        |  10 +
 python/core/layertree/qgslayertreemodel.sip        |   3 +
 python/core/layertree/qgslayertreenode.sip         |  10 +
 python/core/qgscacheindexfeatureid.sip             |  34 -
 python/core/qgsvectorfilewriter.sip                | 143 ++++
 python/core/qgsvectorlayercache.sip                |  18 +-
 python/core/symbology-ng/qgssymbollayerv2.sip      |  76 +-
 python/gui/gui.sip                                 |   1 +
 python/gui/qgsextentgroupbox.sip                   |   2 +-
 python/gui/qgsfiledownloader.sip                   |  66 ++
 python/plugins/CMakeLists.txt                      |  11 +
 .../qgis/MergeLines_LOCAL_1606.py => __init__.py}  |   0
 python/plugins/db_manager/db_model.py              |   7 +-
 .../plugins/db_manager/db_plugins/CMakeLists.txt   |   1 +
 python/plugins/db_manager/db_plugins/__init__.py   |   3 +-
 python/plugins/db_manager/db_plugins/connector.py  |   6 +
 .../db_manager/db_plugins/gpkg/CMakeLists.txt      |   9 +
 .../db_plugins/gpkg/__init__.py}                   |   0
 .../db_manager/db_plugins/gpkg/connector.py        | 868 +++++++++++++++++++++
 .../db_manager/db_plugins/gpkg/data_model.py       |  51 ++
 .../db_manager/db_plugins/gpkg/icons/gpkg_icon.png | Bin 0 -> 23317 bytes
 .../db_plugins/{spatialite => gpkg}/info_model.py  |  33 +-
 .../db_plugins/{spatialite => gpkg}/plugin.py      | 133 ++--
 .../db_manager/db_plugins/gpkg/resources.qrc       |   5 +
 .../db_manager/db_plugins/gpkg/sql_dictionary.py   |  28 +
 .../db_manager/db_plugins/spatialite/connector.py  |  74 +-
 .../db_manager/db_plugins/spatialite/info_model.py |   5 +-
 .../db_manager/db_plugins/spatialite/plugin.py     |  24 +-
 python/plugins/db_manager/db_tree.py               |   2 +-
 python/plugins/db_manager/dlg_sql_layer_window.py  |   2 +
 python/plugins/db_manager/dlg_table_properties.py  |  26 +-
 python/plugins/processing/algs/help/qgis.yaml      |  14 +-
 python/plugins/processing/algs/qgis/Difference.py  |   2 +-
 .../processing/algs/qgis/ImportIntoPostGIS.py      |   2 +-
 .../processing/algs/qgis/MergeLines_BASE_1606.py   |  90 ---
 .../processing/algs/qgis/MergeLines_REMOTE_1606.py |  89 ---
 .../processing/algs/qgis/SimplifyGeometries.py     |  21 +-
 .../processing/algs/qgis/SymmetricalDifference.py  |   2 +-
 .../algs/qgis/ui/FieldsCalculatorDialog.py         |   2 +
 .../processing/algs/qgis/ui/FieldsMapperDialogs.py |  10 +-
 .../plugins/processing/gui/AlgorithmDialogBase.py  |   7 +-
 python/plugins/processing/gui/BatchPanel.py        |   8 +-
 python/plugins/processing/gui/ConfigDialog.py      |  22 +-
 python/plugins/processing/gui/menus.py             |   4 +-
 ...=> clip_lines_by_multipolygon_BACKUP_11112.gml} |   0
 .../clip_lines_by_multipolygon_BASE_3790.gml       |  34 -
 src/app/composer/qgsattributeselectiondialog.cpp   |   2 +-
 src/app/ogr/qgsvectorlayersaveasdialog.cpp         | 164 +++-
 src/app/ogr/qgsvectorlayersaveasdialog.h           |   5 +
 src/app/qgisapp.cpp                                |  48 +-
 src/app/qgsattributetabledialog.cpp                |   4 +-
 src/app/qgsfeatureaction.cpp                       |  30 +-
 src/app/qgsfieldcalculator.cpp                     |   4 +-
 src/app/qgsidentifyresultsdialog.cpp               |  52 ++
 src/app/qgsidentifyresultsdialog.h                 |   7 +
 src/app/qgsmaptoolfeatureaction.cpp                |   4 +-
 src/app/qgsoptions.cpp                             |   4 +-
 src/app/qgsrulebasedlabelingwidget.cpp             |   1 -
 src/app/qgsstatusbarmagnifierwidget.cpp            |   5 +-
 src/app/qgsvectorlayerproperties.cpp               |   9 +-
 src/core/CMakeLists.txt                            |   1 +
 src/core/composer/qgscomposerattributetablev2.cpp  |   2 +-
 src/core/composer/qgscomposermapgrid.cpp           |   8 +-
 src/core/dxf/qgsdxfexport.cpp                      |   2 +-
 src/core/geometry/qgsabstractgeometryv2.h          |   2 +-
 src/core/geometry/qgscurvepolygonv2.cpp            |  21 +
 src/core/geometry/qgscurvepolygonv2.h              |   1 +
 src/core/geometry/qgsgeometrycollectionv2.cpp      |  16 +
 src/core/geometry/qgsgeometrycollectionv2.h        |   2 +
 src/core/geometry/qgsgeometryutils.cpp             |  39 +-
 src/core/geometry/qgslinestringv2.h                |   1 +
 src/core/geometry/qgsmultipointv2.h                |   1 +
 src/core/geometry/qgspointv2.h                     |   1 +
 src/core/layertree/qgslayertreegroup.cpp           |  14 +
 src/core/layertree/qgslayertreegroup.h             |   4 +-
 src/core/layertree/qgslayertreelayer.cpp           |  26 +
 src/core/layertree/qgslayertreelayer.h             |  10 +
 src/core/layertree/qgslayertreemodel.cpp           |  11 +-
 src/core/layertree/qgslayertreemodel.h             |   3 +
 src/core/layertree/qgslayertreenode.cpp            |   1 +
 src/core/layertree/qgslayertreenode.h              |  10 +
 src/core/qgsactionmanager.cpp                      |   2 +-
 src/core/qgsattributeaction.h                      |   2 +-
 src/core/qgscacheindex.h                           |   4 +-
 src/core/qgscacheindexfeatureid.cpp                |  32 +-
 src/core/qgscacheindexfeatureid.h                  |  34 -
 src/core/qgsconditionalstyle.cpp                   |   2 +-
 src/core/qgsexpression.cpp                         |   4 +-
 src/core/qgsexpressioncontext.cpp                  |   4 +-
 src/core/qgsvectorfilewriter.cpp                   | 324 +++++++-
 src/core/qgsvectorfilewriter.h                     | 155 +++-
 src/core/qgsvectorlayer.cpp                        |   2 +-
 src/core/qgsvectorlayercache.cpp                   |  67 +-
 src/core/qgsvectorlayercache.h                     |  29 +-
 src/core/qgsvectorlayerfeatureiterator.cpp         |   2 +
 src/core/qgsvectorlayerrenderer.cpp                |   2 +-
 src/core/qgswebpage.h                              |   4 +
 src/core/raster/qgsrasterprojector.cpp             |   1 -
 src/core/symbology-ng/qgsarrowsymbollayer.cpp      |   8 +-
 src/core/symbology-ng/qgslinesymbollayerv2.cpp     |   8 +-
 src/core/symbology-ng/qgssymbolv2.cpp              |  10 +-
 src/gui/CMakeLists.txt                             |   3 +
 .../qgsattributetablefiltermodel.cpp               |   6 -
 src/gui/attributetable/qgsdualview.cpp             |   2 -
 .../qgsfieldconditionalformatwidget.cpp            |   2 +-
 src/gui/qgisgui.h                                  |  16 +
 src/gui/qgsattributedialog.cpp                     |   5 +-
 src/gui/qgsattributedialog.h                       |   5 +-
 src/gui/qgscolorbuttonv2.cpp                       |  10 +-
 src/gui/qgsextentgroupbox.h                        |   2 +-
 src/gui/qgsfiledownloader.cpp                      | 203 +++++
 src/gui/qgsfiledownloader.h                        | 112 +++
 src/gui/qgsmapcanvas.cpp                           |   5 +-
 .../symbology-ng/qgsrulebasedrendererv2widget.cpp  |   3 +-
 src/gui/symbology-ng/qgssymbollayerv2widget.cpp    |   2 +-
 src/providers/memory/qgsmemoryprovider.cpp         |   9 +-
 src/providers/ogr/qgsogrprovider.cpp               | 650 +++++++++++++--
 src/providers/ogr/qgsogrprovider.h                 |   3 +-
 src/providers/oracle/qgsoracleprovider.cpp         |  53 +-
 src/providers/oracle/qgsoracleprovider.h           |  11 +
 src/providers/postgres/qgspostgresprovider.cpp     |  11 +-
 src/providers/spatialite/qgsspatialiteprovider.cpp |  11 +-
 .../virtual/qgsvirtuallayersqlitehelper.cpp        |  14 +-
 .../virtual/qgsvirtuallayersqlitemodule.cpp        |   2 +-
 src/providers/wms/qgswmsprovider.cpp               |   5 +
 src/server/qgshostedrdsbuilder.cpp                 |   2 +-
 src/server/qgsserverprojectparser.cpp              |   4 +
 src/server/qgswfsserver.cpp                        |  39 +
 src/server/qgswmsprojectparser.cpp                 |   9 +-
 src/ui/qgsvectorlayersaveasdialogbase.ui           |  34 +-
 tests/src/core/testqgslayertree.cpp                |  62 ++
 tests/src/core/testqgsvectorlayercache.cpp         | 112 +++
 tests/src/gui/CMakeLists.txt                       |   1 +
 tests/src/gui/testqgsfiledownloader.cpp            | 251 ++++++
 tests/src/python/CMakeLists.txt                    |   6 +-
 tests/src/python/qgis_wrapped_server.py            |  60 +-
 ...ndpoint.py => test_authmanager_password_ows.py} |  25 +-
 ...ger_endpoint.py => test_authmanager_pki_ows.py} | 108 ++-
 tests/src/python/test_authmanager_pki_postgres.py  | 233 ++++++
 tests/src/python/test_db_manager_gpkg.py           | 433 ++++++++++
 tests/src/python/test_provider_ogr_gpkg.py         |  69 ++
 tests/src/python/test_qgsfiledownloader.py         | 147 ++++
 tests/src/python/test_qgsserver.py                 |  12 +-
 tests/src/python/test_qgsvectorfilewriter.py       | 172 ++++
 tests/src/python/utilities.py                      |   6 +-
 170 files changed, 5785 insertions(+), 940 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 834f3b7..0bfeac2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,6 +1,6 @@
 SET(CPACK_PACKAGE_VERSION_MAJOR "2")
 SET(CPACK_PACKAGE_VERSION_MINOR "18")
-SET(CPACK_PACKAGE_VERSION_PATCH "0")
+SET(CPACK_PACKAGE_VERSION_PATCH "1")
 SET(COMPLETE_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH})
 SET(RELEASE_NAME "Las Palmas")
 IF (POLICY CMP0048) # in CMake 3.0.0+
diff --git a/ChangeLog b/ChangeLog
index 02a3fdc..27d022b 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,544 @@
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Speed up inserting features into memory layers by ~65%
+
+    By avoiding an unnecessary map lookup
+
+    (cherry-picked from b679cbe)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Fix QgsFeatureRequest with expression not using provider fields
+    and limit (fix #15771)
+
+    (cherry-picked from 5e3bef7)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Fix QgsGeometryUtils::sqrDistToLine returns bad values (eg nan values)
+
+    (cherry-picked from 1d3f1f0)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    QgsCacheIndexFeatureId can also handle non-FilterFid requests
+    in certain circumstances
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from b5c1d0f)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Don't query list of visible features when showing selected features
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 5346023)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Avoid warning noise during attribute table load
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from b0801f3)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Use cached feature iterator if cache has all needed features
+
+    Previously a cached feature iterator would only be returned
+    if cache was either full or used a cache index
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 5e3d8fe)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Recognise that a cache can be filled using a FilterNone request
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from afd5d1e)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-23
+
+    Fix test for request size vs cache size
+
+    On behalf of Faunalia, sponsored by ENEL
+
+    (cherry-picked from 38a4aac)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-21
+
+    Make max canvas scale 1600% (fix #15861)
+
+    Max canvas sacle should be a multiple of 2 so that zooming in
+    to the max and then back out again results in 100% zoom option.
+
+    Additionally, make the min/max zoom level not come from QSettings
+    as these aren't exposed anywhere
+
+    (cherry-picked from 6d0e8e6)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-11-22
+
+    [processing] Fix import to postgis alg when table name not set (fix #15869, 15097)
+
+    (cherry-picked from 271e67e)
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-21
+
+    oracle provider: don't try to verify requested geometry type in estimated metadata mode
+
+Matthias Kuhn <matthias at opengis.ch>	2016-10-17
+
+    Don't rely on RTTI to convert symbol layer to sip objects
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-16
+
+    oracle provider: allow switching workspaces through property
+
+Harrissou Sant-anna <delazj at gmail.com>	2016-11-15
+
+    typo fix
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-16
+
+    fix a62fdb0 indentation
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-16
+
+    oracle provider: fix retrieval of column comments for geometryless tables (fixes #15853)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-15
+
+    Fix compilation without QtWebKit
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-14
+
+    Fix missing docs and sip coverage and tests (followup 968e02d6)
+
+    (cherry picked from commit 9950b085d4c59ad432f15d4c1e3cdb4ef8bede5f)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-14
+
+    Propagate layer/group name changes in the layer tree (fixes #15844)
+
+    (cherry picked from commit 968e02d6fe38b024855ef75852eb033b4ad9ecbd)
+
+rldhont <rldhont at gmail.com>	2016-11-14
+
+    Fix typo
+
+rldhont <rldhont at gmail.com>	2016-11-14
+
+    [BUGFIX][QGIS Server] Transform feature collections bbox to ESPG:4326
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-11
+
+    fix builds on trusty and precise
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-11
+
+    fix translation
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-11
+
+    Make file downloader test more stable
+
+Matthias Kuhn <matthias at opengis.ch>	2016-11-11
+
+    [spatialite] Don't skip default values
+
+    When inserting multiple features in a single prepared statement, the spatialite
+    provider would skip any default for individual features, even though they have
+    been specified in the field list, resulting in missing fields.
+
+Merge: 84bc1fc 925964b
+Alexander Bruy <alexander.bruy at gmail.com>	2016-11-10
+
+    Merge pull request #3682 from arnaud-morvan/processing_fix_fieldmapper_dialog_init
+
+    [processing] fix fieldmapper dialog init method
+
+Merge: bf3c0f1 57aa7fd
+Alessandro Pasotti <elpaso at itopen.it>	2016-11-10
+
+    Merge pull request #3740 from elpaso/downloader_2_18
+
+    [bugfix] File downloader for identify dialog hyperlinks
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    Travis won: ported all test cases to Python
+
+    and disabled C++ companion test (still useful locally and
+    for debugging)
+
+    For the curious: QTemporaryFile is not working as expected
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    Try to convince Travis to behave like a normal mechanical being
+
+Hugo Mercier <hugo.mercier at oslandia.com>	2016-11-09
+
+    [virtual] fix encoding issue
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    Removed debug output
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    Added note and removed debug output
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-09
+
+    File downloader for identify dialog hyperlinks
+
+    fixes #14703
+
+    Include C++ and Python tests
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-09
+
+    debian packaging: include internal webkit bindings in yakkety
+
+rldhont <rldhont at gmail.com>	2016-11-08
+
+    [BUGFIX][QGIS Server] Revert layer order in WMS GetContext request
+
+Merge: dc9ebe2 e78fc62
+volaya <volayaf at gmail.com>	2016-11-08
+
+    Merge pull request #3686 from arnaud-morvan/processing_reset_menus
+
+    [processing] Add button to reset processing menus in config dialog
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-08
+
+    dxf export: skip nan z coordinates
+
+    (cherry picked from commit 0189609dcf22b58f2fd02dde1b7c9e1c98443606)
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-05
+
+    Add missing /Transfer/ annotations to geometry classes
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-05
+
+    Add missing /Factory/ annotations to geometry classes
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-04
+
+    Remove per-pixel debug msg slowing down reprojection (fixes #15796)
+
+    Even at level 5, the debug string would still be built and thrown away,
+    and affects both debug builds and RelWithDebugInfo (used for windows -dev builds).
+
+Martin Dobias <wonder.sk at gmail.com>	2016-11-04
+
+    Fix crash with tiled WMS (fixes #15799)
+
+Merge: 0796ecb 9c535b5
+Alessandro Pasotti <elpaso at itopen.it>	2016-11-03
+
+    Merge pull request #3716 from elpaso/auth_tests_more_2_18
+
+    More Authentication Tests
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests] Use from qgis.PyQt import
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests] Use from Qt import
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests] Py3 compat and postgres 9.4 default
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests] Chmod 0400 PKI certificates
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests] Server connection timeout
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-11-03
+
+    [tests][bugfix] Skip diff image if it does not exists
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-10-27
+
+    [tests] PKI authentication tests
+
+Alessandro Pasotti <apasotti at boundlessgeo.com>	2016-10-25
+
+    [tests] PKI auth tests on a PKI-enabled QGIS Server
+
+rldhont <rldhont at gmail.com>	2016-11-03
+
+    [BUGFIX][QGIS Server] No flags in QgsFeatureRequest if expression needs geometry
+
+rldhont <rldhont at gmail.com>	2016-11-02
+
+    [BUGFIX][QGIS Server] Apply filter element
+
+Even Rouault <even.rouault at spatialys.com>	2016-11-02
+
+    Fix test for QT5
+
+Even Rouault <even.rouault at spatialys.com>	2016-11-02
+
+    test_provider_ogr_gpkg.py: Test disabling walForSqlite3 setting
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-01
+
+    fix german translation string
+
+Juergen E. Fischer <jef at norbit.de>	2016-11-01
+
+    fix typos
+
+    (cherry picked from commit 7a326b1b8d7026bc2782afc0fdc5abbaa1065370)
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-31
+
+    [OGR provider] Make addAttributes() return the requested field type, precision and width so as to make  QgsVectorLayerEditBuffer::commitChanges() API
+
+    Fixes #15614
+
+Hugo Mercier <hugo.mercier at oslandia.com>	2016-10-28
+
+    Don't delete QgsAttributeDialog too early. Fixes #15737
+
+volaya <volayaf at gmail.com>	2016-10-28
+
+    [processing] added missing imports
+
+    fixes #15766
+
+volaya <volayaf at gmail.com>	2016-10-28
+
+    [processing] fixed alg names for vector menu
+
+    fixes #15764
+
+volaya <volayaf at gmail.com>	2016-10-28
+
+    [processing] replace greater than and lower than characters in log
+
+    fixes #15748
+
+volaya <volayaf at gmail.com>	2016-10-28
+
+    [processing] allow only one row in batch interface
+
+    fixes #15703
+
+arnaud.morvan at camptocamp.com <arnaud.morvan at camptocamp.com>	2016-08-21
+
+    Add button to reset processing menus in config dialog
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Fix can't save custom colours in color picker (fix #15760)
+
+    Also fixes a memory leak caused by color picker widget not
+    being deleted
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Fix color picker does not show previous color
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Fix color pickers opens in dock layout when editing rules via
+    layer properties
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Remove repo junk
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Use QgsExpressionContextScope::addVariable instead of setVariable
+
+    ...where appropriate (ie, read-only, non user set variables).
+    It's much faster as it doesn't need to check whether the
+    variable already exists.
+
+    Results in ~10% improvement in rendering speed. Refs #15752.
+
+    (cherry-picked from 85897885445c7383938ac89318d12a7d37024bb4)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Optimise QgsAbstractGeometry
+
+    Make nCoordinates virtual, and provide shortcuts for some
+    geometry types. The base method which calls coordinateSequence()
+    is quite slow in certain circumstances.
+
+    Speeds up rendering point layers by ~25%, also likely to
+    speed up lots of geometry heavy operations throughout QGIS
+
+    Refs #15752
+
+    (cherry-picked from 49432a84683e99bdc2592e7ae808e81fa3bc40cd)
+
+Nyall Dawson <nyall.dawson at gmail.com>	2016-10-28
+
+    Speed up point layer rendering - don't calculate unused label obstacles
+
+    Cuts render time by ~60%. Fix #15752.
+
+    (cherry-picked from 5798a82c8011ea7f44a1ed1d55ef0719786e8056)
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-27
+
+    Vector layer properties GUI: tweak message to be "Save in database (GeoPackage)" for GeoPackage
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-27
+
+    [OGR provider] Field type declaration: update with more sensible values for min/max length
+
+    In particular put 0 as minimum length for integer, integer64, real and text fields,
+    so as to let OGR decide the default (and not limit text fields for format that
+    don't have limitations)
+
+arnaud.morvan at camptocamp.com <arnaud.morvan at camptocamp.com>	2016-10-27
+
+    [processing] Fix fieldmapper dialog init method
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-27
+
+    [DBManager GPKG] Set appropriate icon for line layers
+
+volaya <volayaf at gmail.com>	2016-10-27
+
+    [processing] fixed simplify geometries
+
+volaya <volayaf at gmail.com>	2016-10-27
+
+    indentation fix
+
+volaya <volayaf at gmail.com>	2016-10-27
+
+    [processing] fixed field loading in field calculator
+
+    fixes #15767
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-27
+
+    [OGR provider] Do not list qgis_projects (from QgisGeoPackage plugin) as sublayer
+
+Matthias Kuhn <matthias at opengis.ch>	2016-10-26
+
+    Fix adding features with "evaluate default values"
+
+Denis Rouzaud <denis.rouzaud at gmail.com>	2016-10-26
+
+    Revert "[Postgres] fix writing default value when primary key has varchar columns"
+
+    This reverts commit 5768c5a6e7dae24a1ab2ae614da7a87dad2c1020.
+
+Denis Rouzaud <denis.rouzaud at gmail.com>	2016-10-26
+
+    [Postgres] fix writing default value when primary key has varchar columns
+
+    kudos to @m-kuhn to find and solve this
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    [FEATURE] [OGR provider] Load/save style in database for GPKG and Spatialite
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    QgsVectorLayer::loadNamedStyle(): make it work with non database URI
+
+    Such as OGR GPKG
+
+Juergen E. Fischer <jef at norbit.de>	2016-10-26
+
+    include qgsactionmanager.h in install
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-25
+
+    [DB Manager] Fix refresh issue when renaming GPKG table, and disable add geometry column button if already one existing
+
+rldhont <rldhont at gmail.com>	2016-10-25
+
+    [BUGFIX][QGIS Server] Do not cache invalid layer
+
+    After readLayerXml, the server stored layers in cache and in registry without verifying validity.
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-24
+
+    Blacklist PyQgsDBManagerGpkg test
+
+    The test consistently segfaults on Travis, but run fine locally,
+    normally or under Valgrind.
+    Trying to run it under Valgrind on Travis runners does not succeed:
+    https://travis-ci.org/rouault/Quantum-GIS/builds/168674938
+
+    So blacklist for now
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    [DBManager] Remove geopackage support from spatialite plugin
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    [DBManager] Add tests for GPKG plugin
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    [FEATURE] [DBManager] Add a GeoPackage dedicated plugin
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-18
+
+    [OGR provider] Support full SELECT subset string
+
+    This will be useful for DBManager SQL request layer.
+
+Even Rouault <even.rouault at spatialys.com>	2016-10-13
+
+    [FEATURE] Vector layer save as: offer file/layer overwriting, new layer creation, feature and field appending
+
+    When saving a vector layer into an existing file, depending on the capabilities
+    of the output driver, the user can now decide whether:
+    - to overwrite the whole file
+    - to overwrite only the target layer (layer name is now configurable)
+    - to append features to the existing target layer
+    - to append features, add new fields if there are any.
+
+    All above is available for drivers like GPKG, SpatiaLite, FileGDB, ...
+    For drivers like Shapefile, MapInfo .tab, feature append is also available.
+
+    Backported from master 688d1a5eba225b07cfbe707b19756683c9a46deb
+
+Juergen E. Fischer <jef at norbit.de>	2016-07-10
+
+    msvc: use /bigobj for sip modules
+
+    (cherry picked from commit d0b3e39cdab1da17d7a977ba3def5ce1b64ff707)
+
+Juergen E. Fischer <jef at norbit.de>	2016-10-21
+
+    Release of 2.18 (Las Palmas)
+
+Juergen E. Fischer <jef at norbit.de>	2016-10-21
+
+    changelog and news update for 2.18
+
 Juergen E. Fischer <jef at norbit.de>	2016-10-21
 
     release.pl: stay on same master* branch
diff --git a/ci/travis/linux/qt4/script.sh b/ci/travis/linux/qt4/script.sh
index 8e499f4..cf10efb 100755
--- a/ci/travis/linux/qt4/script.sh
+++ b/ci/travis/linux/qt4/script.sh
@@ -24,4 +24,4 @@ if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then
   chmod -R ugo-w ~/.ccache
 fi
 
-xvfb-run ctest -V -E 'qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest' -S ./qgis-test-travis.ctest --output-on-failure
+xvfb-run ctest -V -E 'qgis_filedownloader|qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest|PyQgsDBManagerGpkg' -S ./qgis-test-travis.ctest --output-on-failure
diff --git a/ci/travis/linux/qt5/blacklist.txt b/ci/travis/linux/qt5/blacklist.txt
index 9321962..04ad5b6 100755
--- a/ci/travis/linux/qt5/blacklist.txt
+++ b/ci/travis/linux/qt5/blacklist.txt
@@ -6,7 +6,7 @@ PyQgsMapUnitScale
 PyQgsPalLabelingServer
 PyQgsRelationEditWidget
 PyQgsServer
-PyQgsAuthManagerEndpointTest
+PyQgsAuthManagerPasswordOWSTest
 PyQgsServerAccessControl
 PyQgsSipCoverage
 PyQgsSpatialiteProvider
@@ -16,3 +16,4 @@ qgis_composermapgridtest
 qgis_composerutils
 ProcessingGrass7AlgorithmsImageryTest
 ProcessingGrass7AlgorithmsRasterTest
+PyQgsDBManagerGpkg
diff --git a/ci/travis/linux/qt5/script.sh b/ci/travis/linux/qt5/script.sh
index 017a7e7..a1f58c3 100755
--- a/ci/travis/linux/qt5/script.sh
+++ b/ci/travis/linux/qt5/script.sh
@@ -25,5 +25,5 @@ fi
 
 DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
 
-xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest|qgis_ogcutilstest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure
+xvfb-run ctest -V -E "qgis_filedownloader|qgis_openstreetmaptest|qgis_wcsprovidertest|qgis_ziplayertest|qgis_ogcutilstest|$(cat ${DIR}/blacklist.txt | paste -sd '|' -)" -S ./qgis-test-travis.ctest --output-on-failure
 # xvfb-run ctest -V -E "qgis_openstreetmaptest|qgis_wcsprovidertest" -S ./qgis-test-travis.ctest --output-on-failure
diff --git a/cmake/SIPMacros.cmake b/cmake/SIPMacros.cmake
index 0583a09..e684bd3 100644
--- a/cmake/SIPMacros.cmake
+++ b/cmake/SIPMacros.cmake
@@ -92,6 +92,10 @@ MACRO(GENERATE_SIP_PYTHON_MODULE_CODE MODULE_NAME MODULE_SIP CPP_FILES)
     ENDIF(MSVC)
   ENDIF(PEDANTIC)
 
+  IF(MSVC)
+    ADD_DEFINITIONS( /bigobj )
+  ENDIF(MSVC)
+
   SET(SIPCMD ${SIP_BINARY_PATH} ${_sip_tags} -w -e ${_sip_x} ${SIP_EXTRA_OPTIONS} -j ${SIP_CONCAT_PARTS} -c ${CMAKE_CURRENT_BINARY_DIR}/${_module_path} ${_sip_includes} ${_abs_module_sip})
   SET(SUPPRESS_SIP_WARNINGS FALSE CACHE BOOL "Hide SIP warnings")
   MARK_AS_ADVANCED(SUPPRESS_SIP_WARNINGS)
diff --git a/debian/changelog b/debian/changelog
index ae164de..391f0ac 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,8 +1,14 @@
-qgis (2.18.0) UNRELEASED; urgency=medium
+qgis (2.18.1) UNRELEASED; urgency=medium
+
+  * Release of 2.18.1
+
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 25 Nov 2016 14:13:12 +0100
+
+qgis (2.18.0) unstable; urgency=medium
 
   * Release of 2.18.0
 
- -- Jürgen E. Fischer <jef at norbit.de>  Fri, 21 Oct 2016 14:14:02 +0200
+ -- Jürgen E. Fischer <jef at norbit.de>  Fri, 25 Nov 2016 14:13:12 +0100
 
 qgis (2.17.0) unstable; urgency=medium
 
diff --git a/debian/rules b/debian/rules
index dccf69d..8d223ae 100755
--- a/debian/rules
+++ b/debian/rules
@@ -125,7 +125,7 @@ ifneq (,$(findstring $(DISTRIBUTION),"sid stretch"))
 	CMAKE_OPTS += -DPOSTGRES_LIBRARY=/usr/lib/$(DEB_BUILD_MULTIARCH)/libpq.so
 endif
 
-ifneq (,$(findstring $(DISTRIBUTION),"sid stretch"))
+ifneq (,$(findstring $(DISTRIBUTION),"sid stretch yakkety"))
 	CMAKE_OPTS += -DWITH_INTERNAL_WEBKIT_BINDINGS=TRUE
 endif
 
diff --git a/i18n/qgis_de.ts b/i18n/qgis_de.ts
index eebb95d..2fbb635 100644
--- a/i18n/qgis_de.ts
+++ b/i18n/qgis_de.ts
@@ -14804,7 +14804,7 @@ Strg (Cmd) erhöht um 15 Grad.</translation>
     <message>
         <location filename="../python/plugins/processing/python-i18n.cpp" line="1646"/>
         <source>Merge lines</source>
-        <translation>Zeilen zusammenführen</translation>
+        <translation>Linien zusammenführen</translation>
     </message>
     <message>
         <location filename="../python/plugins/processing/python-i18n.cpp" line="1647"/>
@@ -67672,7 +67672,7 @@ Fehler:%2
     </message>
     <message>
         <location filename="../src/providers/oracle/qgsoracleprovider.cpp" line="722"/>
-        <source>No spatial index on column %1 found - expect poor performance.</source>
+        <source>No spatial index on column %1.%2.%3 found - expect poor performance.</source>
         <translation>Kein räumlicher Index auf Spalte %1.%2.%3 gefunden - schlechte Performance ist zu erwarten.</translation>
     </message>
     <message>
diff --git a/ms-windows/osgeo4w/package.cmd b/ms-windows/osgeo4w/package.cmd
index dd3579a..30aa888 100644
--- a/ms-windows/osgeo4w/package.cmd
+++ b/ms-windows/osgeo4w/package.cmd
@@ -341,7 +341,6 @@ tar -C %OSGEO4W_ROOT% -cjf %ARCH%/release/qgis/%PACKAGENAME%-server/%PACKAGENAME
 	"apps/%PACKAGENAME%/bin/wms_metadata.xml" ^
 	"apps/%PACKAGENAME%/bin/schemaExtension.xsd" ^
 	"apps/%PACKAGENAME%/python/qgis/_server.pyd" ^
-	"apps/%PACKAGENAME%/python/qgis/_server.lib" ^
 	"apps/%PACKAGENAME%/python/qgis/server/" ^
 	"httpd.d/httpd_%PACKAGENAME%.conf.tmpl" ^
 	"etc/postinstall/%PACKAGENAME%-server.bat" ^
diff --git a/python/core/geometry/qgsabstractgeometryv2.sip b/python/core/geometry/qgsabstractgeometryv2.sip
index 4f1ffae..b5d4e84 100644
--- a/python/core/geometry/qgsabstractgeometryv2.sip
+++ b/python/core/geometry/qgsabstractgeometryv2.sip
@@ -85,7 +85,7 @@ class QgsAbstractGeometryV2
 
     /** Clones the geometry by performing a deep copy
      */
-    virtual QgsAbstractGeometryV2* clone() const = 0;
+    virtual QgsAbstractGeometryV2* clone() const = 0 /Factory/;
 
     /** Clears the geometry, ie reset it to a null geometry
      */
@@ -248,7 +248,7 @@ class QgsAbstractGeometryV2
 
     /** Returns the number of nodes contained in the geometry
      */
-    int nCoordinates() const;
+    virtual int nCoordinates() const;
 
     /** Returns the point corresponding to a specified vertex id
      */
diff --git a/python/core/geometry/qgscircularstringv2.sip b/python/core/geometry/qgscircularstringv2.sip
index 40f7af2..3c36283 100644
--- a/python/core/geometry/qgscircularstringv2.sip
+++ b/python/core/geometry/qgscircularstringv2.sip
@@ -13,7 +13,7 @@ class QgsCircularStringV2: public QgsCurveV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCircularStringV2* clone() const;
+    virtual QgsCircularStringV2* clone() const /Factory/;
     virtual void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -58,7 +58,7 @@ class QgsCircularStringV2: public QgsCurveV2
      * of the curve.
      * @param tolerance segmentation tolerance
      * @param toleranceType maximum segmentation angle or maximum difference between approximation and curve*/
-    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const;
+    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
 
     void draw( QPainter& p ) const;
     void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
diff --git a/python/core/geometry/qgscompoundcurvev2.sip b/python/core/geometry/qgscompoundcurvev2.sip
index 75b5be2..e163d4e 100644
--- a/python/core/geometry/qgscompoundcurvev2.sip
+++ b/python/core/geometry/qgscompoundcurvev2.sip
@@ -15,7 +15,7 @@ class QgsCompoundCurveV2: public QgsCurveV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCompoundCurveV2* clone() const;
+    virtual QgsCompoundCurveV2* clone() const /Factory/;
     virtual void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -38,7 +38,7 @@ class QgsCompoundCurveV2: public QgsCurveV2
      * of the curve.
      * @param tolerance segmentation tolerance
      * @param toleranceType maximum segmentation angle or maximum difference between approximation and curve*/
-    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const;
+    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
 
     /** Returns the number of curves in the geometry.
      */
diff --git a/python/core/geometry/qgscurvepolygonv2.sip b/python/core/geometry/qgscurvepolygonv2.sip
index d6c4ee6..eee8a23 100644
--- a/python/core/geometry/qgscurvepolygonv2.sip
+++ b/python/core/geometry/qgscurvepolygonv2.sip
@@ -12,7 +12,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
 
     virtual QString geometryType() const;
     virtual int dimension() const;
-    virtual QgsCurvePolygonV2* clone() const;
+    virtual QgsCurvePolygonV2* clone() const /Factory/;
     void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -28,7 +28,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
     //surface interface
     virtual double area() const;
     virtual double perimeter() const;
-    QgsPolygonV2* surfaceToPolygon() const;
+    QgsPolygonV2* surfaceToPolygon() const /Factory/;
     virtual QgsAbstractGeometryV2* boundary() const /Factory/;
 
 
@@ -68,6 +68,7 @@ class QgsCurvePolygonV2: public QgsSurfaceV2
     virtual bool deleteVertex( QgsVertexId position );
 
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
     double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;
 
diff --git a/python/core/geometry/qgscurvev2.sip b/python/core/geometry/qgscurvev2.sip
index 890ec31..6cca0df 100644
--- a/python/core/geometry/qgscurvev2.sip
+++ b/python/core/geometry/qgscurvev2.sip
@@ -10,7 +10,7 @@ class QgsCurveV2: public QgsAbstractGeometryV2
     virtual bool operator==( const QgsCurveV2& other ) const = 0;
     virtual bool operator!=( const QgsCurveV2& other ) const = 0;
 
-    virtual QgsCurveV2* clone() const = 0;
+    virtual QgsCurveV2* clone() const = 0 /Factory/;
 
     /** Returns the starting point of the curve.
      * @see endPoint
@@ -35,7 +35,7 @@ class QgsCurveV2: public QgsAbstractGeometryV2
      * @param tolerance segmentation tolerance
      * @param toleranceType maximum segmentation angle or maximum difference between approximation and curve
      */
-    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const = 0;
+    virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const = 0 /Factory/;
 
     /** Adds a curve to a painter path.
      */
diff --git a/python/core/geometry/qgsgeometrycollectionv2.sip b/python/core/geometry/qgsgeometrycollectionv2.sip
index 2423dcc..61f96bf 100644
--- a/python/core/geometry/qgsgeometrycollectionv2.sip
+++ b/python/core/geometry/qgsgeometrycollectionv2.sip
@@ -10,7 +10,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     //QgsGeometryCollectionV2& operator=( const QgsGeometryCollectionV2& c );
     virtual ~QgsGeometryCollectionV2();
 
-    virtual QgsGeometryCollectionV2* clone() const;
+    virtual QgsGeometryCollectionV2* clone() const /Factory/;
 
     /** Returns the number of geometries within the collection.
      */
@@ -67,6 +67,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     virtual QgsRectangle boundingBox() const;
 
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
     virtual double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const;
 
@@ -84,7 +85,7 @@ class QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     /** Returns a geometry without curves. Caller takes ownership
     * @param tolerance segmentation tolerance
     * @param toleranceType maximum segmentation angle or maximum difference between approximation and curve*/
-    QgsAbstractGeometryV2* segmentize( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const;
+    QgsAbstractGeometryV2* segmentize( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
 
     /** Returns approximate rotation angle for a vertex. Usually average angle between adjacent segments.
      * @param vertex the vertex id
diff --git a/python/core/geometry/qgslinestringv2.sip b/python/core/geometry/qgslinestringv2.sip
index 1663516..966100c 100644
--- a/python/core/geometry/qgslinestringv2.sip
+++ b/python/core/geometry/qgslinestringv2.sip
@@ -138,6 +138,7 @@ class QgsLineStringV2: public QgsCurveV2
     virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const /Factory/;
 
     int numPoints() const;
+    virtual int nCoordinates() const;
     void points( QList<QgsPointV2>& pt ) const;
 
     void draw( QPainter& p ) const;
diff --git a/python/core/geometry/qgsmulticurvev2.sip b/python/core/geometry/qgsmulticurvev2.sip
index bce8521..4b5fb40 100644
--- a/python/core/geometry/qgsmulticurvev2.sip
+++ b/python/core/geometry/qgsmulticurvev2.sip
@@ -6,7 +6,7 @@ class QgsMultiCurveV2: public QgsGeometryCollectionV2
 
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiCurveV2* clone() const;
+    virtual QgsMultiCurveV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiCurveV2: public QgsGeometryCollectionV2
     QString asJSON( int precision = 17 ) const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
     /** Returns a geometry without curves. Caller takes ownership*/
     QgsAbstractGeometryV2* segmentize() const /Factory/;
 
diff --git a/python/core/geometry/qgsmultilinestringv2.sip b/python/core/geometry/qgsmultilinestringv2.sip
index f42c39e..fd6346f 100644
--- a/python/core/geometry/qgsmultilinestringv2.sip
+++ b/python/core/geometry/qgsmultilinestringv2.sip
@@ -6,7 +6,7 @@ class QgsMultiLineStringV2: public QgsMultiCurveV2
 
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiLineStringV2* clone() const;
+    virtual QgsMultiLineStringV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiLineStringV2: public QgsMultiCurveV2
     QString asJSON( int precision = 17 ) const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     /** Returns the geometry converted to the more generic curve type QgsMultiCurveV2
     @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgsmultipointv2.sip b/python/core/geometry/qgsmultipointv2.sip
index ff355e6..67b7b64 100644
--- a/python/core/geometry/qgsmultipointv2.sip
+++ b/python/core/geometry/qgsmultipointv2.sip
@@ -5,7 +5,7 @@ class QgsMultiPointV2: public QgsGeometryCollectionV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiPointV2* clone() const;
+    virtual QgsMultiPointV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -16,9 +16,10 @@ class QgsMultiPointV2: public QgsGeometryCollectionV2
     QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const;
     QString asJSON( int precision = 17 ) const;
 
+    virtual int nCoordinates() const;
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     virtual QgsAbstractGeometryV2* boundary() const /Factory/;
 
diff --git a/python/core/geometry/qgsmultipolygonv2.sip b/python/core/geometry/qgsmultipolygonv2.sip
index 4ce4280..746d793 100644
--- a/python/core/geometry/qgsmultipolygonv2.sip
+++ b/python/core/geometry/qgsmultipolygonv2.sip
@@ -5,7 +5,7 @@ class QgsMultiPolygonV2: public QgsMultiSurfaceV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiPolygonV2* clone() const;
+    virtual QgsMultiPolygonV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiPolygonV2: public QgsMultiSurfaceV2
 
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     /** Returns the geometry converted to the more generic curve type QgsMultiSurfaceV2
     @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgsmultisurfacev2.sip b/python/core/geometry/qgsmultisurfacev2.sip
index 7b3d9ff..ea6b22d 100644
--- a/python/core/geometry/qgsmultisurfacev2.sip
+++ b/python/core/geometry/qgsmultisurfacev2.sip
@@ -5,7 +5,7 @@ class QgsMultiSurfaceV2: public QgsGeometryCollectionV2
 %End
   public:
     virtual QString geometryType() const;
-    virtual QgsMultiSurfaceV2* clone() const;
+    virtual QgsMultiSurfaceV2* clone() const /Factory/;
 
     bool fromWkt( const QString& wkt );
 
@@ -18,7 +18,7 @@ class QgsMultiSurfaceV2: public QgsGeometryCollectionV2
 
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
-    virtual bool addGeometry( QgsAbstractGeometryV2* g );
+    virtual bool addGeometry( QgsAbstractGeometryV2* g /Transfer/ );
 
     virtual QgsAbstractGeometryV2* boundary() const /Factory/;
 
diff --git a/python/core/geometry/qgspointv2.sip b/python/core/geometry/qgspointv2.sip
index b51a748..f8a08fa 100644
--- a/python/core/geometry/qgspointv2.sip
+++ b/python/core/geometry/qgspointv2.sip
@@ -157,6 +157,7 @@ class QgsPointV2: public QgsAbstractGeometryV2
                     bool transformZ = false );
     void transform( const QTransform& t );
     virtual QList< QList< QList< QgsPointV2 > > > coordinateSequence() const;
+    virtual int nCoordinates() const;
     virtual QgsAbstractGeometryV2* boundary() const /Factory/;
 
     //low-level editing
diff --git a/python/core/geometry/qgspolygonv2.sip b/python/core/geometry/qgspolygonv2.sip
index 900e089..075ce0a 100644
--- a/python/core/geometry/qgspolygonv2.sip
+++ b/python/core/geometry/qgspolygonv2.sip
@@ -11,7 +11,7 @@ class QgsPolygonV2: public QgsCurvePolygonV2
     bool operator!=( const QgsPolygonV2& other ) const;
 
     virtual QString geometryType() const;
-    virtual QgsPolygonV2* clone() const;
+    virtual QgsPolygonV2* clone() const /Factory/;
     void clear();
 
     virtual bool fromWkb( QgsConstWkbPtr wkb );
@@ -25,7 +25,7 @@ class QgsPolygonV2: public QgsCurvePolygonV2
     // inherited: QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const;
     // inherited: QString asJSON( int precision = 17 ) const;
 
-    QgsPolygonV2* surfaceToPolygon() const;
+    QgsPolygonV2* surfaceToPolygon() const /Factory/;
 
     /** Returns the geometry converted to the more generic curve type QgsCurvePolygonV2
      @return the converted geometry. Caller takes ownership*/
diff --git a/python/core/geometry/qgssurfacev2.sip b/python/core/geometry/qgssurfacev2.sip
index 571d337..a172608 100644
--- a/python/core/geometry/qgssurfacev2.sip
+++ b/python/core/geometry/qgssurfacev2.sip
@@ -6,7 +6,7 @@ class QgsSurfaceV2: public QgsAbstractGeometryV2
 
   public:
 
-    virtual QgsPolygonV2* surfaceToPolygon() const = 0;
+    virtual QgsPolygonV2* surfaceToPolygon() const = 0 /Factory/;
 
     virtual QgsRectangle boundingBox() const;
 
diff --git a/python/core/layertree/qgslayertreelayer.sip b/python/core/layertree/qgslayertreelayer.sip
index 79b64f2..21b694b 100644
--- a/python/core/layertree/qgslayertreelayer.sip
+++ b/python/core/layertree/qgslayertreelayer.sip
@@ -31,6 +31,13 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
 
     QgsMapLayer* layer() const;
 
+    //! Get layer's name
+    //! @note added in 2.18.1
+    QString name() const;
+    //! Set layer's name
+    //! @note added in 2.18.1
+    void setName( const QString& n );
+
     QString layerName() const;
     void setLayerName( const QString& n );
 
@@ -47,6 +54,9 @@ class QgsLayerTreeLayer : QgsLayerTreeNode
   protected slots:
     void registryLayersAdded( const QList<QgsMapLayer*>& layers );
     void registryLayersWillBeRemoved( const QStringList& layerIds );
+    //! Emits a nameChanged() signal if layer's name has changed
+    //! @note added in 2.18.1
+    void layerNameChanged();
 
   signals:
     //! emitted when a previously unavailable layer got loaded
diff --git a/python/core/layertree/qgslayertreemodel.sip b/python/core/layertree/qgslayertreemodel.sip
index de401db..88d93f2 100644
--- a/python/core/layertree/qgslayertreemodel.sip
+++ b/python/core/layertree/qgslayertreemodel.sip
@@ -209,6 +209,9 @@ class QgsLayerTreeModel : QAbstractItemModel
     void nodeRemovedChildren();
 
     void nodeVisibilityChanged( QgsLayerTreeNode* node );
+    //! Updates model when node's name has changed
+    //! @note added in 2.18.1
+    void nodeNameChanged( QgsLayerTreeNode* node, const QString& name );
 
     void nodeCustomPropertyChanged( QgsLayerTreeNode* node, const QString& key );
 
diff --git a/python/core/layertree/qgslayertreenode.sip b/python/core/layertree/qgslayertreenode.sip
index 23db81c..abf9cb3 100644
--- a/python/core/layertree/qgslayertreenode.sip
+++ b/python/core/layertree/qgslayertreenode.sip
@@ -76,6 +76,13 @@ class QgsLayerTreeNode : QObject
     //! Get list of children of the node. Children are owned by the parent
     QList<QgsLayerTreeNode*> children();
 
+    //! Return name of the node
+    //! @note added in 2.18.1
+    virtual QString name() const = 0;
+    //! Set name of the node. Emits nameChanged signal.
+    //! @note added in 2.18.1
+    virtual void setName( const QString& name ) = 0;
+
     //! Read layer tree from XML. Returns new instance
     static QgsLayerTreeNode *readXML( QDomElement &element );
     //! Write layer tree to XML
@@ -119,6 +126,9 @@ class QgsLayerTreeNode : QObject
     void customPropertyChanged( QgsLayerTreeNode *node, const QString& key );
     //! Emitted when the collapsed/expanded state of a node within the tree has been changed
     void expandedChanged( QgsLayerTreeNode *node, bool expanded );
+    //! Emitted when the name of the node is changed
+    //! @note added in 2.18.1
+    void nameChanged( QgsLayerTreeNode* node, QString name );
 
   protected:
 
diff --git a/python/core/qgscacheindexfeatureid.sip b/python/core/qgscacheindexfeatureid.sip
index b6c7bd1..2fc8aec 100644
--- a/python/core/qgscacheindexfeatureid.sip
+++ b/python/core/qgscacheindexfeatureid.sip
@@ -6,42 +6,8 @@ class QgsCacheIndexFeatureId : QgsAbstractCacheIndex
   public:
     QgsCacheIndexFeatureId( QgsVectorLayerCache* );
 
-    /**
-     * Is called, whenever a feature is removed from the cache. You should update your indexes, so
-     * they become invalid in case this feature was required to successfuly answer a request.
-     */
     virtual void flushFeature( const QgsFeatureId fid );
-
-    /**
-     * Sometimes, the whole cache changes its state and its easier to just withdraw everything.
-     * In this case, this method is issued. Be sure to clear all cache information in here.
-     */
     virtual void flush();
-
-    /**
-     * @brief
-     * Implement this method to update the the indices, in case you need information contained by the request
-     * to properly index. (E.g. spatial index)
-     * Does nothing by default
-     *
-     * @param featureRequest  The feature request that was answered
-     * @param fids            The feature ids that have been returned
-     */
     virtual void requestCompleted( const QgsFeatureRequest& featureRequest, const QgsFeatureIds& fids );
-
-    /**
-     * Is called, when a feature request is issued on a cached layer.
-     * If this cache index is able to completely answer the feature request, it will return true
-     * and write the list of feature ids of cached features to cachedFeatures. If it is not able
-     * it will return false and the cachedFeatures state is undefined.
-     *
-     * @param featureIterator  A reference to a {@link QgsFeatureIterator}. A valid featureIterator will
-     *                         be assigned in case this index is able to answer the request and the return
-     *                         value is true.
-     * @param featureRequest   The feature request, for which this index is queried.
-     *
-     * @return   True, if this index holds the information to answer the request.
-     *
-     */
     virtual bool getCacheIterator( QgsFeatureIterator& featureIterator, const QgsFeatureRequest& featureRequest );
 };
diff --git a/python/core/qgsvectorfilewriter.sip b/python/core/qgsvectorfilewriter.sip
index 237ac87..e8d453c 100644
--- a/python/core/qgsvectorfilewriter.sip
+++ b/python/core/qgsvectorfilewriter.sip
@@ -132,6 +132,43 @@ class QgsVectorFileWriter
         virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
     };
 
+    /** Edition capability flags
+      * @note Added in QGIS 3.0 */
+    enum EditionCapability
+    {
+        /** Flag to indicate that a new layer can be added to the dataset */
+        CanAddNewLayer,
+
+        /** Flag to indicate that new features can be added to an existing layer */
+        CanAppendToExistingLayer ,
+
+        /** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
+        CanAddNewFieldsToExistingLayer,
+
+        /** Flag to indicate that an existing layer can be deleted */
+        CanDeleteLayer
+    };
+
+    typedef QFlags<QgsVectorFileWriter::EditionCapability> EditionCapabilities;
+
+    /** Enumeration to describe how to handle existing files
+        @note Added in QGIS 3.0
+     */
+    enum ActionOnExistingFile
+    {
+        /** Create or overwrite file */
+        CreateOrOverwriteFile,
+
+        /** Create or overwrite layer */
+        CreateOrOverwriteLayer,
+
+        /** Append features to existing layer, but do not create new fields */
+        AppendToLayerNoNewFields,
+
+        /** Append features to existing layer, and create new fields if needed */
+        AppendToLayerAddFields
+    };
+
     /** Write contents of vector layer to an (OGR supported) vector formt
      * @param layer layer to write
      * @param fileName file name to write to
@@ -219,6 +256,88 @@ class QgsVectorFileWriter
                                             FieldValueConverter* fieldValueConverter = nullptr
                                           );
 
+
+    /**
+     * Options to pass to writeAsVectorFormat()
+     * @note Added in QGIS 3.0
+     */
+    class SaveVectorOptions
+    {
+      public:
+        /** Constructor */
+        SaveVectorOptions();
+
+        /** Destructor */
+        virtual ~SaveVectorOptions();
+
+        /** OGR driver to use */
+        QString driverName;
+
+        /** Layer name. If let empty, it will be derived from the filename */
+        QString layerName;
+
+        /** Action on existing file  */
+        QgsVectorFileWriter::ActionOnExistingFile actionOnExistingFile;
+
+        /** Encoding to use */
+        QString fileEncoding;
+
+        /** Transform to reproject exported geometries with, or invalid transform
+         * for no transformation */
+        const QgsCoordinateTransform* ct;
+
+        /** Write only selected features of layer */
+        bool onlySelectedFeatures;
+
+        /** List of OGR data source creation options */
+        QStringList datasourceOptions;
+
+        /** List of OGR layer creation options */
+        QStringList layerOptions;
+
+        /** Only write geometries */
+        bool skipAttributeCreation;
+
+        /** Attributes to export (empty means all unless skipAttributeCreation is set) */
+        QgsAttributeList attributes;
+
+        /** Symbology to export */
+        QgsVectorFileWriter::SymbologyExport symbologyExport;
+
+        /** Scale of symbology */
+        double symbologyScale;
+
+        /** If not empty, only features intersecting the extent will be saved */
+        QgsRectangle filterExtent;
+
+        /** Set to a valid geometry type to override the default geometry type for the layer. This parameter
+         * allows for conversion of geometryless tables to null geometries, etc */
+        QgsWKBTypes::Type overrideGeometryType;
+
+        /** Set to true to force creation of multi* geometries */
+        bool forceMulti;
+
+        /** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
+        bool includeZ;
+
+        /** Field value converter */
+        QgsVectorFileWriter::FieldValueConverter* fieldValueConverter;
+    };
+
+    /** Writes a layer out to a vector file.
+     * @param layer source layer to write
+     * @param fileName file name to write to
+     * @param options options.
+     * @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
+     * @param errorMessage pointer to buffer fo error message
+     * @note added in 3.0
+     */
+    static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
+                                            const QString& fileName,
+                                            const SaveVectorOptions& options,
+                                            QString *newFilename = nullptr,
+                                            QString *errorMessage = nullptr );
+
     /** Create a new vector file writer */
     QgsVectorFileWriter( const QString& vectorFileName,
                          const QString& fileEncoding,
@@ -292,6 +411,28 @@ class QgsVectorFileWriter
 
     static bool driverMetadata( const QString& driverName, MetaData& driverMetadata );
 
+    /**
+     * Return edition capabilites for an existing dataset name.
+     * @note added in QGIS 3.0
+     */
+    static EditionCapabilities editionCapabilities( const QString& datasetName );
+
+    /**
+     * Returns whether the target layer already exists.
+     * @note added in QGIS 3.0
+     */
+    static bool targetLayerExists( const QString& datasetName,
+                                   const QString& layerName );
+
+    /**
+     * Returns whether there are among the attributes specified some that do not exist yet in the layer
+     * @note added in QGIS 3.0
+     */
+    static bool areThereNewFieldsToCreate( const QString& datasetName,
+                                           const QString& layerName,
+                                           QgsVectorLayer* layer,
+                                           const QgsAttributeList& attributes );
+
   protected:
     //! @note not available in python bindings
     // OGRGeometryH createEmptyGeometry( QGis::WkbType wkbType );
@@ -300,3 +441,5 @@ class QgsVectorFileWriter
 
     QgsVectorFileWriter( const QgsVectorFileWriter& rh );
 };
+
+QFlags<QgsVectorFileWriter::EditionCapability> operator|(QgsVectorFileWriter::EditionCapability f1, QFlags<QgsVectorFileWriter::EditionCapability> f2);
diff --git a/python/core/qgsvectorlayercache.sip b/python/core/qgsvectorlayercache.sip
index 9b4d464..f446e20 100644
--- a/python/core/qgsvectorlayercache.sip
+++ b/python/core/qgsvectorlayercache.sip
@@ -65,9 +65,18 @@ class QgsVectorLayerCache : QObject
      * be used for slow data sources, be aware, that the call to this method might take a long time.
      *
      * @param fullCache   True: enable full caching, False: disable full caching
+     * @see hasFullCache()
      */
     void setFullCache( bool fullCache );
 
+    /** Returns true if the cache is complete, ie it contains all features. This may happen as
+     * a result of a call to setFullCache() or by through a feature request which resulted in
+     * all available features being cached.
+     * @see setFullCache()
+     * @note added in QGIS 3.0
+     */
+    bool hasFullCache() const;
+
     /**
      * @brief
      * Adds a {@link QgsAbstractCacheIndex} to this cache. Cache indices know about features present
@@ -93,8 +102,15 @@ class QgsVectorLayerCache : QObject
      * Check if a certain feature id is cached.
      * @param  fid The feature id to look for
      * @return True if this id is in the cache
+     * @see cachedFeatureIds()
+     */
+    bool isFidCached( const QgsFeatureId fid ) const;
+
+    /** Returns the set of feature IDs for features which are cached.
+     * @note added in QGIS 3.0
+     * @see isFidCached()
      */
-    bool isFidCached( const QgsFeatureId fid );
+    QgsFeatureIds cachedFeatureIds() const;
 
     /**
      * Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features
diff --git a/python/core/symbology-ng/qgssymbollayerv2.sip b/python/core/symbology-ng/qgssymbollayerv2.sip
index f374881..02fd0d6 100644
--- a/python/core/symbology-ng/qgssymbollayerv2.sip
+++ b/python/core/symbology-ng/qgssymbollayerv2.sip
@@ -1,6 +1,7 @@
 class QgsSymbolLayerV2
 {
 %TypeHeaderCode
+#include <qgssymbolv2.h>
 #include <qgssymbollayerv2.h>
 #include <qgslinesymbollayerv2.h>
 %End
@@ -9,57 +10,52 @@ class QgsSymbolLayerV2
   switch (sipCpp->type())
   {
     case QgsSymbolV2::Marker:
-      if (dynamic_cast<QgsEllipseSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsEllipseSymbolLayerV2;
-      else if (dynamic_cast<QgsFontMarkerSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsFontMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsSimpleMarkerSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsSimpleMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsFilledMarkerSymbolLayer*>(sipCpp) != NULL)
-    sipType = sipType_QgsFilledMarkerSymbolLayer;
-      else if (dynamic_cast<QgsSvgMarkerSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsSvgMarkerSymbolLayerV2;
-      else if (dynamic_cast<QgsVectorFieldSymbolLayer*>(sipCpp) != NULL)
-    sipType = sipType_QgsVectorFieldSymbolLayer;
+      if ( sipCpp->layerType() == "EllipseMarker" )
+        sipType = sipType_QgsEllipseSymbolLayerV2;
+      else if ( sipCpp->layerType() == "FontMarker" )
+        sipType = sipType_QgsFontMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "SimpleMarker" )
+        sipType = sipType_QgsSimpleMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "FilledMarker" )
+        sipType = sipType_QgsFilledMarkerSymbolLayer;
+      else if ( sipCpp->layerType() == "SvgMarker" )
+        sipType = sipType_QgsSvgMarkerSymbolLayerV2;
+      else if ( sipCpp->layerType() == "VectorField" )
+        sipType = sipType_QgsVectorFieldSymbolLayer;
       else
     sipType = sipType_QgsMarkerSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Line:
-      if (dynamic_cast<QgsMarkerLineSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsMarkerLineSymbolLayerV2;
-      else if (dynamic_cast<QgsSimpleLineSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsSimpleLineSymbolLayerV2;
-      else if (dynamic_cast<QgsArrowSymbolLayer*>(sipCpp) != NULL)
-    sipType = sipType_QgsArrowSymbolLayer;
+      if ( sipCpp->layerType() == "MarkerLine" )
+        sipType = sipType_QgsMarkerLineSymbolLayerV2;
+      else if ( sipCpp->layerType() == "SimpleLine" )
+        sipType = sipType_QgsSimpleLineSymbolLayerV2;
+      else if ( sipCpp->layerType() == "ArrowLine" )
+        sipType = sipType_QgsArrowSymbolLayer;
       else
     sipType = sipType_QgsLineSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Fill:
-      if (dynamic_cast<QgsSimpleFillSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsSimpleFillSymbolLayerV2;
-      else if (dynamic_cast<QgsImageFillSymbolLayer*>(sipCpp) != NULL)
-      {
-    if (dynamic_cast<QgsLinePatternFillSymbolLayer*>(sipCpp) != NULL)
-      sipType = sipType_QgsLinePatternFillSymbolLayer;
-    else if (dynamic_cast<QgsPointPatternFillSymbolLayer*>(sipCpp) != NULL)
-      sipType = sipType_QgsPointPatternFillSymbolLayer;
-    else if (dynamic_cast<QgsSVGFillSymbolLayer*>(sipCpp) != NULL)
-      sipType = sipType_QgsSVGFillSymbolLayer;
-    else if (dynamic_cast<QgsRasterFillSymbolLayer*>(sipCpp) != NULL)
-      sipType = sipType_QgsRasterFillSymbolLayer;
-    else
-      sipType = sipType_QgsImageFillSymbolLayer;
-      }
-      else if (dynamic_cast<QgsCentroidFillSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsCentroidFillSymbolLayerV2;
-      else if (dynamic_cast<QgsGradientFillSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsGradientFillSymbolLayerV2;
-      else if (dynamic_cast<QgsShapeburstFillSymbolLayerV2*>(sipCpp) != NULL)
-    sipType = sipType_QgsShapeburstFillSymbolLayerV2;
+      if ( sipCpp->layerType() == "SimpleFill" )
+        sipType = sipType_QgsSimpleFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "LinePatternFill" )
+        sipType = sipType_QgsLinePatternFillSymbolLayer;
+      else if ( sipCpp->layerType() == "PointPatternFill" )
+        sipType = sipType_QgsPointPatternFillSymbolLayer;
+      else if ( sipCpp->layerType() == "SVGFill" )
+        sipType = sipType_QgsSVGFillSymbolLayer;
+      else if ( sipCpp->layerType() == "RasterFill" )
+        sipType = sipType_QgsRasterFillSymbolLayer;
+      else if ( sipCpp->layerType() == "CentroidFill" )
+        sipType = sipType_QgsCentroidFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "GradientFill" )
+        sipType = sipType_QgsGradientFillSymbolLayerV2;
+      else if ( sipCpp->layerType() == "ShapeburstFill" )
+        sipType = sipType_QgsShapeburstFillSymbolLayerV2;
       else
-    sipType = sipType_QgsFillSymbolLayerV2;
+        sipType = sipType_QgsFillSymbolLayerV2;
       break;
 
     case QgsSymbolV2::Hybrid:
diff --git a/python/gui/gui.sip b/python/gui/gui.sip
index eba2b43..240c636 100644
--- a/python/gui/gui.sip
+++ b/python/gui/gui.sip
@@ -81,6 +81,7 @@
 %Include qgsfieldvalidator.sip
 %Include qgsfiledropedit.sip
 %Include qgsfilewidget.sip
+%Include qgsfiledownloader.sip
 %Include qgsfilterlineedit.sip
 %Include qgsfocuswatcher.sip
 %Include qgsformannotationitem.sip
diff --git a/python/gui/qgsextentgroupbox.sip b/python/gui/qgsextentgroupbox.sip
index 1a5c658..c2b72d0 100644
--- a/python/gui/qgsextentgroupbox.sip
+++ b/python/gui/qgsextentgroupbox.sip
@@ -58,7 +58,7 @@ class QgsExtentGroupBox : QgsCollapsibleGroupBox
     //! set output extent to be the same as current extent (may be transformed to output CRS)
     void setOutputExtentFromCurrent();
 
-    //! set output extent to custom extent (may be transformed to outut CRS)
+    //! set output extent to custom extent (may be transformed to output CRS)
     void setOutputExtentFromUser( const QgsRectangle& extent, const QgsCoordinateReferenceSystem& crs );
 
   signals:
diff --git a/python/gui/qgsfiledownloader.sip b/python/gui/qgsfiledownloader.sip
new file mode 100644
index 0000000..63a110d
--- /dev/null
+++ b/python/gui/qgsfiledownloader.sip
@@ -0,0 +1,66 @@
+/***************************************************************************
+  qgsfiledownloader.sip
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : elpaso at itopen dot it
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+/** \ingroup gui
+ * QgsFileDownloader is a utility class for downloading files.
+ *
+ * To use this class, it is necessary to pass the URL and an output file name as
+ * arguments to the constructor, the download will start immediately.
+ * The download is asynchronous and depending on the guiNotificationsEnabled
+ * parameter accepted by the constructor (default = true) the class will
+ * show a progress dialog and report all errors in a QMessageBox::warning dialog.
+ * If the guiNotificationsEnabled parameter is set to false, the class can still
+ * be used through the signals and slots mechanism.
+ * The object will destroy itself when the request completes, errors or is canceled.
+ *
+ * @note added in QGIS 2.18.1
+ */
+class QgsFileDownloader : public QObject
+{
+  %TypeHeaderCode
+  #include <qgsfiledownloader.h>
+  %End
+  public:
+    /**
+     * QgsFileDownloader
+     * @param url the download url
+     * @param outputFileName file name where the downloaded content will be stored
+     * @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
+     */
+    QgsFileDownloader(QUrl url, QString outputFileName, bool guiNotificationsEnabled = true);
+
+    signals:
+      /** Emitted when the download has completed successfully  */
+      void downloadCompleted();
+      /** Emitted always when the downloader exits */
+      void downloadExited();
+      /** Emitted when the download was canceled by the user */
+      void downloadCanceled();
+      /** Emitted when an error makes the download fail */
+       void downloadError( QStringList errorMessages );
+      /** Emitted when data ready to be processed */
+      void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+
+    public slots:
+      /**
+       * Called when a download is canceled by the user
+       * this slot aborts the download and deletes the object
+       */
+      void onDownloadCanceled();
+
+    private:
+      ~QgsFileDownloader();
+
+};
diff --git a/python/plugins/CMakeLists.txt b/python/plugins/CMakeLists.txt
index e52ac13..ff0c40a 100644
--- a/python/plugins/CMakeLists.txt
+++ b/python/plugins/CMakeLists.txt
@@ -68,6 +68,17 @@ MACRO (PLUGIN_INSTALL plugin subdir )
   ENDFOREACH(file)
 ENDMACRO (PLUGIN_INSTALL)
 
+ADD_CUSTOM_TARGET(staged-plugins-copy-init-py ALL DEPENDS staged-plugins)
+
+# Dummy file to stage to output/python/plugins for testing purposes
+ADD_CUSTOM_COMMAND(TARGET staged-plugins-copy-init-py
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E make_directory "${PYTHON_OUTPUT_DIRECTORY}/plugins"
+    COMMAND ${CMAKE_COMMAND} -E copy __init__.py "${PYTHON_OUTPUT_DIRECTORY}/plugins"
+    WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
+    DEPENDS __init__.py
+)
+
 ADD_SUBDIRECTORY(GdalTools)
 ADD_SUBDIRECTORY(db_manager)
 ADD_SUBDIRECTORY(processing)
diff --git a/python/plugins/processing/algs/qgis/MergeLines_LOCAL_1606.py b/python/plugins/__init__.py
similarity index 100%
rename from python/plugins/processing/algs/qgis/MergeLines_LOCAL_1606.py
rename to python/plugins/__init__.py
diff --git a/python/plugins/db_manager/db_model.py b/python/plugins/db_manager/db_model.py
index c842630..71bb8c0 100644
--- a/python/plugins/db_manager/db_model.py
+++ b/python/plugins/db_manager/db_model.py
@@ -254,9 +254,9 @@ class TableItem(TreeItem):
             if geom_type is not None:
                 if geom_type.find('POINT') != -1:
                     return self.layerPointIcon
-                elif geom_type.find('LINESTRING') != -1:
+                elif geom_type.find('LINESTRING') != -1 or geom_type in ('CIRCULARSTRING', 'COMPOUNDCURVE', 'MULTICURVE'):
                     return self.layerLineIcon
-                elif geom_type.find('POLYGON') != -1:
+                elif geom_type.find('POLYGON') != -1 or geom_type == 'MULTISURFACE':
                     return self.layerPolygonIcon
                 return self.layerUnknownIcon
 
@@ -298,6 +298,7 @@ class DBModel(QAbstractItemModel):
             self.importVector.connect(self.vectorImport)
 
         self.hasSpatialiteSupport = "spatialite" in supportedDbTypes()
+        self.hasGPKGSupport = "gpkg" in supportedDbTypes()
 
         self.rootItem = TreeItem(None, None)
         for dbtype in supportedDbTypes():
@@ -399,7 +400,7 @@ class DBModel(QAbstractItemModel):
                     flags |= Qt.ItemIsDropEnabled
 
             # SL/Geopackage db files can be dropped everywhere in the tree
-            if self.hasSpatialiteSupport:
+            if self.hasSpatialiteSupport or self.hasGPKGSupport:
                 flags |= Qt.ItemIsDropEnabled
 
         return flags
diff --git a/python/plugins/db_manager/db_plugins/CMakeLists.txt b/python/plugins/db_manager/db_plugins/CMakeLists.txt
index c3e16d1..c94a1d3 100644
--- a/python/plugins/db_manager/db_plugins/CMakeLists.txt
+++ b/python/plugins/db_manager/db_plugins/CMakeLists.txt
@@ -1,5 +1,6 @@
 ADD_SUBDIRECTORY(postgis)
 ADD_SUBDIRECTORY(spatialite)
+ADD_SUBDIRECTORY(gpkg)
 IF(WITH_ORACLE)
   ADD_SUBDIRECTORY(oracle)
 ENDIF(WITH_ORACLE)
diff --git a/python/plugins/db_manager/db_plugins/__init__.py b/python/plugins/db_manager/db_plugins/__init__.py
index 16a8a04..ec56d2a 100644
--- a/python/plugins/db_manager/db_plugins/__init__.py
+++ b/python/plugins/db_manager/db_plugins/__init__.py
@@ -24,7 +24,8 @@ email                : brush.tyler at gmail.com
 class NotSupportedDbType(Exception):
 
     def __init__(self, dbtype):
-        self.msg = self.tr("%s is not supported yet") % dbtype
+        from qgis.PyQt.QtWidgets import QApplication
+        self.msg = QApplication.translate("DBManagerPlugin", "%s is not supported yet" % dbtype)
         Exception(self, self.msg)
 
     def __str__(self):
diff --git a/python/plugins/db_manager/db_plugins/connector.py b/python/plugins/db_manager/db_plugins/connector.py
index 6ed3686..5821021 100644
--- a/python/plugins/db_manager/db_plugins/connector.py
+++ b/python/plugins/db_manager/db_plugins/connector.py
@@ -47,6 +47,12 @@ class DBConnector:
     def hasSpatialSupport(self):
         return False
 
+    def canAddGeometryColumn(self, table):
+        return self.hasSpatialSupport()
+
+    def canAddSpatialIndex(self, table):
+        return self.hasSpatialSupport()
+
     def hasRasterSupport(self):
         return False
 
diff --git a/python/plugins/db_manager/db_plugins/gpkg/CMakeLists.txt b/python/plugins/db_manager/db_plugins/gpkg/CMakeLists.txt
new file mode 100644
index 0000000..afc6b2d
--- /dev/null
+++ b/python/plugins/db_manager/db_plugins/gpkg/CMakeLists.txt
@@ -0,0 +1,9 @@
+
+FILE(GLOB PY_FILES *.py)
+FILE(GLOB ICON_FILES icons/*.png)
+
+PYQT_ADD_RESOURCES(PYRC_FILES resources.qrc)
+
+PLUGIN_INSTALL(db_manager db_plugins/gpkg ${PY_FILES} ${PYRC_FILES})
+PLUGIN_INSTALL(db_manager db_plugins/gpkg/icons ${ICON_FILES})
+
diff --git a/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_LOCAL_3790.gml b/python/plugins/db_manager/db_plugins/gpkg/__init__.py
similarity index 100%
rename from python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_LOCAL_3790.gml
rename to python/plugins/db_manager/db_plugins/gpkg/__init__.py
diff --git a/python/plugins/db_manager/db_plugins/gpkg/connector.py b/python/plugins/db_manager/db_plugins/gpkg/connector.py
new file mode 100644
index 0000000..9291958
--- /dev/null
+++ b/python/plugins/db_manager/db_plugins/gpkg/connector.py
@@ -0,0 +1,868 @@
+# -*- coding: utf-8 -*-
+
+"""
+/***************************************************************************
+Name                 : DB Manager
+Description          : Database manager plugin for QGIS
+Date                 : Oct 14 2016
+copyright            : (C) 2016 by Even Rouault
+                       (C) 2011 by Giuseppe Sucameli
+email                : even.rouault at spatialys.com
+
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+"""
+from builtins import str
+
+from functools import cmp_to_key
+
+from qgis.PyQt.QtWidgets import QApplication
+
+from ..connector import DBConnector
+from ..plugin import ConnectionError, DbError, Table
+
+from pyspatialite import dbapi2 as sqlite
+
+from osgeo import gdal, ogr, osr
+
+
+def classFactory():
+    return GPKGDBConnector
+
+
+class GPKGDBConnector(DBConnector):
+
+    def __init__(self, uri):
+        DBConnector.__init__(self, uri)
+
+        self.dbname = uri.database()
+        self.has_raster = False
+        self.mapSridToName = {}
+        self._opendb()
+
+    def _opendb(self):
+
+        self.gdal_ds = None
+        if hasattr(gdal, 'OpenEx'):
+            # GDAL >= 2
+            self.gdal_ds = gdal.OpenEx(self.dbname.encode('UTF-8'), gdal.OF_UPDATE)
+            if self.gdal_ds is None:
+                self.gdal_ds = gdal.OpenEx(self.dbname.encode('UTF-8'))
+            if self.gdal_ds is None or self.gdal_ds.GetDriver().ShortName != 'GPKG':
+                raise ConnectionError(QApplication.translate("DBManagerPlugin", '"{0}" not found').format(self.dbname))
+            self.has_raster = self.gdal_ds.RasterCount != 0 or self.gdal_ds.GetMetadata('SUBDATASETS') is not None
+            self.connection = None
+            self.gdal2 = True
+        else:
+            # GDAL 1.X compat. To be removed at some point
+            self.gdal_ds = ogr.Open(self.dbname.encode('UTF-8'), update=1)
+            if self.gdal_ds is None:
+                self.gdal_ds = ogr.Open(self.dbname.encode('UTF-8'))
+            if self.gdal_ds is None or self.gdal_ds.GetDriver().GetName() != 'GPKG':
+                raise ConnectionError(QApplication.translate("DBManagerPlugin", '"{0}" not found').format(self.dbname))
+            # For GDAL 1.X, we cannot issue direct SQL SELECT to the OGR datasource
+            # so we need a direct sqlite connection
+            try:
+                self.connection = sqlite.connect(str(self.dbname))
+            except self.connection_error_types() as e:
+                raise ConnectionError(e)
+            self.gdal2 = False
+
+    def unquoteId(self, quotedId):
+        if len(quotedId) <= 2 or quotedId[0] != '"' or quotedId[len(quotedId) - 1] != '"':
+            return quotedId
+        unquoted = ''
+        i = 1
+        while i < len(quotedId) - 1:
+            if quotedId[i] == '"' and quotedId[i + 1] == '"':
+                unquoted += '"'
+                i += 2
+            else:
+                unquoted += quotedId[i]
+                i += 1
+        return unquoted
+
+    def _fetchOne(self, sql):
+        if not self.gdal2:
+            # GDAL 1.X compat. To be removed at some point
+            c = self._get_cursor()
+            self._execute(c, sql)
+            res = c.fetchone()
+            if res is not None:
+                return res
+            else:
+                return None
+        else:
+            sql_lyr = self.gdal_ds.ExecuteSQL(sql.encode('UTF-8'))
+            if sql_lyr is None:
+                return None
+            f = sql_lyr.GetNextFeature()
+            if f is None:
+                ret = None
+            else:
+                ret = [f.GetField(i) for i in range(f.GetFieldCount())]
+            self.gdal_ds.ReleaseResultSet(sql_lyr)
+            return ret
+
+    def _fetchAll(self, sql, include_fid_and_geometry=False):
+        if not self.gdal2:
+            # GDAL 1.X compat. To be removed at some point
+            c = self._get_cursor()
+            self._execute(c, sql)
+            return c.fetchall()
+        else:
+            sql_lyr = self.gdal_ds.ExecuteSQL(sql.encode('UTF-8'))
+            if sql_lyr is None:
+                return None
+            ret = []
+            while True:
+                f = sql_lyr.GetNextFeature()
+                if f is None:
+                    break
+                else:
+                    if include_fid_and_geometry:
+                        field_vals = [f.GetFID()]
+                        if sql_lyr.GetLayerDefn().GetGeomType() != ogr.wkbNone:
+                            geom = f.GetGeometryRef()
+                            if geom is not None:
+                                geom = geom.ExportToWkt()
+                            field_vals += [geom]
+                        field_vals += [f.GetField(i) for i in range(f.GetFieldCount())]
+                        ret.append(field_vals)
+                    else:
+                        ret.append([f.GetField(i) for i in range(f.GetFieldCount())])
+            self.gdal_ds.ReleaseResultSet(sql_lyr)
+            return ret
+
+    def _fetchAllFromLayer(self, table):
+
+        lyr = self.gdal_ds.GetLayerByName(table.name.encode('UTF-8'))
+        if lyr is None:
+            return []
+
+        lyr.ResetReading()
+        ret = []
+        while True:
+            f = lyr.GetNextFeature()
+            if f is None:
+                break
+            else:
+                field_vals = [f.GetFID()]
+                if lyr.GetLayerDefn().GetGeomType() != ogr.wkbNone:
+                    geom = f.GetGeometryRef()
+                    if geom is not None:
+                        geom = geom.ExportToWkt()
+                    field_vals += [geom]
+                field_vals += [f.GetField(i) for i in range(f.GetFieldCount())]
+                ret.append(field_vals)
+        return ret
+
+    def _execute_and_commit(self, sql):
+        if not self.gdal2:
+            DBConnector._execute_and_commit(self, sql)
+        else:
+            sql_lyr = self.gdal_ds.ExecuteSQL(sql.encode('UTF-8'))
+            self.gdal_ds.ReleaseResultSet(sql_lyr)
+
+    def _execute(self, cursor, sql):
+
+        if self.gdal2 and self.connection is None:
+            # Needed when evaluating a SQL query
+            try:
+                self.connection = sqlite.connect(str(self.dbname))
+            except self.connection_error_types() as e:
+                raise ConnectionError(e)
+
+        return DBConnector._execute(self, cursor, sql)
+
+    def _commit(self):
+        if self.gdal2:
+            return
+
+        try:
+            self.connection.commit()
+
+        except self.connection_error_types() as e:
+            raise ConnectionError(e)
+
+        except self.execution_error_types() as e:
+            # do the rollback to avoid a "current transaction aborted, commands ignored" errors
+            self._rollback()
+            raise DbError(e)
+
+    @classmethod
+    def isValidDatabase(self, path):
+        if hasattr(gdal, 'OpenEx'):
+            ds = gdal.OpenEx(self.dbname)
+            if ds is None or ds.GetDriver().ShortName != 'GPKG':
+                return False
+        else:
+            ds = ogr.Open(path)
+            if ds is None or ds.GetDriver().GetName() != 'GPKG':
+                return False
+        return True
+
+    def getInfo(self):
+        return None
+
+    def getSpatialInfo(self):
+        return None
+
+    def hasSpatialSupport(self):
+        return True
+
+    # Used by DlgTableProperties
+    def canAddGeometryColumn(self, table):
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        return lyr.GetGeomType() == ogr.wkbNone
+
+    # Used by DlgTableProperties
+    def canAddSpatialIndex(self, table):
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None or lyr.GetGeometryColumn() == '':
+            return False
+        return not self.hasSpatialIndex(table,
+                                        lyr.GetGeometryColumn())
+
+    def hasRasterSupport(self):
+        return self.has_raster
+
+    def hasCustomQuerySupport(self):
+        return True
+
+    def hasTableColumnEditingSupport(self):
+        return True
+
+    def hasCreateSpatialViewSupport(self):
+        return False
+
+    def fieldTypes(self):
+        # From "Table 1. GeoPackage Data Types" (http://www.geopackage.org/spec/)
+        return [
+            "TEXT",
+            "MEDIUMINT",
+            "INTEGER",
+            "TINYINT",
+            "SMALLINT",
+            "DOUBLE",
+            "FLOAT"
+            "DATE",
+            "DATETIME",
+            "BOOLEAN",
+        ]
+
+    def getSchemas(self):
+        return None
+
+    def getTables(self, schema=None, add_sys_tables=False):
+        """ get list of tables """
+        items = []
+
+        try:
+            vectors = self.getVectorTables(schema)
+            for tbl in vectors:
+                items.append(tbl)
+        except DbError:
+            pass
+
+        try:
+            rasters = self.getRasterTables(schema)
+            for tbl in rasters:
+                items.append(tbl)
+        except DbError:
+            pass
+
+        for i, tbl in enumerate(items):
+            tbl.insert(3, False) # not system table
+
+        return sorted(items, key=cmp_to_key(lambda x, y: (x[1] > y[1]) - (x[1] < y[1])))
+
+    def getVectorTables(self, schema=None):
+
+        items = []
+        for i in range(self.gdal_ds.GetLayerCount()):
+            lyr = self.gdal_ds.GetLayer(i)
+            geomtype = lyr.GetGeomType()
+            if hasattr(ogr, 'GT_Flatten'):
+                geomtype_flatten = ogr.GT_Flatten(geomtype)
+            else:
+                geomtype_flatten = geomtype
+            if geomtype_flatten == ogr.wkbPoint:
+                geomname = 'POINT'
+            elif geomtype_flatten == ogr.wkbLineString:
+                geomname = 'LINESTRING'
+            elif geomtype_flatten == ogr.wkbPolygon:
+                geomname = 'POLYGON'
+            elif geomtype_flatten == ogr.wkbMultiPoint:
+                geomname = 'MULTIPOINT'
+            elif geomtype_flatten == ogr.wkbMultiLineString:
+                geomname = 'MULTILINESTRING'
+            elif geomtype_flatten == ogr.wkbMultiPolygon:
+                geomname = 'MULTIPOLYGON'
+            elif geomtype_flatten == ogr.wkbGeometryCollection:
+                geomname = 'GEOMETRYCOLLECTION'
+            if self.gdal2:
+                if geomtype_flatten == ogr.wkbCircularString:
+                    geomname = 'CIRCULARSTRING'
+                elif geomtype_flatten == ogr.wkbCompoundCurve:
+                    geomname = 'COMPOUNDCURVE'
+                elif geomtype_flatten == ogr.wkbCurvePolygon:
+                    geomname = 'CURVEPOLYGON'
+                elif geomtype_flatten == ogr.wkbMultiCurve:
+                    geomname = 'MULTICURVE'
+                elif geomtype_flatten == ogr.wkbMultiSurface:
+                    geomname = 'MULTISURFACE'
+            geomdim = 'XY'
+            if hasattr(ogr, 'GT_HasZ') and ogr.GT_HasZ(lyr.GetGeomType()):
+                geomdim += 'Z'
+            if hasattr(ogr, 'GT_HasM') and ogr.GT_HasM(lyr.GetGeomType()):
+                geomdim += 'M'
+            srs = lyr.GetSpatialRef()
+            srid = None
+            if srs is not None:
+                if srs.IsProjected():
+                    name = srs.GetAttrValue('PROJCS', 0)
+                elif srs.IsGeographic():
+                    name = srs.GetAttrValue('GEOGCS', 0)
+                else:
+                    name = None
+                srid = srs.GetAuthorityCode(None)
+                if srid is not None:
+                    srid = int(srid)
+                else:
+                    srid = self._fetchOne('SELECT srid FROM gpkg_spatial_ref_sys WHERE table_name = %s' % self.quoteString(lyr.GetName()))
+                    if srid is not None:
+                        srid = int(srid)
+                self.mapSridToName[srid] = name
+
+            if geomtype == ogr.wkbNone:
+                item = list([Table.TableType,
+                             lyr.GetName().decode('UTF-8'),
+                             False, # is_view
+                             ])
+            else:
+                item = list([Table.VectorType,
+                             lyr.GetName().decode('UTF-8'),
+                             False, # is_view
+                             lyr.GetName().decode('UTF-8'),
+                             lyr.GetGeometryColumn().decode('UTF-8'),
+                             geomname,
+                             geomdim,
+                             srid])
+            items.append(item)
+        return items
+
+    def getRasterTables(self, schema=None):
+        """ get list of table with a geometry column
+                it returns:
+                        name (table name)
+                        type = 'view' (is a view?)
+                        geometry_column:
+                                r.table_name (the prefix table name, use this to load the layer)
+                                r.geometry_column
+                                srid
+        """
+
+        sql = u"""SELECT table_name, 0 AS is_view, table_name AS r_table_name, '' AS r_geometry_column, srs_id FROM gpkg_contents WHERE data_type = 'tiles'"""
+        ret = self._fetchAll(sql)
+        if ret is None:
+            return []
+        items = []
+        for i, tbl in enumerate(ret):
+            item = list(tbl)
+            item.insert(0, Table.RasterType)
+            items.append(item)
+        return items
+
+    def getTableRowCount(self, table):
+        lyr = self.gdal_ds.GetLayerByName(self.getSchemaTableName(table)[1].encode('UTF-8'))
+        return lyr.GetFeatureCount() if lyr is not None else None
+
+    def getTableFields(self, table):
+        """ return list of columns in table """
+        sql = u"PRAGMA table_info(%s)" % (self.quoteId(table))
+        ret = self._fetchAll(sql)
+        if ret is None:
+            ret = []
+        return ret
+
+    def getTableIndexes(self, table):
+        """ get info about table's indexes """
+        sql = u"PRAGMA index_list(%s)" % (self.quoteId(table))
+        indexes = self._fetchAll(sql)
+        if indexes is None:
+            return []
+
+        for i, idx in enumerate(indexes):
+            # sqlite has changed the number of columns returned by index_list since 3.8.9
+            # I am not using self.getInfo() here because this behaviour
+            # can be changed back without notice as done for index_info, see:
+            # http://repo.or.cz/sqlite.git/commit/53555d6da78e52a430b1884b5971fef33e9ccca4
+            if len(idx) == 3:
+                num, name, unique = idx
+            if len(idx) == 5:
+                num, name, unique, createdby, partial = idx
+            sql = u"PRAGMA index_info(%s)" % (self.quoteId(name))
+
+            idx = [num, name, unique]
+            cols = []
+            for seq, cid, cname in self._fetchAll(sql):
+                cols.append(cid)
+            idx.append(cols)
+            indexes[i] = idx
+
+        return indexes
+
+    def getTableConstraints(self, table):
+        return None
+
+    def getTableTriggers(self, table):
+
+        _, tablename = self.getSchemaTableName(table)
+        # Do not list rtree related triggers as we don't want them to be dropped
+        sql = u"SELECT name, sql FROM sqlite_master WHERE tbl_name = %s AND type = 'trigger'" % (self.quoteString(tablename))
+        if self.isVectorTable(table):
+            sql += u" AND name NOT LIKE 'rtree_%%'"
+        elif self.isRasterTable(table):
+            sql += u" AND name NOT LIKE '%%_zoom_insert'"
+            sql += u" AND name NOT LIKE '%%_zoom_update'"
+            sql += u" AND name NOT LIKE '%%_tile_column_insert'"
+            sql += u" AND name NOT LIKE '%%_tile_column_update'"
+            sql += u" AND name NOT LIKE '%%_tile_row_insert'"
+            sql += u" AND name NOT LIKE '%%_tile_row_update'"
+        return self._fetchAll(sql)
+
+    def deleteTableTrigger(self, trigger, table=None):
+        """ delete trigger """
+        sql = u"DROP TRIGGER %s" % self.quoteId(trigger)
+        self._execute_and_commit(sql)
+
+    def getTableExtent(self, table, geom, force=False):
+        """ find out table extent """
+        _, tablename = self.getSchemaTableName(table)
+
+        if self.isRasterTable(table):
+
+            md = self.gdal_ds.GetMetadata('SUBDATASETS')
+            if md is None or len(md) == 0:
+                ds = self.gdal_ds
+            else:
+                subdataset_name = 'GPKG:%s:%s' % (self.gdal_ds.GetDescription(), tablename)
+                ds = gdal.Open(subdataset_name)
+            if ds is None:
+                return None
+            gt = ds.GetGeoTransform()
+            minx = gt[0]
+            maxx = gt[0] + gt[1] * ds.RasterYSize
+            maxy = gt[3]
+            miny = gt[3] + gt[5] * ds.RasterYSize
+
+            return (minx, miny, maxx, maxy)
+
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return None
+        ret = lyr.GetExtent(force=force, can_return_null=True)
+        if ret is None:
+            return None
+        minx, maxx, miny, maxy = ret
+        return (minx, miny, maxx, maxy)
+
+    def getViewDefinition(self, view):
+        """ returns definition of the view """
+        return None
+
+    def getSpatialRefInfo(self, srid):
+        if srid in self.mapSridToName:
+            return self.mapSridToName[srid]
+
+        sql = u"SELECT srs_name FROM gpkg_spatial_ref_sys WHERE srs_id = %s" % self.quoteString(srid)
+        res = self._fetchOne(sql)
+        if res is not None:
+            res = res[0]
+        self.mapSridToName[srid] = res
+        return res
+
+    def isVectorTable(self, table):
+
+        _, tablename = self.getSchemaTableName(table)
+        return self.gdal_ds.GetLayerByName(tablename.encode('UTF-8')) is not None
+
+    def isRasterTable(self, table):
+        if self.has_raster and not self.isVectorTable(table):
+            _, tablename = self.getSchemaTableName(table)
+            md = self.gdal_ds.GetMetadata('SUBDATASETS')
+            if md is None or len(md) == 0:
+                sql = u"SELECT COUNT(*) FROM gpkg_contents WHERE data_type = 'tiles' AND table_name = %s" % self.quoteString(tablename)
+                ret = self._fetchOne(sql)
+                return ret is not None and ret[0] == 1
+            else:
+                subdataset_name = 'GPKG:%s:%s' % (self.gdal_ds.GetDescription(), tablename)
+                for key in md:
+                    if md[key] == subdataset_name:
+                        return True
+
+        return False
+
+    def getOGRFieldTypeFromSQL(self, sql_type):
+        ogr_type = ogr.OFTString
+        ogr_subtype = ogr.OFSTNone
+        width = 0
+        if not sql_type.startswith('TEXT ('):
+            pos = sql_type.find(' (')
+            if pos >= 0:
+                sql_type = sql_type[0:pos]
+        if sql_type == 'BOOLEAN':
+            ogr_type = ogr.OFTInteger
+            ogr_subtype = ogr.OFSTBoolean
+        elif sql_type in ('TINYINT', 'SMALLINT', 'MEDIUMINT'):
+            ogr_type = ogr.OFTInteger
+        elif sql_type == 'INTEGER':
+            ogr_type = ogr.OFTInteger64
+        elif sql_type == 'FLOAT':
+            ogr_type = ogr.OFTReal
+            ogr_subtype = ogr.OFSTFloat32
+        elif sql_type == 'DOUBLE':
+            ogr_type = ogr.OFTReal
+        elif sql_type == 'DATE':
+            ogr_type = ogr.OFTDate
+        elif sql_type == 'DATETIME':
+            ogr_type = ogr.OFTDateTime
+        elif sql_type.startswith('TEXT (') and sql_type.endswith(')'):
+            width = int(sql_type[len('TEXT ('):-1])
+        return (ogr_type, ogr_subtype, width)
+
+    def createOGRFieldDefnFromSQL(self, sql_fielddef):
+        f_split = sql_fielddef.split(' ')
+        quoted_name = f_split[0]
+        name = self.unquoteId(quoted_name)
+        sql_type = f_split[1].upper()
+        if len(f_split) >= 3 and f_split[2].startswith('(') and f_split[2].endswith(')'):
+            sql_type += ' ' + f_split[2]
+            f_split = [f for f in f_split[3:]]
+        else:
+            f_split = [f for f in f_split[2:]]
+        ogr_type, ogr_subtype, width = self.getOGRFieldTypeFromSQL(sql_type)
+        fld_defn = ogr.FieldDefn(name.encode('UTF-8'), ogr_type)
+        fld_defn.SetSubType(ogr_subtype)
+        fld_defn.SetWidth(width)
+        if len(f_split) >= 2 and f_split[0] == 'NOT' and f_split[1] == 'NULL':
+            fld_defn.SetNullable(False)
+            f_split = [f for f in f_split[2:]]
+        elif len(f_split) >= 1:
+            f_split = [f for f in f_split[1:]]
+        if len(f_split) >= 2 and f_split[0] == 'DEFAULT':
+            new_default = f_split[1]
+            if new_default == '':
+                fld_defn.SetDefault(None)
+            elif new_default == 'NULL' or ogr_type in (ogr.OFTInteger, ogr.OFTReal):
+                fld_defn.SetDefault(new_default.encode('UTF-8'))
+            elif new_default.startswith("'") and new_default.endswith("'"):
+                fld_defn.SetDefault(new_default.encode('UTF-8'))
+            else:
+                fld_defn.SetDefault(self.quoteString(new_default).encode('UTF-8'))
+        return fld_defn
+
+    def createTable(self, table, field_defs, pkey):
+        """ create ordinary table
+                        'fields' is array containing field definitions
+                        'pkey' is the primary key name
+        """
+        if len(field_defs) == 0:
+            return False
+
+        options = []
+        if pkey is not None and pkey != "":
+            options += ['FID=' + pkey]
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.CreateLayer(tablename.encode('UTF-8'), geom_type=ogr.wkbNone, options=options)
+        if lyr is None:
+            return False
+        for field_def in field_defs:
+            fld_defn = self.createOGRFieldDefnFromSQL(field_def)
+            if fld_defn.GetName().decode('UTF-8') == pkey:
+                continue
+            if lyr.CreateField(fld_defn) != 0:
+                return False
+
+        return True
+
+    def deleteTable(self, table):
+        """ delete table from the database """
+        if self.isRasterTable(table):
+            return False
+
+        _, tablename = self.getSchemaTableName(table)
+        for i in range(self.gdal_ds.GetLayerCount()):
+            if self.gdal_ds.GetLayer(i).GetName().decode('UTF-8') == tablename:
+                return self.gdal_ds.DeleteLayer(i) == 0
+        return False
+
+    def emptyTable(self, table):
+        """ delete all rows from table """
+        if self.isRasterTable(table):
+            return False
+
+        sql = u"DELETE FROM %s" % self.quoteId(table)
+        self._execute_and_commit(sql)
+
+    def renameTable(self, table, new_table):
+        """ rename a table """
+
+        if self.isRasterTable(table):
+            return False
+
+        _, tablename = self.getSchemaTableName(table)
+        if new_table == tablename:
+            return True
+
+        if tablename.find('"') >= 0:
+            tablename = self.quotedId(tablename)
+        if new_table.find('"') >= 0:
+            new_table = self.quotedId(new_table)
+
+        gdal.ErrorReset()
+        self.gdal_ds.ExecuteSQL(('ALTER TABLE %s RENAME TO %s' % (tablename, new_table)).encode('UTF-8'))
+        if gdal.GetLastErrorMsg() != '':
+            return False
+        # we need to reopen after renaming since OGR doesn't update its
+        # internal state
+        self._opendb()
+        return True
+
+    def moveTable(self, table, new_table, new_schema=None):
+        return self.renameTable(table, new_table)
+
+    def runVacuum(self):
+        """ run vacuum on the db """
+        self._execute_and_commit("VACUUM")
+
+    def addTableColumn(self, table, field_def):
+        """ add a column to table """
+
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        fld_defn = self.createOGRFieldDefnFromSQL(field_def)
+        return lyr.CreateField(fld_defn) == 0
+
+    def deleteTableColumn(self, table, column):
+        """ delete column from a table """
+        if self.isGeometryColumn(table, column):
+            return False
+
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        idx = lyr.GetLayerDefn().GetFieldIndex(column.encode('UTF-8'))
+        if idx >= 0:
+            return lyr.DeleteField(idx) == 0
+        return False
+
+    def updateTableColumn(self, table, column, new_name, new_data_type=None, new_not_null=None, new_default=None):
+        if self.isGeometryColumn(table, column):
+            return False
+
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        if lyr.TestCapability(ogr.OLCAlterFieldDefn) == 0:
+            return False
+        idx = lyr.GetLayerDefn().GetFieldIndex(column.encode('UTF-8'))
+        if idx >= 0:
+            old_fielddefn = lyr.GetLayerDefn().GetFieldDefn(idx)
+            flag = 0
+            if new_name is not None:
+                flag |= ogr.ALTER_NAME_FLAG
+            else:
+                new_name = column
+            if new_data_type is None:
+                ogr_type = old_fielddefn.GetType()
+                ogr_subtype = old_fielddefn.GetSubType()
+                width = old_fielddefn.GetWidth()
+            else:
+                flag |= ogr.ALTER_TYPE_FLAG
+                flag |= ogr.ALTER_WIDTH_PRECISION_FLAG
+                ogr_type, ogr_subtype, width = self.getOGRFieldTypeFromSQL(new_data_type)
+            new_fielddefn = ogr.FieldDefn(new_name.encode('UTF-8'), ogr_type)
+            new_fielddefn.SetSubType(ogr_subtype)
+            new_fielddefn.SetWidth(width)
+            if new_default is not None:
+                flag |= ogr.ALTER_DEFAULT_FLAG
+                if new_default == '':
+                    new_fielddefn.SetDefault(None)
+                elif new_default == 'NULL' or ogr_type in (ogr.OFTInteger, ogr.OFTReal):
+                    new_fielddefn.SetDefault(new_default.encode('UTF-8'))
+                elif new_default.startswith("'") and new_default.endswith("'"):
+                    new_fielddefn.SetDefault(new_default.encode('UTF-8'))
+                else:
+                    new_fielddefn.SetDefault(self.quoteString(new_default).encode('UTF-8'))
+            else:
+                new_fielddefn.SetDefault(old_fielddefn.GetDefault())
+            if new_not_null is not None:
+                flag |= ogr.ALTER_NULLABLE_FLAG
+                new_fielddefn.SetNullable(not new_not_null)
+            else:
+                new_fielddefn.SetNullable(old_fielddefn.IsNullable())
+            return lyr.AlterFieldDefn(idx, new_fielddefn, flag) == 0
+
+        return False
+
+    def isGeometryColumn(self, table, column):
+
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        return column == lyr.GetGeometryColumn()
+
+    def addGeometryColumn(self, table, geom_column='geometry', geom_type='POINT', srid=-1, dim=2):
+
+        _, tablename = self.getSchemaTableName(table)
+        lyr = self.gdal_ds.GetLayerByName(tablename.encode('UTF-8'))
+        if lyr is None:
+            return False
+        ogr_type = ogr.wkbUnknown
+        if geom_type == 'POINT':
+            ogr_type = ogr.wkbPoint
+        elif geom_type == 'LINESTRING':
+            ogr_type = ogr.wkbLineString
+        elif geom_type == 'POLYGON':
+            ogr_type = ogr.wkbPolygon
+        elif geom_type == 'MULTIPOINT':
+            ogr_type = ogr.wkbMultiPoint
+        elif geom_type == 'MULTILINESTRING':
+            ogr_type = ogr.wkbMultiLineString
+        elif geom_type == 'MULTIPOLYGON':
+            ogr_type = ogr.wkbMultiPolygon
+        elif geom_type == 'GEOMETRYCOLLECTION':
+            ogr_type = ogr.wkbGeometryCollection
+
+        if dim == 3:
+            ogr_type = ogr_type | ogr.wkb25DBit
+        elif dim == 4:
+            if hasattr(ogr, 'GT_HasZ'):
+                ogr_type = ogr.GT_SetZ(ogr_type)
+            else:
+                ogr_type = ogr_type | ogr.wkb25DBit
+            if hasattr(ogr, 'GT_HasM'):
+                ogr_type = ogr.GT_SetM(ogr_type)
+
+        geom_field_defn = ogr.GeomFieldDefn(self.unquoteId(geom_column).encode('UTF-8'), ogr_type)
+        if srid > 0:
+            sr = osr.SpatialReference()
+            if sr.ImportFromEPSG(srid) == 0:
+                geom_field_defn.SetSpatialRef(sr)
+
+        if lyr.CreateGeomField(geom_field_defn) != 0:
+            return False
+        self._opendb()
+        return True
+
+    def deleteGeometryColumn(self, table, geom_column):
+        return False # not supported
+
+    def addTableUniqueConstraint(self, table, column):
+        """ add a unique constraint to a table """
+        return False  # constraints not supported
+
+    def deleteTableConstraint(self, table, constraint):
+        """ delete constraint in a table """
+        return False  # constraints not supported
+
+    def addTablePrimaryKey(self, table, column):
+        """ add a primery key (with one column) to a table """
+        sql = u"ALTER TABLE %s ADD PRIMARY KEY (%s)" % (self.quoteId(table), self.quoteId(column))
+        self._execute_and_commit(sql)
+
+    def createTableIndex(self, table, name, column, unique=False):
+        """ create index on one column using default options """
+        unique_str = u"UNIQUE" if unique else ""
+        sql = u"CREATE %s INDEX %s ON %s (%s)" % (
+            unique_str, self.quoteId(name), self.quoteId(table), self.quoteId(column))
+        self._execute_and_commit(sql)
+
+    def deleteTableIndex(self, table, name):
+        schema, tablename = self.getSchemaTableName(table)
+        sql = u"DROP INDEX %s" % self.quoteId((schema, name))
+        self._execute_and_commit(sql)
+
+    def createSpatialIndex(self, table, geom_column):
+        if self.isRasterTable(table):
+            return False
+        _, tablename = self.getSchemaTableName(table)
+        sql = u"SELECT CreateSpatialIndex(%s, %s)" % (
+            self.quoteId(tablename), self.quoteId(geom_column))
+        res = self._fetchOne(sql)
+        return res is not None and res[0] == 1
+
+    def deleteSpatialIndex(self, table, geom_column):
+        if self.isRasterTable(table):
+            return False
+        _, tablename = self.getSchemaTableName(table)
+        sql = u"SELECT DisableSpatialIndex(%s, %s)" % (
+            self.quoteId(tablename), self.quoteId(geom_column))
+        res = self._fetchOne(sql)
+        return res is not None and res[0] == 1
+
+    def hasSpatialIndex(self, table, geom_column):
+        if self.isRasterTable(table) or geom_column is None:
+            return False
+        _, tablename = self.getSchemaTableName(table)
+        if self.gdal2:
+            # Only try this for GDAL >= 2 (but only available in >= 2.1.2)
+            sql = u"SELECT HasSpatialIndex(%s, %s)" % (self.quoteString(tablename), self.quoteString(geom_column))
+            gdal.PushErrorHandler()
+            ret = self._fetchOne(sql)
+            gdal.PopErrorHandler()
+        else:
+            ret = None
+        if ret is None:
+            # might be the case for GDAL < 2.1.2
+            sql = u"SELECT COUNT(*) FROM sqlite_master WHERE type = 'table' AND name LIKE %s" % self.quoteString("%%rtree_" + tablename + "_%%")
+            ret = self._fetchOne(sql)
+        if ret is None:
+            return False
+        else:
+            return ret[0] >= 1
+
+    def execution_error_types(self):
+        return sqlite.Error, sqlite.ProgrammingError, sqlite.Warning
+
+    def connection_error_types(self):
+        return sqlite.InterfaceError, sqlite.OperationalError
+
+    def getSqlDictionary(self):
+        from .sql_dictionary import getSqlDictionary
+
+        sql_dict = getSqlDictionary()
+
+        items = []
+        for tbl in self.getTables():
+            items.append(tbl[1])  # table name
+
+            for fld in self.getTableFields(tbl[0]):
+                items.append(fld[1])  # field name
+
+        sql_dict["identifier"] = items
+        return sql_dict
+
+    def getQueryBuilderDictionary(self):
+        from .sql_dictionary import getQueryBuilderDictionary
+
+        return getQueryBuilderDictionary()
diff --git a/python/plugins/db_manager/db_plugins/gpkg/data_model.py b/python/plugins/db_manager/db_plugins/gpkg/data_model.py
new file mode 100644
index 0000000..f5f5af0
--- /dev/null
+++ b/python/plugins/db_manager/db_plugins/gpkg/data_model.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+"""
+/***************************************************************************
+Name                 : DB Manager
+Description          : Database manager plugin for QGIS
+Date                 : May 23, 2011
+copyright            : (C) 2011 by Giuseppe Sucameli
+email                : brush.tyler at gmail.com
+
+ ***************************************************************************/
+
+/***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+"""
+
+from ..data_model import TableDataModel, SqlResultModel
+
+
+class GPKGTableDataModel(TableDataModel):
+
+    def __init__(self, table, parent=None):
+        TableDataModel.__init__(self, table, parent)
+
+        #fields_txt = u", ".join(self.fields)
+        #table_txt = self.db.quoteId((self.table.schemaName(), self.table.name))
+
+        # run query and get results
+        #sql = u"SELECT %s FROM %s" % (fields_txt, table_txt)
+        #self.resdata = self.db._fetchAll(sql, include_fid_and_geometry = True)
+
+        self.resdata = self.db._fetchAllFromLayer(table)
+
+        self.fetchedFrom = 0
+        self.fetchedCount = len(self.resdata)
+
+    def _sanitizeTableField(self, field):
+        return self.db.quoteId(field.name)
+
+    def rowCount(self, index=None):
+        return self.fetchedCount
+
+
+class GPKGSqlResultModel(SqlResultModel):
+    pass
diff --git a/python/plugins/db_manager/db_plugins/gpkg/icons/gpkg_icon.png b/python/plugins/db_manager/db_plugins/gpkg/icons/gpkg_icon.png
new file mode 100644
index 0000000..17bf308
Binary files /dev/null and b/python/plugins/db_manager/db_plugins/gpkg/icons/gpkg_icon.png differ
diff --git a/python/plugins/db_manager/db_plugins/spatialite/info_model.py b/python/plugins/db_manager/db_plugins/gpkg/info_model.py
similarity index 59%
copy from python/plugins/db_manager/db_plugins/spatialite/info_model.py
copy to python/plugins/db_manager/db_plugins/gpkg/info_model.py
index 7e0d10d..d262d61 100644
--- a/python/plugins/db_manager/db_plugins/spatialite/info_model.py
+++ b/python/plugins/db_manager/db_plugins/gpkg/info_model.py
@@ -23,10 +23,10 @@ email                : brush.tyler at gmail.com
 from qgis.PyQt.QtWidgets import QApplication
 
 from ..info_model import DatabaseInfo
-from ..html_elems import HtmlTable, HtmlParagraph
+from ..html_elems import HtmlTable
 
 
-class SLDatabaseInfo(DatabaseInfo):
+class GPKGDatabaseInfo(DatabaseInfo):
 
     def __init__(self, db):
         self.db = db
@@ -38,35 +38,10 @@ class SLDatabaseInfo(DatabaseInfo):
         return HtmlTable(tbl)
 
     def generalInfo(self):
-        info = self.db.connector.getInfo()
-        tbl = [
-            (QApplication.translate("DBManagerPlugin", "SQLite version:"), info[0])
-        ]
-        return HtmlTable(tbl)
+        return None
 
     def spatialInfo(self):
-        ret = []
-
-        info = self.db.connector.getSpatialInfo()
-        if info is None:
-            return
-
-        tbl = [
-            (QApplication.translate("DBManagerPlugin", "Library:"), info[0]),
-            ("GEOS:", info[1]),
-            ("Proj:", info[2])
-        ]
-        ret.append(HtmlTable(tbl))
-
-        if self.db.connector.is_gpkg:
-            pass
-
-        elif not self.db.connector.has_geometry_columns:
-            ret.append(HtmlParagraph(
-                QApplication.translate("DBManagerPlugin", "<warning> geometry_columns table doesn't exist!\n"
-                                                          "This table is essential for many GIS applications for enumeration of tables.")))
-
-        return ret
+        return None
 
     def privilegesDetails(self):
         return None
diff --git a/python/plugins/db_manager/db_plugins/spatialite/plugin.py b/python/plugins/db_manager/db_plugins/gpkg/plugin.py
similarity index 70%
copy from python/plugins/db_manager/db_plugins/spatialite/plugin.py
copy to python/plugins/db_manager/db_plugins/gpkg/plugin.py
index 5c946bc..9e3c31a 100644
--- a/python/plugins/db_manager/db_plugins/spatialite/plugin.py
+++ b/python/plugins/db_manager/db_plugins/gpkg/plugin.py
@@ -19,9 +19,10 @@ email                : brush.tyler at gmail.com
  *                                                                         *
  ***************************************************************************/
 """
+from builtins import str
 
 # this will disable the dbplugin if the connector raise an ImportError
-from .connector import SpatiaLiteDBConnector
+from .connector import GPKGDBConnector
 
 from qgis.PyQt.QtCore import Qt, QSettings, QFileInfo
 from qgis.PyQt.QtGui import QIcon
@@ -32,47 +33,48 @@ from qgis.gui import QgsMessageBar
 from ..plugin import DBPlugin, Database, Table, VectorTable, RasterTable, TableField, TableIndex, TableTrigger, \
     InvalidDataException
 
-from . import resources_rc  # NOQA
+from . import resources_rc
+hasattr(resources_rc, 'foo')
 
 
 def classFactory():
-    return SpatiaLiteDBPlugin
+    return GPKGDBPlugin
 
 
-class SpatiaLiteDBPlugin(DBPlugin):
+class GPKGDBPlugin(DBPlugin):
 
     @classmethod
     def icon(self):
-        return QIcon(":/db_manager/spatialite/icon")
+        return QIcon(":/db_manager/gpkg/icon")
 
     @classmethod
     def typeName(self):
-        return 'spatialite'
+        return 'gpkg'
 
     @classmethod
     def typeNameString(self):
-        return 'SpatiaLite/Geopackage'
+        return 'GeoPackage'
 
     @classmethod
     def providerName(self):
-        return 'spatialite'
+        return 'ogr'
 
     @classmethod
     def connectionSettingsKey(self):
-        return '/SpatiaLite/connections'
+        return '/GPKG/connections'
 
     def databasesFactory(self, connection, uri):
-        return SLDatabase(connection, uri)
+        return GPKGDatabase(connection, uri)
 
     def connect(self, parent=None):
         conn_name = self.connectionName()
         settings = QSettings()
         settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
 
-        if not settings.contains("sqlitepath"):  # non-existent entry?
+        if not settings.contains("gpkgpath"):  # non-existent entry?
             raise InvalidDataException(u'there is no defined database connection "%s".' % conn_name)
 
-        database = settings.value("sqlitepath")
+        database = settings.value("gpkgpath")
 
         uri = QgsDataSourceURI()
         uri.setDatabase(database)
@@ -82,14 +84,15 @@ class SpatiaLiteDBPlugin(DBPlugin):
     def addConnection(self, conn_name, uri):
         settings = QSettings()
         settings.beginGroup(u"/%s/%s" % (self.connectionSettingsKey(), conn_name))
-        settings.setValue("sqlitepath", uri.database())
+        settings.setValue("gpkgpath", uri.database())
         return True
 
     @classmethod
     def addConnectionActionSlot(self, item, action, parent, index):
         QApplication.restoreOverrideCursor()
         try:
-            filename = QFileDialog.getOpenFileName(parent, "Choose Sqlite/Spatialite/Geopackage file")
+            filename = QFileDialog.getOpenFileName(parent,
+                                                   parent.tr("Choose GeoPackage file"), None, "GeoPackage (*.gpkg)")
             if not filename:
                 return
         finally:
@@ -102,32 +105,32 @@ class SpatiaLiteDBPlugin(DBPlugin):
         index.internalPointer().itemChanged()
 
 
-class SLDatabase(Database):
+class GPKGDatabase(Database):
 
     def __init__(self, connection, uri):
         Database.__init__(self, connection, uri)
 
     def connectorsFactory(self, uri):
-        return SpatiaLiteDBConnector(uri)
+        return GPKGDBConnector(uri)
 
     def dataTablesFactory(self, row, db, schema=None):
-        return SLTable(row, db, schema)
+        return GPKGTable(row, db, schema)
 
     def vectorTablesFactory(self, row, db, schema=None):
-        return SLVectorTable(row, db, schema)
+        return GPKGVectorTable(row, db, schema)
 
     def rasterTablesFactory(self, row, db, schema=None):
-        return SLRasterTable(row, db, schema)
+        return GPKGRasterTable(row, db, schema)
 
     def info(self):
-        from .info_model import SLDatabaseInfo
+        from .info_model import GPKGDatabaseInfo
 
-        return SLDatabaseInfo(self)
+        return GPKGDatabaseInfo(self)
 
     def sqlResultModel(self, sql, parent):
-        from .data_model import SLSqlResultModel
+        from .data_model import GPKGSqlResultModel
 
-        return SLSqlResultModel(self, sql, parent)
+        return GPKGSqlResultModel(self, sql, parent)
 
     def registerDatabaseActions(self, mainWindow):
         action = QAction(self.tr("Run &Vacuum"), self)
@@ -153,7 +156,7 @@ class SLDatabase(Database):
         self.database().refresh()
 
     def runAction(self, action):
-        action = unicode(action)
+        action = str(action)
 
         if action.startswith("vacuum/"):
             if action == "vacuum/run":
@@ -165,14 +168,15 @@ class SLDatabase(Database):
     def uniqueIdFunction(self):
         return None
 
-    def explicitSpatialIndex(self):
-        return True
+    def toSqlLayer(self, sql, geomCol, uniqueCol, layerName="QueryLayer", layerType=None, avoidSelectById=False, filter=""):
+        from qgis.core import QgsVectorLayer
 
-    def spatialIndexClause(self, src_table, src_column, dest_table, dest_column):
-        return u""" "%s".ROWID IN (\nSELECT ROWID FROM SpatialIndex WHERE f_table_name='%s' AND search_frame="%s"."%s") """ % (src_table, src_table, dest_table, dest_column)
+        vl = QgsVectorLayer(self.uri().database(), layerName, 'ogr')
+        vl.setSubsetString(sql)
+        return vl
 
 
-class SLTable(Table):
+class GPKGTable(Table):
 
     def __init__(self, row, db, schema=None):
         Table.__init__(self, db, None)
@@ -183,48 +187,43 @@ class SLTable(Table):
         return ogrUri
 
     def mimeUri(self):
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage vectors, let's use OGR
-            return u"vector:ogr:%s:%s" % (self.name, self.ogrUri())
-        return Table.mimeUri(self)
+
+        # QGIS has no provider to load Geopackage vectors, let's use OGR
+        return u"vector:ogr:%s:%s" % (self.name, self.ogrUri())
 
     def toMapLayer(self):
         from qgis.core import QgsVectorLayer
 
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage vectors, let's use OGR
-            provider = "ogr"
-            uri = self.ogrUri()
-        else:
-            provider = self.database().dbplugin().providerName()
-            uri = self.uri().uri()
+        provider = "ogr"
+        uri = self.ogrUri()
 
         return QgsVectorLayer(uri, self.name, provider)
 
     def tableFieldsFactory(self, row, table):
-        return SLTableField(row, table)
+        return GPKGTableField(row, table)
 
     def tableIndexesFactory(self, row, table):
-        return SLTableIndex(row, table)
+        return GPKGTableIndex(row, table)
 
     def tableTriggersFactory(self, row, table):
-        return SLTableTrigger(row, table)
+        return GPKGTableTrigger(row, table)
 
     def tableDataModel(self, parent):
-        from .data_model import SLTableDataModel
+        from .data_model import GPKGTableDataModel
 
-        return SLTableDataModel(self, parent)
+        return GPKGTableDataModel(self, parent)
 
 
-class SLVectorTable(SLTable, VectorTable):
+class GPKGVectorTable(GPKGTable, VectorTable):
 
     def __init__(self, row, db, schema=None):
-        SLTable.__init__(self, row[:-5], db, schema)
+        GPKGTable.__init__(self, row[:-5], db, schema)
         VectorTable.__init__(self, db, schema)
-        # SpatiaLite does case-insensitive checks for table names, but the
-        # SL provider didn't do the same in QGis < 1.9, so self.geomTableName
+        # GPKG does case-insensitive checks for table names, but the
+        # GPKG provider didn't do the same in Qgis < 1.9, so self.geomTableName
         # stores the table name like stored in the geometry_columns table
         self.geomTableName, self.geomColumn, self.geomType, self.geomDim, self.srid = row[-5:]
+        self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=False)
 
     def uri(self):
         uri = self.database().uri()
@@ -252,26 +251,29 @@ class SLVectorTable(SLTable, VectorTable):
     def refreshTableEstimatedExtent(self):
         return
 
+    def refreshTableExtent(self):
+        prevExtent = self.extent
+        self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn, force=True)
+        if self.extent != prevExtent:
+            self.refresh()
+
     def runAction(self, action):
-        if SLTable.runAction(self, action):
+        if GPKGTable.runAction(self, action):
             return True
         return VectorTable.runAction(self, action)
 
 
-class SLRasterTable(SLTable, RasterTable):
+class GPKGRasterTable(GPKGTable, RasterTable):
 
     def __init__(self, row, db, schema=None):
-        SLTable.__init__(self, row[:-3], db, schema)
+        GPKGTable.__init__(self, row[:-3], db, schema)
         RasterTable.__init__(self, db, schema)
         self.prefixName, self.geomColumn, self.srid = row[-3:]
         self.geomType = 'RASTER'
+        self.extent = self.database().connector.getTableExtent((self.schemaName(), self.name), self.geomColumn)
 
-        # def info(self):
-        #from .info_model import SLRasterTableInfo
-        #return SLRasterTableInfo(self)
-
-    def rasterliteGdalUri(self):
-        gdalUri = u'RASTERLITE:%s,table=%s' % (self.uri().database(), self.prefixName)
+    def gpkgGdalUri(self):
+        gdalUri = u'GPKG:%s:%s' % (self.uri().database(), self.prefixName)
         return gdalUri
 
     def mimeUri(self):
@@ -282,20 +284,15 @@ class SLRasterTable(SLTable, RasterTable):
     def toMapLayer(self):
         from qgis.core import QgsRasterLayer, QgsContrastEnhancement
 
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage rasters, let's use GDAL
-            uri = self.ogrUri()
-        else:
-            # QGIS has no provider to load Rasterlite rasters, let's use GDAL
-            uri = self.rasterliteGdalUri()
-
+        # QGIS has no provider to load rasters, let's use GDAL
+        uri = self.gpkgGdalUri()
         rl = QgsRasterLayer(uri, self.name)
         if rl.isValid():
             rl.setContrastEnhancement(QgsContrastEnhancement.StretchToMinimumMaximum)
         return rl
 
 
-class SLTableField(TableField):
+class GPKGTableField(TableField):
 
     def __init__(self, row, table):
         TableField.__init__(self, table)
@@ -303,14 +300,14 @@ class SLTableField(TableField):
         self.hasDefault = self.default
 
 
-class SLTableIndex(TableIndex):
+class GPKGTableIndex(TableIndex):
 
     def __init__(self, row, table):
         TableIndex.__init__(self, table)
         self.num, self.name, self.isUnique, self.columns = row
 
 
-class SLTableTrigger(TableTrigger):
+class GPKGTableTrigger(TableTrigger):
 
     def __init__(self, row, table):
         TableTrigger.__init__(self, table)
diff --git a/python/plugins/db_manager/db_plugins/gpkg/resources.qrc b/python/plugins/db_manager/db_plugins/gpkg/resources.qrc
new file mode 100644
index 0000000..1623e16
--- /dev/null
+++ b/python/plugins/db_manager/db_plugins/gpkg/resources.qrc
@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/db_manager/gpkg">
+        <file alias="icon">icons/gpkg_icon.png</file>
+    </qresource>
+</RCC>
diff --git a/python/plugins/db_manager/db_plugins/gpkg/sql_dictionary.py b/python/plugins/db_manager/db_plugins/gpkg/sql_dictionary.py
new file mode 100644
index 0000000..a08980e
--- /dev/null
+++ b/python/plugins/db_manager/db_plugins/gpkg/sql_dictionary.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+    sql_dictionary.py
+    ---------------------
+    Date                 : April 2012
+    Copyright            : (C) 2012 by Giuseppe Sucameli
+    Email                : brush dot tyler at gmail dot com
+***************************************************************************
+*                                                                         *
+*   This program is free software; you can redistribute it and/or modify  *
+*   it under the terms of the GNU General Public License as published by  *
+*   the Free Software Foundation; either version 2 of the License, or     *
+*   (at your option) any later version.                                   *
+*                                                                         *
+***************************************************************************
+"""
+
+
+def getSqlDictionary(spatial=True):
+    from ..spatialite.sql_dictionary import getSqlDictionary
+    return getSqlDictionary(spatial)
+
+
+def getQueryBuilderDictionary():
+    from ..spatialite.sql_dictionary import getQueryBuilderDictionary
+    return getQueryBuilderDictionary()
diff --git a/python/plugins/db_manager/db_plugins/spatialite/connector.py b/python/plugins/db_manager/db_plugins/spatialite/connector.py
index 0bd73be..1d0dcd8 100644
--- a/python/plugins/db_manager/db_plugins/spatialite/connector.py
+++ b/python/plugins/db_manager/db_plugins/spatialite/connector.py
@@ -50,7 +50,6 @@ class SpatiaLiteDBConnector(DBConnector):
 
         self._checkSpatial()
         self._checkRaster()
-        self._checkGeopackage()
 
     def _connectionInfo(self):
         return unicode(self.dbname)
@@ -87,11 +86,6 @@ class SpatiaLiteDBConnector(DBConnector):
         self.has_raster = self._checkRasterTables()
         return self.has_raster
 
-    def _checkGeopackage(self):
-        """ check if it's a geopackage db """
-        self.is_gpkg = self._checkGeopackageTables()
-        return self.is_gpkg
-
     def _checkGeometryColumnsTable(self):
         try:
             c = self._get_cursor()
@@ -113,36 +107,6 @@ class SpatiaLiteDBConnector(DBConnector):
         ret = c.fetchone()
         return ret and ret[0]
 
-    def _checkGeopackageTables(self):
-        try:
-            sql = u"SELECT HasGeoPackage()"
-            result = self._execute(None, sql).fetchone()[0] == 1
-        except ConnectionError:
-            result = False
-        except Exception:
-            # SpatiaLite < 4.2 does not have HasGeoPackage() function
-            result = False
-
-        if result:
-            try:
-                sql = u"SELECT CheckGeoPackageMetaData()"
-                result = self._execute(None, sql).fetchone()[0] == 1
-            except ConnectionError:
-                result = False
-        else:
-            # Spatialite < 4.2 has no GeoPackage support, check for filename and GPKG layout
-            ver = map(int, self.getInfo()[0].split('.')[0:2])
-            if ver[0] < 4 or (ver[0] == 4 and ver[1] < 2):
-                hasGpkgFileExt = self.dbname[-5:] == ".gpkg" or self.dbname[-11:] == ".geopackage"
-
-                sql = u"SELECT count(*) = 3 FROM sqlite_master WHERE name IN ('gpkg_geometry_columns', 'gpkg_spatial_ref_sys', 'gpkg_contents')"
-                ret = self._execute(None, sql).fetchone()
-                hasGpkgLayout = ret and ret[0]
-
-                result = hasGpkgFileExt and hasGpkgLayout
-
-        return result
-
     def getInfo(self):
         c = self._get_cursor()
         self._execute(c, u"SELECT sqlite_version()")
@@ -154,7 +118,7 @@ class SpatiaLiteDBConnector(DBConnector):
                 - geos version
                 - proj version
         """
-        if not self.has_spatial and not self.is_gpkg:
+        if not self.has_spatial:
             return
 
         c = self._get_cursor()
@@ -182,9 +146,6 @@ class SpatiaLiteDBConnector(DBConnector):
     def hasCreateSpatialViewSupport(self):
         return True
 
-    def isGpkg(self):
-        return self.is_gpkg
-
     def fieldTypes(self):
         return [
             "integer", "bigint", "smallint",  # integers
@@ -302,14 +263,6 @@ class SpatiaLiteDBConnector(DBConnector):
                                                 WHERE m.type in ('table', 'view')
                                                 ORDER BY m.name, g.f_geometry_column""" % cols
 
-        elif self.is_gpkg:
-            # get info from gpkg_geometry_columns table
-            dim = " 'XY' || CASE z WHEN 1 THEN 'Z' END || CASE m WHEN 1 THEN 'M' END AS coord_dimension "
-            sql = u"""SELECT m.name, m.type = 'view', g.table_name, g.column_name, g.geometry_type_name AS gtype, %s, g.srs_id
-                                                FROM sqlite_master AS m JOIN gpkg_geometry_columns AS g ON upper(m.name) = upper(g.table_name)
-                                                WHERE m.type in ('table', 'view')
-                                                ORDER BY m.name, g.column_name""" % dim
-
         else:
             return []
 
@@ -335,8 +288,6 @@ class SpatiaLiteDBConnector(DBConnector):
                                 srid
         """
 
-        if self.is_gpkg:
-            return []  # Not implemented
         if not self.has_geometry_columns:
             return []
         if not self.has_raster:
@@ -442,10 +393,7 @@ class SpatiaLiteDBConnector(DBConnector):
         return ret[0] if ret is not None else None
 
     def getSpatialRefInfo(self, srid):
-        if self.is_gpkg:
-            sql = u"SELECT srs_name FROM gpkg_spatial_ref_sys WHERE srs_id = %s" % self.quoteString(srid)
-        else:
-            sql = u"SELECT ref_sys_name FROM spatial_ref_sys WHERE srid = %s" % self.quoteString(srid)
+        sql = u"SELECT ref_sys_name FROM spatial_ref_sys WHERE srid = %s" % self.quoteString(srid)
         c = self._execute(None, sql)
         ret = c.fetchone()
         return ret[0] if ret is not None else None
@@ -498,8 +446,6 @@ class SpatiaLiteDBConnector(DBConnector):
         """ delete table from the database """
         if self.isRasterTable(table):
             return False
-        if self.is_gpkg:
-            return False  # Not implemented
 
         c = self._get_cursor()
         sql = u"DROP TABLE %s" % self.quoteId(table)
@@ -513,8 +459,6 @@ class SpatiaLiteDBConnector(DBConnector):
         """ delete all rows from table """
         if self.isRasterTable(table):
             return False
-        if self.is_gpkg:
-            return False  # Not implemented
 
         sql = u"DELETE FROM %s" % self.quoteId(table)
         self._execute_and_commit(sql)
@@ -527,8 +471,6 @@ class SpatiaLiteDBConnector(DBConnector):
 
         if self.isRasterTable(table):
             return False
-        if self.is_gpkg:
-            return False  # Not implemented
 
         c = self._get_cursor()
 
@@ -568,8 +510,6 @@ class SpatiaLiteDBConnector(DBConnector):
         return self.renameTable(view, new_name)
 
     def createSpatialView(self, view, query):
-        if self.is_gpkg:
-            return False  # Not implemented
 
         self.createView(view, query)
         # get type info about the view
@@ -649,8 +589,6 @@ class SpatiaLiteDBConnector(DBConnector):
         return False  # column editing not supported
 
     def isGeometryColumn(self, table, column):
-        if self.is_gpkg:
-            return False  # Not implemented
 
         c = self._get_cursor()
         schema, tablename = self.getSchemaTableName(table)
@@ -660,8 +598,6 @@ class SpatiaLiteDBConnector(DBConnector):
         return c.fetchone()[0] == 't'
 
     def addGeometryColumn(self, table, geom_column='geometry', geom_type='POINT', srid=-1, dim=2):
-        if self.is_gpkg:
-            return False  # Not implemented
 
         schema, tablename = self.getSchemaTableName(table)
         sql = u"SELECT AddGeometryColumn(%s, %s, %d, %s, %s)" % (
@@ -699,8 +635,6 @@ class SpatiaLiteDBConnector(DBConnector):
     def createSpatialIndex(self, table, geom_column='geometry'):
         if self.isRasterTable(table):
             return False
-        if self.is_gpkg:
-            return False  # Not implemented
 
         schema, tablename = self.getSchemaTableName(table)
         sql = u"SELECT CreateSpatialIndex(%s, %s)" % (self.quoteString(tablename), self.quoteString(geom_column))
@@ -709,8 +643,6 @@ class SpatiaLiteDBConnector(DBConnector):
     def deleteSpatialIndex(self, table, geom_column='geometry'):
         if self.isRasterTable(table):
             return False
-        if self.is_gpkg:
-            return False  # Not implemented
 
         schema, tablename = self.getSchemaTableName(table)
         try:
@@ -724,8 +656,6 @@ class SpatiaLiteDBConnector(DBConnector):
             self.deleteTable(idx_table_name)
 
     def hasSpatialIndex(self, table, geom_column='geometry'):
-        if self.is_gpkg:
-            return False  # Not implemented
         if not self.has_geometry_columns or self.isRasterTable(table):
             return False
         c = self._get_cursor()
diff --git a/python/plugins/db_manager/db_plugins/spatialite/info_model.py b/python/plugins/db_manager/db_plugins/spatialite/info_model.py
index 7e0d10d..81a0457 100644
--- a/python/plugins/db_manager/db_plugins/spatialite/info_model.py
+++ b/python/plugins/db_manager/db_plugins/spatialite/info_model.py
@@ -58,10 +58,7 @@ class SLDatabaseInfo(DatabaseInfo):
         ]
         ret.append(HtmlTable(tbl))
 
-        if self.db.connector.is_gpkg:
-            pass
-
-        elif not self.db.connector.has_geometry_columns:
+        if not self.db.connector.has_geometry_columns:
             ret.append(HtmlParagraph(
                 QApplication.translate("DBManagerPlugin", "<warning> geometry_columns table doesn't exist!\n"
                                                           "This table is essential for many GIS applications for enumeration of tables.")))
diff --git a/python/plugins/db_manager/db_plugins/spatialite/plugin.py b/python/plugins/db_manager/db_plugins/spatialite/plugin.py
index 5c946bc..4b5e25a 100644
--- a/python/plugins/db_manager/db_plugins/spatialite/plugin.py
+++ b/python/plugins/db_manager/db_plugins/spatialite/plugin.py
@@ -51,7 +51,7 @@ class SpatiaLiteDBPlugin(DBPlugin):
 
     @classmethod
     def typeNameString(self):
-        return 'SpatiaLite/Geopackage'
+        return 'SpatiaLite'
 
     @classmethod
     def providerName(self):
@@ -89,7 +89,7 @@ class SpatiaLiteDBPlugin(DBPlugin):
     def addConnectionActionSlot(self, item, action, parent, index):
         QApplication.restoreOverrideCursor()
         try:
-            filename = QFileDialog.getOpenFileName(parent, "Choose Sqlite/Spatialite/Geopackage file")
+            filename = QFileDialog.getOpenFileName(parent, "Choose Sqlite/Spatialite file")
             if not filename:
                 return
         finally:
@@ -183,21 +183,13 @@ class SLTable(Table):
         return ogrUri
 
     def mimeUri(self):
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage vectors, let's use OGR
-            return u"vector:ogr:%s:%s" % (self.name, self.ogrUri())
         return Table.mimeUri(self)
 
     def toMapLayer(self):
         from qgis.core import QgsVectorLayer
 
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage vectors, let's use OGR
-            provider = "ogr"
-            uri = self.ogrUri()
-        else:
-            provider = self.database().dbplugin().providerName()
-            uri = self.uri().uri()
+        provider = self.database().dbplugin().providerName()
+        uri = self.uri().uri()
 
         return QgsVectorLayer(uri, self.name, provider)
 
@@ -282,12 +274,8 @@ class SLRasterTable(SLTable, RasterTable):
     def toMapLayer(self):
         from qgis.core import QgsRasterLayer, QgsContrastEnhancement
 
-        if self.database().connector.isGpkg():
-            # QGIS has no provider to load Geopackage rasters, let's use GDAL
-            uri = self.ogrUri()
-        else:
-            # QGIS has no provider to load Rasterlite rasters, let's use GDAL
-            uri = self.rasterliteGdalUri()
+        # QGIS has no provider to load Rasterlite rasters, let's use GDAL
+        uri = self.rasterliteGdalUri()
 
         rl = QgsRasterLayer(uri, self.name)
         if rl.isValid():
diff --git a/python/plugins/db_manager/db_tree.py b/python/plugins/db_manager/db_tree.py
index 04b1dd8..111e09d 100644
--- a/python/plugins/db_manager/db_tree.py
+++ b/python/plugins/db_manager/db_tree.py
@@ -137,7 +137,7 @@ class DBTree(QTreeView):
                 menu.addAction(self.tr("Re-connect"), self.reconnect)
             menu.addAction(self.tr("Remove"), self.delete)
 
-        elif not index.parent().isValid() and item.typeName() == "spatialite":
+        elif not index.parent().isValid() and item.typeName() in ("spatialite", "gpkg"):
             menu.addAction(self.tr("New Connection..."), self.newConnection)
 
         if not menu.isEmpty():
diff --git a/python/plugins/db_manager/dlg_sql_layer_window.py b/python/plugins/db_manager/dlg_sql_layer_window.py
index 18c87bb..36bc271 100644
--- a/python/plugins/db_manager/dlg_sql_layer_window.py
+++ b/python/plugins/db_manager/dlg_sql_layer_window.py
@@ -68,6 +68,8 @@ class DlgSqlLayerWindow(QWidget, Ui_Dialog):
             dbplugin = createDbPlugin('oracle', 'oracle')
         elif layer.dataProvider().name() == 'virtual':
             dbplugin = createDbPlugin('vlayers', 'virtual')
+        elif layer.dataProvider().name() == 'ogr':
+            dbplugin = createDbPlugin('gpkg', 'gpkg')
         if dbplugin:
             dbplugin.connectToUri(uri)
             db = dbplugin.db
diff --git a/python/plugins/db_manager/dlg_table_properties.py b/python/plugins/db_manager/dlg_table_properties.py
index d88d770..7d7d4c1 100644
--- a/python/plugins/db_manager/dlg_table_properties.py
+++ b/python/plugins/db_manager/dlg_table_properties.py
@@ -68,6 +68,9 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.btnAddSpatialIndex.clicked.connect(self.createSpatialIndex)
         self.btnDeleteIndex.clicked.connect(self.deleteIndex)
 
+        self.refresh()
+
+    def refresh(self):
         self.populateViews()
         self.checkSupports()
 
@@ -76,9 +79,8 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.btnEditColumn.setEnabled(allowEditColumns)
         self.btnDeleteColumn.setEnabled(allowEditColumns)
 
-        allowSpatial = self.db.connector.hasSpatialSupport()
-        self.btnAddGeometryColumn.setEnabled(allowSpatial)
-        self.btnAddSpatialIndex.setEnabled(allowSpatial)
+        self.btnAddGeometryColumn.setEnabled(self.db.connector.canAddGeometryColumn((self.table.schemaName(), self.table.name)))
+        self.btnAddSpatialIndex.setEnabled(self.db.connector.canAddSpatialIndex((self.table.schemaName(), self.table.name)))
 
     def populateViews(self):
         self.populateFields()
@@ -118,7 +120,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         try:
             # add column to table
             self.table.addField(fld)
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
@@ -130,7 +132,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         dlg = DlgAddGeometryColumn(self, self.table)
         if not dlg.exec_():
             return
-        self.populateViews()
+        self.refresh()
 
     def editColumn(self):
         """ open dialog to change column info and alter table appropriately """
@@ -152,7 +154,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.aboutToChangeTable.emit()
         try:
             fld.update(new_fld.name, new_fld.type2String(), new_fld.notNull, new_fld.default2String())
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
@@ -177,7 +179,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.aboutToChangeTable.emit()
         try:
             fld.delete()
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
@@ -210,7 +212,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         dlg = DlgCreateConstraint(self, self.table)
         if not dlg.exec_():
             return
-        self.populateViews()
+        self.refresh()
 
     def deleteConstraint(self):
         """ delete a constraint """
@@ -232,7 +234,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.aboutToChangeTable.emit()
         try:
             constr.delete()
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
@@ -273,7 +275,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         dlg = DlgCreateIndex(self, self.table)
         if not dlg.exec_():
             return
-        self.populateViews()
+        self.refresh()
 
     def createSpatialIndex(self):
         """ create spatial index for the geometry column """
@@ -293,7 +295,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
 
         try:
             self.table.createSpatialIndex()
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
@@ -327,7 +329,7 @@ class DlgTableProperties(QDialog, Ui_Dialog):
         self.aboutToChangeTable.emit()
         try:
             idx.delete()
-            self.populateViews()
+            self.refresh()
         except BaseError as e:
             DlgDbError.showError(e, self)
             return
diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml
index 10a9574..b491311 100644
--- a/python/plugins/processing/algs/help/qgis.yaml
+++ b/python/plugins/processing/algs/help/qgis.yaml
@@ -383,11 +383,11 @@ qgis:selectbyattributesum:
 qgis:selectbyexpression: >
   This algorithm creates a selection in a vector layer. The criteria for selecting features is based on a QGIS expression.
 
-  For more information about expressions see the<a href ="{qgisdocs}/user_manual/working_with_vector/expression.html">user manual</a>
+  For more information about expressions see the <a href ="{qgisdocs}/user_manual/working_with_vector/expression.html">user manual</a>
 
 
 qgis:selectbylocation: >
-  This algorithm creates creates a selection in a vector layer. The criteria for selecting features is based on the spatial relationship between each feature and the features in an additional layer.
+  This algorithm creates a selection in a vector layer. The criteria for selecting features is based on the spatial relationship between each feature and the features in an additional layer.
 
 
 qgis:setstyleforrasterlayer: >
@@ -407,18 +407,18 @@ qgis:snappointstogrid: >
 
 
 qgis:splitlineswithlines: >
-  This algorithm split the lines in a line layer using the lines in another line layer to define the breaking points. Intersection between geometries in both layers are considered as split points.
+  This algorithm splits the lines in a line layer using the lines in another line layer to define the breaking points. Intersection between geometries in both layers are considered as split points.
 
 qgis:splitvectorlayer: >
-  This algorithm takes a vector layer and an attribute and generates a set of vector layers in an outut folder. Each of the layers created in that folder contains all features from the input layer with the same value for the specified attribute.
+  This algorithm takes a vector layer and an attribute and generates a set of vector layers in an output folder. Each of the layers created in that folder contains all features from the input layer with the same value for the specified attribute.
 
-  The number of files generated is equal to the nuber of different values found for the specified attribute.
+  The number of files generated is equal to the number of different values found for the specified attribute.
 
 qgis:statisticsbycategories:
 
 
 qgis:sumlinelengths: >
-  This algorithm takes a polygon layer and a lines layer and measure the total length of lines and the total numer of them that cross each polygon.
+  This algorithm takes a polygon layer and a line layer and measure the total length of lines and the total number of them that cross each polygon.
 
   The resulting layer has the same features as the input polygon layer, but with two additional attributes containing the length and lines count of layers across each polygon. The names of these two fields can be configured in the algorithm parameters.
 
@@ -443,7 +443,7 @@ qgis:vectorgrid:
 qgis:vectorlayerhistogram: >
   This algorithm generates a histogram with the values of the attribute of a vector layer.
 
-  The ttribute to use for computing the histogram must be a numeric attribute.
+  The attribute to use for computing the histogram must be a numeric attribute.
 
 qgis:vectorlayerscatterplot:
 
diff --git a/python/plugins/processing/algs/qgis/Difference.py b/python/plugins/processing/algs/qgis/Difference.py
index 93a3225..c36af09 100644
--- a/python/plugins/processing/algs/qgis/Difference.py
+++ b/python/plugins/processing/algs/qgis/Difference.py
@@ -29,7 +29,7 @@ import os
 
 from qgis.PyQt.QtGui import QIcon
 
-from qgis.core import QGis, QgsFeatureRequest, QgsFeature, QgsGeometry
+from qgis.core import QGis, QgsFeatureRequest, QgsFeature, QgsGeometry, QgsWKBTypes
 from processing.core.ProcessingLog import ProcessingLog
 from processing.core.GeoAlgorithm import GeoAlgorithm
 from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
diff --git a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
index 1547065..655fbc0 100644
--- a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
+++ b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
@@ -103,7 +103,7 @@ class ImportIntoPostGIS(GeoAlgorithm):
         table = self.getParameterValue(self.TABLENAME).strip()
         if table == '':
             table = layer.name()
-            table = "_".join(table.split(".")[:-1])
+            table = table.replace('.', '_')
         table = table.replace(' ', '').lower()[0:62]
         providerName = 'postgres'
 
diff --git a/python/plugins/processing/algs/qgis/MergeLines_BASE_1606.py b/python/plugins/processing/algs/qgis/MergeLines_BASE_1606.py
deleted file mode 100644
index e3bbe3b..0000000
--- a/python/plugins/processing/algs/qgis/MergeLines_BASE_1606.py
+++ /dev/null
@@ -1,90 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
-    Smooth.py
-    ---------
-    Date                 : July 2016
-    Copyright            : (C) 2016 by Nyall Dawson
-    Email                : nyall dot dawson at gmail dot com
-***************************************************************************
-*                                                                         *
-*   This program is free software; you can redistribute it and/or modify  *
-*   it under the terms of the GNU General Public License as published by  *
-*   the Free Software Foundation; either version 2 of the License, or     *
-*   (at your option) any later version.                                   *
-*                                                                         *
-***************************************************************************
-"""
-
-__author__ = 'Nyall Dawson'
-__date__ = 'July 2016'
-__copyright__ = '(C) 2016, Nyall Dawson'
-
-# This will get replaced with a git SHA1 when you do a git archive323
-
-__revision__ = '$Format:%H$'
-
-import os
-
-from qgis.core import QgsFeature
-
-from qgis.PyQt.QtGui import QIcon
-
-from processing.core.GeoAlgorithm import GeoAlgorithm
-from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
-from processing.core.parameters import ParameterVector
-from processing.core.outputs import OutputVector
-from processing.tools import dataobjects, vector
-
-pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
-
-
-class MergeLines(GeoAlgorithm):
-
-    INPUT_LAYER = 'INPUT_LAYER'
-    OUTPUT_LAYER = 'OUTPUT_LAYER'
-
-    def getIcon(self):
-        return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png'))
-
-    def defineCharacteristics(self):
-        self.name, self.i18n_name = self.trAlgorithm('Merge lines')
-        self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
-
-        self.addParameter(ParameterVector(self.INPUT_LAYER,
-                                          self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_LINE]))
-        self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Merged')))
-
-    def processAlgorithm(self, progress):
-        layer = dataobjects.getObjectFromUri(
-            self.getParameterValue(self.INPUT_LAYER))
-        provider = layer.dataProvider()
-
-        writer = self.getOutputFromName(
-            self.OUTPUT_LAYER).getVectorWriter(
-                layer.fields().toList(),
-                provider.wkbType(),
-                layer.crs())
-
-        features = vector.features(layer)
-        total = 100.0 / len(features)
-
-        for current, inFeat in enumerate(features):
-            outFeat = QgsFeature()
-            attrs = inFeat.attributes()
-            outFeat.setAttributes(attrs)
-
-            inGeom = inFeat.geometry()
-            if inGeom:
-                outGeom = inGeom.mergeLines()
-                if outGeom is None:
-                    raise GeoAlgorithmExecutionException(
-                        self.tr('Error merging lines'))
-
-                outFeat.setGeometry(outGeom)
-
-            writer.addFeature(outFeat)
-            progress.setPercentage(int(current * total))
-
-        del writer
diff --git a/python/plugins/processing/algs/qgis/MergeLines_REMOTE_1606.py b/python/plugins/processing/algs/qgis/MergeLines_REMOTE_1606.py
deleted file mode 100644
index c3c831e..0000000
--- a/python/plugins/processing/algs/qgis/MergeLines_REMOTE_1606.py
+++ /dev/null
@@ -1,89 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
-    Smooth.py
-    ---------
-    Date                 : July 2016
-    Copyright            : (C) 2016 by Nyall Dawson
-    Email                : nyall dot dawson at gmail dot com
-***************************************************************************
-*                                                                         *
-*   This program is free software; you can redistribute it and/or modify  *
-*   it under the terms of the GNU General Public License as published by  *
-*   the Free Software Foundation; either version 2 of the License, or     *
-*   (at your option) any later version.                                   *
-*                                                                         *
-***************************************************************************
-"""
-
-__author__ = 'Nyall Dawson'
-__date__ = 'July 2016'
-__copyright__ = '(C) 2016, Nyall Dawson'
-
-# This will get replaced with a git SHA1 when you do a git archive323
-
-__revision__ = '$Format:%H$'
-
-import os
-
-from qgis.core import QgsFeature
-
-from qgis.PyQt.QtGui import QIcon
-
-from processing.core.GeoAlgorithm import GeoAlgorithm
-from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
-from processing.core.parameters import ParameterVector
-from processing.core.outputs import OutputVector
-from processing.tools import dataobjects, vector
-
-pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
-
-
-class MergeLines(GeoAlgorithm):
-
-    INPUT_LAYER = 'INPUT_LAYER'
-    OUTPUT_LAYER = 'OUTPUT_LAYER'
-
-    def getIcon(self):
-        return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'to_lines.png'))
-
-    def defineCharacteristics(self):
-        self.name, self.i18n_name = self.trAlgorithm('Merge lines')
-        self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
-
-        self.addParameter(ParameterVector(self.INPUT_LAYER,
-                                          self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_LINE]))
-        self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Merged')))
-
-    def processAlgorithm(self, progress):
-        layer = dataobjects.getObjectFromUri(
-            self.getParameterValue(self.INPUT_LAYER))
-
-        writer = self.getOutputFromName(
-            self.OUTPUT_LAYER).getVectorWriter(
-                layer.fields().toList(),
-                layer.wkbType(),
-                layer.crs())
-
-        features = vector.features(layer)
-        total = 100.0 / len(features)
-
-        for current, inFeat in enumerate(features):
-            outFeat = QgsFeature()
-            attrs = inFeat.attributes()
-            outFeat.setAttributes(attrs)
-
-            inGeom = inFeat.geometry()
-            if inGeom:
-                outGeom = inGeom.mergeLines()
-                if outGeom is None:
-                    raise GeoAlgorithmExecutionException(
-                        self.tr('Error merging lines'))
-
-                outFeat.setGeometry(outGeom)
-
-            writer.addFeature(outFeat)
-            progress.setPercentage(int(current * total))
-
-        del writer
diff --git a/python/plugins/processing/algs/qgis/SimplifyGeometries.py b/python/plugins/processing/algs/qgis/SimplifyGeometries.py
index 9423c27..c04cc96 100644
--- a/python/plugins/processing/algs/qgis/SimplifyGeometries.py
+++ b/python/plugins/processing/algs/qgis/SimplifyGeometries.py
@@ -75,15 +75,16 @@ class SimplifyGeometries(GeoAlgorithm):
         features = vector.features(layer)
         total = 100.0 / len(features)
         for current, f in enumerate(features):
-            featGeometry = QgsGeometry(f.geometry())
-            attrs = f.attributes()
-            pointsBefore += self.geomVertexCount(featGeometry)
-            newGeometry = featGeometry.simplify(tolerance)
-            pointsAfter += self.geomVertexCount(newGeometry)
-            feature = QgsFeature()
-            feature.setGeometry(newGeometry)
-            feature.setAttributes(attrs)
-            writer.addFeature(feature)
+            featGeometry = f.geometry()
+            if featGeometry is not None:
+                attrs = f.attributes()
+                pointsBefore += self.geomVertexCount(featGeometry)
+                newGeometry = featGeometry.simplify(tolerance)
+                pointsAfter += self.geomVertexCount(newGeometry)
+                feature = QgsFeature()
+                feature.setGeometry(newGeometry)
+                feature.setAttributes(attrs)
+                writer.addFeature(feature)
             progress.setPercentage(int(current * total))
 
         del writer
@@ -114,4 +115,4 @@ class SimplifyGeometries(GeoAlgorithm):
 
             return len(points)
         else:
-            return None
+            return 0
diff --git a/python/plugins/processing/algs/qgis/SymmetricalDifference.py b/python/plugins/processing/algs/qgis/SymmetricalDifference.py
index 2282f84..b17935a 100644
--- a/python/plugins/processing/algs/qgis/SymmetricalDifference.py
+++ b/python/plugins/processing/algs/qgis/SymmetricalDifference.py
@@ -29,7 +29,7 @@ import os
 
 from qgis.PyQt.QtGui import QIcon
 
-from qgis.core import QGis, QgsFeature, QgsGeometry, QgsFeatureRequest, NULL
+from qgis.core import QGis, QgsFeature, QgsGeometry, QgsFeatureRequest, NULL, QgsWKBTypes
 from processing.core.ProcessingLog import ProcessingLog
 from processing.core.GeoAlgorithm import GeoAlgorithm
 from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
index ea34618..bed8a64 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
@@ -90,6 +90,7 @@ class FieldsCalculatorDialog(BASE, WIDGET):
         self.builder.loadRecent('fieldcalc')
 
         self.initContext()
+        self.updateLayer()
 
     def initContext(self):
         exp_context = self.builder.expressionContext()
@@ -171,6 +172,7 @@ class FieldsCalculatorDialog(BASE, WIDGET):
         if self.layer is None:
             return
 
+        self.mExistingFieldComboBox.clear()
         fields = self.layer.pendingFields()
         for f in fields:
             self.mExistingFieldComboBox.addItem(f.name())
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py b/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py
index 2886cd3..0894ac6 100644
--- a/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py
+++ b/python/plugins/processing/algs/qgis/ui/FieldsMapperDialogs.py
@@ -26,6 +26,8 @@ __copyright__ = '(C) 2014, Arnaud Morvan'
 __revision__ = '$Format:%H$'
 
 
+from qgis.core import QgsMapLayerRegistry
+
 from qgis.PyQt.QtWidgets import QComboBox, QSpacerItem
 
 from processing.core.parameters import ParameterVector
@@ -93,12 +95,16 @@ class FieldsMapperParametersPanel(ParametersPanel):
 class FieldsMapperParametersDialog(AlgorithmDialog):
 
     def __init__(self, alg):
-        AlgorithmDialogBase.__init__(self, alg)
+        AlgorithmDialog.__init__(self, alg)
 
-        self.alg = alg
+        QgsMapLayerRegistry.instance().layerWasAdded.disconnect(self.mainWidget.layerAdded)
+        QgsMapLayerRegistry.instance().layersWillBeRemoved.disconnect(self.mainWidget.layersWillBeRemoved)
+        self.tabWidget.widget(0).layout().removeWidget(self.mainWidget)
 
         self.mainWidget = FieldsMapperParametersPanel(self, alg)
         self.setMainWidget()
+        QgsMapLayerRegistry.instance().layerWasAdded.connect(self.mainWidget.layerAdded)
+        QgsMapLayerRegistry.instance().layersWillBeRemoved.connect(self.mainWidget.layersWillBeRemoved)
 
     def setParamValue(self, param, widget, alg=None):
         if isinstance(param, ParameterFieldsMapping):
diff --git a/python/plugins/processing/gui/AlgorithmDialogBase.py b/python/plugins/processing/gui/AlgorithmDialogBase.py
index de07d67..4679192 100644
--- a/python/plugins/processing/gui/AlgorithmDialogBase.py
+++ b/python/plugins/processing/gui/AlgorithmDialogBase.py
@@ -64,9 +64,9 @@ class AlgorithmDialogBase(BASE, WIDGET):
 
         self.setWindowTitle(self.alg.displayName())
 
-        #~ desktop = QDesktopWidget()
-        #~ if desktop.physicalDpiX() > 96:
-        #~ self.txtHelp.setZoomFactor(desktop.physicalDpiX() / 96)
+        # ~ desktop = QDesktopWidget()
+        # ~ if desktop.physicalDpiX() > 96:
+        # ~ self.txtHelp.setZoomFactor(desktop.physicalDpiX() / 96)
 
         algHelp = self.alg.shortHelp()
         if algHelp is None:
@@ -139,6 +139,7 @@ class AlgorithmDialogBase(BASE, WIDGET):
         self.btnClose.setEnabled(True)
 
     def setInfo(self, msg, error=False):
+        msg = msg.replace("<", "<").replace(">", ">")
         if error:
             self.txtLog.append('<span style="color:red"><br>%s<br></span>' % msg)
         else:
diff --git a/python/plugins/processing/gui/BatchPanel.py b/python/plugins/processing/gui/BatchPanel.py
index c120ee8..9effc5d 100644
--- a/python/plugins/processing/gui/BatchPanel.py
+++ b/python/plugins/processing/gui/BatchPanel.py
@@ -347,13 +347,7 @@ class BatchPanel(BASE, WIDGET):
             self.tblParameters.setCellWidget(row, column, item)
 
     def removeRows(self):
-        # ~ self.tblParameters.setUpdatesEnabled(False)
-        # ~ indexes = self.tblParameters.selectionModel().selectedIndexes()
-        # ~ indexes.sort()
-        # ~ for i in reversed(indexes):
-            # ~ self.tblParameters.model().removeRow(i.row())
-        # ~ self.tblParameters.setUpdatesEnabled(True)
-        if self.tblParameters.rowCount() > 2:
+        if self.tblParameters.rowCount() > 1:
             self.tblParameters.setRowCount(self.tblParameters.rowCount() - 1)
 
     def fillParameterValues(self, column):
diff --git a/python/plugins/processing/gui/ConfigDialog.py b/python/plugins/processing/gui/ConfigDialog.py
index 0b252d2..54b8f55 100644
--- a/python/plugins/processing/gui/ConfigDialog.py
+++ b/python/plugins/processing/gui/ConfigDialog.py
@@ -42,6 +42,7 @@ from qgis.PyQt.QtWidgets import (QFileDialog,
                                  QHBoxLayout,
                                  QComboBox)
 from qgis.PyQt.QtGui import (QIcon,
+                             QPushButton,
                              QStandardItemModel,
                              QStandardItem)
 
@@ -55,7 +56,7 @@ from processing.core.ProcessingConfig import (ProcessingConfig,
                                               Setting)
 from processing.core.Processing import Processing
 from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog
-from processing.gui.menus import updateMenus
+from processing.gui.menus import defaultMenuEntries, updateMenus
 from processing.gui.menus import menusSettingsGroup
 
 
@@ -198,6 +199,16 @@ class ConfigDialog(BASE, WIDGET):
 
         rootItem.insertRow(0, [menusItem, emptyItem])
 
+        button = QPushButton(self.tr('Reset to defaults'))
+        button.clicked.connect(self.resetMenusToDefaults)
+        layout = QHBoxLayout()
+        layout.setContentsMargins(0, 0, 0, 0)
+        layout.addWidget(button)
+        layout.addStretch()
+        widget = QWidget()
+        widget.setLayout(layout)
+        self.tree.setIndexWidget(emptyItem.index(), widget)
+
         providers = Processing.providers
         for provider in providers:
             providerDescription = provider.getDescription()
@@ -240,6 +251,15 @@ class ConfigDialog(BASE, WIDGET):
         self.tree.sortByColumn(0, Qt.AscendingOrder)
         self.adjustColumns()
 
+    def resetMenusToDefaults(self):
+        providers = Processing.providers
+        for provider in providers:
+            for alg in provider.algs:
+                d = defaultMenuEntries.get(alg.commandLineName(), "")
+                setting = ProcessingConfig.settings["MENU_" + alg.commandLineName()]
+                item = self.items[setting]
+                item.setData(d, Qt.EditRole)
+
     def accept(self):
         for setting in self.items.keys():
             if isinstance(setting.value, bool):
diff --git a/python/plugins/processing/gui/menus.py b/python/plugins/processing/gui/menus.py
index 881a320..2050e27 100644
--- a/python/plugins/processing/gui/menus.py
+++ b/python/plugins/processing/gui/menus.py
@@ -19,13 +19,13 @@ vectorMenu = QApplication.translate('MainWindow', 'Vect&or')
 analysisToolsMenu = vectorMenu + "/" + Processing.tr('&Analysis Tools')
 defaultMenuEntries.update({'qgis:distancematrix': analysisToolsMenu,
                            'qgis:sumlinelengths': analysisToolsMenu,
-                           'qgis:pointsinpolygon': analysisToolsMenu,
+                           'qgis:countpointsinpolygon': analysisToolsMenu,
                            'qgis:listuniquevalues': analysisToolsMenu,
                            'qgis:basicstatisticsfornumericfields': analysisToolsMenu,
                            'qgis:basicstatisticsfortextfields': analysisToolsMenu,
                            'qgis:nearestneighbouranalysis': analysisToolsMenu,
                            'qgis:meancoordinates': analysisToolsMenu,
-                           'qgis:lineintersecions': analysisToolsMenu})
+                           'qgis:lineintersections': analysisToolsMenu})
 researchToolsMenu = vectorMenu + "/" + Processing.tr('&Research Tools')
 defaultMenuEntries.update({'qgis:randomselection': researchToolsMenu,
                            'qgis:randomselectionwithinsubsets': researchToolsMenu,
diff --git a/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_REMOTE_3790.gml b/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_BACKUP_11112.gml
similarity index 100%
rename from python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_REMOTE_3790.gml
rename to python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_BACKUP_11112.gml
diff --git a/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_BASE_3790.gml b/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_BASE_3790.gml
deleted file mode 100644
index 7423966..0000000
--- a/python/plugins/processing/tests/testdata/expected/clip_lines_by_multipolygon_BASE_3790.gml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8" ?>
-<ogr:FeatureCollection
-     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-     xsi:schemaLocation="http://ogr.maptools.org/ clip_lines_by_multipolygon.xsd"
-     xmlns:ogr="http://ogr.maptools.org/"
-     xmlns:gml="http://www.opengis.net/gml">
-  <gml:boundedBy>
-    <gml:Box>
-      <gml:coord><gml:X>2</gml:X><gml:Y>-1</gml:Y></gml:coord>
-      <gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
-    </gml:Box>
-  </gml:boundedBy>
-                                                                                                                                                               
-  <gml:featureMember>
-    <ogr:clip_lines_by_multipolygon fid="lines.0">
-      <ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,2 8,2</gml:coordinates></gml:LineString></ogr:geometryProperty>
-    </ogr:clip_lines_by_multipolygon>
-  </gml:featureMember>
-  <gml:featureMember>
-    <ogr:clip_lines_by_multipolygon fid="lines.2">
-      <ogr:geometryProperty><gml:MultiLineString srsName="EPSG:4326"><gml:lineStringMember><gml:LineString><gml:coordinates>2,1 2,2</gml:coordinates></gml:LineString></gml:lineStringMember><gml:lineStringMember><gml:LineString><gml:coordinates>2,2 3,2</gml:coordinates></gml:LineString></gml:lineStringMember><gml:lineStringMember><gml:LineString><gml:coordinates>3,2 3,3</gml:coordinates></gml:LineString></gml:lineStringMember></gml:MultiLineString></ogr:geometryProperty>
-    </ogr:clip_lines_by_multipolygon>
-  </gml:featureMember>
-  <gml:featureMember>
-    <ogr:clip_lines_by_multipolygon fid="lines.3">
-      <ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 4,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
-    </ogr:clip_lines_by_multipolygon>
-  </gml:featureMember>
-  <gml:featureMember>
-    <ogr:clip_lines_by_multipolygon fid="lines.5">
-      <ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
-    </ogr:clip_lines_by_multipolygon>
-  </gml:featureMember>
-</ogr:FeatureCollection>
diff --git a/src/app/composer/qgsattributeselectiondialog.cpp b/src/app/composer/qgsattributeselectiondialog.cpp
index 0e4fdc5..7c360de 100644
--- a/src/app/composer/qgsattributeselectiondialog.cpp
+++ b/src/app/composer/qgsattributeselectiondialog.cpp
@@ -107,7 +107,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   }
 
   QScopedPointer< QgsExpressionContext > expContext( object->createExpressionContext() );
-  expContext->lastScope()->setVariable( "row_number", 1 );
+  expContext->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
   expContext->setHighlightedVariables( QStringList() << "row_number" );
   return QgsExpressionContext( *expContext );
 }
diff --git a/src/app/ogr/qgsvectorlayersaveasdialog.cpp b/src/app/ogr/qgsvectorlayersaveasdialog.cpp
index 48a5a75..ecc47cc 100644
--- a/src/app/ogr/qgsvectorlayersaveasdialog.cpp
+++ b/src/app/ogr/qgsvectorlayersaveasdialog.cpp
@@ -24,6 +24,7 @@
 #include "qgseditorwidgetfactory.h"
 #include "qgseditorwidgetregistry.h"
 
+#include <QMessageBox>
 #include <QSettings>
 #include <QFileDialog>
 #include <QTextCodec>
@@ -38,6 +39,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( long srsid, QWidget* par
     , mLayer( 0 )
     , mAttributeTableItemChangedSlotEnabled( true )
     , mReplaceRawFieldValuesStateChangedSlotEnabled( true )
+    , mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
 {
   setup();
 }
@@ -47,6 +49,7 @@ QgsVectorLayerSaveAsDialog::QgsVectorLayerSaveAsDialog( QgsVectorLayer *layer, i
     , mLayer( layer )
     , mAttributeTableItemChangedSlotEnabled( true )
     , mReplaceRawFieldValuesStateChangedSlotEnabled( true )
+    , mActionOnExistingFile( QgsVectorFileWriter::CreateOrOverwriteFile )
 {
   if ( layer )
   {
@@ -211,6 +214,121 @@ QgsVectorLayerSaveAsDialog::~QgsVectorLayerSaveAsDialog()
 
 void QgsVectorLayerSaveAsDialog::accept()
 {
+  if ( QFile::exists( filename() ) )
+  {
+    QgsVectorFileWriter::EditionCapabilities caps =
+      QgsVectorFileWriter::editionCapabilities( filename() );
+    bool layerExists = QgsVectorFileWriter::targetLayerExists( filename(),
+                       layername() );
+    if ( layerExists )
+    {
+      if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) &&
+           ( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
+           ( caps & QgsVectorFileWriter::CanAddNewLayer ) )
+      {
+        QMessageBox msgBox;
+        msgBox.setIcon( QMessageBox::Question );
+        msgBox.setWindowTitle( tr( "The layer already exists" ) );
+        msgBox.setText( tr( "Do you want to overwrite the whole file or overwrite the layer?" ) );
+        QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
+        QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
+        msgBox.setStandardButtons( QMessageBox::Cancel );
+        msgBox.setDefaultButton( QMessageBox::Cancel );
+        int ret = msgBox.exec();
+        if ( ret == QMessageBox::Cancel )
+          return;
+        if ( msgBox.clickedButton() == overwriteFileButton )
+          mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
+        else if ( msgBox.clickedButton() == overwriteLayerButton )
+          mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
+      }
+      else if ( !( caps & QgsVectorFileWriter::CanAppendToExistingLayer ) )
+      {
+        if ( QMessageBox::question( this,
+                                    tr( "The file already exists" ),
+                                    tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
+        {
+          return;
+        }
+        mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
+      }
+      else if (( caps & QgsVectorFileWriter::CanDeleteLayer ) &&
+               ( caps & QgsVectorFileWriter::CanAddNewLayer ) )
+      {
+        QMessageBox msgBox;
+        msgBox.setIcon( QMessageBox::Question );
+        msgBox.setWindowTitle( tr( "The layer already exists" ) );
+        msgBox.setText( tr( "Do you want to overwrite the whole file, overwrite the layer or append features to the layer?" ) );
+        QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
+        QPushButton *overwriteLayerButton = msgBox.addButton( tr( "Overwrite layer" ), QMessageBox::ActionRole );
+        QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
+        msgBox.setStandardButtons( QMessageBox::Cancel );
+        msgBox.setDefaultButton( QMessageBox::Cancel );
+        int ret = msgBox.exec();
+        if ( ret == QMessageBox::Cancel )
+          return;
+        if ( msgBox.clickedButton() == overwriteFileButton )
+          mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
+        else if ( msgBox.clickedButton() == overwriteLayerButton )
+          mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
+        else if ( msgBox.clickedButton() == appendToLayerButton )
+          mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
+      }
+      else
+      {
+        QMessageBox msgBox;
+        msgBox.setIcon( QMessageBox::Question );
+        msgBox.setWindowTitle( tr( "The layer already exists" ) );
+        msgBox.setText( tr( "Do you want to overwrite the whole file or append features to the layer?" ) );
+        QPushButton *overwriteFileButton = msgBox.addButton( tr( "Overwrite file" ), QMessageBox::ActionRole );
+        QPushButton *appendToLayerButton = msgBox.addButton( tr( "Append to layer" ), QMessageBox::ActionRole );
+        msgBox.setStandardButtons( QMessageBox::Cancel );
+        msgBox.setDefaultButton( QMessageBox::Cancel );
+        int ret = msgBox.exec();
+        if ( ret == QMessageBox::Cancel )
+          return;
+        if ( msgBox.clickedButton() == overwriteFileButton )
+          mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
+        else if ( msgBox.clickedButton() == appendToLayerButton )
+          mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerNoNewFields;
+      }
+
+      if ( mActionOnExistingFile == QgsVectorFileWriter::AppendToLayerNoNewFields )
+      {
+        if ( QgsVectorFileWriter::areThereNewFieldsToCreate( filename(),
+             layername(),
+             mLayer,
+             selectedAttributes() ) )
+        {
+          if ( QMessageBox::question( this,
+                                      tr( "The existing layer has different fields" ),
+                                      tr( "Do you want to add the missing fields to the layer?" ) ) == QMessageBox::Yes )
+          {
+            mActionOnExistingFile = QgsVectorFileWriter::AppendToLayerAddFields;
+          }
+        }
+      }
+
+    }
+    else
+    {
+      if (( caps & QgsVectorFileWriter::CanAddNewLayer ) )
+      {
+        mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteLayer;
+      }
+      else
+      {
+        if ( QMessageBox::question( this,
+                                    tr( "The file already exists" ),
+                                    tr( "Do you want to overwrite the existing file?" ) ) == QMessageBox::NoButton )
+        {
+          return;
+        }
+        mActionOnExistingFile = QgsVectorFileWriter::CreateOrOverwriteFile;
+      }
+    }
+  }
+
   QSettings settings;
   settings.setValue( "/UI/lastVectorFileFilterDir", QFileInfo( filename() ).absolutePath() );
   settings.setValue( "/UI/lastVectorFormat", format() );
@@ -227,12 +345,13 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
   bool selectAllFields = true;
   bool fieldsAsDisplayedValues = false;
 
-  if ( format() == "KML" )
+  const QString sFormat( format() );
+  if ( sFormat == "KML" )
   {
     mAttributesSelection->setEnabled( true );
     selectAllFields = false;
   }
-  else if ( format() == "DXF" )
+  else if ( sFormat == "DXF" )
   {
     mAttributesSelection->setEnabled( false );
     selectAllFields = false;
@@ -240,7 +359,23 @@ void QgsVectorLayerSaveAsDialog::on_mFormatComboBox_currentIndexChanged( int idx
   else
   {
     mAttributesSelection->setEnabled( true );
-    fieldsAsDisplayedValues = ( format() == "CSV" || format() == "XLS" || format() == "XLSX" || format() == "ODS" );
+    fieldsAsDisplayedValues = ( sFormat == "CSV" || sFormat == "XLS" || sFormat == "XLSX" || sFormat == "ODS" );
+  }
+
+  leLayername->setEnabled( sFormat == "KML" ||
+                           sFormat == "GPKG" ||
+                           sFormat == "XLSX" ||
+                           sFormat == "ODS" ||
+                           sFormat == "FileGDB" ||
+                           sFormat == "SQLite" ||
+                           sFormat == "SpatiaLite" );
+  if ( !leLayername->isEnabled() )
+    leLayername->setText( QString() );
+  else if ( leLayername->text().isEmpty() &&
+            !leFilename->text().isEmpty() )
+  {
+    QString layerName = QFileInfo( leFilename->text() ).baseName();
+    leLayername->setText( layerName ) ;
   }
 
   if ( mLayer )
@@ -484,7 +619,14 @@ void QgsVectorLayerSaveAsDialog::on_mAttributeTable_itemChanged( QTableWidgetIte
 
 void QgsVectorLayerSaveAsDialog::on_leFilename_textChanged( const QString& text )
 {
-  buttonBox->button( QDialogButtonBox::Ok )->setEnabled( QFileInfo( text ).absoluteDir().exists() );
+  buttonBox->button( QDialogButtonBox::Ok )->setEnabled(
+    !text.isEmpty() && QFileInfo( text ).absoluteDir().exists() );
+
+  if ( leLayername->isEnabled() )
+  {
+    QString layerName = QFileInfo( text ).baseName();
+    leLayername->setText( layerName );
+  }
 }
 
 void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
@@ -492,7 +634,7 @@ void QgsVectorLayerSaveAsDialog::on_browseFilename_clicked()
   QSettings settings;
   QString dirName = leFilename->text().isEmpty() ? settings.value( "/UI/lastVectorFileFilterDir", QDir::homePath() ).toString() : leFilename->text();
   QString filterString = QgsVectorFileWriter::filterForDriver( format() );
-  QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString );
+  QString outputFile = QFileDialog::getSaveFileName( nullptr, tr( "Save layer as..." ), dirName, filterString, nullptr, QFileDialog::DontConfirmOverwrite );
   if ( !outputFile.isNull() )
   {
     leFilename->setText( outputFile );
@@ -510,6 +652,11 @@ QString QgsVectorLayerSaveAsDialog::filename() const
   return leFilename->text();
 }
 
+QString QgsVectorLayerSaveAsDialog::layername() const
+{
+  return leLayername->text();
+}
+
 QString QgsVectorLayerSaveAsDialog::encoding() const
 {
   return mEncodingComboBox->currentText();
@@ -610,7 +757,7 @@ QStringList QgsVectorLayerSaveAsDialog::layerOptions() const
         case QgsVectorFileWriter::String:
         {
           QLineEdit* le = mLayerOptionsGroupBox->findChild<QLineEdit*>( it.key() );
-          if ( le )
+          if ( le && !le->text().isEmpty() )
             options << QString( "%1=%2" ).arg( it.key(), le->text() );
           break;
         }
@@ -734,6 +881,11 @@ bool QgsVectorLayerSaveAsDialog::includeZ() const
   return mIncludeZCheckBox->isChecked();
 }
 
+QgsVectorFileWriter::ActionOnExistingFile QgsVectorLayerSaveAsDialog::creationActionOnExistingFile() const
+{
+  return mActionOnExistingFile;
+}
+
 void QgsVectorLayerSaveAsDialog::setIncludeZ( bool checked )
 {
   mIncludeZCheckBox->setChecked( checked );
diff --git a/src/app/ogr/qgsvectorlayersaveasdialog.h b/src/app/ogr/qgsvectorlayersaveasdialog.h
index 31881c5..71742bb 100644
--- a/src/app/ogr/qgsvectorlayersaveasdialog.h
+++ b/src/app/ogr/qgsvectorlayersaveasdialog.h
@@ -45,6 +45,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
     QString format() const;
     QString encoding() const;
     QString filename() const;
+    QString layername() const;
     QStringList datasourceOptions() const;
     QStringList layerOptions() const;
     long crs() const;
@@ -99,6 +100,9 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
      */
     void setIncludeZ( bool checked );
 
+    /** Returns creation action */
+    QgsVectorFileWriter::ActionOnExistingFile creationActionOnExistingFile() const;
+
   private slots:
 
     void on_mFormatComboBox_currentIndexChanged( int idx );
@@ -125,6 +129,7 @@ class APP_EXPORT QgsVectorLayerSaveAsDialog : public QDialog, private Ui::QgsVec
     QgsVectorLayer *mLayer;
     bool mAttributeTableItemChangedSlotEnabled;
     bool mReplaceRawFieldValuesStateChangedSlotEnabled;
+    QgsVectorFileWriter::ActionOnExistingFile mActionOnExistingFile;
 };
 
 #endif // QGSVECTORLAYERSAVEASDIALOG_H
diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp
index cffbfa9..7eb8940 100644
--- a/src/app/qgisapp.cpp
+++ b/src/app/qgisapp.cpp
@@ -3545,7 +3545,11 @@ bool QgisApp::addVectorLayers( const QStringList &theLayerQStringList, const QSt
     QString base;
     if ( dataSourceType == "file" )
     {
-      QFileInfo fi( src );
+      QString srcWithoutLayername( src );
+      int posPipe = srcWithoutLayername.indexOf( '|' );
+      if ( posPipe >= 0 )
+        srcWithoutLayername.resize( posPipe );
+      QFileInfo fi( srcWithoutLayername );
       base = fi.completeBaseName();
 
       // if needed prompt for zipitem layers
@@ -6252,22 +6256,29 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
     // No need to use the converter if there is nothing to convert
     if ( !dialog->attributesAsDisplayedValues().isEmpty() )
       converterPtr = &converter;
+
+    QgsVectorFileWriter::SaveVectorOptions options;
+    options.driverName = format;
+    options.layerName = dialog->layername();
+    options.actionOnExistingFile = dialog->creationActionOnExistingFile();
+    options.fileEncoding = encoding;
+    options.ct = ct;
+    options.onlySelectedFeatures = dialog->onlySelected();
+    options.datasourceOptions = datasourceOptions;
+    options.layerOptions = dialog->layerOptions();
+    options.skipAttributeCreation = dialog->selectedAttributes().isEmpty();
+    options.symbologyExport = static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() );
+    options.symbologyScale = dialog->scaleDenominator();
+    if ( dialog->hasFilterExtent() )
+      options.filterExtent = filterExtent;
+    options.overrideGeometryType = autoGeometryType ? QgsWKBTypes::Unknown : forcedGeometryType;
+    options.forceMulti = dialog->forceMulti();
+    options.includeZ = dialog->includeZ();
+    options.attributes = dialog->selectedAttributes();
+    options.fieldValueConverter = converterPtr;
+
     error = QgsVectorFileWriter::writeAsVectorFormat(
-              vlayer, vectorFilename, encoding, ct, format,
-              dialog->onlySelected(),
-              &errorMessage,
-              datasourceOptions, dialog->layerOptions(),
-              dialog->attributeSelection() && dialog->selectedAttributes().isEmpty(),
-              &newFilename,
-              static_cast< QgsVectorFileWriter::SymbologyExport >( dialog->symbologyExport() ),
-              dialog->scaleDenominator(),
-              dialog->hasFilterExtent() ? &filterExtent : nullptr,
-              autoGeometryType ? QgsWKBTypes::Unknown : forcedGeometryType,
-              dialog->forceMulti(),
-              dialog->includeZ(),
-              dialog->selectedAttributes(),
-              converterPtr
-            );
+              vlayer, vectorFilename, options, &newFilename, &errorMessage );
 
     delete ct;
 
@@ -6277,7 +6288,10 @@ void QgisApp::saveAsVectorFileGeneral( QgsVectorLayer* vlayer, bool symbologyOpt
     {
       if ( dialog->addToCanvas() )
       {
-        addVectorLayers( QStringList( newFilename ), encoding, "file" );
+        QString uri( newFilename );
+        if ( !dialog->layername().isEmpty() )
+          uri += "|layername=" + dialog->layername();
+        addVectorLayers( QStringList( uri ), encoding, "file" );
       }
       emit layerSavedAs( vlayer, vectorFilename );
       messageBar()->pushMessage( tr( "Saving done" ),
diff --git a/src/app/qgsattributetabledialog.cpp b/src/app/qgsattributetabledialog.cpp
index d2b8f0d..28f683f 100644
--- a/src/app/qgsattributetabledialog.cpp
+++ b/src/app/qgsattributetabledialog.cpp
@@ -58,7 +58,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   if ( layer )
     expContext << QgsExpressionContextUtils::layerScope( layer );
 
-  expContext.lastScope()->setVariable( "row_number", 1 );
+  expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
 
   expContext.setHighlightedVariables( QStringList() << "row_number" );
 
@@ -472,7 +472,7 @@ void QgsAttributeTableDialog::runFieldCalculation( QgsVectorLayer* layer, const
     }
 
     context.setFeature( feature );
-    context.lastScope()->setVariable( QString( "row_number" ), rownum );
+    context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), rownum, true ) );
 
     QVariant value = exp.evaluate( &context );
     fld.convertCompatible( value );
diff --git a/src/app/qgsfeatureaction.cpp b/src/app/qgsfeatureaction.cpp
index ed79ab4..9a35e80 100644
--- a/src/app/qgsfeatureaction.cpp
+++ b/src/app/qgsfeatureaction.cpp
@@ -100,7 +100,9 @@ bool QgsFeatureAction::viewFeatureForm( QgsHighlight *h )
 
   QgsAttributeDialog *dialog = newDialog( true );
   dialog->setHighlight( h );
-  dialog->show(); // will also delete the dialog on close (show() is overridden)
+  // delete the dialog when it is closed
+  dialog->setAttribute( Qt::WA_DeleteOnClose );
+  dialog->show();
 
   return true;
 }
@@ -110,22 +112,27 @@ bool QgsFeatureAction::editFeature( bool showModal )
   if ( !mLayer )
     return false;
 
-  QgsAttributeDialog *dialog = newDialog( false );
-
-  if ( !mFeature->isValid() )
-    dialog->setMode( QgsAttributeForm::AddFeatureMode );
-
   if ( showModal )
   {
-    dialog->setAttribute( Qt::WA_DeleteOnClose );
-    int rv = dialog->exec();
+    QScopedPointer<QgsAttributeDialog> dialog( newDialog( false ) );
+
+    if ( !mFeature->isValid() )
+      dialog->setMode( QgsAttributeForm::AddFeatureMode );
 
+    int rv = dialog->exec();
     mFeature->setAttributes( dialog->feature()->attributes() );
     return rv;
   }
   else
   {
-    dialog->show(); // will also delete the dialog on close (show() is overridden)
+    QgsAttributeDialog* dialog = newDialog( false );
+
+    if ( !mFeature->isValid() )
+      dialog->setMode( QgsAttributeForm::AddFeatureMode );
+
+    // delete the dialog when it is closed
+    dialog->setAttribute( Qt::WA_DeleteOnClose );
+    dialog->show();
   }
 
   return true;
@@ -205,6 +212,8 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap& defaultAttributes, boo
   else
   {
     QgsAttributeDialog *dialog = newDialog( false );
+    // delete the dialog when it is closed
+    dialog->setAttribute( Qt::WA_DeleteOnClose );
     dialog->setMode( QgsAttributeForm::AddFeatureMode );
     dialog->setEditCommandMessage( text() );
 
@@ -213,12 +222,11 @@ bool QgsFeatureAction::addFeature( const QgsAttributeMap& defaultAttributes, boo
     if ( !showModal )
     {
       setParent( dialog ); // keep dialog until the dialog is closed and destructed
-      dialog->show(); // will also delete the dialog on close (show() is overridden)
+      dialog->show();
       mFeature = nullptr;
       return true;
     }
 
-    dialog->setAttribute( Qt::WA_DeleteOnClose );
     dialog->exec();
   }
 
diff --git a/src/app/qgsfieldcalculator.cpp b/src/app/qgsfieldcalculator.cpp
index 7d1e6da..f766dd3 100644
--- a/src/app/qgsfieldcalculator.cpp
+++ b/src/app/qgsfieldcalculator.cpp
@@ -43,7 +43,7 @@ QgsFieldCalculator::QgsFieldCalculator( QgsVectorLayer* vl, QWidget* parent )
   << QgsExpressionContextUtils::projectScope()
   << QgsExpressionContextUtils::layerScope( mVectorLayer );
 
-  expContext.lastScope()->setVariable( "row_number", 1 );
+  expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), 1, true ) );
   expContext.setHighlightedVariables( QStringList() << "row_number" );
 
   builder->setLayer( vl );
@@ -281,7 +281,7 @@ void QgsFieldCalculator::accept()
     while ( fit.nextFeature( feature ) )
     {
       expContext.setFeature( feature );
-      expContext.lastScope()->setVariable( QString( "row_number" ), rownum );
+      expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), rownum, true ) );
 
       QVariant value = exp.evaluate( &expContext );
       if ( exp.hasEvalError() )
diff --git a/src/app/qgsidentifyresultsdialog.cpp b/src/app/qgsidentifyresultsdialog.cpp
index a878524..9d30d97 100644
--- a/src/app/qgsidentifyresultsdialog.cpp
+++ b/src/app/qgsidentifyresultsdialog.cpp
@@ -38,6 +38,7 @@
 #include "qgswebview.h"
 #include "qgswebframe.h"
 #include "qgsstringutils.h"
+#include "qgsfiledownloader.h"
 
 #include <QCloseEvent>
 #include <QLabel>
@@ -55,6 +56,11 @@
 #include <QMessageBox>
 #include <QComboBox>
 #include <QTextDocument>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QFileDialog>
+#include <QFileInfo>
+#include <QRegExp>
 
 //graph
 #include <qwt_plot.h>
@@ -68,6 +74,7 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
   setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Minimum );
   page()->setNetworkAccessManager( QgsNetworkAccessManager::instance() );
   // page()->setLinkDelegationPolicy( QWebPage::DelegateAllLinks );
+  page()->setForwardUnsupportedContent( true );
   page()->setLinkDelegationPolicy( QWebPage::DontDelegateLinks );
   settings()->setAttribute( QWebSettings::LocalContentCanAccessRemoteUrls, true );
   settings()->setAttribute( QWebSettings::JavascriptCanOpenWindows, true );
@@ -75,6 +82,51 @@ QgsIdentifyResultsWebView::QgsIdentifyResultsWebView( QWidget *parent ) : QgsWeb
 #ifdef QGISDEBUG
   settings()->setAttribute( QWebSettings::DeveloperExtrasEnabled, true );
 #endif
+  connect( page(), SIGNAL( downloadRequested( QNetworkRequest ) ), this, SLOT( downloadRequested( QNetworkRequest ) ) );
+  connect( page(), SIGNAL( unsupportedContent( QNetworkReply* ) ), this, SLOT( unsupportedContent( QNetworkReply* ) ) );
+}
+
+
+void QgsIdentifyResultsWebView::downloadRequested( const QNetworkRequest &request )
+{
+  handleDownload( request.url() );
+}
+
+void QgsIdentifyResultsWebView::unsupportedContent( QNetworkReply * reply )
+{
+  handleDownload( reply->url() );
+}
+
+void QgsIdentifyResultsWebView::handleDownload( QUrl url )
+{
+  if ( ! url.isValid() )
+  {
+    QMessageBox::warning( this, tr( "Invalid URL" ), tr( "The download URL is not valid: %1" ).arg( url.toString( ) ) );
+  }
+  else
+  {
+    const QString DOWNLOADER_LAST_DIR_KEY( "Qgis/fileDownloaderLastDir" );
+    QSettings settings;
+    // Try to get some information from the URL
+    QFileInfo info( url.toString( ) );
+    QString savePath = settings.value( DOWNLOADER_LAST_DIR_KEY ).toString( );
+    QString fileName = info.fileName().replace( QRegExp( "[^A-z0-9\\-_\\.]" ), "_" );
+    if ( ! savePath.isEmpty() && ! fileName.isEmpty( ) )
+    {
+      savePath = QDir::cleanPath( savePath + QDir::separator() + fileName );
+    }
+    QString targetFile = QFileDialog::getSaveFileName( this,
+                         tr( "Save as" ),
+                         savePath,
+                         info.suffix( ).isEmpty() ? QString( ) : "*." +  info.suffix( )
+                                                     );
+    if ( ! targetFile.isEmpty() )
+    {
+      settings.setValue( DOWNLOADER_LAST_DIR_KEY, QFileInfo( targetFile ).dir().absolutePath( ) );
+      // Start the download
+      new QgsFileDownloader( url, targetFile );
+    }
+  }
 }
 
 void QgsIdentifyResultsWebView::print()
diff --git a/src/app/qgsidentifyresultsdialog.h b/src/app/qgsidentifyresultsdialog.h
index eb00e0e..4037389 100644
--- a/src/app/qgsidentifyresultsdialog.h
+++ b/src/app/qgsidentifyresultsdialog.h
@@ -31,6 +31,9 @@
 
 #include <QWidget>
 #include <QList>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QUrl>
 
 class QCloseEvent;
 class QTreeWidgetItem;
@@ -57,9 +60,13 @@ class APP_EXPORT QgsIdentifyResultsWebView : public QgsWebView
     QSize sizeHint() const override;
   public slots:
     void print();
+    void downloadRequested( const QNetworkRequest &request );
+    void unsupportedContent( QNetworkReply *reply );
   protected:
     void contextMenuEvent( QContextMenuEvent* ) override;
     QgsWebView *createWindow( QWebPage::WebWindowType type ) override;
+  private:
+    void handleDownload( QUrl url );
 };
 
 class APP_EXPORT QgsIdentifyResultsFeatureItem: public QTreeWidgetItem
diff --git a/src/app/qgsmaptoolfeatureaction.cpp b/src/app/qgsmaptoolfeatureaction.cpp
index e31e826..d50565c 100644
--- a/src/app/qgsmaptoolfeatureaction.cpp
+++ b/src/app/qgsmaptoolfeatureaction.cpp
@@ -149,8 +149,8 @@ bool QgsMapToolFeatureAction::doAction( QgsVectorLayer *layer, int x, int y )
       << QgsExpressionContextUtils::projectScope()
       << QgsExpressionContextUtils::mapSettingsScope( mCanvas->mapSettings() );
       QgsExpressionContextScope* actionScope = new QgsExpressionContextScope();
-      actionScope->setVariable( "click_x", point.x() );
-      actionScope->setVariable( "click_y", point.y() );
+      actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QString( "click_x" ), point.x(), true ) );
+      actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QString( "click_y" ), point.y(), true ) );
       context << actionScope;
 
       int actionIdx = layer->actions()->defaultAction();
diff --git a/src/app/qgsoptions.cpp b/src/app/qgsoptions.cpp
index 9b11c60..031e8f4 100644
--- a/src/app/qgsoptions.cpp
+++ b/src/app/qgsoptions.cpp
@@ -600,8 +600,8 @@ QgsOptions::QgsOptions( QWidget *parent, Qt::WindowFlags fl )
   mSimplifyMaximumScaleComboBox->setScale( 1.0 / mSettings->value( "/qgis/simplifyMaxScale", 1 ).toFloat() );
 
   // Magnifier
-  double magnifierMin = 100 * mSettings->value( "/qgis/magnifier_factor_min", 0.1 ).toDouble();
-  double magnifierMax = 100 * mSettings->value( "/qgis/magnifier_factor_max", 10 ).toDouble();
+  double magnifierMin = 100 * QgisGui::CANVAS_MAGNIFICATION_MIN;
+  double magnifierMax = 100 * QgisGui::CANVAS_MAGNIFICATION_MAX;
   double magnifierVal = 100 * mSettings->value( "/qgis/magnifier_factor_default", 1.0 ).toDouble();
   doubleSpinBoxMagnifierDefault->setRange( magnifierMin, magnifierMax );
   doubleSpinBoxMagnifierDefault->setSingleStep( 50 );
diff --git a/src/app/qgsrulebasedlabelingwidget.cpp b/src/app/qgsrulebasedlabelingwidget.cpp
index aff5147..954dd23 100644
--- a/src/app/qgsrulebasedlabelingwidget.cpp
+++ b/src/app/qgsrulebasedlabelingwidget.cpp
@@ -141,7 +141,6 @@ void QgsRuleBasedLabelingWidget::editRule( const QModelIndex& index )
   QgsRuleBasedLabeling::Rule* rule = mModel->ruleForIndex( index );
 
   QgsLabelingRulePropsWidget* widget = new QgsLabelingRulePropsWidget( rule, mLayer, this, mCanvas );
-  widget->setDockMode( true );
   widget->setPanelTitle( tr( "Edit rule" ) );
   connect( widget, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( ruleWidgetPanelAccepted( QgsPanelWidget* ) ) );
   connect( widget, SIGNAL( widgetChanged() ), this, SLOT( liveUpdateRuleFromPanel() ) );
diff --git a/src/app/qgsstatusbarmagnifierwidget.cpp b/src/app/qgsstatusbarmagnifierwidget.cpp
index 0a3f971..7f314a2 100644
--- a/src/app/qgsstatusbarmagnifierwidget.cpp
+++ b/src/app/qgsstatusbarmagnifierwidget.cpp
@@ -22,13 +22,14 @@
 #include <qgsapplication.h>
 #include "qgsstatusbarmagnifierwidget.h"
 #include "qgsdoublespinbox.h"
+#include "qgisgui.h"
 
 QgsStatusBarMagnifierWidget::QgsStatusBarMagnifierWidget( QWidget* parent )
     : QWidget( parent )
 {
   QSettings settings;
-  int minimumFactor = ( int ) 100 * settings.value( "/qgis/magnifier_factor_min", 0.1 ).toDouble();
-  int maximumFactor = ( int ) 100 * settings.value( "/qgis/magnifier_factor_max", 10 ).toDouble();
+  int minimumFactor = 100 * QgisGui::CANVAS_MAGNIFICATION_MIN;
+  int maximumFactor = 100 * QgisGui::CANVAS_MAGNIFICATION_MAX;
   int defaultFactor = ( int ) 100 * settings.value( "/qgis/magnifier_factor_default", 1.0 ).toDouble();
 
   // label
diff --git a/src/app/qgsvectorlayerproperties.cpp b/src/app/qgsvectorlayerproperties.cpp
index fb5a5e6..a19554c 100644
--- a/src/app/qgsvectorlayerproperties.cpp
+++ b/src/app/qgsvectorlayerproperties.cpp
@@ -181,7 +181,14 @@ QgsVectorLayerProperties::QgsVectorLayerProperties(
                       this, SLOT( loadStyleMenuTriggered( QAction * ) ) );
 
     //for saving
-    mSaveAsMenu->addAction( tr( "Save in database (%1)" ).arg( mLayer->providerType() ) );
+    QString providerName = mLayer->providerType();
+    if ( providerName == "ogr" )
+    {
+      providerName = mLayer->dataProvider()->storageType();
+      if ( providerName == "GPKG" )
+        providerName = "GeoPackage";
+    }
+    mSaveAsMenu->addAction( tr( "Save in database (%1)" ).arg( providerName ) );
   }
 
   QObject::connect( mSaveAsMenu, SIGNAL( triggered( QAction * ) ),
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 754ef22..e2d9ae8 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -610,6 +610,7 @@ SET(QGIS_CORE_HDRS
 
   qgis.h
   qgsaction.h
+  qgsactionmanager.h
   qgsaggregatecalculator.h
   qgsannotation.h
   qgsattributetableconfig.h
diff --git a/src/core/composer/qgscomposerattributetablev2.cpp b/src/core/composer/qgscomposerattributetablev2.cpp
index 76503f6..a0366f0 100644
--- a/src/core/composer/qgscomposerattributetablev2.cpp
+++ b/src/core/composer/qgscomposerattributetablev2.cpp
@@ -557,7 +557,7 @@ bool QgsComposerAttributeTableV2::getTableContents( QgsComposerTableContents &co
       {
         // Lets assume it's an expression
         QgsExpression* expression = new QgsExpression(( *columnIt )->attribute() );
-        context->lastScope()->setVariable( QString( "row_number" ), counter + 1 );
+        context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "row_number" ), counter + 1, true ) );
         expression->prepare( context.data() );
         QVariant value = expression->evaluate( context.data() );
         currentRow << value;
diff --git a/src/core/composer/qgscomposermapgrid.cpp b/src/core/composer/qgscomposermapgrid.cpp
index 9f89f73..c1911a8 100644
--- a/src/core/composer/qgscomposermapgrid.cpp
+++ b/src/core/composer/qgscomposermapgrid.cpp
@@ -1502,8 +1502,8 @@ QString QgsComposerMapGrid::gridAnnotationString( double value, QgsComposerMapGr
   }
   else if ( mGridAnnotationFormat == CustomFormat )
   {
-    expressionContext.lastScope()->setVariable( "grid_number", value );
-    expressionContext.lastScope()->setVariable( "grid_axis", coord == QgsComposerMapGrid::Longitude ? "x" : "y" );
+    expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_number" ), value, true ) );
+    expressionContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_axis" ), coord == QgsComposerMapGrid::Longitude ? "x" : "y", true ) );
     if ( !mGridAnnotationExpression.data() )
     {
       mGridAnnotationExpression.reset( new QgsExpression( mGridAnnotationExpressionString ) );
@@ -2238,8 +2238,8 @@ QgsExpressionContext* QgsComposerMapGrid::createExpressionContext() const
 {
   QgsExpressionContext* context = QgsComposerObject::createExpressionContext();
   context->appendScope( new QgsExpressionContextScope( tr( "Grid" ) ) );
-  context->lastScope()->setVariable( "grid_number", 0 );
-  context->lastScope()->setVariable( "grid_axis", "x" );
+  context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_number" ), 0, true ) );
+  context->lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "grid_axis" ), "x", true ) );
   context->setHighlightedVariables( QStringList() << "grid_number" << "grid_axis" );
   return context;
 }
diff --git a/src/core/dxf/qgsdxfexport.cpp b/src/core/dxf/qgsdxfexport.cpp
index 9ef65a1..859fdee 100644
--- a/src/core/dxf/qgsdxfexport.cpp
+++ b/src/core/dxf/qgsdxfexport.cpp
@@ -433,7 +433,7 @@ void QgsDxfExport::writeGroup( int code, const QgsPointV2 &p )
 {
   writeGroup( code + 10, p.x() );
   writeGroup( code + 20, p.y() );
-  if ( p.is3D() )
+  if ( p.is3D() && qIsFinite( p.z() ) )
     writeGroup( code + 30, p.z() );
 }
 
diff --git a/src/core/geometry/qgsabstractgeometryv2.h b/src/core/geometry/qgsabstractgeometryv2.h
index 27bc7fc..fc0437b 100644
--- a/src/core/geometry/qgsabstractgeometryv2.h
+++ b/src/core/geometry/qgsabstractgeometryv2.h
@@ -232,7 +232,7 @@ class CORE_EXPORT QgsAbstractGeometryV2
 
     /** Returns the number of nodes contained in the geometry
      */
-    int nCoordinates() const;
+    virtual int nCoordinates() const;
 
     /** Returns the point corresponding to a specified vertex id
      */
diff --git a/src/core/geometry/qgscurvepolygonv2.cpp b/src/core/geometry/qgscurvepolygonv2.cpp
index 94f41f3..dc57188 100644
--- a/src/core/geometry/qgscurvepolygonv2.cpp
+++ b/src/core/geometry/qgscurvepolygonv2.cpp
@@ -632,6 +632,27 @@ QgsCoordinateSequenceV2 QgsCurvePolygonV2::coordinateSequence() const
   return mCoordinateSequence;
 }
 
+int QgsCurvePolygonV2::nCoordinates() const
+{
+  if ( !mCoordinateSequence.isEmpty() )
+    return QgsAbstractGeometryV2::nCoordinates();
+
+  int count = 0;
+
+  if ( mExteriorRing )
+  {
+    count += mExteriorRing->nCoordinates();
+  }
+
+  QList<QgsCurveV2*>::const_iterator it = mInteriorRings.constBegin();
+  for ( ; it != mInteriorRings.constEnd(); ++it )
+  {
+    count += ( *it )->nCoordinates();
+  }
+
+  return count;
+}
+
 double QgsCurvePolygonV2::closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt, QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const
 {
   if ( !mExteriorRing )
diff --git a/src/core/geometry/qgscurvepolygonv2.h b/src/core/geometry/qgscurvepolygonv2.h
index 19f7dba..30be9da 100644
--- a/src/core/geometry/qgscurvepolygonv2.h
+++ b/src/core/geometry/qgscurvepolygonv2.h
@@ -93,6 +93,7 @@ class CORE_EXPORT QgsCurvePolygonV2: public QgsSurfaceV2
     virtual bool deleteVertex( QgsVertexId position ) override;
 
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override;
     double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const override;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const override;
 
diff --git a/src/core/geometry/qgsgeometrycollectionv2.cpp b/src/core/geometry/qgsgeometrycollectionv2.cpp
index 3e81c17..d99744f 100644
--- a/src/core/geometry/qgsgeometrycollectionv2.cpp
+++ b/src/core/geometry/qgsgeometrycollectionv2.cpp
@@ -369,6 +369,22 @@ QgsCoordinateSequenceV2 QgsGeometryCollectionV2::coordinateSequence() const
   return mCoordinateSequence;
 }
 
+int QgsGeometryCollectionV2::nCoordinates() const
+{
+  if ( !mCoordinateSequence.isEmpty() )
+    return QgsAbstractGeometryV2::nCoordinates();
+
+  int count = 0;
+
+  QVector< QgsAbstractGeometryV2* >::const_iterator geomIt = mGeometries.constBegin();
+  for ( ; geomIt != mGeometries.constEnd(); ++geomIt )
+  {
+    count += ( *geomIt )->nCoordinates();
+  }
+
+  return count;
+}
+
 double QgsGeometryCollectionV2::closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const
 {
   return QgsGeometryUtils::closestSegmentFromComponents( mGeometries, QgsGeometryUtils::PART, pt, segmentPt, vertexAfter, leftOf, epsilon );
diff --git a/src/core/geometry/qgsgeometrycollectionv2.h b/src/core/geometry/qgsgeometrycollectionv2.h
index 241d39f..97259e1 100644
--- a/src/core/geometry/qgsgeometrycollectionv2.h
+++ b/src/core/geometry/qgsgeometrycollectionv2.h
@@ -91,6 +91,8 @@ class CORE_EXPORT QgsGeometryCollectionV2: public QgsAbstractGeometryV2
     virtual QgsRectangle boundingBox() const override;
 
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override;
+
     virtual double closestSegment( const QgsPointV2& pt, QgsPointV2& segmentPt,  QgsVertexId& vertexAfter, bool* leftOf, double epsilon ) const override;
     bool nextVertex( QgsVertexId& id, QgsPointV2& vertex ) const override;
 
diff --git a/src/core/geometry/qgsgeometryutils.cpp b/src/core/geometry/qgsgeometryutils.cpp
index 4a53d96..24c8710 100644
--- a/src/core/geometry/qgsgeometryutils.cpp
+++ b/src/core/geometry/qgsgeometryutils.cpp
@@ -248,30 +248,31 @@ double QgsGeometryUtils::sqrDistance2D( const QgsPointV2& pt1, const QgsPointV2&
 
 double QgsGeometryUtils::sqrDistToLine( double ptX, double ptY, double x1, double y1, double x2, double y2, double& minDistX, double& minDistY, double epsilon )
 {
-  //normal vector
-  double nx = y2 - y1;
-  double ny = -( x2 - x1 );
+  minDistX = x1;
+  minDistY = y1;
 
-  double t;
-  t = ( ptX * ny - ptY * nx - x1 * ny + y1 * nx ) / (( x2 - x1 ) * ny - ( y2 - y1 ) * nx );
+  double dx = x2 - x1;
+  double dy = y2 - y1;
 
-  if ( t < 0.0 )
+  if ( !qgsDoubleNear( dx, 0.0 ) || !qgsDoubleNear( dy, 0.0 ) )
   {
-    minDistX = x1;
-    minDistY = y1;
-  }
-  else if ( t > 1.0 )
-  {
-    minDistX = x2;
-    minDistY = y2;
-  }
-  else
-  {
-    minDistX = x1 + t * ( x2 - x1 );
-    minDistY = y1 + t * ( y2 - y1 );
+    double t = (( ptX - x1 ) * dx + ( ptY - y1 ) * dy ) / ( dx * dx + dy * dy );
+    if ( t > 1 )
+    {
+      minDistX = x2;
+      minDistY = y2;
+    }
+    else if ( t > 0 )
+    {
+      minDistX += dx * t ;
+      minDistY += dy * t ;
+    }
   }
 
-  double dist = ( minDistX - ptX ) * ( minDistX - ptX ) + ( minDistY - ptY ) * ( minDistY - ptY );
+  dx = ptX - minDistX;
+  dy = ptY - minDistY;
+
+  double dist = dx * dx + dy * dy;
 
   //prevent rounding errors if the point is directly on the segment
   if ( qgsDoubleNear( dist, 0.0, epsilon ) )
diff --git a/src/core/geometry/qgslinestringv2.h b/src/core/geometry/qgslinestringv2.h
index 895fe98..8763f6a 100644
--- a/src/core/geometry/qgslinestringv2.h
+++ b/src/core/geometry/qgslinestringv2.h
@@ -164,6 +164,7 @@ class CORE_EXPORT QgsLineStringV2: public QgsCurveV2
     virtual QgsLineStringV2* curveToLine( double tolerance = M_PI_2 / 90, SegmentationToleranceType toleranceType = MaximumAngle ) const override;
 
     int numPoints() const override;
+    virtual int nCoordinates() const override { return mX.size(); }
     void points( QgsPointSequenceV2 &pt ) const override;
 
     void draw( QPainter& p ) const override;
diff --git a/src/core/geometry/qgsmultipointv2.h b/src/core/geometry/qgsmultipointv2.h
index 8978ab5..08b7d70 100644
--- a/src/core/geometry/qgsmultipointv2.h
+++ b/src/core/geometry/qgsmultipointv2.h
@@ -40,6 +40,7 @@ class CORE_EXPORT QgsMultiPointV2: public QgsGeometryCollectionV2
     QDomElement asGML3( QDomDocument& doc, int precision = 17, const QString& ns = "gml" ) const override;
     QString asJSON( int precision = 17 ) const override;
 
+    virtual int nCoordinates() const override { return mGeometries.size(); }
 
     /** Adds a geometry and takes ownership. Returns true in case of success*/
     virtual bool addGeometry( QgsAbstractGeometryV2* g ) override;
diff --git a/src/core/geometry/qgspointv2.h b/src/core/geometry/qgspointv2.h
index 286d16a..d37d134 100644
--- a/src/core/geometry/qgspointv2.h
+++ b/src/core/geometry/qgspointv2.h
@@ -169,6 +169,7 @@ class CORE_EXPORT QgsPointV2: public QgsAbstractGeometryV2
                     bool transformZ = false ) override;
     void transform( const QTransform& t ) override;
     virtual QgsCoordinateSequenceV2 coordinateSequence() const override;
+    virtual int nCoordinates() const override { return 1; }
     virtual QgsAbstractGeometryV2* boundary() const override;
 
     //low-level editing
diff --git a/src/core/layertree/qgslayertreegroup.cpp b/src/core/layertree/qgslayertreegroup.cpp
index 91dfb91..6a0aa46 100644
--- a/src/core/layertree/qgslayertreegroup.cpp
+++ b/src/core/layertree/qgslayertreegroup.cpp
@@ -46,6 +46,20 @@ QgsLayerTreeGroup::QgsLayerTreeGroup( const QgsLayerTreeGroup& other )
   connect( this, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ), this, SLOT( nodeVisibilityChanged( QgsLayerTreeNode* ) ) );
 }
 
+QString QgsLayerTreeGroup::name() const
+{
+  return mName;
+}
+
+void QgsLayerTreeGroup::setName( const QString& n )
+{
+  if ( mName == n )
+    return;
+
+  mName = n;
+  emit nameChanged( this, n );
+}
+
 
 QgsLayerTreeGroup* QgsLayerTreeGroup::insertGroup( int index, const QString& name )
 {
diff --git a/src/core/layertree/qgslayertreegroup.h b/src/core/layertree/qgslayertreegroup.h
index 4fad9cc..5f8fd62 100644
--- a/src/core/layertree/qgslayertreegroup.h
+++ b/src/core/layertree/qgslayertreegroup.h
@@ -36,9 +36,9 @@ class CORE_EXPORT QgsLayerTreeGroup : public QgsLayerTreeNode
     QgsLayerTreeGroup( const QgsLayerTreeGroup& other );
 
     //! Get group's name
-    QString name() const { return mName; }
+    QString name() const override;
     //! Set group's name
-    void setName( const QString& n ) { mName = n; }
+    void setName( const QString& n ) override;
 
     //! Insert a new group node with given name at specified position. Newly created node is owned by this group.
     QgsLayerTreeGroup* insertGroup( int index, const QString& name );
diff --git a/src/core/layertree/qgslayertreelayer.cpp b/src/core/layertree/qgslayertreelayer.cpp
index 2f577df..e1a9466 100644
--- a/src/core/layertree/qgslayertreelayer.cpp
+++ b/src/core/layertree/qgslayertreelayer.cpp
@@ -58,6 +58,7 @@ void QgsLayerTreeLayer::attachToLayer()
   {
     mLayer = l;
     mLayerName = l->name();
+    connect( l, SIGNAL( nameChanged() ), this, SLOT( layerNameChanged() ) );
     // make sure we are notified if the layer is removed
     connect( QgsMapLayerRegistry::instance(), SIGNAL( layersWillBeRemoved( QStringList ) ), this, SLOT( registryLayersWillBeRemoved( QStringList ) ) );
   }
@@ -70,6 +71,15 @@ void QgsLayerTreeLayer::attachToLayer()
   }
 }
 
+QString QgsLayerTreeLayer::name() const
+{
+  return layerName();
+}
+
+void QgsLayerTreeLayer::setName( const QString& n )
+{
+  setLayerName( n );
+}
 
 QString QgsLayerTreeLayer::layerName() const
 {
@@ -79,9 +89,19 @@ QString QgsLayerTreeLayer::layerName() const
 void QgsLayerTreeLayer::setLayerName( const QString& n )
 {
   if ( mLayer )
+  {
+    if ( mLayer->name() == n )
+      return;
     mLayer->setName( n );
+    // no need to emit signal: we will be notified from layer's nameChanged() signal
+  }
   else
+  {
+    if ( mLayerName == n )
+      return;
     mLayerName = n;
+    emit nameChanged( this, n );
+  }
 }
 
 void QgsLayerTreeLayer::setVisible( Qt::CheckState state )
@@ -170,3 +190,9 @@ void QgsLayerTreeLayer::registryLayersWillBeRemoved( const QStringList& layerIds
     mLayer = nullptr;
   }
 }
+
+void QgsLayerTreeLayer::layerNameChanged()
+{
+  Q_ASSERT( mLayer );
+  emit nameChanged( this, mLayer->name() );
+}
diff --git a/src/core/layertree/qgslayertreelayer.h b/src/core/layertree/qgslayertreelayer.h
index 3030f6c..2fb3a05 100644
--- a/src/core/layertree/qgslayertreelayer.h
+++ b/src/core/layertree/qgslayertreelayer.h
@@ -51,6 +51,13 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
 
     QgsMapLayer* layer() const { return mLayer; }
 
+    //! Get layer's name
+    //! @note added in 2.18.1
+    QString name() const override;
+    //! Set layer's name
+    //! @note added in 2.18.1
+    void setName( const QString& n ) override;
+
     QString layerName() const;
     void setLayerName( const QString& n );
 
@@ -67,6 +74,9 @@ class CORE_EXPORT QgsLayerTreeLayer : public QgsLayerTreeNode
   protected slots:
     void registryLayersAdded( const QList<QgsMapLayer*>& layers );
     void registryLayersWillBeRemoved( const QStringList& layerIds );
+    //! Emits a nameChanged() signal if layer's name has changed
+    //! @note added in 2.18.1
+    void layerNameChanged();
 
   signals:
     //! emitted when a previously unavailable layer got loaded
diff --git a/src/core/layertree/qgslayertreemodel.cpp b/src/core/layertree/qgslayertreemodel.cpp
index d283752..366aa19 100644
--- a/src/core/layertree/qgslayertreemodel.cpp
+++ b/src/core/layertree/qgslayertreemodel.cpp
@@ -753,6 +753,15 @@ void QgsLayerTreeModel::nodeVisibilityChanged( QgsLayerTreeNode* node )
   emit dataChanged( index, index );
 }
 
+void QgsLayerTreeModel::nodeNameChanged( QgsLayerTreeNode* node, const QString& name )
+{
+  Q_UNUSED( name );
+  Q_ASSERT( node );
+
+  QModelIndex index = node2index( node );
+  emit dataChanged( index, index );
+}
+
 
 void QgsLayerTreeModel::nodeCustomPropertyChanged( QgsLayerTreeNode* node, const QString& key )
 {
@@ -865,7 +874,6 @@ void QgsLayerTreeModel::connectToLayer( QgsLayerTreeLayer* nodeLayer )
     connect( layer, SIGNAL( editingStarted() ), this, SLOT( layerNeedsUpdate() ), Qt::UniqueConnection );
     connect( layer, SIGNAL( editingStopped() ), this, SLOT( layerNeedsUpdate() ), Qt::UniqueConnection );
     connect( layer, SIGNAL( layerModified() ), this, SLOT( layerNeedsUpdate() ), Qt::UniqueConnection );
-    connect( layer, SIGNAL( layerNameChanged() ), this, SLOT( layerNeedsUpdate() ), Qt::UniqueConnection );
   }
 }
 
@@ -938,6 +946,7 @@ void QgsLayerTreeModel::connectToRootNode()
   connect( mRootNode, SIGNAL( willRemoveChildren( QgsLayerTreeNode*, int, int ) ), this, SLOT( nodeWillRemoveChildren( QgsLayerTreeNode*, int, int ) ) );
   connect( mRootNode, SIGNAL( removedChildren( QgsLayerTreeNode*, int, int ) ), this, SLOT( nodeRemovedChildren() ) );
   connect( mRootNode, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ), this, SLOT( nodeVisibilityChanged( QgsLayerTreeNode* ) ) );
+  connect( mRootNode, SIGNAL( nameChanged( QgsLayerTreeNode*, QString ) ), this, SLOT( nodeNameChanged( QgsLayerTreeNode*, QString ) ) );
 
   connect( mRootNode, SIGNAL( customPropertyChanged( QgsLayerTreeNode*, QString ) ), this, SLOT( nodeCustomPropertyChanged( QgsLayerTreeNode*, QString ) ) );
 
diff --git a/src/core/layertree/qgslayertreemodel.h b/src/core/layertree/qgslayertreemodel.h
index ee1cdee..846e71a 100644
--- a/src/core/layertree/qgslayertreemodel.h
+++ b/src/core/layertree/qgslayertreemodel.h
@@ -234,6 +234,9 @@ class CORE_EXPORT QgsLayerTreeModel : public QAbstractItemModel
     void nodeRemovedChildren();
 
     void nodeVisibilityChanged( QgsLayerTreeNode* node );
+    //! Updates model when node's name has changed
+    //! @note added in 2.18.1
+    void nodeNameChanged( QgsLayerTreeNode* node, const QString& name );
 
     void nodeCustomPropertyChanged( QgsLayerTreeNode* node, const QString& key );
 
diff --git a/src/core/layertree/qgslayertreenode.cpp b/src/core/layertree/qgslayertreenode.cpp
index a77e00c..a3513fe 100644
--- a/src/core/layertree/qgslayertreenode.cpp
+++ b/src/core/layertree/qgslayertreenode.cpp
@@ -136,6 +136,7 @@ void QgsLayerTreeNode::insertChildrenPrivate( int index, QList<QgsLayerTreeNode*
     connect( nodes[i], SIGNAL( customPropertyChanged( QgsLayerTreeNode*, QString ) ), this, SIGNAL( customPropertyChanged( QgsLayerTreeNode*, QString ) ) );
     connect( nodes[i], SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ), this, SIGNAL( visibilityChanged( QgsLayerTreeNode*, Qt::CheckState ) ) );
     connect( nodes[i], SIGNAL( expandedChanged( QgsLayerTreeNode*, bool ) ), this, SIGNAL( expandedChanged( QgsLayerTreeNode*, bool ) ) );
+    connect( nodes[i], SIGNAL( nameChanged( QgsLayerTreeNode*, QString ) ), this, SIGNAL( nameChanged( QgsLayerTreeNode*, QString ) ) );
   }
   emit addedChildren( this, index, indexTo );
 }
diff --git a/src/core/layertree/qgslayertreenode.h b/src/core/layertree/qgslayertreenode.h
index f2a9320..9f5eb62 100644
--- a/src/core/layertree/qgslayertreenode.h
+++ b/src/core/layertree/qgslayertreenode.h
@@ -83,6 +83,13 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
     //! Get list of children of the node. Children are owned by the parent
     QList<QgsLayerTreeNode*> children() { return mChildren; }
 
+    //! Return name of the node
+    //! @note added in 2.18.1
+    virtual QString name() const = 0;
+    //! Set name of the node. Emits nameChanged signal.
+    //! @note added in 2.18.1
+    virtual void setName( const QString& name ) = 0;
+
     //! Read layer tree from XML. Returns new instance
     static QgsLayerTreeNode *readXML( QDomElement &element );
     //! Write layer tree to XML
@@ -126,6 +133,9 @@ class CORE_EXPORT QgsLayerTreeNode : public QObject
     void customPropertyChanged( QgsLayerTreeNode *node, const QString& key );
     //! Emitted when the collapsed/expanded state of a node within the tree has been changed
     void expandedChanged( QgsLayerTreeNode *node, bool expanded );
+    //! Emitted when the name of the node is changed
+    //! @note added in 2.18.1
+    void nameChanged( QgsLayerTreeNode* node, QString name );
 
   protected:
 
diff --git a/src/core/qgsactionmanager.cpp b/src/core/qgsactionmanager.cpp
index 047eada..e2413dc 100644
--- a/src/core/qgsactionmanager.cpp
+++ b/src/core/qgsactionmanager.cpp
@@ -67,7 +67,7 @@ void QgsActionManager::doAction( int index, const QgsFeature& feat, int defaultV
 {
   QgsExpressionContext context = createExpressionContext();
   QgsExpressionContextScope* actionScope = new QgsExpressionContextScope();
-  actionScope->setVariable( "current_field", feat.attribute( defaultValueIndex ) );
+  actionScope->addVariable( QgsExpressionContextScope::StaticVariable( QString( "current_field" ), feat.attribute( defaultValueIndex ), true ) );
   context << actionScope;
   doAction( index, feat, context );
 }
diff --git a/src/core/qgsattributeaction.h b/src/core/qgsattributeaction.h
index a571470..a391c58 100644
--- a/src/core/qgsattributeaction.h
+++ b/src/core/qgsattributeaction.h
@@ -1,5 +1,5 @@
 /***************************************************************************
-                        qgsactionmanager.h
+                        qgsattributeaction.h
 
 This is a legacy header to keep backwards compatibility until QGIS 3.
 
diff --git a/src/core/qgscacheindex.h b/src/core/qgscacheindex.h
index 80bd359..836b7f5 100644
--- a/src/core/qgscacheindex.h
+++ b/src/core/qgscacheindex.h
@@ -58,8 +58,8 @@ class CORE_EXPORT QgsAbstractCacheIndex
     /**
      * Is called, when a feature request is issued on a cached layer.
      * If this cache index is able to completely answer the feature request, it will return true
-     * and write the list of feature ids of cached features to cachedFeatures. If it is not able
-     * it will return false and the cachedFeatures state is undefined.
+     * and set the iterator to a valid iterator over the cached features. If it is not able
+     * it will return false.
      *
      * @param featureIterator  A reference to a {@link QgsFeatureIterator}. A valid featureIterator will
      *                         be assigned in case this index is able to answer the request and the return
diff --git a/src/core/qgscacheindexfeatureid.cpp b/src/core/qgscacheindexfeatureid.cpp
index 496171a..9c91634 100644
--- a/src/core/qgscacheindexfeatureid.cpp
+++ b/src/core/qgscacheindexfeatureid.cpp
@@ -42,12 +42,36 @@ void QgsCacheIndexFeatureId::requestCompleted( const QgsFeatureRequest& featureR
 
 bool QgsCacheIndexFeatureId::getCacheIterator( QgsFeatureIterator &featureIterator, const QgsFeatureRequest &featureRequest )
 {
-  if ( featureRequest.filterType() == QgsFeatureRequest::FilterFid )
+  switch ( featureRequest.filterType() )
   {
-    if ( C->isFidCached( featureRequest.filterFid() ) )
+    case QgsFeatureRequest::FilterFid:
     {
-      featureIterator = QgsFeatureIterator( new QgsCachedFeatureIterator( C, featureRequest ) );
-      return true;
+      if ( C->isFidCached( featureRequest.filterFid() ) )
+      {
+        featureIterator = QgsFeatureIterator( new QgsCachedFeatureIterator( C, featureRequest ) );
+        return true;
+      }
+      break;
+    }
+    case QgsFeatureRequest::FilterFids:
+    {
+      if ( C->cachedFeatureIds().contains( featureRequest.filterFids() ) )
+      {
+        featureIterator = QgsFeatureIterator( new QgsCachedFeatureIterator( C, featureRequest ) );
+        return true;
+      }
+      break;
+    }
+    case QgsFeatureRequest::FilterNone:
+    case QgsFeatureRequest::FilterRect:
+    case QgsFeatureRequest::FilterExpression:
+    {
+      if ( C->hasFullCache() )
+      {
+        featureIterator = QgsFeatureIterator( new QgsCachedFeatureIterator( C, featureRequest ) );
+        return true;
+      }
+      break;
     }
   }
 
diff --git a/src/core/qgscacheindexfeatureid.h b/src/core/qgscacheindexfeatureid.h
index 29ab923..25a7483 100644
--- a/src/core/qgscacheindexfeatureid.h
+++ b/src/core/qgscacheindexfeatureid.h
@@ -28,43 +28,9 @@ class CORE_EXPORT QgsCacheIndexFeatureId : public QgsAbstractCacheIndex
   public:
     QgsCacheIndexFeatureId( QgsVectorLayerCache* );
 
-    /**
-     * Is called, whenever a feature is removed from the cache. You should update your indexes, so
-     * they become invalid in case this feature was required to successfuly answer a request.
-     */
     virtual void flushFeature( const QgsFeatureId fid ) override;
-
-    /**
-     * Sometimes, the whole cache changes its state and its easier to just withdraw everything.
-     * In this case, this method is issued. Be sure to clear all cache information in here.
-     */
     virtual void flush() override;
-
-    /**
-     * @brief
-     * Implement this method to update the the indices, in case you need information contained by the request
-     * to properly index. (E.g. spatial index)
-     * Does nothing by default
-     *
-     * @param featureRequest  The feature request that was answered
-     * @param fids            The feature ids that have been returned
-     */
     virtual void requestCompleted( const QgsFeatureRequest& featureRequest, const QgsFeatureIds& fids ) override;
-
-    /**
-     * Is called, when a feature request is issued on a cached layer.
-     * If this cache index is able to completely answer the feature request, it will return true
-     * and write the list of feature ids of cached features to cachedFeatures. If it is not able
-     * it will return false and the cachedFeatures state is undefined.
-     *
-     * @param featureIterator  A reference to a {@link QgsFeatureIterator}. A valid featureIterator will
-     *                         be assigned in case this index is able to answer the request and the return
-     *                         value is true.
-     * @param featureRequest   The feature request, for which this index is queried.
-     *
-     * @return   True, if this index holds the information to answer the request.
-     *
-     */
     virtual bool getCacheIterator( QgsFeatureIterator& featureIterator, const QgsFeatureRequest& featureRequest ) override;
 
   private:
diff --git a/src/core/qgsconditionalstyle.cpp b/src/core/qgsconditionalstyle.cpp
index 3cfa6b5..169af65 100644
--- a/src/core/qgsconditionalstyle.cpp
+++ b/src/core/qgsconditionalstyle.cpp
@@ -195,7 +195,7 @@ void QgsConditionalStyle::setSymbol( QgsSymbolV2* value )
 bool QgsConditionalStyle::matches( const QVariant& value, QgsExpressionContext& context ) const
 {
   QgsExpression exp( mRule );
-  context.lastScope()->setVariable( "value", value );
+  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "value" ), value, true ) );
   return exp.evaluate( &context ).toBool();
 }
 
diff --git a/src/core/qgsexpression.cpp b/src/core/qgsexpression.cpp
index 0ed5e2a..a52d3ae 100644
--- a/src/core/qgsexpression.cpp
+++ b/src/core/qgsexpression.cpp
@@ -1348,7 +1348,7 @@ static QVariant fcnFeature( const QVariantList&, const QgsExpressionContext* con
   if ( !context )
     return QVariant();
 
-  return context->variable( QgsExpressionContext::EXPR_FEATURE );
+  return context->feature();
 }
 static QVariant fcnAttribute( const QVariantList& values, const QgsExpressionContext*, QgsExpression* parent )
 {
@@ -4825,7 +4825,7 @@ QVariant QgsExpression::NodeColumnRef::eval( QgsExpression *parent, const QgsExp
 
   if ( context && context->hasVariable( QgsExpressionContext::EXPR_FEATURE ) )
   {
-    QgsFeature feature = qvariant_cast<QgsFeature>( context->variable( QgsExpressionContext::EXPR_FEATURE ) );
+    QgsFeature feature = context->feature();
     if ( index >= 0 )
       return feature.attribute( index );
     else
diff --git a/src/core/qgsexpressioncontext.cpp b/src/core/qgsexpressioncontext.cpp
index 5105603..69660ab 100644
--- a/src/core/qgsexpressioncontext.cpp
+++ b/src/core/qgsexpressioncontext.cpp
@@ -193,12 +193,12 @@ void QgsExpressionContextScope::addFunction( const QString& name, QgsScopedExpre
 
 void QgsExpressionContextScope::setFeature( const QgsFeature &feature )
 {
-  setVariable( QgsExpressionContext::EXPR_FEATURE, QVariant::fromValue( feature ) );
+  addVariable( StaticVariable( QgsExpressionContext::EXPR_FEATURE, QVariant::fromValue( feature ), true ) );
 }
 
 void QgsExpressionContextScope::setFields( const QgsFields &fields )
 {
-  setVariable( QgsExpressionContext::EXPR_FIELDS, QVariant::fromValue( fields ) );
+  addVariable( StaticVariable( QgsExpressionContext::EXPR_FIELDS, QVariant::fromValue( fields ), true ) );
 }
 
 
diff --git a/src/core/qgsvectorfilewriter.cpp b/src/core/qgsvectorfilewriter.cpp
index c44a14f..1ba1b3c 100644
--- a/src/core/qgsvectorfilewriter.cpp
+++ b/src/core/qgsvectorfilewriter.cpp
@@ -95,7 +95,8 @@ QgsVectorFileWriter::QgsVectorFileWriter(
     , mFieldValueConverter( nullptr )
 {
   init( theVectorFileName, theFileEncoding, fields, QGis::fromOldWkbType( geometryType ),
-        srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr );
+        srs, driverName, datasourceOptions, layerOptions, newFilename, nullptr,
+        QString(), CreateOrOverwriteFile );
 }
 
 QgsVectorFileWriter::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, QgsVectorFileWriter::SymbologyExport symbologyExport )
@@ -111,7 +112,8 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName, const Q
     , mFieldValueConverter( nullptr )
 {
   init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
-        datasourceOptions, layerOptions, newFilename, nullptr );
+        datasourceOptions, layerOptions, newFilename, nullptr,
+        QString(), CreateOrOverwriteFile );
 }
 
 QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
@@ -124,7 +126,9 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
     const QStringList& layerOptions,
     QString* newFilename,
     QgsVectorFileWriter::SymbologyExport symbologyExport,
-    FieldValueConverter* fieldValueConverter )
+    FieldValueConverter* fieldValueConverter,
+    const QString& layerName,
+    ActionOnExistingFile action )
     : mDS( nullptr )
     , mLayer( nullptr )
     , mOgrRef( nullptr )
@@ -137,7 +141,8 @@ QgsVectorFileWriter::QgsVectorFileWriter( const QString& vectorFileName,
     , mFieldValueConverter( nullptr )
 {
   init( vectorFileName, fileEncoding, fields, geometryType, srs, driverName,
-        datasourceOptions, layerOptions, newFilename, fieldValueConverter );
+        datasourceOptions, layerOptions, newFilename, fieldValueConverter,
+        layerName, action );
 }
 
 void QgsVectorFileWriter::init( QString vectorFileName,
@@ -149,7 +154,9 @@ void QgsVectorFileWriter::init( QString vectorFileName,
                                 QStringList datasourceOptions,
                                 QStringList layerOptions,
                                 QString* newFilename,
-                                FieldValueConverter* fieldValueConverter )
+                                FieldValueConverter* fieldValueConverter,
+                                const QString& layerNameIn,
+                                ActionOnExistingFile action )
 {
   mRenderContext.setRendererScale( mSymbologyScaleDenominator );
 
@@ -237,7 +244,8 @@ void QgsVectorFileWriter::init( QString vectorFileName,
     }
 #endif
 
-    deleteShapeFile( vectorFileName );
+    if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
+      deleteShapeFile( vectorFileName );
   }
   else
   {
@@ -260,7 +268,27 @@ void QgsVectorFileWriter::init( QString vectorFileName,
       }
     }
 
-    QFile::remove( vectorFileName );
+    if ( action == CreateOrOverwriteFile )
+    {
+      if ( vectorFileName.endsWith( ".gdb", Qt::CaseInsensitive ) )
+      {
+        QDir dir( vectorFileName );
+        if ( dir.exists() )
+        {
+          QFileInfoList fileList = dir.entryInfoList(
+                                     QDir::NoDotAndDotDot | QDir::System | QDir::Hidden  | QDir::AllDirs | QDir::Files, QDir::DirsFirst );
+          Q_FOREACH ( QFileInfo info, fileList )
+          {
+            QFile::remove( info.absoluteFilePath() );
+          }
+        }
+        QDir().rmdir( vectorFileName );
+      }
+      else
+      {
+        QFile::remove( vectorFileName );
+      }
+    }
   }
 
   if ( metadataFound && !metadata.compulsoryEncoding.isEmpty() )
@@ -285,7 +313,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
   }
 
   // create the data source
-  mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
+  if ( action == CreateOrOverwriteFile )
+    mDS = OGR_Dr_CreateDataSource( poDriver, TO8F( vectorFileName ), options );
+  else
+    mDS = OGROpen( TO8F( vectorFileName ), TRUE, nullptr );
 
   if ( options )
   {
@@ -298,12 +329,47 @@ void QgsVectorFileWriter::init( QString vectorFileName,
   if ( !mDS )
   {
     mError = ErrCreateDataSource;
-    mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
-                    .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
+    if ( action == CreateOrOverwriteFile )
+      mErrorMessage = QObject::tr( "creation of data source failed (OGR error:%1)" )
+                      .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
+    else
+      mErrorMessage = QObject::tr( "opening of data source in update mode failed (OGR error:%1)" )
+                      .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
     return;
   }
 
-  QgsDebugMsg( "Created data source" );
+  QString layerName( layerNameIn );
+  if ( layerName.isEmpty() )
+    layerName = QFileInfo( vectorFileName ).baseName();
+
+  if ( action == CreateOrOverwriteLayer )
+  {
+    const int layer_count = OGR_DS_GetLayerCount( mDS );
+    for ( int i = 0; i < layer_count; i++ )
+    {
+      OGRLayerH hLayer = OGR_DS_GetLayer( mDS, i );
+      if ( EQUAL( OGR_L_GetName( hLayer ), TO8F( layerName ) ) )
+      {
+        if ( OGR_DS_DeleteLayer( mDS, i ) != OGRERR_NONE )
+        {
+          mError = ErrCreateLayer;
+          mErrorMessage = QObject::tr( "overwriting of existing layer failed (OGR error:%1)" )
+                          .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
+          return;
+        }
+        break;
+      }
+    }
+  }
+
+  if ( action == CreateOrOverwriteFile )
+  {
+    QgsDebugMsg( "Created data source" );
+  }
+  else
+  {
+    QgsDebugMsg( "Opened data source in update mode" );
+  }
 
   // use appropriate codec
   mCodec = QTextCodec::codecForName( fileEncoding.toLocal8Bit().constData() );
@@ -331,7 +397,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
   }
 
   // datasource created, now create the output layer
-  QString layerName = QFileInfo( vectorFileName ).baseName();
   OGRwkbGeometryType wkbType = ogrTypeFromWkbType( geometryType );
 
   // Remove FEATURE_DATASET layer option (used for ESRI File GDB driver) if its value is not set
@@ -354,7 +419,10 @@ void QgsVectorFileWriter::init( QString vectorFileName,
   // disable encoding conversion of OGR Shapefile layer
   CPLSetConfigOption( "SHAPE_ENCODING", "" );
 
-  mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
+  if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
+    mLayer = OGR_DS_CreateLayer( mDS, TO8F( layerName ), mOgrRef, wkbType, options );
+  else
+    mLayer = OGR_DS_GetLayerByName( mDS, TO8F( layerName ) );
 
   if ( options )
   {
@@ -391,8 +459,12 @@ void QgsVectorFileWriter::init( QString vectorFileName,
 
   if ( !mLayer )
   {
-    mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
-                    .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
+    if ( action == CreateOrOverwriteFile || action == CreateOrOverwriteLayer )
+      mErrorMessage = QObject::tr( "creation of layer failed (OGR error:%1)" )
+                      .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
+    else
+      mErrorMessage = QObject::tr( "opening of layer failed (OGR error:%1)" )
+                      .arg( QString::fromUtf8( CPLGetLastErrorMsg() ) );
     mError = ErrCreateLayer;
     return;
   }
@@ -410,17 +482,30 @@ void QgsVectorFileWriter::init( QString vectorFileName,
 
   mFieldValueConverter = fieldValueConverter;
 
-  for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
+  for ( int fldIdx = 0; ( action == CreateOrOverwriteFile ||
+                          action == CreateOrOverwriteLayer ||
+                          action == AppendToLayerAddFields ) &&
+        fldIdx < fields.count(); ++fldIdx )
   {
     QgsField attrField = fields[fldIdx];
 
-    OGRFieldType ogrType = OFTString; //default to string
-
     if ( fieldValueConverter )
     {
       attrField = fieldValueConverter->fieldDefinition( fields[fldIdx] );
     }
 
+    QString name( attrField.name() );
+    if ( action == AppendToLayerAddFields )
+    {
+      int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
+      if ( ogrIdx >= 0 )
+      {
+        mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
+        continue;
+      }
+    }
+
+    OGRFieldType ogrType = OFTString; //default to string
     int ogrWidth = attrField.length();
     int ogrPrecision = attrField.precision();
     if ( ogrPrecision > 0 )
@@ -499,8 +584,6 @@ void QgsVectorFileWriter::init( QString vectorFileName,
         return;
     }
 
-    QString name( attrField.name() );
-
     if ( mOgrDriverName == "SQLite" && name.compare( "ogc_fid", Qt::CaseInsensitive ) == 0 )
     {
       int i;
@@ -594,6 +677,18 @@ void QgsVectorFileWriter::init( QString vectorFileName,
     mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
   }
 
+  if ( action == AppendToLayerNoNewFields )
+  {
+    for ( int fldIdx = 0; fldIdx < fields.count(); ++fldIdx )
+    {
+      QgsField attrField = fields.at( fldIdx );
+      QString name( attrField.name() );
+      int ogrIdx = OGR_FD_GetFieldIndex( defn, mCodec->fromUnicode( name ) );
+      if ( ogrIdx >= 0 )
+        mAttrIdxToOgrIdx.insert( fldIdx, ogrIdx );
+    }
+  }
+
   QgsDebugMsg( "Done creating fields" );
 
   mWkbType = geometryType;
@@ -2067,7 +2162,8 @@ void QgsVectorFileWriter::resetMap( const QgsAttributeList &attributes )
   mAttrIdxToOgrIdx.clear();
   for ( int i = 0; i < attributes.size(); i++ )
   {
-    mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
+    if ( omap.find( i ) != omap.end() )
+      mAttrIdxToOgrIdx.insert( attributes[i], omap[i] );
   }
 }
 
@@ -2158,6 +2254,57 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
     QgsAttributeList attributes,
     FieldValueConverter* fieldValueConverter )
 {
+  SaveVectorOptions options;
+  options.fileEncoding = fileEncoding;
+  options.ct = ct;
+  options.driverName = driverName;
+  options.onlySelectedFeatures = onlySelected;
+  options.datasourceOptions = datasourceOptions;
+  options.layerOptions = layerOptions;
+  options.skipAttributeCreation = skipAttributeCreation;
+  options.symbologyExport = symbologyExport;
+  options.symbologyScale = symbologyScale;
+  if ( filterExtent )
+    options.filterExtent = *filterExtent;
+  options.overrideGeometryType = overrideGeometryType;
+  options.forceMulti = forceMulti;
+  options.includeZ = includeZ;
+  options.attributes = attributes;
+  options.fieldValueConverter = fieldValueConverter;
+  return writeAsVectorFormat( layer, fileName, options, newFilename, errorMessage );
+}
+
+QgsVectorFileWriter::SaveVectorOptions::SaveVectorOptions()
+    : driverName( "ESRI Shapefile" )
+    , layerName( QString() )
+    , actionOnExistingFile( CreateOrOverwriteFile )
+    , fileEncoding( QString() )
+    , ct( nullptr )
+    , onlySelectedFeatures( false )
+    , datasourceOptions( QStringList() )
+    , layerOptions( QStringList() )
+    , skipAttributeCreation( false )
+    , attributes( QgsAttributeList() )
+    , symbologyExport( NoSymbology )
+    , symbologyScale( 1.0 )
+    , filterExtent( QgsRectangle() )
+    , overrideGeometryType( QgsWKBTypes::Unknown )
+    , forceMulti( false )
+    , fieldValueConverter( nullptr )
+{
+}
+
+QgsVectorFileWriter::SaveVectorOptions::~SaveVectorOptions()
+{
+}
+
+QgsVectorFileWriter::WriterError
+QgsVectorFileWriter::writeAsVectorFormat( QgsVectorLayer* layer,
+    const QString& fileName,
+    const SaveVectorOptions& options,
+    QString *newFilename,
+    QString *errorMessage )
+{
   if ( !layer )
   {
     return ErrInvalidLayer;
@@ -2165,10 +2312,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
 
   bool shallTransform = false;
   const QgsCoordinateReferenceSystem* outputCRS = nullptr;
-  if ( ct )
+  if ( options.ct )
   {
     // This means we should transform
-    outputCRS = &( ct->destCRS() );
+    outputCRS = &( options.ct->destCRS() );
     shallTransform = true;
   }
   else
@@ -2178,18 +2325,19 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
   }
 
   QgsWKBTypes::Type destWkbType = QGis::fromOldWkbType( layer->wkbType() );
-  if ( overrideGeometryType != QgsWKBTypes::Unknown )
+  if ( options.overrideGeometryType != QgsWKBTypes::Unknown )
   {
-    destWkbType = QgsWKBTypes::flatType( overrideGeometryType );
-    if ( QgsWKBTypes::hasZ( overrideGeometryType ) || includeZ )
+    destWkbType = QgsWKBTypes::flatType( options.overrideGeometryType );
+    if ( QgsWKBTypes::hasZ( options.overrideGeometryType ) || options.includeZ )
       destWkbType = QgsWKBTypes::addZ( destWkbType );
   }
-  if ( forceMulti )
+  if ( options.forceMulti )
   {
     destWkbType = QgsWKBTypes::multiType( destWkbType );
   }
 
-  if ( skipAttributeCreation )
+  QgsAttributeList attributes( options.attributes );
+  if ( options.skipAttributeCreation )
     attributes.clear();
   else if ( attributes.isEmpty() )
   {
@@ -2227,7 +2375,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
     if ( layer->storageType() == "ESRI Shapefile" && !QgsWKBTypes::isMultiType( destWkbType ) )
     {
       QgsFeatureRequest req;
-      if ( onlySelected )
+      if ( options.onlySelectedFeatures )
       {
         req.setFilterFids( layer->selectedFeaturesIds() );
       }
@@ -2261,11 +2409,17 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
   }
 
   QgsVectorFileWriter* writer =
-    new QgsVectorFileWriter( fileName, fileEncoding, fields, destWkbType,
-                             outputCRS, driverName, datasourceOptions, layerOptions,
-                             newFilename, symbologyExport,
-                             fieldValueConverter );
-  writer->setSymbologyScaleDenominator( symbologyScale );
+    new QgsVectorFileWriter( fileName,
+                             options.fileEncoding, fields, destWkbType,
+                             outputCRS, options.driverName,
+                             options.datasourceOptions,
+                             options.layerOptions,
+                             newFilename,
+                             options.symbologyExport,
+                             options.fieldValueConverter,
+                             options.layerName,
+                             options.actionOnExistingFile );
+  writer->setSymbologyScaleDenominator( options.symbologyScale );
 
   if ( newFilename )
   {
@@ -2298,7 +2452,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
     req.setFlags( QgsFeatureRequest::NoGeometry );
   }
   req.setSubsetOfAttributes( attributes );
-  if ( onlySelected )
+  if ( options.onlySelectedFeatures )
     req.setFilterFids( layer->selectedFeaturesIds() );
   QgsFeatureIterator fit = layer->getFeatures( req );
 
@@ -2314,7 +2468,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
     if ( r->capabilities() & QgsFeatureRendererV2::SymbolLevels
          && r->usingSymbolLevels() )
     {
-      QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, ct, errorMessage );
+      QgsVectorFileWriter::WriterError error = writer->exportFeaturesSymbolLevels( layer, fit, options.ct, errorMessage );
       delete writer;
       return ( error == NoError ) ? NoError : ErrFeatureWriteFailed;
     }
@@ -2324,9 +2478,9 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
 
   //unit type
   QGis::UnitType mapUnits = layer->crs().mapUnits();
-  if ( ct )
+  if ( options.ct )
   {
-    mapUnits = ct->destCRS().mapUnits();
+    mapUnits = options.ct->destCRS().mapUnits();
   }
 
   writer->startRender( layer );
@@ -2353,7 +2507,7 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
       {
         if ( fet.geometry() )
         {
-          fet.geometry()->transform( *ct );
+          fet.geometry()->transform( *( options.ct ) );
         }
       }
       catch ( QgsCsException &e )
@@ -2370,10 +2524,10 @@ QgsVectorFileWriter::WriterError QgsVectorFileWriter::writeAsVectorFormat( QgsVe
       }
     }
 
-    if ( fet.constGeometry() && filterExtent && !fet.constGeometry()->intersects( *filterExtent ) )
+    if ( fet.constGeometry() && !options.filterExtent.isNull() && !fet.constGeometry()->intersects( options.filterExtent ) )
       continue;
 
-    if ( attributes.size() < 1 && skipAttributeCreation )
+    if ( attributes.size() < 1 && options.skipAttributeCreation )
     {
       fet.initAttributes( 0 );
     }
@@ -2866,3 +3020,91 @@ void QgsVectorFileWriter::addRendererAttributes( QgsVectorLayer* vl, QgsAttribut
     }
   }
 }
+
+QgsVectorFileWriter::EditionCapabilities QgsVectorFileWriter::editionCapabilities( const QString& datasetName )
+{
+  OGRSFDriverH hDriver = nullptr;
+  OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
+  if ( !hDS )
+    return 0;
+  QString drvName = OGR_Dr_GetName( hDriver );
+  QgsVectorFileWriter::EditionCapabilities caps = 0;
+  if ( OGR_DS_TestCapability( hDS, ODsCCreateLayer ) )
+  {
+    // Shapefile driver returns True for a "foo.shp" dataset name,
+    // creating "bar.shp" new layer, but this would be a bit confusing
+    // for the user, so pretent that it does not support that
+    if ( !( drvName == "ESRI Shapefile" && QFile::exists( datasetName ) ) )
+      caps |= CanAddNewLayer;
+  }
+  if ( OGR_DS_TestCapability( hDS, ODsCDeleteLayer ) )
+  {
+    caps |= CanDeleteLayer;
+  }
+  int layer_count = OGR_DS_GetLayerCount( hDS );
+  if ( layer_count )
+  {
+    OGRLayerH hLayer = OGR_DS_GetLayer( hDS, 0 );
+    if ( hLayer )
+    {
+      if ( OGR_L_TestCapability( hLayer, OLCSequentialWrite ) )
+      {
+        caps |= CanAppendToExistingLayer;
+        if ( OGR_L_TestCapability( hLayer, OLCCreateField ) )
+        {
+          caps |= CanAddNewFieldsToExistingLayer;
+        }
+      }
+    }
+  }
+  OGR_DS_Destroy( hDS );
+  return caps;
+}
+
+bool QgsVectorFileWriter::targetLayerExists( const QString& datasetName,
+    const QString& layerNameIn )
+{
+  OGRSFDriverH hDriver = nullptr;
+  OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
+  if ( !hDS )
+    return false;
+
+  QString layerName( layerNameIn );
+  if ( layerName.isEmpty() )
+    layerName = QFileInfo( datasetName ).baseName();
+
+  bool ret = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
+  OGR_DS_Destroy( hDS );
+  return ret;
+}
+
+
+bool QgsVectorFileWriter::areThereNewFieldsToCreate( const QString& datasetName,
+    const QString& layerName,
+    QgsVectorLayer* layer,
+    const QgsAttributeList& attributes )
+{
+  OGRSFDriverH hDriver = nullptr;
+  OGRDataSourceH hDS = OGROpen( TO8F( datasetName ), TRUE, &hDriver );
+  if ( !hDS )
+    return false;
+  OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
+  if ( !hLayer )
+  {
+    OGR_DS_Destroy( hDS );
+    return false;
+  }
+  bool ret = false;
+  OGRFeatureDefnH defn = OGR_L_GetLayerDefn( hLayer );
+  Q_FOREACH ( int idx, attributes )
+  {
+    QgsField fld = layer->fields().at( idx );
+    if ( OGR_FD_GetFieldIndex( defn, TO8F( fld.name() ) ) < 0 )
+    {
+      ret = true;
+      break;
+    }
+  }
+  OGR_DS_Destroy( hDS );
+  return ret;
+}
diff --git a/src/core/qgsvectorfilewriter.h b/src/core/qgsvectorfilewriter.h
index 78e9862..cdcc9c3 100644
--- a/src/core/qgsvectorfilewriter.h
+++ b/src/core/qgsvectorfilewriter.h
@@ -199,6 +199,45 @@ class CORE_EXPORT QgsVectorFileWriter
         virtual QVariant convert( int fieldIdxInLayer, const QVariant& value );
     };
 
+    /** Edition capability flags
+      * @note Added in QGIS 3.0 */
+    enum EditionCapability
+    {
+      /** Flag to indicate that a new layer can be added to the dataset */
+      CanAddNewLayer                 = 1 << 0,
+
+      /** Flag to indicate that new features can be added to an existing layer */
+      CanAppendToExistingLayer       = 1 << 1,
+
+      /** Flag to indicate that new fields can be added to an existing layer. Imply CanAppendToExistingLayer */
+      CanAddNewFieldsToExistingLayer = 1 << 2,
+
+      /** Flag to indicate that an existing layer can be deleted */
+      CanDeleteLayer                 = 1 << 3
+    };
+
+    /** Combination of CanAddNewLayer, CanAppendToExistingLayer, CanAddNewFieldsToExistingLayer or CanDeleteLayer
+      * @note Added in QGIS 3.0 */
+    Q_DECLARE_FLAGS( EditionCapabilities, EditionCapability )
+
+    /** Enumeration to describe how to handle existing files
+        @note Added in QGIS 3.0
+     */
+    typedef enum
+    {
+      /** Create or overwrite file */
+      CreateOrOverwriteFile,
+
+      /** Create or overwrite layer */
+      CreateOrOverwriteLayer,
+
+      /** Append features to existing layer, but do not create new fields */
+      AppendToLayerNoNewFields,
+
+      /** Append features to existing layer, and create new fields if needed */
+      AppendToLayerAddFields
+    } ActionOnExistingFile;
+
     /** Write contents of vector layer to an (OGR supported) vector formt
      * @param layer layer to write
      * @param fileName file name to write to
@@ -286,6 +325,88 @@ class CORE_EXPORT QgsVectorFileWriter
                                             FieldValueConverter* fieldValueConverter = nullptr
                                           );
 
+
+    /** \ingroup core
+     * Options to pass to writeAsVectorFormat()
+     * @note Added in QGIS 3.0
+     */
+    class CORE_EXPORT SaveVectorOptions
+    {
+      public:
+        /** Constructor */
+        SaveVectorOptions();
+
+        /** Destructor */
+        virtual ~SaveVectorOptions();
+
+        /** OGR driver to use */
+        QString driverName;
+
+        /** Layer name. If let empty, it will be derived from the filename */
+        QString layerName;
+
+        /** Action on existing file  */
+        ActionOnExistingFile actionOnExistingFile;
+
+        /** Encoding to use */
+        QString fileEncoding;
+
+        /** Transform to reproject exported geometries with, or invalid transform
+         * for no transformation */
+        const QgsCoordinateTransform* ct;
+
+        /** Write only selected features of layer */
+        bool onlySelectedFeatures;
+
+        /** List of OGR data source creation options */
+        QStringList datasourceOptions;
+
+        /** List of OGR layer creation options */
+        QStringList layerOptions;
+
+        /** Only write geometries */
+        bool skipAttributeCreation;
+
+        /** Attributes to export (empty means all unless skipAttributeCreation is set) */
+        QgsAttributeList attributes;
+
+        /** Symbology to export */
+        SymbologyExport symbologyExport;
+
+        /** Scale of symbology */
+        double symbologyScale;
+
+        /** If not empty, only features intersecting the extent will be saved */
+        QgsRectangle filterExtent;
+
+        /** Set to a valid geometry type to override the default geometry type for the layer. This parameter
+         * allows for conversion of geometryless tables to null geometries, etc */
+        QgsWKBTypes::Type overrideGeometryType;
+
+        /** Set to true to force creation of multi* geometries */
+        bool forceMulti;
+
+        /** Set to true to include z dimension in output. This option is only valid if overrideGeometryType is set */
+        bool includeZ;
+
+        /** Field value converter */
+        FieldValueConverter* fieldValueConverter;
+    };
+
+    /** Writes a layer out to a vector file.
+     * @param layer source layer to write
+     * @param fileName file name to write to
+     * @param options options.
+     * @param newFilename QString pointer which will contain the new file name created (in case it is different to fileName).
+     * @param errorMessage pointer to buffer fo error message
+     * @note added in 3.0
+     */
+    static WriterError writeAsVectorFormat( QgsVectorLayer* layer,
+                                            const QString& fileName,
+                                            const SaveVectorOptions& options,
+                                            QString *newFilename = nullptr,
+                                            QString *errorMessage = nullptr );
+
     /** Create a new vector file writer */
     QgsVectorFileWriter( const QString& vectorFileName,
                          const QString& fileEncoding,
@@ -367,6 +488,28 @@ class CORE_EXPORT QgsVectorFileWriter
      */
     static OGRwkbGeometryType ogrTypeFromWkbType( QgsWKBTypes::Type type );
 
+    /**
+     * Return edition capabilites for an existing dataset name.
+     * @note added in QGIS 3.0
+     */
+    static EditionCapabilities editionCapabilities( const QString& datasetName );
+
+    /**
+     * Returns whether the target layer already exists.
+     * @note added in QGIS 3.0
+     */
+    static bool targetLayerExists( const QString& datasetName,
+                                   const QString& layerName );
+
+    /**
+     * Returns whether there are among the attributes specified some that do not exist yet in the layer
+     * @note added in QGIS 3.0
+     */
+    static bool areThereNewFieldsToCreate( const QString& datasetName,
+                                           const QString& layerName,
+                                           QgsVectorLayer* layer,
+                                           const QgsAttributeList& attributes );
+
   protected:
     //! @note not available in python bindings
     OGRGeometryH createEmptyGeometry( QgsWKBTypes::Type wkbType );
@@ -418,6 +561,8 @@ class CORE_EXPORT QgsVectorFileWriter
      * @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,
@@ -429,14 +574,18 @@ class CORE_EXPORT QgsVectorFileWriter
                          const QStringList &layerOptions,
                          QString *newFilename,
                          SymbologyExport symbologyExport,
-                         FieldValueConverter* fieldValueConverter
+                         FieldValueConverter* fieldValueConverter,
+                         const QString& layerName,
+                         ActionOnExistingFile action
                        );
 
     void init( QString vectorFileName, QString fileEncoding, const QgsFields& fields,
                QgsWKBTypes::Type geometryType, const QgsCoordinateReferenceSystem* srs,
                const QString& driverName, QStringList datasourceOptions,
                QStringList layerOptions, QString* newFilename,
-               FieldValueConverter* fieldValueConverter );
+               FieldValueConverter* fieldValueConverter,
+               const QString& layerName,
+               ActionOnExistingFile action );
     void resetMap( const QgsAttributeList &attributes );
 
     QgsRenderContext mRenderContext;
@@ -462,4 +611,6 @@ class CORE_EXPORT QgsVectorFileWriter
     QgsVectorFileWriter& operator=( const QgsVectorFileWriter& rh );
 };
 
+Q_DECLARE_OPERATORS_FOR_FLAGS( QgsVectorFileWriter::EditionCapabilities )
+
 #endif
diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp
index 5fac29c..def0bf1 100644
--- a/src/core/qgsvectorlayer.cpp
+++ b/src/core/qgsvectorlayer.cpp
@@ -4394,7 +4394,7 @@ QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &theResultFl
 QString QgsVectorLayer::loadNamedStyle( const QString &theURI, bool &theResultFlag, bool loadFromLocalDB )
 {
   QgsDataSourceURI dsUri( theURI );
-  if ( !loadFromLocalDB && !dsUri.database().isEmpty() )
+  if ( !loadFromLocalDB && mDataProvider && mDataProvider->isSaveAndLoadStyleToDBSupported() )
   {
     QgsProviderRegistry * pReg = QgsProviderRegistry::instance();
     QLibrary *myLib = pReg->providerLibrary( mProviderKey );
diff --git a/src/core/qgsvectorlayercache.cpp b/src/core/qgsvectorlayercache.cpp
index ed3698e..69b28f9 100644
--- a/src/core/qgsvectorlayercache.cpp
+++ b/src/core/qgsvectorlayercache.cpp
@@ -174,12 +174,16 @@ QgsVectorLayer* QgsVectorLayerCache::layer()
 void QgsVectorLayerCache::requestCompleted( const QgsFeatureRequest& featureRequest, const QgsFeatureIds& fids )
 {
   // If a request is too large for the cache don't notify to prevent from indexing incomplete requests
-  if ( fids.count() < mCache.size() )
+  if ( fids.count() <= mCache.size() )
   {
     Q_FOREACH ( QgsAbstractCacheIndex* idx, mCacheIndices )
     {
       idx->requestCompleted( featureRequest, fids );
     }
+    if ( featureRequest.filterType() == QgsFeatureRequest::FilterNone )
+    {
+      mFullCache = true;
+    }
   }
 }
 
@@ -266,6 +270,54 @@ void QgsVectorLayerCache::invalidate()
   emit invalidated();
 }
 
+bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featureRequest, QgsFeatureIterator& it )
+{
+  // check first for available indices
+  Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
+  {
+    if ( idx->getCacheIterator( it, featureRequest ) )
+    {
+      return true;
+    }
+  }
+
+  // no indexes available, but maybe we have already cached all required features anyway?
+  switch ( featureRequest.filterType() )
+  {
+    case QgsFeatureRequest::FilterFid:
+    {
+      if ( mCache.contains( featureRequest.filterFid() ) )
+      {
+        it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
+        return true;
+      }
+      break;
+    }
+    case QgsFeatureRequest::FilterFids:
+    {
+      if ( mCache.keys().toSet().contains( featureRequest.filterFids() ) )
+      {
+        it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
+        return true;
+      }
+      break;
+    }
+    case QgsFeatureRequest::FilterNone:
+    case QgsFeatureRequest::FilterRect:
+    case QgsFeatureRequest::FilterExpression:
+    {
+      if ( mFullCache )
+      {
+        it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
+        return true;
+      }
+      break;
+    }
+
+  }
+  return false;
+}
+
 QgsFeatureIterator QgsVectorLayerCache::getFeatures( const QgsFeatureRequest &featureRequest )
 {
   QgsFeatureIterator it;
@@ -281,15 +333,8 @@ QgsFeatureIterator QgsVectorLayerCache::getFeatures( const QgsFeatureRequest &fe
     }
     else
     {
-      // Check if an index is able to deliver the requested features
-      Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
-      {
-        if ( idx->getCacheIterator( it, featureRequest ) )
-        {
-          requiresWriterIt = false;
-          break;
-        }
-      }
+      // may still be able to satisfy request using cache
+      requiresWriterIt = !canUseCacheForRequest( featureRequest, it );
     }
   }
   else
@@ -319,7 +364,7 @@ QgsFeatureIterator QgsVectorLayerCache::getFeatures( const QgsFeatureRequest &fe
   return it;
 }
 
-bool QgsVectorLayerCache::isFidCached( const QgsFeatureId fid )
+bool QgsVectorLayerCache::isFidCached( const QgsFeatureId fid ) const
 {
   return mCache.contains( fid );
 }
diff --git a/src/core/qgsvectorlayercache.h b/src/core/qgsvectorlayercache.h
index 66bf050..6ff9c21 100644
--- a/src/core/qgsvectorlayercache.h
+++ b/src/core/qgsvectorlayercache.h
@@ -131,9 +131,18 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
      * be used for slow data sources, be aware, that the call to this method might take a long time.
      *
      * @param fullCache   True: enable full caching, False: disable full caching
+     * @see hasFullCache()
      */
     void setFullCache( bool fullCache );
 
+    /** Returns true if the cache is complete, ie it contains all features. This may happen as
+     * a result of a call to setFullCache() or by through a feature request which resulted in
+     * all available features being cached.
+     * @see setFullCache()
+     * @note added in QGIS 3.0
+     */
+    bool hasFullCache() const { return mFullCache; }
+
     /**
      * @brief
      * Adds a {@link QgsAbstractCacheIndex} to this cache. Cache indices know about features present
@@ -159,8 +168,15 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
      * Check if a certain feature id is cached.
      * @param  fid The feature id to look for
      * @return True if this id is in the cache
+     * @see cachedFeatureIds()
+     */
+    bool isFidCached( const QgsFeatureId fid ) const;
+
+    /** Returns the set of feature IDs for features which are cached.
+     * @note added in QGIS 3.0
+     * @see isFidCached()
      */
-    bool isFidCached( const QgsFeatureId fid );
+    QgsFeatureIds cachedFeatureIds() const { return mCache.keys().toSet(); }
 
     /**
      * Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features
@@ -291,5 +307,16 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
     friend class QgsCachedFeatureIterator;
     friend class QgsCachedFeatureWriterIterator;
     friend class QgsCachedFeature;
+
+    /** Returns true if the cache contains all the features required for a specified request.
+     * @param featureRequest feature request
+     * @param it will be set to iterator for matching features
+     * @returns true if cache can satisfy request
+     * @note this method only checks for available features, not whether the cache
+     * contains required attributes or geometry. For that, use checkInformationCovered()
+     */
+    bool canUseCacheForRequest( const QgsFeatureRequest& featureRequest, QgsFeatureIterator& it );
+
+    friend class TestVectorLayerCache;
 };
 #endif // QgsVectorLayerCache_H
diff --git a/src/core/qgsvectorlayerfeatureiterator.cpp b/src/core/qgsvectorlayerfeatureiterator.cpp
index 6648834..cd8492c 100644
--- a/src/core/qgsvectorlayerfeatureiterator.cpp
+++ b/src/core/qgsvectorlayerfeatureiterator.cpp
@@ -161,6 +161,8 @@ QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeat
       if ( source->mFields.fieldOrigin( idx ) != QgsFields::OriginProvider )
       {
         mProviderRequest.disableFilter();
+        // can't limit at provider side
+        mProviderRequest.setLimit( -1 );
       }
     }
   }
diff --git a/src/core/qgsvectorlayerrenderer.cpp b/src/core/qgsvectorlayerrenderer.cpp
index 0e19bab..c643f67 100644
--- a/src/core/qgsvectorlayerrenderer.cpp
+++ b/src/core/qgsvectorlayerrenderer.cpp
@@ -324,7 +324,7 @@ void QgsVectorLayerRenderer::drawRendererV2( QgsFeatureIterator& fit )
           }
         }
         // new labeling engine
-        if ( mContext.labelingEngineV2() )
+        if ( mContext.labelingEngineV2() && ( mLabelProvider || mDiagramProvider ) )
         {
           QScopedPointer<QgsGeometry> obstacleGeometry;
           QgsSymbolV2List symbols = mRendererV2->originalSymbolsForFeature( fet, mContext );
diff --git a/src/core/qgswebpage.h b/src/core/qgswebpage.h
index 57a1d5a..ac2cf3d 100644
--- a/src/core/qgswebpage.h
+++ b/src/core/qgswebpage.h
@@ -181,6 +181,10 @@ class CORE_EXPORT QWebPage : public QObject
       return new QMenu();
     }
 
+    void setForwardUnsupportedContent( bool )
+    {
+    }
+
   signals:
 
     void loadFinished( bool ok );
diff --git a/src/core/raster/qgsrasterprojector.cpp b/src/core/raster/qgsrasterprojector.cpp
index 4bf3f50..ada3af6 100644
--- a/src/core/raster/qgsrasterprojector.cpp
+++ b/src/core/raster/qgsrasterprojector.cpp
@@ -918,7 +918,6 @@ QgsRasterBlock * QgsRasterProjector::block2( int bandNo, QgsRectangle  const & e
       if ( !inside ) continue; // we have everything set to no data
 
       qgssize srcIndex = static_cast< qgssize >( srcRow ) * pd.srcCols() + srcCol;
-      QgsDebugMsgLevel( QString( "row = %1 col = %2 srcRow = %3 srcCol = %4" ).arg( i ).arg( j ).arg( srcRow ).arg( srcCol ), 5 );
 
       // isNoData() may be slow so we check doNoData first
       if ( doNoData && inputBlock->isNoData( srcRow, srcCol ) )
diff --git a/src/core/symbology-ng/qgsarrowsymbollayer.cpp b/src/core/symbology-ng/qgsarrowsymbollayer.cpp
index 7893566..0699e62 100644
--- a/src/core/symbology-ng/qgsarrowsymbollayer.cpp
+++ b/src/core/symbology-ng/qgsarrowsymbollayer.cpp
@@ -691,8 +691,8 @@ void QgsArrowSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbolV2Re
   }
 
   context.renderContext().expressionContext().appendScope( mExpressionScope.data() );
-  mExpressionScope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1 );
-  mExpressionScope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, 1 );
+  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() + 1, true ) );
+  mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, 1, true ) );
   if ( isCurved() )
   {
     _resolveDataDefined( context );
@@ -727,7 +727,7 @@ void QgsArrowSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbolV2Re
     {
       for ( int pIdx = 0; pIdx < points.size() - 1; pIdx += 2 )
       {
-        mExpressionScope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1 );
+        mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
         _resolveDataDefined( context );
 
         if ( points.size() - pIdx >= 3 )
@@ -778,7 +778,7 @@ void QgsArrowSymbolLayer::renderPolyline( const QPolygonF& points, QgsSymbolV2Re
       // only straight arrows
       for ( int pIdx = 0; pIdx < points.size() - 1; pIdx++ )
       {
-        mExpressionScope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1 );
+        mExpressionScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, pIdx + 1, true ) );
         _resolveDataDefined( context );
 
         // origin point
diff --git a/src/core/symbology-ng/qgslinesymbollayerv2.cpp b/src/core/symbology-ng/qgslinesymbollayerv2.cpp
index 7df7c38..0f56fd0 100644
--- a/src/core/symbology-ng/qgslinesymbollayerv2.cpp
+++ b/src/core/symbology-ng/qgslinesymbollayerv2.cpp
@@ -1010,7 +1010,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineInterval( const QPolygonF& points
       // "c" is 1 for regular point or in interval (0,1] for begin of line segment
       lastPt += c * diff;
       lengthLeft -= painterUnitInterval;
-      scope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum );
+      scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
       mMarker->renderPoint( lastPt, context.feature(), rc, -1, context.selected() );
       c = 1; // reset c (if wasn't 1 already)
     }
@@ -1044,7 +1044,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
 
   QgsExpressionContextScope* scope = new QgsExpressionContextScope();
   context.renderContext().expressionContext().appendScope( scope );
-  scope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size() );
+  scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_COUNT, points.size(), true ) );
 
   double offsetAlongLine = mOffsetAlongLine;
   if ( hasDataDefinedProperty( QgsSymbolLayerV2::EXPR_OFFSET_ALONG_LINE ) )
@@ -1071,7 +1071,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
     int pointNum = 0;
     while ( context.renderContext().geometry()->nextVertex( vId, vPoint ) )
     {
-      scope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum );
+      scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
 
       if (( placement == Vertex && vId.type == QgsVertexId::SegmentVertex )
           || ( placement == CurvePoint && vId.type == QgsVertexId::CurveVertex ) )
@@ -1137,7 +1137,7 @@ void QgsMarkerLineSymbolLayerV2::renderPolylineVertex( const QPolygonF& points,
   int pointNum = 0;
   for ( ; i < maxCount; ++i )
   {
-    scope->setVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum );
+    scope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_POINT_NUM, ++pointNum, true ) );
 
     if ( isRing && placement == Vertex && i == points.count() - 1 )
     {
diff --git a/src/core/symbology-ng/qgssymbolv2.cpp b/src/core/symbology-ng/qgssymbolv2.cpp
index 46e33e1..f633ab6 100644
--- a/src/core/symbology-ng/qgssymbolv2.cpp
+++ b/src/core/symbology-ng/qgssymbolv2.cpp
@@ -732,8 +732,8 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
   {
     context.expressionContext().appendScope( mSymbolRenderContext->expressionContextScope() );
     QgsExpressionContextUtils::updateSymbolScope( this, mSymbolRenderContext->expressionContextScope() );
-    mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount() );
-    mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1 );
+    mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, mSymbolRenderContext->geometryPartCount(), true ) );
+    mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, 1, true ) );
   }
 
   // Collection of markers to paint, only used for no curve types.
@@ -831,7 +831,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
       for ( int i = 0; i < mp->numGeometries(); ++i )
       {
         mSymbolRenderContext->setGeometryPartNum( i + 1 );
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         const QgsPointV2* point = static_cast< const QgsPointV2* >( mp->geometryN( i ) );
         _getPoint( pt, context, point );
@@ -867,7 +867,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
       for ( unsigned int i = 0; i < num && wkbPtr; ++i )
       {
         mSymbolRenderContext->setGeometryPartNum( i + 1 );
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         if ( geomCollection )
         {
@@ -917,7 +917,7 @@ void QgsSymbolV2::renderFeature( const QgsFeature& feature, QgsRenderContext& co
       for ( unsigned int i = 0; i < num && wkbPtr; ++i )
       {
         mSymbolRenderContext->setGeometryPartNum( i + 1 );
-        mSymbolRenderContext->expressionContextScope()->setVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1 );
+        mSymbolRenderContext->expressionContextScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_NUM, i + 1, true ) );
 
         if ( geomCollection )
         {
diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt
index 0814ed6..a526b37 100644
--- a/src/gui/CMakeLists.txt
+++ b/src/gui/CMakeLists.txt
@@ -307,6 +307,7 @@ SET(QGIS_GUI_SRCS
   qgsuserinputdockwidget.cpp
   qgsvariableeditorwidget.cpp
   qgsvertexmarker.cpp
+  qgsfiledownloader.cpp
 )
 
 IF (WITH_QTWEBKIT)
@@ -454,6 +455,7 @@ SET(QGIS_GUI_MOC_HDRS
   qgsunitselectionwidget.h
   qgsuserinputdockwidget.h
   qgsvariableeditorwidget.h
+  qgsfiledownloader.h
 
   raster/qgsmultibandcolorrendererwidget.h
   raster/qgspalettedrendererwidget.h
@@ -648,6 +650,7 @@ SET(QGIS_GUI_HDRS
   qgsuserinputdockwidget.h
   qgsvectorlayertools.h
   qgsvertexmarker.h
+  qgsfiledownloader.h
 
   attributetable/qgsfeaturemodel.h
 
diff --git a/src/gui/attributetable/qgsattributetablefiltermodel.cpp b/src/gui/attributetable/qgsattributetablefiltermodel.cpp
index 7ed131c..1051aff 100644
--- a/src/gui/attributetable/qgsattributetablefiltermodel.cpp
+++ b/src/gui/attributetable/qgsattributetablefiltermodel.cpp
@@ -292,11 +292,6 @@ void QgsAttributeTableFilterModel::setFilterMode( FilterMode filterMode )
       disconnect( mCanvas, SIGNAL( extentsChanged() ), this, SLOT( extentsChanged() ) );
     }
 
-    if ( filterMode == ShowSelected )
-    {
-      generateListOfVisibleFeatures();
-    }
-
     mFilterMode = filterMode;
     invalidateFilter();
   }
@@ -350,7 +345,6 @@ void QgsAttributeTableFilterModel::selectionChanged()
 {
   if ( ShowSelected == mFilterMode )
   {
-    generateListOfVisibleFeatures();
     invalidateFilter();
   }
   else if ( mSelectedOnTop )
diff --git a/src/gui/attributetable/qgsdualview.cpp b/src/gui/attributetable/qgsdualview.cpp
index f56292b..97f788d 100644
--- a/src/gui/attributetable/qgsdualview.cpp
+++ b/src/gui/attributetable/qgsdualview.cpp
@@ -732,9 +732,7 @@ void QgsDualView::progress( int i, bool& cancel )
     mProgressDlg->show();
   }
 
-  mProgressDlg->setValue( i );
   mProgressDlg->setLabelText( tr( "%1 features loaded." ).arg( i ) );
-
   QCoreApplication::processEvents();
 
   cancel = mProgressDlg && mProgressDlg->wasCanceled();
diff --git a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
index c5df6b8..b11ed69 100644
--- a/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
+++ b/src/gui/attributetable/qgsfieldconditionalformatwidget.cpp
@@ -77,7 +77,7 @@ void QgsFieldConditionalFormatWidget::setExpression()
   context << QgsExpressionContextUtils::globalScope()
   << QgsExpressionContextUtils::projectScope()
   << QgsExpressionContextUtils::layerScope( mLayer );
-  context.lastScope()->setVariable( "value", 0 );
+  context.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QString( "value" ), 0, true ) );
   context.setHighlightedVariables( QStringList() << "value" );
 
   QgsExpressionBuilderDialog dlg( mLayer, mRuleEdit->text(), this, "generic", context );
diff --git a/src/gui/qgisgui.h b/src/gui/qgisgui.h
index 1f7fa95..b3d5dd2 100644
--- a/src/gui/qgisgui.h
+++ b/src/gui/qgisgui.h
@@ -25,6 +25,7 @@ class QFont;
 /** \ingroup gui
  * /namespace QgisGui
  * The QgisGui namespace contains constants and helper functions used throughout the QGIS GUI.
+ * \note not available in Python bindings
  */
 namespace QgisGui
 {
@@ -49,6 +50,21 @@ namespace QgisGui
   static const Qt::WindowFlags ModalDialogFlags = nullptr;
 
   /**
+   * Minimum magnification level allowed in map canvases.
+   * @see CANVAS_MAGNIFICATION_MAX
+   * @note added in QGIS 3.0
+   */
+  constexpr double CANVAS_MAGNIFICATION_MIN = 0.1;
+
+  /**
+   * Maximum magnification level allowed in map canvases.
+   * @see CANVAS_MAGNIFICATION_MAX
+   * @note added in QGIS 3.0
+   */
+  // Must be a factor of 2, so zooming in to max from 100% then zooming back out will result in 100% mag
+  constexpr double CANVAS_MAGNIFICATION_MAX = 16.0;
+
+  /**
     Open files, preferring to have the default file selector be the
     last one used, if any; also, prefer to start in the last directory
     associated with filterName.
diff --git a/src/gui/qgsattributedialog.cpp b/src/gui/qgsattributedialog.cpp
index e9c6807..6a510fa 100644
--- a/src/gui/qgsattributedialog.cpp
+++ b/src/gui/qgsattributedialog.cpp
@@ -81,11 +81,8 @@ void QgsAttributeDialog::accept()
   QDialog::accept();
 }
 
-void QgsAttributeDialog::show( bool autoDelete )
+void QgsAttributeDialog::show()
 {
-  if ( autoDelete )
-    setAttribute( Qt::WA_DeleteOnClose );
-
   QDialog::show();
   raise();
   activateWindow();
diff --git a/src/gui/qgsattributedialog.h b/src/gui/qgsattributedialog.h
index b0601c0..f40b285 100644
--- a/src/gui/qgsattributedialog.h
+++ b/src/gui/qgsattributedialog.h
@@ -139,9 +139,8 @@ class GUI_EXPORT QgsAttributeDialog : public QDialog
     void accept() override;
     void reject() override;
 
-    //! Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form and is deleted when
-    //! closed.
-    void show( bool autoDelete = true );
+    //! Show the dialog non-blocking. Reparents this dialog to be a child of the dialog form
+    void show();
 
   private:
     void init( QgsVectorLayer* layer, QgsFeature* feature, const QgsAttributeEditorContext& context, bool showDialogButtons );
diff --git a/src/gui/qgscolorbuttonv2.cpp b/src/gui/qgscolorbuttonv2.cpp
index 332dcc3..349ed11 100644
--- a/src/gui/qgscolorbuttonv2.cpp
+++ b/src/gui/qgscolorbuttonv2.cpp
@@ -94,12 +94,19 @@ const QPixmap& QgsColorButtonV2::transparentBackground()
 
 void QgsColorButtonV2::showColorDialog()
 {
+  QColor currentColor = color();
   if ( QgsPanelWidget* panel = QgsPanelWidget::findParentPanel( this ) )
   {
-    QgsCompoundColorWidget* colorWidget = new QgsCompoundColorWidget( panel, color(), panel->dockMode() ? QgsCompoundColorWidget::LayoutVertical :
+    QgsCompoundColorWidget* colorWidget = new QgsCompoundColorWidget( panel, currentColor, panel->dockMode() ? QgsCompoundColorWidget::LayoutVertical :
         QgsCompoundColorWidget::LayoutDefault );
     colorWidget->setPanelTitle( mColorDialogTitle );
     colorWidget->setAllowAlpha( mAllowAlpha );
+
+    if ( currentColor.isValid() )
+    {
+      colorWidget->setPreviousColor( currentColor );
+    }
+
     connect( colorWidget, SIGNAL( currentColorChanged( QColor ) ), this, SLOT( setValidTemporaryColor( QColor ) ) );
     connect( colorWidget, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( panelAccepted( QgsPanelWidget* ) ) );
     panel->openPanel( colorWidget );
@@ -393,6 +400,7 @@ void QgsColorButtonV2::panelAccepted( QgsPanelWidget* widget )
   if ( QgsCompoundColorWidget* colorWidget = qobject_cast< QgsCompoundColorWidget* >( widget ) )
   {
     addRecentColor( colorWidget->color() );
+    colorWidget->deleteLater();
   }
 }
 
diff --git a/src/gui/qgsextentgroupbox.h b/src/gui/qgsextentgroupbox.h
index dd7b794..b990cc7 100644
--- a/src/gui/qgsextentgroupbox.h
+++ b/src/gui/qgsextentgroupbox.h
@@ -84,7 +84,7 @@ class GUI_EXPORT QgsExtentGroupBox : public QgsCollapsibleGroupBox, private Ui::
     //! set output extent to be the same as current extent (may be transformed to output CRS)
     void setOutputExtentFromCurrent();
 
-    //! set output extent to custom extent (may be transformed to outut CRS)
+    //! set output extent to custom extent (may be transformed to output CRS)
     void setOutputExtentFromUser( const QgsRectangle& extent, const QgsCoordinateReferenceSystem& crs );
 
   signals:
diff --git a/src/gui/qgsfiledownloader.cpp b/src/gui/qgsfiledownloader.cpp
new file mode 100644
index 0000000..36cde63
--- /dev/null
+++ b/src/gui/qgsfiledownloader.cpp
@@ -0,0 +1,203 @@
+/***************************************************************************
+  qgsfiledownloader.cpp
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#include "qgsfiledownloader.h"
+#include "qgsnetworkaccessmanager.h"
+
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QMessageBox>
+#ifndef QT_NO_OPENSSL
+#include <QSslError>
+#endif
+
+QgsFileDownloader::QgsFileDownloader( QUrl url, QString outputFileName, bool enableGuiNotifications )
+    : mUrl( url )
+    , mReply( nullptr )
+    , mProgressDialog( nullptr )
+    , mDownloadCanceled( false )
+    , mErrors()
+    , mGuiNotificationsEnabled( enableGuiNotifications )
+{
+  mFile.setFileName( outputFileName );
+  startDownload();
+}
+
+
+QgsFileDownloader::~QgsFileDownloader()
+{
+  if ( mReply )
+  {
+    mReply->abort();
+    mReply->deleteLater();
+  }
+  if ( mProgressDialog )
+  {
+    mProgressDialog->deleteLater();
+  }
+}
+
+
+void QgsFileDownloader::startDownload()
+{
+  QgsNetworkAccessManager* nam = QgsNetworkAccessManager::instance();
+
+  QNetworkRequest request( mUrl );
+
+  mReply = nam->get( request );
+
+  connect( mReply, SIGNAL( readyRead() ), this, SLOT( onReadyRead() ) );
+  connect( mReply, SIGNAL( error( QNetworkReply::NetworkError ) ), this, SLOT( onNetworkError( QNetworkReply::NetworkError ) ) );
+  connect( mReply, SIGNAL( finished() ), this, SLOT( onFinished() ) );
+  connect( mReply, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( onDownloadProgress( qint64, qint64 ) ) );
+  connect( nam, SIGNAL( requestTimedOut( QNetworkReply* ) ), this, SLOT( onRequestTimedOut() ) );
+#ifndef QT_NO_OPENSSL
+  connect( nam, SIGNAL( sslErrors( QNetworkReply*, QList<QSslError> ) ), this, SLOT( onSslErrors( QNetworkReply*, QList<QSslError> ) ) );
+#endif
+  if ( mGuiNotificationsEnabled )
+  {
+    mProgressDialog = new QProgressDialog();
+    mProgressDialog->setWindowTitle( tr( "Download" ) );
+    mProgressDialog->setLabelText( tr( "Downloading %1." ).arg( mFile.fileName() ) );
+    mProgressDialog->show();
+    connect( mProgressDialog, SIGNAL( canceled() ), this, SLOT( onDownloadCanceled() ) );
+  }
+}
+
+void QgsFileDownloader::onDownloadCanceled()
+{
+  mDownloadCanceled = true;
+  emit downloadCanceled();
+  onFinished();
+}
+
+void QgsFileDownloader::onRequestTimedOut()
+{
+  error( tr( "Network request %1 timed out" ).arg( mUrl.toString() ) );
+}
+
+#ifndef QT_NO_OPENSSL
+void QgsFileDownloader::onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors )
+{
+  Q_UNUSED( reply );
+  QStringList errorMessages;
+  errorMessages <<  "SSL Errors: ";
+  for ( int end = errors.size(), i = 0; i != end; ++i )
+  {
+    errorMessages << errors[i].errorString();
+  }
+  error( errorMessages );
+}
+#endif
+
+
+void QgsFileDownloader::error( QStringList errorMessages )
+{
+  for ( int end = errorMessages.size(), i = 0; i != end; ++i )
+  {
+    mErrors.append( errorMessages[i] );
+  }
+  // Show error
+  if ( mGuiNotificationsEnabled )
+  {
+    QMessageBox::warning( nullptr, tr( "Download failed" ), mErrors.join( "<br>" ) );
+  }
+  emit downloadError( mErrors );
+}
+
+void QgsFileDownloader::error( QString errorMessage )
+{
+  error( QStringList() << errorMessage );
+}
+
+void QgsFileDownloader::onReadyRead()
+{
+  Q_ASSERT( mReply );
+  if ( ! mFile.isOpen() && ! mFile.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
+  {
+    error( tr( "Cannot open output file: %1" ).arg( mFile.fileName() ) );
+    onFinished();
+  }
+  else
+  {
+    QByteArray data = mReply->readAll();
+    mFile.write( data );
+  }
+}
+
+void QgsFileDownloader::onFinished()
+{
+  // when canceled
+  if ( ! mErrors.isEmpty() || mDownloadCanceled )
+  {
+    mFile.close();
+    mFile.remove();
+    if ( mGuiNotificationsEnabled )
+      mProgressDialog->hide();
+  }
+  else
+  {
+    // download finished normally
+    if ( mGuiNotificationsEnabled )
+      mProgressDialog->hide();
+    mFile.flush();
+    mFile.close();
+
+    // get redirection url
+    QVariant redirectionTarget = mReply->attribute( QNetworkRequest::RedirectionTargetAttribute );
+    if ( mReply->error() )
+    {
+      mFile.remove();
+      error( tr( "Download failed: %1." ).arg( mReply->errorString() ) );
+    }
+    else if ( !redirectionTarget.isNull() )
+    {
+      QUrl newUrl = mUrl.resolved( redirectionTarget.toUrl() );
+      mUrl = newUrl;
+      mReply->deleteLater();
+      mFile.open( QIODevice::WriteOnly );
+      mFile.resize( 0 );
+      mFile.close();
+      startDownload();
+      return;
+    }
+    // All done
+    emit downloadCompleted();
+  }
+  emit downloadExited();
+  this->deleteLater();
+}
+
+void QgsFileDownloader::onNetworkError( QNetworkReply::NetworkError err )
+{
+  Q_ASSERT( mReply );
+  error( QString( "Network error %1: %2" ).arg( err ).arg( mReply->errorString() ) );
+}
+
+void QgsFileDownloader::onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal )
+{
+  if ( mDownloadCanceled )
+  {
+    return;
+  }
+  if ( mGuiNotificationsEnabled )
+  {
+    mProgressDialog->setMaximum( bytesTotal );
+    mProgressDialog->setValue( bytesReceived );
+  }
+  emit downloadProgress( bytesReceived, bytesTotal );
+}
+
diff --git a/src/gui/qgsfiledownloader.h b/src/gui/qgsfiledownloader.h
new file mode 100644
index 0000000..62e6d29
--- /dev/null
+++ b/src/gui/qgsfiledownloader.h
@@ -0,0 +1,112 @@
+/***************************************************************************
+  qgsfiledownloader.h
+  --------------------------------------
+  Date                 : November 2016
+  Copyright            : (C) 2016 by Alessandro Pasotti
+  Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+#ifndef QGSFILEDOWNLOADER_H
+#define QGSFILEDOWNLOADER_H
+
+#include <QObject>
+#include <QFile>
+#include <QNetworkReply>
+#include <QProgressDialog>
+#ifndef QT_NO_OPENSSL
+#include <QSslError>
+#endif
+
+/** \ingroup gui
+ * QgsFileDownloader is a utility class for downloading files.
+ *
+ * To use this class, it is necessary to pass the URL and an output file name as
+ * arguments to the constructor, the download will start immediately.
+ * The download is asynchronous and depending on the guiNotificationsEnabled
+ * parameter accepted by the constructor (default = true) the class will
+ * show a progress dialog and report all errors in a QMessageBox::warning dialog.
+ * If the guiNotificationsEnabled parameter is set to false, the class can still
+ * be used through the signals and slots mechanism.
+ * The object will destroy itself when the request completes, errors or is canceled.
+ *
+ * @note added in QGIS 2.18.1
+ */
+class GUI_EXPORT QgsFileDownloader : public QObject
+{
+    Q_OBJECT
+  public:
+    /**
+     * QgsFileDownloader
+     * @param url the download url
+     * @param outputFileName file name where the downloaded content will be stored
+     * @param guiNotificationsEnabled if false, the downloader will not display any progress bar or error message
+     */
+    QgsFileDownloader( QUrl url, QString outputFileName, bool guiNotificationsEnabled = true );
+
+  signals:
+    /** Emitted when the download has completed successfully */
+    void downloadCompleted();
+    /** Emitted always when the downloader exits  */
+    void downloadExited();
+    /** Emitted when the download was canceled by the user */
+    void downloadCanceled();
+    /** Emitted when an error makes the download fail  */
+    void downloadError( QStringList errorMessages );
+    /** Emitted when data ready to be processed */
+    void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
+
+  public slots:
+    /**
+     * Called when a download is canceled by the user
+     * this slot aborts the download and deletes
+     * the object
+     */
+    void onDownloadCanceled();
+
+  private slots:
+    /** Called when the network reply data are ready */
+    void onReadyRead();
+    /** Called when the network reply has finished */
+    void onFinished();
+    /** Called on Network Error */
+    void onNetworkError( QNetworkReply::NetworkError err );
+    /** Called on data ready to be processed */
+    void onDownloadProgress( qint64 bytesReceived, qint64 bytesTotal );
+    /** Called when a network request times out  */
+    void onRequestTimedOut();
+    /** Called to start the download */
+    void startDownload();
+#ifndef QT_NO_OPENSSL
+    /**
+     * Called on SSL network Errors
+     * @param reply
+     * @param errors
+     */
+    void onSslErrors( QNetworkReply *reply, const QList<QSslError> &errors );
+#endif
+
+  private:
+    ~QgsFileDownloader();
+    /**
+     * Abort current request and show an error if the instance has GUI
+     * notifications enabled.
+     */
+    void error( QStringList errorMessages );
+    void error( QString errorMessage );
+    QUrl mUrl;
+    QNetworkReply* mReply;
+    QFile mFile;
+    QProgressDialog* mProgressDialog;
+    bool mDownloadCanceled;
+    QStringList mErrors;
+    bool mGuiNotificationsEnabled;
+};
+
+#endif // QGSFILEDOWNLOADER_H
diff --git a/src/gui/qgsmapcanvas.cpp b/src/gui/qgsmapcanvas.cpp
index 4f13ca0..f1009a4 100644
--- a/src/gui/qgsmapcanvas.cpp
+++ b/src/gui/qgsmapcanvas.cpp
@@ -329,9 +329,8 @@ QgsMapCanvas::~QgsMapCanvas()
 void QgsMapCanvas::setMagnificationFactor( double factor )
 {
   // do not go higher or lower than min max magnification ratio
-  QSettings settings;
-  double magnifierMin = settings.value( "/qgis/magnifier_factor_min", 0.1 ).toDouble();
-  double magnifierMax = settings.value( "/qgis/magnifier_factor_max", 10 ).toDouble();
+  double magnifierMin = QgisGui::CANVAS_MAGNIFICATION_MIN;
+  double magnifierMax = QgisGui::CANVAS_MAGNIFICATION_MAX;
   factor = qBound( magnifierMin, factor, magnifierMax );
 
   // the magnifier widget is in integer percent
diff --git a/src/gui/symbology-ng/qgsrulebasedrendererv2widget.cpp b/src/gui/symbology-ng/qgsrulebasedrendererv2widget.cpp
index 5e7bf17..93f3be6 100644
--- a/src/gui/symbology-ng/qgsrulebasedrendererv2widget.cpp
+++ b/src/gui/symbology-ng/qgsrulebasedrendererv2widget.cpp
@@ -178,7 +178,6 @@ void QgsRuleBasedRendererV2Widget::editRule( const QModelIndex& index )
   QgsRuleBasedRendererV2::Rule* rule = mModel->ruleForIndex( index );
 
   QgsRendererRulePropsWidget* widget = new QgsRendererRulePropsWidget( rule, mLayer, mStyle, this, mMapCanvas );
-  widget->setDockMode( true );
   widget->setPanelTitle( tr( "Edit rule" ) );
   connect( widget, SIGNAL( panelAccepted( QgsPanelWidget* ) ), this, SLOT( ruleWidgetPanelAccepted( QgsPanelWidget* ) ) );
   connect( widget, SIGNAL( widgetChanged() ), this, SLOT( liveUpdateRuleFromPanel() ) );
@@ -799,7 +798,7 @@ void QgsRendererRulePropsWidget::apply()
 void QgsRendererRulePropsWidget::setDockMode( bool dockMode )
 {
   QgsPanelWidget::setDockMode( dockMode );
-  mSymbolSelector->setDockMode( true );
+  mSymbolSelector->setDockMode( dockMode );
 }
 
 ////////
diff --git a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
index 9966053..4865f04 100644
--- a/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
+++ b/src/gui/symbology-ng/qgssymbollayerv2widget.cpp
@@ -81,7 +81,7 @@ static QgsExpressionContext _getExpressionContext( const void* context )
   {
     //cheat a bit - set the symbol color variable to match the symbol layer's color (when we should really be using the *symbols*
     //color, but that's not accessible here). 99% of the time these will be the same anyway
-    symbolScope->setVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbolLayer->color() );
+    symbolScope->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_SYMBOL_COLOR, symbolLayer->color(), true ) );
   }
   expContext << symbolScope;
   expContext.lastScope()->addVariable( QgsExpressionContextScope::StaticVariable( QgsExpressionContext::EXPR_GEOMETRY_PART_COUNT, 1, true ) );
diff --git a/src/providers/memory/qgsmemoryprovider.cpp b/src/providers/memory/qgsmemoryprovider.cpp
index d4a2edf..70bc960 100644
--- a/src/providers/memory/qgsmemoryprovider.cpp
+++ b/src/providers/memory/qgsmemoryprovider.cpp
@@ -331,15 +331,14 @@ bool QgsMemoryProvider::addFeatures( QgsFeatureList & flist )
   // TODO: sanity checks of fields and geometries
   for ( QgsFeatureList::iterator it = flist.begin(); it != flist.end(); ++it )
   {
-    mFeatures[mNextFeatureId] = *it;
-    QgsFeature& newfeat = mFeatures[mNextFeatureId];
-    newfeat.setFeatureId( mNextFeatureId );
-    newfeat.setValid( true );
     it->setFeatureId( mNextFeatureId );
+    it->setValid( true );
+
+    mFeatures.insert( mNextFeatureId, *it );
 
     // update spatial index
     if ( mSpatialIndex )
-      mSpatialIndex->insertFeature( newfeat );
+      mSpatialIndex->insertFeature( *it );
 
     mNextFeatureId++;
   }
diff --git a/src/providers/ogr/qgsogrprovider.cpp b/src/providers/ogr/qgsogrprovider.cpp
index 9fc0b46..5d2497d 100644
--- a/src/providers/ogr/qgsogrprovider.cpp
+++ b/src/providers/ogr/qgsogrprovider.cpp
@@ -34,6 +34,7 @@ email                : sherman at mrcc.com
 #include <QDir>
 #include <QFileInfo>
 #include <QMap>
+#include <QMessageBox>
 #include <QString>
 #include <QTextCodec>
 #include <QSettings>
@@ -65,6 +66,7 @@ static const QString TEXT_PROVIDER_DESCRIPTION =
   + GDALVersionInfo( "RELEASE_NAME" )
   + ')';
 
+static OGRwkbGeometryType ogrWkbGeometryTypeFromName( const QString& typeName );
 
 class QgsCPLErrorHandler
 {
@@ -281,35 +283,18 @@ QgsVectorLayerImport::ImportError QgsOgrProvider::createEmptyLayer(
   return QgsVectorLayerImport::NoError;
 }
 
-
-QgsOgrProvider::QgsOgrProvider( QString const & uri )
-    : QgsVectorDataProvider( uri )
-    , mFirstFieldIsFid( false )
-    , ogrDataSource( nullptr )
-    , mExtent( nullptr )
-    , mForceRecomputeExtent( false )
-    , ogrLayer( nullptr )
-    , ogrOrigLayer( nullptr )
-    , mLayerIndex( 0 )
-    , mIsSubLayer( false )
-    , mOgrGeometryTypeFilter( wkbUnknown )
-    , ogrDriver( nullptr )
-    , mValid( false )
-    , mOGRGeomType( wkbUnknown )
-    , mFeaturesCounted( -1 )
-    , mWriteAccess( false )
-    , mWriteAccessPossible( false )
-    , mDynamicWriteAccess( false )
-    , mShapefileMayBeCorrupted( false )
-    , mUpdateModeStackDepth( 0 )
-    , mCapabilities( 0 )
+static QString AnalyzeURI( QString const & uri,
+                           bool& isSubLayer,
+                           int& layerIndex,
+                           QString& layerName,
+                           QString& subsetString,
+                           OGRwkbGeometryType& ogrGeometryTypeFilter )
 {
-  QgsApplication::registerOgrDrivers();
-
-  QSettings settings;
-  CPLSetConfigOption( "SHAPE_ENCODING", settings.value( "/qgis/ignoreShapeEncoding", true ).toBool() ? "" : nullptr );
-
-  // make connection to the data source
+  isSubLayer = false;
+  layerIndex = 0;
+  layerName = QString::null;
+  subsetString = QString::null;
+  ogrGeometryTypeFilter = wkbUnknown;
 
   QgsDebugMsg( "Data source uri is [" + uri + ']' );
 
@@ -325,14 +310,12 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
 
   if ( !uri.contains( '|', Qt::CaseSensitive ) )
   {
-    mFilePath = uri;
-    mLayerIndex = 0;
-    mLayerName = QString::null;
+    return uri;
   }
   else
   {
     QStringList theURIParts = uri.split( '|' );
-    mFilePath = theURIParts.at( 0 );
+    QString filePath = theURIParts.at( 0 );
 
     for ( int i = 1 ; i < theURIParts.size(); i++ )
     {
@@ -344,43 +327,85 @@ QgsOgrProvider::QgsOgrProvider( QString const & uri )
       if ( field == "layerid" )
       {
         bool ok;
-        mLayerIndex = value.toInt( &ok );
-        if ( ! ok )
+        layerIndex = value.toInt( &ok );
+        if ( ! ok || layerIndex < 0 )
         {
-          mLayerIndex = -1;
+          layerIndex = -1;
         }
         else
         {
-          mIsSubLayer = true;
+          isSubLayer = true;
         }
       }
       else if ( field == "layername" )
       {
-        mLayerName = value;
-        mIsSubLayer = true;
+        layerName = value;
+        isSubLayer = true;
       }
 
-      if ( field == "subset" )
+      else if ( field == "subset" )
       {
-        mSubsetString = value;
+        subsetString = value;
       }
 
-      if ( field == "geometrytype" )
+      else if ( field == "geometrytype" )
       {
-        mOgrGeometryTypeFilter = ogrWkbGeometryTypeFromName( value );
+        ogrGeometryTypeFilter = ogrWkbGeometryTypeFromName( value );
       }
     }
+
+    return filePath;
   }
 
+}
+
+QgsOgrProvider::QgsOgrProvider( QString const & uri )
+    : QgsVectorDataProvider( uri )
+    , mFirstFieldIsFid( false )
+    , ogrDataSource( nullptr )
+    , mExtent( nullptr )
+    , mForceRecomputeExtent( false )
+    , ogrLayer( nullptr )
+    , ogrOrigLayer( nullptr )
+    , mLayerIndex( 0 )
+    , mIsSubLayer( false )
+    , mOgrGeometryTypeFilter( wkbUnknown )
+    , ogrDriver( nullptr )
+    , mValid( false )
+    , mOGRGeomType( wkbUnknown )
+    , mFeaturesCounted( -1 )
+    , mWriteAccess( false )
+    , mWriteAccessPossible( false )
+    , mDynamicWriteAccess( false )
+    , mShapefileMayBeCorrupted( false )
+    , mUpdateModeStackDepth( 0 )
+    , mCapabilities( 0 )
+{
+  QgsApplication::registerOgrDrivers();
+
+  QSettings settings;
+  CPLSetConfigOption( "SHAPE_ENCODING", settings.value( "/qgis/ignoreShapeEncoding", true ).toBool() ? "" : nullptr );
+
+  // make connection to the data source
+
+  QgsDebugMsg( "Data source uri is [" + uri + ']' );
+
+  mFilePath = AnalyzeURI( uri,
+                          mIsSubLayer,
+                          mLayerIndex,
+                          mLayerName,
+                          mSubsetString,
+                          mOgrGeometryTypeFilter );
+
   open( OpenModeInitial );
 
   mNativeTypes
-  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), "integer", QVariant::Int, 1, 10 )
+  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer)" ), "integer", QVariant::Int, 0, 11 )
 #if defined(GDAL_VERSION_NUM) && GDAL_VERSION_NUM >= 2000000
-  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), "integer64", QVariant::LongLong, 1, 10 )
+  << QgsVectorDataProvider::NativeType( tr( "Whole number (integer 64 bit)" ), "integer64", QVariant::LongLong, 0, 21 )
 #endif
-  << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "double", QVariant::Double, 1, 20, 0, 15 )
-  << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), "string", QVariant::String, 1, 255 )
+  << QgsVectorDataProvider::NativeType( tr( "Decimal number (real)" ), "double", QVariant::Double, 0, 20, 0, 15 )
+  << QgsVectorDataProvider::NativeType( tr( "Text (string)" ), "string", QVariant::String, 0, 65535 )
   << QgsVectorDataProvider::NativeType( tr( "Date" ), "date", QVariant::Date, 8, 8 );
 
   // Some drivers do not support datetime type
@@ -605,7 +630,7 @@ QString QgsOgrProvider::ogrWkbGeometryTypeName( OGRwkbGeometryType type ) const
   return geom;
 }
 
-OGRwkbGeometryType QgsOgrProvider::ogrWkbGeometryTypeFromName( const QString& typeName ) const
+static OGRwkbGeometryType ogrWkbGeometryTypeFromName( const QString& typeName )
 {
   if ( typeName == "Point" ) return wkbPoint;
   else if ( typeName == "LineString" ) return wkbLineString;
@@ -652,6 +677,14 @@ QStringList QgsOgrProvider::subLayers() const
       continue;
     }
 
+    if ( !mIsSubLayer && ( theLayerName == "layer_styles" ||
+                           theLayerName == "qgis_projects" ) )
+    {
+      // Ignore layer_styles (coming from QGIS styling support) and
+      // qgis_projects (coming from http://plugins.qgis.org/plugins/QgisGeopackage/)
+      continue;
+    }
+
     QgsDebugMsg( QString( "id = %1 name = %2 layerGeomType = %3" ).arg( i ).arg( theLayerName ).arg( layerGeomType ) );
 
     if ( wkbFlatten( layerGeomType ) != wkbUnknown )
@@ -1309,10 +1342,12 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
 
   bool returnvalue = true;
 
-  QMap< QString, QVariant::Type > mapFieldTypesToPatch;
+  QMap< QString, QgsField > mapFieldNameToOriginalField;
 
   for ( QList<QgsField>::const_iterator iter = attributes.begin(); iter != attributes.end(); ++iter )
   {
+    mapFieldNameToOriginalField[ iter->name()] = *iter;
+
     OGRFieldType type;
 
     switch ( iter->type() )
@@ -1328,7 +1363,6 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
           type = OFTInteger64;
         else
         {
-          mapFieldTypesToPatch[ iter->name()] = iter->type();
           type = OFTReal;
         }
         break;
@@ -1371,13 +1405,23 @@ bool QgsOgrProvider::addAttributes( const QList<QgsField> &attributes )
   }
   loadFields();
 
-  // Patch field type in case of Integer64->Real mapping so that QVariant::LongLong
-  // is still returned to the caller
-  for ( QMap< QString, QVariant::Type >::const_iterator it = mapFieldTypesToPatch.begin(); it != mapFieldTypesToPatch.end(); ++it )
+  // The check in QgsVectorLayerEditBuffer::commitChanges() is questionable with
+  // real-world drivers that might only be able to satisfy request only partially.
+  // So to avoid erroring out, patch field type, width and precision to match
+  // what was requested.
+  // For example in case of Integer64->Real mapping so that QVariant::LongLong is
+  // still returned to the caller
+  // Or if a field width was specified but not strictly enforced by the driver (#15614)
+  for ( QMap< QString, QgsField >::const_iterator it = mapFieldNameToOriginalField.begin();
+        it != mapFieldNameToOriginalField.end(); ++it )
   {
     int idx = mAttributeFields.fieldNameIndex( it.key() );
     if ( idx >= 0 )
-      mAttributeFields[ idx ].setType( *it );
+    {
+      mAttributeFields[ idx ].setType( it->type() );
+      mAttributeFields[ idx ].setLength( it->length() );
+      mAttributeFields[ idx ].setPrecision( it->precision() );
+    }
   }
 
   return returnvalue;
@@ -3251,8 +3295,14 @@ OGRLayerH QgsOgrProviderUtils::setSubsetString( OGRLayerH layer, OGRDataSourceH
       layerName = encoding->fromUnicode( modifiedLayerName );
     }
   }
-  QByteArray sql = "SELECT * FROM " + quotedIdentifier( layerName, ogrDriverName );
-  sql += " WHERE " + encoding->fromUnicode( subsetString );
+  QByteArray sql;
+  if ( subsetString.startsWith( "SELECT ", Qt::CaseInsensitive ) )
+    sql = encoding->fromUnicode( subsetString );
+  else
+  {
+    sql = "SELECT * FROM " + quotedIdentifier( layerName, ogrDriverName );
+    sql += " WHERE " + encoding->fromUnicode( subsetString );
+  }
 
   QgsDebugMsg( QString( "SQL: %1" ).arg( encoding->toUnicode( sql ) ) );
   return OGR_DS_ExecuteSQL( ds, sql.constData(), nullptr, nullptr );
@@ -3534,6 +3584,496 @@ bool QgsOgrProvider::leaveUpdateMode()
   return true;
 }
 
+bool QgsOgrProvider::isSaveAndLoadStyleToDBSupported()
+{
+  // We could potentially extend support for styling to other drivers
+  // with multiple layer support.
+  return ogrDriverName == "GPKG" || ogrDriverName == "SQLite";
+}
+
+// ---------------------------------------------------------------------------
+
+static
+OGRDataSourceH LoadDataSourceAndLayer( const QString& uri,
+                                       OGRLayerH& hUserLayer,
+                                       QString& errCause )
+{
+  hUserLayer = nullptr;
+  bool isSubLayer;
+  int layerIndex;
+  QString layerName;
+  QString subsetString;
+  OGRwkbGeometryType ogrGeometryType;
+  QString filePath = AnalyzeURI( uri,
+                                 isSubLayer,
+                                 layerIndex,
+                                 layerName,
+                                 subsetString,
+                                 ogrGeometryType );
+
+  OGRDataSourceH hDS = QgsOgrProviderUtils::OGROpenWrapper( TO8F( filePath ), true, nullptr );
+  if ( !hDS )
+  {
+    QgsDebugMsg( "Connection to database failed.." );
+    errCause = QObject::tr( "Connection to database failed" );
+    return nullptr;
+  }
+
+  if ( !layerName.isEmpty() )
+  {
+    hUserLayer = OGR_DS_GetLayerByName( hDS, TO8F( layerName ) );
+    if ( !hUserLayer )
+    {
+      errCause = QObject::tr( "Cannot find layer %1." ).arg( layerName );
+      QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+      return nullptr;
+    }
+  }
+  else
+  {
+    hUserLayer = OGR_DS_GetLayer( hDS, layerIndex );
+    if ( !hUserLayer )
+    {
+      errCause = QObject::tr( "Cannot find layer %1." ).arg( layerIndex );
+      QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+      return nullptr;
+    }
+  }
+
+  return hDS;
+}
+
+
+QGISEXTERN bool saveStyle( const QString& uri, const QString& qmlStyle, const QString& sldStyle,
+                           const QString& styleName, const QString& styleDescription,
+                           const QString& uiFileContent, bool useAsDefault, QString& errCause )
+{
+  OGRLayerH hUserLayer = nullptr;
+  OGRDataSourceH hDS = LoadDataSourceAndLayer( uri, hUserLayer, errCause );
+  if ( !hDS )
+    return false;
+
+  // check if layer_styles table already exist
+  OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, "layer_styles" );
+  if ( !hLayer )
+  {
+    // if not create it
+    // Note: we use the same schema as in the spatialite and postgre providers
+    //for cross interoperability
+
+    char** options = nullptr;
+    // TODO: might need change if other drivers than GPKG / SQLite
+    options = CSLSetNameValue( options, "FID", "id" );
+    hLayer = OGR_DS_CreateLayer( hDS, "layer_styles", nullptr, wkbNone, options );
+    CSLDestroy( options );
+    if ( !hLayer )
+    {
+      errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." );
+      QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+      return false;
+    }
+    bool ok = true;
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "f_table_catalog", OFTString );
+      OGR_Fld_SetWidth( fld, 256 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "f_table_schema", OFTString );
+      OGR_Fld_SetWidth( fld, 256 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "f_table_name", OFTString );
+      OGR_Fld_SetWidth( fld, 256 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "f_geometry_column", OFTString );
+      OGR_Fld_SetWidth( fld, 256 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "styleName", OFTString );
+      OGR_Fld_SetWidth( fld, 30 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "styleQML", OFTString );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "styleSLD", OFTString );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "useAsDefault", OFTInteger );
+#if GDAL_VERSION_MAJOR >= 2
+      OGR_Fld_SetSubType( fld, OFSTBoolean );
+#endif
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "description", OFTString );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "owner", OFTString );
+      OGR_Fld_SetWidth( fld, 30 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "ui", OFTString );
+      OGR_Fld_SetWidth( fld, 30 );
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    {
+      OGRFieldDefnH fld = OGR_Fld_Create( "update_time", OFTDateTime );
+#if GDAL_VERSION_MAJOR >= 2
+      OGR_Fld_SetDefault( fld, "CURRENT_TIMESTAMP" );
+#endif
+      ok &= OGR_L_CreateField( hLayer, fld, true ) == OGRERR_NONE;
+      OGR_Fld_Destroy( fld );
+    }
+    if ( !ok )
+    {
+      errCause = QObject::tr( "Unable to save layer style. It's not possible to create the destination table on the database." );
+      QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+      return false;
+    }
+  }
+
+  QString realStyleName =
+    styleName.isEmpty() ? QString( OGR_L_GetName( hUserLayer ) ) : styleName;
+
+  OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
+
+  if ( useAsDefault )
+  {
+    QString oldDefaultQuery = QString( "useAsDefault = 1 AND f_table_schema=''"
+                                       " AND f_table_name=%1"
+                                       " AND f_geometry_column=%2" )
+                              .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) )
+                              .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) );
+    OGR_L_SetAttributeFilter( hLayer, TO8F( oldDefaultQuery ) );
+    OGRFeatureH hFeature = OGR_L_GetNextFeature( hLayer );
+    if ( hFeature )
+    {
+      OGR_F_SetFieldInteger( hFeature,
+                             OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ),
+                             0 );
+      bool ok = OGR_L_SetFeature( hLayer, hFeature ) == 0;
+      OGR_F_Destroy( hFeature );
+      if ( !ok )
+      {
+        QgsDebugMsg( "Could not unset previous useAsDefault style" );
+      }
+    }
+  }
+
+  QString checkQuery = QString( "f_table_schema=''"
+                                " AND f_table_name=%1"
+                                " AND f_geometry_column=%2"
+                                " AND styleName=%3" )
+                       .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) )
+                       .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) )
+                       .arg( QgsOgrProviderUtils::quotedValue( realStyleName ) );
+  OGR_L_SetAttributeFilter( hLayer, TO8F( checkQuery ) );
+  OGR_L_ResetReading( hLayer );
+  OGRFeatureH hFeature = OGR_L_GetNextFeature( hLayer );
+  bool bNew = true;
+
+  if ( hFeature != NULL )
+  {
+    QSettings settings;
+    // Only used in tests. Do not define it for interactive implication
+    QVariant overwriteStyle = settings.value( "/qgis/overwriteStyle" );
+    if (( !overwriteStyle.isNull() && !overwriteStyle.toBool() ) ||
+        ( overwriteStyle.isNull() &&
+          QMessageBox::question( nullptr, QObject::tr( "Save style in database" ),
+                                 QObject::tr( "A style named \"%1\" already exists in the database for this layer. Do you want to overwrite it?" )
+                                 .arg( realStyleName ),
+                                 QMessageBox::Yes | QMessageBox::No ) == QMessageBox::No ) )
+    {
+      errCause = QObject::tr( "Operation aborted" );
+      OGR_F_Destroy( hFeature );
+      QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+      return false;
+    }
+    bNew = false;
+  }
+  else
+  {
+    hFeature = OGR_F_Create( hLayerDefn );
+    OGR_F_SetFieldString( hFeature,
+                          OGR_FD_GetFieldIndex( hLayerDefn, "f_table_catalog" ),
+                          "" );
+    OGR_F_SetFieldString( hFeature,
+                          OGR_FD_GetFieldIndex( hLayerDefn, "f_table_schema" ),
+                          "" );
+    OGR_F_SetFieldString( hFeature,
+                          OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ),
+                          OGR_L_GetName( hUserLayer ) );
+    OGR_F_SetFieldString( hFeature,
+                          OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ),
+                          OGR_L_GetGeometryColumn( hUserLayer ) );
+    OGR_F_SetFieldString( hFeature,
+                          OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ),
+                          TO8F( realStyleName ) );
+    if ( !uiFileContent.isEmpty() )
+    {
+      OGR_F_SetFieldString( hFeature,
+                            OGR_FD_GetFieldIndex( hLayerDefn, "ui" ),
+                            TO8F( uiFileContent ) );
+    }
+  }
+  OGR_F_SetFieldString( hFeature,
+                        OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ),
+                        TO8F( qmlStyle ) );
+  OGR_F_SetFieldString( hFeature,
+                        OGR_FD_GetFieldIndex( hLayerDefn, "styleSLD" ),
+                        TO8F( sldStyle ) );
+  OGR_F_SetFieldInteger( hFeature,
+                         OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ),
+                         useAsDefault ? 1 : 0 );
+  OGR_F_SetFieldString( hFeature,
+                        OGR_FD_GetFieldIndex( hLayerDefn, "description" ),
+                        TO8F( styleDescription.isEmpty() ? QDateTime::currentDateTime().toString() : styleDescription ) );
+  OGR_F_SetFieldString( hFeature,
+                        OGR_FD_GetFieldIndex( hLayerDefn, "owner" ),
+                        "" );
+
+  bool bFeatureOK;
+  if ( bNew )
+    bFeatureOK = OGR_L_CreateFeature( hLayer, hFeature ) == OGRERR_NONE;
+  else
+    bFeatureOK = OGR_L_SetFeature( hLayer, hFeature ) == OGRERR_NONE;
+
+  OGR_F_Destroy( hFeature );
+
+  QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+
+  if ( !bFeatureOK )
+  {
+    QgsMessageLog::logMessage( QObject::tr( "Error updating style" ) );
+    errCause = QObject::tr( "Error looking for style. The query was logged" );
+    return false;
+  }
+
+  return true;
+}
+
+
+QGISEXTERN QString loadStyle( const QString& uri, QString& errCause )
+{
+  OGRLayerH hUserLayer = nullptr;
+  OGRDataSourceH hDS = LoadDataSourceAndLayer( uri, hUserLayer, errCause );
+  if ( !hDS )
+    return "";
+
+  // check if layer_styles table already exist
+  OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, "layer_styles" );
+  if ( !hLayer )
+  {
+    errCause = QObject::tr( "Cannot find layer_styles layer" );
+    QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+    return "";
+  }
+
+  QString selectQmlQuery = QString( "f_table_schema=''"
+                                    " AND f_table_name=%1"
+                                    " AND f_geometry_column=%2"
+                                    " ORDER BY CASE WHEN useAsDefault THEN 1 ELSE 2 END"
+                                    ",update_time DESC LIMIT 1" )
+                           .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetName( hUserLayer ) ) ) )
+                           .arg( QgsOgrProviderUtils::quotedValue( QString( OGR_L_GetGeometryColumn( hUserLayer ) ) ) );
+  OGR_L_SetAttributeFilter( hLayer, TO8F( selectQmlQuery ) );
+  OGR_L_ResetReading( hLayer );
+  OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
+  QString styleQML;
+  qlonglong moreRecentTimestamp = 0;
+  while ( true )
+  {
+    OGRFeatureH hFeat = OGR_L_GetNextFeature( hLayer );
+    if ( !hFeat )
+      break;
+    if ( OGR_F_GetFieldAsInteger( hFeat, OGR_FD_GetFieldIndex( hLayerDefn, "useAsDefault" ) ) )
+    {
+      styleQML = QString::fromUtf8(
+                   OGR_F_GetFieldAsString( hFeat, OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) );
+      OGR_F_Destroy( hFeat );
+      break;
+    }
+
+    int  year, month, day, hour, minute, second, TZ;
+    OGR_F_GetFieldAsDateTime( hFeat, OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ),
+                              &year, &month, &day, &hour, &minute, &second, &TZ );
+    qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
+                   ( qlonglong )month * 31 * 24 * 3600 + ( qlonglong )year * 12 * 31 * 24 * 3600;
+    if ( ts > moreRecentTimestamp )
+    {
+      moreRecentTimestamp = ts;
+      styleQML = QString::fromUtf8(
+                   OGR_F_GetFieldAsString( hFeat, OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) );
+
+    }
+    OGR_F_Destroy( hFeat );
+  }
+
+  QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+  return styleQML;
+}
+
+QGISEXTERN int listStyles( const QString &uri, QStringList &ids, QStringList &names,
+                           QStringList &descriptions, QString& errCause )
+{
+  OGRLayerH hUserLayer = nullptr;
+  OGRDataSourceH hDS = LoadDataSourceAndLayer( uri, hUserLayer, errCause );
+  if ( !hDS )
+    return -1;
+
+  // check if layer_styles table already exist
+  OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, "layer_styles" );
+  if ( !hLayer || OGR_L_GetFeatureCount( hLayer, TRUE ) == 0 )
+  {
+    QgsMessageLog::logMessage( QObject::tr( "No styles available on DB" ) );
+    errCause = QObject::tr( "No styles available on DB" );
+    QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+    return 0;
+  }
+
+  OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
+
+  OGR_L_ResetReading( hLayer );
+
+  QList<qlonglong> listTimestamp;
+  QMap<int, QString> mapIdToStyleName;
+  QMap<int, QString> mapIdToDescription;
+  QMap<qlonglong, QList<int> > mapTimestampToId;
+  int numberOfRelatedStyles = 0;
+  while ( true )
+  {
+    OGRFeatureH hFeature = OGR_L_GetNextFeature( hLayer );
+    if ( !hFeature )
+      break;
+
+    QString tableName( QString::fromUtf8(
+                         OGR_F_GetFieldAsString( hFeature,
+                                                 OGR_FD_GetFieldIndex( hLayerDefn, "f_table_name" ) ) ) );
+    QString geometryColumn( QString::fromUtf8(
+                              OGR_F_GetFieldAsString( hFeature,
+                                                      OGR_FD_GetFieldIndex( hLayerDefn, "f_geometry_column" ) ) ) );
+    QString styleName( QString::fromUtf8(
+                         OGR_F_GetFieldAsString( hFeature,
+                                                 OGR_FD_GetFieldIndex( hLayerDefn, "styleName" ) ) ) );
+    QString description( QString::fromUtf8(
+                           OGR_F_GetFieldAsString( hFeature,
+                                                   OGR_FD_GetFieldIndex( hLayerDefn, "description" ) ) ) );
+    int fid = static_cast<int>( OGR_F_GetFID( hFeature ) );
+    if ( tableName == QString::fromUtf8( OGR_L_GetName( hUserLayer ) ) &&
+         geometryColumn == QString::fromUtf8( OGR_L_GetGeometryColumn( hUserLayer ) ) )
+    {
+      // Append first all related styles
+      QString id( QString( "%1" ).arg( fid ) );
+      ids.append( id );
+      names.append( styleName );
+      descriptions.append( description );
+      ++ numberOfRelatedStyles;
+    }
+    else
+    {
+      int  year, month, day, hour, minute, second, TZ;
+      OGR_F_GetFieldAsDateTime( hFeature, OGR_FD_GetFieldIndex( hLayerDefn, "update_time" ),
+                                &year, &month, &day, &hour, &minute, &second, &TZ );
+      qlonglong ts = second + minute * 60 + hour * 3600 + day * 24 * 3600 +
+                     ( qlonglong )month * 31 * 24 * 3600 + ( qlonglong )year * 12 * 31 * 24 * 3600;
+
+      listTimestamp.append( ts );
+      mapIdToStyleName[fid] = styleName;
+      mapIdToDescription[fid] = styleName;
+      mapTimestampToId[ts].append( fid );
+    }
+
+    OGR_F_Destroy( hFeature );
+  }
+
+  qSort( listTimestamp.begin(), listTimestamp.end() );
+  // Sort from most recent to least recent
+  for ( int i = listTimestamp.size() - 1; i >= 0; i-- )
+  {
+    const QList<int>& listId = mapTimestampToId[listTimestamp[i]];
+    for ( int j = 0; j < listId.size(); j++ )
+    {
+      int fid = listId[j];
+      QString id( QString( "%1" ).arg( fid ) );
+      ids.append( id );
+      names.append( mapIdToStyleName[fid] );
+      descriptions.append( mapIdToDescription[fid] );
+    }
+  }
+
+  QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+
+  return numberOfRelatedStyles;
+}
+
+QGISEXTERN QString getStyleById( const QString& uri, QString styleId, QString& errCause )
+{
+  OGRLayerH hUserLayer = nullptr;
+  OGRDataSourceH hDS = LoadDataSourceAndLayer( uri, hUserLayer, errCause );
+  if ( !hDS )
+    return "";
+
+  // check if layer_styles table already exist
+  OGRLayerH hLayer = OGR_DS_GetLayerByName( hDS, "layer_styles" );
+  if ( !hLayer )
+  {
+    errCause = QObject::tr( "Cannot find layer_styles layer" );
+    QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+    return "";
+  }
+
+  bool ok;
+  int id = styleId.toInt( &ok );
+  if ( !ok )
+  {
+    errCause = QObject::tr( "Invalid style identifier" );
+    QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+    return "";
+  }
+
+  OGRFeatureH hFeature = OGR_L_GetFeature( hLayer, id );
+  if ( !hFeature )
+  {
+    errCause = QObject::tr( "No style corresponding to style identifier" );
+    QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+    return "";
+  }
+
+  OGRFeatureDefnH hLayerDefn = OGR_L_GetLayerDefn( hLayer );
+  QString styleQML( QString::fromUtf8(
+                      OGR_F_GetFieldAsString( hFeature,
+                                              OGR_FD_GetFieldIndex( hLayerDefn, "styleQML" ) ) ) );
+
+  OGR_F_Destroy( hFeature );
+
+  QgsOgrProviderUtils::OGRDestroyWrapper( hDS );
+
+  return styleQML;
+}
+
+
 // ---------------------------------------------------------------------------
 
 QGISEXTERN QgsVectorLayerImport::ImportError createEmptyLayer(
diff --git a/src/providers/ogr/qgsogrprovider.h b/src/providers/ogr/qgsogrprovider.h
index cd63608..de8a0fb 100644
--- a/src/providers/ogr/qgsogrprovider.h
+++ b/src/providers/ogr/qgsogrprovider.h
@@ -171,6 +171,8 @@ class QgsOgrProvider : public QgsVectorDataProvider
 
     virtual bool leaveUpdateMode() override;
 
+    virtual bool isSaveAndLoadStyleToDBSupported() override;
+
     /** Return vector file filter string
      *
      * Returns a string suitable for a QFileDialog of vector file formats
@@ -303,7 +305,6 @@ class QgsOgrProvider : public QgsVectorDataProvider
   private:
     unsigned char *getGeometryPointer( OGRFeatureH fet );
     QString ogrWkbGeometryTypeName( OGRwkbGeometryType type ) const;
-    OGRwkbGeometryType ogrWkbGeometryTypeFromName( const QString& typeName ) const;
     QgsFields mAttributeFields;
     bool mFirstFieldIsFid;
     OGRDataSourceH ogrDataSource;
diff --git a/src/providers/oracle/qgsoracleprovider.cpp b/src/providers/oracle/qgsoracleprovider.cpp
index b3d8321..6799d4f 100644
--- a/src/providers/oracle/qgsoracleprovider.cpp
+++ b/src/providers/oracle/qgsoracleprovider.cpp
@@ -209,6 +209,35 @@ QgsOracleProvider::~QgsOracleProvider()
   disconnectDb();
 }
 
+QString QgsOracleProvider::getWorkspace() const
+{
+  return mUri.param( "dbworkspace" );
+}
+
+void QgsOracleProvider::setWorkspace( const QString &workspace )
+{
+  QgsDataSourceURI prevUri( mUri );
+
+  disconnectDb();
+
+  if ( workspace.isEmpty() )
+    mUri.removeParam( "dbworkspace" );
+  else
+    mUri.setParam( "dbworkspace", workspace );
+
+  mConnection = QgsOracleConn::connectDb( mUri );
+  if ( !mConnection )
+  {
+    mUri = prevUri;
+    QgsDebugMsg( QString( "restoring previous uri:%1" ).arg( mUri.uri() ) );
+    mConnection = QgsOracleConn::connectDb( mUri );
+  }
+  else
+  {
+    setDataSourceUri( mUri.uri() );
+  }
+}
+
 QgsAbstractFeatureSource *QgsOracleProvider::featureSource() const
 {
   return new QgsOracleFeatureSource( this );
@@ -584,13 +613,14 @@ bool QgsOracleProvider::loadFields()
 
     qry.finish();
 
-    if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2 AND t.column_name<>%3" )
+    if ( exec( qry, QString( "SELECT column_name,comments FROM all_col_comments t WHERE t.owner=%1 AND t.table_name=%2" )
                .arg( quotedValue( mOwnerName ) )
-               .arg( quotedValue( mTableName ) )
-               .arg( quotedValue( mGeometryColumn ) ) ) )
+               .arg( quotedValue( mTableName ) ) ) )
     {
       while ( qry.next() )
       {
+        if ( qry.value( 0 ).toString() == mGeometryColumn )
+          continue;
         comments.insert( qry.value( 0 ).toString(), qry.value( 1 ).toString() );
       }
     }
@@ -719,7 +749,9 @@ bool QgsOracleProvider::loadFields()
 
     if ( !mHasSpatialIndex )
     {
-      QgsMessageLog::logMessage( tr( "No spatial index on column %1 found - expect poor performance." )
+      QgsMessageLog::logMessage( tr( "No spatial index on column %1.%2.%3 found - expect poor performance." )
+                                 .arg( mOwnerName )
+                                 .arg( mTableName )
                                  .arg( mGeometryColumn ),
                                  tr( "Oracle" ) );
     }
@@ -2111,7 +2143,7 @@ bool QgsOracleProvider::setSubsetString( const QString& theSQL, bool updateFeatu
   }
   qry.finish();
 
-  if ( mPrimaryKeyType == pktInt && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) )
+  if ( mPrimaryKeyType == pktInt && !mUseEstimatedMetadata && !uniqueData( mQuery, mAttributeFields[ mPrimaryKeyAttrs[0] ].name() ) )
   {
     mSqlWhereClause = prevWhere;
     return false;
@@ -2321,9 +2353,14 @@ bool QgsOracleProvider::getGeometryDetails()
                                  .arg( qry.lastQuery() ), tr( "Oracle" ) );
     }
 
-    if ( exec( qry, QString( mUseEstimatedMetadata
-                             ?  "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<1000) WHERE rownum<=2"
-                             :  "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) )
+    if ( mUseEstimatedMetadata && mRequestedGeomType != QGis::WKBUnknown )
+    {
+      QgsDebugMsg( "Trusting requested geometry type" );
+      detectedType = mRequestedGeomType;
+    }
+    else if ( exec( qry, QString( mUseEstimatedMetadata
+                                  ?  "SELECT DISTINCT gtype FROM (SELECT t.%1.sdo_gtype AS gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<100) WHERE rownum<=2"
+                                  :  "SELECT DISTINCT t.%1.sdo_gtype FROM %2 t WHERE t.%1 IS NOT NULL AND rownum<=2" ).arg( quotedIdentifier( geomCol ) ).arg( mQuery ) ) )
     {
       if ( qry.next() )
       {
diff --git a/src/providers/oracle/qgsoracleprovider.h b/src/providers/oracle/qgsoracleprovider.h
index b0ccddf..55cf91e 100644
--- a/src/providers/oracle/qgsoracleprovider.h
+++ b/src/providers/oracle/qgsoracleprovider.h
@@ -54,6 +54,7 @@ enum QgsOraclePrimaryKeyType
 class QgsOracleProvider : public QgsVectorDataProvider
 {
     Q_OBJECT
+    Q_PROPERTY( QString workspace READ getWorkspace WRITE setWorkspace )
 
   public:
 
@@ -281,6 +282,16 @@ class QgsOracleProvider : public QgsVectorDataProvider
      */
     virtual bool isSaveAndLoadStyleToDBSupported() override { return true; }
 
+    /**
+     * Switch to oracle workspace
+     */
+    void setWorkspace( const QString &workspace );
+
+    /**
+     * Retrieve oracle workspace name
+     */
+    QString getWorkspace() const;
+
   private:
     QString whereClause( QgsFeatureId featureId ) const;
     QString pkParamWhereClause() const;
diff --git a/src/providers/postgres/qgspostgresprovider.cpp b/src/providers/postgres/qgspostgresprovider.cpp
index 7a1f1ed..8cc0b92 100644
--- a/src/providers/postgres/qgspostgresprovider.cpp
+++ b/src/providers/postgres/qgspostgresprovider.cpp
@@ -2049,12 +2049,19 @@ bool QgsPostgresProvider::addFeatures( QgsFeatureList &flist )
         if ( value.isNull() )
         {
           const QgsField &fld = field( attrIdx );
-          v = paramValue( defaultValues[ i ], defaultValues[ i ] );
+          if ( providerProperty( EvaluateDefaultValues, false ).toBool() )
+            v = defaultValues[ i ];
+          else
+            v = paramValue( defaultValues[ i ], defaultValues[ i ] );
+
           features->setAttribute( attrIdx, convertValue( fld.type(), v ) );
         }
         else
         {
-          v = paramValue( value.toString(), defaultValues[ i ] );
+          if ( providerProperty( EvaluateDefaultValues, false ).toBool() )
+            v = value.toString();
+          else
+            v = paramValue( value.toString(), defaultValues[ i ] );
 
           if ( v != value.toString() )
           {
diff --git a/src/providers/spatialite/qgsspatialiteprovider.cpp b/src/providers/spatialite/qgsspatialiteprovider.cpp
index 67ed892..9db5bb6 100644
--- a/src/providers/spatialite/qgsspatialiteprovider.cpp
+++ b/src/providers/spatialite/qgsspatialiteprovider.cpp
@@ -3662,9 +3662,6 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
 
     for ( int i = 0; i < attributevec.count(); ++i )
     {
-      if ( !attributevec.at( i ).isValid() )
-        continue;
-
       if ( i >= mAttributeFields.count() )
         continue;
 
@@ -3720,8 +3717,6 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
         for ( int i = 0; i < attributevec.count(); ++i )
         {
           QVariant v = attributevec.at( i );
-          if ( !v.isValid() )
-            continue;
 
           // binding values for each attribute
           if ( i >= mAttributeFields.count() )
@@ -3733,7 +3728,11 @@ bool QgsSpatiaLiteProvider::addFeatures( QgsFeatureList & flist )
 
           QVariant::Type type = mAttributeFields.at( i ).type();
 
-          if ( v.isNull() )
+          if ( !v.isValid() )
+          {
+            ++ia;
+          }
+          else if ( v.isNull() )
           {
             // binding a NULL value
             sqlite3_bind_null( stmt, ++ia );
diff --git a/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp b/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
index 7e11f3a..04730b4 100644
--- a/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
+++ b/src/providers/virtual/qgsvirtuallayersqlitehelper.cpp
@@ -30,7 +30,7 @@ QgsScopedSqlite::QgsScopedSqlite( const QString& path, bool withExtension )
     sqlite3_auto_extension( reinterpret_cast < void( * )() > ( qgsvlayerModuleInit ) );
   }
   int r;
-  r = sqlite3_open( path.toLocal8Bit().constData(), &db_ );
+  r = sqlite3_open( path.toUtf8().constData(), &db_ );
   if ( withExtension )
   {
     // reset the automatic extensions
@@ -41,7 +41,7 @@ QgsScopedSqlite::QgsScopedSqlite( const QString& path, bool withExtension )
   {
     QString err = QString( "%1 [%2]" ).arg( sqlite3_errmsg( db_ ), path );
     QgsDebugMsg( err );
-    throw std::runtime_error( err.toLocal8Bit().constData() );
+    throw std::runtime_error( err.toUtf8().constData() );
   }
   // enable extended result codes
   sqlite3_extended_result_codes( db_, 1 );
@@ -91,12 +91,12 @@ namespace Sqlite
       : db_( db )
       , nBind_( 1 )
   {
-    QByteArray ba( q.toLocal8Bit() );
+    QByteArray ba( q.toUtf8() );
     int r = sqlite3_prepare_v2( db, ba.constData(), ba.size(), &stmt_, nullptr );
     if ( r )
     {
       QString err = QString( "Query preparation error on %1: %2" ).arg( q ).arg( sqlite3_errmsg( db ) );
-      throw std::runtime_error( err.toLocal8Bit().constData() );
+      throw std::runtime_error( err.toUtf8().constData() );
     }
   }
 
@@ -109,7 +109,7 @@ namespace Sqlite
 
   Query& Query::bind( const QString& str, int idx )
   {
-    QByteArray ba( str.toLocal8Bit() );
+    QByteArray ba( str.toUtf8() );
     int r = sqlite3_bind_text( stmt_, idx, ba.constData(), ba.size(), SQLITE_TRANSIENT );
     if ( r )
     {
@@ -126,11 +126,11 @@ namespace Sqlite
   void Query::exec( sqlite3* db, const QString& sql )
   {
     char *errMsg = nullptr;
-    int r = sqlite3_exec( db, sql.toLocal8Bit().constData(), nullptr, nullptr, &errMsg );
+    int r = sqlite3_exec( db, sql.toUtf8().constData(), nullptr, nullptr, &errMsg );
     if ( r )
     {
       QString err = QString( "Query execution error on %1: %2 - %3" ).arg( sql ).arg( r ).arg( errMsg );
-      throw std::runtime_error( err.toLocal8Bit().constData() );
+      throw std::runtime_error( err.toUtf8().constData() );
     }
   }
 
diff --git a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
index c971620..0e93c11 100644
--- a/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
+++ b/src/providers/virtual/qgsvirtuallayersqlitemodule.cpp
@@ -56,7 +56,7 @@ void initVirtualLayerMetadata( sqlite3* db )
   char *errMsg;
   if ( create_meta )
   {
-    r = sqlite3_exec( db, QString( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toLocal8Bit().constData(), nullptr, nullptr, &errMsg );
+    r = sqlite3_exec( db, QString( "CREATE TABLE _meta (version INT, url TEXT); INSERT INTO _meta (version) VALUES(%1);" ).arg( VIRTUAL_LAYER_VERSION ).toUtf8().constData(), nullptr, nullptr, &errMsg );
     if ( r )
     {
       throw std::runtime_error( errMsg );
diff --git a/src/providers/wms/qgswmsprovider.cpp b/src/providers/wms/qgswmsprovider.cpp
index d4b8c20..057077f 100644
--- a/src/providers/wms/qgswmsprovider.cpp
+++ b/src/providers/wms/qgswmsprovider.cpp
@@ -511,6 +511,9 @@ static bool _fuzzyContainsRect( const QRectF& r1, const QRectF& r2 )
 
 void QgsWmsProvider::fetchOtherResTiles( QgsTileMode tileMode, const QgsRectangle& viewExtent, int imageWidth, QList<QRectF>& missingRects, double tres, int resOffset, QList<TileImage>& otherResTiles )
 {
+  if ( !mTileMatrixSet )
+    return;  // there is no tile matrix set defined for ordinary WMS (with user-specified tile size)
+
   const QgsWmtsTileMatrix* tmOther = mTileMatrixSet->findOtherResolution( tres, resOffset );
   if ( !tmOther )
     return;
@@ -654,6 +657,8 @@ QImage *QgsWmsProvider::draw( QgsRectangle const & viewExtent, int pixelWidth, i
     }
     else if ( mSettings.mMaxWidth != 0 && mSettings.mMaxHeight != 0 )
     {
+      // this is an ordinary WMS server, but the user requested tiled approach
+      // so we will pretend it is a WMS-C server with just one tile matrix
       tempTm.reset( new QgsWmtsTileMatrix );
       tempTm->topLeft      = QgsPoint( mLayerExtent.xMinimum(), mLayerExtent.yMaximum() );
       tempTm->tileWidth    = mSettings.mMaxWidth;
diff --git a/src/server/qgshostedrdsbuilder.cpp b/src/server/qgshostedrdsbuilder.cpp
index b347afc..2e67b65 100644
--- a/src/server/qgshostedrdsbuilder.cpp
+++ b/src/server/qgshostedrdsbuilder.cpp
@@ -62,7 +62,7 @@ QgsMapLayer* QgsHostedRDSBuilder::createMapLayer( const QDomElement& elem,
     {
       rl = qobject_cast<QgsRasterLayer*>( QgsMSLayerCache::instance()->searchLayer( uri, layerName ) );
     }
-    if ( !rl )
+    if ( !rl || !rl->isValid() )
     {
       QgsDebugMsg( "hostedrds layer not in cache, so create and insert it" );
       rl = new QgsRasterLayer( uri, layerNameFromUri( uri ) );
diff --git a/src/server/qgsserverprojectparser.cpp b/src/server/qgsserverprojectparser.cpp
index f2f240b..01745d4 100644
--- a/src/server/qgsserverprojectparser.cpp
+++ b/src/server/qgsserverprojectparser.cpp
@@ -278,6 +278,10 @@ QgsMapLayer* QgsServerProjectParser::createLayerFromElement( const QDomElement&
     layer->readLayerXML( const_cast<QDomElement&>( elem ) ); //should be changed to const in QgsMapLayer
     //layer->setLayerName( layerName( elem ) );
 
+    if ( !layer->isValid() )
+    {
+      return nullptr;
+    }
     // Insert layer in registry and cache before addValueRelationLayersForLayer
     if ( !QgsMapLayerRegistry::instance()->mapLayer( id ) )
       QgsMapLayerRegistry::instance()->addMapLayer( layer, false, false );
diff --git a/src/server/qgswfsserver.cpp b/src/server/qgswfsserver.cpp
index a503d01..9ccbc6d 100644
--- a/src/server/qgswfsserver.cpp
+++ b/src/server/qgswfsserver.cpp
@@ -657,6 +657,29 @@ int QgsWFSServer::getFeature( QgsRequestHandler& request, const QString& format
               {
                 throw QgsMapServiceException( "RequestNotWellFormed", filter->parserErrorString() );
               }
+              QgsFeatureRequest req;
+              if ( filter->needsGeometry() )
+              {
+                req.setFlags( QgsFeatureRequest::NoFlags );
+              }
+              else
+              {
+                req.setFlags( QgsFeatureRequest::ExactIntersect | ( mWithGeom ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) );
+              }
+              req.setFilterExpression( filter->expression() );
+#ifdef HAVE_SERVER_PYTHON_PLUGINS
+              mAccessControl->filterFeatures( layer, req );
+
+              QStringList attributes = QStringList();
+              Q_FOREACH ( int idx, attrIndexes )
+              {
+                attributes.append( layer->pendingFields().field( idx ).name() );
+              }
+              req.setSubsetOfAttributes(
+                mAccessControl->layerAttributes( layer, attributes ),
+                layer->pendingFields() );
+#endif
+              QgsFeatureIterator fit = layer->getFeatures( req );
               while ( fit.nextFeature( feature ) && ( !hasFeatureLimit || featureCounter < maxFeatures + startIndex ) )
               {
                 expressionContext.setFeature( feature );
@@ -1197,6 +1220,22 @@ void QgsWFSServer::startGetFeature( QgsRequestHandler& request, const QString& f
   if ( format == "GeoJSON" )
   {
     fcString = "{\"type\": \"FeatureCollection\",\n";
+    if ( crs.isValid() )
+    {
+      QgsGeometry* exportGeom = QgsGeometry::fromRect( *rect );
+      QgsCoordinateTransform transform;
+      transform.setSourceCrs( crs );
+      transform.setDestCRS( QgsCoordinateReferenceSystem( 4326, QgsCoordinateReferenceSystem::EpsgCrsId ) );
+      try
+      {
+        if ( exportGeom->transform( transform ) == 0 )
+          rect = new QgsRectangle( exportGeom->boundingBox() );
+      }
+      catch ( QgsCsException &cse )
+      {
+        Q_UNUSED( cse );
+      }
+    }
     fcString += " \"bbox\": [ " + qgsDoubleToString( rect->xMinimum(), prec ) + ", " + qgsDoubleToString( rect->yMinimum(), prec ) + ", " + qgsDoubleToString( rect->xMaximum(), prec ) + ", " + qgsDoubleToString( rect->yMaximum(), prec ) + "],\n";
     fcString += " \"features\": [\n";
     result = fcString.toUtf8();
diff --git a/src/server/qgswmsprojectparser.cpp b/src/server/qgswmsprojectparser.cpp
index 8746e92..7a3015f 100644
--- a/src/server/qgswmsprojectparser.cpp
+++ b/src/server/qgswmsprojectparser.cpp
@@ -1768,7 +1768,14 @@ void QgsWMSProjectParser::addOWSLayers( QDomDocument &doc,
         layerElem.appendChild( metaUrlElem );
       }
 
-      parentElem.appendChild( layerElem );
+      if ( parentElem.hasChildNodes() )
+      {
+        parentElem.insertBefore( layerElem, parentElem.firstChild() );
+      }
+      else
+      {
+        parentElem.appendChild( layerElem );
+      }
     }
     else
     {
diff --git a/src/ui/qgsvectorlayersaveasdialogbase.ui b/src/ui/qgsvectorlayersaveasdialogbase.ui
index 76d06aa..9011e86 100644
--- a/src/ui/qgsvectorlayersaveasdialogbase.ui
+++ b/src/ui/qgsvectorlayersaveasdialogbase.ui
@@ -23,21 +23,21 @@
    <item>
     <widget class="QWidget" name="widget" native="true">
      <layout class="QGridLayout" name="gridLayout_4">
-      <item row="2" column="0">
+      <item row="3" column="0">
        <widget class="QLabel" name="label_3">
         <property name="text">
          <string>CRS</string>
         </property>
        </widget>
       </item>
-      <item row="1" column="1">
+      <item row="1" column="2">
        <widget class="QLineEdit" name="leFilename">
         <property name="enabled">
          <bool>false</bool>
         </property>
        </widget>
       </item>
-      <item row="1" column="2">
+      <item row="1" column="3">
        <widget class="QPushButton" name="browseFilename">
         <property name="enabled">
          <bool>false</bool>
@@ -57,26 +57,43 @@
         </property>
        </widget>
       </item>
-      <item row="0" column="1" colspan="2">
+      <item row="0" column="2" colspan="2">
        <widget class="QComboBox" name="mFormatComboBox"/>
       </item>
       <item row="1" column="0">
        <widget class="QLabel" name="label">
         <property name="text">
-         <string>Save as</string>
+         <string>File name</string>
         </property>
         <property name="buddy">
          <cstring>leFilename</cstring>
         </property>
        </widget>
       </item>
-      <item row="2" column="1" colspan="2">
+      <item row="3" column="2" colspan="2">
        <widget class="QgsProjectionSelectionWidget" name="mCrsSelector" native="true">
         <property name="focusPolicy">
          <enum>Qt::StrongFocus</enum>
         </property>
        </widget>
       </item>
+      <item row="2" column="0">
+       <widget class="QLabel" name="label_7">
+        <property name="text">
+         <string>Layer name</string>
+        </property>
+        <property name="buddy">
+         <cstring>leFilename</cstring>
+        </property>
+       </widget>
+      </item>
+      <item row="2" column="2" colspan="2">
+       <widget class="QLineEdit" name="leLayername">
+        <property name="enabled">
+         <bool>false</bool>
+        </property>
+       </widget>
+      </item>
      </layout>
     </widget>
    </item>
@@ -90,8 +107,8 @@
        <rect>
         <x>0</x>
         <y>0</y>
-        <width>550</width>
-        <height>953</height>
+        <width>557</width>
+        <height>922</height>
        </rect>
       </property>
       <layout class="QVBoxLayout" name="verticalLayout">
@@ -434,6 +451,7 @@
   <tabstop>mFormatComboBox</tabstop>
   <tabstop>leFilename</tabstop>
   <tabstop>browseFilename</tabstop>
+  <tabstop>leLayername</tabstop>
   <tabstop>mCrsSelector</tabstop>
   <tabstop>scrollArea</tabstop>
   <tabstop>mEncodingComboBox</tabstop>
diff --git a/tests/src/core/testqgslayertree.cpp b/tests/src/core/testqgslayertree.cpp
index baa98fc..3785b58 100644
--- a/tests/src/core/testqgslayertree.cpp
+++ b/tests/src/core/testqgslayertree.cpp
@@ -35,6 +35,8 @@ class TestQgsLayerTree : public QObject
   private slots:
     void initTestCase();
     void cleanupTestCase();
+    void testGroupNameChanged();
+    void testLayerNameChanged();
     void testCheckStateParentToChild();
     void testCheckStateChildToParent();
     void testCheckStateMutuallyExclusive();
@@ -80,6 +82,66 @@ void TestQgsLayerTree::cleanupTestCase()
   QgsApplication::exitQgis();
 }
 
+void TestQgsLayerTree::testGroupNameChanged()
+{
+  QgsLayerTreeNode* secondGroup = mRoot->children()[1];
+
+  QSignalSpy spy( mRoot, SIGNAL( nameChanged( QgsLayerTreeNode*, QString ) ) );
+  secondGroup->setName( "grp2+" );
+
+  QCOMPARE( secondGroup->name(), QString( "grp2+" ) );
+
+  QCOMPARE( spy.count(), 1 );
+  QList<QVariant> arguments = spy.takeFirst();
+  //QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), secondGroup );
+  QCOMPARE( arguments.at( 1 ).toString(), QString( "grp2+" ) );
+
+  secondGroup->setName( "grp2" );
+  QCOMPARE( secondGroup->name(), QString( "grp2" ) );
+}
+
+void TestQgsLayerTree::testLayerNameChanged()
+{
+  QgsVectorLayer* vl = new QgsVectorLayer( "Point?field=col1:integer", "vl", "memory" );
+  QVERIFY( vl->isValid() );
+
+  QgsLayerTreeLayer* n = new QgsLayerTreeLayer( vl->id(), vl->name() );
+  mRoot->addChildNode( n );
+
+  QSignalSpy spy( mRoot, SIGNAL( nameChanged( QgsLayerTreeNode*, QString ) ) );
+
+  QCOMPARE( n->name(), QString( "vl" ) );
+  n->setName( "changed 1" );
+
+  QCOMPARE( n->name(), QString( "changed 1" ) );
+  QCOMPARE( spy.count(), 1 );
+  QList<QVariant> arguments = spy.takeFirst();
+  //QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
+  QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 1" ) );
+
+  QgsMapLayerRegistry::instance()->addMapLayers( QList<QgsMapLayer*>() << vl );
+
+  // set name via map layer
+  vl->setName( "changed 2" );
+  QCOMPARE( n->name(), QString( "changed 2" ) );
+  QCOMPARE( spy.count(), 1 );
+  arguments = spy.takeFirst();
+  //QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
+  QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 2" ) );
+
+  // set name via layer tree
+  n->setName( "changed 3" );
+  QCOMPARE( n->name(), QString( "changed 3" ) );
+  QCOMPARE( spy.count(), 1 );
+  arguments = spy.takeFirst();
+  //QCOMPARE( arguments.at( 0 ).value<QgsLayerTreeNode*>(), n );
+  QCOMPARE( arguments.at( 1 ).toString(), QString( "changed 3" ) );
+
+  QgsMapLayerRegistry::instance()->removeMapLayers( QList<QgsMapLayer*>() << vl );
+
+  mRoot->removeChildNode( n );
+}
+
 void TestQgsLayerTree::testCheckStateParentToChild()
 {
   mRoot->setVisible( Qt::Unchecked );
diff --git a/tests/src/core/testqgsvectorlayercache.cpp b/tests/src/core/testqgsvectorlayercache.cpp
index 3bfb0ba..dcceb36 100644
--- a/tests/src/core/testqgsvectorlayercache.cpp
+++ b/tests/src/core/testqgsvectorlayercache.cpp
@@ -51,6 +51,9 @@ class TestVectorLayerCache : public QObject
     void testCacheAttrActions(); // Test attribute add/ attribute delete
     void testFeatureActions();   // Test adding/removing features works
     void testSubsetRequest();
+    void testFullCache();
+    void testFullCacheThroughRequest();
+    void testCanUseCacheForRequest();
 
     void onCommittedFeaturesAdded( const QString&, const QgsFeatureList& );
 
@@ -218,6 +221,115 @@ void TestVectorLayerCache::testSubsetRequest()
   QVERIFY( a == f.attribute( 3 ) );
 }
 
+void TestVectorLayerCache::testFullCache()
+{
+  // cache is too small to fit all features
+  QgsVectorLayerCache cache( mPointsLayer, 2 );
+  QVERIFY( !cache.hasFullCache() );
+  QVERIFY( cache.cacheSize() < mPointsLayer->featureCount() );
+  // but we set it to full cache
+  cache.setFullCache( true );
+  // so now it should have sufficient size for all features
+  QVERIFY( cache.cacheSize() >= mPointsLayer->featureCount() );
+  QVERIFY( cache.hasFullCache() );
+
+  // double check that everything is indeed in the cache
+  QgsFeatureIterator it = mPointsLayer->getFeatures();
+  QgsFeature f;
+  while ( it.nextFeature( f ) )
+  {
+    QVERIFY( cache.isFidCached( f.id() ) );
+  }
+}
+
+void TestVectorLayerCache::testFullCacheThroughRequest()
+{
+  // make sure cache is sufficient size for all features
+  QgsVectorLayerCache cache( mPointsLayer, mPointsLayer->featureCount() * 2 );
+  QVERIFY( !cache.hasFullCache() );
+
+  // now request all features from cache
+  QgsFeatureIterator it = cache.getFeatures( QgsFeatureRequest() );
+  QgsFeature f;
+  while ( it.nextFeature( f ) )
+  {
+    // suck in all features
+  }
+
+  // cache should now contain all features
+  it = mPointsLayer->getFeatures();
+  while ( it.nextFeature( f ) )
+  {
+    QVERIFY( cache.isFidCached( f.id() ) );
+  }
+
+  // so it should be a full cache!
+  QVERIFY( cache.hasFullCache() );
+}
+
+void TestVectorLayerCache::testCanUseCacheForRequest()
+{
+  //first get some feature ids from layer
+  QgsFeature f;
+  QgsFeatureIterator it = mPointsLayer->getFeatures();
+  it.nextFeature( f );
+  QgsFeatureId id1 = f.id();
+  it.nextFeature( f );
+  QgsFeatureId id2 = f.id();
+
+  QgsVectorLayerCache cache( mPointsLayer, 10 );
+  // initially nothing in cache, so can't use it to fulfill the request
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) );
+
+  // get just the first feature into the cache
+  it = cache.getFeatures( QgsFeatureRequest().setFilterFid( id1 ) );
+  while ( it.nextFeature( f ) ) { }
+  QCOMPARE( cache.cachedFeatureIds(), QgsFeatureIds() << id1 );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) );
+  //verify that the returned iterator was correct
+  QVERIFY( it.nextFeature( f ) );
+  QCOMPARE( f.id(), id1 );
+  QVERIFY( !it.nextFeature( f ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) );
+
+  // get feature 2 into cache
+  it = cache.getFeatures( QgsFeatureRequest().setFilterFid( id2 ) );
+  while ( it.nextFeature( f ) ) { }
+  QCOMPARE( cache.cachedFeatureIds(), QgsFeatureIds() << id1 << id2 );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) );
+  QVERIFY( it.nextFeature( f ) );
+  QCOMPARE( f.id(), id1 );
+  QVERIFY( !it.nextFeature( f ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) );
+  QVERIFY( it.nextFeature( f ) );
+  QCOMPARE( f.id(), id2 );
+  QVERIFY( !it.nextFeature( f ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) );
+  QVERIFY( it.nextFeature( f ) );
+  QgsFeatureIds result;
+  result << f.id();
+  QVERIFY( it.nextFeature( f ) );
+  result << f.id();
+  QCOMPARE( result, QgsFeatureIds() << id1 << id2 );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) );
+  QVERIFY( !cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) );
+
+  // can only use rect/expression requests if cache has everything
+  cache.setFullCache( true );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id1 ), it ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFid( id2 ), it ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << id1 << id2 ), it ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterRect( QgsRectangle( 1, 2, 3, 4 ) ), it ) );
+  QVERIFY( cache.canUseCacheForRequest( QgsFeatureRequest().setFilterExpression( "$x<5" ), it ) );
+}
+
 void TestVectorLayerCache::onCommittedFeaturesAdded( const QString& layerId, const QgsFeatureList& features )
 {
   Q_UNUSED( layerId )
diff --git a/tests/src/gui/CMakeLists.txt b/tests/src/gui/CMakeLists.txt
index 32e3af9..f1c823a 100644
--- a/tests/src/gui/CMakeLists.txt
+++ b/tests/src/gui/CMakeLists.txt
@@ -140,4 +140,5 @@ ADD_QGIS_TEST(rubberbandtest testqgsrubberband.cpp)
 ADD_QGIS_TEST(scalecombobox testqgsscalecombobox.cpp)
 ADD_QGIS_TEST(spinbox testqgsspinbox.cpp)
 ADD_QGIS_TEST(sqlcomposerdialog testqgssqlcomposerdialog.cpp)
+ADD_QGIS_TEST(filedownloader testqgsfiledownloader.cpp)
 
diff --git a/tests/src/gui/testqgsfiledownloader.cpp b/tests/src/gui/testqgsfiledownloader.cpp
new file mode 100644
index 0000000..fa9a7dc
--- /dev/null
+++ b/tests/src/gui/testqgsfiledownloader.cpp
@@ -0,0 +1,251 @@
+/***************************************************************************
+    testqgsfilefiledownloader.cpp
+     --------------------------------------
+    Date                 : 11.8.2016
+    Copyright            : (C) 2016 Alessandro Pasotti
+    Email                : apasotti at boundlessgeo dot com
+ ***************************************************************************
+ *                                                                         *
+ *   This program is free software; you can redistribute it and/or modify  *
+ *   it under the terms of the GNU General Public License as published by  *
+ *   the Free Software Foundation; either version 2 of the License, or     *
+ *   (at your option) any later version.                                   *
+ *                                                                         *
+ ***************************************************************************/
+
+
+#include <QtTest/QtTest>
+#include <QObject>
+#include <QTemporaryFile>
+#include <QUrl>
+#include <QEventLoop>
+#include <QTimer>
+
+#include <qgsapplication.h>
+#include <qgsfiledownloader.h>
+
+class TestQgsFileDownloader: public QObject
+{
+    Q_OBJECT
+  public:
+    TestQgsFileDownloader()
+        : mTempFile( nullptr )
+        , mErrorMessage()
+        , mCanceled( false )
+        , mProgress( false )
+        , mError( false )
+        , mCompleted( false )
+        , mExited( false )
+        , mFileDownloader( nullptr )
+    {}
+
+  public slots:
+    /** Called when the download has completed successfully */
+    void downloadCompleted()
+    {
+      mCompleted = true;
+    }
+    /** Called when the download exits */
+    void downloadExited()
+    {
+      mExited = true;
+    }
+    /** Called when the download was canceled by the user */
+    void downloadCanceled()
+    {
+      mCanceled = true;
+    }
+    /** Called when an error makes the download fail */
+    void downloadError( QStringList errorMessages )
+    {
+      mError = true;
+      errorMessages.sort();
+      mErrorMessage = errorMessages.join( ";" );
+    }
+    /** Called when data ready to be processed */
+    void downloadProgress( qint64 bytesReceived, qint64 bytesTotal )
+    {
+      Q_UNUSED( bytesReceived );
+      Q_UNUSED( bytesTotal );
+      mProgress = true;
+    }
+
+  private slots:
+    void initTestCase(); // will be called before the first testfunction is executed.
+    void cleanupTestCase(); // will be called after the last testfunction was executed.
+    void init(); // will be called before each testfunction is executed.
+    void cleanup(); // will be called after every testfunction.
+
+    void testValidDownload();
+    void testInValidDownload();
+    void testCanceledDownload();
+    void testInvalidFile();
+    void testInvalidUrl();
+    void testBlankUrl();
+#ifndef QT_NO_OPENSSL
+    void testSslError_data();
+    void testSslError();
+#endif
+
+  private:
+    void makeCall( QUrl url , QString fileName, bool cancel = false );
+    QTemporaryFile *mTempFile;
+    QString mErrorMessage;
+    bool mCanceled;
+    bool mProgress;
+    bool mError;
+    bool mCompleted;
+    bool mExited;
+    QgsFileDownloader *mFileDownloader;
+};
+
+void TestQgsFileDownloader::makeCall( QUrl url, QString fileName, bool cancel )
+{
+  QEventLoop loop;
+
+  mFileDownloader = new QgsFileDownloader( url, fileName, false );
+  connect( mFileDownloader, SIGNAL( downloadCompleted() ), this, SLOT( downloadCompleted() ) );
+  connect( mFileDownloader, SIGNAL( downloadCanceled() ), this, SLOT( downloadCanceled() ) );
+  connect( mFileDownloader, SIGNAL( downloadExited() ), this, SLOT( downloadExited() ) );
+  connect( mFileDownloader, SIGNAL( downloadError( QStringList ) ), this, SLOT( downloadError( QStringList ) ) );
+  connect( mFileDownloader, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) );
+
+  connect( mFileDownloader, SIGNAL( downloadExited() ), &loop, SLOT( quit() ) );
+
+  if ( cancel )
+    QTimer::singleShot( 1000, mFileDownloader, SLOT( onDownloadCanceled() ) );
+
+  loop.exec();
+
+}
+
+void TestQgsFileDownloader::initTestCase()
+{
+  QgsApplication::init();
+  QgsApplication::initQgis();
+
+}
+
+void TestQgsFileDownloader::cleanupTestCase()
+{
+  QgsApplication::exitQgis();
+}
+
+void TestQgsFileDownloader::init()
+{
+  mErrorMessage.clear();
+  mCanceled = false;
+  mProgress = false;
+  mError = false;
+  mCompleted = false;
+  mExited = false;
+  mTempFile = new QTemporaryFile( );
+  Q_ASSERT( mTempFile->open() );
+  mTempFile->close();
+}
+
+
+
+void TestQgsFileDownloader::cleanup()
+{
+  delete mTempFile;
+}
+
+void TestQgsFileDownloader::testValidDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "http://www.qgis.org" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( mCompleted );
+  QVERIFY( mProgress );
+  QVERIFY( !mError );
+  QVERIFY( !mCanceled );
+  QVERIFY( mTempFile->size() > 0 );
+}
+
+void TestQgsFileDownloader::testInValidDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "http://www.doesnotexistofthatimsure.qgis" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QVERIFY( mTempFile->size() == 0 );
+  QCOMPARE( mErrorMessage, QString( "Network error 3: Host www.doesnotexistofthatimsure.qgis not found" ) );
+}
+
+void TestQgsFileDownloader::testCanceledDownload()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), mTempFile->fileName(), true );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( !mError );
+  QVERIFY( mProgress );
+  QVERIFY( mCanceled );
+  QVERIFY( mTempFile->size() == 0 );
+}
+
+void TestQgsFileDownloader::testInvalidFile()
+{
+  makeCall( QUrl( "https://github.com/qgis/QGIS/archive/master.zip" ), QString() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Cannot open output file: " ) );
+}
+
+void TestQgsFileDownloader::testInvalidUrl()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "xyz://www" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"xyz\" is unknown" ) );
+}
+
+void TestQgsFileDownloader::testBlankUrl()
+{
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( "" ), mTempFile->fileName() );
+  QVERIFY( mExited );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+  QCOMPARE( mErrorMessage, QString( "Network error 301: Protocol \"\" is unknown" ) );
+}
+
+#ifndef QT_NO_OPENSSL
+void TestQgsFileDownloader::testSslError_data()
+{
+  QTest::addColumn<QString>( "url" );
+  QTest::addColumn<QString>( "result" );
+
+  QTest::newRow( "expired" ) << "https://expired.badssl.com/" << "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired";
+  QTest::newRow( "self-signed" ) << "https://self-signed.badssl.com/" << "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted";
+  QTest::newRow( "untrusted-root" ) << "https://untrusted-root.badssl.com/" << "Network error 6: SSL handshake failed;No certificates could be verified;SSL Errors: ;The issuer certificate of a locally looked up certificate could not be found;The root CA certificate is not trusted for this purpose";
+}
+
+void TestQgsFileDownloader::testSslError()
+{
+  QFETCH( QString, url );
+  QFETCH( QString, result );
+  QVERIFY( ! mTempFile->fileName().isEmpty() );
+  makeCall( QUrl( url ), mTempFile->fileName() );
+  QCOMPARE( mErrorMessage, result );
+  QVERIFY( !mCompleted );
+  QVERIFY( mError );
+  QVERIFY( !mCanceled );
+}
+
+#endif
+
+
+QTEST_MAIN( TestQgsFileDownloader )
+#include "testqgsfiledownloader.moc"
+
+
diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt
index c93f17b..0ab0ad7 100644
--- a/tests/src/python/CMakeLists.txt
+++ b/tests/src/python/CMakeLists.txt
@@ -112,6 +112,8 @@ ADD_PYTHON_TEST(PyQgsLayerDefinition test_qgslayerdefinition.py)
 ADD_PYTHON_TEST(PyQgsWFSProvider test_provider_wfs.py)
 ADD_PYTHON_TEST(PyQgsWFSProviderGUI test_provider_wfs_gui.py)
 ADD_PYTHON_TEST(PyQgsConsole test_console.py)
+ADD_PYTHON_TEST(PyQgsDBManagerGpkg test_db_manager_gpkg.py)
+ADD_PYTHON_TEST(PyQgsFileDownloader test_qgsfiledownloader.py)
 
 IF (NOT WIN32)
   ADD_PYTHON_TEST(PyQgsLogger test_qgslogger.py)
@@ -149,5 +151,7 @@ IF (WITH_SERVER)
   ADD_PYTHON_TEST(PyQgsServerAccessControl test_qgsserver_accesscontrol.py)
   ADD_PYTHON_TEST(PyQgsServerWFST test_qgsserver_wfst.py)
   ADD_PYTHON_TEST(PyQgsOfflineEditingWFS test_offline_editing_wfs.py)
-  ADD_PYTHON_TEST(PyQgsAuthManagerEndpointTest test_authmanager_endpoint.py)
+  ADD_PYTHON_TEST(PyQgsAuthManagerPasswordOWSTest test_authmanager_password_ows.py)
+  #ADD_PYTHON_TEST(PyQgsAuthManagerPKIOWSTest test_authmanager_pki_ows.py)
+  ADD_PYTHON_TEST(PyQgsAuthManagerPKIPostgresTest test_authmanager_pki_postgres.py)
 ENDIF (WITH_SERVER)
diff --git a/tests/src/python/qgis_wrapped_server.py b/tests/src/python/qgis_wrapped_server.py
index b270428..0ec7bc5 100644
--- a/tests/src/python/qgis_wrapped_server.py
+++ b/tests/src/python/qgis_wrapped_server.py
@@ -13,6 +13,21 @@ environment variables:
   * QGIS_SERVER_USERNAME (default ="username")
   * QGIS_SERVER_PASSWORD (default ="password")
 
+PKI authentication with HTTPS can be enabled with:
+
+  * QGIS_SERVER_PKI_CERTIFICATE (server certificate)
+  * QGIS_SERVER_PKI_KEY (server private key)
+  * QGIS_SERVER_PKI_AUTHORITY (root CA)
+  * QGIS_SERVER_PKI_USERNAME (valid username)
+
+ Sample run:
+
+ QGIS_SERVER_PKI_USERNAME=Gerardus QGIS_SERVER_PORT=47547 QGIS_SERVER_HOST=localhost \
+    QGIS_SERVER_PKI_KEY=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_key.pem \
+    QGIS_SERVER_PKI_CERTIFICATE=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/localhost_ssl_cert.pem \
+    QGIS_SERVER_PKI_AUTHORITY=/home/dev/QGIS/tests/testdata/auth_system/certs_keys/chains_subissuer-issuer-root_issuer2-root2.pem \
+    python /home/dev/QGIS/tests/src/python/qgis_wrapped_server.py
+
 .. note:: This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or
@@ -31,12 +46,27 @@ __revision__ = '$Format:%H$'
 
 import os
 import sys
+import ssl
 import urllib.parse
 from http.server import BaseHTTPRequestHandler, HTTPServer
 from qgis.server import QgsServer, QgsServerFilter
 
 QGIS_SERVER_PORT = int(os.environ.get('QGIS_SERVER_PORT', '8081'))
 QGIS_SERVER_HOST = os.environ.get('QGIS_SERVER_HOST', '127.0.0.1')
+# PKI authentication
+QGIS_SERVER_PKI_CERTIFICATE = os.environ.get('QGIS_SERVER_PKI_CERTIFICATE')
+QGIS_SERVER_PKI_KEY = os.environ.get('QGIS_SERVER_PKI_KEY')
+QGIS_SERVER_PKI_AUTHORITY = os.environ.get('QGIS_SERVER_PKI_AUTHORITY')
+QGIS_SERVER_PKI_USERNAME = os.environ.get('QGIS_SERVER_PKI_USERNAME')
+
+# Check if PKI - https is enabled
+https = (QGIS_SERVER_PKI_CERTIFICATE is not None and
+         os.path.isfile(QGIS_SERVER_PKI_CERTIFICATE) and
+         QGIS_SERVER_PKI_KEY is not None and
+         os.path.isfile(QGIS_SERVER_PKI_KEY) and
+         QGIS_SERVER_PKI_AUTHORITY is not None and
+         os.path.isfile(QGIS_SERVER_PKI_AUTHORITY) and
+         QGIS_SERVER_PKI_USERNAME)
 
 qgs_server = QgsServer()
 
@@ -66,8 +96,20 @@ if os.environ.get('QGIS_SERVER_HTTP_BASIC_AUTH') is not None:
 class Handler(BaseHTTPRequestHandler):
 
     def do_GET(self):
+        # For PKI: check the username from client certificate
+        if https:
+            try:
+                ssl.match_hostname(self.connection.getpeercert(), QGIS_SERVER_PKI_USERNAME)
+            except Exception as ex:
+                print("SSL Exception %s" % ex)
+                self.send_response(401)
+                self.end_headers()
+                self.wfile.write('UNAUTHORIZED')
+                return
         # CGI vars:
         for k, v in self.headers.items():
+            # Uncomment to print debug info about env vars passed into QGIS Server env
+            #print('Setting ENV var %s to %s' % ('HTTP_%s' % k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper(), v))
             qgs_server.putenv('HTTP_%s' % k.replace(' ', '-').replace('-', '_').replace(' ', '-').upper(), v)
         qgs_server.putenv('SERVER_PORT', str(self.server.server_port))
         qgs_server.putenv('SERVER_NAME', self.server.server_name)
@@ -96,7 +138,19 @@ class Handler(BaseHTTPRequestHandler):
 
 if __name__ == '__main__':
     server = HTTPServer((QGIS_SERVER_HOST, QGIS_SERVER_PORT), Handler)
-    print('Starting server on %s:%s, use <Ctrl-C> to stop' %
-          (QGIS_SERVER_HOST, server.server_port))
-    sys.stdout.flush()
+    if https:
+        server.socket = ssl.wrap_socket(server.socket,
+                                        certfile=QGIS_SERVER_PKI_CERTIFICATE,
+                                        keyfile=QGIS_SERVER_PKI_KEY,
+                                        ca_certs=QGIS_SERVER_PKI_AUTHORITY,
+                                        cert_reqs=ssl.CERT_REQUIRED,
+                                        server_side=True,
+                                        ssl_version=ssl.PROTOCOL_TLSv1)
+    message = 'Starting server on %s://%s:%s, use <Ctrl-C> to stop' % \
+              ('https' if https else 'http', QGIS_SERVER_HOST, server.server_port)
+    try:
+        print(message, flush=True)
+    except:
+        print(message)
+        sys.stdout.flush()
     server.serve_forever()
diff --git a/tests/src/python/test_authmanager_endpoint.py b/tests/src/python/test_authmanager_password_ows.py
similarity index 85%
copy from tests/src/python/test_authmanager_endpoint.py
copy to tests/src/python/test_authmanager_password_ows.py
index 25a0a74..f1a2eb9 100644
--- a/tests/src/python/test_authmanager_endpoint.py
+++ b/tests/src/python/test_authmanager_password_ows.py
@@ -8,7 +8,7 @@ and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
 configuration to access an HTTP Basic protected endpoint.
 
 
-From build dir, run: ctest -R PyQgsAuthManagerEnpointTest -V
+From build dir, run: ctest -R PyQgsAuthManagerPasswordOWSTest -V
 
 .. note:: This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -22,7 +22,10 @@ import subprocess
 import tempfile
 import random
 import string
-import urllib
+try:
+    from urllib.parse import quote
+except:
+    from urllib import quote
 
 __author__ = 'Alessandro Pasotti'
 __date__ = '18/09/2016'
@@ -47,7 +50,7 @@ from qgis.testing import (
 try:
     QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
 except:
-    QGIS_SERVER_ENDPOINT_PORT = '0' # Auto
+    QGIS_SERVER_ENDPOINT_PORT = '0'  # Auto
 
 
 QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
@@ -84,11 +87,14 @@ class TestAuthManager(unittest.TestCase):
         cls.auth_config.setConfig('username', cls.username)
         cls.auth_config.setConfig('password', cls.password)
         assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        cls.hostname = '127.0.0.1'
+        cls.protocol = 'http'
 
         os.environ['QGIS_SERVER_HTTP_BASIC_AUTH'] = '1'
         os.environ['QGIS_SERVER_USERNAME'] = cls.username
         os.environ['QGIS_SERVER_PASSWORD'] = cls.password
         os.environ['QGIS_SERVER_PORT'] = str(cls.port)
+        os.environ['QGIS_SERVER_HOST'] = cls.hostname
         server_path = os.path.dirname(os.path.realpath(__file__)) + \
             '/qgis_wrapped_server.py'
         cls.server = subprocess.Popen([sys.executable, server_path],
@@ -98,7 +104,7 @@ class TestAuthManager(unittest.TestCase):
         cls.port = int(re.findall(b':(\d+)', line)[0])
         assert cls.port != 0
         # Wait for the server process to start
-        assert waitServer('http://127.0.0.1:%s' % cls.port), "Server is not responding! http://127.0.0.1:%s" % cls.port
+        assert waitServer('%s://%s:%s' % (cls.protocol, cls.hostname, cls.port)), "Server is not responding! '%s://%s:%s" % (cls.protocol, cls.hostname, cls.port)
 
     @classmethod
     def tearDownClass(cls):
@@ -125,13 +131,16 @@ class TestAuthManager(unittest.TestCase):
         parms = {
             'srsname': 'EPSG:4326',
             'typename': type_name,
-            'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port, cls.project_path),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
             'version': 'auto',
             'table': '',
         }
         if authcfg is not None:
             parms.update({'authcfg': authcfg})
-        uri = ' '.join([("%s='%s'" % (k, v.decode('utf-8'))) for k, v in list(parms.items())])
+        try: # Py2
+            uri = ' '.join([("%s='%s'" % (k, v.decode('utf-8'))) for k, v in list(parms.items())])
+        except AttributeError: # Py3
+            uri = ' '.join([("%s='%s'" % (k, v)) for k, v in list(parms.items())])
         wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
         return wfs_layer
 
@@ -144,11 +153,11 @@ class TestAuthManager(unittest.TestCase):
             layer_name = 'wms_' + layers.replace(',', '')
         parms = {
             'crs': 'EPSG:4326',
-            'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port, cls.project_path),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
             'format': 'image/png',
             # This is needed because of a really weird implementation in QGIS Server, that
             # replaces _ in the the real layer name with spaces
-            'layers': urllib.quote(layers.replace('_', ' ')),
+            'layers': quote(layers.replace('_', ' ')),
             'styles': '',
             'version': 'auto',
             #'sql': '',
diff --git a/tests/src/python/test_authmanager_endpoint.py b/tests/src/python/test_authmanager_pki_ows.py
similarity index 56%
rename from tests/src/python/test_authmanager_endpoint.py
rename to tests/src/python/test_authmanager_pki_ows.py
index 25a0a74..6ef49ca 100644
--- a/tests/src/python/test_authmanager_endpoint.py
+++ b/tests/src/python/test_authmanager_pki_ows.py
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 """
-Tests for auth manager WMS/WFS using QGIS Server through HTTP Basic
+Tests for auth manager WMS/WFS using QGIS Server through PKI
 enabled qgis_wrapped_server.py.
 
 This is an integration test for QGIS Desktop Auth Manager WFS and WMS provider
@@ -8,7 +8,7 @@ and QGIS Server WFS/WMS that check if QGIS can use a stored auth manager auth
 configuration to access an HTTP Basic protected endpoint.
 
 
-From build dir, run: ctest -R PyQgsAuthManagerEnpointTest -V
+From build dir, run: ctest -R PyQgsAuthManagerPKIOWSTest -V
 
 .. note:: This program is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
@@ -20,12 +20,11 @@ import sys
 import re
 import subprocess
 import tempfile
-import random
-import string
 import urllib
+import stat
 
 __author__ = 'Alessandro Pasotti'
-__date__ = '18/09/2016'
+__date__ = '25/10/2016'
 __copyright__ = 'Copyright 2016, The QGIS Project'
 # This will get replaced with a git SHA1 when you do a git archive
 __revision__ = '$Format:%H$'
@@ -39,6 +38,9 @@ from qgis.core import (
     QgsVectorLayer,
     QgsRasterLayer,
 )
+
+from qgis.PyQt.QtNetwork import QSslCertificate
+
 from qgis.testing import (
     start_app,
     unittest,
@@ -47,7 +49,7 @@ from qgis.testing import (
 try:
     QGIS_SERVER_ENDPOINT_PORT = os.environ['QGIS_SERVER_ENDPOINT_PORT']
 except:
-    QGIS_SERVER_ENDPOINT_PORT = '0' # Auto
+    QGIS_SERVER_ENDPOINT_PORT = '0'  # Auto
 
 
 QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
@@ -60,6 +62,47 @@ qgis_app = start_app()
 class TestAuthManager(unittest.TestCase):
 
     @classmethod
+    def setUpAuth(cls):
+        """Run before all tests and set up authentication"""
+        authm = QgsAuthManager.instance()
+        assert (authm.setMasterPassword('masterpassword', True))
+        cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
+        cls.sslcert = os.path.join(cls.certsdata_path, 'gerardus_cert.pem')
+        cls.sslkey = os.path.join(cls.certsdata_path, 'gerardus_key.pem')
+        assert os.path.isfile(cls.sslcert)
+        assert os.path.isfile(cls.sslkey)
+        assert os.path.isfile(cls.sslrootcert_path)
+        os.chmod(cls.sslcert, stat.S_IRUSR)
+        os.chmod(cls.sslkey, stat.S_IRUSR)
+        os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
+        cls.auth_config = QgsAuthMethodConfig("PKI-Paths")
+        cls.auth_config.setConfig('certpath', cls.sslcert)
+        cls.auth_config.setConfig('keypath', cls.sslkey)
+        cls.auth_config.setName('test_pki_auth_config')
+        cls.username = 'Gerardus'
+        cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
+        assert cls.sslrootcert is not None
+        authm.storeCertAuthorities(cls.sslrootcert)
+        authm.rebuildCaCertsCache()
+        authm.rebuildTrustedCaCertsCache()
+        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        assert cls.auth_config.isValid()
+
+        cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
+        cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
+        cls.server_rootcert = cls.sslrootcert_path
+        os.chmod(cls.server_cert, stat.S_IRUSR)
+        os.chmod(cls.server_key, stat.S_IRUSR)
+        os.chmod(cls.server_rootcert, stat.S_IRUSR)
+
+        os.environ['QGIS_SERVER_HOST'] = cls.hostname
+        os.environ['QGIS_SERVER_PORT'] = str(cls.port)
+        os.environ['QGIS_SERVER_PKI_KEY'] = cls.server_key
+        os.environ['QGIS_SERVER_PKI_CERTIFICATE'] = cls.server_cert
+        os.environ['QGIS_SERVER_PKI_USERNAME'] = cls.username
+        os.environ['QGIS_SERVER_PKI_AUTHORITY'] = cls.server_rootcert
+
+    @classmethod
     def setUpClass(cls):
         """Run before all tests:
         Creates an auth configuration"""
@@ -71,34 +114,23 @@ class TestAuthManager(unittest.TestCase):
                 del os.environ[ev]
             except KeyError:
                 pass
-        cls.testdata_path = unitTestDataPath('qgis_server') + '/'
-        cls.project_path = cls.testdata_path + "test_project.qgs"
-        # Enable auth
-        #os.environ['QGIS_AUTH_PASSWORD_FILE'] = QGIS_AUTH_PASSWORD_FILE
-        authm = QgsAuthManager.instance()
-        assert (authm.setMasterPassword('masterpassword', True))
-        cls.auth_config = QgsAuthMethodConfig('Basic')
-        cls.auth_config.setName('test_auth_config')
-        cls.username = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
-        cls.password = cls.username[::-1] # reversed
-        cls.auth_config.setConfig('username', cls.username)
-        cls.auth_config.setConfig('password', cls.password)
-        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        cls.testdata_path = unitTestDataPath('qgis_server')
+        cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
+        cls.project_path = os.path.join(cls.testdata_path, "test_project.qgs")
+        cls.hostname = 'localhost'
+        cls.protocol = 'https'
 
-        os.environ['QGIS_SERVER_HTTP_BASIC_AUTH'] = '1'
-        os.environ['QGIS_SERVER_USERNAME'] = cls.username
-        os.environ['QGIS_SERVER_PASSWORD'] = cls.password
-        os.environ['QGIS_SERVER_PORT'] = str(cls.port)
-        server_path = os.path.dirname(os.path.realpath(__file__)) + \
-            '/qgis_wrapped_server.py'
+        cls.setUpAuth()
+
+        server_path = os.path.join(os.path.dirname(os.path.realpath(__file__)),
+                                   'qgis_wrapped_server.py')
         cls.server = subprocess.Popen([sys.executable, server_path],
                                       env=os.environ, stdout=subprocess.PIPE)
-
         line = cls.server.stdout.readline()
         cls.port = int(re.findall(b':(\d+)', line)[0])
         assert cls.port != 0
         # Wait for the server process to start
-        assert waitServer('http://127.0.0.1:%s' % cls.port), "Server is not responding! http://127.0.0.1:%s" % cls.port
+        assert waitServer('%s://%s:%s' % (cls.protocol, cls.hostname, cls.port)), "Server is not responding! %s://%s:%s" % (cls.protocol, cls.hostname, cls.port)
 
     @classmethod
     def tearDownClass(cls):
@@ -125,13 +157,16 @@ class TestAuthManager(unittest.TestCase):
         parms = {
             'srsname': 'EPSG:4326',
             'typename': type_name,
-            'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port, cls.project_path),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
             'version': 'auto',
             'table': '',
         }
         if authcfg is not None:
             parms.update({'authcfg': authcfg})
-        uri = ' '.join([("%s='%s'" % (k, v.decode('utf-8'))) for k, v in list(parms.items())])
+        try: # Py2
+            uri = ' '.join([("%s='%s'" % (k, v.decode('utf-8'))) for k, v in list(parms.items())])
+        except AttributeError: # Py3
+            uri = ' '.join([("%s='%s'" % (k, v)) for k, v in list(parms.items())])
         wfs_layer = QgsVectorLayer(uri, layer_name, 'WFS')
         return wfs_layer
 
@@ -144,7 +179,7 @@ class TestAuthManager(unittest.TestCase):
             layer_name = 'wms_' + layers.replace(',', '')
         parms = {
             'crs': 'EPSG:4326',
-            'url': 'http://127.0.0.1:%s/?map=%s' % (cls.port, cls.project_path),
+            'url': '%s://%s:%s/?map=%s' % (cls.protocol, cls.hostname, cls.port, cls.project_path),
             'format': 'image/png',
             # This is needed because of a really weird implementation in QGIS Server, that
             # replaces _ in the the real layer name with spaces
@@ -161,22 +196,15 @@ class TestAuthManager(unittest.TestCase):
 
     def testValidAuthAccess(self):
         """
-        Access the HTTP Basic protected layer with valid credentials
+        Access the protected layer with valid credentials
+        Note: cannot test invalid access in a separate test  because
+              it would fail the subsequent (valid) calls due to cached connections
         """
         wfs_layer = self._getWFSLayer('testlayer_èé', authcfg=self.auth_config.id())
         self.assertTrue(wfs_layer.isValid())
         wms_layer = self._getWMSLayer('testlayer_èé', authcfg=self.auth_config.id())
         self.assertTrue(wms_layer.isValid())
 
-    def testInvalidAuthAccess(self):
-        """
-        Access the HTTP Basic protected layer with no credentials
-        """
-        wfs_layer = self._getWFSLayer('testlayer èé')
-        self.assertFalse(wfs_layer.isValid())
-        wms_layer = self._getWMSLayer('testlayer_èé')
-        self.assertFalse(wms_layer.isValid())
-
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/src/python/test_authmanager_pki_postgres.py b/tests/src/python/test_authmanager_pki_postgres.py
new file mode 100644
index 0000000..63f5410
--- /dev/null
+++ b/tests/src/python/test_authmanager_pki_postgres.py
@@ -0,0 +1,233 @@
+# -*- coding: utf-8 -*-
+"""
+Tests for auth manager PKI access to postgres.
+
+This is an integration test for QGIS Desktop Auth Manager postgres provider that
+checks if QGIS can use a stored auth manager auth configuration to access
+a PKI protected postgres.
+
+Configuration form the environment:
+
+    * QGIS_POSTGRES_SERVER_PORT (default: 55432)
+    * QGIS_POSTGRES_EXECUTABLE_PATH (default: /usr/lib/postgresql/9.4/bin)
+
+
+From build dir, run: ctest -R PyQgsAuthManagerPKIPostgresTest -V
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+import os
+import time
+import signal
+import stat
+import subprocess
+import tempfile
+
+from shutil import rmtree
+
+from utilities import unitTestDataPath
+from qgis.core import (
+    QgsAuthManager,
+    QgsAuthMethodConfig,
+    QgsVectorLayer,
+    QgsDataSourceURI,
+    QgsWKBTypes,
+)
+
+
+from qgis.PyQt.QtNetwork import QSslCertificate
+
+from qgis.testing import (
+    start_app,
+    unittest,
+)
+
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '25/10/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+QGIS_POSTGRES_SERVER_PORT = os.environ.get('QGIS_POSTGRES_SERVER_PORT', '55432')
+QGIS_POSTGRES_EXECUTABLE_PATH = os.environ.get('QGIS_POSTGRES_EXECUTABLE_PATH', '/usr/lib/postgresql/9.4/bin')
+
+assert os.path.exists(QGIS_POSTGRES_EXECUTABLE_PATH)
+
+QGIS_AUTH_DB_DIR_PATH = tempfile.mkdtemp()
+
+# Postgres test path
+QGIS_PG_TEST_PATH = tempfile.mkdtemp()
+
+os.environ['QGIS_AUTH_DB_DIR_PATH'] = QGIS_AUTH_DB_DIR_PATH
+
+qgis_app = start_app()
+
+QGIS_POSTGRES_CONF_TEMPLATE = """
+hba_file = '%(tempfolder)s/pg_hba.conf'
+listen_addresses = '*'
+port = %(port)s
+max_connections = 100
+unix_socket_directories = '%(tempfolder)s'
+ssl = true
+ssl_ciphers = 'DEFAULT:!LOW:!EXP:!MD5:@STRENGTH'	# allowed SSL ciphers
+ssl_cert_file = '%(server_cert)s'
+ssl_key_file = '%(server_key)s'
+ssl_ca_file = '%(sslrootcert_path)s'
+password_encryption = on
+"""
+
+QGIS_POSTGRES_HBA_TEMPLATE = """
+hostssl    all           all             0.0.0.0/0              cert clientcert=1
+hostssl    all           all             ::1/0                  cert clientcert=1
+host       all           all             127.0.0.1/32           trust
+host       all           all             ::1/32                 trust
+"""
+
+
+class TestAuthManager(unittest.TestCase):
+
+    @classmethod
+    def setUpAuth(cls):
+        """Run before all tests and set up authentication"""
+        authm = QgsAuthManager.instance()
+        assert (authm.setMasterPassword('masterpassword', True))
+        cls.pg_conf = os.path.join(cls.tempfolder, 'postgresql.conf')
+        cls.pg_hba = os.path.join(cls.tempfolder, 'pg_hba.conf')
+        # Client side
+        cls.sslrootcert_path = os.path.join(cls.certsdata_path, 'chains_subissuer-issuer-root_issuer2-root2.pem')
+        cls.sslcert = os.path.join(cls.certsdata_path, 'gerardus_cert.pem')
+        cls.sslkey = os.path.join(cls.certsdata_path, 'gerardus_key.pem')
+        assert os.path.isfile(cls.sslcert)
+        assert os.path.isfile(cls.sslkey)
+        assert os.path.isfile(cls.sslrootcert_path)
+        os.chmod(cls.sslcert, stat.S_IRUSR)
+        os.chmod(cls.sslkey, stat.S_IRUSR)
+        os.chmod(cls.sslrootcert_path, stat.S_IRUSR)
+        cls.auth_config = QgsAuthMethodConfig("PKI-Paths")
+        cls.auth_config.setConfig('certpath', cls.sslcert)
+        cls.auth_config.setConfig('keypath', cls.sslkey)
+        cls.auth_config.setName('test_pki_auth_config')
+        cls.username = 'Gerardus'
+        cls.sslrootcert = QSslCertificate.fromPath(cls.sslrootcert_path)
+        assert cls.sslrootcert is not None
+        authm.storeCertAuthorities(cls.sslrootcert)
+        authm.rebuildCaCertsCache()
+        authm.rebuildTrustedCaCertsCache()
+        authm.rebuildCertTrustCache()
+        assert (authm.storeAuthenticationConfig(cls.auth_config)[0])
+        assert cls.auth_config.isValid()
+
+        # Server side
+        cls.server_cert = os.path.join(cls.certsdata_path, 'localhost_ssl_cert.pem')
+        cls.server_key = os.path.join(cls.certsdata_path, 'localhost_ssl_key.pem')
+        cls.server_rootcert = cls.sslrootcert_path
+        os.chmod(cls.server_cert, stat.S_IRUSR)
+        os.chmod(cls.server_key, stat.S_IRUSR)
+        os.chmod(cls.server_rootcert, stat.S_IRUSR)
+
+        # Place conf in the data folder
+        with open(cls.pg_conf, 'w+') as f:
+            f.write(QGIS_POSTGRES_CONF_TEMPLATE % {
+                'port': cls.port,
+                'tempfolder': cls.tempfolder,
+                'server_cert': cls.server_cert,
+                'server_key': cls.server_key,
+                'sslrootcert_path': cls.sslrootcert_path,
+            })
+
+        with open(cls.pg_hba, 'w+') as f:
+            f.write(QGIS_POSTGRES_HBA_TEMPLATE)
+
+    @classmethod
+    def setUpClass(cls):
+        """Run before all tests:
+        Creates an auth configuration"""
+        cls.port = QGIS_POSTGRES_SERVER_PORT
+        cls.dbname = 'test_pki'
+        cls.tempfolder = QGIS_PG_TEST_PATH
+        cls.certsdata_path = os.path.join(unitTestDataPath('auth_system'), 'certs_keys')
+        cls.hostname = 'localhost'
+        cls.data_path = os.path.join(cls.tempfolder, 'data')
+        os.mkdir(cls.data_path)
+
+        cls.setUpAuth()
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'initdb'), '-D', cls.data_path])
+
+        cls.server = subprocess.Popen([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'postgres'), '-D',
+                                       cls.data_path, '-c',
+                                       "config_file=%s" % cls.pg_conf],
+                                      env=os.environ,
+                                      stdout=subprocess.PIPE,
+                                      stderr=subprocess.PIPE)
+        # Wait max 10 secs for the server to start
+        end = time.time() + 10
+        while True:
+            line = cls.server.stderr.readline()
+            print(line)
+            if line.find(b"database system is ready to accept") != -1:
+                break
+            if time.time() > end:
+                raise Exception("Timeout connecting to postgresql")
+        # Create a DB
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'createdb'), '-h', 'localhost', '-p', cls.port, 'test_pki'])
+        # Inject test SQL from test path
+        test_sql = os.path.join(unitTestDataPath('provider'), 'testdata_pg.sql')
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-f', test_sql, cls.dbname])
+        # Create a role
+        subprocess.check_call([os.path.join(QGIS_POSTGRES_EXECUTABLE_PATH, 'psql'), '-h', 'localhost', '-p', cls.port, '-c', 'CREATE ROLE "%s" WITH SUPERUSER LOGIN' % cls.username, cls.dbname])
+
+    @classmethod
+    def tearDownClass(cls):
+        """Run after all tests"""
+        cls.server.terminate()
+        os.kill(cls.server.pid, signal.SIGABRT)
+        del cls.server
+        time.sleep(2)
+        rmtree(QGIS_AUTH_DB_DIR_PATH)
+        rmtree(cls.tempfolder)
+
+    def setUp(self):
+        """Run before each test."""
+        pass
+
+    def tearDown(self):
+        """Run after each test."""
+        pass
+
+    @classmethod
+    def _getPostGISLayer(cls, type_name, layer_name=None, authcfg=None):
+        """
+        PG layer factory
+        """
+        if layer_name is None:
+            layer_name = 'pg_' + type_name
+        uri = QgsDataSourceURI()
+        uri.setWkbType(QgsWKBTypes.Point)
+        uri.setConnection("localhost", cls.port, cls.dbname, "", "", QgsDataSourceURI.SSLverifyFull, authcfg)
+        uri.setKeyColumn('pk')
+        uri.setSrid('EPSG:4326')
+        uri.setDataSource('qgis_test', 'someData', "geom", "", "pk")
+        # Note: do not expand here!
+        layer = QgsVectorLayer(uri.uri(False), layer_name, 'postgres')
+        return layer
+
+    def testValidAuthAccess(self):
+        """
+        Access the protected layer with valid credentials
+        """
+        pg_layer = self._getPostGISLayer('testlayer_èé', authcfg=self.auth_config.id())
+        self.assertTrue(pg_layer.isValid())
+
+    def testInvalidAuthAccess(self):
+        """
+        Access the protected layer with not valid credentials
+        """
+        pg_layer = self._getPostGISLayer('testlayer_èé')
+        self.assertFalse(pg_layer.isValid())
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_db_manager_gpkg.py b/tests/src/python/test_db_manager_gpkg.py
new file mode 100644
index 0000000..1980113
--- /dev/null
+++ b/tests/src/python/test_db_manager_gpkg.py
@@ -0,0 +1,433 @@
+# -*- coding: utf-8 -*-
+"""QGIS Unit tests for the DBManager GPKG plugin
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+__author__ = 'Even Rouault'
+__date__ = '2016-10-17'
+__copyright__ = 'Copyright 2016, Even Rouault'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+import qgis  # NOQA
+
+import os
+import tempfile
+import shutil
+from osgeo import gdal, ogr, osr
+
+from qgis.core import QgsDataSourceURI
+from qgis.PyQt.QtCore import QCoreApplication, QSettings
+from qgis.testing import start_app, unittest
+
+from plugins.db_manager.db_plugins import supportedDbTypes, createDbPlugin
+from plugins.db_manager.db_plugins.plugin import TableField
+
+
+def GDAL_COMPUTE_VERSION(maj, min, rev):
+    return ((maj) * 1000000 + (min) * 10000 + (rev) * 100)
+
+
+class TestPyQgsDBManagerGpkg(unittest.TestCase):
+
+    @classmethod
+    def setUpClass(cls):
+        """Run before all tests"""
+
+        QCoreApplication.setOrganizationName("QGIS_Test")
+        QCoreApplication.setOrganizationDomain("TestPyQgsDBManagerGpkg.com")
+        QCoreApplication.setApplicationName("TestPyQgsDBManagerGpkg")
+        QSettings().clear()
+        start_app()
+
+        cls.basetestpath = tempfile.mkdtemp()
+
+        cls.test_gpkg = os.path.join(cls.basetestpath, 'TestPyQgsDBManagerGpkg.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(cls.test_gpkg)
+        lyr = ds.CreateLayer('testLayer', geom_type=ogr.wkbLineString, options=['SPATIAL_INDEX=NO'])
+        cls.supportsAlterFieldDefn = lyr.TestCapability(ogr.OLCAlterFieldDefn) == 1
+        lyr.CreateField(ogr.FieldDefn('text_field', ogr.OFTString))
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f['text_field'] = 'foo'
+        f.SetGeometry(ogr.CreateGeometryFromWkt('LINESTRING(1 2,3 4)'))
+        lyr.CreateFeature(f)
+        f = None
+        ds = None
+
+    @classmethod
+    def tearDownClass(cls):
+        """Run after all tests"""
+
+        QSettings().clear()
+        shutil.rmtree(cls.basetestpath, True)
+
+    def testSupportedDbTypes(self):
+        self.assertIn('gpkg', supportedDbTypes())
+
+    def testCreateDbPlugin(self):
+        plugin = createDbPlugin('gpkg')
+        self.assertIsNotNone(plugin)
+
+    def testConnect(self):
+        connection_name = 'testConnect'
+        plugin = createDbPlugin('gpkg')
+
+        uri = QgsDataSourceURI()
+        uri.setDatabase(self.test_gpkg)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connections = plugin.connections()
+        self.assertEqual(len(connections), 1)
+
+        connection = createDbPlugin('gpkg', connection_name + '_does_not_exist')
+        connection_succeeded = False
+        try:
+            connection.connect()
+            connection_succeeded = True
+        except:
+            pass
+        self.assertFalse(connection_succeeded, 'exception should have been raised')
+
+        connection = connections[0]
+        connection.connect()
+
+        connection.reconnect()
+
+        connection.remove()
+
+        self.assertEqual(len(plugin.connections()), 0)
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection_succeeded = False
+        try:
+            connection.connect()
+            connection_succeeded = True
+        except:
+            pass
+        self.assertFalse(connection_succeeded, 'exception should have been raised')
+
+    def testListLayer(self):
+        connection_name = 'testListLayer'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+        uri.setDatabase(self.test_gpkg)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+        self.assertEqual(table.name, 'testLayer')
+        info = table.info()
+        expected_html = """<div class="section"><h2>General info</h2><div><table><tr><td>Relation type: </td><td>Table </td></tr><tr><td>Rows: </td><td>1 </td></tr></table></div></div><div class="section"><h2>GeoPackage</h2><div><table><tr><td>Column: </td><td>geom </td></tr><tr><td>Geometry: </td><td>LINESTRING </td></tr><tr><td>Dimension: </td><td>XY </td></tr><tr><td>Spatial ref: </td><td>Undefined (-1) </td></tr><tr><td>Exte [...]
+        self.assertEqual(info.toHtml(), expected_html, info.toHtml())
+
+        connection.remove()
+
+    def testCreateRenameDeleteTable(self):
+        connection_name = 'testCreateRenameDeleteTable'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg_new = os.path.join(self.basetestpath, 'testCreateRenameDeleteTable.gpkg')
+        shutil.copy(self.test_gpkg, test_gpkg_new)
+
+        uri.setDatabase(test_gpkg_new)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+        self.assertTrue(table.rename('newName'))
+        self.assertEqual(table.name, 'newName')
+
+        connection.reconnect()
+
+        db = connection.database()
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+        self.assertEqual(table.name, 'newName')
+
+        fields = []
+        geom = ['geometry', 'POINT', 4326, 3]
+        field1 = TableField(table)
+        field1.name = 'fid'
+        field1.dataType = 'INTEGER'
+        field1.notNull = True
+        field1.primaryKey = True
+
+        field2 = TableField(table)
+        field2.name = 'str_field'
+        field2.dataType = 'TEXT'
+        field2.modifier = 20
+
+        fields = [field1, field2]
+        self.assertTrue(db.createVectorTable('newName2', fields, geom))
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 2)
+        new_table = tables[1]
+        self.assertEqual(new_table.name, 'newName2')
+        fields = new_table.fields()
+        self.assertEqual(len(fields), 3)
+        self.assertFalse(new_table.hasSpatialIndex())
+
+        self.assertTrue(new_table.createSpatialIndex())
+        self.assertTrue(new_table.hasSpatialIndex())
+
+        self.assertTrue(new_table.delete())
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+
+        connection.remove()
+
+    def testCreateRenameDeleteFields(self):
+
+        if not self.supportsAlterFieldDefn:
+            return
+
+        connection_name = 'testCreateRenameDeleteFields'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg_new = os.path.join(self.basetestpath, 'testCreateRenameDeleteFields.gpkg')
+        shutil.copy(self.test_gpkg, test_gpkg_new)
+
+        uri.setDatabase(test_gpkg_new)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+
+        field_before_count = len(table.fields())
+
+        field = TableField(table)
+        field.name = 'real_field'
+        field.dataType = 'DOUBLE'
+        self.assertTrue(table.addField(field))
+
+        self.assertEqual(len(table.fields()), field_before_count + 1)
+
+        self.assertTrue(field.update('real_field2', new_type_str='TEXT (30)', new_not_null=True, new_default_str='foo'))
+
+        field = table.fields()[field_before_count]
+        self.assertEqual(field.name, 'real_field2')
+        self.assertEqual(field.dataType, 'TEXT(30)')
+        self.assertEqual(field.notNull, 1)
+        self.assertEqual(field.default, "'foo'")
+
+        self.assertTrue(table.deleteField(field))
+
+        self.assertEqual(len(table.fields()), field_before_count)
+
+        connection.remove()
+
+    def testTableDataModel(self):
+        connection_name = 'testTableDataModel'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+        uri.setDatabase(self.test_gpkg)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+        self.assertEqual(table.name, 'testLayer')
+        model = table.tableDataModel(None)
+        self.assertEqual(model.rowCount(), 1)
+        self.assertEqual(model.getData(0, 0), 1) # fid
+        self.assertEqual(model.getData(0, 1), 'LINESTRING (1 2,3 4)')
+        self.assertEqual(model.getData(0, 2), 'foo')
+
+        connection.remove()
+
+    def testRaster(self):
+
+        if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 0, 2):
+            return
+
+        connection_name = 'testRaster'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg_new = os.path.join(self.basetestpath, 'testRaster.gpkg')
+        shutil.copy(self.test_gpkg, test_gpkg_new)
+        mem_ds = gdal.GetDriverByName('MEM').Create('', 20, 20)
+        mem_ds.SetGeoTransform([2, 0.01, 0, 49, 0, -0.01])
+        sr = osr.SpatialReference()
+        sr.ImportFromEPSG(4326)
+        mem_ds.SetProjection(sr.ExportToWkt())
+        mem_ds.GetRasterBand(1).Fill(255)
+        gdal.GetDriverByName('GPKG').CreateCopy(test_gpkg_new, mem_ds, options=['APPEND_SUBDATASET=YES', 'RASTER_TABLE=raster_table'])
+        mem_ds = None
+
+        uri.setDatabase(test_gpkg_new)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 2)
+        table = None
+        for i in range(2):
+            if tables[i].name == 'raster_table':
+                table = tables[i]
+                break
+        self.assertIsNotNone(table)
+        info = table.info()
+        expected_html = """<div class="section"><h2>General info</h2><div><table><tr><td>Relation type: </td><td>Table </td></tr><tr><td>Rows: </td><td>Unknown (<a href="action:rows/count">find out</a>) </td></tr></table></div></div><div class="section"><h2>GeoPackage</h2><div><table><tr><td>Column: </td><td> </td></tr><tr><td>Geometry: </td><td>RASTER </td></tr><tr><td>Spatial ref: </td><td>WGS 84 geodetic (4326) </td></tr><tr><td>Extent [...]
+        self.assertEqual(info.toHtml(), expected_html, info.toHtml())
+
+        connection.remove()
+
+    def testTwoRaster(self):
+
+        if int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(2, 0, 2):
+            return
+
+        connection_name = 'testTwoRaster'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg_new = os.path.join(self.basetestpath, 'testTwoRaster.gpkg')
+        shutil.copy(self.test_gpkg, test_gpkg_new)
+        mem_ds = gdal.GetDriverByName('MEM').Create('', 20, 20)
+        mem_ds.SetGeoTransform([2, 0.01, 0, 49, 0, -0.01])
+        sr = osr.SpatialReference()
+        sr.ImportFromEPSG(4326)
+        mem_ds.SetProjection(sr.ExportToWkt())
+        mem_ds.GetRasterBand(1).Fill(255)
+        for i in range(2):
+            gdal.GetDriverByName('GPKG').CreateCopy(test_gpkg_new, mem_ds, options=['APPEND_SUBDATASET=YES', 'RASTER_TABLE=raster_table%d' % (i + 1)])
+        mem_ds = None
+
+        uri.setDatabase(test_gpkg_new)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 3)
+        table = None
+        for i in range(2):
+            if tables[i].name.startswith('raster_table'):
+                table = tables[i]
+                info = table.info()
+                info.toHtml()
+
+        connection.remove()
+
+    def testNonSpatial(self):
+
+        connection_name = 'testNonSpatial'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg = os.path.join(self.basetestpath, 'testNonSpatial.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(test_gpkg)
+        lyr = ds.CreateLayer('testNonSpatial', geom_type=ogr.wkbNone)
+        lyr.CreateField(ogr.FieldDefn('text_field', ogr.OFTString))
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f['text_field'] = 'foo'
+        lyr.CreateFeature(f)
+        f = None
+        ds = None
+
+        uri.setDatabase(test_gpkg)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        self.assertEqual(len(tables), 1)
+        table = tables[0]
+        self.assertEqual(table.name, 'testNonSpatial')
+        info = table.info()
+        expected_html = """<div class="section"><h2>General info</h2><div><table><tr><td>Relation type: </td><td>Table </td></tr><tr><td>Rows: </td><td>1 </td></tr></table></div></div><div class="section"><h2>Fields</h2><div><table class="header"><tr><th># </th><th>Name </th><th>Type </th><th>Null </th><th>Default </th></tr><tr><td>0 </td><td class="underline">fid </td><td>INTEGER </td><td>Y </td><td> </td></tr><tr><td [...]
+        self.assertEqual(info.toHtml(), expected_html, info.toHtml())
+
+        connection.remove()
+
+    def testAllGeometryTypes(self):
+
+        connection_name = 'testAllGeometryTypes'
+        plugin = createDbPlugin('gpkg')
+        uri = QgsDataSourceURI()
+
+        test_gpkg = os.path.join(self.basetestpath, 'testAllGeometryTypes.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(test_gpkg)
+        ds.CreateLayer('testPoint', geom_type=ogr.wkbPoint)
+        ds.CreateLayer('testLineString', geom_type=ogr.wkbLineString)
+        ds.CreateLayer('testPolygon', geom_type=ogr.wkbPolygon)
+        ds.CreateLayer('testMultiPoint', geom_type=ogr.wkbMultiPoint)
+        ds.CreateLayer('testMultiLineString', geom_type=ogr.wkbMultiLineString)
+        ds.CreateLayer('testMultiPolygon', geom_type=ogr.wkbMultiPolygon)
+        ds.CreateLayer('testGeometryCollection', geom_type=ogr.wkbGeometryCollection)
+
+        if int(gdal.VersionInfo('VERSION_NUM')) >= GDAL_COMPUTE_VERSION(2, 0, 0):
+            ds.CreateLayer('testCircularString', geom_type=ogr.wkbCircularString)
+            ds.CreateLayer('testCompoundCurve', geom_type=ogr.wkbCompoundCurve)
+            ds.CreateLayer('testCurvePolygon', geom_type=ogr.wkbCurvePolygon)
+            ds.CreateLayer('testMultiCurve', geom_type=ogr.wkbMultiCurve)
+            ds.CreateLayer('testMultiSurface', geom_type=ogr.wkbMultiSurface)
+        ds = None
+
+        uri.setDatabase(test_gpkg)
+        self.assertTrue(plugin.addConnection(connection_name, uri))
+
+        connection = createDbPlugin('gpkg', connection_name)
+        connection.connect()
+
+        db = connection.database()
+        self.assertIsNotNone(db)
+
+        tables = db.tables()
+        for i in range(len(tables)):
+            table = tables[i]
+            info = table.info()
+
+        connection.remove()
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_provider_ogr_gpkg.py b/tests/src/python/test_provider_ogr_gpkg.py
index 3024ab5..5669655 100644
--- a/tests/src/python/test_provider_ogr_gpkg.py
+++ b/tests/src/python/test_provider_ogr_gpkg.py
@@ -20,6 +20,7 @@ import shutil
 import glob
 from osgeo import gdal, ogr
 
+from qgis.PyQt.QtCore import QCoreApplication, QSettings
 from qgis.core import QgsVectorLayer, QgsFeature, QgsGeometry, QgsFeatureRequest, QgsRectangle
 from qgis.testing import start_app, unittest
 from utilities import unitTestDataPath
@@ -48,10 +49,17 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
         # Create test layer
         cls.basetestpath = tempfile.mkdtemp()
 
+        QCoreApplication.setOrganizationName("QGIS_Test")
+        QCoreApplication.setOrganizationDomain("TestPyQgsOGRProviderGpkg.com")
+        QCoreApplication.setApplicationName("TestPyQgsOGRProviderGpkg")
+        QSettings().clear()
+        start_app()
+
     @classmethod
     def tearDownClass(cls):
         """Run after all tests"""
         shutil.rmtree(cls.basetestpath, True)
+        QSettings().clear()
 
     def testSingleToMultiPolygonPromotion(self):
 
@@ -200,5 +208,66 @@ class TestPyQgsOGRProviderGpkg(unittest.TestCase):
         self.assertTrue(QgsGeometry.compare(provider_extent.asPolygon()[0], reference.asPolygon()[0], 0.00001),
                         provider_extent.asPolygon()[0])
 
+    def testSelectSubsetString(self):
+
+        tmpfile = os.path.join(self.basetestpath, 'testSelectSubsetString.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
+        lyr = ds.CreateLayer('test', geom_type=ogr.wkbMultiPolygon)
+        lyr.CreateField(ogr.FieldDefn('foo', ogr.OFTString))
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f['foo'] = 'bar'
+        lyr.CreateFeature(f)
+        f = None
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f['foo'] = 'baz'
+        lyr.CreateFeature(f)
+        f = None
+        ds = None
+
+        vl = QgsVectorLayer('{}|layerid=0'.format(tmpfile), 'test', 'ogr')
+        vl.setSubsetString("SELECT fid, foo FROM test WHERE foo = 'baz'")
+        got = [feat for feat in vl.getFeatures()]
+        self.assertEqual(len(got), 1)
+
+    def testDisablewalForSqlite3(self):
+        ''' Test disabling walForSqlite3 setting '''
+        QSettings().setValue("/qgis/walForSqlite3", False)
+
+        tmpfile = os.path.join(self.basetestpath, 'testDisablewalForSqlite3.gpkg')
+        ds = ogr.GetDriverByName('GPKG').CreateDataSource(tmpfile)
+        lyr = ds.CreateLayer('test', geom_type=ogr.wkbPoint)
+        lyr.CreateField(ogr.FieldDefn('attr0', ogr.OFTInteger))
+        lyr.CreateField(ogr.FieldDefn('attr1', ogr.OFTInteger))
+        f = ogr.Feature(lyr.GetLayerDefn())
+        f.SetGeometry(ogr.CreateGeometryFromWkt('POINT(0 0)'))
+        lyr.CreateFeature(f)
+        f = None
+        ds = None
+
+        vl = QgsVectorLayer(u'{}'.format(tmpfile), u'test', u'ogr')
+
+        # Test that we are using default delete mode and not WAL
+        ds = ogr.Open(tmpfile)
+        lyr = ds.ExecuteSQL('PRAGMA journal_mode')
+        f = lyr.GetNextFeature()
+        res = f.GetField(0)
+        ds.ReleaseResultSet(lyr)
+        ds = None
+        self.assertEqual(res, 'delete')
+
+        self.assertTrue(vl.startEditing())
+        feature = next(vl.getFeatures())
+        self.assertTrue(vl.changeAttributeValue(feature.id(), 1, 1001))
+
+        # Commit changes
+        cbk = ErrorReceiver()
+        vl.dataProvider().raiseError.connect(cbk.receiveError)
+        self.assertTrue(vl.commitChanges())
+        self.assertIsNone(cbk.msg)
+        vl = None
+
+        QSettings().setValue("/qgis/walForSqlite3", None)
+
+
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/src/python/test_qgsfiledownloader.py b/tests/src/python/test_qgsfiledownloader.py
new file mode 100644
index 0000000..776ad49
--- /dev/null
+++ b/tests/src/python/test_qgsfiledownloader.py
@@ -0,0 +1,147 @@
+# -*- coding: utf-8 -*-
+"""
+Test the QgsFileDownloader class
+
+.. note:: This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation; either version 2 of the License, or
+(at your option) any later version.
+"""
+from __future__ import print_function
+from future import standard_library
+import os
+import tempfile
+from functools import partial
+from qgis.PyQt.QtCore import QEventLoop, QUrl, QTimer
+from qgis.gui import (QgsFileDownloader,)
+from qgis.testing import start_app, unittest
+
+standard_library.install_aliases()
+
+__author__ = 'Alessandro Pasotti'
+__date__ = '08/11/2016'
+__copyright__ = 'Copyright 2016, The QGIS Project'
+# This will get replaced with a git SHA1 when you do a git archive
+__revision__ = '$Format:%H$'
+
+
+start_app()
+
+
+class TestQgsFileDownloader(unittest.TestCase):
+
+    """
+    This class tests the QgsFileDownloader class
+    """
+
+    def _make_download(self, url, destination, cancel=False):
+        self.completed_was_called = False
+        self.error_was_called = False
+        self.canceled_was_called = False
+        self.progress_was_called = False
+        self.exited_was_called = False
+
+        loop = QEventLoop()
+
+        downloader = QgsFileDownloader(QUrl(url), destination, False)
+        downloader.downloadCompleted.connect(partial(self._set_slot, 'completed'))
+        downloader.downloadExited.connect(partial(self._set_slot, 'exited'))
+        downloader.downloadCanceled.connect(partial(self._set_slot, 'canceled'))
+        downloader.downloadError.connect(partial(self._set_slot, 'error'))
+        downloader.downloadProgress.connect(partial(self._set_slot, 'progress'))
+
+        downloader.downloadExited.connect(loop.quit)
+
+        if cancel:
+            downloader.downloadProgress.connect(downloader.onDownloadCanceled)
+
+        loop.exec_()
+
+    def test_validDownload(self):
+        """Tests a valid download"""
+        destination = tempfile.mktemp()
+        self._make_download('http://www.qgis.org', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertTrue(self.completed_was_called)
+        self.assertTrue(self.progress_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertFalse(self.error_was_called)
+        self.assertTrue(os.path.isfile(destination))
+        self.assertGreater(os.path.getsize(destination), 0)
+
+    def test_inValidDownload(self):
+        """Tests an invalid download"""
+        destination = tempfile.mktemp()
+        self._make_download('http://www.doesnotexistofthatimsure.qgis', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertTrue(self.progress_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertEqual(self.error_args[1], [u'Network error 3: Host www.doesnotexistofthatimsure.qgis not found'])
+        self.assertFalse(os.path.isfile(destination))
+
+    def test_dowloadCanceled(self):
+        """Tests user canceled download"""
+        destination = tempfile.mktemp()
+        self._make_download('https://github.com/qgis/QGIS/archive/master.zip', destination, True)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertTrue(self.canceled_was_called)
+        self.assertFalse(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+
+    def test_InvalidUrl(self):
+        destination = tempfile.mktemp()
+        self._make_download('xyz://www', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+        self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"xyz\" is unknown"])
+
+    def test_InvalidFile(self):
+        self._make_download('https://github.com/qgis/QGIS/archive/master.zip', "")
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertEqual(self.error_args[1], [u"Cannot open output file: "])
+
+    def test_BlankUrl(self):
+        destination = tempfile.mktemp()
+        self._make_download('', destination)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called)
+        self.assertFalse(self.canceled_was_called)
+        self.assertTrue(self.error_was_called)
+        self.assertFalse(os.path.isfile(destination))
+        self.assertEqual(self.error_args[1], [u"Network error 301: Protocol \"\" is unknown"])
+
+    def ssl_compare(self, name, url, error):
+        destination = tempfile.mktemp()
+        self._make_download(url, destination)
+        msg = "Failed in %s: %s" % (name, url)
+        self.assertTrue(self.exited_was_called)
+        self.assertFalse(self.completed_was_called, msg)
+        self.assertFalse(self.canceled_was_called, msg)
+        self.assertTrue(self.error_was_called, msg)
+        self.assertFalse(os.path.isfile(destination), msg)
+        result = sorted(self.error_args[1])
+        result = ';'.join(result)
+        self.assertEqual(result, error, msg + "expected:\n%s\nactual:\n%s\n" % (result, error))
+
+    def test_sslExpired(self):
+        self.ssl_compare("expired", "https://expired.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate has expired")
+        self.ssl_compare("self-signed", "https://self-signed.badssl.com/", "Network error 6: SSL handshake failed;SSL Errors: ;The certificate is self-signed, and untrusted")
+        self.ssl_compare("untrusted-root", "https://untrusted-root.badssl.com/", "Network error 6: SSL handshake failed;No certificates could be verified;SSL Errors: ;The issuer certificate of a locally looked up certificate could not be found;The root CA certificate is not trusted for this purpose")
+
+    def _set_slot(self, *args, **kwargs):
+        #print('_set_slot(%s) called' % args[0])
+        setattr(self, args[0] + '_was_called', True)
+        setattr(self, args[0] + '_args', args)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/src/python/test_qgsserver.py b/tests/src/python/test_qgsserver.py
index a41692b..c9beb68 100644
--- a/tests/src/python/test_qgsserver.py
+++ b/tests/src/python/test_qgsserver.py
@@ -459,11 +459,13 @@ class TestQgsServer(unittest.TestCase):
                 report, encoded_rendered_file.strip(), tempfile.gettempdir(), image
             )
 
-        with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
-            encoded_diff_file = base64.b64encode(diff_file.read())
-            message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
-                encoded_diff_file.strip(), tempfile.gettempdir(), image
-            )
+        # If the failure is in image sizes the diff file will not exists.
+        if os.path.exists(os.path.join(tempfile.gettempdir(), image + "_result_diff.png")):
+            with open(os.path.join(tempfile.gettempdir(), image + "_result_diff.png"), "rb") as diff_file:
+                encoded_diff_file = base64.b64encode(diff_file.read())
+                message += "\nDiff:\necho '%s' | base64 -d > %s/%s_result_diff.png" % (
+                    encoded_diff_file.strip(), tempfile.gettempdir(), image
+                )
 
         self.assertTrue(test, message)
 
diff --git a/tests/src/python/test_qgsvectorfilewriter.py b/tests/src/python/test_qgsvectorfilewriter.py
index f2d94f8..bd261bc 100644
--- a/tests/src/python/test_qgsvectorfilewriter.py
+++ b/tests/src/python/test_qgsvectorfilewriter.py
@@ -29,6 +29,7 @@ from qgis.core import (QgsVectorLayer,
 from qgis.PyQt.QtCore import QDate, QTime, QDateTime, QVariant, QDir
 import os
 import osgeo.gdal
+from osgeo import gdal, ogr
 import platform
 from qgis.testing import start_app, unittest
 from utilities import writeShape, compareWkt
@@ -461,6 +462,177 @@ class TestQgsVectorLayer(unittest.TestCase):
         int8_idx = created_layer.fieldNameIndex('int8')
         self.assertEqual(f.attributes()[int8_idx], 2123456789)
 
+    def testOverwriteLayer(self):
+        """Tests writing a layer with a field value converter."""
+
+        ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
+        provider = ml.dataProvider()
+
+        ft = QgsFeature()
+        ft.setAttributes([1])
+        provider.addFeatures([ft])
+
+        options = QgsVectorFileWriter.SaveVectorOptions()
+        options.driverName = 'GPKG'
+        options.layerName = 'test'
+        filename = '/vsimem/out.gpkg'
+        write_result = QgsVectorFileWriter.writeAsVectorFormat(
+            ml,
+            filename,
+            options)
+        self.assertEqual(write_result, QgsVectorFileWriter.NoError)
+
+        ds = ogr.Open(filename, update=1)
+        lyr = ds.GetLayerByName('test')
+        self.assertIsNotNone(lyr)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 1)
+        ds.CreateLayer('another_layer')
+        del f
+        del lyr
+        del ds
+
+        caps = QgsVectorFileWriter.editionCapabilities(filename)
+        self.assertTrue((caps & QgsVectorFileWriter.CanAddNewLayer))
+        self.assertTrue((caps & QgsVectorFileWriter.CanAppendToExistingLayer))
+        self.assertTrue((caps & QgsVectorFileWriter.CanAddNewFieldsToExistingLayer))
+        self.assertTrue((caps & QgsVectorFileWriter.CanDeleteLayer))
+
+        self.assertTrue(QgsVectorFileWriter.targetLayerExists(filename, 'test'))
+
+        self.assertFalse(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0]))
+
+        # Test CreateOrOverwriteLayer
+        ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
+        provider = ml.dataProvider()
+
+        ft = QgsFeature()
+        ft.setAttributes([2])
+        provider.addFeatures([ft])
+
+        options = QgsVectorFileWriter.SaveVectorOptions()
+        options.driverName = 'GPKG'
+        options.layerName = 'test'
+        options.actionOnExistingFile = QgsVectorFileWriter.CreateOrOverwriteLayer
+        filename = '/vsimem/out.gpkg'
+        write_result = QgsVectorFileWriter.writeAsVectorFormat(
+            ml,
+            filename,
+            options)
+        self.assertEqual(write_result, QgsVectorFileWriter.NoError)
+
+        ds = ogr.Open(filename)
+        lyr = ds.GetLayerByName('test')
+        self.assertIsNotNone(lyr)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 2)
+        # another_layer should still exist
+        self.assertIsNotNone(ds.GetLayerByName('another_layer'))
+        del f
+        del lyr
+        del ds
+
+        # Test CreateOrOverwriteFile
+        ml = QgsVectorLayer('Point?field=firstfield:int', 'test', 'memory')
+        provider = ml.dataProvider()
+
+        ft = QgsFeature()
+        ft.setAttributes([3])
+        provider.addFeatures([ft])
+
+        options = QgsVectorFileWriter.SaveVectorOptions()
+        options.driverName = 'GPKG'
+        options.layerName = 'test'
+        filename = '/vsimem/out.gpkg'
+        write_result = QgsVectorFileWriter.writeAsVectorFormat(
+            ml,
+            filename,
+            options)
+        self.assertEqual(write_result, QgsVectorFileWriter.NoError)
+
+        ds = ogr.Open(filename)
+        lyr = ds.GetLayerByName('test')
+        self.assertIsNotNone(lyr)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 3)
+        # another_layer should no longer exist
+        self.assertIsNone(ds.GetLayerByName('another_layer'))
+        del f
+        del lyr
+        del ds
+
+        # Test AppendToLayerNoNewFields
+        ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
+        provider = ml.dataProvider()
+
+        ft = QgsFeature()
+        ft.setAttributes([4, -10])
+        provider.addFeatures([ft])
+
+        self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
+
+        options = QgsVectorFileWriter.SaveVectorOptions()
+        options.driverName = 'GPKG'
+        options.layerName = 'test'
+        options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerNoNewFields
+        filename = '/vsimem/out.gpkg'
+        write_result = QgsVectorFileWriter.writeAsVectorFormat(
+            ml,
+            filename,
+            options)
+        self.assertEqual(write_result, QgsVectorFileWriter.NoError)
+
+        ds = ogr.Open(filename)
+        lyr = ds.GetLayerByName('test')
+        self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 1)
+        self.assertIsNotNone(lyr)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 3)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 4)
+        del f
+        del lyr
+        del ds
+
+        # Test AppendToLayerAddFields
+        ml = QgsVectorLayer('Point?field=firstfield:int&field=secondfield:int', 'test', 'memory')
+        provider = ml.dataProvider()
+
+        ft = QgsFeature()
+        ft.setAttributes([5, -1])
+        provider.addFeatures([ft])
+
+        self.assertTrue(QgsVectorFileWriter.areThereNewFieldsToCreate(filename, 'test', ml, [0, 1]))
+
+        options = QgsVectorFileWriter.SaveVectorOptions()
+        options.driverName = 'GPKG'
+        options.layerName = 'test'
+        options.actionOnExistingFile = QgsVectorFileWriter.AppendToLayerAddFields
+        filename = '/vsimem/out.gpkg'
+        write_result = QgsVectorFileWriter.writeAsVectorFormat(
+            ml,
+            filename,
+            options)
+        self.assertEqual(write_result, QgsVectorFileWriter.NoError)
+
+        ds = ogr.Open(filename)
+        lyr = ds.GetLayerByName('test')
+        self.assertEqual(lyr.GetLayerDefn().GetFieldCount(), 2)
+        self.assertIsNotNone(lyr)
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 3)
+        self.assertFalse(f.IsFieldSet('secondfield'))
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 4)
+        self.assertFalse(f.IsFieldSet('secondfield'))
+        f = lyr.GetNextFeature()
+        self.assertEqual(f['firstfield'], 5)
+        self.assertEqual(f['secondfield'], -1)
+        del f
+        del lyr
+        del ds
+
+        gdal.Unlink(filename)
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/tests/src/python/utilities.py b/tests/src/python/utilities.py
index 4189ca3..26433d4 100644
--- a/tests/src/python/utilities.py
+++ b/tests/src/python/utilities.py
@@ -19,9 +19,9 @@ import glob
 import platform
 import tempfile
 try:
-    from urllib2 import urlopen, HTTPError
+    from urllib2 import urlopen, HTTPError, URLError
 except ImportError:
-    from urllib.request import urlopen, HTTPError
+    from urllib.request import urlopen, HTTPError, URLError
 
 from qgis.PyQt.QtCore import QDir
 
@@ -833,7 +833,7 @@ def waitServer(url, timeout=10):
         try:
             urlopen(url, timeout=1)
             return True
-        except HTTPError:
+        except (HTTPError, URLError):
             return True
         except Exception as e:
             if now() > end:

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-grass/qgis.git



More information about the Pkg-grass-devel mailing list