1 /*-------------------------------------------------------------------------
2 *
3 * ogr_fdw.c
4 * foreign-data wrapper for GIS data access.
5 *
6 * Copyright (c) 2014-2015, Paul Ramsey <pramsey@cleverelephant.ca>
7 *
8 *-------------------------------------------------------------------------
9 */
10
11 /*
12 * System
13 */
14 #include <sys/stat.h>
15 #include <unistd.h>
16
17 #include "postgres.h"
18
19 /*
20 * Require PostgreSQL >= 9.3
21 */
22 #if PG_VERSION_NUM < 90300
23 #error "OGR FDW requires PostgreSQL version 9.3 or higher"
24
25 #else
26
27 /*
28 * Definition of stringToQualifiedNameList
29 */
30 #if PG_VERSION_NUM >= 100000
31 #include "utils/regproc.h"
32 #endif
33
34 /*
35 * Local structures
36 */
37 #include "ogr_fdw.h"
38
39 PG_MODULE_MAGIC;
40
41 /*
42 * Describes the valid options for objects that use this wrapper.
43 */
44 struct OgrFdwOption
45 {
46 const char* optname;
47 Oid optcontext; /* Oid of catalog in which option may appear */
48 bool optrequired; /* Flag mandatory options */
49 bool optfound; /* Flag whether options was specified by user */
50 };
51
52 #define OPT_DRIVER "format"
53 #define OPT_SOURCE "datasource"
54 #define OPT_LAYER "layer"
55 #define OPT_COLUMN "column_name"
56 #define OPT_CONFIG_OPTIONS "config_options"
57 #define OPT_OPEN_OPTIONS "open_options"
58 #define OPT_UPDATEABLE "updateable"
59 #define OPT_CHAR_ENCODING "character_encoding"
60
61 #define OGR_FDW_FRMT_INT64 "%lld"
62 #define OGR_FDW_CAST_INT64(x) (long long)(x)
63
64 /*
65 * Valid options for ogr_fdw.
66 * ForeignDataWrapperRelationId (no options)
67 * ForeignServerRelationId (CREATE SERVER options)
68 * UserMappingRelationId (CREATE USER MAPPING options)
69 * ForeignTableRelationId (CREATE FOREIGN TABLE options)
70 *
71 * {optname, optcontext, optrequired, optfound}
72 */
73 static struct OgrFdwOption valid_options[] =
74 {
75
76 /* OGR column mapping */
77 {OPT_COLUMN, AttributeRelationId, false, false},
78
79 /* OGR datasource options */
80 {OPT_SOURCE, ForeignServerRelationId, true, false},
81 {OPT_DRIVER, ForeignServerRelationId, false, false},
82 {OPT_UPDATEABLE, ForeignServerRelationId, false, false},
83 {OPT_CONFIG_OPTIONS, ForeignServerRelationId, false, false},
84 {OPT_CHAR_ENCODING, ForeignServerRelationId, false, false},
85 #if GDAL_VERSION_MAJOR >= 2
86 {OPT_OPEN_OPTIONS, ForeignServerRelationId, false, false},
87 #endif
88 /* OGR layer options */
89 {OPT_LAYER, ForeignTableRelationId, true, false},
90 {OPT_UPDATEABLE, ForeignTableRelationId, false, false},
91
92 /* EOList marker */
93 {NULL, InvalidOid, false, false}
94 };
95
96 /*
97 * SQL functions
98 */
99 extern Datum ogr_fdw_handler(PG_FUNCTION_ARGS);
100 extern Datum ogr_fdw_validator(PG_FUNCTION_ARGS);
101
102 PG_FUNCTION_INFO_V1(ogr_fdw_handler);
103 PG_FUNCTION_INFO_V1(ogr_fdw_validator);
104 void _PG_init(void);
105
106 /*
107 * FDW query callback routines
108 */
109 static void ogrGetForeignRelSize(PlannerInfo* root,
110 RelOptInfo* baserel,
111 Oid foreigntableid);
112 static void ogrGetForeignPaths(PlannerInfo* root,
113 RelOptInfo* baserel,
114 Oid foreigntableid);
115 static ForeignScan* ogrGetForeignPlan(PlannerInfo* root,
116 RelOptInfo* baserel,
117 Oid foreigntableid,
118 ForeignPath* best_path,
119 List* tlist,
120 List* scan_clauses
121 #if PG_VERSION_NUM >= 90500
122 , Plan* outer_plan
123 #endif
124 );
125 static void ogrBeginForeignScan(ForeignScanState* node, int eflags);
126 static TupleTableSlot* ogrIterateForeignScan(ForeignScanState* node);
127 static void ogrReScanForeignScan(ForeignScanState* node);
128 static void ogrEndForeignScan(ForeignScanState* node);
129
130 /*
131 * FDW modify callback routines
132 */
133 static void ogrAddForeignUpdateTargets(Query* parsetree,
134 RangeTblEntry* target_rte,
135 Relation target_relation);
136 static void ogrBeginForeignModify(ModifyTableState* mtstate,
137 ResultRelInfo* rinfo,
138 List* fdw_private,
139 int subplan_index,
140 int eflags);
141 static TupleTableSlot* ogrExecForeignInsert(EState* estate,
142 ResultRelInfo* rinfo,
143 TupleTableSlot* slot,
144 TupleTableSlot* planSlot);
145 static TupleTableSlot* ogrExecForeignUpdate(EState* estate,
146 ResultRelInfo* rinfo,
147 TupleTableSlot* slot,
148 TupleTableSlot* planSlot);
149 static TupleTableSlot* ogrExecForeignDelete(EState* estate,
150 ResultRelInfo* rinfo,
151 TupleTableSlot* slot,
152 TupleTableSlot* planSlot);
153 static void ogrEndForeignModify(EState* estate,
154 ResultRelInfo* rinfo);
155 static int ogrIsForeignRelUpdatable(Relation rel);
156
157
158 #if PG_VERSION_NUM >= 90500
159 static List* ogrImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid);
160 #endif
161
162 /*
163 * Helper functions
164 */
165 static OgrConnection ogrGetConnectionFromTable(Oid foreigntableid, OgrUpdateable updateable);
166 static void ogr_fdw_exit(int code, Datum arg);
167 static void ogrReadColumnData(OgrFdwState* state);
168
169 /* Global to hold GEOMETRYOID */
170 Oid GEOMETRYOID = InvalidOid;
171
172
173 #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,1,0)
174
175 const char* const gdalErrorTypes[] =
176 {
177 "None",
178 "AppDefined",
179 "OutOfMemory",
180 "FileIO",
181 "OpenFailed",
182 "IllegalArg",
183 "NotSupported",
184 "AssertionFailed",
185 "NoWriteAccess",
186 "UserInterrupt",
187 "ObjectNull",
188 "HttpResponse",
189 "AWSBucketNotFound",
190 "AWSObjectNotFound",
191 "AWSAccessDenied",
192 "AWSInvalidCredentials",
193 "AWSSignatureDoesNotMatch"
194 };
195
196 /* In theory this function should be declared "static void CPL_STDCALL" */
197 /* since this is the official signature of error handler callbacks. */
198 /* That would be needed if both GDAL and ogr_fdw were compiled with Visual */
199 /* Studio, but with non-Visual Studio compilers, the macro expands to empty, */
200 /* so if both GDAL and ogr_fdw are compiled with gcc things are fine. In case */
201 /* of mixes, crashes may occur but there is no clean fix... So let this as a note */
202 /* in case of future issue... */
203 static void
ogrErrorHandler(CPLErr eErrClass,int err_no,const char * msg)204 ogrErrorHandler(CPLErr eErrClass, int err_no, const char* msg)
205 {
206 const char* gdalErrType = "unknown type";
207 if (err_no >= 0 && err_no <
208 (int)sizeof(gdalErrorTypes) / sizeof(gdalErrorTypes[0]))
209 {
210 gdalErrType = gdalErrorTypes[err_no];
211 }
212 switch (eErrClass)
213 {
214 case CE_None:
215 elog(NOTICE, "GDAL %s [%d] %s", gdalErrType, err_no, msg);
216 break;
217 case CE_Debug:
218 elog(DEBUG2, "GDAL %s [%d] %s", gdalErrType, err_no, msg);
219 break;
220 case CE_Warning:
221 elog(WARNING, "GDAL %s [%d] %s", gdalErrType, err_no, msg);
222 break;
223 case CE_Failure:
224 case CE_Fatal:
225 default:
226 elog(ERROR, "GDAL %s [%d] %s", gdalErrType, err_no, msg);
227 break;
228 }
229 return;
230 }
231
232 #endif /* GDAL 2.1.0+ */
233
234 void
_PG_init(void)235 _PG_init(void)
236 {
237 on_proc_exit(&ogr_fdw_exit, PointerGetDatum(NULL));
238
239 #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,1,0)
240 /* Hook up the GDAL error handlers to PgSQL elog() */
241 CPLSetErrorHandler(ogrErrorHandler);
242 CPLSetCurrentErrorHandlerCatchDebug(true);
243 #endif
244 }
245
246 /*
247 * ogr_fdw_exit: Exit callback function.
248 */
249 static void
ogr_fdw_exit(int code,Datum arg)250 ogr_fdw_exit(int code, Datum arg)
251 {
252 OGRCleanupAll();
253 }
254
255 /*
256 * Function to get the geometry OID if required
257 */
258 Oid
ogrGetGeometryOid(void)259 ogrGetGeometryOid(void)
260 {
261 if (GEOMETRYOID == InvalidOid)
262 {
263 Oid typoid = TypenameGetTypid("geometry");
264 if (OidIsValid(typoid) && get_typisdefined(typoid))
265 {
266 GEOMETRYOID = typoid;
267 }
268 else
269 {
270 GEOMETRYOID = BYTEAOID;
271 }
272 }
273
274 return GEOMETRYOID;
275 }
276
277 /*
278 * Foreign-data wrapper handler function: return a struct with pointers
279 * to my callback routines.
280 */
281 Datum
ogr_fdw_handler(PG_FUNCTION_ARGS)282 ogr_fdw_handler(PG_FUNCTION_ARGS)
283 {
284 FdwRoutine* fdwroutine = makeNode(FdwRoutine);
285
286 /* Read support */
287 fdwroutine->GetForeignRelSize = ogrGetForeignRelSize;
288 fdwroutine->GetForeignPaths = ogrGetForeignPaths;
289 fdwroutine->GetForeignPlan = ogrGetForeignPlan;
290 fdwroutine->BeginForeignScan = ogrBeginForeignScan;
291 fdwroutine->IterateForeignScan = ogrIterateForeignScan;
292 fdwroutine->ReScanForeignScan = ogrReScanForeignScan;
293 fdwroutine->EndForeignScan = ogrEndForeignScan;
294
295 /* Write support */
296 fdwroutine->AddForeignUpdateTargets = ogrAddForeignUpdateTargets;
297 fdwroutine->BeginForeignModify = ogrBeginForeignModify;
298 fdwroutine->ExecForeignInsert = ogrExecForeignInsert;
299 fdwroutine->ExecForeignUpdate = ogrExecForeignUpdate;
300 fdwroutine->ExecForeignDelete = ogrExecForeignDelete;
301 fdwroutine->EndForeignModify = ogrEndForeignModify;
302 fdwroutine->IsForeignRelUpdatable = ogrIsForeignRelUpdatable;
303
304 #if PG_VERSION_NUM >= 90500
305 /* Support functions for IMPORT FOREIGN SCHEMA */
306 fdwroutine->ImportForeignSchema = ogrImportForeignSchema;
307 #endif
308
309 PG_RETURN_POINTER(fdwroutine);
310 }
311
312 /*
313 * When attempting a soft open (allowing for failure and retry),
314 * we might need to call the opening
315 * routines twice, so all the opening machinery is placed here
316 * for convenient re-calling.
317 */
318 static OGRErr
ogrGetDataSourceAttempt(OgrConnection * ogr,bool bUpdateable,char ** open_option_list)319 ogrGetDataSourceAttempt(OgrConnection* ogr, bool bUpdateable, char** open_option_list)
320 {
321 GDALDriverH ogr_dr = NULL;
322 #if GDAL_VERSION_MAJOR >= 2
323 unsigned int open_flags = GDAL_OF_VECTOR;
324
325 if (bUpdateable)
326 {
327 open_flags |= GDAL_OF_UPDATE;
328 }
329 else
330 {
331 open_flags |= GDAL_OF_READONLY;
332 }
333 #endif
334
335 if (ogr->dr_str)
336 {
337 ogr_dr = GDALGetDriverByName(ogr->dr_str);
338 if (!ogr_dr)
339 {
340 ereport(ERROR,
341 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
342 errmsg("unable to find format \"%s\"", ogr->dr_str),
343 errhint("See the formats list at http://www.gdal.org/ogr_formats.html")));
344 }
345 #if GDAL_VERSION_MAJOR < 2
346 ogr->ds = OGR_Dr_Open(ogr_dr, ogr->ds_str, bUpdateable);
347 #else
348 {
349 char** driver_list = CSLAddString(NULL, ogr->dr_str);
350 ogr->ds = GDALOpenEx(ogr->ds_str, /* file/data source */
351 open_flags, /* open flags */
352 (const char* const*)driver_list, /* driver */
353 (const char* const*)open_option_list, /* open options */
354 NULL); /* sibling files */
355 CSLDestroy(driver_list);
356 }
357 #endif
358 }
359 /* No driver, try a blind open... */
360 else
361 {
362 #if GDAL_VERSION_MAJOR < 2
363 ogr->ds = OGROpen(ogr->ds_str, bUpdateable, &ogr_dr);
364 #else
365 ogr->ds = GDALOpenEx(ogr->ds_str,
366 open_flags,
367 NULL,
368 (const char* const*)open_option_list,
369 NULL);
370 #endif
371 }
372 return ogr->ds ? OGRERR_NONE : OGRERR_FAILURE;
373 }
374
375 /*
376 * Given a connection string and (optional) driver string, try to connect
377 * with appropriate error handling and reporting. Used in query startup,
378 * and in FDW options validation.
379 */
380 static OGRErr
ogrGetDataSource(OgrConnection * ogr,OgrUpdateable updateable)381 ogrGetDataSource(OgrConnection* ogr, OgrUpdateable updateable)
382 {
383 char** open_option_list = NULL;
384 bool bUpdateable = (updateable == OGR_UPDATEABLE_TRUE || updateable == OGR_UPDATEABLE_TRY);
385 OGRErr err;
386
387 /* Set the GDAL config options into the environment */
388 if (ogr->config_options)
389 {
390 char** option_iter;
391 char** option_list = CSLTokenizeString(ogr->config_options);
392
393 for (option_iter = option_list; option_iter && *option_iter; option_iter++)
394 {
395 char* key;
396 const char* value;
397 value = CPLParseNameValue(*option_iter, &key);
398 if (!(key && value))
399 {
400 elog(ERROR, "bad config option string '%s'", ogr->config_options);
401 }
402
403 elog(DEBUG1, "GDAL config option '%s' set to '%s'", key, value);
404 CPLSetConfigOption(key, value);
405 CPLFree(key);
406 }
407 CSLDestroy(option_list);
408 }
409
410 /* Parse the GDAL layer open options */
411 if (ogr->open_options)
412 {
413 open_option_list = CSLTokenizeString(ogr->open_options);
414 }
415
416 /* Cannot search for drivers if they aren't registered, */
417 /* but don't do registration if we already have drivers loaded */
418 if (GDALGetDriverCount() <= 0)
419 {
420 GDALAllRegister();
421 }
422
423 /* First attempt at connection */
424 err = ogrGetDataSourceAttempt(ogr, bUpdateable, open_option_list);
425
426 /* Failed on soft updateable attempt, try and fall back to readonly */
427 if ((!ogr->ds) && updateable == OGR_UPDATEABLE_TRY)
428 {
429 err = ogrGetDataSourceAttempt(ogr, false, open_option_list);
430 /* Succeeded with readonly connection */
431 if (ogr->ds)
432 {
433 ogr->ds_updateable = ogr->lyr_updateable = OGR_UPDATEABLE_FALSE;
434 }
435 }
436
437 /* Open failed, provide error hint if OGR gives us one. */
438 if (!ogr->ds)
439 {
440 const char* ogrerrmsg = CPLGetLastErrorMsg();
441 if (ogrerrmsg && !streq(ogrerrmsg, ""))
442 {
443 ereport(ERROR,
444 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
445 errmsg("unable to connect to data source \"%s\"", ogr->ds_str),
446 errhint("%s", ogrerrmsg)));
447 }
448 else
449 {
450 ereport(ERROR,
451 (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION),
452 errmsg("unable to connect to data source \"%s\"", ogr->ds_str)));
453 }
454 }
455
456 CSLDestroy(open_option_list);
457
458 return err;
459 }
460
461 static bool
ogrCanReallyCountFast(const OgrConnection * con)462 ogrCanReallyCountFast(const OgrConnection* con)
463 {
464 GDALDriverH dr = GDALGetDatasetDriver(con->ds);
465 const char* dr_str = GDALGetDriverShortName(dr);
466
467 if (streq(dr_str, "ESRI Shapefile") ||
468 streq(dr_str, "FileGDB") ||
469 streq(dr_str, "OpenFileGDB"))
470 {
471 return true;
472 }
473 return false;
474 }
475
476 static void
ogrEreportError(const char * errstr)477 ogrEreportError(const char* errstr)
478 {
479 const char* ogrerrmsg = CPLGetLastErrorMsg();
480 if (ogrerrmsg && !streq(ogrerrmsg, ""))
481 {
482 ereport(ERROR,
483 (errcode(ERRCODE_FDW_ERROR),
484 errmsg("%s", errstr),
485 errhint("%s", ogrerrmsg)));
486 }
487 else
488 {
489 ereport(ERROR,
490 (errcode(ERRCODE_FDW_ERROR),
491 errmsg("%s", errstr)));
492 }
493 }
494
495 /*
496 * Make sure the datasource is cleaned up when we're done
497 * with a connection.
498 */
499 static void
ogrFinishConnection(OgrConnection * ogr)500 ogrFinishConnection(OgrConnection* ogr)
501 {
502 if (ogr->lyr && OGR_L_SyncToDisk(ogr->lyr) != OGRERR_NONE)
503 {
504 elog(NOTICE, "failed to flush writes to OGR data source");
505 }
506
507 if (ogr->ds)
508 {
509 GDALClose(ogr->ds);
510 }
511
512 ogr->ds = NULL;
513 }
514
515 static OgrConnection
ogrGetConnectionFromServer(Oid foreignserverid,OgrUpdateable updateable)516 ogrGetConnectionFromServer(Oid foreignserverid, OgrUpdateable updateable)
517 {
518 ForeignServer* server;
519 OgrConnection ogr;
520 ListCell* cell;
521 OGRErr err;
522
523 /* Null all values */
524 memset(&ogr, 0, sizeof(OgrConnection));
525 ogr.ds_updateable = OGR_UPDATEABLE_UNSET;
526 ogr.lyr_updateable = OGR_UPDATEABLE_UNSET;
527
528 server = GetForeignServer(foreignserverid);
529
530 foreach (cell, server->options)
531 {
532 DefElem* def = (DefElem*) lfirst(cell);
533 if (streq(def->defname, OPT_SOURCE))
534 {
535 ogr.ds_str = defGetString(def);
536 }
537 if (streq(def->defname, OPT_DRIVER))
538 {
539 ogr.dr_str = defGetString(def);
540 }
541 if (streq(def->defname, OPT_CONFIG_OPTIONS))
542 {
543 ogr.config_options = defGetString(def);
544 }
545 if (streq(def->defname, OPT_OPEN_OPTIONS))
546 {
547 ogr.open_options = defGetString(def);
548 }
549 if (streq(def->defname, OPT_CHAR_ENCODING))
550 {
551 ogr.char_encoding = pg_char_to_encoding(defGetString(def));
552 }
553 if (streq(def->defname, OPT_UPDATEABLE))
554 {
555 if (defGetBoolean(def))
556 {
557 ogr.ds_updateable = OGR_UPDATEABLE_TRUE;
558 }
559 else
560 {
561 ogr.ds_updateable = OGR_UPDATEABLE_FALSE;
562 /* Over-ride the open mode to favour user-defined mode */
563 updateable = OGR_UPDATEABLE_FALSE;
564 }
565 }
566 }
567
568 if (!ogr.ds_str)
569 {
570 elog(ERROR, "FDW table '%s' option is missing", OPT_SOURCE);
571 }
572
573 /*
574 * TODO: Connections happen twice for each query, having a
575 * connection pool will certainly make things faster.
576 */
577
578 /* Connect! */
579 err = ogrGetDataSource(&ogr, updateable);
580 if (err == OGRERR_FAILURE)
581 {
582 elog(ERROR, "ogrGetDataSource failed");
583 }
584 return ogr;
585 }
586
587
588 /*
589 * Read the options (data source connection from server and
590 * layer name from table) from a foreign table and use them
591 * to connect to an OGR layer. Return a connection object that
592 * has handles for both the datasource and layer.
593 */
594 static OgrConnection
ogrGetConnectionFromTable(Oid foreigntableid,OgrUpdateable updateable)595 ogrGetConnectionFromTable(Oid foreigntableid, OgrUpdateable updateable)
596 {
597 ForeignTable* table;
598 /* UserMapping *mapping; */
599 /* ForeignDataWrapper *wrapper; */
600 ListCell* cell;
601 OgrConnection ogr;
602
603 /* Gather all data for the foreign table. */
604 table = GetForeignTable(foreigntableid);
605 /* mapping = GetUserMapping(GetUserId(), table->serverid); */
606
607 ogr = ogrGetConnectionFromServer(table->serverid, updateable);
608
609 foreach (cell, table->options)
610 {
611 DefElem* def = (DefElem*) lfirst(cell);
612 if (streq(def->defname, OPT_LAYER))
613 {
614 ogr.lyr_str = defGetString(def);
615 }
616 if (streq(def->defname, OPT_UPDATEABLE))
617 {
618 if (defGetBoolean(def))
619 {
620 if (ogr.ds_updateable == OGR_UPDATEABLE_FALSE)
621 {
622 ereport(ERROR, (
623 errcode(ERRCODE_FDW_ERROR),
624 errmsg("data source \"%s\" is not updateable", ogr.ds_str),
625 errhint("cannot set table '%s' option to true", OPT_UPDATEABLE)
626 ));
627 }
628 ogr.lyr_updateable = OGR_UPDATEABLE_TRUE;
629 }
630 else
631 {
632 ogr.lyr_updateable = OGR_UPDATEABLE_FALSE;
633 }
634 }
635 }
636
637 if (!ogr.lyr_str)
638 {
639 elog(ERROR, "FDW table '%s' option is missing", OPT_LAYER);
640 }
641
642 /* Does the layer exist in the data source? */
643 ogr.lyr = GDALDatasetGetLayerByName(ogr.ds, ogr.lyr_str);
644 if (!ogr.lyr)
645 {
646 const char* ogrerr = CPLGetLastErrorMsg();
647 ereport(ERROR, (
648 errcode(ERRCODE_FDW_TABLE_NOT_FOUND),
649 errmsg("unable to connect to %s to \"%s\"", OPT_LAYER, ogr.lyr_str),
650 (ogrerr && ! streq(ogrerr, ""))
651 ? errhint("%s", ogrerr)
652 : errhint("Does the layer exist?")
653 ));
654 }
655
656 if (OGR_L_TestCapability(ogr.lyr, OLCStringsAsUTF8))
657 {
658 ogr.char_encoding = PG_UTF8;
659 }
660
661 return ogr;
662 }
663
664
665 /*
666 * Validate the options given to a FOREIGN DATA WRAPPER, SERVER,
667 * USER MAPPING or FOREIGN TABLE that uses ogr_fdw.
668 *
669 * Raise an ERROR if the option or its value is considered invalid.
670 */
671 Datum
ogr_fdw_validator(PG_FUNCTION_ARGS)672 ogr_fdw_validator(PG_FUNCTION_ARGS)
673 {
674 List* options_list = untransformRelOptions(PG_GETARG_DATUM(0));
675 Oid catalog = PG_GETARG_OID(1);
676 ListCell* cell;
677 struct OgrFdwOption* opt;
678 const char* source = NULL, *driver = NULL;
679 const char* config_options = NULL, *open_options = NULL;
680 OgrUpdateable updateable = OGR_UPDATEABLE_FALSE;
681
682 /* Initialize found state to not found */
683 for (opt = valid_options; opt->optname; opt++)
684 {
685 opt->optfound = false;
686 }
687
688 /*
689 * Check that only options supported by ogr_fdw, and allowed for the
690 * current object type, are given.
691 */
692 foreach (cell, options_list)
693 {
694 DefElem* def = (DefElem*) lfirst(cell);
695 bool optfound = false;
696
697 for (opt = valid_options; opt->optname; opt++)
698 {
699 if (catalog == opt->optcontext && streq(opt->optname, def->defname))
700 {
701 /* Mark that this user option was found */
702 opt->optfound = optfound = true;
703
704 /* Store some options for testing later */
705 if (streq(opt->optname, OPT_SOURCE))
706 {
707 source = defGetString(def);
708 }
709 if (streq(opt->optname, OPT_DRIVER))
710 {
711 driver = defGetString(def);
712 }
713 if (streq(opt->optname, OPT_CONFIG_OPTIONS))
714 {
715 config_options = defGetString(def);
716 }
717 if (streq(opt->optname, OPT_OPEN_OPTIONS))
718 {
719 open_options = defGetString(def);
720 }
721 if (streq(opt->optname, OPT_UPDATEABLE))
722 {
723 if (defGetBoolean(def))
724 {
725 updateable = OGR_UPDATEABLE_TRY;
726 }
727 }
728
729 break;
730 }
731 }
732
733 if (!optfound)
734 {
735 /*
736 * Unknown option specified, complain about it. Provide a hint
737 * with list of valid options for the object.
738 */
739 const struct OgrFdwOption* opt;
740 StringInfoData buf;
741
742 initStringInfo(&buf);
743 for (opt = valid_options; opt->optname; opt++)
744 {
745 if (catalog == opt->optcontext)
746 appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "",
747 opt->optname);
748 }
749
750 ereport(ERROR, (
751 errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
752 errmsg("invalid option \"%s\"", def->defname),
753 buf.len > 0
754 ? errhint("Valid options in this context are: %s", buf.data)
755 : errhint("There are no valid options in this context.")));
756 }
757 }
758
759 /* Check that all the mandatory options were found */
760 for (opt = valid_options; opt->optname; opt++)
761 {
762 /* Required option for this catalog type is missing? */
763 if (catalog == opt->optcontext && opt->optrequired && ! opt->optfound)
764 {
765 ereport(ERROR, (
766 errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED),
767 errmsg("required option \"%s\" is missing", opt->optname)));
768 }
769 }
770
771 /* Make sure server connection can actually be established */
772 if (catalog == ForeignServerRelationId && source)
773 {
774 OgrConnection ogr;
775 OGRErr err;
776
777 ogr.ds_str = source;
778 ogr.dr_str = driver;
779 ogr.config_options = config_options;
780 ogr.open_options = open_options;
781
782 err = ogrGetDataSource(&ogr, updateable);
783 if (err == OGRERR_FAILURE)
784 {
785 elog(ERROR, "ogrGetDataSource failed");
786 }
787 if (ogr.ds)
788 {
789 GDALClose(ogr.ds);
790 }
791 }
792
793 PG_RETURN_VOID();
794 }
795
796 /*
797 * Initialize an OgrFdwPlanState on the heap.
798 */
799 static OgrFdwState*
getOgrFdwState(Oid foreigntableid,OgrFdwStateType state_type)800 getOgrFdwState(Oid foreigntableid, OgrFdwStateType state_type)
801 {
802 OgrFdwState* state;
803 size_t size;
804 OgrUpdateable updateable = OGR_UPDATEABLE_FALSE;
805
806 switch (state_type)
807 {
808 case OGR_PLAN_STATE:
809 size = sizeof(OgrFdwPlanState);
810 updateable = OGR_UPDATEABLE_FALSE;
811 break;
812 case OGR_EXEC_STATE:
813 size = sizeof(OgrFdwExecState);
814 updateable = OGR_UPDATEABLE_FALSE;
815 break;
816 case OGR_MODIFY_STATE:
817 updateable = OGR_UPDATEABLE_TRUE;
818 size = sizeof(OgrFdwModifyState);
819 break;
820 default:
821 elog(ERROR, "invalid state type");
822 }
823
824 state = palloc0(size);
825 state->type = state_type;
826
827 /* Connect! */
828 state->ogr = ogrGetConnectionFromTable(foreigntableid, updateable);
829 state->foreigntableid = foreigntableid;
830
831 return state;
832 }
833
834
835
836 /*
837 * ogrGetForeignRelSize
838 * Obtain relation size estimates for a foreign table
839 */
840 static void
ogrGetForeignRelSize(PlannerInfo * root,RelOptInfo * baserel,Oid foreigntableid)841 ogrGetForeignRelSize(PlannerInfo* root,
842 RelOptInfo* baserel,
843 Oid foreigntableid)
844 {
845 /* Initialize the OGR connection */
846 OgrFdwState* state = (OgrFdwState*)getOgrFdwState(foreigntableid, OGR_PLAN_STATE);
847 OgrFdwPlanState* planstate = (OgrFdwPlanState*)state;
848 List* scan_clauses = baserel->baserestrictinfo;
849
850 /* Set to NULL to clear the restriction clauses in OGR */
851 OGR_L_SetIgnoredFields(planstate->ogr.lyr, NULL);
852 OGR_L_SetSpatialFilter(planstate->ogr.lyr, NULL);
853 OGR_L_SetAttributeFilter(planstate->ogr.lyr, NULL);
854
855 /*
856 * The estimate number of rows returned must actually use restrictions.
857 * Since OGR can't really give us a fast count with restrictions on
858 * (usually involves a scan) and restrictions in the baserel mean we
859 * must punt row count estimates.
860 */
861
862 /* TODO: calculate the row width based on the attribute types of the OGR table */
863
864 /*
865 * OGR asks drivers to honestly state if they can provide a fast
866 * row count, but too many drivers lie. We are only listing drivers
867 * we trust in ogrCanReallyCountFast()
868 */
869
870 /* If we can quickly figure how many rows this layer has, then do so */
871 if (scan_clauses == NIL &&
872 OGR_L_TestCapability(planstate->ogr.lyr, OLCFastFeatureCount) == TRUE &&
873 ogrCanReallyCountFast(&(planstate->ogr)))
874 {
875 /* Count rows, but don't force a slow count */
876 int rows = OGR_L_GetFeatureCount(planstate->ogr.lyr, false);
877 /* Only use row count if return is valid (>0) */
878 if (rows >= 0)
879 {
880 planstate->nrows = rows;
881 baserel->rows = rows;
882 }
883 }
884
885 /* Save connection state for next calls */
886 baserel->fdw_private = (void*) planstate;
887
888 return;
889 }
890
891
892
893 /*
894 * ogrGetForeignPaths
895 * Create possible access paths for a scan on the foreign table
896 *
897 * Currently there is only one
898 * possible access path, which simply returns all records in the order in
899 * the data file.
900 */
901 static void
ogrGetForeignPaths(PlannerInfo * root,RelOptInfo * baserel,Oid foreigntableid)902 ogrGetForeignPaths(PlannerInfo* root,
903 RelOptInfo* baserel,
904 Oid foreigntableid)
905 {
906 OgrFdwPlanState* planstate = (OgrFdwPlanState*)(baserel->fdw_private);
907
908 /* TODO: replace this with something that looks at the OGRDriver and */
909 /* makes a determination based on that? Better: add connection caching */
910 /* so that slow startup doesn't matter so much */
911 planstate->startup_cost = 25;
912
913 /* TODO: more research on what the total cost is supposed to mean, */
914 /* relative to the startup cost? */
915 planstate->total_cost = planstate->startup_cost + baserel->rows;
916
917 /* Built the (one) path we are providing. Providing fancy paths is */
918 /* really only possible with back-ends that can properly provide */
919 /* explain info on how they complete the query, not for something as */
920 /* obtuse as OGR. (So far, have only seen it w/ the postgres_fdw */
921 add_path(baserel,
922 (Path*) create_foreignscan_path(root, baserel,
923 #if PG_VERSION_NUM >= 90600
924 NULL, /* PathTarget */
925 #endif
926 baserel->rows,
927 planstate->startup_cost,
928 planstate->total_cost,
929 NIL, /* no pathkeys */
930 NULL, /* no outer rel either */
931 NULL /* no extra plan */
932 #if PG_VERSION_NUM >= 90500
933 , NIL /* no fdw_private list */
934 #endif
935 )
936 ); /* no fdw_private data */
937 }
938
939
940 /*
941 * Convert an OgrFdwSpatialFilter into a List so it can
942 * be safely passed through the fdw_private list.
943 */
944 static List*
ogrSpatialFilterToList(const OgrFdwSpatialFilter * spatial_filter)945 ogrSpatialFilterToList(const OgrFdwSpatialFilter* spatial_filter)
946 {
947 List *l = NIL;
948 if (spatial_filter)
949 {
950 l = lappend(l, makeInteger(spatial_filter->ogrfldnum));
951 l = lappend(l, makeFloat(psprintf("%.17g", spatial_filter->minx)));
952 l = lappend(l, makeFloat(psprintf("%.17g", spatial_filter->miny)));
953 l = lappend(l, makeFloat(psprintf("%.17g", spatial_filter->maxx)));
954 l = lappend(l, makeFloat(psprintf("%.17g", spatial_filter->maxy)));
955 }
956 return l;
957 }
958
959 /*
960 * Convert the List form back into an OgrFdwSpatialFilter
961 * after passing through fdw_private.
962 */
963 static OgrFdwSpatialFilter*
ogrSpatialFilterFromList(const List * lst)964 ogrSpatialFilterFromList(const List* lst)
965 {
966 OgrFdwSpatialFilter* spatial_filter;
967
968 if (lst == NIL)
969 return NULL;
970
971 Assert(list_length(lst) == 5);
972
973 spatial_filter = palloc(sizeof(OgrFdwSpatialFilter));
974 spatial_filter->ogrfldnum = intVal(linitial(lst));
975 spatial_filter->minx = floatVal(lsecond(lst));
976 spatial_filter->miny = floatVal(lthird(lst));
977 spatial_filter->maxx = floatVal(lfourth(lst));
978 spatial_filter->maxy = floatVal(list_nth(lst, 4)); /* list_nth counts from zero */
979 return spatial_filter;
980 }
981
982 /*
983 * fileGetForeignPlan
984 * Create a ForeignScan plan node for scanning the foreign table
985 */
986 static ForeignScan*
ogrGetForeignPlan(PlannerInfo * root,RelOptInfo * baserel,Oid foreigntableid,ForeignPath * best_path,List * tlist,List * scan_clauses,Plan * outer_plan)987 ogrGetForeignPlan(PlannerInfo* root,
988 RelOptInfo* baserel,
989 Oid foreigntableid,
990 ForeignPath* best_path,
991 List* tlist,
992 List* scan_clauses
993 #if PG_VERSION_NUM >= 90500
994 , Plan* outer_plan
995 #endif
996 )
997 {
998 Index scan_relid = baserel->relid;
999 bool sql_generated;
1000 StringInfoData sql;
1001 List* params_list = NULL;
1002 List* fdw_private;
1003 OgrFdwPlanState* planstate = (OgrFdwPlanState*)(baserel->fdw_private);
1004 OgrFdwState* state = (OgrFdwState*)(baserel->fdw_private);
1005 OgrFdwSpatialFilter* spatial_filter = NULL;
1006 char* attribute_filter = NULL;
1007
1008 /* Add in column mapping data to build SQL with the right OGR column names */
1009 ogrReadColumnData(state);
1010
1011 /*
1012 * TODO: Review the columns requested (via params_list) and only pull those back, using
1013 * OGR_L_SetIgnoredFields. This is less important than pushing restrictions
1014 * down to OGR via OGR_L_SetAttributeFilter (done) and (TODO) OGR_L_SetSpatialFilter.
1015 */
1016 initStringInfo(&sql);
1017 sql_generated = ogrDeparse(&sql, root, baserel, scan_clauses, state, ¶ms_list, &spatial_filter);
1018
1019 /* Extract the OGR SQL from the StringInfoData */
1020 if (sql_generated && sql.len > 0)
1021 attribute_filter = sql.data;
1022
1023 /* Log filters at debug level one as necessary */
1024 if (attribute_filter)
1025 elog(DEBUG1, "OGR SQL: %s", attribute_filter);
1026 if (spatial_filter)
1027 elog(DEBUG1, "OGR spatial filter (%g %g, %g %g)",
1028 spatial_filter->minx, spatial_filter->miny,
1029 spatial_filter->maxx, spatial_filter->maxy);
1030 /*
1031 * Here we strip RestrictInfo
1032 * nodes from the clauses and ignore pseudoconstants (which will be
1033 * handled elsewhere).
1034 * Some FDW implementations (mysql_fdw) just pass this full list on to the
1035 * make_foreignscan function. postgres_fdw carefully separates local and remote
1036 * clauses and only passes the local ones to make_foreignscan, so this
1037 * is probably best practice, though re-applying the clauses is probably
1038 * the least of our performance worries with this fdw. For now, we just
1039 * pass them all to make_foreignscan, see no evil, etc.
1040 */
1041 scan_clauses = extract_actual_clauses(scan_clauses, false);
1042
1043 /* Pack the data we want to pass to the execution stage into a List. */
1044 /* The members of this list must by copyable by PgSQL, which means */
1045 /* they need to be Lists themselves, or Value nodes, otherwise when */
1046 /* the plan gets copied the copy might fail. */
1047 fdw_private = list_make3(makeString(attribute_filter), params_list, ogrSpatialFilterToList(spatial_filter));
1048
1049 /* Clean up our connection */
1050 ogrFinishConnection(&(planstate->ogr));
1051
1052 /* Create the ForeignScan node */
1053 return make_foreignscan(tlist,
1054 scan_clauses,
1055 scan_relid,
1056 NIL, /* no expressions to evaluate */
1057 fdw_private
1058 #if PG_VERSION_NUM >= 90500
1059 , NIL /* no scan_tlist */
1060 , NIL /* no remote quals */
1061 , outer_plan
1062 #endif
1063 );
1064
1065
1066 }
1067
1068 static void
pgCanConvertToOgr(Oid pg_type,OGRFieldType ogr_type,const char * colname,const char * tblname)1069 pgCanConvertToOgr(Oid pg_type, OGRFieldType ogr_type, const char* colname, const char* tblname)
1070 {
1071 if (pg_type == BOOLOID && ogr_type == OFTInteger)
1072 {
1073 return;
1074 }
1075 else if (pg_type == INT2OID && ogr_type == OFTInteger)
1076 {
1077 return;
1078 }
1079 else if (pg_type == INT4OID && ogr_type == OFTInteger)
1080 {
1081 return;
1082 }
1083 else if (pg_type == INT8OID)
1084 {
1085 #if GDAL_VERSION_MAJOR >= 2
1086 if (ogr_type == OFTInteger64)
1087 {
1088 return;
1089 }
1090 #else
1091 if (ogr_type == OFTInteger)
1092 {
1093 return;
1094 }
1095 #endif
1096 }
1097 else if (pg_type == NUMERICOID && ogr_type == OFTReal)
1098 {
1099 return;
1100 }
1101 else if (pg_type == FLOAT4OID && ogr_type == OFTReal)
1102 {
1103 return;
1104 }
1105 else if (pg_type == FLOAT8OID && ogr_type == OFTReal)
1106 {
1107 return;
1108 }
1109 else if (pg_type == TEXTOID && ogr_type == OFTString)
1110 {
1111 return;
1112 }
1113 else if (pg_type == VARCHAROID && ogr_type == OFTString)
1114 {
1115 return;
1116 }
1117 else if (pg_type == CHAROID && ogr_type == OFTString)
1118 {
1119 return;
1120 }
1121 else if (pg_type == BPCHAROID && ogr_type == OFTString)
1122 {
1123 return;
1124 }
1125 else if (pg_type == NAMEOID && ogr_type == OFTString)
1126 {
1127 return;
1128 }
1129 else if (pg_type == BYTEAOID && ogr_type == OFTBinary)
1130 {
1131 return;
1132 }
1133 else if (pg_type == DATEOID && ogr_type == OFTDate)
1134 {
1135 return;
1136 }
1137 else if (pg_type == TIMEOID && ogr_type == OFTTime)
1138 {
1139 return;
1140 }
1141 else if (pg_type == TIMESTAMPOID && ogr_type == OFTDateTime)
1142 {
1143 return;
1144 }
1145
1146 ereport(ERROR, (
1147 errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
1148 errmsg("column \"%s\" of foreign table \"%s\" converts \"%s\" to OGR \"%s\"",
1149 colname, tblname,
1150 format_type_be(pg_type), OGR_GetFieldTypeName(ogr_type))
1151 ));
1152 }
1153
1154 static void
ogrCanConvertToPg(OGRFieldType ogr_type,Oid pg_type,const char * colname,const char * tblname)1155 ogrCanConvertToPg(OGRFieldType ogr_type, Oid pg_type, const char* colname, const char* tblname)
1156 {
1157 switch (ogr_type)
1158 {
1159 case OFTInteger:
1160 if (pg_type == BOOLOID || pg_type == INT4OID || pg_type == INT8OID ||
1161 pg_type == NUMERICOID || pg_type == FLOAT4OID ||
1162 pg_type == FLOAT8OID || pg_type == TEXTOID || pg_type == VARCHAROID)
1163 {
1164 return;
1165 }
1166 break;
1167
1168 case OFTReal:
1169 if (pg_type == NUMERICOID || pg_type == FLOAT4OID || pg_type == FLOAT8OID ||
1170 pg_type == TEXTOID || pg_type == VARCHAROID)
1171 {
1172 return;
1173 }
1174 break;
1175
1176 case OFTBinary:
1177 if (pg_type == BYTEAOID)
1178 {
1179 return;
1180 }
1181 break;
1182
1183 case OFTString:
1184 if (pg_type == TEXTOID || pg_type == VARCHAROID || pg_type == CHAROID || pg_type == BPCHAROID)
1185 {
1186 return;
1187 }
1188 break;
1189
1190 case OFTDate:
1191 if (pg_type == DATEOID || pg_type == TIMESTAMPOID || pg_type == TEXTOID || pg_type == VARCHAROID)
1192 {
1193 return;
1194 }
1195 break;
1196
1197 case OFTTime:
1198 if (pg_type == TIMEOID || pg_type == TEXTOID || pg_type == VARCHAROID)
1199 {
1200 return;
1201 }
1202 break;
1203
1204 case OFTDateTime:
1205 if (pg_type == TIMESTAMPOID || pg_type == TEXTOID || pg_type == VARCHAROID)
1206 {
1207 return;
1208 }
1209 break;
1210
1211 #if GDAL_VERSION_MAJOR >= 2
1212 case OFTInteger64:
1213 if (pg_type == INT8OID || pg_type == NUMERICOID || pg_type == FLOAT8OID ||
1214 pg_type == TEXTOID || pg_type == VARCHAROID)
1215 {
1216 return;
1217 }
1218 break;
1219 #endif
1220
1221 case OFTWideString:
1222 case OFTIntegerList:
1223 #if GDAL_VERSION_MAJOR >= 2
1224 case OFTInteger64List:
1225 #endif
1226 case OFTRealList:
1227 case OFTStringList:
1228 case OFTWideStringList:
1229 {
1230 ereport(ERROR, (
1231 errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
1232 errmsg("column \"%s\" of foreign table \"%s\" uses an OGR array, currently unsupported", colname, tblname)
1233 ));
1234 break;
1235 }
1236 }
1237 ereport(ERROR, (
1238 errcode(ERRCODE_FDW_INVALID_DATA_TYPE),
1239 errmsg("column \"%s\" of foreign table \"%s\" converts OGR \"%s\" to \"%s\"",
1240 colname, tblname,
1241 OGR_GetFieldTypeName(ogr_type), format_type_be(pg_type))
1242 ));
1243 }
1244
1245 #ifdef OGR_FDW_HEXWKB
1246
1247 static char* hexchr = "0123456789ABCDEF";
1248
1249 static char*
ogrBytesToHex(unsigned char * bytes,size_t size)1250 ogrBytesToHex(unsigned char* bytes, size_t size)
1251 {
1252 char* hex;
1253 int i;
1254 if (! bytes || ! size)
1255 {
1256 elog(ERROR, "ogrBytesToHex: invalid input");
1257 return NULL;
1258 }
1259 hex = palloc(size * 2 + 1);
1260 hex[2 * size] = '\0';
1261 for (i = 0; i < size; i++)
1262 {
1263 /* Top four bits to 0-F */
1264 hex[2 * i] = hexchr[bytes[i] >> 4];
1265 /* Bottom four bits to 0-F */
1266 hex[2 * i + 1] = hexchr[bytes[i] & 0x0F];
1267 }
1268 return hex;
1269 }
1270
1271 #endif
1272
1273 static void
freeOgrFdwTable(OgrFdwTable * table)1274 freeOgrFdwTable(OgrFdwTable* table)
1275 {
1276 if (table)
1277 {
1278 if (table->tblname)
1279 {
1280 pfree(table->tblname);
1281 }
1282 if (table->cols)
1283 {
1284 pfree(table->cols);
1285 }
1286 pfree(table);
1287 }
1288 }
1289
1290 typedef struct
1291 {
1292 char* fldname;
1293 int fldnum;
1294 } OgrFieldEntry;
1295
1296 static int
ogrFieldEntryCmpFunc(const void * a,const void * b)1297 ogrFieldEntryCmpFunc(const void* a, const void* b)
1298 {
1299 const char* a_name = ((OgrFieldEntry*)a)->fldname;
1300 const char* b_name = ((OgrFieldEntry*)b)->fldname;
1301
1302 return strcasecmp(a_name, b_name);
1303 }
1304
1305
1306 /*
1307 * The execstate holds a foreign table relation id and an OGR connection,
1308 * this function finds all the OGR fields that match up to columns in the
1309 * foreign table definition, using columns name match and data type consistency
1310 * as the criteria for making a match.
1311 * The results of the matching are stored in the execstate before the function
1312 * returns.
1313 */
1314 static void
ogrReadColumnData(OgrFdwState * state)1315 ogrReadColumnData(OgrFdwState* state)
1316 {
1317 Relation rel;
1318 TupleDesc tupdesc;
1319 int i;
1320 OgrFdwTable* tbl;
1321 OGRFeatureDefnH dfn;
1322 int ogr_ncols;
1323 int fid_count = 0;
1324 int geom_count = 0;
1325 int ogr_geom_count = 0;
1326 int field_count = 0;
1327 OgrFieldEntry* ogr_fields;
1328 int ogr_fields_count = 0;
1329 char* tblname = get_rel_name(state->foreigntableid);
1330
1331 /* Blow away any existing table in the state */
1332 if (state->table)
1333 {
1334 freeOgrFdwTable(state->table);
1335 state->table = NULL;
1336 }
1337
1338 /* Fresh table */
1339 tbl = palloc0(sizeof(OgrFdwTable));
1340
1341 /* One column for each PgSQL foreign table column */
1342 #if PG_VERSION_NUM < 120000
1343 rel = heap_open(state->foreigntableid, NoLock);
1344 #else
1345 rel = table_open(state->foreigntableid, NoLock);
1346 #endif /* PG_VERSION_NUM */
1347
1348 tupdesc = rel->rd_att;
1349 state->tupdesc = tupdesc;
1350 tbl->ncols = tupdesc->natts;
1351 tbl->cols = palloc0(tbl->ncols * sizeof(OgrFdwColumn));
1352 tbl->tblname = pstrdup(tblname);
1353
1354 /* Get OGR metadata ready */
1355 dfn = OGR_L_GetLayerDefn(state->ogr.lyr);
1356 ogr_ncols = OGR_FD_GetFieldCount(dfn);
1357 #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(1,11,0))
1358 ogr_geom_count = OGR_FD_GetGeomFieldCount(dfn);
1359 #else
1360 ogr_geom_count = (OGR_FD_GetGeomType(dfn) != wkbNone) ? 1 : 0;
1361 #endif
1362
1363
1364 /* Prepare sorted list of OGR column names */
1365 /* TODO: change this to a hash table, to avoid repeated strcmp */
1366 /* We will search both the original and laundered OGR field names for matches */
1367 ogr_fields_count = 2 * ogr_ncols;
1368 ogr_fields = palloc0(ogr_fields_count * sizeof(OgrFieldEntry));
1369 for (i = 0; i < ogr_ncols; i++)
1370 {
1371 char* fldname = pstrdup(OGR_Fld_GetNameRef(OGR_FD_GetFieldDefn(dfn, i)));
1372 char* fldname_laundered = palloc(STR_MAX_LEN);
1373 strncpy(fldname_laundered, fldname, STR_MAX_LEN);
1374 ogrStringLaunder(fldname_laundered);
1375 ogr_fields[2 * i].fldname = fldname;
1376 ogr_fields[2 * i].fldnum = i;
1377 ogr_fields[2 * i + 1].fldname = fldname_laundered;
1378 ogr_fields[2 * i + 1].fldnum = i;
1379 }
1380 qsort(ogr_fields, ogr_fields_count, sizeof(OgrFieldEntry), ogrFieldEntryCmpFunc);
1381
1382
1383 /* loop through foreign table columns */
1384 for (i = 0; i < tbl->ncols; i++)
1385 {
1386 List* options;
1387 ListCell* lc;
1388 OgrFieldEntry* found_entry;
1389 OgrFieldEntry entry;
1390
1391 #if PG_VERSION_NUM >= 110000
1392 Form_pg_attribute att_tuple = &tupdesc->attrs[i];
1393 #else
1394 Form_pg_attribute att_tuple = tupdesc->attrs[i];
1395 #endif
1396 OgrFdwColumn col = tbl->cols[i];
1397 col.pgattnum = att_tuple->attnum;
1398 col.pgtype = att_tuple->atttypid;
1399 col.pgtypmod = att_tuple->atttypmod;
1400 col.pgattisdropped = att_tuple->attisdropped;
1401
1402 /* Skip filling in any further metadata about dropped columns */
1403 if (col.pgattisdropped)
1404 {
1405 continue;
1406 }
1407
1408 /* Find the appropriate conversion functions */
1409 getTypeInputInfo(col.pgtype, &col.pginputfunc, &col.pginputioparam);
1410 getTypeBinaryInputInfo(col.pgtype, &col.pgrecvfunc, &col.pgrecvioparam);
1411 getTypeOutputInfo(col.pgtype, &col.pgoutputfunc, &col.pgoutputvarlena);
1412 getTypeBinaryOutputInfo(col.pgtype, &col.pgsendfunc, &col.pgsendvarlena);
1413
1414 /* Get the PgSQL column name */
1415 #if PG_VERSION_NUM >= 110000
1416 col.pgname = get_attname(rel->rd_id, att_tuple->attnum, false);
1417 #else
1418 col.pgname = get_attname(rel->rd_id, att_tuple->attnum);
1419 #endif
1420
1421 /* Handle FID first */
1422 if (strcaseeq(col.pgname, "fid") &&
1423 (col.pgtype == INT4OID || col.pgtype == INT8OID))
1424 {
1425 if (fid_count >= 1)
1426 {
1427 elog(ERROR, "FDW table '%s' includes more than one FID column", tblname);
1428 }
1429
1430 col.ogrvariant = OGR_FID;
1431 col.ogrfldnum = fid_count++;
1432 tbl->cols[i] = col;
1433 continue;
1434 }
1435
1436 /* If the OGR source has geometries, can we match them to Pg columns? */
1437 /* We'll match to the first ones we find, irrespective of name */
1438 if (geom_count < ogr_geom_count && col.pgtype == ogrGetGeometryOid())
1439 {
1440 col.ogrvariant = OGR_GEOMETRY;
1441 col.ogrfldtype = OFTBinary;
1442 col.ogrfldnum = geom_count++;
1443 tbl->cols[i] = col;
1444 continue;
1445 }
1446
1447 /* Now we search for matches in the OGR fields */
1448
1449 /* By default, search for the PgSQL column name */
1450 entry.fldname = col.pgname;
1451 entry.fldnum = 0;
1452
1453 /*
1454 * But, if there is a 'column_name' option for this column, we
1455 * want to search for *that* in the OGR layer.
1456 */
1457 options = GetForeignColumnOptions(state->foreigntableid, i + 1);
1458 foreach (lc, options)
1459 {
1460 DefElem* def = (DefElem*) lfirst(lc);
1461
1462 if (streq(def->defname, OPT_COLUMN))
1463 {
1464 entry.fldname = defGetString(def);
1465 break;
1466 }
1467 }
1468
1469 /* Search PgSQL column name in the OGR column name list */
1470 found_entry = bsearch(&entry, ogr_fields, ogr_fields_count, sizeof(OgrFieldEntry), ogrFieldEntryCmpFunc);
1471
1472 /* Column name matched, so save this entry, if the types are consistent */
1473 if (found_entry)
1474 {
1475 OGRFieldDefnH fld = OGR_FD_GetFieldDefn(dfn, found_entry->fldnum);
1476 OGRFieldType fldtype = OGR_Fld_GetType(fld);
1477
1478 /* Error if types mismatched when column names match */
1479 ogrCanConvertToPg(fldtype, col.pgtype, col.pgname, tblname);
1480
1481 col.ogrvariant = OGR_FIELD;
1482 col.ogrfldnum = found_entry->fldnum;
1483 col.ogrfldtype = fldtype;
1484 field_count++;
1485 }
1486 else
1487 {
1488 col.ogrvariant = OGR_UNMATCHED;
1489 }
1490 tbl->cols[i] = col;
1491 }
1492
1493 elog(DEBUG2, "ogrReadColumnData matched %d FID, %d GEOM, %d FIELDS out of %d PGSQL COLUMNS", fid_count, geom_count,
1494 field_count, tbl->ncols);
1495
1496 /* Clean up */
1497
1498 state->table = tbl;
1499 for (i = 0; i < 2 * ogr_ncols; i++)
1500 {
1501 if (ogr_fields[i].fldname)
1502 {
1503 pfree(ogr_fields[i].fldname);
1504 }
1505 }
1506 pfree(ogr_fields);
1507 #if PG_VERSION_NUM < 120000
1508 heap_close(rel, NoLock);
1509 #else
1510 table_close(rel, NoLock);
1511 #endif /* PG_VERSION_NUM */
1512
1513
1514 return;
1515 }
1516
1517 /*
1518 * ogrLookupGeometryFunctionOid
1519 *
1520 * Find the procedure Oids of useful functions so we can call
1521 * them later.
1522 */
1523 static Oid
ogrLookupGeometryFunctionOid(const char * proname)1524 ogrLookupGeometryFunctionOid(const char* proname)
1525 {
1526 List* names;
1527 FuncCandidateList clist;
1528
1529 /* This only works if PostGIS is installed */
1530 if (ogrGetGeometryOid() == InvalidOid || ogrGetGeometryOid() == BYTEAOID)
1531 {
1532 return InvalidOid;
1533 }
1534
1535 names = stringToQualifiedNameList(proname);
1536 #if PG_VERSION_NUM < 90400
1537 clist = FuncnameGetCandidates(names, -1, NIL, false, false);
1538 #else
1539 clist = FuncnameGetCandidates(names, -1, NIL, false, false, false);
1540 #endif
1541 if (streq(proname, "st_setsrid"))
1542 {
1543 do
1544 {
1545 int i;
1546 for (i = 0; i < clist->nargs; i++)
1547 {
1548 if (clist->args[i] == ogrGetGeometryOid())
1549 {
1550 return clist->oid;
1551 }
1552 }
1553 }
1554 while ((clist = clist->next));
1555 }
1556 else if (streq(proname, "postgis_typmod_srid"))
1557 {
1558 return clist->oid;
1559 }
1560
1561 return InvalidOid;
1562 }
1563
1564 /*
1565 * ogrBeginForeignScan
1566 */
1567 static void
ogrBeginForeignScan(ForeignScanState * node,int eflags)1568 ogrBeginForeignScan(ForeignScanState* node, int eflags)
1569 {
1570 OgrFdwState* state;
1571 OgrFdwExecState* execstate;
1572 OgrFdwSpatialFilter* spatial_filter;
1573 Oid foreigntableid = RelationGetRelid(node->ss.ss_currentRelation);
1574 ForeignScan* fsplan = (ForeignScan*)node->ss.ps.plan;
1575
1576 /* Do nothing in EXPLAIN (no ANALYZE) case */
1577 if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
1578 return;
1579
1580 /* Initialize OGR connection */
1581 state = getOgrFdwState(foreigntableid, OGR_EXEC_STATE);
1582 execstate = (OgrFdwExecState*)state;
1583
1584 /* Read the OGR layer definition and PgSQL foreign table definitions */
1585 ogrReadColumnData(state);
1586
1587 /* Collect the procedure Oids for PostGIS functions we might need */
1588 execstate->setsridfunc = ogrLookupGeometryFunctionOid("st_setsrid");
1589 execstate->typmodsridfunc = ogrLookupGeometryFunctionOid("postgis_typmod_srid");
1590
1591 /* Get OGR SQL generated by the deparse step during the planner function. */
1592 execstate->sql = (char*) strVal(list_nth(fsplan->fdw_private, 0));
1593
1594 /* TODO: Use the parse step attribute list to restrict requested columns */
1595 // execstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private, 1);
1596
1597 /* Get spatial filter generated by the deparse step. */
1598 spatial_filter = ogrSpatialFilterFromList(list_nth(fsplan->fdw_private, 2));
1599
1600 if (spatial_filter)
1601 {
1602 OGR_L_SetSpatialFilterRectEx(execstate->ogr.lyr,
1603 spatial_filter->ogrfldnum,
1604 spatial_filter->minx,
1605 spatial_filter->miny,
1606 spatial_filter->maxx,
1607 spatial_filter->maxy
1608 );
1609 }
1610
1611 if (execstate->sql && strlen(execstate->sql) > 0)
1612 {
1613 OGRErr err = OGR_L_SetAttributeFilter(execstate->ogr.lyr, execstate->sql);
1614 if (err != OGRERR_NONE)
1615 {
1616 const char* ogrerr = CPLGetLastErrorMsg();
1617
1618 if (ogrerr && ! streq(ogrerr, ""))
1619 {
1620 ereport(NOTICE,
1621 (errcode(ERRCODE_FDW_ERROR),
1622 errmsg("unable to set OGR SQL '%s' on layer", execstate->sql),
1623 errhint("%s", ogrerr)));
1624 }
1625 else
1626 {
1627 ereport(NOTICE,
1628 (errcode(ERRCODE_FDW_ERROR),
1629 errmsg("unable to set OGR SQL '%s' on layer", execstate->sql)));
1630 }
1631 }
1632 }
1633 else
1634 {
1635 OGR_L_SetAttributeFilter(execstate->ogr.lyr, NULL);
1636 }
1637
1638 /* Save the state for the next call */
1639 node->fdw_state = (void*) execstate;
1640
1641 return;
1642 }
1643
1644 /*
1645 * Rather than explicitly try and form PgSQL datums, use the type
1646 * input functions, that accept cstring representations, and convert
1647 * to the input format. We have to lookup the right input function for
1648 * each column in the foreign table.
1649 */
1650 static Datum
pgDatumFromCString(const char * cstr,Oid pgtype,int pgtypmod,Oid pginputfunc)1651 pgDatumFromCString(const char* cstr, Oid pgtype, int pgtypmod, Oid pginputfunc)
1652 {
1653 Datum value;
1654 Datum cdata = CStringGetDatum(cstr);
1655
1656 value = OidFunctionCall3(pginputfunc, cdata,
1657 ObjectIdGetDatum(InvalidOid),
1658 Int32GetDatum(pgtypmod));
1659
1660 return value;
1661 }
1662
1663 static inline void
ogrNullSlot(Datum * values,bool * nulls,int i)1664 ogrNullSlot(Datum* values, bool* nulls, int i)
1665 {
1666 values[i] = PointerGetDatum(NULL);
1667 nulls[i] = true;
1668 }
1669
1670 /*
1671 * The ogrIterateForeignScan is getting a new TupleTableSlot to handle
1672 * for each iteration. Each slot contains an entry for every column in
1673 * in the foreign table, that has to be filled out, either with a value
1674 * or a NULL for columns that either have been deleted or were not requested
1675 * in the query.
1676 *
1677 * The tupledescriptor tells us about the types of each slot.
1678 * For now we assume our slot has exactly the same number of
1679 * records and equivalent types to our OGR layer, and that our
1680 * foreign table's first two columns are an integer primary key
1681 * using int8 as the type, and then a geometry using bytea as
1682 * the type, then everything else.
1683 */
1684 static OGRErr
ogrFeatureToSlot(const OGRFeatureH feat,TupleTableSlot * slot,const OgrFdwExecState * execstate)1685 ogrFeatureToSlot(const OGRFeatureH feat, TupleTableSlot* slot, const OgrFdwExecState* execstate)
1686 {
1687 const OgrFdwTable* tbl = execstate->table;
1688 int i;
1689 Datum* values = slot->tts_values;
1690 bool* nulls = slot->tts_isnull;
1691 TupleDesc tupdesc = slot->tts_tupleDescriptor;
1692 int have_typmod_funcs = (execstate->setsridfunc && execstate->typmodsridfunc);
1693
1694 /* Check our assumption that slot and setup data match */
1695 if (tbl->ncols != tupdesc->natts)
1696 {
1697 elog(ERROR, "FDW metadata table and exec table have mismatching number of columns");
1698 return OGRERR_FAILURE;
1699 }
1700
1701 /* For each pgtable column, get a value from OGR */
1702 for (i = 0; i < tbl->ncols; i++)
1703 {
1704 OgrFdwColumn col = tbl->cols[i];
1705 const char* pgname = col.pgname;
1706 Oid pgtype = col.pgtype;
1707 int pgtypmod = col.pgtypmod;
1708 Oid pginputfunc = col.pginputfunc;
1709 int ogrfldnum = col.ogrfldnum;
1710 OGRFieldType ogrfldtype = col.ogrfldtype;
1711 OgrColumnVariant ogrvariant = col.ogrvariant;
1712
1713 /*
1714 * Fill in dropped attributes with NULL
1715 */
1716 if (col.pgattisdropped)
1717 {
1718 ogrNullSlot(values, nulls, i);
1719 continue;
1720 }
1721
1722 if (ogrvariant == OGR_FID)
1723 {
1724 GIntBig fid = OGR_F_GetFID(feat);
1725
1726 if (fid == OGRNullFID)
1727 {
1728 ogrNullSlot(values, nulls, i);
1729 }
1730 else
1731 {
1732 char fidstr[256];
1733 snprintf(fidstr, 256, OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
1734
1735 nulls[i] = false;
1736 values[i] = pgDatumFromCString(fidstr, pgtype, pgtypmod, pginputfunc);
1737 }
1738 }
1739 else if (ogrvariant == OGR_GEOMETRY)
1740 {
1741 int wkbsize;
1742 int varsize;
1743 bytea* varlena;
1744 unsigned char* wkb;
1745 OGRErr err;
1746
1747 #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(1,11,0))
1748 OGRGeometryH geom = OGR_F_GetGeomFieldRef(feat, ogrfldnum);
1749 #else
1750 OGRGeometryH geom = OGR_F_GetGeometryRef(feat);
1751 #endif
1752
1753 /* No geometry ? NULL */
1754 if (! geom)
1755 {
1756 /* No geometry column, so make the output null */
1757 ogrNullSlot(values, nulls, i);
1758 continue;
1759 }
1760
1761 /*
1762 * Start by generating standard PgSQL variable length byte
1763 * buffer, with WKB filled into the data area.
1764 */
1765 wkbsize = OGR_G_WkbSize(geom);
1766 varsize = wkbsize + VARHDRSZ;
1767 varlena = palloc(varsize);
1768 wkb = (unsigned char*)VARDATA(varlena);
1769 err = OGR_G_ExportToWkb(geom, wkbNDR, wkb);
1770 SET_VARSIZE(varlena, varsize);
1771
1772 /* Couldn't create WKB from OGR geometry? error */
1773 if (err != OGRERR_NONE)
1774 {
1775 return err;
1776 }
1777
1778 if (pgtype == BYTEAOID)
1779 {
1780 /*
1781 * Nothing special to do for bytea, just send the varlena data through!
1782 */
1783 nulls[i] = false;
1784 values[i] = PointerGetDatum(varlena);
1785 }
1786 else if (pgtype == ogrGetGeometryOid())
1787 {
1788 /*
1789 * For geometry we need to convert the varlena WKB data into a serialized
1790 * geometry (aka "gserialized"). For that, we can use the type's "recv" function
1791 * which takes in WKB and spits out serialized form, or the "input" function
1792 * that takes in HEXWKB. The "input" function is more lax about geometry
1793 * structure errors (unclosed polys, etc).
1794 */
1795 #ifdef OGR_FDW_HEXWKB
1796 char* hexwkb = ogrBytesToHex(wkb, wkbsize);
1797 /*
1798 * Use the input function to convert the WKB from OGR into
1799 * a PostGIS internal format.
1800 */
1801 nulls[i] = false;
1802 values[i] = OidFunctionCall1(col.pginputfunc, PointerGetDatum(hexwkb));
1803 pfree(hexwkb);
1804 #else
1805 /*
1806 * The "recv" function expects to receive a StringInfo pointer
1807 * on the first argument, so we form one of those ourselves by
1808 * hand. Rather than copy into a fresh buffer, we'll just use the
1809 * existing varlena buffer and point to the data area.
1810 *
1811 * The "recv" function tests for basic geometry validity,
1812 * things like polygon closure, etc. So don't feed it junk.
1813 */
1814 StringInfoData strinfo;
1815 strinfo.data = (char*)wkb;
1816 strinfo.len = wkbsize;
1817 strinfo.maxlen = strinfo.len;
1818 strinfo.cursor = 0;
1819
1820 /*
1821 * Use the recv function to convert the WKB from OGR into
1822 * a PostGIS internal format.
1823 */
1824 nulls[i] = false;
1825 values[i] = OidFunctionCall1(col.pgrecvfunc, PointerGetDatum(&strinfo));
1826 #endif
1827
1828 /*
1829 * Apply the typmod restriction to the incoming geometry, so it's
1830 * not really a restriction anymore, it's more like a requirement.
1831 *
1832 * TODO: In the case where the OGR input actually *knows* what SRID
1833 * it is, we should actually apply *that* and let the restriction run
1834 * its usual course.
1835 */
1836 if (have_typmod_funcs && col.pgtypmod >= 0)
1837 {
1838 Datum srid = OidFunctionCall1(execstate->typmodsridfunc, Int32GetDatum(col.pgtypmod));
1839 values[i] = OidFunctionCall2(execstate->setsridfunc, values[i], srid);
1840 }
1841 }
1842 else
1843 {
1844 elog(NOTICE, "conversion to geometry called with column type not equal to bytea or geometry");
1845 ogrNullSlot(values, nulls, i);
1846 }
1847
1848 }
1849 else if (ogrvariant == OGR_FIELD)
1850 {
1851 #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(2,2,0))
1852 int field_not_null = OGR_F_IsFieldSet(feat, ogrfldnum) && ! OGR_F_IsFieldNull(feat, ogrfldnum);
1853 #else
1854 int field_not_null = OGR_F_IsFieldSet(feat, ogrfldnum);
1855 #endif
1856
1857 /* Ensure that the OGR data type fits the destination Pg column */
1858 ogrCanConvertToPg(ogrfldtype, pgtype, pgname, tbl->tblname);
1859
1860 /* Only convert non-null fields */
1861 if (field_not_null)
1862 {
1863 switch (ogrfldtype)
1864 {
1865 case OFTBinary:
1866 {
1867 /*
1868 * Convert binary fields to bytea directly
1869 */
1870 int bufsize;
1871 GByte* buf = OGR_F_GetFieldAsBinary(feat, ogrfldnum, &bufsize);
1872 int varsize = bufsize + VARHDRSZ;
1873 bytea* varlena = palloc(varsize);
1874 memcpy(VARDATA(varlena), buf, bufsize);
1875 SET_VARSIZE(varlena, varsize);
1876 nulls[i] = false;
1877 values[i] = PointerGetDatum(varlena);
1878 break;
1879 }
1880 case OFTInteger:
1881 case OFTReal:
1882 case OFTString:
1883 #if GDAL_VERSION_MAJOR >= 2
1884 case OFTInteger64:
1885 #endif
1886 {
1887 /*
1888 * Convert numbers and strings via a string representation.
1889 * Handling numbers directly would be faster, but require a lot of extra code.
1890 * For now, we go via text.
1891 */
1892 const char* cstr_in = OGR_F_GetFieldAsString(feat, ogrfldnum);
1893 size_t cstr_len = cstr_in ? strlen(cstr_in) : 0;
1894 if (cstr_in && cstr_len > 0)
1895 {
1896 char* cstr_decoded;
1897 if (execstate->ogr.char_encoding)
1898 {
1899 cstr_decoded = pg_any_to_server(cstr_in, cstr_len, execstate->ogr.char_encoding);
1900 }
1901 else
1902 {
1903 cstr_decoded = pstrdup(cstr_in);
1904 }
1905 nulls[i] = false;
1906 values[i] = pgDatumFromCString(cstr_decoded, pgtype, pgtypmod, pginputfunc);
1907 /* Free cstr_decoded if it is a copy */
1908 if (cstr_in != cstr_decoded)
1909 pfree(cstr_decoded);
1910 }
1911 else
1912 {
1913 ogrNullSlot(values, nulls, i);
1914 }
1915 break;
1916 }
1917 case OFTDate:
1918 case OFTTime:
1919 case OFTDateTime:
1920 {
1921 /*
1922 * OGR date/times have a weird access method, so we use that to pull
1923 * out the raw data and turn it into a string for PgSQL's (very
1924 * sophisticated) date/time parsing routines to handle.
1925 */
1926 int year, month, day, hour, minute, second, tz;
1927 char cstr[256];
1928
1929 OGR_F_GetFieldAsDateTime(feat, ogrfldnum,
1930 &year, &month, &day,
1931 &hour, &minute, &second, &tz);
1932
1933 if (ogrfldtype == OFTDate)
1934 {
1935 snprintf(cstr, 256, "%d-%02d-%02d", year, month, day);
1936 }
1937 else if (ogrfldtype == OFTTime)
1938 {
1939 snprintf(cstr, 256, "%02d:%02d:%02d", hour, minute, second);
1940 }
1941 else
1942 {
1943 snprintf(cstr, 256, "%d-%02d-%02d %02d:%02d:%02d", year, month, day, hour, minute, second);
1944 }
1945 nulls[i] = false;
1946 values[i] = pgDatumFromCString(cstr, pgtype, pgtypmod, pginputfunc);
1947 break;
1948
1949 }
1950 case OFTIntegerList:
1951 case OFTRealList:
1952 case OFTStringList:
1953 {
1954 /* TODO, map these OGR array types into PgSQL arrays (fun!) */
1955 elog(ERROR, "unsupported OGR array type \"%s\"", OGR_GetFieldTypeName(ogrfldtype));
1956 break;
1957 }
1958 default:
1959 {
1960 elog(ERROR, "unsupported OGR type \"%s\"", OGR_GetFieldTypeName(ogrfldtype));
1961 break;
1962 }
1963
1964 }
1965 }
1966 else
1967 {
1968 ogrNullSlot(values, nulls, i);
1969 }
1970 }
1971 /* Fill in unmatched columns with NULL */
1972 else if (ogrvariant == OGR_UNMATCHED)
1973 {
1974 ogrNullSlot(values, nulls, i);
1975 }
1976 else
1977 {
1978 elog(ERROR, "OGR FDW unsupported column variant in \"%s\", %d", pgname, ogrvariant);
1979 return OGRERR_FAILURE;
1980 }
1981
1982 }
1983
1984 /* done! */
1985 return OGRERR_NONE;
1986 }
1987
1988 static void
ogrStaticText(char * text,const char * str)1989 ogrStaticText(char* text, const char* str)
1990 {
1991 size_t len = strlen(str);
1992 memcpy(VARDATA(text), str, len);
1993 SET_VARSIZE(text, len + VARHDRSZ);
1994 return;
1995 }
1996
1997 /*
1998 * EWKB includes a flag that indicates an SRID embedded in the
1999 * binary. The EWKB has an endian byte, four bytes of type information
2000 * and then 4 bytes of optional SRID information. If that info is
2001 * there, we want to over-write it, and remove the SRID flag, to
2002 * generate more "standard" WKB for OGR to consume.
2003 */
2004 static size_t
ogrEwkbStripSrid(unsigned char * wkb,size_t wkbsize)2005 ogrEwkbStripSrid(unsigned char* wkb, size_t wkbsize)
2006 {
2007 unsigned int type = 0;
2008 int has_srid = 0;
2009 size_t newwkbsize = wkbsize;
2010 memcpy(&type, wkb + 1, 4);
2011 /* has_z = type & 0x80000000; */
2012 /* has_m = type & 0x40000000; */
2013 has_srid = type & 0x20000000;
2014
2015 /* Flatten SRID flag away */
2016 type &= 0xDFFFFFFF;
2017 memcpy(wkb + 1, &type, 4);
2018
2019 /* If there was an SRID number embedded, overwrite it */
2020 if (has_srid)
2021 {
2022 newwkbsize -= 4; /* no space for SRID number needed */
2023 memmove(wkb + 5, wkb + 9, newwkbsize - 5);
2024 }
2025
2026 return newwkbsize;
2027 }
2028
2029 OGRErr
pgDatumToOgrGeometry(Datum pg_geometry,Oid pgsendfunc,OGRGeometryH * ogr_geometry)2030 pgDatumToOgrGeometry (Datum pg_geometry, Oid pgsendfunc, OGRGeometryH* ogr_geometry)
2031 {
2032 OGRErr err;
2033 bytea* wkb_bytea = DatumGetByteaP(OidFunctionCall1(pgsendfunc, pg_geometry));
2034 unsigned char* wkb = (unsigned char*)VARDATA_ANY(wkb_bytea);
2035 size_t wkbsize = VARSIZE_ANY_EXHDR(wkb_bytea);
2036 wkbsize = ogrEwkbStripSrid(wkb, wkbsize);
2037 err = OGR_G_CreateFromWkb(wkb, NULL, ogr_geometry, wkbsize);
2038 if (wkb_bytea)
2039 pfree(wkb_bytea);
2040 return err;
2041 }
2042
2043 static OGRErr
ogrSlotToFeature(const TupleTableSlot * slot,OGRFeatureH feat,const OgrFdwTable * tbl)2044 ogrSlotToFeature(const TupleTableSlot* slot, OGRFeatureH feat, const OgrFdwTable* tbl)
2045 {
2046 int i;
2047 Datum* values = slot->tts_values;
2048 bool* nulls = slot->tts_isnull;
2049 TupleDesc tupdesc = slot->tts_tupleDescriptor;
2050
2051 int year, month, day, hour, minute, second;
2052
2053 /* Prepare date-time part tokens for use later */
2054 char txtyear[STR_MAX_LEN];
2055 char txtmonth[STR_MAX_LEN];
2056 char txtday[STR_MAX_LEN];
2057 char txthour[STR_MAX_LEN];
2058 char txtminute[STR_MAX_LEN];
2059 char txtsecond[STR_MAX_LEN];
2060
2061 ogrStaticText(txtyear, "year");
2062 ogrStaticText(txtmonth, "month");
2063 ogrStaticText(txtday, "day");
2064 ogrStaticText(txthour, "hour");
2065 ogrStaticText(txtminute, "minute");
2066 ogrStaticText(txtsecond, "second");
2067
2068 /* Check our assumption that slot and setup data match */
2069 if (tbl->ncols != tupdesc->natts)
2070 {
2071 elog(ERROR, "FDW metadata table and slot table have mismatching number of columns");
2072 return OGRERR_FAILURE;
2073 }
2074
2075 /* For each pgtable column, set a value on the feature OGR */
2076 for (i = 0; i < tbl->ncols; i++)
2077 {
2078 OgrFdwColumn col = tbl->cols[i];
2079 const char* pgname = col.pgname;
2080 Oid pgtype = col.pgtype;
2081 Oid pgoutputfunc = col.pgoutputfunc;
2082 int ogrfldnum = col.ogrfldnum;
2083 OGRFieldType ogrfldtype = col.ogrfldtype;
2084 OgrColumnVariant ogrvariant = col.ogrvariant;
2085
2086 /* Skip dropped attributes */
2087 if (col.pgattisdropped)
2088 {
2089 continue;
2090 }
2091
2092 /* Skip the FID, we have to treat it as immutable anyways */
2093 if (ogrvariant == OGR_FID)
2094 {
2095 if (nulls[i])
2096 {
2097 OGR_F_SetFID(feat, OGRNullFID);
2098 }
2099 else
2100 {
2101 if (pgtype == INT4OID)
2102 {
2103 int32 val = DatumGetInt32(values[i]);
2104 OGR_F_SetFID(feat, val);
2105 }
2106 else if (pgtype == INT8OID)
2107 {
2108 int64 val = DatumGetInt64(values[i]);
2109 OGR_F_SetFID(feat, val);
2110 }
2111 else
2112 {
2113 elog(ERROR, "unable to handle non-integer fid");
2114 }
2115 }
2116 continue;
2117 }
2118
2119 /* TODO: For updates, we should only set the fields that are */
2120 /* in the target list, and flag the others as unchanged */
2121 if (ogrvariant == OGR_GEOMETRY)
2122 {
2123 OGRErr err;
2124 if (nulls[i])
2125 {
2126 #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(1,11,0))
2127 err = OGR_F_SetGeomFieldDirectly(feat, ogrfldnum, NULL);
2128 #else
2129 err = OGR_F_SetGeometryDirectly(feat, NULL);
2130 #endif
2131 continue;
2132 }
2133 else
2134 {
2135 OGRGeometryH geom;
2136 err = pgDatumToOgrGeometry (values[i], col.pgsendfunc, &geom);
2137 if (err != OGRERR_NONE)
2138 return err;
2139
2140 #if (GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(1,11,0))
2141 err = OGR_F_SetGeomFieldDirectly(feat, ogrfldnum, geom);
2142 #else
2143 err = OGR_F_SetGeometryDirectly(feat, geom);
2144 #endif
2145 }
2146 }
2147 else if (ogrvariant == OGR_FIELD)
2148 {
2149 /* Ensure that the OGR data type fits the destination Pg column */
2150 pgCanConvertToOgr(pgtype, ogrfldtype, pgname, tbl->tblname);
2151
2152 /* Skip NULL case */
2153 if (nulls[i])
2154 {
2155 OGR_F_UnsetField(feat, ogrfldnum);
2156 continue;
2157 }
2158
2159 switch (pgtype)
2160 {
2161 case BOOLOID:
2162 {
2163 int8 val = DatumGetBool(values[i]);
2164 OGR_F_SetFieldInteger(feat, ogrfldnum, val);
2165 break;
2166 }
2167 case INT2OID:
2168 {
2169 int16 val = DatumGetInt16(values[i]);
2170 OGR_F_SetFieldInteger(feat, ogrfldnum, val);
2171 break;
2172 }
2173 case INT4OID:
2174 {
2175 int32 val = DatumGetInt32(values[i]);
2176 OGR_F_SetFieldInteger(feat, ogrfldnum, val);
2177 break;
2178 }
2179 case INT8OID:
2180 {
2181 int64 val = DatumGetInt64(values[i]);
2182 #if GDAL_VERSION_MAJOR >= 2
2183 OGR_F_SetFieldInteger64(feat, ogrfldnum, val);
2184 #else
2185 if (val < INT_MAX)
2186 {
2187 OGR_F_SetFieldInteger(feat, ogrfldnum, (int32)val);
2188 }
2189 else
2190 {
2191 elog(ERROR, "unable to coerce int64 into int32 OGR field");
2192 }
2193 #endif
2194 break;
2195
2196 }
2197
2198 case NUMERICOID:
2199 {
2200 Datum d;
2201 float8 f;
2202
2203 /* Convert to string */
2204 d = OidFunctionCall1(pgoutputfunc, values[i]);
2205 /* Convert back to float8 */
2206 f = DatumGetFloat8(DirectFunctionCall1(float8in, d));
2207
2208 OGR_F_SetFieldDouble(feat, ogrfldnum, f);
2209 break;
2210 }
2211 case FLOAT4OID:
2212 {
2213 OGR_F_SetFieldDouble(feat, ogrfldnum, DatumGetFloat4(values[i]));
2214 break;
2215 }
2216 case FLOAT8OID:
2217 {
2218 OGR_F_SetFieldDouble(feat, ogrfldnum, DatumGetFloat8(values[i]));
2219 break;
2220 }
2221
2222 case TEXTOID:
2223 case VARCHAROID:
2224 case NAMEOID:
2225 case BPCHAROID: /* char(n) */
2226 {
2227 bytea* varlena = (bytea*)DatumGetPointer(values[i]);
2228 size_t varsize = VARSIZE_ANY_EXHDR(varlena);
2229 char* str = palloc0(varsize + 1);
2230 memcpy(str, VARDATA_ANY(varlena), varsize);
2231 OGR_F_SetFieldString(feat, ogrfldnum, str);
2232 pfree(str);
2233 break;
2234 }
2235
2236 case CHAROID: /* char */
2237 {
2238 char str[2];
2239 str[0] = DatumGetChar(values[i]);
2240 str[1] = '\0';
2241 OGR_F_SetFieldString(feat, ogrfldnum, str);
2242 break;
2243 }
2244
2245 case BYTEAOID:
2246 {
2247 bytea* varlena = PG_DETOAST_DATUM(values[i]);
2248 size_t varsize = VARSIZE_ANY_EXHDR(varlena);
2249 OGR_F_SetFieldBinary(feat, ogrfldnum, varsize, (GByte*)VARDATA_ANY(varlena));
2250 break;
2251 }
2252
2253 case DATEOID:
2254 {
2255 /* Convert date to timestamp */
2256 Datum d = DirectFunctionCall1(date_timestamp, values[i]);
2257
2258 /* Read out the parts */
2259 year = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtyear), d)));
2260 month = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtmonth), d)));
2261 day = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtday), d)));
2262 OGR_F_SetFieldDateTime(feat, ogrfldnum, year, month, day, 0, 0, 0, 0);
2263 break;
2264 }
2265
2266 /* TODO: handle time zones explicitly */
2267 case TIMEOID:
2268 case TIMETZOID:
2269 {
2270 /* Read the parts of the time */
2271 hour = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txthour), values[i])));
2272 minute = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txtminute), values[i])));
2273 second = lround(DatumGetFloat8(DirectFunctionCall2(time_part, PointerGetDatum(txtsecond), values[i])));
2274 OGR_F_SetFieldDateTime(feat, ogrfldnum, 0, 0, 0, hour, minute, second, 0);
2275 break;
2276 }
2277
2278
2279 case TIMESTAMPOID:
2280 case TIMESTAMPTZOID:
2281 {
2282 Datum d = values[i];
2283 year = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtyear), d)));
2284 month = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtmonth), d)));
2285 day = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtday), d)));
2286 hour = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txthour), d)));
2287 minute = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtminute), d)));
2288 second = lround(DatumGetFloat8(DirectFunctionCall2(timestamp_part, PointerGetDatum(txtsecond), d)));
2289 OGR_F_SetFieldDateTime(feat, ogrfldnum, year, month, day, hour, minute, second, 0);
2290 break;
2291 }
2292
2293 /* TODO: array types for string, integer, float */
2294 default:
2295 {
2296 elog(ERROR, "OGR FDW unsupported PgSQL column type in \"%s\", %d", pgname, pgtype);
2297 return OGRERR_FAILURE;
2298 }
2299 }
2300 }
2301 /* Fill in unmatched columns with NULL */
2302 else if (ogrvariant == OGR_UNMATCHED)
2303 {
2304 OGR_F_UnsetField(feat, ogrfldnum);
2305 }
2306 else
2307 {
2308 elog(ERROR, "OGR FDW unsupported column variant in \"%s\", %d", pgname, ogrvariant);
2309 return OGRERR_FAILURE;
2310 }
2311
2312 }
2313
2314 /* done! */
2315 return OGRERR_NONE;
2316 }
2317
2318 /*
2319 * ogrIterateForeignScan
2320 * Read next record from OGR and store it into the
2321 * ScanTupleSlot as a virtual tuple
2322 */
2323 static TupleTableSlot*
ogrIterateForeignScan(ForeignScanState * node)2324 ogrIterateForeignScan(ForeignScanState* node)
2325 {
2326 OgrFdwExecState* execstate = (OgrFdwExecState*) node->fdw_state;
2327 TupleTableSlot* slot = node->ss.ss_ScanTupleSlot;
2328 OGRFeatureH feat;
2329
2330 /*
2331 * Clear the slot. If it gets through w/o being filled up, that means
2332 * we're all done.
2333 */
2334 ExecClearTuple(slot);
2335
2336 /*
2337 * First time through, reset reading. Then keep reading until
2338 * we run out of records, then return a cleared (NULL) slot, to
2339 * notify the core we're done.
2340 */
2341 if (execstate->rownum == 0)
2342 {
2343 OGR_L_ResetReading(execstate->ogr.lyr);
2344 }
2345
2346 /* If we rectreive a feature from OGR, copy it over into the slot */
2347 feat = OGR_L_GetNextFeature(execstate->ogr.lyr);
2348 if (feat)
2349 {
2350 /* convert result to arrays of values and null indicators */
2351 if (OGRERR_NONE != ogrFeatureToSlot(feat, slot, execstate))
2352 {
2353 ogrEreportError("failure reading OGR data source");
2354 }
2355
2356 /* store the virtual tuple */
2357 ExecStoreVirtualTuple(slot);
2358
2359 /* increment row count */
2360 execstate->rownum++;
2361
2362 /* Release OGR feature object */
2363 OGR_F_Destroy(feat);
2364 }
2365
2366 return slot;
2367 }
2368
2369 /*
2370 * ogrReScanForeignScan
2371 * Rescan table, possibly with new parameters
2372 */
2373 static void
ogrReScanForeignScan(ForeignScanState * node)2374 ogrReScanForeignScan(ForeignScanState* node)
2375 {
2376 OgrFdwExecState* execstate = (OgrFdwExecState*) node->fdw_state;
2377
2378 OGR_L_ResetReading(execstate->ogr.lyr);
2379 execstate->rownum = 0;
2380
2381 return;
2382 }
2383
2384 /*
2385 * ogrEndForeignScan
2386 * Finish scanning foreign table and dispose objects used for this scan
2387 */
2388 static void
ogrEndForeignScan(ForeignScanState * node)2389 ogrEndForeignScan(ForeignScanState* node)
2390 {
2391 OgrFdwExecState* execstate = (OgrFdwExecState*) node->fdw_state;
2392 if (execstate)
2393 {
2394 elog(DEBUG2, "OGR FDW processed %d rows from OGR", execstate->rownum);
2395 ogrFinishConnection(&(execstate->ogr));
2396 }
2397
2398 return;
2399 }
2400
2401 /* ======================================================== */
2402 /* WRITE SUPPORT */
2403 /* ======================================================== */
2404
2405 /* if the scanning functions above respected the targetlist,
2406 we would only be getting back the SET target=foo columns in the slots below,
2407 so we would need to add the "fid" to all targetlists (and also disallow fid changing
2408 perhaps).
2409
2410 since we always pull complete tables in the scan functions, the
2411 slots below are basically full tables, in fact they include (?) one entry
2412 for each OGR column, even when the table does not include the column,
2413 just nulling out the entries that are not in the table definition
2414
2415 it might be better to update the scan code to properly manage target lists
2416 first, and then come back here and do things properly
2417
2418 we will need a ogrSlotToFeature to feed into the OGR_L_SetFeature and
2419 OGR_L_CreateFeature functions. Also will use OGR_L_DeleteFeature and
2420 fid value
2421
2422 in ogrGetForeignPlan we get a tlist that includes just the attributes we
2423 are interested in, can use that to pare down the request perhaps
2424 */
2425
2426 static int
ogrGetFidColumn(const TupleDesc td)2427 ogrGetFidColumn(const TupleDesc td)
2428 {
2429 int i;
2430 for (i = 0; i < td->natts; i++)
2431 {
2432 #if PG_VERSION_NUM >= 110000
2433 NameData attname = td->attrs[i].attname;
2434 Oid atttypeid = td->attrs[i].atttypid;
2435 #else
2436 NameData attname = td->attrs[i]->attname;
2437 Oid atttypeid = td->attrs[i]->atttypid;
2438 #endif
2439 if ((atttypeid == INT4OID || atttypeid == INT8OID) &&
2440 strcaseeq("fid", attname.data))
2441 {
2442 return i;
2443 }
2444 }
2445 return -1;
2446 }
2447
2448 /*
2449 * ogrAddForeignUpdateTargets
2450 *
2451 * For now we no-op this callback, as we are making the presence of
2452 * "fid" in the FDW table definition a requirement for any update.
2453 * It might be possible to add nonexisting "junk" columns? In which case
2454 * there could always be a virtual fid travelling with the queries,
2455 * and the FDW table itself wouldn't need such a column?
2456 */
2457 static void
ogrAddForeignUpdateTargets(Query * parsetree,RangeTblEntry * target_rte,Relation target_relation)2458 ogrAddForeignUpdateTargets(Query* parsetree,
2459 RangeTblEntry* target_rte,
2460 Relation target_relation)
2461 {
2462 ListCell* cell;
2463 Form_pg_attribute att;
2464 Var* var;
2465 TargetEntry* tle;
2466 TupleDesc tupdesc = target_relation->rd_att;
2467 int fid_column = ogrGetFidColumn(tupdesc);
2468
2469 elog(DEBUG2, "ogrAddForeignUpdateTargets");
2470
2471 if (fid_column < 0)
2472 {
2473 elog(ERROR, "table '%s' does not have a 'fid' column", RelationGetRelationName(target_relation));
2474 }
2475
2476 #if PG_VERSION_NUM >= 110000
2477 att = &tupdesc->attrs[fid_column];
2478 #else
2479 att = tupdesc->attrs[fid_column];
2480 #endif
2481 /* Make a Var representing the desired value */
2482 var = makeVar(parsetree->resultRelation,
2483 att->attnum,
2484 att->atttypid,
2485 att->atttypmod,
2486 att->attcollation,
2487 0);
2488
2489 /* Wrap it in a resjunk TLE with the right name ... */
2490 tle = makeTargetEntry((Expr*)var,
2491 list_length(parsetree->targetList) + 1,
2492 pstrdup(NameStr(att->attname)),
2493 true);
2494
2495 parsetree->targetList = lappend(parsetree->targetList, tle);
2496
2497 foreach (cell, parsetree->targetList)
2498 {
2499 TargetEntry* target = (TargetEntry*) lfirst(cell);
2500 elog(DEBUG4, "parsetree->targetList %s:%d", target->resname, target->resno);
2501 }
2502
2503 return;
2504
2505 }
2506
2507 /*
2508 * ogrBeginForeignModify
2509 * For now the only thing we'll do here is set up the connection
2510 * and pass that on to the next functions.
2511 */
2512 static void
ogrBeginForeignModify(ModifyTableState * mtstate,ResultRelInfo * rinfo,List * fdw_private,int subplan_index,int eflags)2513 ogrBeginForeignModify(ModifyTableState* mtstate,
2514 ResultRelInfo* rinfo,
2515 List* fdw_private,
2516 int subplan_index,
2517 int eflags)
2518 {
2519 Oid foreigntableid;
2520 OgrFdwState* state;
2521
2522 elog(DEBUG2, "ogrBeginForeignModify");
2523
2524 foreigntableid = RelationGetRelid(rinfo->ri_RelationDesc);
2525 state = getOgrFdwState(foreigntableid, OGR_MODIFY_STATE);
2526
2527 /* Read the OGR layer definition and PgSQL foreign table definitions */
2528 ogrReadColumnData(state);
2529
2530 /* Save OGR connection, etc, for later */
2531 rinfo->ri_FdwState = state;
2532 return;
2533 }
2534
2535 /*
2536 * ogrExecForeignUpdate
2537 * Find out what the fid is, get the OGR feature for that FID,
2538 * and then update the values on that feature.
2539 */
2540 static TupleTableSlot*
ogrExecForeignUpdate(EState * estate,ResultRelInfo * rinfo,TupleTableSlot * slot,TupleTableSlot * planSlot)2541 ogrExecForeignUpdate(EState* estate,
2542 ResultRelInfo* rinfo,
2543 TupleTableSlot* slot,
2544 TupleTableSlot* planSlot)
2545 {
2546 OgrFdwModifyState* modstate = rinfo->ri_FdwState;
2547 TupleDesc td = slot->tts_tupleDescriptor;
2548 Relation rel = rinfo->ri_RelationDesc;
2549 Oid foreigntableid = RelationGetRelid(rel);
2550 int fid_column;
2551 Oid fid_type;
2552 Datum fid_datum;
2553 int64 fid;
2554 OGRFeatureH feat;
2555 OGRErr err;
2556
2557 /* Is there a fid column? */
2558 fid_column = ogrGetFidColumn(td);
2559 if (fid_column < 0)
2560 {
2561 elog(ERROR, "cannot find 'fid' column in table '%s'", get_rel_name(foreigntableid));
2562 }
2563
2564 #if PG_VERSION_NUM >= 120000
2565 slot_getallattrs(slot);
2566 #endif
2567
2568 /* What is the value of the FID for this record? */
2569 fid_datum = slot->tts_values[fid_column];
2570 #if PG_VERSION_NUM >= 110000
2571 fid_type = td->attrs[fid_column].atttypid;
2572 #else
2573 fid_type = td->attrs[fid_column]->atttypid;
2574 #endif
2575 if (fid_type == INT8OID)
2576 {
2577 fid = DatumGetInt64(fid_datum);
2578 }
2579 else
2580 {
2581 fid = DatumGetInt32(fid_datum);
2582 }
2583
2584 elog(DEBUG2, "ogrExecForeignUpdate fid=" OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
2585
2586 /* Get the OGR feature for this fid */
2587 feat = OGR_L_GetFeature(modstate->ogr.lyr, fid);
2588
2589 /* If we found a feature, then copy data from the slot onto the feature */
2590 /* and then back into the layer */
2591 if (! feat)
2592 {
2593 ogrEreportError("failure reading OGR feature");
2594 }
2595
2596 err = ogrSlotToFeature(slot, feat, modstate->table);
2597 if (err != OGRERR_NONE)
2598 {
2599 ogrEreportError("failure populating OGR feature");
2600 }
2601
2602 err = OGR_L_SetFeature(modstate->ogr.lyr, feat);
2603 if (err != OGRERR_NONE)
2604 {
2605 ogrEreportError("failure writing back OGR feature");
2606 }
2607
2608 OGR_F_Destroy(feat);
2609
2610 /* TODO: slot handling? what happens with RETURNING clauses? */
2611
2612 return slot;
2613 }
2614
2615 // typedef struct TupleTableSlot
2616 // {
2617 // NodeTag type;
2618 // bool tts_isempty; /* true = slot is empty */
2619 // bool tts_shouldFree; /* should pfree tts_tuple? */
2620 // bool tts_shouldFreeMin; /* should pfree tts_mintuple? */
2621 // bool tts_slow; /* saved state for slot_deform_tuple */
2622 // HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */
2623 // TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */
2624 // MemoryContext tts_mcxt; /* slot itself is in this context */
2625 // Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */
2626 // int tts_nvalid; /* # of valid values in tts_values */
2627 // Datum *tts_values; /* current per-attribute values */
2628 // bool *tts_isnull; /* current per-attribute isnull flags */
2629 // MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */
2630 // HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */
2631 // long tts_off; /* saved state for slot_deform_tuple */
2632 // } TupleTableSlot;
2633
2634 // typedef struct tupleDesc
2635 // {
2636 // int natts; /* number of attributes in the tuple */
2637 // Form_pg_attribute *attrs;
2638 // /* attrs[N] is a pointer to the description of Attribute Number N+1 */
2639 // TupleConstr *constr; /* constraints, or NULL if none */
2640 // Oid tdtypeid; /* composite type ID for tuple type */
2641 // int32 tdtypmod; /* typmod for tuple type */
2642 // bool tdhasoid; /* tuple has oid attribute in its header */
2643 // int tdrefcount; /* reference count, or -1 if not counting */
2644 // } *TupleDesc;
2645 //
2646
2647 // typedef struct ResultRelInfo
2648 // {
2649 // NodeTag type;
2650 // Index ri_RangeTableIndex;
2651 // Relation ri_RelationDesc;
2652 // int ri_NumIndices;
2653 // RelationPtr ri_IndexRelationDescs;
2654 // IndexInfo **ri_IndexRelationInfo;
2655 // TriggerDesc *ri_TrigDesc;
2656 // FmgrInfo *ri_TrigFunctions;
2657 // List **ri_TrigWhenExprs;
2658 // Instrumentation *ri_TrigInstrument;
2659 // struct FdwRoutine *ri_FdwRoutine;
2660 // void *ri_FdwState;
2661 // List *ri_WithCheckOptions;
2662 // List *ri_WithCheckOptionExprs;
2663 // List **ri_ConstraintExprs;
2664 // JunkFilter *ri_junkFilter;
2665 // ProjectionInfo *ri_projectReturning;
2666 // ProjectionInfo *ri_onConflictSetProj;
2667 // List *ri_onConflictSetWhere;
2668 // } ResultRelInfo;
2669
2670 // typedef struct TargetEntry
2671 // {
2672 // Expr xpr;
2673 // Expr *expr; /* expression to evaluate */
2674 // AttrNumber resno; /* attribute number (see notes above) */
2675 // char *resname; /* name of the column (could be NULL) */
2676 // Index ressortgroupref;/* nonzero if referenced by a sort/group
2677 // * clause */
2678 // Oid resorigtbl; /* OID of column's source table */
2679 // AttrNumber resorigcol; /* column's number in source table */
2680 // bool resjunk; /* set to true to eliminate the attribute from
2681 // * final target list */
2682 // } TargetEntry;
2683
2684 // TargetEntry *
2685 // makeTargetEntry(Expr *expr,
2686 // AttrNumber resno,
2687 // char *resname,
2688 // bool resjunk)
2689
2690 // Var *
2691 // makeVar(Index varno,
2692 // AttrNumber varattno,
2693 // Oid vartype,
2694 // int32 vartypmod,
2695 // Oid varcollid,
2696 // Index varlevelsup)
2697
2698 // typedef struct Var
2699 // {
2700 // Expr xpr;
2701 // Index varno; /* index of this var's relation in the range
2702 // * table, or INNER_VAR/OUTER_VAR/INDEX_VAR */
2703 // AttrNumber varattno; /* attribute number of this var, or zero for
2704 // * all */
2705 // Oid vartype; /* pg_type OID for the type of this var */
2706 // int32 vartypmod; /* pg_attribute typmod value */
2707 // Oid varcollid; /* OID of collation, or InvalidOid if none */
2708 // Index varlevelsup; /* for subquery variables referencing outer
2709 // * relations; 0 in a normal var, >0 means N
2710 // * levels up */
2711 // Index varnoold; /* original value of varno, for debugging */
2712 // AttrNumber varoattno; /* original value of varattno */
2713 // int location; /* token location, or -1 if unknown */
2714 // } Var;
2715
2716 static TupleTableSlot*
ogrExecForeignInsert(EState * estate,ResultRelInfo * rinfo,TupleTableSlot * slot,TupleTableSlot * planSlot)2717 ogrExecForeignInsert(EState* estate,
2718 ResultRelInfo* rinfo,
2719 TupleTableSlot* slot,
2720 TupleTableSlot* planSlot)
2721 {
2722 OgrFdwModifyState* modstate = rinfo->ri_FdwState;
2723 OGRFeatureDefnH ogr_fd = OGR_L_GetLayerDefn(modstate->ogr.lyr);
2724 OGRFeatureH feat = OGR_F_Create(ogr_fd);
2725 int fid_column;
2726 OGRErr err;
2727 GIntBig fid;
2728
2729 elog(DEBUG2, "ogrExecForeignInsert");
2730
2731 #if PG_VERSION_NUM >= 120000
2732 /*
2733 * PgSQL 12 passes an unpopulated slot to us, and for now
2734 * we force it to populate itself and then read directly
2735 * from it. For future, using the slot_getattr() infra
2736 * would be cleaner, but version dependent.
2737 */
2738 slot_getallattrs(slot);
2739 #endif
2740
2741 /* Copy the data from the slot onto the feature */
2742 if (!feat)
2743 {
2744 ogrEreportError("failure creating OGR feature");
2745 }
2746
2747 err = ogrSlotToFeature(slot, feat, modstate->table);
2748 if (err != OGRERR_NONE)
2749 {
2750 ogrEreportError("failure populating OGR feature");
2751 }
2752
2753 err = OGR_L_CreateFeature(modstate->ogr.lyr, feat);
2754 if (err != OGRERR_NONE)
2755 {
2756 ogrEreportError("failure writing OGR feature");
2757 }
2758
2759 fid = OGR_F_GetFID(feat);
2760 OGR_F_Destroy(feat);
2761
2762 /* Update the FID for RETURNING slot */
2763 fid_column = ogrGetFidColumn(slot->tts_tupleDescriptor);
2764 if (fid_column >= 0)
2765 {
2766 slot->tts_values[fid_column] = Int64GetDatum(fid);
2767 slot->tts_isnull[fid_column] = false;
2768 slot->tts_nvalid++;
2769 }
2770
2771 return slot;
2772 }
2773
2774
2775
2776 static TupleTableSlot*
ogrExecForeignDelete(EState * estate,ResultRelInfo * rinfo,TupleTableSlot * slot,TupleTableSlot * planSlot)2777 ogrExecForeignDelete(EState* estate,
2778 ResultRelInfo* rinfo,
2779 TupleTableSlot* slot,
2780 TupleTableSlot* planSlot)
2781 {
2782 OgrFdwModifyState* modstate = rinfo->ri_FdwState;
2783 TupleDesc td = planSlot->tts_tupleDescriptor;
2784 Relation rel = rinfo->ri_RelationDesc;
2785 Oid foreigntableid = RelationGetRelid(rel);
2786 int fid_column;
2787 Oid fid_type;
2788 Datum fid_datum;
2789 int64 fid;
2790 OGRErr err;
2791
2792 /* Is there a fid column? */
2793 fid_column = ogrGetFidColumn(td);
2794 if (fid_column < 0)
2795 {
2796 elog(ERROR, "cannot find 'fid' column in table '%s'", get_rel_name(foreigntableid));
2797 }
2798
2799 #if PG_VERSION_NUM >= 120000
2800 slot_getallattrs(planSlot);
2801 #endif
2802
2803 /* What is the value of the FID for this record? */
2804 fid_datum = planSlot->tts_values[fid_column];
2805 #if PG_VERSION_NUM >= 110000
2806 fid_type = td->attrs[fid_column].atttypid;
2807 #else
2808 fid_type = td->attrs[fid_column]->atttypid;
2809 #endif
2810
2811 if (fid_type == INT8OID)
2812 {
2813 fid = DatumGetInt64(fid_datum);
2814 }
2815 else
2816 {
2817 fid = DatumGetInt32(fid_datum);
2818 }
2819
2820 elog(DEBUG2, "ogrExecForeignDelete fid=" OGR_FDW_FRMT_INT64, OGR_FDW_CAST_INT64(fid));
2821
2822 /* Delete the OGR feature for this fid */
2823 err = OGR_L_DeleteFeature(modstate->ogr.lyr, fid);
2824
2825 if (err != OGRERR_NONE)
2826 {
2827 return NULL;
2828 }
2829 else
2830 {
2831 return slot;
2832 }
2833 }
2834
2835 static void
ogrEndForeignModify(EState * estate,ResultRelInfo * rinfo)2836 ogrEndForeignModify(EState* estate, ResultRelInfo* rinfo)
2837 {
2838 OgrFdwModifyState* modstate = rinfo->ri_FdwState;
2839
2840 elog(DEBUG2, "ogrEndForeignModify");
2841
2842 ogrFinishConnection(&(modstate->ogr));
2843
2844 return;
2845 }
2846
2847 static int
ogrIsForeignRelUpdatable(Relation rel)2848 ogrIsForeignRelUpdatable(Relation rel)
2849 {
2850 const int readonly = 0;
2851 int foreign_rel_updateable = 0;
2852 TupleDesc td = RelationGetDescr(rel);
2853 OgrConnection ogr;
2854 Oid foreigntableid = RelationGetRelid(rel);
2855
2856 elog(DEBUG2, "ogrIsForeignRelUpdatable");
2857
2858 /* Before we say "yes"... */
2859 /* Does the foreign relation have a "fid" column? */
2860 /* Is that column an integer? */
2861 if (ogrGetFidColumn(td) < 0)
2862 {
2863 elog(NOTICE, "no \"fid\" column in foreign table '%s'", get_rel_name(foreigntableid));
2864 return readonly;
2865 }
2866
2867 /* Is it backed by a writable OGR driver? */
2868 /* Can we open the relation in read/write mode? */
2869 ogr = ogrGetConnectionFromTable(foreigntableid, OGR_UPDATEABLE_TRY);
2870
2871 /* Something in the open process set the readonly flags */
2872 /* Perhaps user has manually set the foreign table option to readonly */
2873 if (ogr.ds_updateable == OGR_UPDATEABLE_FALSE ||
2874 ogr.lyr_updateable == OGR_UPDATEABLE_FALSE)
2875 {
2876 return readonly;
2877 }
2878
2879 /* No data source or layer objects? Readonly */
2880 if (!(ogr.ds && ogr.lyr))
2881 {
2882 return readonly;
2883 }
2884
2885 if (OGR_L_TestCapability(ogr.lyr, OLCRandomWrite))
2886 {
2887 foreign_rel_updateable |= (1 << CMD_UPDATE);
2888 }
2889
2890 if (OGR_L_TestCapability(ogr.lyr, OLCSequentialWrite))
2891 {
2892 foreign_rel_updateable |= (1 << CMD_INSERT);
2893 }
2894
2895 if (OGR_L_TestCapability(ogr.lyr, OLCDeleteFeature))
2896 {
2897 foreign_rel_updateable |= (1 << CMD_DELETE);
2898 }
2899
2900 ogrFinishConnection(&ogr);
2901
2902 return foreign_rel_updateable;
2903 }
2904
2905 #if PG_VERSION_NUM >= 90500
2906
2907 /*
2908 * PostgreSQL 9.5 or above. Import a foreign schema
2909 */
2910 static List*
ogrImportForeignSchema(ImportForeignSchemaStmt * stmt,Oid serverOid)2911 ogrImportForeignSchema(ImportForeignSchemaStmt* stmt, Oid serverOid)
2912 {
2913 List* commands = NIL;
2914 ForeignServer* server;
2915 ListCell* lc;
2916 bool import_all = false;
2917 bool launder_column_names, launder_table_names;
2918 OgrConnection ogr;
2919 int i;
2920 char layer_name[STR_MAX_LEN];
2921 char table_name[STR_MAX_LEN];
2922
2923 /* Are we importing all layers in the OGR datasource? */
2924 import_all = streq(stmt->remote_schema, "ogr_all");
2925
2926 /* Make connection to server */
2927 server = GetForeignServer(serverOid);
2928 ogr = ogrGetConnectionFromServer(serverOid, OGR_UPDATEABLE_FALSE);
2929
2930 /* Launder by default */
2931 launder_column_names = launder_table_names = true;
2932
2933 /* Read user-provided statement laundering options */
2934 foreach (lc, stmt->options)
2935 {
2936 DefElem* def = (DefElem*) lfirst(lc);
2937
2938 if (streq(def->defname, "launder_column_names"))
2939 {
2940 launder_column_names = defGetBoolean(def);
2941 }
2942 else if (streq(def->defname, "launder_table_names"))
2943 {
2944 launder_table_names = defGetBoolean(def);
2945 }
2946 else
2947 ereport(ERROR,
2948 (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
2949 errmsg("invalid option \"%s\"", def->defname)));
2950 }
2951
2952 for (i = 0; i < GDALDatasetGetLayerCount(ogr.ds); i++)
2953 {
2954 bool import_layer = false;
2955 OGRLayerH ogr_lyr = GDALDatasetGetLayer(ogr.ds, i);
2956
2957 if (! ogr_lyr)
2958 {
2959 elog(DEBUG1, "Skipping OGR layer %d, unable to read layer", i);
2960 continue;
2961 }
2962
2963 /* Layer name is never laundered, since it's the link back to OGR */
2964 strncpy(layer_name, OGR_L_GetName(ogr_lyr), STR_MAX_LEN);
2965
2966 /*
2967 * We need to compare against created table names
2968 * because PgSQL does an extra check on CREATE FOREIGN TABLE
2969 */
2970 strncpy(table_name, layer_name, STR_MAX_LEN);
2971 if (launder_table_names)
2972 {
2973 ogrStringLaunder(table_name);
2974 }
2975
2976 /*
2977 * Only include if we are importing "ogr_all" or
2978 * the layer prefix starts with the remote schema
2979 */
2980 import_layer = import_all ||
2981 (strncmp(layer_name, stmt->remote_schema, strlen(stmt->remote_schema)) == 0);
2982
2983 /* Apply restrictions for LIMIT TO and EXCEPT */
2984 if (import_layer && (
2985 stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
2986 stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT))
2987 {
2988 /* Limited list? Assume we are taking no items */
2989 if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO)
2990 {
2991 import_layer = false;
2992 }
2993
2994 /* Check the list for our items */
2995 foreach (lc, stmt->table_list)
2996 {
2997 RangeVar* rv = (RangeVar*) lfirst(lc);
2998 /* Found one! */
2999 if (streq(rv->relname, table_name))
3000 {
3001 if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO)
3002 {
3003 import_layer = true;
3004 }
3005 else
3006 {
3007 import_layer = false;
3008 }
3009
3010 break;
3011 }
3012
3013 }
3014 }
3015
3016 if (import_layer)
3017 {
3018 OGRErr err;
3019 stringbuffer_t buf;
3020 stringbuffer_init(&buf);
3021
3022 err = ogrLayerToSQL(ogr_lyr,
3023 server->servername,
3024 launder_table_names,
3025 launder_column_names,
3026 NULL,
3027 ogrGetGeometryOid() != BYTEAOID,
3028 &buf
3029 );
3030
3031 if (err != OGRERR_NONE)
3032 {
3033 elog(ERROR, "unable to generate IMPORT SQL for '%s'", table_name);
3034 }
3035
3036 commands = lappend(commands, pstrdup(stringbuffer_getstring(&buf)));
3037 stringbuffer_release(&buf);
3038 }
3039 }
3040
3041 elog(NOTICE, "Number of tables to be created %d", list_length(commands));
3042
3043 ogrFinishConnection(&ogr);
3044
3045 return commands;
3046 }
3047
3048 #endif /* PostgreSQL 9.5+ for ogrImportForeignSchema */
3049
3050
3051
3052 #endif /* PostgreSQL 9.3+ version check */
3053
3054