Coverage Report

Created: 2026-05-16 08:20

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