Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * postgres_fdw.c
4 : : * Foreign-data wrapper for remote PostgreSQL servers
5 : : *
6 : : * Portions Copyright (c) 2012-2024, PostgreSQL Global Development Group
7 : : *
8 : : * IDENTIFICATION
9 : : * contrib/postgres_fdw/postgres_fdw.c
10 : : *
11 : : *-------------------------------------------------------------------------
12 : : */
13 : : #include "postgres.h"
14 : :
15 : : #include <limits.h>
16 : :
17 : : #include "access/htup_details.h"
18 : : #include "access/sysattr.h"
19 : : #include "access/table.h"
20 : : #include "catalog/pg_class.h"
21 : : #include "catalog/pg_opfamily.h"
22 : : #include "commands/defrem.h"
23 : : #include "commands/explain.h"
24 : : #include "commands/vacuum.h"
25 : : #include "executor/execAsync.h"
26 : : #include "foreign/fdwapi.h"
27 : : #include "funcapi.h"
28 : : #include "miscadmin.h"
29 : : #include "nodes/makefuncs.h"
30 : : #include "nodes/nodeFuncs.h"
31 : : #include "optimizer/appendinfo.h"
32 : : #include "optimizer/clauses.h"
33 : : #include "optimizer/cost.h"
34 : : #include "optimizer/inherit.h"
35 : : #include "optimizer/optimizer.h"
36 : : #include "optimizer/pathnode.h"
37 : : #include "optimizer/paths.h"
38 : : #include "optimizer/planmain.h"
39 : : #include "optimizer/prep.h"
40 : : #include "optimizer/restrictinfo.h"
41 : : #include "optimizer/tlist.h"
42 : : #include "parser/parsetree.h"
43 : : #include "postgres_fdw.h"
44 : : #include "storage/latch.h"
45 : : #include "utils/builtins.h"
46 : : #include "utils/float.h"
47 : : #include "utils/guc.h"
48 : : #include "utils/lsyscache.h"
49 : : #include "utils/memutils.h"
50 : : #include "utils/rel.h"
51 : : #include "utils/sampling.h"
52 : : #include "utils/selfuncs.h"
53 : :
4070 tgl@sss.pgh.pa.us 54 :CBC 16 : PG_MODULE_MAGIC;
55 : :
56 : : /* Default CPU cost to start up a foreign query. */
57 : : #define DEFAULT_FDW_STARTUP_COST 100.0
58 : :
59 : : /* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */
60 : : #define DEFAULT_FDW_TUPLE_COST 0.2
61 : :
62 : : /* If no remote estimates, assume a sort costs 20% extra */
63 : : #define DEFAULT_FDW_SORT_MULTIPLIER 1.2
64 : :
65 : : /*
66 : : * Indexes of FDW-private information stored in fdw_private lists.
67 : : *
68 : : * These items are indexed with the enum FdwScanPrivateIndex, so an item
69 : : * can be fetched with list_nth(). For example, to get the SELECT statement:
70 : : * sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
71 : : */
72 : : enum FdwScanPrivateIndex
73 : : {
74 : : /* SQL statement to execute remotely (as a String node) */
75 : : FdwScanPrivateSelectSql,
76 : : /* Integer list of attribute numbers retrieved by the SELECT */
77 : : FdwScanPrivateRetrievedAttrs,
78 : : /* Integer representing the desired fetch_size */
79 : : FdwScanPrivateFetchSize,
80 : :
81 : : /*
82 : : * String describing join i.e. names of relations being joined and types
83 : : * of join, added when the scan is join
84 : : */
85 : : FdwScanPrivateRelations,
86 : : };
87 : :
88 : : /*
89 : : * Similarly, this enum describes what's kept in the fdw_private list for
90 : : * a ModifyTable node referencing a postgres_fdw foreign table. We store:
91 : : *
92 : : * 1) INSERT/UPDATE/DELETE statement text to be sent to the remote server
93 : : * 2) Integer list of target attribute numbers for INSERT/UPDATE
94 : : * (NIL for a DELETE)
95 : : * 3) Length till the end of VALUES clause for INSERT
96 : : * (-1 for a DELETE/UPDATE)
97 : : * 4) Boolean flag showing if the remote query has a RETURNING clause
98 : : * 5) Integer list of attribute numbers retrieved by RETURNING, if any
99 : : */
100 : : enum FdwModifyPrivateIndex
101 : : {
102 : : /* SQL statement to execute remotely (as a String node) */
103 : : FdwModifyPrivateUpdateSql,
104 : : /* Integer list of target attribute numbers for INSERT/UPDATE */
105 : : FdwModifyPrivateTargetAttnums,
106 : : /* Length till the end of VALUES clause (as an Integer node) */
107 : : FdwModifyPrivateLen,
108 : : /* has-returning flag (as a Boolean node) */
109 : : FdwModifyPrivateHasReturning,
110 : : /* Integer list of attribute numbers retrieved by RETURNING */
111 : : FdwModifyPrivateRetrievedAttrs,
112 : : };
113 : :
114 : : /*
115 : : * Similarly, this enum describes what's kept in the fdw_private list for
116 : : * a ForeignScan node that modifies a foreign table directly. We store:
117 : : *
118 : : * 1) UPDATE/DELETE statement text to be sent to the remote server
119 : : * 2) Boolean flag showing if the remote query has a RETURNING clause
120 : : * 3) Integer list of attribute numbers retrieved by RETURNING, if any
121 : : * 4) Boolean flag showing if we set the command es_processed
122 : : */
123 : : enum FdwDirectModifyPrivateIndex
124 : : {
125 : : /* SQL statement to execute remotely (as a String node) */
126 : : FdwDirectModifyPrivateUpdateSql,
127 : : /* has-returning flag (as a Boolean node) */
128 : : FdwDirectModifyPrivateHasReturning,
129 : : /* Integer list of attribute numbers retrieved by RETURNING */
130 : : FdwDirectModifyPrivateRetrievedAttrs,
131 : : /* set-processed flag (as a Boolean node) */
132 : : FdwDirectModifyPrivateSetProcessed,
133 : : };
134 : :
135 : : /*
136 : : * Execution state of a foreign scan using postgres_fdw.
137 : : */
138 : : typedef struct PgFdwScanState
139 : : {
140 : : Relation rel; /* relcache entry for the foreign table. NULL
141 : : * for a foreign join scan. */
142 : : TupleDesc tupdesc; /* tuple descriptor of scan */
143 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
144 : :
145 : : /* extracted fdw_private data */
146 : : char *query; /* text of SELECT command */
147 : : List *retrieved_attrs; /* list of retrieved attribute numbers */
148 : :
149 : : /* for remote query execution */
150 : : PGconn *conn; /* connection for the scan */
151 : : PgFdwConnState *conn_state; /* extra per-connection state */
152 : : unsigned int cursor_number; /* quasi-unique ID for my cursor */
153 : : bool cursor_exists; /* have we created the cursor? */
154 : : int numParams; /* number of parameters passed to query */
155 : : FmgrInfo *param_flinfo; /* output conversion functions for them */
156 : : List *param_exprs; /* executable expressions for param values */
157 : : const char **param_values; /* textual values of query parameters */
158 : :
159 : : /* for storing result tuples */
160 : : HeapTuple *tuples; /* array of currently-retrieved tuples */
161 : : int num_tuples; /* # of tuples in array */
162 : : int next_tuple; /* index of next one to return */
163 : :
164 : : /* batch-level state, for optimizing rewinds and avoiding useless fetch */
165 : : int fetch_ct_2; /* Min(# of fetches done, 2) */
166 : : bool eof_reached; /* true if last fetch reached EOF */
167 : :
168 : : /* for asynchronous execution */
169 : : bool async_capable; /* engage asynchronous-capable logic? */
170 : :
171 : : /* working memory contexts */
172 : : MemoryContext batch_cxt; /* context holding current batch of tuples */
173 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
174 : :
175 : : int fetch_size; /* number of tuples per fetch */
176 : : } PgFdwScanState;
177 : :
178 : : /*
179 : : * Execution state of a foreign insert/update/delete operation.
180 : : */
181 : : typedef struct PgFdwModifyState
182 : : {
183 : : Relation rel; /* relcache entry for the foreign table */
184 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
185 : :
186 : : /* for remote query execution */
187 : : PGconn *conn; /* connection for the scan */
188 : : PgFdwConnState *conn_state; /* extra per-connection state */
189 : : char *p_name; /* name of prepared statement, if created */
190 : :
191 : : /* extracted fdw_private data */
192 : : char *query; /* text of INSERT/UPDATE/DELETE command */
193 : : char *orig_query; /* original text of INSERT command */
194 : : List *target_attrs; /* list of target attribute numbers */
195 : : int values_end; /* length up to the end of VALUES */
196 : : int batch_size; /* value of FDW option "batch_size" */
197 : : bool has_returning; /* is there a RETURNING clause? */
198 : : List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
199 : :
200 : : /* info about parameters for prepared statement */
201 : : AttrNumber ctidAttno; /* attnum of input resjunk ctid column */
202 : : int p_nums; /* number of parameters to transmit */
203 : : FmgrInfo *p_flinfo; /* output conversion functions for them */
204 : :
205 : : /* batch operation stuff */
206 : : int num_slots; /* number of slots to insert */
207 : :
208 : : /* working memory context */
209 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
210 : :
211 : : /* for update row movement if subplan result rel */
212 : : struct PgFdwModifyState *aux_fmstate; /* foreign-insert state, if
213 : : * created */
214 : : } PgFdwModifyState;
215 : :
216 : : /*
217 : : * Execution state of a foreign scan that modifies a foreign table directly.
218 : : */
219 : : typedef struct PgFdwDirectModifyState
220 : : {
221 : : Relation rel; /* relcache entry for the foreign table */
222 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
223 : :
224 : : /* extracted fdw_private data */
225 : : char *query; /* text of UPDATE/DELETE command */
226 : : bool has_returning; /* is there a RETURNING clause? */
227 : : List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
228 : : bool set_processed; /* do we set the command es_processed? */
229 : :
230 : : /* for remote query execution */
231 : : PGconn *conn; /* connection for the update */
232 : : PgFdwConnState *conn_state; /* extra per-connection state */
233 : : int numParams; /* number of parameters passed to query */
234 : : FmgrInfo *param_flinfo; /* output conversion functions for them */
235 : : List *param_exprs; /* executable expressions for param values */
236 : : const char **param_values; /* textual values of query parameters */
237 : :
238 : : /* for storing result tuples */
239 : : PGresult *result; /* result for query */
240 : : int num_tuples; /* # of result tuples */
241 : : int next_tuple; /* index of next one to return */
242 : : Relation resultRel; /* relcache entry for the target relation */
243 : : AttrNumber *attnoMap; /* array of attnums of input user columns */
244 : : AttrNumber ctidAttno; /* attnum of input ctid column */
245 : : AttrNumber oidAttno; /* attnum of input oid column */
246 : : bool hasSystemCols; /* are there system columns of resultRel? */
247 : :
248 : : /* working memory context */
249 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
250 : : } PgFdwDirectModifyState;
251 : :
252 : : /*
253 : : * Workspace for analyzing a foreign table.
254 : : */
255 : : typedef struct PgFdwAnalyzeState
256 : : {
257 : : Relation rel; /* relcache entry for the foreign table */
258 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
259 : : List *retrieved_attrs; /* attr numbers retrieved by query */
260 : :
261 : : /* collected sample rows */
262 : : HeapTuple *rows; /* array of size targrows */
263 : : int targrows; /* target # of sample rows */
264 : : int numrows; /* # of sample rows collected */
265 : :
266 : : /* for random sampling */
267 : : double samplerows; /* # of rows fetched */
268 : : double rowstoskip; /* # of rows to skip before next sample */
269 : : ReservoirStateData rstate; /* state for reservoir sampling */
270 : :
271 : : /* working memory contexts */
272 : : MemoryContext anl_cxt; /* context for per-analyze lifespan data */
273 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
274 : : } PgFdwAnalyzeState;
275 : :
276 : : /*
277 : : * This enum describes what's kept in the fdw_private list for a ForeignPath.
278 : : * We store:
279 : : *
280 : : * 1) Boolean flag showing if the remote query has the final sort
281 : : * 2) Boolean flag showing if the remote query has the LIMIT clause
282 : : */
283 : : enum FdwPathPrivateIndex
284 : : {
285 : : /* has-final-sort flag (as a Boolean node) */
286 : : FdwPathPrivateHasFinalSort,
287 : : /* has-limit flag (as a Boolean node) */
288 : : FdwPathPrivateHasLimit,
289 : : };
290 : :
291 : : /* Struct for extra information passed to estimate_path_cost_size() */
292 : : typedef struct
293 : : {
294 : : PathTarget *target;
295 : : bool has_final_sort;
296 : : bool has_limit;
297 : : double limit_tuples;
298 : : int64 count_est;
299 : : int64 offset_est;
300 : : } PgFdwPathExtraData;
301 : :
302 : : /*
303 : : * Identify the attribute where data conversion fails.
304 : : */
305 : : typedef struct ConversionLocation
306 : : {
307 : : AttrNumber cur_attno; /* attribute number being processed, or 0 */
308 : : Relation rel; /* foreign table being processed, or NULL */
309 : : ForeignScanState *fsstate; /* plan node being processed, or NULL */
310 : : } ConversionLocation;
311 : :
312 : : /* Callback argument for ec_member_matches_foreign */
313 : : typedef struct
314 : : {
315 : : Expr *current; /* current expr, or NULL if not yet found */
316 : : List *already_used; /* expressions already dealt with */
317 : : } ec_member_foreign_arg;
318 : :
319 : : /*
320 : : * SQL functions
321 : : */
322 : 11 : PG_FUNCTION_INFO_V1(postgres_fdw_handler);
323 : :
324 : : /*
325 : : * FDW callback routines
326 : : */
327 : : static void postgresGetForeignRelSize(PlannerInfo *root,
328 : : RelOptInfo *baserel,
329 : : Oid foreigntableid);
330 : : static void postgresGetForeignPaths(PlannerInfo *root,
331 : : RelOptInfo *baserel,
332 : : Oid foreigntableid);
333 : : static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
334 : : RelOptInfo *foreignrel,
335 : : Oid foreigntableid,
336 : : ForeignPath *best_path,
337 : : List *tlist,
338 : : List *scan_clauses,
339 : : Plan *outer_plan);
340 : : static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
341 : : static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
342 : : static void postgresReScanForeignScan(ForeignScanState *node);
343 : : static void postgresEndForeignScan(ForeignScanState *node);
344 : : static void postgresAddForeignUpdateTargets(PlannerInfo *root,
345 : : Index rtindex,
346 : : RangeTblEntry *target_rte,
347 : : Relation target_relation);
348 : : static List *postgresPlanForeignModify(PlannerInfo *root,
349 : : ModifyTable *plan,
350 : : Index resultRelation,
351 : : int subplan_index);
352 : : static void postgresBeginForeignModify(ModifyTableState *mtstate,
353 : : ResultRelInfo *resultRelInfo,
354 : : List *fdw_private,
355 : : int subplan_index,
356 : : int eflags);
357 : : static TupleTableSlot *postgresExecForeignInsert(EState *estate,
358 : : ResultRelInfo *resultRelInfo,
359 : : TupleTableSlot *slot,
360 : : TupleTableSlot *planSlot);
361 : : static TupleTableSlot **postgresExecForeignBatchInsert(EState *estate,
362 : : ResultRelInfo *resultRelInfo,
363 : : TupleTableSlot **slots,
364 : : TupleTableSlot **planSlots,
365 : : int *numSlots);
366 : : static int postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo);
367 : : static TupleTableSlot *postgresExecForeignUpdate(EState *estate,
368 : : ResultRelInfo *resultRelInfo,
369 : : TupleTableSlot *slot,
370 : : TupleTableSlot *planSlot);
371 : : static TupleTableSlot *postgresExecForeignDelete(EState *estate,
372 : : ResultRelInfo *resultRelInfo,
373 : : TupleTableSlot *slot,
374 : : TupleTableSlot *planSlot);
375 : : static void postgresEndForeignModify(EState *estate,
376 : : ResultRelInfo *resultRelInfo);
377 : : static void postgresBeginForeignInsert(ModifyTableState *mtstate,
378 : : ResultRelInfo *resultRelInfo);
379 : : static void postgresEndForeignInsert(EState *estate,
380 : : ResultRelInfo *resultRelInfo);
381 : : static int postgresIsForeignRelUpdatable(Relation rel);
382 : : static bool postgresPlanDirectModify(PlannerInfo *root,
383 : : ModifyTable *plan,
384 : : Index resultRelation,
385 : : int subplan_index);
386 : : static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
387 : : static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
388 : : static void postgresEndDirectModify(ForeignScanState *node);
389 : : static void postgresExplainForeignScan(ForeignScanState *node,
390 : : ExplainState *es);
391 : : static void postgresExplainForeignModify(ModifyTableState *mtstate,
392 : : ResultRelInfo *rinfo,
393 : : List *fdw_private,
394 : : int subplan_index,
395 : : ExplainState *es);
396 : : static void postgresExplainDirectModify(ForeignScanState *node,
397 : : ExplainState *es);
398 : : static void postgresExecForeignTruncate(List *rels,
399 : : DropBehavior behavior,
400 : : bool restart_seqs);
401 : : static bool postgresAnalyzeForeignTable(Relation relation,
402 : : AcquireSampleRowsFunc *func,
403 : : BlockNumber *totalpages);
404 : : static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt,
405 : : Oid serverOid);
406 : : static void postgresGetForeignJoinPaths(PlannerInfo *root,
407 : : RelOptInfo *joinrel,
408 : : RelOptInfo *outerrel,
409 : : RelOptInfo *innerrel,
410 : : JoinType jointype,
411 : : JoinPathExtraData *extra);
412 : : static bool postgresRecheckForeignScan(ForeignScanState *node,
413 : : TupleTableSlot *slot);
414 : : static void postgresGetForeignUpperPaths(PlannerInfo *root,
415 : : UpperRelationKind stage,
416 : : RelOptInfo *input_rel,
417 : : RelOptInfo *output_rel,
418 : : void *extra);
419 : : static bool postgresIsForeignPathAsyncCapable(ForeignPath *path);
420 : : static void postgresForeignAsyncRequest(AsyncRequest *areq);
421 : : static void postgresForeignAsyncConfigureWait(AsyncRequest *areq);
422 : : static void postgresForeignAsyncNotify(AsyncRequest *areq);
423 : :
424 : : /*
425 : : * Helper functions
426 : : */
427 : : static void estimate_path_cost_size(PlannerInfo *root,
428 : : RelOptInfo *foreignrel,
429 : : List *param_join_conds,
430 : : List *pathkeys,
431 : : PgFdwPathExtraData *fpextra,
432 : : double *p_rows, int *p_width,
433 : : Cost *p_startup_cost, Cost *p_total_cost);
434 : : static void get_remote_estimate(const char *sql,
435 : : PGconn *conn,
436 : : double *rows,
437 : : int *width,
438 : : Cost *startup_cost,
439 : : Cost *total_cost);
440 : : static void adjust_foreign_grouping_path_cost(PlannerInfo *root,
441 : : List *pathkeys,
442 : : double retrieved_rows,
443 : : double width,
444 : : double limit_tuples,
445 : : Cost *p_startup_cost,
446 : : Cost *p_run_cost);
447 : : static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
448 : : EquivalenceClass *ec, EquivalenceMember *em,
449 : : void *arg);
450 : : static void create_cursor(ForeignScanState *node);
451 : : static void fetch_more_data(ForeignScanState *node);
452 : : static void close_cursor(PGconn *conn, unsigned int cursor_number,
453 : : PgFdwConnState *conn_state);
454 : : static PgFdwModifyState *create_foreign_modify(EState *estate,
455 : : RangeTblEntry *rte,
456 : : ResultRelInfo *resultRelInfo,
457 : : CmdType operation,
458 : : Plan *subplan,
459 : : char *query,
460 : : List *target_attrs,
461 : : int values_end,
462 : : bool has_returning,
463 : : List *retrieved_attrs);
464 : : static TupleTableSlot **execute_foreign_modify(EState *estate,
465 : : ResultRelInfo *resultRelInfo,
466 : : CmdType operation,
467 : : TupleTableSlot **slots,
468 : : TupleTableSlot **planSlots,
469 : : int *numSlots);
470 : : static void prepare_foreign_modify(PgFdwModifyState *fmstate);
471 : : static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
472 : : ItemPointer tupleid,
473 : : TupleTableSlot **slots,
474 : : int numSlots);
475 : : static void store_returning_result(PgFdwModifyState *fmstate,
476 : : TupleTableSlot *slot, PGresult *res);
477 : : static void finish_foreign_modify(PgFdwModifyState *fmstate);
478 : : static void deallocate_query(PgFdwModifyState *fmstate);
479 : : static List *build_remote_returning(Index rtindex, Relation rel,
480 : : List *returningList);
481 : : static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
482 : : static void execute_dml_stmt(ForeignScanState *node);
483 : : static TupleTableSlot *get_returning_data(ForeignScanState *node);
484 : : static void init_returning_filter(PgFdwDirectModifyState *dmstate,
485 : : List *fdw_scan_tlist,
486 : : Index rtindex);
487 : : static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
488 : : ResultRelInfo *resultRelInfo,
489 : : TupleTableSlot *slot,
490 : : EState *estate);
491 : : static void prepare_query_params(PlanState *node,
492 : : List *fdw_exprs,
493 : : int numParams,
494 : : FmgrInfo **param_flinfo,
495 : : List **param_exprs,
496 : : const char ***param_values);
497 : : static void process_query_params(ExprContext *econtext,
498 : : FmgrInfo *param_flinfo,
499 : : List *param_exprs,
500 : : const char **param_values);
501 : : static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
502 : : HeapTuple *rows, int targrows,
503 : : double *totalrows,
504 : : double *totaldeadrows);
505 : : static void analyze_row_processor(PGresult *res, int row,
506 : : PgFdwAnalyzeState *astate);
507 : : static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch);
508 : : static void fetch_more_data_begin(AsyncRequest *areq);
509 : : static void complete_pending_request(AsyncRequest *areq);
510 : : static HeapTuple make_tuple_from_result_row(PGresult *res,
511 : : int row,
512 : : Relation rel,
513 : : AttInMetadata *attinmeta,
514 : : List *retrieved_attrs,
515 : : ForeignScanState *fsstate,
516 : : MemoryContext temp_context);
517 : : static void conversion_error_callback(void *arg);
518 : : static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
519 : : JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
520 : : JoinPathExtraData *extra);
521 : : static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
522 : : Node *havingQual);
523 : : static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
524 : : RelOptInfo *rel);
525 : : static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
526 : : static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
527 : : Path *epq_path, List *restrictlist);
528 : : static void add_foreign_grouping_paths(PlannerInfo *root,
529 : : RelOptInfo *input_rel,
530 : : RelOptInfo *grouped_rel,
531 : : GroupPathExtraData *extra);
532 : : static void add_foreign_ordered_paths(PlannerInfo *root,
533 : : RelOptInfo *input_rel,
534 : : RelOptInfo *ordered_rel);
535 : : static void add_foreign_final_paths(PlannerInfo *root,
536 : : RelOptInfo *input_rel,
537 : : RelOptInfo *final_rel,
538 : : FinalPathExtraData *extra);
539 : : static void apply_server_options(PgFdwRelationInfo *fpinfo);
540 : : static void apply_table_options(PgFdwRelationInfo *fpinfo);
541 : : static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
542 : : const PgFdwRelationInfo *fpinfo_o,
543 : : const PgFdwRelationInfo *fpinfo_i);
544 : : static int get_batch_size_option(Relation rel);
545 : :
546 : :
547 : : /*
548 : : * Foreign-data wrapper handler function: return a struct with pointers
549 : : * to my callback routines.
550 : : */
551 : : Datum
552 : 645 : postgres_fdw_handler(PG_FUNCTION_ARGS)
553 : : {
554 : 645 : FdwRoutine *routine = makeNode(FdwRoutine);
555 : :
556 : : /* Functions for scanning foreign tables */
557 : 645 : routine->GetForeignRelSize = postgresGetForeignRelSize;
558 : 645 : routine->GetForeignPaths = postgresGetForeignPaths;
559 : 645 : routine->GetForeignPlan = postgresGetForeignPlan;
560 : 645 : routine->BeginForeignScan = postgresBeginForeignScan;
561 : 645 : routine->IterateForeignScan = postgresIterateForeignScan;
562 : 645 : routine->ReScanForeignScan = postgresReScanForeignScan;
563 : 645 : routine->EndForeignScan = postgresEndForeignScan;
564 : :
565 : : /* Functions for updating foreign tables */
4053 566 : 645 : routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets;
567 : 645 : routine->PlanForeignModify = postgresPlanForeignModify;
568 : 645 : routine->BeginForeignModify = postgresBeginForeignModify;
569 : 645 : routine->ExecForeignInsert = postgresExecForeignInsert;
1180 tomas.vondra@postgre 570 : 645 : routine->ExecForeignBatchInsert = postgresExecForeignBatchInsert;
571 : 645 : routine->GetForeignModifyBatchSize = postgresGetForeignModifyBatchSize;
4053 tgl@sss.pgh.pa.us 572 : 645 : routine->ExecForeignUpdate = postgresExecForeignUpdate;
573 : 645 : routine->ExecForeignDelete = postgresExecForeignDelete;
574 : 645 : routine->EndForeignModify = postgresEndForeignModify;
2200 rhaas@postgresql.org 575 : 645 : routine->BeginForeignInsert = postgresBeginForeignInsert;
576 : 645 : routine->EndForeignInsert = postgresEndForeignInsert;
3959 tgl@sss.pgh.pa.us 577 : 645 : routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
2949 rhaas@postgresql.org 578 : 645 : routine->PlanDirectModify = postgresPlanDirectModify;
579 : 645 : routine->BeginDirectModify = postgresBeginDirectModify;
580 : 645 : routine->IterateDirectModify = postgresIterateDirectModify;
581 : 645 : routine->EndDirectModify = postgresEndDirectModify;
582 : :
583 : : /* Function for EvalPlanQual rechecks */
2987 584 : 645 : routine->RecheckForeignScan = postgresRecheckForeignScan;
585 : : /* Support functions for EXPLAIN */
4053 tgl@sss.pgh.pa.us 586 : 645 : routine->ExplainForeignScan = postgresExplainForeignScan;
587 : 645 : routine->ExplainForeignModify = postgresExplainForeignModify;
2949 rhaas@postgresql.org 588 : 645 : routine->ExplainDirectModify = postgresExplainDirectModify;
589 : :
590 : : /* Support function for TRUNCATE */
1102 fujii@postgresql.org 591 : 645 : routine->ExecForeignTruncate = postgresExecForeignTruncate;
592 : :
593 : : /* Support functions for ANALYZE */
4070 tgl@sss.pgh.pa.us 594 : 645 : routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
595 : :
596 : : /* Support functions for IMPORT FOREIGN SCHEMA */
3566 597 : 645 : routine->ImportForeignSchema = postgresImportForeignSchema;
598 : :
599 : : /* Support functions for join push-down */
2987 rhaas@postgresql.org 600 : 645 : routine->GetForeignJoinPaths = postgresGetForeignJoinPaths;
601 : :
602 : : /* Support functions for upper relation push-down */
2732 603 : 645 : routine->GetForeignUpperPaths = postgresGetForeignUpperPaths;
604 : :
605 : : /* Support functions for asynchronous execution */
1110 efujita@postgresql.o 606 : 645 : routine->IsForeignPathAsyncCapable = postgresIsForeignPathAsyncCapable;
607 : 645 : routine->ForeignAsyncRequest = postgresForeignAsyncRequest;
608 : 645 : routine->ForeignAsyncConfigureWait = postgresForeignAsyncConfigureWait;
609 : 645 : routine->ForeignAsyncNotify = postgresForeignAsyncNotify;
610 : :
4070 tgl@sss.pgh.pa.us 611 : 645 : PG_RETURN_POINTER(routine);
612 : : }
613 : :
614 : : /*
615 : : * postgresGetForeignRelSize
616 : : * Estimate # of rows and width of the result of the scan
617 : : *
618 : : * We should consider the effect of all baserestrictinfo clauses here, but
619 : : * not any join clauses.
620 : : */
621 : : static void
622 : 1143 : postgresGetForeignRelSize(PlannerInfo *root,
623 : : RelOptInfo *baserel,
624 : : Oid foreigntableid)
625 : : {
626 : : PgFdwRelationInfo *fpinfo;
627 : : ListCell *lc;
628 : :
629 : : /*
630 : : * We use PgFdwRelationInfo to pass various information to subsequent
631 : : * functions.
632 : : */
4053 633 : 1143 : fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
4042 634 : 1143 : baserel->fdw_private = (void *) fpinfo;
635 : :
636 : : /* Base foreign tables need to be pushed down always. */
2987 rhaas@postgresql.org 637 : 1143 : fpinfo->pushdown_safe = true;
638 : :
639 : : /* Look up foreign-table catalog info. */
4042 tgl@sss.pgh.pa.us 640 : 1143 : fpinfo->table = GetForeignTable(foreigntableid);
641 : 1143 : fpinfo->server = GetForeignServer(fpinfo->table->serverid);
642 : :
643 : : /*
644 : : * Extract user-settable option values. Note that per-table settings of
645 : : * use_remote_estimate, fetch_size and async_capable override per-server
646 : : * settings of them, respectively.
647 : : */
648 : 1143 : fpinfo->use_remote_estimate = false;
649 : 1143 : fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
650 : 1143 : fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
3085 651 : 1143 : fpinfo->shippable_extensions = NIL;
2993 rhaas@postgresql.org 652 : 1143 : fpinfo->fetch_size = 100;
1110 efujita@postgresql.o 653 : 1143 : fpinfo->async_capable = false;
654 : :
2547 peter_e@gmx.net 655 : 1143 : apply_server_options(fpinfo);
656 : 1143 : apply_table_options(fpinfo);
657 : :
658 : : /*
659 : : * If the table or the server is configured to use remote estimates,
660 : : * identify which user to do remote access as during planning. This
661 : : * should match what ExecCheckPermissions() does. If we fail due to lack
662 : : * of permissions, the query would have failed at runtime anyway.
663 : : */
4042 tgl@sss.pgh.pa.us 664 [ + + ]: 1143 : if (fpinfo->use_remote_estimate)
665 : : {
666 : : Oid userid;
667 : :
501 alvherre@alvh.no-ip. 668 [ + + ]: 294 : userid = OidIsValid(baserel->userid) ? baserel->userid : GetUserId();
4042 tgl@sss.pgh.pa.us 669 : 294 : fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
670 : : }
671 : : else
672 : 849 : fpinfo->user = NULL;
673 : :
674 : : /*
675 : : * Identify which baserestrictinfo clauses can be sent to the remote
676 : : * server and which can't.
677 : : */
3691 678 : 1141 : classifyConditions(root, baserel, baserel->baserestrictinfo,
679 : : &fpinfo->remote_conds, &fpinfo->local_conds);
680 : :
681 : : /*
682 : : * Identify which attributes will need to be retrieved from the remote
683 : : * server. These include all attrs needed for joins or final output, plus
684 : : * all attrs used in the local_conds. (Note: if we end up using a
685 : : * parameterized scan, it's possible that some of the join clauses will be
686 : : * sent to the remote and thus we wouldn't really need to retrieve the
687 : : * columns used in them. Doesn't seem worth detecting that case though.)
688 : : */
4042 689 : 1141 : fpinfo->attrs_used = NULL;
2953 690 : 1141 : pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid,
691 : : &fpinfo->attrs_used);
4042 692 [ + + + + : 1216 : foreach(lc, fpinfo->local_conds)
+ + ]
693 : : {
2560 694 : 75 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
695 : :
4053 696 : 75 : pull_varattnos((Node *) rinfo->clause, baserel->relid,
697 : : &fpinfo->attrs_used);
698 : : }
699 : :
700 : : /*
701 : : * Compute the selectivity and cost of the local_conds, so we don't have
702 : : * to do it over again for each path. The best we can do for these
703 : : * conditions is to estimate selectivity on the basis of local statistics.
704 : : */
4042 705 : 2282 : fpinfo->local_conds_sel = clauselist_selectivity(root,
706 : : fpinfo->local_conds,
707 : 1141 : baserel->relid,
708 : : JOIN_INNER,
709 : : NULL);
710 : :
711 : 1141 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
712 : :
713 : : /*
714 : : * Set # of retrieved rows and cached relation costs to some negative
715 : : * value, so that we can detect when they are set to some sensible values,
716 : : * during one (usually the first) of the calls to estimate_path_cost_size.
717 : : */
1766 efujita@postgresql.o 718 : 1141 : fpinfo->retrieved_rows = -1;
2958 rhaas@postgresql.org 719 : 1141 : fpinfo->rel_startup_cost = -1;
720 : 1141 : fpinfo->rel_total_cost = -1;
721 : :
722 : : /*
723 : : * If the table or the server is configured to use remote estimates,
724 : : * connect to the foreign server and execute EXPLAIN to estimate the
725 : : * number of rows selected by the restriction clauses, as well as the
726 : : * average row width. Otherwise, estimate using whatever statistics we
727 : : * have locally, in a way similar to ordinary tables.
728 : : */
4042 tgl@sss.pgh.pa.us 729 [ + + ]: 1141 : if (fpinfo->use_remote_estimate)
730 : : {
731 : : /*
732 : : * Get cost/size estimates with help of remote server. Save the
733 : : * values in fpinfo so we don't need to do it again to generate the
734 : : * basic foreign path.
735 : : */
1839 efujita@postgresql.o 736 : 292 : estimate_path_cost_size(root, baserel, NIL, NIL, NULL,
737 : : &fpinfo->rows, &fpinfo->width,
738 : : &fpinfo->startup_cost, &fpinfo->total_cost);
739 : :
740 : : /* Report estimated baserel size to planner. */
4042 tgl@sss.pgh.pa.us 741 : 292 : baserel->rows = fpinfo->rows;
2953 742 : 292 : baserel->reltarget->width = fpinfo->width;
743 : : }
744 : : else
745 : : {
746 : : /*
747 : : * If the foreign table has never been ANALYZEd, it will have
748 : : * reltuples < 0, meaning "unknown". We can't do much if we're not
749 : : * allowed to consult the remote server, but we can use a hack similar
750 : : * to plancat.c's treatment of empty relations: use a minimum size
751 : : * estimate of 10 pages, and divide by the column-datatype-based width
752 : : * estimate to get the corresponding number of tuples.
753 : : */
1323 754 [ + + ]: 849 : if (baserel->tuples < 0)
755 : : {
4069 756 : 275 : baserel->pages = 10;
4070 757 : 275 : baserel->tuples =
2953 758 : 275 : (10 * BLCKSZ) / (baserel->reltarget->width +
759 : : MAXALIGN(SizeofHeapTupleHeader));
760 : : }
761 : :
762 : : /* Estimate baserel size as best we can with local statistics. */
4070 763 : 849 : set_baserel_size_estimates(root, baserel);
764 : :
765 : : /* Fill in basically-bogus cost estimates for use later. */
1839 efujita@postgresql.o 766 : 849 : estimate_path_cost_size(root, baserel, NIL, NIL, NULL,
767 : : &fpinfo->rows, &fpinfo->width,
768 : : &fpinfo->startup_cost, &fpinfo->total_cost);
769 : : }
770 : :
771 : : /*
772 : : * fpinfo->relation_name gets the numeric rangetable index of the foreign
773 : : * table RTE. (If this query gets EXPLAIN'd, we'll convert that to a
774 : : * human-readable string at that time.)
775 : : */
1595 tgl@sss.pgh.pa.us 776 : 1141 : fpinfo->relation_name = psprintf("%u", baserel->relid);
777 : :
778 : : /* No outer and inner relations. */
2586 rhaas@postgresql.org 779 : 1141 : fpinfo->make_outerrel_subquery = false;
780 : 1141 : fpinfo->make_innerrel_subquery = false;
781 : 1141 : fpinfo->lower_subquery_rels = NULL;
131 akorotkov@postgresql 782 :GNC 1141 : fpinfo->hidden_subquery_rels = NULL;
783 : : /* Set the relation index. */
2586 rhaas@postgresql.org 784 :CBC 1141 : fpinfo->relation_index = baserel->relid;
4070 tgl@sss.pgh.pa.us 785 : 1141 : }
786 : :
787 : : /*
788 : : * get_useful_ecs_for_relation
789 : : * Determine which EquivalenceClasses might be involved in useful
790 : : * orderings of this relation.
791 : : *
792 : : * This function is in some respects a mirror image of the core function
793 : : * pathkeys_useful_for_merging: for a regular table, we know what indexes
794 : : * we have and want to test whether any of them are useful. For a foreign
795 : : * table, we don't know what indexes are present on the remote side but
796 : : * want to speculate about which ones we'd like to use if they existed.
797 : : *
798 : : * This function returns a list of potentially-useful equivalence classes,
799 : : * but it does not guarantee that an EquivalenceMember exists which contains
800 : : * Vars only from the given relation. For example, given ft1 JOIN t1 ON
801 : : * ft1.x + t1.x = 0, this function will say that the equivalence class
802 : : * containing ft1.x + t1.x is potentially useful. Supposing ft1 is remote and
803 : : * t1 is local (or on a different server), it will turn out that no useful
804 : : * ORDER BY clause can be generated. It's not our job to figure that out
805 : : * here; we're only interested in identifying relevant ECs.
806 : : */
807 : : static List *
3036 rhaas@postgresql.org 808 : 511 : get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel)
809 : : {
810 : 511 : List *useful_eclass_list = NIL;
811 : : ListCell *lc;
812 : : Relids relids;
813 : :
814 : : /*
815 : : * First, consider whether any active EC is potentially useful for a merge
816 : : * join against this relation.
817 : : */
818 [ + + ]: 511 : if (rel->has_eclass_joins)
819 : : {
820 [ + - + + : 634 : foreach(lc, root->eq_classes)
+ + ]
821 : : {
822 : 439 : EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc);
823 : :
824 [ + + ]: 439 : if (eclass_useful_for_merging(root, cur_ec, rel))
825 : 241 : useful_eclass_list = lappend(useful_eclass_list, cur_ec);
826 : : }
827 : : }
828 : :
829 : : /*
830 : : * Next, consider whether there are any non-EC derivable join clauses that
831 : : * are merge-joinable. If the joininfo list is empty, we can exit
832 : : * quickly.
833 : : */
834 [ + + ]: 511 : if (rel->joininfo == NIL)
835 : 377 : return useful_eclass_list;
836 : :
837 : : /* If this is a child rel, we must use the topmost parent rel to search. */
2568 838 [ + + + - : 134 : if (IS_OTHER_REL(rel))
- + ]
839 : : {
840 [ - + ]: 20 : Assert(!bms_is_empty(rel->top_parent_relids));
841 : 20 : relids = rel->top_parent_relids;
842 : : }
843 : : else
3036 844 : 114 : relids = rel->relids;
845 : :
846 : : /* Check each join clause in turn. */
847 [ + - + + : 329 : foreach(lc, rel->joininfo)
+ + ]
848 : : {
849 : 195 : RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
850 : :
851 : : /* Consider only mergejoinable clauses */
852 [ + + ]: 195 : if (restrictinfo->mergeopfamilies == NIL)
853 : 14 : continue;
854 : :
855 : : /* Make sure we've got canonical ECs. */
856 : 181 : update_mergeclause_eclasses(root, restrictinfo);
857 : :
858 : : /*
859 : : * restrictinfo->mergeopfamilies != NIL is sufficient to guarantee
860 : : * that left_ec and right_ec will be initialized, per comments in
861 : : * distribute_qual_to_rels.
862 : : *
863 : : * We want to identify which side of this merge-joinable clause
864 : : * contains columns from the relation produced by this RelOptInfo. We
865 : : * test for overlap, not containment, because there could be extra
866 : : * relations on either side. For example, suppose we've got something
867 : : * like ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y) LEFT JOIN D ON
868 : : * A.y = D.y. The input rel might be the joinrel between A and B, and
869 : : * we'll consider the join clause A.y = D.y. relids contains a
870 : : * relation not involved in the join class (B) and the equivalence
871 : : * class for the left-hand side of the clause contains a relation not
872 : : * involved in the input rel (C). Despite the fact that we have only
873 : : * overlap and not containment in either direction, A.y is potentially
874 : : * useful as a sort column.
875 : : *
876 : : * Note that it's even possible that relids overlaps neither side of
877 : : * the join clause. For example, consider A LEFT JOIN B ON A.x = B.x
878 : : * AND A.x = 1. The clause A.x = 1 will appear in B's joininfo list,
879 : : * but overlaps neither side of B. In that case, we just skip this
880 : : * join clause, since it doesn't suggest a useful sort order for this
881 : : * relation.
882 : : */
2890 883 [ + + ]: 181 : if (bms_overlap(relids, restrictinfo->right_ec->ec_relids))
3036 884 : 82 : useful_eclass_list = list_append_unique_ptr(useful_eclass_list,
2489 tgl@sss.pgh.pa.us 885 : 82 : restrictinfo->right_ec);
2890 rhaas@postgresql.org 886 [ + + ]: 99 : else if (bms_overlap(relids, restrictinfo->left_ec->ec_relids))
3036 887 : 90 : useful_eclass_list = list_append_unique_ptr(useful_eclass_list,
2489 tgl@sss.pgh.pa.us 888 : 90 : restrictinfo->left_ec);
889 : : }
890 : :
3036 rhaas@postgresql.org 891 : 134 : return useful_eclass_list;
892 : : }
893 : :
894 : : /*
895 : : * get_useful_pathkeys_for_relation
896 : : * Determine which orderings of a relation might be useful.
897 : : *
898 : : * Getting data in sorted order can be useful either because the requested
899 : : * order matches the final output ordering for the overall query we're
900 : : * planning, or because it enables an efficient merge join. Here, we try
901 : : * to figure out which pathkeys to consider.
902 : : */
903 : : static List *
904 : 1448 : get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
905 : : {
906 : 1448 : List *useful_pathkeys_list = NIL;
907 : : List *useful_eclass_list;
908 : 1448 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
909 : 1448 : EquivalenceClass *query_ec = NULL;
910 : : ListCell *lc;
911 : :
912 : : /*
913 : : * Pushing the query_pathkeys to the remote server is always worth
914 : : * considering, because it might let us avoid a local sort.
915 : : */
1839 efujita@postgresql.o 916 : 1448 : fpinfo->qp_is_pushdown_safe = false;
3036 rhaas@postgresql.org 917 [ + + ]: 1448 : if (root->query_pathkeys)
918 : : {
919 : 590 : bool query_pathkeys_ok = true;
920 : :
921 [ + - + + : 1124 : foreach(lc, root->query_pathkeys)
+ + ]
922 : : {
923 : 759 : PathKey *pathkey = (PathKey *) lfirst(lc);
924 : :
925 : : /*
926 : : * The planner and executor don't have any clever strategy for
927 : : * taking data sorted by a prefix of the query's pathkeys and
928 : : * getting it to be sorted by all of those pathkeys. We'll just
929 : : * end up resorting the entire data set. So, unless we can push
930 : : * down all of the query pathkeys, forget it.
931 : : */
745 tgl@sss.pgh.pa.us 932 [ + + ]: 759 : if (!is_foreign_pathkey(root, rel, pathkey))
933 : : {
3036 rhaas@postgresql.org 934 : 225 : query_pathkeys_ok = false;
935 : 225 : break;
936 : : }
937 : : }
938 : :
939 [ + + ]: 590 : if (query_pathkeys_ok)
940 : : {
941 : 365 : useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
1839 efujita@postgresql.o 942 : 365 : fpinfo->qp_is_pushdown_safe = true;
943 : : }
944 : : }
945 : :
946 : : /*
947 : : * Even if we're not using remote estimates, having the remote side do the
948 : : * sort generally won't be any worse than doing it locally, and it might
949 : : * be much better if the remote side can generate data in the right order
950 : : * without needing a sort at all. However, what we're going to do next is
951 : : * try to generate pathkeys that seem promising for possible merge joins,
952 : : * and that's more speculative. A wrong choice might hurt quite a bit, so
953 : : * bail out if we can't use remote estimates.
954 : : */
3036 rhaas@postgresql.org 955 [ + + ]: 1448 : if (!fpinfo->use_remote_estimate)
956 : 937 : return useful_pathkeys_list;
957 : :
958 : : /* Get the list of interesting EquivalenceClasses. */
959 : 511 : useful_eclass_list = get_useful_ecs_for_relation(root, rel);
960 : :
961 : : /* Extract unique EC for query, if any, so we don't consider it again. */
962 [ + + ]: 511 : if (list_length(root->query_pathkeys) == 1)
963 : : {
964 : 165 : PathKey *query_pathkey = linitial(root->query_pathkeys);
965 : :
966 : 165 : query_ec = query_pathkey->pk_eclass;
967 : : }
968 : :
969 : : /*
970 : : * As a heuristic, the only pathkeys we consider here are those of length
971 : : * one. It's surely possible to consider more, but since each one we
972 : : * choose to consider will generate a round-trip to the remote side, we
973 : : * need to be a bit cautious here. It would sure be nice to have a local
974 : : * cache of information about remote index definitions...
975 : : */
976 [ + + + + : 901 : foreach(lc, useful_eclass_list)
+ + ]
977 : : {
978 : 390 : EquivalenceClass *cur_ec = lfirst(lc);
979 : : PathKey *pathkey;
980 : :
981 : : /* If redundant with what we did above, skip it. */
982 [ + + ]: 390 : if (cur_ec == query_ec)
983 : 87 : continue;
984 : :
985 : : /* Can't push down the sort if the EC's opfamily is not shippable. */
745 tgl@sss.pgh.pa.us 986 [ - + ]: 359 : if (!is_shippable(linitial_oid(cur_ec->ec_opfamilies),
987 : : OperatorFamilyRelationId, fpinfo))
745 tgl@sss.pgh.pa.us 988 :UBC 0 : continue;
989 : :
990 : : /* If no pushable expression for this rel, skip it. */
745 tgl@sss.pgh.pa.us 991 [ + + ]:CBC 359 : if (find_em_for_rel(root, cur_ec, rel) == NULL)
3036 rhaas@postgresql.org 992 : 56 : continue;
993 : :
994 : : /* Looks like we can generate a pathkey, so let's do it. */
995 : 303 : pathkey = make_canonical_pathkey(root, cur_ec,
996 : 303 : linitial_oid(cur_ec->ec_opfamilies),
997 : : BTLessStrategyNumber,
998 : : false);
999 : 303 : useful_pathkeys_list = lappend(useful_pathkeys_list,
1000 : 303 : list_make1(pathkey));
1001 : : }
1002 : :
1003 : 511 : return useful_pathkeys_list;
1004 : : }
1005 : :
1006 : : /*
1007 : : * postgresGetForeignPaths
1008 : : * Create possible scan paths for a scan on the foreign table
1009 : : */
1010 : : static void
4070 tgl@sss.pgh.pa.us 1011 : 1141 : postgresGetForeignPaths(PlannerInfo *root,
1012 : : RelOptInfo *baserel,
1013 : : Oid foreigntableid)
1014 : : {
1015 : 1141 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
1016 : : ForeignPath *path;
1017 : : List *ppi_list;
1018 : : ListCell *lc;
1019 : :
1020 : : /*
1021 : : * Create simplest ForeignScan path node and add it to baserel. This path
1022 : : * corresponds to SeqScan path of regular tables (though depending on what
1023 : : * baserestrict conditions we were able to send to remote, there might
1024 : : * actually be an indexscan happening there). We already did all the work
1025 : : * to estimate cost and size of this path.
1026 : : *
1027 : : * Although this path uses no join clauses, it could still have required
1028 : : * parameterization due to LATERAL refs in its tlist.
1029 : : */
4042 1030 : 1141 : path = create_foreignscan_path(root, baserel,
1031 : : NULL, /* default pathtarget */
1032 : : fpinfo->rows,
1033 : : fpinfo->startup_cost,
1034 : : fpinfo->total_cost,
1035 : : NIL, /* no pathkeys */
1036 : : baserel->lateral_relids,
1037 : : NULL, /* no extra plan */
1038 : : NIL, /* no fdw_restrictinfo list */
1039 : : NIL); /* no fdw_private list */
1040 : 1141 : add_path(baserel, (Path *) path);
1041 : :
1042 : : /* Add paths with pathkeys */
243 efujita@postgresql.o 1043 :GNC 1141 : add_paths_with_pathkeys_for_rel(root, baserel, NULL, NIL);
1044 : :
1045 : : /*
1046 : : * If we're not using remote estimates, stop here. We have no way to
1047 : : * estimate whether any join clauses would be worth sending across, so
1048 : : * don't bother building parameterized paths.
1049 : : */
4042 tgl@sss.pgh.pa.us 1050 [ + + ]:CBC 1141 : if (!fpinfo->use_remote_estimate)
1051 : 849 : return;
1052 : :
1053 : : /*
1054 : : * Thumb through all join clauses for the rel to identify which outer
1055 : : * relations could supply one or more safe-to-send-to-remote join clauses.
1056 : : * We'll build a parameterized path for each such outer relation.
1057 : : *
1058 : : * It's convenient to manage this by representing each candidate outer
1059 : : * relation by the ParamPathInfo node for it. We can then use the
1060 : : * ppi_clauses list in the ParamPathInfo node directly as a list of the
1061 : : * interesting join clauses for that rel. This takes care of the
1062 : : * possibility that there are multiple safe join clauses for such a rel,
1063 : : * and also ensures that we account for unsafe join clauses that we'll
1064 : : * still have to enforce locally (since the parameterized-path machinery
1065 : : * insists that we handle all movable clauses).
1066 : : */
3691 1067 : 292 : ppi_list = NIL;
4042 1068 [ + + + + : 429 : foreach(lc, baserel->joininfo)
+ + ]
1069 : : {
1070 : 137 : RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
1071 : : Relids required_outer;
1072 : : ParamPathInfo *param_info;
1073 : :
1074 : : /* Check if clause can be moved to this rel */
3893 1075 [ + + ]: 137 : if (!join_clause_is_movable_to(rinfo, baserel))
4042 1076 : 94 : continue;
1077 : :
1078 : : /* See if it is safe to send to remote */
1079 [ + + ]: 43 : if (!is_foreign_expr(root, baserel, rinfo->clause))
1080 : 7 : continue;
1081 : :
1082 : : /* Calculate required outer rels for the resulting path */
1083 : 36 : required_outer = bms_union(rinfo->clause_relids,
1084 : 36 : baserel->lateral_relids);
1085 : : /* We do not want the foreign rel itself listed in required_outer */
1086 : 36 : required_outer = bms_del_member(required_outer, baserel->relid);
1087 : :
1088 : : /*
1089 : : * required_outer probably can't be empty here, but if it were, we
1090 : : * couldn't make a parameterized path.
1091 : : */
1092 [ - + ]: 36 : if (bms_is_empty(required_outer))
3691 tgl@sss.pgh.pa.us 1093 :UBC 0 : continue;
1094 : :
1095 : : /* Get the ParamPathInfo */
3691 tgl@sss.pgh.pa.us 1096 :CBC 36 : param_info = get_baserel_parampathinfo(root, baserel,
1097 : : required_outer);
1098 [ - + ]: 36 : Assert(param_info != NULL);
1099 : :
1100 : : /*
1101 : : * Add it to list unless we already have it. Testing pointer equality
1102 : : * is OK since get_baserel_parampathinfo won't make duplicates.
1103 : : */
1104 : 36 : ppi_list = list_append_unique_ptr(ppi_list, param_info);
1105 : : }
1106 : :
1107 : : /*
1108 : : * The above scan examined only "generic" join clauses, not those that
1109 : : * were absorbed into EquivalenceClauses. See if we can make anything out
1110 : : * of EquivalenceClauses.
1111 : : */
4042 1112 [ + + ]: 292 : if (baserel->has_eclass_joins)
1113 : : {
1114 : : /*
1115 : : * We repeatedly scan the eclass list looking for column references
1116 : : * (or expressions) belonging to the foreign rel. Each time we find
1117 : : * one, we generate a list of equivalence joinclauses for it, and then
1118 : : * see if any are safe to send to the remote. Repeat till there are
1119 : : * no more candidate EC members.
1120 : : */
1121 : : ec_member_foreign_arg arg;
1122 : :
1123 : 127 : arg.already_used = NIL;
1124 : : for (;;)
1125 : 139 : {
1126 : : List *clauses;
1127 : :
1128 : : /* Make clauses, skipping any that join to lateral_referencers */
1129 : 266 : arg.current = NULL;
1130 : 266 : clauses = generate_implied_equalities_for_column(root,
1131 : : baserel,
1132 : : ec_member_matches_foreign,
1133 : : (void *) &arg,
1134 : : baserel->lateral_referencers);
1135 : :
1136 : : /* Done if there are no more expressions in the foreign rel */
1137 [ + + ]: 266 : if (arg.current == NULL)
1138 : : {
1139 [ - + ]: 127 : Assert(clauses == NIL);
1140 : 127 : break;
1141 : : }
1142 : :
1143 : : /* Scan the extracted join clauses */
1144 [ + + + + : 304 : foreach(lc, clauses)
+ + ]
1145 : : {
1146 : 165 : RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
1147 : : Relids required_outer;
1148 : : ParamPathInfo *param_info;
1149 : :
1150 : : /* Check if clause can be moved to this rel */
3893 1151 [ - + ]: 165 : if (!join_clause_is_movable_to(rinfo, baserel))
4042 tgl@sss.pgh.pa.us 1152 :UBC 0 : continue;
1153 : :
1154 : : /* See if it is safe to send to remote */
4042 tgl@sss.pgh.pa.us 1155 [ + + ]:CBC 165 : if (!is_foreign_expr(root, baserel, rinfo->clause))
1156 : 7 : continue;
1157 : :
1158 : : /* Calculate required outer rels for the resulting path */
1159 : 158 : required_outer = bms_union(rinfo->clause_relids,
1160 : 158 : baserel->lateral_relids);
1161 : 158 : required_outer = bms_del_member(required_outer, baserel->relid);
1162 [ - + ]: 158 : if (bms_is_empty(required_outer))
3691 tgl@sss.pgh.pa.us 1163 :UBC 0 : continue;
1164 : :
1165 : : /* Get the ParamPathInfo */
3691 tgl@sss.pgh.pa.us 1166 :CBC 158 : param_info = get_baserel_parampathinfo(root, baserel,
1167 : : required_outer);
1168 [ - + ]: 158 : Assert(param_info != NULL);
1169 : :
1170 : : /* Add it to list unless we already have it */
1171 : 158 : ppi_list = list_append_unique_ptr(ppi_list, param_info);
1172 : : }
1173 : :
1174 : : /* Try again, now ignoring the expression we found this time */
4042 1175 : 139 : arg.already_used = lappend(arg.already_used, arg.current);
1176 : : }
1177 : : }
1178 : :
1179 : : /*
1180 : : * Now build a path for each useful outer relation.
1181 : : */
3691 1182 [ + + + + : 476 : foreach(lc, ppi_list)
+ + ]
1183 : : {
1184 : 184 : ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
1185 : : double rows;
1186 : : int width;
1187 : : Cost startup_cost;
1188 : : Cost total_cost;
1189 : :
1190 : : /* Get a cost estimate from the remote */
1191 : 184 : estimate_path_cost_size(root, baserel,
1192 : : param_info->ppi_clauses, NIL, NULL,
1193 : : &rows, &width,
1194 : : &startup_cost, &total_cost);
1195 : :
1196 : : /*
1197 : : * ppi_rows currently won't get looked at by anything, but still we
1198 : : * may as well ensure that it matches our idea of the rowcount.
1199 : : */
1200 : 184 : param_info->ppi_rows = rows;
1201 : :
1202 : : /* Make the path */
1203 : 184 : path = create_foreignscan_path(root, baserel,
1204 : : NULL, /* default pathtarget */
1205 : : rows,
1206 : : startup_cost,
1207 : : total_cost,
1208 : : NIL, /* no pathkeys */
1209 : : param_info->ppi_req_outer,
1210 : : NULL,
1211 : : NIL, /* no fdw_restrictinfo list */
1212 : : NIL); /* no fdw_private list */
1213 : 184 : add_path(baserel, (Path *) path);
1214 : : }
1215 : : }
1216 : :
1217 : : /*
1218 : : * postgresGetForeignPlan
1219 : : * Create ForeignScan plan node which implements selected best path
1220 : : */
1221 : : static ForeignScan *
4070 1222 : 956 : postgresGetForeignPlan(PlannerInfo *root,
1223 : : RelOptInfo *foreignrel,
1224 : : Oid foreigntableid,
1225 : : ForeignPath *best_path,
1226 : : List *tlist,
1227 : : List *scan_clauses,
1228 : : Plan *outer_plan)
1229 : : {
2987 rhaas@postgresql.org 1230 : 956 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
1231 : : Index scan_relid;
1232 : : List *fdw_private;
3104 1233 : 956 : List *remote_exprs = NIL;
4070 tgl@sss.pgh.pa.us 1234 : 956 : List *local_exprs = NIL;
4042 1235 : 956 : List *params_list = NIL;
2560 1236 : 956 : List *fdw_scan_tlist = NIL;
1237 : 956 : List *fdw_recheck_quals = NIL;
1238 : : List *retrieved_attrs;
1239 : : StringInfoData sql;
1839 efujita@postgresql.o 1240 : 956 : bool has_final_sort = false;
1241 : 956 : bool has_limit = false;
1242 : : ListCell *lc;
1243 : :
1244 : : /*
1245 : : * Get FDW private data created by postgresGetForeignUpperPaths(), if any.
1246 : : */
1247 [ + + ]: 956 : if (best_path->fdw_private)
1248 : : {
821 peter@eisentraut.org 1249 : 148 : has_final_sort = boolVal(list_nth(best_path->fdw_private,
1250 : : FdwPathPrivateHasFinalSort));
1251 : 148 : has_limit = boolVal(list_nth(best_path->fdw_private,
1252 : : FdwPathPrivateHasLimit));
1253 : : }
1254 : :
2568 rhaas@postgresql.org 1255 [ + + + + ]: 956 : if (IS_SIMPLE_REL(foreignrel))
1256 : : {
1257 : : /*
1258 : : * For base relations, set scan_relid as the relid of the relation.
1259 : : */
2987 1260 : 686 : scan_relid = foreignrel->relid;
1261 : :
1262 : : /*
1263 : : * In a base-relation scan, we must apply the given scan_clauses.
1264 : : *
1265 : : * Separate the scan_clauses into those that can be executed remotely
1266 : : * and those that can't. baserestrictinfo clauses that were
1267 : : * previously determined to be safe or unsafe by classifyConditions
1268 : : * are found in fpinfo->remote_conds and fpinfo->local_conds. Anything
1269 : : * else in the scan_clauses list will be a join clause, which we have
1270 : : * to check for remote-safety.
1271 : : *
1272 : : * Note: the join clauses we see here should be the exact same ones
1273 : : * previously examined by postgresGetForeignPaths. Possibly it'd be
1274 : : * worth passing forward the classification work done then, rather
1275 : : * than repeating it here.
1276 : : *
1277 : : * This code must match "extract_actual_clauses(scan_clauses, false)"
1278 : : * except for the additional decision about remote versus local
1279 : : * execution.
1280 : : */
2560 tgl@sss.pgh.pa.us 1281 [ + + + + : 1027 : foreach(lc, scan_clauses)
+ + ]
1282 : : {
1283 : 341 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
1284 : :
1285 : : /* Ignore any pseudoconstants, they're dealt with elsewhere */
1286 [ + + ]: 341 : if (rinfo->pseudoconstant)
1287 : 4 : continue;
1288 : :
1289 [ + + ]: 337 : if (list_member_ptr(fpinfo->remote_conds, rinfo))
1290 : 253 : remote_exprs = lappend(remote_exprs, rinfo->clause);
1291 [ + + ]: 84 : else if (list_member_ptr(fpinfo->local_conds, rinfo))
1292 : 71 : local_exprs = lappend(local_exprs, rinfo->clause);
1293 [ + + ]: 13 : else if (is_foreign_expr(root, foreignrel, rinfo->clause))
1294 : 11 : remote_exprs = lappend(remote_exprs, rinfo->clause);
1295 : : else
1296 : 2 : local_exprs = lappend(local_exprs, rinfo->clause);
1297 : : }
1298 : :
1299 : : /*
1300 : : * For a base-relation scan, we have to support EPQ recheck, which
1301 : : * should recheck all the remote quals.
1302 : : */
1303 : 686 : fdw_recheck_quals = remote_exprs;
1304 : : }
1305 : : else
1306 : : {
1307 : : /*
1308 : : * Join relation or upper relation - set scan_relid to 0.
1309 : : */
2987 rhaas@postgresql.org 1310 : 270 : scan_relid = 0;
1311 : :
1312 : : /*
1313 : : * For a join rel, baserestrictinfo is NIL and we are not considering
1314 : : * parameterization right now, so there should be no scan_clauses for
1315 : : * a joinrel or an upper rel either.
1316 : : */
1317 [ - + ]: 270 : Assert(!scan_clauses);
1318 : :
1319 : : /*
1320 : : * Instead we get the conditions to apply from the fdw_private
1321 : : * structure.
1322 : : */
2560 tgl@sss.pgh.pa.us 1323 : 270 : remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false);
1324 : 270 : local_exprs = extract_actual_clauses(fpinfo->local_conds, false);
1325 : :
1326 : : /*
1327 : : * We leave fdw_recheck_quals empty in this case, since we never need
1328 : : * to apply EPQ recheck clauses. In the case of a joinrel, EPQ
1329 : : * recheck is handled elsewhere --- see postgresGetForeignJoinPaths().
1330 : : * If we're planning an upperrel (ie, remote grouping or aggregation)
1331 : : * then there's no EPQ to do because SELECT FOR UPDATE wouldn't be
1332 : : * allowed, and indeed we *can't* put the remote clauses into
1333 : : * fdw_recheck_quals because the unaggregated Vars won't be available
1334 : : * locally.
1335 : : */
1336 : :
1337 : : /* Build the list of columns to be fetched from the foreign server. */
2987 rhaas@postgresql.org 1338 : 270 : fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
1339 : :
1340 : : /*
1341 : : * Ensure that the outer plan produces a tuple whose descriptor
1342 : : * matches our scan tuple slot. Also, remove the local conditions
1343 : : * from outer plan's quals, lest they be evaluated twice, once by the
1344 : : * local plan and once by the scan.
1345 : : */
1346 [ + + ]: 270 : if (outer_plan)
1347 : : {
1348 : : /*
1349 : : * Right now, we only consider grouping and aggregation beyond
1350 : : * joins. Queries involving aggregates or grouping do not require
1351 : : * EPQ mechanism, hence should not have an outer plan here.
1352 : : */
2568 1353 [ + - - + ]: 20 : Assert(!IS_UPPER_REL(foreignrel));
1354 : :
1355 : : /*
1356 : : * First, update the plan's qual list if possible. In some cases
1357 : : * the quals might be enforced below the topmost plan level, in
1358 : : * which case we'll fail to remove them; it's not worth working
1359 : : * harder than this.
1360 : : */
2987 1361 [ + + + + : 23 : foreach(lc, local_exprs)
+ + ]
1362 : : {
1363 : 3 : Node *qual = lfirst(lc);
1364 : :
1365 : 3 : outer_plan->qual = list_delete(outer_plan->qual, qual);
1366 : :
1367 : : /*
1368 : : * For an inner join the local conditions of foreign scan plan
1369 : : * can be part of the joinquals as well. (They might also be
1370 : : * in the mergequals or hashquals, but we can't touch those
1371 : : * without breaking the plan.)
1372 : : */
1950 tgl@sss.pgh.pa.us 1373 [ + + ]: 3 : if (IsA(outer_plan, NestLoop) ||
1374 [ + - ]: 1 : IsA(outer_plan, MergeJoin) ||
1375 [ - + ]: 1 : IsA(outer_plan, HashJoin))
1376 : : {
1377 : 2 : Join *join_plan = (Join *) outer_plan;
1378 : :
1379 [ + - ]: 2 : if (join_plan->jointype == JOIN_INNER)
1380 : 2 : join_plan->joinqual = list_delete(join_plan->joinqual,
1381 : : qual);
1382 : : }
1383 : : }
1384 : :
1385 : : /*
1386 : : * Now fix the subplan's tlist --- this might result in inserting
1387 : : * a Result node atop the plan tree.
1388 : : */
1389 : 20 : outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist,
1390 : 20 : best_path->path.parallel_safe);
1391 : : }
1392 : : }
1393 : :
1394 : : /*
1395 : : * Build the query string to be sent for execution, and identify
1396 : : * expressions to be sent as parameters.
1397 : : */
4042 1398 : 956 : initStringInfo(&sql);
2987 rhaas@postgresql.org 1399 : 956 : deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
1400 : : remote_exprs, best_path->path.pathkeys,
1401 : : has_final_sort, has_limit, false,
1402 : : &retrieved_attrs, ¶ms_list);
1403 : :
1404 : : /* Remember remote_exprs for possible use by postgresPlanDirectModify */
2560 tgl@sss.pgh.pa.us 1405 : 956 : fpinfo->final_remote_exprs = remote_exprs;
1406 : :
1407 : : /*
1408 : : * Build the fdw_private list that will be available to the executor.
1409 : : * Items in the list must match order in enum FdwScanPrivateIndex.
1410 : : */
1411 : 956 : fdw_private = list_make3(makeString(sql.data),
1412 : : retrieved_attrs,
1413 : : makeInteger(fpinfo->fetch_size));
2568 rhaas@postgresql.org 1414 [ + + + + : 956 : if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
+ + + + ]
2987 1415 : 270 : fdw_private = lappend(fdw_private,
1595 tgl@sss.pgh.pa.us 1416 : 270 : makeString(fpinfo->relation_name));
1417 : :
1418 : : /*
1419 : : * Create the ForeignScan node for the given relation.
1420 : : *
1421 : : * Note that the remote parameter expressions are stored in the fdw_exprs
1422 : : * field of the finished plan node; we can't keep them in private state
1423 : : * because then they wouldn't be subject to later planner processing.
1424 : : */
4070 1425 : 956 : return make_foreignscan(tlist,
1426 : : local_exprs,
1427 : : scan_relid,
1428 : : params_list,
1429 : : fdw_private,
1430 : : fdw_scan_tlist,
1431 : : fdw_recheck_quals,
1432 : : outer_plan);
1433 : : }
1434 : :
1435 : : /*
1436 : : * Construct a tuple descriptor for the scan tuples handled by a foreign join.
1437 : : */
1438 : : static TupleDesc
1045 1439 : 152 : get_tupdesc_for_join_scan_tuples(ForeignScanState *node)
1440 : : {
1441 : 152 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
1442 : 152 : EState *estate = node->ss.ps.state;
1443 : : TupleDesc tupdesc;
1444 : :
1445 : : /*
1446 : : * The core code has already set up a scan tuple slot based on
1447 : : * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough,
1448 : : * but there's one case where it isn't. If we have any whole-row row
1449 : : * identifier Vars, they may have vartype RECORD, and we need to replace
1450 : : * that with the associated table's actual composite type. This ensures
1451 : : * that when we read those ROW() expression values from the remote server,
1452 : : * we can convert them to a composite type the local server knows.
1453 : : */
1454 : 152 : tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
1455 [ + + ]: 635 : for (int i = 0; i < tupdesc->natts; i++)
1456 : : {
1457 : 483 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
1458 : : Var *var;
1459 : : RangeTblEntry *rte;
1460 : : Oid reltype;
1461 : :
1462 : : /* Nothing to do if it's not a generic RECORD attribute */
1463 [ + + - + ]: 483 : if (att->atttypid != RECORDOID || att->atttypmod >= 0)
1464 : 481 : continue;
1465 : :
1466 : : /*
1467 : : * If we can't identify the referenced table, do nothing. This'll
1468 : : * likely lead to failure later, but perhaps we can muddle through.
1469 : : */
1470 : 2 : var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
1471 : : i)->expr;
1472 [ + - - + ]: 2 : if (!IsA(var, Var) || var->varattno != 0)
1045 tgl@sss.pgh.pa.us 1473 :UBC 0 : continue;
1045 tgl@sss.pgh.pa.us 1474 :CBC 2 : rte = list_nth(estate->es_range_table, var->varno - 1);
1475 [ - + ]: 2 : if (rte->rtekind != RTE_RELATION)
1045 tgl@sss.pgh.pa.us 1476 :UBC 0 : continue;
1045 tgl@sss.pgh.pa.us 1477 :CBC 2 : reltype = get_rel_type_id(rte->relid);
1478 [ - + ]: 2 : if (!OidIsValid(reltype))
1045 tgl@sss.pgh.pa.us 1479 :UBC 0 : continue;
1045 tgl@sss.pgh.pa.us 1480 :CBC 2 : att->atttypid = reltype;
1481 : : /* shouldn't need to change anything else */
1482 : : }
1483 : 152 : return tupdesc;
1484 : : }
1485 : :
1486 : : /*
1487 : : * postgresBeginForeignScan
1488 : : * Initiate an executor scan of a foreign PostgreSQL table.
1489 : : */
1490 : : static void
4070 1491 : 843 : postgresBeginForeignScan(ForeignScanState *node, int eflags)
1492 : : {
1493 : 843 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
1494 : 843 : EState *estate = node->ss.ps.state;
1495 : : PgFdwScanState *fsstate;
1496 : : RangeTblEntry *rte;
1497 : : Oid userid;
1498 : : ForeignTable *table;
1499 : : UserMapping *user;
1500 : : int rtindex;
1501 : : int numParams;
1502 : :
1503 : : /*
1504 : : * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
1505 : : */
1506 [ + + ]: 843 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
1507 : 362 : return;
1508 : :
1509 : : /*
1510 : : * We'll save private state in node->fdw_state.
1511 : : */
4053 1512 : 481 : fsstate = (PgFdwScanState *) palloc0(sizeof(PgFdwScanState));
1513 : 481 : node->fdw_state = (void *) fsstate;
1514 : :
1515 : : /*
1516 : : * Identify which user to do the remote access as. This should match what
1517 : : * ExecCheckPermissions() does.
1518 : : */
501 alvherre@alvh.no-ip. 1519 [ + + ]: 481 : userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
2987 rhaas@postgresql.org 1520 [ + + ]: 481 : if (fsplan->scan.scanrelid > 0)
2830 tgl@sss.pgh.pa.us 1521 : 330 : rtindex = fsplan->scan.scanrelid;
1522 : : else
440 1523 : 151 : rtindex = bms_next_member(fsplan->fs_base_relids, -1);
2019 1524 : 481 : rte = exec_rt_fetch(rtindex, estate);
1525 : :
1526 : : /* Get info about foreign table. */
2830 1527 : 481 : table = GetForeignTable(rte->relid);
1528 : 481 : user = GetUserMapping(userid, table->serverid);
1529 : :
1530 : : /*
1531 : : * Get connection to the foreign server. Connection manager will
1532 : : * establish new connection if necessary.
1533 : : */
1110 efujita@postgresql.o 1534 : 481 : fsstate->conn = GetConnection(user, false, &fsstate->conn_state);
1535 : :
1536 : : /* Assign a unique ID for my cursor */
4053 tgl@sss.pgh.pa.us 1537 : 471 : fsstate->cursor_number = GetCursorNumber(fsstate->conn);
1538 : 471 : fsstate->cursor_exists = false;
1539 : :
1540 : : /* Get private info created by planner functions. */
4041 1541 : 471 : fsstate->query = strVal(list_nth(fsplan->fdw_private,
1542 : : FdwScanPrivateSelectSql));
1543 : 471 : fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
1544 : : FdwScanPrivateRetrievedAttrs);
2993 rhaas@postgresql.org 1545 : 471 : fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
1546 : : FdwScanPrivateFetchSize));
1547 : :
1548 : : /* Create contexts for batches of tuples and per-tuple temp workspace. */
4053 tgl@sss.pgh.pa.us 1549 : 471 : fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
1550 : : "postgres_fdw tuple data",
1551 : : ALLOCSET_DEFAULT_SIZES);
1552 : 471 : fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
1553 : : "postgres_fdw temporary data",
1554 : : ALLOCSET_SMALL_SIZES);
1555 : :
1556 : : /*
1557 : : * Get info we'll need for converting data fetched from the foreign server
1558 : : * into local representation and error reporting during that process.
1559 : : */
2987 rhaas@postgresql.org 1560 [ + + ]: 471 : if (fsplan->scan.scanrelid > 0)
1561 : : {
2830 tgl@sss.pgh.pa.us 1562 : 320 : fsstate->rel = node->ss.ss_currentRelation;
2987 rhaas@postgresql.org 1563 : 320 : fsstate->tupdesc = RelationGetDescr(fsstate->rel);
1564 : : }
1565 : : else
1566 : : {
2830 tgl@sss.pgh.pa.us 1567 : 151 : fsstate->rel = NULL;
1045 1568 : 151 : fsstate->tupdesc = get_tupdesc_for_join_scan_tuples(node);
1569 : : }
1570 : :
2987 rhaas@postgresql.org 1571 : 471 : fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
1572 : :
1573 : : /*
1574 : : * Prepare for processing of parameters used in remote query, if any.
1575 : : */
2949 1576 : 471 : numParams = list_length(fsplan->fdw_exprs);
1577 : 471 : fsstate->numParams = numParams;
4070 tgl@sss.pgh.pa.us 1578 [ + + ]: 471 : if (numParams > 0)
2949 rhaas@postgresql.org 1579 : 18 : prepare_query_params((PlanState *) node,
1580 : : fsplan->fdw_exprs,
1581 : : numParams,
1582 : : &fsstate->param_flinfo,
1583 : : &fsstate->param_exprs,
1584 : : &fsstate->param_values);
1585 : :
1586 : : /* Set the async-capable flag */
1068 efujita@postgresql.o 1587 : 471 : fsstate->async_capable = node->ss.ps.async_capable;
1588 : : }
1589 : :
1590 : : /*
1591 : : * postgresIterateForeignScan
1592 : : * Retrieve next row from the result set, or clear tuple slot to indicate
1593 : : * EOF.
1594 : : */
1595 : : static TupleTableSlot *
4070 tgl@sss.pgh.pa.us 1596 : 68805 : postgresIterateForeignScan(ForeignScanState *node)
1597 : : {
4053 1598 : 68805 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
4070 1599 : 68805 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
1600 : :
1601 : : /*
1602 : : * In sync mode, if this is the first call after Begin or ReScan, we need
1603 : : * to create the cursor on the remote side. In async mode, we would have
1604 : : * already created the cursor before we get here, even if this is the
1605 : : * first call after Begin or ReScan.
1606 : : */
4053 1607 [ + + ]: 68805 : if (!fsstate->cursor_exists)
4070 1608 : 743 : create_cursor(node);
1609 : :
1610 : : /*
1611 : : * Get some more tuples, if we've run out.
1612 : : */
4053 1613 [ + + ]: 68803 : if (fsstate->next_tuple >= fsstate->num_tuples)
1614 : : {
1615 : : /* In async mode, just clear tuple slot. */
1110 efujita@postgresql.o 1616 [ + + ]: 1992 : if (fsstate->async_capable)
1617 : 32 : return ExecClearTuple(slot);
1618 : : /* No point in another fetch if we already detected EOF, though. */
4053 tgl@sss.pgh.pa.us 1619 [ + + ]: 1960 : if (!fsstate->eof_reached)
4070 1620 : 1301 : fetch_more_data(node);
1621 : : /* If we didn't get any tuples, must be end of data. */
4053 1622 [ + + ]: 1955 : if (fsstate->next_tuple >= fsstate->num_tuples)
4070 1623 : 729 : return ExecClearTuple(slot);
1624 : : }
1625 : :
1626 : : /*
1627 : : * Return the next tuple.
1628 : : */
2028 andres@anarazel.de 1629 : 68037 : ExecStoreHeapTuple(fsstate->tuples[fsstate->next_tuple++],
1630 : : slot,
1631 : : false);
1632 : :
4070 tgl@sss.pgh.pa.us 1633 : 68037 : return slot;
1634 : : }
1635 : :
1636 : : /*
1637 : : * postgresReScanForeignScan
1638 : : * Restart the scan.
1639 : : */
1640 : : static void
1641 : 399 : postgresReScanForeignScan(ForeignScanState *node)
1642 : : {
4053 1643 : 399 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
1644 : : char sql[64];
1645 : : PGresult *res;
1646 : :
1647 : : /* If we haven't created the cursor yet, nothing to do. */
1648 [ + + ]: 399 : if (!fsstate->cursor_exists)
4070 1649 : 43 : return;
1650 : :
1651 : : /*
1652 : : * If the node is async-capable, and an asynchronous fetch for it has
1653 : : * begun, the asynchronous fetch might not have yet completed. Check if
1654 : : * the node is async-capable, and an asynchronous fetch for it is still in
1655 : : * progress; if so, complete the asynchronous fetch before restarting the
1656 : : * scan.
1657 : : */
808 efujita@postgresql.o 1658 [ + + ]: 368 : if (fsstate->async_capable &&
1659 [ + + ]: 21 : fsstate->conn_state->pendingAreq &&
1660 [ + + ]: 2 : fsstate->conn_state->pendingAreq->requestee == (PlanState *) node)
1661 : 1 : fetch_more_data(node);
1662 : :
1663 : : /*
1664 : : * If any internal parameters affecting this node have changed, we'd
1665 : : * better destroy and recreate the cursor. Otherwise, rewinding it should
1666 : : * be good enough. If we've only fetched zero or one batch, we needn't
1667 : : * even rewind the cursor, just rescan what we have.
1668 : : */
4070 tgl@sss.pgh.pa.us 1669 [ + + ]: 368 : if (node->ss.ps.chgParam != NULL)
1670 : : {
4053 1671 : 338 : fsstate->cursor_exists = false;
4070 1672 : 338 : snprintf(sql, sizeof(sql), "CLOSE c%u",
1673 : : fsstate->cursor_number);
1674 : : }
4053 1675 [ + + ]: 30 : else if (fsstate->fetch_ct_2 > 1)
1676 : : {
4070 1677 : 18 : snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u",
1678 : : fsstate->cursor_number);
1679 : : }
1680 : : else
1681 : : {
1682 : : /* Easy: just rescan what we already have in memory, if anything */
4053 1683 : 12 : fsstate->next_tuple = 0;
4070 1684 : 12 : return;
1685 : : }
1686 : :
1687 : : /*
1688 : : * We don't use a PG_TRY block here, so be careful not to throw error
1689 : : * without releasing the PGresult.
1690 : : */
1110 efujita@postgresql.o 1691 : 356 : res = pgfdw_exec_query(fsstate->conn, sql, fsstate->conn_state);
4070 tgl@sss.pgh.pa.us 1692 [ - + ]: 356 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3723 tgl@sss.pgh.pa.us 1693 :UBC 0 : pgfdw_report_error(ERROR, res, fsstate->conn, true, sql);
4070 tgl@sss.pgh.pa.us 1694 :CBC 356 : PQclear(res);
1695 : :
1696 : : /* Now force a fresh FETCH. */
4053 1697 : 356 : fsstate->tuples = NULL;
1698 : 356 : fsstate->num_tuples = 0;
1699 : 356 : fsstate->next_tuple = 0;
1700 : 356 : fsstate->fetch_ct_2 = 0;
1701 : 356 : fsstate->eof_reached = false;
1702 : : }
1703 : :
1704 : : /*
1705 : : * postgresEndForeignScan
1706 : : * Finish scanning foreign table and dispose objects used for this scan
1707 : : */
1708 : : static void
4070 1709 : 817 : postgresEndForeignScan(ForeignScanState *node)
1710 : : {
4053 1711 : 817 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
1712 : :
1713 : : /* if fsstate is NULL, we are in EXPLAIN; nothing to do */
1714 [ + + ]: 817 : if (fsstate == NULL)
4070 1715 : 362 : return;
1716 : :
1717 : : /* Close the cursor if open, to prevent accumulation of cursors */
4053 1718 [ + + ]: 455 : if (fsstate->cursor_exists)
1110 efujita@postgresql.o 1719 : 440 : close_cursor(fsstate->conn, fsstate->cursor_number,
1720 : : fsstate->conn_state);
1721 : :
1722 : : /* Release remote connection */
4053 tgl@sss.pgh.pa.us 1723 : 455 : ReleaseConnection(fsstate->conn);
1724 : 455 : fsstate->conn = NULL;
1725 : :
1726 : : /* MemoryContexts will be deleted automatically. */
1727 : : }
1728 : :
1729 : : /*
1730 : : * postgresAddForeignUpdateTargets
1731 : : * Add resjunk column(s) needed for update/delete on a foreign table
1732 : : */
1733 : : static void
1110 1734 : 172 : postgresAddForeignUpdateTargets(PlannerInfo *root,
1735 : : Index rtindex,
1736 : : RangeTblEntry *target_rte,
1737 : : Relation target_relation)
1738 : : {
1739 : : Var *var;
1740 : :
1741 : : /*
1742 : : * In postgres_fdw, what we need is the ctid, same as for a regular table.
1743 : : */
1744 : :
1745 : : /* Make a Var representing the desired value */
1746 : 172 : var = makeVar(rtindex,
1747 : : SelfItemPointerAttributeNumber,
1748 : : TIDOID,
1749 : : -1,
1750 : : InvalidOid,
1751 : : 0);
1752 : :
1753 : : /* Register it as a row-identity column needed by this target rel */
1754 : 172 : add_row_identity_var(root, var, rtindex, "ctid");
4053 1755 : 172 : }
1756 : :
1757 : : /*
1758 : : * postgresPlanForeignModify
1759 : : * Plan an insert/update/delete operation on a foreign table
1760 : : */
1761 : : static List *
1762 : 153 : postgresPlanForeignModify(PlannerInfo *root,
1763 : : ModifyTable *plan,
1764 : : Index resultRelation,
1765 : : int subplan_index)
1766 : : {
1767 : 153 : CmdType operation = plan->operation;
4051 1768 [ + - ]: 153 : RangeTblEntry *rte = planner_rt_fetch(resultRelation, root);
1769 : : Relation rel;
1770 : : StringInfoData sql;
4053 1771 : 153 : List *targetAttrs = NIL;
2107 jdavis@postgresql.or 1772 : 153 : List *withCheckOptionList = NIL;
4053 tgl@sss.pgh.pa.us 1773 : 153 : List *returningList = NIL;
4041 1774 : 153 : List *retrieved_attrs = NIL;
3264 andres@anarazel.de 1775 : 153 : bool doNothing = false;
1180 tomas.vondra@postgre 1776 : 153 : int values_end_len = -1;
1777 : :
4053 tgl@sss.pgh.pa.us 1778 : 153 : initStringInfo(&sql);
1779 : :
1780 : : /*
1781 : : * Core code already has some lock on each rel being planned, so we can
1782 : : * use NoLock here.
1783 : : */
1910 andres@anarazel.de 1784 : 153 : rel = table_open(rte->relid, NoLock);
1785 : :
1786 : : /*
1787 : : * In an INSERT, we transmit all columns that are defined in the foreign
1788 : : * table. In an UPDATE, if there are BEFORE ROW UPDATE triggers on the
1789 : : * foreign table, we transmit all columns like INSERT; else we transmit
1790 : : * only columns that were explicitly targets of the UPDATE, so as to avoid
1791 : : * unnecessary data transmission. (We can't do that for INSERT since we
1792 : : * would miss sending default values for columns not listed in the source
1793 : : * statement, and for UPDATE if there are BEFORE ROW UPDATE triggers since
1794 : : * those triggers might change values for non-target columns, in which
1795 : : * case we would miss sending changed values for those columns.)
1796 : : */
1767 efujita@postgresql.o 1797 [ + + + + ]: 153 : if (operation == CMD_INSERT ||
1798 : 50 : (operation == CMD_UPDATE &&
1799 [ + + ]: 50 : rel->trigdesc &&
1800 [ + + ]: 18 : rel->trigdesc->trig_update_before_row))
4051 tgl@sss.pgh.pa.us 1801 : 102 : {
1802 : 102 : TupleDesc tupdesc = RelationGetDescr(rel);
1803 : : int attnum;
1804 : :
1805 [ + + ]: 427 : for (attnum = 1; attnum <= tupdesc->natts; attnum++)
1806 : : {
2429 andres@anarazel.de 1807 : 325 : Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
1808 : :
4051 tgl@sss.pgh.pa.us 1809 [ + + ]: 325 : if (!attr->attisdropped)
1810 : 308 : targetAttrs = lappend_int(targetAttrs, attnum);
1811 : : }
1812 : : }
1813 [ + + ]: 51 : else if (operation == CMD_UPDATE)
1814 : : {
1815 : : int col;
495 alvherre@alvh.no-ip. 1816 : 35 : RelOptInfo *rel = find_base_rel(root, resultRelation);
1817 : 35 : Bitmapset *allUpdatedCols = get_rel_all_updated_cols(root, rel);
1818 : :
3425 tgl@sss.pgh.pa.us 1819 : 35 : col = -1;
1842 peter@eisentraut.org 1820 [ + + ]: 76 : while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
1821 : : {
1822 : : /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
3425 tgl@sss.pgh.pa.us 1823 : 41 : AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber;
1824 : :
2489 1825 [ - + ]: 41 : if (attno <= InvalidAttrNumber) /* shouldn't happen */
4053 tgl@sss.pgh.pa.us 1826 [ # # ]:UBC 0 : elog(ERROR, "system-column update is not supported");
3425 tgl@sss.pgh.pa.us 1827 :CBC 41 : targetAttrs = lappend_int(targetAttrs, attno);
1828 : : }
1829 : : }
1830 : :
1831 : : /*
1832 : : * Extract the relevant WITH CHECK OPTION list if any.
1833 : : */
2107 jdavis@postgresql.or 1834 [ + + ]: 153 : if (plan->withCheckOptionLists)
1835 : 16 : withCheckOptionList = (List *) list_nth(plan->withCheckOptionLists,
1836 : : subplan_index);
1837 : :
1838 : : /*
1839 : : * Extract the relevant RETURNING list if any.
1840 : : */
4053 tgl@sss.pgh.pa.us 1841 [ + + ]: 153 : if (plan->returningLists)
1842 : 24 : returningList = (List *) list_nth(plan->returningLists, subplan_index);
1843 : :
1844 : : /*
1845 : : * ON CONFLICT DO UPDATE and DO NOTHING case with inference specification
1846 : : * should have already been rejected in the optimizer, as presently there
1847 : : * is no way to recognize an arbiter index on a foreign table. Only DO
1848 : : * NOTHING is supported without an inference specification.
1849 : : */
3264 andres@anarazel.de 1850 [ + + ]: 153 : if (plan->onConflictAction == ONCONFLICT_NOTHING)
1851 : 1 : doNothing = true;
1852 [ - + ]: 152 : else if (plan->onConflictAction != ONCONFLICT_NONE)
3264 andres@anarazel.de 1853 [ # # ]:UBC 0 : elog(ERROR, "unexpected ON CONFLICT specification: %d",
1854 : : (int) plan->onConflictAction);
1855 : :
1856 : : /*
1857 : : * Construct the SQL command string.
1858 : : */
4053 tgl@sss.pgh.pa.us 1859 [ + + + - ]:CBC 153 : switch (operation)
1860 : : {
1861 : 87 : case CMD_INSERT:
2175 rhaas@postgresql.org 1862 : 87 : deparseInsertSql(&sql, rte, resultRelation, rel,
1863 : : targetAttrs, doNothing,
1864 : : withCheckOptionList, returningList,
1865 : : &retrieved_attrs, &values_end_len);
4053 tgl@sss.pgh.pa.us 1866 : 87 : break;
1867 : 50 : case CMD_UPDATE:
2175 rhaas@postgresql.org 1868 : 50 : deparseUpdateSql(&sql, rte, resultRelation, rel,
1869 : : targetAttrs,
1870 : : withCheckOptionList, returningList,
1871 : : &retrieved_attrs);
4053 tgl@sss.pgh.pa.us 1872 : 50 : break;
1873 : 16 : case CMD_DELETE:
2175 rhaas@postgresql.org 1874 : 16 : deparseDeleteSql(&sql, rte, resultRelation, rel,
1875 : : returningList,
1876 : : &retrieved_attrs);
4053 tgl@sss.pgh.pa.us 1877 : 16 : break;
4053 tgl@sss.pgh.pa.us 1878 :UBC 0 : default:
1879 [ # # ]: 0 : elog(ERROR, "unexpected operation: %d", (int) operation);
1880 : : break;
1881 : : }
1882 : :
1910 andres@anarazel.de 1883 :CBC 153 : table_close(rel, NoLock);
1884 : :
1885 : : /*
1886 : : * Build the fdw_private list that will be available to the executor.
1887 : : * Items in the list must match enum FdwModifyPrivateIndex, above.
1888 : : */
1180 tomas.vondra@postgre 1889 : 153 : return list_make5(makeString(sql.data),
1890 : : targetAttrs,
1891 : : makeInteger(values_end_len),
1892 : : makeBoolean((retrieved_attrs != NIL)),
1893 : : retrieved_attrs);
1894 : : }
1895 : :
1896 : : /*
1897 : : * postgresBeginForeignModify
1898 : : * Begin an insert/update/delete operation on a foreign table
1899 : : */
1900 : : static void
4053 tgl@sss.pgh.pa.us 1901 : 153 : postgresBeginForeignModify(ModifyTableState *mtstate,
1902 : : ResultRelInfo *resultRelInfo,
1903 : : List *fdw_private,
1904 : : int subplan_index,
1905 : : int eflags)
1906 : : {
1907 : : PgFdwModifyState *fmstate;
1908 : : char *query;
1909 : : List *target_attrs;
1910 : : bool has_returning;
1911 : : int values_end_len;
1912 : : List *retrieved_attrs;
1913 : : RangeTblEntry *rte;
1914 : :
1915 : : /*
1916 : : * Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
1917 : : * stays NULL.
1918 : : */
1919 [ + + ]: 153 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
1920 : 38 : return;
1921 : :
1922 : : /* Deconstruct fdw_private data. */
2200 rhaas@postgresql.org 1923 : 115 : query = strVal(list_nth(fdw_private,
1924 : : FdwModifyPrivateUpdateSql));
1925 : 115 : target_attrs = (List *) list_nth(fdw_private,
1926 : : FdwModifyPrivateTargetAttnums);
1180 tomas.vondra@postgre 1927 : 115 : values_end_len = intVal(list_nth(fdw_private,
1928 : : FdwModifyPrivateLen));
821 peter@eisentraut.org 1929 : 115 : has_returning = boolVal(list_nth(fdw_private,
1930 : : FdwModifyPrivateHasReturning));
2200 rhaas@postgresql.org 1931 : 115 : retrieved_attrs = (List *) list_nth(fdw_private,
1932 : : FdwModifyPrivateRetrievedAttrs);
1933 : :
1934 : : /* Find RTE. */
2019 tgl@sss.pgh.pa.us 1935 : 115 : rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
1936 : : mtstate->ps.state);
1937 : :
1938 : : /* Construct an execution state. */
2200 rhaas@postgresql.org 1939 : 115 : fmstate = create_foreign_modify(mtstate->ps.state,
1940 : : rte,
1941 : : resultRelInfo,
1942 : : mtstate->operation,
1110 tgl@sss.pgh.pa.us 1943 : 115 : outerPlanState(mtstate)->plan,
1944 : : query,
1945 : : target_attrs,
1946 : : values_end_len,
1947 : : has_returning,
1948 : : retrieved_attrs);
1949 : :
4053 1950 : 115 : resultRelInfo->ri_FdwState = fmstate;
1951 : : }
1952 : :
1953 : : /*
1954 : : * postgresExecForeignInsert
1955 : : * Insert one row into a foreign table
1956 : : */
1957 : : static TupleTableSlot *
1958 : 888 : postgresExecForeignInsert(EState *estate,
1959 : : ResultRelInfo *resultRelInfo,
1960 : : TupleTableSlot *slot,
1961 : : TupleTableSlot *planSlot)
1962 : : {
1817 efujita@postgresql.o 1963 : 888 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
1964 : : TupleTableSlot **rslot;
1068 tgl@sss.pgh.pa.us 1965 : 888 : int numSlots = 1;
1966 : :
1967 : : /*
1968 : : * If the fmstate has aux_fmstate set, use the aux_fmstate (see
1969 : : * postgresBeginForeignInsert())
1970 : : */
1180 tomas.vondra@postgre 1971 [ - + ]: 888 : if (fmstate->aux_fmstate)
1180 tomas.vondra@postgre 1972 :UBC 0 : resultRelInfo->ri_FdwState = fmstate->aux_fmstate;
1180 tomas.vondra@postgre 1973 :CBC 888 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT,
1974 : : &slot, &planSlot, &numSlots);
1975 : : /* Revert that change */
1976 [ - + ]: 884 : if (fmstate->aux_fmstate)
1180 tomas.vondra@postgre 1977 :UBC 0 : resultRelInfo->ri_FdwState = fmstate;
1978 : :
1180 tomas.vondra@postgre 1979 [ + + ]:CBC 884 : return rslot ? *rslot : NULL;
1980 : : }
1981 : :
1982 : : /*
1983 : : * postgresExecForeignBatchInsert
1984 : : * Insert multiple rows into a foreign table
1985 : : */
1986 : : static TupleTableSlot **
1987 : 41 : postgresExecForeignBatchInsert(EState *estate,
1988 : : ResultRelInfo *resultRelInfo,
1989 : : TupleTableSlot **slots,
1990 : : TupleTableSlot **planSlots,
1991 : : int *numSlots)
1992 : : {
1993 : 41 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
1994 : : TupleTableSlot **rslot;
1995 : :
1996 : : /*
1997 : : * If the fmstate has aux_fmstate set, use the aux_fmstate (see
1998 : : * postgresBeginForeignInsert())
1999 : : */
1817 efujita@postgresql.o 2000 [ - + ]: 41 : if (fmstate->aux_fmstate)
1817 efujita@postgresql.o 2001 :UBC 0 : resultRelInfo->ri_FdwState = fmstate->aux_fmstate;
1817 efujita@postgresql.o 2002 :CBC 41 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT,
2003 : : slots, planSlots, numSlots);
2004 : : /* Revert that change */
2005 [ - + ]: 40 : if (fmstate->aux_fmstate)
1817 efujita@postgresql.o 2006 :UBC 0 : resultRelInfo->ri_FdwState = fmstate;
2007 : :
1817 efujita@postgresql.o 2008 :CBC 40 : return rslot;
2009 : : }
2010 : :
2011 : : /*
2012 : : * postgresGetForeignModifyBatchSize
2013 : : * Determine the maximum number of tuples that can be inserted in bulk
2014 : : *
2015 : : * Returns the batch size specified for server or table. When batching is not
2016 : : * allowed (e.g. for tables with BEFORE/AFTER ROW triggers or with RETURNING
2017 : : * clause), returns 1.
2018 : : */
2019 : : static int
1180 tomas.vondra@postgre 2020 : 141 : postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo)
2021 : : {
2022 : : int batch_size;
481 efujita@postgresql.o 2023 : 141 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2024 : :
2025 : : /* should be called only once */
1180 tomas.vondra@postgre 2026 [ - + ]: 141 : Assert(resultRelInfo->ri_BatchSize == 0);
2027 : :
2028 : : /*
2029 : : * Should never get called when the insert is being performed on a table
2030 : : * that is also among the target relations of an UPDATE operation, because
2031 : : * postgresBeginForeignInsert() currently rejects such insert attempts.
2032 : : */
1151 2033 [ + + - + ]: 141 : Assert(fmstate == NULL || fmstate->aux_fmstate == NULL);
2034 : :
2035 : : /*
2036 : : * In EXPLAIN without ANALYZE, ri_FdwState is NULL, so we have to lookup
2037 : : * the option directly in server/table options. Otherwise just use the
2038 : : * value we determined earlier.
2039 : : */
2040 [ + + ]: 141 : if (fmstate)
2041 : 128 : batch_size = fmstate->batch_size;
2042 : : else
1180 2043 : 13 : batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc);
2044 : :
2045 : : /*
2046 : : * Disable batching when we have to use RETURNING, there are any
2047 : : * BEFORE/AFTER ROW INSERT triggers on the foreign table, or there are any
2048 : : * WITH CHECK OPTION constraints from parent views.
2049 : : *
2050 : : * When there are any BEFORE ROW INSERT triggers on the table, we can't
2051 : : * support it, because such triggers might query the table we're inserting
2052 : : * into and act differently if the tuples that have already been processed
2053 : : * and prepared for insertion are not there.
2054 : : */
2055 [ + + ]: 141 : if (resultRelInfo->ri_projectReturning != NULL ||
618 efujita@postgresql.o 2056 [ + + ]: 120 : resultRelInfo->ri_WithCheckOptions != NIL ||
1180 tomas.vondra@postgre 2057 [ + + ]: 111 : (resultRelInfo->ri_TrigDesc &&
724 efujita@postgresql.o 2058 [ + + ]: 14 : (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
2059 [ + - ]: 1 : resultRelInfo->ri_TrigDesc->trig_insert_after_row)))
1180 tomas.vondra@postgre 2060 : 44 : return 1;
2061 : :
2062 : : /*
2063 : : * If the foreign table has no columns, disable batching as the INSERT
2064 : : * syntax doesn't allow batching multiple empty rows into a zero-column
2065 : : * table in a single statement. This is needed for COPY FROM, in which
2066 : : * case fmstate must be non-NULL.
2067 : : */
549 efujita@postgresql.o 2068 [ + + + + ]: 97 : if (fmstate && list_length(fmstate->target_attrs) == 0)
2069 : 1 : return 1;
2070 : :
2071 : : /*
2072 : : * Otherwise use the batch size specified for server/table. The number of
2073 : : * parameters in a batch is limited to 65535 (uint16), so make sure we
2074 : : * don't exceed this limit by using the maximum batch_size possible.
2075 : : */
1041 tomas.vondra@postgre 2076 [ + + + - ]: 96 : if (fmstate && fmstate->p_nums > 0)
2077 : 88 : batch_size = Min(batch_size, PQ_QUERY_PARAM_MAX_LIMIT / fmstate->p_nums);
2078 : :
1180 2079 : 96 : return batch_size;
2080 : : }
2081 : :
2082 : : /*
2083 : : * postgresExecForeignUpdate
2084 : : * Update one row in a foreign table
2085 : : */
2086 : : static TupleTableSlot *
4053 tgl@sss.pgh.pa.us 2087 : 71 : postgresExecForeignUpdate(EState *estate,
2088 : : ResultRelInfo *resultRelInfo,
2089 : : TupleTableSlot *slot,
2090 : : TupleTableSlot *planSlot)
2091 : : {
2092 : : TupleTableSlot **rslot;
1068 2093 : 71 : int numSlots = 1;
2094 : :
1180 tomas.vondra@postgre 2095 : 71 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_UPDATE,
2096 : : &slot, &planSlot, &numSlots);
2097 : :
2098 [ + + ]: 71 : return rslot ? rslot[0] : NULL;
2099 : : }
2100 : :
2101 : : /*
2102 : : * postgresExecForeignDelete
2103 : : * Delete one row from a foreign table
2104 : : */
2105 : : static TupleTableSlot *
4053 tgl@sss.pgh.pa.us 2106 : 17 : postgresExecForeignDelete(EState *estate,
2107 : : ResultRelInfo *resultRelInfo,
2108 : : TupleTableSlot *slot,
2109 : : TupleTableSlot *planSlot)
2110 : : {
2111 : : TupleTableSlot **rslot;
1068 2112 : 17 : int numSlots = 1;
2113 : :
1180 tomas.vondra@postgre 2114 : 17 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_DELETE,
2115 : : &slot, &planSlot, &numSlots);
2116 : :
2117 [ + - ]: 17 : return rslot ? rslot[0] : NULL;
2118 : : }
2119 : :
2120 : : /*
2121 : : * postgresEndForeignModify
2122 : : * Finish an insert/update/delete operation on a foreign table
2123 : : */
2124 : : static void
4053 tgl@sss.pgh.pa.us 2125 : 143 : postgresEndForeignModify(EState *estate,
2126 : : ResultRelInfo *resultRelInfo)
2127 : : {
2128 : 143 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2129 : :
2130 : : /* If fmstate is NULL, we are in EXPLAIN; nothing to do */
2131 [ + + ]: 143 : if (fmstate == NULL)
2132 : 38 : return;
2133 : :
2134 : : /* Destroy the execution state */
2200 rhaas@postgresql.org 2135 : 105 : finish_foreign_modify(fmstate);
2136 : : }
2137 : :
2138 : : /*
2139 : : * postgresBeginForeignInsert
2140 : : * Begin an insert operation on a foreign table
2141 : : */
2142 : : static void
2143 : 60 : postgresBeginForeignInsert(ModifyTableState *mtstate,
2144 : : ResultRelInfo *resultRelInfo)
2145 : : {
2146 : : PgFdwModifyState *fmstate;
2175 2147 : 60 : ModifyTable *plan = castNode(ModifyTable, mtstate->ps.plan);
2148 : 60 : EState *estate = mtstate->ps.state;
2149 : : Index resultRelation;
2200 2150 : 60 : Relation rel = resultRelInfo->ri_RelationDesc;
2151 : : RangeTblEntry *rte;
2152 : 60 : TupleDesc tupdesc = RelationGetDescr(rel);
2153 : : int attnum;
2154 : : int values_end_len;
2155 : : StringInfoData sql;
2156 : 60 : List *targetAttrs = NIL;
2157 : 60 : List *retrieved_attrs = NIL;
2158 : 60 : bool doNothing = false;
2159 : :
2160 : : /*
2161 : : * If the foreign table we are about to insert routed rows into is also an
2162 : : * UPDATE subplan result rel that will be updated later, proceeding with
2163 : : * the INSERT will result in the later UPDATE incorrectly modifying those
2164 : : * routed rows, so prevent the INSERT --- it would be nice if we could
2165 : : * handle this case; but for now, throw an error for safety.
2166 : : */
1817 efujita@postgresql.o 2167 [ + + + + ]: 60 : if (plan && plan->operation == CMD_UPDATE &&
2168 [ + + ]: 9 : (resultRelInfo->ri_usesFdwDirectModify ||
1110 tgl@sss.pgh.pa.us 2169 [ + + ]: 5 : resultRelInfo->ri_FdwState))
1817 efujita@postgresql.o 2170 [ + - ]: 6 : ereport(ERROR,
2171 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2172 : : errmsg("cannot route tuples into foreign table to be updated \"%s\"",
2173 : : RelationGetRelationName(rel))));
2174 : :
2200 rhaas@postgresql.org 2175 : 54 : initStringInfo(&sql);
2176 : :
2177 : : /* We transmit all columns that are defined in the foreign table. */
2178 [ + + ]: 160 : for (attnum = 1; attnum <= tupdesc->natts; attnum++)
2179 : : {
2180 : 106 : Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
2181 : :
2182 [ + + ]: 106 : if (!attr->attisdropped)
2183 : 104 : targetAttrs = lappend_int(targetAttrs, attnum);
2184 : : }
2185 : :
2186 : : /* Check if we add the ON CONFLICT clause to the remote query. */
2187 [ + + ]: 54 : if (plan)
2188 : : {
2174 2189 : 32 : OnConflictAction onConflictAction = plan->onConflictAction;
2190 : :
2191 : : /* We only support DO NOTHING without an inference specification. */
2200 2192 [ + + ]: 32 : if (onConflictAction == ONCONFLICT_NOTHING)
2193 : 2 : doNothing = true;
2194 [ - + ]: 30 : else if (onConflictAction != ONCONFLICT_NONE)
2200 rhaas@postgresql.org 2195 [ # # ]:UBC 0 : elog(ERROR, "unexpected ON CONFLICT specification: %d",
2196 : : (int) onConflictAction);
2197 : : }
2198 : :
2199 : : /*
2200 : : * If the foreign table is a partition that doesn't have a corresponding
2201 : : * RTE entry, we need to create a new RTE describing the foreign table for
2202 : : * use by deparseInsertSql and create_foreign_modify() below, after first
2203 : : * copying the parent's RTE and modifying some fields to describe the
2204 : : * foreign partition to work on. However, if this is invoked by UPDATE,
2205 : : * the existing RTE may already correspond to this partition if it is one
2206 : : * of the UPDATE subplan target rels; in that case, we can just use the
2207 : : * existing RTE as-is.
2208 : : */
1161 heikki.linnakangas@i 2209 [ + + ]:CBC 54 : if (resultRelInfo->ri_RangeTableIndex == 0)
2210 : : {
2211 : 36 : ResultRelInfo *rootResultRelInfo = resultRelInfo->ri_RootResultRelInfo;
2212 : :
2213 : 36 : rte = exec_rt_fetch(rootResultRelInfo->ri_RangeTableIndex, estate);
2175 rhaas@postgresql.org 2214 : 36 : rte = copyObject(rte);
2215 : 36 : rte->relid = RelationGetRelid(rel);
2216 : 36 : rte->relkind = RELKIND_FOREIGN_TABLE;
2217 : :
2218 : : /*
2219 : : * For UPDATE, we must use the RT index of the first subplan target
2220 : : * rel's RTE, because the core code would have built expressions for
2221 : : * the partition, such as RETURNING, using that RT index as varno of
2222 : : * Vars contained in those expressions.
2223 : : */
2224 [ + + + + ]: 36 : if (plan && plan->operation == CMD_UPDATE &&
1161 heikki.linnakangas@i 2225 [ + - ]: 3 : rootResultRelInfo->ri_RangeTableIndex == plan->rootRelation)
2175 rhaas@postgresql.org 2226 : 3 : resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
2227 : : else
1161 heikki.linnakangas@i 2228 : 33 : resultRelation = rootResultRelInfo->ri_RangeTableIndex;
2229 : : }
2230 : : else
2231 : : {
2232 : 18 : resultRelation = resultRelInfo->ri_RangeTableIndex;
2233 : 18 : rte = exec_rt_fetch(resultRelation, estate);
2234 : : }
2235 : :
2236 : : /* Construct the SQL command string. */
2175 rhaas@postgresql.org 2237 : 54 : deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
2238 : : resultRelInfo->ri_WithCheckOptions,
2239 : : resultRelInfo->ri_returningList,
2240 : : &retrieved_attrs, &values_end_len);
2241 : :
2242 : : /* Construct an execution state. */
2200 2243 : 54 : fmstate = create_foreign_modify(mtstate->ps.state,
2244 : : rte,
2245 : : resultRelInfo,
2246 : : CMD_INSERT,
2247 : : NULL,
2248 : : sql.data,
2249 : : targetAttrs,
2250 : : values_end_len,
2251 : : retrieved_attrs != NIL,
2252 : : retrieved_attrs);
2253 : :
2254 : : /*
2255 : : * If the given resultRelInfo already has PgFdwModifyState set, it means
2256 : : * the foreign table is an UPDATE subplan result rel; in which case, store
2257 : : * the resulting state into the aux_fmstate of the PgFdwModifyState.
2258 : : */
1817 efujita@postgresql.o 2259 [ - + ]: 54 : if (resultRelInfo->ri_FdwState)
2260 : : {
1817 efujita@postgresql.o 2261 [ # # # # ]:UBC 0 : Assert(plan && plan->operation == CMD_UPDATE);
2262 [ # # ]: 0 : Assert(resultRelInfo->ri_usesFdwDirectModify == false);
2263 : 0 : ((PgFdwModifyState *) resultRelInfo->ri_FdwState)->aux_fmstate = fmstate;
2264 : : }
2265 : : else
1817 efujita@postgresql.o 2266 :CBC 54 : resultRelInfo->ri_FdwState = fmstate;
2200 rhaas@postgresql.org 2267 : 54 : }
2268 : :
2269 : : /*
2270 : : * postgresEndForeignInsert
2271 : : * Finish an insert operation on a foreign table
2272 : : */
2273 : : static void
2274 : 50 : postgresEndForeignInsert(EState *estate,
2275 : : ResultRelInfo *resultRelInfo)
2276 : : {
2277 : 50 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2278 : :
2279 [ - + ]: 50 : Assert(fmstate != NULL);
2280 : :
2281 : : /*
2282 : : * If the fmstate has aux_fmstate set, get the aux_fmstate (see
2283 : : * postgresBeginForeignInsert())
2284 : : */
1817 efujita@postgresql.o 2285 [ - + ]: 50 : if (fmstate->aux_fmstate)
1817 efujita@postgresql.o 2286 :UBC 0 : fmstate = fmstate->aux_fmstate;
2287 : :
2288 : : /* Destroy the execution state */
2200 rhaas@postgresql.org 2289 :CBC 50 : finish_foreign_modify(fmstate);
2290 : 50 : }
2291 : :
2292 : : /*
2293 : : * postgresIsForeignRelUpdatable
2294 : : * Determine whether a foreign table supports INSERT, UPDATE and/or
2295 : : * DELETE.
2296 : : */
2297 : : static int
3959 tgl@sss.pgh.pa.us 2298 : 317 : postgresIsForeignRelUpdatable(Relation rel)
2299 : : {
2300 : : bool updatable;
2301 : : ForeignTable *table;
2302 : : ForeignServer *server;
2303 : : ListCell *lc;
2304 : :
2305 : : /*
2306 : : * By default, all postgres_fdw foreign tables are assumed updatable. This
2307 : : * can be overridden by a per-server setting, which in turn can be
2308 : : * overridden by a per-table setting.
2309 : : */
2310 : 317 : updatable = true;
2311 : :
2312 : 317 : table = GetForeignTable(RelationGetRelid(rel));
2313 : 317 : server = GetForeignServer(table->serverid);
2314 : :
2315 [ + - + + : 1425 : foreach(lc, server->options)
+ + ]
2316 : : {
2317 : 1108 : DefElem *def = (DefElem *) lfirst(lc);
2318 : :
2319 [ - + ]: 1108 : if (strcmp(def->defname, "updatable") == 0)
3959 tgl@sss.pgh.pa.us 2320 :UBC 0 : updatable = defGetBoolean(def);
2321 : : }
3959 tgl@sss.pgh.pa.us 2322 [ + - + + :CBC 760 : foreach(lc, table->options)
+ + ]
2323 : : {
2324 : 443 : DefElem *def = (DefElem *) lfirst(lc);
2325 : :
2326 [ - + ]: 443 : if (strcmp(def->defname, "updatable") == 0)
3959 tgl@sss.pgh.pa.us 2327 :UBC 0 : updatable = defGetBoolean(def);
2328 : : }
2329 : :
2330 : : /*
2331 : : * Currently "updatable" means support for INSERT, UPDATE and DELETE.
2332 : : */
2333 : : return updatable ?
3959 tgl@sss.pgh.pa.us 2334 [ + - ]:CBC 317 : (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0;
2335 : : }
2336 : :
2337 : : /*
2338 : : * postgresRecheckForeignScan
2339 : : * Execute a local join execution plan for a foreign join
2340 : : */
2341 : : static bool
2987 rhaas@postgresql.org 2342 :UBC 0 : postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
2343 : : {
2344 : 0 : Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
2345 : 0 : PlanState *outerPlan = outerPlanState(node);
2346 : : TupleTableSlot *result;
2347 : :
2348 : : /* For base foreign relations, it suffices to set fdw_recheck_quals */
2349 [ # # ]: 0 : if (scanrelid > 0)
2350 : 0 : return true;
2351 : :
2352 [ # # ]: 0 : Assert(outerPlan != NULL);
2353 : :
2354 : : /* Execute a local join execution plan */
2355 : 0 : result = ExecProcNode(outerPlan);
2356 [ # # # # ]: 0 : if (TupIsNull(result))
2357 : 0 : return false;
2358 : :
2359 : : /* Store result in the given slot */
2360 : 0 : ExecCopySlot(slot, result);
2361 : :
2362 : 0 : return true;
2363 : : }
2364 : :
2365 : : /*
2366 : : * find_modifytable_subplan
2367 : : * Helper routine for postgresPlanDirectModify to find the
2368 : : * ModifyTable subplan node that scans the specified RTI.
2369 : : *
2370 : : * Returns NULL if the subplan couldn't be identified. That's not a fatal
2371 : : * error condition, we just abandon trying to do the update directly.
2372 : : */
2373 : : static ForeignScan *
1110 tgl@sss.pgh.pa.us 2374 :CBC 129 : find_modifytable_subplan(PlannerInfo *root,
2375 : : ModifyTable *plan,
2376 : : Index rtindex,
2377 : : int subplan_index)
2378 : : {
2379 : 129 : Plan *subplan = outerPlan(plan);
2380 : :
2381 : : /*
2382 : : * The cases we support are (1) the desired ForeignScan is the immediate
2383 : : * child of ModifyTable, or (2) it is the subplan_index'th child of an
2384 : : * Append node that is the immediate child of ModifyTable. There is no
2385 : : * point in looking further down, as that would mean that local joins are
2386 : : * involved, so we can't do the update directly.
2387 : : *
2388 : : * There could be a Result atop the Append too, acting to compute the
2389 : : * UPDATE targetlist values. We ignore that here; the tlist will be
2390 : : * checked by our caller.
2391 : : *
2392 : : * In principle we could examine all the children of the Append, but it's
2393 : : * currently unlikely that the core planner would generate such a plan
2394 : : * with the children out-of-order. Moreover, such a search risks costing
2395 : : * O(N^2) time when there are a lot of children.
2396 : : */
2397 [ + + ]: 129 : if (IsA(subplan, Append))
2398 : : {
2399 : 33 : Append *appendplan = (Append *) subplan;
2400 : :
2401 [ + - ]: 33 : if (subplan_index < list_length(appendplan->appendplans))
2402 : 33 : subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
2403 : : }
1012 2404 [ + + ]: 96 : else if (IsA(subplan, Result) &&
2405 [ + + ]: 6 : outerPlan(subplan) != NULL &&
2406 [ + - ]: 5 : IsA(outerPlan(subplan), Append))
2407 : : {
1110 2408 : 5 : Append *appendplan = (Append *) outerPlan(subplan);
2409 : :
2410 [ + - ]: 5 : if (subplan_index < list_length(appendplan->appendplans))
2411 : 5 : subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
2412 : : }
2413 : :
2414 : : /* Now, have we got a ForeignScan on the desired rel? */
2415 [ + + ]: 129 : if (IsA(subplan, ForeignScan))
2416 : : {
2417 : 114 : ForeignScan *fscan = (ForeignScan *) subplan;
2418 : :
440 2419 [ + - ]: 114 : if (bms_is_member(rtindex, fscan->fs_base_relids))
1110 2420 : 114 : return fscan;
2421 : : }
2422 : :
2423 : 15 : return NULL;
2424 : : }
2425 : :
2426 : : /*
2427 : : * postgresPlanDirectModify
2428 : : * Consider a direct foreign table modification
2429 : : *
2430 : : * Decide whether it is safe to modify a foreign table directly, and if so,
2431 : : * rewrite subplan accordingly.
2432 : : */
2433 : : static bool
2949 rhaas@postgresql.org 2434 : 193 : postgresPlanDirectModify(PlannerInfo *root,
2435 : : ModifyTable *plan,
2436 : : Index resultRelation,
2437 : : int subplan_index)
2438 : : {
2439 : 193 : CmdType operation = plan->operation;
2440 : : RelOptInfo *foreignrel;
2441 : : RangeTblEntry *rte;
2442 : : PgFdwRelationInfo *fpinfo;
2443 : : Relation rel;
2444 : : StringInfoData sql;
2445 : : ForeignScan *fscan;
1110 tgl@sss.pgh.pa.us 2446 : 193 : List *processed_tlist = NIL;
2949 rhaas@postgresql.org 2447 : 193 : List *targetAttrs = NIL;
2448 : : List *remote_exprs;
2449 : 193 : List *params_list = NIL;
2450 : 193 : List *returningList = NIL;
2451 : 193 : List *retrieved_attrs = NIL;
2452 : :
2453 : : /*
2454 : : * Decide whether it is safe to modify a foreign table directly.
2455 : : */
2456 : :
2457 : : /*
2458 : : * The table modification must be an UPDATE or DELETE.
2459 : : */
2460 [ + + + + ]: 193 : if (operation != CMD_UPDATE && operation != CMD_DELETE)
2461 : 64 : return false;
2462 : :
2463 : : /*
2464 : : * Try to locate the ForeignScan subplan that's scanning resultRelation.
2465 : : */
1110 tgl@sss.pgh.pa.us 2466 : 129 : fscan = find_modifytable_subplan(root, plan, resultRelation, subplan_index);
2467 [ + + ]: 129 : if (!fscan)
2949 rhaas@postgresql.org 2468 : 15 : return false;
2469 : :
2470 : : /*
2471 : : * It's unsafe to modify a foreign table directly if there are any quals
2472 : : * that should be evaluated locally.
2473 : : */
1110 tgl@sss.pgh.pa.us 2474 [ + + ]: 114 : if (fscan->scan.plan.qual != NIL)
2949 rhaas@postgresql.org 2475 : 5 : return false;
2476 : :
2477 : : /* Safe to fetch data about the target foreign rel */
2258 2478 [ + + ]: 109 : if (fscan->scan.scanrelid == 0)
2479 : : {
2480 : 10 : foreignrel = find_join_rel(root, fscan->fs_relids);
2481 : : /* We should have a rel for this foreign join. */
2482 [ - + ]: 10 : Assert(foreignrel);
2483 : : }
2484 : : else
2485 : 99 : foreignrel = root->simple_rel_array[resultRelation];
2560 tgl@sss.pgh.pa.us 2486 : 109 : rte = root->simple_rte_array[resultRelation];
2487 : 109 : fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
2488 : :
2489 : : /*
2490 : : * It's unsafe to update a foreign table directly, if any expressions to
2491 : : * assign to the target columns are unsafe to evaluate remotely.
2492 : : */
2949 rhaas@postgresql.org 2493 [ + + ]: 109 : if (operation == CMD_UPDATE)
2494 : : {
2495 : : ListCell *lc,
2496 : : *lc2;
2497 : :
2498 : : /*
2499 : : * The expressions of concern are the first N columns of the processed
2500 : : * targetlist, where N is the length of the rel's update_colnos.
2501 : : */
1110 tgl@sss.pgh.pa.us 2502 : 50 : get_translated_update_targetlist(root, resultRelation,
2503 : : &processed_tlist, &targetAttrs);
2504 [ + - + - : 103 : forboth(lc, processed_tlist, lc2, targetAttrs)
+ - + + +
- + + +
+ ]
2505 : : {
2506 : 58 : TargetEntry *tle = lfirst_node(TargetEntry, lc);
2507 : 58 : AttrNumber attno = lfirst_int(lc2);
2508 : :
2509 : : /* update's new-value expressions shouldn't be resjunk */
2510 [ - + ]: 58 : Assert(!tle->resjunk);
2511 : :
2489 2512 [ - + ]: 58 : if (attno <= InvalidAttrNumber) /* shouldn't happen */
2949 rhaas@postgresql.org 2513 [ # # ]:UBC 0 : elog(ERROR, "system-column update is not supported");
2514 : :
2560 tgl@sss.pgh.pa.us 2515 [ + + ]:CBC 58 : if (!is_foreign_expr(root, foreignrel, (Expr *) tle->expr))
2949 rhaas@postgresql.org 2516 : 5 : return false;
2517 : : }
2518 : : }
2519 : :
2520 : : /*
2521 : : * Ok, rewrite subplan so as to modify the foreign table directly.
2522 : : */
2523 : 104 : initStringInfo(&sql);
2524 : :
2525 : : /*
2526 : : * Core code already has some lock on each rel being planned, so we can
2527 : : * use NoLock here.
2528 : : */
1910 andres@anarazel.de 2529 : 104 : rel = table_open(rte->relid, NoLock);
2530 : :
2531 : : /*
2532 : : * Recall the qual clauses that must be evaluated remotely. (These are
2533 : : * bare clauses not RestrictInfos, but deparse.c's appendConditions()
2534 : : * doesn't care.)
2535 : : */
2560 tgl@sss.pgh.pa.us 2536 : 104 : remote_exprs = fpinfo->final_remote_exprs;
2537 : :
2538 : : /*
2539 : : * Extract the relevant RETURNING list if any.
2540 : : */
2949 rhaas@postgresql.org 2541 [ + + ]: 104 : if (plan->returningLists)
2542 : : {
2543 : 35 : returningList = (List *) list_nth(plan->returningLists, subplan_index);
2544 : :
2545 : : /*
2546 : : * When performing an UPDATE/DELETE .. RETURNING on a join directly,
2547 : : * we fetch from the foreign server any Vars specified in RETURNING
2548 : : * that refer not only to the target relation but to non-target
2549 : : * relations. So we'll deparse them into the RETURNING clause of the
2550 : : * remote query; use a targetlist consisting of them instead, which
2551 : : * will be adjusted to be new fdw_scan_tlist of the foreign-scan plan
2552 : : * node below.
2553 : : */
2258 2554 [ + + ]: 35 : if (fscan->scan.scanrelid == 0)
2555 : 4 : returningList = build_remote_returning(resultRelation, rel,
2556 : : returningList);
2557 : : }
2558 : :
2559 : : /*
2560 : : * Construct the SQL command string.
2561 : : */
2949 2562 [ + + - ]: 104 : switch (operation)
2563 : : {
2564 : 45 : case CMD_UPDATE:
2565 : 45 : deparseDirectUpdateSql(&sql, root, resultRelation, rel,
2566 : : foreignrel,
2567 : : processed_tlist,
2568 : : targetAttrs,
2569 : : remote_exprs, ¶ms_list,
2570 : : returningList, &retrieved_attrs);
2571 : 45 : break;
2572 : 59 : case CMD_DELETE:
2573 : 59 : deparseDirectDeleteSql(&sql, root, resultRelation, rel,
2574 : : foreignrel,
2575 : : remote_exprs, ¶ms_list,
2576 : : returningList, &retrieved_attrs);
2577 : 59 : break;
2949 rhaas@postgresql.org 2578 :UBC 0 : default:
2579 [ # # ]: 0 : elog(ERROR, "unexpected operation: %d", (int) operation);
2580 : : break;
2581 : : }
2582 : :
2583 : : /*
2584 : : * Update the operation and target relation info.
2585 : : */
2949 rhaas@postgresql.org 2586 :CBC 104 : fscan->operation = operation;
1278 heikki.linnakangas@i 2587 : 104 : fscan->resultRelation = resultRelation;
2588 : :
2589 : : /*
2590 : : * Update the fdw_exprs list that will be available to the executor.
2591 : : */
2949 rhaas@postgresql.org 2592 : 104 : fscan->fdw_exprs = params_list;
2593 : :
2594 : : /*
2595 : : * Update the fdw_private list that will be available to the executor.
2596 : : * Items in the list must match enum FdwDirectModifyPrivateIndex, above.
2597 : : */
2598 : 104 : fscan->fdw_private = list_make4(makeString(sql.data),
2599 : : makeBoolean((retrieved_attrs != NIL)),
2600 : : retrieved_attrs,
2601 : : makeBoolean(plan->canSetTag));
2602 : :
2603 : : /*
2604 : : * Update the foreign-join-related fields.
2605 : : */
2258 2606 [ + + ]: 104 : if (fscan->scan.scanrelid == 0)
2607 : : {
2608 : : /* No need for the outer subplan. */
2609 : 8 : fscan->scan.plan.lefttree = NULL;
2610 : :
2611 : : /* Build new fdw_scan_tlist if UPDATE/DELETE .. RETURNING. */
2612 [ + + ]: 8 : if (returningList)
2613 : 2 : rebuild_fdw_scan_tlist(fscan, returningList);
2614 : : }
2615 : :
2616 : : /*
2617 : : * Finally, unset the async-capable flag if it is set, as we currently
2618 : : * don't support asynchronous execution of direct modifications.
2619 : : */
1067 efujita@postgresql.o 2620 [ + + ]: 104 : if (fscan->scan.plan.async_capable)
2621 : 8 : fscan->scan.plan.async_capable = false;
2622 : :
1910 andres@anarazel.de 2623 : 104 : table_close(rel, NoLock);
2949 rhaas@postgresql.org 2624 : 104 : return true;
2625 : : }
2626 : :
2627 : : /*
2628 : : * postgresBeginDirectModify
2629 : : * Prepare a direct foreign table modification
2630 : : */
2631 : : static void
2632 : 104 : postgresBeginDirectModify(ForeignScanState *node, int eflags)
2633 : : {
2634 : 104 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
2635 : 104 : EState *estate = node->ss.ps.state;
2636 : : PgFdwDirectModifyState *dmstate;
2637 : : Index rtindex;
2638 : : Oid userid;
2639 : : ForeignTable *table;
2640 : : UserMapping *user;
2641 : : int numParams;
2642 : :
2643 : : /*
2644 : : * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
2645 : : */
2646 [ + + ]: 104 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
2647 : 32 : return;
2648 : :
2649 : : /*
2650 : : * We'll save private state in node->fdw_state.
2651 : : */
2652 : 72 : dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState));
2653 : 72 : node->fdw_state = (void *) dmstate;
2654 : :
2655 : : /*
2656 : : * Identify which user to do the remote access as. This should match what
2657 : : * ExecCheckPermissions() does.
2658 : : */
501 alvherre@alvh.no-ip. 2659 [ - + ]: 72 : userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
2660 : :
2661 : : /* Get info about foreign table. */
2662 : 72 : rtindex = node->resultRelInfo->ri_RangeTableIndex;
2258 rhaas@postgresql.org 2663 [ + + ]: 72 : if (fsplan->scan.scanrelid == 0)
2664 : 4 : dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
2665 : : else
2666 : 68 : dmstate->rel = node->ss.ss_currentRelation;
2949 2667 : 72 : table = GetForeignTable(RelationGetRelid(dmstate->rel));
2668 : 72 : user = GetUserMapping(userid, table->serverid);
2669 : :
2670 : : /*
2671 : : * Get connection to the foreign server. Connection manager will
2672 : : * establish new connection if necessary.
2673 : : */
1110 efujita@postgresql.o 2674 : 72 : dmstate->conn = GetConnection(user, false, &dmstate->conn_state);
2675 : :
2676 : : /* Update the foreign-join-related fields. */
2258 rhaas@postgresql.org 2677 [ + + ]: 72 : if (fsplan->scan.scanrelid == 0)
2678 : : {
2679 : : /* Save info about foreign table. */
2680 : 4 : dmstate->resultRel = dmstate->rel;
2681 : :
2682 : : /*
2683 : : * Set dmstate->rel to NULL to teach get_returning_data() and
2684 : : * make_tuple_from_result_row() that columns fetched from the remote
2685 : : * server are described by fdw_scan_tlist of the foreign-scan plan
2686 : : * node, not the tuple descriptor for the target relation.
2687 : : */
2688 : 4 : dmstate->rel = NULL;
2689 : : }
2690 : :
2691 : : /* Initialize state variable */
2930 tgl@sss.pgh.pa.us 2692 : 72 : dmstate->num_tuples = -1; /* -1 means not set yet */
2693 : :
2694 : : /* Get private info created by planner functions. */
2949 rhaas@postgresql.org 2695 : 72 : dmstate->query = strVal(list_nth(fsplan->fdw_private,
2696 : : FdwDirectModifyPrivateUpdateSql));
821 peter@eisentraut.org 2697 : 72 : dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private,
2698 : : FdwDirectModifyPrivateHasReturning));
2949 rhaas@postgresql.org 2699 : 72 : dmstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
2700 : : FdwDirectModifyPrivateRetrievedAttrs);
821 peter@eisentraut.org 2701 : 72 : dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private,
2702 : : FdwDirectModifyPrivateSetProcessed));
2703 : :
2704 : : /* Create context for per-tuple temp workspace. */
2949 rhaas@postgresql.org 2705 : 72 : dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
2706 : : "postgres_fdw temporary data",
2707 : : ALLOCSET_SMALL_SIZES);
2708 : :
2709 : : /* Prepare for input conversion of RETURNING results. */
2710 [ + + ]: 72 : if (dmstate->has_returning)
2711 : : {
2712 : : TupleDesc tupdesc;
2713 : :
2258 2714 [ + + ]: 16 : if (fsplan->scan.scanrelid == 0)
1045 tgl@sss.pgh.pa.us 2715 : 1 : tupdesc = get_tupdesc_for_join_scan_tuples(node);
2716 : : else
2258 rhaas@postgresql.org 2717 : 15 : tupdesc = RelationGetDescr(dmstate->rel);
2718 : :
2719 : 16 : dmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
2720 : :
2721 : : /*
2722 : : * When performing an UPDATE/DELETE .. RETURNING on a join directly,
2723 : : * initialize a filter to extract an updated/deleted tuple from a scan
2724 : : * tuple.
2725 : : */
2726 [ + + ]: 16 : if (fsplan->scan.scanrelid == 0)
2727 : 1 : init_returning_filter(dmstate, fsplan->fdw_scan_tlist, rtindex);
2728 : : }
2729 : :
2730 : : /*
2731 : : * Prepare for processing of parameters used in remote query, if any.
2732 : : */
2949 2733 : 72 : numParams = list_length(fsplan->fdw_exprs);
2734 : 72 : dmstate->numParams = numParams;
2735 [ - + ]: 72 : if (numParams > 0)
2949 rhaas@postgresql.org 2736 :UBC 0 : prepare_query_params((PlanState *) node,
2737 : : fsplan->fdw_exprs,
2738 : : numParams,
2739 : : &dmstate->param_flinfo,
2740 : : &dmstate->param_exprs,
2741 : : &dmstate->param_values);
2742 : : }
2743 : :
2744 : : /*
2745 : : * postgresIterateDirectModify
2746 : : * Execute a direct foreign table modification
2747 : : */
2748 : : static TupleTableSlot *
2949 rhaas@postgresql.org 2749 :CBC 418 : postgresIterateDirectModify(ForeignScanState *node)
2750 : : {
2751 : 418 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
2752 : 418 : EState *estate = node->ss.ps.state;
1278 heikki.linnakangas@i 2753 : 418 : ResultRelInfo *resultRelInfo = node->resultRelInfo;
2754 : :
2755 : : /*
2756 : : * If this is the first call after Begin, execute the statement.
2757 : : */
2949 rhaas@postgresql.org 2758 [ + + ]: 418 : if (dmstate->num_tuples == -1)
2759 : 71 : execute_dml_stmt(node);
2760 : :
2761 : : /*
2762 : : * If the local query doesn't specify RETURNING, just clear tuple slot.
2763 : : */
2764 [ + + ]: 414 : if (!resultRelInfo->ri_projectReturning)
2765 : : {
2766 : 50 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
2767 : 50 : Instrumentation *instr = node->ss.ps.instrument;
2768 : :
2769 [ - + ]: 50 : Assert(!dmstate->has_returning);
2770 : :
2771 : : /* Increment the command es_processed count if necessary. */
2772 [ + - ]: 50 : if (dmstate->set_processed)
2773 : 50 : estate->es_processed += dmstate->num_tuples;
2774 : :
2775 : : /* Increment the tuple count for EXPLAIN ANALYZE if necessary. */
2776 [ - + ]: 50 : if (instr)
2949 rhaas@postgresql.org 2777 :UBC 0 : instr->tuplecount += dmstate->num_tuples;
2778 : :
2949 rhaas@postgresql.org 2779 :CBC 50 : return ExecClearTuple(slot);
2780 : : }
2781 : :
2782 : : /*
2783 : : * Get the next RETURNING tuple.
2784 : : */
2785 : 364 : return get_returning_data(node);
2786 : : }
2787 : :
2788 : : /*
2789 : : * postgresEndDirectModify
2790 : : * Finish a direct foreign table modification
2791 : : */
2792 : : static void
2793 : 96 : postgresEndDirectModify(ForeignScanState *node)
2794 : : {
2795 : 96 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
2796 : :
2797 : : /* if dmstate is NULL, we are in EXPLAIN; nothing to do */
2798 [ + + ]: 96 : if (dmstate == NULL)
2799 : 32 : return;
2800 : :
2801 : : /* Release PGresult */
651 peter@eisentraut.org 2802 : 64 : PQclear(dmstate->result);
2803 : :
2804 : : /* Release remote connection */
2949 rhaas@postgresql.org 2805 : 64 : ReleaseConnection(dmstate->conn);
2806 : 64 : dmstate->conn = NULL;
2807 : :
2808 : : /* MemoryContext will be deleted automatically. */
2809 : : }
2810 : :
2811 : : /*
2812 : : * postgresExplainForeignScan
2813 : : * Produce extra output for EXPLAIN of a ForeignScan on a foreign table
2814 : : */
2815 : : static void
4053 tgl@sss.pgh.pa.us 2816 : 372 : postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
2817 : : {
1595 2818 : 372 : ForeignScan *plan = castNode(ForeignScan, node->ss.ps.plan);
2819 : 372 : List *fdw_private = plan->fdw_private;
2820 : :
2821 : : /*
2822 : : * Identify foreign scans that are really joins or upper relations. The
2823 : : * input looks something like "(1) LEFT JOIN (2)", and we must replace the
2824 : : * digit string(s), which are RT indexes, with the correct relation names.
2825 : : * We do that here, not when the plan is created, because we can't know
2826 : : * what aliases ruleutils.c will assign at plan creation time.
2827 : : */
2987 rhaas@postgresql.org 2828 [ + + ]: 372 : if (list_length(fdw_private) > FdwScanPrivateRelations)
2829 : : {
2830 : : StringInfo relations;
2831 : : char *rawrelations;
2832 : : char *ptr;
2833 : : int minrti,
2834 : : rtoffset;
2835 : :
1595 tgl@sss.pgh.pa.us 2836 : 117 : rawrelations = strVal(list_nth(fdw_private, FdwScanPrivateRelations));
2837 : :
2838 : : /*
2839 : : * A difficulty with using a string representation of RT indexes is
2840 : : * that setrefs.c won't update the string when flattening the
2841 : : * rangetable. To find out what rtoffset was applied, identify the
2842 : : * minimum RT index appearing in the string and compare it to the
2843 : : * minimum member of plan->fs_base_relids. (We expect all the relids
2844 : : * in the join will have been offset by the same amount; the Asserts
2845 : : * below should catch it if that ever changes.)
2846 : : */
2847 : 117 : minrti = INT_MAX;
2848 : 117 : ptr = rawrelations;
2849 [ + + ]: 2738 : while (*ptr)
2850 : : {
2851 [ + + ]: 2621 : if (isdigit((unsigned char) *ptr))
2852 : : {
2853 : 228 : int rti = strtol(ptr, &ptr, 10);
2854 : :
2855 [ + + ]: 228 : if (rti < minrti)
2856 : 128 : minrti = rti;
2857 : : }
2858 : : else
2859 : 2393 : ptr++;
2860 : : }
440 2861 : 117 : rtoffset = bms_next_member(plan->fs_base_relids, -1) - minrti;
2862 : :
2863 : : /* Now we can translate the string */
1595 2864 : 117 : relations = makeStringInfo();
2865 : 117 : ptr = rawrelations;
2866 [ + + ]: 2738 : while (*ptr)
2867 : : {
2868 [ + + ]: 2621 : if (isdigit((unsigned char) *ptr))
2869 : : {
2870 : 228 : int rti = strtol(ptr, &ptr, 10);
2871 : : RangeTblEntry *rte;
2872 : : char *relname;
2873 : : char *refname;
2874 : :
2875 : 228 : rti += rtoffset;
440 2876 [ - + ]: 228 : Assert(bms_is_member(rti, plan->fs_base_relids));
1595 2877 : 228 : rte = rt_fetch(rti, es->rtable);
2878 [ - + ]: 228 : Assert(rte->rtekind == RTE_RELATION);
2879 : : /* This logic should agree with explain.c's ExplainTargetRel */
2880 : 228 : relname = get_rel_name(rte->relid);
1594 2881 [ + + ]: 228 : if (es->verbose)
2882 : : {
2883 : : char *namespace;
2884 : :
992 2885 : 215 : namespace = get_namespace_name_or_temp(get_rel_namespace(rte->relid));
1594 2886 : 215 : appendStringInfo(relations, "%s.%s",
2887 : : quote_identifier(namespace),
2888 : : quote_identifier(relname));
2889 : : }
2890 : : else
1277 drowley@postgresql.o 2891 : 13 : appendStringInfoString(relations,
2892 : : quote_identifier(relname));
1595 tgl@sss.pgh.pa.us 2893 : 228 : refname = (char *) list_nth(es->rtable_names, rti - 1);
2894 [ - + ]: 228 : if (refname == NULL)
1595 tgl@sss.pgh.pa.us 2895 :UBC 0 : refname = rte->eref->aliasname;
1595 tgl@sss.pgh.pa.us 2896 [ + + ]:CBC 228 : if (strcmp(refname, relname) != 0)
2897 : 143 : appendStringInfo(relations, " %s",
2898 : : quote_identifier(refname));
2899 : : }
2900 : : else
2901 : 2393 : appendStringInfoChar(relations, *ptr++);
2902 : : }
2903 : 117 : ExplainPropertyText("Relations", relations->data, es);
2904 : : }
2905 : :
2906 : : /*
2907 : : * Add remote query, when VERBOSE option is specified.
2908 : : */
4053 2909 [ + + ]: 372 : if (es->verbose)
2910 : : {
2911 : : char *sql;
2912 : :
2913 : 336 : sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
2914 : 336 : ExplainPropertyText("Remote SQL", sql, es);
2915 : : }
2916 : 372 : }
2917 : :
2918 : : /*
2919 : : * postgresExplainForeignModify
2920 : : * Produce extra output for EXPLAIN of a ModifyTable on a foreign table
2921 : : */
2922 : : static void
2923 : 38 : postgresExplainForeignModify(ModifyTableState *mtstate,
2924 : : ResultRelInfo *rinfo,
2925 : : List *fdw_private,
2926 : : int subplan_index,
2927 : : ExplainState *es)
2928 : : {
2929 [ + - ]: 38 : if (es->verbose)
2930 : : {
2931 : 38 : char *sql = strVal(list_nth(fdw_private,
2932 : : FdwModifyPrivateUpdateSql));
2933 : :
2934 : 38 : ExplainPropertyText("Remote SQL", sql, es);
2935 : :
2936 : : /*
2937 : : * For INSERT we should always have batch size >= 1, but UPDATE and
2938 : : * DELETE don't support batching so don't show the property.
2939 : : */
1180 tomas.vondra@postgre 2940 [ + + ]: 38 : if (rinfo->ri_BatchSize > 0)
2941 : 13 : ExplainPropertyInteger("Batch Size", NULL, rinfo->ri_BatchSize, es);
2942 : : }
4053 tgl@sss.pgh.pa.us 2943 : 38 : }
2944 : :
2945 : : /*
2946 : : * postgresExplainDirectModify
2947 : : * Produce extra output for EXPLAIN of a ForeignScan that modifies a
2948 : : * foreign table directly
2949 : : */
2950 : : static void
2949 rhaas@postgresql.org 2951 : 32 : postgresExplainDirectModify(ForeignScanState *node, ExplainState *es)
2952 : : {
2953 : : List *fdw_private;
2954 : : char *sql;
2955 : :
2956 [ + - ]: 32 : if (es->verbose)
2957 : : {
2958 : 32 : fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
2959 : 32 : sql = strVal(list_nth(fdw_private, FdwDirectModifyPrivateUpdateSql));
2960 : 32 : ExplainPropertyText("Remote SQL", sql, es);
2961 : : }
2962 : 32 : }
2963 : :
2964 : : /*
2965 : : * postgresExecForeignTruncate
2966 : : * Truncate one or more foreign tables
2967 : : */
2968 : : static void
1102 fujii@postgresql.org 2969 : 15 : postgresExecForeignTruncate(List *rels,
2970 : : DropBehavior behavior,
2971 : : bool restart_seqs)
2972 : : {
2973 : 15 : Oid serverid = InvalidOid;
2974 : 15 : UserMapping *user = NULL;
2975 : 15 : PGconn *conn = NULL;
2976 : : StringInfoData sql;
2977 : : ListCell *lc;
2978 : 15 : bool server_truncatable = true;
2979 : :
2980 : : /*
2981 : : * By default, all postgres_fdw foreign tables are assumed truncatable.
2982 : : * This can be overridden by a per-server setting, which in turn can be
2983 : : * overridden by a per-table setting.
2984 : : */
2985 [ + - + + : 29 : foreach(lc, rels)
+ + ]
2986 : : {
2987 : 17 : ForeignServer *server = NULL;
2988 : 17 : Relation rel = lfirst(lc);
2989 : 17 : ForeignTable *table = GetForeignTable(RelationGetRelid(rel));
2990 : : ListCell *cell;
2991 : : bool truncatable;
2992 : :
2993 : : /*
2994 : : * First time through, determine whether the foreign server allows
2995 : : * truncates. Since all specified foreign tables are assumed to belong
2996 : : * to the same foreign server, this result can be used for other
2997 : : * foreign tables.
2998 : : */
2999 [ + + ]: 17 : if (!OidIsValid(serverid))
3000 : : {
3001 : 15 : serverid = table->serverid;
3002 : 15 : server = GetForeignServer(serverid);
3003 : :
3004 [ + - + + : 60 : foreach(cell, server->options)
+ + ]
3005 : : {
3006 : 48 : DefElem *defel = (DefElem *) lfirst(cell);
3007 : :
3008 [ + + ]: 48 : if (strcmp(defel->defname, "truncatable") == 0)
3009 : : {
3010 : 3 : server_truncatable = defGetBoolean(defel);
3011 : 3 : break;
3012 : : }
3013 : : }
3014 : : }
3015 : :
3016 : : /*
3017 : : * Confirm that all specified foreign tables belong to the same
3018 : : * foreign server.
3019 : : */
3020 [ - + ]: 17 : Assert(table->serverid == serverid);
3021 : :
3022 : : /* Determine whether this foreign table allows truncations */
3023 : 17 : truncatable = server_truncatable;
3024 [ + - + + : 34 : foreach(cell, table->options)
+ + ]
3025 : : {
3026 : 24 : DefElem *defel = (DefElem *) lfirst(cell);
3027 : :
3028 [ + + ]: 24 : if (strcmp(defel->defname, "truncatable") == 0)
3029 : : {
3030 : 7 : truncatable = defGetBoolean(defel);
3031 : 7 : break;
3032 : : }
3033 : : }
3034 : :
3035 [ + + ]: 17 : if (!truncatable)
3036 [ + - ]: 3 : ereport(ERROR,
3037 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3038 : : errmsg("foreign table \"%s\" does not allow truncates",
3039 : : RelationGetRelationName(rel))));
3040 : : }
3041 [ - + ]: 12 : Assert(OidIsValid(serverid));
3042 : :
3043 : : /*
3044 : : * Get connection to the foreign server. Connection manager will
3045 : : * establish new connection if necessary.
3046 : : */
3047 : 12 : user = GetUserMapping(GetUserId(), serverid);
3048 : 12 : conn = GetConnection(user, false, NULL);
3049 : :
3050 : : /* Construct the TRUNCATE command string */
3051 : 12 : initStringInfo(&sql);
1083 3052 : 12 : deparseTruncateSql(&sql, rels, behavior, restart_seqs);
3053 : :
3054 : : /* Issue the TRUNCATE command to remote server */
1102 3055 : 12 : do_sql_command(conn, sql.data);
3056 : :
3057 : 11 : pfree(sql.data);
3058 : 11 : }
3059 : :
3060 : : /*
3061 : : * estimate_path_cost_size
3062 : : * Get cost and size estimates for a foreign scan on given foreign relation
3063 : : * either a base relation or a join between foreign relations or an upper
3064 : : * relation containing foreign relations.
3065 : : *
3066 : : * param_join_conds are the parameterization clauses with outer relations.
3067 : : * pathkeys specify the expected sort order if any for given path being costed.
3068 : : * fpextra specifies additional post-scan/join-processing steps such as the
3069 : : * final sort and the LIMIT restriction.
3070 : : *
3071 : : * The function returns the cost and size estimates in p_rows, p_width,
3072 : : * p_startup_cost and p_total_cost variables.
3073 : : */
3074 : : static void
4042 tgl@sss.pgh.pa.us 3075 : 2582 : estimate_path_cost_size(PlannerInfo *root,
3076 : : RelOptInfo *foreignrel,
3077 : : List *param_join_conds,
3078 : : List *pathkeys,
3079 : : PgFdwPathExtraData *fpextra,
3080 : : double *p_rows, int *p_width,
3081 : : Cost *p_startup_cost, Cost *p_total_cost)
3082 : : {
2987 rhaas@postgresql.org 3083 : 2582 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
3084 : : double rows;
3085 : : double retrieved_rows;
3086 : : int width;
3087 : : Cost startup_cost;
3088 : : Cost total_cost;
3089 : :
3090 : : /* Make sure the core code has set up the relation's reltarget */
1907 efujita@postgresql.o 3091 [ - + ]: 2582 : Assert(foreignrel->reltarget);
3092 : :
3093 : : /*
3094 : : * If the table or the server is configured to use remote estimates,
3095 : : * connect to the foreign server and execute EXPLAIN to estimate the
3096 : : * number of rows selected by the restriction+join clauses. Otherwise,
3097 : : * estimate rows using whatever statistics we have locally, in a way
3098 : : * similar to ordinary tables.
3099 : : */
4042 tgl@sss.pgh.pa.us 3100 [ + + ]: 2582 : if (fpinfo->use_remote_estimate)
3101 : : {
3102 : : List *remote_param_join_conds;
3103 : : List *local_param_join_conds;
3104 : : StringInfoData sql;
3105 : : PGconn *conn;
3106 : : Selectivity local_sel;
3107 : : QualCost local_cost;
2987 rhaas@postgresql.org 3108 : 1243 : List *fdw_scan_tlist = NIL;
3109 : : List *remote_conds;
3110 : :
3111 : : /* Required only to be passed to deparseSelectStmtForRel */
3112 : : List *retrieved_attrs;
3113 : :
3114 : : /*
3115 : : * param_join_conds might contain both clauses that are safe to send
3116 : : * across, and clauses that aren't.
3117 : : */
3118 : 1243 : classifyConditions(root, foreignrel, param_join_conds,
3119 : : &remote_param_join_conds, &local_param_join_conds);
3120 : :
3121 : : /* Build the list of columns to be fetched from the foreign server. */
2568 3122 [ + + + + : 1243 : if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
+ + - + ]
2987 3123 : 504 : fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
3124 : : else
3125 : 739 : fdw_scan_tlist = NIL;
3126 : :
3127 : : /*
3128 : : * The complete list of remote conditions includes everything from
3129 : : * baserestrictinfo plus any extra join_conds relevant to this
3130 : : * particular path.
3131 : : */
1707 tgl@sss.pgh.pa.us 3132 : 1243 : remote_conds = list_concat(remote_param_join_conds,
2997 rhaas@postgresql.org 3133 : 1243 : fpinfo->remote_conds);
3134 : :
3135 : : /*
3136 : : * Construct EXPLAIN query including the desired SELECT, FROM, and
3137 : : * WHERE clauses. Params and other-relation Vars are replaced by dummy
3138 : : * values, so don't request params_list.
3139 : : */
4042 tgl@sss.pgh.pa.us 3140 : 1243 : initStringInfo(&sql);
3141 : 1243 : appendStringInfoString(&sql, "EXPLAIN ");
2987 rhaas@postgresql.org 3142 [ + + + + ]: 1243 : deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
3143 : : remote_conds, pathkeys,
1839 efujita@postgresql.o 3144 [ + + + - ]: 1243 : fpextra ? fpextra->has_final_sort : false,
3145 [ + + + + ]: 1243 : fpextra ? fpextra->has_limit : false,
3146 : : false, &retrieved_attrs, NULL);
3147 : :
3148 : : /* Get the remote estimate */
1110 3149 : 1243 : conn = GetConnection(fpinfo->user, false, NULL);
4042 tgl@sss.pgh.pa.us 3150 : 1243 : get_remote_estimate(sql.data, conn, &rows, &width,
3151 : : &startup_cost, &total_cost);
3152 : 1242 : ReleaseConnection(conn);
3153 : :
3154 : 1242 : retrieved_rows = rows;
3155 : :
3156 : : /* Factor in the selectivity of the locally-checked quals */
3691 3157 : 1242 : local_sel = clauselist_selectivity(root,
3158 : : local_param_join_conds,
2987 rhaas@postgresql.org 3159 : 1242 : foreignrel->relid,
3160 : : JOIN_INNER,
3161 : : NULL);
3691 tgl@sss.pgh.pa.us 3162 : 1242 : local_sel *= fpinfo->local_conds_sel;
3163 : :
3164 : 1242 : rows = clamp_row_est(rows * local_sel);
3165 : :
3166 : : /* Add in the eval cost of the locally-checked quals */
4042 3167 : 1242 : startup_cost += fpinfo->local_conds_cost.startup;
3168 : 1242 : total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
2987 rhaas@postgresql.org 3169 : 1242 : cost_qual_eval(&local_cost, local_param_join_conds, root);
3691 tgl@sss.pgh.pa.us 3170 : 1242 : startup_cost += local_cost.startup;
3171 : 1242 : total_cost += local_cost.per_tuple * retrieved_rows;
3172 : :
3173 : : /*
3174 : : * Add in tlist eval cost for each output row. In case of an
3175 : : * aggregate, some of the tlist expressions such as grouping
3176 : : * expressions will be evaluated remotely, so adjust the costs.
3177 : : */
1907 efujita@postgresql.o 3178 : 1242 : startup_cost += foreignrel->reltarget->cost.startup;
3179 : 1242 : total_cost += foreignrel->reltarget->cost.startup;
3180 : 1242 : total_cost += foreignrel->reltarget->cost.per_tuple * rows;
3181 [ + + - + ]: 1242 : if (IS_UPPER_REL(foreignrel))
3182 : : {
3183 : : QualCost tlist_cost;
3184 : :
3185 : 40 : cost_qual_eval(&tlist_cost, fdw_scan_tlist, root);
3186 : 40 : startup_cost -= tlist_cost.startup;
3187 : 40 : total_cost -= tlist_cost.startup;
3188 : 40 : total_cost -= tlist_cost.per_tuple * rows;
3189 : : }
3190 : : }
3191 : : else
3192 : : {
2987 rhaas@postgresql.org 3193 : 1339 : Cost run_cost = 0;
3194 : :
3195 : : /*
3196 : : * We don't support join conditions in this mode (hence, no
3197 : : * parameterized paths can be made).
3198 : : */
3199 [ - + ]: 1339 : Assert(param_join_conds == NIL);
3200 : :
3201 : : /*
3202 : : * We will come here again and again with different set of pathkeys or
3203 : : * additional post-scan/join-processing steps that caller wants to
3204 : : * cost. We don't need to calculate the cost/size estimates for the
3205 : : * underlying scan, join, or grouping each time. Instead, use those
3206 : : * estimates if we have cached them already.
3207 : : */
1902 efujita@postgresql.o 3208 [ + + + - ]: 1339 : if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0)
3209 : : {
1164 3210 [ - + ]: 309 : Assert(fpinfo->retrieved_rows >= 0);
3211 : :
1766 3212 : 309 : rows = fpinfo->rows;
3213 : 309 : retrieved_rows = fpinfo->retrieved_rows;
3214 : 309 : width = fpinfo->width;
2958 rhaas@postgresql.org 3215 : 309 : startup_cost = fpinfo->rel_startup_cost;
3216 : 309 : run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost;
3217 : :
3218 : : /*
3219 : : * If we estimate the costs of a foreign scan or a foreign join
3220 : : * with additional post-scan/join-processing steps, the scan or
3221 : : * join costs obtained from the cache wouldn't yet contain the
3222 : : * eval costs for the final scan/join target, which would've been
3223 : : * updated by apply_scanjoin_target_to_paths(); add the eval costs
3224 : : * now.
3225 : : */
1839 efujita@postgresql.o 3226 [ + + + + : 309 : if (fpextra && !IS_UPPER_REL(foreignrel))
+ - ]
3227 : : {
3228 : : /* Shouldn't get here unless we have LIMIT */
3229 [ - + ]: 89 : Assert(fpextra->has_limit);
3230 [ + + - + ]: 89 : Assert(foreignrel->reloptkind == RELOPT_BASEREL ||
3231 : : foreignrel->reloptkind == RELOPT_JOINREL);
3232 : 89 : startup_cost += foreignrel->reltarget->cost.startup;
3233 : 89 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3234 : : }
3235 : : }
2568 rhaas@postgresql.org 3236 [ + + + + ]: 1030 : else if (IS_JOIN_REL(foreignrel))
2987 3237 : 88 : {
3238 : : PgFdwRelationInfo *fpinfo_i;
3239 : : PgFdwRelationInfo *fpinfo_o;
3240 : : QualCost join_cost;
3241 : : QualCost remote_conds_cost;
3242 : : double nrows;
3243 : :
3244 : : /* Use rows/width estimates made by the core code. */
1766 efujita@postgresql.o 3245 : 88 : rows = foreignrel->rows;
3246 : 88 : width = foreignrel->reltarget->width;
3247 : :
3248 : : /* For join we expect inner and outer relations set */
2987 rhaas@postgresql.org 3249 [ + - - + ]: 88 : Assert(fpinfo->innerrel && fpinfo->outerrel);
3250 : :
3251 : 88 : fpinfo_i = (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
3252 : 88 : fpinfo_o = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
3253 : :
3254 : : /* Estimate of number of rows in cross product */
3255 : 88 : nrows = fpinfo_i->rows * fpinfo_o->rows;
3256 : :
3257 : : /*
3258 : : * Back into an estimate of the number of retrieved rows. Just in
3259 : : * case this is nuts, clamp to at most nrows.
3260 : : */
1766 efujita@postgresql.o 3261 : 88 : retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
2987 rhaas@postgresql.org 3262 [ + + ]: 88 : retrieved_rows = Min(retrieved_rows, nrows);
3263 : :
3264 : : /*
3265 : : * The cost of foreign join is estimated as cost of generating
3266 : : * rows for the joining relations + cost for applying quals on the
3267 : : * rows.
3268 : : */
3269 : :
3270 : : /*
3271 : : * Calculate the cost of clauses pushed down to the foreign server
3272 : : */
3273 : 88 : cost_qual_eval(&remote_conds_cost, fpinfo->remote_conds, root);
3274 : : /* Calculate the cost of applying join clauses */
3275 : 88 : cost_qual_eval(&join_cost, fpinfo->joinclauses, root);
3276 : :
3277 : : /*
3278 : : * Startup cost includes startup cost of joining relations and the
3279 : : * startup cost for join and other clauses. We do not include the
3280 : : * startup cost specific to join strategy (e.g. setting up hash
3281 : : * tables) since we do not know what strategy the foreign server
3282 : : * is going to use.
3283 : : */
3284 : 88 : startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost;
3285 : 88 : startup_cost += join_cost.startup;
3286 : 88 : startup_cost += remote_conds_cost.startup;
3287 : 88 : startup_cost += fpinfo->local_conds_cost.startup;
3288 : :
3289 : : /*
3290 : : * Run time cost includes:
3291 : : *
3292 : : * 1. Run time cost (total_cost - startup_cost) of relations being
3293 : : * joined
3294 : : *
3295 : : * 2. Run time cost of applying join clauses on the cross product
3296 : : * of the joining relations.
3297 : : *
3298 : : * 3. Run time cost of applying pushed down other clauses on the
3299 : : * result of join
3300 : : *
3301 : : * 4. Run time cost of applying nonpushable other clauses locally
3302 : : * on the result fetched from the foreign server.
3303 : : */
3304 : 88 : run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost;
3305 : 88 : run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost;
3306 : 88 : run_cost += nrows * join_cost.per_tuple;
3307 : 88 : nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
3308 : 88 : run_cost += nrows * remote_conds_cost.per_tuple;
3309 : 88 : run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
3310 : :
3311 : : /* Add in tlist eval cost for each output row */
1907 efujita@postgresql.o 3312 : 88 : startup_cost += foreignrel->reltarget->cost.startup;
3313 : 88 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3314 : : }
2568 rhaas@postgresql.org 3315 [ + + + + ]: 942 : else if (IS_UPPER_REL(foreignrel))
2732 3316 : 93 : {
1802 efujita@postgresql.o 3317 : 93 : RelOptInfo *outerrel = fpinfo->outerrel;
3318 : : PgFdwRelationInfo *ofpinfo;
3319 : : AggClauseCosts aggcosts;
3320 : : double input_rows;
3321 : : int numGroupCols;
2732 rhaas@postgresql.org 3322 : 93 : double numGroups = 1;
3323 : :
3324 : : /* The upper relation should have its outer relation set */
1802 efujita@postgresql.o 3325 [ - + ]: 93 : Assert(outerrel);
3326 : : /* and that outer relation should have its reltarget set */
3327 [ - + ]: 93 : Assert(outerrel->reltarget);
3328 : :
3329 : : /*
3330 : : * This cost model is mixture of costing done for sorted and
3331 : : * hashed aggregates in cost_agg(). We are not sure which
3332 : : * strategy will be considered at remote side, thus for
3333 : : * simplicity, we put all startup related costs in startup_cost
3334 : : * and all finalization and run cost are added in total_cost.
3335 : : */
3336 : :
3337 : 93 : ofpinfo = (PgFdwRelationInfo *) outerrel->fdw_private;
3338 : :
3339 : : /* Get rows from input rel */
2732 rhaas@postgresql.org 3340 : 93 : input_rows = ofpinfo->rows;
3341 : :
3342 : : /* Collect statistics about aggregates for estimating costs. */
638 peter@eisentraut.org 3343 [ + - + - : 558 : MemSet(&aggcosts, 0, sizeof(AggClauseCosts));
+ - + - +
+ ]
2732 rhaas@postgresql.org 3344 [ + + ]: 93 : if (root->parse->hasAggs)
3345 : : {
1237 heikki.linnakangas@i 3346 : 89 : get_agg_clause_costs(root, AGGSPLIT_SIMPLE, &aggcosts);
3347 : : }
3348 : :
3349 : : /* Get number of grouping columns and possible number of groups */
452 tgl@sss.pgh.pa.us 3350 : 93 : numGroupCols = list_length(root->processed_groupClause);
2732 rhaas@postgresql.org 3351 : 93 : numGroups = estimate_num_groups(root,
3352 : : get_sortgrouplist_exprs(root->processed_groupClause,
3353 : : fpinfo->grouped_tlist),
3354 : : input_rows, NULL, NULL);
3355 : :
3356 : : /*
3357 : : * Get the retrieved_rows and rows estimates. If there are HAVING
3358 : : * quals, account for their selectivity.
3359 : : */
544 tgl@sss.pgh.pa.us 3360 [ + + ]: 93 : if (root->hasHavingQual)
3361 : : {
3362 : : /* Factor in the selectivity of the remotely-checked quals */
3363 : : retrieved_rows =
1958 efujita@postgresql.o 3364 : 14 : clamp_row_est(numGroups *
3365 : 14 : clauselist_selectivity(root,
3366 : : fpinfo->remote_conds,
3367 : : 0,
3368 : : JOIN_INNER,
3369 : : NULL));
3370 : : /* Factor in the selectivity of the locally-checked quals */
3371 : 14 : rows = clamp_row_est(retrieved_rows * fpinfo->local_conds_sel);
3372 : : }
3373 : : else
3374 : : {
3375 : 79 : rows = retrieved_rows = numGroups;
3376 : : }
3377 : :
3378 : : /* Use width estimate made by the core code. */
1766 3379 : 93 : width = foreignrel->reltarget->width;
3380 : :
3381 : : /*-----
3382 : : * Startup cost includes:
3383 : : * 1. Startup cost for underneath input relation, adjusted for
3384 : : * tlist replacement by apply_scanjoin_target_to_paths()
3385 : : * 2. Cost of performing aggregation, per cost_agg()
3386 : : *-----
3387 : : */
2732 rhaas@postgresql.org 3388 : 93 : startup_cost = ofpinfo->rel_startup_cost;
1802 efujita@postgresql.o 3389 : 93 : startup_cost += outerrel->reltarget->cost.startup;
2732 rhaas@postgresql.org 3390 : 93 : startup_cost += aggcosts.transCost.startup;
3391 : 93 : startup_cost += aggcosts.transCost.per_tuple * input_rows;
1891 tgl@sss.pgh.pa.us 3392 : 93 : startup_cost += aggcosts.finalCost.startup;
2732 rhaas@postgresql.org 3393 : 93 : startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
3394 : :
3395 : : /*-----
3396 : : * Run time cost includes:
3397 : : * 1. Run time cost of underneath input relation, adjusted for
3398 : : * tlist replacement by apply_scanjoin_target_to_paths()
3399 : : * 2. Run time cost of performing aggregation, per cost_agg()
3400 : : *-----
3401 : : */
3402 : 93 : run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
1802 efujita@postgresql.o 3403 : 93 : run_cost += outerrel->reltarget->cost.per_tuple * input_rows;
1891 tgl@sss.pgh.pa.us 3404 : 93 : run_cost += aggcosts.finalCost.per_tuple * numGroups;
2732 rhaas@postgresql.org 3405 : 93 : run_cost += cpu_tuple_cost * numGroups;
3406 : :
3407 : : /* Account for the eval cost of HAVING quals, if any */
544 tgl@sss.pgh.pa.us 3408 [ + + ]: 93 : if (root->hasHavingQual)
3409 : : {
3410 : : QualCost remote_cost;
3411 : :
3412 : : /* Add in the eval cost of the remotely-checked quals */
1958 efujita@postgresql.o 3413 : 14 : cost_qual_eval(&remote_cost, fpinfo->remote_conds, root);
3414 : 14 : startup_cost += remote_cost.startup;
3415 : 14 : run_cost += remote_cost.per_tuple * numGroups;
3416 : : /* Add in the eval cost of the locally-checked quals */
3417 : 14 : startup_cost += fpinfo->local_conds_cost.startup;
3418 : 14 : run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
3419 : : }
3420 : :
3421 : : /* Add in tlist eval cost for each output row */
1907 3422 : 93 : startup_cost += foreignrel->reltarget->cost.startup;
3423 : 93 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3424 : : }
3425 : : else
3426 : : {
3427 : : Cost cpu_per_tuple;
3428 : :
3429 : : /* Use rows/width estimates made by set_baserel_size_estimates. */
1766 3430 : 849 : rows = foreignrel->rows;
3431 : 849 : width = foreignrel->reltarget->width;
3432 : :
3433 : : /*
3434 : : * Back into an estimate of the number of retrieved rows. Just in
3435 : : * case this is nuts, clamp to at most foreignrel->tuples.
3436 : : */
3437 : 849 : retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
2732 rhaas@postgresql.org 3438 [ + + ]: 849 : retrieved_rows = Min(retrieved_rows, foreignrel->tuples);
3439 : :
3440 : : /*
3441 : : * Cost as though this were a seqscan, which is pessimistic. We
3442 : : * effectively imagine the local_conds are being evaluated
3443 : : * remotely, too.
3444 : : */
3445 : 849 : startup_cost = 0;
3446 : 849 : run_cost = 0;
3447 : 849 : run_cost += seq_page_cost * foreignrel->pages;
3448 : :
3449 : 849 : startup_cost += foreignrel->baserestrictcost.startup;
3450 : 849 : cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple;
3451 : 849 : run_cost += cpu_per_tuple * foreignrel->tuples;
3452 : :
3453 : : /* Add in tlist eval cost for each output row */
1907 efujita@postgresql.o 3454 : 849 : startup_cost += foreignrel->reltarget->cost.startup;
3455 : 849 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3456 : : }
3457 : :
3458 : : /*
3459 : : * Without remote estimates, we have no real way to estimate the cost
3460 : : * of generating sorted output. It could be free if the query plan
3461 : : * the remote side would have chosen generates properly-sorted output
3462 : : * anyway, but in most cases it will cost something. Estimate a value
3463 : : * high enough that we won't pick the sorted path when the ordering
3464 : : * isn't locally useful, but low enough that we'll err on the side of
3465 : : * pushing down the ORDER BY clause when it's useful to do so.
3466 : : */
3085 rhaas@postgresql.org 3467 [ + + ]: 1339 : if (pathkeys != NIL)
3468 : : {
1839 efujita@postgresql.o 3469 [ + + - + ]: 254 : if (IS_UPPER_REL(foreignrel))
3470 : : {
3471 [ + - - + ]: 30 : Assert(foreignrel->reloptkind == RELOPT_UPPER_REL &&
3472 : : fpinfo->stage == UPPERREL_GROUP_AGG);
3473 : 30 : adjust_foreign_grouping_path_cost(root, pathkeys,
3474 : : retrieved_rows, width,
3475 : : fpextra->limit_tuples,
3476 : : &startup_cost, &run_cost);
3477 : : }
3478 : : else
3479 : : {
3480 : 224 : startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
3481 : 224 : run_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
3482 : : }
3483 : : }
3484 : :
4042 tgl@sss.pgh.pa.us 3485 : 1339 : total_cost = startup_cost + run_cost;
3486 : :
3487 : : /* Adjust the cost estimates if we have LIMIT */
1839 efujita@postgresql.o 3488 [ + + + + ]: 1339 : if (fpextra && fpextra->has_limit)
3489 : : {
3490 : 91 : adjust_limit_rows_costs(&rows, &startup_cost, &total_cost,
3491 : : fpextra->offset_est, fpextra->count_est);
3492 : 91 : retrieved_rows = rows;
3493 : : }
3494 : : }
3495 : :
3496 : : /*
3497 : : * If this includes the final sort step, the given target, which will be
3498 : : * applied to the resulting path, might have different expressions from
3499 : : * the foreignrel's reltarget (see make_sort_input_target()); adjust tlist
3500 : : * eval costs.
3501 : : */
3502 [ + + + + ]: 2581 : if (fpextra && fpextra->has_final_sort &&
3503 [ + + ]: 107 : fpextra->target != foreignrel->reltarget)
3504 : : {
3505 : 6 : QualCost oldcost = foreignrel->reltarget->cost;
3506 : 6 : QualCost newcost = fpextra->target->cost;
3507 : :
3508 : 6 : startup_cost += newcost.startup - oldcost.startup;
3509 : 6 : total_cost += newcost.startup - oldcost.startup;
3510 : 6 : total_cost += (newcost.per_tuple - oldcost.per_tuple) * rows;
3511 : : }
3512 : :
3513 : : /*
3514 : : * Cache the retrieved rows and cost estimates for scans, joins, or
3515 : : * groupings without any parameterization, pathkeys, or additional
3516 : : * post-scan/join-processing steps, before adding the costs for
3517 : : * transferring data from the foreign server. These estimates are useful
3518 : : * for costing remote joins involving this relation or costing other
3519 : : * remote operations on this relation such as remote sorts and remote
3520 : : * LIMIT restrictions, when the costs can not be obtained from the foreign
3521 : : * server. This function will be called at least once for every foreign
3522 : : * relation without any parameterization, pathkeys, or additional
3523 : : * post-scan/join-processing steps.
3524 : : */
3525 [ + + + + : 2581 : if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL)
+ + ]
3526 : : {
1766 3527 : 1569 : fpinfo->retrieved_rows = retrieved_rows;
2958 rhaas@postgresql.org 3528 : 1569 : fpinfo->rel_startup_cost = startup_cost;
3529 : 1569 : fpinfo->rel_total_cost = total_cost;
3530 : : }
3531 : :
3532 : : /*
3533 : : * Add some additional cost factors to account for connection overhead
3534 : : * (fdw_startup_cost), transferring data across the network
3535 : : * (fdw_tuple_cost per retrieved row), and local manipulation of the data
3536 : : * (cpu_tuple_cost per retrieved row).
3537 : : */
4042 tgl@sss.pgh.pa.us 3538 : 2581 : startup_cost += fpinfo->fdw_startup_cost;
3539 : 2581 : total_cost += fpinfo->fdw_startup_cost;
3540 : 2581 : total_cost += fpinfo->fdw_tuple_cost * retrieved_rows;
3541 : 2581 : total_cost += cpu_tuple_cost * retrieved_rows;
3542 : :
3543 : : /*
3544 : : * If we have LIMIT, we should prefer performing the restriction remotely
3545 : : * rather than locally, as the former avoids extra row fetches from the
3546 : : * remote that the latter might cause. But since the core code doesn't
3547 : : * account for such fetches when estimating the costs of the local
3548 : : * restriction (see create_limit_path()), there would be no difference
3549 : : * between the costs of the local restriction and the costs of the remote
3550 : : * restriction estimated above if we don't use remote estimates (except
3551 : : * for the case where the foreignrel is a grouping relation, the given
3552 : : * pathkeys is not NIL, and the effects of a bounded sort for that rel is
3553 : : * accounted for in costing the remote restriction). Tweak the costs of
3554 : : * the remote restriction to ensure we'll prefer it if LIMIT is a useful
3555 : : * one.
3556 : : */
1839 efujita@postgresql.o 3557 [ + + + + ]: 2581 : if (!fpinfo->use_remote_estimate &&
3558 [ + + ]: 121 : fpextra && fpextra->has_limit &&
3559 [ + - ]: 91 : fpextra->limit_tuples > 0 &&
3560 [ + + ]: 91 : fpextra->limit_tuples < fpinfo->rows)
3561 : : {
3562 [ - + ]: 85 : Assert(fpinfo->rows > 0);
3563 : 85 : total_cost -= (total_cost - startup_cost) * 0.05 *
3564 : 85 : (fpinfo->rows - fpextra->limit_tuples) / fpinfo->rows;
3565 : : }
3566 : :
3567 : : /* Return results. */
4042 tgl@sss.pgh.pa.us 3568 : 2581 : *p_rows = rows;
3569 : 2581 : *p_width = width;
3570 : 2581 : *p_startup_cost = startup_cost;
3571 : 2581 : *p_total_cost = total_cost;
3572 : 2581 : }
3573 : :
3574 : : /*
3575 : : * Estimate costs of executing a SQL statement remotely.
3576 : : * The given "sql" must be an EXPLAIN command.
3577 : : */
3578 : : static void
4070 3579 : 1243 : get_remote_estimate(const char *sql, PGconn *conn,
3580 : : double *rows, int *width,
3581 : : Cost *startup_cost, Cost *total_cost)
3582 : : {
3583 : 1243 : PGresult *volatile res = NULL;
3584 : :
3585 : : /* PGresult must be released before leaving this function. */
3586 [ + + ]: 1243 : PG_TRY();
3587 : : {
3588 : : char *line;
3589 : : char *p;
3590 : : int n;
3591 : :
3592 : : /*
3593 : : * Execute EXPLAIN remotely.
3594 : : */
1110 efujita@postgresql.o 3595 : 1243 : res = pgfdw_exec_query(conn, sql, NULL);
4070 tgl@sss.pgh.pa.us 3596 [ - + ]: 1242 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3723 tgl@sss.pgh.pa.us 3597 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, sql);
3598 : :
3599 : : /*
3600 : : * Extract cost numbers for topmost plan node. Note we search for a
3601 : : * left paren from the end of the line to avoid being confused by
3602 : : * other uses of parentheses.
3603 : : */
4070 tgl@sss.pgh.pa.us 3604 :CBC 1242 : line = PQgetvalue(res, 0, 0);
3605 : 1242 : p = strrchr(line, '(');
3606 [ - + ]: 1242 : if (p == NULL)
4070 tgl@sss.pgh.pa.us 3607 [ # # ]:UBC 0 : elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
4070 tgl@sss.pgh.pa.us 3608 :CBC 1242 : n = sscanf(p, "(cost=%lf..%lf rows=%lf width=%d)",
3609 : : startup_cost, total_cost, rows, width);
3610 [ - + ]: 1242 : if (n != 4)
4070 tgl@sss.pgh.pa.us 3611 [ # # ]:UBC 0 : elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
3612 : : }
1626 peter@eisentraut.org 3613 :GBC 1 : PG_FINALLY();
3614 : : {
651 peter@eisentraut.org 3615 :CBC 1243 : PQclear(res);
3616 : : }
4070 tgl@sss.pgh.pa.us 3617 [ + + ]: 1243 : PG_END_TRY();
3618 : 1242 : }
3619 : :
3620 : : /*
3621 : : * Adjust the cost estimates of a foreign grouping path to include the cost of
3622 : : * generating properly-sorted output.
3623 : : */
3624 : : static void
1839 efujita@postgresql.o 3625 : 30 : adjust_foreign_grouping_path_cost(PlannerInfo *root,
3626 : : List *pathkeys,
3627 : : double retrieved_rows,
3628 : : double width,
3629 : : double limit_tuples,
3630 : : Cost *p_startup_cost,
3631 : : Cost *p_run_cost)
3632 : : {
3633 : : /*
3634 : : * If the GROUP BY clause isn't sort-able, the plan chosen by the remote
3635 : : * side is unlikely to generate properly-sorted output, so it would need
3636 : : * an explicit sort; adjust the given costs with cost_sort(). Likewise,
3637 : : * if the GROUP BY clause is sort-able but isn't a superset of the given
3638 : : * pathkeys, adjust the costs with that function. Otherwise, adjust the
3639 : : * costs by applying the same heuristic as for the scan or join case.
3640 : : */
452 tgl@sss.pgh.pa.us 3641 [ + - ]: 30 : if (!grouping_is_sortable(root->processed_groupClause) ||
1839 efujita@postgresql.o 3642 [ + + ]: 30 : !pathkeys_contained_in(pathkeys, root->group_pathkeys))
3643 : 22 : {
3644 : : Path sort_path; /* dummy for result of cost_sort */
3645 : :
3646 : 22 : cost_sort(&sort_path,
3647 : : root,
3648 : : pathkeys,
3649 : 22 : *p_startup_cost + *p_run_cost,
3650 : : retrieved_rows,
3651 : : width,
3652 : : 0.0,
3653 : : work_mem,
3654 : : limit_tuples);
3655 : :
3656 : 22 : *p_startup_cost = sort_path.startup_cost;
3657 : 22 : *p_run_cost = sort_path.total_cost - sort_path.startup_cost;
3658 : : }
3659 : : else
3660 : : {
3661 : : /*
3662 : : * The default extra cost seems too large for foreign-grouping cases;
3663 : : * add 1/4th of that default.
3664 : : */
3665 : 8 : double sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER
3666 : : - 1.0) * 0.25;
3667 : :
3668 : 8 : *p_startup_cost *= sort_multiplier;
3669 : 8 : *p_run_cost *= sort_multiplier;
3670 : : }
3671 : 30 : }
3672 : :
3673 : : /*
3674 : : * Detect whether we want to process an EquivalenceClass member.
3675 : : *
3676 : : * This is a callback for use by generate_implied_equalities_for_column.
3677 : : */
3678 : : static bool
4042 tgl@sss.pgh.pa.us 3679 : 298 : ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
3680 : : EquivalenceClass *ec, EquivalenceMember *em,
3681 : : void *arg)
3682 : : {
3683 : 298 : ec_member_foreign_arg *state = (ec_member_foreign_arg *) arg;
3684 : 298 : Expr *expr = em->em_expr;
3685 : :
3686 : : /*
3687 : : * If we've identified what we're processing in the current scan, we only
3688 : : * want to match that expression.
3689 : : */
3690 [ - + ]: 298 : if (state->current != NULL)
4042 tgl@sss.pgh.pa.us 3691 :UBC 0 : return equal(expr, state->current);
3692 : :
3693 : : /*
3694 : : * Otherwise, ignore anything we've already processed.
3695 : : */
4042 tgl@sss.pgh.pa.us 3696 [ + + ]:CBC 298 : if (list_member(state->already_used, expr))
3697 : 159 : return false;
3698 : :
3699 : : /* This is the new target to process. */
3700 : 139 : state->current = expr;
3701 : 139 : return true;
3702 : : }
3703 : :
3704 : : /*
3705 : : * Create cursor for node's query with current parameter values.
3706 : : */
3707 : : static void
4070 3708 : 793 : create_cursor(ForeignScanState *node)
3709 : : {
4053 3710 : 793 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
4042 3711 : 793 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
4053 3712 : 793 : int numParams = fsstate->numParams;
3713 : 793 : const char **values = fsstate->param_values;
3714 : 793 : PGconn *conn = fsstate->conn;
3715 : : StringInfoData buf;
3716 : : PGresult *res;
3717 : :
3718 : : /* First, process a pending asynchronous request, if any. */
1110 efujita@postgresql.o 3719 [ + + ]: 793 : if (fsstate->conn_state->pendingAreq)
3720 : 1 : process_pending_request(fsstate->conn_state->pendingAreq);
3721 : :
3722 : : /*
3723 : : * Construct array of query parameter values in text format. We do the
3724 : : * conversions in the short-lived per-tuple context, so as not to cause a
3725 : : * memory leak over repeated scans.
3726 : : */
4042 tgl@sss.pgh.pa.us 3727 [ + + ]: 793 : if (numParams > 0)
3728 : : {
3729 : : MemoryContext oldcontext;
3730 : :
3731 : 346 : oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
3732 : :
2949 rhaas@postgresql.org 3733 : 346 : process_query_params(econtext,
3734 : : fsstate->param_flinfo,
3735 : : fsstate->param_exprs,
3736 : : values);
3737 : :
4042 tgl@sss.pgh.pa.us 3738 : 346 : MemoryContextSwitchTo(oldcontext);
3739 : : }
3740 : :
3741 : : /* Construct the DECLARE CURSOR command */
4070 3742 : 793 : initStringInfo(&buf);
3743 : 793 : appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
3744 : : fsstate->cursor_number, fsstate->query);
3745 : :
3746 : : /*
3747 : : * Notice that we pass NULL for paramTypes, thus forcing the remote server
3748 : : * to infer types for all parameters. Since we explicitly cast every
3749 : : * parameter (see deparse.c), the "inference" is trivial and will produce
3750 : : * the desired result. This allows us to avoid assuming that the remote
3751 : : * server has the same OIDs we do for the parameters' types.
3752 : : */
2915 rhaas@postgresql.org 3753 [ - + ]: 793 : if (!PQsendQueryParams(conn, buf.data, numParams,
3754 : : NULL, values, NULL, NULL, 0))
2915 rhaas@postgresql.org 3755 :UBC 0 : pgfdw_report_error(ERROR, NULL, conn, false, buf.data);
3756 : :
3757 : : /*
3758 : : * Get the result, and check for success.
3759 : : *
3760 : : * We don't use a PG_TRY block here, so be careful not to throw error
3761 : : * without releasing the PGresult.
3762 : : */
97 noah@leadboat.com 3763 :GNC 793 : res = pgfdw_get_result(conn);
4070 tgl@sss.pgh.pa.us 3764 [ + + ]:CBC 793 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3723 3765 : 3 : pgfdw_report_error(ERROR, res, conn, true, fsstate->query);
4070 3766 : 790 : PQclear(res);
3767 : :
3768 : : /* Mark the cursor as created, and show no tuples have been retrieved */
4053 3769 : 790 : fsstate->cursor_exists = true;
3770 : 790 : fsstate->tuples = NULL;
3771 : 790 : fsstate->num_tuples = 0;
3772 : 790 : fsstate->next_tuple = 0;
3773 : 790 : fsstate->fetch_ct_2 = 0;
3774 : 790 : fsstate->eof_reached = false;
3775 : :
3776 : : /* Clean up */
4070 3777 : 790 : pfree(buf.data);
3778 : 790 : }
3779 : :
3780 : : /*
3781 : : * Fetch some more rows from the node's cursor.
3782 : : */
3783 : : static void
3784 : 1459 : fetch_more_data(ForeignScanState *node)
3785 : : {
4053 3786 : 1459 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
4070 3787 : 1459 : PGresult *volatile res = NULL;
3788 : : MemoryContext oldcontext;
3789 : :
3790 : : /*
3791 : : * We'll store the tuples in the batch_cxt. First, flush the previous
3792 : : * batch.
3793 : : */
4053 3794 : 1459 : fsstate->tuples = NULL;
3795 : 1459 : MemoryContextReset(fsstate->batch_cxt);
3796 : 1459 : oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt);
3797 : :
3798 : : /* PGresult must be released before leaving this function. */
4070 3799 [ + + ]: 1459 : PG_TRY();
3800 : : {
4053 3801 : 1459 : PGconn *conn = fsstate->conn;
3802 : : int numrows;
3803 : : int i;
3804 : :
1110 efujita@postgresql.o 3805 [ + + ]: 1459 : if (fsstate->async_capable)
3806 : : {
3807 [ - + ]: 158 : Assert(fsstate->conn_state->pendingAreq);
3808 : :
3809 : : /*
3810 : : * The query was already sent by an earlier call to
3811 : : * fetch_more_data_begin. So now we just fetch the result.
3812 : : */
97 noah@leadboat.com 3813 :GNC 158 : res = pgfdw_get_result(conn);
3814 : : /* On error, report the original query, not the FETCH. */
1110 efujita@postgresql.o 3815 [ - + ]:CBC 158 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
1110 efujita@postgresql.o 3816 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
3817 : :
3818 : : /* Reset per-connection state */
1110 efujita@postgresql.o 3819 :CBC 158 : fsstate->conn_state->pendingAreq = NULL;
3820 : : }
3821 : : else
3822 : : {
3823 : : char sql[64];
3824 : :
3825 : : /* This is a regular synchronous fetch. */
3826 : 1301 : snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
3827 : : fsstate->fetch_size, fsstate->cursor_number);
3828 : :
3829 : 1301 : res = pgfdw_exec_query(conn, sql, fsstate->conn_state);
3830 : : /* On error, report the original query, not the FETCH. */
3831 [ + + ]: 1301 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3832 : 1 : pgfdw_report_error(ERROR, res, conn, false, fsstate->query);
3833 : : }
3834 : :
3835 : : /* Convert the data into HeapTuples */
4070 tgl@sss.pgh.pa.us 3836 : 1458 : numrows = PQntuples(res);
4053 3837 : 1458 : fsstate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
3838 : 1458 : fsstate->num_tuples = numrows;
3839 : 1458 : fsstate->next_tuple = 0;
3840 : :
4070 3841 [ + + ]: 70581 : for (i = 0; i < numrows; i++)
3842 : : {
2986 rhaas@postgresql.org 3843 [ - + ]: 69127 : Assert(IsA(node->ss.ps.plan, ForeignScan));
3844 : :
4053 tgl@sss.pgh.pa.us 3845 : 138250 : fsstate->tuples[i] =
4070 3846 : 69127 : make_tuple_from_result_row(res, i,
3847 : : fsstate->rel,
3848 : : fsstate->attinmeta,
3849 : : fsstate->retrieved_attrs,
3850 : : node,
3851 : : fsstate->temp_cxt);
3852 : : }
3853 : :
3854 : : /* Update fetch_ct_2 */
4053 3855 [ + + ]: 1454 : if (fsstate->fetch_ct_2 < 2)
3856 : 920 : fsstate->fetch_ct_2++;
3857 : :
3858 : : /* Must be EOF if we didn't get as many tuples as we asked for. */
2993 rhaas@postgresql.org 3859 : 1454 : fsstate->eof_reached = (numrows < fsstate->fetch_size);
3860 : : }
1626 peter@eisentraut.org 3861 : 5 : PG_FINALLY();
3862 : : {
651 3863 : 1459 : PQclear(res);
3864 : : }
4070 tgl@sss.pgh.pa.us 3865 [ + + ]: 1459 : PG_END_TRY();
3866 : :
3867 : 1454 : MemoryContextSwitchTo(oldcontext);
3868 : 1454 : }
3869 : :
3870 : : /*
3871 : : * Force assorted GUC parameters to settings that ensure that we'll output
3872 : : * data values in a form that is unambiguous to the remote server.
3873 : : *
3874 : : * This is rather expensive and annoying to do once per row, but there's
3875 : : * little choice if we want to be sure values are transmitted accurately;
3876 : : * we can't leave the settings in place between rows for fear of affecting
3877 : : * user-visible computations.
3878 : : *
3879 : : * We use the equivalent of a function SET option to allow the settings to
3880 : : * persist only until the caller calls reset_transmission_modes(). If an
3881 : : * error is thrown in between, guc.c will take care of undoing the settings.
3882 : : *
3883 : : * The return value is the nestlevel that must be passed to
3884 : : * reset_transmission_modes() to undo things.
3885 : : */
3886 : : int
4052 3887 : 4002 : set_transmission_modes(void)
3888 : : {
3889 : 4002 : int nestlevel = NewGUCNestLevel();
3890 : :
3891 : : /*
3892 : : * The values set here should match what pg_dump does. See also
3893 : : * configure_remote_session in connection.c.
3894 : : */
3895 [ + + ]: 4002 : if (DateStyle != USE_ISO_DATES)
3896 : 4000 : (void) set_config_option("datestyle", "ISO",
3897 : : PGC_USERSET, PGC_S_SESSION,
3898 : : GUC_ACTION_SAVE, true, 0, false);
3899 [ + + ]: 4002 : if (IntervalStyle != INTSTYLE_POSTGRES)
3900 : 4000 : (void) set_config_option("intervalstyle", "postgres",
3901 : : PGC_USERSET, PGC_S_SESSION,
3902 : : GUC_ACTION_SAVE, true, 0, false);
3903 [ + + ]: 4002 : if (extra_float_digits < 3)
3904 : 4000 : (void) set_config_option("extra_float_digits", "3",
3905 : : PGC_USERSET, PGC_S_SESSION,
3906 : : GUC_ACTION_SAVE, true, 0, false);
3907 : :
3908 : : /*
3909 : : * In addition force restrictive search_path, in case there are any
3910 : : * regproc or similar constants to be printed.
3911 : : */
637 3912 : 4002 : (void) set_config_option("search_path", "pg_catalog",
3913 : : PGC_USERSET, PGC_S_SESSION,
3914 : : GUC_ACTION_SAVE, true, 0, false);
3915 : :
4052 3916 : 4002 : return nestlevel;
3917 : : }
3918 : :
3919 : : /*
3920 : : * Undo the effects of set_transmission_modes().
3921 : : */
3922 : : void
3923 : 4002 : reset_transmission_modes(int nestlevel)
3924 : : {
3925 : 4002 : AtEOXact_GUC(true, nestlevel);
3926 : 4002 : }
3927 : :
3928 : : /*
3929 : : * Utility routine to close a cursor.
3930 : : */
3931 : : static void
1110 efujita@postgresql.o 3932 : 479 : close_cursor(PGconn *conn, unsigned int cursor_number,
3933 : : PgFdwConnState *conn_state)
3934 : : {
3935 : : char sql[64];
3936 : : PGresult *res;
3937 : :
4070 tgl@sss.pgh.pa.us 3938 : 479 : snprintf(sql, sizeof(sql), "CLOSE c%u", cursor_number);
3939 : :
3940 : : /*
3941 : : * We don't use a PG_TRY block here, so be careful not to throw error
3942 : : * without releasing the PGresult.
3943 : : */
1110 efujita@postgresql.o 3944 : 479 : res = pgfdw_exec_query(conn, sql, conn_state);
4070 tgl@sss.pgh.pa.us 3945 [ - + ]: 479 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3723 tgl@sss.pgh.pa.us 3946 :UBC 0 : pgfdw_report_error(ERROR, res, conn, true, sql);
4070 tgl@sss.pgh.pa.us 3947 :CBC 479 : PQclear(res);
3948 : 479 : }
3949 : :
3950 : : /*
3951 : : * create_foreign_modify
3952 : : * Construct an execution state of a foreign insert/update/delete
3953 : : * operation
3954 : : */
3955 : : static PgFdwModifyState *
2200 rhaas@postgresql.org 3956 : 169 : create_foreign_modify(EState *estate,
3957 : : RangeTblEntry *rte,
3958 : : ResultRelInfo *resultRelInfo,
3959 : : CmdType operation,
3960 : : Plan *subplan,
3961 : : char *query,
3962 : : List *target_attrs,
3963 : : int values_end,
3964 : : bool has_returning,
3965 : : List *retrieved_attrs)
3966 : : {
3967 : : PgFdwModifyState *fmstate;
3968 : 169 : Relation rel = resultRelInfo->ri_RelationDesc;
3969 : 169 : TupleDesc tupdesc = RelationGetDescr(rel);
3970 : : Oid userid;
3971 : : ForeignTable *table;
3972 : : UserMapping *user;
3973 : : AttrNumber n_params;
3974 : : Oid typefnoid;
3975 : : bool isvarlena;
3976 : : ListCell *lc;
3977 : :
3978 : : /* Begin constructing PgFdwModifyState. */
3979 : 169 : fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
3980 : 169 : fmstate->rel = rel;
3981 : :
3982 : : /* Identify which user to do the remote access as. */
495 alvherre@alvh.no-ip. 3983 : 169 : userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
3984 : :
3985 : : /* Get info about foreign table. */
2200 rhaas@postgresql.org 3986 : 169 : table = GetForeignTable(RelationGetRelid(rel));
3987 : 169 : user = GetUserMapping(userid, table->serverid);
3988 : :
3989 : : /* Open connection; report that we'll create a prepared statement. */
1110 efujita@postgresql.o 3990 : 169 : fmstate->conn = GetConnection(user, true, &fmstate->conn_state);
2200 rhaas@postgresql.org 3991 : 169 : fmstate->p_name = NULL; /* prepared statement not made yet */
3992 : :
3993 : : /* Set up remote query information. */
3994 : 169 : fmstate->query = query;
1180 tomas.vondra@postgre 3995 [ + + ]: 169 : if (operation == CMD_INSERT)
3996 : : {
1073 3997 : 128 : fmstate->query = pstrdup(fmstate->query);
1180 3998 : 128 : fmstate->orig_query = pstrdup(fmstate->query);
3999 : : }
2200 rhaas@postgresql.org 4000 : 169 : fmstate->target_attrs = target_attrs;
1180 tomas.vondra@postgre 4001 : 169 : fmstate->values_end = values_end;
2200 rhaas@postgresql.org 4002 : 169 : fmstate->has_returning = has_returning;
4003 : 169 : fmstate->retrieved_attrs = retrieved_attrs;
4004 : :
4005 : : /* Create context for per-tuple temp workspace. */
4006 : 169 : fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
4007 : : "postgres_fdw temporary data",
4008 : : ALLOCSET_SMALL_SIZES);
4009 : :
4010 : : /* Prepare for input conversion of RETURNING results. */
4011 [ + + ]: 169 : if (fmstate->has_returning)
4012 : 58 : fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
4013 : :
4014 : : /* Prepare for output conversion of parameters used in prepared stmt. */
4015 : 169 : n_params = list_length(fmstate->target_attrs) + 1;
4016 : 169 : fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
4017 : 169 : fmstate->p_nums = 0;
4018 : :
4019 [ + + + + ]: 169 : if (operation == CMD_UPDATE || operation == CMD_DELETE)
4020 : : {
4021 [ - + ]: 41 : Assert(subplan != NULL);
4022 : :
4023 : : /* Find the ctid resjunk column in the subplan's result */
4024 : 41 : fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
4025 : : "ctid");
4026 [ - + ]: 41 : if (!AttributeNumberIsValid(fmstate->ctidAttno))
2200 rhaas@postgresql.org 4027 [ # # ]:UBC 0 : elog(ERROR, "could not find junk ctid column");
4028 : :
4029 : : /* First transmittable parameter will be ctid */
2200 rhaas@postgresql.org 4030 :CBC 41 : getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
4031 : 41 : fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
4032 : 41 : fmstate->p_nums++;
4033 : : }
4034 : :
4035 [ + + + + ]: 169 : if (operation == CMD_INSERT || operation == CMD_UPDATE)
4036 : : {
4037 : : /* Set up for remaining transmittable parameters */
4038 [ + + + + : 538 : foreach(lc, fmstate->target_attrs)
+ + ]
4039 : : {
4040 : 379 : int attnum = lfirst_int(lc);
4041 : 379 : Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
4042 : :
4043 [ - + ]: 379 : Assert(!attr->attisdropped);
4044 : :
4045 : : /* Ignore generated columns; they are set to DEFAULT */
983 efujita@postgresql.o 4046 [ + + ]: 379 : if (attr->attgenerated)
4047 : 4 : continue;
2200 rhaas@postgresql.org 4048 : 375 : getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
4049 : 375 : fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
4050 : 375 : fmstate->p_nums++;
4051 : : }
4052 : : }
4053 : :
4054 [ - + ]: 169 : Assert(fmstate->p_nums <= n_params);
4055 : :
4056 : : /* Set batch_size from foreign server/table options. */
1180 tomas.vondra@postgre 4057 [ + + ]: 169 : if (operation == CMD_INSERT)
4058 : 128 : fmstate->batch_size = get_batch_size_option(rel);
4059 : :
4060 : 169 : fmstate->num_slots = 1;
4061 : :
4062 : : /* Initialize auxiliary state */
1817 efujita@postgresql.o 4063 : 169 : fmstate->aux_fmstate = NULL;
4064 : :
2200 rhaas@postgresql.org 4065 : 169 : return fmstate;
4066 : : }
4067 : :
4068 : : /*
4069 : : * execute_foreign_modify
4070 : : * Perform foreign-table modification as required, and fetch RETURNING
4071 : : * result if any. (This is the shared guts of postgresExecForeignInsert,
4072 : : * postgresExecForeignBatchInsert, postgresExecForeignUpdate, and
4073 : : * postgresExecForeignDelete.)
4074 : : */
4075 : : static TupleTableSlot **
1914 efujita@postgresql.o 4076 : 1017 : execute_foreign_modify(EState *estate,
4077 : : ResultRelInfo *resultRelInfo,
4078 : : CmdType operation,
4079 : : TupleTableSlot **slots,
4080 : : TupleTableSlot **planSlots,
4081 : : int *numSlots)
4082 : : {
4083 : 1017 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
1893 tgl@sss.pgh.pa.us 4084 : 1017 : ItemPointer ctid = NULL;
4085 : : const char **p_values;
4086 : : PGresult *res;
4087 : : int n_rows;
4088 : : StringInfoData sql;
4089 : :
4090 : : /* The operation should be INSERT, UPDATE, or DELETE */
1914 efujita@postgresql.o 4091 [ + + + + : 1017 : Assert(operation == CMD_INSERT ||
- + ]
4092 : : operation == CMD_UPDATE ||
4093 : : operation == CMD_DELETE);
4094 : :
4095 : : /* First, process a pending asynchronous request, if any. */
1110 4096 [ + + ]: 1017 : if (fmstate->conn_state->pendingAreq)
4097 : 1 : process_pending_request(fmstate->conn_state->pendingAreq);
4098 : :
4099 : : /*
4100 : : * If the existing query was deparsed and prepared for a different number
4101 : : * of rows, rebuild it for the proper number.
4102 : : */
1180 tomas.vondra@postgre 4103 [ + + + + ]: 1017 : if (operation == CMD_INSERT && fmstate->num_slots != *numSlots)
4104 : : {
4105 : : /* Destroy the prepared statement created previously */
4106 [ + + ]: 26 : if (fmstate->p_name)
4107 : 11 : deallocate_query(fmstate);
4108 : :
4109 : : /* Build INSERT string with numSlots records in its VALUES clause. */
4110 : 26 : initStringInfo(&sql);
983 efujita@postgresql.o 4111 : 26 : rebuildInsertSql(&sql, fmstate->rel,
4112 : : fmstate->orig_query, fmstate->target_attrs,
4113 : : fmstate->values_end, fmstate->p_nums,
4114 : 26 : *numSlots - 1);
1180 tomas.vondra@postgre 4115 : 26 : pfree(fmstate->query);
4116 : 26 : fmstate->query = sql.data;
4117 : 26 : fmstate->num_slots = *numSlots;
4118 : : }
4119 : :
4120 : : /* Set up the prepared statement on the remote server, if we didn't yet */
1914 efujita@postgresql.o 4121 [ + + ]: 1017 : if (!fmstate->p_name)
4122 : 174 : prepare_foreign_modify(fmstate);
4123 : :
4124 : : /*
4125 : : * For UPDATE/DELETE, get the ctid that was passed up as a resjunk column
4126 : : */
4127 [ + + + + ]: 1017 : if (operation == CMD_UPDATE || operation == CMD_DELETE)
4128 : : {
4129 : : Datum datum;
4130 : : bool isNull;
4131 : :
1180 tomas.vondra@postgre 4132 : 88 : datum = ExecGetJunkAttribute(planSlots[0],
1914 efujita@postgresql.o 4133 : 88 : fmstate->ctidAttno,
4134 : : &isNull);
4135 : : /* shouldn't ever get a null result... */
4136 [ - + ]: 88 : if (isNull)
1914 efujita@postgresql.o 4137 [ # # ]:UBC 0 : elog(ERROR, "ctid is NULL");
1914 efujita@postgresql.o 4138 :CBC 88 : ctid = (ItemPointer) DatumGetPointer(datum);
4139 : : }
4140 : :
4141 : : /* Convert parameters needed by prepared statement to text form */
1180 tomas.vondra@postgre 4142 : 1017 : p_values = convert_prep_stmt_params(fmstate, ctid, slots, *numSlots);
4143 : :
4144 : : /*
4145 : : * Execute the prepared statement.
4146 : : */
1914 efujita@postgresql.o 4147 [ - + ]: 1017 : if (!PQsendQueryPrepared(fmstate->conn,
4148 : 1017 : fmstate->p_name,
1180 tomas.vondra@postgre 4149 : 1017 : fmstate->p_nums * (*numSlots),
4150 : : p_values,
4151 : : NULL,
4152 : : NULL,
4153 : : 0))
1914 efujita@postgresql.o 4154 :UBC 0 : pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
4155 : :
4156 : : /*
4157 : : * Get the result, and check for success.
4158 : : *
4159 : : * We don't use a PG_TRY block here, so be careful not to throw error
4160 : : * without releasing the PGresult.
4161 : : */
97 noah@leadboat.com 4162 :GNC 1017 : res = pgfdw_get_result(fmstate->conn);
1914 efujita@postgresql.o 4163 [ + + ]:CBC 2034 : if (PQresultStatus(res) !=
4164 [ + + ]: 1017 : (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
4165 : 5 : pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
4166 : :
4167 : : /* Check number of rows affected, and fetch RETURNING tuple if any */
4168 [ + + ]: 1012 : if (fmstate->has_returning)
4169 : : {
1180 tomas.vondra@postgre 4170 [ - + ]: 82 : Assert(*numSlots == 1);
1914 efujita@postgresql.o 4171 : 82 : n_rows = PQntuples(res);
4172 [ + + ]: 82 : if (n_rows > 0)
1180 tomas.vondra@postgre 4173 : 81 : store_returning_result(fmstate, slots[0], res);
4174 : : }
4175 : : else
1914 efujita@postgresql.o 4176 : 930 : n_rows = atoi(PQcmdTuples(res));
4177 : :
4178 : : /* And clean up */
4179 : 1012 : PQclear(res);
4180 : :
4181 : 1012 : MemoryContextReset(fmstate->temp_cxt);
4182 : :
1180 tomas.vondra@postgre 4183 : 1012 : *numSlots = n_rows;
4184 : :
4185 : : /*
4186 : : * Return NULL if nothing was inserted/updated/deleted on the remote end
4187 : : */
4188 [ + + ]: 1012 : return (n_rows > 0) ? slots : NULL;
4189 : : }
4190 : :
4191 : : /*
4192 : : * prepare_foreign_modify
4193 : : * Establish a prepared statement for execution of INSERT/UPDATE/DELETE
4194 : : */
4195 : : static void
4053 tgl@sss.pgh.pa.us 4196 : 174 : prepare_foreign_modify(PgFdwModifyState *fmstate)
4197 : : {
4198 : : char prep_name[NAMEDATALEN];
4199 : : char *p_name;
4200 : : PGresult *res;
4201 : :
4202 : : /*
4203 : : * The caller would already have processed a pending asynchronous request
4204 : : * if any, so no need to do it here.
4205 : : */
4206 : :
4207 : : /* Construct name we'll use for the prepared statement. */
4208 : 174 : snprintf(prep_name, sizeof(prep_name), "pgsql_fdw_prep_%u",
4209 : : GetPrepStmtNumber(fmstate->conn));
4210 : 174 : p_name = pstrdup(prep_name);
4211 : :
4212 : : /*
4213 : : * We intentionally do not specify parameter types here, but leave the
4214 : : * remote server to derive them by default. This avoids possible problems
4215 : : * with the remote server using different type OIDs than we do. All of
4216 : : * the prepared statements we use in this module are simple enough that
4217 : : * the remote server will make the right choices.
4218 : : */
2915 rhaas@postgresql.org 4219 [ - + ]: 174 : if (!PQsendPrepare(fmstate->conn,
4220 : : p_name,
4221 : 174 : fmstate->query,
4222 : : 0,
4223 : : NULL))
2915 rhaas@postgresql.org 4224 :UBC 0 : pgfdw_report_error(ERROR, NULL, fmstate->conn, false, fmstate->query);
4225 : :
4226 : : /*
4227 : : * Get the result, and check for success.
4228 : : *
4229 : : * We don't use a PG_TRY block here, so be careful not to throw error
4230 : : * without releasing the PGresult.
4231 : : */
97 noah@leadboat.com 4232 :GNC 174 : res = pgfdw_get_result(fmstate->conn);
4053 tgl@sss.pgh.pa.us 4233 [ - + ]:CBC 174 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3723 tgl@sss.pgh.pa.us 4234 :UBC 0 : pgfdw_report_error(ERROR, res, fmstate->conn, true, fmstate->query);
4053 tgl@sss.pgh.pa.us 4235 :CBC 174 : PQclear(res);
4236 : :
4237 : : /* This action shows that the prepare has been done. */
4238 : 174 : fmstate->p_name = p_name;
4239 : 174 : }
4240 : :
4241 : : /*
4242 : : * convert_prep_stmt_params
4243 : : * Create array of text strings representing parameter values
4244 : : *
4245 : : * tupleid is ctid to send, or NULL if none
4246 : : * slot is slot to get remaining parameters from, or NULL if none
4247 : : *
4248 : : * Data is constructed in temp_cxt; caller should reset that after use.
4249 : : */
4250 : : static const char **
4251 : 1017 : convert_prep_stmt_params(PgFdwModifyState *fmstate,
4252 : : ItemPointer tupleid,
4253 : : TupleTableSlot **slots,
4254 : : int numSlots)
4255 : : {
4256 : : const char **p_values;
4257 : : int i;
4258 : : int j;
4259 : 1017 : int pindex = 0;
4260 : : MemoryContext oldcontext;
4261 : :
4262 : 1017 : oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt);
4263 : :
1180 tomas.vondra@postgre 4264 : 1017 : p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums * numSlots);
4265 : :
4266 : : /* ctid is provided only for UPDATE/DELETE, which don't allow batching */
4267 [ + + - + ]: 1017 : Assert(!(tupleid != NULL && numSlots > 1));
4268 : :
4269 : : /* 1st parameter should be ctid, if it's in use */
4053 tgl@sss.pgh.pa.us 4270 [ + + ]: 1017 : if (tupleid != NULL)
4271 : : {
1180 tomas.vondra@postgre 4272 [ - + ]: 88 : Assert(numSlots == 1);
4273 : : /* don't need set_transmission_modes for TID output */
4053 tgl@sss.pgh.pa.us 4274 : 88 : p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
4275 : : PointerGetDatum(tupleid));
4276 : 88 : pindex++;
4277 : : }
4278 : :
4279 : : /* get following parameters from slots */
1180 tomas.vondra@postgre 4280 [ + - + + ]: 1017 : if (slots != NULL && fmstate->target_attrs != NIL)
4281 : : {
983 efujita@postgresql.o 4282 : 997 : TupleDesc tupdesc = RelationGetDescr(fmstate->rel);
4283 : : int nestlevel;
4284 : : ListCell *lc;
4285 : :
4052 tgl@sss.pgh.pa.us 4286 : 997 : nestlevel = set_transmission_modes();
4287 : :
1180 tomas.vondra@postgre 4288 [ + + ]: 2116 : for (i = 0; i < numSlots; i++)
4289 : : {
4290 : 1119 : j = (tupleid != NULL) ? 1 : 0;
4291 [ + - + + : 4723 : foreach(lc, fmstate->target_attrs)
+ + ]
4292 : : {
4293 : 3604 : int attnum = lfirst_int(lc);
983 efujita@postgresql.o 4294 : 3604 : Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
4295 : : Datum value;
4296 : : bool isnull;
4297 : :
4298 : : /* Ignore generated columns; they are set to DEFAULT */
4299 [ + + ]: 3604 : if (attr->attgenerated)
4300 : 7 : continue;
1180 tomas.vondra@postgre 4301 : 3597 : value = slot_getattr(slots[i], attnum, &isnull);
4302 [ + + ]: 3597 : if (isnull)
4303 : 583 : p_values[pindex] = NULL;
4304 : : else
4305 : 3014 : p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[j],
4306 : : value);
4307 : 3597 : pindex++;
4308 : 3597 : j++;
4309 : : }
4310 : : }
4311 : :
4052 tgl@sss.pgh.pa.us 4312 : 997 : reset_transmission_modes(nestlevel);
4313 : : }
4314 : :
1180 tomas.vondra@postgre 4315 [ - + ]: 1017 : Assert(pindex == fmstate->p_nums * numSlots);
4316 : :
4053 tgl@sss.pgh.pa.us 4317 : 1017 : MemoryContextSwitchTo(oldcontext);
4318 : :
4319 : 1017 : return p_values;
4320 : : }
4321 : :
4322 : : /*
4323 : : * store_returning_result
4324 : : * Store the result of a RETURNING clause
4325 : : *
4326 : : * On error, be sure to release the PGresult on the way out. Callers do not
4327 : : * have PG_TRY blocks to ensure this happens.
4328 : : */
4329 : : static void
4330 : 81 : store_returning_result(PgFdwModifyState *fmstate,
4331 : : TupleTableSlot *slot, PGresult *res)
4332 : : {
4333 [ + - ]: 81 : PG_TRY();
4334 : : {
4335 : : HeapTuple newtup;
4336 : :
4337 : 81 : newtup = make_tuple_from_result_row(res, 0,
4338 : : fmstate->rel,
4339 : : fmstate->attinmeta,
4340 : : fmstate->retrieved_attrs,
4341 : : NULL,
4342 : : fmstate->temp_cxt);
4343 : :
4344 : : /*
4345 : : * The returning slot will not necessarily be suitable to store
4346 : : * heaptuples directly, so allow for conversion.
4347 : : */
1822 andres@anarazel.de 4348 : 81 : ExecForceStoreHeapTuple(newtup, slot, true);
4349 : : }
4053 tgl@sss.pgh.pa.us 4350 :UBC 0 : PG_CATCH();
4351 : : {
651 peter@eisentraut.org 4352 : 0 : PQclear(res);
4053 tgl@sss.pgh.pa.us 4353 : 0 : PG_RE_THROW();
4354 : : }
4053 tgl@sss.pgh.pa.us 4355 [ - + ]:CBC 81 : PG_END_TRY();
4356 : 81 : }
4357 : :
4358 : : /*
4359 : : * finish_foreign_modify
4360 : : * Release resources for a foreign insert/update/delete operation
4361 : : */
4362 : : static void
2200 rhaas@postgresql.org 4363 : 155 : finish_foreign_modify(PgFdwModifyState *fmstate)
4364 : : {
4365 [ - + ]: 155 : Assert(fmstate != NULL);
4366 : :
4367 : : /* If we created a prepared statement, destroy it */
1180 tomas.vondra@postgre 4368 : 155 : deallocate_query(fmstate);
4369 : :
4370 : : /* Release remote connection */
2200 rhaas@postgresql.org 4371 : 155 : ReleaseConnection(fmstate->conn);
4372 : 155 : fmstate->conn = NULL;
4373 : 155 : }
4374 : :
4375 : : /*
4376 : : * deallocate_query
4377 : : * Deallocate a prepared statement for a foreign insert/update/delete
4378 : : * operation
4379 : : */
4380 : : static void
1180 tomas.vondra@postgre 4381 : 166 : deallocate_query(PgFdwModifyState *fmstate)
4382 : : {
4383 : : char sql[64];
4384 : : PGresult *res;
4385 : :
4386 : : /* do nothing if the query is not allocated */
4387 [ + + ]: 166 : if (!fmstate->p_name)
4388 : 4 : return;
4389 : :
4390 : 162 : snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
4391 : :
4392 : : /*
4393 : : * We don't use a PG_TRY block here, so be careful not to throw error
4394 : : * without releasing the PGresult.
4395 : : */
1110 efujita@postgresql.o 4396 : 162 : res = pgfdw_exec_query(fmstate->conn, sql, fmstate->conn_state);
1180 tomas.vondra@postgre 4397 [ - + ]: 162 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
1180 tomas.vondra@postgre 4398 :UBC 0 : pgfdw_report_error(ERROR, res, fmstate->conn, true, sql);
1180 tomas.vondra@postgre 4399 :CBC 162 : PQclear(res);
1174 michael@paquier.xyz 4400 : 162 : pfree(fmstate->p_name);
1180 tomas.vondra@postgre 4401 : 162 : fmstate->p_name = NULL;
4402 : : }
4403 : :
4404 : : /*
4405 : : * build_remote_returning
4406 : : * Build a RETURNING targetlist of a remote query for performing an
4407 : : * UPDATE/DELETE .. RETURNING on a join directly
4408 : : */
4409 : : static List *
2258 rhaas@postgresql.org 4410 : 4 : build_remote_returning(Index rtindex, Relation rel, List *returningList)
4411 : : {
4412 : 4 : bool have_wholerow = false;
4413 : 4 : List *tlist = NIL;
4414 : : List *vars;
4415 : : ListCell *lc;
4416 : :
4417 [ - + ]: 4 : Assert(returningList);
4418 : :
4419 : 4 : vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS);
4420 : :
4421 : : /*
4422 : : * If there's a whole-row reference to the target relation, then we'll
4423 : : * need all the columns of the relation.
4424 : : */
4425 [ + + + - : 4 : foreach(lc, vars)
+ + ]
4426 : : {
4427 : 2 : Var *var = (Var *) lfirst(lc);
4428 : :
4429 [ + - ]: 2 : if (IsA(var, Var) &&
4430 [ + - ]: 2 : var->varno == rtindex &&
4431 [ + - ]: 2 : var->varattno == InvalidAttrNumber)
4432 : : {
4433 : 2 : have_wholerow = true;
4434 : 2 : break;
4435 : : }
4436 : : }
4437 : :
4438 [ + + ]: 4 : if (have_wholerow)
4439 : : {
4440 : 2 : TupleDesc tupdesc = RelationGetDescr(rel);
4441 : : int i;
4442 : :
4443 [ + + ]: 20 : for (i = 1; i <= tupdesc->natts; i++)
4444 : : {
4445 : 18 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1);
4446 : : Var *var;
4447 : :
4448 : : /* Ignore dropped attributes. */
4449 [ + + ]: 18 : if (attr->attisdropped)
4450 : 2 : continue;
4451 : :
4452 : 16 : var = makeVar(rtindex,
4453 : : i,
4454 : : attr->atttypid,
4455 : : attr->atttypmod,
4456 : : attr->attcollation,
4457 : : 0);
4458 : :
4459 : 16 : tlist = lappend(tlist,
4460 : 16 : makeTargetEntry((Expr *) var,
4461 : 16 : list_length(tlist) + 1,
4462 : : NULL,
4463 : : false));
4464 : : }
4465 : : }
4466 : :
4467 : : /* Now add any remaining columns to tlist. */
4468 [ + + + + : 30 : foreach(lc, vars)
+ + ]
4469 : : {
4470 : 26 : Var *var = (Var *) lfirst(lc);
4471 : :
4472 : : /*
4473 : : * No need for whole-row references to the target relation. We don't
4474 : : * need system columns other than ctid and oid either, since those are
4475 : : * set locally.
4476 : : */
4477 [ + - ]: 26 : if (IsA(var, Var) &&
4478 [ + + ]: 26 : var->varno == rtindex &&
4479 [ + + ]: 18 : var->varattno <= InvalidAttrNumber &&
1972 andres@anarazel.de 4480 [ + - ]: 2 : var->varattno != SelfItemPointerAttributeNumber)
2258 rhaas@postgresql.org 4481 : 2 : continue; /* don't need it */
4482 : :
4483 [ + + ]: 24 : if (tlist_member((Expr *) var, tlist))
4484 : 16 : continue; /* already got it */
4485 : :
4486 : 8 : tlist = lappend(tlist,
4487 : 8 : makeTargetEntry((Expr *) var,
4488 : 8 : list_length(tlist) + 1,
4489 : : NULL,
4490 : : false));
4491 : : }
4492 : :
4493 : 4 : list_free(vars);
4494 : :
4495 : 4 : return tlist;
4496 : : }
4497 : :
4498 : : /*
4499 : : * rebuild_fdw_scan_tlist
4500 : : * Build new fdw_scan_tlist of given foreign-scan plan node from given
4501 : : * tlist
4502 : : *
4503 : : * There might be columns that the fdw_scan_tlist of the given foreign-scan
4504 : : * plan node contains that the given tlist doesn't. The fdw_scan_tlist would
4505 : : * have contained resjunk columns such as 'ctid' of the target relation and
4506 : : * 'wholerow' of non-target relations, but the tlist might not contain them,
4507 : : * for example. So, adjust the tlist so it contains all the columns specified
4508 : : * in the fdw_scan_tlist; else setrefs.c will get confused.
4509 : : */
4510 : : static void
4511 : 2 : rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist)
4512 : : {
4513 : 2 : List *new_tlist = tlist;
4514 : 2 : List *old_tlist = fscan->fdw_scan_tlist;
4515 : : ListCell *lc;
4516 : :
4517 [ + - + + : 16 : foreach(lc, old_tlist)
+ + ]
4518 : : {
4519 : 14 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
4520 : :
4521 [ + + ]: 14 : if (tlist_member(tle->expr, new_tlist))
4522 : 8 : continue; /* already got it */
4523 : :
4524 : 6 : new_tlist = lappend(new_tlist,
4525 : 6 : makeTargetEntry(tle->expr,
4526 : 6 : list_length(new_tlist) + 1,
4527 : : NULL,
4528 : : false));
4529 : : }
4530 : 2 : fscan->fdw_scan_tlist = new_tlist;
4531 : 2 : }
4532 : :
4533 : : /*
4534 : : * Execute a direct UPDATE/DELETE statement.
4535 : : */
4536 : : static void
2949 4537 : 71 : execute_dml_stmt(ForeignScanState *node)
4538 : : {
4539 : 71 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
4540 : 71 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
4541 : 71 : int numParams = dmstate->numParams;
4542 : 71 : const char **values = dmstate->param_values;
4543 : :
4544 : : /* First, process a pending asynchronous request, if any. */
1110 efujita@postgresql.o 4545 [ + + ]: 71 : if (dmstate->conn_state->pendingAreq)
4546 : 1 : process_pending_request(dmstate->conn_state->pendingAreq);
4547 : :
4548 : : /*
4549 : : * Construct array of query parameter values in text format.
4550 : : */
2949 rhaas@postgresql.org 4551 [ - + ]: 71 : if (numParams > 0)
2949 rhaas@postgresql.org 4552 :UBC 0 : process_query_params(econtext,
4553 : : dmstate->param_flinfo,
4554 : : dmstate->param_exprs,
4555 : : values);
4556 : :
4557 : : /*
4558 : : * Notice that we pass NULL for paramTypes, thus forcing the remote server
4559 : : * to infer types for all parameters. Since we explicitly cast every
4560 : : * parameter (see deparse.c), the "inference" is trivial and will produce
4561 : : * the desired result. This allows us to avoid assuming that the remote
4562 : : * server has the same OIDs we do for the parameters' types.
4563 : : */
2915 rhaas@postgresql.org 4564 [ - + ]:CBC 71 : if (!PQsendQueryParams(dmstate->conn, dmstate->query, numParams,
4565 : : NULL, values, NULL, NULL, 0))
2915 rhaas@postgresql.org 4566 :UBC 0 : pgfdw_report_error(ERROR, NULL, dmstate->conn, false, dmstate->query);
4567 : :
4568 : : /*
4569 : : * Get the result, and check for success.
4570 : : *
4571 : : * We don't use a PG_TRY block here, so be careful not to throw error
4572 : : * without releasing the PGresult.
4573 : : */
97 noah@leadboat.com 4574 :GNC 71 : dmstate->result = pgfdw_get_result(dmstate->conn);
2949 rhaas@postgresql.org 4575 [ + + ]:CBC 142 : if (PQresultStatus(dmstate->result) !=
4576 [ + + ]: 71 : (dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
4577 : 4 : pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, true,
4578 : 4 : dmstate->query);
4579 : :
4580 : : /* Get the number of rows affected. */
4581 [ + + ]: 67 : if (dmstate->has_returning)
4582 : 14 : dmstate->num_tuples = PQntuples(dmstate->result);
4583 : : else
4584 : 53 : dmstate->num_tuples = atoi(PQcmdTuples(dmstate->result));
4585 : 67 : }
4586 : :
4587 : : /*
4588 : : * Get the result of a RETURNING clause.
4589 : : */
4590 : : static TupleTableSlot *
4591 : 364 : get_returning_data(ForeignScanState *node)
4592 : : {
4593 : 364 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
4594 : 364 : EState *estate = node->ss.ps.state;
1278 heikki.linnakangas@i 4595 : 364 : ResultRelInfo *resultRelInfo = node->resultRelInfo;
2949 rhaas@postgresql.org 4596 : 364 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
4597 : : TupleTableSlot *resultSlot;
4598 : :
4599 [ - + ]: 364 : Assert(resultRelInfo->ri_projectReturning);
4600 : :
4601 : : /* If we didn't get any tuples, must be end of data. */
4602 [ + + ]: 364 : if (dmstate->next_tuple >= dmstate->num_tuples)
4603 : 17 : return ExecClearTuple(slot);
4604 : :
4605 : : /* Increment the command es_processed count if necessary. */
4606 [ + + ]: 347 : if (dmstate->set_processed)
4607 : 346 : estate->es_processed += 1;
4608 : :
4609 : : /*
4610 : : * Store a RETURNING tuple. If has_returning is false, just emit a dummy
4611 : : * tuple. (has_returning is false when the local query is of the form
4612 : : * "UPDATE/DELETE .. RETURNING 1" for example.)
4613 : : */
4614 [ + + ]: 347 : if (!dmstate->has_returning)
4615 : : {
4616 : 12 : ExecStoreAllNullTuple(slot);
2258 4617 : 12 : resultSlot = slot;
4618 : : }
4619 : : else
4620 : : {
4621 : : /*
4622 : : * On error, be sure to release the PGresult on the way out. Callers
4623 : : * do not have PG_TRY blocks to ensure this happens.
4624 : : */
2949 4625 [ + - ]: 335 : PG_TRY();
4626 : : {
4627 : : HeapTuple newtup;
4628 : :
4629 : 335 : newtup = make_tuple_from_result_row(dmstate->result,
4630 : : dmstate->next_tuple,
4631 : : dmstate->rel,
4632 : : dmstate->attinmeta,
4633 : : dmstate->retrieved_attrs,
4634 : : node,
4635 : : dmstate->temp_cxt);
2028 andres@anarazel.de 4636 : 335 : ExecStoreHeapTuple(newtup, slot, false);
4637 : : }
2949 rhaas@postgresql.org 4638 :UBC 0 : PG_CATCH();
4639 : : {
651 peter@eisentraut.org 4640 : 0 : PQclear(dmstate->result);
2949 rhaas@postgresql.org 4641 : 0 : PG_RE_THROW();
4642 : : }
2949 rhaas@postgresql.org 4643 [ - + ]:CBC 335 : PG_END_TRY();
4644 : :
4645 : : /* Get the updated/deleted tuple. */
2258 4646 [ + + ]: 335 : if (dmstate->rel)
4647 : 319 : resultSlot = slot;
4648 : : else
1278 heikki.linnakangas@i 4649 : 16 : resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot, estate);
4650 : : }
2949 rhaas@postgresql.org 4651 : 347 : dmstate->next_tuple++;
4652 : :
4653 : : /* Make slot available for evaluation of the local query RETURNING list. */
2258 4654 : 347 : resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple =
4655 : : resultSlot;
4656 : :
2949 4657 : 347 : return slot;
4658 : : }
4659 : :
4660 : : /*
4661 : : * Initialize a filter to extract an updated/deleted tuple from a scan tuple.
4662 : : */
4663 : : static void
2258 4664 : 1 : init_returning_filter(PgFdwDirectModifyState *dmstate,
4665 : : List *fdw_scan_tlist,
4666 : : Index rtindex)
4667 : : {
4668 : 1 : TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
4669 : : ListCell *lc;
4670 : : int i;
4671 : :
4672 : : /*
4673 : : * Calculate the mapping between the fdw_scan_tlist's entries and the
4674 : : * result tuple's attributes.
4675 : : *
4676 : : * The "map" is an array of indexes of the result tuple's attributes in
4677 : : * fdw_scan_tlist, i.e., one entry for every attribute of the result
4678 : : * tuple. We store zero for any attributes that don't have the
4679 : : * corresponding entries in that list, marking that a NULL is needed in
4680 : : * the result tuple.
4681 : : *
4682 : : * Also get the indexes of the entries for ctid and oid if any.
4683 : : */
4684 : 1 : dmstate->attnoMap = (AttrNumber *)
4685 : 1 : palloc0(resultTupType->natts * sizeof(AttrNumber));
4686 : :
4687 : 1 : dmstate->ctidAttno = dmstate->oidAttno = 0;
4688 : :
4689 : 1 : i = 1;
4690 : 1 : dmstate->hasSystemCols = false;
4691 [ + - + + : 16 : foreach(lc, fdw_scan_tlist)
+ + ]
4692 : : {
4693 : 15 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
4694 : 15 : Var *var = (Var *) tle->expr;
4695 : :
4696 [ - + ]: 15 : Assert(IsA(var, Var));
4697 : :
4698 : : /*
4699 : : * If the Var is a column of the target relation to be retrieved from
4700 : : * the foreign server, get the index of the entry.
4701 : : */
4702 [ + + + + ]: 25 : if (var->varno == rtindex &&
4703 : 10 : list_member_int(dmstate->retrieved_attrs, i))
4704 : : {
4705 : 8 : int attrno = var->varattno;
4706 : :
4707 [ - + ]: 8 : if (attrno < 0)
4708 : : {
4709 : : /*
4710 : : * We don't retrieve system columns other than ctid and oid.
4711 : : */
2258 rhaas@postgresql.org 4712 [ # # ]:UBC 0 : if (attrno == SelfItemPointerAttributeNumber)
4713 : 0 : dmstate->ctidAttno = i;
4714 : : else
4715 : 0 : Assert(false);
4716 : 0 : dmstate->hasSystemCols = true;
4717 : : }
4718 : : else
4719 : : {
4720 : : /*
4721 : : * We don't retrieve whole-row references to the target
4722 : : * relation either.
4723 : : */
2258 rhaas@postgresql.org 4724 [ - + ]:CBC 8 : Assert(attrno > 0);
4725 : :
4726 : 8 : dmstate->attnoMap[attrno - 1] = i;
4727 : : }
4728 : : }
4729 : 15 : i++;
4730 : : }
4731 : 1 : }
4732 : :
4733 : : /*
4734 : : * Extract and return an updated/deleted tuple from a scan tuple.
4735 : : */
4736 : : static TupleTableSlot *
4737 : 16 : apply_returning_filter(PgFdwDirectModifyState *dmstate,
4738 : : ResultRelInfo *resultRelInfo,
4739 : : TupleTableSlot *slot,
4740 : : EState *estate)
4741 : : {
4742 : 16 : TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
4743 : : TupleTableSlot *resultSlot;
4744 : : Datum *values;
4745 : : bool *isnull;
4746 : : Datum *old_values;
4747 : : bool *old_isnull;
4748 : : int i;
4749 : :
4750 : : /*
4751 : : * Use the return tuple slot as a place to store the result tuple.
4752 : : */
1278 heikki.linnakangas@i 4753 : 16 : resultSlot = ExecGetReturningSlot(estate, resultRelInfo);
4754 : :
4755 : : /*
4756 : : * Extract all the values of the scan tuple.
4757 : : */
2258 rhaas@postgresql.org 4758 : 16 : slot_getallattrs(slot);
4759 : 16 : old_values = slot->tts_values;
4760 : 16 : old_isnull = slot->tts_isnull;
4761 : :
4762 : : /*
4763 : : * Prepare to build the result tuple.
4764 : : */
4765 : 16 : ExecClearTuple(resultSlot);
4766 : 16 : values = resultSlot->tts_values;
4767 : 16 : isnull = resultSlot->tts_isnull;
4768 : :
4769 : : /*
4770 : : * Transpose data into proper fields of the result tuple.
4771 : : */
4772 [ + + ]: 160 : for (i = 0; i < resultTupType->natts; i++)
4773 : : {
4774 : 144 : int j = dmstate->attnoMap[i];
4775 : :
4776 [ + + ]: 144 : if (j == 0)
4777 : : {
4778 : 16 : values[i] = (Datum) 0;
4779 : 16 : isnull[i] = true;
4780 : : }
4781 : : else
4782 : : {
4783 : 128 : values[i] = old_values[j - 1];
4784 : 128 : isnull[i] = old_isnull[j - 1];
4785 : : }
4786 : : }
4787 : :
4788 : : /*
4789 : : * Build the virtual tuple.
4790 : : */
4791 : 16 : ExecStoreVirtualTuple(resultSlot);
4792 : :
4793 : : /*
4794 : : * If we have any system columns to return, materialize a heap tuple in
4795 : : * the slot from column values set above and install system columns in
4796 : : * that tuple.
4797 : : */
4798 [ - + ]: 16 : if (dmstate->hasSystemCols)
4799 : : {
1977 andres@anarazel.de 4800 :UBC 0 : HeapTuple resultTup = ExecFetchSlotHeapTuple(resultSlot, true, NULL);
4801 : :
4802 : : /* ctid */
2258 rhaas@postgresql.org 4803 [ # # ]: 0 : if (dmstate->ctidAttno)
4804 : : {
4805 : 0 : ItemPointer ctid = NULL;
4806 : :
4807 : 0 : ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]);
4808 : 0 : resultTup->t_self = *ctid;
4809 : : }
4810 : :
4811 : : /*
4812 : : * And remaining columns
4813 : : *
4814 : : * Note: since we currently don't allow the target relation to appear
4815 : : * on the nullable side of an outer join, any system columns wouldn't
4816 : : * go to NULL.
4817 : : *
4818 : : * Note: no need to care about tableoid here because it will be
4819 : : * initialized in ExecProcessReturning().
4820 : : */
4821 : 0 : HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId);
4822 : 0 : HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId);
4823 [ # # ]: 0 : HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId);
4824 : : }
4825 : :
4826 : : /*
4827 : : * And return the result tuple.
4828 : : */
2258 rhaas@postgresql.org 4829 :CBC 16 : return resultSlot;
4830 : : }
4831 : :
4832 : : /*
4833 : : * Prepare for processing of parameters used in remote query.
4834 : : */
4835 : : static void
2949 4836 : 18 : prepare_query_params(PlanState *node,
4837 : : List *fdw_exprs,
4838 : : int numParams,
4839 : : FmgrInfo **param_flinfo,
4840 : : List **param_exprs,
4841 : : const char ***param_values)
4842 : : {
4843 : : int i;
4844 : : ListCell *lc;
4845 : :
4846 [ - + ]: 18 : Assert(numParams > 0);
4847 : :
4848 : : /* Prepare for output conversion of parameters used in remote query. */
4849 : 18 : *param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * numParams);
4850 : :
4851 : 18 : i = 0;
4852 [ + - + + : 37 : foreach(lc, fdw_exprs)
+ + ]
4853 : : {
4854 : 19 : Node *param_expr = (Node *) lfirst(lc);
4855 : : Oid typefnoid;
4856 : : bool isvarlena;
4857 : :
4858 : 19 : getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena);
4859 : 19 : fmgr_info(typefnoid, &(*param_flinfo)[i]);
4860 : 19 : i++;
4861 : : }
4862 : :
4863 : : /*
4864 : : * Prepare remote-parameter expressions for evaluation. (Note: in
4865 : : * practice, we expect that all these expressions will be just Params, so
4866 : : * we could possibly do something more efficient than using the full
4867 : : * expression-eval machinery for this. But probably there would be little
4868 : : * benefit, and it'd require postgres_fdw to know more than is desirable
4869 : : * about Param evaluation.)
4870 : : */
2588 andres@anarazel.de 4871 : 18 : *param_exprs = ExecInitExprList(fdw_exprs, node);
4872 : :
4873 : : /* Allocate buffer for text form of query parameters. */
2949 rhaas@postgresql.org 4874 : 18 : *param_values = (const char **) palloc0(numParams * sizeof(char *));
4875 : 18 : }
4876 : :
4877 : : /*
4878 : : * Construct array of query parameter values in text format.
4879 : : */
4880 : : static void
4881 : 346 : process_query_params(ExprContext *econtext,
4882 : : FmgrInfo *param_flinfo,
4883 : : List *param_exprs,
4884 : : const char **param_values)
4885 : : {
4886 : : int nestlevel;
4887 : : int i;
4888 : : ListCell *lc;
4889 : :
4890 : 346 : nestlevel = set_transmission_modes();
4891 : :
4892 : 346 : i = 0;
4893 [ + - + + : 892 : foreach(lc, param_exprs)
+ + ]
4894 : : {
4895 : 546 : ExprState *expr_state = (ExprState *) lfirst(lc);
4896 : : Datum expr_value;
4897 : : bool isNull;
4898 : :
4899 : : /* Evaluate the parameter expression */
2642 andres@anarazel.de 4900 : 546 : expr_value = ExecEvalExpr(expr_state, econtext, &isNull);
4901 : :
4902 : : /*
4903 : : * Get string representation of each parameter value by invoking
4904 : : * type-specific output function, unless the value is null.
4905 : : */
2949 rhaas@postgresql.org 4906 [ - + ]: 546 : if (isNull)
2949 rhaas@postgresql.org 4907 :UBC 0 : param_values[i] = NULL;
4908 : : else
2949 rhaas@postgresql.org 4909 :CBC 546 : param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value);
4910 : :
2946 tgl@sss.pgh.pa.us 4911 : 546 : i++;
4912 : : }
4913 : :
2949 rhaas@postgresql.org 4914 : 346 : reset_transmission_modes(nestlevel);
4915 : 346 : }
4916 : :
4917 : : /*
4918 : : * postgresAnalyzeForeignTable
4919 : : * Test whether analyzing this foreign table is supported
4920 : : */
4921 : : static bool
4070 tgl@sss.pgh.pa.us 4922 : 40 : postgresAnalyzeForeignTable(Relation relation,
4923 : : AcquireSampleRowsFunc *func,
4924 : : BlockNumber *totalpages)
4925 : : {
4926 : : ForeignTable *table;
4927 : : UserMapping *user;
4928 : : PGconn *conn;
4929 : : StringInfoData sql;
4069 4930 : 40 : PGresult *volatile res = NULL;
4931 : :
4932 : : /* Return the row-analysis function pointer */
4070 4933 : 40 : *func = postgresAcquireSampleRowsFunc;
4934 : :
4935 : : /*
4936 : : * Now we have to get the number of pages. It's annoying that the ANALYZE
4937 : : * API requires us to return that now, because it forces some duplication
4938 : : * of effort between this routine and postgresAcquireSampleRowsFunc. But
4939 : : * it's probably not worth redefining that API at this point.
4940 : : */
4941 : :
4942 : : /*
4943 : : * Get the connection to use. We do the remote access as the table's
4944 : : * owner, even if the ANALYZE was started by some other user.
4945 : : */
4069 4946 : 40 : table = GetForeignTable(RelationGetRelid(relation));
2999 rhaas@postgresql.org 4947 : 40 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
1110 efujita@postgresql.o 4948 : 40 : conn = GetConnection(user, false, NULL);
4949 : :
4950 : : /*
4951 : : * Construct command to get page count for relation.
4952 : : */
4069 tgl@sss.pgh.pa.us 4953 : 40 : initStringInfo(&sql);
4954 : 40 : deparseAnalyzeSizeSql(&sql, relation);
4955 : :
4956 : : /* In what follows, do not risk leaking any PGresults. */
4957 [ + - ]: 40 : PG_TRY();
4958 : : {
1110 efujita@postgresql.o 4959 : 40 : res = pgfdw_exec_query(conn, sql.data, NULL);
4069 tgl@sss.pgh.pa.us 4960 [ - + ]: 40 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3723 tgl@sss.pgh.pa.us 4961 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, sql.data);
4962 : :
4069 tgl@sss.pgh.pa.us 4963 [ + - - + ]:CBC 40 : if (PQntuples(res) != 1 || PQnfields(res) != 1)
4069 tgl@sss.pgh.pa.us 4964 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from deparseAnalyzeSizeSql query");
4069 tgl@sss.pgh.pa.us 4965 :CBC 40 : *totalpages = strtoul(PQgetvalue(res, 0, 0), NULL, 10);
4966 : : }
1626 peter@eisentraut.org 4967 :UBC 0 : PG_FINALLY();
4968 : : {
651 peter@eisentraut.org 4969 :CBC 40 : PQclear(res);
4970 : : }
4069 tgl@sss.pgh.pa.us 4971 [ - + ]: 40 : PG_END_TRY();
4972 : :
4973 : 40 : ReleaseConnection(conn);
4974 : :
4070 4975 : 40 : return true;
4976 : : }
4977 : :
4978 : : /*
4979 : : * postgresGetAnalyzeInfoForForeignTable
4980 : : * Count tuples in foreign table (just get pg_class.reltuples).
4981 : : *
4982 : : * can_tablesample determines if the remote relation supports acquiring the
4983 : : * sample using TABLESAMPLE.
4984 : : */
4985 : : static double
463 tomas.vondra@postgre 4986 : 40 : postgresGetAnalyzeInfoForForeignTable(Relation relation, bool *can_tablesample)
4987 : : {
4988 : : ForeignTable *table;
4989 : : UserMapping *user;
4990 : : PGconn *conn;
4991 : : StringInfoData sql;
471 4992 : 40 : PGresult *volatile res = NULL;
4993 : 40 : volatile double reltuples = -1;
463 4994 : 40 : volatile char relkind = 0;
4995 : :
4996 : : /* assume the remote relation does not support TABLESAMPLE */
4997 : 40 : *can_tablesample = false;
4998 : :
4999 : : /*
5000 : : * Get the connection to use. We do the remote access as the table's
5001 : : * owner, even if the ANALYZE was started by some other user.
5002 : : */
471 5003 : 40 : table = GetForeignTable(RelationGetRelid(relation));
5004 : 40 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
5005 : 40 : conn = GetConnection(user, false, NULL);
5006 : :
5007 : : /*
5008 : : * Construct command to get page count for relation.
5009 : : */
5010 : 40 : initStringInfo(&sql);
463 5011 : 40 : deparseAnalyzeInfoSql(&sql, relation);
5012 : :
5013 : : /* In what follows, do not risk leaking any PGresults. */
471 5014 [ + - ]: 40 : PG_TRY();
5015 : : {
5016 : 40 : res = pgfdw_exec_query(conn, sql.data, NULL);
5017 [ - + ]: 40 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
471 tomas.vondra@postgre 5018 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, sql.data);
5019 : :
463 tomas.vondra@postgre 5020 [ + - - + ]:CBC 40 : if (PQntuples(res) != 1 || PQnfields(res) != 2)
362 drowley@postgresql.o 5021 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
471 tomas.vondra@postgre 5022 :CBC 40 : reltuples = strtod(PQgetvalue(res, 0, 0), NULL);
463 5023 : 40 : relkind = *(PQgetvalue(res, 0, 1));
5024 : : }
471 tomas.vondra@postgre 5025 :UBC 0 : PG_FINALLY();
5026 : : {
471 tomas.vondra@postgre 5027 [ + - ]:CBC 40 : if (res)
5028 : 40 : PQclear(res);
5029 : : }
5030 [ - + ]: 40 : PG_END_TRY();
5031 : :
5032 : 40 : ReleaseConnection(conn);
5033 : :
5034 : : /* TABLESAMPLE is supported only for regular tables and matviews */
463 tomas.vondra@postgre 5035 :UBC 0 : *can_tablesample = (relkind == RELKIND_RELATION ||
463 tomas.vondra@postgre 5036 [ - + - - ]:CBC 40 : relkind == RELKIND_MATVIEW ||
463 tomas.vondra@postgre 5037 [ # # ]:UBC 0 : relkind == RELKIND_PARTITIONED_TABLE);
5038 : :
471 tomas.vondra@postgre 5039 :CBC 40 : return reltuples;
5040 : : }
5041 : :
5042 : : /*
5043 : : * Acquire a random sample of rows from foreign table managed by postgres_fdw.
5044 : : *
5045 : : * Selected rows are returned in the caller-allocated array rows[],
5046 : : * which must have at least targrows entries.
5047 : : * The actual number of rows selected is returned as the function result.
5048 : : * We also count the total number of rows in the table and return it into
5049 : : * *totalrows. Note that *totaldeadrows is always set to 0.
5050 : : *
5051 : : * Note that the returned list of rows is not always in order by physical
5052 : : * position in the table. Therefore, correlation estimates derived later
5053 : : * may be meaningless, but it's OK because we don't use the estimates
5054 : : * currently (the planner only pays attention to correlation for indexscans).
5055 : : */
5056 : : static int
4070 tgl@sss.pgh.pa.us 5057 : 40 : postgresAcquireSampleRowsFunc(Relation relation, int elevel,
5058 : : HeapTuple *rows, int targrows,
5059 : : double *totalrows,
5060 : : double *totaldeadrows)
5061 : : {
5062 : : PgFdwAnalyzeState astate;
5063 : : ForeignTable *table;
5064 : : ForeignServer *server;
5065 : : UserMapping *user;
5066 : : PGconn *conn;
5067 : : int server_version_num;
471 tomas.vondra@postgre 5068 : 40 : PgFdwSamplingMethod method = ANALYZE_SAMPLE_AUTO; /* auto is default */
5069 : 40 : double sample_frac = -1.0;
5070 : : double reltuples;
5071 : : unsigned int cursor_number;
5072 : : StringInfoData sql;
4070 tgl@sss.pgh.pa.us 5073 : 40 : PGresult *volatile res = NULL;
5074 : : ListCell *lc;
5075 : :
5076 : : /* Initialize workspace state */
5077 : 40 : astate.rel = relation;
5078 : 40 : astate.attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(relation));
5079 : :
5080 : 40 : astate.rows = rows;
5081 : 40 : astate.targrows = targrows;
5082 : 40 : astate.numrows = 0;
5083 : 40 : astate.samplerows = 0;
5084 : 40 : astate.rowstoskip = -1; /* -1 means not set yet */
3257 simon@2ndQuadrant.co 5085 : 40 : reservoir_init_selection_state(&astate.rstate, targrows);
5086 : :
5087 : : /* Remember ANALYZE context, and create a per-tuple temp context */
4070 tgl@sss.pgh.pa.us 5088 : 40 : astate.anl_cxt = CurrentMemoryContext;
5089 : 40 : astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext,
5090 : : "postgres_fdw temporary data",
5091 : : ALLOCSET_SMALL_SIZES);
5092 : :
5093 : : /*
5094 : : * Get the connection to use. We do the remote access as the table's
5095 : : * owner, even if the ANALYZE was started by some other user.
5096 : : */
5097 : 40 : table = GetForeignTable(RelationGetRelid(relation));
2993 rhaas@postgresql.org 5098 : 40 : server = GetForeignServer(table->serverid);
2999 5099 : 40 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
1110 efujita@postgresql.o 5100 : 40 : conn = GetConnection(user, false, NULL);
5101 : :
5102 : : /* We'll need server version, so fetch it now. */
471 tomas.vondra@postgre 5103 : 40 : server_version_num = PQserverVersion(conn);
5104 : :
5105 : : /*
5106 : : * What sampling method should we use?
5107 : : */
5108 [ + - + + : 176 : foreach(lc, server->options)
+ + ]
5109 : : {
5110 : 136 : DefElem *def = (DefElem *) lfirst(lc);
5111 : :
5112 [ - + ]: 136 : if (strcmp(def->defname, "analyze_sampling") == 0)
5113 : : {
471 tomas.vondra@postgre 5114 :UBC 0 : char *value = defGetString(def);
5115 : :
5116 [ # # ]: 0 : if (strcmp(value, "off") == 0)
5117 : 0 : method = ANALYZE_SAMPLE_OFF;
5118 [ # # ]: 0 : else if (strcmp(value, "auto") == 0)
5119 : 0 : method = ANALYZE_SAMPLE_AUTO;
5120 [ # # ]: 0 : else if (strcmp(value, "random") == 0)
5121 : 0 : method = ANALYZE_SAMPLE_RANDOM;
5122 [ # # ]: 0 : else if (strcmp(value, "system") == 0)
5123 : 0 : method = ANALYZE_SAMPLE_SYSTEM;
5124 [ # # ]: 0 : else if (strcmp(value, "bernoulli") == 0)
5125 : 0 : method = ANALYZE_SAMPLE_BERNOULLI;
5126 : :
5127 : 0 : break;
5128 : : }
5129 : : }
5130 : :
471 tomas.vondra@postgre 5131 [ + - + + :CBC 93 : foreach(lc, table->options)
+ + ]
5132 : : {
5133 : 53 : DefElem *def = (DefElem *) lfirst(lc);
5134 : :
5135 [ - + ]: 53 : if (strcmp(def->defname, "analyze_sampling") == 0)
5136 : : {
471 tomas.vondra@postgre 5137 :UBC 0 : char *value = defGetString(def);
5138 : :
5139 [ # # ]: 0 : if (strcmp(value, "off") == 0)
5140 : 0 : method = ANALYZE_SAMPLE_OFF;
5141 [ # # ]: 0 : else if (strcmp(value, "auto") == 0)
5142 : 0 : method = ANALYZE_SAMPLE_AUTO;
5143 [ # # ]: 0 : else if (strcmp(value, "random") == 0)
5144 : 0 : method = ANALYZE_SAMPLE_RANDOM;
5145 [ # # ]: 0 : else if (strcmp(value, "system") == 0)
5146 : 0 : method = ANALYZE_SAMPLE_SYSTEM;
5147 [ # # ]: 0 : else if (strcmp(value, "bernoulli") == 0)
5148 : 0 : method = ANALYZE_SAMPLE_BERNOULLI;
5149 : :
5150 : 0 : break;
5151 : : }
5152 : : }
5153 : :
5154 : : /*
5155 : : * Error-out if explicitly required one of the TABLESAMPLE methods, but
5156 : : * the server does not support it.
5157 : : */
471 tomas.vondra@postgre 5158 [ - + - - ]:CBC 40 : if ((server_version_num < 95000) &&
471 tomas.vondra@postgre 5159 [ # # ]:UBC 0 : (method == ANALYZE_SAMPLE_SYSTEM ||
5160 : : method == ANALYZE_SAMPLE_BERNOULLI))
5161 [ # # ]: 0 : ereport(ERROR,
5162 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5163 : : errmsg("remote server does not support TABLESAMPLE feature")));
5164 : :
5165 : : /*
5166 : : * If we've decided to do remote sampling, calculate the sampling rate. We
5167 : : * need to get the number of tuples from the remote server, but skip that
5168 : : * network round-trip if not needed.
5169 : : */
471 tomas.vondra@postgre 5170 [ + - ]:CBC 40 : if (method != ANALYZE_SAMPLE_OFF)
5171 : : {
5172 : : bool can_tablesample;
5173 : :
463 5174 : 40 : reltuples = postgresGetAnalyzeInfoForForeignTable(relation,
5175 : : &can_tablesample);
5176 : :
5177 : : /*
5178 : : * Make sure we're not choosing TABLESAMPLE when the remote relation
5179 : : * does not support that. But only do this for "auto" - if the user
5180 : : * explicitly requested BERNOULLI/SYSTEM, it's better to fail.
5181 : : */
5182 [ - + - - ]: 40 : if (!can_tablesample && (method == ANALYZE_SAMPLE_AUTO))
463 tomas.vondra@postgre 5183 :UBC 0 : method = ANALYZE_SAMPLE_RANDOM;
5184 : :
5185 : : /*
5186 : : * Remote's reltuples could be 0 or -1 if the table has never been
5187 : : * vacuumed/analyzed. In that case, disable sampling after all.
5188 : : */
471 tomas.vondra@postgre 5189 [ + + + - ]:CBC 40 : if ((reltuples <= 0) || (targrows >= reltuples))
5190 : 40 : method = ANALYZE_SAMPLE_OFF;
5191 : : else
5192 : : {
5193 : : /*
5194 : : * All supported sampling methods require sampling rate, not
5195 : : * target rows directly, so we calculate that using the remote
5196 : : * reltuples value. That's imperfect, because it might be off a
5197 : : * good deal, but that's not something we can (or should) address
5198 : : * here.
5199 : : *
5200 : : * If reltuples is too low (i.e. when table grew), we'll end up
5201 : : * sampling more rows - but then we'll apply the local sampling,
5202 : : * so we get the expected sample size. This is the same outcome as
5203 : : * without remote sampling.
5204 : : *
5205 : : * If reltuples is too high (e.g. after bulk DELETE), we will end
5206 : : * up sampling too few rows.
5207 : : *
5208 : : * We can't really do much better here - we could try sampling a
5209 : : * bit more rows, but we don't know how off the reltuples value is
5210 : : * so how much is "a bit more"?
5211 : : *
5212 : : * Furthermore, the targrows value for partitions is determined
5213 : : * based on table size (relpages), which can be off in different
5214 : : * ways too. Adjusting the sampling rate here might make the issue
5215 : : * worse.
5216 : : */
471 tomas.vondra@postgre 5217 :UBC 0 : sample_frac = targrows / reltuples;
5218 : :
5219 : : /*
5220 : : * We should never get sampling rate outside the valid range
5221 : : * (between 0.0 and 1.0), because those cases should be covered by
5222 : : * the previous branch that sets ANALYZE_SAMPLE_OFF.
5223 : : */
464 5224 [ # # # # ]: 0 : Assert(sample_frac >= 0.0 && sample_frac <= 1.0);
5225 : : }
5226 : : }
5227 : :
5228 : : /*
5229 : : * For "auto" method, pick the one we believe is best. For servers with
5230 : : * TABLESAMPLE support we pick BERNOULLI, for old servers we fall-back to
5231 : : * random() to at least reduce network transfer.
5232 : : */
463 tomas.vondra@postgre 5233 [ - + ]:CBC 40 : if (method == ANALYZE_SAMPLE_AUTO)
5234 : : {
463 tomas.vondra@postgre 5235 [ # # ]:UBC 0 : if (server_version_num < 95000)
5236 : 0 : method = ANALYZE_SAMPLE_RANDOM;
5237 : : else
5238 : 0 : method = ANALYZE_SAMPLE_BERNOULLI;
5239 : : }
5240 : :
5241 : : /*
5242 : : * Construct cursor that retrieves whole rows from remote.
5243 : : */
4070 tgl@sss.pgh.pa.us 5244 :CBC 40 : cursor_number = GetCursorNumber(conn);
5245 : 40 : initStringInfo(&sql);
5246 : 40 : appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number);
5247 : :
471 tomas.vondra@postgre 5248 : 40 : deparseAnalyzeSql(&sql, relation, method, sample_frac, &astate.retrieved_attrs);
5249 : :
5250 : : /* In what follows, do not risk leaking any PGresults. */
4070 tgl@sss.pgh.pa.us 5251 [ + + ]: 40 : PG_TRY();
5252 : : {
5253 : : char fetch_sql[64];
5254 : : int fetch_size;
5255 : :
1110 efujita@postgresql.o 5256 : 40 : res = pgfdw_exec_query(conn, sql.data, NULL);
4070 tgl@sss.pgh.pa.us 5257 [ - + ]: 40 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
3723 tgl@sss.pgh.pa.us 5258 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, sql.data);
4070 tgl@sss.pgh.pa.us 5259 :CBC 40 : PQclear(res);
5260 : 40 : res = NULL;
5261 : :
5262 : : /*
5263 : : * Determine the fetch size. The default is arbitrary, but shouldn't
5264 : : * be enormous.
5265 : : */
1747 efujita@postgresql.o 5266 : 40 : fetch_size = 100;
5267 [ + - + + : 176 : foreach(lc, server->options)
+ + ]
5268 : : {
5269 : 136 : DefElem *def = (DefElem *) lfirst(lc);
5270 : :
5271 [ - + ]: 136 : if (strcmp(def->defname, "fetch_size") == 0)
5272 : : {
1012 fujii@postgresql.org 5273 :UBC 0 : (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
1747 efujita@postgresql.o 5274 : 0 : break;
5275 : : }
5276 : : }
1747 efujita@postgresql.o 5277 [ + - + + :CBC 93 : foreach(lc, table->options)
+ + ]
5278 : : {
5279 : 53 : DefElem *def = (DefElem *) lfirst(lc);
5280 : :
5281 [ - + ]: 53 : if (strcmp(def->defname, "fetch_size") == 0)
5282 : : {
1012 fujii@postgresql.org 5283 :UBC 0 : (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
1747 efujita@postgresql.o 5284 : 0 : break;
5285 : : }
5286 : : }
5287 : :
5288 : : /* Construct command to fetch rows from remote. */
1747 efujita@postgresql.o 5289 :CBC 40 : snprintf(fetch_sql, sizeof(fetch_sql), "FETCH %d FROM c%u",
5290 : : fetch_size, cursor_number);
5291 : :
5292 : : /* Retrieve and process rows a batch at a time. */
5293 : : for (;;)
4070 tgl@sss.pgh.pa.us 5294 : 152 : {
5295 : : int numrows;
5296 : : int i;
5297 : :
5298 : : /* Allow users to cancel long query */
5299 [ - + ]: 192 : CHECK_FOR_INTERRUPTS();
5300 : :
5301 : : /*
5302 : : * XXX possible future improvement: if rowstoskip is large, we
5303 : : * could issue a MOVE rather than physically fetching the rows,
5304 : : * then just adjust rowstoskip and samplerows appropriately.
5305 : : */
5306 : :
5307 : : /* Fetch some rows */
1110 efujita@postgresql.o 5308 : 192 : res = pgfdw_exec_query(conn, fetch_sql, NULL);
5309 : : /* On error, report the original query, not the FETCH. */
4070 tgl@sss.pgh.pa.us 5310 [ - + ]: 192 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3723 tgl@sss.pgh.pa.us 5311 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, sql.data);
5312 : :
5313 : : /* Process whatever we got. */
4070 tgl@sss.pgh.pa.us 5314 :CBC 192 : numrows = PQntuples(res);
5315 [ + + ]: 15916 : for (i = 0; i < numrows; i++)
5316 : 15725 : analyze_row_processor(res, i, &astate);
5317 : :
5318 : 191 : PQclear(res);
5319 : 191 : res = NULL;
5320 : :
5321 : : /* Must be EOF if we didn't get all the rows requested. */
5322 [ + + ]: 191 : if (numrows < fetch_size)
5323 : 39 : break;
5324 : : }
5325 : :
5326 : : /* Close the cursor, just to be tidy. */
1110 efujita@postgresql.o 5327 : 39 : close_cursor(conn, cursor_number, NULL);
5328 : : }
4070 tgl@sss.pgh.pa.us 5329 : 1 : PG_CATCH();
5330 : : {
651 peter@eisentraut.org 5331 : 1 : PQclear(res);
4070 tgl@sss.pgh.pa.us 5332 : 1 : PG_RE_THROW();
5333 : : }
5334 [ - + ]: 39 : PG_END_TRY();
5335 : :
5336 : 39 : ReleaseConnection(conn);
5337 : :
5338 : : /* We assume that we have no dead tuple. */
5339 : 39 : *totaldeadrows = 0.0;
5340 : :
5341 : : /*
5342 : : * Without sampling, we've retrieved all living tuples from foreign
5343 : : * server, so report that as totalrows. Otherwise use the reltuples
5344 : : * estimate we got from the remote side.
5345 : : */
471 tomas.vondra@postgre 5346 [ + - ]: 39 : if (method == ANALYZE_SAMPLE_OFF)
5347 : 39 : *totalrows = astate.samplerows;
5348 : : else
471 tomas.vondra@postgre 5349 :UBC 0 : *totalrows = reltuples;
5350 : :
5351 : : /*
5352 : : * Emit some interesting relation info
5353 : : */
4070 tgl@sss.pgh.pa.us 5354 [ - + ]:CBC 39 : ereport(elevel,
5355 : : (errmsg("\"%s\": table contains %.0f rows, %d rows in sample",
5356 : : RelationGetRelationName(relation),
5357 : : *totalrows, astate.numrows)));
5358 : :
5359 : 39 : return astate.numrows;
5360 : : }
5361 : :
5362 : : /*
5363 : : * Collect sample rows from the result of query.
5364 : : * - Use all tuples in sample until target # of samples are collected.
5365 : : * - Subsequently, replace already-sampled tuples randomly.
5366 : : */
5367 : : static void
5368 : 15725 : analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
5369 : : {
5370 : 15725 : int targrows = astate->targrows;
5371 : : int pos; /* array index to store tuple in */
5372 : : MemoryContext oldcontext;
5373 : :
5374 : : /* Always increment sample row counter. */
5375 : 15725 : astate->samplerows += 1;
5376 : :
5377 : : /*
5378 : : * Determine the slot where this sample row should be stored. Set pos to
5379 : : * negative value to indicate the row should be skipped.
5380 : : */
5381 [ + - ]: 15725 : if (astate->numrows < targrows)
5382 : : {
5383 : : /* First targrows rows are always included into the sample */
5384 : 15725 : pos = astate->numrows++;
5385 : : }
5386 : : else
5387 : : {
5388 : : /*
5389 : : * Now we start replacing tuples in the sample until we reach the end
5390 : : * of the relation. Same algorithm as in acquire_sample_rows in
5391 : : * analyze.c; see Jeff Vitter's paper.
5392 : : */
4070 tgl@sss.pgh.pa.us 5393 [ # # ]:UBC 0 : if (astate->rowstoskip < 0)
3257 simon@2ndQuadrant.co 5394 : 0 : astate->rowstoskip = reservoir_get_next_S(&astate->rstate, astate->samplerows, targrows);
5395 : :
4070 tgl@sss.pgh.pa.us 5396 [ # # ]: 0 : if (astate->rowstoskip <= 0)
5397 : : {
5398 : : /* Choose a random reservoir element to replace. */
868 5399 : 0 : pos = (int) (targrows * sampler_random_fract(&astate->rstate.randstate));
4070 5400 [ # # # # ]: 0 : Assert(pos >= 0 && pos < targrows);
5401 : 0 : heap_freetuple(astate->rows[pos]);
5402 : : }
5403 : : else
5404 : : {
5405 : : /* Skip this tuple. */
5406 : 0 : pos = -1;
5407 : : }
5408 : :
5409 : 0 : astate->rowstoskip -= 1;
5410 : : }
5411 : :
4070 tgl@sss.pgh.pa.us 5412 [ + - ]:CBC 15725 : if (pos >= 0)
5413 : : {
5414 : : /*
5415 : : * Create sample tuple from current result row, and store it in the
5416 : : * position determined above. The tuple has to be created in anl_cxt.
5417 : : */
5418 : 15725 : oldcontext = MemoryContextSwitchTo(astate->anl_cxt);
5419 : :
5420 : 15725 : astate->rows[pos] = make_tuple_from_result_row(res, row,
5421 : : astate->rel,
5422 : : astate->attinmeta,
5423 : : astate->retrieved_attrs,
5424 : : NULL,
5425 : : astate->temp_cxt);
5426 : :
5427 : 15724 : MemoryContextSwitchTo(oldcontext);
5428 : : }
5429 : 15724 : }
5430 : :
5431 : : /*
5432 : : * Import a foreign schema
5433 : : */
5434 : : static List *
3566 5435 : 8 : postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
5436 : : {
5437 : 8 : List *commands = NIL;
5438 : 8 : bool import_collate = true;
5439 : 8 : bool import_default = false;
983 efujita@postgresql.o 5440 : 8 : bool import_generated = true;
3566 tgl@sss.pgh.pa.us 5441 : 8 : bool import_not_null = true;
5442 : : ForeignServer *server;
5443 : : UserMapping *mapping;
5444 : : PGconn *conn;
5445 : : StringInfoData buf;
5446 : 8 : PGresult *volatile res = NULL;
5447 : : int numrows,
5448 : : i;
5449 : : ListCell *lc;
5450 : :
5451 : : /* Parse statement options */
5452 [ + + + + : 12 : foreach(lc, stmt->options)
+ + ]
5453 : : {
5454 : 4 : DefElem *def = (DefElem *) lfirst(lc);
5455 : :
5456 [ + + ]: 4 : if (strcmp(def->defname, "import_collate") == 0)
5457 : 1 : import_collate = defGetBoolean(def);
5458 [ + + ]: 3 : else if (strcmp(def->defname, "import_default") == 0)
5459 : 1 : import_default = defGetBoolean(def);
983 efujita@postgresql.o 5460 [ + + ]: 2 : else if (strcmp(def->defname, "import_generated") == 0)
5461 : 1 : import_generated = defGetBoolean(def);
3566 tgl@sss.pgh.pa.us 5462 [ + - ]: 1 : else if (strcmp(def->defname, "import_not_null") == 0)
5463 : 1 : import_not_null = defGetBoolean(def);
5464 : : else
3566 tgl@sss.pgh.pa.us 5465 [ # # ]:UBC 0 : ereport(ERROR,
5466 : : (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
5467 : : errmsg("invalid option \"%s\"", def->defname)));
5468 : : }
5469 : :
5470 : : /*
5471 : : * Get connection to the foreign server. Connection manager will
5472 : : * establish new connection if necessary.
5473 : : */
3566 tgl@sss.pgh.pa.us 5474 :CBC 8 : server = GetForeignServer(serverOid);
5475 : 8 : mapping = GetUserMapping(GetUserId(), server->serverid);
1110 efujita@postgresql.o 5476 : 8 : conn = GetConnection(mapping, false, NULL);
5477 : :
5478 : : /* Don't attempt to import collation if remote server hasn't got it */
3566 tgl@sss.pgh.pa.us 5479 [ - + ]: 8 : if (PQserverVersion(conn) < 90100)
3566 tgl@sss.pgh.pa.us 5480 :UBC 0 : import_collate = false;
5481 : :
5482 : : /* Create workspace for strings */
3566 tgl@sss.pgh.pa.us 5483 :CBC 8 : initStringInfo(&buf);
5484 : :
5485 : : /* In what follows, do not risk leaking any PGresults. */
5486 [ + + ]: 8 : PG_TRY();
5487 : : {
5488 : : /* Check that the schema really exists */
5489 : 8 : appendStringInfoString(&buf, "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = ");
5490 : 8 : deparseStringLiteral(&buf, stmt->remote_schema);
5491 : :
1110 efujita@postgresql.o 5492 : 8 : res = pgfdw_exec_query(conn, buf.data, NULL);
3566 tgl@sss.pgh.pa.us 5493 [ - + ]: 8 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3566 tgl@sss.pgh.pa.us 5494 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, buf.data);
5495 : :
3566 tgl@sss.pgh.pa.us 5496 [ + + ]:CBC 8 : if (PQntuples(res) != 1)
5497 [ + - ]: 1 : ereport(ERROR,
5498 : : (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND),
5499 : : errmsg("schema \"%s\" is not present on foreign server \"%s\"",
5500 : : stmt->remote_schema, server->servername)));
5501 : :
5502 : 7 : PQclear(res);
5503 : 7 : res = NULL;
5504 : 7 : resetStringInfo(&buf);
5505 : :
5506 : : /*
5507 : : * Fetch all table data from this schema, possibly restricted by
5508 : : * EXCEPT or LIMIT TO. (We don't actually need to pay any attention
5509 : : * to EXCEPT/LIMIT TO here, because the core code will filter the
5510 : : * statements we return according to those lists anyway. But it
5511 : : * should save a few cycles to not process excluded tables in the
5512 : : * first place.)
5513 : : *
5514 : : * Import table data for partitions only when they are explicitly
5515 : : * specified in LIMIT TO clause. Otherwise ignore them and only
5516 : : * include the definitions of the root partitioned tables to allow
5517 : : * access to the complete remote data set locally in the schema
5518 : : * imported.
5519 : : *
5520 : : * Note: because we run the connection with search_path restricted to
5521 : : * pg_catalog, the format_type() and pg_get_expr() outputs will always
5522 : : * include a schema name for types/functions in other schemas, which
5523 : : * is what we want.
5524 : : */
983 efujita@postgresql.o 5525 : 7 : appendStringInfoString(&buf,
5526 : : "SELECT relname, "
5527 : : " attname, "
5528 : : " format_type(atttypid, atttypmod), "
5529 : : " attnotnull, "
5530 : : " pg_get_expr(adbin, adrelid), ");
5531 : :
5532 : : /* Generated columns are supported since Postgres 12 */
5533 [ + - ]: 7 : if (PQserverVersion(conn) >= 120000)
5534 : 7 : appendStringInfoString(&buf,
5535 : : " attgenerated, ");
5536 : : else
983 efujita@postgresql.o 5537 :UBC 0 : appendStringInfoString(&buf,
5538 : : " NULL, ");
5539 : :
3566 tgl@sss.pgh.pa.us 5540 [ + + ]:CBC 7 : if (import_collate)
5541 : 6 : appendStringInfoString(&buf,
5542 : : " collname, "
5543 : : " collnsp.nspname ");
5544 : : else
956 5545 : 1 : appendStringInfoString(&buf,
5546 : : " NULL, NULL ");
5547 : :
5548 : 7 : appendStringInfoString(&buf,
5549 : : "FROM pg_class c "
5550 : : " JOIN pg_namespace n ON "
5551 : : " relnamespace = n.oid "
5552 : : " LEFT JOIN pg_attribute a ON "
5553 : : " attrelid = c.oid AND attnum > 0 "
5554 : : " AND NOT attisdropped "
5555 : : " LEFT JOIN pg_attrdef ad ON "
5556 : : " adrelid = c.oid AND adnum = attnum ");
5557 : :
5558 [ + + ]: 7 : if (import_collate)
5559 : 6 : appendStringInfoString(&buf,
5560 : : " LEFT JOIN pg_collation coll ON "
5561 : : " coll.oid = attcollation "
5562 : : " LEFT JOIN pg_namespace collnsp ON "
5563 : : " collnsp.oid = collnamespace ");
5564 : :
3566 5565 : 7 : appendStringInfoString(&buf,
5566 : : "WHERE c.relkind IN ("
5567 : : CppAsString2(RELKIND_RELATION) ","
5568 : : CppAsString2(RELKIND_VIEW) ","
5569 : : CppAsString2(RELKIND_FOREIGN_TABLE) ","
5570 : : CppAsString2(RELKIND_MATVIEW) ","
5571 : : CppAsString2(RELKIND_PARTITIONED_TABLE) ") "
5572 : : " AND n.nspname = ");
5573 : 7 : deparseStringLiteral(&buf, stmt->remote_schema);
5574 : :
5575 : : /* Partitions are supported since Postgres 10 */
1103 fujii@postgresql.org 5576 [ + - ]: 7 : if (PQserverVersion(conn) >= 100000 &&
5577 [ + + ]: 7 : stmt->list_type != FDW_IMPORT_SCHEMA_LIMIT_TO)
2571 rhaas@postgresql.org 5578 : 5 : appendStringInfoString(&buf, " AND NOT c.relispartition ");
5579 : :
5580 : : /* Apply restrictions for LIMIT TO and EXCEPT */
3566 tgl@sss.pgh.pa.us 5581 [ + + ]: 7 : if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
5582 [ + + ]: 5 : stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
5583 : : {
5584 : 3 : bool first_item = true;
5585 : :
5586 : 3 : appendStringInfoString(&buf, " AND c.relname ");
5587 [ + + ]: 3 : if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
5588 : 1 : appendStringInfoString(&buf, "NOT ");
5589 : 3 : appendStringInfoString(&buf, "IN (");
5590 : :
5591 : : /* Append list of table names within IN clause */
5592 [ + - + + : 11 : foreach(lc, stmt->table_list)
+ + ]
5593 : : {
5594 : 8 : RangeVar *rv = (RangeVar *) lfirst(lc);
5595 : :
5596 [ + + ]: 8 : if (first_item)
5597 : 3 : first_item = false;
5598 : : else
5599 : 5 : appendStringInfoString(&buf, ", ");
5600 : 8 : deparseStringLiteral(&buf, rv->relname);
5601 : : }
3261 peter_e@gmx.net 5602 : 3 : appendStringInfoChar(&buf, ')');
5603 : : }
5604 : :
5605 : : /* Append ORDER BY at the end of query to ensure output ordering */
3209 heikki.linnakangas@i 5606 : 7 : appendStringInfoString(&buf, " ORDER BY c.relname, a.attnum");
5607 : :
5608 : : /* Fetch the data */
1110 efujita@postgresql.o 5609 : 7 : res = pgfdw_exec_query(conn, buf.data, NULL);
3566 tgl@sss.pgh.pa.us 5610 [ - + ]: 7 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
3566 tgl@sss.pgh.pa.us 5611 :UBC 0 : pgfdw_report_error(ERROR, res, conn, false, buf.data);
5612 : :
5613 : : /* Process results */
3566 tgl@sss.pgh.pa.us 5614 :CBC 7 : numrows = PQntuples(res);
5615 : : /* note: incrementation of i happens in inner loop's while() test */
5616 [ + + ]: 43 : for (i = 0; i < numrows;)
5617 : : {
5618 : 36 : char *tablename = PQgetvalue(res, i, 0);
5619 : 36 : bool first_item = true;
5620 : :
5621 : 36 : resetStringInfo(&buf);
5622 : 36 : appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n",
5623 : : quote_identifier(tablename));
5624 : :
5625 : : /* Scan all rows for this table */
5626 : : do
5627 : : {
5628 : : char *attname;
5629 : : char *typename;
5630 : : char *attnotnull;
5631 : : char *attgenerated;
5632 : : char *attdefault;
5633 : : char *collname;
5634 : : char *collnamespace;
5635 : :
5636 : : /* If table has no columns, we'll see nulls here */
5637 [ + + ]: 71 : if (PQgetisnull(res, i, 1))
5638 : 5 : continue;
5639 : :
5640 : 66 : attname = PQgetvalue(res, i, 1);
5641 : 66 : typename = PQgetvalue(res, i, 2);
5642 : 66 : attnotnull = PQgetvalue(res, i, 3);
956 5643 [ + + ]: 66 : attdefault = PQgetisnull(res, i, 4) ? (char *) NULL :
3566 5644 : 15 : PQgetvalue(res, i, 4);
956 5645 [ + - ]: 66 : attgenerated = PQgetisnull(res, i, 5) ? (char *) NULL :
3566 5646 : 66 : PQgetvalue(res, i, 5);
983 efujita@postgresql.o 5647 [ + + ]: 66 : collname = PQgetisnull(res, i, 6) ? (char *) NULL :
3566 tgl@sss.pgh.pa.us 5648 : 19 : PQgetvalue(res, i, 6);
983 efujita@postgresql.o 5649 [ + + ]: 66 : collnamespace = PQgetisnull(res, i, 7) ? (char *) NULL :
5650 : 19 : PQgetvalue(res, i, 7);
5651 : :
3566 tgl@sss.pgh.pa.us 5652 [ + + ]: 66 : if (first_item)
5653 : 31 : first_item = false;
5654 : : else
5655 : 35 : appendStringInfoString(&buf, ",\n");
5656 : :
5657 : : /* Print column name and type */
5658 : 66 : appendStringInfo(&buf, " %s %s",
5659 : : quote_identifier(attname),
5660 : : typename);
5661 : :
5662 : : /*
5663 : : * Add column_name option so that renaming the foreign table's
5664 : : * column doesn't break the association to the underlying
5665 : : * column.
5666 : : */
5667 : 66 : appendStringInfoString(&buf, " OPTIONS (column_name ");
5668 : 66 : deparseStringLiteral(&buf, attname);
3261 peter_e@gmx.net 5669 : 66 : appendStringInfoChar(&buf, ')');
5670 : :
5671 : : /* Add COLLATE if needed */
3566 tgl@sss.pgh.pa.us 5672 [ + + + + : 66 : if (import_collate && collname != NULL && collnamespace != NULL)
+ - ]
5673 : 19 : appendStringInfo(&buf, " COLLATE %s.%s",
5674 : : quote_identifier(collnamespace),
5675 : : quote_identifier(collname));
5676 : :
5677 : : /* Add DEFAULT if needed */
983 efujita@postgresql.o 5678 [ + + + + : 66 : if (import_default && attdefault != NULL &&
+ - ]
5679 [ + + ]: 3 : (!attgenerated || !attgenerated[0]))
3566 tgl@sss.pgh.pa.us 5680 : 2 : appendStringInfo(&buf, " DEFAULT %s", attdefault);
5681 : :
5682 : : /* Add GENERATED if needed */
983 efujita@postgresql.o 5683 [ + + + - ]: 66 : if (import_generated && attgenerated != NULL &&
5684 [ + + ]: 53 : attgenerated[0] == ATTRIBUTE_GENERATED_STORED)
5685 : : {
5686 [ - + ]: 4 : Assert(attdefault != NULL);
5687 : 4 : appendStringInfo(&buf,
5688 : : " GENERATED ALWAYS AS (%s) STORED",
5689 : : attdefault);
5690 : : }
5691 : :
5692 : : /* Add NOT NULL if needed */
3566 tgl@sss.pgh.pa.us 5693 [ + + + + ]: 66 : if (import_not_null && attnotnull[0] == 't')
5694 : 4 : appendStringInfoString(&buf, " NOT NULL");
5695 : : }
5696 [ + + ]: 71 : while (++i < numrows &&
5697 [ + + ]: 64 : strcmp(PQgetvalue(res, i, 0), tablename) == 0);
5698 : :
5699 : : /*
5700 : : * Add server name and table-level options. We specify remote
5701 : : * schema and table name as options (the latter to ensure that
5702 : : * renaming the foreign table doesn't break the association).
5703 : : */
5704 : 36 : appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (",
5705 : 36 : quote_identifier(server->servername));
5706 : :
5707 : 36 : appendStringInfoString(&buf, "schema_name ");
5708 : 36 : deparseStringLiteral(&buf, stmt->remote_schema);
5709 : 36 : appendStringInfoString(&buf, ", table_name ");
5710 : 36 : deparseStringLiteral(&buf, tablename);
5711 : :
5712 : 36 : appendStringInfoString(&buf, ");");
5713 : :
5714 : 36 : commands = lappend(commands, pstrdup(buf.data));
5715 : : }
5716 : : }
1626 peter@eisentraut.org 5717 : 1 : PG_FINALLY();
5718 : : {
651 5719 : 8 : PQclear(res);
5720 : : }
3566 tgl@sss.pgh.pa.us 5721 [ + + ]: 8 : PG_END_TRY();
5722 : :
5723 : 7 : ReleaseConnection(conn);
5724 : :
5725 : 7 : return commands;
5726 : : }
5727 : :
5728 : : /*
5729 : : * Check if reltarget is safe enough to push down semi-join. Reltarget is not
5730 : : * safe, if it contains references to inner rel relids, which do not belong to
5731 : : * outer rel.
5732 : : */
5733 : : static bool
131 akorotkov@postgresql 5734 :GNC 58 : semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel)
5735 : : {
5736 : : List *vars;
5737 : : ListCell *lc;
5738 : 58 : bool ok = true;
5739 : :
5740 [ - + ]: 58 : Assert(joinrel->reltarget);
5741 : :
5742 : 58 : vars = pull_var_clause((Node *) joinrel->reltarget->exprs, PVC_INCLUDE_PLACEHOLDERS);
5743 : :
5744 [ + - + + : 431 : foreach(lc, vars)
+ + ]
5745 : : {
5746 : 388 : Var *var = (Var *) lfirst(lc);
5747 : :
5748 [ - + ]: 388 : if (!IsA(var, Var))
131 akorotkov@postgresql 5749 :UNC 0 : continue;
5750 : :
131 akorotkov@postgresql 5751 [ + + ]:GNC 388 : if (bms_is_member(var->varno, innerrel->relids) &&
5752 [ + - ]: 15 : !bms_is_member(var->varno, outerrel->relids))
5753 : : {
5754 : : /*
5755 : : * The planner can create semi-join, which refers to inner rel
5756 : : * vars in its target list. However, we deparse semi-join as an
5757 : : * exists() subquery, so can't handle references to inner rel in
5758 : : * the target list.
5759 : : */
5760 : 15 : ok = false;
5761 : 15 : break;
5762 : : }
5763 : : }
5764 : 58 : return ok;
5765 : : }
5766 : :
5767 : : /*
5768 : : * Assess whether the join between inner and outer relations can be pushed down
5769 : : * to the foreign server. As a side effect, save information we obtain in this
5770 : : * function to PgFdwRelationInfo passed in.
5771 : : */
5772 : : static bool
2987 rhaas@postgresql.org 5773 :CBC 367 : foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
5774 : : RelOptInfo *outerrel, RelOptInfo *innerrel,
5775 : : JoinPathExtraData *extra)
5776 : : {
5777 : : PgFdwRelationInfo *fpinfo;
5778 : : PgFdwRelationInfo *fpinfo_o;
5779 : : PgFdwRelationInfo *fpinfo_i;
5780 : : ListCell *lc;
5781 : : List *joinclauses;
5782 : :
5783 : : /*
5784 : : * We support pushing down INNER, LEFT, RIGHT, FULL OUTER and SEMI joins.
5785 : : * Constructing queries representing ANTI joins is hard, hence not
5786 : : * considered right now.
5787 : : */
5788 [ + + + + : 367 : if (jointype != JOIN_INNER && jointype != JOIN_LEFT &&
+ - ]
131 akorotkov@postgresql 5789 [ + + + + ]:GNC 123 : jointype != JOIN_RIGHT && jointype != JOIN_FULL &&
5790 : : jointype != JOIN_SEMI)
5791 : 19 : return false;
5792 : :
5793 : : /*
5794 : : * We can't push down semi-join if its reltarget is not safe
5795 : : */
5796 [ + + + + ]: 348 : if ((jointype == JOIN_SEMI) && !semijoin_target_ok(root, joinrel, outerrel, innerrel))
2987 rhaas@postgresql.org 5797 :CBC 15 : return false;
5798 : :
5799 : : /*
5800 : : * If either of the joining relations is marked as unsafe to pushdown, the
5801 : : * join can not be pushed down.
5802 : : */
5803 : 333 : fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private;
5804 : 333 : fpinfo_o = (PgFdwRelationInfo *) outerrel->fdw_private;
5805 : 333 : fpinfo_i = (PgFdwRelationInfo *) innerrel->fdw_private;
5806 [ + - + + : 333 : if (!fpinfo_o || !fpinfo_o->pushdown_safe ||
+ - ]
5807 [ - + ]: 328 : !fpinfo_i || !fpinfo_i->pushdown_safe)
2987 rhaas@postgresql.org 5808 :GBC 5 : return false;
5809 : :
5810 : : /*
5811 : : * If joining relations have local conditions, those conditions are
5812 : : * required to be applied before joining the relations. Hence the join can
5813 : : * not be pushed down.
5814 : : */
2987 rhaas@postgresql.org 5815 [ + + + + ]:CBC 328 : if (fpinfo_o->local_conds || fpinfo_i->local_conds)
5816 : 9 : return false;
5817 : :
5818 : : /*
5819 : : * Merge FDW options. We might be tempted to do this after we have deemed
5820 : : * the foreign join to be OK. But we must do this beforehand so that we
5821 : : * know which quals can be evaluated on the foreign server, which might
5822 : : * depend on shippable_extensions.
5823 : : */
2547 peter_e@gmx.net 5824 : 319 : fpinfo->server = fpinfo_o->server;
5825 : 319 : merge_fdw_options(fpinfo, fpinfo_o, fpinfo_i);
5826 : :
5827 : : /*
5828 : : * Separate restrict list into join quals and pushed-down (other) quals.
5829 : : *
5830 : : * Join quals belonging to an outer join must all be shippable, else we
5831 : : * cannot execute the join remotely. Add such quals to 'joinclauses'.
5832 : : *
5833 : : * Add other quals to fpinfo->remote_conds if they are shippable, else to
5834 : : * fpinfo->local_conds. In an inner join it's okay to execute conditions
5835 : : * either locally or remotely; the same is true for pushed-down conditions
5836 : : * at an outer join.
5837 : : *
5838 : : * Note we might return failure after having already scribbled on
5839 : : * fpinfo->remote_conds and fpinfo->local_conds. That's okay because we
5840 : : * won't consult those lists again if we deem the join unshippable.
5841 : : */
2560 tgl@sss.pgh.pa.us 5842 : 319 : joinclauses = NIL;
5843 [ + + + + : 636 : foreach(lc, extra->restrictlist)
+ + ]
5844 : : {
5845 : 320 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
5846 : 320 : bool is_remote_clause = is_foreign_expr(root, joinrel,
5847 : : rinfo->clause);
5848 : :
2186 5849 [ + + ]: 320 : if (IS_OUTER_JOIN(jointype) &&
5850 [ + + + - ]: 127 : !RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
5851 : : {
2560 5852 [ + + ]: 111 : if (!is_remote_clause)
5853 : 3 : return false;
5854 : 108 : joinclauses = lappend(joinclauses, rinfo);
5855 : : }
5856 : : else
5857 : : {
5858 [ + + ]: 209 : if (is_remote_clause)
5859 : 197 : fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
5860 : : else
5861 : 12 : fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
5862 : : }
5863 : : }
5864 : :
5865 : : /*
5866 : : * deparseExplicitTargetList() isn't smart enough to handle anything other
5867 : : * than a Var. In particular, if there's some PlaceHolderVar that would
5868 : : * need to be evaluated within this join tree (because there's an upper
5869 : : * reference to a quantity that may go to NULL as a result of an outer
5870 : : * join), then we can't try to push the join down because we'll fail when
5871 : : * we get to deparseExplicitTargetList(). However, a PlaceHolderVar that
5872 : : * needs to be evaluated *at the top* of this join tree is OK, because we
5873 : : * can do that locally after fetching the results from the remote side.
5874 : : */
2861 rhaas@postgresql.org 5875 [ + + + + : 319 : foreach(lc, root->placeholder_list)
+ + ]
5876 : : {
5877 : 11 : PlaceHolderInfo *phinfo = lfirst(lc);
5878 : : Relids relids;
5879 : :
5880 : : /* PlaceHolderInfo refers to parent relids, not child relids. */
2243 5881 [ + + - + ]: 11 : relids = IS_OTHER_REL(joinrel) ?
5882 [ + - ]: 22 : joinrel->top_parent_relids : joinrel->relids;
5883 : :
2861 5884 [ + - + + ]: 22 : if (bms_is_subset(phinfo->ph_eval_at, relids) &&
5885 : 11 : bms_nonempty_difference(relids, phinfo->ph_eval_at))
5886 : 8 : return false;
5887 : : }
5888 : :
5889 : : /* Save the join clauses, for later use. */
2987 5890 : 308 : fpinfo->joinclauses = joinclauses;
5891 : :
5892 : 308 : fpinfo->outerrel = outerrel;
5893 : 308 : fpinfo->innerrel = innerrel;
5894 : 308 : fpinfo->jointype = jointype;
5895 : :
5896 : : /*
5897 : : * By default, both the input relations are not required to be deparsed as
5898 : : * subqueries, but there might be some relations covered by the input
5899 : : * relations that are required to be deparsed as subqueries, so save the
5900 : : * relids of those relations for later use by the deparser.
5901 : : */
2586 5902 : 308 : fpinfo->make_outerrel_subquery = false;
5903 : 308 : fpinfo->make_innerrel_subquery = false;
5904 [ - + ]: 308 : Assert(bms_is_subset(fpinfo_o->lower_subquery_rels, outerrel->relids));
5905 [ - + ]: 308 : Assert(bms_is_subset(fpinfo_i->lower_subquery_rels, innerrel->relids));
5906 : 616 : fpinfo->lower_subquery_rels = bms_union(fpinfo_o->lower_subquery_rels,
5907 : 308 : fpinfo_i->lower_subquery_rels);
131 akorotkov@postgresql 5908 :GNC 616 : fpinfo->hidden_subquery_rels = bms_union(fpinfo_o->hidden_subquery_rels,
5909 : 308 : fpinfo_i->hidden_subquery_rels);
5910 : :
5911 : : /*
5912 : : * Pull the other remote conditions from the joining relations into join
5913 : : * clauses or other remote clauses (remote_conds) of this relation
5914 : : * wherever possible. This avoids building subqueries at every join step.
5915 : : *
5916 : : * For an inner join, clauses from both the relations are added to the
5917 : : * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from
5918 : : * the outer side are added to remote_conds since those can be evaluated
5919 : : * after the join is evaluated. The clauses from inner side are added to
5920 : : * the joinclauses, since they need to be evaluated while constructing the
5921 : : * join.
5922 : : *
5923 : : * For SEMI-JOIN clauses from inner relation can not be added to
5924 : : * remote_conds, but should be treated as join clauses (as they are
5925 : : * deparsed to EXISTS subquery, where inner relation can be referred). A
5926 : : * list of relation ids, which can't be referred to from higher levels, is
5927 : : * preserved as a hidden_subquery_rels list.
5928 : : *
5929 : : * For a FULL OUTER JOIN, the other clauses from either relation can not
5930 : : * be added to the joinclauses or remote_conds, since each relation acts
5931 : : * as an outer relation for the other.
5932 : : *
5933 : : * The joining sides can not have local conditions, thus no need to test
5934 : : * shippability of the clauses being pulled up.
5935 : : */
2987 rhaas@postgresql.org 5936 [ + + - + :CBC 308 : switch (jointype)
+ - ]
5937 : : {
5938 : 170 : case JOIN_INNER:
5939 : 340 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
1707 tgl@sss.pgh.pa.us 5940 : 170 : fpinfo_i->remote_conds);
2987 rhaas@postgresql.org 5941 : 340 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
1707 tgl@sss.pgh.pa.us 5942 : 170 : fpinfo_o->remote_conds);
2987 rhaas@postgresql.org 5943 : 170 : break;
5944 : :
5945 : 58 : case JOIN_LEFT:
5946 : 116 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
1707 tgl@sss.pgh.pa.us 5947 : 58 : fpinfo_i->remote_conds);
2987 rhaas@postgresql.org 5948 : 116 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
1707 tgl@sss.pgh.pa.us 5949 : 58 : fpinfo_o->remote_conds);
2987 rhaas@postgresql.org 5950 : 58 : break;
5951 : :
2987 rhaas@postgresql.org 5952 :UBC 0 : case JOIN_RIGHT:
5953 : 0 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
1707 tgl@sss.pgh.pa.us 5954 : 0 : fpinfo_o->remote_conds);
2987 rhaas@postgresql.org 5955 : 0 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
1707 tgl@sss.pgh.pa.us 5956 : 0 : fpinfo_i->remote_conds);
2987 rhaas@postgresql.org 5957 : 0 : break;
5958 : :
131 akorotkov@postgresql 5959 :GNC 38 : case JOIN_SEMI:
5960 : 76 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
5961 : 38 : fpinfo_i->remote_conds);
5962 : 76 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
5963 : 38 : fpinfo->remote_conds);
5964 : 38 : fpinfo->remote_conds = list_copy(fpinfo_o->remote_conds);
5965 : 76 : fpinfo->hidden_subquery_rels = bms_union(fpinfo->hidden_subquery_rels,
5966 : 38 : innerrel->relids);
5967 : 38 : break;
5968 : :
2987 rhaas@postgresql.org 5969 :CBC 42 : case JOIN_FULL:
5970 : :
5971 : : /*
5972 : : * In this case, if any of the input relations has conditions, we
5973 : : * need to deparse that relation as a subquery so that the
5974 : : * conditions can be evaluated before the join. Remember it in
5975 : : * the fpinfo of this relation so that the deparser can take
5976 : : * appropriate action. Also, save the relids of base relations
5977 : : * covered by that relation for later use by the deparser.
5978 : : */
2586 5979 [ + + ]: 42 : if (fpinfo_o->remote_conds)
5980 : : {
5981 : 14 : fpinfo->make_outerrel_subquery = true;
5982 : 14 : fpinfo->lower_subquery_rels =
5983 : 14 : bms_add_members(fpinfo->lower_subquery_rels,
5984 : 14 : outerrel->relids);
5985 : : }
5986 [ + + ]: 42 : if (fpinfo_i->remote_conds)
5987 : : {
5988 : 14 : fpinfo->make_innerrel_subquery = true;
5989 : 14 : fpinfo->lower_subquery_rels =
5990 : 14 : bms_add_members(fpinfo->lower_subquery_rels,
5991 : 14 : innerrel->relids);
5992 : : }
2987 5993 : 42 : break;
5994 : :
2987 rhaas@postgresql.org 5995 :UBC 0 : default:
5996 : : /* Should not happen, we have just checked this above */
5997 [ # # ]: 0 : elog(ERROR, "unsupported join type %d", jointype);
5998 : : }
5999 : :
6000 : : /*
6001 : : * For an inner join, all restrictions can be treated alike. Treating the
6002 : : * pushed down conditions as join conditions allows a top level full outer
6003 : : * join to be deparsed without requiring subqueries.
6004 : : */
2916 rhaas@postgresql.org 6005 [ + + ]:CBC 308 : if (jointype == JOIN_INNER)
6006 : : {
6007 [ - + ]: 170 : Assert(!fpinfo->joinclauses);
6008 : 170 : fpinfo->joinclauses = fpinfo->remote_conds;
6009 : 170 : fpinfo->remote_conds = NIL;
6010 : : }
131 akorotkov@postgresql 6011 [ + + + - :GNC 138 : else if (jointype == JOIN_LEFT || jointype == JOIN_RIGHT || jointype == JOIN_FULL)
+ + ]
6012 : : {
6013 : : /*
6014 : : * Conditions, generated from semi-joins, should be evaluated before
6015 : : * LEFT/RIGHT/FULL join.
6016 : : */
6017 [ - + ]: 100 : if (!bms_is_empty(fpinfo_o->hidden_subquery_rels))
6018 : : {
131 akorotkov@postgresql 6019 :UNC 0 : fpinfo->make_outerrel_subquery = true;
6020 : 0 : fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, outerrel->relids);
6021 : : }
6022 : :
131 akorotkov@postgresql 6023 [ + + ]:GNC 100 : if (!bms_is_empty(fpinfo_i->hidden_subquery_rels))
6024 : : {
6025 : 2 : fpinfo->make_innerrel_subquery = true;
6026 : 2 : fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, innerrel->relids);
6027 : : }
6028 : : }
6029 : :
6030 : : /* Mark that this join can be pushed down safely */
2916 rhaas@postgresql.org 6031 :CBC 308 : fpinfo->pushdown_safe = true;
6032 : :
6033 : : /* Get user mapping */
2830 tgl@sss.pgh.pa.us 6034 [ + + ]: 308 : if (fpinfo->use_remote_estimate)
6035 : : {
6036 [ + + ]: 220 : if (fpinfo_o->use_remote_estimate)
6037 : 152 : fpinfo->user = fpinfo_o->user;
6038 : : else
6039 : 68 : fpinfo->user = fpinfo_i->user;
6040 : : }
6041 : : else
6042 : 88 : fpinfo->user = NULL;
6043 : :
6044 : : /*
6045 : : * Set # of retrieved rows and cached relation costs to some negative
6046 : : * value, so that we can detect when they are set to some sensible values,
6047 : : * during one (usually the first) of the calls to estimate_path_cost_size.
6048 : : */
1766 efujita@postgresql.o 6049 : 308 : fpinfo->retrieved_rows = -1;
2916 rhaas@postgresql.org 6050 : 308 : fpinfo->rel_startup_cost = -1;
6051 : 308 : fpinfo->rel_total_cost = -1;
6052 : :
6053 : : /*
6054 : : * Set the string describing this join relation to be used in EXPLAIN
6055 : : * output of corresponding ForeignScan. Note that the decoration we add
6056 : : * to the base relation names mustn't include any digits, or it'll confuse
6057 : : * postgresExplainForeignScan.
6058 : : */
1595 tgl@sss.pgh.pa.us 6059 : 308 : fpinfo->relation_name = psprintf("(%s) %s JOIN (%s)",
6060 : : fpinfo_o->relation_name,
6061 : : get_jointype_name(fpinfo->jointype),
6062 : : fpinfo_i->relation_name);
6063 : :
6064 : : /*
6065 : : * Set the relation index. This is defined as the position of this
6066 : : * joinrel in the join_rel_list list plus the length of the rtable list.
6067 : : * Note that since this joinrel is at the end of the join_rel_list list
6068 : : * when we are called, we can get the position by list_length.
6069 : : */
2489 6070 [ - + ]: 308 : Assert(fpinfo->relation_index == 0); /* shouldn't be set yet */
2586 rhaas@postgresql.org 6071 : 308 : fpinfo->relation_index =
6072 : 308 : list_length(root->parse->rtable) + list_length(root->join_rel_list);
6073 : :
2987 6074 : 308 : return true;
6075 : : }
6076 : :
6077 : : static void
2958 6078 : 1448 : add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
6079 : : Path *epq_path, List *restrictlist)
6080 : : {
2489 tgl@sss.pgh.pa.us 6081 : 1448 : List *useful_pathkeys_list = NIL; /* List of all pathkeys */
6082 : : ListCell *lc;
6083 : :
2958 rhaas@postgresql.org 6084 : 1448 : useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
6085 : :
6086 : : /*
6087 : : * Before creating sorted paths, arrange for the passed-in EPQ path, if
6088 : : * any, to return columns needed by the parent ForeignScan node so that
6089 : : * they will propagate up through Sort nodes injected below, if necessary.
6090 : : */
578 efujita@postgresql.o 6091 [ + + + + ]: 1448 : if (epq_path != NULL && useful_pathkeys_list != NIL)
6092 : : {
6093 : 28 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
6094 : 28 : PathTarget *target = copy_pathtarget(epq_path->pathtarget);
6095 : :
6096 : : /* Include columns required for evaluating PHVs in the tlist. */
6097 : 28 : add_new_columns_to_pathtarget(target,
6098 : 28 : pull_var_clause((Node *) target->exprs,
6099 : : PVC_RECURSE_PLACEHOLDERS));
6100 : :
6101 : : /* Include columns required for evaluating the local conditions. */
6102 [ + + + + : 31 : foreach(lc, fpinfo->local_conds)
+ + ]
6103 : : {
6104 : 3 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
6105 : :
6106 : 3 : add_new_columns_to_pathtarget(target,
6107 : 3 : pull_var_clause((Node *) rinfo->clause,
6108 : : PVC_RECURSE_PLACEHOLDERS));
6109 : : }
6110 : :
6111 : : /*
6112 : : * If we have added any new columns, adjust the tlist of the EPQ path.
6113 : : *
6114 : : * Note: the plan created using this path will only be used to execute
6115 : : * EPQ checks, where accuracy of the plan cost and width estimates
6116 : : * would not be important, so we do not do set_pathtarget_cost_width()
6117 : : * for the new pathtarget here. See also postgresGetForeignPlan().
6118 : : */
6119 [ + + ]: 28 : if (list_length(target->exprs) > list_length(epq_path->pathtarget->exprs))
6120 : : {
6121 : : /* The EPQ path is a join path, so it is projection-capable. */
6122 [ - + ]: 4 : Assert(is_projection_capable_path(epq_path));
6123 : :
6124 : : /*
6125 : : * Use create_projection_path() here, so as to avoid modifying it
6126 : : * in place.
6127 : : */
6128 : 4 : epq_path = (Path *) create_projection_path(root,
6129 : : rel,
6130 : : epq_path,
6131 : : target);
6132 : : }
6133 : : }
6134 : :
6135 : : /* Create one path for each set of pathkeys we found above. */
2958 rhaas@postgresql.org 6136 [ + + + + : 2116 : foreach(lc, useful_pathkeys_list)
+ + ]
6137 : : {
6138 : : double rows;
6139 : : int width;
6140 : : Cost startup_cost;
6141 : : Cost total_cost;
6142 : 668 : List *useful_pathkeys = lfirst(lc);
6143 : : Path *sorted_epq_path;
6144 : :
1839 efujita@postgresql.o 6145 : 668 : estimate_path_cost_size(root, rel, NIL, useful_pathkeys, NULL,
6146 : : &rows, &width, &startup_cost, &total_cost);
6147 : :
6148 : : /*
6149 : : * The EPQ path must be at least as well sorted as the path itself, in
6150 : : * case it gets used as input to a mergejoin.
6151 : : */
2279 rhaas@postgresql.org 6152 : 668 : sorted_epq_path = epq_path;
6153 [ + + ]: 668 : if (sorted_epq_path != NULL &&
6154 [ + + ]: 28 : !pathkeys_contained_in(useful_pathkeys,
6155 : : sorted_epq_path->pathkeys))
6156 : : sorted_epq_path = (Path *)
6157 : 22 : create_sort_path(root,
6158 : : rel,
6159 : : sorted_epq_path,
6160 : : useful_pathkeys,
6161 : : -1.0);
6162 : :
1893 tgl@sss.pgh.pa.us 6163 [ + + + + ]: 668 : if (IS_SIMPLE_REL(rel))
6164 : 415 : add_path(rel, (Path *)
6165 : 415 : create_foreignscan_path(root, rel,
6166 : : NULL,
6167 : : rows,
6168 : : startup_cost,
6169 : : total_cost,
6170 : : useful_pathkeys,
6171 : : rel->lateral_relids,
6172 : : sorted_epq_path,
6173 : : NIL, /* no fdw_restrictinfo
6174 : : * list */
6175 : : NIL));
6176 : : else
6177 : 253 : add_path(rel, (Path *)
6178 : 253 : create_foreign_join_path(root, rel,
6179 : : NULL,
6180 : : rows,
6181 : : startup_cost,
6182 : : total_cost,
6183 : : useful_pathkeys,
6184 : : rel->lateral_relids,
6185 : : sorted_epq_path,
6186 : : restrictlist,
6187 : : NIL));
6188 : : }
2958 rhaas@postgresql.org 6189 : 1448 : }
6190 : :
6191 : : /*
6192 : : * Parse options from foreign server and apply them to fpinfo.
6193 : : *
6194 : : * New options might also require tweaking merge_fdw_options().
6195 : : */
6196 : : static void
2547 peter_e@gmx.net 6197 : 1143 : apply_server_options(PgFdwRelationInfo *fpinfo)
6198 : : {
6199 : : ListCell *lc;
6200 : :
6201 [ + - + + : 4801 : foreach(lc, fpinfo->server->options)
+ + ]
6202 : : {
6203 : 3658 : DefElem *def = (DefElem *) lfirst(lc);
6204 : :
6205 [ + + ]: 3658 : if (strcmp(def->defname, "use_remote_estimate") == 0)
6206 : 94 : fpinfo->use_remote_estimate = defGetBoolean(def);
6207 [ + + ]: 3564 : else if (strcmp(def->defname, "fdw_startup_cost") == 0)
1012 fujii@postgresql.org 6208 : 6 : (void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0,
6209 : : NULL);
2547 peter_e@gmx.net 6210 [ + + ]: 3558 : else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
1012 fujii@postgresql.org 6211 : 2 : (void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0,
6212 : : NULL);
2547 peter_e@gmx.net 6213 [ + + ]: 3556 : else if (strcmp(def->defname, "extensions") == 0)
6214 : 899 : fpinfo->shippable_extensions =
6215 : 899 : ExtractExtensionList(defGetString(def), false);
6216 [ - + ]: 2657 : else if (strcmp(def->defname, "fetch_size") == 0)
1012 fujii@postgresql.org 6217 :UBC 0 : (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
1110 efujita@postgresql.o 6218 [ + + ]:CBC 2657 : else if (strcmp(def->defname, "async_capable") == 0)
6219 : 121 : fpinfo->async_capable = defGetBoolean(def);
6220 : : }
2547 peter_e@gmx.net 6221 : 1143 : }
6222 : :
6223 : : /*
6224 : : * Parse options from foreign table and apply them to fpinfo.
6225 : : *
6226 : : * New options might also require tweaking merge_fdw_options().
6227 : : */
6228 : : static void
6229 : 1143 : apply_table_options(PgFdwRelationInfo *fpinfo)
6230 : : {
6231 : : ListCell *lc;
6232 : :
6233 [ + - + + : 3329 : foreach(lc, fpinfo->table->options)
+ + ]
6234 : : {
6235 : 2186 : DefElem *def = (DefElem *) lfirst(lc);
6236 : :
6237 [ + + ]: 2186 : if (strcmp(def->defname, "use_remote_estimate") == 0)
6238 : 333 : fpinfo->use_remote_estimate = defGetBoolean(def);
6239 [ - + ]: 1853 : else if (strcmp(def->defname, "fetch_size") == 0)
1012 fujii@postgresql.org 6240 :UBC 0 : (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
1110 efujita@postgresql.o 6241 [ - + ]:CBC 1853 : else if (strcmp(def->defname, "async_capable") == 0)
1110 efujita@postgresql.o 6242 :UBC 0 : fpinfo->async_capable = defGetBoolean(def);
6243 : : }
2547 peter_e@gmx.net 6244 :CBC 1143 : }
6245 : :
6246 : : /*
6247 : : * Merge FDW options from input relations into a new set of options for a join
6248 : : * or an upper rel.
6249 : : *
6250 : : * For a join relation, FDW-specific information about the inner and outer
6251 : : * relations is provided using fpinfo_i and fpinfo_o. For an upper relation,
6252 : : * fpinfo_o provides the information for the input relation; fpinfo_i is
6253 : : * expected to NULL.
6254 : : */
6255 : : static void
6256 : 750 : merge_fdw_options(PgFdwRelationInfo *fpinfo,
6257 : : const PgFdwRelationInfo *fpinfo_o,
6258 : : const PgFdwRelationInfo *fpinfo_i)
6259 : : {
6260 : : /* We must always have fpinfo_o. */
6261 [ - + ]: 750 : Assert(fpinfo_o);
6262 : :
6263 : : /* fpinfo_i may be NULL, but if present the servers must both match. */
6264 [ + + - + ]: 750 : Assert(!fpinfo_i ||
6265 : : fpinfo_i->server->serverid == fpinfo_o->server->serverid);
6266 : :
6267 : : /*
6268 : : * Copy the server specific FDW options. (For a join, both relations come
6269 : : * from the same server, so the server options should have the same value
6270 : : * for both relations.)
6271 : : */
6272 : 750 : fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost;
6273 : 750 : fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost;
6274 : 750 : fpinfo->shippable_extensions = fpinfo_o->shippable_extensions;
6275 : 750 : fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate;
6276 : 750 : fpinfo->fetch_size = fpinfo_o->fetch_size;
1110 efujita@postgresql.o 6277 : 750 : fpinfo->async_capable = fpinfo_o->async_capable;
6278 : :
6279 : : /* Merge the table level options from either side of the join. */
2547 peter_e@gmx.net 6280 [ + + ]: 750 : if (fpinfo_i)
6281 : : {
6282 : : /*
6283 : : * We'll prefer to use remote estimates for this join if any table
6284 : : * from either side of the join is using remote estimates. This is
6285 : : * most likely going to be preferred since they're already willing to
6286 : : * pay the price of a round trip to get the remote EXPLAIN. In any
6287 : : * case it's not entirely clear how we might otherwise handle this
6288 : : * best.
6289 : : */
6290 [ + + ]: 481 : fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate ||
2524 bruce@momjian.us 6291 [ + + ]: 162 : fpinfo_i->use_remote_estimate;
6292 : :
6293 : : /*
6294 : : * Set fetch size to maximum of the joining sides, since we are
6295 : : * expecting the rows returned by the join to be proportional to the
6296 : : * relation sizes.
6297 : : */
2547 peter_e@gmx.net 6298 : 319 : fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size);
6299 : :
6300 : : /*
6301 : : * We'll prefer to consider this join async-capable if any table from
6302 : : * either side of the join is considered async-capable. This would be
6303 : : * reasonable because in that case the foreign server would have its
6304 : : * own resources to scan that table asynchronously, and the join could
6305 : : * also be computed asynchronously using the resources.
6306 : : */
1110 efujita@postgresql.o 6307 [ + + ]: 630 : fpinfo->async_capable = fpinfo_o->async_capable ||
6308 [ - + ]: 311 : fpinfo_i->async_capable;
6309 : : }
2547 peter_e@gmx.net 6310 : 750 : }
6311 : :
6312 : : /*
6313 : : * postgresGetForeignJoinPaths
6314 : : * Add possible ForeignPath to joinrel, if join is safe to push down.
6315 : : */
6316 : : static void
2987 rhaas@postgresql.org 6317 : 1180 : postgresGetForeignJoinPaths(PlannerInfo *root,
6318 : : RelOptInfo *joinrel,
6319 : : RelOptInfo *outerrel,
6320 : : RelOptInfo *innerrel,
6321 : : JoinType jointype,
6322 : : JoinPathExtraData *extra)
6323 : : {
6324 : : PgFdwRelationInfo *fpinfo;
6325 : : ForeignPath *joinpath;
6326 : : double rows;
6327 : : int width;
6328 : : Cost startup_cost;
6329 : : Cost total_cost;
6330 : : Path *epq_path; /* Path to create plan to be executed when
6331 : : * EvalPlanQual gets triggered. */
6332 : :
6333 : : /*
6334 : : * Skip if this join combination has been considered already.
6335 : : */
6336 [ + + ]: 1180 : if (joinrel->fdw_private)
6337 : 872 : return;
6338 : :
6339 : : /*
6340 : : * This code does not work for joins with lateral references, since those
6341 : : * must have parameterized paths, which we don't generate yet.
6342 : : */
1893 tgl@sss.pgh.pa.us 6343 [ + + ]: 371 : if (!bms_is_empty(joinrel->lateral_relids))
6344 : 4 : return;
6345 : :
6346 : : /*
6347 : : * Create unfinished PgFdwRelationInfo entry which is used to indicate
6348 : : * that the join relation is already considered, so that we won't waste
6349 : : * time in judging safety of join pushdown and adding the same paths again
6350 : : * if found safe. Once we know that this join can be pushed down, we fill
6351 : : * the entry.
6352 : : */
2987 rhaas@postgresql.org 6353 : 367 : fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
6354 : 367 : fpinfo->pushdown_safe = false;
6355 : 367 : joinrel->fdw_private = fpinfo;
6356 : : /* attrs_used is only for base relations. */
6357 : 367 : fpinfo->attrs_used = NULL;
6358 : :
6359 : : /*
6360 : : * If there is a possibility that EvalPlanQual will be executed, we need
6361 : : * to be able to reconstruct the row using scans of the base relations.
6362 : : * GetExistingLocalJoinPath will find a suitable path for this purpose in
6363 : : * the path list of the joinrel, if one exists. We must be careful to
6364 : : * call it before adding any ForeignPath, since the ForeignPath might
6365 : : * dominate the only suitable local path available. We also do it before
6366 : : * calling foreign_join_ok(), since that function updates fpinfo and marks
6367 : : * it as pushable if the join is found to be pushable.
6368 : : */
6369 [ + + ]: 367 : if (root->parse->commandType == CMD_DELETE ||
6370 [ + + ]: 353 : root->parse->commandType == CMD_UPDATE ||
6371 [ + + ]: 335 : root->rowMarks)
6372 : : {
6373 : 60 : epq_path = GetExistingLocalJoinPath(joinrel);
6374 [ - + ]: 60 : if (!epq_path)
6375 : : {
2987 rhaas@postgresql.org 6376 [ # # ]:UBC 0 : elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
6377 : 0 : return;
6378 : : }
6379 : : }
6380 : : else
2987 rhaas@postgresql.org 6381 :CBC 307 : epq_path = NULL;
6382 : :
6383 [ + + ]: 367 : if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra))
6384 : : {
6385 : : /* Free path required for EPQ if we copied one; we don't need it now */
6386 [ + + ]: 59 : if (epq_path)
6387 : 2 : pfree(epq_path);
6388 : 59 : return;
6389 : : }
6390 : :
6391 : : /*
6392 : : * Compute the selectivity and cost of the local_conds, so we don't have
6393 : : * to do it over again for each path. The best we can do for these
6394 : : * conditions is to estimate selectivity on the basis of local statistics.
6395 : : * The local conditions are applied after the join has been computed on
6396 : : * the remote side like quals in WHERE clause, so pass jointype as
6397 : : * JOIN_INNER.
6398 : : */
6399 : 308 : fpinfo->local_conds_sel = clauselist_selectivity(root,
6400 : : fpinfo->local_conds,
6401 : : 0,
6402 : : JOIN_INNER,
6403 : : NULL);
6404 : 308 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
6405 : :
6406 : : /*
6407 : : * If we are going to estimate costs locally, estimate the join clause
6408 : : * selectivity here while we have special join info.
6409 : : */
2830 tgl@sss.pgh.pa.us 6410 [ + + ]: 308 : if (!fpinfo->use_remote_estimate)
2987 rhaas@postgresql.org 6411 : 88 : fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses,
6412 : : 0, fpinfo->jointype,
6413 : : extra->sjinfo);
6414 : :
6415 : : /* Estimate costs for bare join relation */
1839 efujita@postgresql.o 6416 : 308 : estimate_path_cost_size(root, joinrel, NIL, NIL, NULL,
6417 : : &rows, &width, &startup_cost, &total_cost);
6418 : : /* Now update this information in the joinrel */
2987 rhaas@postgresql.org 6419 : 307 : joinrel->rows = rows;
2953 tgl@sss.pgh.pa.us 6420 : 307 : joinrel->reltarget->width = width;
2987 rhaas@postgresql.org 6421 : 307 : fpinfo->rows = rows;
6422 : 307 : fpinfo->width = width;
6423 : 307 : fpinfo->startup_cost = startup_cost;
6424 : 307 : fpinfo->total_cost = total_cost;
6425 : :
6426 : : /*
6427 : : * Create a new join path and add it to the joinrel which represents a
6428 : : * join between foreign tables.
6429 : : */
1893 tgl@sss.pgh.pa.us 6430 : 307 : joinpath = create_foreign_join_path(root,
6431 : : joinrel,
6432 : : NULL, /* default pathtarget */
6433 : : rows,
6434 : : startup_cost,
6435 : : total_cost,
6436 : : NIL, /* no pathkeys */
6437 : : joinrel->lateral_relids,
6438 : : epq_path,
6439 : : extra->restrictlist,
6440 : : NIL); /* no fdw_private */
6441 : :
6442 : : /* Add generated path into joinrel by add_path(). */
2987 rhaas@postgresql.org 6443 : 307 : add_path(joinrel, (Path *) joinpath);
6444 : :
6445 : : /* Consider pathkeys for the join relation */
243 efujita@postgresql.o 6446 :GNC 307 : add_paths_with_pathkeys_for_rel(root, joinrel, epq_path,
6447 : : extra->restrictlist);
6448 : :
6449 : : /* XXX Consider parameterized paths for the join relation */
6450 : : }
6451 : :
6452 : : /*
6453 : : * Assess whether the aggregation, grouping and having operations can be pushed
6454 : : * down to the foreign server. As a side effect, save information we obtain in
6455 : : * this function to PgFdwRelationInfo of the input relation.
6456 : : */
6457 : : static bool
2204 rhaas@postgresql.org 6458 :CBC 155 : foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
6459 : : Node *havingQual)
6460 : : {
2732 6461 : 155 : Query *query = root->parse;
6462 : 155 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
2204 6463 : 155 : PathTarget *grouping_target = grouped_rel->reltarget;
6464 : : PgFdwRelationInfo *ofpinfo;
6465 : : ListCell *lc;
6466 : : int i;
2732 6467 : 155 : List *tlist = NIL;
6468 : :
6469 : : /* We currently don't support pushing Grouping Sets. */
6470 [ + + ]: 155 : if (query->groupingSets)
6471 : 6 : return false;
6472 : :
6473 : : /* Get the fpinfo of the underlying scan relation. */
6474 : 149 : ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
6475 : :
6476 : : /*
6477 : : * If underlying scan relation has any local conditions, those conditions
6478 : : * are required to be applied before performing aggregation. Hence the
6479 : : * aggregate cannot be pushed down.
6480 : : */
6481 [ + + ]: 149 : if (ofpinfo->local_conds)
6482 : 9 : return false;
6483 : :
6484 : : /*
6485 : : * Examine grouping expressions, as well as other expressions we'd need to
6486 : : * compute, and check whether they are safe to push down to the foreign
6487 : : * server. All GROUP BY expressions will be part of the grouping target
6488 : : * and thus there is no need to search for them separately. Add grouping
6489 : : * expressions into target list which will be passed to foreign server.
6490 : : *
6491 : : * A tricky fine point is that we must not put any expression into the
6492 : : * target list that is just a foreign param (that is, something that
6493 : : * deparse.c would conclude has to be sent to the foreign server). If we
6494 : : * do, the expression will also appear in the fdw_exprs list of the plan
6495 : : * node, and setrefs.c will get confused and decide that the fdw_exprs
6496 : : * entry is actually a reference to the fdw_scan_tlist entry, resulting in
6497 : : * a broken plan. Somewhat oddly, it's OK if the expression contains such
6498 : : * a node, as long as it's not at top level; then no match is possible.
6499 : : */
6500 : 140 : i = 0;
6501 [ + - + + : 415 : foreach(lc, grouping_target->exprs)
+ + ]
6502 : : {
6503 : 293 : Expr *expr = (Expr *) lfirst(lc);
6504 [ + - ]: 293 : Index sgref = get_pathtarget_sortgroupref(grouping_target, i);
6505 : : ListCell *l;
6506 : :
6507 : : /*
6508 : : * Check whether this expression is part of GROUP BY clause. Note we
6509 : : * check the whole GROUP BY clause not just processed_groupClause,
6510 : : * because we will ship all of it, cf. appendGroupByClause.
6511 : : */
6512 [ + + + + ]: 293 : if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause))
6513 : 92 : {
6514 : : TargetEntry *tle;
6515 : :
6516 : : /*
6517 : : * If any GROUP BY expression is not shippable, then we cannot
6518 : : * push down aggregation to the foreign server.
6519 : : */
6520 [ + + ]: 95 : if (!is_foreign_expr(root, grouped_rel, expr))
6521 : 18 : return false;
6522 : :
6523 : : /*
6524 : : * If it would be a foreign param, we can't put it into the tlist,
6525 : : * so we have to fail.
6526 : : */
1814 tgl@sss.pgh.pa.us 6527 [ + + ]: 94 : if (is_foreign_param(root, grouped_rel, expr))
6528 : 2 : return false;
6529 : :
6530 : : /*
6531 : : * Pushable, so add to tlist. We need to create a TLE for this
6532 : : * expression and apply the sortgroupref to it. We cannot use
6533 : : * add_to_flat_tlist() here because that avoids making duplicate
6534 : : * entries in the tlist. If there are duplicate entries with
6535 : : * distinct sortgrouprefs, we have to duplicate that situation in
6536 : : * the output tlist.
6537 : : */
2284 6538 : 92 : tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false);
6539 : 92 : tle->ressortgroupref = sgref;
6540 : 92 : tlist = lappend(tlist, tle);
6541 : : }
6542 : : else
6543 : : {
6544 : : /*
6545 : : * Non-grouping expression we need to compute. Can we ship it
6546 : : * as-is to the foreign server?
6547 : : */
1814 6548 [ + + ]: 198 : if (is_foreign_expr(root, grouped_rel, expr) &&
6549 [ + + ]: 177 : !is_foreign_param(root, grouped_rel, expr))
2732 rhaas@postgresql.org 6550 : 175 : {
6551 : : /* Yes, so add to tlist as-is; OK to suppress duplicates */
6552 : 175 : tlist = add_to_flat_tlist(tlist, list_make1(expr));
6553 : : }
6554 : : else
6555 : : {
6556 : : /* Not pushable as a whole; extract its Vars and aggregates */
6557 : : List *aggvars;
6558 : :
6559 : 23 : aggvars = pull_var_clause((Node *) expr,
6560 : : PVC_INCLUDE_AGGREGATES);
6561 : :
6562 : : /*
6563 : : * If any aggregate expression is not shippable, then we
6564 : : * cannot push down aggregation to the foreign server. (We
6565 : : * don't have to check is_foreign_param, since that certainly
6566 : : * won't return true for any such expression.)
6567 : : */
6568 [ + + ]: 23 : if (!is_foreign_expr(root, grouped_rel, (Expr *) aggvars))
6569 : 15 : return false;
6570 : :
6571 : : /*
6572 : : * Add aggregates, if any, into the targetlist. Plain Vars
6573 : : * outside an aggregate can be ignored, because they should be
6574 : : * either same as some GROUP BY column or part of some GROUP
6575 : : * BY expression. In either case, they are already part of
6576 : : * the targetlist and thus no need to add them again. In fact
6577 : : * including plain Vars in the tlist when they do not match a
6578 : : * GROUP BY column would cause the foreign server to complain
6579 : : * that the shipped query is invalid.
6580 : : */
6581 [ + + + + : 14 : foreach(l, aggvars)
+ + ]
6582 : : {
555 drowley@postgresql.o 6583 : 6 : Expr *aggref = (Expr *) lfirst(l);
6584 : :
6585 [ + + ]: 6 : if (IsA(aggref, Aggref))
6586 : 4 : tlist = add_to_flat_tlist(tlist, list_make1(aggref));
6587 : : }
6588 : : }
6589 : : }
6590 : :
2732 rhaas@postgresql.org 6591 : 275 : i++;
6592 : : }
6593 : :
6594 : : /*
6595 : : * Classify the pushable and non-pushable HAVING clauses and save them in
6596 : : * remote_conds and local_conds of the grouped rel's fpinfo.
6597 : : */
2204 6598 [ + + ]: 122 : if (havingQual)
6599 : : {
6600 [ + - + + : 34 : foreach(lc, (List *) havingQual)
+ + ]
6601 : : {
2732 6602 : 19 : Expr *expr = (Expr *) lfirst(lc);
6603 : : RestrictInfo *rinfo;
6604 : :
6605 : : /*
6606 : : * Currently, the core code doesn't wrap havingQuals in
6607 : : * RestrictInfos, so we must make our own.
6608 : : */
2560 tgl@sss.pgh.pa.us 6609 [ - + ]: 19 : Assert(!IsA(expr, RestrictInfo));
1179 6610 : 19 : rinfo = make_restrictinfo(root,
6611 : : expr,
6612 : : true,
6613 : : false,
6614 : : false,
6615 : : false,
6616 : : root->qual_security_level,
6617 : : grouped_rel->relids,
6618 : : NULL,
6619 : : NULL);
2560 6620 [ + + ]: 19 : if (is_foreign_expr(root, grouped_rel, expr))
6621 : 16 : fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
6622 : : else
6623 : 3 : fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
6624 : : }
6625 : : }
6626 : :
6627 : : /*
6628 : : * If there are any local conditions, pull Vars and aggregates from it and
6629 : : * check whether they are safe to pushdown or not.
6630 : : */
2732 rhaas@postgresql.org 6631 [ + + ]: 122 : if (fpinfo->local_conds)
6632 : : {
2560 tgl@sss.pgh.pa.us 6633 : 3 : List *aggvars = NIL;
6634 : :
6635 [ + - + + : 6 : foreach(lc, fpinfo->local_conds)
+ + ]
6636 : : {
6637 : 3 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
6638 : :
6639 : 3 : aggvars = list_concat(aggvars,
6640 : 3 : pull_var_clause((Node *) rinfo->clause,
6641 : : PVC_INCLUDE_AGGREGATES));
6642 : : }
6643 : :
2732 rhaas@postgresql.org 6644 [ + - + + : 7 : foreach(lc, aggvars)
+ + ]
6645 : : {
6646 : 5 : Expr *expr = (Expr *) lfirst(lc);
6647 : :
6648 : : /*
6649 : : * If aggregates within local conditions are not safe to push
6650 : : * down, then we cannot push down the query. Vars are already
6651 : : * part of GROUP BY clause which are checked above, so no need to
6652 : : * access them again here. Again, we need not check
6653 : : * is_foreign_param for a foreign aggregate.
6654 : : */
6655 [ + - ]: 5 : if (IsA(expr, Aggref))
6656 : : {
6657 [ + + ]: 5 : if (!is_foreign_expr(root, grouped_rel, expr))
6658 : 1 : return false;
6659 : :
2560 tgl@sss.pgh.pa.us 6660 : 4 : tlist = add_to_flat_tlist(tlist, list_make1(expr));
6661 : : }
6662 : : }
6663 : : }
6664 : :
6665 : : /* Store generated targetlist */
2732 rhaas@postgresql.org 6666 : 121 : fpinfo->grouped_tlist = tlist;
6667 : :
6668 : : /* Safe to pushdown */
6669 : 121 : fpinfo->pushdown_safe = true;
6670 : :
6671 : : /*
6672 : : * Set # of retrieved rows and cached relation costs to some negative
6673 : : * value, so that we can detect when they are set to some sensible values,
6674 : : * during one (usually the first) of the calls to estimate_path_cost_size.
6675 : : */
1766 efujita@postgresql.o 6676 : 121 : fpinfo->retrieved_rows = -1;
2732 rhaas@postgresql.org 6677 : 121 : fpinfo->rel_startup_cost = -1;
6678 : 121 : fpinfo->rel_total_cost = -1;
6679 : :
6680 : : /*
6681 : : * Set the string describing this grouped relation to be used in EXPLAIN
6682 : : * output of corresponding ForeignScan. Note that the decoration we add
6683 : : * to the base relation name mustn't include any digits, or it'll confuse
6684 : : * postgresExplainForeignScan.
6685 : : */
1595 tgl@sss.pgh.pa.us 6686 : 121 : fpinfo->relation_name = psprintf("Aggregate on (%s)",
6687 : : ofpinfo->relation_name);
6688 : :
2732 rhaas@postgresql.org 6689 : 121 : return true;
6690 : : }
6691 : :
6692 : : /*
6693 : : * postgresGetForeignUpperPaths
6694 : : * Add paths for post-join operations like aggregation, grouping etc. if
6695 : : * corresponding operations are safe to push down.
6696 : : */
6697 : : static void
6698 : 942 : postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage,
6699 : : RelOptInfo *input_rel, RelOptInfo *output_rel,
6700 : : void *extra)
6701 : : {
6702 : : PgFdwRelationInfo *fpinfo;
6703 : :
6704 : : /*
6705 : : * If input rel is not safe to pushdown, then simply return as we cannot
6706 : : * perform any post-join operations on the foreign server.
6707 : : */
6708 [ + + ]: 942 : if (!input_rel->fdw_private ||
6709 [ + + ]: 876 : !((PgFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe)
6710 : 122 : return;
6711 : :
6712 : : /* Ignore stages we don't support; and skip any duplicate calls. */
1839 efujita@postgresql.o 6713 [ + + + + ]: 820 : if ((stage != UPPERREL_GROUP_AGG &&
6714 [ + + ]: 519 : stage != UPPERREL_ORDERED &&
6715 : 803 : stage != UPPERREL_FINAL) ||
6716 [ - + ]: 803 : output_rel->fdw_private)
2732 rhaas@postgresql.org 6717 : 17 : return;
6718 : :
6719 : 803 : fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
6720 : 803 : fpinfo->pushdown_safe = false;
1839 efujita@postgresql.o 6721 : 803 : fpinfo->stage = stage;
2732 rhaas@postgresql.org 6722 : 803 : output_rel->fdw_private = fpinfo;
6723 : :
1839 efujita@postgresql.o 6724 [ + + + - ]: 803 : switch (stage)
6725 : : {
6726 : 155 : case UPPERREL_GROUP_AGG:
6727 : 155 : add_foreign_grouping_paths(root, input_rel, output_rel,
6728 : : (GroupPathExtraData *) extra);
6729 : 155 : break;
6730 : 146 : case UPPERREL_ORDERED:
6731 : 146 : add_foreign_ordered_paths(root, input_rel, output_rel);
6732 : 146 : break;
6733 : 502 : case UPPERREL_FINAL:
6734 : 502 : add_foreign_final_paths(root, input_rel, output_rel,
6735 : : (FinalPathExtraData *) extra);
6736 : 502 : break;
1839 efujita@postgresql.o 6737 :UBC 0 : default:
6738 [ # # ]: 0 : elog(ERROR, "unexpected upper relation: %d", (int) stage);
6739 : : break;
6740 : : }
6741 : : }
6742 : :
6743 : : /*
6744 : : * add_foreign_grouping_paths
6745 : : * Add foreign path for grouping and/or aggregation.
6746 : : *
6747 : : * Given input_rel represents the underlying scan. The paths are added to the
6748 : : * given grouped_rel.
6749 : : */
6750 : : static void
2732 rhaas@postgresql.org 6751 :CBC 155 : add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
6752 : : RelOptInfo *grouped_rel,
6753 : : GroupPathExtraData *extra)
6754 : : {
6755 : 155 : Query *parse = root->parse;
6756 : 155 : PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
6757 : 155 : PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
6758 : : ForeignPath *grouppath;
6759 : : double rows;
6760 : : int width;
6761 : : Cost startup_cost;
6762 : : Cost total_cost;
6763 : :
6764 : : /* Nothing to be done, if there is no grouping or aggregation required. */
6765 [ + + + - : 155 : if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
- + ]
2732 rhaas@postgresql.org 6766 [ # # ]:UBC 0 : !root->hasHavingQual)
6767 : 0 : return;
6768 : :
2204 rhaas@postgresql.org 6769 [ + + - + ]:CBC 155 : Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE ||
6770 : : extra->patype == PARTITIONWISE_AGGREGATE_FULL);
6771 : :
6772 : : /* save the input_rel as outerrel in fpinfo */
2732 6773 : 155 : fpinfo->outerrel = input_rel;
6774 : :
6775 : : /*
6776 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
6777 : : * details from the input relation's fpinfo.
6778 : : */
6779 : 155 : fpinfo->table = ifpinfo->table;
6780 : 155 : fpinfo->server = ifpinfo->server;
6781 : 155 : fpinfo->user = ifpinfo->user;
2524 bruce@momjian.us 6782 : 155 : merge_fdw_options(fpinfo, ifpinfo, NULL);
6783 : :
6784 : : /*
6785 : : * Assess if it is safe to push down aggregation and grouping.
6786 : : *
6787 : : * Use HAVING qual from extra. In case of child partition, it will have
6788 : : * translated Vars.
6789 : : */
2204 rhaas@postgresql.org 6790 [ + + ]: 155 : if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual))
2732 6791 : 34 : return;
6792 : :
6793 : : /*
6794 : : * Compute the selectivity and cost of the local_conds, so we don't have
6795 : : * to do it over again for each path. (Currently we create just a single
6796 : : * path here, but in future it would be possible that we build more paths
6797 : : * such as pre-sorted paths as in postgresGetForeignPaths and
6798 : : * postgresGetForeignJoinPaths.) The best we can do for these conditions
6799 : : * is to estimate selectivity on the basis of local statistics.
6800 : : */
1958 efujita@postgresql.o 6801 : 121 : fpinfo->local_conds_sel = clauselist_selectivity(root,
6802 : : fpinfo->local_conds,
6803 : : 0,
6804 : : JOIN_INNER,
6805 : : NULL);
6806 : :
6807 : 121 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
6808 : :
6809 : : /* Estimate the cost of push down */
1839 6810 : 121 : estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL,
6811 : : &rows, &width, &startup_cost, &total_cost);
6812 : :
6813 : : /* Now update this information in the fpinfo */
2732 rhaas@postgresql.org 6814 : 121 : fpinfo->rows = rows;
6815 : 121 : fpinfo->width = width;
6816 : 121 : fpinfo->startup_cost = startup_cost;
6817 : 121 : fpinfo->total_cost = total_cost;
6818 : :
6819 : : /* Create and add foreign path to the grouping relation. */
1893 tgl@sss.pgh.pa.us 6820 : 121 : grouppath = create_foreign_upper_path(root,
6821 : : grouped_rel,
6822 : 121 : grouped_rel->reltarget,
6823 : : rows,
6824 : : startup_cost,
6825 : : total_cost,
6826 : : NIL, /* no pathkeys */
6827 : : NULL,
6828 : : NIL, /* no fdw_restrictinfo list */
6829 : : NIL); /* no fdw_private */
6830 : :
6831 : : /* Add generated path into grouped_rel by add_path(). */
2732 rhaas@postgresql.org 6832 : 121 : add_path(grouped_rel, (Path *) grouppath);
6833 : : }
6834 : :
6835 : : /*
6836 : : * add_foreign_ordered_paths
6837 : : * Add foreign paths for performing the final sort remotely.
6838 : : *
6839 : : * Given input_rel contains the source-data Paths. The paths are added to the
6840 : : * given ordered_rel.
6841 : : */
6842 : : static void
1839 efujita@postgresql.o 6843 : 146 : add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel,
6844 : : RelOptInfo *ordered_rel)
6845 : : {
6846 : 146 : Query *parse = root->parse;
6847 : 146 : PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
6848 : 146 : PgFdwRelationInfo *fpinfo = ordered_rel->fdw_private;
6849 : : PgFdwPathExtraData *fpextra;
6850 : : double rows;
6851 : : int width;
6852 : : Cost startup_cost;
6853 : : Cost total_cost;
6854 : : List *fdw_private;
6855 : : ForeignPath *ordered_path;
6856 : : ListCell *lc;
6857 : :
6858 : : /* Shouldn't get here unless the query has ORDER BY */
6859 [ - + ]: 146 : Assert(parse->sortClause);
6860 : :
6861 : : /* We don't support cases where there are any SRFs in the targetlist */
6862 [ - + ]: 146 : if (parse->hasTargetSRFs)
1839 efujita@postgresql.o 6863 :UBC 0 : return;
6864 : :
6865 : : /* Save the input_rel as outerrel in fpinfo */
1839 efujita@postgresql.o 6866 :CBC 146 : fpinfo->outerrel = input_rel;
6867 : :
6868 : : /*
6869 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
6870 : : * details from the input relation's fpinfo.
6871 : : */
6872 : 146 : fpinfo->table = ifpinfo->table;
6873 : 146 : fpinfo->server = ifpinfo->server;
6874 : 146 : fpinfo->user = ifpinfo->user;
6875 : 146 : merge_fdw_options(fpinfo, ifpinfo, NULL);
6876 : :
6877 : : /*
6878 : : * If the input_rel is a base or join relation, we would already have
6879 : : * considered pushing down the final sort to the remote server when
6880 : : * creating pre-sorted foreign paths for that relation, because the
6881 : : * query_pathkeys is set to the root->sort_pathkeys in that case (see
6882 : : * standard_qp_callback()).
6883 : : */
6884 [ + + ]: 146 : if (input_rel->reloptkind == RELOPT_BASEREL ||
6885 [ + + ]: 107 : input_rel->reloptkind == RELOPT_JOINREL)
6886 : : {
6887 [ - + ]: 100 : Assert(root->query_pathkeys == root->sort_pathkeys);
6888 : :
6889 : : /* Safe to push down if the query_pathkeys is safe to push down */
6890 : 100 : fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe;
6891 : :
6892 : 100 : return;
6893 : : }
6894 : :
6895 : : /* The input_rel should be a grouping relation */
6896 [ + - - + ]: 46 : Assert(input_rel->reloptkind == RELOPT_UPPER_REL &&
6897 : : ifpinfo->stage == UPPERREL_GROUP_AGG);
6898 : :
6899 : : /*
6900 : : * We try to create a path below by extending a simple foreign path for
6901 : : * the underlying grouping relation to perform the final sort remotely,
6902 : : * which is stored into the fdw_private list of the resulting path.
6903 : : */
6904 : :
6905 : : /* Assess if it is safe to push down the final sort */
6906 [ + + + + : 94 : foreach(lc, root->sort_pathkeys)
+ + ]
6907 : : {
6908 : 52 : PathKey *pathkey = (PathKey *) lfirst(lc);
6909 : 52 : EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
6910 : :
6911 : : /*
6912 : : * is_foreign_expr would detect volatile expressions as well, but
6913 : : * checking ec_has_volatile here saves some cycles.
6914 : : */
6915 [ + + ]: 52 : if (pathkey_ec->ec_has_volatile)
6916 : 4 : return;
6917 : :
6918 : : /*
6919 : : * Can't push down the sort if pathkey's opfamily is not shippable.
6920 : : */
745 tgl@sss.pgh.pa.us 6921 [ - + ]: 48 : if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId,
6922 : : fpinfo))
745 tgl@sss.pgh.pa.us 6923 :UBC 0 : return;
6924 : :
6925 : : /*
6926 : : * The EC must contain a shippable EM that is computed in input_rel's
6927 : : * reltarget, else we can't push down the sort.
6928 : : */
745 tgl@sss.pgh.pa.us 6929 [ - + ]:CBC 48 : if (find_em_for_rel_target(root,
6930 : : pathkey_ec,
6931 : : input_rel) == NULL)
1839 efujita@postgresql.o 6932 :UBC 0 : return;
6933 : : }
6934 : :
6935 : : /* Safe to push down */
1839 efujita@postgresql.o 6936 :CBC 42 : fpinfo->pushdown_safe = true;
6937 : :
6938 : : /* Construct PgFdwPathExtraData */
6939 : 42 : fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData));
6940 : 42 : fpextra->target = root->upper_targets[UPPERREL_ORDERED];
6941 : 42 : fpextra->has_final_sort = true;
6942 : :
6943 : : /* Estimate the costs of performing the final sort remotely */
6944 : 42 : estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra,
6945 : : &rows, &width, &startup_cost, &total_cost);
6946 : :
6947 : : /*
6948 : : * Build the fdw_private list that will be used by postgresGetForeignPlan.
6949 : : * Items in the list must match order in enum FdwPathPrivateIndex.
6950 : : */
821 peter@eisentraut.org 6951 : 42 : fdw_private = list_make2(makeBoolean(true), makeBoolean(false));
6952 : :
6953 : : /* Create foreign ordering path */
1839 efujita@postgresql.o 6954 : 42 : ordered_path = create_foreign_upper_path(root,
6955 : : input_rel,
6956 : 42 : root->upper_targets[UPPERREL_ORDERED],
6957 : : rows,
6958 : : startup_cost,
6959 : : total_cost,
6960 : : root->sort_pathkeys,
6961 : : NULL, /* no extra plan */
6962 : : NIL, /* no fdw_restrictinfo
6963 : : * list */
6964 : : fdw_private);
6965 : :
6966 : : /* and add it to the ordered_rel */
6967 : 42 : add_path(ordered_rel, (Path *) ordered_path);
6968 : : }
6969 : :
6970 : : /*
6971 : : * add_foreign_final_paths
6972 : : * Add foreign paths for performing the final processing remotely.
6973 : : *
6974 : : * Given input_rel contains the source-data Paths. The paths are added to the
6975 : : * given final_rel.
6976 : : */
6977 : : static void
6978 : 502 : add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel,
6979 : : RelOptInfo *final_rel,
6980 : : FinalPathExtraData *extra)
6981 : : {
6982 : 502 : Query *parse = root->parse;
6983 : 502 : PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private;
6984 : 502 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) final_rel->fdw_private;
6985 : 502 : bool has_final_sort = false;
6986 : 502 : List *pathkeys = NIL;
6987 : : PgFdwPathExtraData *fpextra;
6988 : 502 : bool save_use_remote_estimate = false;
6989 : : double rows;
6990 : : int width;
6991 : : Cost startup_cost;
6992 : : Cost total_cost;
6993 : : List *fdw_private;
6994 : : ForeignPath *final_path;
6995 : :
6996 : : /*
6997 : : * Currently, we only support this for SELECT commands
6998 : : */
6999 [ + + ]: 502 : if (parse->commandType != CMD_SELECT)
7000 : 384 : return;
7001 : :
7002 : : /*
7003 : : * No work if there is no FOR UPDATE/SHARE clause and if there is no need
7004 : : * to add a LIMIT node
7005 : : */
7006 [ + + + + ]: 395 : if (!parse->rowMarks && !extra->limit_needed)
7007 : 265 : return;
7008 : :
7009 : : /* We don't support cases where there are any SRFs in the targetlist */
7010 [ - + ]: 130 : if (parse->hasTargetSRFs)
1839 efujita@postgresql.o 7011 :UBC 0 : return;
7012 : :
7013 : : /* Save the input_rel as outerrel in fpinfo */
1839 efujita@postgresql.o 7014 :CBC 130 : fpinfo->outerrel = input_rel;
7015 : :
7016 : : /*
7017 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
7018 : : * details from the input relation's fpinfo.
7019 : : */
7020 : 130 : fpinfo->table = ifpinfo->table;
7021 : 130 : fpinfo->server = ifpinfo->server;
7022 : 130 : fpinfo->user = ifpinfo->user;
7023 : 130 : merge_fdw_options(fpinfo, ifpinfo, NULL);
7024 : :
7025 : : /*
7026 : : * If there is no need to add a LIMIT node, there might be a ForeignPath
7027 : : * in the input_rel's pathlist that implements all behavior of the query.
7028 : : * Note: we would already have accounted for the query's FOR UPDATE/SHARE
7029 : : * (if any) before we get here.
7030 : : */
7031 [ + + ]: 130 : if (!extra->limit_needed)
7032 : : {
7033 : : ListCell *lc;
7034 : :
7035 [ - + ]: 4 : Assert(parse->rowMarks);
7036 : :
7037 : : /*
7038 : : * Grouping and aggregation are not supported with FOR UPDATE/SHARE,
7039 : : * so the input_rel should be a base, join, or ordered relation; and
7040 : : * if it's an ordered relation, its input relation should be a base or
7041 : : * join relation.
7042 : : */
7043 [ - + - - : 4 : Assert(input_rel->reloptkind == RELOPT_BASEREL ||
- - - - -
- - - ]
7044 : : input_rel->reloptkind == RELOPT_JOINREL ||
7045 : : (input_rel->reloptkind == RELOPT_UPPER_REL &&
7046 : : ifpinfo->stage == UPPERREL_ORDERED &&
7047 : : (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL ||
7048 : : ifpinfo->outerrel->reloptkind == RELOPT_JOINREL)));
7049 : :
7050 [ + - + - : 4 : foreach(lc, input_rel->pathlist)
+ - ]
7051 : : {
7052 : 4 : Path *path = (Path *) lfirst(lc);
7053 : :
7054 : : /*
7055 : : * apply_scanjoin_target_to_paths() uses create_projection_path()
7056 : : * to adjust each of its input paths if needed, whereas
7057 : : * create_ordered_paths() uses apply_projection_to_path() to do
7058 : : * that. So the former might have put a ProjectionPath on top of
7059 : : * the ForeignPath; look through ProjectionPath and see if the
7060 : : * path underneath it is ForeignPath.
7061 : : */
7062 [ - + ]: 4 : if (IsA(path, ForeignPath) ||
1839 efujita@postgresql.o 7063 [ # # ]:UBC 0 : (IsA(path, ProjectionPath) &&
7064 [ # # ]: 0 : IsA(((ProjectionPath *) path)->subpath, ForeignPath)))
7065 : : {
7066 : : /*
7067 : : * Create foreign final path; this gets rid of a
7068 : : * no-longer-needed outer plan (if any), which makes the
7069 : : * EXPLAIN output look cleaner
7070 : : */
1839 efujita@postgresql.o 7071 :CBC 4 : final_path = create_foreign_upper_path(root,
7072 : : path->parent,
7073 : : path->pathtarget,
7074 : : path->rows,
7075 : : path->startup_cost,
7076 : : path->total_cost,
7077 : : path->pathkeys,
7078 : : NULL, /* no extra plan */
7079 : : NIL, /* no fdw_restrictinfo
7080 : : * list */
7081 : : NIL); /* no fdw_private */
7082 : :
7083 : : /* and add it to the final_rel */
7084 : 4 : add_path(final_rel, (Path *) final_path);
7085 : :
7086 : : /* Safe to push down */
7087 : 4 : fpinfo->pushdown_safe = true;
7088 : :
7089 : 4 : return;
7090 : : }
7091 : : }
7092 : :
7093 : : /*
7094 : : * If we get here it means no ForeignPaths; since we would already
7095 : : * have considered pushing down all operations for the query to the
7096 : : * remote server, give up on it.
7097 : : */
1839 efujita@postgresql.o 7098 :UBC 0 : return;
7099 : : }
7100 : :
1839 efujita@postgresql.o 7101 [ - + ]:CBC 126 : Assert(extra->limit_needed);
7102 : :
7103 : : /*
7104 : : * If the input_rel is an ordered relation, replace the input_rel with its
7105 : : * input relation
7106 : : */
7107 [ + + ]: 126 : if (input_rel->reloptkind == RELOPT_UPPER_REL &&
7108 [ + - ]: 70 : ifpinfo->stage == UPPERREL_ORDERED)
7109 : : {
7110 : 70 : input_rel = ifpinfo->outerrel;
7111 : 70 : ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private;
7112 : 70 : has_final_sort = true;
7113 : 70 : pathkeys = root->sort_pathkeys;
7114 : : }
7115 : :
7116 : : /* The input_rel should be a base, join, or grouping relation */
7117 [ + + + + : 126 : Assert(input_rel->reloptkind == RELOPT_BASEREL ||
+ - - + ]
7118 : : input_rel->reloptkind == RELOPT_JOINREL ||
7119 : : (input_rel->reloptkind == RELOPT_UPPER_REL &&
7120 : : ifpinfo->stage == UPPERREL_GROUP_AGG));
7121 : :
7122 : : /*
7123 : : * We try to create a path below by extending a simple foreign path for
7124 : : * the underlying base, join, or grouping relation to perform the final
7125 : : * sort (if has_final_sort) and the LIMIT restriction remotely, which is
7126 : : * stored into the fdw_private list of the resulting path. (We
7127 : : * re-estimate the costs of sorting the underlying relation, if
7128 : : * has_final_sort.)
7129 : : */
7130 : :
7131 : : /*
7132 : : * Assess if it is safe to push down the LIMIT and OFFSET to the remote
7133 : : * server
7134 : : */
7135 : :
7136 : : /*
7137 : : * If the underlying relation has any local conditions, the LIMIT/OFFSET
7138 : : * cannot be pushed down.
7139 : : */
7140 [ + + ]: 126 : if (ifpinfo->local_conds)
7141 : 8 : return;
7142 : :
7143 : : /*
7144 : : * Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are
7145 : : * not safe to remote.
7146 : : */
7147 [ + - ]: 118 : if (!is_foreign_expr(root, input_rel, (Expr *) parse->limitOffset) ||
7148 [ - + ]: 118 : !is_foreign_expr(root, input_rel, (Expr *) parse->limitCount))
1839 efujita@postgresql.o 7149 :UBC 0 : return;
7150 : :
7151 : : /* Safe to push down */
1839 efujita@postgresql.o 7152 :CBC 118 : fpinfo->pushdown_safe = true;
7153 : :
7154 : : /* Construct PgFdwPathExtraData */
7155 : 118 : fpextra = (PgFdwPathExtraData *) palloc0(sizeof(PgFdwPathExtraData));
7156 : 118 : fpextra->target = root->upper_targets[UPPERREL_FINAL];
7157 : 118 : fpextra->has_final_sort = has_final_sort;
7158 : 118 : fpextra->has_limit = extra->limit_needed;
7159 : 118 : fpextra->limit_tuples = extra->limit_tuples;
7160 : 118 : fpextra->count_est = extra->count_est;
7161 : 118 : fpextra->offset_est = extra->offset_est;
7162 : :
7163 : : /*
7164 : : * Estimate the costs of performing the final sort and the LIMIT
7165 : : * restriction remotely. If has_final_sort is false, we wouldn't need to
7166 : : * execute EXPLAIN anymore if use_remote_estimate, since the costs can be
7167 : : * roughly estimated using the costs we already have for the underlying
7168 : : * relation, in the same way as when use_remote_estimate is false. Since
7169 : : * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to
7170 : : * false in that case.
7171 : : */
7172 [ + + ]: 118 : if (!fpextra->has_final_sort)
7173 : : {
7174 : 53 : save_use_remote_estimate = ifpinfo->use_remote_estimate;
7175 : 53 : ifpinfo->use_remote_estimate = false;
7176 : : }
7177 : 118 : estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra,
7178 : : &rows, &width, &startup_cost, &total_cost);
7179 [ + + ]: 118 : if (!fpextra->has_final_sort)
7180 : 53 : ifpinfo->use_remote_estimate = save_use_remote_estimate;
7181 : :
7182 : : /*
7183 : : * Build the fdw_private list that will be used by postgresGetForeignPlan.
7184 : : * Items in the list must match order in enum FdwPathPrivateIndex.
7185 : : */
821 peter@eisentraut.org 7186 : 118 : fdw_private = list_make2(makeBoolean(has_final_sort),
7187 : : makeBoolean(extra->limit_needed));
7188 : :
7189 : : /*
7190 : : * Create foreign final path; this gets rid of a no-longer-needed outer
7191 : : * plan (if any), which makes the EXPLAIN output look cleaner
7192 : : */
1839 efujita@postgresql.o 7193 : 118 : final_path = create_foreign_upper_path(root,
7194 : : input_rel,
7195 : 118 : root->upper_targets[UPPERREL_FINAL],
7196 : : rows,
7197 : : startup_cost,
7198 : : total_cost,
7199 : : pathkeys,
7200 : : NULL, /* no extra plan */
7201 : : NIL, /* no fdw_restrictinfo list */
7202 : : fdw_private);
7203 : :
7204 : : /* and add it to the final_rel */
7205 : 118 : add_path(final_rel, (Path *) final_path);
7206 : : }
7207 : :
7208 : : /*
7209 : : * postgresIsForeignPathAsyncCapable
7210 : : * Check whether a given ForeignPath node is async-capable.
7211 : : */
7212 : : static bool
1110 7213 : 233 : postgresIsForeignPathAsyncCapable(ForeignPath *path)
7214 : : {
7215 : 233 : RelOptInfo *rel = ((Path *) path)->parent;
7216 : 233 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
7217 : :
7218 : 233 : return fpinfo->async_capable;
7219 : : }
7220 : :
7221 : : /*
7222 : : * postgresForeignAsyncRequest
7223 : : * Asynchronously request next tuple from a foreign PostgreSQL table.
7224 : : */
7225 : : static void
7226 : 6175 : postgresForeignAsyncRequest(AsyncRequest *areq)
7227 : : {
7228 : 6175 : produce_tuple_asynchronously(areq, true);
7229 : 6175 : }
7230 : :
7231 : : /*
7232 : : * postgresForeignAsyncConfigureWait
7233 : : * Configure a file descriptor event for which we wish to wait.
7234 : : */
7235 : : static void
7236 : 283 : postgresForeignAsyncConfigureWait(AsyncRequest *areq)
7237 : : {
7238 : 283 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
7239 : 283 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
7240 : 283 : AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq;
7241 : 283 : AppendState *requestor = (AppendState *) areq->requestor;
7242 : 283 : WaitEventSet *set = requestor->as_eventset;
7243 : :
7244 : : /* This should not be called unless callback_pending */
7245 [ - + ]: 283 : Assert(areq->callback_pending);
7246 : :
7247 : : /*
7248 : : * If process_pending_request() has been invoked on the given request
7249 : : * before we get here, we might have some tuples already; in which case
7250 : : * complete the request
7251 : : */
989 7252 [ + + ]: 283 : if (fsstate->next_tuple < fsstate->num_tuples)
7253 : : {
7254 : 5 : complete_pending_request(areq);
7255 [ + + ]: 5 : if (areq->request_complete)
7256 : 3 : return;
7257 [ - + ]: 2 : Assert(areq->callback_pending);
7258 : : }
7259 : :
7260 : : /* We must have run out of tuples */
7261 [ - + ]: 280 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
7262 : :
7263 : : /* The core code would have registered postmaster death event */
1110 7264 [ - + ]: 280 : Assert(GetNumRegisteredWaitEvents(set) >= 1);
7265 : :
7266 : : /* Begin an asynchronous data fetch if not already done */
7267 [ + + ]: 280 : if (!pendingAreq)
7268 : 4 : fetch_more_data_begin(areq);
7269 [ + + ]: 276 : else if (pendingAreq->requestor != areq->requestor)
7270 : : {
7271 : : /*
7272 : : * This is the case when the in-process request was made by another
7273 : : * Append. Note that it might be useless to process the request made
7274 : : * by that Append, because the query might not need tuples from that
7275 : : * Append anymore; so we avoid processing it to begin a fetch for the
7276 : : * given request if possible. If there are any child subplans of the
7277 : : * same parent that are ready for new requests, skip the given
7278 : : * request. Likewise, if there are any configured events other than
7279 : : * the postmaster death event, skip it. Otherwise, process the
7280 : : * in-process request, then begin a fetch to configure the event
7281 : : * below, because we might otherwise end up with no configured events
7282 : : * other than the postmaster death event.
7283 : : */
989 7284 [ - + ]: 8 : if (!bms_is_empty(requestor->as_needrequest))
989 efujita@postgresql.o 7285 :UBC 0 : return;
1110 efujita@postgresql.o 7286 [ + + ]:CBC 8 : if (GetNumRegisteredWaitEvents(set) > 1)
7287 : 6 : return;
7288 : 2 : process_pending_request(pendingAreq);
7289 : 2 : fetch_more_data_begin(areq);
7290 : : }
7291 [ + + ]: 268 : else if (pendingAreq->requestee != areq->requestee)
7292 : : {
7293 : : /*
7294 : : * This is the case when the in-process request was made by the same
7295 : : * parent but for a different child. Since we configure only the
7296 : : * event for the request made for that child, skip the given request.
7297 : : */
7298 : 10 : return;
7299 : : }
7300 : : else
7301 [ - + ]: 258 : Assert(pendingAreq == areq);
7302 : :
7303 : 263 : AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
7304 : : NULL, areq);
7305 : : }
7306 : :
7307 : : /*
7308 : : * postgresForeignAsyncNotify
7309 : : * Fetch some more tuples from a file descriptor that becomes ready,
7310 : : * requesting next tuple.
7311 : : */
7312 : : static void
7313 : 148 : postgresForeignAsyncNotify(AsyncRequest *areq)
7314 : : {
7315 : 148 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
7316 : 148 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
7317 : :
7318 : : /* The core code would have initialized the callback_pending flag */
7319 [ - + ]: 148 : Assert(!areq->callback_pending);
7320 : :
7321 : : /*
7322 : : * If process_pending_request() has been invoked on the given request
7323 : : * before we get here, we might have some tuples already; in which case
7324 : : * produce the next tuple
7325 : : */
986 7326 [ - + ]: 148 : if (fsstate->next_tuple < fsstate->num_tuples)
7327 : : {
986 efujita@postgresql.o 7328 :UBC 0 : produce_tuple_asynchronously(areq, true);
7329 : 0 : return;
7330 : : }
7331 : :
7332 : : /* We must have run out of tuples */
986 efujita@postgresql.o 7333 [ - + ]:CBC 148 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
7334 : :
7335 : : /* The request should be currently in-process */
7336 [ - + ]: 148 : Assert(fsstate->conn_state->pendingAreq == areq);
7337 : :
7338 : : /* On error, report the original query, not the FETCH. */
1110 7339 [ - + ]: 148 : if (!PQconsumeInput(fsstate->conn))
1110 efujita@postgresql.o 7340 :UBC 0 : pgfdw_report_error(ERROR, NULL, fsstate->conn, false, fsstate->query);
7341 : :
1110 efujita@postgresql.o 7342 :CBC 148 : fetch_more_data(node);
7343 : :
7344 : 148 : produce_tuple_asynchronously(areq, true);
7345 : : }
7346 : :
7347 : : /*
7348 : : * Asynchronously produce next tuple from a foreign PostgreSQL table.
7349 : : */
7350 : : static void
7351 : 6328 : produce_tuple_asynchronously(AsyncRequest *areq, bool fetch)
7352 : : {
7353 : 6328 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
7354 : 6328 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
7355 : 6328 : AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq;
7356 : : TupleTableSlot *result;
7357 : :
7358 : : /* This should not be called if the request is currently in-process */
7359 [ - + ]: 6328 : Assert(areq != pendingAreq);
7360 : :
7361 : : /* Fetch some more tuples, if we've run out */
7362 [ + + ]: 6328 : if (fsstate->next_tuple >= fsstate->num_tuples)
7363 : : {
7364 : : /* No point in another fetch if we already detected EOF, though */
7365 [ + + ]: 189 : if (!fsstate->eof_reached)
7366 : : {
7367 : : /* Mark the request as pending for a callback */
7368 : 129 : ExecAsyncRequestPending(areq);
7369 : : /* Begin another fetch if requested and if no pending request */
7370 [ + - + + ]: 129 : if (fetch && !pendingAreq)
7371 : 124 : fetch_more_data_begin(areq);
7372 : : }
7373 : : else
7374 : : {
7375 : : /* There's nothing more to do; just return a NULL pointer */
7376 : 60 : result = NULL;
7377 : : /* Mark the request as complete */
7378 : 60 : ExecAsyncRequestDone(areq, result);
7379 : : }
7380 : 189 : return;
7381 : : }
7382 : :
7383 : : /* Get a tuple from the ForeignScan node */
1068 7384 : 6139 : result = areq->requestee->ExecProcNodeReal(areq->requestee);
1110 7385 [ + - + + ]: 6139 : if (!TupIsNull(result))
7386 : : {
7387 : : /* Mark the request as complete */
7388 : 6107 : ExecAsyncRequestDone(areq, result);
7389 : 6107 : return;
7390 : : }
7391 : :
7392 : : /* We must have run out of tuples */
7393 [ - + ]: 32 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
7394 : :
7395 : : /* Fetch some more tuples, if we've not detected EOF yet */
7396 [ + - ]: 32 : if (!fsstate->eof_reached)
7397 : : {
7398 : : /* Mark the request as pending for a callback */
7399 : 32 : ExecAsyncRequestPending(areq);
7400 : : /* Begin another fetch if requested and if no pending request */
7401 [ + + + - ]: 32 : if (fetch && !pendingAreq)
7402 : 30 : fetch_more_data_begin(areq);
7403 : : }
7404 : : else
7405 : : {
7406 : : /* There's nothing more to do; just return a NULL pointer */
1110 efujita@postgresql.o 7407 :UBC 0 : result = NULL;
7408 : : /* Mark the request as complete */
7409 : 0 : ExecAsyncRequestDone(areq, result);
7410 : : }
7411 : : }
7412 : :
7413 : : /*
7414 : : * Begin an asynchronous data fetch.
7415 : : *
7416 : : * Note: this function assumes there is no currently-in-progress asynchronous
7417 : : * data fetch.
7418 : : *
7419 : : * Note: fetch_more_data must be called to fetch the result.
7420 : : */
7421 : : static void
1110 efujita@postgresql.o 7422 :CBC 160 : fetch_more_data_begin(AsyncRequest *areq)
7423 : : {
7424 : 160 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
7425 : 160 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
7426 : : char sql[64];
7427 : :
7428 [ - + ]: 160 : Assert(!fsstate->conn_state->pendingAreq);
7429 : :
7430 : : /* Create the cursor synchronously. */
7431 [ + + ]: 160 : if (!fsstate->cursor_exists)
7432 : 50 : create_cursor(node);
7433 : :
7434 : : /* We will send this query, but not wait for the response. */
7435 : 159 : snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
7436 : : fsstate->fetch_size, fsstate->cursor_number);
7437 : :
633 fujii@postgresql.org 7438 [ - + ]: 159 : if (!PQsendQuery(fsstate->conn, sql))
1110 efujita@postgresql.o 7439 :UBC 0 : pgfdw_report_error(ERROR, NULL, fsstate->conn, false, fsstate->query);
7440 : :
7441 : : /* Remember that the request is in process */
1110 efujita@postgresql.o 7442 :CBC 159 : fsstate->conn_state->pendingAreq = areq;
7443 : 159 : }
7444 : :
7445 : : /*
7446 : : * Process a pending asynchronous request.
7447 : : */
7448 : : void
7449 : 9 : process_pending_request(AsyncRequest *areq)
7450 : : {
7451 : 9 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
866 dgustafsson@postgres 7452 : 9 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
7453 : :
7454 : : /* The request would have been pending for a callback */
989 efujita@postgresql.o 7455 [ - + ]: 9 : Assert(areq->callback_pending);
7456 : :
7457 : : /* The request should be currently in-process */
1110 7458 [ - + ]: 9 : Assert(fsstate->conn_state->pendingAreq == areq);
7459 : :
989 7460 : 9 : fetch_more_data(node);
7461 : :
7462 : : /*
7463 : : * If we didn't get any tuples, must be end of data; complete the request
7464 : : * now. Otherwise, we postpone completing the request until we are called
7465 : : * from postgresForeignAsyncConfigureWait()/postgresForeignAsyncNotify().
7466 : : */
7467 [ - + ]: 9 : if (fsstate->next_tuple >= fsstate->num_tuples)
7468 : : {
7469 : : /* Unlike AsyncNotify, we unset callback_pending ourselves */
989 efujita@postgresql.o 7470 :UBC 0 : areq->callback_pending = false;
7471 : : /* Mark the request as complete */
7472 : 0 : ExecAsyncRequestDone(areq, NULL);
7473 : : /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */
7474 : 0 : ExecAsyncResponse(areq);
7475 : : }
989 efujita@postgresql.o 7476 :CBC 9 : }
7477 : :
7478 : : /*
7479 : : * Complete a pending asynchronous request.
7480 : : */
7481 : : static void
7482 : 5 : complete_pending_request(AsyncRequest *areq)
7483 : : {
7484 : : /* The request would have been pending for a callback */
1110 7485 [ - + ]: 5 : Assert(areq->callback_pending);
7486 : :
7487 : : /* Unlike AsyncNotify, we unset callback_pending ourselves */
7488 : 5 : areq->callback_pending = false;
7489 : :
7490 : : /* We begin a fetch afterwards if necessary; don't fetch */
7491 : 5 : produce_tuple_asynchronously(areq, false);
7492 : :
7493 : : /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */
7494 : 5 : ExecAsyncResponse(areq);
7495 : :
7496 : : /* Also, we do instrumentation ourselves, if required */
1068 7497 [ + + ]: 5 : if (areq->requestee->instrument)
1068 efujita@postgresql.o 7498 :UBC 0 : InstrUpdateTupleCount(areq->requestee->instrument,
1068 efujita@postgresql.o 7499 [ + - - + ]:CBC 1 : TupIsNull(areq->result) ? 0.0 : 1.0);
1110 7500 : 5 : }
7501 : :
7502 : : /*
7503 : : * Create a tuple from the specified row of the PGresult.
7504 : : *
7505 : : * rel is the local representation of the foreign table, attinmeta is
7506 : : * conversion data for the rel's tupdesc, and retrieved_attrs is an
7507 : : * integer list of the table column numbers present in the PGresult.
7508 : : * fsstate is the ForeignScan plan node's execution state.
7509 : : * temp_context is a working context that can be reset after each tuple.
7510 : : *
7511 : : * Note: either rel or fsstate, but not both, can be NULL. rel is NULL
7512 : : * if we're processing a remote join, while fsstate is NULL in a non-query
7513 : : * context such as ANALYZE, or if we're processing a non-scan query node.
7514 : : */
7515 : : static HeapTuple
4070 tgl@sss.pgh.pa.us 7516 : 85268 : make_tuple_from_result_row(PGresult *res,
7517 : : int row,
7518 : : Relation rel,
7519 : : AttInMetadata *attinmeta,
7520 : : List *retrieved_attrs,
7521 : : ForeignScanState *fsstate,
7522 : : MemoryContext temp_context)
7523 : : {
7524 : : HeapTuple tuple;
7525 : : TupleDesc tupdesc;
7526 : : Datum *values;
7527 : : bool *nulls;
4053 7528 : 85268 : ItemPointer ctid = NULL;
7529 : : ConversionLocation errpos;
7530 : : ErrorContextCallback errcallback;
7531 : : MemoryContext oldcontext;
7532 : : ListCell *lc;
7533 : : int j;
7534 : :
4070 7535 [ - + ]: 85268 : Assert(row < PQntuples(res));
7536 : :
7537 : : /*
7538 : : * Do the following work in a temp context that we reset after each tuple.
7539 : : * This cleans up not only the data we have direct access to, but any
7540 : : * cruft the I/O functions might leak.
7541 : : */
7542 : 85268 : oldcontext = MemoryContextSwitchTo(temp_context);
7543 : :
7544 : : /*
7545 : : * Get the tuple descriptor for the row. Use the rel's tupdesc if rel is
7546 : : * provided, otherwise look to the scan node's ScanTupleSlot.
7547 : : */
2987 rhaas@postgresql.org 7548 [ + + ]: 85268 : if (rel)
7549 : 49365 : tupdesc = RelationGetDescr(rel);
7550 : : else
7551 : : {
7552 [ - + ]: 35903 : Assert(fsstate);
2258 7553 : 35903 : tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
7554 : : }
7555 : :
4041 tgl@sss.pgh.pa.us 7556 : 85268 : values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
4070 7557 : 85268 : nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
7558 : : /* Initialize to nulls for any columns not present in result */
4041 7559 : 85268 : memset(nulls, true, tupdesc->natts * sizeof(bool));
7560 : :
7561 : : /*
7562 : : * Set up and install callback to report where conversion error occurs.
7563 : : */
4070 7564 : 85268 : errpos.cur_attno = 0;
921 7565 : 85268 : errpos.rel = rel;
2987 rhaas@postgresql.org 7566 : 85268 : errpos.fsstate = fsstate;
4070 tgl@sss.pgh.pa.us 7567 : 85268 : errcallback.callback = conversion_error_callback;
7568 : 85268 : errcallback.arg = (void *) &errpos;
7569 : 85268 : errcallback.previous = error_context_stack;
7570 : 85268 : error_context_stack = &errcallback;
7571 : :
7572 : : /*
7573 : : * i indexes columns in the relation, j indexes columns in the PGresult.
7574 : : */
4041 7575 : 85268 : j = 0;
7576 [ + + + + : 318183 : foreach(lc, retrieved_attrs)
+ + ]
7577 : : {
7578 : 232920 : int i = lfirst_int(lc);
7579 : : char *valstr;
7580 : :
7581 : : /* fetch next column's textual value */
4070 7582 [ + + ]: 232920 : if (PQgetisnull(res, row, j))
7583 : 683 : valstr = NULL;
7584 : : else
7585 : 232237 : valstr = PQgetvalue(res, row, j);
7586 : :
7587 : : /*
7588 : : * convert value to internal representation
7589 : : *
7590 : : * Note: we ignore system columns other than ctid and oid in result
7591 : : */
2952 rhaas@postgresql.org 7592 : 232920 : errpos.cur_attno = i;
4041 tgl@sss.pgh.pa.us 7593 [ + + ]: 232920 : if (i > 0)
7594 : : {
7595 : : /* ordinary column */
7596 [ - + ]: 231819 : Assert(i <= tupdesc->natts);
7597 : 231819 : nulls[i - 1] = (valstr == NULL);
7598 : : /* Apply the input function even to nulls, to support domains */
7599 : 231814 : values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
7600 : : valstr,
7601 : 231819 : attinmeta->attioparams[i - 1],
7602 : 231819 : attinmeta->atttypmods[i - 1]);
7603 : : }
7604 [ + - ]: 1101 : else if (i == SelfItemPointerAttributeNumber)
7605 : : {
7606 : : /* ctid */
7607 [ + - ]: 1101 : if (valstr != NULL)
7608 : : {
7609 : : Datum datum;
7610 : :
7611 : 1101 : datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
7612 : 1101 : ctid = (ItemPointer) DatumGetPointer(datum);
7613 : : }
7614 : : }
2952 rhaas@postgresql.org 7615 : 232915 : errpos.cur_attno = 0;
7616 : :
4053 tgl@sss.pgh.pa.us 7617 : 232915 : j++;
7618 : : }
7619 : :
7620 : : /* Uninstall error context callback. */
4070 7621 : 85263 : error_context_stack = errcallback.previous;
7622 : :
7623 : : /*
7624 : : * Check we got the expected number of columns. Note: j == 0 and
7625 : : * PQnfields == 1 is expected, since deparse emits a NULL if no columns.
7626 : : */
4041 7627 [ + + - + ]: 85263 : if (j > 0 && j != PQnfields(res))
4070 tgl@sss.pgh.pa.us 7628 [ # # ]:UBC 0 : elog(ERROR, "remote query result does not match the foreign table");
7629 : :
7630 : : /*
7631 : : * Build the result tuple in caller's memory context.
7632 : : */
4070 tgl@sss.pgh.pa.us 7633 :CBC 85263 : MemoryContextSwitchTo(oldcontext);
7634 : :
7635 : 85263 : tuple = heap_form_tuple(tupdesc, values, nulls);
7636 : :
7637 : : /*
7638 : : * If we have a CTID to return, install it in both t_self and t_ctid.
7639 : : * t_self is the normal place, but if the tuple is converted to a
7640 : : * composite Datum, t_self will be lost; setting t_ctid allows CTID to be
7641 : : * preserved during EvalPlanQual re-evaluations (see ROW_MARK_COPY code).
7642 : : */
4053 7643 [ + + ]: 85263 : if (ctid)
3259 7644 : 1101 : tuple->t_self = tuple->t_data->t_ctid = *ctid;
7645 : :
7646 : : /*
7647 : : * Stomp on the xmin, xmax, and cmin fields from the tuple created by
7648 : : * heap_form_tuple. heap_form_tuple actually creates the tuple with
7649 : : * DatumTupleFields, not HeapTupleFields, but the executor expects
7650 : : * HeapTupleFields and will happily extract system columns on that
7651 : : * assumption. If we don't do this then, for example, the tuple length
7652 : : * ends up in the xmin field, which isn't what we want.
7653 : : */
2921 rhaas@postgresql.org 7654 : 85263 : HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
7655 : 85263 : HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId);
7656 [ - + ]: 85263 : HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId);
7657 : :
7658 : : /* Clean up */
4070 tgl@sss.pgh.pa.us 7659 : 85263 : MemoryContextReset(temp_context);
7660 : :
7661 : 85263 : return tuple;
7662 : : }
7663 : :
7664 : : /*
7665 : : * Callback function which is called when error occurs during column value
7666 : : * conversion. Print names of column and relation.
7667 : : *
7668 : : * Note that this function mustn't do any catalog lookups, since we are in
7669 : : * an already-failed transaction. Fortunately, we can get the needed info
7670 : : * from the relation or the query's rangetable instead.
7671 : : */
7672 : : static void
7673 : 5 : conversion_error_callback(void *arg)
7674 : : {
1013 7675 : 5 : ConversionLocation *errpos = (ConversionLocation *) arg;
921 7676 : 5 : Relation rel = errpos->rel;
1013 7677 : 5 : ForeignScanState *fsstate = errpos->fsstate;
2987 rhaas@postgresql.org 7678 : 5 : const char *attname = NULL;
7679 : 5 : const char *relname = NULL;
2844 7680 : 5 : bool is_wholerow = false;
7681 : :
7682 : : /*
7683 : : * If we're in a scan node, always use aliases from the rangetable, for
7684 : : * consistency between the simple-relation and remote-join cases. Look at
7685 : : * the relation's tupdesc only if we're not in a scan node.
7686 : : */
921 tgl@sss.pgh.pa.us 7687 [ + + ]: 5 : if (fsstate)
7688 : : {
7689 : : /* ForeignScan case */
7690 : 4 : ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan);
7691 : 4 : int varno = 0;
7692 : 4 : AttrNumber colno = 0;
7693 : :
7694 [ + + ]: 4 : if (fsplan->scan.scanrelid > 0)
7695 : : {
7696 : : /* error occurred in a scan against a foreign table */
7697 : 1 : varno = fsplan->scan.scanrelid;
7698 : 1 : colno = errpos->cur_attno;
7699 : : }
7700 : : else
7701 : : {
7702 : : /* error occurred in a scan against a foreign join */
7703 : : TargetEntry *tle;
7704 : :
7705 : 3 : tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
7706 : : errpos->cur_attno - 1);
7707 : :
7708 : : /*
7709 : : * Target list can have Vars and expressions. For Vars, we can
7710 : : * get some information, however for expressions we can't. Thus
7711 : : * for expressions, just show generic context message.
7712 : : */
7713 [ + + ]: 3 : if (IsA(tle->expr, Var))
7714 : : {
7715 : 2 : Var *var = (Var *) tle->expr;
7716 : :
7717 : 2 : varno = var->varno;
7718 : 2 : colno = var->varattno;
7719 : : }
7720 : : }
7721 : :
7722 [ + + ]: 4 : if (varno > 0)
7723 : : {
7724 : 3 : EState *estate = fsstate->ss.ps.state;
7725 : 3 : RangeTblEntry *rte = exec_rt_fetch(varno, estate);
7726 : :
7727 : 3 : relname = rte->eref->aliasname;
7728 : :
7729 [ + + ]: 3 : if (colno == 0)
7730 : 1 : is_wholerow = true;
7731 [ + - + - ]: 2 : else if (colno > 0 && colno <= list_length(rte->eref->colnames))
7732 : 2 : attname = strVal(list_nth(rte->eref->colnames, colno - 1));
921 tgl@sss.pgh.pa.us 7733 [ # # ]:UBC 0 : else if (colno == SelfItemPointerAttributeNumber)
7734 : 0 : attname = "ctid";
7735 : : }
7736 : : }
921 tgl@sss.pgh.pa.us 7737 [ + - ]:CBC 1 : else if (rel)
7738 : : {
7739 : : /* Non-ForeignScan case (we should always have a rel here) */
7740 : 1 : TupleDesc tupdesc = RelationGetDescr(rel);
7741 : :
7742 : 1 : relname = RelationGetRelationName(rel);
7743 [ + - + - ]: 1 : if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
7744 : 1 : {
7745 : 1 : Form_pg_attribute attr = TupleDescAttr(tupdesc,
7746 : : errpos->cur_attno - 1);
7747 : :
7748 : 1 : attname = NameStr(attr->attname);
7749 : : }
921 tgl@sss.pgh.pa.us 7750 [ # # ]:UBC 0 : else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
1013 7751 : 0 : attname = "ctid";
7752 : : }
7753 : :
1013 tgl@sss.pgh.pa.us 7754 [ + + + + ]:CBC 5 : if (relname && is_wholerow)
7755 : 1 : errcontext("whole-row reference to foreign table \"%s\"", relname);
7756 [ + + + - ]: 4 : else if (relname && attname)
7757 : 3 : errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
7758 : : else
7759 : 1 : errcontext("processing expression at position %d in select list",
7760 : 1 : errpos->cur_attno);
4070 7761 : 5 : }
7762 : :
7763 : : /*
7764 : : * Given an EquivalenceClass and a foreign relation, find an EC member
7765 : : * that can be used to sort the relation remotely according to a pathkey
7766 : : * using this EC.
7767 : : *
7768 : : * If there is more than one suitable candidate, return an arbitrary
7769 : : * one of them. If there is none, return NULL.
7770 : : *
7771 : : * This checks that the EC member expression uses only Vars from the given
7772 : : * rel and is shippable. Caller must separately verify that the pathkey's
7773 : : * ordering operator is shippable.
7774 : : */
7775 : : EquivalenceMember *
745 7776 : 1754 : find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
7777 : : {
7778 : : ListCell *lc;
7779 : :
131 akorotkov@postgresql 7780 :GNC 1754 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
7781 : :
745 tgl@sss.pgh.pa.us 7782 [ + - + + :CBC 3310 : foreach(lc, ec->ec_members)
+ + ]
7783 : : {
7784 : 3036 : EquivalenceMember *em = (EquivalenceMember *) lfirst(lc);
7785 : :
7786 : : /*
7787 : : * Note we require !bms_is_empty, else we'd accept constant
7788 : : * expressions which are not suitable for the purpose.
7789 : : */
7790 [ + + ]: 3036 : if (bms_is_subset(em->em_relids, rel->relids) &&
7791 [ + + + + ]: 3001 : !bms_is_empty(em->em_relids) &&
131 akorotkov@postgresql 7792 [ + + ]:GNC 2996 : bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) &&
745 tgl@sss.pgh.pa.us 7793 :CBC 1496 : is_foreign_expr(root, rel, em->em_expr))
7794 : 1480 : return em;
7795 : : }
7796 : :
7797 : 274 : return NULL;
7798 : : }
7799 : :
7800 : : /*
7801 : : * Find an EquivalenceClass member that is to be computed as a sort column
7802 : : * in the given rel's reltarget, and is shippable.
7803 : : *
7804 : : * If there is more than one suitable candidate, return an arbitrary
7805 : : * one of them. If there is none, return NULL.
7806 : : *
7807 : : * This checks that the EC member expression uses only Vars from the given
7808 : : * rel and is shippable. Caller must separately verify that the pathkey's
7809 : : * ordering operator is shippable.
7810 : : */
7811 : : EquivalenceMember *
7812 : 251 : find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec,
7813 : : RelOptInfo *rel)
7814 : : {
7815 : 251 : PathTarget *target = rel->reltarget;
7816 : : ListCell *lc1;
7817 : : int i;
7818 : :
1839 efujita@postgresql.o 7819 : 251 : i = 0;
7820 [ + - + - : 421 : foreach(lc1, target->exprs)
+ - ]
7821 : : {
7822 : 421 : Expr *expr = (Expr *) lfirst(lc1);
7823 [ + - ]: 421 : Index sgref = get_pathtarget_sortgroupref(target, i);
7824 : : ListCell *lc2;
7825 : :
7826 : : /* Ignore non-sort expressions */
7827 [ + + + + ]: 757 : if (sgref == 0 ||
7828 : 336 : get_sortgroupref_clause_noerr(sgref,
7829 : 336 : root->parse->sortClause) == NULL)
7830 : : {
7831 : 93 : i++;
7832 : 93 : continue;
7833 : : }
7834 : :
7835 : : /* We ignore binary-compatible relabeling on both ends */
7836 [ + - - + ]: 328 : while (expr && IsA(expr, RelabelType))
1839 efujita@postgresql.o 7837 :UBC 0 : expr = ((RelabelType *) expr)->arg;
7838 : :
7839 : : /* Locate an EquivalenceClass member matching this expr, if any */
1839 efujita@postgresql.o 7840 [ + - + + :CBC 409 : foreach(lc2, ec->ec_members)
+ + ]
7841 : : {
7842 : 332 : EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
7843 : : Expr *em_expr;
7844 : :
7845 : : /* Don't match constants */
7846 [ - + ]: 332 : if (em->em_is_const)
1839 efujita@postgresql.o 7847 :UBC 0 : continue;
7848 : :
7849 : : /* Ignore child members */
1839 efujita@postgresql.o 7850 [ - + ]:CBC 332 : if (em->em_is_child)
1839 efujita@postgresql.o 7851 :UBC 0 : continue;
7852 : :
7853 : : /* Match if same expression (after stripping relabel) */
1839 efujita@postgresql.o 7854 :CBC 332 : em_expr = em->em_expr;
7855 [ + - + + ]: 344 : while (em_expr && IsA(em_expr, RelabelType))
7856 : 12 : em_expr = ((RelabelType *) em_expr)->arg;
7857 : :
745 tgl@sss.pgh.pa.us 7858 [ + + ]: 332 : if (!equal(em_expr, expr))
7859 : 81 : continue;
7860 : :
7861 : : /* Check that expression (including relabels!) is shippable */
7862 [ + - ]: 251 : if (is_foreign_expr(root, rel, em->em_expr))
7863 : 251 : return em;
7864 : : }
7865 : :
1839 efujita@postgresql.o 7866 : 77 : i++;
7867 : : }
7868 : :
745 tgl@sss.pgh.pa.us 7869 :UBC 0 : return NULL;
7870 : : }
7871 : :
7872 : : /*
7873 : : * Determine batch size for a given foreign table. The option specified for
7874 : : * a table has precedence.
7875 : : */
7876 : : static int
1180 tomas.vondra@postgre 7877 :CBC 141 : get_batch_size_option(Relation rel)
7878 : : {
1068 tgl@sss.pgh.pa.us 7879 : 141 : Oid foreigntableid = RelationGetRelid(rel);
7880 : : ForeignTable *table;
7881 : : ForeignServer *server;
7882 : : List *options;
7883 : : ListCell *lc;
7884 : :
7885 : : /* we use 1 by default, which means "no batching" */
7886 : 141 : int batch_size = 1;
7887 : :
7888 : : /*
7889 : : * Load options for table and server. We append server options after table
7890 : : * options, because table options take precedence.
7891 : : */
1180 tomas.vondra@postgre 7892 : 141 : table = GetForeignTable(foreigntableid);
7893 : 141 : server = GetForeignServer(table->serverid);
7894 : :
7895 : 141 : options = NIL;
7896 : 141 : options = list_concat(options, table->options);
7897 : 141 : options = list_concat(options, server->options);
7898 : :
7899 : : /* See if either table or server specifies batch_size. */
7900 [ + - + + : 741 : foreach(lc, options)
+ + ]
7901 : : {
7902 : 633 : DefElem *def = (DefElem *) lfirst(lc);
7903 : :
7904 [ + + ]: 633 : if (strcmp(def->defname, "batch_size") == 0)
7905 : : {
1012 fujii@postgresql.org 7906 : 33 : (void) parse_int(defGetString(def), &batch_size, 0, NULL);
1180 tomas.vondra@postgre 7907 : 33 : break;
7908 : : }
7909 : : }
7910 : :
7911 : 141 : return batch_size;
7912 : : }
|