Coverage Report

Created: 2025-12-03 08:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdblayer_write.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements Open FileGDB OGR driver.
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2022, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "ogr_openfilegdb.h"
15
#include "filegdb_gdbtoogrfieldtype.h"
16
17
#include <cinttypes>
18
#include <cmath>
19
#include <cstddef>
20
#include <cstdio>
21
#include <cstdlib>
22
#include <cstring>
23
#include <cwchar>
24
#include <algorithm>
25
#include <limits>
26
#include <string>
27
28
#include "cpl_conv.h"
29
#include "cpl_error.h"
30
#include "cpl_minixml.h"
31
#include "cpl_string.h"
32
#include "gdal_priv_templates.hpp"
33
#include "ogr_api.h"
34
#include "ogr_core.h"
35
#include "ogr_feature.h"
36
#include "ogr_geometry.h"
37
#include "ogr_spatialref.h"
38
#include "ogr_srs_api.h"
39
#include "ogrsf_frmts.h"
40
#include "filegdbtable.h"
41
#include "filegdbtable_priv.h"
42
#include "filegdb_coordprec_write.h"
43
#include "filegdb_reserved_keywords.h"
44
45
/*************************************************************************/
46
/*                            StringToWString()                          */
47
/*************************************************************************/
48
49
static std::wstring StringToWString(const std::string &utf8string)
50
146k
{
51
146k
    wchar_t *pszUTF16 =
52
146k
        CPLRecodeToWChar(utf8string.c_str(), CPL_ENC_UTF8, CPL_ENC_UCS2);
53
146k
    std::wstring utf16string = pszUTF16;
54
146k
    CPLFree(pszUTF16);
55
146k
    return utf16string;
56
146k
}
57
58
/*************************************************************************/
59
/*                            WStringToString()                          */
60
/*************************************************************************/
61
62
static std::string WStringToString(const std::wstring &utf16string)
63
257k
{
64
257k
    char *pszUTF8 =
65
257k
        CPLRecodeFromWChar(utf16string.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8);
66
257k
    std::string utf8string = pszUTF8;
67
257k
    CPLFree(pszUTF8);
68
257k
    return utf8string;
69
257k
}
70
71
/*************************************************************************/
72
/*                              LaunderName()                            */
73
/*************************************************************************/
74
75
static std::wstring LaunderName(const std::wstring &name)
76
51.5k
{
77
51.5k
    std::wstring newName = name;
78
79
    // https://support.esri.com/en/technical-article/000005588
80
81
    // "Do not start field or table names with an underscore or a number."
82
    // But we can see in the wild table names starting with underscore...
83
    // (cf https://github.com/OSGeo/gdal/issues/4112)
84
51.5k
    if (!newName.empty() && newName[0] >= '0' && newName[0] <= '9')
85
814
    {
86
814
        newName = StringToWString("_") + newName;
87
814
    }
88
89
    // "Essentially, eliminate anything that is not alphanumeric or an
90
    // underscore." Note: alphanumeric unicode is supported
91
1.94M
    for (size_t i = 0; i < newName.size(); i++)
92
1.89M
    {
93
1.89M
        if (!(newName[i] == '_' || (newName[i] >= '0' && newName[i] <= '9') ||
94
1.69M
              (newName[i] >= 'a' && newName[i] <= 'z') ||
95
969k
              (newName[i] >= 'A' && newName[i] <= 'Z') || newName[i] >= 128))
96
646k
        {
97
646k
            newName[i] = '_';
98
646k
        }
99
1.89M
    }
100
101
51.5k
    return newName;
102
51.5k
}
103
104
/*************************************************************************/
105
/*                      EscapeUnsupportedPrefixes()                      */
106
/*************************************************************************/
107
108
static std::wstring EscapeUnsupportedPrefixes(const std::wstring &className)
109
5.48k
{
110
5.48k
    std::wstring newName = className;
111
    // From ESRI docs
112
    // Feature classes starting with these strings are unsupported.
113
5.48k
    static const char *const UNSUPPORTED_PREFIXES[] = {"sde_", "gdb_", "delta_",
114
5.48k
                                                       nullptr};
115
116
21.9k
    for (int i = 0; UNSUPPORTED_PREFIXES[i] != nullptr; i++)
117
16.4k
    {
118
        // cppcheck-suppress stlIfStrFind
119
16.4k
        if (newName.find(StringToWString(UNSUPPORTED_PREFIXES[i])) == 0)
120
0
        {
121
            // Normally table names shouldn't start with underscore, but
122
            // there are such in the wild (cf
123
            // https://github.com/OSGeo/gdal/issues/4112)
124
0
            newName = StringToWString("_") + newName;
125
0
            break;
126
0
        }
127
16.4k
    }
128
129
5.48k
    return newName;
130
5.48k
}
131
132
/*************************************************************************/
133
/*                         EscapeReservedKeywords()                      */
134
/*************************************************************************/
135
136
static std::wstring EscapeReservedKeywords(const std::wstring &name)
137
51.5k
{
138
51.5k
    std::string newName = WStringToString(name);
139
51.5k
    std::string upperName = CPLString(newName).toupper();
140
141
    // Append an underscore to any FGDB reserved words used as field names
142
    // This is the same behavior ArcCatalog follows.
143
51.5k
    for (const char *pszKeyword : apszRESERVED_WORDS)
144
1.44M
    {
145
1.44M
        if (upperName == pszKeyword)
146
9
        {
147
9
            newName += '_';
148
9
            break;
149
9
        }
150
1.44M
    }
151
152
51.5k
    return StringToWString(newName);
153
51.5k
}
154
155
/***********************************************************************/
156
/*                     XMLSerializeGeomFieldBase()                     */
157
/***********************************************************************/
158
159
static void XMLSerializeGeomFieldBase(CPLXMLNode *psRoot,
160
                                      const FileGDBGeomField *poGeomFieldDefn,
161
                                      const OGRSpatialReference *poSRS)
162
23.4k
{
163
23.4k
    auto psExtent = CPLCreateXMLElementAndValue(psRoot, "Extent", "");
164
23.4k
    CPLAddXMLAttributeAndValue(psExtent, "xsi:nil", "true");
165
166
23.4k
    auto psSpatialReference =
167
23.4k
        CPLCreateXMLNode(psRoot, CXT_Element, "SpatialReference");
168
169
23.4k
    if (poSRS == nullptr)
170
13.3k
    {
171
13.3k
        CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
172
13.3k
                                   "typens:UnknownCoordinateSystem");
173
13.3k
    }
174
10.0k
    else
175
10.0k
    {
176
10.0k
        if (poSRS->IsGeographic())
177
924
            CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
178
924
                                       "typens:GeographicCoordinateSystem");
179
9.10k
        else
180
9.10k
            CPLAddXMLAttributeAndValue(psSpatialReference, "xsi:type",
181
9.10k
                                       "typens:ProjectedCoordinateSystem");
182
10.0k
        CPLCreateXMLElementAndValue(psSpatialReference, "WKT",
183
10.0k
                                    poGeomFieldDefn->GetWKT().c_str());
184
10.0k
    }
185
23.4k
    CPLCreateXMLElementAndValue(
186
23.4k
        psSpatialReference, "XOrigin",
187
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetXOrigin()));
188
23.4k
    CPLCreateXMLElementAndValue(
189
23.4k
        psSpatialReference, "YOrigin",
190
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetYOrigin()));
191
23.4k
    CPLCreateXMLElementAndValue(
192
23.4k
        psSpatialReference, "XYScale",
193
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetXYScale()));
194
23.4k
    CPLCreateXMLElementAndValue(
195
23.4k
        psSpatialReference, "ZOrigin",
196
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetZOrigin()));
197
23.4k
    CPLCreateXMLElementAndValue(
198
23.4k
        psSpatialReference, "ZScale",
199
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetZScale()));
200
23.4k
    CPLCreateXMLElementAndValue(
201
23.4k
        psSpatialReference, "MOrigin",
202
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetMOrigin()));
203
23.4k
    CPLCreateXMLElementAndValue(
204
23.4k
        psSpatialReference, "MScale",
205
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetMScale()));
206
23.4k
    CPLCreateXMLElementAndValue(
207
23.4k
        psSpatialReference, "XYTolerance",
208
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetXYTolerance()));
209
23.4k
    CPLCreateXMLElementAndValue(
210
23.4k
        psSpatialReference, "ZTolerance",
211
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetZTolerance()));
212
23.4k
    CPLCreateXMLElementAndValue(
213
23.4k
        psSpatialReference, "MTolerance",
214
23.4k
        CPLSPrintf("%.17g", poGeomFieldDefn->GetMTolerance()));
215
23.4k
    CPLCreateXMLElementAndValue(psSpatialReference, "HighPrecision", "true");
216
23.4k
    if (poSRS)
217
10.0k
    {
218
10.0k
        if (CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_WKID", "YES")))
219
10.0k
        {
220
10.0k
            const char *pszKey = poSRS->IsProjected() ? "PROJCS" : "GEOGCS";
221
10.0k
            const char *pszAuthorityName = poSRS->GetAuthorityName(pszKey);
222
10.0k
            const char *pszAuthorityCode = poSRS->GetAuthorityCode(pszKey);
223
10.0k
            if (pszAuthorityName && pszAuthorityCode &&
224
4.74k
                (EQUAL(pszAuthorityName, "EPSG") ||
225
0
                 EQUAL(pszAuthorityName, "ESRI")))
226
4.74k
            {
227
4.74k
                CPLCreateXMLElementAndValue(psSpatialReference, "WKID",
228
4.74k
                                            pszAuthorityCode);
229
4.74k
                if (CPLTestBool(CPLGetConfigOption(
230
4.74k
                        "OPENFILEGDB_WRITE_LATESTWKID", "YES")))
231
4.74k
                {
232
4.74k
                    CPLCreateXMLElementAndValue(psSpatialReference,
233
4.74k
                                                "LatestWKID", pszAuthorityCode);
234
4.74k
                }
235
4.74k
            }
236
10.0k
        }
237
238
10.0k
        if (poSRS->IsCompound() &&
239
788
            CPLTestBool(CPLGetConfigOption("OPENFILEGDB_WRITE_VCSWKID", "YES")))
240
788
        {
241
788
            const char *pszAuthorityName = poSRS->GetAuthorityName("VERT_CS");
242
788
            const char *pszAuthorityCode = poSRS->GetAuthorityCode("VERT_CS");
243
788
            if (pszAuthorityName && pszAuthorityCode &&
244
788
                (EQUAL(pszAuthorityName, "EPSG") ||
245
0
                 EQUAL(pszAuthorityName, "ESRI")))
246
788
            {
247
788
                CPLCreateXMLElementAndValue(psSpatialReference, "VCSWKID",
248
788
                                            pszAuthorityCode);
249
788
                if (CPLTestBool(CPLGetConfigOption(
250
788
                        "OPENFILEGDB_WRITE_LATESTVCSWKID", "YES")))
251
788
                {
252
788
                    CPLCreateXMLElementAndValue(
253
788
                        psSpatialReference, "LatestVCSWKID", pszAuthorityCode);
254
788
                }
255
788
            }
256
788
        }
257
10.0k
    }
258
23.4k
}
259
260
/***********************************************************************/
261
/*                    CreateFeatureDataset()                           */
262
/***********************************************************************/
263
264
bool OGROpenFileGDBLayer::CreateFeatureDataset(const char *pszFeatureDataset)
265
0
{
266
0
    std::string osPath("\\");
267
0
    osPath += pszFeatureDataset;
268
269
0
    CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "?xml"));
270
0
    CPLAddXMLAttributeAndValue(oTree.get(), "version", "1.0");
271
0
    CPLAddXMLAttributeAndValue(oTree.get(), "encoding", "UTF-8");
272
273
0
    CPLXMLNode *psRoot =
274
0
        CPLCreateXMLNode(nullptr, CXT_Element, "typens:DEFeatureDataset");
275
0
    CPLAddXMLSibling(oTree.get(), psRoot);
276
277
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
278
0
                               "http://www.w3.org/2001/XMLSchema-instance");
279
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
280
0
                               "http://www.w3.org/2001/XMLSchema");
281
0
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:typens",
282
0
                               "http://www.esri.com/schemas/ArcGIS/10.1");
283
0
    CPLAddXMLAttributeAndValue(psRoot, "xsi:type", "typens:DEFeatureDataset");
284
285
0
    CPLCreateXMLElementAndValue(psRoot, "CatalogPath", osPath.c_str());
286
0
    CPLCreateXMLElementAndValue(psRoot, "Name", pszFeatureDataset);
287
0
    CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
288
0
    CPLCreateXMLElementAndValue(psRoot, "DatasetType", "esriDTFeatureDataset");
289
290
0
    {
291
0
        FileGDBTable oTable;
292
0
        if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
293
0
            return false;
294
0
        CPLCreateXMLElementAndValue(
295
0
            psRoot, "DSID",
296
0
            CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount()));
297
0
    }
298
299
0
    CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
300
0
    CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
301
302
0
    if (m_eGeomType != wkbNone)
303
0
    {
304
0
        XMLSerializeGeomFieldBase(psRoot, m_poLyrTable->GetGeomField(),
305
0
                                  GetSpatialRef());
306
0
    }
307
308
0
    char *pszDefinition = CPLSerializeXMLTree(oTree.get());
309
0
    const std::string osDefinition = pszDefinition;
310
0
    CPLFree(pszDefinition);
311
312
0
    m_osFeatureDatasetGUID = OFGDBGenerateUUID();
313
314
0
    if (!m_poDS->RegisterInItemRelationships(
315
0
            m_poDS->m_osRootGUID, m_osFeatureDatasetGUID,
316
0
            "{dc78f1ab-34e4-43ac-ba47-1c4eabd0e7c7}"))
317
0
    {
318
0
        return false;
319
0
    }
320
321
0
    if (!m_poDS->RegisterFeatureDatasetInItems(
322
0
            m_osFeatureDatasetGUID, pszFeatureDataset, osDefinition.c_str()))
323
0
    {
324
0
        return false;
325
0
    }
326
327
0
    return true;
328
0
}
329
330
/***********************************************************************/
331
/*                      GetLaunderedLayerName()                        */
332
/***********************************************************************/
333
334
std::string
335
OGROpenFileGDBLayer::GetLaunderedLayerName(const std::string &osNameOri) const
336
5.48k
{
337
5.48k
    std::wstring wlayerName = StringToWString(osNameOri);
338
339
5.48k
    wlayerName = LaunderName(wlayerName);
340
5.48k
    wlayerName = EscapeReservedKeywords(wlayerName);
341
5.48k
    wlayerName = EscapeUnsupportedPrefixes(wlayerName);
342
343
    // https://desktop.arcgis.com/en/arcmap/latest/manage-data/administer-file-gdbs/file-geodatabase-size-and-name-limits.htm
344
    // document 160 character limit but
345
    // https://desktop.arcgis.com/en/arcmap/latest/manage-data/tables/fundamentals-of-adding-and-deleting-fields.htm#GUID-8E190093-8F8F-4132-AF4F-B0C9220F76B3
346
    // mentions 64. let be optimistic and aim for 160
347
5.48k
    constexpr size_t TABLE_NAME_MAX_SIZE = 160;
348
5.48k
    if (wlayerName.size() > TABLE_NAME_MAX_SIZE)
349
22
        wlayerName.resize(TABLE_NAME_MAX_SIZE);
350
351
    /* Ensures uniqueness of layer name */
352
5.48k
    int numRenames = 1;
353
5.65k
    while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
354
5.65k
            nullptr) &&
355
170
           (numRenames < 10))
356
170
    {
357
170
        wlayerName = StringToWString(CPLSPrintf(
358
170
            "%s_%d",
359
170
            WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 2))
360
170
                .c_str(),
361
170
            numRenames));
362
170
        numRenames++;
363
170
    }
364
5.48k
    while ((m_poDS->GetLayerByName(WStringToString(wlayerName).c_str()) !=
365
5.48k
            nullptr) &&
366
0
           (numRenames < 100))
367
0
    {
368
0
        wlayerName = StringToWString(CPLSPrintf(
369
0
            "%s_%d",
370
0
            WStringToString(wlayerName.substr(0, TABLE_NAME_MAX_SIZE - 3))
371
0
                .c_str(),
372
0
            numRenames));
373
0
        numRenames++;
374
0
    }
375
376
5.48k
    return WStringToString(wlayerName);
377
5.48k
}
378
379
/***********************************************************************/
380
/*                            Create()                                 */
381
/***********************************************************************/
382
383
bool OGROpenFileGDBLayer::Create(const OGRGeomFieldDefn *poSrcGeomFieldDefn)
384
5.54k
{
385
5.54k
    FileGDBTableGeometryType eTableGeomType = FGTGT_NONE;
386
5.54k
    const auto eFlattenType = wkbFlatten(OGR_GT_GetLinear(m_eGeomType));
387
5.54k
    if (eFlattenType == wkbNone)
388
3.44k
        eTableGeomType = FGTGT_NONE;
389
2.09k
    else if (CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
390
2.09k
                 "CREATE_MULTIPATCH", "FALSE")))
391
0
    {
392
        // For compatibility with FileGDB driver
393
0
        eTableGeomType = FGTGT_MULTIPATCH;
394
0
    }
395
2.09k
    else if (eFlattenType == wkbPoint)
396
13
        eTableGeomType = FGTGT_POINT;
397
2.08k
    else if (eFlattenType == wkbMultiPoint)
398
1
        eTableGeomType = FGTGT_MULTIPOINT;
399
2.07k
    else if (OGR_GT_IsCurve(eFlattenType) ||
400
2.03k
             OGR_GT_IsSubClassOf(eFlattenType, wkbMultiCurve))
401
1.25k
    {
402
1.25k
        eTableGeomType = FGTGT_LINE;
403
1.25k
    }
404
822
    else if (wkbFlatten(m_eGeomType) == wkbTIN ||
405
822
             wkbFlatten(m_eGeomType) == wkbPolyhedralSurface ||
406
822
             m_eGeomType == wkbGeometryCollection25D ||
407
822
             m_eGeomType == wkbSetZ(wkbUnknown))
408
753
    {
409
753
        eTableGeomType = FGTGT_MULTIPATCH;
410
753
    }
411
69
    else if (OGR_GT_IsSurface(eFlattenType) ||
412
66
             OGR_GT_IsSubClassOf(eFlattenType, wkbMultiSurface))
413
13
    {
414
13
        eTableGeomType = FGTGT_POLYGON;
415
13
    }
416
56
    else
417
56
    {
418
56
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported geometry type");
419
56
        return false;
420
56
    }
421
422
5.48k
    const std::string osNameOri(m_osName);
423
    /* Launder the Layer name */
424
5.48k
    m_osName = GetLaunderedLayerName(osNameOri);
425
5.48k
    if (osNameOri != m_osName)
426
2.85k
    {
427
2.85k
        CPLError(CE_Warning, CPLE_NotSupported,
428
2.85k
                 "Normalized/laundered layer name: '%s' to '%s'",
429
2.85k
                 osNameOri.c_str(), m_osName.c_str());
430
2.85k
    }
431
432
5.48k
    const char *pszFeatureDataset =
433
5.48k
        m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
434
5.48k
    std::string osFeatureDatasetDef;
435
5.48k
    std::unique_ptr<OGRSpatialReference> poFeatureDatasetSRS;
436
5.48k
    if (pszFeatureDataset)
437
0
    {
438
0
        {
439
0
            FileGDBTable oTable;
440
0
            if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
441
0
                return false;
442
443
0
            FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
444
0
            FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
445
0
            FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
446
447
0
            for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
448
0
                 ++iCurFeat)
449
0
            {
450
0
                iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
451
0
                if (iCurFeat < 0)
452
0
                    break;
453
0
                const auto psName = oTable.GetFieldValue(iName);
454
0
                if (psName && strcmp(psName->String, pszFeatureDataset) == 0)
455
0
                {
456
0
                    const auto psDefinition = oTable.GetFieldValue(iDefinition);
457
0
                    if (psDefinition)
458
0
                    {
459
0
                        osFeatureDatasetDef = psDefinition->String;
460
0
                    }
461
0
                    else
462
0
                    {
463
0
                        CPLError(CE_Failure, CPLE_AppDefined,
464
0
                                 "Feature dataset found, but no definition");
465
0
                        return false;
466
0
                    }
467
468
0
                    const auto psUUID = oTable.GetFieldValue(iUUID);
469
0
                    if (psUUID == nullptr)
470
0
                    {
471
0
                        CPLError(CE_Failure, CPLE_AppDefined,
472
0
                                 "Feature dataset found, but no UUID");
473
0
                        return false;
474
0
                    }
475
476
0
                    m_osFeatureDatasetGUID = psUUID->String;
477
0
                    break;
478
0
                }
479
0
            }
480
0
        }
481
0
        CPLXMLNode *psParentTree =
482
0
            CPLParseXMLString(osFeatureDatasetDef.c_str());
483
0
        if (psParentTree != nullptr)
484
0
        {
485
0
            CPLStripXMLNamespace(psParentTree, nullptr, TRUE);
486
0
            CPLXMLNode *psParentInfo =
487
0
                CPLSearchXMLNode(psParentTree, "=DEFeatureDataset");
488
0
            if (psParentInfo != nullptr)
489
0
            {
490
0
                poFeatureDatasetSRS.reset(m_poDS->BuildSRS(psParentInfo));
491
0
            }
492
0
            CPLDestroyXMLNode(psParentTree);
493
0
        }
494
0
    }
495
496
5.48k
    m_poFeatureDefn =
497
5.48k
        new OGROpenFileGDBFeatureDefn(this, m_osName.c_str(), true);
498
5.48k
    SetDescription(m_poFeatureDefn->GetName());
499
5.48k
    m_poFeatureDefn->SetGeomType(wkbNone);
500
5.48k
    m_poFeatureDefn->Reference();
501
502
5.48k
    m_osThisGUID = OFGDBGenerateUUID();
503
504
5.48k
    m_bValidLayerDefn = true;
505
5.48k
    m_bEditable = true;
506
5.48k
    m_bRegisteredTable = false;
507
5.48k
    m_bTimeInUTC = CPLTestBool(
508
5.48k
        m_aosCreationOptions.FetchNameValueDef("TIME_IN_UTC", "YES"));
509
510
5.48k
    int nTablxOffsetSize = 5;
511
5.48k
    bool bTextUTF16 = false;
512
5.48k
    const char *pszConfigurationKeyword =
513
5.48k
        m_aosCreationOptions.FetchNameValue("CONFIGURATION_KEYWORD");
514
5.48k
    if (pszConfigurationKeyword)
515
0
    {
516
0
        if (EQUAL(pszConfigurationKeyword, "MAX_FILE_SIZE_4GB"))
517
0
        {
518
0
            m_osConfigurationKeyword = "MAX_FILE_SIZE_4GB";
519
0
            nTablxOffsetSize = 4;
520
0
        }
521
0
        else if (EQUAL(pszConfigurationKeyword, "MAX_FILE_SIZE_256TB"))
522
0
        {
523
0
            m_osConfigurationKeyword = "MAX_FILE_SIZE_256TB";
524
0
            nTablxOffsetSize = 6;
525
0
        }
526
0
        else if (EQUAL(pszConfigurationKeyword, "TEXT_UTF16"))
527
0
        {
528
0
            m_osConfigurationKeyword = "TEXT_UTF16";
529
0
            bTextUTF16 = true;
530
0
        }
531
0
        else if (!EQUAL(pszConfigurationKeyword, "DEFAULTS"))
532
0
        {
533
0
            CPLError(CE_Failure, CPLE_NotSupported,
534
0
                     "Unsupported value for CONFIGURATION_KEYWORD: %s",
535
0
                     pszConfigurationKeyword);
536
0
            return false;
537
0
        }
538
0
    }
539
540
5.48k
    m_osPath = '\\';
541
5.48k
    if (pszFeatureDataset)
542
0
    {
543
0
        m_osPath += pszFeatureDataset;
544
0
        m_osPath += '\\';
545
0
    }
546
5.48k
    m_osPath += m_osName;
547
548
5.48k
    const char *pszDocumentation =
549
5.48k
        m_aosCreationOptions.FetchNameValue("DOCUMENTATION");
550
5.48k
    if (pszDocumentation)
551
0
        m_osDocumentation = pszDocumentation;
552
553
5.48k
    const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
554
5.48k
    const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
555
556
5.48k
    m_poLyrTable = new FileGDBTable();
557
5.48k
    if (!m_poLyrTable->Create(m_osGDBFilename.c_str(), nTablxOffsetSize,
558
5.48k
                              eTableGeomType, bGeomTypeHasZ, bGeomTypeHasM))
559
0
    {
560
0
        Close();
561
0
        return false;
562
0
    }
563
5.48k
    if (bTextUTF16)
564
0
        m_poLyrTable->SetTextUTF16();
565
566
    // To be able to test this unusual situation of having an attribute field
567
    // before the geometry field
568
5.48k
    if (CPLTestBool(CPLGetConfigOption(
569
5.48k
            "OPENFILEGDB_CREATE_FIELD_BEFORE_GEOMETRY", "NO")))
570
0
    {
571
0
        OGRFieldDefn oField("field_before_geom", OFTString);
572
0
        m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
573
0
            oField.GetNameRef(), std::string(), FGFT_STRING,
574
0
            /* bNullable = */ true,
575
0
            /* bRequired = */ true,
576
0
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD));
577
0
        m_poFeatureDefn->AddFieldDefn(&oField);
578
0
    }
579
580
5.48k
    if (m_eGeomType != wkbNone && poSrcGeomFieldDefn)
581
2.03k
    {
582
2.03k
        const auto poSRS = poSrcGeomFieldDefn->GetSpatialRef();
583
584
2.03k
        auto poGeomFieldDefn = std::make_unique<OGROpenFileGDBGeomFieldDefn>(
585
2.03k
            this,
586
2.03k
            m_aosCreationOptions.FetchNameValueDef("GEOMETRY_NAME", "SHAPE"),
587
2.03k
            m_eGeomType);
588
2.03k
        poGeomFieldDefn->SetNullable(
589
2.03k
            CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
590
2.03k
                "GEOMETRY_NULLABLE", "YES")));
591
592
2.03k
        if (poSRS)
593
365
        {
594
365
            const char *const apszOptions[] = {
595
365
                "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
596
365
            if (poFeatureDatasetSRS &&
597
0
                !poSRS->IsSame(poFeatureDatasetSRS.get(), apszOptions))
598
0
            {
599
0
                CPLError(CE_Failure, CPLE_AppDefined,
600
0
                         "Layer CRS does not match feature dataset CRS");
601
0
                return false;
602
0
            }
603
604
365
            auto poSRSClone = poSRS->Clone();
605
365
            poGeomFieldDefn->SetSpatialRef(poSRSClone);
606
365
            poSRSClone->Release();
607
365
        }
608
1.67k
        else if (poFeatureDatasetSRS)
609
0
        {
610
0
            auto poSRSClone = poFeatureDatasetSRS->Clone();
611
0
            poGeomFieldDefn->SetSpatialRef(poSRSClone);
612
0
            poSRSClone->Release();
613
0
        }
614
615
2.03k
        std::string osWKT;
616
2.03k
        if (poSRS)
617
365
        {
618
365
            const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
619
365
            char *pszWKT;
620
365
            poSRS->exportToWkt(&pszWKT, apszOptions);
621
365
            osWKT = pszWKT;
622
365
            CPLFree(pszWKT);
623
365
        }
624
1.67k
        else
625
1.67k
        {
626
1.67k
            osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}";
627
1.67k
        }
628
629
2.03k
        const auto oCoordPrec = GDBGridSettingsFromOGR(
630
2.03k
            poSrcGeomFieldDefn, m_aosCreationOptions.List());
631
2.03k
        poGeomFieldDefn->SetCoordinatePrecision(oCoordPrec);
632
2.03k
        const auto &oGridsOptions =
633
2.03k
            oCoordPrec.oFormatSpecificOptions.find("FileGeodatabase")->second;
634
2.03k
        const double dfXOrigin =
635
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("XOrigin"));
636
2.03k
        const double dfYOrigin =
637
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("YOrigin"));
638
2.03k
        const double dfXYScale =
639
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("XYScale"));
640
2.03k
        const double dfXYTolerance =
641
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("XYTolerance"));
642
2.03k
        const double dfZOrigin =
643
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("ZOrigin"));
644
2.03k
        const double dfZScale = CPLAtof(oGridsOptions.FetchNameValue("ZScale"));
645
2.03k
        const double dfZTolerance =
646
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("ZTolerance"));
647
2.03k
        const double dfMOrigin =
648
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("MOrigin"));
649
2.03k
        const double dfMScale = CPLAtof(oGridsOptions.FetchNameValue("MScale"));
650
2.03k
        const double dfMTolerance =
651
2.03k
            CPLAtof(oGridsOptions.FetchNameValue("MTolerance"));
652
653
2.03k
        if (!m_poDS->GetExistingSpatialRef(
654
2.03k
                osWKT, dfXOrigin, dfYOrigin, dfXYScale, dfZOrigin, dfZScale,
655
2.03k
                dfMOrigin, dfMScale, dfXYTolerance, dfZTolerance, dfMTolerance))
656
636
        {
657
636
            m_poDS->AddNewSpatialRef(osWKT, dfXOrigin, dfYOrigin, dfXYScale,
658
636
                                     dfZOrigin, dfZScale, dfMOrigin, dfMScale,
659
636
                                     dfXYTolerance, dfZTolerance, dfMTolerance);
660
636
        }
661
662
        // Will be patched later
663
2.03k
        constexpr double dfSpatialGridResolution = 0;
664
2.03k
        auto poTableGeomField = std::make_unique<FileGDBGeomField>(
665
2.03k
            poGeomFieldDefn->GetNameRef(),
666
2.03k
            std::string(),  // alias
667
2.03k
            CPL_TO_BOOL(poGeomFieldDefn->IsNullable()), osWKT, dfXOrigin,
668
2.03k
            dfYOrigin, dfXYScale, dfXYTolerance,
669
2.03k
            std::vector<double>{dfSpatialGridResolution});
670
2.03k
        poTableGeomField->SetZOriginScaleTolerance(dfZOrigin, dfZScale,
671
2.03k
                                                   dfZTolerance);
672
2.03k
        poTableGeomField->SetMOriginScaleTolerance(dfMOrigin, dfMScale,
673
2.03k
                                                   dfMTolerance);
674
675
2.03k
        if (!m_poLyrTable->CreateField(std::move(poTableGeomField)))
676
0
        {
677
0
            Close();
678
0
            return false;
679
0
        }
680
681
2.03k
        m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
682
2.03k
        m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
683
2.03k
            m_poLyrTable->GetGeomField()));
684
685
2.03k
        m_poFeatureDefn->AddGeomFieldDefn(std::move(poGeomFieldDefn));
686
2.03k
    }
687
688
5.48k
    const std::string osFIDName =
689
5.48k
        m_aosCreationOptions.FetchNameValueDef("FID", "OBJECTID");
690
5.48k
    if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
691
5.48k
            osFIDName, std::string(), FGFT_OBJECTID,
692
5.48k
            /* bNullable = */ false,
693
5.48k
            /* bRequired = */ true,
694
5.48k
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)))
695
0
    {
696
0
        Close();
697
0
        return false;
698
0
    }
699
700
5.48k
    const bool bCreateShapeLength =
701
5.48k
        (eTableGeomType == FGTGT_LINE || eTableGeomType == FGTGT_POLYGON) &&
702
1.27k
        CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
703
1.27k
            "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
704
    // Setting a non-default value doesn't work
705
5.48k
    const char *pszLengthFieldName = m_aosCreationOptions.FetchNameValueDef(
706
5.48k
        "LENGTH_FIELD_NAME", "Shape_Length");
707
708
5.48k
    const bool bCreateShapeArea =
709
5.48k
        eTableGeomType == FGTGT_POLYGON &&
710
13
        CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
711
13
            "CREATE_SHAPE_AREA_AND_LENGTH_FIELDS", "NO"));
712
    // Setting a non-default value doesn't work
713
5.48k
    const char *pszAreaFieldName =
714
5.48k
        m_aosCreationOptions.FetchNameValueDef("AREA_FIELD_NAME", "Shape_Area");
715
716
5.48k
    m_poFeatureDefn->Seal(/* bSealFields = */ true);
717
718
5.48k
    if (bCreateShapeArea)
719
0
    {
720
0
        OGRFieldDefn oField(pszAreaFieldName, OFTReal);
721
0
        oField.SetDefault("FILEGEODATABASE_SHAPE_AREA");
722
0
        if (CreateField(&oField, false) != OGRERR_NONE)
723
0
        {
724
0
            Close();
725
0
            return false;
726
0
        }
727
0
    }
728
5.48k
    if (bCreateShapeLength)
729
0
    {
730
0
        OGRFieldDefn oField(pszLengthFieldName, OFTReal);
731
0
        oField.SetDefault("FILEGEODATABASE_SHAPE_LENGTH");
732
0
        if (CreateField(&oField, false) != OGRERR_NONE)
733
0
        {
734
0
            Close();
735
0
            return false;
736
0
        }
737
0
    }
738
739
5.48k
    m_poLyrTable->CreateIndex("FDO_OBJECTID", osFIDName);
740
741
    // Just to imitate the FileGDB SDK which register the index on the
742
    // geometry column after the OBJECTID one, but the OBJECTID column is the
743
    // first one in .gdbtable
744
5.48k
    if (m_iGeomFieldIdx >= 0)
745
2.03k
        m_poLyrTable->CreateIndex(
746
2.03k
            "FDO_SHAPE", m_poFeatureDefn->GetGeomFieldDefn(0)->GetNameRef());
747
748
5.48k
    if (!m_poDS->RegisterLayerInSystemCatalog(m_osName))
749
0
    {
750
0
        Close();
751
0
        return false;
752
0
    }
753
754
5.48k
    if (pszFeatureDataset != nullptr && m_osFeatureDatasetGUID.empty() &&
755
0
        !CreateFeatureDataset(pszFeatureDataset))
756
0
    {
757
0
        Close();
758
0
        return false;
759
0
    }
760
761
5.48k
    RefreshXMLDefinitionInMemory();
762
763
5.48k
    return true;
764
5.48k
}
765
766
/************************************************************************/
767
/*                       CreateXMLFieldDefinition()                     */
768
/************************************************************************/
769
770
static CPLXMLNode *CreateXMLFieldDefinition(const OGRFieldDefn *poFieldDefn,
771
                                            const FileGDBField *poGDBFieldDefn,
772
                                            bool bArcGISPro32OrLater)
773
1.45M
{
774
1.45M
    auto GPFieldInfoEx =
775
1.45M
        CPLCreateXMLNode(nullptr, CXT_Element, "GPFieldInfoEx");
776
1.45M
    CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
777
1.45M
                               "typens:GPFieldInfoEx");
778
1.45M
    CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
779
1.45M
                                poGDBFieldDefn->GetName().c_str());
780
1.45M
    if (!poGDBFieldDefn->GetAlias().empty())
781
0
    {
782
0
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "AliasName",
783
0
                                    poGDBFieldDefn->GetAlias().c_str());
784
0
    }
785
1.45M
    const auto *psDefault = poGDBFieldDefn->GetDefault();
786
1.45M
    if (!OGR_RawField_IsNull(psDefault) && !OGR_RawField_IsUnset(psDefault))
787
0
    {
788
0
        if (poGDBFieldDefn->GetType() == FGFT_STRING)
789
0
        {
790
0
            auto psDefaultValue = CPLCreateXMLElementAndValue(
791
0
                GPFieldInfoEx, "DefaultValueString", psDefault->String);
792
0
            if (!bArcGISPro32OrLater)
793
0
            {
794
0
                CPLAddXMLAttributeAndValue(
795
0
                    psDefaultValue, "xmlns:typens",
796
0
                    "http://www.esri.com/schemas/ArcGIS/10.3");
797
0
            }
798
0
        }
799
0
        else if (poGDBFieldDefn->GetType() == FGFT_INT32)
800
0
        {
801
0
            auto psDefaultValue = CPLCreateXMLElementAndValue(
802
0
                GPFieldInfoEx, "DefaultValue",
803
0
                CPLSPrintf("%d", psDefault->Integer));
804
0
            CPLAddXMLAttributeAndValue(psDefaultValue, "xsi:type", "xs:int");
805
0
        }
806
0
        else if (poGDBFieldDefn->GetType() == FGFT_FLOAT64)
807
0
        {
808
0
            auto psDefaultValue = CPLCreateXMLElementAndValue(
809
0
                GPFieldInfoEx, "DefaultValueNumeric",
810
0
                CPLSPrintf("%.17g", psDefault->Real));
811
0
            if (!bArcGISPro32OrLater)
812
0
            {
813
0
                CPLAddXMLAttributeAndValue(
814
0
                    psDefaultValue, "xmlns:typens",
815
0
                    "http://www.esri.com/schemas/ArcGIS/10.3");
816
0
            }
817
0
        }
818
0
        else if (poGDBFieldDefn->GetType() == FGFT_INT64)
819
0
        {
820
0
            CPLCreateXMLElementAndValue(
821
0
                GPFieldInfoEx, "DefaultValueInteger",
822
0
                CPLSPrintf(CPL_FRMT_GIB, psDefault->Integer64));
823
0
        }
824
0
        else if (poGDBFieldDefn->GetType() == FGFT_DATETIME ||
825
0
                 poGDBFieldDefn->GetType() == FGFT_DATE)
826
0
        {
827
0
            CPLCreateXMLElementAndValue(
828
0
                GPFieldInfoEx, "DefaultValueNumeric",
829
0
                CPLSPrintf("%.17g", FileGDBOGRDateToDoubleDate(
830
0
                                        psDefault, /* bConvertToUTC = */ true,
831
0
                                        poGDBFieldDefn->IsHighPrecision())));
832
0
        }
833
0
        else if (poGDBFieldDefn->GetType() == FGFT_TIME)
834
0
        {
835
0
            CPLCreateXMLElementAndValue(
836
0
                GPFieldInfoEx, "DefaultValueNumeric",
837
0
                CPLSPrintf("%.0f", FileGDBOGRTimeToDoubleTime(psDefault)));
838
0
        }
839
0
        else if (poGDBFieldDefn->GetType() == FGFT_DATETIME_WITH_OFFSET)
840
0
        {
841
            /*
842
             <DefaultValueTimestampOffset xsi:type="typens:TimestampOffset">
843
                <Timestamp>2023-02-01T04:05:06</Timestamp>
844
                <HoursOffset>6</HoursOffset>
845
                <MinutesOffset>0</MinutesOffset>
846
              </DefaultValueTimestampOffset>
847
            */
848
0
            auto psDefaultValue = CPLCreateXMLNode(
849
0
                GPFieldInfoEx, CXT_Element, "DefaultValueTimestampOffset");
850
0
            CPLAddXMLAttributeAndValue(psDefaultValue, "xsi:type",
851
0
                                       "typens:TimestampOffset");
852
0
            CPLCreateXMLElementAndValue(
853
0
                psDefaultValue, "Timestamp",
854
0
                CPLSPrintf("%04d-%02d-%02dT%02d:%02d:%02d",
855
0
                           psDefault->Date.Year, psDefault->Date.Month,
856
0
                           psDefault->Date.Day, psDefault->Date.Hour,
857
0
                           psDefault->Date.Minute,
858
0
                           static_cast<int>(psDefault->Date.Second)));
859
0
            if (psDefault->Date.TZFlag > 1)
860
0
            {
861
0
                const int nOffsetInMin = (psDefault->Date.TZFlag - 100) * 15;
862
0
                CPLCreateXMLElementAndValue(
863
0
                    psDefaultValue, "HoursOffset",
864
0
                    CPLSPrintf("%d", nOffsetInMin / 60));
865
0
                CPLCreateXMLElementAndValue(
866
0
                    psDefaultValue, "MinutesOffset",
867
0
                    CPLSPrintf("%d", std::abs(nOffsetInMin) % 60));
868
0
            }
869
0
        }
870
0
    }
871
1.45M
    const char *pszFieldType = "";
872
1.45M
    CPL_IGNORE_RET_VAL(pszFieldType);  // Make CSA happy
873
1.45M
    int nLength = 0;
874
1.45M
    switch (poGDBFieldDefn->GetType())
875
1.45M
    {
876
0
        case FGFT_UNDEFINED:
877
0
            CPLAssert(false);
878
0
            break;
879
0
        case FGFT_INT16:
880
0
            nLength = 2;
881
0
            pszFieldType = "esriFieldTypeSmallInteger";
882
0
            break;
883
2.67k
        case FGFT_INT32:
884
2.67k
            nLength = 4;
885
2.67k
            pszFieldType = "esriFieldTypeInteger";
886
2.67k
            break;
887
0
        case FGFT_FLOAT32:
888
0
            nLength = 4;
889
0
            pszFieldType = "esriFieldTypeSingle";
890
0
            break;
891
2.22k
        case FGFT_FLOAT64:
892
2.22k
            nLength = 8;
893
2.22k
            pszFieldType = "esriFieldTypeDouble";
894
2.22k
            break;
895
1.44M
        case FGFT_STRING:
896
1.44M
            nLength = poGDBFieldDefn->GetMaxWidth();
897
1.44M
            pszFieldType = "esriFieldTypeString";
898
1.44M
            break;
899
0
        case FGFT_DATETIME:
900
0
            nLength = 8;
901
0
            pszFieldType = "esriFieldTypeDate";
902
0
            break;
903
0
        case FGFT_OBJECTID:
904
0
            pszFieldType = "esriFieldTypeOID";
905
0
            break;  // shouldn't happen
906
0
        case FGFT_GEOMETRY:
907
0
            pszFieldType = "esriFieldTypeGeometry";
908
0
            break;  // shouldn't happen
909
0
        case FGFT_BINARY:
910
0
            pszFieldType = "esriFieldTypeBlob";
911
0
            break;
912
0
        case FGFT_RASTER:
913
0
            pszFieldType = "esriFieldTypeRaster";
914
0
            break;
915
0
        case FGFT_GUID:
916
0
            pszFieldType = "esriFieldTypeGUID";
917
0
            break;
918
0
        case FGFT_GLOBALID:
919
0
            pszFieldType = "esriFieldTypeGlobalID";
920
0
            break;
921
0
        case FGFT_XML:
922
0
            pszFieldType = "esriFieldTypeXML";
923
0
            break;
924
0
        case FGFT_INT64:
925
0
            nLength = 8;
926
0
            pszFieldType = "esriFieldTypeBigInteger";
927
0
            break;
928
0
        case FGFT_DATE:
929
0
            nLength = 8;
930
0
            pszFieldType = "esriFieldTypeDateOnly";
931
0
            break;
932
0
        case FGFT_TIME:
933
0
            nLength = 8;
934
0
            pszFieldType = "esriFieldTypeTimeOnly";
935
0
            break;
936
0
        case FGFT_DATETIME_WITH_OFFSET:
937
0
            nLength = 8 + 2;
938
0
            pszFieldType = "esriFieldTypeTimestampOffset";
939
0
            break;
940
1.45M
    }
941
1.45M
    auto psFieldType =
942
1.45M
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType", pszFieldType);
943
1.45M
    if (!bArcGISPro32OrLater)
944
1.45M
    {
945
1.45M
        CPLAddXMLAttributeAndValue(psFieldType, "xmlns:typens",
946
1.45M
                                   "http://www.esri.com/schemas/ArcGIS/10.3");
947
1.45M
    }
948
1.45M
    if (poGDBFieldDefn->IsNullable())
949
1.45M
    {
950
1.45M
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "true");
951
1.45M
    }
952
1.45M
    if (poGDBFieldDefn->IsRequired())
953
0
    {
954
0
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
955
0
    }
956
1.45M
    if (!poGDBFieldDefn->IsEditable())
957
0
    {
958
0
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "Editable", "false");
959
0
    }
960
1.45M
    if (poGDBFieldDefn->IsHighPrecision())
961
0
    {
962
0
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "HighPrecision", "true");
963
0
    }
964
1.45M
    CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length",
965
1.45M
                                CPLSPrintf("%d", nLength));
966
1.45M
    CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
967
1.45M
    CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
968
1.45M
    if (!poFieldDefn->GetDomainName().empty())
969
0
    {
970
0
        CPLCreateXMLElementAndValue(GPFieldInfoEx, "DomainName",
971
0
                                    poFieldDefn->GetDomainName().c_str());
972
0
    }
973
1.45M
    return GPFieldInfoEx;
974
1.45M
}
975
976
/************************************************************************/
977
/*                            GetDefault()                              */
978
/************************************************************************/
979
980
static bool GetDefault(const OGRFieldDefn *poField, FileGDBFieldType eType,
981
                       OGRField &sDefault, std::string &osDefaultVal,
982
                       bool bApproxOK)
983
46.0k
{
984
46.0k
    sDefault = FileGDBField::UNSET_FIELD;
985
46.0k
    const char *pszDefault = poField->GetDefault();
986
46.0k
    if (pszDefault != nullptr && !poField->IsDefaultDriverSpecific())
987
0
    {
988
0
        if (eType == FGFT_STRING)
989
0
        {
990
0
            osDefaultVal = pszDefault;
991
0
            if (osDefaultVal[0] == '\'' && osDefaultVal.back() == '\'')
992
0
            {
993
0
                osDefaultVal = osDefaultVal.substr(1);
994
0
                osDefaultVal.pop_back();
995
0
                char *pszTmp =
996
0
                    CPLUnescapeString(osDefaultVal.c_str(), nullptr, CPLES_SQL);
997
0
                osDefaultVal = pszTmp;
998
0
                CPLFree(pszTmp);
999
0
            }
1000
0
            sDefault.String = &osDefaultVal[0];
1001
0
        }
1002
0
        else if (eType == FGFT_INT16 || eType == FGFT_INT32)
1003
0
            sDefault.Integer = atoi(pszDefault);
1004
0
        else if (eType == FGFT_FLOAT32 || eType == FGFT_FLOAT64)
1005
0
            sDefault.Real = CPLAtof(pszDefault);
1006
0
        else if (eType == FGFT_DATETIME || eType == FGFT_DATE ||
1007
0
                 eType == FGFT_TIME || eType == FGFT_DATETIME_WITH_OFFSET)
1008
0
        {
1009
0
            osDefaultVal = pszDefault;
1010
0
            if (osDefaultVal == "CURRENT_TIMESTAMP" ||
1011
0
                osDefaultVal == "CURRENT_TIME" ||
1012
0
                osDefaultVal == "CURRENT_DATE")
1013
0
            {
1014
0
                CPLError(bApproxOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
1015
0
                         "%s is not supported as a default value in File "
1016
0
                         "Geodatabase",
1017
0
                         osDefaultVal.c_str());
1018
0
                return bApproxOK;
1019
0
            }
1020
0
            if (osDefaultVal[0] == '\'' && osDefaultVal.back() == '\'')
1021
0
            {
1022
0
                osDefaultVal = osDefaultVal.substr(1);
1023
0
                osDefaultVal.pop_back();
1024
0
                char *pszTmp =
1025
0
                    CPLUnescapeString(osDefaultVal.c_str(), nullptr, CPLES_SQL);
1026
0
                osDefaultVal = pszTmp;
1027
0
                CPLFree(pszTmp);
1028
0
            }
1029
0
            if (!OGRParseDate(osDefaultVal.c_str(), &sDefault, 0))
1030
0
            {
1031
0
                CPLError(bApproxOK ? CE_Warning : CE_Failure, CPLE_AppDefined,
1032
0
                         "Cannot parse %s as a date time",
1033
0
                         osDefaultVal.c_str());
1034
0
                return bApproxOK;
1035
0
            }
1036
0
        }
1037
0
        else if (eType == FGFT_INT64)
1038
0
            sDefault.Integer64 = CPLAtoGIntBig(pszDefault);
1039
0
    }
1040
46.0k
    return true;
1041
46.0k
}
1042
1043
/************************************************************************/
1044
/*                         GetGDBFieldType()                            */
1045
/************************************************************************/
1046
1047
static FileGDBFieldType GetGDBFieldType(const OGRFieldDefn *poField,
1048
                                        bool bArcGISPro32OrLater)
1049
46.0k
{
1050
46.0k
    FileGDBFieldType eType = FGFT_UNDEFINED;
1051
46.0k
    switch (poField->GetType())
1052
46.0k
    {
1053
108
        case OFTInteger:
1054
108
            eType =
1055
108
                poField->GetSubType() == OFSTInt16 ? FGFT_INT16 : FGFT_INT32;
1056
108
            break;
1057
106
        case OFTReal:
1058
106
            eType = poField->GetSubType() == OFSTFloat32 ? FGFT_FLOAT32
1059
106
                                                         : FGFT_FLOAT64;
1060
106
            break;
1061
7
        case OFTInteger64:
1062
7
            eType = bArcGISPro32OrLater ? FGFT_INT64 : FGFT_FLOAT64;
1063
7
            break;
1064
45.8k
        case OFTString:
1065
45.8k
        case OFTWideString:
1066
45.8k
        case OFTStringList:
1067
45.8k
        case OFTWideStringList:
1068
45.8k
        case OFTIntegerList:
1069
45.8k
        case OFTInteger64List:
1070
45.8k
        case OFTRealList:
1071
45.8k
            eType = FGFT_STRING;
1072
45.8k
            break;
1073
0
        case OFTBinary:
1074
0
            eType = FGFT_BINARY;
1075
0
            break;
1076
0
        case OFTDate:
1077
0
            eType = bArcGISPro32OrLater ? FGFT_DATE : FGFT_DATETIME;
1078
0
            break;
1079
0
        case OFTTime:
1080
0
            eType = bArcGISPro32OrLater ? FGFT_TIME : FGFT_DATETIME;
1081
0
            break;
1082
0
        case OFTDateTime:
1083
0
            eType =
1084
0
                bArcGISPro32OrLater ? FGFT_DATETIME_WITH_OFFSET : FGFT_DATETIME;
1085
0
            break;
1086
46.0k
    }
1087
46.0k
    return eType;
1088
46.0k
}
1089
1090
/************************************************************************/
1091
/*                        GetGPFieldInfoExsNode()                       */
1092
/************************************************************************/
1093
1094
static CPLXMLNode *GetGPFieldInfoExsNode(CPLXMLNode *psParent)
1095
0
{
1096
0
    CPLXMLNode *psInfo = CPLSearchXMLNode(psParent, "=DEFeatureClassInfo");
1097
0
    if (psInfo == nullptr)
1098
0
        psInfo = CPLSearchXMLNode(psParent, "=typens:DEFeatureClassInfo");
1099
0
    if (psInfo == nullptr)
1100
0
        psInfo = CPLSearchXMLNode(psParent, "=DETableInfo");
1101
0
    if (psInfo == nullptr)
1102
0
        psInfo = CPLSearchXMLNode(psParent, "=typens:DETableInfo");
1103
0
    if (psInfo != nullptr)
1104
0
    {
1105
0
        return CPLGetXMLNode(psInfo, "GPFieldInfoExs");
1106
0
    }
1107
0
    return nullptr;
1108
0
}
1109
1110
/************************************************************************/
1111
/*                      GetLaunderedFieldName()                         */
1112
/************************************************************************/
1113
1114
std::string
1115
OGROpenFileGDBLayer::GetLaunderedFieldName(const std::string &osNameOri) const
1116
46.0k
{
1117
46.0k
    std::wstring osName = LaunderName(StringToWString(osNameOri));
1118
46.0k
    osName = EscapeReservedKeywords(osName);
1119
1120
    /* Truncate to 64 characters */
1121
46.0k
    constexpr size_t FIELD_NAME_MAX_SIZE = 64;
1122
46.0k
    if (osName.size() > FIELD_NAME_MAX_SIZE)
1123
832
        osName.resize(FIELD_NAME_MAX_SIZE);
1124
1125
    /* Ensures uniqueness of field name */
1126
46.0k
    int numRenames = 1;
1127
62.5k
    while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
1128
62.5k
            0) &&
1129
17.3k
           (numRenames < 10))
1130
16.5k
    {
1131
16.5k
        osName = StringToWString(CPLSPrintf(
1132
16.5k
            "%s_%d",
1133
16.5k
            WStringToString(osName.substr(0, FIELD_NAME_MAX_SIZE - 2)).c_str(),
1134
16.5k
            numRenames));
1135
16.5k
        numRenames++;
1136
16.5k
    }
1137
55.0k
    while ((m_poFeatureDefn->GetFieldIndex(WStringToString(osName).c_str()) >=
1138
55.0k
            0) &&
1139
8.98k
           (numRenames < 100))
1140
8.98k
    {
1141
8.98k
        osName = StringToWString(CPLSPrintf(
1142
8.98k
            "%s_%d",
1143
8.98k
            WStringToString(osName.substr(0, FIELD_NAME_MAX_SIZE - 3)).c_str(),
1144
8.98k
            numRenames));
1145
8.98k
        numRenames++;
1146
8.98k
    }
1147
1148
46.0k
    return WStringToString(osName);
1149
46.0k
}
1150
1151
/************************************************************************/
1152
/*                            CreateField()                             */
1153
/************************************************************************/
1154
1155
OGRErr OGROpenFileGDBLayer::CreateField(const OGRFieldDefn *poFieldIn,
1156
                                        int bApproxOK)
1157
46.0k
{
1158
46.0k
    if (!m_bEditable)
1159
0
        return OGRERR_FAILURE;
1160
1161
46.0k
    if (!BuildLayerDefinition())
1162
0
        return OGRERR_FAILURE;
1163
1164
46.0k
    if (m_poDS->IsInTransaction() &&
1165
0
        ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
1166
0
         !m_poDS->BackupSystemTablesForTransaction()))
1167
0
    {
1168
0
        return OGRERR_FAILURE;
1169
0
    }
1170
1171
    /* Clean field names */
1172
46.0k
    OGRFieldDefn oField(poFieldIn);
1173
46.0k
    OGRFieldDefn *poField = &oField;
1174
1175
46.0k
    if (poField->GetType() == OFTInteger64 && !m_bArcGISPro32OrLater)
1176
7
    {
1177
7
        CPLError(CE_Warning, CPLE_AppDefined,
1178
7
                 "Field %s of type Integer64 will be written as a Float64. "
1179
7
                 "To get Integer64, use layer creation option "
1180
7
                 "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
1181
7
                 poField->GetNameRef());
1182
7
    }
1183
46.0k
    else if (poField->GetType() == OFTDate && !m_bArcGISPro32OrLater)
1184
0
    {
1185
0
        CPLError(CE_Warning, CPLE_AppDefined,
1186
0
                 "Field %s of type Date will be written as a DateTime. "
1187
0
                 "To get DateTime, use layer creation option "
1188
0
                 "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
1189
0
                 poField->GetNameRef());
1190
0
    }
1191
46.0k
    else if (poField->GetType() == OFTTime && !m_bArcGISPro32OrLater)
1192
0
    {
1193
0
        CPLError(CE_Warning, CPLE_AppDefined,
1194
0
                 "Field %s of type Time will be written as a DateTime. "
1195
0
                 "To get DateTime, use layer creation option "
1196
0
                 "TARGET_ARCGIS_VERSION=ARCGIS_PRO_3_2_OR_LATER",
1197
0
                 poField->GetNameRef());
1198
0
    }
1199
1200
46.0k
    const std::string osFidColumn = GetFIDColumn();
1201
46.0k
    if (!osFidColumn.empty() &&
1202
46.0k
        EQUAL(poField->GetNameRef(), osFidColumn.c_str()))
1203
0
    {
1204
0
        if (poField->GetType() != OFTInteger &&
1205
0
            poField->GetType() != OFTInteger64 &&
1206
            // typically a GeoPackage exported with QGIS as a shapefile and
1207
            // re-imported See https://github.com/qgis/QGIS/pull/43118
1208
0
            !(poField->GetType() == OFTReal && poField->GetWidth() <= 20 &&
1209
0
              poField->GetPrecision() == 0))
1210
0
        {
1211
0
            CPLError(CE_Failure, CPLE_AppDefined,
1212
0
                     "Wrong field type for %s : %d", poField->GetNameRef(),
1213
0
                     poField->GetType());
1214
0
            return OGRERR_FAILURE;
1215
0
        }
1216
1217
0
        m_iFIDAsRegularColumnIndex = m_poFeatureDefn->GetFieldCount();
1218
0
        whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField);
1219
0
        return OGRERR_NONE;
1220
0
    }
1221
1222
46.0k
    const std::string osFieldNameOri(poField->GetNameRef());
1223
46.0k
    const std::string osFieldName = GetLaunderedFieldName(osFieldNameOri);
1224
46.0k
    if (osFieldName != osFieldNameOri)
1225
15.4k
    {
1226
15.4k
        if (!bApproxOK ||
1227
15.4k
            (m_poFeatureDefn->GetFieldIndex(osFieldName.c_str()) >= 0))
1228
0
        {
1229
0
            CPLError(CE_Failure, CPLE_NotSupported,
1230
0
                     "Failed to add field named '%s'", osFieldNameOri.c_str());
1231
0
            return OGRERR_FAILURE;
1232
0
        }
1233
15.4k
        CPLError(CE_Warning, CPLE_NotSupported,
1234
15.4k
                 "Normalized/laundered field name: '%s' to '%s'",
1235
15.4k
                 osFieldNameOri.c_str(), osFieldName.c_str());
1236
1237
15.4k
        poField->SetName(osFieldName.c_str());
1238
15.4k
    }
1239
1240
46.0k
    const char *pszColumnTypes =
1241
46.0k
        m_aosCreationOptions.FetchNameValue("COLUMN_TYPES");
1242
46.0k
    std::string gdbFieldType;
1243
46.0k
    if (pszColumnTypes != nullptr)
1244
0
    {
1245
0
        char **papszTokens = CSLTokenizeString2(pszColumnTypes, ",", 0);
1246
0
        const char *pszFieldType =
1247
0
            CSLFetchNameValue(papszTokens, poField->GetNameRef());
1248
0
        if (pszFieldType != nullptr)
1249
0
        {
1250
0
            OGRFieldType fldtypeCheck;
1251
0
            OGRFieldSubType eSubType;
1252
0
            if (GDBToOGRFieldType(pszFieldType, &fldtypeCheck, &eSubType))
1253
0
            {
1254
0
                if (fldtypeCheck != poField->GetType())
1255
0
                {
1256
0
                    CPLError(CE_Warning, CPLE_AppDefined,
1257
0
                             "Ignoring COLUMN_TYPES=%s=%s : %s not consistent "
1258
0
                             "with OGR data type",
1259
0
                             poField->GetNameRef(), pszFieldType, pszFieldType);
1260
0
                }
1261
0
                else
1262
0
                    gdbFieldType = pszFieldType;
1263
0
            }
1264
0
            else
1265
0
                CPLError(CE_Warning, CPLE_AppDefined,
1266
0
                         "Ignoring COLUMN_TYPES=%s=%s : %s not recognized",
1267
0
                         poField->GetNameRef(), pszFieldType, pszFieldType);
1268
0
        }
1269
0
        CSLDestroy(papszTokens);
1270
0
    }
1271
1272
46.0k
    FileGDBFieldType eType = FGFT_UNDEFINED;
1273
46.0k
    if (!gdbFieldType.empty())
1274
0
    {
1275
0
        if (gdbFieldType == "esriFieldTypeSmallInteger")
1276
0
            eType = FGFT_INT16;
1277
0
        else if (gdbFieldType == "esriFieldTypeInteger")
1278
0
            eType = FGFT_INT32;
1279
0
        else if (gdbFieldType == "esriFieldTypeBigInteger")
1280
0
            eType = FGFT_INT64;
1281
0
        else if (gdbFieldType == "esriFieldTypeSingle")
1282
0
            eType = FGFT_FLOAT32;
1283
0
        else if (gdbFieldType == "esriFieldTypeDouble")
1284
0
            eType = FGFT_FLOAT64;
1285
0
        else if (gdbFieldType == "esriFieldTypeString")
1286
0
            eType = FGFT_STRING;
1287
0
        else if (gdbFieldType == "esriFieldTypeDate")
1288
0
            eType = FGFT_DATETIME;
1289
0
        else if (gdbFieldType == "esriFieldTypeBlob")
1290
0
            eType = FGFT_BINARY;
1291
0
        else if (gdbFieldType == "esriFieldTypeGUID")
1292
0
            eType = FGFT_GUID;
1293
0
        else if (gdbFieldType == "esriFieldTypeGlobalID")
1294
0
            eType = FGFT_GLOBALID;
1295
0
        else if (gdbFieldType == "esriFieldTypeXML")
1296
0
            eType = FGFT_XML;
1297
0
        else if (gdbFieldType == "esriFieldTypeDateOnly")
1298
0
            eType = FGFT_DATE;
1299
0
        else if (gdbFieldType == "esriFieldTypeTimeOnly")
1300
0
            eType = FGFT_TIME;
1301
0
        else if (gdbFieldType == "esriFieldTypeTimestampOffset")
1302
0
            eType = FGFT_DATETIME_WITH_OFFSET;
1303
0
        else
1304
0
        {
1305
0
            CPLAssert(false);
1306
0
        }
1307
0
    }
1308
46.0k
    else
1309
46.0k
    {
1310
46.0k
        eType = GetGDBFieldType(poField, m_bArcGISPro32OrLater);
1311
46.0k
    }
1312
1313
46.0k
    int nWidth = 0;
1314
46.0k
    if (eType == FGFT_GLOBALID || eType == FGFT_GUID)
1315
0
    {
1316
0
        nWidth = 38;
1317
0
    }
1318
46.0k
    else if (poField->GetType() == OFTString)
1319
45.8k
    {
1320
45.8k
        nWidth = poField->GetWidth();
1321
45.8k
        if (nWidth == 0)
1322
45.8k
        {
1323
            // Hard-coded non-zero default string width if the user doesn't
1324
            // override it with the below configuration option.
1325
            // See comment at declaration of DEFAULT_STRING_WIDTH for more
1326
            // details
1327
45.8k
            nWidth = DEFAULT_STRING_WIDTH;
1328
45.8k
            if (const char *pszVal = CPLGetConfigOption(
1329
45.8k
                    "OPENFILEGDB_DEFAULT_STRING_WIDTH", nullptr))
1330
0
            {
1331
0
                const int nVal = atoi(pszVal);
1332
0
                if (nVal >= 0)
1333
0
                    nWidth = nVal;
1334
0
            }
1335
            // Advertise a non-zero user-modified width back to the created
1336
            // OGRFieldDefn, only if it is less than the hard-coded default
1337
            // value (this will avoid potential issues with excessively large
1338
            // field width afterwards)
1339
45.8k
            if (nWidth < DEFAULT_STRING_WIDTH)
1340
0
                poField->SetWidth(nWidth);
1341
45.8k
        }
1342
45.8k
    }
1343
1344
46.0k
    OGRField sDefault = FileGDBField::UNSET_FIELD;
1345
46.0k
    std::string osDefaultVal;
1346
46.0k
    if (!GetDefault(poField, eType, sDefault, osDefaultVal,
1347
46.0k
                    CPL_TO_BOOL(bApproxOK)))
1348
0
        return OGRERR_FAILURE;
1349
1350
46.0k
    if (!poField->GetDomainName().empty() &&
1351
0
        (!m_osThisGUID.empty() ||
1352
0
         m_poDS->FindUUIDFromName(GetName(), m_osThisGUID)))
1353
0
    {
1354
0
        if (!m_poDS->LinkDomainToTable(poField->GetDomainName(), m_osThisGUID))
1355
0
        {
1356
0
            poField->SetDomainName(std::string());
1357
0
        }
1358
0
    }
1359
1360
46.0k
    const bool bNullable =
1361
46.0k
        CPL_TO_BOOL(poField->IsNullable()) && eType != FGFT_GLOBALID;
1362
46.0k
    bool bRequired = (eType == FGFT_GLOBALID);
1363
46.0k
    bool bEditable = (eType != FGFT_GLOBALID);
1364
1365
46.0k
    if (poField->GetType() == OFTReal)
1366
106
    {
1367
106
        const char *pszDefault = poField->GetDefault();
1368
106
        if (pszDefault && EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_AREA"))
1369
0
        {
1370
0
            m_iAreaField = m_poFeatureDefn->GetFieldCount();
1371
0
            bRequired = true;
1372
0
            bEditable = false;
1373
0
        }
1374
106
        else if (pszDefault &&
1375
0
                 EQUAL(pszDefault, "FILEGEODATABASE_SHAPE_LENGTH"))
1376
0
        {
1377
0
            m_iLengthField = m_poFeatureDefn->GetFieldCount();
1378
0
            bRequired = true;
1379
0
            bEditable = false;
1380
0
        }
1381
106
    }
1382
1383
46.0k
    const char *pszAlias = poField->GetAlternativeNameRef();
1384
46.0k
    if (!m_poLyrTable->CreateField(std::make_unique<FileGDBField>(
1385
46.0k
            poField->GetNameRef(),
1386
46.0k
            pszAlias ? std::string(pszAlias) : std::string(), eType, bNullable,
1387
46.0k
            bRequired, bEditable, nWidth, sDefault)))
1388
5.06k
    {
1389
5.06k
        return OGRERR_FAILURE;
1390
5.06k
    }
1391
1392
40.9k
    whileUnsealing(m_poFeatureDefn)->AddFieldDefn(poField);
1393
1394
40.9k
    if (m_bRegisteredTable)
1395
0
    {
1396
        // If the table is already registered (that is updating an existing
1397
        // layer), patch the XML definition to add the new field
1398
0
        CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
1399
0
        if (oTree)
1400
0
        {
1401
0
            CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
1402
0
            if (psGPFieldInfoExs)
1403
0
            {
1404
0
                CPLAddXMLChild(psGPFieldInfoExs,
1405
0
                               CreateXMLFieldDefinition(
1406
0
                                   poField,
1407
0
                                   m_poLyrTable->GetField(
1408
0
                                       m_poLyrTable->GetFieldCount() - 1),
1409
0
                                   m_bArcGISPro32OrLater));
1410
1411
0
                char *pszDefinition = CPLSerializeXMLTree(oTree.get());
1412
0
                m_osDefinition = pszDefinition;
1413
0
                CPLFree(pszDefinition);
1414
1415
0
                m_poDS->UpdateXMLDefinition(m_osName.c_str(),
1416
0
                                            m_osDefinition.c_str());
1417
0
            }
1418
0
        }
1419
0
    }
1420
40.9k
    else
1421
40.9k
    {
1422
40.9k
        RefreshXMLDefinitionInMemory();
1423
40.9k
    }
1424
1425
40.9k
    return OGRERR_NONE;
1426
46.0k
}
1427
1428
/************************************************************************/
1429
/*                           AlterFieldDefn()                           */
1430
/************************************************************************/
1431
1432
OGRErr OGROpenFileGDBLayer::AlterFieldDefn(int iFieldToAlter,
1433
                                           OGRFieldDefn *poNewFieldDefn,
1434
                                           int nFlagsIn)
1435
0
{
1436
0
    if (!m_bEditable)
1437
0
        return OGRERR_FAILURE;
1438
1439
0
    if (!BuildLayerDefinition())
1440
0
        return OGRERR_FAILURE;
1441
1442
0
    if (m_poDS->IsInTransaction() &&
1443
0
        ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
1444
0
         !m_poDS->BackupSystemTablesForTransaction()))
1445
0
    {
1446
0
        return OGRERR_FAILURE;
1447
0
    }
1448
1449
0
    if (iFieldToAlter < 0 || iFieldToAlter >= m_poFeatureDefn->GetFieldCount())
1450
0
    {
1451
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
1452
0
        return OGRERR_FAILURE;
1453
0
    }
1454
1455
0
    if (iFieldToAlter == m_iFIDAsRegularColumnIndex)
1456
0
    {
1457
0
        CPLError(CE_Failure, CPLE_NotSupported, "Cannot alter field %s",
1458
0
                 GetFIDColumn());
1459
0
        return OGRERR_FAILURE;
1460
0
    }
1461
1462
0
    const int nGDBIdx = m_poLyrTable->GetFieldIdx(
1463
0
        m_poFeatureDefn->GetFieldDefn(iFieldToAlter)->GetNameRef());
1464
0
    if (nGDBIdx < 0)
1465
0
        return OGRERR_FAILURE;
1466
1467
0
    OGRFieldDefn *poFieldDefn = m_poFeatureDefn->GetFieldDefn(iFieldToAlter);
1468
0
    auto oTemporaryUnsealer(poFieldDefn->GetTemporaryUnsealer());
1469
0
    OGRFieldDefn oField(poFieldDefn);
1470
0
    const std::string osOldFieldName(poFieldDefn->GetNameRef());
1471
0
    const std::string osOldDomainName(
1472
0
        std::string(poFieldDefn->GetDomainName()));
1473
0
    const bool bRenamedField = (nFlagsIn & ALTER_NAME_FLAG) != 0 &&
1474
0
                               poNewFieldDefn->GetNameRef() != osOldFieldName;
1475
1476
0
    if (nFlagsIn & ALTER_TYPE_FLAG)
1477
0
    {
1478
0
        if (poFieldDefn->GetType() != poNewFieldDefn->GetType() ||
1479
0
            poFieldDefn->GetSubType() != poNewFieldDefn->GetSubType())
1480
0
        {
1481
0
            CPLError(CE_Failure, CPLE_NotSupported,
1482
0
                     "Altering the field type is not supported");
1483
0
            return OGRERR_FAILURE;
1484
0
        }
1485
0
    }
1486
0
    if (nFlagsIn & ALTER_NAME_FLAG)
1487
0
    {
1488
0
        if (bRenamedField)
1489
0
        {
1490
0
            const std::string osFieldNameOri(poNewFieldDefn->GetNameRef());
1491
0
            const std::string osFieldNameLaundered =
1492
0
                GetLaunderedFieldName(osFieldNameOri);
1493
0
            if (osFieldNameLaundered != osFieldNameOri)
1494
0
            {
1495
0
                CPLError(CE_Failure, CPLE_AppDefined,
1496
0
                         "Invalid field name: %s. "
1497
0
                         "A potential valid name would be: %s",
1498
0
                         osFieldNameOri.c_str(), osFieldNameLaundered.c_str());
1499
0
                return OGRERR_FAILURE;
1500
0
            }
1501
1502
0
            oField.SetName(poNewFieldDefn->GetNameRef());
1503
0
        }
1504
0
    }
1505
0
    if (nFlagsIn & ALTER_WIDTH_PRECISION_FLAG)
1506
0
    {
1507
0
        if (oField.GetType() == OFTString)
1508
0
            oField.SetWidth(poNewFieldDefn->GetWidth());
1509
0
    }
1510
0
    if (nFlagsIn & ALTER_DEFAULT_FLAG)
1511
0
    {
1512
0
        oField.SetDefault(poNewFieldDefn->GetDefault());
1513
0
    }
1514
0
    if (nFlagsIn & ALTER_NULLABLE_FLAG)
1515
0
    {
1516
        // could be potentially done, but involves .gdbtable rewriting
1517
0
        if (poFieldDefn->IsNullable() != poNewFieldDefn->IsNullable())
1518
0
        {
1519
0
            CPLError(CE_Failure, CPLE_NotSupported,
1520
0
                     "Altering the nullable state of a field "
1521
0
                     "is not currently supported for OpenFileGDB");
1522
0
            return OGRERR_FAILURE;
1523
0
        }
1524
0
    }
1525
0
    if (nFlagsIn & ALTER_DOMAIN_FLAG)
1526
0
    {
1527
0
        oField.SetDomainName(poNewFieldDefn->GetDomainName());
1528
0
    }
1529
0
    if (nFlagsIn & ALTER_ALTERNATIVE_NAME_FLAG)
1530
0
    {
1531
0
        oField.SetAlternativeName(poNewFieldDefn->GetAlternativeNameRef());
1532
0
    }
1533
1534
0
    const auto eType = GetGDBFieldType(&oField, m_bArcGISPro32OrLater);
1535
1536
0
    int nWidth = 0;
1537
0
    if (eType == FGFT_GLOBALID || eType == FGFT_GUID)
1538
0
    {
1539
0
        nWidth = 38;
1540
0
    }
1541
0
    else if (oField.GetType() == OFTString)
1542
0
    {
1543
0
        nWidth = oField.GetWidth();
1544
0
        if (nWidth == 0)
1545
0
        {
1546
            // Can be useful to try to replicate FileGDB driver, but do
1547
            // not use its 65536 default value.
1548
0
            nWidth = atoi(CPLGetConfigOption("OPENFILEGDB_STRING_WIDTH", "0"));
1549
0
        }
1550
0
    }
1551
1552
0
    OGRField sDefault = FileGDBField::UNSET_FIELD;
1553
0
    std::string osDefaultVal;
1554
0
    if (!GetDefault(&oField, eType, sDefault, osDefaultVal,
1555
0
                    /*bApproxOK=*/false))
1556
0
        return OGRERR_FAILURE;
1557
1558
0
    const char *pszAlias = oField.GetAlternativeNameRef();
1559
0
    if (!m_poLyrTable->AlterField(
1560
0
            nGDBIdx, oField.GetNameRef(),
1561
0
            pszAlias ? std::string(pszAlias) : std::string(), eType,
1562
0
            CPL_TO_BOOL(oField.IsNullable()), nWidth, sDefault))
1563
0
    {
1564
0
        return OGRERR_FAILURE;
1565
0
    }
1566
1567
0
    poFieldDefn->SetSubType(OFSTNone);
1568
0
    poFieldDefn->SetName(oField.GetNameRef());
1569
0
    poFieldDefn->SetAlternativeName(oField.GetAlternativeNameRef());
1570
0
    poFieldDefn->SetType(oField.GetType());
1571
0
    poFieldDefn->SetSubType(oField.GetSubType());
1572
0
    poFieldDefn->SetWidth(oField.GetWidth());
1573
0
    poFieldDefn->SetDefault(oField.GetDefault());
1574
0
    poFieldDefn->SetNullable(oField.IsNullable());
1575
0
    poFieldDefn->SetDomainName(oField.GetDomainName());
1576
1577
0
    if (m_bRegisteredTable)
1578
0
    {
1579
        // If the table is already registered (that is updating an existing
1580
        // layer), patch the XML definition
1581
0
        CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
1582
0
        if (oTree)
1583
0
        {
1584
0
            CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
1585
0
            if (psGPFieldInfoExs)
1586
0
            {
1587
0
                CPLXMLNode *psLastChild = nullptr;
1588
0
                for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
1589
0
                     psIter = psIter->psNext)
1590
0
                {
1591
0
                    if (psIter->eType == CXT_Element &&
1592
0
                        strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
1593
0
                        CPLGetXMLValue(psIter, "Name", "") == osOldFieldName)
1594
0
                    {
1595
0
                        CPLXMLNode *psNext = psIter->psNext;
1596
0
                        psIter->psNext = nullptr;
1597
0
                        CPLDestroyXMLNode(psIter);
1598
0
                        psIter = CreateXMLFieldDefinition(
1599
0
                            poFieldDefn, m_poLyrTable->GetField(nGDBIdx),
1600
0
                            m_bArcGISPro32OrLater);
1601
0
                        psIter->psNext = psNext;
1602
0
                        if (psLastChild == nullptr)
1603
0
                            psGPFieldInfoExs->psChild = psIter;
1604
0
                        else
1605
0
                            psLastChild->psNext = psIter;
1606
0
                        break;
1607
0
                    }
1608
0
                    psLastChild = psIter;
1609
0
                }
1610
1611
0
                if (bRenamedField && m_iAreaField == iFieldToAlter)
1612
0
                {
1613
0
                    CPLXMLNode *psNode =
1614
0
                        CPLSearchXMLNode(oTree.get(), "=AreaFieldName");
1615
0
                    if (psNode)
1616
0
                    {
1617
0
                        CPLSetXMLValue(psNode, "", poFieldDefn->GetNameRef());
1618
0
                    }
1619
0
                }
1620
0
                else if (bRenamedField && m_iLengthField == iFieldToAlter)
1621
0
                {
1622
0
                    CPLXMLNode *psNode =
1623
0
                        CPLSearchXMLNode(oTree.get(), "=LengthFieldName");
1624
0
                    if (psNode)
1625
0
                    {
1626
0
                        CPLSetXMLValue(psNode, "", poFieldDefn->GetNameRef());
1627
0
                    }
1628
0
                }
1629
1630
0
                char *pszDefinition = CPLSerializeXMLTree(oTree.get());
1631
0
                m_osDefinition = pszDefinition;
1632
0
                CPLFree(pszDefinition);
1633
1634
0
                m_poDS->UpdateXMLDefinition(m_osName.c_str(),
1635
0
                                            m_osDefinition.c_str());
1636
0
            }
1637
0
        }
1638
0
    }
1639
0
    else
1640
0
    {
1641
0
        RefreshXMLDefinitionInMemory();
1642
0
    }
1643
1644
0
    if (osOldDomainName != oField.GetDomainName() &&
1645
0
        (!m_osThisGUID.empty() ||
1646
0
         m_poDS->FindUUIDFromName(GetName(), m_osThisGUID)))
1647
0
    {
1648
0
        if (osOldDomainName.empty())
1649
0
        {
1650
0
            if (!m_poDS->LinkDomainToTable(oField.GetDomainName(),
1651
0
                                           m_osThisGUID))
1652
0
            {
1653
0
                poFieldDefn->SetDomainName(std::string());
1654
0
            }
1655
0
        }
1656
0
        else
1657
0
        {
1658
0
            bool bDomainStillUsed = false;
1659
0
            for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
1660
0
            {
1661
0
                if (m_poFeatureDefn->GetFieldDefn(i)->GetDomainName() ==
1662
0
                    osOldDomainName)
1663
0
                {
1664
0
                    bDomainStillUsed = true;
1665
0
                    break;
1666
0
                }
1667
0
            }
1668
0
            if (!bDomainStillUsed)
1669
0
            {
1670
0
                m_poDS->UnlinkDomainToTable(osOldDomainName, m_osThisGUID);
1671
0
            }
1672
0
        }
1673
0
    }
1674
1675
0
    return OGRERR_NONE;
1676
0
}
1677
1678
/************************************************************************/
1679
/*                         AlterGeomFieldDefn()                         */
1680
/************************************************************************/
1681
1682
OGRErr OGROpenFileGDBLayer::AlterGeomFieldDefn(
1683
    int iGeomFieldToAlter, const OGRGeomFieldDefn *poNewGeomFieldDefn,
1684
    int nFlagsIn)
1685
0
{
1686
0
    if (!m_bEditable)
1687
0
        return OGRERR_FAILURE;
1688
1689
0
    if (!BuildLayerDefinition())
1690
0
        return OGRERR_FAILURE;
1691
1692
0
    if (m_poDS->IsInTransaction() &&
1693
0
        ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
1694
0
         !m_poDS->BackupSystemTablesForTransaction()))
1695
0
    {
1696
0
        return OGRERR_FAILURE;
1697
0
    }
1698
1699
0
    if (iGeomFieldToAlter < 0 ||
1700
0
        iGeomFieldToAlter >= m_poFeatureDefn->GetGeomFieldCount())
1701
0
    {
1702
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
1703
0
        return OGRERR_FAILURE;
1704
0
    }
1705
1706
0
    const int nGDBIdx = m_poLyrTable->GetFieldIdx(
1707
0
        m_poFeatureDefn->GetGeomFieldDefn(iGeomFieldToAlter)->GetNameRef());
1708
0
    if (nGDBIdx < 0)
1709
0
        return OGRERR_FAILURE;
1710
1711
0
    const auto poGeomFieldDefn =
1712
0
        m_poFeatureDefn->GetGeomFieldDefn(iGeomFieldToAlter);
1713
0
    auto oTemporaryUnsealer(poGeomFieldDefn->GetTemporaryUnsealer());
1714
0
    OGRGeomFieldDefn oField(poGeomFieldDefn);
1715
1716
0
    if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_TYPE_FLAG) != 0)
1717
0
    {
1718
0
        if (poGeomFieldDefn->GetType() != poNewGeomFieldDefn->GetType())
1719
0
        {
1720
0
            CPLError(CE_Failure, CPLE_NotSupported,
1721
0
                     "Altering the geometry field type is not supported for "
1722
0
                     "the FileGeodatabase format");
1723
0
            return OGRERR_FAILURE;
1724
0
        }
1725
0
    }
1726
1727
0
    const std::string osOldFieldName = poGeomFieldDefn->GetNameRef();
1728
0
    const bool bRenamedField =
1729
0
        (nFlagsIn & ALTER_GEOM_FIELD_DEFN_NAME_FLAG) != 0 &&
1730
0
        poNewGeomFieldDefn->GetNameRef() != osOldFieldName;
1731
0
    if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_NAME_FLAG) != 0)
1732
0
    {
1733
0
        if (bRenamedField)
1734
0
        {
1735
0
            const std::string osFieldNameOri(poNewGeomFieldDefn->GetNameRef());
1736
0
            const std::string osFieldNameLaundered =
1737
0
                GetLaunderedFieldName(osFieldNameOri);
1738
0
            if (osFieldNameLaundered != osFieldNameOri)
1739
0
            {
1740
0
                CPLError(CE_Failure, CPLE_AppDefined,
1741
0
                         "Invalid field name: %s. "
1742
0
                         "A potential valid name would be: %s",
1743
0
                         osFieldNameOri.c_str(), osFieldNameLaundered.c_str());
1744
0
                return OGRERR_FAILURE;
1745
0
            }
1746
1747
0
            oField.SetName(poNewGeomFieldDefn->GetNameRef());
1748
0
        }
1749
        // oField.SetAlternativeName(poNewGeomFieldDefn->GetAlternativeNameRef());
1750
0
    }
1751
1752
0
    if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_NULLABLE_FLAG) != 0)
1753
0
    {
1754
        // could be potentially done, but involves .gdbtable rewriting
1755
0
        if (poGeomFieldDefn->IsNullable() != poNewGeomFieldDefn->IsNullable())
1756
0
        {
1757
0
            CPLError(CE_Failure, CPLE_NotSupported,
1758
0
                     "Altering the nullable state of the geometry field "
1759
0
                     "is not currently supported for OpenFileGDB");
1760
0
            return OGRERR_FAILURE;
1761
0
        }
1762
0
    }
1763
1764
0
    if ((nFlagsIn & ALTER_GEOM_FIELD_DEFN_SRS_FLAG) != 0)
1765
0
    {
1766
0
        const auto poOldSRS = poGeomFieldDefn->GetSpatialRef();
1767
0
        const auto poNewSRS = poNewGeomFieldDefn->GetSpatialRef();
1768
1769
0
        const char *const apszOptions[] = {
1770
0
            "IGNORE_DATA_AXIS_TO_SRS_AXIS_MAPPING=YES", nullptr};
1771
0
        if ((poOldSRS == nullptr && poNewSRS != nullptr) ||
1772
0
            (poOldSRS != nullptr && poNewSRS == nullptr) ||
1773
0
            (poOldSRS != nullptr && poNewSRS != nullptr &&
1774
0
             !poOldSRS->IsSame(poNewSRS, apszOptions)))
1775
0
        {
1776
0
            if (!m_osFeatureDatasetGUID.empty())
1777
0
            {
1778
                // Could potentially be done (would require changing the SRS
1779
                // in all layers of the feature dataset)
1780
0
                CPLError(CE_Failure, CPLE_NotSupported,
1781
0
                         "Altering the SRS of the geometry field of a layer "
1782
0
                         "in a feature daaset is not currently supported "
1783
0
                         "for OpenFileGDB");
1784
0
                return OGRERR_FAILURE;
1785
0
            }
1786
1787
0
            if (poNewSRS)
1788
0
            {
1789
0
                auto poNewSRSClone = poNewSRS->Clone();
1790
0
                oField.SetSpatialRef(poNewSRSClone);
1791
0
                poNewSRSClone->Release();
1792
0
            }
1793
0
            else
1794
0
            {
1795
0
                oField.SetSpatialRef(nullptr);
1796
0
            }
1797
0
        }
1798
0
    }
1799
1800
0
    std::string osWKT = "{B286C06B-0879-11D2-AACA-00C04FA33C20}";  // No SRS
1801
0
    if (oField.GetSpatialRef())
1802
0
    {
1803
0
        const char *const apszOptions[] = {"FORMAT=WKT1_ESRI", nullptr};
1804
0
        char *pszWKT;
1805
0
        oField.GetSpatialRef()->exportToWkt(&pszWKT, apszOptions);
1806
0
        osWKT = pszWKT;
1807
0
        CPLFree(pszWKT);
1808
0
    }
1809
1810
0
    if (!m_poLyrTable->AlterGeomField(oField.GetNameRef(),
1811
0
                                      std::string(),  // Alias
1812
0
                                      CPL_TO_BOOL(oField.IsNullable()), osWKT))
1813
0
    {
1814
0
        return OGRERR_FAILURE;
1815
0
    }
1816
1817
0
    poGeomFieldDefn->SetName(oField.GetNameRef());
1818
0
    poGeomFieldDefn->SetSpatialRef(oField.GetSpatialRef());
1819
1820
0
    if (m_bRegisteredTable)
1821
0
    {
1822
        // If the table is already registered (that is updating an existing
1823
        // layer), patch the XML definition
1824
0
        CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
1825
0
        if (oTree)
1826
0
        {
1827
0
            CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
1828
0
            if (psGPFieldInfoExs)
1829
0
            {
1830
0
                for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
1831
0
                     psIter = psIter->psNext)
1832
0
                {
1833
0
                    if (psIter->eType == CXT_Element &&
1834
0
                        strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
1835
0
                        CPLGetXMLValue(psIter, "Name", "") == osOldFieldName)
1836
0
                    {
1837
0
                        CPLXMLNode *psNode = CPLGetXMLNode(psIter, "Name");
1838
0
                        if (psNode && psNode->psChild &&
1839
0
                            psNode->psChild->eType == CXT_Text)
1840
0
                        {
1841
0
                            CPLFree(psNode->psChild->pszValue);
1842
0
                            psNode->psChild->pszValue =
1843
0
                                CPLStrdup(poGeomFieldDefn->GetNameRef());
1844
0
                        }
1845
0
                        break;
1846
0
                    }
1847
0
                }
1848
1849
0
                CPLXMLNode *psNode =
1850
0
                    CPLSearchXMLNode(oTree.get(), "=ShapeFieldName");
1851
0
                if (psNode)
1852
0
                {
1853
0
                    CPLSetXMLValue(psNode, "", poGeomFieldDefn->GetNameRef());
1854
0
                }
1855
1856
0
                CPLXMLNode *psFeatureClassInfo =
1857
0
                    CPLSearchXMLNode(oTree.get(), "=DEFeatureClassInfo");
1858
0
                if (psFeatureClassInfo == nullptr)
1859
0
                    psFeatureClassInfo = CPLSearchXMLNode(
1860
0
                        oTree.get(), "=typens:DEFeatureClassInfo");
1861
0
                if (psFeatureClassInfo)
1862
0
                {
1863
0
                    psNode = CPLGetXMLNode(psFeatureClassInfo, "Extent");
1864
0
                    if (psNode)
1865
0
                    {
1866
0
                        if (CPLRemoveXMLChild(psFeatureClassInfo, psNode))
1867
0
                            CPLDestroyXMLNode(psNode);
1868
0
                    }
1869
1870
0
                    psNode =
1871
0
                        CPLGetXMLNode(psFeatureClassInfo, "SpatialReference");
1872
0
                    if (psNode)
1873
0
                    {
1874
0
                        if (CPLRemoveXMLChild(psFeatureClassInfo, psNode))
1875
0
                            CPLDestroyXMLNode(psNode);
1876
0
                    }
1877
1878
0
                    XMLSerializeGeomFieldBase(psFeatureClassInfo,
1879
0
                                              m_poLyrTable->GetGeomField(),
1880
0
                                              GetSpatialRef());
1881
0
                }
1882
1883
0
                char *pszDefinition = CPLSerializeXMLTree(oTree.get());
1884
0
                m_osDefinition = pszDefinition;
1885
0
                CPLFree(pszDefinition);
1886
1887
0
                m_poDS->UpdateXMLDefinition(m_osName.c_str(),
1888
0
                                            m_osDefinition.c_str());
1889
0
            }
1890
0
        }
1891
0
    }
1892
0
    else
1893
0
    {
1894
0
        RefreshXMLDefinitionInMemory();
1895
0
    }
1896
1897
0
    return OGRERR_NONE;
1898
0
}
1899
1900
/************************************************************************/
1901
/*                             DeleteField()                            */
1902
/************************************************************************/
1903
1904
OGRErr OGROpenFileGDBLayer::DeleteField(int iFieldToDelete)
1905
0
{
1906
0
    if (!m_bEditable)
1907
0
        return OGRERR_FAILURE;
1908
1909
0
    if (!BuildLayerDefinition())
1910
0
        return OGRERR_FAILURE;
1911
1912
0
    if (m_poDS->IsInTransaction() &&
1913
0
        ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
1914
0
         !m_poDS->BackupSystemTablesForTransaction()))
1915
0
    {
1916
0
        return OGRERR_FAILURE;
1917
0
    }
1918
1919
0
    if (iFieldToDelete < 0 ||
1920
0
        iFieldToDelete >= m_poFeatureDefn->GetFieldCount())
1921
0
    {
1922
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid field index");
1923
0
        return OGRERR_FAILURE;
1924
0
    }
1925
1926
0
    if (iFieldToDelete == m_iFIDAsRegularColumnIndex)
1927
0
    {
1928
0
        CPLError(CE_Failure, CPLE_NotSupported, "Cannot delete field %s",
1929
0
                 GetFIDColumn());
1930
0
        return OGRERR_FAILURE;
1931
0
    }
1932
1933
0
    const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(iFieldToDelete);
1934
0
    const int nGDBIdx = m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
1935
0
    if (nGDBIdx < 0)
1936
0
        return OGRERR_FAILURE;
1937
0
    const bool bRet = m_poLyrTable->DeleteField(nGDBIdx);
1938
0
    m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
1939
1940
0
    if (!bRet)
1941
0
        return OGRERR_FAILURE;
1942
1943
0
    const std::string osDeletedFieldName = poFieldDefn->GetNameRef();
1944
0
    const std::string osOldDomainName =
1945
0
        std::string(poFieldDefn->GetDomainName());
1946
1947
0
    whileUnsealing(m_poFeatureDefn)->DeleteFieldDefn(iFieldToDelete);
1948
1949
0
    if (m_iFIDAsRegularColumnIndex > iFieldToDelete)
1950
0
        m_iFIDAsRegularColumnIndex--;
1951
1952
0
    if (iFieldToDelete < m_iAreaField)
1953
0
        m_iAreaField--;
1954
0
    if (iFieldToDelete < m_iLengthField)
1955
0
        m_iLengthField--;
1956
1957
0
    bool bEmptyAreaFieldName = false;
1958
0
    bool bEmptyLengthFieldName = false;
1959
0
    if (m_iAreaField == iFieldToDelete)
1960
0
    {
1961
0
        bEmptyAreaFieldName = true;
1962
0
        m_iAreaField = -1;
1963
0
    }
1964
0
    else if (m_iLengthField == iFieldToDelete)
1965
0
    {
1966
0
        bEmptyLengthFieldName = true;
1967
0
        m_iLengthField = -1;
1968
0
    }
1969
1970
0
    if (m_bRegisteredTable)
1971
0
    {
1972
        // If the table is already registered (that is updating an existing
1973
        // layer), patch the XML definition to add the new field
1974
0
        CPLXMLTreeCloser oTree(CPLParseXMLString(m_osDefinition.c_str()));
1975
0
        if (oTree)
1976
0
        {
1977
0
            CPLXMLNode *psGPFieldInfoExs = GetGPFieldInfoExsNode(oTree.get());
1978
0
            if (psGPFieldInfoExs)
1979
0
            {
1980
0
                CPLXMLNode *psLastChild = nullptr;
1981
0
                for (CPLXMLNode *psIter = psGPFieldInfoExs->psChild; psIter;
1982
0
                     psIter = psIter->psNext)
1983
0
                {
1984
0
                    if (psIter->eType == CXT_Element &&
1985
0
                        strcmp(psIter->pszValue, "GPFieldInfoEx") == 0 &&
1986
0
                        CPLGetXMLValue(psIter, "Name", "") ==
1987
0
                            osDeletedFieldName)
1988
0
                    {
1989
0
                        if (psLastChild == nullptr)
1990
0
                            psGPFieldInfoExs->psChild = psIter->psNext;
1991
0
                        else
1992
0
                            psLastChild->psNext = psIter->psNext;
1993
0
                        psIter->psNext = nullptr;
1994
0
                        CPLDestroyXMLNode(psIter);
1995
0
                        break;
1996
0
                    }
1997
0
                    psLastChild = psIter;
1998
0
                }
1999
2000
0
                if (bEmptyAreaFieldName)
2001
0
                {
2002
0
                    CPLXMLNode *psNode =
2003
0
                        CPLSearchXMLNode(oTree.get(), "=AreaFieldName");
2004
0
                    if (psNode && psNode->psChild)
2005
0
                    {
2006
0
                        CPLDestroyXMLNode(psNode->psChild);
2007
0
                        psNode->psChild = nullptr;
2008
0
                    }
2009
0
                }
2010
0
                else if (bEmptyLengthFieldName)
2011
0
                {
2012
0
                    CPLXMLNode *psNode =
2013
0
                        CPLSearchXMLNode(oTree.get(), "=LengthFieldName");
2014
0
                    if (psNode && psNode->psChild)
2015
0
                    {
2016
0
                        CPLDestroyXMLNode(psNode->psChild);
2017
0
                        psNode->psChild = nullptr;
2018
0
                    }
2019
0
                }
2020
2021
0
                char *pszDefinition = CPLSerializeXMLTree(oTree.get());
2022
0
                m_osDefinition = pszDefinition;
2023
0
                CPLFree(pszDefinition);
2024
2025
0
                m_poDS->UpdateXMLDefinition(m_osName.c_str(),
2026
0
                                            m_osDefinition.c_str());
2027
0
            }
2028
0
        }
2029
0
    }
2030
0
    else
2031
0
    {
2032
0
        RefreshXMLDefinitionInMemory();
2033
0
    }
2034
2035
0
    if (!osOldDomainName.empty())
2036
0
    {
2037
0
        bool bDomainStillUsed = false;
2038
0
        for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
2039
0
        {
2040
0
            if (m_poFeatureDefn->GetFieldDefn(i)->GetDomainName() ==
2041
0
                osOldDomainName)
2042
0
            {
2043
0
                bDomainStillUsed = true;
2044
0
                break;
2045
0
            }
2046
0
        }
2047
0
        if (!bDomainStillUsed)
2048
0
        {
2049
0
            if (!m_osThisGUID.empty() ||
2050
0
                m_poDS->FindUUIDFromName(GetName(), m_osThisGUID))
2051
0
            {
2052
0
                m_poDS->UnlinkDomainToTable(osOldDomainName, m_osThisGUID);
2053
0
            }
2054
0
        }
2055
0
    }
2056
2057
0
    return OGRERR_NONE;
2058
0
}
2059
2060
/************************************************************************/
2061
/*                            GetLength()                               */
2062
/************************************************************************/
2063
2064
static double GetLength(const OGRCurvePolygon *poPoly)
2065
0
{
2066
0
    double dfLength = 0;
2067
0
    for (const auto *poRing : *poPoly)
2068
0
        dfLength += poRing->get_Length();
2069
0
    return dfLength;
2070
0
}
2071
2072
static double GetLength(const OGRMultiSurface *poMS)
2073
0
{
2074
0
    double dfLength = 0;
2075
0
    for (const auto *poPoly : *poMS)
2076
0
    {
2077
0
        auto poCurvePolygon = dynamic_cast<const OGRCurvePolygon *>(poPoly);
2078
0
        if (poCurvePolygon)
2079
0
            dfLength += GetLength(poCurvePolygon);
2080
0
    }
2081
0
    return dfLength;
2082
0
}
2083
2084
/************************************************************************/
2085
/*                      PrepareFileGDBFeature()                         */
2086
/************************************************************************/
2087
2088
bool OGROpenFileGDBLayer::PrepareFileGDBFeature(OGRFeature *poFeature,
2089
                                                std::vector<OGRField> &fields,
2090
                                                const OGRGeometry *&poGeom,
2091
                                                bool bUpdate)
2092
757k
{
2093
    // Check geometry type
2094
757k
    poGeom = poFeature->GetGeometryRef();
2095
757k
    const auto eFlattenType =
2096
757k
        poGeom ? wkbFlatten(poGeom->getGeometryType()) : wkbNone;
2097
757k
    if (poGeom)
2098
1.79k
    {
2099
1.79k
        switch (m_poLyrTable->GetGeometryType())
2100
1.79k
        {
2101
0
            case FGTGT_NONE:
2102
0
                break;
2103
2104
0
            case FGTGT_POINT:
2105
0
            {
2106
0
                if (eFlattenType != wkbPoint)
2107
0
                {
2108
0
                    CPLError(
2109
0
                        CE_Failure, CPLE_NotSupported,
2110
0
                        "Can only insert a Point in a esriGeometryPoint layer");
2111
0
                    return false;
2112
0
                }
2113
0
                break;
2114
0
            }
2115
2116
0
            case FGTGT_MULTIPOINT:
2117
0
            {
2118
0
                if (eFlattenType != wkbMultiPoint)
2119
0
                {
2120
0
                    CPLError(CE_Failure, CPLE_NotSupported,
2121
0
                             "Can only insert a MultiPoint in a "
2122
0
                             "esriGeometryMultiPoint layer");
2123
0
                    return false;
2124
0
                }
2125
0
                break;
2126
0
            }
2127
2128
1.76k
            case FGTGT_LINE:
2129
1.76k
            {
2130
1.76k
                if (eFlattenType != wkbLineString &&
2131
1.73k
                    eFlattenType != wkbMultiLineString &&
2132
1.73k
                    eFlattenType != wkbCircularString &&
2133
1.73k
                    eFlattenType != wkbCompoundCurve &&
2134
1.73k
                    eFlattenType != wkbMultiCurve)
2135
362
                {
2136
362
                    CPLError(
2137
362
                        CE_Failure, CPLE_NotSupported,
2138
362
                        "Can only insert a "
2139
362
                        "LineString/MultiLineString/CircularString/"
2140
362
                        "CompoundCurve/MultiCurve in a esriGeometryLine layer");
2141
362
                    return false;
2142
362
                }
2143
1.40k
                break;
2144
1.76k
            }
2145
2146
1.40k
            case FGTGT_POLYGON:
2147
14
            {
2148
14
                if (eFlattenType != wkbPolygon &&
2149
11
                    eFlattenType != wkbMultiPolygon &&
2150
6
                    eFlattenType != wkbCurvePolygon &&
2151
6
                    eFlattenType != wkbMultiSurface)
2152
6
                {
2153
6
                    CPLError(CE_Failure, CPLE_NotSupported,
2154
6
                             "Can only insert a "
2155
6
                             "Polygon/MultiPolygon/CurvePolygon/MultiSurface "
2156
6
                             "in a esriGeometryPolygon layer");
2157
6
                    return false;
2158
6
                }
2159
8
                break;
2160
14
            }
2161
2162
10
            case FGTGT_MULTIPATCH:
2163
10
            {
2164
10
                if (eFlattenType != wkbTIN &&
2165
9
                    eFlattenType != wkbPolyhedralSurface &&
2166
8
                    eFlattenType != wkbGeometryCollection)
2167
6
                {
2168
6
                    if (CPLTestBool(m_aosCreationOptions.FetchNameValueDef(
2169
6
                            "CREATE_MULTIPATCH", "FALSE")) &&
2170
0
                        eFlattenType == wkbMultiPolygon)
2171
0
                    {
2172
                        // ok
2173
0
                    }
2174
6
                    else
2175
6
                    {
2176
6
                        CPLError(
2177
6
                            CE_Failure, CPLE_NotSupported,
2178
6
                            "Can only insert a "
2179
6
                            "TIN/PolyhedralSurface/GeometryCollection in a "
2180
6
                            "esriGeometryMultiPatch layer");
2181
6
                        return false;
2182
6
                    }
2183
6
                }
2184
4
                break;
2185
10
            }
2186
1.79k
        }
2187
2188
        // Treat empty geometries as NULL, like the FileGDB driver
2189
1.41k
        if (poGeom->IsEmpty() &&
2190
1.07k
            !CPLTestBool(CPLGetConfigOption(
2191
1.07k
                "OGR_OPENFILEGDB_WRITE_EMPTY_GEOMETRY", "NO")))
2192
1.07k
        {
2193
1.07k
            poGeom = nullptr;
2194
1.07k
        }
2195
1.41k
    }
2196
2197
757k
    if (m_iAreaField >= 0)
2198
0
    {
2199
0
        const int i = m_iAreaField;
2200
0
        if (poGeom != nullptr)
2201
0
        {
2202
0
            if (eFlattenType == wkbPolygon || eFlattenType == wkbCurvePolygon)
2203
0
                poFeature->SetField(i, poGeom->toCurvePolygon()->get_Area());
2204
0
            else if (eFlattenType == wkbMultiPolygon ||
2205
0
                     eFlattenType == wkbMultiSurface)
2206
0
                poFeature->SetField(i, poGeom->toMultiSurface()->get_Area());
2207
0
            else
2208
0
                poFeature->SetFieldNull(
2209
0
                    i);  // shouldn't happen in nominal situation
2210
0
        }
2211
0
        else
2212
0
        {
2213
0
            poFeature->SetFieldNull(i);
2214
0
        }
2215
0
    }
2216
2217
757k
    if (m_iLengthField >= 0)
2218
0
    {
2219
0
        const int i = m_iLengthField;
2220
0
        if (poGeom != nullptr)
2221
0
        {
2222
0
            if (OGR_GT_IsCurve(eFlattenType))
2223
0
                poFeature->SetField(i, poGeom->toCurve()->get_Length());
2224
0
            else if (OGR_GT_IsSubClassOf(eFlattenType, wkbMultiCurve))
2225
0
                poFeature->SetField(i, poGeom->toMultiCurve()->get_Length());
2226
0
            else if (eFlattenType == wkbPolygon ||
2227
0
                     eFlattenType == wkbCurvePolygon)
2228
0
                poFeature->SetField(i, GetLength(poGeom->toCurvePolygon()));
2229
0
            else if (eFlattenType == wkbMultiPolygon ||
2230
0
                     eFlattenType == wkbMultiSurface)
2231
0
                poFeature->SetField(i, GetLength(poGeom->toMultiSurface()));
2232
0
            else
2233
0
                poFeature->SetFieldNull(
2234
0
                    i);  // shouldn't happen in nominal situation
2235
0
        }
2236
0
        else
2237
0
        {
2238
0
            poFeature->SetFieldNull(i);
2239
0
        }
2240
0
    }
2241
2242
757k
    fields.resize(m_poLyrTable->GetFieldCount(), FileGDBField::UNSET_FIELD);
2243
757k
    m_aosTempStrings.clear();
2244
2.66M
    for (int i = 0; i < m_poFeatureDefn->GetFieldCount(); ++i)
2245
1.90M
    {
2246
1.90M
        if (i == m_iFIDAsRegularColumnIndex)
2247
0
            continue;
2248
1.90M
        const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(i);
2249
1.90M
        const int idxFileGDB =
2250
1.90M
            m_poLyrTable->GetFieldIdx(poFieldDefn->GetNameRef());
2251
1.90M
        if (idxFileGDB < 0)
2252
0
            continue;
2253
1.90M
        if (!poFeature->IsFieldSetAndNotNull(i))
2254
1.13M
        {
2255
1.13M
            if (m_poLyrTable->GetField(idxFileGDB)->GetType() == FGFT_GLOBALID)
2256
0
            {
2257
0
                m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
2258
0
                fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2259
0
            }
2260
1.13M
            continue;
2261
1.13M
        }
2262
771k
        memset(&fields[idxFileGDB], 0, sizeof(OGRField));
2263
771k
        switch (m_poLyrTable->GetField(idxFileGDB)->GetType())
2264
771k
        {
2265
0
            case FGFT_UNDEFINED:
2266
0
                CPLAssert(false);
2267
0
                break;
2268
0
            case FGFT_INT16:
2269
0
                fields[idxFileGDB].Integer =
2270
0
                    poFeature->GetRawFieldRef(i)->Integer;
2271
0
                break;
2272
342
            case FGFT_INT32:
2273
342
                fields[idxFileGDB].Integer =
2274
342
                    poFeature->GetRawFieldRef(i)->Integer;
2275
342
                break;
2276
0
            case FGFT_FLOAT32:
2277
0
                fields[idxFileGDB].Real = poFeature->GetRawFieldRef(i)->Real;
2278
0
                break;
2279
968
            case FGFT_FLOAT64:
2280
968
            {
2281
968
                if (poFieldDefn->GetType() == OFTReal)
2282
903
                {
2283
903
                    fields[idxFileGDB].Real =
2284
903
                        poFeature->GetRawFieldRef(i)->Real;
2285
903
                }
2286
65
                else
2287
65
                {
2288
65
                    fields[idxFileGDB].Real = poFeature->GetFieldAsDouble(i);
2289
65
                }
2290
968
                break;
2291
0
            }
2292
770k
            case FGFT_STRING:
2293
770k
            case FGFT_GUID:
2294
770k
            case FGFT_XML:
2295
770k
            {
2296
770k
                if (poFieldDefn->GetType() == OFTString)
2297
770k
                {
2298
770k
                    fields[idxFileGDB].String =
2299
770k
                        poFeature->GetRawFieldRef(i)->String;
2300
770k
                }
2301
0
                else
2302
0
                {
2303
0
                    m_aosTempStrings.emplace_back(
2304
0
                        poFeature->GetFieldAsString(i));
2305
0
                    fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2306
0
                }
2307
770k
                break;
2308
770k
            }
2309
0
            case FGFT_DATETIME:
2310
0
            case FGFT_DATE:
2311
0
            {
2312
0
                fields[idxFileGDB].Date = poFeature->GetRawFieldRef(i)->Date;
2313
0
                if (m_bTimeInUTC && fields[idxFileGDB].Date.TZFlag <= 1)
2314
0
                {
2315
0
                    if (!m_bRegisteredTable &&
2316
0
                        m_poLyrTable->GetTotalRecordCount() == 0 &&
2317
0
                        m_aosCreationOptions.FetchNameValue("TIME_IN_UTC") ==
2318
0
                            nullptr)
2319
0
                    {
2320
                        // If the user didn't explicitly set TIME_IN_UTC, and
2321
                        // this is the first feature written, automatically
2322
                        // adjust m_bTimeInUTC from the first value
2323
0
                        m_bTimeInUTC = false;
2324
0
                    }
2325
0
                    else if (!m_bWarnedDateNotConvertibleUTC)
2326
0
                    {
2327
0
                        m_bWarnedDateNotConvertibleUTC = true;
2328
0
                        CPLError(
2329
0
                            CE_Warning, CPLE_AppDefined,
2330
0
                            "Attempt at writing a datetime with a unknown time "
2331
0
                            "zone "
2332
0
                            "or local time in a layer that expects dates "
2333
0
                            "to be convertible to UTC. It will be written as "
2334
0
                            "if it was expressed in UTC.");
2335
0
                    }
2336
0
                }
2337
0
                break;
2338
0
            }
2339
0
            case FGFT_OBJECTID:
2340
0
                CPLAssert(false);
2341
0
                break;  // shouldn't happen
2342
0
            case FGFT_GEOMETRY:
2343
0
                CPLAssert(false);
2344
0
                break;  // shouldn't happen
2345
0
            case FGFT_RASTER:
2346
0
                CPLAssert(false);
2347
0
                break;  // shouldn't happen
2348
0
            case FGFT_BINARY:
2349
0
                fields[idxFileGDB].Binary =
2350
0
                    poFeature->GetRawFieldRef(i)->Binary;
2351
0
                break;
2352
0
            case FGFT_GLOBALID:
2353
0
            {
2354
0
                if (bUpdate)
2355
0
                {
2356
0
                    m_aosTempStrings.emplace_back(
2357
0
                        poFeature->GetFieldAsString(i));
2358
0
                    fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2359
0
                }
2360
0
                else if (poFeature->GetRawFieldRef(i)->String[0] != '\0')
2361
0
                {
2362
0
                    if (CPLTestBool(CPLGetConfigOption(
2363
0
                            "OPENFILEGDB_REGENERATE_GLOBALID", "YES")))
2364
0
                    {
2365
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2366
0
                                 "Value found in a GlobalID field. It will be "
2367
0
                                 "replaced by a "
2368
0
                                 "newly generated UUID.");
2369
0
                        m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
2370
0
                        fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2371
0
                    }
2372
0
                    else
2373
0
                    {
2374
0
                        m_aosTempStrings.emplace_back(
2375
0
                            poFeature->GetFieldAsString(i));
2376
0
                        fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2377
0
                    }
2378
0
                }
2379
0
                else
2380
0
                {
2381
0
                    m_aosTempStrings.emplace_back(OFGDBGenerateUUID());
2382
0
                    fields[idxFileGDB].String = &m_aosTempStrings.back()[0];
2383
0
                }
2384
0
                break;
2385
0
            }
2386
0
            case FGFT_INT64:
2387
0
            {
2388
0
                fields[idxFileGDB].Integer64 =
2389
0
                    poFeature->GetRawFieldRef(i)->Integer64;
2390
0
                break;
2391
0
            }
2392
0
            case FGFT_TIME:
2393
0
            case FGFT_DATETIME_WITH_OFFSET:
2394
0
            {
2395
0
                fields[idxFileGDB].Date = poFeature->GetRawFieldRef(i)->Date;
2396
0
                break;
2397
0
            }
2398
771k
        }
2399
771k
    }
2400
2401
757k
    return true;
2402
757k
}
2403
2404
/************************************************************************/
2405
/*                   CheckFIDAndFIDColumnConsistency()                  */
2406
/************************************************************************/
2407
2408
static bool CheckFIDAndFIDColumnConsistency(const OGRFeature *poFeature,
2409
                                            int iFIDAsRegularColumnIndex)
2410
0
{
2411
0
    bool ok = false;
2412
0
    if (!poFeature->IsFieldSetAndNotNull(iFIDAsRegularColumnIndex))
2413
0
    {
2414
        // nothing to do
2415
0
    }
2416
0
    else if (poFeature->GetDefnRef()
2417
0
                 ->GetFieldDefn(iFIDAsRegularColumnIndex)
2418
0
                 ->GetType() == OFTReal)
2419
0
    {
2420
0
        const double dfFID =
2421
0
            poFeature->GetFieldAsDouble(iFIDAsRegularColumnIndex);
2422
0
        if (GDALIsValueInRange<int64_t>(dfFID))
2423
0
        {
2424
0
            const auto nFID = static_cast<GIntBig>(dfFID);
2425
0
            if (nFID == poFeature->GetFID())
2426
0
            {
2427
0
                ok = true;
2428
0
            }
2429
0
        }
2430
0
    }
2431
0
    else if (poFeature->GetFieldAsInteger64(iFIDAsRegularColumnIndex) ==
2432
0
             poFeature->GetFID())
2433
0
    {
2434
0
        ok = true;
2435
0
    }
2436
0
    if (!ok)
2437
0
    {
2438
0
        CPLError(CE_Failure, CPLE_AppDefined,
2439
0
                 "Inconsistent values of FID and field of same name");
2440
0
    }
2441
0
    return ok;
2442
0
}
2443
2444
/************************************************************************/
2445
/*                           ICreateFeature()                           */
2446
/************************************************************************/
2447
2448
OGRErr OGROpenFileGDBLayer::ICreateFeature(OGRFeature *poFeature)
2449
757k
{
2450
757k
    if (!m_bEditable)
2451
0
        return OGRERR_FAILURE;
2452
2453
757k
    if (!BuildLayerDefinition())
2454
0
        return OGRERR_FAILURE;
2455
2456
757k
    if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
2457
0
        !BeginEmulatedTransaction())
2458
0
    {
2459
0
        return OGRERR_FAILURE;
2460
0
    }
2461
2462
    /* In case the FID column has also been created as a regular field */
2463
757k
    if (m_iFIDAsRegularColumnIndex >= 0)
2464
0
    {
2465
0
        if (poFeature->GetFID() == OGRNullFID)
2466
0
        {
2467
0
            if (poFeature->IsFieldSetAndNotNull(m_iFIDAsRegularColumnIndex))
2468
0
            {
2469
0
                if (m_poFeatureDefn->GetFieldDefn(m_iFIDAsRegularColumnIndex)
2470
0
                        ->GetType() == OFTReal)
2471
0
                {
2472
0
                    bool ok = false;
2473
0
                    const double dfFID =
2474
0
                        poFeature->GetFieldAsDouble(m_iFIDAsRegularColumnIndex);
2475
0
                    if (dfFID >= static_cast<double>(
2476
0
                                     std::numeric_limits<int64_t>::min()) &&
2477
0
                        dfFID <= static_cast<double>(
2478
0
                                     std::numeric_limits<int64_t>::max()))
2479
0
                    {
2480
0
                        const auto nFID = static_cast<GIntBig>(dfFID);
2481
0
                        if (static_cast<double>(nFID) == dfFID)
2482
0
                        {
2483
0
                            poFeature->SetFID(nFID);
2484
0
                            ok = true;
2485
0
                        }
2486
0
                    }
2487
0
                    if (!ok)
2488
0
                    {
2489
0
                        CPLError(
2490
0
                            CE_Failure, CPLE_AppDefined,
2491
0
                            "Value of FID %g cannot be parsed to an Integer64",
2492
0
                            dfFID);
2493
0
                        return OGRERR_FAILURE;
2494
0
                    }
2495
0
                }
2496
0
                else
2497
0
                {
2498
0
                    poFeature->SetFID(poFeature->GetFieldAsInteger64(
2499
0
                        m_iFIDAsRegularColumnIndex));
2500
0
                }
2501
0
            }
2502
0
        }
2503
0
        else if (!CheckFIDAndFIDColumnConsistency(poFeature,
2504
0
                                                  m_iFIDAsRegularColumnIndex))
2505
0
        {
2506
0
            return OGRERR_FAILURE;
2507
0
        }
2508
0
    }
2509
2510
757k
    const auto nFID64Bit = poFeature->GetFID();
2511
757k
    if (nFID64Bit < -1 || nFID64Bit == 0 || nFID64Bit > INT_MAX)
2512
0
    {
2513
0
        CPLError(CE_Failure, CPLE_NotSupported,
2514
0
                 "Only 32 bit positive integers FID supported by FileGDB");
2515
0
        return OGRERR_FAILURE;
2516
0
    }
2517
2518
757k
    int nFID32Bit = (nFID64Bit > 0) ? static_cast<int>(nFID64Bit) : 0;
2519
2520
757k
    poFeature->FillUnsetWithDefault(FALSE, nullptr);
2521
2522
757k
    const OGRGeometry *poGeom = nullptr;
2523
757k
    std::vector<OGRField> fields;
2524
757k
    if (!PrepareFileGDBFeature(poFeature, fields, poGeom, /*bUpdate=*/false))
2525
374
        return OGRERR_FAILURE;
2526
2527
757k
    m_eSpatialIndexState = SPI_INVALID;
2528
757k
    m_nFilteredFeatureCount = -1;
2529
2530
757k
    if (!m_poLyrTable->CreateFeature(fields, poGeom, &nFID32Bit))
2531
329
        return OGRERR_FAILURE;
2532
2533
757k
    poFeature->SetFID(nFID32Bit);
2534
757k
    return OGRERR_NONE;
2535
757k
}
2536
2537
/************************************************************************/
2538
/*                           ISetFeature()                              */
2539
/************************************************************************/
2540
2541
OGRErr OGROpenFileGDBLayer::ISetFeature(OGRFeature *poFeature)
2542
0
{
2543
0
    if (!m_bEditable)
2544
0
        return OGRERR_FAILURE;
2545
2546
0
    if (!BuildLayerDefinition())
2547
0
        return OGRERR_FAILURE;
2548
2549
0
    if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
2550
0
        !BeginEmulatedTransaction())
2551
0
    {
2552
0
        return OGRERR_FAILURE;
2553
0
    }
2554
2555
    /* In case the FID column has also been created as a regular field */
2556
0
    if (m_iFIDAsRegularColumnIndex >= 0 &&
2557
0
        !CheckFIDAndFIDColumnConsistency(poFeature, m_iFIDAsRegularColumnIndex))
2558
0
    {
2559
0
        return OGRERR_FAILURE;
2560
0
    }
2561
2562
0
    const GIntBig nFID = poFeature->GetFID();
2563
0
    if (nFID <= 0 || !CPL_INT64_FITS_ON_INT32(nFID))
2564
0
        return OGRERR_NON_EXISTING_FEATURE;
2565
2566
0
    const int nFID32Bit = static_cast<int>(nFID);
2567
0
    if (nFID32Bit > m_poLyrTable->GetTotalRecordCount())
2568
0
        return OGRERR_NON_EXISTING_FEATURE;
2569
0
    if (!m_poLyrTable->SelectRow(nFID32Bit - 1))
2570
0
        return OGRERR_NON_EXISTING_FEATURE;
2571
2572
0
    const OGRGeometry *poGeom = nullptr;
2573
0
    std::vector<OGRField> fields;
2574
0
    if (!PrepareFileGDBFeature(poFeature, fields, poGeom, /*bUpdate=*/true))
2575
0
        return OGRERR_FAILURE;
2576
2577
0
    m_eSpatialIndexState = SPI_INVALID;
2578
0
    m_nFilteredFeatureCount = -1;
2579
2580
0
    if (!m_poLyrTable->UpdateFeature(nFID32Bit, fields, poGeom))
2581
0
        return OGRERR_FAILURE;
2582
2583
0
    return OGRERR_NONE;
2584
0
}
2585
2586
/************************************************************************/
2587
/*                           DeleteFeature()                            */
2588
/************************************************************************/
2589
2590
OGRErr OGROpenFileGDBLayer::DeleteFeature(GIntBig nFID)
2591
2592
0
{
2593
0
    if (!m_bEditable)
2594
0
        return OGRERR_FAILURE;
2595
2596
0
    if (!BuildLayerDefinition())
2597
0
        return OGRERR_FAILURE;
2598
2599
0
    if (m_poDS->IsInTransaction() && !m_bHasCreatedBackupForTransaction &&
2600
0
        !BeginEmulatedTransaction())
2601
0
    {
2602
0
        return OGRERR_FAILURE;
2603
0
    }
2604
2605
0
    if (nFID <= 0 || !CPL_INT64_FITS_ON_INT32(nFID))
2606
0
        return OGRERR_NON_EXISTING_FEATURE;
2607
2608
0
    const int nFID32Bit = static_cast<int>(nFID);
2609
0
    if (nFID32Bit > m_poLyrTable->GetTotalRecordCount())
2610
0
        return OGRERR_NON_EXISTING_FEATURE;
2611
0
    if (!m_poLyrTable->SelectRow(nFID32Bit - 1))
2612
0
        return OGRERR_NON_EXISTING_FEATURE;
2613
2614
0
    m_eSpatialIndexState = SPI_INVALID;
2615
0
    m_nFilteredFeatureCount = -1;
2616
2617
0
    return m_poLyrTable->DeleteFeature(nFID32Bit) ? OGRERR_NONE
2618
0
                                                  : OGRERR_FAILURE;
2619
0
}
2620
2621
/************************************************************************/
2622
/*                     RefreshXMLDefinitionInMemory()                   */
2623
/************************************************************************/
2624
2625
void OGROpenFileGDBLayer::RefreshXMLDefinitionInMemory()
2626
46.4k
{
2627
46.4k
    CPLXMLTreeCloser oTree(CPLCreateXMLNode(nullptr, CXT_Element, "?xml"));
2628
46.4k
    CPLAddXMLAttributeAndValue(oTree.get(), "version", "1.0");
2629
46.4k
    CPLAddXMLAttributeAndValue(oTree.get(), "encoding", "UTF-8");
2630
2631
46.4k
    CPLXMLNode *psRoot =
2632
46.4k
        CPLCreateXMLNode(nullptr, CXT_Element,
2633
46.4k
                         m_eGeomType == wkbNone ? "typens:DETableInfo"
2634
46.4k
                                                : "typens:DEFeatureClassInfo");
2635
46.4k
    CPLAddXMLSibling(oTree.get(), psRoot);
2636
2637
46.4k
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:typens",
2638
46.4k
                               m_bArcGISPro32OrLater
2639
46.4k
                                   ? "http://www.esri.com/schemas/ArcGIS/10.8"
2640
46.4k
                                   : "http://www.esri.com/schemas/ArcGIS/10.3");
2641
46.4k
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xsi",
2642
46.4k
                               "http://www.w3.org/2001/XMLSchema-instance");
2643
46.4k
    CPLAddXMLAttributeAndValue(psRoot, "xmlns:xs",
2644
46.4k
                               "http://www.w3.org/2001/XMLSchema");
2645
46.4k
    CPLAddXMLAttributeAndValue(psRoot, "xsi:type",
2646
46.4k
                               m_eGeomType == wkbNone
2647
46.4k
                                   ? "typens:DETableInfo"
2648
46.4k
                                   : "typens:DEFeatureClassInfo");
2649
46.4k
    CPLCreateXMLElementAndValue(psRoot, "CatalogPath", m_osPath.c_str());
2650
46.4k
    CPLCreateXMLElementAndValue(psRoot, "Name", m_osName.c_str());
2651
46.4k
    CPLCreateXMLElementAndValue(psRoot, "ChildrenExpanded", "false");
2652
46.4k
    CPLCreateXMLElementAndValue(psRoot, "DatasetType",
2653
46.4k
                                m_eGeomType == wkbNone ? "esriDTTable"
2654
46.4k
                                                       : "esriDTFeatureClass");
2655
2656
46.4k
    {
2657
46.4k
        FileGDBTable oTable;
2658
46.4k
        if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), false))
2659
0
            return;
2660
46.4k
        CPLCreateXMLElementAndValue(
2661
46.4k
            psRoot, "DSID",
2662
46.4k
            CPLSPrintf("%" PRId64, 1 + oTable.GetTotalRecordCount()));
2663
46.4k
    }
2664
2665
0
    CPLCreateXMLElementAndValue(psRoot, "Versioned", "false");
2666
46.4k
    CPLCreateXMLElementAndValue(psRoot, "CanVersion", "false");
2667
46.4k
    if (!m_osConfigurationKeyword.empty())
2668
0
    {
2669
0
        CPLCreateXMLElementAndValue(psRoot, "ConfigurationKeyword",
2670
0
                                    m_osConfigurationKeyword.c_str());
2671
0
    }
2672
46.4k
    if (m_bArcGISPro32OrLater)
2673
0
    {
2674
0
        CPLCreateXMLElementAndValue(psRoot, "RequiredGeodatabaseClientVersion",
2675
0
                                    "13.2");
2676
0
    }
2677
46.4k
    CPLCreateXMLElementAndValue(psRoot, "HasOID", "true");
2678
46.4k
    CPLCreateXMLElementAndValue(psRoot, "OIDFieldName", GetFIDColumn());
2679
46.4k
    auto GPFieldInfoExs =
2680
46.4k
        CPLCreateXMLNode(psRoot, CXT_Element, "GPFieldInfoExs");
2681
46.4k
    CPLAddXMLAttributeAndValue(GPFieldInfoExs, "xsi:type",
2682
46.4k
                               "typens:ArrayOfGPFieldInfoEx");
2683
2684
1.57M
    for (int i = 0; i < m_poLyrTable->GetFieldCount(); ++i)
2685
1.52M
    {
2686
1.52M
        const auto *poGDBFieldDefn = m_poLyrTable->GetField(i);
2687
1.52M
        if (poGDBFieldDefn->GetType() == FGFT_OBJECTID)
2688
46.4k
        {
2689
46.4k
            auto GPFieldInfoEx =
2690
46.4k
                CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
2691
46.4k
            CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
2692
46.4k
                                       "typens:GPFieldInfoEx");
2693
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
2694
46.4k
                                        poGDBFieldDefn->GetName().c_str());
2695
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
2696
46.4k
                                        "esriFieldTypeOID");
2697
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable", "false");
2698
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "4");
2699
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
2700
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
2701
46.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
2702
46.4k
        }
2703
1.47M
        else if (poGDBFieldDefn->GetType() == FGFT_GEOMETRY)
2704
23.4k
        {
2705
23.4k
            auto GPFieldInfoEx =
2706
23.4k
                CPLCreateXMLNode(GPFieldInfoExs, CXT_Element, "GPFieldInfoEx");
2707
23.4k
            CPLAddXMLAttributeAndValue(GPFieldInfoEx, "xsi:type",
2708
23.4k
                                       "typens:GPFieldInfoEx");
2709
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Name",
2710
23.4k
                                        poGDBFieldDefn->GetName().c_str());
2711
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "FieldType",
2712
23.4k
                                        "esriFieldTypeGeometry");
2713
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "IsNullable",
2714
23.4k
                                        poGDBFieldDefn->IsNullable() ? "true"
2715
23.4k
                                                                     : "false");
2716
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Length", "0");
2717
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Precision", "0");
2718
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Scale", "0");
2719
23.4k
            CPLCreateXMLElementAndValue(GPFieldInfoEx, "Required", "true");
2720
23.4k
        }
2721
1.45M
        else
2722
1.45M
        {
2723
1.45M
            const int nOGRIdx = m_poFeatureDefn->GetFieldIndex(
2724
1.45M
                poGDBFieldDefn->GetName().c_str());
2725
1.45M
            if (nOGRIdx >= 0)
2726
1.45M
            {
2727
1.45M
                const auto poFieldDefn = m_poFeatureDefn->GetFieldDefn(nOGRIdx);
2728
1.45M
                CPLAddXMLChild(GPFieldInfoExs, CreateXMLFieldDefinition(
2729
1.45M
                                                   poFieldDefn, poGDBFieldDefn,
2730
1.45M
                                                   m_bArcGISPro32OrLater));
2731
1.45M
            }
2732
1.45M
        }
2733
1.52M
    }
2734
2735
46.4k
    CPLCreateXMLElementAndValue(psRoot, "CLSID",
2736
46.4k
                                m_eGeomType == wkbNone
2737
46.4k
                                    ? "{7A566981-C114-11D2-8A28-006097AFF44E}"
2738
46.4k
                                    : "{52353152-891A-11D0-BEC6-00805F7C4268}");
2739
46.4k
    CPLCreateXMLElementAndValue(psRoot, "EXTCLSID", "");
2740
2741
46.4k
    const char *pszLayerAlias =
2742
46.4k
        m_aosCreationOptions.FetchNameValue("LAYER_ALIAS");
2743
46.4k
    if (pszLayerAlias != nullptr)
2744
0
    {
2745
0
        CPLCreateXMLElementAndValue(psRoot, "AliasName", pszLayerAlias);
2746
0
    }
2747
2748
46.4k
    CPLCreateXMLElementAndValue(psRoot, "IsTimeInUTC",
2749
46.4k
                                m_bTimeInUTC ? "true" : " false");
2750
2751
46.4k
    if (m_eGeomType != wkbNone)
2752
23.4k
    {
2753
23.4k
        const auto poGeomFieldDefn = m_poLyrTable->GetGeomField();
2754
23.4k
        CPLCreateXMLElementAndValue(psRoot, "FeatureType", "esriFTSimple");
2755
2756
23.4k
        const char *pszShapeType = "";
2757
23.4k
        switch (m_poLyrTable->GetGeometryType())
2758
23.4k
        {
2759
0
            case FGTGT_NONE:
2760
0
                break;
2761
127
            case FGTGT_POINT:
2762
127
                pszShapeType = "esriGeometryPoint";
2763
127
                break;
2764
107
            case FGTGT_MULTIPOINT:
2765
107
                pszShapeType = "esriGeometryMultipoint";
2766
107
                break;
2767
9.31k
            case FGTGT_LINE:
2768
9.31k
                pszShapeType = "esriGeometryPolyline";
2769
9.31k
                break;
2770
130
            case FGTGT_POLYGON:
2771
130
                pszShapeType = "esriGeometryPolygon";
2772
130
                break;
2773
13.7k
            case FGTGT_MULTIPATCH:
2774
13.7k
                pszShapeType = "esriGeometryMultiPatch";
2775
13.7k
                break;
2776
23.4k
        }
2777
23.4k
        CPLCreateXMLElementAndValue(psRoot, "ShapeType", pszShapeType);
2778
23.4k
        CPLCreateXMLElementAndValue(psRoot, "ShapeFieldName",
2779
23.4k
                                    poGeomFieldDefn->GetName().c_str());
2780
2781
23.4k
        const bool bGeomTypeHasZ = CPL_TO_BOOL(OGR_GT_HasZ(m_eGeomType));
2782
23.4k
        const bool bGeomTypeHasM = CPL_TO_BOOL(OGR_GT_HasM(m_eGeomType));
2783
23.4k
        CPLCreateXMLElementAndValue(psRoot, "HasM",
2784
23.4k
                                    bGeomTypeHasM ? "true" : "false");
2785
23.4k
        CPLCreateXMLElementAndValue(psRoot, "HasZ",
2786
23.4k
                                    bGeomTypeHasZ ? "true" : "false");
2787
23.4k
        CPLCreateXMLElementAndValue(psRoot, "HasSpatialIndex", "false");
2788
23.4k
        const char *pszAreaFieldName =
2789
23.4k
            m_iAreaField >= 0
2790
23.4k
                ? m_poFeatureDefn->GetFieldDefn(m_iAreaField)->GetNameRef()
2791
23.4k
                : "";
2792
23.4k
        CPLCreateXMLElementAndValue(psRoot, "AreaFieldName", pszAreaFieldName);
2793
23.4k
        const char *pszLengthFieldName =
2794
23.4k
            m_iLengthField >= 0
2795
23.4k
                ? m_poFeatureDefn->GetFieldDefn(m_iLengthField)->GetNameRef()
2796
23.4k
                : "";
2797
23.4k
        CPLCreateXMLElementAndValue(psRoot, "LengthFieldName",
2798
23.4k
                                    pszLengthFieldName);
2799
2800
23.4k
        XMLSerializeGeomFieldBase(psRoot, poGeomFieldDefn, GetSpatialRef());
2801
23.4k
    }
2802
2803
46.4k
    char *pszDefinition = CPLSerializeXMLTree(oTree.get());
2804
46.4k
    m_osDefinition = pszDefinition;
2805
46.4k
    CPLFree(pszDefinition);
2806
46.4k
}
2807
2808
/************************************************************************/
2809
/*                            RegisterTable()                           */
2810
/************************************************************************/
2811
2812
bool OGROpenFileGDBLayer::RegisterTable()
2813
5.48k
{
2814
5.48k
    m_bRegisteredTable = true;
2815
2816
5.48k
    CPLAssert(!m_osThisGUID.empty());
2817
2818
5.48k
    const char *pszFeatureDataset =
2819
5.48k
        m_aosCreationOptions.FetchNameValue("FEATURE_DATASET");
2820
5.48k
    if (pszFeatureDataset)
2821
0
    {
2822
0
        if (!m_poDS->RegisterInItemRelationships(
2823
0
                m_osFeatureDatasetGUID, m_osThisGUID,
2824
0
                pszDatasetInFeatureDatasetUUID))
2825
0
        {
2826
0
            return false;
2827
0
        }
2828
0
    }
2829
5.48k
    else
2830
5.48k
    {
2831
5.48k
        if (!m_poDS->RegisterInItemRelationships(m_poDS->m_osRootGUID,
2832
5.48k
                                                 m_osThisGUID,
2833
                                                 // DatasetInFolder
2834
5.48k
                                                 pszDatasetInFolderUUID))
2835
0
        {
2836
0
            return false;
2837
0
        }
2838
5.48k
    }
2839
2840
5.48k
    if (m_eGeomType != wkbNone)
2841
2.03k
    {
2842
2.03k
        return m_poDS->RegisterFeatureClassInItems(
2843
2.03k
            m_osThisGUID, m_osName, m_osPath, m_poLyrTable,
2844
2.03k
            m_osDefinition.c_str(), m_osDocumentation.c_str());
2845
2.03k
    }
2846
3.44k
    else
2847
3.44k
    {
2848
3.44k
        return m_poDS->RegisterASpatialTableInItems(
2849
3.44k
            m_osThisGUID, m_osName, m_osPath, m_osDefinition.c_str(),
2850
3.44k
            m_osDocumentation.c_str());
2851
3.44k
    }
2852
5.48k
}
2853
2854
/************************************************************************/
2855
/*                             SyncToDisk()                             */
2856
/************************************************************************/
2857
2858
OGRErr OGROpenFileGDBLayer::SyncToDisk()
2859
39.0k
{
2860
39.0k
    if (!m_bEditable || m_poLyrTable == nullptr)
2861
22.6k
        return OGRERR_NONE;
2862
2863
16.4k
    if (!m_bRegisteredTable && !RegisterTable())
2864
0
        return OGRERR_FAILURE;
2865
2866
16.4k
    return m_poLyrTable->Sync() ? OGRERR_NONE : OGRERR_FAILURE;
2867
16.4k
}
2868
2869
/************************************************************************/
2870
/*                        CreateSpatialIndex()                          */
2871
/************************************************************************/
2872
2873
void OGROpenFileGDBLayer::CreateSpatialIndex()
2874
0
{
2875
0
    if (!m_bEditable)
2876
0
        return;
2877
2878
0
    if (!BuildLayerDefinition())
2879
0
        return;
2880
2881
0
    m_poLyrTable->CreateSpatialIndex();
2882
0
}
2883
2884
/************************************************************************/
2885
/*                           CreateIndex()                              */
2886
/************************************************************************/
2887
2888
void OGROpenFileGDBLayer::CreateIndex(const std::string &osIdxName,
2889
                                      const std::string &osExpression)
2890
0
{
2891
0
    if (!m_bEditable)
2892
0
        return;
2893
2894
0
    if (!BuildLayerDefinition())
2895
0
        return;
2896
2897
0
    const auto wIdxName = StringToWString(osIdxName);
2898
0
    if (EscapeReservedKeywords(wIdxName) != wIdxName)
2899
0
    {
2900
0
        CPLError(CE_Failure, CPLE_AppDefined,
2901
0
                 "Invalid index name: must not be a reserved keyword");
2902
0
        return;
2903
0
    }
2904
2905
0
    m_poLyrTable->CreateIndex(osIdxName, osExpression);
2906
0
}
2907
2908
/************************************************************************/
2909
/*                                Repack()                              */
2910
/************************************************************************/
2911
2912
bool OGROpenFileGDBLayer::Repack(GDALProgressFunc pfnProgress,
2913
                                 void *pProgressData)
2914
0
{
2915
0
    if (!m_bEditable)
2916
0
        return false;
2917
2918
0
    if (!BuildLayerDefinition())
2919
0
        return false;
2920
2921
0
    return m_poLyrTable->Repack(pfnProgress, pProgressData);
2922
0
}
2923
2924
/************************************************************************/
2925
/*                        RecomputeExtent()                             */
2926
/************************************************************************/
2927
2928
void OGROpenFileGDBLayer::RecomputeExtent()
2929
0
{
2930
0
    if (!m_bEditable)
2931
0
        return;
2932
2933
0
    if (!BuildLayerDefinition())
2934
0
        return;
2935
2936
0
    m_poLyrTable->RecomputeExtent();
2937
0
}
2938
2939
/************************************************************************/
2940
/*                        CheckFreeListConsistency()                    */
2941
/************************************************************************/
2942
2943
bool OGROpenFileGDBLayer::CheckFreeListConsistency()
2944
0
{
2945
0
    if (!BuildLayerDefinition())
2946
0
        return false;
2947
2948
0
    return m_poLyrTable->CheckFreeListConsistency();
2949
0
}
2950
2951
/************************************************************************/
2952
/*                        BeginEmulatedTransaction()                    */
2953
/************************************************************************/
2954
2955
bool OGROpenFileGDBLayer::BeginEmulatedTransaction()
2956
0
{
2957
0
    if (!BuildLayerDefinition())
2958
0
        return false;
2959
2960
0
    if (SyncToDisk() != OGRERR_NONE)
2961
0
        return false;
2962
2963
0
    bool bRet = true;
2964
2965
0
    const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
2966
0
    const std::string osThisBasename =
2967
0
        CPLGetBasenameSafe(m_osGDBFilename.c_str());
2968
0
    char **papszFiles = VSIReadDir(osThisDirname.c_str());
2969
0
    for (char **papszIter = papszFiles;
2970
0
         papszIter != nullptr && *papszIter != nullptr; ++papszIter)
2971
0
    {
2972
0
        const std::string osBasename = CPLGetBasenameSafe(*papszIter);
2973
0
        if (osBasename == osThisBasename)
2974
0
        {
2975
0
            const std::string osDestFilename = CPLFormFilenameSafe(
2976
0
                m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
2977
0
            const std::string osSourceFilename =
2978
0
                CPLFormFilenameSafe(osThisDirname.c_str(), *papszIter, nullptr);
2979
0
            if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
2980
0
                0)
2981
0
            {
2982
0
                bRet = false;
2983
0
            }
2984
0
        }
2985
0
    }
2986
0
    CSLDestroy(papszFiles);
2987
2988
0
    m_bHasCreatedBackupForTransaction = true;
2989
2990
0
    m_poFeatureDefnBackup.reset(m_poFeatureDefn->Clone());
2991
2992
0
    return bRet;
2993
0
}
2994
2995
/************************************************************************/
2996
/*                        CommitEmulatedTransaction()                   */
2997
/************************************************************************/
2998
2999
bool OGROpenFileGDBLayer::CommitEmulatedTransaction()
3000
0
{
3001
0
    m_poFeatureDefnBackup.reset();
3002
3003
0
    m_bHasCreatedBackupForTransaction = false;
3004
0
    return true;
3005
0
}
3006
3007
/************************************************************************/
3008
/*                        RollbackEmulatedTransaction()                 */
3009
/************************************************************************/
3010
3011
bool OGROpenFileGDBLayer::RollbackEmulatedTransaction()
3012
0
{
3013
0
    if (!m_bHasCreatedBackupForTransaction)
3014
0
        return true;
3015
3016
0
    SyncToDisk();
3017
3018
    // Restore feature definition
3019
0
    if (m_poFeatureDefnBackup != nullptr &&
3020
0
        !m_poFeatureDefn->IsSame(m_poFeatureDefnBackup.get()))
3021
0
    {
3022
0
        auto oTemporaryUnsealer(m_poFeatureDefn->GetTemporaryUnsealer());
3023
0
        {
3024
0
            const int nFieldCount = m_poFeatureDefn->GetFieldCount();
3025
0
            for (int i = nFieldCount - 1; i >= 0; i--)
3026
0
                m_poFeatureDefn->DeleteFieldDefn(i);
3027
0
        }
3028
0
        {
3029
0
            const int nFieldCount = m_poFeatureDefnBackup->GetFieldCount();
3030
0
            for (int i = 0; i < nFieldCount; i++)
3031
0
                m_poFeatureDefn->AddFieldDefn(
3032
0
                    m_poFeatureDefnBackup->GetFieldDefn(i));
3033
0
        }
3034
0
    }
3035
0
    m_poFeatureDefnBackup.reset();
3036
3037
0
    Close();
3038
3039
0
    bool bRet = true;
3040
3041
0
    const std::string osThisDirname = CPLGetPathSafe(m_osGDBFilename.c_str());
3042
0
    const std::string osThisBasename =
3043
0
        CPLGetBasenameSafe(m_osGDBFilename.c_str());
3044
3045
    // Delete files in working directory that match our basename
3046
0
    {
3047
0
        char **papszFiles = VSIReadDir(osThisDirname.c_str());
3048
0
        for (char **papszIter = papszFiles;
3049
0
             papszIter != nullptr && *papszIter != nullptr; ++papszIter)
3050
0
        {
3051
0
            const std::string osBasename = CPLGetBasenameSafe(*papszIter);
3052
0
            if (osBasename == osThisBasename)
3053
0
            {
3054
0
                const std::string osDestFilename = CPLFormFilenameSafe(
3055
0
                    osThisDirname.c_str(), *papszIter, nullptr);
3056
0
                VSIUnlink(osDestFilename.c_str());
3057
0
            }
3058
0
        }
3059
0
        CSLDestroy(papszFiles);
3060
0
    }
3061
3062
    // Restore backup files
3063
0
    bool bBackupFound = false;
3064
0
    {
3065
0
        char **papszFiles = VSIReadDir(m_poDS->GetBackupDirName().c_str());
3066
0
        for (char **papszIter = papszFiles;
3067
0
             papszIter != nullptr && *papszIter != nullptr; ++papszIter)
3068
0
        {
3069
0
            const std::string osBasename = CPLGetBasenameSafe(*papszIter);
3070
0
            if (osBasename == osThisBasename)
3071
0
            {
3072
0
                bBackupFound = true;
3073
0
                const std::string osDestFilename = CPLFormFilenameSafe(
3074
0
                    osThisDirname.c_str(), *papszIter, nullptr);
3075
0
                const std::string osSourceFilename = CPLFormFilenameSafe(
3076
0
                    m_poDS->GetBackupDirName().c_str(), *papszIter, nullptr);
3077
0
                if (CPLCopyFile(osDestFilename.c_str(),
3078
0
                                osSourceFilename.c_str()) != 0)
3079
0
                {
3080
0
                    bRet = false;
3081
0
                }
3082
0
            }
3083
0
        }
3084
0
        CSLDestroy(papszFiles);
3085
0
    }
3086
3087
0
    if (bBackupFound)
3088
0
    {
3089
0
        m_poLyrTable = new FileGDBTable();
3090
0
        if (m_poLyrTable->Open(m_osGDBFilename, m_bEditable, GetDescription()))
3091
0
        {
3092
0
            if (m_iGeomFieldIdx >= 0)
3093
0
            {
3094
0
                m_iGeomFieldIdx = m_poLyrTable->GetGeomFieldIdx();
3095
0
                if (m_iGeomFieldIdx < 0)
3096
0
                {
3097
0
                    Close();
3098
0
                    bRet = false;
3099
0
                }
3100
0
                else
3101
0
                {
3102
0
                    m_bValidLayerDefn = TRUE;
3103
0
                }
3104
0
            }
3105
0
            else
3106
0
            {
3107
0
                m_bValidLayerDefn = TRUE;
3108
0
            }
3109
0
        }
3110
0
        else
3111
0
        {
3112
0
            Close();
3113
0
            bRet = false;
3114
0
        }
3115
0
    }
3116
3117
0
    m_bHasCreatedBackupForTransaction = false;
3118
3119
0
    delete m_poAttributeIterator;
3120
0
    m_poAttributeIterator = nullptr;
3121
3122
0
    delete m_poIterMinMax;
3123
0
    m_poIterMinMax = nullptr;
3124
3125
0
    delete m_poSpatialIndexIterator;
3126
0
    m_poSpatialIndexIterator = nullptr;
3127
3128
0
    delete m_poCombinedIterator;
3129
0
    m_poCombinedIterator = nullptr;
3130
3131
0
    if (m_pQuadTree != nullptr)
3132
0
        CPLQuadTreeDestroy(m_pQuadTree);
3133
0
    m_pQuadTree = nullptr;
3134
3135
0
    CPLFree(m_pahFilteredFeatures);
3136
0
    m_pahFilteredFeatures = nullptr;
3137
3138
0
    m_nFilteredFeatureCount = -1;
3139
3140
0
    m_eSpatialIndexState = SPI_INVALID;
3141
3142
0
    if (m_poLyrTable && m_iGeomFieldIdx >= 0)
3143
0
    {
3144
0
        m_poGeomConverter.reset(FileGDBOGRGeometryConverter::BuildConverter(
3145
0
            m_poLyrTable->GetGeomField()));
3146
0
    }
3147
3148
0
    return bRet;
3149
0
}
3150
3151
/************************************************************************/
3152
/*                           Rename()                                   */
3153
/************************************************************************/
3154
3155
OGRErr OGROpenFileGDBLayer::Rename(const char *pszDstTableName)
3156
0
{
3157
0
    if (!m_bEditable)
3158
0
        return OGRERR_FAILURE;
3159
3160
0
    if (!BuildLayerDefinition())
3161
0
        return OGRERR_FAILURE;
3162
3163
0
    if (SyncToDisk() != OGRERR_NONE)
3164
0
        return OGRERR_FAILURE;
3165
3166
0
    if (m_poDS->IsInTransaction() &&
3167
0
        ((!m_bHasCreatedBackupForTransaction && !BeginEmulatedTransaction()) ||
3168
0
         !m_poDS->BackupSystemTablesForTransaction()))
3169
0
    {
3170
0
        return OGRERR_FAILURE;
3171
0
    }
3172
3173
0
    const std::string osLaunderedName(GetLaunderedLayerName(pszDstTableName));
3174
0
    if (pszDstTableName != osLaunderedName)
3175
0
    {
3176
0
        CPLError(CE_Failure, CPLE_AppDefined,
3177
0
                 "%s is not a valid layer name. %s would be a valid one.",
3178
0
                 pszDstTableName, osLaunderedName.c_str());
3179
0
        return OGRERR_FAILURE;
3180
0
    }
3181
3182
0
    if (m_poDS->GetLayerByName(pszDstTableName) != nullptr)
3183
0
    {
3184
0
        CPLError(CE_Failure, CPLE_AppDefined, "Layer %s already exists",
3185
0
                 pszDstTableName);
3186
0
        return OGRERR_FAILURE;
3187
0
    }
3188
3189
0
    const std::string osOldName(m_osName);
3190
3191
0
    m_osName = pszDstTableName;
3192
0
    SetDescription(pszDstTableName);
3193
0
    whileUnsealing(m_poFeatureDefn)->SetName(pszDstTableName);
3194
3195
0
    auto nLastSlashPos = m_osPath.rfind('\\');
3196
0
    if (nLastSlashPos != std::string::npos)
3197
0
    {
3198
0
        m_osPath.resize(nLastSlashPos + 1);
3199
0
    }
3200
0
    else
3201
0
    {
3202
0
        m_osPath = '\\';
3203
0
    }
3204
0
    m_osPath += m_osName;
3205
3206
0
    RefreshXMLDefinitionInMemory();
3207
3208
    // Update GDB_SystemCatalog
3209
0
    {
3210
0
        FileGDBTable oTable;
3211
0
        if (!oTable.Open(m_poDS->m_osGDBSystemCatalogFilename.c_str(), true))
3212
0
            return OGRERR_FAILURE;
3213
3214
0
        FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
3215
3216
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
3217
0
             ++iCurFeat)
3218
0
        {
3219
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
3220
0
            if (iCurFeat < 0)
3221
0
                break;
3222
0
            const auto psName = oTable.GetFieldValue(iName);
3223
0
            if (psName && psName->String == osOldName)
3224
0
            {
3225
0
                auto asFields = oTable.GetAllFieldValues();
3226
3227
0
                CPLFree(asFields[iName].String);
3228
0
                asFields[iName].String = CPLStrdup(m_osName.c_str());
3229
3230
0
                bool bRet =
3231
0
                    oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
3232
0
                    oTable.Sync();
3233
0
                oTable.FreeAllFieldValues(asFields);
3234
0
                if (!bRet)
3235
0
                    return OGRERR_FAILURE;
3236
0
                break;
3237
0
            }
3238
0
        }
3239
0
    }
3240
3241
    // Update GDB_Items
3242
0
    {
3243
0
        FileGDBTable oTable;
3244
0
        if (!oTable.Open(m_poDS->m_osGDBItemsFilename.c_str(), true))
3245
0
            return OGRERR_FAILURE;
3246
3247
0
        FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
3248
0
        FETCH_FIELD_IDX_WITH_RET(iPath, "Path", FGFT_STRING, OGRERR_FAILURE);
3249
0
        FETCH_FIELD_IDX_WITH_RET(iPhysicalName, "PhysicalName", FGFT_STRING,
3250
0
                                 OGRERR_FAILURE);
3251
0
        FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML,
3252
0
                                 OGRERR_FAILURE);
3253
3254
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
3255
0
             ++iCurFeat)
3256
0
        {
3257
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
3258
0
            if (iCurFeat < 0)
3259
0
                break;
3260
0
            const auto psName = oTable.GetFieldValue(iName);
3261
0
            if (psName && psName->String == osOldName)
3262
0
            {
3263
0
                auto asFields = oTable.GetAllFieldValues();
3264
3265
0
                CPLFree(asFields[iName].String);
3266
0
                asFields[iName].String = CPLStrdup(m_osName.c_str());
3267
3268
0
                if (!OGR_RawField_IsNull(&asFields[iPath]) &&
3269
0
                    !OGR_RawField_IsUnset(&asFields[iPath]))
3270
0
                {
3271
0
                    CPLFree(asFields[iPath].String);
3272
0
                }
3273
0
                asFields[iPath].String = CPLStrdup(m_osPath.c_str());
3274
3275
0
                if (!OGR_RawField_IsNull(&asFields[iPhysicalName]) &&
3276
0
                    !OGR_RawField_IsUnset(&asFields[iPhysicalName]))
3277
0
                {
3278
0
                    CPLFree(asFields[iPhysicalName].String);
3279
0
                }
3280
0
                CPLString osUCName(m_osName);
3281
0
                osUCName.toupper();
3282
0
                asFields[iPhysicalName].String = CPLStrdup(osUCName.c_str());
3283
3284
0
                if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
3285
0
                    !OGR_RawField_IsUnset(&asFields[iDefinition]))
3286
0
                {
3287
0
                    CPLFree(asFields[iDefinition].String);
3288
0
                }
3289
0
                asFields[iDefinition].String =
3290
0
                    CPLStrdup(m_osDefinition.c_str());
3291
3292
0
                bool bRet =
3293
0
                    oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr) &&
3294
0
                    oTable.Sync();
3295
0
                oTable.FreeAllFieldValues(asFields);
3296
0
                if (!bRet)
3297
0
                    return OGRERR_FAILURE;
3298
0
                break;
3299
0
            }
3300
0
        }
3301
0
    }
3302
3303
0
    return OGRERR_NONE;
3304
0
}