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