Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_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
16
#include <stddef.h>
17
#include <stdio.h>
18
#include <string.h>
19
#include <limits>
20
#include <map>
21
#include <memory>
22
#include <string>
23
#include <utility>
24
#include <vector>
25
26
#include "cpl_conv.h"
27
#include "cpl_error.h"
28
#include "cpl_string.h"
29
#include "cpl_vsi.h"
30
#include "filegdbtable.h"
31
#include "gdal.h"
32
#include "ogr_core.h"
33
#include "ogrsf_frmts.h"
34
35
#include "filegdb_fielddomain.h"
36
#include "filegdb_relationship.h"
37
38
/************************************************************************/
39
/*                       GetExistingSpatialRef()                        */
40
/************************************************************************/
41
42
bool OGROpenFileGDBDataSource::GetExistingSpatialRef(
43
    const std::string &osWKT, double dfXOrigin, double dfYOrigin,
44
    double dfXYScale, double dfZOrigin, double dfZScale, double dfMOrigin,
45
    double dfMScale, double dfXYTolerance, double dfZTolerance,
46
    double dfMTolerance)
47
2.28k
{
48
2.28k
    FileGDBTable oTable;
49
2.28k
    if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), false))
50
0
        return false;
51
52
2.28k
    FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
53
2.28k
    FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
54
2.28k
    FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
55
2.28k
    FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
56
2.28k
    FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
57
2.28k
    FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
58
2.28k
    FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
59
2.28k
    FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
60
2.28k
    FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
61
2.28k
    FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
62
2.28k
    FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
63
64
2.28k
    int64_t iCurFeat = 0;
65
5.25k
    while (iCurFeat < oTable.GetTotalRecordCount())
66
4.56k
    {
67
4.56k
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
68
4.56k
        if (iCurFeat < 0)
69
0
            break;
70
4.56k
        iCurFeat++;
71
4.56k
        const auto psSRTEXT = oTable.GetFieldValue(iSRTEXT);
72
4.56k
        if (psSRTEXT && psSRTEXT->String == osWKT)
73
1.58k
        {
74
1.58k
            const auto fetchRealVal = [&oTable](int idx, double dfExpected)
75
15.8k
            {
76
15.8k
                const auto psVal = oTable.GetFieldValue(idx);
77
15.8k
                return psVal && psVal->Real == dfExpected;
78
15.8k
            };
79
1.58k
            if (fetchRealVal(iFalseX, dfXOrigin) &&
80
1.58k
                fetchRealVal(iFalseY, dfYOrigin) &&
81
1.58k
                fetchRealVal(iXYUnits, dfXYScale) &&
82
1.58k
                fetchRealVal(iFalseZ, dfZOrigin) &&
83
1.58k
                fetchRealVal(iZUnits, dfZScale) &&
84
1.58k
                fetchRealVal(iFalseM, dfMOrigin) &&
85
1.58k
                fetchRealVal(iMUnits, dfMScale) &&
86
1.58k
                fetchRealVal(iXYTolerance, dfXYTolerance) &&
87
1.58k
                fetchRealVal(iZTolerance, dfZTolerance) &&
88
1.58k
                fetchRealVal(iMTolerance, dfMTolerance))
89
1.58k
            {
90
1.58k
                return true;
91
1.58k
            }
92
1.58k
        }
93
4.56k
    }
94
693
    return false;
95
2.28k
}
96
97
/************************************************************************/
98
/*                          AddNewSpatialRef()                          */
99
/************************************************************************/
100
101
bool OGROpenFileGDBDataSource::AddNewSpatialRef(
102
    const std::string &osWKT, double dfXOrigin, double dfYOrigin,
103
    double dfXYScale, double dfZOrigin, double dfZScale, double dfMOrigin,
104
    double dfMScale, double dfXYTolerance, double dfZTolerance,
105
    double dfMTolerance)
106
1.28k
{
107
1.28k
    FileGDBTable oTable;
108
1.28k
    if (!oTable.Open(m_osGDBSpatialRefsFilename.c_str(), true))
109
0
        return false;
110
111
1.28k
    FETCH_FIELD_IDX(iSRTEXT, "SRTEXT", FGFT_STRING);
112
1.28k
    FETCH_FIELD_IDX(iFalseX, "FalseX", FGFT_FLOAT64);
113
1.28k
    FETCH_FIELD_IDX(iFalseY, "FalseY", FGFT_FLOAT64);
114
1.28k
    FETCH_FIELD_IDX(iXYUnits, "XYUnits", FGFT_FLOAT64);
115
1.28k
    FETCH_FIELD_IDX(iFalseZ, "FalseZ", FGFT_FLOAT64);
116
1.28k
    FETCH_FIELD_IDX(iZUnits, "ZUnits", FGFT_FLOAT64);
117
1.28k
    FETCH_FIELD_IDX(iFalseM, "FalseM", FGFT_FLOAT64);
118
1.28k
    FETCH_FIELD_IDX(iMUnits, "MUnits", FGFT_FLOAT64);
119
1.28k
    FETCH_FIELD_IDX(iXYTolerance, "XYTolerance", FGFT_FLOAT64);
120
1.28k
    FETCH_FIELD_IDX(iZTolerance, "ZTolerance", FGFT_FLOAT64);
121
1.28k
    FETCH_FIELD_IDX(iMTolerance, "MTolerance", FGFT_FLOAT64);
122
123
1.28k
    std::vector<OGRField> fields(oTable.GetFieldCount(),
124
1.28k
                                 FileGDBField::UNSET_FIELD);
125
1.28k
    fields[iSRTEXT].String = const_cast<char *>(osWKT.c_str());
126
1.28k
    fields[iFalseX].Real = dfXOrigin;
127
1.28k
    fields[iFalseY].Real = dfYOrigin;
128
1.28k
    fields[iXYUnits].Real = dfXYScale;
129
1.28k
    fields[iFalseZ].Real = dfZOrigin;
130
1.28k
    fields[iZUnits].Real = dfZScale;
131
1.28k
    fields[iFalseM].Real = dfMOrigin;
132
1.28k
    fields[iMUnits].Real = dfMScale;
133
1.28k
    fields[iXYTolerance].Real = dfXYTolerance;
134
1.28k
    fields[iZTolerance].Real = dfZTolerance;
135
1.28k
    fields[iMTolerance].Real = dfMTolerance;
136
137
1.28k
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
138
1.28k
}
139
140
/************************************************************************/
141
/*                    RegisterLayerInSystemCatalog()                    */
142
/************************************************************************/
143
144
bool OGROpenFileGDBDataSource::RegisterLayerInSystemCatalog(
145
    const std::string &osLayerName)
146
5.08k
{
147
5.08k
    FileGDBTable oTable;
148
5.08k
    if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
149
0
        return false;
150
151
5.08k
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
152
5.08k
    FETCH_FIELD_IDX(iFileFormat, "FileFormat", FGFT_INT32);
153
154
5.08k
    std::vector<OGRField> fields(oTable.GetFieldCount(),
155
5.08k
                                 FileGDBField::UNSET_FIELD);
156
5.08k
    fields[iName].String = const_cast<char *>(osLayerName.c_str());
157
5.08k
    fields[iFileFormat].Integer = 0;
158
5.08k
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
159
5.08k
}
160
161
/************************************************************************/
162
/*                    RegisterInItemRelationships()                     */
163
/************************************************************************/
164
165
bool OGROpenFileGDBDataSource::RegisterInItemRelationships(
166
    const std::string &osOriginGUID, const std::string &osDestGUID,
167
    const std::string &osTypeGUID)
168
5.08k
{
169
5.08k
    FileGDBTable oTable;
170
5.08k
    if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
171
0
        return false;
172
173
5.08k
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
174
5.08k
    FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
175
5.08k
    FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
176
5.08k
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
177
5.08k
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
178
179
5.08k
    std::vector<OGRField> fields(oTable.GetFieldCount(),
180
5.08k
                                 FileGDBField::UNSET_FIELD);
181
5.08k
    const std::string osGUID = OFGDBGenerateUUID();
182
5.08k
    fields[iUUID].String = const_cast<char *>(osGUID.c_str());
183
5.08k
    fields[iOriginID].String = const_cast<char *>(osOriginGUID.c_str());
184
5.08k
    fields[iDestID].String = const_cast<char *>(osDestGUID.c_str());
185
5.08k
    fields[iType].String = const_cast<char *>(osTypeGUID.c_str());
186
5.08k
    fields[iProperties].Integer = 1;
187
5.08k
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
188
5.08k
}
189
190
/************************************************************************/
191
/*              RegisterRelationshipInItemRelationships()               */
192
/************************************************************************/
193
194
bool OGROpenFileGDBDataSource::RegisterRelationshipInItemRelationships(
195
    const std::string &osRelationshipGUID, const std::string &osOriginGUID,
196
    const std::string &osDestGUID)
197
0
{
198
    // Relationships to register:
199
    // 1. Origin table -> new relationship as DatasetsRelatedThrough
200
    // 2. Destination table -> new relationship as DatasetsRelatedThrough
201
    // 3. Root dataset -> new relationship as DatasetInFolder
202
0
    if (!RegisterInItemRelationships(osOriginGUID, osRelationshipGUID,
203
0
                                     pszDatasetsRelatedThroughUUID))
204
0
        return false;
205
0
    if (!RegisterInItemRelationships(osDestGUID, osRelationshipGUID,
206
0
                                     pszDatasetsRelatedThroughUUID))
207
0
        return false;
208
0
    if (!RegisterInItemRelationships(m_osRootGUID, osRelationshipGUID,
209
0
                                     pszDatasetInFolderUUID))
210
0
        return false;
211
212
0
    return true;
213
0
}
214
215
/************************************************************************/
216
/*              RemoveRelationshipFromItemRelationships()               */
217
/************************************************************************/
218
219
bool OGROpenFileGDBDataSource::RemoveRelationshipFromItemRelationships(
220
    const std::string &osRelationshipGUID)
221
0
{
222
0
    FileGDBTable oTable;
223
0
    if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
224
0
        return false;
225
226
    // while we've only found item relationships with the relationship UUID in
227
    // the DestID field, let's be super-careful and also check against the
228
    // OriginID UUID, just in case there's some previously unencountered
229
    // situations where a relationship UUID is placed in OriginID
230
0
    FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID, false);
231
0
    FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, false);
232
233
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
234
0
         ++iCurFeat)
235
0
    {
236
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
237
0
        if (iCurFeat < 0)
238
0
            break;
239
240
0
        const auto psOriginID = oTable.GetFieldValue(iOriginID);
241
0
        if (psOriginID && psOriginID->String == osRelationshipGUID)
242
0
        {
243
0
            oTable.DeleteFeature(iCurFeat + 1);
244
0
        }
245
0
        else
246
0
        {
247
0
            const auto psDestID = oTable.GetFieldValue(iDestID);
248
0
            if (psDestID && psDestID->String == osRelationshipGUID)
249
0
            {
250
0
                oTable.DeleteFeature(iCurFeat + 1);
251
0
            }
252
0
        }
253
0
    }
254
255
0
    return true;
256
0
}
257
258
/************************************************************************/
259
/*                         LinkDomainToTable()                          */
260
/************************************************************************/
261
262
bool OGROpenFileGDBDataSource::LinkDomainToTable(
263
    const std::string &osDomainName, const std::string &osLayerGUID)
264
0
{
265
0
    std::string osDomainUUID;
266
0
    if (!FindUUIDFromName(osDomainName, osDomainUUID))
267
0
        return false;
268
269
    // Check if the domain is already linked to this table
270
0
    {
271
0
        FileGDBTable oTable;
272
0
        if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), false))
273
0
            return false;
274
275
0
        FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
276
0
        FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
277
278
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
279
0
             ++iCurFeat)
280
0
        {
281
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
282
0
            if (iCurFeat < 0)
283
0
                break;
284
285
0
            const auto psOriginID = oTable.GetFieldValue(iOriginID);
286
0
            if (psOriginID && EQUAL(psOriginID->String, osLayerGUID.c_str()))
287
0
            {
288
0
                const auto psDestID = oTable.GetFieldValue(iDestID);
289
0
                if (psDestID && EQUAL(psDestID->String, osDomainUUID.c_str()))
290
0
                {
291
0
                    return true;
292
0
                }
293
0
            }
294
0
        }
295
0
    }
296
297
0
    return RegisterInItemRelationships(osLayerGUID, osDomainUUID,
298
0
                                       pszDomainInDatasetUUID);
299
0
}
300
301
/************************************************************************/
302
/*                        UnlinkDomainToTable()                         */
303
/************************************************************************/
304
305
bool OGROpenFileGDBDataSource::UnlinkDomainToTable(
306
    const std::string &osDomainName, const std::string &osLayerGUID)
307
0
{
308
0
    std::string osDomainUUID;
309
0
    if (!FindUUIDFromName(osDomainName, osDomainUUID))
310
0
        return false;
311
312
0
    FileGDBTable oTable;
313
0
    if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
314
0
        return false;
315
316
0
    FETCH_FIELD_IDX(iOriginID, "OriginID", FGFT_GUID);
317
0
    FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
318
319
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
320
0
         ++iCurFeat)
321
0
    {
322
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
323
0
        if (iCurFeat < 0)
324
0
            break;
325
326
0
        const auto psOriginID = oTable.GetFieldValue(iOriginID);
327
0
        if (psOriginID && EQUAL(psOriginID->String, osLayerGUID.c_str()))
328
0
        {
329
0
            const auto psDestID = oTable.GetFieldValue(iDestID);
330
0
            if (psDestID && EQUAL(psDestID->String, osDomainUUID.c_str()))
331
0
            {
332
0
                return oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync();
333
0
            }
334
0
        }
335
0
    }
336
337
0
    return true;
338
0
}
339
340
/************************************************************************/
341
/*                        UpdateXMLDefinition()                         */
342
/************************************************************************/
343
344
bool OGROpenFileGDBDataSource::UpdateXMLDefinition(
345
    const std::string &osLayerName, const char *pszXMLDefinition)
346
0
{
347
0
    FileGDBTable oTable;
348
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
349
0
        return false;
350
351
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
352
0
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
353
354
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
355
0
         ++iCurFeat)
356
0
    {
357
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
358
0
        if (iCurFeat < 0)
359
0
            break;
360
0
        const auto psName = oTable.GetFieldValue(iName);
361
0
        if (psName && psName->String == osLayerName)
362
0
        {
363
0
            auto asFields = oTable.GetAllFieldValues();
364
0
            if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
365
0
                !OGR_RawField_IsUnset(&asFields[iDefinition]))
366
0
            {
367
0
                CPLFree(asFields[iDefinition].String);
368
0
            }
369
0
            asFields[iDefinition].String = CPLStrdup(pszXMLDefinition);
370
0
            bool bRet = oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
371
0
            oTable.FreeAllFieldValues(asFields);
372
0
            return bRet;
373
0
        }
374
0
    }
375
376
0
    CPLError(CE_Failure, CPLE_AppDefined,
377
0
             "Cannot find record for Name=%s in GDB_Items table",
378
0
             osLayerName.c_str());
379
0
    return false;
380
0
}
381
382
/************************************************************************/
383
/*                          FindUUIDFromName()                          */
384
/************************************************************************/
385
386
bool OGROpenFileGDBDataSource::FindUUIDFromName(const std::string &osName,
387
                                                std::string &osUUIDOut)
388
0
{
389
0
    FileGDBTable oTable;
390
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
391
0
        return false;
392
393
0
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
394
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
395
396
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
397
0
         ++iCurFeat)
398
0
    {
399
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
400
0
        if (iCurFeat < 0)
401
0
            break;
402
0
        const auto psName = oTable.GetFieldValue(iName);
403
0
        if (psName && psName->String == osName)
404
0
        {
405
0
            const auto psUUID = oTable.GetFieldValue(iUUID);
406
0
            if (psUUID)
407
0
            {
408
0
                osUUIDOut = psUUID->String;
409
0
                return true;
410
0
            }
411
0
        }
412
0
    }
413
414
0
    return false;
415
0
}
416
417
/************************************************************************/
418
/*                   RegisterFeatureDatasetInItems()                    */
419
/************************************************************************/
420
421
bool OGROpenFileGDBDataSource::RegisterFeatureDatasetInItems(
422
    const std::string &osFeatureDatasetGUID, const std::string &osName,
423
    const char *pszXMLDefinition)
424
0
{
425
0
    FileGDBTable oTable;
426
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
427
0
        return false;
428
429
0
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
430
0
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
431
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
432
0
    FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
433
0
    FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
434
0
    FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
435
0
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
436
0
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
437
438
0
    std::vector<OGRField> fields(oTable.GetFieldCount(),
439
0
                                 FileGDBField::UNSET_FIELD);
440
0
    fields[iUUID].String = const_cast<char *>(osFeatureDatasetGUID.c_str());
441
0
    fields[iType].String = const_cast<char *>(pszFeatureDatasetTypeUUID);
442
0
    fields[iName].String = const_cast<char *>(osName.c_str());
443
0
    CPLString osUCName(osName);
444
0
    osUCName.toupper();
445
0
    fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
446
0
    std::string osPath("\\");
447
0
    osPath += osName;
448
0
    fields[iPath].String = const_cast<char *>(osPath.c_str());
449
0
    fields[iURL].String = const_cast<char *>("");
450
0
    fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
451
0
    fields[iProperties].Integer = 1;
452
0
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
453
0
}
454
455
/************************************************************************/
456
/*                    RegisterFeatureClassInItems()                     */
457
/************************************************************************/
458
459
bool OGROpenFileGDBDataSource::RegisterFeatureClassInItems(
460
    const std::string &osLayerGUID, const std::string &osLayerName,
461
    const std::string &osPath, const FileGDBTable *poLyrTable,
462
    const char *pszXMLDefinition, const char *pszDocumentation)
463
2.28k
{
464
2.28k
    FileGDBTable oTable;
465
2.28k
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
466
0
        return false;
467
468
2.28k
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
469
2.28k
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
470
2.28k
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
471
2.28k
    FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
472
2.28k
    FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
473
2.28k
    FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
474
2.28k
    FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
475
2.28k
    FETCH_FIELD_IDX(iDatasetInfo1, "DatasetInfo1", FGFT_STRING);
476
2.28k
    FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
477
2.28k
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
478
2.28k
    FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
479
2.28k
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
480
481
2.28k
    std::vector<OGRField> fields(oTable.GetFieldCount(),
482
2.28k
                                 FileGDBField::UNSET_FIELD);
483
2.28k
    fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
484
2.28k
    fields[iType].String = const_cast<char *>(pszFeatureClassTypeUUID);
485
2.28k
    fields[iName].String = const_cast<char *>(osLayerName.c_str());
486
2.28k
    CPLString osUCName(osLayerName);
487
2.28k
    osUCName.toupper();
488
2.28k
    fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
489
2.28k
    fields[iPath].String = const_cast<char *>(osPath.c_str());
490
2.28k
    fields[iDatasetSubtype1].Integer = 1;
491
2.28k
    fields[iDatasetSubtype2].Integer = poLyrTable->GetGeometryType();
492
2.28k
    const auto poGeomFieldDefn = poLyrTable->GetGeomField();
493
2.28k
    if (poGeomFieldDefn)  // should always be true
494
2.28k
        fields[iDatasetInfo1].String =
495
2.28k
            const_cast<char *>(poGeomFieldDefn->GetName().c_str());
496
2.28k
    fields[iURL].String = const_cast<char *>("");
497
2.28k
    fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
498
2.28k
    if (pszDocumentation && pszDocumentation[0])
499
0
        fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
500
2.28k
    fields[iProperties].Integer = 1;
501
2.28k
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
502
2.28k
}
503
504
/************************************************************************/
505
/*                    RegisterASpatialTableInItems()                    */
506
/************************************************************************/
507
508
bool OGROpenFileGDBDataSource::RegisterASpatialTableInItems(
509
    const std::string &osLayerGUID, const std::string &osLayerName,
510
    const std::string &osPath, const char *pszXMLDefinition,
511
    const char *pszDocumentation)
512
2.80k
{
513
2.80k
    FileGDBTable oTable;
514
2.80k
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
515
0
        return false;
516
517
2.80k
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
518
2.80k
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
519
2.80k
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
520
2.80k
    FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
521
2.80k
    FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
522
2.80k
    FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
523
2.80k
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
524
2.80k
    FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
525
2.80k
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
526
527
2.80k
    std::vector<OGRField> fields(oTable.GetFieldCount(),
528
2.80k
                                 FileGDBField::UNSET_FIELD);
529
2.80k
    fields[iUUID].String = const_cast<char *>(osLayerGUID.c_str());
530
2.80k
    fields[iType].String = const_cast<char *>(pszTableTypeUUID);
531
2.80k
    fields[iName].String = const_cast<char *>(osLayerName.c_str());
532
2.80k
    CPLString osUCName(osLayerName);
533
2.80k
    osUCName.toupper();
534
2.80k
    fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
535
2.80k
    fields[iPath].String = const_cast<char *>(osPath.c_str());
536
2.80k
    fields[iURL].String = const_cast<char *>("");
537
2.80k
    fields[iDefinition].String = const_cast<char *>(pszXMLDefinition);
538
2.80k
    if (pszDocumentation && pszDocumentation[0])
539
0
        fields[iDocumentation].String = const_cast<char *>(pszDocumentation);
540
2.80k
    fields[iProperties].Integer = 1;
541
2.80k
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
542
2.80k
}
543
544
/************************************************************************/
545
/*                       CreateGDBSystemCatalog()                       */
546
/************************************************************************/
547
548
bool OGROpenFileGDBDataSource::CreateGDBSystemCatalog()
549
592
{
550
    // Write GDB_SystemCatalog file
551
592
    m_osGDBSystemCatalogFilename =
552
592
        CPLFormFilenameSafe(m_osDirName.c_str(), "a00000001.gdbtable", nullptr);
553
592
    FileGDBTable oTable;
554
592
    if (!oTable.Create(m_osGDBSystemCatalogFilename.c_str(), 4, FGTGT_NONE,
555
592
                       false, false) ||
556
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
557
592
            "ID", std::string(), FGFT_OBJECTID,
558
592
            /* bNullable = */ false,
559
592
            /* bRequired = */ true,
560
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
561
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
562
592
            "Name", std::string(), FGFT_STRING,
563
592
            /* bNullable = */ false,
564
592
            /* bRequired = */ false,
565
592
            /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
566
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
567
592
            "FileFormat", std::string(), FGFT_INT32,
568
592
            /* bNullable = */ false,
569
592
            /* bRequired = */ false,
570
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
571
0
    {
572
0
        return false;
573
0
    }
574
575
592
    std::vector<OGRField> fields(oTable.GetFieldCount(),
576
592
                                 FileGDBField::UNSET_FIELD);
577
578
592
    for (const auto &pair : std::vector<std::pair<const char *, int>>{
579
592
             {"GDB_SystemCatalog", 0},
580
592
             {"GDB_DBTune", 0},
581
592
             {"GDB_SpatialRefs", 0},
582
592
             {"GDB_Items", 0},
583
592
             {"GDB_ItemTypes", 0},
584
592
             {"GDB_ItemRelationships", 0},
585
592
             {"GDB_ItemRelationshipTypes", 0},
586
592
             {"GDB_ReplicaLog", 2}})
587
4.73k
    {
588
4.73k
        fields[1].String = const_cast<char *>(pair.first);
589
4.73k
        fields[2].Integer = pair.second;
590
4.73k
        if (!oTable.CreateFeature(fields, nullptr))
591
0
            return false;
592
4.73k
    }
593
594
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
595
592
        this, m_osGDBSystemCatalogFilename.c_str(), "GDB_SystemCatalog", "", "",
596
592
        true));
597
598
592
    return oTable.Sync();
599
592
}
600
601
/************************************************************************/
602
/*                          CreateGDBDBTune()                           */
603
/************************************************************************/
604
605
bool OGROpenFileGDBDataSource::CreateGDBDBTune()
606
592
{
607
    // Write GDB_DBTune file
608
592
    const std::string osFilename(CPLFormFilenameSafe(
609
592
        m_osDirName.c_str(), "a00000002.gdbtable", nullptr));
610
592
    FileGDBTable oTable;
611
592
    if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
612
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
613
592
            "Keyword", std::string(), FGFT_STRING,
614
592
            /* bNullable = */ false,
615
592
            /* bRequired = */ false,
616
592
            /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
617
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
618
592
            "ParameterName", std::string(), FGFT_STRING,
619
592
            /* bNullable = */ false,
620
592
            /* bRequired = */ false,
621
592
            /* bEditable = */ true, 32, FileGDBField::UNSET_FIELD)) ||
622
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
623
592
            "ConfigString", std::string(), FGFT_STRING,
624
592
            /* bNullable = */ true,
625
592
            /* bRequired = */ false,
626
592
            /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)))
627
0
    {
628
0
        return false;
629
0
    }
630
631
592
    std::vector<OGRField> fields(oTable.GetFieldCount(),
632
592
                                 FileGDBField::UNSET_FIELD);
633
634
592
    static const struct
635
592
    {
636
592
        const char *pszKeyword;
637
592
        const char *pszParameterName;
638
592
        const char *pszConfigString;
639
592
    } apsData[] = {
640
592
        {"DEFAULTS", "UI_TEXT", "The default datafile configuration."},
641
592
        {"DEFAULTS", "CHARACTER_FORMAT", "UTF8"},
642
592
        {"DEFAULTS", "GEOMETRY_FORMAT", "Compressed"},
643
592
        {"DEFAULTS", "GEOMETRY_STORAGE", "InLine"},
644
592
        {"DEFAULTS", "BLOB_STORAGE", "InLine"},
645
592
        {"DEFAULTS", "MAX_FILE_SIZE", "1TB"},
646
592
        {"DEFAULTS", "RASTER_STORAGE", "InLine"},
647
592
        {"TEXT_UTF16", "UI_TEXT", "The UTF16 text format configuration."},
648
592
        {"TEXT_UTF16", "CHARACTER_FORMAT", "UTF16"},
649
592
        {"MAX_FILE_SIZE_4GB", "UI_TEXT",
650
592
         "The 4GB maximum datafile size configuration."},
651
592
        {"MAX_FILE_SIZE_4GB", "MAX_FILE_SIZE", "4GB"},
652
592
        {"MAX_FILE_SIZE_256TB", "UI_TEXT",
653
592
         "The 256TB maximum datafile size configuration."},
654
592
        {"MAX_FILE_SIZE_256TB", "MAX_FILE_SIZE", "256TB"},
655
592
        {"GEOMETRY_UNCOMPRESSED", "UI_TEXT",
656
592
         "The Uncompressed Geometry configuration."},
657
592
        {"GEOMETRY_UNCOMPRESSED", "GEOMETRY_FORMAT", "Uncompressed"},
658
592
        {"GEOMETRY_OUTOFLINE", "UI_TEXT",
659
592
         "The Outofline Geometry configuration."},
660
592
        {"GEOMETRY_OUTOFLINE", "GEOMETRY_STORAGE", "OutOfLine"},
661
592
        {"BLOB_OUTOFLINE", "UI_TEXT", "The Outofline Blob configuration."},
662
592
        {"BLOB_OUTOFLINE", "BLOB_STORAGE", "OutOfLine"},
663
592
        {"GEOMETRY_AND_BLOB_OUTOFLINE", "UI_TEXT",
664
592
         "The Outofline Geometry and Blob configuration."},
665
592
        {"GEOMETRY_AND_BLOB_OUTOFLINE", "GEOMETRY_STORAGE", "OutOfLine"},
666
592
        {"GEOMETRY_AND_BLOB_OUTOFLINE", "BLOB_STORAGE", "OutOfLine"},
667
592
        {"TERRAIN_DEFAULTS", "UI_TERRAIN_TEXT",
668
592
         "The terrains default configuration."},
669
592
        {"TERRAIN_DEFAULTS", "GEOMETRY_STORAGE", "OutOfLine"},
670
592
        {"TERRAIN_DEFAULTS", "BLOB_STORAGE", "OutOfLine"},
671
592
        {"MOSAICDATASET_DEFAULTS", "UI_MOSAIC_TEXT",
672
592
         "The Outofline Raster and Blob configuration."},
673
592
        {"MOSAICDATASET_DEFAULTS", "RASTER_STORAGE", "OutOfLine"},
674
592
        {"MOSAICDATASET_DEFAULTS", "BLOB_STORAGE", "OutOfLine"},
675
592
        {"MOSAICDATASET_INLINE", "UI_MOSAIC_TEXT",
676
592
         "The mosaic dataset inline configuration."},
677
592
        {"MOSAICDATASET_INLINE", "CHARACTER_FORMAT", "UTF8"},
678
592
        {"MOSAICDATASET_INLINE", "GEOMETRY_FORMAT", "Compressed"},
679
592
        {"MOSAICDATASET_INLINE", "GEOMETRY_STORAGE", "InLine"},
680
592
        {"MOSAICDATASET_INLINE", "BLOB_STORAGE", "InLine"},
681
592
        {"MOSAICDATASET_INLINE", "MAX_FILE_SIZE", "1TB"},
682
592
        {"MOSAICDATASET_INLINE", "RASTER_STORAGE", "InLine"}};
683
684
592
    for (const auto &record : apsData)
685
20.7k
    {
686
20.7k
        fields[0].String = const_cast<char *>(record.pszKeyword);
687
20.7k
        fields[1].String = const_cast<char *>(record.pszParameterName);
688
20.7k
        fields[2].String = const_cast<char *>(record.pszConfigString);
689
20.7k
        if (!oTable.CreateFeature(fields, nullptr))
690
0
            return false;
691
20.7k
    }
692
693
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
694
592
        this, osFilename.c_str(), "GDB_DBTune", "", "", true));
695
696
592
    return oTable.Sync();
697
592
}
698
699
/************************************************************************/
700
/*                        CreateGDBSpatialRefs()                        */
701
/************************************************************************/
702
703
bool OGROpenFileGDBDataSource::CreateGDBSpatialRefs()
704
592
{
705
    // Write GDB_SpatialRefs file
706
592
    m_osGDBSpatialRefsFilename =
707
592
        CPLFormFilenameSafe(m_osDirName.c_str(), "a00000003.gdbtable", nullptr);
708
592
    FileGDBTable oTable;
709
592
    if (!oTable.Create(m_osGDBSpatialRefsFilename.c_str(), 4, FGTGT_NONE, false,
710
592
                       false) ||
711
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
712
592
            "ID", std::string(), FGFT_OBJECTID,
713
592
            /* bNullable = */ false,
714
592
            /* bRequired = */ true,
715
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
716
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
717
592
            "SRTEXT", std::string(), FGFT_STRING,
718
592
            /* bNullable = */ false,
719
592
            /* bRequired = */ false,
720
592
            /* bEditable = */ true, 2048, FileGDBField::UNSET_FIELD)) ||
721
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
722
592
            "FalseX", std::string(), FGFT_FLOAT64,
723
592
            /* bNullable = */ true,
724
592
            /* bRequired = */ false,
725
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
726
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
727
592
            "FalseY", std::string(), FGFT_FLOAT64,
728
592
            /* bNullable = */ true,
729
592
            /* bRequired = */ false,
730
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
731
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
732
592
            "XYUnits", std::string(), FGFT_FLOAT64,
733
592
            /* bNullable = */ true,
734
592
            /* bRequired = */ false,
735
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
736
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
737
592
            "FalseZ", std::string(), FGFT_FLOAT64,
738
592
            /* bNullable = */ true,
739
592
            /* bRequired = */ false,
740
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
741
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
742
592
            "ZUnits", std::string(), FGFT_FLOAT64,
743
592
            /* bNullable = */ true,
744
592
            /* bRequired = */ false,
745
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
746
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
747
592
            "FalseM", std::string(), FGFT_FLOAT64,
748
592
            /* bNullable = */ true,
749
592
            /* bRequired = */ false,
750
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
751
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
752
592
            "MUnits", std::string(), FGFT_FLOAT64,
753
592
            /* bNullable = */ true,
754
592
            /* bRequired = */ false,
755
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
756
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
757
592
            "XYTolerance", std::string(), FGFT_FLOAT64,
758
592
            /* bNullable = */ true,
759
592
            /* bRequired = */ false,
760
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
761
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
762
592
            "ZTolerance", std::string(), FGFT_FLOAT64,
763
592
            /* bNullable = */ true,
764
592
            /* bRequired = */ false,
765
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
766
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
767
592
            "MTolerance", std::string(), FGFT_FLOAT64,
768
592
            /* bNullable = */ true,
769
592
            /* bRequired = */ false,
770
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
771
0
    {
772
0
        return false;
773
0
    }
774
775
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
776
592
        this, m_osGDBSpatialRefsFilename.c_str(), "GDB_SpatialRefs", "", "",
777
592
        true));
778
779
592
    return oTable.Sync();
780
592
}
781
782
/************************************************************************/
783
/*                           CreateGDBItems()                           */
784
/************************************************************************/
785
786
bool OGROpenFileGDBDataSource::CreateGDBItems()
787
592
{
788
    // Write GDB_Items file
789
592
    const char *ESRI_WKT_WGS84 =
790
592
        "GEOGCS[\"GCS_WGS_1984\",DATUM[\"D_WGS_1984\",SPHEROID[\"WGS_1984\","
791
592
        "6378137.0,298.257223563]],PRIMEM[\"Greenwich\",0.0],UNIT[\"Degree\",0."
792
592
        "0174532925199433]]";
793
592
    auto poGeomField = std::unique_ptr<FileGDBGeomField>(
794
592
        new FileGDBGeomField("Shape", "", true, ESRI_WKT_WGS84, -180, -90,
795
592
                             1000000, 0.000002, {0.012, 0.4, 12.0}));
796
592
    poGeomField->SetZOriginScaleTolerance(-100000, 10000, 0.001);
797
592
    poGeomField->SetMOriginScaleTolerance(-100000, 10000, 0.001);
798
799
592
    if (!AddNewSpatialRef(poGeomField->GetWKT(), poGeomField->GetXOrigin(),
800
592
                          poGeomField->GetYOrigin(), poGeomField->GetXYScale(),
801
592
                          poGeomField->GetZOrigin(), poGeomField->GetZScale(),
802
592
                          poGeomField->GetMOrigin(), poGeomField->GetMScale(),
803
592
                          poGeomField->GetXYTolerance(),
804
592
                          poGeomField->GetZTolerance(),
805
592
                          poGeomField->GetMTolerance()))
806
0
    {
807
0
        return false;
808
0
    }
809
810
592
    m_osGDBItemsFilename =
811
592
        CPLFormFilenameSafe(m_osDirName.c_str(), "a00000004.gdbtable", nullptr);
812
592
    FileGDBTable oTable;
813
592
    if (!oTable.Create(m_osGDBItemsFilename.c_str(), 4, FGTGT_POLYGON, false,
814
592
                       false) ||
815
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
816
592
            "ObjectID", std::string(), FGFT_OBJECTID,
817
592
            /* bNullable = */ false,
818
592
            /* bRequired = */ true,
819
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
820
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
821
592
            "UUID", std::string(), FGFT_GLOBALID,
822
592
            /* bNullable = */ false,
823
592
            /* bRequired = */ true,
824
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
825
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
826
592
            "Type", std::string(), FGFT_GUID,
827
592
            /* bNullable = */ false,
828
592
            /* bRequired = */ false,
829
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
830
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
831
592
            "Name", std::string(), FGFT_STRING,
832
592
            /* bNullable = */ true,
833
592
            /* bRequired = */ false,
834
592
            /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
835
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
836
592
            "PhysicalName", std::string(), FGFT_STRING,
837
592
            /* bNullable = */ true,
838
592
            /* bRequired = */ false,
839
592
            /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
840
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
841
592
            "Path", std::string(), FGFT_STRING,
842
592
            /* bNullable = */ true,
843
592
            /* bRequired = */ false,
844
592
            /* bEditable = */ true, 260, FileGDBField::UNSET_FIELD)) ||
845
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
846
592
            "DatasetSubtype1", std::string(), FGFT_INT32,
847
592
            /* bNullable = */ true,
848
592
            /* bRequired = */ false,
849
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
850
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
851
592
            "DatasetSubtype2", std::string(), FGFT_INT32,
852
592
            /* bNullable = */ true,
853
592
            /* bRequired = */ false,
854
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
855
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
856
592
            "DatasetInfo1", std::string(), FGFT_STRING,
857
592
            /* bNullable = */ true,
858
592
            /* bRequired = */ false,
859
592
            /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
860
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
861
592
            "DatasetInfo2", std::string(), FGFT_STRING,
862
592
            /* bNullable = */ true,
863
592
            /* bRequired = */ false,
864
592
            /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
865
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
866
592
            "URL", std::string(), FGFT_STRING,
867
592
            /* bNullable = */ true,
868
592
            /* bRequired = */ false,
869
592
            /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
870
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
871
592
            "Definition", std::string(), FGFT_XML,
872
592
            /* bNullable = */ true,
873
592
            /* bRequired = */ false,
874
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
875
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
876
592
            "Documentation", std::string(), FGFT_XML,
877
592
            /* bNullable = */ true,
878
592
            /* bRequired = */ false,
879
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
880
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
881
592
            "ItemInfo", std::string(), FGFT_XML,
882
592
            /* bNullable = */ true,
883
592
            /* bRequired = */ false,
884
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
885
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
886
592
            "Properties", std::string(), FGFT_INT32,
887
592
            /* bNullable = */ true,
888
592
            /* bRequired = */ false,
889
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
890
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
891
592
            "Defaults", std::string(), FGFT_BINARY,
892
592
            /* bNullable = */ true,
893
592
            /* bRequired = */ false,
894
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
895
592
        !oTable.CreateField(std::move(poGeomField)))
896
0
    {
897
0
        return false;
898
0
    }
899
900
592
    std::vector<OGRField> fields(oTable.GetFieldCount(),
901
592
                                 FileGDBField::UNSET_FIELD);
902
592
    m_osRootGUID = OFGDBGenerateUUID();
903
592
    fields[1].String = const_cast<char *>(m_osRootGUID.c_str());
904
592
    fields[2].String = const_cast<char *>(pszFolderTypeUUID);
905
592
    fields[3].String = const_cast<char *>("");
906
592
    fields[4].String = const_cast<char *>("");
907
592
    fields[5].String = const_cast<char *>("\\");
908
592
    fields[10].String = const_cast<char *>("");
909
592
    fields[14].Integer = 1;
910
592
    if (!oTable.CreateFeature(fields, nullptr))
911
0
        return false;
912
913
592
    const std::string osWorkspaceUUID(OFGDBGenerateUUID());
914
592
    fields[1].String = const_cast<char *>(osWorkspaceUUID.c_str());
915
592
    fields[2].String = const_cast<char *>(pszWorkspaceTypeUUID);
916
592
    fields[3].String = const_cast<char *>("Workspace");
917
592
    fields[4].String = const_cast<char *>("WORKSPACE");
918
592
    fields[5].String = const_cast<char *>("");   // Path
919
592
    fields[10].String = const_cast<char *>("");  // URL
920
592
    fields[11].String = const_cast<char *>(
921
592
        "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
922
592
        "<DEWorkspace xmlns:typens=\"http://www.esri.com/schemas/ArcGIS/10.3\" "
923
592
        "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "
924
592
        "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" "
925
592
        "xsi:type=\"typens:DEWorkspace\">\n"
926
592
        "  <CatalogPath>\\</CatalogPath>\n"
927
592
        "  <Name/>\n"
928
592
        "  <ChildrenExpanded>false</ChildrenExpanded>\n"
929
592
        "  <WorkspaceType>esriLocalDatabaseWorkspace</WorkspaceType>\n"
930
592
        "  <WorkspaceFactoryProgID/>\n"
931
592
        "  <ConnectionString/>\n"
932
592
        "  <ConnectionInfo xsi:nil=\"true\"/>\n"
933
592
        "  <Domains xsi:type=\"typens:ArrayOfDomain\"/>\n"
934
592
        "  <MajorVersion>3</MajorVersion>\n"
935
592
        "  <MinorVersion>0</MinorVersion>\n"
936
592
        "  <BugfixVersion>0</BugfixVersion>\n"
937
592
        "</DEWorkspace>");
938
592
    fields[14].Integer = 0;
939
940
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
941
592
        this, m_osGDBItemsFilename.c_str(), "GDB_Items", "", "", true));
942
943
592
    return oTable.CreateFeature(fields, nullptr) && oTable.Sync();
944
592
}
945
946
/************************************************************************/
947
/*                         CreateGDBItemTypes()                         */
948
/************************************************************************/
949
950
bool OGROpenFileGDBDataSource::CreateGDBItemTypes()
951
592
{
952
    // Write GDB_ItemTypes file
953
592
    const std::string osFilename(CPLFormFilenameSafe(
954
592
        m_osDirName.c_str(), "a00000005.gdbtable", nullptr));
955
592
    FileGDBTable oTable;
956
592
    if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
957
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
958
592
            "ObjectID", std::string(), FGFT_OBJECTID,
959
592
            /* bNullable = */ false,
960
592
            /* bRequired = */ true,
961
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
962
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
963
592
            "UUID", std::string(), FGFT_GUID,
964
592
            /* bNullable = */ false,
965
592
            /* bRequired = */ false,
966
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
967
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
968
592
            "ParentTypeID", std::string(), FGFT_GUID,
969
592
            /* bNullable = */ false,
970
592
            /* bRequired = */ false,
971
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
972
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
973
592
            "Name", std::string(), FGFT_STRING,
974
592
            /* bNullable = */ false,
975
592
            /* bRequired = */ false,
976
592
            /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)))
977
0
    {
978
0
        return false;
979
0
    }
980
981
592
    std::vector<OGRField> fields(oTable.GetFieldCount(),
982
592
                                 FileGDBField::UNSET_FIELD);
983
984
592
    static const struct
985
592
    {
986
592
        const char *pszUUID;
987
592
        const char *pszParentTypeID;
988
592
        const char *pszName;
989
592
    } apsData[] = {
990
592
        {"{8405add5-8df8-4227-8fac-3fcade073386}",
991
592
         "{00000000-0000-0000-0000-000000000000}", "Item"},
992
592
        {pszFolderTypeUUID, "{8405add5-8df8-4227-8fac-3fcade073386}", "Folder"},
993
592
        {"{ffd09c28-fe70-4e25-907c-af8e8a5ec5f3}",
994
592
         "{8405add5-8df8-4227-8fac-3fcade073386}", "Resource"},
995
592
        {"{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
996
592
         "{ffd09c28-fe70-4e25-907c-af8e8a5ec5f3}", "Dataset"},
997
592
        {"{fbdd7dd6-4a25-40b7-9a1a-ecc3d1172447}",
998
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Tin"},
999
592
        {"{d4912162-3413-476e-9da4-2aefbbc16939}",
1000
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "AbstractTable"},
1001
592
        {"{b606a7e1-fa5b-439c-849c-6e9c2481537b}",
1002
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Relationship Class"},
1003
592
        {pszFeatureDatasetTypeUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
1004
592
         "Feature Dataset"},
1005
592
        {"{73718a66-afb9-4b88-a551-cffa0ae12620}",
1006
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Geometric Network"},
1007
592
        {"{767152d3-ed66-4325-8774-420d46674e07}",
1008
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Topology"},
1009
592
        {"{e6302665-416b-44fa-be33-4e15916ba101}",
1010
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Survey Dataset"},
1011
592
        {"{d5a40288-029e-4766-8c81-de3f61129371}",
1012
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Schematic Dataset"},
1013
592
        {"{db1b697a-3bb6-426a-98a2-6ee7a4c6aed3}",
1014
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Toolbox"},
1015
592
        {pszWorkspaceTypeUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
1016
592
         "Workspace"},
1017
592
        {"{dc9ef677-1aa3-45a7-8acd-303a5202d0dc}",
1018
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Workspace Extension"},
1019
592
        {"{77292603-930f-475d-ae4f-b8970f42f394}",
1020
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Extension Dataset"},
1021
592
        {"{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
1022
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Domain"},
1023
592
        {"{4ed4a58e-621f-4043-95ed-850fba45fcbc}",
1024
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Replica"},
1025
592
        {"{d98421eb-d582-4713-9484-43304d0810f6}",
1026
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Replica Dataset"},
1027
592
        {"{dc64b6e4-dc0f-43bd-b4f5-f22385dcf055}",
1028
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "Historical Marker"},
1029
592
        {pszTableTypeUUID, "{d4912162-3413-476e-9da4-2aefbbc16939}", "Table"},
1030
592
        {pszFeatureClassTypeUUID, "{d4912162-3413-476e-9da4-2aefbbc16939}",
1031
592
         "Feature Class"},
1032
592
        {"{5ed667a3-9ca9-44a2-8029-d95bf23704b9}",
1033
592
         "{d4912162-3413-476e-9da4-2aefbbc16939}", "Raster Dataset"},
1034
592
        {"{35b601f7-45ce-4aff-adb7-7702d3839b12}",
1035
592
         "{d4912162-3413-476e-9da4-2aefbbc16939}", "Raster Catalog"},
1036
592
        {"{7771fc7d-a38b-4fd3-8225-639d17e9a131}",
1037
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Network Dataset"},
1038
592
        {"{76357537-3364-48af-a4be-783c7c28b5cb}",
1039
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Terrain"},
1040
592
        {"{a3803369-5fc2-4963-bae0-13effc09dd73}",
1041
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Parcel Fabric"},
1042
592
        {"{a300008d-0cea-4f6a-9dfa-46af829a3df2}",
1043
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Representation Class"},
1044
592
        {"{787bea35-4a86-494f-bb48-500b96145b58}",
1045
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Catalog Dataset"},
1046
592
        {"{f8413dcb-2248-4935-bfe9-315f397e5110}",
1047
592
         "{77292603-930f-475d-ae4f-b8970f42f394}", "Mosaic Dataset"},
1048
592
        {pszRangeDomainTypeUUID, "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
1049
592
         "Range Domain"},
1050
592
        {pszCodedDomainTypeUUID, "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}",
1051
592
         "Coded Value Domain"}};
1052
1053
592
    for (const auto &record : apsData)
1054
18.9k
    {
1055
18.9k
        fields[1].String = const_cast<char *>(record.pszUUID);
1056
18.9k
        fields[2].String = const_cast<char *>(record.pszParentTypeID);
1057
18.9k
        fields[3].String = const_cast<char *>(record.pszName);
1058
18.9k
        if (!oTable.CreateFeature(fields, nullptr))
1059
0
            return false;
1060
18.9k
    }
1061
1062
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
1063
592
        this, osFilename.c_str(), "GDB_ItemTypes", "", "", true));
1064
1065
592
    return oTable.Sync();
1066
592
}
1067
1068
/************************************************************************/
1069
/*                     CreateGDBItemRelationships()                     */
1070
/************************************************************************/
1071
1072
bool OGROpenFileGDBDataSource::CreateGDBItemRelationships()
1073
592
{
1074
    // Write GDB_ItemRelationships file
1075
592
    m_osGDBItemRelationshipsFilename =
1076
592
        CPLFormFilenameSafe(m_osDirName.c_str(), "a00000006.gdbtable", nullptr);
1077
592
    FileGDBTable oTable;
1078
592
    if (!oTable.Create(m_osGDBItemRelationshipsFilename.c_str(), 4, FGTGT_NONE,
1079
592
                       false, false) ||
1080
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1081
592
            "ObjectID", std::string(), FGFT_OBJECTID,
1082
592
            /* bNullable = */ false,
1083
592
            /* bRequired = */ true,
1084
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
1085
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1086
592
            "UUID", std::string(), FGFT_GLOBALID,
1087
592
            /* bNullable = */ false,
1088
592
            /* bRequired = */ true,
1089
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
1090
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1091
592
            "OriginID", std::string(), FGFT_GUID,
1092
592
            /* bNullable = */ false,
1093
592
            /* bRequired = */ false,
1094
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1095
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1096
592
            "DestID", std::string(), FGFT_GUID,
1097
592
            /* bNullable = */ false,
1098
592
            /* bRequired = */ false,
1099
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1100
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1101
592
            "Type", std::string(), FGFT_GUID,
1102
592
            /* bNullable = */ false,
1103
592
            /* bRequired = */ false,
1104
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1105
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1106
592
            "Attributes", std::string(), FGFT_XML,
1107
592
            /* bNullable = */ true,
1108
592
            /* bRequired = */ false,
1109
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1110
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1111
592
            "Properties", std::string(), FGFT_INT32,
1112
592
            /* bNullable = */ true,
1113
592
            /* bRequired = */ false,
1114
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
1115
0
    {
1116
0
        return false;
1117
0
    }
1118
1119
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
1120
592
        this, m_osGDBItemRelationshipsFilename.c_str(), "GDB_ItemRelationships",
1121
592
        "", "", true));
1122
1123
592
    return oTable.Sync();
1124
592
}
1125
1126
/************************************************************************/
1127
/*                   CreateGDBItemRelationshipTypes()                   */
1128
/************************************************************************/
1129
1130
bool OGROpenFileGDBDataSource::CreateGDBItemRelationshipTypes()
1131
592
{
1132
    // Write GDB_ItemRelationshipTypes file
1133
592
    const std::string osFilename(CPLFormFilenameSafe(
1134
592
        m_osDirName.c_str(), "a00000007.gdbtable", nullptr));
1135
592
    FileGDBTable oTable;
1136
592
    if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) ||
1137
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1138
592
            "ObjectID", std::string(), FGFT_OBJECTID,
1139
592
            /* bNullable = */ false,
1140
592
            /* bRequired = */ true,
1141
592
            /* bEditable = */ false, 0, FileGDBField::UNSET_FIELD)) ||
1142
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1143
592
            "UUID", std::string(), FGFT_GUID,
1144
592
            /* bNullable = */ false,
1145
592
            /* bRequired = */ false,
1146
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1147
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1148
592
            "OrigItemTypeID", std::string(), FGFT_GUID,
1149
592
            /* bNullable = */ false,
1150
592
            /* bRequired = */ false,
1151
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1152
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1153
592
            "DestItemTypeID", std::string(), FGFT_GUID,
1154
592
            /* bNullable = */ false,
1155
592
            /* bRequired = */ false,
1156
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)) ||
1157
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1158
592
            "Name", std::string(), FGFT_STRING,
1159
592
            /* bNullable = */ true,
1160
592
            /* bRequired = */ false,
1161
592
            /* bEditable = */ true, 160, FileGDBField::UNSET_FIELD)) ||
1162
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1163
592
            "ForwardLabel", std::string(), FGFT_STRING,
1164
592
            /* bNullable = */ true,
1165
592
            /* bRequired = */ false,
1166
592
            /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
1167
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1168
592
            "BackwardLabel", std::string(), FGFT_STRING,
1169
592
            /* bNullable = */ true,
1170
592
            /* bRequired = */ false,
1171
592
            /* bEditable = */ true, 255, FileGDBField::UNSET_FIELD)) ||
1172
592
        !oTable.CreateField(std::make_unique<FileGDBField>(
1173
592
            "IsContainment", std::string(), FGFT_INT16,
1174
592
            /* bNullable = */ true,
1175
592
            /* bRequired = */ false,
1176
592
            /* bEditable = */ true, 0, FileGDBField::UNSET_FIELD)))
1177
0
    {
1178
0
        return false;
1179
0
    }
1180
1181
592
    static const struct
1182
592
    {
1183
592
        const char *pszUUID;
1184
592
        const char *pszOrigItemTypeID;
1185
592
        const char *pszDestItemTypeID;
1186
592
        const char *pszName;
1187
592
        const char *pszForwardLabel;
1188
592
        const char *pszBackwardLabel;
1189
592
        int IsContainment;
1190
592
    } apsData[] = {
1191
592
        {"{0d10b3a7-2f64-45e6-b7ac-2fc27bf2133c}", pszFolderTypeUUID,
1192
592
         pszFolderTypeUUID, "FolderInFolder", "Parent Folder Of",
1193
592
         "Child Folder Of", 1},
1194
592
        {"{5dd0c1af-cb3d-4fea-8c51-cb3ba8d77cdb}", pszFolderTypeUUID,
1195
592
         "{8405add5-8df8-4227-8fac-3fcade073386}", "ItemInFolder",
1196
592
         "Contains Item", "Contained In Folder", 1},
1197
592
        {pszDatasetInFeatureDatasetUUID, pszFeatureDatasetTypeUUID,
1198
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetInFeatureDataset",
1199
592
         "Contains Dataset", "Contained In FeatureDataset", 1},
1200
592
        {pszDatasetInFolderUUID, pszFolderTypeUUID,
1201
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetInFolder",
1202
592
         "Contains Dataset", "Contained in Dataset", 1},
1203
592
        {pszDomainInDatasetUUID, "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
1204
592
         "{8637f1ed-8c04-4866-a44a-1cb8288b3c63}", "DomainInDataset",
1205
592
         "Contains Domain", "Contained in Dataset", 0},
1206
592
        {"{725badab-3452-491b-a795-55f32d67229c}",
1207
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}",
1208
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetsRelatedThrough",
1209
592
         "Origin Of", "Destination Of", 0},
1210
592
        {"{d088b110-190b-4229-bdf7-89fddd14d1ea}",
1211
592
         "{767152d3-ed66-4325-8774-420d46674e07}", pszFeatureClassTypeUUID,
1212
592
         "FeatureClassInTopology", "Spatially Manages Feature Class",
1213
592
         "Participates In Topology", 0},
1214
592
        {"{dc739a70-9b71-41e8-868c-008cf46f16d7}",
1215
592
         "{73718a66-afb9-4b88-a551-cffa0ae12620}", pszFeatureClassTypeUUID,
1216
592
         "FeatureClassInGeometricNetwork", "Spatially Manages Feature Class",
1217
592
         "Participates In Geometric Network", 0},
1218
592
        {"{b32b8563-0b96-4d32-92c4-086423ae9962}",
1219
592
         "{7771fc7d-a38b-4fd3-8225-639d17e9a131}", pszFeatureClassTypeUUID,
1220
592
         "FeatureClassInNetworkDataset", "Spatially Manages Feature Class",
1221
592
         "Participates In Network Dataset", 0},
1222
592
        {"{908a4670-1111-48c6-8269-134fdd3fe617}",
1223
592
         "{7771fc7d-a38b-4fd3-8225-639d17e9a131}", pszTableTypeUUID,
1224
592
         "TableInNetworkDataset", "Manages Table",
1225
592
         "Participates In Network Dataset", 0},
1226
592
        {"{55d2f4dc-cb17-4e32-a8c7-47591e8c71de}",
1227
592
         "{76357537-3364-48af-a4be-783c7c28b5cb}", pszFeatureClassTypeUUID,
1228
592
         "FeatureClassInTerrain", "Spatially Manages Feature Class",
1229
592
         "Participates In Terrain", 0},
1230
592
        {"{583a5baa-3551-41ae-8aa8-1185719f3889}",
1231
592
         "{a3803369-5fc2-4963-bae0-13effc09dd73}", pszFeatureClassTypeUUID,
1232
592
         "FeatureClassInParcelFabric", "Spatially Manages Feature Class",
1233
592
         "Participates In Parcel Fabric", 0},
1234
592
        {"{5f9085e0-788f-4354-ae3c-34c83a7ea784}",
1235
592
         "{a3803369-5fc2-4963-bae0-13effc09dd73}", pszTableTypeUUID,
1236
592
         "TableInParcelFabric", "Manages Table",
1237
592
         "Participates In Parcel Fabric", 0},
1238
592
        {"{e79b44e3-f833-4b12-90a1-364ec4ddc43e}", pszFeatureClassTypeUUID,
1239
592
         "{a300008d-0cea-4f6a-9dfa-46af829a3df2}",
1240
592
         "RepresentationOfFeatureClass", "Feature Class Representation",
1241
592
         "Represented Feature Class", 0},
1242
592
        {"{8db31af1-df7c-4632-aa10-3cc44b0c6914}",
1243
592
         "{4ed4a58e-621f-4043-95ed-850fba45fcbc}",
1244
592
         "{d98421eb-d582-4713-9484-43304d0810f6}", "ReplicaDatasetInReplica",
1245
592
         "Replicated Dataset", "Participates In Replica", 1},
1246
592
        {"{d022de33-45bd-424c-88bf-5b1b6b957bd3}",
1247
592
         "{d98421eb-d582-4713-9484-43304d0810f6}",
1248
592
         "{28da9e89-ff80-4d6d-8926-4ee2b161677d}", "DatasetOfReplicaDataset",
1249
592
         "Replicated Dataset", "Dataset of Replicated Dataset", 0},
1250
592
    };
1251
1252
592
    std::vector<OGRField> fields(oTable.GetFieldCount(),
1253
592
                                 FileGDBField::UNSET_FIELD);
1254
592
    for (const auto &record : apsData)
1255
9.47k
    {
1256
9.47k
        fields[1].String = const_cast<char *>(record.pszUUID);
1257
9.47k
        fields[2].String = const_cast<char *>(record.pszOrigItemTypeID);
1258
9.47k
        fields[3].String = const_cast<char *>(record.pszDestItemTypeID);
1259
9.47k
        fields[4].String = const_cast<char *>(record.pszName);
1260
9.47k
        fields[5].String = const_cast<char *>(record.pszForwardLabel);
1261
9.47k
        fields[6].String = const_cast<char *>(record.pszBackwardLabel);
1262
9.47k
        fields[7].Integer = record.IsContainment;
1263
9.47k
        if (!oTable.CreateFeature(fields, nullptr))
1264
0
            return false;
1265
9.47k
    }
1266
1267
592
    m_apoHiddenLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
1268
592
        this, osFilename.c_str(), "GDB_ItemRelationshipTypes", "", "", true));
1269
1270
592
    return oTable.Sync();
1271
592
}
1272
1273
/************************************************************************/
1274
/*                               Create()                               */
1275
/************************************************************************/
1276
1277
bool OGROpenFileGDBDataSource::Create(const char *pszName)
1278
592
{
1279
1280
592
    if (!EQUAL(CPLGetExtensionSafe(pszName).c_str(), "gdb"))
1281
0
    {
1282
0
        CPLError(CE_Failure, CPLE_NotSupported,
1283
0
                 "Extension of the directory should be gdb");
1284
0
        return false;
1285
0
    }
1286
1287
    /* Don't try to create on top of something already there */
1288
592
    VSIStatBufL sStat;
1289
592
    if (VSIStatL(pszName, &sStat) == 0)
1290
0
    {
1291
0
        CPLError(CE_Failure, CPLE_AppDefined, "%s already exists.", pszName);
1292
0
        return false;
1293
0
    }
1294
1295
592
    if (VSIMkdir(pszName, 0755) != 0)
1296
0
    {
1297
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s.",
1298
0
                 pszName);
1299
0
        return false;
1300
0
    }
1301
1302
592
    CPL_IGNORE_RET_VAL(OFGDBGenerateUUID(/* bInit = */ true));
1303
1304
592
    m_osDirName = pszName;
1305
592
    eAccess = GA_Update;
1306
1307
592
    {
1308
        // Write "gdb" file
1309
592
        const std::string osFilename(
1310
592
            CPLFormFilenameSafe(pszName, "gdb", nullptr));
1311
592
        VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
1312
592
        if (!fp)
1313
0
            return false;
1314
        // Write what the FileGDB SDK writes...
1315
592
        VSIFWriteL("\x05\x00\x00\x00\xDE\xAD\xBE\xEF", 1, 8, fp);
1316
592
        VSIFCloseL(fp);
1317
592
    }
1318
1319
0
    {
1320
        // Write "timestamps" file
1321
592
        const std::string osFilename(
1322
592
            CPLFormFilenameSafe(pszName, "timestamps", nullptr));
1323
592
        VSILFILE *fp = VSIFOpenL(osFilename.c_str(), "wb");
1324
592
        if (!fp)
1325
0
            return false;
1326
        // Write what the FileGDB SDK writes...
1327
592
        std::vector<GByte> values(400, 0xFF);
1328
592
        VSIFWriteL(values.data(), 1, values.size(), fp);
1329
592
        VSIFCloseL(fp);
1330
592
    }
1331
1332
592
    return CreateGDBSystemCatalog() && CreateGDBDBTune() &&
1333
592
           CreateGDBSpatialRefs() && CreateGDBItems() && CreateGDBItemTypes() &&
1334
592
           CreateGDBItemRelationships() && CreateGDBItemRelationshipTypes();
1335
    // GDB_ReplicaLog can be omitted.
1336
592
}
1337
1338
/************************************************************************/
1339
/*                            ICreateLayer()                            */
1340
/************************************************************************/
1341
1342
OGRLayer *
1343
OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName,
1344
                                       const OGRGeomFieldDefn *poGeomFieldDefn,
1345
                                       CSLConstList papszOptions)
1346
5.13k
{
1347
5.13k
    if (eAccess != GA_Update)
1348
0
        return nullptr;
1349
1350
5.13k
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1351
0
        return nullptr;
1352
1353
5.13k
    if (m_osRootGUID.empty())
1354
0
    {
1355
0
        CPLError(CE_Failure, CPLE_AppDefined, "Root UUID missing");
1356
0
        return nullptr;
1357
0
    }
1358
1359
5.13k
    auto eType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
1360
1361
5.13k
    FileGDBTable oTable;
1362
5.13k
    if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false) ||
1363
5.13k
        oTable.GetTotalRecordCount() >= INT32_MAX)
1364
0
        return nullptr;
1365
5.13k
    const int nTableNum = static_cast<int>(1 + oTable.GetTotalRecordCount());
1366
5.13k
    oTable.Close();
1367
1368
5.13k
    const std::string osFilename(CPLFormFilenameSafe(
1369
5.13k
        m_osDirName.c_str(), CPLSPrintf("a%08x.gdbtable", nTableNum), nullptr));
1370
1371
5.13k
    if (wkbFlatten(eType) == wkbLineString)
1372
0
        eType = OGR_GT_SetModifier(wkbMultiLineString, OGR_GT_HasZ(eType),
1373
0
                                   OGR_GT_HasM(eType));
1374
5.13k
    else if (wkbFlatten(eType) == wkbPolygon)
1375
0
        eType = OGR_GT_SetModifier(wkbMultiPolygon, OGR_GT_HasZ(eType),
1376
0
                                   OGR_GT_HasM(eType));
1377
1378
5.13k
    auto poLayer = std::make_unique<OGROpenFileGDBLayer>(
1379
5.13k
        this, osFilename.c_str(), pszLayerName, eType, papszOptions);
1380
5.13k
    if (!poLayer->Create(poGeomFieldDefn))
1381
49
        return nullptr;
1382
5.08k
    if (m_bInTransaction)
1383
0
    {
1384
0
        if (!poLayer->BeginEmulatedTransaction())
1385
0
            return nullptr;
1386
0
        m_oSetLayersCreatedInTransaction.insert(poLayer.get());
1387
0
    }
1388
5.08k
    m_apoLayers.emplace_back(std::move(poLayer));
1389
1390
5.08k
    return m_apoLayers.back().get();
1391
5.08k
}
1392
1393
/************************************************************************/
1394
/*                            DeleteLayer()                             */
1395
/************************************************************************/
1396
1397
OGRErr OGROpenFileGDBDataSource::DeleteLayer(int iLayer)
1398
0
{
1399
0
    if (eAccess != GA_Update)
1400
0
        return OGRERR_FAILURE;
1401
1402
0
    if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
1403
0
        return OGRERR_FAILURE;
1404
1405
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1406
0
        return false;
1407
1408
0
    auto poLayer = m_apoLayers[iLayer].get();
1409
1410
    // Remove from GDB_SystemCatalog
1411
0
    {
1412
0
        FileGDBTable oTable;
1413
0
        if (!oTable.Open(m_osGDBSystemCatalogFilename.c_str(), true))
1414
0
            return OGRERR_FAILURE;
1415
1416
0
        FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
1417
1418
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1419
0
             ++iCurFeat)
1420
0
        {
1421
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1422
0
            if (iCurFeat < 0)
1423
0
                break;
1424
0
            const auto psName = oTable.GetFieldValue(iName);
1425
0
            if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
1426
0
            {
1427
0
                oTable.DeleteFeature(iCurFeat + 1);
1428
0
                break;
1429
0
            }
1430
0
        }
1431
0
    }
1432
1433
    // Remove from GDB_Items
1434
0
    std::string osUUID;
1435
0
    {
1436
0
        FileGDBTable oTable;
1437
0
        if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
1438
0
            return OGRERR_FAILURE;
1439
1440
0
        FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, OGRERR_FAILURE);
1441
0
        FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, OGRERR_FAILURE);
1442
1443
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1444
0
             ++iCurFeat)
1445
0
        {
1446
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1447
0
            if (iCurFeat < 0)
1448
0
                break;
1449
0
            const auto psName = oTable.GetFieldValue(iName);
1450
0
            if (psName && strcmp(psName->String, poLayer->GetName()) == 0)
1451
0
            {
1452
0
                const auto psUUID = oTable.GetFieldValue(iUUID);
1453
0
                if (psUUID)
1454
0
                {
1455
0
                    osUUID = psUUID->String;
1456
0
                }
1457
1458
0
                oTable.DeleteFeature(iCurFeat + 1);
1459
0
                break;
1460
0
            }
1461
0
        }
1462
0
    }
1463
1464
    // Remove from GDB_ItemRelationships
1465
0
    if (!osUUID.empty())
1466
0
    {
1467
0
        FileGDBTable oTable;
1468
0
        if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
1469
0
            return OGRERR_FAILURE;
1470
1471
0
        FETCH_FIELD_IDX_WITH_RET(iOriginID, "OriginID", FGFT_GUID,
1472
0
                                 OGRERR_FAILURE);
1473
0
        FETCH_FIELD_IDX_WITH_RET(iDestID, "DestID", FGFT_GUID, OGRERR_FAILURE);
1474
1475
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1476
0
             ++iCurFeat)
1477
0
        {
1478
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1479
0
            if (iCurFeat < 0)
1480
0
                break;
1481
1482
0
            const auto psOriginID = oTable.GetFieldValue(iOriginID);
1483
0
            if (psOriginID && psOriginID->String == osUUID)
1484
0
            {
1485
0
                oTable.DeleteFeature(iCurFeat + 1);
1486
0
            }
1487
0
            else
1488
0
            {
1489
0
                const auto psDestID = oTable.GetFieldValue(iDestID);
1490
0
                if (psDestID && psDestID->String == osUUID)
1491
0
                {
1492
0
                    oTable.DeleteFeature(iCurFeat + 1);
1493
0
                }
1494
0
            }
1495
0
        }
1496
0
    }
1497
1498
0
    const std::string osDirname =
1499
0
        CPLGetPathSafe(poLayer->GetFilename().c_str());
1500
0
    const std::string osFilenameBase =
1501
0
        CPLGetBasenameSafe(poLayer->GetFilename().c_str());
1502
1503
0
    if (m_bInTransaction)
1504
0
    {
1505
0
        auto oIter =
1506
0
            m_oSetLayersCreatedInTransaction.find(m_apoLayers[iLayer].get());
1507
0
        if (oIter != m_oSetLayersCreatedInTransaction.end())
1508
0
        {
1509
0
            m_oSetLayersCreatedInTransaction.erase(oIter);
1510
0
        }
1511
0
        else
1512
0
        {
1513
0
            poLayer->BeginEmulatedTransaction();
1514
0
            poLayer->Close();
1515
0
            m_oSetLayersDeletedInTransaction.insert(
1516
0
                std::move(m_apoLayers[iLayer]));
1517
0
        }
1518
0
    }
1519
1520
    // Delete OGR layer
1521
0
    m_apoLayers.erase(m_apoLayers.begin() + iLayer);
1522
1523
    // Remove files associated with the layer
1524
0
    char **papszFiles = VSIReadDir(osDirname.c_str());
1525
0
    for (char **papszIter = papszFiles; papszIter && *papszIter; ++papszIter)
1526
0
    {
1527
0
        if (STARTS_WITH(*papszIter, osFilenameBase.c_str()))
1528
0
        {
1529
0
            VSIUnlink(
1530
0
                CPLFormFilenameSafe(osDirname.c_str(), *papszIter, nullptr)
1531
0
                    .c_str());
1532
0
        }
1533
0
    }
1534
0
    CSLDestroy(papszFiles);
1535
1536
0
    return OGRERR_NONE;
1537
0
}
1538
1539
/************************************************************************/
1540
/*                             FlushCache()                             */
1541
/************************************************************************/
1542
1543
CPLErr OGROpenFileGDBDataSource::FlushCache(bool /*bAtClosing*/)
1544
13.9k
{
1545
13.9k
    if (eAccess != GA_Update)
1546
12.8k
        return CE_None;
1547
1548
1.18k
    CPLErr eErr = CE_None;
1549
1.18k
    for (auto &poLayer : m_apoLayers)
1550
10.1k
    {
1551
10.1k
        if (poLayer->SyncToDisk() != OGRERR_NONE)
1552
0
            eErr = CE_Failure;
1553
10.1k
    }
1554
1.18k
    return eErr;
1555
13.9k
}
1556
1557
/************************************************************************/
1558
/*                           AddFieldDomain()                           */
1559
/************************************************************************/
1560
1561
bool OGROpenFileGDBDataSource::AddFieldDomain(
1562
    std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
1563
0
{
1564
0
    const std::string domainName(domain->GetName());
1565
0
    if (eAccess != GA_Update)
1566
0
    {
1567
0
        CPLError(CE_Failure, CPLE_NotSupported,
1568
0
                 "AddFieldDomain() not supported on read-only dataset");
1569
0
        return false;
1570
0
    }
1571
1572
0
    if (GetFieldDomain(domainName) != nullptr)
1573
0
    {
1574
0
        failureReason = "A domain of identical name already exists";
1575
0
        return false;
1576
0
    }
1577
1578
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1579
0
        return false;
1580
1581
0
    std::string osXML =
1582
0
        BuildXMLFieldDomainDef(domain.get(), false, failureReason);
1583
0
    if (osXML.empty())
1584
0
    {
1585
0
        return false;
1586
0
    }
1587
1588
0
    const std::string osThisGUID = OFGDBGenerateUUID();
1589
1590
0
    FileGDBTable oTable;
1591
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
1592
0
        return false;
1593
1594
0
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
1595
0
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
1596
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
1597
0
    FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
1598
0
    FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
1599
0
    FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
1600
0
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
1601
0
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
1602
1603
0
    std::vector<OGRField> fields(oTable.GetFieldCount(),
1604
0
                                 FileGDBField::UNSET_FIELD);
1605
0
    fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
1606
0
    switch (domain->GetDomainType())
1607
0
    {
1608
0
        case OFDT_CODED:
1609
0
            fields[iType].String = const_cast<char *>(pszCodedDomainTypeUUID);
1610
0
            break;
1611
1612
0
        case OFDT_RANGE:
1613
0
            fields[iType].String = const_cast<char *>(pszRangeDomainTypeUUID);
1614
0
            break;
1615
1616
0
        case OFDT_GLOB:
1617
0
            CPLAssert(false);
1618
0
            break;
1619
0
    }
1620
0
    fields[iName].String = const_cast<char *>(domainName.c_str());
1621
0
    CPLString osUCName(domainName);
1622
0
    osUCName.toupper();
1623
0
    fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
1624
0
    fields[iPath].String = const_cast<char *>("");
1625
0
    fields[iURL].String = const_cast<char *>("");
1626
0
    fields[iDefinition].String = const_cast<char *>(osXML.c_str());
1627
0
    fields[iProperties].Integer = 1;
1628
1629
0
    if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
1630
0
        return false;
1631
1632
0
    m_oMapFieldDomains[domainName] = std::move(domain);
1633
1634
0
    return true;
1635
0
}
1636
1637
/************************************************************************/
1638
/*                         DeleteFieldDomain()                          */
1639
/************************************************************************/
1640
1641
bool OGROpenFileGDBDataSource::DeleteFieldDomain(
1642
    const std::string &name, std::string & /*failureReason*/)
1643
0
{
1644
0
    if (eAccess != GA_Update)
1645
0
    {
1646
0
        CPLError(CE_Failure, CPLE_NotSupported,
1647
0
                 "DeleteFieldDomain() not supported on read-only dataset");
1648
0
        return false;
1649
0
    }
1650
1651
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1652
0
        return false;
1653
1654
    // Remove object from GDB_Items
1655
0
    std::string osUUID;
1656
0
    {
1657
0
        FileGDBTable oTable;
1658
0
        if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
1659
0
            return false;
1660
1661
0
        FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
1662
0
        FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
1663
0
        FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
1664
1665
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1666
0
             ++iCurFeat)
1667
0
        {
1668
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1669
0
            if (iCurFeat < 0)
1670
0
                break;
1671
0
            const auto psName = oTable.GetFieldValue(iName);
1672
0
            if (psName && psName->String == name)
1673
0
            {
1674
0
                const auto psType = oTable.GetFieldValue(iType);
1675
0
                if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
1676
0
                               EQUAL(psType->String, pszCodedDomainTypeUUID)))
1677
0
                {
1678
0
                    const auto psUUID = oTable.GetFieldValue(iUUID);
1679
0
                    if (psUUID)
1680
0
                    {
1681
0
                        osUUID = psUUID->String;
1682
0
                    }
1683
1684
0
                    if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
1685
0
                    {
1686
0
                        return false;
1687
0
                    }
1688
0
                    break;
1689
0
                }
1690
0
            }
1691
0
        }
1692
0
    }
1693
0
    if (osUUID.empty())
1694
0
        return false;
1695
1696
    // Remove links from layers to domain, into GDB_ItemRelationships
1697
0
    {
1698
0
        FileGDBTable oTable;
1699
0
        if (!oTable.Open(m_osGDBItemRelationshipsFilename.c_str(), true))
1700
0
            return false;
1701
1702
0
        FETCH_FIELD_IDX(iDestID, "DestID", FGFT_GUID);
1703
0
        FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
1704
1705
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1706
0
             ++iCurFeat)
1707
0
        {
1708
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1709
0
            if (iCurFeat < 0)
1710
0
                break;
1711
1712
0
            const auto psType = oTable.GetFieldValue(iType);
1713
0
            if (psType && EQUAL(psType->String, pszDomainInDatasetUUID))
1714
0
            {
1715
0
                const auto psDestID = oTable.GetFieldValue(iDestID);
1716
0
                if (psDestID && EQUAL(psDestID->String, osUUID.c_str()))
1717
0
                {
1718
0
                    if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
1719
0
                    {
1720
0
                        return false;
1721
0
                    }
1722
0
                }
1723
0
            }
1724
0
        }
1725
1726
0
        if (!oTable.Sync())
1727
0
        {
1728
0
            return false;
1729
0
        }
1730
0
    }
1731
1732
0
    m_oMapFieldDomains.erase(name);
1733
1734
0
    return true;
1735
0
}
1736
1737
/************************************************************************/
1738
/*                         UpdateFieldDomain()                          */
1739
/************************************************************************/
1740
1741
bool OGROpenFileGDBDataSource::UpdateFieldDomain(
1742
    std::unique_ptr<OGRFieldDomain> &&domain, std::string &failureReason)
1743
0
{
1744
0
    const std::string domainName(domain->GetName());
1745
0
    if (eAccess != GA_Update)
1746
0
    {
1747
0
        CPLError(CE_Failure, CPLE_NotSupported,
1748
0
                 "UpdateFieldDomain() not supported on read-only dataset");
1749
0
        return false;
1750
0
    }
1751
1752
0
    if (GetFieldDomain(domainName) == nullptr)
1753
0
    {
1754
0
        failureReason = "The domain should already exist to be updated";
1755
0
        return false;
1756
0
    }
1757
1758
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1759
0
        return false;
1760
1761
0
    std::string osXML =
1762
0
        BuildXMLFieldDomainDef(domain.get(), false, failureReason);
1763
0
    if (osXML.empty())
1764
0
    {
1765
0
        return false;
1766
0
    }
1767
1768
0
    FileGDBTable oTable;
1769
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
1770
0
        return false;
1771
1772
0
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
1773
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
1774
0
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
1775
1776
0
    bool bMatchFound = false;
1777
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
1778
0
         ++iCurFeat)
1779
0
    {
1780
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
1781
0
        if (iCurFeat < 0)
1782
0
            break;
1783
0
        const auto psName = oTable.GetFieldValue(iName);
1784
0
        if (psName && psName->String == domainName)
1785
0
        {
1786
0
            const auto psType = oTable.GetFieldValue(iType);
1787
0
            if (psType && (EQUAL(psType->String, pszRangeDomainTypeUUID) ||
1788
0
                           EQUAL(psType->String, pszCodedDomainTypeUUID)))
1789
0
            {
1790
0
                auto asFields = oTable.GetAllFieldValues();
1791
1792
0
                if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
1793
0
                    !OGR_RawField_IsUnset(&asFields[iDefinition]))
1794
0
                {
1795
0
                    CPLFree(asFields[iDefinition].String);
1796
0
                }
1797
0
                asFields[iDefinition].String = CPLStrdup(osXML.c_str());
1798
1799
0
                const char *pszNewTypeUUID = "";
1800
0
                CPL_IGNORE_RET_VAL(pszNewTypeUUID);  // Make CSA happy
1801
0
                switch (domain->GetDomainType())
1802
0
                {
1803
0
                    case OFDT_CODED:
1804
0
                        pszNewTypeUUID = pszCodedDomainTypeUUID;
1805
0
                        break;
1806
1807
0
                    case OFDT_RANGE:
1808
0
                        pszNewTypeUUID = pszRangeDomainTypeUUID;
1809
0
                        break;
1810
1811
0
                    case OFDT_GLOB:
1812
0
                        CPLAssert(false);
1813
0
                        break;
1814
0
                }
1815
1816
0
                if (!OGR_RawField_IsNull(&asFields[iType]) &&
1817
0
                    !OGR_RawField_IsUnset(&asFields[iType]))
1818
0
                {
1819
0
                    CPLFree(asFields[iType].String);
1820
0
                }
1821
0
                asFields[iType].String = CPLStrdup(pszNewTypeUUID);
1822
1823
0
                bool bRet =
1824
0
                    oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
1825
0
                oTable.FreeAllFieldValues(asFields);
1826
0
                if (!bRet)
1827
0
                    return false;
1828
0
                bMatchFound = true;
1829
0
                break;
1830
0
            }
1831
0
        }
1832
1833
0
        if (!oTable.Sync())
1834
0
        {
1835
0
            return false;
1836
0
        }
1837
0
    }
1838
1839
0
    if (!bMatchFound)
1840
0
        return false;
1841
1842
0
    m_oMapFieldDomains[domainName] = std::move(domain);
1843
1844
0
    return true;
1845
0
}
1846
1847
/************************************************************************/
1848
/*                        GetRelationshipNames()                        */
1849
/************************************************************************/
1850
1851
std::vector<std::string> OGROpenFileGDBDataSource::GetRelationshipNames(
1852
    CPL_UNUSED CSLConstList papszOptions) const
1853
1854
0
{
1855
0
    std::vector<std::string> oasNames;
1856
0
    oasNames.reserve(m_osMapRelationships.size());
1857
0
    for (auto it = m_osMapRelationships.begin();
1858
0
         it != m_osMapRelationships.end(); ++it)
1859
0
    {
1860
0
        oasNames.emplace_back(it->first);
1861
0
    }
1862
0
    return oasNames;
1863
0
}
1864
1865
/************************************************************************/
1866
/*                          GetRelationship()                           */
1867
/************************************************************************/
1868
1869
const GDALRelationship *
1870
OGROpenFileGDBDataSource::GetRelationship(const std::string &name) const
1871
1872
0
{
1873
0
    auto it = m_osMapRelationships.find(name);
1874
0
    if (it == m_osMapRelationships.end())
1875
0
        return nullptr;
1876
1877
0
    return it->second.get();
1878
0
}
1879
1880
/************************************************************************/
1881
/*                          AddRelationship()                           */
1882
/************************************************************************/
1883
1884
bool OGROpenFileGDBDataSource::AddRelationship(
1885
    std::unique_ptr<GDALRelationship> &&relationship,
1886
    std::string &failureReason)
1887
0
{
1888
0
    if (FlushCache(false) != CE_None)
1889
0
        return false;
1890
1891
0
    const std::string relationshipName(relationship->GetName());
1892
0
    if (eAccess != GA_Update)
1893
0
    {
1894
0
        CPLError(CE_Failure, CPLE_NotSupported,
1895
0
                 "AddRelationship() not supported on read-only dataset");
1896
0
        return false;
1897
0
    }
1898
1899
0
    if (GetRelationship(relationshipName) != nullptr)
1900
0
    {
1901
0
        failureReason = "A relationship of identical name already exists";
1902
0
        return false;
1903
0
    }
1904
1905
0
    if (relationship->GetCardinality() ==
1906
0
        GDALRelationshipCardinality::GRC_MANY_TO_ONE)
1907
0
    {
1908
0
        failureReason = "Many to one relationships are not supported";
1909
0
        return false;
1910
0
    }
1911
0
    else if (relationship->GetCardinality() ==
1912
0
                 GDALRelationshipCardinality::GRC_MANY_TO_MANY &&
1913
0
             !relationship->GetMappingTableName().empty() &&
1914
0
             relationship->GetName() != relationship->GetMappingTableName())
1915
0
    {
1916
0
        failureReason = "Mapping table name must match relationship name for "
1917
0
                        "many-to-many relationships";
1918
0
        return false;
1919
0
    }
1920
1921
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
1922
0
        return false;
1923
1924
0
    const std::string osThisGUID = OFGDBGenerateUUID();
1925
1926
0
    FileGDBTable oTable;
1927
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
1928
0
        oTable.GetTotalRecordCount() >= INT32_MAX)
1929
0
        return false;
1930
1931
    // hopefully this just needs to be a unique value. Seems to autoincrement
1932
    // when created from ArcMap at least!
1933
0
    const int iDsId = static_cast<int>(oTable.GetTotalRecordCount() + 1);
1934
1935
0
    std::string osMappingTableOidName;
1936
0
    if (relationship->GetCardinality() ==
1937
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
1938
0
    {
1939
0
        if (!relationship->GetMappingTableName().empty())
1940
0
        {
1941
0
            auto poLayer =
1942
0
                GetLayerByName(relationship->GetMappingTableName().c_str());
1943
0
            if (poLayer)
1944
0
            {
1945
0
                osMappingTableOidName = poLayer->GetFIDColumn();
1946
0
            }
1947
0
        }
1948
0
        else
1949
0
        {
1950
            // auto create mapping table
1951
0
            CPLStringList aosOptions;
1952
0
            aosOptions.SetNameValue("FID", "RID");
1953
0
            OGRLayer *poMappingTable = ICreateLayer(
1954
0
                relationship->GetName().c_str(), nullptr, aosOptions.List());
1955
0
            if (!poMappingTable)
1956
0
            {
1957
0
                failureReason = "Could not create mapping table " +
1958
0
                                relationship->GetMappingTableName();
1959
0
                return false;
1960
0
            }
1961
1962
0
            OGRFieldDefn oOriginFkFieldDefn("origin_fk", OFTString);
1963
0
            if (poMappingTable->CreateField(&oOriginFkFieldDefn) != OGRERR_NONE)
1964
0
            {
1965
0
                failureReason =
1966
0
                    "Could not create origin_fk field in mapping table " +
1967
0
                    relationship->GetMappingTableName();
1968
0
                return false;
1969
0
            }
1970
1971
0
            OGRFieldDefn oDestinationFkFieldDefn("destination_fk", OFTString);
1972
0
            if (poMappingTable->CreateField(&oDestinationFkFieldDefn) !=
1973
0
                OGRERR_NONE)
1974
0
            {
1975
0
                failureReason =
1976
0
                    "Could not create destination_fk field in mapping table " +
1977
0
                    relationship->GetMappingTableName();
1978
0
                return false;
1979
0
            }
1980
1981
0
            osMappingTableOidName = "RID";
1982
0
            relationship->SetMappingTableName(relationship->GetName());
1983
0
            relationship->SetLeftMappingTableFields({"origin_fk"});
1984
0
            relationship->SetRightMappingTableFields({"destination_fk"});
1985
0
        }
1986
0
    }
1987
1988
0
    std::string osXML = BuildXMLRelationshipDef(
1989
0
        relationship.get(), iDsId, osMappingTableOidName, failureReason);
1990
0
    if (osXML.empty())
1991
0
    {
1992
0
        return false;
1993
0
    }
1994
1995
0
    std::string osItemInfoXML =
1996
0
        BuildXMLRelationshipItemInfo(relationship.get(), failureReason);
1997
0
    if (osItemInfoXML.empty())
1998
0
    {
1999
0
        return false;
2000
0
    }
2001
2002
0
    std::string osDocumentationXML =
2003
0
        BuildXMLRelationshipDocumentation(relationship.get(), failureReason);
2004
0
    if (osDocumentationXML.empty())
2005
0
    {
2006
0
        return false;
2007
0
    }
2008
2009
0
    std::string osOriginUUID;
2010
0
    if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
2011
0
    {
2012
0
        failureReason = ("Left table " + relationship->GetLeftTableName() +
2013
0
                         " is not an existing layer in the dataset")
2014
0
                            .c_str();
2015
0
        return false;
2016
0
    }
2017
0
    std::string osDestinationUUID;
2018
0
    if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
2019
0
    {
2020
0
        failureReason = ("Right table " + relationship->GetRightTableName() +
2021
0
                         " is not an existing layer in the dataset")
2022
0
                            .c_str();
2023
0
        return false;
2024
0
    }
2025
2026
0
    FETCH_FIELD_IDX(iUUID, "UUID", FGFT_GLOBALID);
2027
0
    FETCH_FIELD_IDX(iType, "Type", FGFT_GUID);
2028
0
    FETCH_FIELD_IDX(iName, "Name", FGFT_STRING);
2029
0
    FETCH_FIELD_IDX(iPhysicalName, "PhysicalName", FGFT_STRING);
2030
0
    FETCH_FIELD_IDX(iPath, "Path", FGFT_STRING);
2031
0
    FETCH_FIELD_IDX(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32);
2032
0
    FETCH_FIELD_IDX(iDatasetSubtype2, "DatasetSubtype2", FGFT_INT32);
2033
0
    FETCH_FIELD_IDX(iURL, "URL", FGFT_STRING);
2034
0
    FETCH_FIELD_IDX(iDefinition, "Definition", FGFT_XML);
2035
0
    FETCH_FIELD_IDX(iDocumentation, "Documentation", FGFT_XML);
2036
0
    FETCH_FIELD_IDX(iItemInfo, "ItemInfo", FGFT_XML);
2037
0
    FETCH_FIELD_IDX(iProperties, "Properties", FGFT_INT32);
2038
2039
0
    std::vector<OGRField> fields(oTable.GetFieldCount(),
2040
0
                                 FileGDBField::UNSET_FIELD);
2041
0
    fields[iUUID].String = const_cast<char *>(osThisGUID.c_str());
2042
0
    fields[iType].String = const_cast<char *>(pszRelationshipTypeUUID);
2043
0
    fields[iName].String = const_cast<char *>(relationshipName.c_str());
2044
0
    CPLString osUCName(relationshipName);
2045
0
    osUCName.toupper();
2046
0
    fields[iPhysicalName].String = const_cast<char *>(osUCName.c_str());
2047
0
    const std::string osPath = "\\" + relationshipName;
2048
0
    fields[iPath].String = const_cast<char *>(osPath.c_str());
2049
0
    switch (relationship->GetCardinality())
2050
0
    {
2051
0
        case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
2052
0
            fields[iDatasetSubtype1].Integer = 1;
2053
0
            break;
2054
0
        case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
2055
0
            fields[iDatasetSubtype1].Integer = 2;
2056
0
            break;
2057
0
        case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
2058
0
            fields[iDatasetSubtype1].Integer = 3;
2059
0
            break;
2060
0
        case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
2061
            // unreachable
2062
0
            break;
2063
0
    }
2064
0
    fields[iDatasetSubtype2].Integer = 0;
2065
0
    fields[iURL].String = const_cast<char *>("");
2066
0
    fields[iDefinition].String = const_cast<char *>(osXML.c_str());
2067
0
    fields[iDocumentation].String =
2068
0
        const_cast<char *>(osDocumentationXML.c_str());
2069
0
    fields[iItemInfo].String = const_cast<char *>(osItemInfoXML.c_str());
2070
0
    fields[iProperties].Integer = 1;
2071
2072
0
    if (!(oTable.CreateFeature(fields, nullptr) && oTable.Sync()))
2073
0
        return false;
2074
2075
0
    if (!RegisterRelationshipInItemRelationships(osThisGUID, osOriginUUID,
2076
0
                                                 osDestinationUUID))
2077
0
        return false;
2078
2079
0
    m_osMapRelationships[relationshipName] = std::move(relationship);
2080
2081
0
    return true;
2082
0
}
2083
2084
/************************************************************************/
2085
/*                         DeleteRelationship()                         */
2086
/************************************************************************/
2087
2088
bool OGROpenFileGDBDataSource::DeleteRelationship(const std::string &name,
2089
                                                  std::string &failureReason)
2090
0
{
2091
0
    if (eAccess != GA_Update)
2092
0
    {
2093
0
        CPLError(CE_Failure, CPLE_NotSupported,
2094
0
                 "DeleteRelationship() not supported on read-only dataset");
2095
0
        return false;
2096
0
    }
2097
2098
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
2099
0
        return false;
2100
2101
    // Remove from GDB_Items
2102
0
    std::string osUUID;
2103
0
    {
2104
0
        FileGDBTable oTable;
2105
0
        if (!oTable.Open(m_osGDBItemsFilename.c_str(), true))
2106
0
            return false;
2107
2108
0
        FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
2109
0
        FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
2110
0
        FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
2111
2112
0
        for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
2113
0
             ++iCurFeat)
2114
0
        {
2115
0
            iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
2116
0
            if (iCurFeat < 0)
2117
0
                break;
2118
2119
0
            const auto psType = oTable.GetFieldValue(iType);
2120
0
            if (!psType || !EQUAL(psType->String, pszRelationshipTypeUUID))
2121
0
            {
2122
0
                continue;
2123
0
            }
2124
2125
0
            const auto psName = oTable.GetFieldValue(iName);
2126
0
            if (psName && strcmp(psName->String, name.c_str()) != 0)
2127
0
            {
2128
0
                continue;
2129
0
            }
2130
2131
0
            const auto psUUID = oTable.GetFieldValue(iUUID);
2132
0
            if (psUUID)
2133
0
            {
2134
0
                osUUID = psUUID->String;
2135
0
                if (!(oTable.DeleteFeature(iCurFeat + 1) && oTable.Sync()))
2136
0
                {
2137
0
                    failureReason =
2138
0
                        "Could not delete relationship from GDB_Items table";
2139
0
                    return false;
2140
0
                }
2141
0
            }
2142
0
        }
2143
0
    }
2144
2145
0
    if (osUUID.empty())
2146
0
    {
2147
0
        failureReason = "Could not find relationship with name " + name;
2148
0
        return false;
2149
0
    }
2150
2151
0
    if (!RemoveRelationshipFromItemRelationships(osUUID))
2152
0
    {
2153
0
        failureReason =
2154
0
            "Could not remove relationship from GDB_ItemRelationships";
2155
0
        return false;
2156
0
    }
2157
2158
0
    m_osMapRelationships.erase(name);
2159
0
    return true;
2160
0
}
2161
2162
/************************************************************************/
2163
/*                         UpdateRelationship()                         */
2164
/************************************************************************/
2165
2166
bool OGROpenFileGDBDataSource::UpdateRelationship(
2167
    std::unique_ptr<GDALRelationship> &&relationship,
2168
    std::string &failureReason)
2169
0
{
2170
0
    const std::string relationshipName(relationship->GetName());
2171
0
    if (eAccess != GA_Update)
2172
0
    {
2173
0
        CPLError(CE_Failure, CPLE_NotSupported,
2174
0
                 "UpdateRelationship() not supported on read-only dataset");
2175
0
        return false;
2176
0
    }
2177
2178
0
    if (GetRelationship(relationshipName) == nullptr)
2179
0
    {
2180
0
        failureReason = "The relationship should already exist to be updated";
2181
0
        return false;
2182
0
    }
2183
2184
0
    if (relationship->GetCardinality() ==
2185
0
        GDALRelationshipCardinality::GRC_MANY_TO_ONE)
2186
0
    {
2187
0
        failureReason = "Many to one relationships are not supported";
2188
0
        return false;
2189
0
    }
2190
2191
0
    if (m_bInTransaction && !BackupSystemTablesForTransaction())
2192
0
        return false;
2193
2194
0
    std::string osOriginUUID;
2195
0
    if (!FindUUIDFromName(relationship->GetLeftTableName(), osOriginUUID))
2196
0
    {
2197
0
        failureReason = ("Left table " + relationship->GetLeftTableName() +
2198
0
                         " is not an existing layer in the dataset")
2199
0
                            .c_str();
2200
0
        return false;
2201
0
    }
2202
0
    std::string osDestinationUUID;
2203
0
    if (!FindUUIDFromName(relationship->GetRightTableName(), osDestinationUUID))
2204
0
    {
2205
0
        failureReason = ("Right table " + relationship->GetRightTableName() +
2206
0
                         " is not an existing layer in the dataset")
2207
0
                            .c_str();
2208
0
        return false;
2209
0
    }
2210
2211
0
    FileGDBTable oTable;
2212
0
    if (!oTable.Open(m_osGDBItemsFilename.c_str(), true) ||
2213
0
        oTable.GetTotalRecordCount() >= INT32_MAX)
2214
0
    {
2215
0
        return false;
2216
0
    }
2217
2218
    // hopefully this just needs to be a unique value. Seems to autoincrement
2219
    // when created from ArcMap at least!
2220
0
    const int iDsId = static_cast<int>(oTable.GetTotalRecordCount()) + 1;
2221
2222
0
    std::string osMappingTableOidName;
2223
0
    if (relationship->GetCardinality() ==
2224
0
        GDALRelationshipCardinality::GRC_MANY_TO_MANY)
2225
0
    {
2226
0
        if (!relationship->GetMappingTableName().empty())
2227
0
        {
2228
0
            auto poLayer =
2229
0
                GetLayerByName(relationship->GetMappingTableName().c_str());
2230
0
            if (poLayer)
2231
0
            {
2232
0
                osMappingTableOidName = poLayer->GetFIDColumn();
2233
0
            }
2234
0
        }
2235
0
        if (osMappingTableOidName.empty())
2236
0
        {
2237
0
            failureReason = "Relationship mapping table does not exist";
2238
0
            return false;
2239
0
        }
2240
0
    }
2241
2242
0
    std::string osXML = BuildXMLRelationshipDef(
2243
0
        relationship.get(), iDsId, osMappingTableOidName, failureReason);
2244
0
    if (osXML.empty())
2245
0
    {
2246
0
        return false;
2247
0
    }
2248
2249
0
    FETCH_FIELD_IDX_WITH_RET(iUUID, "UUID", FGFT_GLOBALID, false);
2250
0
    FETCH_FIELD_IDX_WITH_RET(iType, "Type", FGFT_GUID, false);
2251
0
    FETCH_FIELD_IDX_WITH_RET(iName, "Name", FGFT_STRING, false);
2252
0
    FETCH_FIELD_IDX_WITH_RET(iDefinition, "Definition", FGFT_XML, false);
2253
0
    FETCH_FIELD_IDX_WITH_RET(iDatasetSubtype1, "DatasetSubtype1", FGFT_INT32,
2254
0
                             false);
2255
2256
0
    bool bMatchFound = false;
2257
0
    std::string osUUID;
2258
0
    for (int64_t iCurFeat = 0; iCurFeat < oTable.GetTotalRecordCount();
2259
0
         ++iCurFeat)
2260
0
    {
2261
0
        iCurFeat = oTable.GetAndSelectNextNonEmptyRow(iCurFeat);
2262
0
        if (iCurFeat < 0)
2263
0
            break;
2264
0
        const auto psName = oTable.GetFieldValue(iName);
2265
0
        if (psName && psName->String == relationshipName)
2266
0
        {
2267
0
            const auto psType = oTable.GetFieldValue(iType);
2268
0
            if (psType && EQUAL(psType->String, pszRelationshipTypeUUID))
2269
0
            {
2270
0
                const auto psUUID = oTable.GetFieldValue(iUUID);
2271
0
                if (psUUID)
2272
0
                {
2273
0
                    osUUID = psUUID->String;
2274
0
                }
2275
2276
0
                auto asFields = oTable.GetAllFieldValues();
2277
2278
0
                if (!OGR_RawField_IsNull(&asFields[iDefinition]) &&
2279
0
                    !OGR_RawField_IsUnset(&asFields[iDefinition]))
2280
0
                {
2281
0
                    CPLFree(asFields[iDefinition].String);
2282
0
                }
2283
0
                asFields[iDefinition].String = CPLStrdup(osXML.c_str());
2284
2285
0
                switch (relationship->GetCardinality())
2286
0
                {
2287
0
                    case GDALRelationshipCardinality::GRC_ONE_TO_ONE:
2288
0
                        asFields[iDatasetSubtype1].Integer = 1;
2289
0
                        break;
2290
0
                    case GDALRelationshipCardinality::GRC_ONE_TO_MANY:
2291
0
                        asFields[iDatasetSubtype1].Integer = 2;
2292
0
                        break;
2293
0
                    case GDALRelationshipCardinality::GRC_MANY_TO_MANY:
2294
0
                        asFields[iDatasetSubtype1].Integer = 3;
2295
0
                        break;
2296
0
                    case GDALRelationshipCardinality::GRC_MANY_TO_ONE:
2297
                        // unreachable
2298
0
                        break;
2299
0
                }
2300
2301
0
                bool bRet =
2302
0
                    oTable.UpdateFeature(iCurFeat + 1, asFields, nullptr);
2303
0
                oTable.FreeAllFieldValues(asFields);
2304
0
                if (!bRet)
2305
0
                    return false;
2306
0
                bMatchFound = true;
2307
0
                break;
2308
0
            }
2309
0
        }
2310
2311
0
        if (!oTable.Sync())
2312
0
        {
2313
0
            return false;
2314
0
        }
2315
0
    }
2316
2317
0
    if (!bMatchFound)
2318
0
        return false;
2319
2320
    // First delete all existing item relationships for the item, and then we'll
2321
    // rebuild them again.
2322
0
    if (!RemoveRelationshipFromItemRelationships(osUUID))
2323
0
    {
2324
0
        failureReason =
2325
0
            "Could not remove relationship from GDB_ItemRelationships";
2326
0
        return false;
2327
0
    }
2328
0
    if (!RegisterRelationshipInItemRelationships(osUUID, osOriginUUID,
2329
0
                                                 osDestinationUUID))
2330
0
    {
2331
0
        failureReason =
2332
0
            "Could not register relationship in GDB_ItemRelationships";
2333
0
        return false;
2334
0
    }
2335
2336
0
    m_osMapRelationships[relationshipName] = std::move(relationship);
2337
2338
0
    return true;
2339
0
}
2340
2341
/************************************************************************/
2342
/*                          StartTransaction()                          */
2343
/************************************************************************/
2344
2345
OGRErr OGROpenFileGDBDataSource::StartTransaction(int bForce)
2346
0
{
2347
0
    if (!bForce)
2348
0
    {
2349
0
        CPLError(CE_Failure, CPLE_NotSupported,
2350
0
                 "Transactions only supported in forced mode");
2351
0
        return OGRERR_UNSUPPORTED_OPERATION;
2352
0
    }
2353
2354
0
    if (eAccess != GA_Update)
2355
0
        return OGRERR_FAILURE;
2356
2357
0
    if (m_bInTransaction)
2358
0
    {
2359
0
        CPLError(CE_Failure, CPLE_AppDefined,
2360
0
                 "Transaction is already in progress");
2361
0
        return OGRERR_FAILURE;
2362
0
    }
2363
2364
0
    m_osTransactionBackupDirname = CPLFormFilenameSafe(
2365
0
        m_osDirName.c_str(), ".ogrtransaction_backup", nullptr);
2366
0
    VSIStatBufL sStat;
2367
0
    if (VSIStatL(m_osTransactionBackupDirname.c_str(), &sStat) == 0)
2368
0
    {
2369
0
        CPLError(CE_Failure, CPLE_AppDefined,
2370
0
                 "A previous backup directory %s already exists, which means "
2371
0
                 "that a previous transaction was not cleanly committed or "
2372
0
                 "rolled back.\n"
2373
0
                 "Either manually restore the previous state from that "
2374
0
                 "directory or remove it, before creating a new transaction.",
2375
0
                 m_osTransactionBackupDirname.c_str());
2376
0
        return OGRERR_FAILURE;
2377
0
    }
2378
0
    else if (VSIMkdir(m_osTransactionBackupDirname.c_str(), 0755) != 0)
2379
0
    {
2380
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s",
2381
0
                 m_osTransactionBackupDirname.c_str());
2382
0
        return OGRERR_FAILURE;
2383
0
    }
2384
2385
0
    m_bInTransaction = true;
2386
0
    return OGRERR_NONE;
2387
0
}
2388
2389
/************************************************************************/
2390
/*                  BackupSystemTablesForTransaction()                  */
2391
/************************************************************************/
2392
2393
bool OGROpenFileGDBDataSource::BackupSystemTablesForTransaction()
2394
0
{
2395
0
    if (m_bSystemTablesBackedup)
2396
0
        return true;
2397
2398
0
    char **papszFiles = VSIReadDir(m_osDirName.c_str());
2399
0
    for (char **papszIter = papszFiles;
2400
0
         papszIter != nullptr && *papszIter != nullptr; ++papszIter)
2401
0
    {
2402
0
        const std::string osBasename = CPLGetBasenameSafe(*papszIter);
2403
0
        if (osBasename.size() == strlen("a00000001") &&
2404
0
            osBasename.compare(0, 8, "a0000000") == 0 && osBasename[8] >= '1' &&
2405
0
            osBasename[8] <= '8')
2406
0
        {
2407
0
            const std::string osDestFilename = CPLFormFilenameSafe(
2408
0
                m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
2409
0
            const std::string osSourceFilename =
2410
0
                CPLFormFilenameSafe(m_osDirName.c_str(), *papszIter, nullptr);
2411
0
            if (CPLCopyFile(osDestFilename.c_str(), osSourceFilename.c_str()) !=
2412
0
                0)
2413
0
            {
2414
0
                CSLDestroy(papszFiles);
2415
0
                return false;
2416
0
            }
2417
0
        }
2418
0
    }
2419
2420
0
    CSLDestroy(papszFiles);
2421
0
    m_bSystemTablesBackedup = true;
2422
0
    return true;
2423
0
}
2424
2425
/************************************************************************/
2426
/*                         CommitTransaction()                          */
2427
/************************************************************************/
2428
2429
OGRErr OGROpenFileGDBDataSource::CommitTransaction()
2430
0
{
2431
0
    if (!m_bInTransaction)
2432
0
    {
2433
0
        CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
2434
0
        return OGRERR_FAILURE;
2435
0
    }
2436
2437
0
    for (auto &poLayer : m_apoLayers)
2438
0
        poLayer->CommitEmulatedTransaction();
2439
2440
0
    VSIRmdirRecursive(m_osTransactionBackupDirname.c_str());
2441
2442
0
    m_bInTransaction = false;
2443
0
    m_bSystemTablesBackedup = false;
2444
0
    m_oSetLayersCreatedInTransaction.clear();
2445
0
    m_oSetLayersDeletedInTransaction.clear();
2446
2447
0
    return OGRERR_NONE;
2448
0
}
2449
2450
/************************************************************************/
2451
/*                        RollbackTransaction()                         */
2452
/************************************************************************/
2453
2454
OGRErr OGROpenFileGDBDataSource::RollbackTransaction()
2455
0
{
2456
0
    if (!m_bInTransaction)
2457
0
    {
2458
0
        CPLError(CE_Failure, CPLE_AppDefined, "No transaction in progress");
2459
0
        return OGRERR_FAILURE;
2460
0
    }
2461
2462
0
    OGRErr eErr = OGRERR_NONE;
2463
2464
    // Restore system tables
2465
0
    {
2466
0
        char **papszFiles = VSIReadDir(m_osTransactionBackupDirname.c_str());
2467
0
        if (papszFiles == nullptr)
2468
0
        {
2469
0
            CPLError(CE_Failure, CPLE_AppDefined,
2470
0
                     "Backup directory %s no longer found! Original database "
2471
0
                     "cannot be restored",
2472
0
                     m_osTransactionBackupDirname.c_str());
2473
0
            return OGRERR_FAILURE;
2474
0
        }
2475
0
        for (char **papszIter = papszFiles;
2476
0
             papszIter != nullptr && *papszIter != nullptr; ++papszIter)
2477
0
        {
2478
0
            const std::string osBasename = CPLGetBasenameSafe(*papszIter);
2479
0
            if (osBasename.size() == strlen("a00000001") &&
2480
0
                osBasename.compare(0, 8, "a0000000") == 0 &&
2481
0
                osBasename[8] >= '1' && osBasename[8] <= '8')
2482
0
            {
2483
0
                const std::string osDestFilename = CPLFormFilenameSafe(
2484
0
                    m_osDirName.c_str(), *papszIter, nullptr);
2485
0
                const std::string osSourceFilename = CPLFormFilenameSafe(
2486
0
                    m_osTransactionBackupDirname.c_str(), *papszIter, nullptr);
2487
0
                if (CPLCopyFile(osDestFilename.c_str(),
2488
0
                                osSourceFilename.c_str()) != 0)
2489
0
                {
2490
0
                    eErr = OGRERR_FAILURE;
2491
0
                }
2492
0
            }
2493
0
        }
2494
0
        CSLDestroy(papszFiles);
2495
0
    }
2496
2497
    // Restore layers in their original state
2498
0
    for (auto &poLayer : m_apoLayers)
2499
0
        poLayer->RollbackEmulatedTransaction();
2500
0
    for (auto &poLayer : m_oSetLayersDeletedInTransaction)
2501
0
        poLayer->RollbackEmulatedTransaction();
2502
2503
    // Remove layers created during transaction
2504
0
    for (auto poLayer : m_oSetLayersCreatedInTransaction)
2505
0
    {
2506
0
        const std::string osThisBasename =
2507
0
            CPLGetBasenameSafe(poLayer->GetFilename().c_str());
2508
0
        poLayer->Close();
2509
2510
0
        char **papszFiles = VSIReadDir(m_osDirName.c_str());
2511
0
        for (char **papszIter = papszFiles;
2512
0
             papszIter != nullptr && *papszIter != nullptr; ++papszIter)
2513
0
        {
2514
0
            const std::string osBasename = CPLGetBasenameSafe(*papszIter);
2515
0
            if (osBasename == osThisBasename)
2516
0
            {
2517
0
                const std::string osDestFilename = CPLFormFilenameSafe(
2518
0
                    m_osDirName.c_str(), *papszIter, nullptr);
2519
0
                VSIUnlink(osDestFilename.c_str());
2520
0
            }
2521
0
        }
2522
0
        CSLDestroy(papszFiles);
2523
0
    }
2524
2525
0
    if (eErr == OGRERR_NONE)
2526
0
    {
2527
0
        if (VSIRmdirRecursive(m_osTransactionBackupDirname.c_str()) != 0)
2528
0
        {
2529
0
            CPLError(
2530
0
                CE_Warning, CPLE_AppDefined,
2531
0
                "Backup directory %s could not be destroyed. But original "
2532
0
                "dataset "
2533
0
                "should have been properly restored. You will need to manually "
2534
0
                "remove the backup directory.",
2535
0
                m_osTransactionBackupDirname.c_str());
2536
0
        }
2537
0
    }
2538
0
    else
2539
0
    {
2540
0
        CPLError(CE_Failure, CPLE_AppDefined,
2541
0
                 "Backup directory %s could not be properly restored onto "
2542
0
                 "live database. Corruption is likely!",
2543
0
                 m_osTransactionBackupDirname.c_str());
2544
0
    }
2545
2546
0
    m_bInTransaction = false;
2547
0
    m_bSystemTablesBackedup = false;
2548
0
    m_oSetLayersCreatedInTransaction.clear();
2549
0
    m_oSetLayersDeletedInTransaction.clear();
2550
2551
0
    return eErr;
2552
0
}