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