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 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
17 *
18 * The above copyright notice and this permission notice shall be included
19 * in all copies or substantial portions of the Software.
20 *
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 ****************************************************************************/
29
30 #include "ogr_pgeo.h"
31 #include "cpl_conv.h"
32 #include "cpl_string.h"
33 #include <vector>
34 #include <unordered_set>
35
36 CPL_CVSID("$Id: ogrpgeodatasource.cpp b813ff1ee9d538514d85e8bfd50b1cbe7afbcc09 2021-08-23 11:13:07 +0200 Even Rouault $")
37
38 /************************************************************************/
39 /* OGRPGeoDataSource() */
40 /************************************************************************/
41
OGRPGeoDataSource()42 OGRPGeoDataSource::OGRPGeoDataSource() :
43 papoLayers(nullptr),
44 nLayers(0),
45 pszName(nullptr),
46 bDSUpdate(FALSE)
47 {}
48
49 /************************************************************************/
50 /* ~OGRPGeoDataSource() */
51 /************************************************************************/
52
~OGRPGeoDataSource()53 OGRPGeoDataSource::~OGRPGeoDataSource()
54
55 {
56 CPLFree( pszName );
57
58 for( int i = 0; i < nLayers; i++ )
59 delete papoLayers[i];
60
61 CPLFree( papoLayers );
62 }
63
64 /************************************************************************/
65 /* CheckDSNStringTemplate() */
66 /* The string will be used as the formatting argument of sprintf with */
67 /* a string in vararg. So let's check there's only one '%s', and nothing*/
68 /* else */
69 /************************************************************************/
70
CheckDSNStringTemplate(const char * pszStr)71 static int CheckDSNStringTemplate(const char* pszStr)
72 {
73 int nPercentSFound = FALSE;
74 while(*pszStr)
75 {
76 if (*pszStr == '%')
77 {
78 if (pszStr[1] != 's')
79 {
80 return FALSE;
81 }
82 else
83 {
84 if (nPercentSFound)
85 return FALSE;
86 nPercentSFound = TRUE;
87 }
88 }
89 pszStr ++;
90 }
91 return TRUE;
92 }
93
94 /************************************************************************/
95 /* Open() */
96 /************************************************************************/
97
Open(const char * pszNewName,int bUpdate,CPL_UNUSED int bTestOpen)98 int OGRPGeoDataSource::Open( const char * pszNewName, int bUpdate,
99 CPL_UNUSED int bTestOpen )
100 {
101 CPLAssert( nLayers == 0 );
102
103 /* -------------------------------------------------------------------- */
104 /* If this is the name of an MDB file, then construct the */
105 /* appropriate connection string. Otherwise clip of PGEO: to */
106 /* get the DSN. */
107 /* */
108 /* -------------------------------------------------------------------- */
109 if( STARTS_WITH_CI(pszNewName, "PGEO:") )
110 {
111 char *pszDSN = CPLStrdup( pszNewName + 5 );
112 CPLDebug( "PGeo", "EstablishSession(%s)", pszDSN );
113 if( !oSession.EstablishSession( pszDSN, nullptr, nullptr ) )
114 {
115 CPLError( CE_Failure, CPLE_AppDefined,
116 "Unable to initialize ODBC connection to DSN for %s,\n"
117 "%s", pszDSN, oSession.GetLastError() );
118 CPLFree( pszDSN );
119 return FALSE;
120 }
121 }
122 else
123 {
124 const char* pszDSNStringTemplate = CPLGetConfigOption( "PGEO_DRIVER_TEMPLATE", nullptr );
125 if( pszDSNStringTemplate && !CheckDSNStringTemplate(pszDSNStringTemplate))
126 {
127 CPLError( CE_Failure, CPLE_AppDefined,
128 "Illegal value for PGEO_DRIVER_TEMPLATE option");
129 return FALSE;
130 }
131 if ( !oSession.ConnectToMsAccess( pszNewName, pszDSNStringTemplate ) )
132 {
133 return FALSE;
134 }
135 }
136
137 pszName = CPLStrdup( pszNewName );
138
139 bDSUpdate = bUpdate;
140
141 /* -------------------------------------------------------------------- */
142 /* Collect list of tables and their supporting info from */
143 /* GDB_GeomColumns. */
144 /* -------------------------------------------------------------------- */
145 std::vector<char **> apapszGeomColumns;
146 CPLODBCStatement oStmt( &oSession );
147
148 oStmt.Append( "SELECT TableName, FieldName, ShapeType, ExtentLeft, ExtentRight, ExtentBottom, ExtentTop, SRID, HasZ FROM GDB_GeomColumns" );
149
150 if( !oStmt.ExecuteSQL() )
151 {
152 CPLDebug( "PGEO",
153 "SELECT on GDB_GeomColumns fails, perhaps not a personal geodatabase?\n%s",
154 oSession.GetLastError() );
155 return FALSE;
156 }
157
158 while( oStmt.Fetch() )
159 {
160 int i, iNew = static_cast<int>(apapszGeomColumns.size());
161 char **papszRecord = nullptr;
162 for( i = 0; i < 9; i++ )
163 papszRecord = CSLAddString( papszRecord,
164 oStmt.GetColData(i) );
165 apapszGeomColumns.resize(iNew+1);
166 apapszGeomColumns[iNew] = papszRecord;
167 }
168
169 /* -------------------------------------------------------------------- */
170 /* Create a layer for each spatial table. */
171 /* -------------------------------------------------------------------- */
172 papoLayers = (OGRPGeoLayer **) CPLCalloc(apapszGeomColumns.size(),
173 sizeof(void*));
174
175 std::unordered_set<std::string> oSetSpatialTableNames;
176 for( unsigned int iTable = 0; iTable < apapszGeomColumns.size(); iTable++ )
177 {
178 char **papszRecord = apapszGeomColumns[iTable];
179 if ( EQUAL(papszRecord[0], "GDB_Items"))
180 {
181 // don't expose this internal layer
182 CSLDestroy( papszRecord );
183 continue;
184 }
185
186 OGRPGeoTableLayer *poLayer = new OGRPGeoTableLayer( this );
187
188 if( poLayer->Initialize( papszRecord[0], // TableName
189 papszRecord[1], // FieldName
190 atoi(papszRecord[2]), // ShapeType
191 CPLAtof(papszRecord[3]), // ExtentLeft
192 CPLAtof(papszRecord[4]), // ExtentRight
193 CPLAtof(papszRecord[5]), // ExtentBottom
194 CPLAtof(papszRecord[6]), // ExtentTop
195 atoi(papszRecord[7]), // SRID
196 atoi(papszRecord[8])) // HasZ
197 != CE_None )
198 {
199 delete poLayer;
200 }
201 else
202 {
203 papoLayers[nLayers++] = poLayer;
204 oSetSpatialTableNames.insert( CPLString( papszRecord[ 0 ] ) );
205 }
206
207 CSLDestroy( papszRecord );
208 }
209
210
211 /* -------------------------------------------------------------------- */
212 /* Add non-spatial tables. */
213 /* -------------------------------------------------------------------- */
214 CPLODBCStatement oTableList( &oSession );
215
216 if( oTableList.GetTables() )
217 {
218 while( oTableList.Fetch() )
219 {
220 const CPLString osTableName = CPLString( oTableList.GetColData(2) );
221 const CPLString osLCTableName(CPLString(osTableName).tolower());
222 // a bunch of internal tables we don't want to expose...
223 if( !osTableName.empty()
224 && !(osLCTableName.size() >= 4 && osLCTableName.substr(0, 4) == "msys") // MS Access internal tables
225 && oSetSpatialTableNames.find( osTableName ) == oSetSpatialTableNames.end() // spatial tables, already handled above
226 && !osLCTableName.endsWith( "_shape_index") // gdb spatial index tables, internal details only
227 && !(osLCTableName.size() >= 4 && osLCTableName.substr(0, 4) == "gdb_") // gdb private tables
228 )
229 {
230 OGRPGeoTableLayer *poLayer = new OGRPGeoTableLayer( this );
231
232 if( poLayer->Initialize( osTableName.c_str(), // TableName
233 nullptr, // FieldName
234 0, // ShapeType (ESRI_LAYERGEOMTYPE_NULL)
235 0, // ExtentLeft
236 0, // ExtentRight
237 0, // ExtentBottom
238 0, // ExtentTop
239 0, // SRID
240 0) // HasZ
241 != CE_None )
242 {
243 delete poLayer;
244 }
245 else
246 {
247 papoLayers = static_cast< OGRPGeoLayer **>( CPLRealloc(papoLayers, sizeof(void*) * ( nLayers+1 ) ) );
248 papoLayers[nLayers++] = poLayer;
249 }
250 }
251 }
252
253 return TRUE;
254 }
255
256 return TRUE;
257 }
258
259 /************************************************************************/
260 /* TestCapability() */
261 /************************************************************************/
262
TestCapability(CPL_UNUSED const char * pszCap)263 int OGRPGeoDataSource::TestCapability( CPL_UNUSED const char * pszCap )
264 {
265 return FALSE;
266 }
267
268 /************************************************************************/
269 /* GetLayer() */
270 /************************************************************************/
271
GetLayer(int iLayer)272 OGRLayer *OGRPGeoDataSource::GetLayer( int iLayer )
273
274 {
275 if( iLayer < 0 || iLayer >= nLayers )
276 return nullptr;
277 else
278 return papoLayers[iLayer];
279 }
280
281 /************************************************************************/
282 /* ExecuteSQL() */
283 /************************************************************************/
284
ExecuteSQL(const char * pszSQLCommand,OGRGeometry * poSpatialFilter,const char * pszDialect)285 OGRLayer * OGRPGeoDataSource::ExecuteSQL( const char *pszSQLCommand,
286 OGRGeometry *poSpatialFilter,
287 const char *pszDialect )
288
289 {
290 /* -------------------------------------------------------------------- */
291 /* Use generic implementation for recognized dialects */
292 /* -------------------------------------------------------------------- */
293 if( IsGenericSQLDialect(pszDialect) )
294 return OGRDataSource::ExecuteSQL( pszSQLCommand,
295 poSpatialFilter,
296 pszDialect );
297
298 /* -------------------------------------------------------------------- */
299 /* Execute statement. */
300 /* -------------------------------------------------------------------- */
301 CPLODBCStatement *poStmt = new CPLODBCStatement( &oSession );
302
303 poStmt->Append( pszSQLCommand );
304 if( !poStmt->ExecuteSQL() )
305 {
306 CPLError( CE_Failure, CPLE_AppDefined,
307 "%s", oSession.GetLastError() );
308 delete poStmt;
309 return nullptr;
310 }
311
312 /* -------------------------------------------------------------------- */
313 /* Are there result columns for this statement? */
314 /* -------------------------------------------------------------------- */
315 if( poStmt->GetColCount() == 0 )
316 {
317 delete poStmt;
318 CPLErrorReset();
319 return nullptr;
320 }
321
322 /* -------------------------------------------------------------------- */
323 /* Create a results layer. It will take ownership of the */
324 /* statement. */
325 /* -------------------------------------------------------------------- */
326 OGRPGeoSelectLayer* poLayer = new OGRPGeoSelectLayer( this, poStmt );
327
328 if( poSpatialFilter != nullptr )
329 poLayer->SetSpatialFilter( poSpatialFilter );
330
331 return poLayer;
332 }
333
334 /************************************************************************/
335 /* ReleaseResultSet() */
336 /************************************************************************/
337
ReleaseResultSet(OGRLayer * poLayer)338 void OGRPGeoDataSource::ReleaseResultSet( OGRLayer * poLayer )
339
340 {
341 delete poLayer;
342 }
343