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