/src/gdal/ogr/ogrsf_frmts/carto/ogrcartodatasource.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: Carto Translator |
4 | | * Purpose: Implements OGRCARTODataSource class |
5 | | * Author: Even Rouault, even dot rouault at spatialys.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2013, Even Rouault <even dot rouault at spatialys.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "ogr_carto.h" |
14 | | #include "ogr_pgdump.h" |
15 | | #include "ogrlibjsonutils.h" |
16 | | |
17 | | /************************************************************************/ |
18 | | /* OGRCARTODataSource() */ |
19 | | /************************************************************************/ |
20 | | |
21 | | OGRCARTODataSource::OGRCARTODataSource() |
22 | 322 | : pszAccount(nullptr), papoLayers(nullptr), nLayers(0), bReadWrite(false), |
23 | 322 | bBatchInsert(true), bCopyMode(true), bUseHTTPS(false), |
24 | 322 | bMustCleanPersistent(false), bHasOGRMetadataFunction(-1), |
25 | 322 | nPostGISMajor(2), nPostGISMinor(0) |
26 | 322 | { |
27 | 322 | } |
28 | | |
29 | | /************************************************************************/ |
30 | | /* ~OGRCARTODataSource() */ |
31 | | /************************************************************************/ |
32 | | |
33 | | OGRCARTODataSource::~OGRCARTODataSource() |
34 | | |
35 | 322 | { |
36 | 322 | for (int i = 0; i < nLayers; i++) |
37 | 0 | delete papoLayers[i]; |
38 | 322 | CPLFree(papoLayers); |
39 | | |
40 | 322 | if (bMustCleanPersistent) |
41 | 322 | { |
42 | 322 | char **papszOptions = nullptr; |
43 | 322 | papszOptions = CSLSetNameValue(papszOptions, "CLOSE_PERSISTENT", |
44 | 322 | CPLSPrintf("CARTO:%p", this)); |
45 | 322 | CPLHTTPDestroyResult(CPLHTTPFetch(GetAPIURL(), papszOptions)); |
46 | 322 | CSLDestroy(papszOptions); |
47 | 322 | } |
48 | | |
49 | 322 | CPLFree(pszAccount); |
50 | 322 | } |
51 | | |
52 | | /************************************************************************/ |
53 | | /* TestCapability() */ |
54 | | /************************************************************************/ |
55 | | |
56 | | int OGRCARTODataSource::TestCapability(const char *pszCap) const |
57 | | |
58 | 0 | { |
59 | 0 | if (bReadWrite && EQUAL(pszCap, ODsCCreateLayer)) |
60 | 0 | return TRUE; |
61 | 0 | else if (bReadWrite && EQUAL(pszCap, ODsCDeleteLayer)) |
62 | 0 | return TRUE; |
63 | 0 | else if (bReadWrite && EQUAL(pszCap, ODsCCreateGeomFieldAfterCreateLayer)) |
64 | 0 | return TRUE; |
65 | 0 | else if (EQUAL(pszCap, ODsCRandomLayerWrite)) |
66 | 0 | return bReadWrite; |
67 | 0 | else |
68 | 0 | return FALSE; |
69 | 0 | } |
70 | | |
71 | | /************************************************************************/ |
72 | | /* GetLayer() */ |
73 | | /************************************************************************/ |
74 | | |
75 | | const OGRLayer *OGRCARTODataSource::GetLayer(int iLayer) const |
76 | | |
77 | 0 | { |
78 | 0 | if (iLayer < 0 || iLayer >= nLayers) |
79 | 0 | return nullptr; |
80 | 0 | else |
81 | 0 | return papoLayers[iLayer]; |
82 | 0 | } |
83 | | |
84 | | /************************************************************************/ |
85 | | /* OGRCARTOGetOptionValue() */ |
86 | | /************************************************************************/ |
87 | | |
88 | | static CPLString OGRCARTOGetOptionValue(const char *pszFilename, |
89 | | const char *pszOptionName) |
90 | 322 | { |
91 | 322 | CPLString osOptionName(pszOptionName); |
92 | 322 | osOptionName += "="; |
93 | 322 | const char *pszOptionValue = strstr(pszFilename, osOptionName); |
94 | 322 | if (!pszOptionValue) |
95 | 153 | return ""; |
96 | | |
97 | 169 | CPLString osOptionValue(pszOptionValue + osOptionName.size()); |
98 | 169 | const char *pszSpace = strchr(osOptionValue.c_str(), ' '); |
99 | 169 | if (pszSpace) |
100 | 69 | osOptionValue.resize(pszSpace - osOptionValue.c_str()); |
101 | 169 | return osOptionValue; |
102 | 322 | } |
103 | | |
104 | | /************************************************************************/ |
105 | | /* Open() */ |
106 | | /************************************************************************/ |
107 | | |
108 | | int OGRCARTODataSource::Open(const char *pszFilename, char **papszOpenOptionsIn, |
109 | | int bUpdateIn) |
110 | | |
111 | 322 | { |
112 | 322 | bReadWrite = CPL_TO_BOOL(bUpdateIn); |
113 | 322 | bBatchInsert = CPLTestBool( |
114 | 322 | CSLFetchNameValueDef(papszOpenOptionsIn, "BATCH_INSERT", "YES")); |
115 | 322 | bCopyMode = CPLTestBool( |
116 | 322 | CSLFetchNameValueDef(papszOpenOptionsIn, "COPY_MODE", "YES")); |
117 | 322 | if (bCopyMode) |
118 | 322 | bBatchInsert = TRUE; |
119 | | |
120 | 322 | if (CSLFetchNameValue(papszOpenOptionsIn, "ACCOUNT")) |
121 | 0 | pszAccount = |
122 | 0 | CPLStrdup(CSLFetchNameValue(papszOpenOptionsIn, "ACCOUNT")); |
123 | 322 | else |
124 | 322 | { |
125 | 322 | if (STARTS_WITH_CI(pszFilename, "CARTODB:")) |
126 | 183 | pszAccount = CPLStrdup(pszFilename + strlen("CARTODB:")); |
127 | 139 | else |
128 | 139 | pszAccount = CPLStrdup(pszFilename + strlen("CARTO:")); |
129 | 322 | char *pchSpace = strchr(pszAccount, ' '); |
130 | 322 | if (pchSpace) |
131 | 113 | *pchSpace = '\0'; |
132 | 322 | if (pszAccount[0] == 0) |
133 | 0 | { |
134 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Missing account name"); |
135 | 0 | return FALSE; |
136 | 0 | } |
137 | 322 | } |
138 | | |
139 | 322 | osAPIKey = CSLFetchNameValueDef( |
140 | 322 | papszOpenOptionsIn, "API_KEY", |
141 | 322 | CPLGetConfigOption("CARTO_API_KEY", |
142 | 322 | CPLGetConfigOption("CARTODB_API_KEY", ""))); |
143 | | |
144 | 322 | CPLString osTables = OGRCARTOGetOptionValue(pszFilename, "tables"); |
145 | | |
146 | | /*if( osTables.empty() && osAPIKey.empty() ) |
147 | | { |
148 | | CPLError(CE_Failure, CPLE_AppDefined, |
149 | | "When not specifying tables option, CARTO_API_KEY must be |
150 | | defined"); return FALSE; |
151 | | }*/ |
152 | | |
153 | 322 | bUseHTTPS = CPLTestBool(CPLGetConfigOption( |
154 | 322 | "CARTO_HTTPS", CPLGetConfigOption("CARTODB_HTTPS", "YES"))); |
155 | | |
156 | 322 | OGRLayer *poSchemaLayer = ExecuteSQLInternal("SELECT current_schema()"); |
157 | 322 | if (poSchemaLayer) |
158 | 0 | { |
159 | 0 | OGRFeature *poFeat = poSchemaLayer->GetNextFeature(); |
160 | 0 | if (poFeat) |
161 | 0 | { |
162 | 0 | if (poFeat->GetFieldCount() == 1) |
163 | 0 | { |
164 | 0 | osCurrentSchema = poFeat->GetFieldAsString(0); |
165 | 0 | } |
166 | 0 | delete poFeat; |
167 | 0 | } |
168 | 0 | ReleaseResultSet(poSchemaLayer); |
169 | 0 | } |
170 | 322 | if (osCurrentSchema.empty()) |
171 | 322 | return FALSE; |
172 | | |
173 | | /* -------------------------------------------------------------------- */ |
174 | | /* Find out PostGIS version */ |
175 | | /* -------------------------------------------------------------------- */ |
176 | 0 | if (bReadWrite) |
177 | 0 | { |
178 | 0 | OGRLayer *poPostGISVersionLayer = |
179 | 0 | ExecuteSQLInternal("SELECT postgis_version()"); |
180 | 0 | if (poPostGISVersionLayer) |
181 | 0 | { |
182 | 0 | OGRFeature *poFeat = poPostGISVersionLayer->GetNextFeature(); |
183 | 0 | if (poFeat) |
184 | 0 | { |
185 | 0 | if (poFeat->GetFieldCount() == 1) |
186 | 0 | { |
187 | 0 | const char *pszVersion = poFeat->GetFieldAsString(0); |
188 | 0 | nPostGISMajor = atoi(pszVersion); |
189 | 0 | const char *pszDot = strchr(pszVersion, '.'); |
190 | 0 | nPostGISMinor = 0; |
191 | 0 | if (pszDot) |
192 | 0 | nPostGISMinor = atoi(pszDot + 1); |
193 | 0 | } |
194 | 0 | delete poFeat; |
195 | 0 | } |
196 | 0 | ReleaseResultSet(poPostGISVersionLayer); |
197 | 0 | } |
198 | 0 | } |
199 | |
|
200 | 0 | if (!osAPIKey.empty() && bUpdateIn) |
201 | 0 | { |
202 | 0 | ExecuteSQLInternal( |
203 | 0 | "DROP FUNCTION IF EXISTS ogr_table_metadata(TEXT,TEXT); " |
204 | 0 | "CREATE OR REPLACE FUNCTION ogr_table_metadata(schema_name TEXT, " |
205 | 0 | "table_name TEXT) RETURNS TABLE " |
206 | 0 | "(attname TEXT, typname TEXT, attlen INT, format_type TEXT, " |
207 | 0 | "attnum INT, attnotnull BOOLEAN, indisprimary BOOLEAN, " |
208 | 0 | "defaultexpr TEXT, dim INT, srid INT, geomtyp TEXT, srtext TEXT) " |
209 | 0 | "AS $$ " |
210 | 0 | "SELECT a.attname::text, t.typname::text, a.attlen::int, " |
211 | 0 | "format_type(a.atttypid,a.atttypmod)::text, " |
212 | 0 | "a.attnum::int, " |
213 | 0 | "a.attnotnull::boolean, " |
214 | 0 | "i.indisprimary::boolean, " |
215 | 0 | "pg_get_expr(def.adbin, c.oid)::text AS defaultexpr, " |
216 | 0 | "(CASE WHEN t.typname = 'geometry' THEN " |
217 | 0 | "postgis_typmod_dims(a.atttypmod) ELSE NULL END)::int dim, " |
218 | 0 | "(CASE WHEN t.typname = 'geometry' THEN " |
219 | 0 | "postgis_typmod_srid(a.atttypmod) ELSE NULL END)::int srid, " |
220 | 0 | "(CASE WHEN t.typname = 'geometry' THEN " |
221 | 0 | "postgis_typmod_type(a.atttypmod) ELSE NULL END)::text geomtyp, " |
222 | 0 | "srtext " |
223 | 0 | "FROM pg_class c " |
224 | 0 | "JOIN pg_attribute a ON a.attnum > 0 AND " |
225 | 0 | "a.attrelid = c.oid AND c.relname = $2 " |
226 | 0 | "AND c.relname IN (SELECT CDB_UserTables())" |
227 | 0 | "JOIN pg_type t ON a.atttypid = t.oid " |
228 | 0 | "JOIN pg_namespace n ON c.relnamespace=n.oid AND n.nspname = $1 " |
229 | 0 | "LEFT JOIN pg_index i ON c.oid = i.indrelid AND " |
230 | 0 | "i.indisprimary = 't' AND a.attnum = ANY(i.indkey) " |
231 | 0 | "LEFT JOIN pg_attrdef def ON def.adrelid = c.oid AND " |
232 | 0 | "def.adnum = a.attnum " |
233 | 0 | "LEFT JOIN spatial_ref_sys srs ON srs.srid = " |
234 | 0 | "postgis_typmod_srid(a.atttypmod) " |
235 | 0 | "ORDER BY a.attnum " |
236 | 0 | "$$ LANGUAGE SQL"); |
237 | 0 | } |
238 | |
|
239 | 0 | if (!osTables.empty()) |
240 | 0 | { |
241 | 0 | char **papszTables = CSLTokenizeString2(osTables, ",", 0); |
242 | 0 | for (int i = 0; papszTables && papszTables[i]; i++) |
243 | 0 | { |
244 | 0 | papoLayers = (OGRCARTOTableLayer **)CPLRealloc( |
245 | 0 | papoLayers, (nLayers + 1) * sizeof(OGRCARTOTableLayer *)); |
246 | 0 | papoLayers[nLayers++] = |
247 | 0 | new OGRCARTOTableLayer(this, papszTables[i]); |
248 | 0 | } |
249 | 0 | CSLDestroy(papszTables); |
250 | 0 | return TRUE; |
251 | 0 | } |
252 | | |
253 | 0 | OGRLayer *poTableListLayer = ExecuteSQLInternal("SELECT CDB_UserTables()"); |
254 | 0 | if (poTableListLayer) |
255 | 0 | { |
256 | 0 | OGRFeature *poFeat; |
257 | 0 | while ((poFeat = poTableListLayer->GetNextFeature()) != nullptr) |
258 | 0 | { |
259 | 0 | if (poFeat->GetFieldCount() == 1) |
260 | 0 | { |
261 | 0 | papoLayers = (OGRCARTOTableLayer **)CPLRealloc( |
262 | 0 | papoLayers, (nLayers + 1) * sizeof(OGRCARTOTableLayer *)); |
263 | 0 | papoLayers[nLayers++] = |
264 | 0 | new OGRCARTOTableLayer(this, poFeat->GetFieldAsString(0)); |
265 | 0 | } |
266 | 0 | delete poFeat; |
267 | 0 | } |
268 | 0 | ReleaseResultSet(poTableListLayer); |
269 | 0 | } |
270 | 0 | else if (osCurrentSchema == "public") |
271 | 0 | return FALSE; |
272 | | |
273 | | /* There's currently a bug with CDB_UserTables() on multi-user accounts */ |
274 | 0 | if (nLayers == 0 && osCurrentSchema != "public") |
275 | 0 | { |
276 | 0 | CPLString osSQL; |
277 | 0 | osSQL.Printf("SELECT c.relname FROM pg_class c, pg_namespace n " |
278 | 0 | "WHERE c.relkind in ('r', 'v') AND c.relname !~ '^pg_' " |
279 | 0 | "AND c.relnamespace=n.oid AND n.nspname = '%s'", |
280 | 0 | OGRCARTOEscapeLiteral(osCurrentSchema).c_str()); |
281 | 0 | poTableListLayer = ExecuteSQLInternal(osSQL); |
282 | 0 | if (poTableListLayer) |
283 | 0 | { |
284 | 0 | OGRFeature *poFeat; |
285 | 0 | while ((poFeat = poTableListLayer->GetNextFeature()) != nullptr) |
286 | 0 | { |
287 | 0 | if (poFeat->GetFieldCount() == 1) |
288 | 0 | { |
289 | 0 | papoLayers = (OGRCARTOTableLayer **)CPLRealloc( |
290 | 0 | papoLayers, |
291 | 0 | (nLayers + 1) * sizeof(OGRCARTOTableLayer *)); |
292 | 0 | papoLayers[nLayers++] = new OGRCARTOTableLayer( |
293 | 0 | this, poFeat->GetFieldAsString(0)); |
294 | 0 | } |
295 | 0 | delete poFeat; |
296 | 0 | } |
297 | 0 | ReleaseResultSet(poTableListLayer); |
298 | 0 | } |
299 | 0 | else |
300 | 0 | return FALSE; |
301 | 0 | } |
302 | | |
303 | 0 | return TRUE; |
304 | 0 | } |
305 | | |
306 | | /************************************************************************/ |
307 | | /* GetAPIURL() */ |
308 | | /************************************************************************/ |
309 | | |
310 | | const char *OGRCARTODataSource::GetAPIURL() const |
311 | 966 | { |
312 | 966 | const char *pszAPIURL = CPLGetConfigOption( |
313 | 966 | "CARTO_API_URL", CPLGetConfigOption("CARTODB_API_URL", nullptr)); |
314 | 966 | if (pszAPIURL) |
315 | 0 | return pszAPIURL; |
316 | 966 | else if (bUseHTTPS) |
317 | 966 | return CPLSPrintf("https://%s.carto.com/api/v2/sql", pszAccount); |
318 | 0 | else |
319 | 0 | return CPLSPrintf("http://%s.carto.com/api/v2/sql", pszAccount); |
320 | 966 | } |
321 | | |
322 | | /************************************************************************/ |
323 | | /* FetchSRSId() */ |
324 | | /************************************************************************/ |
325 | | |
326 | | int OGRCARTODataSource::FetchSRSId(const OGRSpatialReference *poSRS) |
327 | | |
328 | 0 | { |
329 | 0 | const char *pszAuthorityName; |
330 | |
|
331 | 0 | if (poSRS == nullptr) |
332 | 0 | return 0; |
333 | | |
334 | 0 | OGRSpatialReference oSRS(*poSRS); |
335 | | // cppcheck-suppress uselessAssignmentPtrArg |
336 | 0 | poSRS = nullptr; |
337 | |
|
338 | 0 | pszAuthorityName = oSRS.GetAuthorityName(nullptr); |
339 | |
|
340 | 0 | if (pszAuthorityName == nullptr || strlen(pszAuthorityName) == 0) |
341 | 0 | { |
342 | | /* -------------------------------------------------------------------- |
343 | | */ |
344 | | /* Try to identify an EPSG code */ |
345 | | /* -------------------------------------------------------------------- |
346 | | */ |
347 | 0 | oSRS.AutoIdentifyEPSG(); |
348 | |
|
349 | 0 | pszAuthorityName = oSRS.GetAuthorityName(nullptr); |
350 | 0 | if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG")) |
351 | 0 | { |
352 | 0 | const char *pszAuthorityCode = oSRS.GetAuthorityCode(nullptr); |
353 | 0 | if (pszAuthorityCode != nullptr && strlen(pszAuthorityCode) > 0) |
354 | 0 | { |
355 | | /* Import 'clean' SRS */ |
356 | 0 | oSRS.importFromEPSG(atoi(pszAuthorityCode)); |
357 | |
|
358 | 0 | pszAuthorityName = oSRS.GetAuthorityName(nullptr); |
359 | 0 | } |
360 | 0 | } |
361 | 0 | } |
362 | | /* -------------------------------------------------------------------- */ |
363 | | /* Check whether the EPSG authority code is already mapped to a */ |
364 | | /* SRS ID. */ |
365 | | /* -------------------------------------------------------------------- */ |
366 | 0 | if (pszAuthorityName != nullptr && EQUAL(pszAuthorityName, "EPSG")) |
367 | 0 | { |
368 | | /* For the root authority name 'EPSG', the authority code |
369 | | * should always be integral |
370 | | */ |
371 | 0 | const int nAuthorityCode = atoi(oSRS.GetAuthorityCode(nullptr)); |
372 | |
|
373 | 0 | return nAuthorityCode; |
374 | 0 | } |
375 | | |
376 | 0 | return 0; |
377 | 0 | } |
378 | | |
379 | | /************************************************************************/ |
380 | | /* ICreateLayer() */ |
381 | | /************************************************************************/ |
382 | | |
383 | | OGRLayer * |
384 | | OGRCARTODataSource::ICreateLayer(const char *pszNameIn, |
385 | | const OGRGeomFieldDefn *poGeomFieldDefn, |
386 | | CSLConstList papszOptions) |
387 | 0 | { |
388 | 0 | if (!bReadWrite) |
389 | 0 | { |
390 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
391 | 0 | "Operation not available in read-only mode"); |
392 | 0 | return nullptr; |
393 | 0 | } |
394 | | |
395 | 0 | const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone; |
396 | 0 | const auto poSpatialRef = |
397 | 0 | poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr; |
398 | | |
399 | | /* -------------------------------------------------------------------- */ |
400 | | /* Do we already have this layer? If so, set it up for overwrite */ |
401 | | /* away? */ |
402 | | /* -------------------------------------------------------------------- */ |
403 | 0 | bool bOverwriteOption = |
404 | 0 | CSLFetchNameValue(papszOptions, "OVERWRITE") != nullptr && |
405 | 0 | !EQUAL(CSLFetchNameValue(papszOptions, "OVERWRITE"), "NO"); |
406 | |
|
407 | 0 | for (int iLayer = 0; iLayer < nLayers; iLayer++) |
408 | 0 | { |
409 | 0 | if (EQUAL(pszNameIn, papoLayers[iLayer]->GetName())) |
410 | 0 | { |
411 | 0 | if (bOverwriteOption) |
412 | 0 | { |
413 | | /* We set DropOnCreation so the remote table isn't dropped */ |
414 | | /* As we are going to overwrite it in a single transaction */ |
415 | 0 | papoLayers[iLayer]->SetDropOnCreation(true); |
416 | 0 | DeleteLayer(iLayer); |
417 | 0 | } |
418 | 0 | else |
419 | 0 | { |
420 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
421 | 0 | "Layer %s already exists, CreateLayer failed.\n" |
422 | 0 | "Use the layer creation option OVERWRITE=YES to " |
423 | 0 | "replace it.", |
424 | 0 | pszNameIn); |
425 | 0 | return nullptr; |
426 | 0 | } |
427 | 0 | } |
428 | 0 | } |
429 | | |
430 | 0 | CPLString osName(pszNameIn); |
431 | 0 | if (CPLFetchBool(papszOptions, "LAUNDER", true)) |
432 | 0 | { |
433 | 0 | char *pszTmp = OGRPGCommonLaunderName(pszNameIn, "CARTO", false); |
434 | 0 | osName = pszTmp; |
435 | 0 | CPLFree(pszTmp); |
436 | 0 | } |
437 | |
|
438 | 0 | OGRCARTOTableLayer *poLayer = new OGRCARTOTableLayer(this, osName); |
439 | 0 | if (bOverwriteOption) |
440 | 0 | poLayer->SetDropOnCreation(true); |
441 | |
|
442 | 0 | const bool bGeomNullable = |
443 | 0 | CPLFetchBool(papszOptions, "GEOMETRY_NULLABLE", true); |
444 | 0 | int nSRID = poSpatialRef ? FetchSRSId(poSpatialRef) : 0; |
445 | 0 | bool bCartoify = |
446 | 0 | CPLFetchBool(papszOptions, "CARTODBFY", |
447 | 0 | CPLFetchBool(papszOptions, "CARTODBIFY", true)); |
448 | 0 | if (bCartoify) |
449 | 0 | { |
450 | 0 | if (nSRID != 4326) |
451 | 0 | { |
452 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
453 | 0 | "Cannot register table in dashboard with " |
454 | 0 | "cdb_cartodbfytable() since its SRS is not EPSG:4326." |
455 | 0 | " Check the documentation for more information"); |
456 | 0 | bCartoify = false; |
457 | 0 | } |
458 | 0 | else if (eGType == wkbNone) |
459 | 0 | { |
460 | 0 | CPLError( |
461 | 0 | CE_Warning, CPLE_AppDefined, |
462 | 0 | "Cannot register table in dashboard with " |
463 | 0 | "cdb_cartodbfytable() since its geometry type isn't defined." |
464 | 0 | " Check the documentation for more information"); |
465 | 0 | bCartoify = false; |
466 | 0 | } |
467 | 0 | } |
468 | |
|
469 | 0 | poLayer->SetLaunderFlag(CPLFetchBool(papszOptions, "LAUNDER", true)); |
470 | |
|
471 | 0 | OGRSpatialReference *poSRSClone = nullptr; |
472 | 0 | if (poSpatialRef) |
473 | 0 | { |
474 | 0 | poSRSClone = poSpatialRef->Clone(); |
475 | 0 | poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); |
476 | 0 | } |
477 | 0 | poLayer->SetDeferredCreation(eGType, poSRSClone, bGeomNullable, bCartoify); |
478 | 0 | if (poSRSClone) |
479 | 0 | poSRSClone->Release(); |
480 | 0 | papoLayers = (OGRCARTOTableLayer **)CPLRealloc( |
481 | 0 | papoLayers, (nLayers + 1) * sizeof(OGRCARTOTableLayer *)); |
482 | 0 | papoLayers[nLayers++] = poLayer; |
483 | |
|
484 | 0 | return poLayer; |
485 | 0 | } |
486 | | |
487 | | /************************************************************************/ |
488 | | /* DeleteLayer() */ |
489 | | /************************************************************************/ |
490 | | |
491 | | OGRErr OGRCARTODataSource::DeleteLayer(int iLayer) |
492 | 0 | { |
493 | 0 | if (!bReadWrite) |
494 | 0 | { |
495 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
496 | 0 | "Operation not available in read-only mode"); |
497 | 0 | return OGRERR_FAILURE; |
498 | 0 | } |
499 | | |
500 | 0 | if (iLayer < 0 || iLayer >= nLayers) |
501 | 0 | { |
502 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
503 | 0 | "Layer %d not in legal range of 0 to %d.", iLayer, |
504 | 0 | nLayers - 1); |
505 | 0 | return OGRERR_FAILURE; |
506 | 0 | } |
507 | | |
508 | | /* -------------------------------------------------------------------- */ |
509 | | /* Blow away our OGR structures related to the layer. This is */ |
510 | | /* pretty dangerous if anything has a reference to this layer! */ |
511 | | /* -------------------------------------------------------------------- */ |
512 | 0 | CPLString osLayerName = papoLayers[iLayer]->GetLayerDefn()->GetName(); |
513 | |
|
514 | 0 | CPLDebug("CARTO", "DeleteLayer(%s)", osLayerName.c_str()); |
515 | |
|
516 | 0 | int bDeferredCreation = papoLayers[iLayer]->GetDeferredCreation(); |
517 | 0 | bool bDropOnCreation = papoLayers[iLayer]->GetDropOnCreation(); |
518 | 0 | papoLayers[iLayer]->CancelDeferredCreation(); |
519 | 0 | delete papoLayers[iLayer]; |
520 | 0 | memmove(papoLayers + iLayer, papoLayers + iLayer + 1, |
521 | 0 | sizeof(void *) * (nLayers - iLayer - 1)); |
522 | 0 | nLayers--; |
523 | |
|
524 | 0 | if (osLayerName.empty()) |
525 | 0 | return OGRERR_NONE; |
526 | | |
527 | 0 | if (!bDeferredCreation && !bDropOnCreation) |
528 | 0 | { |
529 | 0 | CPLString osSQL; |
530 | 0 | osSQL.Printf("DROP TABLE %s", |
531 | 0 | OGRCARTOEscapeIdentifier(osLayerName).c_str()); |
532 | |
|
533 | 0 | json_object *poObj = RunSQL(osSQL); |
534 | 0 | if (poObj == nullptr) |
535 | 0 | return OGRERR_FAILURE; |
536 | 0 | json_object_put(poObj); |
537 | 0 | } |
538 | | |
539 | 0 | return OGRERR_NONE; |
540 | 0 | } |
541 | | |
542 | | /************************************************************************/ |
543 | | /* AddHTTPOptions() */ |
544 | | /************************************************************************/ |
545 | | |
546 | | char **OGRCARTODataSource::AddHTTPOptions() |
547 | 322 | { |
548 | 322 | bMustCleanPersistent = true; |
549 | | |
550 | 322 | return CSLAddString(nullptr, CPLSPrintf("PERSISTENT=CARTO:%p", this)); |
551 | 322 | } |
552 | | |
553 | | /************************************************************************/ |
554 | | /* RunCopyFrom() */ |
555 | | /************************************************************************/ |
556 | | |
557 | | json_object *OGRCARTODataSource::RunCopyFrom(const char *pszSQL, |
558 | | const char *pszCopyFile) |
559 | 0 | { |
560 | | |
561 | | /* -------------------------------------------------------------------- */ |
562 | | /* Set up our copyfrom end point URL */ |
563 | | /* -------------------------------------------------------------------- */ |
564 | 0 | const char *pszAPIURL = GetAPIURL(); |
565 | 0 | CPLString osURL(pszAPIURL); |
566 | 0 | osURL += "/copyfrom?q="; |
567 | |
|
568 | 0 | if (!(strlen(pszSQL) > 0)) |
569 | 0 | { |
570 | 0 | CPLDebug("CARTO", "RunCopyFrom: pszSQL is empty"); |
571 | 0 | return nullptr; |
572 | 0 | } |
573 | | |
574 | 0 | if (!(strlen(pszCopyFile) > 0)) |
575 | 0 | { |
576 | 0 | CPLDebug("CARTO", "RunCopyFrom: pszCopyFile is empty"); |
577 | 0 | return nullptr; |
578 | 0 | } |
579 | | |
580 | | /* -------------------------------------------------------------------- */ |
581 | | /* URL encode the COPY sql and add to URL with API key */ |
582 | | /* -------------------------------------------------------------------- */ |
583 | 0 | CPLDebug("CARTO", "RunCopyFrom: osCopySQL = %s", pszSQL); |
584 | 0 | char *pszEscapedSQL = CPLEscapeString(pszSQL, -1, CPLES_URL); |
585 | 0 | osURL += pszEscapedSQL; |
586 | 0 | CPLFree(pszEscapedSQL); |
587 | |
|
588 | 0 | if (!osAPIKey.empty()) |
589 | 0 | { |
590 | 0 | osURL += "&api_key="; |
591 | 0 | osURL += osAPIKey; |
592 | 0 | } |
593 | | |
594 | | /* -------------------------------------------------------------------- */ |
595 | | /* Set the POST payload */ |
596 | | /* -------------------------------------------------------------------- */ |
597 | 0 | CPLString osSQL("POSTFIELDS="); |
598 | 0 | osSQL += pszCopyFile; |
599 | | |
600 | | /* -------------------------------------------------------------------- */ |
601 | | /* Make the HTTP request */ |
602 | | /* -------------------------------------------------------------------- */ |
603 | 0 | char **papszOptions = CSLAddString( |
604 | 0 | !STARTS_WITH(pszAPIURL, "/vsimem/") ? AddHTTPOptions() : nullptr, |
605 | 0 | osSQL); |
606 | 0 | CPLHTTPResult *psResult = CPLHTTPFetch(osURL, papszOptions); |
607 | 0 | CSLDestroy(papszOptions); |
608 | 0 | if (psResult == nullptr) |
609 | 0 | { |
610 | 0 | CPLDebug("CARTO", "RunCopyFrom: null return from CPLHTTPFetch"); |
611 | 0 | return nullptr; |
612 | 0 | } |
613 | | |
614 | | /* -------------------------------------------------------------------- */ |
615 | | /* Check for some error conditions and report. HTML Messages */ |
616 | | /* are transformed info failure. */ |
617 | | /* -------------------------------------------------------------------- */ |
618 | 0 | if (psResult->pszContentType && |
619 | 0 | STARTS_WITH(psResult->pszContentType, "text/html")) |
620 | 0 | { |
621 | 0 | CPLDebug("CARTO", "RunCopyFrom HTML Response:%s", psResult->pabyData); |
622 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
623 | 0 | "HTML error page returned by server"); |
624 | 0 | CPLHTTPDestroyResult(psResult); |
625 | 0 | return nullptr; |
626 | 0 | } |
627 | 0 | if (psResult->pszErrBuf != nullptr) |
628 | 0 | { |
629 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "RunCopyFrom Error Message:%s", |
630 | 0 | psResult->pszErrBuf); |
631 | 0 | } |
632 | 0 | else if (psResult->nStatus != 0) |
633 | 0 | { |
634 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "RunCopyFrom Error Status:%d", |
635 | 0 | psResult->nStatus); |
636 | 0 | } |
637 | |
|
638 | 0 | if (psResult->pabyData == nullptr) |
639 | 0 | { |
640 | 0 | CPLHTTPDestroyResult(psResult); |
641 | 0 | return nullptr; |
642 | 0 | } |
643 | | |
644 | 0 | json_object *poObj = nullptr; |
645 | 0 | const char *pszText = reinterpret_cast<const char *>(psResult->pabyData); |
646 | 0 | if (!OGRJSonParse(pszText, &poObj, true)) |
647 | 0 | { |
648 | 0 | CPLDebug("CARTO", "RunCopyFrom unable to parse JSON return: %s", |
649 | 0 | pszText); |
650 | 0 | CPLHTTPDestroyResult(psResult); |
651 | 0 | return nullptr; |
652 | 0 | } |
653 | | |
654 | 0 | CPLHTTPDestroyResult(psResult); |
655 | |
|
656 | 0 | if (poObj != nullptr) |
657 | 0 | { |
658 | 0 | if (json_object_get_type(poObj) == json_type_object) |
659 | 0 | { |
660 | 0 | json_object *poError = CPL_json_object_object_get(poObj, "error"); |
661 | 0 | if (poError != nullptr && |
662 | 0 | json_object_get_type(poError) == json_type_array && |
663 | 0 | json_object_array_length(poError) > 0) |
664 | 0 | { |
665 | 0 | poError = json_object_array_get_idx(poError, 0); |
666 | 0 | if (poError != nullptr && |
667 | 0 | json_object_get_type(poError) == json_type_string) |
668 | 0 | { |
669 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
670 | 0 | "Error returned by server : %s", |
671 | 0 | json_object_get_string(poError)); |
672 | 0 | json_object_put(poObj); |
673 | 0 | return nullptr; |
674 | 0 | } |
675 | 0 | } |
676 | 0 | } |
677 | 0 | else |
678 | 0 | { |
679 | 0 | json_object_put(poObj); |
680 | 0 | return nullptr; |
681 | 0 | } |
682 | 0 | } |
683 | | |
684 | 0 | return poObj; |
685 | 0 | } |
686 | | |
687 | | /************************************************************************/ |
688 | | /* RunSQL() */ |
689 | | /************************************************************************/ |
690 | | |
691 | | json_object *OGRCARTODataSource::RunSQL(const char *pszUnescapedSQL) |
692 | 322 | { |
693 | 322 | CPLString osSQL("POSTFIELDS=q="); |
694 | | /* Do post escaping */ |
695 | 13.8k | for (int i = 0; pszUnescapedSQL[i] != 0; i++) |
696 | 13.5k | { |
697 | 13.5k | const int ch = ((unsigned char *)pszUnescapedSQL)[i]; |
698 | 13.5k | if (ch != '&' && ch >= 32 && ch < 128) |
699 | 13.5k | osSQL += (char)ch; |
700 | 0 | else |
701 | 0 | osSQL += CPLSPrintf("%%%02X", ch); |
702 | 13.5k | } |
703 | | |
704 | | /* -------------------------------------------------------------------- */ |
705 | | /* Provide the API Key */ |
706 | | /* -------------------------------------------------------------------- */ |
707 | 322 | if (!osAPIKey.empty()) |
708 | 0 | { |
709 | 0 | osSQL += "&api_key="; |
710 | 0 | osSQL += osAPIKey; |
711 | 0 | } |
712 | | |
713 | | /* -------------------------------------------------------------------- */ |
714 | | /* Collection the header options and execute request. */ |
715 | | /* -------------------------------------------------------------------- */ |
716 | 322 | const char *pszAPIURL = GetAPIURL(); |
717 | 322 | char **papszOptions = CSLAddString( |
718 | 322 | !STARTS_WITH(pszAPIURL, "/vsimem/") ? AddHTTPOptions() : nullptr, |
719 | 322 | osSQL); |
720 | 322 | CPLHTTPResult *psResult = CPLHTTPFetch(GetAPIURL(), papszOptions); |
721 | 322 | CSLDestroy(papszOptions); |
722 | 322 | if (psResult == nullptr) |
723 | 0 | return nullptr; |
724 | | |
725 | | /* -------------------------------------------------------------------- */ |
726 | | /* Check for some error conditions and report. HTML Messages */ |
727 | | /* are transformed info failure. */ |
728 | | /* -------------------------------------------------------------------- */ |
729 | 322 | if (psResult->pszContentType && |
730 | 0 | STARTS_WITH(psResult->pszContentType, "text/html")) |
731 | 0 | { |
732 | 0 | CPLDebug("CARTO", "RunSQL HTML Response:%s", psResult->pabyData); |
733 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
734 | 0 | "HTML error page returned by server"); |
735 | 0 | CPLHTTPDestroyResult(psResult); |
736 | 0 | return nullptr; |
737 | 0 | } |
738 | 322 | if (psResult->pszErrBuf != nullptr) |
739 | 322 | { |
740 | 322 | CPLError(CE_Failure, CPLE_AppDefined, "RunSQL Error Message:%s", |
741 | 322 | psResult->pszErrBuf); |
742 | 322 | } |
743 | 0 | else if (psResult->nStatus != 0) |
744 | 0 | { |
745 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "RunSQL Error Status:%d", |
746 | 0 | psResult->nStatus); |
747 | 0 | } |
748 | | |
749 | 322 | if (psResult->pabyData == nullptr) |
750 | 322 | { |
751 | 322 | CPLHTTPDestroyResult(psResult); |
752 | 322 | return nullptr; |
753 | 322 | } |
754 | | |
755 | 0 | if (strlen((const char *)psResult->pabyData) < 1000) |
756 | 0 | CPLDebug("CARTO", "RunSQL Response:%s", psResult->pabyData); |
757 | |
|
758 | 0 | json_object *poObj = nullptr; |
759 | 0 | const char *pszText = reinterpret_cast<const char *>(psResult->pabyData); |
760 | 0 | if (!OGRJSonParse(pszText, &poObj, true)) |
761 | 0 | { |
762 | 0 | CPLHTTPDestroyResult(psResult); |
763 | 0 | return nullptr; |
764 | 0 | } |
765 | | |
766 | 0 | CPLHTTPDestroyResult(psResult); |
767 | |
|
768 | 0 | if (poObj != nullptr) |
769 | 0 | { |
770 | 0 | if (json_object_get_type(poObj) == json_type_object) |
771 | 0 | { |
772 | 0 | json_object *poError = CPL_json_object_object_get(poObj, "error"); |
773 | 0 | if (poError != nullptr && |
774 | 0 | json_object_get_type(poError) == json_type_array && |
775 | 0 | json_object_array_length(poError) > 0) |
776 | 0 | { |
777 | 0 | poError = json_object_array_get_idx(poError, 0); |
778 | 0 | if (poError != nullptr && |
779 | 0 | json_object_get_type(poError) == json_type_string) |
780 | 0 | { |
781 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
782 | 0 | "Error returned by server : %s", |
783 | 0 | json_object_get_string(poError)); |
784 | 0 | json_object_put(poObj); |
785 | 0 | return nullptr; |
786 | 0 | } |
787 | 0 | } |
788 | 0 | } |
789 | 0 | else |
790 | 0 | { |
791 | 0 | json_object_put(poObj); |
792 | 0 | return nullptr; |
793 | 0 | } |
794 | 0 | } |
795 | | |
796 | 0 | return poObj; |
797 | 0 | } |
798 | | |
799 | | /************************************************************************/ |
800 | | /* OGRCARTOGetSingleRow() */ |
801 | | /************************************************************************/ |
802 | | |
803 | | json_object *OGRCARTOGetSingleRow(json_object *poObj) |
804 | 0 | { |
805 | 0 | if (poObj == nullptr) |
806 | 0 | { |
807 | 0 | return nullptr; |
808 | 0 | } |
809 | | |
810 | 0 | json_object *poRows = CPL_json_object_object_get(poObj, "rows"); |
811 | 0 | if (poRows == nullptr || json_object_get_type(poRows) != json_type_array || |
812 | 0 | json_object_array_length(poRows) != 1) |
813 | 0 | { |
814 | 0 | return nullptr; |
815 | 0 | } |
816 | | |
817 | 0 | json_object *poRowObj = json_object_array_get_idx(poRows, 0); |
818 | 0 | if (poRowObj == nullptr || |
819 | 0 | json_object_get_type(poRowObj) != json_type_object) |
820 | 0 | { |
821 | 0 | return nullptr; |
822 | 0 | } |
823 | | |
824 | 0 | return poRowObj; |
825 | 0 | } |
826 | | |
827 | | /************************************************************************/ |
828 | | /* ExecuteSQL() */ |
829 | | /************************************************************************/ |
830 | | |
831 | | OGRLayer *OGRCARTODataSource::ExecuteSQL(const char *pszSQLCommand, |
832 | | OGRGeometry *poSpatialFilter, |
833 | | const char *pszDialect) |
834 | | |
835 | 0 | { |
836 | 0 | return ExecuteSQLInternal(pszSQLCommand, poSpatialFilter, pszDialect, true); |
837 | 0 | } |
838 | | |
839 | | OGRLayer *OGRCARTODataSource::ExecuteSQLInternal(const char *pszSQLCommand, |
840 | | OGRGeometry *poSpatialFilter, |
841 | | const char *pszDialect, |
842 | | bool bRunDeferredActions) |
843 | | |
844 | 322 | { |
845 | 322 | if (bRunDeferredActions) |
846 | 0 | { |
847 | 0 | for (int iLayer = 0; iLayer < nLayers; iLayer++) |
848 | 0 | { |
849 | 0 | papoLayers[iLayer]->RunDeferredCreationIfNecessary(); |
850 | 0 | CPL_IGNORE_RET_VAL(papoLayers[iLayer]->FlushDeferredBuffer()); |
851 | 0 | papoLayers[iLayer]->RunDeferredCartofy(); |
852 | 0 | } |
853 | 0 | } |
854 | | |
855 | | /* Skip leading spaces */ |
856 | 322 | while (*pszSQLCommand == ' ') |
857 | 0 | pszSQLCommand++; |
858 | | |
859 | | /* -------------------------------------------------------------------- */ |
860 | | /* Use generic implementation for recognized dialects */ |
861 | | /* -------------------------------------------------------------------- */ |
862 | 322 | if (IsGenericSQLDialect(pszDialect)) |
863 | 0 | return GDALDataset::ExecuteSQL(pszSQLCommand, poSpatialFilter, |
864 | 0 | pszDialect); |
865 | | |
866 | | /* -------------------------------------------------------------------- */ |
867 | | /* Special case DELLAYER: command. */ |
868 | | /* -------------------------------------------------------------------- */ |
869 | 322 | if (STARTS_WITH_CI(pszSQLCommand, "DELLAYER:")) |
870 | 0 | { |
871 | 0 | const char *pszLayerName = pszSQLCommand + 9; |
872 | |
|
873 | 0 | while (*pszLayerName == ' ') |
874 | 0 | pszLayerName++; |
875 | |
|
876 | 0 | for (int iLayer = 0; iLayer < nLayers; iLayer++) |
877 | 0 | { |
878 | 0 | if (EQUAL(papoLayers[iLayer]->GetName(), pszLayerName)) |
879 | 0 | { |
880 | 0 | DeleteLayer(iLayer); |
881 | 0 | break; |
882 | 0 | } |
883 | 0 | } |
884 | 0 | return nullptr; |
885 | 0 | } |
886 | | |
887 | 322 | if (!STARTS_WITH_CI(pszSQLCommand, "SELECT") && |
888 | 322 | !STARTS_WITH_CI(pszSQLCommand, "EXPLAIN") && |
889 | 0 | !STARTS_WITH_CI(pszSQLCommand, "WITH")) |
890 | 0 | { |
891 | 0 | RunSQL(pszSQLCommand); |
892 | 0 | return nullptr; |
893 | 0 | } |
894 | | |
895 | 322 | OGRCARTOResultLayer *poLayer = new OGRCARTOResultLayer(this, pszSQLCommand); |
896 | | |
897 | 322 | if (poSpatialFilter != nullptr) |
898 | 0 | poLayer->SetSpatialFilter(poSpatialFilter); |
899 | | |
900 | 322 | if (!poLayer->IsOK()) |
901 | 322 | { |
902 | 322 | delete poLayer; |
903 | 322 | return nullptr; |
904 | 322 | } |
905 | | |
906 | 0 | return poLayer; |
907 | 322 | } |
908 | | |
909 | | /************************************************************************/ |
910 | | /* ReleaseResultSet() */ |
911 | | /************************************************************************/ |
912 | | |
913 | | void OGRCARTODataSource::ReleaseResultSet(OGRLayer *poLayer) |
914 | | |
915 | 0 | { |
916 | 0 | delete poLayer; |
917 | 0 | } |