Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements OGRPGeoDataSource class.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2010-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "ogr_pgeo.h"
15 : #include "cpl_conv.h"
16 : #include "cpl_string.h"
17 : #include <vector>
18 : #include <unordered_set>
19 : #include "filegdb_fielddomain.h"
20 : #include "filegdb_relationship.h"
21 :
22 : #ifdef __linux
23 : #include <sys/types.h>
24 : #include <sys/stat.h>
25 : #include <fcntl.h>
26 : #endif
27 :
28 : /************************************************************************/
29 : /* OGRPGeoDataSource() */
30 : /************************************************************************/
31 :
32 0 : OGRPGeoDataSource::OGRPGeoDataSource() : papoLayers(nullptr), nLayers(0)
33 : {
34 : // Retrieve numeric values from MS Access files using ODBC numeric types, to
35 : // avoid loss of precision and missing values on Windows (see
36 : // https://github.com/OSGeo/gdal/issues/3885)
37 0 : m_nStatementFlags |= CPLODBCStatement::Flag::RetrieveNumericColumnsAsDouble;
38 0 : }
39 :
40 : /************************************************************************/
41 : /* ~OGRPGeoDataSource() */
42 : /************************************************************************/
43 :
44 0 : OGRPGeoDataSource::~OGRPGeoDataSource()
45 :
46 : {
47 0 : for (int i = 0; i < nLayers; i++)
48 0 : delete papoLayers[i];
49 :
50 0 : CPLFree(papoLayers);
51 0 : }
52 :
53 : /************************************************************************/
54 : /* CheckDSNStringTemplate() */
55 : /* The string will be used as the formatting argument of sprintf with */
56 : /* a string in vararg. So let's check there's only one '%s', and nothing*/
57 : /* else */
58 : /************************************************************************/
59 :
60 0 : static int CheckDSNStringTemplate(const char *pszStr)
61 : {
62 0 : int nPercentSFound = FALSE;
63 0 : while (*pszStr)
64 : {
65 0 : if (*pszStr == '%')
66 : {
67 0 : if (pszStr[1] != 's')
68 : {
69 0 : return FALSE;
70 : }
71 : else
72 : {
73 0 : if (nPercentSFound)
74 0 : return FALSE;
75 0 : nPercentSFound = TRUE;
76 : }
77 : }
78 0 : pszStr++;
79 : }
80 0 : return TRUE;
81 : }
82 :
83 : /************************************************************************/
84 : /* Open() */
85 : /************************************************************************/
86 :
87 0 : int OGRPGeoDataSource::Open(GDALOpenInfo *poOpenInfo)
88 : {
89 0 : CPLAssert(nLayers == 0);
90 :
91 0 : const char *pszNewName = poOpenInfo->pszFilename;
92 : /* -------------------------------------------------------------------- */
93 : /* If this is the name of an MDB file, then construct the */
94 : /* appropriate connection string. Otherwise clip of PGEO: to */
95 : /* get the DSN. */
96 : /* */
97 : /* -------------------------------------------------------------------- */
98 0 : if (STARTS_WITH_CI(pszNewName, "PGEO:"))
99 : {
100 0 : const std::string osDSN = pszNewName + 5;
101 0 : CPLDebug("PGeo", "EstablishSession(%s)", osDSN.c_str());
102 0 : if (!oSession.EstablishSession(osDSN.c_str(), nullptr, nullptr))
103 : {
104 0 : CPLError(CE_Failure, CPLE_AppDefined,
105 : "Unable to initialize ODBC connection to DSN for %s,\n"
106 : "%s",
107 : osDSN.c_str(), oSession.GetLastError());
108 0 : return FALSE;
109 : }
110 : }
111 : else
112 : {
113 : const char *pszDSNStringTemplate =
114 0 : CPLGetConfigOption("PGEO_DRIVER_TEMPLATE", nullptr);
115 0 : if (pszDSNStringTemplate &&
116 0 : !CheckDSNStringTemplate(pszDSNStringTemplate))
117 : {
118 0 : CPLError(CE_Failure, CPLE_AppDefined,
119 : "Illegal value for PGEO_DRIVER_TEMPLATE option");
120 0 : return FALSE;
121 : }
122 0 : if (!oSession.ConnectToMsAccess(pszNewName, pszDSNStringTemplate))
123 : {
124 0 : return FALSE;
125 : }
126 : }
127 :
128 : /* -------------------------------------------------------------------- */
129 : /* Collect list of tables and their supporting info from */
130 : /* GDB_GeomColumns. */
131 : /* -------------------------------------------------------------------- */
132 0 : std::vector<char **> apapszGeomColumns;
133 0 : CPLODBCStatement oStmt(&oSession);
134 :
135 0 : oStmt.Append(
136 : "SELECT TableName, FieldName, ShapeType, ExtentLeft, ExtentRight, "
137 : "ExtentBottom, ExtentTop, SRID, HasZ, HasM FROM GDB_GeomColumns");
138 :
139 0 : if (!oStmt.ExecuteSQL())
140 : {
141 0 : CPLDebug("PGEO",
142 : "SELECT on GDB_GeomColumns fails, perhaps not a personal "
143 : "geodatabase?\n%s",
144 : oSession.GetLastError());
145 0 : return FALSE;
146 : }
147 :
148 0 : while (oStmt.Fetch())
149 : {
150 0 : int i, iNew = static_cast<int>(apapszGeomColumns.size());
151 0 : char **papszRecord = nullptr;
152 0 : for (i = 0; i < 10; i++)
153 0 : papszRecord = CSLAddString(papszRecord, oStmt.GetColData(i));
154 0 : apapszGeomColumns.resize(iNew + 1);
155 0 : apapszGeomColumns[iNew] = papszRecord;
156 : }
157 :
158 0 : const bool bListAllTables = CPLTestBool(CSLFetchNameValueDef(
159 0 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "NO"));
160 :
161 : // Collate a list of all tables in the data source, skipping over internal
162 : // and system tables
163 0 : CPLODBCStatement oTableList(&oSession);
164 0 : std::vector<CPLString> aosTableNames;
165 0 : if (oTableList.GetTables())
166 : {
167 0 : while (oTableList.Fetch())
168 : {
169 0 : const CPLString osTableName = CPLString(oTableList.GetColData(2));
170 0 : const CPLString osLCTableName(CPLString(osTableName).tolower());
171 0 : m_aosAllLCTableNames.insert(osLCTableName);
172 :
173 0 : if (osLCTableName == "gdb_items")
174 : {
175 0 : m_bHasGdbItemsTable = true;
176 : }
177 :
178 0 : if (!osTableName.empty() &&
179 0 : (bListAllTables || !IsPrivateLayerName(osTableName)))
180 : {
181 0 : aosTableNames.emplace_back(osTableName);
182 : }
183 : }
184 : }
185 :
186 : // Create a layer for each spatial table.
187 0 : papoLayers =
188 0 : (OGRPGeoLayer **)CPLCalloc(apapszGeomColumns.size(), sizeof(void *));
189 :
190 0 : std::unordered_set<std::string> oSetSpatialTableNames;
191 0 : for (unsigned int iTable = 0; iTable < apapszGeomColumns.size(); iTable++)
192 : {
193 0 : char **papszRecord = apapszGeomColumns[iTable];
194 0 : if (EQUAL(papszRecord[0], "GDB_Items"))
195 : {
196 : // don't expose this internal layer
197 0 : CSLDestroy(papszRecord);
198 0 : continue;
199 : }
200 :
201 : OGRPGeoTableLayer *poLayer =
202 0 : new OGRPGeoTableLayer(this, m_nStatementFlags);
203 :
204 0 : if (poLayer->Initialize(papszRecord[0], // TableName
205 0 : papszRecord[1], // FieldName
206 0 : atoi(papszRecord[2]), // ShapeType
207 0 : CPLAtof(papszRecord[3]), // ExtentLeft
208 0 : CPLAtof(papszRecord[4]), // ExtentRight
209 0 : CPLAtof(papszRecord[5]), // ExtentBottom
210 0 : CPLAtof(papszRecord[6]), // ExtentTop
211 0 : atoi(papszRecord[7]), // SRID
212 0 : atoi(papszRecord[8]), // HasZ
213 0 : atoi(papszRecord[9])) // HasM
214 0 : != CE_None)
215 : {
216 0 : delete poLayer;
217 : }
218 : else
219 : {
220 0 : papoLayers[nLayers++] = poLayer;
221 0 : oSetSpatialTableNames.insert(CPLString(papszRecord[0]));
222 : }
223 :
224 0 : CSLDestroy(papszRecord);
225 : }
226 :
227 : // Add non-spatial tables.
228 0 : for (const CPLString &osTableName : aosTableNames)
229 : {
230 0 : if (oSetSpatialTableNames.find(osTableName) !=
231 0 : oSetSpatialTableNames.end())
232 : {
233 : // a spatial table, already handled above
234 0 : continue;
235 : }
236 :
237 : OGRPGeoTableLayer *poLayer =
238 0 : new OGRPGeoTableLayer(this, m_nStatementFlags);
239 :
240 0 : if (poLayer->Initialize(osTableName.c_str(), // TableName
241 : nullptr, // FieldName
242 : 0, // ShapeType (ESRI_LAYERGEOMTYPE_NULL)
243 : 0, // ExtentLeft
244 : 0, // ExtentRight
245 : 0, // ExtentBottom
246 : 0, // ExtentTop
247 : 0, // SRID
248 : 0, // HasZ
249 : 0) // HasM
250 0 : != CE_None)
251 : {
252 0 : delete poLayer;
253 : }
254 : else
255 : {
256 0 : papoLayers = static_cast<OGRPGeoLayer **>(
257 0 : CPLRealloc(papoLayers, sizeof(void *) * (nLayers + 1)));
258 0 : papoLayers[nLayers++] = poLayer;
259 : }
260 : }
261 :
262 : // collect domains and relationships
263 0 : if (m_bHasGdbItemsTable)
264 : {
265 0 : CPLODBCStatement oItemsStmt(&oSession);
266 0 : oItemsStmt.Append("SELECT Definition FROM GDB_Items");
267 0 : if (oItemsStmt.ExecuteSQL())
268 : {
269 0 : while (oItemsStmt.Fetch())
270 : {
271 : const CPLString osDefinition =
272 0 : CPLString(oItemsStmt.GetColData(0, ""));
273 0 : if (strstr(osDefinition, "GPCodedValueDomain2") != nullptr ||
274 0 : strstr(osDefinition, "GPRangeDomain2") != nullptr)
275 : {
276 0 : if (auto poDomain = ParseXMLFieldDomainDef(osDefinition))
277 : {
278 0 : const std::string domainName(poDomain->GetName());
279 0 : m_oMapFieldDomains[domainName] = std::move(poDomain);
280 : }
281 : }
282 0 : else if (strstr(osDefinition, "DERelationshipClassInfo") !=
283 : nullptr)
284 : {
285 0 : if (auto poRelationship =
286 0 : ParseXMLRelationshipDef(osDefinition))
287 : {
288 : const std::string relationshipName(
289 0 : poRelationship->GetName());
290 0 : m_osMapRelationships[relationshipName] =
291 0 : std::move(poRelationship);
292 : }
293 : }
294 : }
295 : }
296 : }
297 :
298 0 : return TRUE;
299 : }
300 :
301 : /************************************************************************/
302 : /* TestCapability() */
303 : /************************************************************************/
304 :
305 0 : int OGRPGeoDataSource::TestCapability(CPL_UNUSED const char *pszCap)
306 : {
307 0 : if (EQUAL(pszCap, ODsCMeasuredGeometries))
308 0 : return TRUE;
309 0 : else if (EQUAL(pszCap, ODsCCurveGeometries))
310 0 : return TRUE;
311 0 : else if (EQUAL(pszCap, ODsCZGeometries))
312 0 : return TRUE;
313 :
314 0 : return FALSE;
315 : }
316 :
317 : /************************************************************************/
318 : /* GetLayer() */
319 : /************************************************************************/
320 :
321 0 : OGRLayer *OGRPGeoDataSource::GetLayer(int iLayer)
322 :
323 : {
324 0 : if (iLayer < 0 || iLayer >= nLayers)
325 0 : return nullptr;
326 : else
327 0 : return papoLayers[iLayer];
328 : }
329 :
330 0 : OGRLayer *OGRPGeoDataSource::GetLayerByName(const char *pszLayerName)
331 : {
332 0 : OGRLayer *poLayer = GDALDataset::GetLayerByName(pszLayerName);
333 0 : if (poLayer != nullptr)
334 0 : return poLayer;
335 :
336 : // if table name doesn't exist in database, don't try any further
337 0 : const CPLString osLCTableName(CPLString(pszLayerName).tolower());
338 0 : if (m_aosAllLCTableNames.find(osLCTableName) == m_aosAllLCTableNames.end())
339 0 : return nullptr;
340 :
341 0 : for (const auto &poInvisibleLayer : m_apoInvisibleLayers)
342 : {
343 0 : if (EQUAL(poInvisibleLayer->GetName(), pszLayerName))
344 0 : return poInvisibleLayer.get();
345 : }
346 :
347 : std::unique_ptr<OGRPGeoTableLayer> poInvisibleLayer(
348 0 : new OGRPGeoTableLayer(this, m_nStatementFlags));
349 :
350 0 : if (poInvisibleLayer->Initialize(pszLayerName, // TableName
351 : nullptr, // FieldName
352 : 0, // ShapeType (ESRI_LAYERGEOMTYPE_NULL)
353 : 0, // ExtentLeft
354 : 0, // ExtentRight
355 : 0, // ExtentBottom
356 : 0, // ExtentTop
357 : 0, // SRID
358 : 0, // HasZ
359 : 0) // HasM
360 0 : == CE_None)
361 : {
362 0 : m_apoInvisibleLayers.emplace_back(std::move(poInvisibleLayer));
363 0 : poLayer = m_apoInvisibleLayers.back().get();
364 : }
365 0 : return poLayer;
366 : }
367 :
368 : /************************************************************************/
369 : /* IsPrivateLayerName() */
370 : /************************************************************************/
371 :
372 0 : bool OGRPGeoDataSource::IsPrivateLayerName(const CPLString &osName)
373 : {
374 0 : const CPLString osLCTableName(CPLString(osName).tolower());
375 :
376 : return (
377 0 : (osLCTableName.size() >= 4 &&
378 0 : osLCTableName.substr(0, 4) == "msys") // MS Access internal tables
379 0 : ||
380 0 : osLCTableName.endsWith(
381 : "_shape_index") // gdb spatial index tables, internal details only
382 0 : || (osLCTableName.size() >= 4 &&
383 0 : osLCTableName.substr(0, 4) == "gdb_") // gdb private tables
384 0 : || osLCTableName == "selections" ||
385 0 : osLCTableName ==
386 : "selectedobjects" // found in older personal geodatabases
387 0 : );
388 : }
389 :
390 : /************************************************************************/
391 : /* IsLayerPrivate() */
392 : /************************************************************************/
393 :
394 0 : bool OGRPGeoDataSource::IsLayerPrivate(int iLayer) const
395 : {
396 0 : if (iLayer < 0 || iLayer >= nLayers)
397 0 : return false;
398 :
399 0 : const std::string osName(papoLayers[iLayer]->GetName());
400 0 : return IsPrivateLayerName(osName);
401 : }
402 :
403 : /************************************************************************/
404 : /* OGRPGeoSingleFeatureLayer */
405 : /************************************************************************/
406 :
407 : class OGRPGeoSingleFeatureLayer final : public OGRLayer
408 : {
409 : private:
410 : char *pszVal;
411 : OGRFeatureDefn *poFeatureDefn;
412 : int iNextShapeId;
413 :
414 : public:
415 : OGRPGeoSingleFeatureLayer(const char *pszLayerName, const char *pszVal);
416 : virtual ~OGRPGeoSingleFeatureLayer();
417 :
418 0 : virtual void ResetReading() override
419 : {
420 0 : iNextShapeId = 0;
421 0 : }
422 :
423 : virtual OGRFeature *GetNextFeature() override;
424 :
425 0 : virtual OGRFeatureDefn *GetLayerDefn() override
426 : {
427 0 : return poFeatureDefn;
428 : }
429 :
430 0 : virtual int TestCapability(const char *) override
431 : {
432 0 : return FALSE;
433 : }
434 : };
435 :
436 : /************************************************************************/
437 : /* OGRPGeoSingleFeatureLayer() */
438 : /************************************************************************/
439 :
440 0 : OGRPGeoSingleFeatureLayer::OGRPGeoSingleFeatureLayer(const char *pszLayerName,
441 0 : const char *pszValIn)
442 0 : : pszVal(pszValIn ? CPLStrdup(pszValIn) : nullptr),
443 0 : poFeatureDefn(new OGRFeatureDefn(pszLayerName)), iNextShapeId(0)
444 : {
445 0 : SetDescription(poFeatureDefn->GetName());
446 0 : poFeatureDefn->Reference();
447 0 : OGRFieldDefn oField("FIELD_1", OFTString);
448 0 : poFeatureDefn->AddFieldDefn(&oField);
449 0 : }
450 :
451 : /************************************************************************/
452 : /* ~OGRPGeoSingleFeatureLayer() */
453 : /************************************************************************/
454 :
455 0 : OGRPGeoSingleFeatureLayer::~OGRPGeoSingleFeatureLayer()
456 : {
457 0 : if (poFeatureDefn != nullptr)
458 0 : poFeatureDefn->Release();
459 0 : CPLFree(pszVal);
460 0 : }
461 :
462 : /************************************************************************/
463 : /* GetNextFeature() */
464 : /************************************************************************/
465 :
466 0 : OGRFeature *OGRPGeoSingleFeatureLayer::GetNextFeature()
467 : {
468 0 : if (iNextShapeId != 0)
469 0 : return nullptr;
470 :
471 0 : OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
472 0 : if (pszVal)
473 0 : poFeature->SetField(0, pszVal);
474 0 : poFeature->SetFID(iNextShapeId++);
475 0 : return poFeature;
476 : }
477 :
478 : /************************************************************************/
479 : /* ExecuteSQL() */
480 : /************************************************************************/
481 :
482 0 : OGRLayer *OGRPGeoDataSource::ExecuteSQL(const char *pszSQLCommand,
483 : OGRGeometry *poSpatialFilter,
484 : const char *pszDialect)
485 :
486 : {
487 :
488 : /* -------------------------------------------------------------------- */
489 : /* Special case GetLayerDefinition */
490 : /* -------------------------------------------------------------------- */
491 0 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerDefinition "))
492 : {
493 0 : OGRPGeoTableLayer *poLayer = cpl::down_cast<OGRPGeoTableLayer *>(
494 : GetLayerByName(pszSQLCommand + strlen("GetLayerDefinition ")));
495 0 : if (poLayer)
496 : {
497 : OGRLayer *poRet = new OGRPGeoSingleFeatureLayer(
498 0 : "LayerDefinition", poLayer->GetXMLDefinition().c_str());
499 0 : return poRet;
500 : }
501 :
502 0 : return nullptr;
503 : }
504 :
505 : /* -------------------------------------------------------------------- */
506 : /* Special case GetLayerMetadata */
507 : /* -------------------------------------------------------------------- */
508 0 : if (STARTS_WITH_CI(pszSQLCommand, "GetLayerMetadata "))
509 : {
510 0 : OGRPGeoTableLayer *poLayer = cpl::down_cast<OGRPGeoTableLayer *>(
511 : GetLayerByName(pszSQLCommand + strlen("GetLayerMetadata ")));
512 0 : if (poLayer)
513 : {
514 : OGRLayer *poRet = new OGRPGeoSingleFeatureLayer(
515 0 : "LayerMetadata", poLayer->GetXMLDocumentation().c_str());
516 0 : return poRet;
517 : }
518 :
519 0 : return nullptr;
520 : }
521 :
522 : /* -------------------------------------------------------------------- */
523 : /* Use generic implementation for recognized dialects */
524 : /* -------------------------------------------------------------------- */
525 0 : if (IsGenericSQLDialect(pszDialect))
526 0 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter,
527 0 : pszDialect);
528 :
529 : /* -------------------------------------------------------------------- */
530 : /* Execute statement. */
531 : /* -------------------------------------------------------------------- */
532 : CPLODBCStatement *poStmt =
533 0 : new CPLODBCStatement(&oSession, m_nStatementFlags);
534 :
535 0 : poStmt->Append(pszSQLCommand);
536 0 : if (!poStmt->ExecuteSQL())
537 : {
538 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", oSession.GetLastError());
539 0 : delete poStmt;
540 0 : return nullptr;
541 : }
542 :
543 : /* -------------------------------------------------------------------- */
544 : /* Are there result columns for this statement? */
545 : /* -------------------------------------------------------------------- */
546 0 : if (poStmt->GetColCount() == 0)
547 : {
548 0 : delete poStmt;
549 0 : CPLErrorReset();
550 0 : return nullptr;
551 : }
552 :
553 : /* -------------------------------------------------------------------- */
554 : /* Create a results layer. It will take ownership of the */
555 : /* statement. */
556 : /* -------------------------------------------------------------------- */
557 0 : OGRPGeoSelectLayer *poLayer = new OGRPGeoSelectLayer(this, poStmt);
558 :
559 0 : if (poSpatialFilter != nullptr)
560 0 : poLayer->SetSpatialFilter(poSpatialFilter);
561 :
562 0 : return poLayer;
563 : }
564 :
565 : /************************************************************************/
566 : /* GetRelationshipNames() */
567 : /************************************************************************/
568 :
569 0 : std::vector<std::string> OGRPGeoDataSource::GetRelationshipNames(
570 : CPL_UNUSED CSLConstList papszOptions) const
571 :
572 : {
573 0 : std::vector<std::string> oasNames;
574 0 : oasNames.reserve(m_osMapRelationships.size());
575 0 : for (auto it = m_osMapRelationships.begin();
576 0 : it != m_osMapRelationships.end(); ++it)
577 : {
578 0 : oasNames.emplace_back(it->first);
579 : }
580 0 : return oasNames;
581 : }
582 :
583 : /************************************************************************/
584 : /* GetRelationship() */
585 : /************************************************************************/
586 :
587 : const GDALRelationship *
588 0 : OGRPGeoDataSource::GetRelationship(const std::string &name) const
589 :
590 : {
591 0 : auto it = m_osMapRelationships.find(name);
592 0 : if (it == m_osMapRelationships.end())
593 0 : return nullptr;
594 :
595 0 : return it->second.get();
596 : }
597 :
598 : /************************************************************************/
599 : /* ReleaseResultSet() */
600 : /************************************************************************/
601 :
602 0 : void OGRPGeoDataSource::ReleaseResultSet(OGRLayer *poLayer)
603 :
604 : {
605 0 : delete poLayer;
606 0 : }
607 :
608 : /************************************************************************/
609 : /* ReleaseResultSet() */
610 : /************************************************************************/
611 :
612 0 : bool OGRPGeoDataSource::CountStarWorking() const
613 : {
614 : #ifdef _WIN32
615 : return true;
616 : #else
617 : // SELECT COUNT(*) worked in mdbtools 0.9.0 to 0.9.2, but got broken in
618 : // 0.9.3. So test if it is working
619 : // See https://github.com/OSGeo/gdal/issues/4103
620 0 : if (!m_COUNT_STAR_state_known)
621 : {
622 0 : m_COUNT_STAR_state_known = true;
623 :
624 : #ifdef __linux
625 : // Temporarily redirect stderr to /dev/null
626 0 : int new_fd = open("/dev/null", O_WRONLY | O_CREAT | O_TRUNC, 0666);
627 0 : int old_stderr = -1;
628 0 : if (new_fd != -1)
629 : {
630 0 : old_stderr = dup(fileno(stderr));
631 0 : if (old_stderr != -1)
632 : {
633 0 : dup2(new_fd, fileno(stderr));
634 : }
635 0 : close(new_fd);
636 : }
637 : #endif
638 :
639 : CPLErrorStateBackuper oStateBackuper(CPLErrorHandlerPusher);
640 :
641 0 : CPLODBCStatement oStmt(&oSession);
642 0 : oStmt.Append("SELECT COUNT(*) FROM GDB_GeomColumns");
643 0 : if (oStmt.ExecuteSQL() && oStmt.Fetch())
644 : {
645 0 : m_COUNT_STAR_working = true;
646 : }
647 :
648 : #ifdef __linux
649 0 : if (old_stderr != -1)
650 : {
651 0 : dup2(old_stderr, fileno(stderr));
652 0 : close(old_stderr);
653 : }
654 : #endif
655 : }
656 0 : return m_COUNT_STAR_working;
657 : #endif
658 : }
|