Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements OGRODBCDataSource class.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2003, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "ogr_odbc.h"
15 : #include "cpl_conv.h"
16 : #include "cpl_string.h"
17 : #include "ogrodbcdrivercore.h"
18 :
19 : /************************************************************************/
20 : /* OGRODBCDataSource() */
21 : /************************************************************************/
22 :
23 2 : OGRODBCDataSource::OGRODBCDataSource() : papoLayers(nullptr), nLayers(0)
24 : {
25 2 : }
26 :
27 : /************************************************************************/
28 : /* ~OGRODBCDataSource() */
29 : /************************************************************************/
30 :
31 4 : OGRODBCDataSource::~OGRODBCDataSource()
32 :
33 : {
34 2 : for (int i = 0; i < nLayers; i++)
35 0 : delete papoLayers[i];
36 :
37 2 : CPLFree(papoLayers);
38 4 : }
39 :
40 : /************************************************************************/
41 : /* CheckDSNStringTemplate() */
42 : /* The string will be used as the formatting argument of sprintf with */
43 : /* a string in vararg. So let's check there's only one '%s', and nothing*/
44 : /* else */
45 : /************************************************************************/
46 :
47 0 : static int CheckDSNStringTemplate(const char *pszStr)
48 : {
49 0 : int nPercentSFound = FALSE;
50 0 : while (*pszStr)
51 : {
52 0 : if (*pszStr == '%')
53 : {
54 0 : if (pszStr[1] != 's')
55 : {
56 0 : return FALSE;
57 : }
58 : else
59 : {
60 0 : if (nPercentSFound)
61 0 : return FALSE;
62 0 : nPercentSFound = TRUE;
63 : }
64 : }
65 0 : pszStr++;
66 : }
67 0 : return TRUE;
68 : }
69 :
70 : /************************************************************************/
71 : /* OpenMDB() */
72 : /************************************************************************/
73 :
74 2 : int OGRODBCDataSource::OpenMDB(GDALOpenInfo *poOpenInfo)
75 : {
76 : #ifndef _WIN32
77 : // Try to register MDB Tools driver
78 2 : CPLODBCDriverInstaller::InstallMdbToolsDriver();
79 : #endif /* ndef WIN32 */
80 :
81 2 : const char *pszOptionName = "PGEO_DRIVER_TEMPLATE";
82 : const char *pszDSNStringTemplate =
83 2 : CPLGetConfigOption(pszOptionName, nullptr);
84 2 : if (pszDSNStringTemplate == nullptr)
85 : {
86 2 : pszOptionName = "MDB_DRIVER_TEMPLATE";
87 2 : pszDSNStringTemplate = CPLGetConfigOption(pszOptionName, nullptr);
88 2 : if (pszDSNStringTemplate == nullptr)
89 : {
90 2 : pszOptionName = "";
91 : }
92 : }
93 2 : if (pszDSNStringTemplate && !CheckDSNStringTemplate(pszDSNStringTemplate))
94 : {
95 0 : CPLError(CE_Failure, CPLE_AppDefined, "Illegal value for %s option",
96 : pszOptionName);
97 0 : return FALSE;
98 : }
99 :
100 2 : const char *pszNewName = poOpenInfo->pszFilename;
101 2 : if (!oSession.ConnectToMsAccess(pszNewName, pszDSNStringTemplate))
102 : {
103 2 : return FALSE;
104 : }
105 :
106 : // Retrieve numeric values from MS Access files using ODBC numeric types, to
107 : // avoid loss of precision and missing values on Windows (see
108 : // https://github.com/OSGeo/gdal/issues/3885)
109 0 : m_nStatementFlags |= CPLODBCStatement::Flag::RetrieveNumericColumnsAsDouble;
110 :
111 : // Collate a list of all tables in the data source
112 0 : CPLODBCStatement oTableList(&oSession);
113 0 : std::vector<CPLString> aosTableNames;
114 0 : if (oTableList.GetTables())
115 : {
116 0 : while (oTableList.Fetch())
117 : {
118 0 : const char *pszSchema = oTableList.GetColData(1);
119 0 : const char *pszTableName = oTableList.GetColData(2);
120 0 : if (pszTableName != nullptr)
121 : {
122 0 : CPLString osLayerName;
123 :
124 0 : if (pszSchema != nullptr && strlen(pszSchema) > 0)
125 : {
126 0 : osLayerName = pszSchema;
127 0 : osLayerName += ".";
128 : }
129 :
130 0 : osLayerName += pszTableName;
131 :
132 0 : m_aosAllLCTableNames.insert(CPLString(osLayerName).tolower());
133 :
134 0 : aosTableNames.emplace_back(osLayerName);
135 : }
136 : }
137 : }
138 : else
139 : {
140 0 : return FALSE;
141 : }
142 :
143 : /* -------------------------------------------------------------------- */
144 : /* Check if it is a PGeo MDB. */
145 : /* -------------------------------------------------------------------- */
146 0 : for (const CPLString &osTableName : aosTableNames)
147 : {
148 0 : const CPLString osLCTableName(CPLString(osTableName).tolower());
149 0 : if (osLCTableName == "gdb_geomcolumns" /* PGeo */)
150 0 : return FALSE;
151 : }
152 :
153 0 : const bool bListAllTables = CPLTestBool(CSLFetchNameValueDef(
154 0 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "NO"));
155 :
156 : /* -------------------------------------------------------------------- */
157 : /* Return all tables as non-spatial tables. */
158 : /* -------------------------------------------------------------------- */
159 0 : for (const CPLString &osTableName : aosTableNames)
160 : {
161 0 : const CPLString osLCTableName(CPLString(osTableName).tolower());
162 0 : if (bListAllTables || !(osLCTableName.size() >= 4 &&
163 0 : osLCTableName.substr(0, 4) ==
164 : "msys") // MS Access internal tables
165 : )
166 : {
167 0 : OpenTable(osTableName, nullptr);
168 : }
169 : }
170 :
171 0 : return TRUE;
172 : }
173 :
174 : /************************************************************************/
175 : /* Open() */
176 : /************************************************************************/
177 :
178 2 : int OGRODBCDataSource::Open(GDALOpenInfo *poOpenInfo)
179 : {
180 2 : CPLAssert(nLayers == 0);
181 :
182 2 : const char *pszNewName = poOpenInfo->pszFilename;
183 :
184 2 : constexpr const char *ODBC_PREFIX = "ODBC:";
185 4 : if (!STARTS_WITH_CI(pszNewName, ODBC_PREFIX) &&
186 2 : OGRODBCDriverIsSupportedMsAccessFileExtension(
187 4 : CPLGetExtensionSafe(pszNewName).c_str()))
188 2 : return OpenMDB(poOpenInfo);
189 :
190 : /* -------------------------------------------------------------------- */
191 : /* Start parsing dataset name from the end of string, fetching */
192 : /* the name of spatial reference table and names for SRID and */
193 : /* SRTEXT columns first. */
194 : /* -------------------------------------------------------------------- */
195 0 : char *pszWrkName = CPLStrdup(pszNewName + strlen(ODBC_PREFIX));
196 0 : char **papszTables = nullptr;
197 0 : char **papszGeomCol = nullptr;
198 0 : char *pszSRSTableName = nullptr;
199 0 : char *pszSRIDCol = nullptr;
200 0 : char *pszSRTextCol = nullptr;
201 0 : char *pszDelimiter = nullptr;
202 :
203 0 : if ((pszDelimiter = strrchr(pszWrkName, ':')) != nullptr)
204 : {
205 0 : char *pszOBracket = strchr(pszDelimiter + 1, '(');
206 :
207 0 : if (strchr(pszDelimiter, '\\') != nullptr ||
208 0 : strchr(pszDelimiter, '/') != nullptr)
209 : {
210 : /*
211 : ** if there are special tokens then this isn't really
212 : ** the srs table name, so avoid further processing.
213 : */
214 : }
215 0 : else if (pszOBracket == nullptr)
216 : {
217 0 : pszSRSTableName = CPLStrdup(pszDelimiter + 1);
218 0 : *pszDelimiter = '\0';
219 : }
220 : else
221 : {
222 0 : char *pszCBracket = strchr(pszOBracket, ')');
223 0 : if (pszCBracket != nullptr)
224 0 : *pszCBracket = '\0';
225 :
226 0 : char *pszComma = strchr(pszOBracket, ',');
227 0 : if (pszComma != nullptr)
228 : {
229 0 : *pszComma = '\0';
230 0 : pszSRIDCol = CPLStrdup(pszComma + 1);
231 : }
232 :
233 0 : *pszOBracket = '\0';
234 0 : pszSRSTableName = CPLStrdup(pszDelimiter + 1);
235 0 : pszSRTextCol = CPLStrdup(pszOBracket + 1);
236 :
237 0 : *pszDelimiter = '\0';
238 : }
239 : }
240 :
241 : /* -------------------------------------------------------------------- */
242 : /* Strip off any comma delimited set of tables names to access */
243 : /* from the end of the string first. Also allow an optional */
244 : /* bracketed geometry column name after the table name. */
245 : /* -------------------------------------------------------------------- */
246 0 : while ((pszDelimiter = strrchr(pszWrkName, ',')) != nullptr)
247 : {
248 0 : char *pszOBracket = strstr(pszDelimiter + 1, "(");
249 0 : if (pszOBracket == nullptr)
250 : {
251 0 : papszTables = CSLAddString(papszTables, pszDelimiter + 1);
252 0 : papszGeomCol = CSLAddString(papszGeomCol, "");
253 : }
254 : else
255 : {
256 0 : char *pszCBracket = strstr(pszOBracket, ")");
257 :
258 0 : if (pszCBracket != nullptr)
259 0 : *pszCBracket = '\0';
260 :
261 0 : *pszOBracket = '\0';
262 0 : papszTables = CSLAddString(papszTables, pszDelimiter + 1);
263 0 : papszGeomCol = CSLAddString(papszGeomCol, pszOBracket + 1);
264 : }
265 0 : *pszDelimiter = '\0';
266 : }
267 :
268 : /* -------------------------------------------------------------------- */
269 : /* Split out userid, password and DSN. The general form is */
270 : /* user/password@dsn. But if there are no @ characters the */
271 : /* whole thing is assumed to be a DSN. */
272 : /* -------------------------------------------------------------------- */
273 0 : std::string osUserId;
274 0 : std::string osPassword;
275 0 : std::string osDSN;
276 :
277 0 : const char *pszAt = strchr(pszWrkName, '@');
278 0 : if (pszAt == nullptr)
279 : {
280 0 : osDSN = pszWrkName;
281 : }
282 : else
283 : {
284 0 : osDSN = pszAt + 1;
285 0 : osUserId.assign(pszWrkName, pszAt - pszWrkName);
286 0 : const auto nSlashPos = osUserId.find('/');
287 0 : if (nSlashPos != std::string::npos)
288 : {
289 0 : osPassword = osUserId.substr(nSlashPos + 1);
290 0 : osUserId.resize(nSlashPos);
291 : }
292 : }
293 :
294 0 : CPLFree(pszWrkName);
295 :
296 : /* -------------------------------------------------------------------- */
297 : /* Initialize based on the DSN. */
298 : /* -------------------------------------------------------------------- */
299 0 : CPLDebug("OGR_ODBC",
300 : "EstablishSession(DSN:\"%s\", userid:\"%s\", password:\"%s\")",
301 : osDSN.c_str(), osUserId.c_str(), osPassword.c_str());
302 :
303 0 : if (!oSession.EstablishSession(
304 0 : osDSN.c_str(), osUserId.empty() ? nullptr : osUserId.c_str(),
305 0 : osPassword.empty() ? nullptr : osPassword.c_str()))
306 : {
307 0 : CPLError(CE_Failure, CPLE_AppDefined,
308 : "Unable to initialize ODBC connection to DSN for %s,\n"
309 : "%s",
310 : pszNewName + strlen(ODBC_PREFIX), oSession.GetLastError());
311 0 : CSLDestroy(papszTables);
312 0 : CSLDestroy(papszGeomCol);
313 0 : CPLFree(pszSRIDCol);
314 0 : CPLFree(pszSRTextCol);
315 0 : CPLFree(pszSRSTableName);
316 0 : return FALSE;
317 : }
318 :
319 : /* -------------------------------------------------------------------- */
320 : /* If no explicit list of tables was given, check for a list in */
321 : /* a geometry_columns table. */
322 : /* -------------------------------------------------------------------- */
323 0 : if (papszTables == nullptr)
324 : {
325 0 : CPLODBCStatement oStmt(&oSession);
326 :
327 0 : oStmt.Append("SELECT f_table_name, f_geometry_column, geometry_type"
328 : " FROM geometry_columns");
329 0 : if (oStmt.ExecuteSQL())
330 : {
331 0 : while (oStmt.Fetch())
332 : {
333 0 : papszTables = CSLAddString(papszTables, oStmt.GetColData(0));
334 0 : papszGeomCol = CSLAddString(papszGeomCol, oStmt.GetColData(1));
335 : }
336 : }
337 : }
338 :
339 : /* -------------------------------------------------------------------- */
340 : /* Otherwise our final resort is to return all tables as */
341 : /* non-spatial tables. */
342 : /* -------------------------------------------------------------------- */
343 0 : if (papszTables == nullptr)
344 : {
345 0 : CPLODBCStatement oTableList(&oSession);
346 :
347 0 : if (oTableList.GetTables())
348 : {
349 0 : while (oTableList.Fetch())
350 : {
351 0 : const char *pszSchema = oTableList.GetColData(1);
352 0 : CPLString osLayerName;
353 :
354 0 : if (pszSchema != nullptr && strlen(pszSchema) > 0)
355 : {
356 0 : osLayerName = pszSchema;
357 0 : osLayerName += ".";
358 : }
359 :
360 0 : osLayerName += oTableList.GetColData(2);
361 :
362 0 : papszTables = CSLAddString(papszTables, osLayerName);
363 :
364 0 : papszGeomCol = CSLAddString(papszGeomCol, "");
365 : }
366 : }
367 : }
368 :
369 : /* -------------------------------------------------------------------- */
370 : /* If we have an explicit list of requested tables, use them */
371 : /* (non-spatial). */
372 : /* -------------------------------------------------------------------- */
373 0 : for (int iTable = 0;
374 0 : papszTables != nullptr && papszTables[iTable] != nullptr; iTable++)
375 : {
376 0 : if (strlen(papszGeomCol[iTable]) > 0)
377 0 : OpenTable(papszTables[iTable], papszGeomCol[iTable]);
378 : else
379 0 : OpenTable(papszTables[iTable], nullptr);
380 : }
381 :
382 0 : CSLDestroy(papszTables);
383 0 : CSLDestroy(papszGeomCol);
384 :
385 : #if 0
386 : // NOTE: nothing uses the SRS cache currently. Hence disabled.
387 :
388 : /* -------------------------------------------------------------------- */
389 : /* If no explicit list of tables was given, check for a list in */
390 : /* a geometry_columns table. */
391 : /* -------------------------------------------------------------------- */
392 : if (pszSRSTableName)
393 : {
394 : CPLODBCStatement oSRSList(&oSession);
395 :
396 : if (!pszSRTextCol)
397 : pszSRTextCol = CPLStrdup("srtext");
398 : if (!pszSRIDCol)
399 : pszSRIDCol = CPLStrdup("srid");
400 :
401 : oSRSList.Append("SELECT ");
402 : oSRSList.Append(pszSRIDCol);
403 : oSRSList.Append(",");
404 : oSRSList.Append(pszSRTextCol);
405 : oSRSList.Append(" FROM ");
406 : oSRSList.Append(pszSRSTableName);
407 :
408 : CPLDebug("OGR_ODBC", "ExecuteSQL(%s) to read SRS table",
409 : oSRSList.GetCommand());
410 : if (oSRSList.ExecuteSQL())
411 : {
412 : while (oSRSList.Fetch())
413 : {
414 : const char *pszSRID = oSRSList.GetColData(pszSRIDCol);
415 : if (!pszSRID)
416 : continue;
417 :
418 : const char *pszSRText = oSRSList.GetColData(pszSRTextCol);
419 :
420 : if (pszSRText)
421 : {
422 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSRS(new OGRSpatialReference());
423 : poSRS->SetAxisMappingStrategy(
424 : OAMS_TRADITIONAL_GIS_ORDER);
425 : if (poSRS->importFromWkt(pszSRText) == OGRERR_NONE )
426 : {
427 : m_oSRSCache[atoi(pszSRID)] = std::move(poSRS);
428 : }
429 : }
430 : }
431 : }
432 : }
433 : #endif
434 :
435 0 : if (pszSRIDCol)
436 0 : CPLFree(pszSRIDCol);
437 0 : if (pszSRTextCol)
438 0 : CPLFree(pszSRTextCol);
439 0 : if (pszSRSTableName)
440 0 : CPLFree(pszSRSTableName);
441 :
442 0 : return TRUE;
443 : }
444 :
445 : /************************************************************************/
446 : /* OpenTable() */
447 : /************************************************************************/
448 :
449 0 : int OGRODBCDataSource::OpenTable(const char *pszNewName, const char *pszGeomCol)
450 : {
451 : /* -------------------------------------------------------------------- */
452 : /* Create the layer object. */
453 : /* -------------------------------------------------------------------- */
454 0 : OGRODBCTableLayer *poLayer = new OGRODBCTableLayer(this, m_nStatementFlags);
455 :
456 0 : if (poLayer->Initialize(pszNewName, pszGeomCol))
457 : {
458 0 : delete poLayer;
459 0 : return FALSE;
460 : }
461 :
462 : /* -------------------------------------------------------------------- */
463 : /* Add layer to data source layer list. */
464 : /* -------------------------------------------------------------------- */
465 0 : papoLayers = (OGRODBCLayer **)CPLRealloc(
466 0 : papoLayers, sizeof(OGRODBCLayer *) * (nLayers + 1));
467 0 : papoLayers[nLayers++] = poLayer;
468 :
469 0 : return TRUE;
470 : }
471 :
472 : /************************************************************************/
473 : /* TestCapability() */
474 : /************************************************************************/
475 :
476 0 : int OGRODBCDataSource::TestCapability(CPL_UNUSED const char *pszCap)
477 : {
478 0 : return FALSE;
479 : }
480 :
481 : /************************************************************************/
482 : /* GetLayer() */
483 : /************************************************************************/
484 :
485 0 : OGRLayer *OGRODBCDataSource::GetLayer(int iLayer)
486 :
487 : {
488 0 : if (iLayer < 0 || iLayer >= nLayers)
489 0 : return nullptr;
490 : else
491 0 : return papoLayers[iLayer];
492 : }
493 :
494 : /************************************************************************/
495 : /* GetLayerByName() */
496 : /************************************************************************/
497 :
498 0 : OGRLayer *OGRODBCDataSource::GetLayerByName(const char *pszLayerName)
499 : {
500 0 : OGRLayer *poLayer = GDALDataset::GetLayerByName(pszLayerName);
501 0 : if (poLayer != nullptr)
502 0 : return poLayer;
503 :
504 : // if table name doesn't exist in database, don't try any further
505 0 : const CPLString osLCTableName(CPLString(pszLayerName).tolower());
506 0 : if (m_aosAllLCTableNames.find(osLCTableName) == m_aosAllLCTableNames.end())
507 0 : return nullptr;
508 :
509 : // try to open the table -- if successful the table will be added to
510 : // papoLayers as the last item
511 0 : if (OpenTable(pszLayerName, nullptr))
512 0 : return papoLayers[nLayers - 1];
513 : else
514 0 : return nullptr;
515 : }
516 :
517 : /************************************************************************/
518 : /* IsPrivateLayerName() */
519 : /************************************************************************/
520 :
521 0 : bool OGRODBCDataSource::IsPrivateLayerName(const CPLString &osName)
522 : {
523 0 : const CPLString osLCTableName(CPLString(osName).tolower());
524 :
525 0 : return osLCTableName.size() >= 4 &&
526 0 : osLCTableName.substr(0, 4) == "msys"; // MS Access internal tables
527 : }
528 :
529 : /************************************************************************/
530 : /* IsLayerPrivate() */
531 : /************************************************************************/
532 :
533 0 : bool OGRODBCDataSource::IsLayerPrivate(int iLayer) const
534 : {
535 0 : if (iLayer < 0 || iLayer >= nLayers)
536 0 : return false;
537 :
538 0 : const std::string osName(papoLayers[iLayer]->GetName());
539 0 : return IsPrivateLayerName(osName);
540 : }
541 :
542 : /************************************************************************/
543 : /* ExecuteSQL() */
544 : /************************************************************************/
545 :
546 0 : OGRLayer *OGRODBCDataSource::ExecuteSQL(const char *pszSQLCommand,
547 : OGRGeometry *poSpatialFilter,
548 : const char *pszDialect)
549 :
550 : {
551 : /* -------------------------------------------------------------------- */
552 : /* Use generic implementation for recognized dialects */
553 : /* -------------------------------------------------------------------- */
554 0 : if (IsGenericSQLDialect(pszDialect))
555 0 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter,
556 0 : pszDialect);
557 :
558 : /* -------------------------------------------------------------------- */
559 : /* Execute statement. */
560 : /* -------------------------------------------------------------------- */
561 : CPLODBCStatement *poStmt =
562 0 : new CPLODBCStatement(&oSession, m_nStatementFlags);
563 :
564 0 : CPLDebug("ODBC", "ExecuteSQL(%s) called.", pszSQLCommand);
565 0 : poStmt->Append(pszSQLCommand);
566 0 : if (!poStmt->ExecuteSQL())
567 : {
568 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", oSession.GetLastError());
569 0 : delete poStmt;
570 0 : return nullptr;
571 : }
572 :
573 : /* -------------------------------------------------------------------- */
574 : /* Are there result columns for this statement? */
575 : /* -------------------------------------------------------------------- */
576 0 : if (poStmt->GetColCount() == 0)
577 : {
578 0 : delete poStmt;
579 0 : CPLErrorReset();
580 0 : return nullptr;
581 : }
582 :
583 : /* -------------------------------------------------------------------- */
584 : /* Create a results layer. It will take ownership of the */
585 : /* statement. */
586 : /* -------------------------------------------------------------------- */
587 :
588 0 : OGRODBCSelectLayer *poLayer = new OGRODBCSelectLayer(this, poStmt);
589 :
590 0 : if (poSpatialFilter != nullptr)
591 0 : poLayer->SetSpatialFilter(poSpatialFilter);
592 :
593 0 : return poLayer;
594 : }
595 :
596 : /************************************************************************/
597 : /* ReleaseResultSet() */
598 : /************************************************************************/
599 :
600 0 : void OGRODBCDataSource::ReleaseResultSet(OGRLayer *poLayer)
601 :
602 : {
603 0 : delete poLayer;
604 0 : }
|