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