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) 2014, 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 :
16 : #include <stddef.h>
17 : #include <stdio.h>
18 : #include <string.h>
19 : #include <map>
20 : #include <memory>
21 : #include <string>
22 : #include <utility>
23 : #include <vector>
24 :
25 : #include "cpl_conv.h"
26 : #include "cpl_error.h"
27 : #include "cpl_string.h"
28 : #include "cpl_vsi.h"
29 : #include "filegdbtable.h"
30 : #include "gdal.h"
31 : #include "ogr_core.h"
32 : #include "ogr_feature.h"
33 : #include "ogr_geometry.h"
34 : #include "ogr_mem.h"
35 : #include "ogrsf_frmts.h"
36 : #include "ogr_swq.h"
37 :
38 : #include "filegdb_fielddomain.h"
39 : #include "filegdb_relationship.h"
40 :
41 : /***********************************************************************/
42 : /* OGROpenFileGDBGroup */
43 : /***********************************************************************/
44 :
45 : class OGROpenFileGDBGroup final : public GDALGroup
46 : {
47 : protected:
48 : friend class OGROpenFileGDBDataSource;
49 : std::vector<std::shared_ptr<GDALGroup>> m_apoSubGroups{};
50 : std::vector<OGRLayer *> m_apoLayers{};
51 : std::string m_osDefinition{};
52 :
53 : public:
54 510 : OGROpenFileGDBGroup(const std::string &osParentName, const char *pszName)
55 510 : : GDALGroup(osParentName, pszName)
56 : {
57 510 : }
58 :
59 20 : void SetDefinition(const std::string &osDefinition)
60 : {
61 20 : m_osDefinition = osDefinition;
62 20 : }
63 :
64 23 : const std::string &GetDefinition() const
65 : {
66 23 : return m_osDefinition;
67 : }
68 :
69 : std::vector<std::string>
70 : GetGroupNames(CSLConstList papszOptions) const override;
71 : std::shared_ptr<GDALGroup>
72 : OpenGroup(const std::string &osName,
73 : CSLConstList papszOptions) const override;
74 :
75 : std::vector<std::string>
76 : GetVectorLayerNames(CSLConstList papszOptions) const override;
77 : OGRLayer *OpenVectorLayer(const std::string &osName,
78 : CSLConstList papszOptions) const override;
79 : };
80 :
81 : /************************************************************************/
82 : /* OGROpenFileGDBDataSource() */
83 : /************************************************************************/
84 797 : OGROpenFileGDBDataSource::OGROpenFileGDBDataSource()
85 797 : : m_papszFiles(nullptr), bLastSQLUsedOptimizedImplementation(false)
86 : {
87 797 : }
88 :
89 : /************************************************************************/
90 : /* ~OGROpenFileGDBDataSource() */
91 : /************************************************************************/
92 1594 : OGROpenFileGDBDataSource::~OGROpenFileGDBDataSource()
93 :
94 : {
95 797 : OGROpenFileGDBDataSource::Close();
96 1594 : }
97 :
98 : /************************************************************************/
99 : /* Close() */
100 : /************************************************************************/
101 :
102 1503 : CPLErr OGROpenFileGDBDataSource::Close()
103 : {
104 1503 : CPLErr eErr = CE_None;
105 1503 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
106 : {
107 797 : if (m_bInTransaction)
108 2 : OGROpenFileGDBDataSource::RollbackTransaction();
109 :
110 797 : if (OGROpenFileGDBDataSource::FlushCache(true) != CE_None)
111 0 : eErr = CE_Failure;
112 :
113 797 : m_apoLayers.clear();
114 797 : m_apoHiddenLayers.clear();
115 797 : CSLDestroy(m_papszFiles);
116 :
117 797 : if (GDALDataset::Close() != CE_None)
118 0 : eErr = CE_Failure;
119 : }
120 1503 : return eErr;
121 : }
122 :
123 : /************************************************************************/
124 : /* FileExists() */
125 : /************************************************************************/
126 :
127 17363 : int OGROpenFileGDBDataSource::FileExists(const char *pszFilename)
128 : {
129 17363 : if (m_papszFiles)
130 17354 : return CSLFindString(m_papszFiles, CPLGetFilename(pszFilename)) >= 0;
131 :
132 : VSIStatBufL sStat;
133 9 : CPLString osFilename(pszFilename);
134 9 : return VSIStatExL(osFilename, &sStat, VSI_STAT_EXISTS_FLAG) == 0;
135 : }
136 :
137 : /************************************************************************/
138 : /* Open() */
139 : /************************************************************************/
140 :
141 556 : bool OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo,
142 : bool &bRetryFileGDBOut)
143 : {
144 556 : bRetryFileGDBOut = false;
145 :
146 556 : if (poOpenInfo->nOpenFlags == (GDAL_OF_RASTER | GDAL_OF_UPDATE))
147 : {
148 0 : CPLError(CE_Failure, CPLE_NotSupported,
149 : "Update mode of rasters is not supported");
150 0 : return false;
151 : }
152 :
153 556 : m_osDirName = poOpenInfo->pszFilename;
154 :
155 1112 : std::string osRasterLayerName;
156 556 : if (STARTS_WITH(poOpenInfo->pszFilename, "OpenFileGDB:"))
157 : {
158 : const CPLStringList aosTokens(CSLTokenizeString2(
159 29 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
160 29 : if (aosTokens.size() == 4 && strlen(aosTokens[1]) == 1)
161 : {
162 0 : m_osDirName = aosTokens[1];
163 0 : m_osDirName += ':';
164 0 : m_osDirName += aosTokens[2];
165 0 : osRasterLayerName = aosTokens[3];
166 : }
167 29 : else if (aosTokens.size() == 3)
168 : {
169 29 : m_osDirName = aosTokens[1];
170 29 : osRasterLayerName = aosTokens[2];
171 : }
172 : else
173 : {
174 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid connection string");
175 0 : return false;
176 : }
177 : }
178 :
179 1112 : FileGDBTable oTable;
180 :
181 : // Whether to open directly a given .gdbtable file (mostly for debugging
182 : // purposes)
183 556 : int nInterestTable = 0;
184 556 : unsigned int unInterestTable = 0;
185 556 : const char *pszFilenameWithoutPath = CPLGetFilename(m_osDirName.c_str());
186 1190 : if (poOpenInfo->nHeaderBytes > 0 &&
187 78 : strlen(pszFilenameWithoutPath) == strlen("a00000000.gdbtable") &&
188 22 : pszFilenameWithoutPath[0] == 'a' &&
189 655 : EQUAL(pszFilenameWithoutPath + strlen("a00000000"), ".gdbtable") &&
190 21 : sscanf(pszFilenameWithoutPath, "a%08x.gdbtable", &unInterestTable) == 1)
191 : {
192 21 : nInterestTable = static_cast<int>(unInterestTable);
193 21 : m_osDirName = CPLGetPathSafe(m_osDirName);
194 : }
195 :
196 646 : if (EQUAL(CPLGetExtensionSafe(m_osDirName).c_str(), "zip") &&
197 90 : !STARTS_WITH(m_osDirName, "/vsizip/"))
198 : {
199 84 : m_osDirName = "/vsizip/" + m_osDirName;
200 : }
201 472 : else if (EQUAL(CPLGetExtensionSafe(m_osDirName).c_str(), "tar") &&
202 0 : !STARTS_WITH(m_osDirName, "/vsitar/"))
203 : {
204 0 : m_osDirName = "/vsitar/" + m_osDirName;
205 : }
206 :
207 1019 : if (STARTS_WITH(m_osDirName, "/vsizip/") ||
208 463 : STARTS_WITH(m_osDirName, "/vsitar/"))
209 : {
210 : /* Look for one subdirectory ending with .gdb extension */
211 94 : char **papszDir = VSIReadDir(m_osDirName);
212 94 : int iCandidate = -1;
213 621 : for (int i = 0; papszDir && papszDir[i] != nullptr; i++)
214 : {
215 : VSIStatBufL sStat;
216 527 : if (EQUAL(CPLGetExtensionSafe(papszDir[i]).c_str(), "gdb") &&
217 84 : VSIStatL(CPLSPrintf("%s/%s", m_osDirName.c_str(), papszDir[i]),
218 611 : &sStat) == 0 &&
219 84 : VSI_ISDIR(sStat.st_mode))
220 : {
221 84 : if (iCandidate < 0)
222 84 : iCandidate = i;
223 : else
224 : {
225 0 : iCandidate = -1;
226 0 : break;
227 : }
228 : }
229 : }
230 94 : if (iCandidate >= 0)
231 : {
232 84 : m_osDirName += "/";
233 84 : m_osDirName += papszDir[iCandidate];
234 : }
235 94 : CSLDestroy(papszDir);
236 : }
237 :
238 : const std::string osTransactionBackupDirname = CPLFormFilenameSafe(
239 1112 : m_osDirName.c_str(), ".ogrtransaction_backup", nullptr);
240 : VSIStatBufL sStat;
241 556 : if (VSIStatL(osTransactionBackupDirname.c_str(), &sStat) == 0)
242 : {
243 2 : if (poOpenInfo->eAccess == GA_Update)
244 : {
245 1 : CPLError(
246 : CE_Failure, CPLE_AppDefined,
247 : "A previous backup directory %s already exists, which means "
248 : "that a previous transaction was not cleanly committed or "
249 : "rolled back.\n"
250 : "Either manually restore the previous state from that "
251 : "directory or remove it, before opening in update mode.",
252 : osTransactionBackupDirname.c_str());
253 1 : return false;
254 : }
255 : else
256 : {
257 1 : CPLError(
258 : CE_Warning, CPLE_AppDefined,
259 : "A previous backup directory %s already exists, which means "
260 : "that a previous transaction was not cleanly committed or "
261 : "rolled back.\n"
262 : "You may want to restore the previous state from that "
263 : "directory or remove it.",
264 : osTransactionBackupDirname.c_str());
265 : }
266 : }
267 :
268 555 : m_papszFiles = VSIReadDir(m_osDirName);
269 :
270 : /* Explore catalog table */
271 : m_osGDBSystemCatalogFilename =
272 555 : CPLFormFilenameSafe(m_osDirName, "a00000001", "gdbtable");
273 1100 : if (!FileExists(m_osGDBSystemCatalogFilename.c_str()) ||
274 545 : !oTable.Open(m_osGDBSystemCatalogFilename.c_str(),
275 545 : poOpenInfo->eAccess == GA_Update))
276 : {
277 36 : if (nInterestTable > 0 && FileExists(poOpenInfo->pszFilename))
278 : {
279 1 : const char *pszLyrName = CPLSPrintf("a%08x", nInterestTable);
280 : auto poLayer = std::make_unique<OGROpenFileGDBLayer>(
281 1 : this, poOpenInfo->pszFilename, pszLyrName, "", "",
282 2 : eAccess == GA_Update);
283 : const std::string osTablX =
284 2 : CPLResetExtensionSafe(poOpenInfo->pszFilename, "gdbtablx");
285 1 : if ((!FileExists(osTablX.c_str()) &&
286 0 : poLayer->GetLayerDefn()->GetFieldCount() == 0 &&
287 2 : poLayer->GetFeatureCount() == 0) ||
288 1 : !poLayer->IsValidLayerDefn())
289 : {
290 0 : return false;
291 : }
292 1 : m_apoLayers.push_back(std::move(poLayer));
293 1 : return true;
294 : }
295 35 : return false;
296 : }
297 :
298 519 : const int idxName = oTable.GetFieldIdx("Name");
299 519 : const int idxFileformat = oTable.GetFieldIdx("FileFormat");
300 2070 : if (!(idxName >= 0 && idxFileformat >= 0 &&
301 517 : oTable.GetField(idxName)->GetType() == FGFT_STRING &&
302 1034 : (oTable.GetField(idxFileformat)->GetType() == FGFT_INT16 ||
303 517 : oTable.GetField(idxFileformat)->GetType() == FGFT_INT32)))
304 : {
305 2 : return false;
306 : }
307 :
308 517 : int iGDBItems = -1; /* V10 */
309 517 : int iGDBFeatureClasses = -1; /* V9.X */
310 517 : int iGDBObjectClasses = -1; /* V9.X */
311 :
312 1034 : std::vector<std::string> aosTableNames;
313 : try
314 : {
315 20068 : for (int i = 0; i < oTable.GetTotalRecordCount(); i++)
316 : {
317 19551 : if (!oTable.SelectRow(i))
318 : {
319 5003 : if (oTable.HasGotError())
320 0 : break;
321 5003 : aosTableNames.push_back("");
322 5003 : continue;
323 : }
324 :
325 14548 : const OGRField *psField = oTable.GetFieldValue(idxName);
326 14548 : if (psField != nullptr)
327 : {
328 14458 : aosTableNames.push_back(psField->String);
329 14458 : if (strcmp(psField->String, "GDB_SpatialRefs") == 0)
330 : {
331 : // Normally a00000003
332 1030 : m_osGDBSpatialRefsFilename = CPLFormFilenameSafe(
333 : m_osDirName, CPLSPrintf("a%08x.gdbtable", i + 1),
334 515 : nullptr);
335 : }
336 13943 : else if (strcmp(psField->String, "GDB_Items") == 0)
337 : {
338 : // Normally a00000004 but it has been seen in some datasets
339 : // to not be a00000004
340 980 : m_osGDBItemsFilename = CPLFormFilenameSafe(
341 : m_osDirName, CPLSPrintf("a%08x.gdbtable", i + 1),
342 490 : nullptr);
343 490 : iGDBItems = i;
344 : }
345 13453 : else if (strcmp(psField->String, "GDB_ItemRelationships") == 0)
346 : {
347 : // Normally a00000006 but can be a00000005 sometimes (e.g
348 : // autotest/ogr/data/filegdb/Domains.gdb)
349 984 : m_osGDBItemRelationshipsFilename = CPLFormFilenameSafe(
350 : m_osDirName, CPLSPrintf("a%08x.gdbtable", i + 1),
351 492 : nullptr);
352 : }
353 12961 : else if (strcmp(psField->String, "GDB_FeatureClasses") == 0)
354 : {
355 : // FileGDB v9
356 23 : iGDBFeatureClasses = i;
357 : }
358 12938 : else if (strcmp(psField->String, "GDB_ObjectClasses") == 0)
359 : {
360 : // FileGDB v9
361 23 : iGDBObjectClasses = i;
362 : }
363 14458 : m_osMapNameToIdx[psField->String] = 1 + i;
364 : }
365 : else
366 : {
367 90 : aosTableNames.push_back("");
368 : }
369 : }
370 : }
371 0 : catch (const std::exception &)
372 : {
373 0 : return false;
374 : }
375 :
376 517 : oTable.Close();
377 :
378 1034 : std::set<int> oSetIgnoredRasterLayerTableNum;
379 517 : if (iGDBItems >= 0)
380 : {
381 490 : eAccess = poOpenInfo->eAccess;
382 :
383 490 : const bool bRet = OpenFileGDBv10(
384 : iGDBItems, nInterestTable, poOpenInfo, osRasterLayerName,
385 : oSetIgnoredRasterLayerTableNum, bRetryFileGDBOut);
386 490 : if (!bRet)
387 2 : return false;
388 : }
389 27 : else if (iGDBFeatureClasses >= 0 && iGDBObjectClasses >= 0)
390 : {
391 23 : if (poOpenInfo->eAccess == GA_Update)
392 : {
393 0 : CPLError(CE_Failure, CPLE_NotSupported,
394 : "Edition of existing FileGDB v9 datasets not supported");
395 0 : return false;
396 : }
397 :
398 23 : int bRet = OpenFileGDBv9(iGDBFeatureClasses, iGDBObjectClasses,
399 : nInterestTable, poOpenInfo, osRasterLayerName,
400 : oSetIgnoredRasterLayerTableNum);
401 23 : if (!bRet)
402 23 : return false;
403 : }
404 : else
405 : {
406 4 : CPLError(CE_Failure, CPLE_AppDefined,
407 : "No GDB_Items nor GDB_FeatureClasses table");
408 4 : return false;
409 : }
410 :
411 511 : if (m_apoLayers.empty() && nInterestTable > 0)
412 : {
413 19 : if (FileExists(poOpenInfo->pszFilename))
414 : {
415 19 : const char *pszLyrName = nullptr;
416 38 : if (nInterestTable <= static_cast<int>(aosTableNames.size()) &&
417 19 : !aosTableNames[nInterestTable - 1].empty())
418 19 : pszLyrName = aosTableNames[nInterestTable - 1].c_str();
419 : else
420 0 : pszLyrName = CPLSPrintf("a%08x", nInterestTable);
421 19 : m_apoLayers.push_back(std::make_unique<OGROpenFileGDBLayer>(
422 19 : this, poOpenInfo->pszFilename, pszLyrName, "", "",
423 38 : eAccess == GA_Update));
424 : }
425 : else
426 : {
427 0 : return false;
428 : }
429 : }
430 :
431 511 : if (nInterestTable == 0)
432 : {
433 491 : const bool bListAllTables = CPLTestBool(CSLFetchNameValueDef(
434 491 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "NO"));
435 :
436 : // add additional tables which are not present in the
437 : // GDB_Items/GDB_FeatureClasses/GDB_ObjectClasses tables
438 10935 : for (const auto &oIter : m_osMapNameToIdx)
439 : {
440 : // test if layer is already added
441 10444 : if (GDALDataset::GetLayerByName(oIter.first.c_str()))
442 0 : continue;
443 :
444 10444 : if (bListAllTables || !IsPrivateLayerName(oIter.first))
445 : {
446 5905 : const int idx = oIter.second;
447 5905 : const CPLString osFilename(CPLFormFilenameSafe(
448 11810 : m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable"));
449 11570 : if (!cpl::contains(oSetIgnoredRasterLayerTableNum, idx) &&
450 5665 : FileExists(osFilename))
451 : {
452 : m_apoLayers.emplace_back(
453 5284 : std::make_unique<OGROpenFileGDBLayer>(
454 5284 : this, osFilename, oIter.first.c_str(), "", "",
455 10568 : eAccess == GA_Update));
456 5284 : if (m_poRootGroup)
457 : {
458 5260 : cpl::down_cast<OGROpenFileGDBGroup *>(
459 : m_poRootGroup.get())
460 : ->m_apoLayers.emplace_back(
461 5260 : m_apoLayers.back().get());
462 : }
463 : }
464 : }
465 : }
466 : }
467 :
468 511 : if (!osRasterLayerName.empty())
469 : {
470 29 : m_aosSubdatasets.Clear();
471 29 : SetDescription(poOpenInfo->pszFilename);
472 29 : return nBands != 0;
473 : }
474 :
475 : // If opening in raster-only mode, return false if there are no raster
476 : // layers
477 482 : if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
478 48 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
479 : {
480 21 : if (m_aosSubdatasets.empty())
481 : {
482 13 : return false;
483 : }
484 8 : else if (m_aosSubdatasets.size() == 2)
485 : {
486 : // If there is a single raster dataset, open it right away.
487 7 : return true;
488 : }
489 : }
490 : // If opening in vector-only mode, return false if there are no vector
491 : // layers and opened in read-only mode
492 461 : else if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
493 434 : (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
494 : {
495 431 : if (m_apoLayers.empty() && poOpenInfo->eAccess == GA_ReadOnly)
496 : {
497 2 : return false;
498 : }
499 : }
500 :
501 460 : return true;
502 : }
503 :
504 : /***********************************************************************/
505 : /* AddLayer() */
506 : /***********************************************************************/
507 :
508 3754 : OGRLayer *OGROpenFileGDBDataSource::AddLayer(
509 : const CPLString &osName, int nInterestTable, int &nCandidateLayers,
510 : int &nLayersCDF, const CPLString &osDefinition,
511 : const CPLString &osDocumentation, OGRwkbGeometryType eGeomType,
512 : const std::string &osParentDefinition)
513 : {
514 3754 : const auto oIter = m_osMapNameToIdx.find(osName);
515 3754 : int idx = 0;
516 3754 : if (oIter != m_osMapNameToIdx.end())
517 3740 : idx = oIter->second;
518 3754 : if (idx > 0 && (nInterestTable <= 0 || nInterestTable == idx))
519 : {
520 3663 : m_osMapNameToIdx.erase(oIter);
521 :
522 7326 : const CPLString osFilename = CPLFormFilenameSafe(
523 7326 : m_osDirName, CPLOPrintf("a%08x", idx).c_str(), "gdbtable");
524 3663 : if (FileExists(osFilename))
525 : {
526 3649 : nCandidateLayers++;
527 :
528 3649 : if (m_papszFiles != nullptr)
529 : {
530 : const CPLString osSDC =
531 3649 : CPLResetExtensionSafe(osFilename, "gdbtable.sdc");
532 : const CPLString osCDF =
533 3649 : CPLResetExtensionSafe(osFilename, "gdbtable.cdf");
534 3649 : if (FileExists(osCDF))
535 : {
536 3 : nLayersCDF++;
537 :
538 3 : if (GDALGetDriverByName("FileGDB"))
539 : {
540 2 : CPLDebug(
541 : "OpenFileGDB",
542 : "%s layer has a %s file using Compressed Data "
543 : "Format (CDF) that is unhandled by the OpenFileGDB "
544 : "driver, but could be handled by the FileGDB "
545 : "driver (which will be attempted).",
546 : osName.c_str(), osCDF.c_str());
547 : }
548 : else
549 : {
550 : // Look into hidden drivers, that is drivers that have
551 : // been built as plugins, but are not currently available
552 : // on the system.
553 2 : std::string osFileGDBDriverMsg;
554 1 : auto poDM = GetGDALDriverManager();
555 : const int nAllDrivercount =
556 1 : poDM->GetDriverCount(/* bIncludeHidden = */ true);
557 239 : for (int i = 0; i < nAllDrivercount; ++i)
558 : {
559 : auto poMissingPluginDriver =
560 238 : poDM->GetDriver(i, /* bIncludeHidden = */ true);
561 238 : if (EQUAL(poMissingPluginDriver->GetDescription(),
562 : "FileGDB"))
563 : {
564 0 : osFileGDBDriverMsg += " However the plugin ";
565 : osFileGDBDriverMsg +=
566 : poMissingPluginDriver->GetMetadataItem(
567 0 : "MISSING_PLUGIN_FILENAME");
568 : osFileGDBDriverMsg +=
569 : " is not available in your "
570 0 : "installation.";
571 0 : if (const char *pszInstallationMsg =
572 0 : poMissingPluginDriver->GetMetadataItem(
573 0 : GDAL_DMD_PLUGIN_INSTALLATION_MESSAGE))
574 : {
575 0 : osFileGDBDriverMsg += " ";
576 0 : osFileGDBDriverMsg += pszInstallationMsg;
577 : }
578 0 : break;
579 : }
580 : }
581 1 : CPLError(CE_Warning, CPLE_AppDefined,
582 : "%s layer has a %s file using Compressed Data "
583 : "Format (CDF) that is unhandled by the "
584 : "OpenFileGDB driver, but could be handled by "
585 : "the FileGDB driver.%s",
586 : osName.c_str(), osCDF.c_str(),
587 : osFileGDBDriverMsg.c_str());
588 : }
589 3 : return nullptr;
590 : }
591 3646 : else if (FileExists(osSDC))
592 : {
593 : // SDC is not handled by the FileGDB driver
594 0 : CPLError(CE_Warning, CPLE_AppDefined,
595 : "%s layer has a %s file using Smart Data "
596 : "Compression (SDC) that is unhandled.",
597 : osName.c_str(), osSDC.c_str());
598 0 : return nullptr;
599 : }
600 : }
601 :
602 3646 : m_apoLayers.emplace_back(std::make_unique<OGROpenFileGDBLayer>(
603 : this, osFilename, osName, osDefinition, osDocumentation,
604 3646 : eAccess == GA_Update, eGeomType, osParentDefinition));
605 3646 : return m_apoLayers.back().get();
606 : }
607 : }
608 105 : return nullptr;
609 : }
610 :
611 11 : std::vector<std::string> OGROpenFileGDBGroup::GetGroupNames(CSLConstList) const
612 : {
613 11 : std::vector<std::string> ret;
614 18 : for (const auto &poSubGroup : m_apoSubGroups)
615 7 : ret.emplace_back(poSubGroup->GetName());
616 11 : return ret;
617 : }
618 :
619 : std::shared_ptr<GDALGroup>
620 6 : OGROpenFileGDBGroup::OpenGroup(const std::string &osName, CSLConstList) const
621 : {
622 10 : for (const auto &poSubGroup : m_apoSubGroups)
623 : {
624 9 : if (poSubGroup->GetName() == osName)
625 5 : return poSubGroup;
626 : }
627 1 : return nullptr;
628 : }
629 :
630 : std::vector<std::string>
631 11 : OGROpenFileGDBGroup::GetVectorLayerNames(CSLConstList) const
632 : {
633 11 : std::vector<std::string> ret;
634 49 : for (const auto &poLayer : m_apoLayers)
635 38 : ret.emplace_back(poLayer->GetName());
636 11 : return ret;
637 : }
638 :
639 23 : OGRLayer *OGROpenFileGDBGroup::OpenVectorLayer(const std::string &osName,
640 : CSLConstList) const
641 : {
642 105 : for (const auto &poLayer : m_apoLayers)
643 : {
644 104 : if (poLayer->GetName() == osName)
645 22 : return poLayer;
646 : }
647 1 : return nullptr;
648 : }
649 :
650 : /***********************************************************************/
651 : /* OpenFileGDBv10() */
652 : /***********************************************************************/
653 :
654 490 : bool OGROpenFileGDBDataSource::OpenFileGDBv10(
655 : int iGDBItems, int nInterestTable, const GDALOpenInfo *poOpenInfo,
656 : const std::string &osRasterLayerName,
657 : std::set<int> &oSetIgnoredRasterLayerTableNum, bool &bRetryFileGDBOut)
658 : {
659 :
660 490 : CPLDebug("OpenFileGDB", "FileGDB v10 or later");
661 :
662 980 : FileGDBTable oTable;
663 :
664 490 : const CPLString osFilename(CPLFormFilenameSafe(
665 980 : m_osDirName, CPLSPrintf("a%08x.gdbtable", iGDBItems + 1), nullptr));
666 :
667 : // Normally we don't need to update in update mode the GDB_Items table,
668 : // but this may help repairing it, if we have corrupted it with past
669 : // GDAL versions.
670 490 : const bool bOpenInUpdateMode =
671 490 : (poOpenInfo->nOpenFlags & GDAL_OF_UPDATE) != 0;
672 490 : if (!oTable.Open(osFilename, bOpenInUpdateMode))
673 0 : return false;
674 :
675 490 : const int iUUID = oTable.GetFieldIdx("UUID");
676 490 : const int iType = oTable.GetFieldIdx("Type");
677 490 : const int iName = oTable.GetFieldIdx("Name");
678 490 : const int iPath = oTable.GetFieldIdx("Path");
679 490 : const int iDefinition = oTable.GetFieldIdx("Definition");
680 490 : const int iDocumentation = oTable.GetFieldIdx("Documentation");
681 490 : if (iUUID < 0 || iType < 0 || iName < 0 || iPath < 0 || iDefinition < 0 ||
682 490 : iDocumentation < 0 ||
683 980 : oTable.GetField(iUUID)->GetType() != FGFT_GLOBALID ||
684 980 : oTable.GetField(iType)->GetType() != FGFT_GUID ||
685 980 : oTable.GetField(iName)->GetType() != FGFT_STRING ||
686 980 : oTable.GetField(iPath)->GetType() != FGFT_STRING ||
687 1470 : oTable.GetField(iDefinition)->GetType() != FGFT_XML ||
688 490 : oTable.GetField(iDocumentation)->GetType() != FGFT_XML)
689 : {
690 0 : CPLError(CE_Failure, CPLE_AppDefined,
691 : "Wrong structure for GDB_Items table");
692 0 : return false;
693 : }
694 :
695 980 : auto poRootGroup = std::make_shared<OGROpenFileGDBGroup>(std::string(), "");
696 490 : m_poRootGroup = poRootGroup;
697 : std::map<std::string, std::shared_ptr<OGROpenFileGDBGroup>>
698 980 : oMapPathToFeatureDataset;
699 :
700 : // First pass to collect FeatureDatasets
701 9679 : for (int i = 0; i < oTable.GetTotalRecordCount(); i++)
702 : {
703 9189 : if (!oTable.SelectRow(i))
704 : {
705 2137 : if (oTable.HasGotError())
706 0 : break;
707 2137 : continue;
708 : }
709 :
710 14104 : CPLString osType;
711 7052 : const OGRField *psTypeField = oTable.GetFieldValue(iType);
712 7052 : if (psTypeField != nullptr)
713 : {
714 7052 : const char *pszType = psTypeField->String;
715 7052 : if (pszType)
716 7052 : osType = pszType;
717 : }
718 :
719 14104 : std::string osPath;
720 7052 : const OGRField *psField = oTable.GetFieldValue(iPath);
721 7052 : if (psField != nullptr)
722 : {
723 7052 : const char *pszPath = psField->String;
724 7052 : if (pszPath)
725 7052 : osPath = pszPath;
726 : }
727 :
728 7052 : if (osPath == "\\")
729 : {
730 490 : psField = oTable.GetFieldValue(iUUID);
731 490 : if (psField != nullptr)
732 : {
733 490 : const char *pszUUID = psField->String;
734 490 : if (pszUUID)
735 490 : m_osRootGUID = pszUUID;
736 : }
737 : }
738 :
739 7052 : psField = oTable.GetFieldValue(iDefinition);
740 7052 : if (psField != nullptr &&
741 6562 : strstr(psField->String, "DEFeatureDataset") != nullptr)
742 : {
743 40 : const std::string osDefinition(psField->String);
744 :
745 40 : std::string osName;
746 20 : psField = oTable.GetFieldValue(iName);
747 20 : if (psField != nullptr)
748 : {
749 20 : const char *pszName = psField->String;
750 20 : if (pszName)
751 20 : osName = pszName;
752 : }
753 :
754 20 : if (!osName.empty() && !osPath.empty())
755 : {
756 : auto poSubGroup = std::make_shared<OGROpenFileGDBGroup>(
757 40 : poRootGroup->GetName(), osName.c_str());
758 20 : poSubGroup->SetDefinition(osDefinition);
759 20 : oMapPathToFeatureDataset[osPath] = poSubGroup;
760 20 : poRootGroup->m_apoSubGroups.emplace_back(poSubGroup);
761 20 : }
762 : }
763 13574 : else if (psField != nullptr &&
764 6542 : osType.tolower().compare(pszRelationshipTypeUUID) == 0)
765 : {
766 : // relationship item
767 720 : auto poRelationship = ParseXMLRelationshipDef(psField->String);
768 240 : if (poRelationship)
769 : {
770 240 : const auto &relationshipName = poRelationship->GetName();
771 480 : m_osMapRelationships.insert(std::pair(
772 720 : std::string(relationshipName), std::move(poRelationship)));
773 : }
774 : }
775 : }
776 :
777 : // Now collect layers
778 490 : int nCandidateLayers = 0;
779 490 : int nLayersCDF = 0;
780 490 : bool bRet = true;
781 9679 : for (int i = 0; i < oTable.GetTotalRecordCount(); i++)
782 : {
783 9189 : if (!oTable.SelectRow(i))
784 : {
785 2137 : if (oTable.HasGotError())
786 0 : break;
787 2137 : continue;
788 : }
789 :
790 7052 : const OGRField *psField = oTable.GetFieldValue(iDefinition);
791 7052 : if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
792 5252 : (strstr(psField->String, "DEFeatureClassInfo") != nullptr ||
793 2424 : strstr(psField->String, "DETableInfo") != nullptr))
794 : {
795 6750 : CPLString osDefinition(psField->String);
796 :
797 3375 : psField = oTable.GetFieldValue(iDocumentation);
798 : CPLString osDocumentation(psField != nullptr ? psField->String
799 6750 : : "");
800 :
801 3375 : std::shared_ptr<OGROpenFileGDBGroup> poParent;
802 3375 : psField = oTable.GetFieldValue(iPath);
803 3375 : if (psField != nullptr)
804 : {
805 3375 : const char *pszPath = psField->String;
806 3375 : if (pszPath)
807 : {
808 6750 : std::string osPath(pszPath);
809 3375 : const auto nPos = osPath.rfind('\\');
810 3375 : if (nPos != 0 && nPos != std::string::npos)
811 : {
812 46 : std::string osPathParent = osPath.substr(0, nPos);
813 : const auto oIter =
814 23 : oMapPathToFeatureDataset.find(osPathParent);
815 23 : if (oIter == oMapPathToFeatureDataset.end())
816 : {
817 0 : CPLError(CE_Warning, CPLE_AppDefined,
818 : "Cannot find feature dataset of "
819 : "path %s referenced by table %s",
820 : osPathParent.c_str(), pszPath);
821 : }
822 : else
823 : {
824 23 : poParent = oIter->second;
825 : }
826 : }
827 : }
828 : }
829 :
830 3375 : psField = oTable.GetFieldValue(iName);
831 3375 : if (psField != nullptr)
832 : {
833 3375 : OGRLayer *poLayer = AddLayer(
834 3375 : psField->String, nInterestTable, nCandidateLayers,
835 : nLayersCDF, osDefinition, osDocumentation, wkbUnknown,
836 6750 : poParent ? poParent->GetDefinition() : std::string());
837 :
838 3375 : if (poLayer)
839 : {
840 3267 : if (poParent)
841 : {
842 : // Add the layer to the group of the corresponding
843 : // feature dataset
844 19 : poParent->m_apoLayers.emplace_back(poLayer);
845 : }
846 : else
847 : {
848 3248 : poRootGroup->m_apoLayers.emplace_back(poLayer);
849 : }
850 : }
851 3375 : }
852 : }
853 3677 : else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
854 1877 : (strstr(psField->String, "GPCodedValueDomain2") != nullptr ||
855 801 : strstr(psField->String, "GPRangeDomain2") != nullptr))
856 : {
857 3285 : auto poDomain = ParseXMLFieldDomainDef(psField->String);
858 1095 : if (poDomain)
859 : {
860 1095 : const auto &domainName = poDomain->GetName();
861 : m_oMapFieldDomains.insert(
862 1095 : std::pair(std::string(domainName), std::move(poDomain)));
863 1095 : }
864 : }
865 2582 : else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
866 1438 : strstr(psField->String, "DERasterDataset"))
867 : {
868 2048 : const std::string osDefinition(psField->String);
869 :
870 1024 : psField = oTable.GetFieldValue(iName);
871 1024 : if (psField)
872 : {
873 2048 : const std::string osLayerName(psField->String);
874 :
875 1024 : psField = oTable.GetFieldValue(iDocumentation);
876 : const std::string osDocumentation(psField ? psField->String
877 2048 : : "");
878 :
879 1024 : if (!osRasterLayerName.empty())
880 : {
881 930 : if (osRasterLayerName == osLayerName)
882 : {
883 27 : bRet = OpenRaster(poOpenInfo, osLayerName, osDefinition,
884 : osDocumentation);
885 : }
886 : }
887 : else
888 : {
889 94 : const int iSubDSNum = 1 + m_aosSubdatasets.size() / 2;
890 : m_aosSubdatasets.SetNameValue(
891 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
892 : CPLSPrintf("OpenFileGDB:\"%s\":%s",
893 94 : poOpenInfo->pszFilename,
894 94 : osLayerName.c_str()));
895 :
896 94 : std::string osDesc(osLayerName);
897 94 : if (!osDocumentation.empty())
898 : {
899 : CPLXMLTreeCloser psTree(
900 186 : CPLParseXMLString(osDocumentation.c_str()));
901 93 : if (psTree)
902 : {
903 93 : const auto psRastInfo = CPLGetXMLNode(
904 : psTree.get(), "=metadata.spdoinfo.rastinfo");
905 93 : if (psRastInfo)
906 : {
907 93 : const char *pszRowCount = CPLGetXMLValue(
908 : psRastInfo, "rowcount", nullptr);
909 93 : const char *pszColCount = CPLGetXMLValue(
910 : psRastInfo, "colcount", nullptr);
911 93 : const char *pszRastBand = CPLGetXMLValue(
912 : psRastInfo, "rastband", nullptr);
913 93 : const char *pszRastBPP = CPLGetXMLValue(
914 : psRastInfo, "rastbpp", nullptr);
915 93 : if (pszRowCount && pszColCount && pszRastBand &&
916 : pszRastBPP)
917 : {
918 : osDesc += CPLSPrintf(
919 : " [%sx%sx%s], %s bits", pszColCount,
920 93 : pszRowCount, pszRastBand, pszRastBPP);
921 : }
922 : }
923 : }
924 : }
925 :
926 : m_aosSubdatasets.SetNameValue(
927 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum),
928 94 : ("Raster " + osDesc).c_str());
929 : }
930 1024 : }
931 : }
932 1558 : else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
933 654 : strstr(psField->String, "DERasterDataset"))
934 : {
935 50 : psField = oTable.GetFieldValue(iName);
936 50 : if (psField)
937 : {
938 100 : const std::string osLayerName(psField->String);
939 50 : auto oIter = m_osMapNameToIdx.find(osLayerName);
940 50 : if (oIter != m_osMapNameToIdx.end())
941 : {
942 47 : oSetIgnoredRasterLayerTableNum.insert(oIter->second);
943 :
944 188 : for (const char *pszPrefix :
945 235 : {"fras_ras_", "fras_aux_", "fras_bnd_", "fras_blk_"})
946 : {
947 : oIter = m_osMapNameToIdx.find(
948 188 : std::string(pszPrefix).append(osLayerName).c_str());
949 188 : if (oIter != m_osMapNameToIdx.end())
950 : {
951 : oSetIgnoredRasterLayerTableNum.insert(
952 188 : oIter->second);
953 : }
954 : }
955 : }
956 : }
957 : }
958 : }
959 :
960 : // If there's at least one .cdf layer and the FileGDB driver is present,
961 : // retry with it.
962 490 : if (nLayersCDF > 0 && GDALGetDriverByName("FileGDB") != nullptr)
963 : {
964 2 : bRetryFileGDBOut = true;
965 2 : return false;
966 : }
967 :
968 488 : return bRet;
969 : }
970 :
971 : /***********************************************************************/
972 : /* OpenFileGDBv9() */
973 : /***********************************************************************/
974 :
975 23 : int OGROpenFileGDBDataSource::OpenFileGDBv9(
976 : int iGDBFeatureClasses, int iGDBObjectClasses, int nInterestTable,
977 : const GDALOpenInfo *poOpenInfo, const std::string &osRasterLayerName,
978 : std::set<int> &oSetIgnoredRasterLayerTableNum)
979 : {
980 46 : auto poTable = std::make_unique<FileGDBTable>();
981 :
982 23 : CPLDebug("OpenFileGDB", "FileGDB v9");
983 :
984 : /* Fetch names of layers */
985 23 : CPLString osFilename(CPLFormFilenameSafe(
986 46 : m_osDirName, CPLSPrintf("a%08x", iGDBObjectClasses + 1), "gdbtable"));
987 23 : if (!poTable->Open(osFilename, false))
988 0 : return FALSE;
989 :
990 23 : int iName = poTable->GetFieldIdx("Name");
991 23 : int iCLSID = poTable->GetFieldIdx("CLSID");
992 23 : if (iName < 0 || poTable->GetField(iName)->GetType() != FGFT_STRING ||
993 46 : iCLSID < 0 || poTable->GetField(iCLSID)->GetType() != FGFT_STRING)
994 : {
995 0 : CPLError(CE_Failure, CPLE_AppDefined,
996 : "Wrong structure for GDB_ObjectClasses table");
997 0 : return FALSE;
998 : }
999 :
1000 46 : std::vector<std::string> aosName;
1001 23 : int nCandidateLayers = 0, nLayersCDF = 0;
1002 433 : for (int i = 0; i < poTable->GetTotalRecordCount(); i++)
1003 : {
1004 410 : if (!poTable->SelectRow(i))
1005 : {
1006 27 : if (poTable->HasGotError())
1007 0 : break;
1008 27 : aosName.push_back("");
1009 27 : continue;
1010 : }
1011 :
1012 383 : const OGRField *psField = poTable->GetFieldValue(iName);
1013 383 : if (psField != nullptr)
1014 : {
1015 766 : std::string osName(psField->String);
1016 383 : psField = poTable->GetFieldValue(iCLSID);
1017 383 : if (psField != nullptr)
1018 : {
1019 : /* Is it a non-spatial table ? */
1020 383 : if (strcmp(psField->String,
1021 : "{7A566981-C114-11D2-8A28-006097AFF44E}") == 0)
1022 : {
1023 51 : aosName.push_back("");
1024 51 : AddLayer(osName, nInterestTable, nCandidateLayers,
1025 102 : nLayersCDF, "", "", wkbNone, std::string());
1026 : }
1027 : else
1028 : {
1029 : /* We should perhaps also check that the CLSID is the one of
1030 : * a spatial table */
1031 332 : aosName.push_back(osName);
1032 : }
1033 : }
1034 : }
1035 : }
1036 23 : poTable->Close();
1037 :
1038 23 : poTable = std::make_unique<FileGDBTable>();
1039 :
1040 : /* Find tables that are spatial layers */
1041 23 : osFilename = CPLFormFilenameSafe(
1042 23 : m_osDirName, CPLSPrintf("a%08x", iGDBFeatureClasses + 1), "gdbtable");
1043 23 : if (!poTable->Open(osFilename, false))
1044 0 : return FALSE;
1045 :
1046 23 : const int iObjectClassID = poTable->GetFieldIdx("ObjectClassID");
1047 23 : const int iFeatureType = poTable->GetFieldIdx("FeatureType");
1048 23 : const int iGeometryType = poTable->GetFieldIdx("GeometryType");
1049 46 : if (iObjectClassID < 0 || iGeometryType < 0 || iFeatureType < 0 ||
1050 46 : poTable->GetField(iObjectClassID)->GetType() != FGFT_INT32 ||
1051 69 : poTable->GetField(iFeatureType)->GetType() != FGFT_INT32 ||
1052 23 : poTable->GetField(iGeometryType)->GetType() != FGFT_INT32)
1053 : {
1054 0 : CPLError(CE_Failure, CPLE_AppDefined,
1055 : "Wrong structure for GDB_FeatureClasses table");
1056 0 : return FALSE;
1057 : }
1058 :
1059 23 : bool bRet = true;
1060 379 : for (int i = 0; i < poTable->GetTotalRecordCount(); i++)
1061 : {
1062 356 : if (!poTable->SelectRow(i))
1063 : {
1064 24 : if (poTable->HasGotError())
1065 0 : break;
1066 24 : continue;
1067 : }
1068 :
1069 332 : const OGRField *psField = poTable->GetFieldValue(iGeometryType);
1070 332 : if (psField == nullptr)
1071 0 : continue;
1072 332 : const int nGeomType = psField->Integer;
1073 332 : OGRwkbGeometryType eGeomType = wkbUnknown;
1074 332 : switch (nGeomType)
1075 : {
1076 0 : case FGTGT_NONE: /* doesn't make sense ! */
1077 0 : break;
1078 64 : case FGTGT_POINT:
1079 64 : eGeomType = wkbPoint;
1080 64 : break;
1081 52 : case FGTGT_MULTIPOINT:
1082 52 : eGeomType = wkbMultiPoint;
1083 52 : break;
1084 76 : case FGTGT_LINE:
1085 76 : eGeomType = wkbMultiLineString;
1086 76 : break;
1087 124 : case FGTGT_POLYGON:
1088 124 : eGeomType = wkbMultiPolygon;
1089 124 : break;
1090 16 : case FGTGT_MULTIPATCH:
1091 16 : eGeomType = wkbUnknown;
1092 16 : break;
1093 : }
1094 :
1095 332 : psField = poTable->GetFieldValue(iObjectClassID);
1096 332 : if (psField == nullptr)
1097 0 : continue;
1098 :
1099 332 : const int idx = psField->Integer;
1100 664 : if (idx > 0 && idx <= static_cast<int>(aosName.size()) &&
1101 332 : !aosName[idx - 1].empty())
1102 : {
1103 664 : const std::string osName(aosName[idx - 1]);
1104 :
1105 332 : psField = poTable->GetFieldValue(iFeatureType);
1106 332 : const bool bIsRaster = psField && psField->Integer == 14;
1107 :
1108 332 : if (bIsRaster && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0)
1109 : {
1110 3 : if (!osRasterLayerName.empty())
1111 : {
1112 2 : if (osRasterLayerName == osName)
1113 : {
1114 2 : bRet = OpenRaster(poOpenInfo, osName, "", "");
1115 : }
1116 : }
1117 : else
1118 : {
1119 1 : const int iSubDSNum = 1 + m_aosSubdatasets.size() / 2;
1120 : m_aosSubdatasets.SetNameValue(
1121 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum),
1122 : CPLSPrintf("OpenFileGDB:\"%s\":%s",
1123 1 : poOpenInfo->pszFilename, osName.c_str()));
1124 : m_aosSubdatasets.SetNameValue(
1125 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum),
1126 1 : ("Raster " + osName).c_str());
1127 3 : }
1128 : }
1129 329 : else if (bIsRaster)
1130 : {
1131 1 : auto oIter = m_osMapNameToIdx.find(osName);
1132 1 : if (oIter != m_osMapNameToIdx.end())
1133 : {
1134 1 : oSetIgnoredRasterLayerTableNum.insert(oIter->second);
1135 :
1136 4 : for (const char *pszPrefix :
1137 5 : {"fras_ras_", "fras_aux_", "fras_bnd_", "fras_blk_"})
1138 : {
1139 : oIter = m_osMapNameToIdx.find(
1140 4 : std::string(pszPrefix).append(osName).c_str());
1141 4 : if (oIter != m_osMapNameToIdx.end())
1142 : {
1143 : oSetIgnoredRasterLayerTableNum.insert(
1144 4 : oIter->second);
1145 : }
1146 : }
1147 : }
1148 : }
1149 : else
1150 : {
1151 328 : AddLayer(osName, nInterestTable, nCandidateLayers, nLayersCDF,
1152 656 : "", "", eGeomType, std::string());
1153 : }
1154 : }
1155 : }
1156 :
1157 23 : if (m_apoLayers.empty() && nCandidateLayers > 0 &&
1158 0 : nCandidateLayers == nLayersCDF)
1159 0 : return FALSE;
1160 :
1161 23 : return bRet;
1162 : }
1163 :
1164 : /***********************************************************************/
1165 : /* TestCapability() */
1166 : /***********************************************************************/
1167 :
1168 717 : int OGROpenFileGDBDataSource::TestCapability(const char *pszCap)
1169 : {
1170 717 : if (EQUAL(pszCap, ODsCCreateLayer) || EQUAL(pszCap, ODsCDeleteLayer) ||
1171 626 : EQUAL(pszCap, ODsCAddFieldDomain) ||
1172 622 : EQUAL(pszCap, ODsCDeleteFieldDomain) ||
1173 621 : EQUAL(pszCap, ODsCUpdateFieldDomain) ||
1174 620 : EQUAL(pszCap, GDsCAddRelationship) ||
1175 620 : EQUAL(pszCap, GDsCDeleteRelationship) ||
1176 620 : EQUAL(pszCap, GDsCUpdateRelationship) ||
1177 620 : EQUAL(pszCap, ODsCEmulatedTransactions))
1178 : {
1179 100 : return eAccess == GA_Update;
1180 : }
1181 :
1182 617 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
1183 183 : return TRUE;
1184 434 : else if (EQUAL(pszCap, ODsCZGeometries))
1185 183 : return TRUE;
1186 251 : else if (EQUAL(pszCap, ODsCCurveGeometries))
1187 200 : return TRUE;
1188 :
1189 51 : return FALSE;
1190 : }
1191 :
1192 : /***********************************************************************/
1193 : /* GetLayer() */
1194 : /***********************************************************************/
1195 :
1196 1194700 : OGRLayer *OGROpenFileGDBDataSource::GetLayer(int iIndex)
1197 : {
1198 1194700 : if (iIndex < 0 || iIndex >= static_cast<int>(m_apoLayers.size()))
1199 18 : return nullptr;
1200 1194680 : return m_apoLayers[iIndex].get();
1201 : }
1202 :
1203 : /***********************************************************************/
1204 : /* BuildLayerFromName() */
1205 : /***********************************************************************/
1206 :
1207 : std::unique_ptr<OGROpenFileGDBLayer>
1208 893 : OGROpenFileGDBDataSource::BuildLayerFromName(const char *pszName)
1209 : {
1210 :
1211 893 : const auto oIter = m_osMapNameToIdx.find(pszName);
1212 893 : if (oIter != m_osMapNameToIdx.end())
1213 : {
1214 164 : const int idx = oIter->second;
1215 164 : const CPLString osFilename(CPLFormFilenameSafe(
1216 164 : m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable"));
1217 164 : if (FileExists(osFilename))
1218 : {
1219 : return std::make_unique<OGROpenFileGDBLayer>(
1220 163 : this, osFilename, pszName, "", "", eAccess == GA_Update);
1221 : }
1222 : }
1223 730 : return nullptr;
1224 : }
1225 :
1226 : /***********************************************************************/
1227 : /* GetLayerByName() */
1228 : /***********************************************************************/
1229 :
1230 : OGROpenFileGDBLayer *
1231 3424 : OGROpenFileGDBDataSource::GetLayerByName(const char *pszName)
1232 : {
1233 35038 : for (auto &poLayer : m_apoLayers)
1234 : {
1235 34205 : if (EQUAL(poLayer->GetName(), pszName))
1236 2591 : return poLayer.get();
1237 : }
1238 :
1239 5510 : for (auto &poLayer : m_apoHiddenLayers)
1240 : {
1241 4678 : if (EQUAL(poLayer->GetName(), pszName))
1242 1 : return poLayer.get();
1243 : }
1244 :
1245 1664 : auto poLayer = BuildLayerFromName(pszName);
1246 832 : if (poLayer)
1247 : {
1248 103 : m_apoHiddenLayers.emplace_back(std::move(poLayer));
1249 103 : return m_apoHiddenLayers.back().get();
1250 : }
1251 729 : return nullptr;
1252 : }
1253 :
1254 : /***********************************************************************/
1255 : /* IsPrivateLayerName() */
1256 : /***********************************************************************/
1257 :
1258 10491 : bool OGROpenFileGDBDataSource::IsPrivateLayerName(const CPLString &osName)
1259 : {
1260 10491 : const CPLString osLCTableName(CPLString(osName).tolower());
1261 :
1262 : // tables beginning with "GDB_" are private tables
1263 : // Also consider "VAT_" tables as private ones.
1264 26915 : return osLCTableName.size() >= 4 && (osLCTableName.substr(0, 4) == "gdb_" ||
1265 26915 : osLCTableName.substr(0, 4) == "vat_");
1266 : }
1267 :
1268 : /***********************************************************************/
1269 : /* IsLayerPrivate() */
1270 : /***********************************************************************/
1271 :
1272 102 : bool OGROpenFileGDBDataSource::IsLayerPrivate(int iLayer) const
1273 : {
1274 102 : if (iLayer < 0 || iLayer >= static_cast<int>(m_apoLayers.size()))
1275 0 : return false;
1276 :
1277 102 : const std::string osName(m_apoLayers[iLayer]->GetName());
1278 102 : return IsPrivateLayerName(osName);
1279 : }
1280 :
1281 : /***********************************************************************/
1282 : /* GetMetadata() */
1283 : /***********************************************************************/
1284 :
1285 26 : char **OGROpenFileGDBDataSource::GetMetadata(const char *pszDomain)
1286 : {
1287 26 : if (pszDomain && EQUAL(pszDomain, "SUBDATASETS"))
1288 6 : return m_aosSubdatasets.List();
1289 20 : return GDALDataset::GetMetadata(pszDomain);
1290 : }
1291 :
1292 : /************************************************************************/
1293 : /* OGROpenFileGDBSingleFeatureLayer() */
1294 : /************************************************************************/
1295 :
1296 239 : OGROpenFileGDBSingleFeatureLayer::OGROpenFileGDBSingleFeatureLayer(
1297 239 : const char *pszLayerName, const char *pszValIn)
1298 239 : : pszVal(pszValIn ? CPLStrdup(pszValIn) : nullptr),
1299 478 : poFeatureDefn(new OGRFeatureDefn(pszLayerName)), iNextShapeId(0)
1300 : {
1301 239 : SetDescription(poFeatureDefn->GetName());
1302 239 : poFeatureDefn->Reference();
1303 478 : OGRFieldDefn oField("FIELD_1", OFTString);
1304 239 : poFeatureDefn->AddFieldDefn(&oField);
1305 239 : }
1306 :
1307 : /************************************************************************/
1308 : /* ~OGROpenFileGDBSingleFeatureLayer() */
1309 : /************************************************************************/
1310 :
1311 478 : OGROpenFileGDBSingleFeatureLayer::~OGROpenFileGDBSingleFeatureLayer()
1312 : {
1313 239 : if (poFeatureDefn != nullptr)
1314 239 : poFeatureDefn->Release();
1315 239 : CPLFree(pszVal);
1316 478 : }
1317 :
1318 : /************************************************************************/
1319 : /* GetNextFeature() */
1320 : /************************************************************************/
1321 :
1322 258 : OGRFeature *OGROpenFileGDBSingleFeatureLayer::GetNextFeature()
1323 : {
1324 258 : if (iNextShapeId != 0)
1325 19 : return nullptr;
1326 :
1327 239 : OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
1328 239 : if (pszVal)
1329 239 : poFeature->SetField(0, pszVal);
1330 239 : poFeature->SetFID(iNextShapeId++);
1331 239 : return poFeature;
1332 : }
1333 :
1334 : /***********************************************************************/
1335 : /* OGROpenFileGDBSimpleSQLLayer */
1336 : /***********************************************************************/
1337 :
1338 : class OGROpenFileGDBSimpleSQLLayer final : public OGRLayer
1339 : {
1340 : OGRLayer *poBaseLayer;
1341 : FileGDBIterator *poIter;
1342 : OGRFeatureDefn *poFeatureDefn;
1343 : GIntBig m_nOffset;
1344 : GIntBig m_nLimit;
1345 : GIntBig m_nSkipped = 0;
1346 : GIntBig m_nIterated = 0;
1347 :
1348 : CPL_DISALLOW_COPY_ASSIGN(OGROpenFileGDBSimpleSQLLayer)
1349 :
1350 : public:
1351 : OGROpenFileGDBSimpleSQLLayer(OGRLayer *poBaseLayer, FileGDBIterator *poIter,
1352 : int nColumns, const swq_col_def *pasColDefs,
1353 : GIntBig nOffset, GIntBig nLimit);
1354 : virtual ~OGROpenFileGDBSimpleSQLLayer();
1355 :
1356 : virtual void ResetReading() override;
1357 : virtual OGRFeature *GetNextFeature() override;
1358 : virtual OGRFeature *GetFeature(GIntBig nFeatureId) override;
1359 :
1360 170 : virtual OGRFeatureDefn *GetLayerDefn() override
1361 : {
1362 170 : return poFeatureDefn;
1363 : }
1364 :
1365 : virtual int TestCapability(const char *) override;
1366 :
1367 60 : virtual const char *GetFIDColumn() override
1368 : {
1369 60 : return poBaseLayer->GetFIDColumn();
1370 : }
1371 :
1372 12 : virtual OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent,
1373 : bool bForce) override
1374 : {
1375 12 : return poBaseLayer->GetExtent(iGeomField, psExtent, bForce);
1376 : }
1377 :
1378 0 : virtual OGRErr IGetExtent3D(int iGeomField, OGREnvelope3D *psExtent,
1379 : bool bForce) override
1380 : {
1381 0 : return poBaseLayer->GetExtent3D(iGeomField, psExtent, bForce);
1382 : }
1383 :
1384 : virtual GIntBig GetFeatureCount(int bForce) override;
1385 : };
1386 :
1387 : /***********************************************************************/
1388 : /* OGROpenFileGDBSimpleSQLLayer() */
1389 : /***********************************************************************/
1390 :
1391 16 : OGROpenFileGDBSimpleSQLLayer::OGROpenFileGDBSimpleSQLLayer(
1392 : OGRLayer *poBaseLayerIn, FileGDBIterator *poIterIn, int nColumns,
1393 16 : const swq_col_def *pasColDefs, GIntBig nOffset, GIntBig nLimit)
1394 : : poBaseLayer(poBaseLayerIn), poIter(poIterIn), poFeatureDefn(nullptr),
1395 16 : m_nOffset(nOffset), m_nLimit(nLimit)
1396 : {
1397 16 : if (nColumns == 1 && strcmp(pasColDefs[0].field_name, "*") == 0)
1398 : {
1399 14 : poFeatureDefn = poBaseLayer->GetLayerDefn();
1400 14 : poFeatureDefn->Reference();
1401 : }
1402 : else
1403 : {
1404 2 : poFeatureDefn = new OGRFeatureDefn(poBaseLayer->GetName());
1405 2 : poFeatureDefn->SetGeomType(poBaseLayer->GetGeomType());
1406 2 : poFeatureDefn->Reference();
1407 2 : if (poBaseLayer->GetGeomType() != wkbNone)
1408 : {
1409 4 : poFeatureDefn->GetGeomFieldDefn(0)->SetName(
1410 2 : poBaseLayer->GetGeometryColumn());
1411 2 : poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(
1412 2 : poBaseLayer->GetSpatialRef());
1413 : }
1414 6 : for (int i = 0; i < nColumns; i++)
1415 : {
1416 4 : if (strcmp(pasColDefs[i].field_name, "*") == 0)
1417 : {
1418 0 : for (int j = 0;
1419 0 : j < poBaseLayer->GetLayerDefn()->GetFieldCount(); j++)
1420 0 : poFeatureDefn->AddFieldDefn(
1421 0 : poBaseLayer->GetLayerDefn()->GetFieldDefn(j));
1422 : }
1423 : else
1424 : {
1425 : OGRFieldDefn *poFieldDefn =
1426 8 : poBaseLayer->GetLayerDefn()->GetFieldDefn(
1427 8 : poBaseLayer->GetLayerDefn()->GetFieldIndex(
1428 4 : pasColDefs[i].field_name));
1429 4 : CPLAssert(poFieldDefn != nullptr); /* already checked before */
1430 4 : poFeatureDefn->AddFieldDefn(poFieldDefn);
1431 : }
1432 : }
1433 : }
1434 16 : SetDescription(poFeatureDefn->GetName());
1435 16 : OGROpenFileGDBSimpleSQLLayer::ResetReading();
1436 16 : }
1437 :
1438 : /***********************************************************************/
1439 : /* ~OGROpenFileGDBSimpleSQLLayer() */
1440 : /***********************************************************************/
1441 :
1442 32 : OGROpenFileGDBSimpleSQLLayer::~OGROpenFileGDBSimpleSQLLayer()
1443 : {
1444 16 : if (poFeatureDefn)
1445 : {
1446 16 : poFeatureDefn->Release();
1447 : }
1448 16 : delete poIter;
1449 32 : }
1450 :
1451 : /***********************************************************************/
1452 : /* ResetReading() */
1453 : /***********************************************************************/
1454 :
1455 204 : void OGROpenFileGDBSimpleSQLLayer::ResetReading()
1456 : {
1457 204 : poIter->Reset();
1458 204 : m_nSkipped = 0;
1459 204 : m_nIterated = 0;
1460 204 : }
1461 :
1462 : /***********************************************************************/
1463 : /* GetFeature() */
1464 : /***********************************************************************/
1465 :
1466 682 : OGRFeature *OGROpenFileGDBSimpleSQLLayer::GetFeature(GIntBig nFeatureId)
1467 : {
1468 682 : OGRFeature *poSrcFeature = poBaseLayer->GetFeature(nFeatureId);
1469 682 : if (poSrcFeature == nullptr)
1470 9 : return nullptr;
1471 :
1472 673 : if (poFeatureDefn == poBaseLayer->GetLayerDefn())
1473 528 : return poSrcFeature;
1474 : else
1475 : {
1476 145 : OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
1477 145 : poFeature->SetFrom(poSrcFeature);
1478 145 : poFeature->SetFID(poSrcFeature->GetFID());
1479 145 : delete poSrcFeature;
1480 145 : return poFeature;
1481 : }
1482 : }
1483 :
1484 : /***********************************************************************/
1485 : /* GetNextFeature() */
1486 : /***********************************************************************/
1487 :
1488 735 : OGRFeature *OGROpenFileGDBSimpleSQLLayer::GetNextFeature()
1489 : {
1490 : while (true)
1491 : {
1492 735 : if (m_nLimit >= 0 && m_nIterated == m_nLimit)
1493 1 : return nullptr;
1494 :
1495 734 : const int64_t nRow = poIter->GetNextRowSortedByValue();
1496 734 : if (nRow < 0)
1497 73 : return nullptr;
1498 661 : OGRFeature *poFeature = GetFeature(nRow + 1);
1499 661 : if (poFeature == nullptr)
1500 0 : return nullptr;
1501 661 : if (m_nOffset >= 0 && m_nSkipped < m_nOffset)
1502 : {
1503 343 : delete poFeature;
1504 343 : m_nSkipped++;
1505 343 : continue;
1506 : }
1507 318 : m_nIterated++;
1508 :
1509 746 : if ((m_poFilterGeom == nullptr ||
1510 603 : FilterGeometry(poFeature->GetGeometryRef())) &&
1511 285 : (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
1512 : {
1513 235 : return poFeature;
1514 : }
1515 :
1516 83 : delete poFeature;
1517 426 : }
1518 : }
1519 :
1520 : /***********************************************************************/
1521 : /* GetFeatureCount() */
1522 : /***********************************************************************/
1523 :
1524 51 : GIntBig OGROpenFileGDBSimpleSQLLayer::GetFeatureCount(int bForce)
1525 : {
1526 :
1527 : /* No filter */
1528 51 : if (m_poFilterGeom == nullptr && m_poAttrQuery == nullptr)
1529 : {
1530 33 : GIntBig nRowCount = poIter->GetRowCount();
1531 33 : if (m_nOffset > 0)
1532 : {
1533 3 : if (m_nOffset <= nRowCount)
1534 2 : nRowCount -= m_nOffset;
1535 : else
1536 1 : nRowCount = 0;
1537 : }
1538 33 : if (m_nLimit >= 0 && nRowCount > m_nLimit)
1539 5 : nRowCount = m_nLimit;
1540 33 : return nRowCount;
1541 : }
1542 :
1543 18 : return OGRLayer::GetFeatureCount(bForce);
1544 : }
1545 :
1546 : /***********************************************************************/
1547 : /* TestCapability() */
1548 : /***********************************************************************/
1549 :
1550 96 : int OGROpenFileGDBSimpleSQLLayer::TestCapability(const char *pszCap)
1551 : {
1552 :
1553 96 : if (EQUAL(pszCap, OLCFastFeatureCount))
1554 : {
1555 0 : return m_poFilterGeom == nullptr && m_poAttrQuery == nullptr;
1556 : }
1557 96 : else if (EQUAL(pszCap, OLCFastGetExtent))
1558 : {
1559 6 : return TRUE;
1560 : }
1561 90 : else if (EQUAL(pszCap, OLCRandomRead))
1562 : {
1563 0 : return TRUE;
1564 : }
1565 90 : else if (EQUAL(pszCap, OLCStringsAsUTF8))
1566 : {
1567 27 : return TRUE; /* ? */
1568 : }
1569 :
1570 63 : return FALSE;
1571 : }
1572 :
1573 : /***********************************************************************/
1574 : /* ExecuteSQL() */
1575 : /***********************************************************************/
1576 :
1577 799 : OGRLayer *OGROpenFileGDBDataSource::ExecuteSQL(const char *pszSQLCommand,
1578 : OGRGeometry *poSpatialFilter,
1579 : const char *pszDialect)
1580 : {
1581 :
1582 : /* -------------------------------------------------------------------- */
1583 : /* Special case GetLayerDefinition */
1584 : /* -------------------------------------------------------------------- */
1585 799 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerDefinition "))
1586 : {
1587 32 : const char *pszLayerName =
1588 : pszSQLCommand + strlen("GetLayerDefinition ");
1589 32 : auto poLayer = GetLayerByName(pszLayerName);
1590 32 : if (poLayer)
1591 : {
1592 : OGRLayer *poRet = new OGROpenFileGDBSingleFeatureLayer(
1593 31 : "LayerDefinition", poLayer->GetXMLDefinition().c_str());
1594 31 : return poRet;
1595 : }
1596 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1597 : pszLayerName);
1598 1 : return nullptr;
1599 : }
1600 :
1601 : /* -------------------------------------------------------------------- */
1602 : /* Special case GetLayerMetadata */
1603 : /* -------------------------------------------------------------------- */
1604 767 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerMetadata "))
1605 : {
1606 21 : const char *pszLayerName = pszSQLCommand + strlen("GetLayerMetadata ");
1607 21 : auto poLayer = GetLayerByName(pszLayerName);
1608 21 : if (poLayer)
1609 : {
1610 : OGRLayer *poRet = new OGROpenFileGDBSingleFeatureLayer(
1611 20 : "LayerMetadata", poLayer->GetXMLDocumentation().c_str());
1612 20 : return poRet;
1613 : }
1614 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1615 : pszLayerName);
1616 1 : return nullptr;
1617 : }
1618 :
1619 : /* -------------------------------------------------------------------- */
1620 : /* Special case GetLayerAttrIndexUse (only for debugging purposes) */
1621 : /* -------------------------------------------------------------------- */
1622 746 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerAttrIndexUse "))
1623 : {
1624 : auto poLayer =
1625 141 : GetLayerByName(pszSQLCommand + strlen("GetLayerAttrIndexUse "));
1626 141 : if (poLayer)
1627 : {
1628 : OGRLayer *poRet = new OGROpenFileGDBSingleFeatureLayer(
1629 : "LayerAttrIndexUse",
1630 141 : CPLSPrintf("%d", poLayer->GetAttrIndexUse()));
1631 141 : return poRet;
1632 : }
1633 :
1634 0 : return nullptr;
1635 : }
1636 :
1637 : /* -------------------------------------------------------------------- */
1638 : /* Special case GetLayerSpatialIndexState (only for debugging purposes)
1639 : */
1640 : /* -------------------------------------------------------------------- */
1641 605 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerSpatialIndexState "))
1642 : {
1643 12 : auto poLayer = GetLayerByName(pszSQLCommand +
1644 : strlen("GetLayerSpatialIndexState "));
1645 12 : if (poLayer)
1646 : {
1647 : OGRLayer *poRet = new OGROpenFileGDBSingleFeatureLayer(
1648 : "LayerSpatialIndexState",
1649 12 : CPLSPrintf("%d", poLayer->GetSpatialIndexState()));
1650 12 : return poRet;
1651 : }
1652 :
1653 0 : return nullptr;
1654 : }
1655 :
1656 : /* -------------------------------------------------------------------- */
1657 : /* Special case GetLastSQLUsedOptimizedImplementation (only for
1658 : * debugging purposes) */
1659 : /* -------------------------------------------------------------------- */
1660 593 : if (EQUAL(pszSQLCommand, "GetLastSQLUsedOptimizedImplementation"))
1661 : {
1662 : OGRLayer *poRet = new OGROpenFileGDBSingleFeatureLayer(
1663 : "GetLastSQLUsedOptimizedImplementation",
1664 46 : CPLSPrintf("%d",
1665 23 : static_cast<int>(bLastSQLUsedOptimizedImplementation)));
1666 23 : return poRet;
1667 : }
1668 :
1669 : /* -------------------------------------------------------------------- */
1670 : /* Special case for "CREATE SPATIAL INDEX ON " */
1671 : /* -------------------------------------------------------------------- */
1672 570 : if (STARTS_WITH_CI(pszSQLCommand, "CREATE SPATIAL INDEX ON "))
1673 : {
1674 0 : const char *pszLayerName =
1675 : pszSQLCommand + strlen("CREATE SPATIAL INDEX ON ");
1676 0 : auto poLayer = GetLayerByName(pszLayerName);
1677 0 : if (poLayer)
1678 : {
1679 0 : poLayer->CreateSpatialIndex();
1680 : }
1681 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1682 : pszLayerName);
1683 0 : return nullptr;
1684 : }
1685 :
1686 : /* -------------------------------------------------------------------- */
1687 : /* Special case for "CREATE INDEX idx_name ON table_name(col_name) */
1688 : /* -------------------------------------------------------------------- */
1689 570 : if (STARTS_WITH_CI(pszSQLCommand, "CREATE INDEX "))
1690 : {
1691 42 : CPLString osSQL(pszSQLCommand);
1692 21 : const auto nPosON = osSQL.ifind(" ON ");
1693 21 : if (nPosON != std::string::npos)
1694 : {
1695 : const std::string osIdxName = osSQL.substr(
1696 21 : strlen("CREATE INDEX "), nPosON - strlen("CREATE INDEX "));
1697 21 : const std::string afterOn = osSQL.substr(nPosON + strlen(" ON "));
1698 21 : const auto nOpenParPos = afterOn.find('(');
1699 21 : if (nOpenParPos != std::string::npos && afterOn.back() == ')')
1700 : {
1701 42 : const std::string osLayerName = afterOn.substr(0, nOpenParPos);
1702 : const std::string osExpression = afterOn.substr(
1703 42 : nOpenParPos + 1, afterOn.size() - nOpenParPos - 2);
1704 21 : if (osExpression.find(',') != std::string::npos)
1705 : {
1706 1 : CPLError(
1707 : CE_Failure, CPLE_NotSupported,
1708 : "Creation of multiple-column indices is not supported");
1709 1 : return nullptr;
1710 : }
1711 20 : auto poLayer = GetLayerByName(osLayerName.c_str());
1712 20 : if (poLayer)
1713 : {
1714 19 : poLayer->CreateIndex(osIdxName, osExpression);
1715 19 : return nullptr;
1716 : }
1717 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1718 : osLayerName.c_str());
1719 1 : return nullptr;
1720 : }
1721 : }
1722 0 : CPLError(CE_Failure, CPLE_AppDefined,
1723 : "Bad syntax. Expected CREATE INDEX idx_name ON "
1724 : "table_name(col_name)");
1725 0 : return nullptr;
1726 : }
1727 :
1728 : /* -------------------------------------------------------------------- */
1729 : /* Special case for "RECOMPUTE EXTENT ON " */
1730 : /* -------------------------------------------------------------------- */
1731 549 : if (STARTS_WITH_CI(pszSQLCommand, "RECOMPUTE EXTENT ON "))
1732 : {
1733 3 : const char *pszLayerName =
1734 : pszSQLCommand + strlen("RECOMPUTE EXTENT ON ");
1735 3 : auto poLayer = GetLayerByName(pszLayerName);
1736 3 : if (poLayer)
1737 : {
1738 2 : poLayer->RecomputeExtent();
1739 : }
1740 : else
1741 : {
1742 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1743 : pszLayerName);
1744 : }
1745 3 : return nullptr;
1746 : }
1747 :
1748 : /* -------------------------------------------------------------------- */
1749 : /* Special case for "DELLAYER:" */
1750 : /* -------------------------------------------------------------------- */
1751 546 : if (STARTS_WITH_CI(pszSQLCommand, "DELLAYER:"))
1752 : {
1753 3 : const char *pszLayerName = pszSQLCommand + strlen("DELLAYER:");
1754 5 : for (int i = 0; i < static_cast<int>(m_apoLayers.size()); ++i)
1755 : {
1756 3 : if (strcmp(pszLayerName, m_apoLayers[i]->GetName()) == 0)
1757 : {
1758 1 : DeleteLayer(i);
1759 1 : return nullptr;
1760 : }
1761 : }
1762 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1763 : pszLayerName);
1764 2 : return nullptr;
1765 : }
1766 :
1767 : /* -------------------------------------------------------------------- */
1768 : /* Special case for "CHECK_FREELIST_CONSISTENCY:" */
1769 : /* -------------------------------------------------------------------- */
1770 543 : if (STARTS_WITH_CI(pszSQLCommand, "CHECK_FREELIST_CONSISTENCY:"))
1771 : {
1772 8 : const char *pszLayerName =
1773 : pszSQLCommand + strlen("CHECK_FREELIST_CONSISTENCY:");
1774 8 : auto poLayer = GetLayerByName(pszLayerName);
1775 8 : if (poLayer)
1776 : {
1777 : return new OGROpenFileGDBSingleFeatureLayer(
1778 : "result",
1779 16 : CPLSPrintf("%d", static_cast<int>(
1780 8 : poLayer->CheckFreeListConsistency())));
1781 : }
1782 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1783 : pszLayerName);
1784 0 : return nullptr;
1785 : }
1786 :
1787 : /* -------------------------------------------------------------------- */
1788 : /* Special case for "REPACK" */
1789 : /* -------------------------------------------------------------------- */
1790 535 : if (EQUAL(pszSQLCommand, "REPACK"))
1791 : {
1792 3 : bool bSuccess = true;
1793 6 : for (auto &poLayer : m_apoLayers)
1794 : {
1795 3 : if (!poLayer->Repack())
1796 0 : bSuccess = false;
1797 : }
1798 : return new OGROpenFileGDBSingleFeatureLayer(
1799 3 : "result", bSuccess ? "true" : "false");
1800 : }
1801 532 : else if (STARTS_WITH(pszSQLCommand, "REPACK "))
1802 : {
1803 2 : const char *pszLayerName = pszSQLCommand + strlen("REPACK ");
1804 2 : auto poLayer = GetLayerByName(pszLayerName);
1805 2 : if (poLayer)
1806 : {
1807 1 : const bool bSuccess = poLayer->Repack();
1808 : return new OGROpenFileGDBSingleFeatureLayer(
1809 1 : "result", bSuccess ? "true" : "false");
1810 : }
1811 1 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid layer name: %s",
1812 : pszLayerName);
1813 1 : return nullptr;
1814 : }
1815 :
1816 530 : bLastSQLUsedOptimizedImplementation = false;
1817 :
1818 : /* -------------------------------------------------------------------- */
1819 : /* Special cases for SQL optimizations */
1820 : /* -------------------------------------------------------------------- */
1821 521 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT ") &&
1822 39 : (pszDialect == nullptr || EQUAL(pszDialect, "") ||
1823 1051 : EQUAL(pszDialect, "OGRSQL")) &&
1824 521 : CPLTestBool(CPLGetConfigOption("OPENFILEGDB_USE_INDEX", "YES")))
1825 : {
1826 521 : swq_select oSelect;
1827 521 : if (oSelect.preparse(pszSQLCommand) != CE_None)
1828 1 : return nullptr;
1829 :
1830 : /* --------------------------------------------------------------------
1831 : */
1832 : /* MIN/MAX/SUM/AVG/COUNT optimization */
1833 : /* --------------------------------------------------------------------
1834 : */
1835 520 : if (oSelect.join_count == 0 && oSelect.poOtherSelect == nullptr &&
1836 520 : oSelect.table_count == 1 && oSelect.order_specs == 0 &&
1837 493 : oSelect.query_mode != SWQM_DISTINCT_LIST &&
1838 493 : oSelect.where_expr == nullptr)
1839 : {
1840 : OGROpenFileGDBLayer *poLayer =
1841 624 : reinterpret_cast<OGROpenFileGDBLayer *>(
1842 312 : GetLayerByName(oSelect.table_defs[0].table_name));
1843 312 : if (poLayer)
1844 : {
1845 311 : OGRMemLayer *poMemLayer = nullptr;
1846 :
1847 311 : int i = 0; // Used after for.
1848 369 : for (; i < oSelect.result_columns(); i++)
1849 : {
1850 333 : swq_col_func col_func = oSelect.column_defs[i].col_func;
1851 514 : if (!(col_func == SWQCF_MIN || col_func == SWQCF_MAX ||
1852 187 : col_func == SWQCF_COUNT || col_func == SWQCF_AVG ||
1853 : col_func == SWQCF_SUM))
1854 275 : break;
1855 :
1856 153 : if (oSelect.column_defs[i].field_name == nullptr)
1857 0 : break;
1858 153 : if (oSelect.column_defs[i].distinct_flag)
1859 0 : break;
1860 153 : if (oSelect.column_defs[i].target_type != SWQ_OTHER)
1861 0 : break;
1862 :
1863 153 : int idx = poLayer->GetLayerDefn()->GetFieldIndex(
1864 153 : oSelect.column_defs[i].field_name);
1865 153 : if (idx < 0)
1866 90 : break;
1867 :
1868 : OGRFieldDefn *poFieldDefn =
1869 63 : poLayer->GetLayerDefn()->GetFieldDefn(idx);
1870 :
1871 64 : if (col_func == SWQCF_SUM &&
1872 1 : poFieldDefn->GetType() == OFTDateTime)
1873 0 : break;
1874 :
1875 63 : int eOutOGRType = -1;
1876 63 : const OGRField *psField = nullptr;
1877 : OGRField sField;
1878 63 : if (col_func == SWQCF_MIN || col_func == SWQCF_MAX)
1879 : {
1880 55 : psField = poLayer->GetMinMaxValue(
1881 : poFieldDefn, col_func == SWQCF_MIN, eOutOGRType);
1882 55 : if (eOutOGRType < 0)
1883 5 : break;
1884 : }
1885 : else
1886 : {
1887 8 : double dfMin = 0.0;
1888 8 : double dfMax = 0.0;
1889 8 : int nCount = 0;
1890 8 : double dfSum = 0.0;
1891 :
1892 8 : if (!poLayer->GetMinMaxSumCount(poFieldDefn, dfMin,
1893 : dfMax, dfSum, nCount))
1894 0 : break;
1895 8 : psField = &sField;
1896 8 : if (col_func == SWQCF_AVG)
1897 : {
1898 6 : if (nCount == 0)
1899 : {
1900 1 : eOutOGRType = OFTReal;
1901 1 : psField = nullptr;
1902 : }
1903 : else
1904 : {
1905 5 : if (poFieldDefn->GetType() == OFTDateTime)
1906 : {
1907 1 : eOutOGRType = OFTDateTime;
1908 1 : FileGDBDoubleDateToOGRDate(dfSum / nCount,
1909 : false, &sField);
1910 : }
1911 : else
1912 : {
1913 4 : eOutOGRType = OFTReal;
1914 4 : sField.Real = dfSum / nCount;
1915 : }
1916 : }
1917 : }
1918 2 : else if (col_func == SWQCF_COUNT)
1919 : {
1920 1 : sField.Integer = nCount;
1921 1 : eOutOGRType = OFTInteger;
1922 : }
1923 : else
1924 : {
1925 1 : sField.Real = dfSum;
1926 1 : eOutOGRType = OFTReal;
1927 : }
1928 : }
1929 :
1930 58 : if (poMemLayer == nullptr)
1931 : {
1932 36 : poMemLayer =
1933 36 : new OGRMemLayer("SELECT", nullptr, wkbNone);
1934 : OGRFeature *poFeature =
1935 36 : new OGRFeature(poMemLayer->GetLayerDefn());
1936 36 : CPL_IGNORE_RET_VAL(
1937 36 : poMemLayer->CreateFeature(poFeature));
1938 36 : delete poFeature;
1939 : }
1940 :
1941 : const char *pszMinMaxFieldName =
1942 102 : CPLSPrintf("%s_%s",
1943 : (col_func == SWQCF_MIN) ? "MIN"
1944 52 : : (col_func == SWQCF_MAX) ? "MAX"
1945 10 : : (col_func == SWQCF_AVG) ? "AVG"
1946 2 : : (col_func == SWQCF_SUM) ? "SUM"
1947 : : "COUNT",
1948 58 : oSelect.column_defs[i].field_name);
1949 : OGRFieldDefn oFieldDefn(
1950 : pszMinMaxFieldName,
1951 116 : static_cast<OGRFieldType>(eOutOGRType));
1952 58 : poMemLayer->CreateField(&oFieldDefn);
1953 58 : if (psField != nullptr)
1954 : {
1955 55 : OGRFeature *poFeature = poMemLayer->GetFeature(0);
1956 55 : poFeature->SetField(oFieldDefn.GetNameRef(), psField);
1957 55 : CPL_IGNORE_RET_VAL(poMemLayer->SetFeature(poFeature));
1958 55 : delete poFeature;
1959 : }
1960 : }
1961 311 : if (i != oSelect.result_columns())
1962 : {
1963 275 : delete poMemLayer;
1964 : }
1965 : else
1966 : {
1967 36 : CPLDebug(
1968 : "OpenFileGDB",
1969 : "Using optimized MIN/MAX/SUM/AVG/COUNT implementation");
1970 36 : bLastSQLUsedOptimizedImplementation = true;
1971 36 : return poMemLayer;
1972 : }
1973 : }
1974 : }
1975 :
1976 : /* --------------------------------------------------------------------
1977 : */
1978 : /* ORDER BY optimization */
1979 : /* --------------------------------------------------------------------
1980 : */
1981 484 : if (oSelect.join_count == 0 && oSelect.poOtherSelect == nullptr &&
1982 484 : oSelect.table_count == 1 && oSelect.order_specs == 1 &&
1983 26 : oSelect.query_mode != SWQM_DISTINCT_LIST)
1984 : {
1985 : OGROpenFileGDBLayer *poLayer =
1986 50 : reinterpret_cast<OGROpenFileGDBLayer *>(
1987 25 : GetLayerByName(oSelect.table_defs[0].table_name));
1988 50 : if (poLayer != nullptr &&
1989 25 : poLayer->HasIndexForField(oSelect.order_defs[0].field_name))
1990 : {
1991 23 : OGRErr eErr = OGRERR_NONE;
1992 23 : if (oSelect.where_expr != nullptr)
1993 : {
1994 : /* The where must be a simple comparison on the column */
1995 : /* that is used for ordering */
1996 12 : if (oSelect.where_expr->eNodeType == SNT_OPERATION &&
1997 4 : OGROpenFileGDBIsComparisonOp(
1998 4 : oSelect.where_expr->nOperation) &&
1999 3 : oSelect.where_expr->nOperation != SWQ_NE &&
2000 3 : oSelect.where_expr->nSubExprCount == 2 &&
2001 3 : (oSelect.where_expr->papoSubExpr[0]->eNodeType ==
2002 0 : SNT_COLUMN ||
2003 0 : oSelect.where_expr->papoSubExpr[0]->eNodeType ==
2004 3 : SNT_CONSTANT) &&
2005 3 : oSelect.where_expr->papoSubExpr[0]->field_type ==
2006 3 : SWQ_STRING &&
2007 3 : EQUAL(oSelect.where_expr->papoSubExpr[0]->string_value,
2008 8 : oSelect.order_defs[0].field_name) &&
2009 2 : oSelect.where_expr->papoSubExpr[1]->eNodeType ==
2010 : SNT_CONSTANT)
2011 : {
2012 : /* ok */
2013 : }
2014 : else
2015 2 : eErr = OGRERR_FAILURE;
2016 : }
2017 23 : if (eErr == OGRERR_NONE)
2018 : {
2019 21 : int i = 0; // Used after for.
2020 40 : for (; i < oSelect.result_columns(); i++)
2021 : {
2022 23 : if (oSelect.column_defs[i].col_func != SWQCF_NONE)
2023 1 : break;
2024 22 : if (oSelect.column_defs[i].field_name == nullptr)
2025 0 : break;
2026 22 : if (oSelect.column_defs[i].distinct_flag)
2027 0 : break;
2028 22 : if (oSelect.column_defs[i].target_type != SWQ_OTHER)
2029 1 : break;
2030 21 : if (strcmp(oSelect.column_defs[i].field_name, "*") !=
2031 27 : 0 &&
2032 12 : poLayer->GetLayerDefn()->GetFieldIndex(
2033 6 : oSelect.column_defs[i].field_name) < 0)
2034 2 : break;
2035 : }
2036 21 : if (i != oSelect.result_columns())
2037 4 : eErr = OGRERR_FAILURE;
2038 : }
2039 23 : if (eErr == OGRERR_NONE)
2040 : {
2041 17 : int op = -1;
2042 17 : swq_expr_node *poValue = nullptr;
2043 17 : if (oSelect.where_expr != nullptr)
2044 : {
2045 2 : op = oSelect.where_expr->nOperation;
2046 2 : poValue = oSelect.where_expr->papoSubExpr[1];
2047 : }
2048 :
2049 34 : FileGDBIterator *poIter = poLayer->BuildIndex(
2050 17 : oSelect.order_defs[0].field_name,
2051 17 : oSelect.order_defs[0].ascending_flag, op, poValue);
2052 :
2053 : /* Check that they are no NULL values */
2054 32 : if (oSelect.where_expr == nullptr && poIter != nullptr &&
2055 15 : poIter->GetRowCount() !=
2056 15 : poLayer->GetFeatureCount(FALSE))
2057 : {
2058 1 : delete poIter;
2059 1 : poIter = nullptr;
2060 : }
2061 :
2062 17 : if (poIter != nullptr)
2063 : {
2064 16 : CPLDebug("OpenFileGDB",
2065 : "Using OGROpenFileGDBSimpleSQLLayer");
2066 16 : bLastSQLUsedOptimizedImplementation = true;
2067 : return new OGROpenFileGDBSimpleSQLLayer(
2068 16 : poLayer, poIter, oSelect.result_columns(),
2069 16 : oSelect.column_defs.data(), oSelect.offset,
2070 16 : oSelect.limit);
2071 : }
2072 : }
2073 : }
2074 : }
2075 : }
2076 :
2077 477 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, pszDialect);
2078 : }
2079 :
2080 : /***********************************************************************/
2081 : /* ReleaseResultSet() */
2082 : /***********************************************************************/
2083 :
2084 748 : void OGROpenFileGDBDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
2085 : {
2086 748 : delete poResultsSet;
2087 748 : }
2088 :
2089 : /***********************************************************************/
2090 : /* GetFileList() */
2091 : /***********************************************************************/
2092 :
2093 9 : char **OGROpenFileGDBDataSource::GetFileList()
2094 : {
2095 9 : int nInterestTable = -1;
2096 9 : const char *pszFilenameWithoutPath = CPLGetFilename(m_osDirName.c_str());
2097 18 : CPLString osFilenameRadix;
2098 9 : unsigned int unInterestTable = 0;
2099 18 : if (strlen(pszFilenameWithoutPath) == strlen("a00000000.gdbtable") &&
2100 9 : pszFilenameWithoutPath[0] == 'a' &&
2101 0 : sscanf(pszFilenameWithoutPath, "a%08x.gdbtable", &unInterestTable) == 1)
2102 : {
2103 0 : nInterestTable = static_cast<int>(unInterestTable);
2104 0 : osFilenameRadix = CPLSPrintf("a%08x.", nInterestTable);
2105 : }
2106 :
2107 9 : char **papszFiles = VSIReadDir(m_osDirName);
2108 18 : CPLStringList osStringList;
2109 9 : char **papszIter = papszFiles;
2110 1172 : for (; papszIter != nullptr && *papszIter != nullptr; papszIter++)
2111 : {
2112 1163 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
2113 6 : continue;
2114 1157 : if (osFilenameRadix.empty() ||
2115 0 : strncmp(*papszIter, osFilenameRadix, osFilenameRadix.size()) == 0)
2116 : {
2117 1157 : osStringList.push_back(
2118 2314 : CPLFormFilenameSafe(m_osDirName, *papszIter, nullptr));
2119 : }
2120 : }
2121 9 : CSLDestroy(papszFiles);
2122 18 : return osStringList.StealList();
2123 : }
2124 :
2125 : /************************************************************************/
2126 : /* BuildSRS() */
2127 : /************************************************************************/
2128 :
2129 : OGRSpatialReference *
2130 2780 : OGROpenFileGDBDataSource::BuildSRS(const CPLXMLNode *psInfo)
2131 : {
2132 : const char *pszWKT =
2133 2780 : CPLGetXMLValue(psInfo, "SpatialReference.WKT", nullptr);
2134 : const int nWKID =
2135 2780 : atoi(CPLGetXMLValue(psInfo, "SpatialReference.WKID", "0"));
2136 : // The concept of LatestWKID is explained in
2137 : // https://support.esri.com/en/technical-article/000013950
2138 : int nLatestWKID =
2139 2780 : atoi(CPLGetXMLValue(psInfo, "SpatialReference.LatestWKID", "0"));
2140 :
2141 2780 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSRS;
2142 2780 : if (nWKID > 0 || nLatestWKID > 0)
2143 : {
2144 : const auto ImportFromCode =
2145 2377 : [](OGRSpatialReference &oSRS, int nLatestCode, int nCode)
2146 : {
2147 2377 : bool bSuccess = false;
2148 2377 : CPLErrorStateBackuper oQuietError(CPLQuietErrorHandler);
2149 :
2150 : // Try first with nLatestWKID as there is a higher chance it is a
2151 : // EPSG code and not an ESRI one.
2152 2377 : if (nLatestCode > 0)
2153 : {
2154 129 : if (nLatestCode > 32767)
2155 : {
2156 1 : if (oSRS.SetFromUserInput(
2157 1 : CPLSPrintf("ESRI:%d", nLatestCode)) == OGRERR_NONE)
2158 : {
2159 0 : bSuccess = true;
2160 : }
2161 : }
2162 128 : else if (oSRS.importFromEPSG(nLatestCode) == OGRERR_NONE)
2163 : {
2164 128 : bSuccess = true;
2165 : }
2166 129 : if (!bSuccess)
2167 : {
2168 1 : CPLDebug("OpenFileGDB", "Cannot import SRID %d",
2169 : nLatestCode);
2170 : }
2171 : }
2172 2377 : if (!bSuccess && nCode > 0)
2173 : {
2174 2249 : if (nCode > 32767)
2175 : {
2176 1 : if (oSRS.SetFromUserInput(CPLSPrintf("ESRI:%d", nCode)) ==
2177 : OGRERR_NONE)
2178 : {
2179 0 : bSuccess = true;
2180 : }
2181 : }
2182 2248 : else if (oSRS.importFromEPSG(nCode) == OGRERR_NONE)
2183 : {
2184 2248 : bSuccess = true;
2185 : }
2186 2249 : if (!bSuccess)
2187 : {
2188 1 : CPLDebug("OpenFileGDB", "Cannot import SRID %d", nCode);
2189 : }
2190 : }
2191 :
2192 4754 : return bSuccess;
2193 : };
2194 :
2195 : poSRS =
2196 4752 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser>(
2197 4752 : new OGRSpatialReference());
2198 2376 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2199 2376 : if (!ImportFromCode(*poSRS.get(), nLatestWKID, nWKID))
2200 : {
2201 0 : poSRS.reset();
2202 : }
2203 : else
2204 : {
2205 2376 : const int nLatestVCSWKID = atoi(
2206 : CPLGetXMLValue(psInfo, "SpatialReference.LatestVCSWKID", "0"));
2207 : const int nVCSWKID =
2208 2376 : atoi(CPLGetXMLValue(psInfo, "SpatialReference.VCSWKID", "0"));
2209 2376 : if (nVCSWKID > 0 || nLatestVCSWKID > 0)
2210 : {
2211 : auto poVertSRS = std::unique_ptr<OGRSpatialReference,
2212 : OGRSpatialReferenceReleaser>(
2213 2 : new OGRSpatialReference());
2214 1 : if (ImportFromCode(*poVertSRS.get(), nLatestVCSWKID, nVCSWKID))
2215 : {
2216 : auto poCompoundSRS =
2217 : std::unique_ptr<OGRSpatialReference,
2218 : OGRSpatialReferenceReleaser>(
2219 0 : new OGRSpatialReference());
2220 0 : if (poCompoundSRS->SetCompoundCS(
2221 0 : std::string(poSRS->GetName())
2222 0 : .append(" + ")
2223 0 : .append(poVertSRS->GetName())
2224 : .c_str(),
2225 0 : poSRS.get(), poVertSRS.get()) == OGRERR_NONE)
2226 : {
2227 0 : poCompoundSRS->SetAxisMappingStrategy(
2228 : OAMS_TRADITIONAL_GIS_ORDER);
2229 0 : poSRS = std::move(poCompoundSRS);
2230 : }
2231 : }
2232 2 : if (!poSRS->IsCompound() &&
2233 1 : !(pszWKT != nullptr && pszWKT[0] != '{'))
2234 : {
2235 0 : poSRS.reset();
2236 : }
2237 : }
2238 : }
2239 : }
2240 7532 : if (pszWKT != nullptr && pszWKT[0] != '{' &&
2241 2377 : (poSRS == nullptr ||
2242 2375 : (strstr(pszWKT, "VERTCS") && !poSRS->IsCompound())))
2243 : {
2244 3 : poSRS.reset(BuildSRS(pszWKT));
2245 : }
2246 5560 : return poSRS.release();
2247 : }
2248 :
2249 : /************************************************************************/
2250 : /* BuildSRS() */
2251 : /************************************************************************/
2252 :
2253 361 : OGRSpatialReference *OGROpenFileGDBDataSource::BuildSRS(const char *pszWKT)
2254 : {
2255 361 : std::shared_ptr<OGRSpatialReference> poSharedObj;
2256 361 : m_oCacheWKTToSRS.tryGet(pszWKT, poSharedObj);
2257 361 : if (poSharedObj)
2258 199 : return poSharedObj->Clone();
2259 :
2260 162 : OGRSpatialReference *poSRS = new OGRSpatialReference();
2261 162 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2262 162 : if (poSRS->importFromWkt(pszWKT) != OGRERR_NONE)
2263 : {
2264 0 : delete poSRS;
2265 0 : poSRS = nullptr;
2266 : }
2267 162 : if (poSRS != nullptr)
2268 : {
2269 162 : if (CPLTestBool(CPLGetConfigOption("USE_OSR_FIND_MATCHES", "YES")))
2270 : {
2271 162 : auto poSRSMatch = poSRS->FindBestMatch(100);
2272 162 : if (poSRSMatch)
2273 : {
2274 161 : poSRS->Release();
2275 161 : poSRS = poSRSMatch;
2276 161 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2277 : }
2278 : m_oCacheWKTToSRS.insert(
2279 162 : pszWKT, std::shared_ptr<OGRSpatialReference>(poSRS->Clone()));
2280 : }
2281 : else
2282 : {
2283 0 : poSRS->AutoIdentifyEPSG();
2284 : }
2285 : }
2286 162 : return poSRS;
2287 : }
|