Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements FileGDB OGR Datasource.
5 : * Author: Ragi Yaser Burhum, ragi@burhum.com
6 : * Paul Ramsey, pramsey at cleverelephant.ca
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010, Ragi Yaser Burhum
10 : * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
11 : * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
12 : *
13 : * Permission is hereby granted, free of charge, to any person obtaining a
14 : * copy of this software and associated documentation files (the "Software"),
15 : * to deal in the Software without restriction, including without limitation
16 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 : * and/or sell copies of the Software, and to permit persons to whom the
18 : * Software is furnished to do so, subject to the following conditions:
19 : *
20 : * The above copyright notice and this permission notice shall be included
21 : * in all copies or substantial portions of the Software.
22 : *
23 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 : * DEALINGS IN THE SOFTWARE.
30 : ****************************************************************************/
31 :
32 : #include "ogr_fgdb.h"
33 : #include "cpl_conv.h"
34 : #include "cpl_string.h"
35 : #include "gdal.h"
36 : #include "FGdbUtils.h"
37 : #include "cpl_multiproc.h"
38 :
39 : #include "filegdb_fielddomain.h"
40 : #include "filegdb_relationship.h"
41 :
42 : using std::vector;
43 : using std::wstring;
44 :
45 : /***********************************************************************/
46 : /* OGRFileGDBGroup */
47 : /***********************************************************************/
48 :
49 : class OGRFileGDBGroup final : public GDALGroup
50 : {
51 : protected:
52 : friend class FGdbDataSource;
53 : std::vector<std::shared_ptr<GDALGroup>> m_apoSubGroups{};
54 : std::vector<OGRLayer *> m_apoLayers{};
55 :
56 : public:
57 93 : OGRFileGDBGroup(const std::string &osParentName, const char *pszName)
58 93 : : GDALGroup(osParentName, pszName)
59 : {
60 93 : }
61 :
62 : std::vector<std::string>
63 : GetGroupNames(CSLConstList papszOptions) const override;
64 : std::shared_ptr<GDALGroup>
65 : OpenGroup(const std::string &osName,
66 : CSLConstList papszOptions) const override;
67 :
68 : std::vector<std::string>
69 : GetVectorLayerNames(CSLConstList papszOptions) const override;
70 : OGRLayer *OpenVectorLayer(const std::string &osName,
71 : CSLConstList papszOptions) const override;
72 : };
73 :
74 2 : std::vector<std::string> OGRFileGDBGroup::GetGroupNames(CSLConstList) const
75 : {
76 2 : std::vector<std::string> ret;
77 4 : for (const auto &poSubGroup : m_apoSubGroups)
78 2 : ret.emplace_back(poSubGroup->GetName());
79 2 : return ret;
80 : }
81 :
82 3 : std::shared_ptr<GDALGroup> OGRFileGDBGroup::OpenGroup(const std::string &osName,
83 : CSLConstList) const
84 : {
85 6 : for (const auto &poSubGroup : m_apoSubGroups)
86 : {
87 5 : if (poSubGroup->GetName() == osName)
88 2 : return poSubGroup;
89 : }
90 1 : return nullptr;
91 : }
92 :
93 : std::vector<std::string>
94 3 : OGRFileGDBGroup::GetVectorLayerNames(CSLConstList) const
95 : {
96 3 : std::vector<std::string> ret;
97 7 : for (const auto &poLayer : m_apoLayers)
98 4 : ret.emplace_back(poLayer->GetName());
99 3 : return ret;
100 : }
101 :
102 5 : OGRLayer *OGRFileGDBGroup::OpenVectorLayer(const std::string &osName,
103 : CSLConstList) const
104 : {
105 8 : for (const auto &poLayer : m_apoLayers)
106 : {
107 7 : if (poLayer->GetName() == osName)
108 4 : return poLayer;
109 : }
110 1 : return nullptr;
111 : }
112 :
113 : /************************************************************************/
114 : /* FGdbDataSource() */
115 : /************************************************************************/
116 :
117 86 : FGdbDataSource::FGdbDataSource(bool bUseDriverMutex,
118 : FGdbDatabaseConnection *pConnection,
119 86 : bool bUseOpenFileGDB)
120 : : OGRDataSource(), m_bUseDriverMutex(bUseDriverMutex),
121 : m_pConnection(pConnection), m_pGeodatabase(nullptr), m_bUpdate(false),
122 86 : m_poOpenFileGDBDrv(nullptr), m_bUseOpenFileGDB(bUseOpenFileGDB)
123 : {
124 86 : bPerLayerCopyingForTransaction = -1;
125 86 : SetDescription(pConnection->m_osName.c_str());
126 86 : poDriver = GDALDriver::FromHandle(GDALGetDriverByName("FileGDB"));
127 86 : }
128 :
129 : /************************************************************************/
130 : /* ~FGdbDataSource() */
131 : /************************************************************************/
132 :
133 172 : FGdbDataSource::~FGdbDataSource()
134 : {
135 172 : CPLMutexHolderOptionalLockD(m_bUseDriverMutex ? FGdbDriver::hMutex
136 : : nullptr);
137 :
138 86 : if (m_pConnection && m_pConnection->IsLocked())
139 0 : CommitTransaction();
140 :
141 : // CloseInternal();
142 86 : size_t count = m_layers.size();
143 495 : for (size_t i = 0; i < count; ++i)
144 : {
145 409 : if (FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]))
146 : {
147 404 : poFgdbLayer->CloseGDBObjects();
148 : }
149 : }
150 :
151 86 : FixIndexes();
152 :
153 86 : if (m_bUseDriverMutex)
154 85 : FGdbDriver::Release(m_osPublicName);
155 :
156 : // size_t count = m_layers.size();
157 495 : for (size_t i = 0; i < count; ++i)
158 : {
159 : // only delete layers coming from this driver -- the tables opened by
160 : // the OpenFileGDB driver will be deleted when we close the OpenFileGDB
161 : // datasource
162 409 : if (FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]))
163 : {
164 404 : delete poFgdbLayer;
165 : }
166 : }
167 172 : }
168 :
169 : /************************************************************************/
170 : /* FixIndexes() */
171 : /************************************************************************/
172 :
173 87 : int FGdbDataSource::FixIndexes()
174 : {
175 87 : int bRet = TRUE;
176 87 : if (m_pConnection && m_pConnection->IsFIDHackInProgress())
177 : {
178 1 : m_pConnection->CloseGeodatabase();
179 :
180 1 : const char *const apszDrivers[2] = {"OpenFileGDB", nullptr};
181 : const char *pszSystemCatalog =
182 1 : CPLFormFilename(m_osFSName, "a00000001.gdbtable", nullptr);
183 : auto poOpenFileGDBDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
184 2 : pszSystemCatalog, GDAL_OF_VECTOR, apszDrivers, nullptr, nullptr));
185 2 : if (poOpenFileGDBDS == nullptr ||
186 1 : poOpenFileGDBDS->GetLayer(0) == nullptr)
187 : {
188 0 : CPLError(CE_Failure, CPLE_AppDefined,
189 : "Cannot open %s with OpenFileGDB driver. "
190 : "Should not happen. Some layers will be corrupted",
191 : pszSystemCatalog);
192 0 : bRet = FALSE;
193 : }
194 : else
195 : {
196 1 : OGRLayer *poLayer = poOpenFileGDBDS->GetLayer(0);
197 1 : size_t count = m_layers.size();
198 2 : for (size_t i = 0; i < count; ++i)
199 : {
200 1 : FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]);
201 1 : if (!poFgdbLayer)
202 0 : continue;
203 :
204 1 : if (poFgdbLayer->m_oMapOGRFIDToFGDBFID.empty())
205 0 : continue;
206 2 : CPLString osFilter = "name = '";
207 1 : osFilter += m_layers[i]->GetName();
208 1 : osFilter += "'";
209 1 : poLayer->SetAttributeFilter(osFilter);
210 1 : poLayer->ResetReading();
211 1 : OGRFeature *poF = poLayer->GetNextFeature();
212 1 : if (poF == nullptr)
213 : {
214 0 : CPLError(CE_Failure, CPLE_AppDefined,
215 : "Cannot find filename for layer %s",
216 0 : m_layers[i]->GetName());
217 0 : bRet = FALSE;
218 : }
219 : else
220 : {
221 1 : if (!poFgdbLayer->EditIndexesForFIDHack(CPLFormFilename(
222 1 : m_osFSName, CPLSPrintf("a%08x", (int)poF->GetFID()),
223 : nullptr)))
224 : {
225 0 : bRet = FALSE;
226 : }
227 : }
228 1 : delete poF;
229 : }
230 : }
231 :
232 1 : m_pConnection->SetFIDHackInProgress(FALSE);
233 : }
234 87 : return bRet;
235 : }
236 :
237 : /************************************************************************/
238 : /* Open() */
239 : /************************************************************************/
240 :
241 86 : int FGdbDataSource::Open(const char *pszNewName, int bUpdate,
242 : const char *pszPublicName)
243 : {
244 86 : m_osFSName = pszNewName;
245 86 : m_osPublicName = (pszPublicName) ? pszPublicName : pszNewName;
246 86 : m_pGeodatabase = m_pConnection->GetGDB();
247 86 : m_bUpdate = CPL_TO_BOOL(bUpdate);
248 86 : m_poOpenFileGDBDrv = (GDALDriver *)GDALGetDriverByName("OpenFileGDB");
249 :
250 86 : return LoadLayers(L"\\");
251 : }
252 :
253 : /************************************************************************/
254 : /* CloseInternal() */
255 : /************************************************************************/
256 :
257 1 : int FGdbDataSource::CloseInternal(int bCloseGeodatabase)
258 : {
259 1 : size_t count = m_layers.size();
260 2 : for (size_t i = 0; i < count; ++i)
261 : {
262 1 : if (FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]))
263 : {
264 1 : poFgdbLayer->CloseGDBObjects();
265 : }
266 : }
267 :
268 1 : int bRet = FixIndexes();
269 1 : if (m_pConnection && bCloseGeodatabase)
270 0 : m_pConnection->CloseGeodatabase();
271 1 : m_pGeodatabase = nullptr;
272 1 : return bRet;
273 : }
274 :
275 : /************************************************************************/
276 : /* ReOpen() */
277 : /************************************************************************/
278 :
279 1 : int FGdbDataSource::ReOpen()
280 : {
281 1 : CPLAssert(m_pGeodatabase == nullptr);
282 :
283 2 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE1") ||
284 1 : !m_pConnection->OpenGeodatabase(m_osFSName))
285 : {
286 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
287 : m_osFSName.c_str());
288 0 : return FALSE;
289 : }
290 :
291 : FGdbDataSource *pDS =
292 1 : new FGdbDataSource(m_bUseDriverMutex, m_pConnection, m_bUseOpenFileGDB);
293 2 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE2") ||
294 1 : !pDS->Open(m_osPublicName, TRUE, m_osFSName))
295 : {
296 0 : pDS->m_bUseDriverMutex = false;
297 0 : delete pDS;
298 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
299 : m_osFSName.c_str());
300 0 : return FALSE;
301 : }
302 :
303 1 : int bRet = TRUE;
304 1 : size_t count = m_layers.size();
305 2 : for (size_t i = 0; i < count; ++i)
306 : {
307 : FGdbLayer *pNewLayer =
308 1 : (FGdbLayer *)pDS->GetLayerByName(m_layers[i]->GetName());
309 1 : FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]);
310 2 : if (pNewLayer &&
311 1 : !EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL_REOPEN", ""), "CASE3"))
312 : {
313 1 : if (poFgdbLayer)
314 : {
315 1 : poFgdbLayer->m_pTable = pNewLayer->m_pTable;
316 : }
317 1 : pNewLayer->m_pTable = nullptr;
318 1 : if (poFgdbLayer)
319 : {
320 1 : poFgdbLayer->m_pEnumRows = pNewLayer->m_pEnumRows;
321 : }
322 1 : pNewLayer->m_pEnumRows = nullptr;
323 : }
324 : else
325 : {
326 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot reopen %s",
327 0 : m_layers[i]->GetName());
328 0 : bRet = FALSE;
329 : }
330 1 : if (poFgdbLayer)
331 : {
332 1 : poFgdbLayer->m_oMapOGRFIDToFGDBFID.clear();
333 1 : poFgdbLayer->m_oMapFGDBFIDToOGRFID.clear();
334 : }
335 : }
336 :
337 1 : m_pGeodatabase = pDS->m_pGeodatabase;
338 1 : pDS->m_pGeodatabase = nullptr;
339 :
340 1 : pDS->m_bUseDriverMutex = false;
341 1 : pDS->m_pConnection = nullptr;
342 1 : delete pDS;
343 :
344 1 : return bRet;
345 : }
346 :
347 : /************************************************************************/
348 : /* OpenFGDBTables() */
349 : /************************************************************************/
350 :
351 70 : bool FGdbDataSource::OpenFGDBTables(OGRFileGDBGroup *group,
352 : const std::wstring &type,
353 : const std::vector<std::wstring> &layers)
354 : {
355 : #ifdef DISPLAY_RELATED_DATASETS
356 : std::vector<std::wstring> relationshipTypes;
357 : m_pGeodatabase->GetDatasetRelationshipTypes(relationshipTypes);
358 : std::vector<std::wstring> datasetTypes;
359 : m_pGeodatabase->GetDatasetTypes(datasetTypes);
360 : #endif
361 :
362 : fgdbError hr;
363 329 : for (unsigned int i = 0; i < layers.size(); i++)
364 : {
365 259 : Table *pTable = new Table;
366 : // CPLDebug("FGDB", "Opening %s", WStringToString(layers[i]).c_str());
367 259 : if (FAILED(hr = m_pGeodatabase->OpenTable(layers[i], *pTable)))
368 : {
369 1 : delete pTable;
370 :
371 2 : std::wstring fgdb_error_desc_w;
372 : fgdbError er;
373 1 : er = FileGDBAPI::ErrorInfo::GetErrorDescription(hr,
374 : fgdb_error_desc_w);
375 1 : const char *pszLikelyReason =
376 : "Might be due to unsupported spatial reference system. Using "
377 : "OpenFileGDB driver or FileGDB SDK >= 1.4 should solve it";
378 1 : if (er == S_OK)
379 : {
380 : std::string fgdb_error_desc =
381 2 : WStringToString(fgdb_error_desc_w);
382 1 : if (fgdb_error_desc == "FileGDB compression is not installed.")
383 : {
384 0 : pszLikelyReason = "Using FileGDB SDK 1.4 or later should "
385 : "solve this issue.";
386 : }
387 : }
388 :
389 1 : GDBErr(hr, "Error opening " + WStringToString(layers[i]),
390 : CE_Warning,
391 2 : (". Skipping it. " + CPLString(pszLikelyReason)).c_str());
392 1 : continue;
393 : }
394 :
395 : #ifdef DISPLAY_RELATED_DATASETS
396 : CPLDebug("FGDB", "Finding datasets related to %s",
397 : WStringToString(layers[i]).c_str());
398 : // Slow !
399 : for (const auto &relType : relationshipTypes)
400 : {
401 : for (const auto &itemType : datasetTypes)
402 : {
403 : std::vector<std::wstring> relatedDatasets;
404 : m_pGeodatabase->GetRelatedDatasets(layers[i], relType, itemType,
405 : relatedDatasets);
406 :
407 : std::vector<std::string> relatedDatasetDefs;
408 : m_pGeodatabase->GetRelatedDatasetDefinitions(
409 : layers[i], relType, itemType, relatedDatasetDefs);
410 :
411 : if (!relatedDatasets.empty() || !relatedDatasetDefs.empty())
412 : {
413 : CPLDebug("FGDB", "relationshipType: %s",
414 : WStringToString(relType).c_str());
415 : CPLDebug("FGDB", "itemType: %s",
416 : WStringToString(itemType).c_str());
417 : }
418 : for (const auto &name : relatedDatasets)
419 : {
420 : CPLDebug("FGDB", "Related dataset: %s",
421 : WStringToString(name).c_str());
422 : }
423 : for (const auto &xml : relatedDatasetDefs)
424 : {
425 : CPLDebug("FGDB", "Related dataset def: %s", xml.c_str());
426 : }
427 : }
428 : }
429 : #endif
430 :
431 258 : FGdbLayer *pLayer = new FGdbLayer();
432 258 : if (!pLayer->Initialize(this, pTable, layers[i], type))
433 : {
434 0 : delete pLayer;
435 0 : return GDBErr(hr, "Error initializing OGRLayer for " +
436 0 : WStringToString(layers[i]));
437 : }
438 :
439 258 : m_layers.push_back(pLayer);
440 258 : group->m_apoLayers.emplace_back(pLayer);
441 : }
442 70 : return true;
443 : }
444 :
445 : /************************************************************************/
446 : /* LoadLayers() */
447 : /************************************************************************/
448 :
449 86 : bool FGdbDataSource::LoadLayers(const std::wstring &root)
450 : {
451 172 : std::vector<wstring> tables;
452 172 : std::vector<wstring> featureclasses;
453 172 : std::vector<wstring> featuredatasets;
454 : fgdbError hr;
455 :
456 172 : auto poRootGroup = std::make_shared<OGRFileGDBGroup>(std::string(), "");
457 86 : m_poRootGroup = poRootGroup;
458 :
459 : /* Find all the Tables in the root */
460 86 : if (FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Table", tables)))
461 : {
462 0 : return GDBErr(hr, "Error reading Tables in " + WStringToString(root));
463 : }
464 : /* Open the tables we found */
465 86 : if (!tables.empty() && !OpenFGDBTables(poRootGroup.get(), L"Table", tables))
466 0 : return false;
467 :
468 : /* Find all the Feature Classes in the root */
469 86 : if (FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Feature Class",
470 : featureclasses)))
471 : {
472 0 : return GDBErr(hr, "Error reading Feature Classes in " +
473 0 : WStringToString(root));
474 : }
475 : /* Open the tables we found */
476 133 : if (!featureclasses.empty() &&
477 133 : !OpenFGDBTables(poRootGroup.get(), L"Feature Class", featureclasses))
478 0 : return false;
479 :
480 : /* Find all the Feature Datasets in the root */
481 86 : if (FAILED(hr = m_pGeodatabase->GetChildDatasets(root, L"Feature Dataset",
482 : featuredatasets)))
483 : {
484 0 : return GDBErr(hr, "Error reading Feature Datasets in " +
485 0 : WStringToString(root));
486 : }
487 : /* Look for Feature Classes inside the Feature Dataset */
488 93 : for (unsigned int i = 0; i < featuredatasets.size(); i++)
489 : {
490 : const std::string featureDatasetPath(
491 7 : WStringToString(featuredatasets[i]));
492 7 : if (FAILED(hr = m_pGeodatabase->GetChildDatasets(
493 : featuredatasets[i], L"Feature Class", featureclasses)))
494 : {
495 0 : return GDBErr(hr, "Error reading Feature Classes in " +
496 0 : featureDatasetPath);
497 : }
498 7 : std::string featureDatasetName(featureDatasetPath);
499 7 : if (!featureDatasetName.empty() && featureDatasetPath[0] == '\\')
500 7 : featureDatasetName = featureDatasetName.substr(1);
501 : auto poFeatureDatasetGroup = std::make_shared<OGRFileGDBGroup>(
502 7 : poRootGroup->GetName(), featureDatasetName.c_str());
503 7 : poRootGroup->m_apoSubGroups.emplace_back(poFeatureDatasetGroup);
504 14 : if (!featureclasses.empty() &&
505 14 : !OpenFGDBTables(poFeatureDatasetGroup.get(), L"Feature Class",
506 : featureclasses))
507 0 : return false;
508 : }
509 :
510 : // Look for items which aren't present in the GDB_Items table (see
511 : // https://github.com/OSGeo/gdal/issues/4463) We do this by browsing the
512 : // catalog table (using the OpenFileGDB driver) and looking for items we
513 : // haven't yet found. If we find any, we have no choice but to load these
514 : // using the OpenFileGDB driver, as the ESRI SDK refuses to acknowledge that
515 : // they exist (despite ArcGIS itself showing them!)
516 86 : int iGDBItems = -1;
517 86 : const char *const apszDrivers[2] = {"OpenFileGDB", nullptr};
518 : const char *pszSystemCatalog =
519 86 : CPLFormFilename(m_osFSName, "a00000001", "gdbtable");
520 86 : if (m_bUseOpenFileGDB)
521 : {
522 84 : m_poOpenFileGDBDS.reset(GDALDataset::Open(
523 : pszSystemCatalog, GDAL_OF_VECTOR, apszDrivers, nullptr, nullptr));
524 : }
525 110 : if (m_poOpenFileGDBDS != nullptr &&
526 24 : m_poOpenFileGDBDS->GetLayer(0) != nullptr)
527 : {
528 24 : OGRLayer *poCatalogLayer = m_poOpenFileGDBDS->GetLayer(0);
529 : const int iNameIndex =
530 24 : poCatalogLayer->GetLayerDefn()->GetFieldIndex("Name");
531 24 : int i = -1;
532 245 : for (auto &poFeat : poCatalogLayer)
533 : {
534 221 : i++;
535 : const std::string osTableName =
536 221 : poFeat->GetFieldAsString(iNameIndex);
537 :
538 221 : if (osTableName.compare("GDB_Items") == 0)
539 : {
540 24 : iGDBItems = i;
541 : }
542 :
543 : // test if layer is already added
544 221 : if (GDALDataset::GetLayerByName(osTableName.c_str()))
545 23 : continue;
546 :
547 396 : const CPLString osLCTableName(CPLString(osTableName).tolower());
548 396 : const bool bIsPrivateLayer = osLCTableName.size() >= 4 &&
549 396 : osLCTableName.substr(0, 4) == "gdb_";
550 198 : if (!bIsPrivateLayer)
551 : {
552 6 : if (OGRLayer *poLayer =
553 6 : m_poOpenFileGDBDS->GetLayerByName(osTableName.c_str()))
554 : {
555 5 : m_layers.push_back(poLayer);
556 5 : poRootGroup->m_apoLayers.emplace_back(poLayer);
557 : }
558 : }
559 : }
560 : }
561 :
562 : // Read relationships. Note that we don't use the SDK method for this, as
563 : // it's too slow!
564 86 : if (iGDBItems >= 0)
565 : {
566 24 : const char *pszGDBItems = CPLFormFilename(
567 : m_osFSName, CPLSPrintf("a%08x", iGDBItems + 1), "gdbtable");
568 : std::unique_ptr<GDALDataset> poGDBItems(GDALDataset::Open(
569 24 : pszGDBItems, GDAL_OF_VECTOR, apszDrivers, nullptr, nullptr));
570 24 : if (poGDBItems != nullptr && poGDBItems->GetLayer(0) != nullptr)
571 : {
572 24 : if (OGRLayer *poItemsLayer = poGDBItems->GetLayer(0))
573 : {
574 24 : const int iType = poItemsLayer->FindFieldIndex("Type", true);
575 : const int iDefinition =
576 24 : poItemsLayer->FindFieldIndex("Definition", true);
577 24 : if (iType < 0 || iDefinition < 0)
578 : {
579 0 : CPLError(CE_Failure, CPLE_AppDefined,
580 : "Wrong structure for GDB_Items table");
581 0 : return FALSE;
582 : }
583 :
584 : // Hunt for relationships in GDB_Items table
585 319 : for (const auto &poFeature : poItemsLayer)
586 : {
587 590 : CPLString osType;
588 295 : const char *pszType = poFeature->GetFieldAsString(iType);
589 295 : if (pszType != nullptr &&
590 295 : EQUAL(pszType, pszRelationshipTypeUUID))
591 : {
592 : // relationship item
593 : auto poRelationship = ParseXMLRelationshipDef(
594 33 : poFeature->GetFieldAsString(iDefinition));
595 11 : if (poRelationship)
596 : {
597 : const std::string relationshipName(
598 22 : poRelationship->GetName());
599 11 : m_osMapRelationships[relationshipName] =
600 22 : std::move(poRelationship);
601 : }
602 : }
603 : }
604 : }
605 : }
606 : }
607 :
608 86 : return true;
609 : }
610 :
611 : /************************************************************************/
612 : /* DeleteLayer() */
613 : /************************************************************************/
614 :
615 2 : OGRErr FGdbDataSource::DeleteLayer(int iLayer)
616 : {
617 2 : if (!m_bUpdate || m_pGeodatabase == nullptr)
618 0 : return OGRERR_FAILURE;
619 :
620 2 : if (iLayer < 0 || iLayer >= static_cast<int>(m_layers.size()))
621 0 : return OGRERR_FAILURE;
622 :
623 2 : FGdbLayer *poBaseLayer = dynamic_cast<FGdbLayer *>(m_layers[iLayer]);
624 2 : if (!poBaseLayer)
625 0 : return OGRERR_FAILURE;
626 :
627 : // Fetch FGDBAPI Table before deleting OGR layer object
628 :
629 : // Table* pTable = poBaseLayer->GetTable();
630 :
631 4 : std::string name = poBaseLayer->GetLayerDefn()->GetName();
632 4 : std::wstring strPath = poBaseLayer->GetTablePath();
633 4 : std::wstring strType = poBaseLayer->GetType();
634 :
635 : // delete OGR layer
636 2 : delete m_layers[iLayer];
637 :
638 : // pTable = NULL; // OGR Layer had ownership of FGDB Table
639 :
640 2 : m_layers.erase(m_layers.begin() + iLayer);
641 :
642 : long hr;
643 :
644 2 : if (FAILED(hr = m_pGeodatabase->Delete(strPath, strType)))
645 : {
646 0 : CPLError(CE_Warning, CPLE_AppDefined,
647 : "%s was not deleted however it has been closed", name.c_str());
648 0 : GDBErr(hr, "Failed deleting dataset");
649 0 : return OGRERR_FAILURE;
650 : }
651 :
652 2 : return OGRERR_NONE;
653 : }
654 :
655 : /************************************************************************/
656 : /* TestCapability() */
657 : /************************************************************************/
658 :
659 123 : int FGdbDataSource::TestCapability(const char *pszCap)
660 : {
661 123 : if (EQUAL(pszCap, ODsCCreateLayer))
662 9 : return m_bUpdate;
663 :
664 114 : else if (EQUAL(pszCap, ODsCDeleteLayer))
665 2 : return m_bUpdate;
666 112 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
667 0 : return m_bUpdate;
668 112 : else if (EQUAL(pszCap, ODsCAddFieldDomain) ||
669 108 : EQUAL(pszCap, ODsCDeleteFieldDomain) ||
670 107 : EQUAL(pszCap, ODsCUpdateFieldDomain))
671 6 : return m_bUpdate;
672 106 : else if (EQUAL(pszCap, ODsCMeasuredGeometries))
673 36 : return TRUE;
674 70 : else if (EQUAL(pszCap, ODsCZGeometries))
675 36 : return TRUE;
676 :
677 34 : return FALSE;
678 : }
679 :
680 : /************************************************************************/
681 : /* GetLayer() */
682 : /************************************************************************/
683 :
684 8033 : OGRLayer *FGdbDataSource::GetLayer(int iLayer)
685 : {
686 8033 : int count = static_cast<int>(m_layers.size());
687 :
688 8033 : if (iLayer < 0 || iLayer >= count)
689 4 : return nullptr;
690 : else
691 8029 : return m_layers[iLayer];
692 : }
693 :
694 : /************************************************************************/
695 : /* ICreateLayer() */
696 : /* */
697 : /* See FGdbLayer::Create for creation options */
698 : /************************************************************************/
699 :
700 : OGRLayer *
701 148 : FGdbDataSource::ICreateLayer(const char *pszLayerName,
702 : const OGRGeomFieldDefn *poSrcGeomFieldDefn,
703 : CSLConstList papszOptions)
704 : {
705 148 : if (!m_bUpdate || m_pGeodatabase == nullptr)
706 0 : return nullptr;
707 :
708 148 : FGdbLayer *pLayer = new FGdbLayer();
709 148 : if (!pLayer->Create(this, pszLayerName, poSrcGeomFieldDefn, papszOptions))
710 : {
711 0 : delete pLayer;
712 0 : return nullptr;
713 : }
714 :
715 148 : m_layers.push_back(pLayer);
716 :
717 148 : return pLayer;
718 : }
719 :
720 : /************************************************************************/
721 : /* OGRFGdbSingleFeatureLayer */
722 : /************************************************************************/
723 :
724 : class OGRFGdbSingleFeatureLayer final : public OGRLayer
725 : {
726 : private:
727 : char *pszVal;
728 : OGRFeatureDefn *poFeatureDefn;
729 : int iNextShapeId;
730 :
731 : public:
732 : OGRFGdbSingleFeatureLayer(const char *pszLayerName, const char *pszVal);
733 : virtual ~OGRFGdbSingleFeatureLayer();
734 :
735 0 : virtual void ResetReading() override
736 : {
737 0 : iNextShapeId = 0;
738 0 : }
739 :
740 : virtual OGRFeature *GetNextFeature() override;
741 :
742 0 : virtual OGRFeatureDefn *GetLayerDefn() override
743 : {
744 0 : return poFeatureDefn;
745 : }
746 :
747 0 : virtual int TestCapability(const char *) override
748 : {
749 0 : return FALSE;
750 : }
751 : };
752 :
753 : /************************************************************************/
754 : /* OGRFGdbSingleFeatureLayer() */
755 : /************************************************************************/
756 :
757 35 : OGRFGdbSingleFeatureLayer::OGRFGdbSingleFeatureLayer(const char *pszLayerName,
758 35 : const char *pszValIn)
759 : {
760 35 : poFeatureDefn = new OGRFeatureDefn(pszLayerName);
761 35 : SetDescription(poFeatureDefn->GetName());
762 35 : poFeatureDefn->Reference();
763 35 : OGRFieldDefn oField("FIELD_1", OFTString);
764 35 : poFeatureDefn->AddFieldDefn(&oField);
765 :
766 35 : iNextShapeId = 0;
767 35 : pszVal = pszValIn ? CPLStrdup(pszValIn) : nullptr;
768 35 : }
769 :
770 : /************************************************************************/
771 : /* ~OGRFGdbSingleFeatureLayer() */
772 : /************************************************************************/
773 :
774 70 : OGRFGdbSingleFeatureLayer::~OGRFGdbSingleFeatureLayer()
775 : {
776 35 : if (poFeatureDefn != nullptr)
777 35 : poFeatureDefn->Release();
778 35 : CPLFree(pszVal);
779 70 : }
780 :
781 : /************************************************************************/
782 : /* GetNextFeature() */
783 : /************************************************************************/
784 :
785 52 : OGRFeature *OGRFGdbSingleFeatureLayer::GetNextFeature()
786 : {
787 52 : if (iNextShapeId != 0)
788 17 : return nullptr;
789 :
790 35 : OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
791 35 : if (pszVal)
792 35 : poFeature->SetField(0, pszVal);
793 35 : poFeature->SetFID(iNextShapeId++);
794 35 : return poFeature;
795 : }
796 :
797 : /************************************************************************/
798 : /* ExecuteSQL() */
799 : /************************************************************************/
800 :
801 132 : OGRLayer *FGdbDataSource::ExecuteSQL(const char *pszSQLCommand,
802 : OGRGeometry *poSpatialFilter,
803 : const char *pszDialect)
804 :
805 : {
806 132 : if (m_pConnection && m_pConnection->IsFIDHackInProgress())
807 : {
808 1 : if (CloseInternal())
809 1 : ReOpen();
810 : }
811 132 : if (m_pGeodatabase == nullptr)
812 0 : return nullptr;
813 :
814 132 : size_t count = m_layers.size();
815 2233 : for (size_t i = 0; i < count; ++i)
816 : {
817 2101 : if (FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]))
818 2101 : poFgdbLayer->EndBulkLoad();
819 : }
820 :
821 : /* -------------------------------------------------------------------- */
822 : /* Use generic implementation for recognized dialects */
823 : /* -------------------------------------------------------------------- */
824 132 : if (IsGenericSQLDialect(pszDialect))
825 0 : return OGRDataSource::ExecuteSQL(pszSQLCommand, poSpatialFilter,
826 0 : pszDialect);
827 :
828 : /* -------------------------------------------------------------------- */
829 : /* Special case GetLayerDefinition */
830 : /* -------------------------------------------------------------------- */
831 132 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerDefinition "))
832 : {
833 36 : FGdbLayer *poLayer = (FGdbLayer *)GetLayerByName(
834 18 : pszSQLCommand + strlen("GetLayerDefinition "));
835 18 : if (poLayer)
836 : {
837 17 : char *pszVal = nullptr;
838 17 : poLayer->GetLayerXML(&pszVal);
839 : OGRLayer *poRet =
840 17 : new OGRFGdbSingleFeatureLayer("LayerDefinition", pszVal);
841 17 : CPLFree(pszVal);
842 17 : return poRet;
843 : }
844 : else
845 1 : return nullptr;
846 : }
847 :
848 : /* -------------------------------------------------------------------- */
849 : /* Special case GetLayerMetadata */
850 : /* -------------------------------------------------------------------- */
851 114 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerMetadata "))
852 : {
853 36 : FGdbLayer *poLayer = (FGdbLayer *)GetLayerByName(
854 18 : pszSQLCommand + strlen("GetLayerMetadata "));
855 18 : if (poLayer)
856 : {
857 17 : char *pszVal = nullptr;
858 17 : poLayer->GetLayerMetadataXML(&pszVal);
859 : OGRLayer *poRet =
860 17 : new OGRFGdbSingleFeatureLayer("LayerMetadata", pszVal);
861 17 : CPLFree(pszVal);
862 17 : return poRet;
863 : }
864 : else
865 1 : return nullptr;
866 : }
867 :
868 : /* -------------------------------------------------------------------- */
869 : /* Special case REPACK */
870 : /* -------------------------------------------------------------------- */
871 96 : if (EQUAL(pszSQLCommand, "REPACK"))
872 : {
873 1 : auto hr = m_pGeodatabase->CompactDatabase();
874 1 : if (FAILED(hr))
875 : {
876 0 : GDBErr(hr, "CompactDatabase failed");
877 0 : return new OGRFGdbSingleFeatureLayer("result", "false");
878 : }
879 1 : return new OGRFGdbSingleFeatureLayer("result", "true");
880 : }
881 :
882 : /* TODO: remove that workaround when the SDK has finally a decent */
883 : /* SQL support ! */
884 95 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT ") && pszDialect == nullptr)
885 : {
886 90 : CPLDebug("FGDB", "Support for SELECT is known to be partially "
887 : "non-compliant with FileGDB SDK API v1.2.\n"
888 : "So for now, we use default OGR SQL engine. "
889 : "Explicitly specify -dialect FileGDB\n"
890 : "to use the SQL engine from the FileGDB SDK API");
891 90 : OGRLayer *poLayer = OGRDataSource::ExecuteSQL(
892 90 : pszSQLCommand, poSpatialFilter, pszDialect);
893 90 : if (poLayer)
894 89 : m_oSetSelectLayers.insert(poLayer);
895 90 : return poLayer;
896 : }
897 :
898 : /* -------------------------------------------------------------------- */
899 : /* Run the SQL */
900 : /* -------------------------------------------------------------------- */
901 5 : EnumRows *pEnumRows = new EnumRows;
902 : long hr;
903 : try
904 : {
905 5 : hr = m_pGeodatabase->ExecuteSQL(StringToWString(pszSQLCommand), true,
906 : *pEnumRows);
907 : }
908 0 : catch (...)
909 : {
910 0 : CPLError(CE_Failure, CPLE_AppDefined,
911 : "Exception occurred at executing '%s'. Application may "
912 : "become unstable",
913 : pszSQLCommand);
914 0 : delete pEnumRows;
915 0 : return nullptr;
916 : }
917 :
918 5 : if (FAILED(hr))
919 : {
920 2 : GDBErr(hr, CPLSPrintf("Failed at executing '%s'", pszSQLCommand));
921 2 : delete pEnumRows;
922 2 : return nullptr;
923 : }
924 :
925 3 : if (STARTS_WITH_CI(pszSQLCommand, "SELECT "))
926 : {
927 2 : OGRLayer *poLayer = new FGdbResultLayer(this, pszSQLCommand, pEnumRows);
928 2 : m_oSetSelectLayers.insert(poLayer);
929 2 : return poLayer;
930 : }
931 : else
932 : {
933 1 : delete pEnumRows;
934 1 : return nullptr;
935 : }
936 : }
937 :
938 : /************************************************************************/
939 : /* ReleaseResultSet() */
940 : /************************************************************************/
941 :
942 126 : void FGdbDataSource::ReleaseResultSet(OGRLayer *poResultsSet)
943 : {
944 126 : if (poResultsSet)
945 126 : m_oSetSelectLayers.erase(poResultsSet);
946 126 : delete poResultsSet;
947 126 : }
948 :
949 : /************************************************************************/
950 : /* HasPerLayerCopyingForTransaction() */
951 : /************************************************************************/
952 :
953 0 : int FGdbDataSource::HasPerLayerCopyingForTransaction()
954 : {
955 0 : if (bPerLayerCopyingForTransaction >= 0)
956 0 : return bPerLayerCopyingForTransaction;
957 : #ifdef _WIN32
958 : bPerLayerCopyingForTransaction = FALSE;
959 : #else
960 0 : bPerLayerCopyingForTransaction =
961 0 : m_poOpenFileGDBDrv != nullptr &&
962 0 : CPLTestBool(
963 : CPLGetConfigOption("FGDB_PER_LAYER_COPYING_TRANSACTION", "TRUE"));
964 : #endif
965 0 : return bPerLayerCopyingForTransaction;
966 : }
967 :
968 : /************************************************************************/
969 : /* SetSymlinkFlagOnAllLayers() */
970 : /************************************************************************/
971 :
972 0 : void FGdbDataSource::SetSymlinkFlagOnAllLayers()
973 : {
974 0 : size_t count = m_layers.size();
975 0 : for (size_t i = 0; i < count; ++i)
976 : {
977 0 : if (FGdbLayer *poFgdbLayer = dynamic_cast<FGdbLayer *>(m_layers[i]))
978 0 : poFgdbLayer->SetSymlinkFlag();
979 : }
980 0 : }
981 :
982 : /************************************************************************/
983 : /* GetFieldDomain() */
984 : /************************************************************************/
985 :
986 : const OGRFieldDomain *
987 25 : FGdbDataSource::GetFieldDomain(const std::string &name) const
988 : {
989 25 : const auto baseRet = GDALDataset::GetFieldDomain(name);
990 25 : if (baseRet)
991 9 : return baseRet;
992 :
993 32 : std::string domainDef;
994 : const auto hr =
995 16 : m_pGeodatabase->GetDomainDefinition(StringToWString(name), domainDef);
996 16 : if (FAILED(hr))
997 : {
998 : // GDBErr(hr, "Failed in GetDomainDefinition()");
999 10 : return nullptr;
1000 : }
1001 :
1002 12 : auto poDomain = ParseXMLFieldDomainDef(domainDef);
1003 6 : if (!poDomain)
1004 0 : return nullptr;
1005 12 : const std::string domainName(poDomain->GetName());
1006 6 : m_oMapFieldDomains[domainName] = std::move(poDomain);
1007 6 : return GDALDataset::GetFieldDomain(name);
1008 : }
1009 :
1010 : /************************************************************************/
1011 : /* GetFieldDomainNames() */
1012 : /************************************************************************/
1013 :
1014 2 : std::vector<std::string> FGdbDataSource::GetFieldDomainNames(CSLConstList) const
1015 : {
1016 4 : std::vector<std::wstring> oDomainNamesWList;
1017 2 : const auto hr = m_pGeodatabase->GetDomains(oDomainNamesWList);
1018 2 : if (FAILED(hr))
1019 : {
1020 0 : GDBErr(hr, "Failed in GetDomains()");
1021 0 : return std::vector<std::string>();
1022 : }
1023 :
1024 4 : std::vector<std::string> oDomainNamesList;
1025 2 : oDomainNamesList.reserve(oDomainNamesWList.size());
1026 8 : for (const auto &osNameW : oDomainNamesWList)
1027 : {
1028 6 : oDomainNamesList.emplace_back(WStringToString(osNameW));
1029 : }
1030 2 : return oDomainNamesList;
1031 : }
1032 :
1033 : /************************************************************************/
1034 : /* AddFieldDomain() */
1035 : /************************************************************************/
1036 :
1037 4 : bool FGdbDataSource::AddFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
1038 : std::string &failureReason)
1039 : {
1040 8 : const std::string domainName(domain->GetName());
1041 4 : if (!m_bUpdate)
1042 : {
1043 0 : CPLError(CE_Failure, CPLE_NotSupported,
1044 : "AddFieldDomain() not supported on read-only dataset");
1045 0 : return false;
1046 : }
1047 :
1048 4 : if (GetFieldDomain(domainName) != nullptr)
1049 : {
1050 0 : failureReason = "A domain of identical name already exists";
1051 0 : return false;
1052 : }
1053 :
1054 : std::string osXML =
1055 8 : BuildXMLFieldDomainDef(domain.get(), true, failureReason);
1056 4 : if (osXML.empty())
1057 : {
1058 0 : return false;
1059 : }
1060 :
1061 4 : const auto hr = m_pGeodatabase->CreateDomain(osXML);
1062 4 : if (FAILED(hr))
1063 : {
1064 0 : GDBErr(hr, "Failed in CreateDomain()");
1065 0 : CPLDebug("FileGDB", "XML of domain: %s", osXML.c_str());
1066 0 : return false;
1067 : }
1068 :
1069 4 : m_oMapFieldDomains[domainName] = std::move(domain);
1070 :
1071 4 : return true;
1072 : }
1073 :
1074 : /************************************************************************/
1075 : /* DeleteFieldDomain() */
1076 : /************************************************************************/
1077 :
1078 2 : bool FGdbDataSource::DeleteFieldDomain(const std::string &name,
1079 : std::string & /*failureReason*/)
1080 : {
1081 2 : if (!m_bUpdate)
1082 : {
1083 0 : CPLError(CE_Failure, CPLE_NotSupported,
1084 : "DeleteFieldDomain() not supported on read-only dataset");
1085 0 : return false;
1086 : }
1087 :
1088 2 : const auto hr = m_pGeodatabase->DeleteDomain(StringToWString(name));
1089 2 : if (FAILED(hr))
1090 : {
1091 1 : GDBErr(hr, "Failed in DeleteDomain()");
1092 1 : return false;
1093 : }
1094 :
1095 1 : m_oMapFieldDomains.erase(name);
1096 :
1097 1 : return true;
1098 : }
1099 :
1100 : /************************************************************************/
1101 : /* UpdateFieldDomain() */
1102 : /************************************************************************/
1103 :
1104 1 : bool FGdbDataSource::UpdateFieldDomain(std::unique_ptr<OGRFieldDomain> &&domain,
1105 : std::string &failureReason)
1106 : {
1107 2 : const std::string domainName(domain->GetName());
1108 1 : if (!m_bUpdate)
1109 : {
1110 0 : CPLError(CE_Failure, CPLE_NotSupported,
1111 : "AddFieldDomain() not supported on read-only dataset");
1112 0 : return false;
1113 : }
1114 :
1115 1 : if (GetFieldDomain(domainName) == nullptr)
1116 : {
1117 0 : failureReason = "The domain should already exist to be updated";
1118 0 : return false;
1119 : }
1120 :
1121 : std::string osXML =
1122 2 : BuildXMLFieldDomainDef(domain.get(), true, failureReason);
1123 1 : if (osXML.empty())
1124 : {
1125 0 : return false;
1126 : }
1127 :
1128 1 : const auto hr = m_pGeodatabase->AlterDomain(osXML);
1129 1 : if (FAILED(hr))
1130 : {
1131 0 : GDBErr(hr, "Failed in AlterDomain()");
1132 0 : CPLDebug("FileGDB", "XML of domain: %s", osXML.c_str());
1133 0 : return false;
1134 : }
1135 :
1136 1 : m_oMapFieldDomains[domainName] = std::move(domain);
1137 :
1138 1 : return true;
1139 : }
1140 :
1141 : /************************************************************************/
1142 : /* GetRelationshipNames() */
1143 : /************************************************************************/
1144 :
1145 : std::vector<std::string>
1146 2 : FGdbDataSource::GetRelationshipNames(CPL_UNUSED CSLConstList papszOptions) const
1147 :
1148 : {
1149 2 : std::vector<std::string> oasNames;
1150 2 : oasNames.reserve(m_osMapRelationships.size());
1151 13 : for (auto it = m_osMapRelationships.begin();
1152 24 : it != m_osMapRelationships.end(); ++it)
1153 : {
1154 11 : oasNames.emplace_back(it->first);
1155 : }
1156 2 : return oasNames;
1157 : }
1158 :
1159 : /************************************************************************/
1160 : /* GetRelationship() */
1161 : /************************************************************************/
1162 :
1163 : const GDALRelationship *
1164 8 : FGdbDataSource::GetRelationship(const std::string &name) const
1165 :
1166 : {
1167 8 : auto it = m_osMapRelationships.find(name);
1168 8 : if (it == m_osMapRelationships.end())
1169 1 : return nullptr;
1170 :
1171 7 : return it->second.get();
1172 : }
|