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 : const CPLString osLCTableName(CPLString(osLayerName).tolower());
133 0 : m_aosAllLCTableNames.insert(osLCTableName);
134 :
135 0 : aosTableNames.emplace_back(osLayerName);
136 : }
137 : }
138 : }
139 : else
140 : {
141 0 : return FALSE;
142 : }
143 :
144 : /* -------------------------------------------------------------------- */
145 : /* Check if it is a PGeo MDB. */
146 : /* -------------------------------------------------------------------- */
147 0 : for (const CPLString &osTableName : aosTableNames)
148 : {
149 0 : const CPLString osLCTableName(CPLString(osTableName).tolower());
150 0 : if (osLCTableName == "gdb_geomcolumns" /* PGeo */)
151 0 : return FALSE;
152 : }
153 :
154 0 : const bool bListAllTables = CPLTestBool(CSLFetchNameValueDef(
155 0 : poOpenInfo->papszOpenOptions, "LIST_ALL_TABLES", "NO"));
156 :
157 : /* -------------------------------------------------------------------- */
158 : /* Return all tables as non-spatial tables. */
159 : /* -------------------------------------------------------------------- */
160 0 : for (const CPLString &osTableName : aosTableNames)
161 : {
162 0 : const CPLString osLCTableName(CPLString(osTableName).tolower());
163 0 : if (bListAllTables || !(osLCTableName.size() >= 4 &&
164 0 : osLCTableName.substr(0, 4) ==
165 : "msys") // MS Access internal tables
166 : )
167 : {
168 0 : OpenTable(osTableName, nullptr);
169 : }
170 : }
171 :
172 0 : return TRUE;
173 : }
174 :
175 : /************************************************************************/
176 : /* Open() */
177 : /************************************************************************/
178 :
179 2 : int OGRODBCDataSource::Open(GDALOpenInfo *poOpenInfo)
180 : {
181 2 : CPLAssert(nLayers == 0);
182 :
183 2 : const char *pszNewName = poOpenInfo->pszFilename;
184 :
185 2 : constexpr const char *ODBC_PREFIX = "ODBC:";
186 4 : if (!STARTS_WITH_CI(pszNewName, ODBC_PREFIX) &&
187 2 : OGRODBCDriverIsSupportedMsAccessFileExtension(
188 : CPLGetExtension(pszNewName)))
189 2 : return OpenMDB(poOpenInfo);
190 :
191 : /* -------------------------------------------------------------------- */
192 : /* Start parsing dataset name from the end of string, fetching */
193 : /* the name of spatial reference table and names for SRID and */
194 : /* SRTEXT columns first. */
195 : /* -------------------------------------------------------------------- */
196 0 : char *pszWrkName = CPLStrdup(pszNewName + strlen(ODBC_PREFIX));
197 0 : char **papszTables = nullptr;
198 0 : char **papszGeomCol = nullptr;
199 0 : char *pszSRSTableName = nullptr;
200 0 : char *pszSRIDCol = nullptr;
201 0 : char *pszSRTextCol = nullptr;
202 0 : char *pszDelimiter = nullptr;
203 :
204 0 : if ((pszDelimiter = strrchr(pszWrkName, ':')) != nullptr)
205 : {
206 0 : char *pszOBracket = strchr(pszDelimiter + 1, '(');
207 :
208 0 : if (strchr(pszDelimiter, '\\') != nullptr ||
209 0 : strchr(pszDelimiter, '/') != nullptr)
210 : {
211 : /*
212 : ** if there are special tokens then this isn't really
213 : ** the srs table name, so avoid further processing.
214 : */
215 : }
216 0 : else if (pszOBracket == nullptr)
217 : {
218 0 : pszSRSTableName = CPLStrdup(pszDelimiter + 1);
219 0 : *pszDelimiter = '\0';
220 : }
221 : else
222 : {
223 0 : char *pszCBracket = strchr(pszOBracket, ')');
224 0 : if (pszCBracket != nullptr)
225 0 : *pszCBracket = '\0';
226 :
227 0 : char *pszComma = strchr(pszOBracket, ',');
228 0 : if (pszComma != nullptr)
229 : {
230 0 : *pszComma = '\0';
231 0 : pszSRIDCol = CPLStrdup(pszComma + 1);
232 : }
233 :
234 0 : *pszOBracket = '\0';
235 0 : pszSRSTableName = CPLStrdup(pszDelimiter + 1);
236 0 : pszSRTextCol = CPLStrdup(pszOBracket + 1);
237 :
238 0 : *pszDelimiter = '\0';
239 : }
240 : }
241 :
242 : /* -------------------------------------------------------------------- */
243 : /* Strip off any comma delimited set of tables names to access */
244 : /* from the end of the string first. Also allow an optional */
245 : /* bracketed geometry column name after the table name. */
246 : /* -------------------------------------------------------------------- */
247 0 : while ((pszDelimiter = strrchr(pszWrkName, ',')) != nullptr)
248 : {
249 0 : char *pszOBracket = strstr(pszDelimiter + 1, "(");
250 0 : if (pszOBracket == nullptr)
251 : {
252 0 : papszTables = CSLAddString(papszTables, pszDelimiter + 1);
253 0 : papszGeomCol = CSLAddString(papszGeomCol, "");
254 : }
255 : else
256 : {
257 0 : char *pszCBracket = strstr(pszOBracket, ")");
258 :
259 0 : if (pszCBracket != nullptr)
260 0 : *pszCBracket = '\0';
261 :
262 0 : *pszOBracket = '\0';
263 0 : papszTables = CSLAddString(papszTables, pszDelimiter + 1);
264 0 : papszGeomCol = CSLAddString(papszGeomCol, pszOBracket + 1);
265 : }
266 0 : *pszDelimiter = '\0';
267 : }
268 :
269 : /* -------------------------------------------------------------------- */
270 : /* Split out userid, password and DSN. The general form is */
271 : /* user/password@dsn. But if there are no @ characters the */
272 : /* whole thing is assumed to be a DSN. */
273 : /* -------------------------------------------------------------------- */
274 0 : std::string osUserId;
275 0 : std::string osPassword;
276 0 : std::string osDSN;
277 :
278 0 : const char *pszAt = strchr(pszWrkName, '@');
279 0 : if (pszAt == nullptr)
280 : {
281 0 : osDSN = pszWrkName;
282 : }
283 : else
284 : {
285 0 : osDSN = pszAt + 1;
286 0 : osUserId.assign(pszWrkName, pszAt - pszWrkName);
287 0 : const auto nSlashPos = osUserId.find('/');
288 0 : if (nSlashPos != std::string::npos)
289 : {
290 0 : osPassword = osUserId.substr(nSlashPos + 1);
291 0 : osUserId.resize(nSlashPos);
292 : }
293 : }
294 :
295 0 : CPLFree(pszWrkName);
296 :
297 : /* -------------------------------------------------------------------- */
298 : /* Initialize based on the DSN. */
299 : /* -------------------------------------------------------------------- */
300 0 : CPLDebug("OGR_ODBC",
301 : "EstablishSession(DSN:\"%s\", userid:\"%s\", password:\"%s\")",
302 : osDSN.c_str(), osUserId.c_str(), osPassword.c_str());
303 :
304 0 : if (!oSession.EstablishSession(
305 0 : osDSN.c_str(), osUserId.empty() ? nullptr : osUserId.c_str(),
306 0 : osPassword.empty() ? nullptr : osPassword.c_str()))
307 : {
308 0 : CPLError(CE_Failure, CPLE_AppDefined,
309 : "Unable to initialize ODBC connection to DSN for %s,\n"
310 : "%s",
311 : pszNewName + strlen(ODBC_PREFIX), oSession.GetLastError());
312 0 : CSLDestroy(papszTables);
313 0 : CSLDestroy(papszGeomCol);
314 0 : CPLFree(pszSRIDCol);
315 0 : CPLFree(pszSRTextCol);
316 0 : CPLFree(pszSRSTableName);
317 0 : return FALSE;
318 : }
319 :
320 : /* -------------------------------------------------------------------- */
321 : /* If no explicit list of tables was given, check for a list in */
322 : /* a geometry_columns table. */
323 : /* -------------------------------------------------------------------- */
324 0 : if (papszTables == nullptr)
325 : {
326 0 : CPLODBCStatement oStmt(&oSession);
327 :
328 0 : oStmt.Append("SELECT f_table_name, f_geometry_column, geometry_type"
329 : " FROM geometry_columns");
330 0 : if (oStmt.ExecuteSQL())
331 : {
332 0 : while (oStmt.Fetch())
333 : {
334 0 : papszTables = CSLAddString(papszTables, oStmt.GetColData(0));
335 0 : papszGeomCol = CSLAddString(papszGeomCol, oStmt.GetColData(1));
336 : }
337 : }
338 : }
339 :
340 : /* -------------------------------------------------------------------- */
341 : /* Otherwise our final resort is to return all tables as */
342 : /* non-spatial tables. */
343 : /* -------------------------------------------------------------------- */
344 0 : if (papszTables == nullptr)
345 : {
346 0 : CPLODBCStatement oTableList(&oSession);
347 :
348 0 : if (oTableList.GetTables())
349 : {
350 0 : while (oTableList.Fetch())
351 : {
352 0 : const char *pszSchema = oTableList.GetColData(1);
353 0 : CPLString osLayerName;
354 :
355 0 : if (pszSchema != nullptr && strlen(pszSchema) > 0)
356 : {
357 0 : osLayerName = pszSchema;
358 0 : osLayerName += ".";
359 : }
360 :
361 0 : osLayerName += oTableList.GetColData(2);
362 :
363 0 : papszTables = CSLAddString(papszTables, osLayerName);
364 :
365 0 : papszGeomCol = CSLAddString(papszGeomCol, "");
366 : }
367 : }
368 : }
369 :
370 : /* -------------------------------------------------------------------- */
371 : /* If we have an explicit list of requested tables, use them */
372 : /* (non-spatial). */
373 : /* -------------------------------------------------------------------- */
374 0 : for (int iTable = 0;
375 0 : papszTables != nullptr && papszTables[iTable] != nullptr; iTable++)
376 : {
377 0 : if (strlen(papszGeomCol[iTable]) > 0)
378 0 : OpenTable(papszTables[iTable], papszGeomCol[iTable]);
379 : else
380 0 : OpenTable(papszTables[iTable], nullptr);
381 : }
382 :
383 0 : CSLDestroy(papszTables);
384 0 : CSLDestroy(papszGeomCol);
385 :
386 : #if 0
387 : // NOTE: nothing uses the SRS cache currently. Hence disabled.
388 :
389 : /* -------------------------------------------------------------------- */
390 : /* If no explicit list of tables was given, check for a list in */
391 : /* a geometry_columns table. */
392 : /* -------------------------------------------------------------------- */
393 : if (pszSRSTableName)
394 : {
395 : CPLODBCStatement oSRSList(&oSession);
396 :
397 : if (!pszSRTextCol)
398 : pszSRTextCol = CPLStrdup("srtext");
399 : if (!pszSRIDCol)
400 : pszSRIDCol = CPLStrdup("srid");
401 :
402 : oSRSList.Append("SELECT ");
403 : oSRSList.Append(pszSRIDCol);
404 : oSRSList.Append(",");
405 : oSRSList.Append(pszSRTextCol);
406 : oSRSList.Append(" FROM ");
407 : oSRSList.Append(pszSRSTableName);
408 :
409 : CPLDebug("OGR_ODBC", "ExecuteSQL(%s) to read SRS table",
410 : oSRSList.GetCommand());
411 : if (oSRSList.ExecuteSQL())
412 : {
413 : while (oSRSList.Fetch())
414 : {
415 : const char *pszSRID = oSRSList.GetColData(pszSRIDCol);
416 : if (!pszSRID)
417 : continue;
418 :
419 : const char *pszSRText = oSRSList.GetColData(pszSRTextCol);
420 :
421 : if (pszSRText)
422 : {
423 : std::unique_ptr<OGRSpatialReference, OGRSpatialReferenceReleaser> poSRS(new OGRSpatialReference());
424 : poSRS->SetAxisMappingStrategy(
425 : OAMS_TRADITIONAL_GIS_ORDER);
426 : if (poSRS->importFromWkt(pszSRText) == OGRERR_NONE )
427 : {
428 : m_oSRSCache[atoi(pszSRID)] = std::move(poSRS);
429 : }
430 : }
431 : }
432 : }
433 : }
434 : #endif
435 :
436 0 : if (pszSRIDCol)
437 0 : CPLFree(pszSRIDCol);
438 0 : if (pszSRTextCol)
439 0 : CPLFree(pszSRTextCol);
440 0 : if (pszSRSTableName)
441 0 : CPLFree(pszSRSTableName);
442 :
443 0 : return TRUE;
444 : }
445 :
446 : /************************************************************************/
447 : /* OpenTable() */
448 : /************************************************************************/
449 :
450 0 : int OGRODBCDataSource::OpenTable(const char *pszNewName, const char *pszGeomCol)
451 : {
452 : /* -------------------------------------------------------------------- */
453 : /* Create the layer object. */
454 : /* -------------------------------------------------------------------- */
455 0 : OGRODBCTableLayer *poLayer = new OGRODBCTableLayer(this, m_nStatementFlags);
456 :
457 0 : if (poLayer->Initialize(pszNewName, pszGeomCol))
458 : {
459 0 : delete poLayer;
460 0 : return FALSE;
461 : }
462 :
463 : /* -------------------------------------------------------------------- */
464 : /* Add layer to data source layer list. */
465 : /* -------------------------------------------------------------------- */
466 0 : papoLayers = (OGRODBCLayer **)CPLRealloc(
467 0 : papoLayers, sizeof(OGRODBCLayer *) * (nLayers + 1));
468 0 : papoLayers[nLayers++] = poLayer;
469 :
470 0 : return TRUE;
471 : }
472 :
473 : /************************************************************************/
474 : /* TestCapability() */
475 : /************************************************************************/
476 :
477 0 : int OGRODBCDataSource::TestCapability(CPL_UNUSED const char *pszCap)
478 : {
479 0 : return FALSE;
480 : }
481 :
482 : /************************************************************************/
483 : /* GetLayer() */
484 : /************************************************************************/
485 :
486 0 : OGRLayer *OGRODBCDataSource::GetLayer(int iLayer)
487 :
488 : {
489 0 : if (iLayer < 0 || iLayer >= nLayers)
490 0 : return nullptr;
491 : else
492 0 : return papoLayers[iLayer];
493 : }
494 :
495 : /************************************************************************/
496 : /* GetLayerByName() */
497 : /************************************************************************/
498 :
499 0 : OGRLayer *OGRODBCDataSource::GetLayerByName(const char *pszLayerName)
500 : {
501 0 : OGRLayer *poLayer = GDALDataset::GetLayerByName(pszLayerName);
502 0 : if (poLayer != nullptr)
503 0 : return poLayer;
504 :
505 : // if table name doesn't exist in database, don't try any further
506 0 : const CPLString osLCTableName(CPLString(pszLayerName).tolower());
507 0 : if (m_aosAllLCTableNames.find(osLCTableName) == m_aosAllLCTableNames.end())
508 0 : return nullptr;
509 :
510 : // try to open the table -- if successful the table will be added to
511 : // papoLayers as the last item
512 0 : if (OpenTable(pszLayerName, nullptr))
513 0 : return papoLayers[nLayers - 1];
514 : else
515 0 : return nullptr;
516 : }
517 :
518 : /************************************************************************/
519 : /* IsPrivateLayerName() */
520 : /************************************************************************/
521 :
522 0 : bool OGRODBCDataSource::IsPrivateLayerName(const CPLString &osName)
523 : {
524 0 : const CPLString osLCTableName(CPLString(osName).tolower());
525 :
526 0 : return osLCTableName.size() >= 4 &&
527 0 : osLCTableName.substr(0, 4) == "msys"; // MS Access internal tables
528 : }
529 :
530 : /************************************************************************/
531 : /* IsLayerPrivate() */
532 : /************************************************************************/
533 :
534 0 : bool OGRODBCDataSource::IsLayerPrivate(int iLayer) const
535 : {
536 0 : if (iLayer < 0 || iLayer >= nLayers)
537 0 : return false;
538 :
539 0 : const std::string osName(papoLayers[iLayer]->GetName());
540 0 : return IsPrivateLayerName(osName);
541 : }
542 :
543 : /************************************************************************/
544 : /* ExecuteSQL() */
545 : /************************************************************************/
546 :
547 0 : OGRLayer *OGRODBCDataSource::ExecuteSQL(const char *pszSQLCommand,
548 : OGRGeometry *poSpatialFilter,
549 : const char *pszDialect)
550 :
551 : {
552 : /* -------------------------------------------------------------------- */
553 : /* Use generic implementation for recognized dialects */
554 : /* -------------------------------------------------------------------- */
555 0 : if (IsGenericSQLDialect(pszDialect))
556 0 : return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter,
557 0 : pszDialect);
558 :
559 : /* -------------------------------------------------------------------- */
560 : /* Execute statement. */
561 : /* -------------------------------------------------------------------- */
562 : CPLODBCStatement *poStmt =
563 0 : new CPLODBCStatement(&oSession, m_nStatementFlags);
564 :
565 0 : CPLDebug("ODBC", "ExecuteSQL(%s) called.", pszSQLCommand);
566 0 : poStmt->Append(pszSQLCommand);
567 0 : if (!poStmt->ExecuteSQL())
568 : {
569 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", oSession.GetLastError());
570 0 : delete poStmt;
571 0 : return nullptr;
572 : }
573 :
574 : /* -------------------------------------------------------------------- */
575 : /* Are there result columns for this statement? */
576 : /* -------------------------------------------------------------------- */
577 0 : if (poStmt->GetColCount() == 0)
578 : {
579 0 : delete poStmt;
580 0 : CPLErrorReset();
581 0 : return nullptr;
582 : }
583 :
584 : /* -------------------------------------------------------------------- */
585 : /* Create a results layer. It will take ownership of the */
586 : /* statement. */
587 : /* -------------------------------------------------------------------- */
588 :
589 0 : OGRODBCSelectLayer *poLayer = new OGRODBCSelectLayer(this, poStmt);
590 :
591 0 : if (poSpatialFilter != nullptr)
592 0 : poLayer->SetSpatialFilter(poSpatialFilter);
593 :
594 0 : return poLayer;
595 : }
596 :
597 : /************************************************************************/
598 : /* ReleaseResultSet() */
599 : /************************************************************************/
600 :
601 0 : void OGRODBCDataSource::ReleaseResultSet(OGRLayer *poLayer)
602 :
603 : {
604 0 : delete poLayer;
605 0 : }
|