LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/filegdb - FGdbLayer.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 472 713 66.2 %
Date: 2025-02-18 14:19:29 Functions: 27 29 93.1 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  OpenGIS Simple Features Reference Implementation
       4             :  * Purpose:  Implements FileGDB OGR layer.
       5             :  * Author:   Ragi Yaser Burhum, ragi@burhum.com
       6             :  *           Paul Ramsey, pramsey at cleverelephant.ca
       7             :  *
       8             :  ******************************************************************************
       9             :  * Copyright (c) 2010, Ragi Yaser Burhum
      10             :  * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
      11             :  * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.com>
      12             :  *
      13             :  * SPDX-License-Identifier: MIT
      14             :  ****************************************************************************/
      15             : 
      16             : #include <cassert>
      17             : #include <cmath>
      18             : 
      19             : #include "ogr_fgdb.h"
      20             : #include "ogrpgeogeometry.h"
      21             : #include "cpl_conv.h"
      22             : #include "cpl_string.h"
      23             : #include "FGdbUtils.h"
      24             : #include "cpl_minixml.h"  // the only way right now to extract schema information
      25             : #include "filegdb_gdbtoogrfieldtype.h"
      26             : #include "filegdb_fielddomain.h"
      27             : #include "filegdb_coordprec_read.h"
      28             : 
      29             : // See https://github.com/Esri/file-geodatabase-api/issues/46
      30             : // On certain FileGDB datasets with binary fields, iterating over a result set
      31             : // where the binary field is requested crashes in EnumRows::Next() at the
      32             : // second iteration.
      33             : // The workaround consists in iterating only over OBJECTID in the main loop,
      34             : // and requesting each feature in a separate request.
      35             : #define WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD
      36             : 
      37             : using std::string;
      38             : using std::wstring;
      39             : 
      40             : /************************************************************************/
      41             : /*                           FGdbBaseLayer()                            */
      42             : /************************************************************************/
      43         134 : FGdbBaseLayer::FGdbBaseLayer()
      44             :     : m_pFeatureDefn(nullptr), m_pSRS(nullptr), m_pEnumRows(nullptr),
      45         134 :       m_suppressColumnMappingError(false), m_forceMulti(false)
      46             : {
      47         134 : }
      48             : 
      49             : /************************************************************************/
      50             : /*                          ~FGdbBaseLayer()                            */
      51             : /************************************************************************/
      52         134 : FGdbBaseLayer::~FGdbBaseLayer()
      53             : {
      54         134 :     if (m_pFeatureDefn)
      55             :     {
      56         134 :         m_pFeatureDefn->Release();
      57         134 :         m_pFeatureDefn = nullptr;
      58             :     }
      59             : 
      60         134 :     FGdbBaseLayer::CloseGDBObjects();
      61             : 
      62         134 :     if (m_pSRS)
      63             :     {
      64         109 :         m_pSRS->Release();
      65         109 :         m_pSRS = nullptr;
      66             :     }
      67         134 : }
      68             : 
      69             : /************************************************************************/
      70             : /*                          CloseGDBObjects()                           */
      71             : /************************************************************************/
      72             : 
      73         398 : void FGdbBaseLayer::CloseGDBObjects()
      74             : {
      75         398 :     if (m_pEnumRows)
      76             :     {
      77         134 :         delete m_pEnumRows;
      78         134 :         m_pEnumRows = nullptr;
      79             :     }
      80         398 : }
      81             : 
      82             : /************************************************************************/
      83             : /*                           GetNextFeature()                           */
      84             : /************************************************************************/
      85             : 
      86          45 : OGRFeature *FGdbBaseLayer::GetNextFeature()
      87             : {
      88             :     while (true)  // want to skip errors
      89             :     {
      90          45 :         if (m_pEnumRows == nullptr)
      91          45 :             return nullptr;
      92             : 
      93             :         long hr;
      94             : 
      95          45 :         Row row;
      96             : 
      97          45 :         if (FAILED(hr = m_pEnumRows->Next(row)))
      98             :         {
      99           0 :             GDBErr(hr, "Failed fetching features");
     100           0 :             return nullptr;
     101             :         }
     102             : 
     103          45 :         if (hr != S_OK)
     104             :         {
     105             :             // It's OK, we are done fetching - failure is caught by FAILED macro
     106           7 :             return nullptr;
     107             :         }
     108             : 
     109          38 :         OGRFeature *pOGRFeature = nullptr;
     110             : 
     111          38 :         if (!OGRFeatureFromGdbRow(&row, &pOGRFeature) || !pOGRFeature)
     112             :         {
     113           0 :             int32 oid = -1;
     114           0 :             CPL_IGNORE_RET_VAL(row.GetOID(oid));
     115             : 
     116           0 :             GDBErr(hr,
     117             :                    CPLSPrintf("Failed translating FGDB row [%d] to OGR Feature",
     118             :                               oid));
     119             : 
     120             :             // return NULL;
     121           0 :             continue;  // skip feature
     122             :         }
     123             : 
     124          38 :         if ((m_poFilterGeom == nullptr ||
     125           0 :              FilterGeometry(pOGRFeature->GetGeometryRef())))
     126             :         {
     127          38 :             return pOGRFeature;
     128             :         }
     129           0 :         delete pOGRFeature;
     130           0 :     }
     131             : }
     132             : 
     133             : /************************************************************************/
     134             : /*                              FGdbLayer()                             */
     135             : /************************************************************************/
     136         132 : FGdbLayer::FGdbLayer()
     137             :     : m_pDS(nullptr), m_pTable(nullptr), m_wstrSubfields(L"*"),
     138         132 :       m_bFilterDirty(true), m_bLaunderReservedKeywords(true)
     139             : {
     140         132 :     m_pEnumRows = new EnumRows;
     141             : 
     142             : #ifdef EXTENT_WORKAROUND
     143         132 :     m_bLayerEnvelopeValid = false;
     144             : #endif
     145         132 : }
     146             : 
     147             : /************************************************************************/
     148             : /*                            ~FGdbLayer()                              */
     149             : /************************************************************************/
     150             : 
     151         264 : FGdbLayer::~FGdbLayer()
     152             : {
     153         132 :     FGdbLayer::CloseGDBObjects();
     154             : 
     155         305 :     for (size_t i = 0; i < m_apoByteArrays.size(); i++)
     156         173 :         delete m_apoByteArrays[i];
     157         132 :     m_apoByteArrays.resize(0);
     158         264 : }
     159             : 
     160             : /************************************************************************/
     161             : /*                        CloseGDBObjects()                             */
     162             : /************************************************************************/
     163             : 
     164         264 : void FGdbLayer::CloseGDBObjects()
     165             : {
     166             : #ifdef EXTENT_WORKAROUND
     167         264 :     WorkAroundExtentProblem();
     168             : #endif
     169             : 
     170         264 :     if (m_pTable)
     171             :     {
     172         132 :         delete m_pTable;
     173         132 :         m_pTable = nullptr;
     174             :     }
     175             : 
     176         264 :     FGdbBaseLayer::CloseGDBObjects();
     177         264 : }
     178             : 
     179             : #ifdef EXTENT_WORKAROUND
     180             : 
     181             : /************************************************************************/
     182             : /*                     UpdateRowWithGeometry()                          */
     183             : /************************************************************************/
     184             : 
     185           0 : bool FGdbLayer::UpdateRowWithGeometry(Row &row, OGRGeometry *poGeom)
     186             : {
     187           0 :     ShapeBuffer shape;
     188             :     long hr;
     189             : 
     190             :     /* Write geometry to a buffer */
     191           0 :     GByte *pabyShape = nullptr;
     192           0 :     int nShapeSize = 0;
     193           0 :     if (OGRWriteToShapeBin(poGeom, &pabyShape, &nShapeSize) != OGRERR_NONE)
     194             :     {
     195           0 :         CPLFree(pabyShape);
     196           0 :         return false;
     197             :     }
     198             : 
     199             :     /* Copy it into a ShapeBuffer */
     200           0 :     if (nShapeSize > 0)
     201             :     {
     202           0 :         shape.Allocate(nShapeSize);
     203           0 :         memcpy(shape.shapeBuffer, pabyShape, nShapeSize);
     204           0 :         shape.inUseLength = nShapeSize;
     205             :     }
     206             : 
     207             :     /* Free the shape buffer */
     208           0 :     CPLFree(pabyShape);
     209             : 
     210             :     /* Write ShapeBuffer into the Row */
     211           0 :     hr = row.SetGeometry(shape);
     212           0 :     if (FAILED(hr))
     213             :     {
     214           0 :         return false;
     215             :     }
     216             : 
     217             :     /* Update row */
     218           0 :     hr = m_pTable->Update(row);
     219           0 :     if (FAILED(hr))
     220             :     {
     221           0 :         return false;
     222             :     }
     223             : 
     224           0 :     return true;
     225             : }
     226             : 
     227             : /************************************************************************/
     228             : /*                    WorkAroundExtentProblem()                         */
     229             : /*                                                                      */
     230             : /* Work-around problem with FileGDB API 1.1 on Linux 64bit. See #4455   */
     231             : /************************************************************************/
     232             : 
     233         264 : void FGdbLayer::WorkAroundExtentProblem()
     234             : {
     235         264 :     if (!m_bLayerEnvelopeValid)
     236         264 :         return;
     237             : 
     238           0 :     OGREnvelope sEnvelope;
     239           0 :     if (FGdbLayer::GetExtent(&sEnvelope, TRUE) != OGRERR_NONE)
     240           0 :         return;
     241             : 
     242             :     /* The characteristic of the bug is that the reported extent */
     243             :     /* is the real extent truncated incorrectly to integer values */
     244             :     /* We work around that by temporary updating one feature with a geometry */
     245             :     /* whose coordinates are integer values but ceil'ed and floor'ed */
     246             :     /* such that they include the real layer extent. */
     247           0 :     if (((double)(int)sEnvelope.MinX == sEnvelope.MinX &&
     248           0 :          (double)(int)sEnvelope.MinY == sEnvelope.MinY &&
     249           0 :          (double)(int)sEnvelope.MaxX == sEnvelope.MaxX &&
     250           0 :          (double)(int)sEnvelope.MaxY == sEnvelope.MaxY) &&
     251           0 :         (fabs(sEnvelope.MinX - sLayerEnvelope.MinX) > 1e-5 ||
     252           0 :          fabs(sEnvelope.MinY - sLayerEnvelope.MinY) > 1e-5 ||
     253           0 :          fabs(sEnvelope.MaxX - sLayerEnvelope.MaxX) > 1e-5 ||
     254           0 :          fabs(sEnvelope.MaxY - sLayerEnvelope.MaxY) > 1e-5))
     255             :     {
     256             :         long hr;
     257           0 :         Row row;
     258           0 :         EnumRows enumRows;
     259             : 
     260           0 :         if (FAILED(hr = m_pTable->Search(StringToWString("*"),
     261             :                                          StringToWString(""), true, enumRows)))
     262           0 :             return;
     263             : 
     264           0 :         if (FAILED(hr = enumRows.Next(row)))
     265           0 :             return;
     266             : 
     267           0 :         if (hr != S_OK)
     268           0 :             return;
     269             : 
     270             :         /* Backup original shape buffer */
     271           0 :         ShapeBuffer originalGdbGeometry;
     272           0 :         if (FAILED(hr = row.GetGeometry(originalGdbGeometry)))
     273           0 :             return;
     274             : 
     275           0 :         OGRGeometry *pOGRGeo = nullptr;
     276           0 :         if ((!GDBGeometryToOGRGeometry(m_forceMulti, &originalGdbGeometry,
     277           0 :                                        m_pSRS, &pOGRGeo)) ||
     278           0 :             pOGRGeo == nullptr)
     279             :         {
     280           0 :             delete pOGRGeo;
     281           0 :             return;
     282             :         }
     283             : 
     284           0 :         OGRwkbGeometryType eType = wkbFlatten(pOGRGeo->getGeometryType());
     285             : 
     286           0 :         delete pOGRGeo;
     287           0 :         pOGRGeo = nullptr;
     288             : 
     289           0 :         OGRPoint oP1(floor(sLayerEnvelope.MinX), floor(sLayerEnvelope.MinY));
     290           0 :         OGRPoint oP2(ceil(sLayerEnvelope.MaxX), ceil(sLayerEnvelope.MaxY));
     291             : 
     292           0 :         OGRLinearRing oLR;
     293           0 :         oLR.addPoint(&oP1);
     294           0 :         oLR.addPoint(&oP2);
     295           0 :         oLR.addPoint(&oP1);
     296             : 
     297           0 :         if (eType == wkbPoint)
     298             :         {
     299           0 :             UpdateRowWithGeometry(row, &oP1);
     300           0 :             UpdateRowWithGeometry(row, &oP2);
     301             :         }
     302           0 :         else if (eType == wkbLineString)
     303             :         {
     304           0 :             UpdateRowWithGeometry(row, &oLR);
     305             :         }
     306           0 :         else if (eType == wkbPolygon)
     307             :         {
     308           0 :             OGRPolygon oPoly;
     309           0 :             oPoly.addRing(&oLR);
     310             : 
     311           0 :             UpdateRowWithGeometry(row, &oPoly);
     312             :         }
     313           0 :         else if (eType == wkbMultiPoint)
     314             :         {
     315           0 :             OGRMultiPoint oColl;
     316           0 :             oColl.addGeometry(&oP1);
     317           0 :             oColl.addGeometry(&oP2);
     318             : 
     319           0 :             UpdateRowWithGeometry(row, &oColl);
     320             :         }
     321           0 :         else if (eType == wkbMultiLineString)
     322             :         {
     323           0 :             OGRMultiLineString oColl;
     324           0 :             oColl.addGeometry(&oLR);
     325             : 
     326           0 :             UpdateRowWithGeometry(row, &oColl);
     327             :         }
     328           0 :         else if (eType == wkbMultiPolygon)
     329             :         {
     330           0 :             OGRMultiPolygon oColl;
     331           0 :             OGRPolygon oPoly;
     332           0 :             oPoly.addRing(&oLR);
     333           0 :             oColl.addGeometry(&oPoly);
     334             : 
     335           0 :             UpdateRowWithGeometry(row, &oColl);
     336             :         }
     337             :         else
     338           0 :             return;
     339             : 
     340             :         /* Restore original ShapeBuffer */
     341           0 :         hr = row.SetGeometry(originalGdbGeometry);
     342           0 :         if (FAILED(hr))
     343           0 :             return;
     344             : 
     345             :         /* Update Row */
     346           0 :         hr = m_pTable->Update(row);
     347           0 :         if (FAILED(hr))
     348           0 :             return;
     349             : 
     350           0 :         CPLDebug("FGDB",
     351             :                  "Workaround extent problem with Linux 64bit FGDB SDK 1.1");
     352             :     }
     353             : }
     354             : #endif  // EXTENT_WORKAROUND
     355             : 
     356             : /************************************************************************/
     357             : /*                             GetRow()                                 */
     358             : /************************************************************************/
     359             : 
     360        1783 : OGRErr FGdbLayer::GetRow(EnumRows &enumRows, Row &row, GIntBig nFID)
     361             : {
     362             :     long hr;
     363        3566 :     CPLString osQuery;
     364             : 
     365             :     /* Querying a 64bit FID causes a runtime exception in FileGDB... */
     366        1783 :     if (!CPL_INT64_FITS_ON_INT32(nFID))
     367             :     {
     368           0 :         return OGRERR_FAILURE;
     369             :     }
     370             : 
     371        1783 :     osQuery.Printf("%s = " CPL_FRMT_GIB, m_strOIDFieldName.c_str(), nFID);
     372             : 
     373        1783 :     if (FAILED(hr = m_pTable->Search(m_wstrSubfields,
     374             :                                      StringToWString(osQuery.c_str()), true,
     375             :                                      enumRows)))
     376             :     {
     377           0 :         GDBErr(hr, "Failed fetching row ");
     378           0 :         return OGRERR_FAILURE;
     379             :     }
     380             : 
     381        1783 :     if (FAILED(hr = enumRows.Next(row)))
     382             :     {
     383           0 :         GDBErr(hr, "Failed fetching row ");
     384           0 :         return OGRERR_FAILURE;
     385             :     }
     386             : 
     387        1783 :     if (hr != S_OK)
     388          34 :         return OGRERR_NON_EXISTING_FEATURE;  // none found - but no failure
     389             : 
     390        1749 :     return OGRERR_NONE;
     391             : }
     392             : 
     393             : /*************************************************************************/
     394             : /*                            Initialize()                               */
     395             : /* Has ownership of the table as soon as it is called.                   */
     396             : /************************************************************************/
     397             : 
     398         132 : bool FGdbLayer::Initialize(FGdbDataSource *pParentDataSource, Table *pTable,
     399             :                            const std::wstring &wstrTablePath,
     400             :                            const std::wstring &wstrType)
     401             : {
     402             :     long hr;
     403             : 
     404         132 :     m_pDS = pParentDataSource;  // we never assume ownership of the parent - so
     405             :                                 // our destructor should not delete
     406             : 
     407         132 :     m_pTable = pTable;
     408             : 
     409         132 :     m_wstrTablePath = wstrTablePath;
     410         132 :     m_wstrType = wstrType;
     411             : 
     412         264 :     wstring wstrQueryName;
     413         132 :     if (FAILED(hr = pParentDataSource->GetGDB()->GetQueryName(wstrTablePath,
     414             :                                                               wstrQueryName)))
     415           0 :         return GDBErr(hr, "Failed at getting underlying table name for " +
     416           0 :                               WStringToString(wstrTablePath));
     417             : 
     418         132 :     m_strName = WStringToString(wstrQueryName);
     419             : 
     420         132 :     m_pFeatureDefn = new OGRFeatureDefn(
     421         132 :         m_strName.c_str());  // TODO: Should I "new" an OGR smart pointer -
     422             :                              // sample says so, but it doesn't seem right
     423         132 :     SetDescription(m_pFeatureDefn->GetName());
     424             :     // as long as we use the same compiler & settings in both the ogr build and
     425             :     // this driver, we should be OK
     426         132 :     m_pFeatureDefn->Reference();
     427             : 
     428         264 :     string tableDef;
     429         132 :     if (FAILED(hr = m_pTable->GetDefinition(tableDef)))
     430           0 :         return GDBErr(hr, "Failed at getting table definition for " +
     431           0 :                               WStringToString(wstrTablePath));
     432             : 
     433             :     // CPLDebug("FGDB", "tableDef = %s", tableDef.c_str());
     434             : 
     435         132 :     bool abort = false;
     436             : 
     437             :     // extract schema information from table
     438         132 :     CPLXMLNode *psRoot = CPLParseXMLString(tableDef.c_str());
     439             : 
     440         132 :     if (psRoot == nullptr)
     441             :     {
     442           0 :         CPLError(
     443             :             CE_Failure, CPLE_AppDefined, "%s",
     444           0 :             ("Failed parsing GDB Table Schema XML for " + m_strName).c_str());
     445           0 :         return false;
     446             :     }
     447             : 
     448         132 :     CPLXMLNode *pDataElementNode =
     449             :         psRoot->psNext;  // Move to next field which should be DataElement
     450             : 
     451         132 :     if (pDataElementNode != nullptr && pDataElementNode->psChild != nullptr &&
     452         132 :         pDataElementNode->eType == CXT_Element &&
     453         132 :         EQUAL(pDataElementNode->pszValue, "esri:DataElement"))
     454             :     {
     455             :         CPLXMLNode *psNode;
     456             : 
     457         132 :         m_bTimeInUTC = CPLTestBool(
     458             :             CPLGetXMLValue(pDataElementNode, "IsTimeInUTC", "false"));
     459             : 
     460         264 :         std::string osAreaFieldName;
     461         264 :         std::string osLengthFieldName;
     462        4170 :         for (psNode = pDataElementNode->psChild; psNode != nullptr;
     463        4038 :              psNode = psNode->psNext)
     464             :         {
     465        4038 :             if (psNode->eType == CXT_Element && psNode->psChild != nullptr)
     466             :             {
     467        2932 :                 if (EQUAL(psNode->pszValue, "OIDFieldName"))
     468             :                 {
     469         132 :                     m_strOIDFieldName = CPLGetXMLValue(psNode, nullptr, "");
     470             :                 }
     471        2800 :                 else if (EQUAL(psNode->pszValue, "ShapeFieldName"))
     472             :                 {
     473         118 :                     m_strShapeFieldName = CPLGetXMLValue(psNode, nullptr, "");
     474             :                 }
     475        2682 :                 else if (EQUAL(psNode->pszValue, "AreaFieldName"))
     476             :                 {
     477          12 :                     osAreaFieldName = CPLGetXMLValue(psNode, nullptr, "");
     478             :                 }
     479        2670 :                 else if (EQUAL(psNode->pszValue, "LengthFieldName"))
     480             :                 {
     481          18 :                     osLengthFieldName = CPLGetXMLValue(psNode, nullptr, "");
     482             :                 }
     483        2652 :                 else if (EQUAL(psNode->pszValue, "Fields"))
     484             :                 {
     485         132 :                     if (!GDBToOGRFields(psNode))
     486             :                     {
     487           0 :                         abort = true;
     488           0 :                         break;
     489             :                     }
     490             :                 }
     491             :             }
     492             :         }
     493             : 
     494         132 :         if (!osAreaFieldName.empty())
     495             :         {
     496             :             const int nIdx =
     497          12 :                 m_pFeatureDefn->GetFieldIndex(osAreaFieldName.c_str());
     498          12 :             if (nIdx >= 0)
     499             :             {
     500          12 :                 m_pFeatureDefn->GetFieldDefn(nIdx)->SetDefault(
     501             :                     "FILEGEODATABASE_SHAPE_AREA");
     502             :             }
     503             :         }
     504             : 
     505         132 :         if (!osLengthFieldName.empty())
     506             :         {
     507             :             const int nIdx =
     508          18 :                 m_pFeatureDefn->GetFieldIndex(osLengthFieldName.c_str());
     509          18 :             if (nIdx >= 0)
     510             :             {
     511          18 :                 m_pFeatureDefn->GetFieldDefn(nIdx)->SetDefault(
     512             :                     "FILEGEODATABASE_SHAPE_LENGTH");
     513             :             }
     514             :         }
     515             : 
     516         132 :         if (m_strShapeFieldName.empty())
     517         146 :             m_pFeatureDefn->SetGeomType(wkbNone);
     518             :     }
     519             :     else
     520             :     {
     521           0 :         CPLError(CE_Failure, CPLE_AppDefined, "%s",
     522           0 :                  ("Failed parsing GDB Table Schema XML (DataElement) for " +
     523           0 :                   m_strName)
     524             :                      .c_str());
     525           0 :         return false;
     526             :     }
     527         132 :     CPLDestroyXMLNode(psRoot);
     528             : 
     529         132 :     if (m_pFeatureDefn->GetGeomFieldCount() != 0)
     530             :     {
     531         118 :         m_pFeatureDefn->GetGeomFieldDefn(0)->SetName(
     532             :             m_strShapeFieldName.c_str());
     533         118 :         m_pFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_pSRS);
     534             :     }
     535             : 
     536         132 :     if (abort)
     537           0 :         return false;
     538             : 
     539         132 :     return true;  // AOToOGRFields(ipFields, m_pFeatureDefn,
     540             :                   // m_vOGRFieldToESRIField);
     541             : }
     542             : 
     543             : /************************************************************************/
     544             : /*                          ParseGeometryDef()                          */
     545             : /************************************************************************/
     546             : 
     547         118 : bool FGdbLayer::ParseGeometryDef(const CPLXMLNode *psRoot)
     548             : {
     549         236 :     string geometryType;
     550         118 :     bool hasZ = false, hasM = false;
     551         236 :     string wkt, wkid, latestwkid;
     552             : 
     553         236 :     OGRGeomCoordinatePrecision oCoordPrec;
     554         118 :     for (const CPLXMLNode *psGeometryDefItem = psRoot->psChild;
     555         944 :          psGeometryDefItem; psGeometryDefItem = psGeometryDefItem->psNext)
     556             :     {
     557             :         // loop through all "GeometryDef" elements
     558             :         //
     559             : 
     560         826 :         if (psGeometryDefItem->eType == CXT_Element &&
     561         708 :             psGeometryDefItem->psChild != nullptr)
     562             :         {
     563         708 :             if (EQUAL(psGeometryDefItem->pszValue, "GeometryType"))
     564             :             {
     565         118 :                 geometryType = CPLGetXMLValue(psGeometryDefItem, nullptr, "");
     566             :             }
     567         590 :             else if (EQUAL(psGeometryDefItem->pszValue, "SpatialReference"))
     568             :             {
     569         118 :                 ParseSpatialReference(
     570             :                     psGeometryDefItem, &wkt, &wkid,
     571             :                     &latestwkid);  // we don't check for success because it
     572             :                                    // may not be there
     573         118 :                 oCoordPrec = GDBGridSettingsToOGR(psGeometryDefItem);
     574             :             }
     575         472 :             else if (EQUAL(psGeometryDefItem->pszValue, "HasM"))
     576             :             {
     577         118 :                 if (!strcmp(CPLGetXMLValue(psGeometryDefItem, nullptr, ""),
     578             :                             "true"))
     579           5 :                     hasM = true;
     580             :             }
     581         354 :             else if (EQUAL(psGeometryDefItem->pszValue, "HasZ"))
     582             :             {
     583         118 :                 if (!strcmp(CPLGetXMLValue(psGeometryDefItem, nullptr, ""),
     584             :                             "true"))
     585          47 :                     hasZ = true;
     586             :             }
     587             :         }
     588             :     }
     589             : 
     590             :     OGRwkbGeometryType ogrGeoType;
     591         118 :     if (!GDBToOGRGeometry(geometryType, hasZ, hasM, &ogrGeoType))
     592           0 :         return false;
     593             : 
     594         118 :     m_pFeatureDefn->SetGeomType(ogrGeoType);
     595             : 
     596         118 :     if (m_pFeatureDefn->GetGeomFieldCount() != 0)
     597         118 :         m_pFeatureDefn->GetGeomFieldDefn(0)->SetCoordinatePrecision(oCoordPrec);
     598             : 
     599         209 :     if (wkbFlatten(ogrGeoType) == wkbMultiLineString ||
     600          91 :         wkbFlatten(ogrGeoType) == wkbMultiPoint)
     601          37 :         m_forceMulti = true;
     602             : 
     603         118 :     if (latestwkid.length() > 0 || wkid.length() > 0)
     604             :     {
     605         108 :         int bSuccess = FALSE;
     606         108 :         m_pSRS = new OGRSpatialReference();
     607         108 :         m_pSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     608         108 :         CPLPushErrorHandler(CPLQuietErrorHandler);
     609         108 :         if (latestwkid.length() > 0)
     610             :         {
     611           0 :             if (m_pSRS->importFromEPSG(atoi(latestwkid.c_str())) == OGRERR_NONE)
     612             :             {
     613           0 :                 bSuccess = TRUE;
     614             :             }
     615             :             else
     616             :             {
     617           0 :                 CPLDebug("FGDB", "Cannot import SRID %s", latestwkid.c_str());
     618             :             }
     619             :         }
     620         108 :         if (!bSuccess && wkid.length() > 0)
     621             :         {
     622         108 :             if (m_pSRS->importFromEPSG(atoi(wkid.c_str())) == OGRERR_NONE)
     623             :             {
     624         108 :                 bSuccess = TRUE;
     625             :             }
     626             :             else
     627             :             {
     628           0 :                 CPLDebug("FGDB", "Cannot import SRID %s", wkid.c_str());
     629             :             }
     630             :         }
     631         108 :         CPLPopErrorHandler();
     632         108 :         CPLErrorReset();
     633         108 :         if (!bSuccess)
     634             :         {
     635           0 :             delete m_pSRS;
     636           0 :             m_pSRS = nullptr;
     637             :         }
     638             :         else
     639         108 :             return true;
     640             :     }
     641             : 
     642          10 :     if (wkt.length() > 0)
     643             :     {
     644           1 :         if (!GDBToOGRSpatialReference(wkt, &m_pSRS))
     645             :         {
     646             :             // report error, but be passive about it
     647           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     648             :                      "Failed Mapping ESRI Spatial Reference");
     649             :         }
     650             :     }
     651             : 
     652          10 :     return true;
     653             : }
     654             : 
     655             : /************************************************************************/
     656             : /*                        ParseSpatialReference()                       */
     657             : /************************************************************************/
     658             : 
     659         118 : bool FGdbLayer::ParseSpatialReference(const CPLXMLNode *psSpatialRefNode,
     660             :                                       string *pOutWkt, string *pOutWKID,
     661             :                                       string *pOutLatestWKID)
     662             : {
     663         118 :     *pOutWkt = "";
     664         118 :     *pOutWKID = "";
     665         118 :     *pOutLatestWKID = "";
     666             : 
     667             :     /* Loop through all the SRS elements we want to store */
     668         118 :     for (const CPLXMLNode *psSRItemNode = psSpatialRefNode->psChild;
     669        1761 :          psSRItemNode; psSRItemNode = psSRItemNode->psNext)
     670             :     {
     671             :         /* The WKID maps (mostly) to an EPSG code */
     672        1643 :         if (psSRItemNode->eType == CXT_Element &&
     673        1525 :             psSRItemNode->psChild != nullptr &&
     674        1525 :             EQUAL(psSRItemNode->pszValue, "WKID"))
     675             :         {
     676         118 :             *pOutWKID = CPLGetXMLValue(psSRItemNode, nullptr, "");
     677             : 
     678             :             // Needed with FileGDB v1.4 with layers with empty SRS
     679         118 :             if (*pOutWKID == "0")
     680          10 :                 *pOutWKID = "";
     681             :         }
     682             :         /* The concept of LatestWKID is explained in
     683             :          * http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r3000000n1000000
     684             :          */
     685        1525 :         else if (psSRItemNode->eType == CXT_Element &&
     686        1407 :                  psSRItemNode->psChild != nullptr &&
     687        1407 :                  EQUAL(psSRItemNode->pszValue, "LatestWKID"))
     688             :         {
     689           0 :             *pOutLatestWKID = CPLGetXMLValue(psSRItemNode, nullptr, "");
     690             :         }
     691             :         /* The WKT well-known text can be converted by OGR */
     692        1525 :         else if (psSRItemNode->eType == CXT_Element &&
     693        1407 :                  psSRItemNode->psChild != nullptr &&
     694        1407 :                  EQUAL(psSRItemNode->pszValue, "WKT"))
     695             :         {
     696         109 :             *pOutWkt = CPLGetXMLValue(psSRItemNode, nullptr, "");
     697             :         }
     698             :     }
     699         118 :     return *pOutWkt != "" || *pOutWKID != "";
     700             : }
     701             : 
     702             : /************************************************************************/
     703             : /*                          GDBToOGRFields()                           */
     704             : /************************************************************************/
     705             : 
     706         132 : bool FGdbLayer::GDBToOGRFields(CPLXMLNode *psRoot)
     707             : {
     708         132 :     m_vOGRFieldToESRIField.clear();
     709             : 
     710         132 :     if (psRoot->psChild == nullptr || psRoot->psChild->psNext == nullptr)
     711             :     {
     712           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Unrecognized GDB XML Schema");
     713             : 
     714           0 :         return false;
     715             :     }
     716             : 
     717         132 :     psRoot = psRoot->psChild->psNext;  // change root to "FieldArray"
     718             : 
     719             :     // CPLAssert(ogrToESRIFieldMapping.size() ==
     720             :     // pOGRFeatureDef->GetFieldCount());
     721             : 
     722             :     CPLXMLNode *psFieldNode;
     723             : 
     724        1778 :     for (psFieldNode = psRoot->psChild; psFieldNode != nullptr;
     725        1646 :          psFieldNode = psFieldNode->psNext)
     726             :     {
     727             :         // loop through all "Field" elements
     728             :         //
     729             : 
     730        1646 :         if (psFieldNode->eType == CXT_Element &&
     731        1514 :             psFieldNode->psChild != nullptr &&
     732        1514 :             EQUAL(psFieldNode->pszValue, "Field"))
     733             :         {
     734             : 
     735             :             CPLXMLNode *psFieldItemNode;
     736        1514 :             std::string fieldName;
     737        1514 :             std::string fieldAlias;
     738        1514 :             std::string fieldType;
     739        1514 :             int nLength = 0;
     740        1514 :             int bNullable = TRUE;
     741        1514 :             std::string osDefault;
     742        1514 :             std::string osDomainName;
     743             : 
     744             :             // loop through all items in Field element
     745             :             //
     746             : 
     747        1514 :             for (psFieldItemNode = psFieldNode->psChild;
     748       15718 :                  psFieldItemNode != nullptr;
     749       14204 :                  psFieldItemNode = psFieldItemNode->psNext)
     750             :             {
     751       14204 :                 if (psFieldItemNode->eType == CXT_Element)
     752             :                 {
     753             :                     const char *pszValue =
     754       12690 :                         CPLGetXMLValue(psFieldItemNode, nullptr, "");
     755       12690 :                     if (EQUAL(psFieldItemNode->pszValue, "Name"))
     756             :                     {
     757        1514 :                         fieldName = pszValue;
     758             :                     }
     759       11176 :                     else if (EQUAL(psFieldItemNode->pszValue, "AliasName"))
     760             :                     {
     761        1514 :                         fieldAlias = pszValue;
     762             :                     }
     763        9662 :                     else if (EQUAL(psFieldItemNode->pszValue, "Type"))
     764             :                     {
     765        1514 :                         fieldType = pszValue;
     766             :                     }
     767        8148 :                     else if (EQUAL(psFieldItemNode->pszValue, "GeometryDef"))
     768             :                     {
     769         118 :                         if (!ParseGeometryDef(psFieldItemNode))
     770           0 :                             return false;  // if we failed parsing the
     771             :                                            // GeometryDef, we are done!
     772             :                     }
     773        8030 :                     else if (EQUAL(psFieldItemNode->pszValue, "Length"))
     774             :                     {
     775        1514 :                         nLength = atoi(pszValue);
     776             :                     }
     777        6516 :                     else if (EQUAL(psFieldItemNode->pszValue, "IsNullable"))
     778             :                     {
     779        1514 :                         bNullable = EQUAL(pszValue, "true");
     780             :                     }
     781        5002 :                     else if (EQUAL(psFieldItemNode->pszValue, "DefaultValue"))
     782             :                     {
     783           4 :                         osDefault = pszValue;
     784             :                     }
     785             :                     // NOTE: when using the GetDefinition() API, the domain name
     786             :                     // is set in <Domain><DomainName>, whereas the raw XML is
     787             :                     // just <DomainName>
     788        4998 :                     else if (EQUAL(psFieldItemNode->pszValue, "Domain"))
     789             :                     {
     790             :                         osDomainName =
     791          18 :                             CPLGetXMLValue(psFieldItemNode, "DomainName", "");
     792             :                     }
     793             :                 }
     794             :             }
     795             : 
     796             :             ///////////////////////////////////////////////////////////////////
     797             :             // At this point we have parsed everything about the current field
     798             : 
     799        1514 :             if (fieldType == "esriFieldTypeGeometry")
     800             :             {
     801         118 :                 m_strShapeFieldName = fieldName;
     802         118 :                 m_pFeatureDefn->GetGeomFieldDefn(0)->SetNullable(bNullable);
     803             : 
     804         118 :                 continue;  // finish here for special field - don't add as OGR
     805             :                            // fielddef
     806             :             }
     807        1396 :             else if (fieldType == "esriFieldTypeOID")
     808             :             {
     809             :                 // m_strOIDFieldName = fieldName; // already set by this point
     810             : 
     811         132 :                 continue;  // finish here for special field - don't add as OGR
     812             :                            // fielddef
     813             :             }
     814             : 
     815             :             OGRFieldType ogrType;
     816             :             OGRFieldSubType eSubType;
     817             :             // CPLDebug("FGDB", "name = %s, type = %s", fieldName.c_str(),
     818             :             // fieldType.c_str() );
     819        1264 :             if (!GDBToOGRFieldType(fieldType, &ogrType, &eSubType))
     820             :             {
     821             :                 // field cannot be mapped, skipping further processing
     822           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     823             :                          "Skipping field: [%s] type: [%s] ", fieldName.c_str(),
     824             :                          fieldType.c_str());
     825           0 :                 continue;
     826             :             }
     827             : 
     828             :             // TODO: Optimization - modify m_wstrSubFields so it only fetches
     829             :             // fields that are mapped
     830             : 
     831        2528 :             OGRFieldDefn fieldTemplate(fieldName.c_str(), ogrType);
     832        1264 :             if (fieldAlias != fieldName)
     833             :             {
     834             :                 // The SDK generates an alias even with it is not explicitly
     835             :                 // written
     836           2 :                 fieldTemplate.SetAlternativeName(fieldAlias.c_str());
     837             :             }
     838        1264 :             fieldTemplate.SetSubType(eSubType);
     839             :             /* On creation (GDBFieldTypeToLengthInBytes) if string width is 0,
     840             :              * we pick up */
     841             :             /* 65536 by default to mean unlimited string length, but we don't
     842             :              * want */
     843             :             /* to advertise such a big number */
     844        1264 :             if (ogrType == OFTString && nLength < 65536)
     845         210 :                 fieldTemplate.SetWidth(nLength);
     846        1264 :             fieldTemplate.SetNullable(bNullable);
     847        1264 :             if (!osDefault.empty())
     848             :             {
     849           4 :                 if (ogrType == OFTString)
     850             :                 {
     851             :                     char *pszTmp =
     852           0 :                         CPLEscapeString(osDefault.c_str(), -1, CPLES_SQL);
     853           0 :                     osDefault = "'";
     854           0 :                     osDefault += pszTmp;
     855           0 :                     CPLFree(pszTmp);
     856           0 :                     osDefault += "'";
     857           0 :                     fieldTemplate.SetDefault(osDefault.c_str());
     858             :                 }
     859           4 :                 else if (ogrType == OFTInteger || ogrType == OFTReal)
     860             :                 {
     861             : #ifdef unreliable
     862             :                     /* Disabling this as GDBs and the FileGDB SDK aren't
     863             :                      * reliable for numeric values */
     864             :                     /* It often occurs that the XML definition in
     865             :                      * a00000004.gdbtable doesn't */
     866             :                     /* match the default values (in binary) found in the field
     867             :                      * definition */
     868             :                     /* section of the .gdbtable of the layers themselves */
     869             :                     /* The Table::GetDefinition() API of FileGDB doesn't seem to
     870             :                      * use the */
     871             :                     /* XML definition, but rather the values found in the field
     872             :                      * definition */
     873             :                     /* section of the .gdbtable of the layers themselves */
     874             :                     /* It seems that the XML definition in a00000004.gdbtable is
     875             :                      * authoritative */
     876             :                     /* in ArcGIS, so we're screwed... */
     877             : 
     878             :                     fieldTemplate.SetDefault(osDefault.c_str());
     879             : #endif
     880             :                 }
     881           0 :                 else if (ogrType == OFTDateTime)
     882             :                 {
     883             :                     int nYear, nMonth, nDay, nHour, nMinute;
     884             :                     float fSecond;
     885           0 :                     if (sscanf(osDefault.c_str(), "%d-%d-%dT%d:%d:%fZ", &nYear,
     886             :                                &nMonth, &nDay, &nHour, &nMinute,
     887           0 :                                &fSecond) == 6 ||
     888           0 :                         sscanf(osDefault.c_str(), "'%d-%d-%d %d:%d:%fZ'",
     889             :                                &nYear, &nMonth, &nDay, &nHour, &nMinute,
     890             :                                &fSecond) == 6)
     891             :                     {
     892           0 :                         fieldTemplate.SetDefault(CPLSPrintf(
     893             :                             "'%04d/%02d/%02d %02d:%02d:%02d'", nYear, nMonth,
     894           0 :                             nDay, nHour, nMinute, (int)(fSecond + 0.5)));
     895             :                     }
     896             :                 }
     897             :             }
     898        1264 :             if (!osDomainName.empty())
     899             :             {
     900          18 :                 fieldTemplate.SetDomainName(osDomainName);
     901             :             }
     902             : 
     903        1264 :             m_pFeatureDefn->AddFieldDefn(&fieldTemplate);
     904             : 
     905        1264 :             m_vOGRFieldToESRIField.push_back(StringToWString(fieldName));
     906        1264 :             m_vOGRFieldToESRIFieldType.push_back(fieldType);
     907        1264 :             if (ogrType == OFTBinary)
     908         173 :                 m_apoByteArrays.push_back(new ByteArray());
     909             :         }
     910             :     }
     911             : 
     912             :     /* Using OpenFileGDB to get reliable default values for integer/real fields
     913             :      */
     914             :     /* and alias */
     915         132 :     if (m_pDS->UseOpenFileGDB())
     916             :     {
     917         130 :         const char *const apszDrivers[] = {"OpenFileGDB", nullptr};
     918         130 :         GDALDataset *poDS = GDALDataset::Open(
     919         130 :             m_pDS->GetFSName(), GDAL_OF_VECTOR, apszDrivers, nullptr, nullptr);
     920         130 :         if (poDS != nullptr)
     921             :         {
     922          19 :             OGRLayer *poLyr = poDS->GetLayerByName(GetName());
     923          19 :             if (poLyr)
     924             :             {
     925          19 :                 const auto poOFGBLayerDefn = poLyr->GetLayerDefn();
     926          19 :                 const int nOFGDBFieldCount = poOFGBLayerDefn->GetFieldCount();
     927          67 :                 for (int i = 0; i < nOFGDBFieldCount; i++)
     928             :                 {
     929             :                     const OGRFieldDefn *poSrcDefn =
     930          48 :                         poOFGBLayerDefn->GetFieldDefn(i);
     931          80 :                     if ((poSrcDefn->GetType() == OFTInteger ||
     932          96 :                          poSrcDefn->GetType() == OFTReal) &&
     933          28 :                         poSrcDefn->GetDefault() != nullptr)
     934             :                     {
     935          15 :                         int nIdxDst = m_pFeatureDefn->GetFieldIndex(
     936          15 :                             poSrcDefn->GetNameRef());
     937          15 :                         if (nIdxDst >= 0)
     938          15 :                             m_pFeatureDefn->GetFieldDefn(nIdxDst)->SetDefault(
     939             :                                 poSrcDefn->GetDefault());
     940             :                     }
     941             : 
     942             :                     // XML parsing by the SDK fails when there are special
     943             :                     // characters, like &, so fallback to using OpenFileGDB.
     944             :                     const char *pszAlternativeName =
     945          48 :                         poSrcDefn->GetAlternativeNameRef();
     946          96 :                     if (pszAlternativeName != nullptr &&
     947          49 :                         pszAlternativeName[0] != '\0' &&
     948           1 :                         strcmp(pszAlternativeName, poSrcDefn->GetNameRef()) !=
     949             :                             0)
     950             :                     {
     951           1 :                         int nIdxDst = m_pFeatureDefn->GetFieldIndex(
     952           1 :                             poSrcDefn->GetNameRef());
     953           1 :                         if (nIdxDst >= 0)
     954           1 :                             m_pFeatureDefn->GetFieldDefn(nIdxDst)
     955           1 :                                 ->SetAlternativeName(pszAlternativeName);
     956             :                     }
     957             :                 }
     958             :             }
     959          19 :             GDALClose(poDS);
     960             :         }
     961             :     }
     962             : 
     963         132 :     return true;
     964             : }
     965             : 
     966             : /************************************************************************/
     967             : /*                            ResetReading()                            */
     968             : /************************************************************************/
     969             : 
     970        1216 : void FGdbLayer::ResetReading()
     971             : {
     972             :     long hr;
     973             : 
     974        1216 :     if (m_pTable == nullptr)
     975           0 :         return;
     976             : 
     977             : #ifdef WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD
     978        1216 :     const std::wstring wstrSubFieldBackup(m_wstrSubfields);
     979        1216 :     if (!m_apoByteArrays.empty())
     980             :     {
     981        1204 :         m_bWorkaroundCrashOnCDFWithBinaryField = CPLTestBool(CPLGetConfigOption(
     982             :             "OGR_FGDB_WORKAROUND_CRASH_ON_BINARY_FIELD", "YES"));
     983        1204 :         if (m_bWorkaroundCrashOnCDFWithBinaryField)
     984             :         {
     985        1204 :             m_wstrSubfields = StringToWString(m_strOIDFieldName);
     986        1550 :             if (!m_strShapeFieldName.empty() && m_poFilterGeom &&
     987         346 :                 !m_poFilterGeom->IsEmpty())
     988             :             {
     989         346 :                 m_wstrSubfields += StringToWString(", " + m_strShapeFieldName);
     990             :             }
     991             :         }
     992             :     }
     993             : #endif
     994             : 
     995        1216 :     if (m_poFilterGeom && !m_poFilterGeom->IsEmpty())
     996             :     {
     997             :         // Search spatial
     998             :         // As of beta1, FileGDB only supports bbox searched, if we have GEOS
     999             :         // installed, we can do the rest ourselves.
    1000             : 
    1001         346 :         OGREnvelope ogrEnv;
    1002             : 
    1003         346 :         m_poFilterGeom->getEnvelope(&ogrEnv);
    1004             : 
    1005             :         // spatial query
    1006             :         FileGDBAPI::Envelope env(ogrEnv.MinX, ogrEnv.MaxX, ogrEnv.MinY,
    1007         692 :                                  ogrEnv.MaxY);
    1008             : 
    1009         346 :         if (FAILED(hr = m_pTable->Search(m_wstrSubfields, m_wstrWhereClause,
    1010             :                                          env, true, *m_pEnumRows)))
    1011           0 :             GDBErr(hr, "Failed Searching");
    1012             :     }
    1013             :     else
    1014             :     {
    1015             :         // Search non-spatial
    1016         870 :         if (FAILED(hr = m_pTable->Search(m_wstrSubfields, m_wstrWhereClause,
    1017             :                                          true, *m_pEnumRows)))
    1018           0 :             GDBErr(hr, "Failed Searching");
    1019             :     }
    1020             : 
    1021             : #ifdef WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD
    1022        1216 :     if (!m_apoByteArrays.empty() && m_bWorkaroundCrashOnCDFWithBinaryField)
    1023        1204 :         m_wstrSubfields = wstrSubFieldBackup;
    1024             : #endif
    1025             : 
    1026        1216 :     m_bFilterDirty = false;
    1027             : }
    1028             : 
    1029             : /************************************************************************/
    1030             : /*                         ISetSpatialFilter()                          */
    1031             : /************************************************************************/
    1032             : 
    1033         379 : OGRErr FGdbLayer::ISetSpatialFilter(int iGeomField, const OGRGeometry *pOGRGeom)
    1034             : {
    1035         379 :     m_bFilterDirty = true;
    1036         379 :     return OGRLayer::ISetSpatialFilter(iGeomField, pOGRGeom);
    1037             : }
    1038             : 
    1039             : /************************************************************************/
    1040             : /*                         SetAttributeFilter()                         */
    1041             : /************************************************************************/
    1042             : 
    1043         514 : OGRErr FGdbLayer::SetAttributeFilter(const char *pszQuery)
    1044             : {
    1045         514 :     m_wstrWhereClause = StringToWString((pszQuery != nullptr) ? pszQuery : "");
    1046             : 
    1047         514 :     m_bFilterDirty = true;
    1048             : 
    1049         514 :     return OGRERR_NONE;
    1050             : }
    1051             : 
    1052             : /************************************************************************/
    1053             : /*                           OGRFeatureFromGdbRow()                      */
    1054             : /************************************************************************/
    1055             : 
    1056        1787 : bool FGdbBaseLayer::OGRFeatureFromGdbRow(Row *pRow, OGRFeature **ppFeature)
    1057             : {
    1058             :     long hr;
    1059             : 
    1060        1787 :     OGRFeature *pOutFeature = new OGRFeature(m_pFeatureDefn);
    1061             : 
    1062             :     /////////////////////////////////////////////////////////
    1063             :     // Translate OID
    1064             :     //
    1065             : 
    1066        1787 :     int32 oid = -1;
    1067        1787 :     if (FAILED(hr = pRow->GetOID(oid)))
    1068             :     {
    1069             :         // this should never happen unless not selecting the OBJECTID
    1070             :     }
    1071             :     else
    1072             :     {
    1073        1787 :         pOutFeature->SetFID(oid);
    1074             :     }
    1075             : 
    1076             :     /////////////////////////////////////////////////////////
    1077             :     // Translate Geometry
    1078             :     //
    1079             : 
    1080        3574 :     ShapeBuffer gdbGeometry;
    1081             :     // Row::GetGeometry() will fail with -2147467259 for NULL geometries
    1082             :     // Row::GetGeometry() will fail with -2147219885 for tables without a
    1083             :     // geometry field
    1084        3504 :     if (!m_pFeatureDefn->IsGeometryIgnored() &&
    1085        1717 :         !FAILED(hr = pRow->GetGeometry(gdbGeometry)))
    1086             :     {
    1087        1470 :         OGRGeometry *pOGRGeo = nullptr;
    1088             : 
    1089        1470 :         if ((!GDBGeometryToOGRGeometry(m_forceMulti, &gdbGeometry, m_pSRS,
    1090             :                                        &pOGRGeo)))
    1091             :         {
    1092           0 :             delete pOutFeature;
    1093           0 :             return GDBErr(hr, "Failed to translate FileGDB Geometry to OGR "
    1094           0 :                               "Geometry for row " +
    1095           0 :                                   string(CPLSPrintf("%d", (int)oid)));
    1096             :         }
    1097             : 
    1098        1470 :         pOutFeature->SetGeometryDirectly(pOGRGeo);
    1099             :     }
    1100             : 
    1101             :     //////////////////////////////////////////////////////////
    1102             :     // Map fields
    1103             :     //
    1104             : 
    1105        1787 :     int mappedFieldCount = static_cast<int>(m_vOGRFieldToESRIField.size());
    1106             : 
    1107        1787 :     bool foundBadColumn = false;
    1108             : 
    1109       24602 :     for (int i = 0; i < mappedFieldCount; ++i)
    1110             :     {
    1111       22815 :         OGRFieldDefn *poFieldDefn = m_pFeatureDefn->GetFieldDefn(i);
    1112             :         // The IsNull() and GetXXX() API are very slow when there are a
    1113             :         // big number of fields, for example with Tiger database.
    1114       22815 :         if (poFieldDefn->IsIgnored())
    1115          86 :             continue;
    1116             : 
    1117       22730 :         const wstring &wstrFieldName = m_vOGRFieldToESRIField[i];
    1118       22730 :         const std::string &strFieldType = m_vOGRFieldToESRIFieldType[i];
    1119             : 
    1120       22730 :         bool isNull = false;
    1121             : 
    1122       22730 :         if (FAILED(hr = pRow->IsNull(wstrFieldName, isNull)))
    1123             :         {
    1124           0 :             GDBErr(hr, "Failed to determine NULL status from column " +
    1125           0 :                            WStringToString(wstrFieldName));
    1126           0 :             foundBadColumn = true;
    1127           0 :             continue;
    1128             :         }
    1129             : 
    1130       22730 :         if (isNull)
    1131             :         {
    1132           1 :             pOutFeature->SetFieldNull(i);
    1133           1 :             continue;
    1134             :         }
    1135             : 
    1136             :         //
    1137             :         // NOTE: This switch statement needs to be kept in sync with
    1138             :         // GDBToOGRFieldType utility function
    1139             :         //       since we are only checking for types we mapped in that utility
    1140             :         //       function
    1141             : 
    1142       22729 :         switch (poFieldDefn->GetType())
    1143             :         {
    1144             : 
    1145        6915 :             case OFTInteger:
    1146             :             {
    1147             :                 int32 val;
    1148             : 
    1149        6915 :                 if (FAILED(hr = pRow->GetInteger(wstrFieldName, val)))
    1150             :                 {
    1151             :                     int16 shortval;
    1152        3496 :                     if (FAILED(hr = pRow->GetShort(wstrFieldName, shortval)))
    1153             :                     {
    1154           0 :                         GDBErr(hr,
    1155           0 :                                "Failed to determine integer value for column " +
    1156           0 :                                    WStringToString(wstrFieldName));
    1157           0 :                         foundBadColumn = true;
    1158           0 :                         continue;
    1159             :                     }
    1160        3496 :                     val = shortval;
    1161             :                 }
    1162             : 
    1163        6915 :                 pOutFeature->SetField(i, (int)val);
    1164             :             }
    1165        6915 :             break;
    1166             : 
    1167        5293 :             case OFTReal:
    1168             :             {
    1169        5293 :                 if (strFieldType == "esriFieldTypeSingle")
    1170             :                 {
    1171             :                     float val;
    1172             : 
    1173        3496 :                     if (FAILED(hr = pRow->GetFloat(wstrFieldName, val)))
    1174             :                     {
    1175           0 :                         GDBErr(hr,
    1176           0 :                                "Failed to determine float value for column " +
    1177           0 :                                    WStringToString(wstrFieldName));
    1178           0 :                         foundBadColumn = true;
    1179           0 :                         continue;
    1180             :                     }
    1181             : 
    1182        3496 :                     pOutFeature->SetField(i, val);
    1183             :                 }
    1184             :                 else
    1185             :                 {
    1186             :                     double val;
    1187             : 
    1188        1797 :                     if (FAILED(hr = pRow->GetDouble(wstrFieldName, val)))
    1189             :                     {
    1190           0 :                         GDBErr(hr,
    1191           0 :                                "Failed to determine real value for column " +
    1192           0 :                                    WStringToString(wstrFieldName));
    1193           0 :                         foundBadColumn = true;
    1194           0 :                         continue;
    1195             :                     }
    1196             : 
    1197        1797 :                     pOutFeature->SetField(i, val);
    1198             :                 }
    1199             :             }
    1200        5293 :             break;
    1201        5275 :             case OFTString:
    1202             :             {
    1203        5275 :                 wstring val;
    1204        5275 :                 std::string strValue;
    1205             : 
    1206        5275 :                 if (strFieldType == "esriFieldTypeGlobalID")
    1207             :                 {
    1208           1 :                     Guid guid;
    1209           2 :                     if (FAILED(hr = pRow->GetGlobalID(guid)) ||
    1210           1 :                         FAILED(hr = guid.ToString(val)))
    1211             :                     {
    1212           0 :                         GDBErr(hr,
    1213           0 :                                "Failed to determine string value for column " +
    1214           0 :                                    WStringToString(wstrFieldName));
    1215           0 :                         foundBadColumn = true;
    1216           0 :                         continue;
    1217             :                     }
    1218           1 :                     strValue = WStringToString(val);
    1219             :                 }
    1220        5274 :                 else if (strFieldType == "esriFieldTypeGUID")
    1221             :                 {
    1222        1748 :                     Guid guid;
    1223        3496 :                     if (FAILED(hr = pRow->GetGUID(wstrFieldName, guid)) ||
    1224        1748 :                         FAILED(hr = guid.ToString(val)))
    1225             :                     {
    1226           0 :                         GDBErr(hr,
    1227           0 :                                "Failed to determine string value for column " +
    1228           0 :                                    WStringToString(wstrFieldName));
    1229           0 :                         foundBadColumn = true;
    1230           0 :                         continue;
    1231             :                     }
    1232        1748 :                     strValue = WStringToString(val);
    1233             :                 }
    1234        3526 :                 else if (strFieldType == "esriFieldTypeXML")
    1235             :                 {
    1236        1748 :                     if (FAILED(hr = pRow->GetXML(wstrFieldName, strValue)))
    1237             :                     {
    1238           0 :                         GDBErr(hr, "Failed to determine XML value for column " +
    1239           0 :                                        WStringToString(wstrFieldName));
    1240           0 :                         foundBadColumn = true;
    1241           0 :                         continue;
    1242             :                     }
    1243             :                 }
    1244             :                 else
    1245             :                 {
    1246        1778 :                     if (FAILED(hr = pRow->GetString(wstrFieldName, val)))
    1247             :                     {
    1248           0 :                         GDBErr(hr,
    1249           0 :                                "Failed to determine string value for column " +
    1250           0 :                                    WStringToString(wstrFieldName));
    1251           0 :                         foundBadColumn = true;
    1252           0 :                         continue;
    1253             :                     }
    1254        1778 :                     strValue = WStringToString(val);
    1255             :                 }
    1256             : 
    1257        5275 :                 pOutFeature->SetField(i, strValue.c_str());
    1258             :             }
    1259        5275 :             break;
    1260             : 
    1261        3496 :             case OFTBinary:
    1262             :             {
    1263        3496 :                 ByteArray binaryBuf;
    1264             : 
    1265        3496 :                 if (FAILED(hr = pRow->GetBinary(wstrFieldName, binaryBuf)))
    1266             :                 {
    1267           0 :                     GDBErr(hr, "Failed to determine binary value for column " +
    1268           0 :                                    WStringToString(wstrFieldName));
    1269           0 :                     foundBadColumn = true;
    1270           0 :                     continue;
    1271             :                 }
    1272             : 
    1273        3496 :                 pOutFeature->SetField(i, (int)binaryBuf.inUseLength,
    1274        3496 :                                       (GByte *)binaryBuf.byteArray);
    1275             :             }
    1276        3496 :             break;
    1277             : 
    1278        1750 :             case OFTDateTime:
    1279             :             {
    1280             :                 struct tm val;
    1281             : 
    1282        1750 :                 if (FAILED(hr = pRow->GetDate(wstrFieldName, val)))
    1283             :                 {
    1284           0 :                     GDBErr(hr, "Failed to determine date value for column " +
    1285           0 :                                    WStringToString(wstrFieldName));
    1286           0 :                     foundBadColumn = true;
    1287           0 :                     continue;
    1288             :                 }
    1289             : 
    1290        1750 :                 pOutFeature->SetField(i, val.tm_year + 1900, val.tm_mon + 1,
    1291             :                                       val.tm_mday, val.tm_hour, val.tm_min,
    1292        1750 :                                       (float)val.tm_sec,
    1293        1750 :                                       m_bTimeInUTC ? 100 : 0);
    1294             :                 // Examine test data to figure out how to extract that
    1295             :             }
    1296        1750 :             break;
    1297             : 
    1298           0 :             default:
    1299             :             {
    1300           0 :                 if (!m_suppressColumnMappingError)
    1301             :                 {
    1302           0 :                     foundBadColumn = true;
    1303           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1304             :                              "Row id: %d col:%d has unhandled col type (%d). "
    1305             :                              "Setting to NULL.",
    1306             :                              (int)oid, (int)i,
    1307           0 :                              m_pFeatureDefn->GetFieldDefn(i)->GetType());
    1308             :                 }
    1309             :             }
    1310             :         }
    1311             :     }
    1312             : 
    1313        1787 :     if (foundBadColumn)
    1314           0 :         m_suppressColumnMappingError = true;
    1315             : 
    1316        1787 :     *ppFeature = pOutFeature;
    1317             : 
    1318        1787 :     return true;
    1319             : }
    1320             : 
    1321             : /************************************************************************/
    1322             : /*                           GetNextFeature()                           */
    1323             : /************************************************************************/
    1324             : 
    1325        2059 : OGRFeature *FGdbLayer::GetNextFeature()
    1326             : {
    1327        2059 :     if (m_bFilterDirty)
    1328          95 :         ResetReading();
    1329             : 
    1330             : #ifdef WORKAROUND_CRASH_ON_CDF_WITH_BINARY_FIELD
    1331        2059 :     if (!m_apoByteArrays.empty() && m_bWorkaroundCrashOnCDFWithBinaryField)
    1332             :     {
    1333             :         while (true)
    1334             :         {
    1335        2018 :             if (m_pEnumRows == nullptr)
    1336        2018 :                 return nullptr;
    1337             : 
    1338             :             long hr;
    1339             : 
    1340        2018 :             Row rowOnlyOid;
    1341             : 
    1342        2018 :             if (FAILED(hr = m_pEnumRows->Next(rowOnlyOid)))
    1343             :             {
    1344          17 :                 GDBErr(hr, "Failed fetching features");
    1345          17 :                 return nullptr;
    1346             :             }
    1347             : 
    1348        2001 :             if (hr != S_OK)
    1349             :             {
    1350             :                 // It's OK, we are done fetching - failure is caught by FAILED
    1351             :                 // macro
    1352         335 :                 return nullptr;
    1353             :             }
    1354             : 
    1355        1666 :             int32 oid = -1;
    1356        1666 :             if (FAILED(hr = rowOnlyOid.GetOID(oid)))
    1357             :             {
    1358           0 :                 GDBErr(hr, "Failed to get oid");
    1359           0 :                 continue;
    1360             :             }
    1361             : 
    1362        1666 :             EnumRows enumRows;
    1363        1666 :             OGRFeature *pOGRFeature = nullptr;
    1364        1666 :             Row rowFull;
    1365        1666 :             if (GetRow(enumRows, rowFull, oid) != OGRERR_NONE ||
    1366        1666 :                 !OGRFeatureFromGdbRow(&rowFull, &pOGRFeature) || !pOGRFeature)
    1367             :             {
    1368           0 :                 GDBErr(hr,
    1369             :                        CPLSPrintf(
    1370             :                            "Failed translating FGDB row [%d] to OGR Feature",
    1371             :                            oid));
    1372             : 
    1373             :                 // return NULL;
    1374           0 :                 continue;  // skip feature
    1375             :             }
    1376             : 
    1377        1946 :             if ((m_poFilterGeom == nullptr ||
    1378         280 :                  FilterGeometry(pOGRFeature->GetGeometryRef())))
    1379             :             {
    1380        1666 :                 return pOGRFeature;
    1381             :             }
    1382           0 :             delete pOGRFeature;
    1383           0 :         }
    1384             :     }
    1385             : #endif
    1386             : 
    1387          41 :     OGRFeature *poFeature = FGdbBaseLayer::GetNextFeature();
    1388          41 :     return poFeature;
    1389             : }
    1390             : 
    1391             : /************************************************************************/
    1392             : /*                             GetFeature()                             */
    1393             : /************************************************************************/
    1394             : 
    1395         134 : OGRFeature *FGdbLayer::GetFeature(GIntBig oid)
    1396             : {
    1397             :     // do query to fetch individual row
    1398         268 :     EnumRows enumRows;
    1399         268 :     Row row;
    1400         134 :     if (!CPL_INT64_FITS_ON_INT32(oid) || m_pTable == nullptr)
    1401          17 :         return nullptr;
    1402             : 
    1403         117 :     int nFID32 = (int)oid;
    1404             : 
    1405         117 :     if (GetRow(enumRows, row, nFID32) != OGRERR_NONE)
    1406          34 :         return nullptr;
    1407             : 
    1408          83 :     OGRFeature *pOGRFeature = nullptr;
    1409             : 
    1410          83 :     if (!OGRFeatureFromGdbRow(&row, &pOGRFeature))
    1411             :     {
    1412           0 :         return nullptr;
    1413             :     }
    1414          83 :     if (pOGRFeature)
    1415             :     {
    1416          83 :         pOGRFeature->SetFID(oid);
    1417             :     }
    1418             : 
    1419          83 :     return pOGRFeature;
    1420             : }
    1421             : 
    1422             : /************************************************************************/
    1423             : /*                          GetFeatureCount()                           */
    1424             : /************************************************************************/
    1425             : 
    1426         213 : GIntBig FGdbLayer::GetFeatureCount(CPL_UNUSED int bForce)
    1427             : {
    1428         213 :     int32 rowCount = 0;
    1429             : 
    1430         213 :     if (m_pTable == nullptr)
    1431           0 :         return 0;
    1432             : 
    1433         213 :     if (m_poFilterGeom != nullptr || !m_wstrWhereClause.empty())
    1434             :     {
    1435          90 :         ResetReading();
    1436          90 :         if (m_pEnumRows == nullptr)
    1437           0 :             return 0;
    1438             : 
    1439          90 :         int nFeatures = 0;
    1440             :         while (true)
    1441             :         {
    1442             :             long hr;
    1443             : 
    1444         315 :             Row row;
    1445             : 
    1446         315 :             if (FAILED(hr = m_pEnumRows->Next(row)))
    1447             :             {
    1448           0 :                 GDBErr(hr, "Failed fetching features");
    1449           0 :                 return 0;
    1450             :             }
    1451             : 
    1452         315 :             if (hr != S_OK)
    1453             :             {
    1454          90 :                 break;
    1455             :             }
    1456             : 
    1457         225 :             if (m_poFilterGeom == nullptr)
    1458             :             {
    1459          85 :                 nFeatures++;
    1460             :             }
    1461             :             else
    1462             :             {
    1463         140 :                 ShapeBuffer gdbGeometry;
    1464         140 :                 if (FAILED(hr = row.GetGeometry(gdbGeometry)))
    1465             :                 {
    1466           0 :                     continue;
    1467             :                 }
    1468             : 
    1469         140 :                 OGRGeometry *pOGRGeo = nullptr;
    1470         140 :                 if (!GDBGeometryToOGRGeometry(m_forceMulti, &gdbGeometry,
    1471         280 :                                               m_pSRS, &pOGRGeo) ||
    1472         140 :                     pOGRGeo == nullptr)
    1473             :                 {
    1474           0 :                     delete pOGRGeo;
    1475           0 :                     continue;
    1476             :                 }
    1477             : 
    1478         140 :                 if (FilterGeometry(pOGRGeo))
    1479             :                 {
    1480         140 :                     nFeatures++;
    1481             :                 }
    1482             : 
    1483         140 :                 delete pOGRGeo;
    1484             :             }
    1485         225 :         }
    1486          90 :         ResetReading();
    1487          90 :         return nFeatures;
    1488             :     }
    1489             : 
    1490             :     long hr;
    1491         123 :     if (FAILED(hr = m_pTable->GetRowCount(rowCount)))
    1492             :     {
    1493           0 :         GDBErr(hr, "Failed counting rows");
    1494           0 :         return 0;
    1495             :     }
    1496             : 
    1497         123 :     return static_cast<int>(rowCount);
    1498             : }
    1499             : 
    1500             : /************************************************************************/
    1501             : /*                         GetMetadataItem()                            */
    1502             : /************************************************************************/
    1503             : 
    1504         189 : const char *FGdbLayer::GetMetadataItem(const char *pszName,
    1505             :                                        const char *pszDomain)
    1506             : {
    1507         189 :     return OGRLayer::GetMetadataItem(pszName, pszDomain);
    1508             : }
    1509             : 
    1510             : /************************************************************************/
    1511             : /*                            IGetExtent()                              */
    1512             : /************************************************************************/
    1513             : 
    1514          60 : OGRErr FGdbLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent, bool bForce)
    1515             : {
    1516          60 :     if (m_pTable == nullptr)
    1517           0 :         return OGRERR_FAILURE;
    1518             : 
    1519         120 :     if (m_poFilterGeom != nullptr || !m_wstrWhereClause.empty() ||
    1520          60 :         m_strShapeFieldName.empty())
    1521             :     {
    1522           0 :         const int nFieldCount = m_pFeatureDefn->GetFieldCount();
    1523           0 :         int *pabSaveFieldIgnored = new int[nFieldCount];
    1524           0 :         for (int i = 0; i < nFieldCount; i++)
    1525             :         {
    1526             :             // cppcheck-suppress uninitdata
    1527           0 :             pabSaveFieldIgnored[i] =
    1528           0 :                 m_pFeatureDefn->GetFieldDefn(i)->IsIgnored();
    1529           0 :             m_pFeatureDefn->GetFieldDefn(i)->SetIgnored(TRUE);
    1530             :         }
    1531           0 :         OGRErr eErr = OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
    1532           0 :         for (int i = 0; i < nFieldCount; i++)
    1533             :         {
    1534           0 :             m_pFeatureDefn->GetFieldDefn(i)->SetIgnored(pabSaveFieldIgnored[i]);
    1535             :         }
    1536           0 :         delete[] pabSaveFieldIgnored;
    1537           0 :         return eErr;
    1538             :     }
    1539             : 
    1540             :     long hr;
    1541         120 :     Envelope envelope;
    1542          60 :     if (FAILED(hr = m_pTable->GetExtent(envelope)))
    1543             :     {
    1544           0 :         GDBErr(hr, "Failed fetching extent");
    1545           0 :         return OGRERR_FAILURE;
    1546             :     }
    1547             : 
    1548          60 :     psExtent->MinX = envelope.xMin;
    1549          60 :     psExtent->MinY = envelope.yMin;
    1550          60 :     psExtent->MaxX = envelope.xMax;
    1551          60 :     psExtent->MaxY = envelope.yMax;
    1552             : 
    1553         172 :     if (std::isnan(psExtent->MinX) || std::isnan(psExtent->MinY) ||
    1554         172 :         std::isnan(psExtent->MaxX) || std::isnan(psExtent->MaxY))
    1555           4 :         return OGRERR_FAILURE;
    1556             : 
    1557          56 :     return OGRERR_NONE;
    1558             : }
    1559             : 
    1560             : /************************************************************************/
    1561             : /*                           GetLayerXML()                              */
    1562             : /* Return XML definition of the Layer as provided by FGDB. Caller must  */
    1563             : /* free result.                                                         */
    1564             : /* Not currently used by the driver, but can be used by external code   */
    1565             : /* for specific purposes.                                               */
    1566             : /************************************************************************/
    1567             : 
    1568          17 : OGRErr FGdbLayer::GetLayerXML(char **ppXml)
    1569             : {
    1570             :     long hr;
    1571          34 :     std::string xml;
    1572             : 
    1573          17 :     if (m_pTable == nullptr)
    1574           0 :         return OGRERR_FAILURE;
    1575             : 
    1576          17 :     if (FAILED(hr = m_pTable->GetDefinition(xml)))
    1577             :     {
    1578           0 :         GDBErr(hr, "Failed fetching XML table definition");
    1579           0 :         return OGRERR_FAILURE;
    1580             :     }
    1581             : 
    1582          17 :     *ppXml = CPLStrdup(xml.c_str());
    1583          17 :     return OGRERR_NONE;
    1584             : }
    1585             : 
    1586             : /************************************************************************/
    1587             : /*                           GetLayerMetadataXML()                      */
    1588             : /* Return XML metadata for the Layer as provided by FGDB. Caller must  */
    1589             : /* free result.                                                         */
    1590             : /* Not currently used by the driver, but can be used by external code   */
    1591             : /* for specific purposes.                                               */
    1592             : /************************************************************************/
    1593             : 
    1594          17 : OGRErr FGdbLayer::GetLayerMetadataXML(char **ppXml)
    1595             : {
    1596             :     long hr;
    1597          34 :     std::string xml;
    1598             : 
    1599          17 :     if (m_pTable == nullptr)
    1600           0 :         return OGRERR_FAILURE;
    1601             : 
    1602          17 :     if (FAILED(hr = m_pTable->GetDocumentation(xml)))
    1603             :     {
    1604           0 :         GDBErr(hr, "Failed fetching XML table metadata");
    1605           0 :         return OGRERR_FAILURE;
    1606             :     }
    1607             : 
    1608          17 :     *ppXml = CPLStrdup(xml.c_str());
    1609          17 :     return OGRERR_NONE;
    1610             : }
    1611             : 
    1612             : /************************************************************************/
    1613             : /*                           TestCapability()                           */
    1614             : /************************************************************************/
    1615             : 
    1616         454 : int FGdbLayer::TestCapability(const char *pszCap)
    1617             : {
    1618             : 
    1619         454 :     if (EQUAL(pszCap, OLCRandomRead))
    1620           0 :         return TRUE;
    1621             : 
    1622         454 :     else if (EQUAL(pszCap, OLCFastFeatureCount))
    1623           0 :         return m_poFilterGeom == nullptr && m_wstrWhereClause.empty();
    1624             : 
    1625         454 :     else if (EQUAL(pszCap, OLCFastSpatialFilter))
    1626           0 :         return TRUE;
    1627             : 
    1628         454 :     else if (EQUAL(pszCap, OLCFastGetExtent))
    1629          28 :         return m_poFilterGeom == nullptr && m_wstrWhereClause.empty();
    1630             : 
    1631         426 :     else if (EQUAL(pszCap,
    1632             :                    OLCStringsAsUTF8)) /* Native UTF16, converted to UTF8 */
    1633          17 :         return TRUE;
    1634             : 
    1635         409 :     else if (EQUAL(pszCap,
    1636             :                    OLCFastSetNextByIndex)) /* TBD FastSetNextByIndex() */
    1637           0 :         return FALSE;
    1638             : 
    1639         409 :     else if (EQUAL(pszCap, OLCIgnoreFields))
    1640          17 :         return TRUE;
    1641             : 
    1642         392 :     else if (EQUAL(pszCap, OLCMeasuredGeometries))
    1643          85 :         return TRUE;
    1644             : 
    1645         307 :     else if (EQUAL(pszCap, OLCZGeometries))
    1646          51 :         return TRUE;
    1647             : 
    1648             :     else
    1649         256 :         return FALSE;
    1650             : }
    1651             : 
    1652             : /************************************************************************/
    1653             : /*                             GetDataset()                             */
    1654             : /************************************************************************/
    1655             : 
    1656          34 : GDALDataset *FGdbLayer::GetDataset()
    1657             : {
    1658          34 :     return m_pDS;
    1659             : }

Generated by: LCOV version 1.14