From c4f8495f11c52a6e44a8b13cd17d410283242772 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Tue, 13 Dec 2022 18:49:48 +0100 Subject: [PATCH] GPKG: Add heuristics to try to detect corrupted RTree generated by GDAL 3.6.0, and disable use of the rtree --- autotest/ogr/ogr_gpkg.py | 39 +++++++++++++++++++ .../gpkg/ogrgeopackagetablelayer.cpp | 38 ++++++++++++++++++ 2 files changed, 77 insertions(+) diff --git a/autotest/ogr/ogr_gpkg.py b/autotest/ogr/ogr_gpkg.py index 9b9b339f0195..b3fbff88c5ce 100755 --- a/autotest/ogr/ogr_gpkg.py +++ b/autotest/ogr/ogr_gpkg.py @@ -7331,6 +7331,45 @@ def test_ogr_gpkg_background_rtree_build(filename): gdal.Unlink(filename) +############################################################################### + + +def test_ogr_gpkg_detect_broken_rtree_gdal_3_6_0(): + + filename = "/vsimem/test_ogr_gpkg_detect_broken_rtree_gdal_3_6_0.gpkg" + + ds = gdaltest.gpkg_dr.CreateDataSource(filename) + lyr = ds.CreateLayer("foo") + for i in range(100): + f = ogr.Feature(lyr.GetLayerDefn()) + f.SetGeometryDirectly( + ogr.CreateGeometryFromWkt("POINT(%d %d)" % (i % 10, i // 10)) + ) + lyr.CreateFeature(f) + ds = None + + # Voluntary corrupt the RTree by removing the entry for the last feature + ds = ogr.Open(filename, update=1) + sql_lyr = ds.ExecuteSQL("DELETE FROM rtree_foo_geom WHERE id = 100") + ds.ReleaseResultSet(sql_lyr) + ds = None + + with gdaltest.config_option("OGR_GPKG_THRESHOLD_DETECT_BROKEN_RTREE", "100"): + ds = ogr.Open(filename) + lyr = ds.GetLayer(0) + with gdaltest.error_handler(): + gdal.ErrorReset() + lyr.SetSpatialFilterRect(8.5, 8.5, 9.5, 9.5) + assert ( + "Spatial index (perhaps created with GDAL 3.6.0) of table foo is corrupted" + in gdal.GetLastErrorMsg() + ) + assert lyr.GetFeatureCount() == 1 + ds = None + + gdal.Unlink(filename) + + ############################################################################### # Test ST_Area() diff --git a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp index b23b98fb01c8..107bbb4ab88b 100644 --- a/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp +++ b/ogr/ogrsf_frmts/gpkg/ogrgeopackagetablelayer.cpp @@ -4170,6 +4170,44 @@ bool OGRGeoPackageTableLayer::HasSpatialIndex() m_osFIDForRTree = m_pszFidColumn; } + // Add heuristics to try to detect corrupted RTree generated by GDAL 3.6.0 + // Cf https://github.com/OSGeo/gdal/pull/6911 + if( m_bHasSpatialIndex ) + { + const auto nFC = GetTotalFeatureCount(); + if( nFC >= atoi(CPLGetConfigOption( + "OGR_GPKG_THRESHOLD_DETECT_BROKEN_RTREE", "100000")) ) + { + CPLString osSQL = "SELECT 1 FROM \""; + osSQL += SQLEscapeName(pszT); + osSQL += "\" WHERE \""; + osSQL += SQLEscapeName(GetFIDColumn()); + osSQL += "\" = "; + osSQL += CPLSPrintf(CPL_FRMT_GIB, nFC); + osSQL += " AND \""; + osSQL += SQLEscapeName(pszC); + osSQL += "\" IS NOT NULL AND NOT ST_IsEmpty(\""; + osSQL += SQLEscapeName(pszC); + osSQL += "\")"; + if( SQLGetInteger(m_poDS->GetDB(), osSQL, nullptr) == 1 ) + { + osSQL = "SELECT 1 FROM \""; + osSQL += SQLEscapeName(m_osRTreeName); + osSQL += "\" WHERE id = "; + osSQL += CPLSPrintf(CPL_FRMT_GIB, nFC); + if( SQLGetInteger(m_poDS->GetDB(), osSQL, nullptr) == 0 ) + { + CPLError(CE_Warning, CPLE_AppDefined, + "Spatial index (perhaps created with GDAL 3.6.0) " + "of table %s is corrupted. Disabling its use. " + "This file should be recreated or its spatial " + "index recreated", m_pszTableName); + m_bHasSpatialIndex = false; + } + } + } + } + return CPL_TO_BOOL(m_bHasSpatialIndex); }