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