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