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-2026, 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_opfamily.h"
21 : : #include "commands/defrem.h"
22 : : #include "commands/explain_format.h"
23 : : #include "commands/explain_state.h"
24 : : #include "commands/vacuum.h"
25 : : #include "executor/execAsync.h"
26 : : #include "executor/instrument.h"
27 : : #include "foreign/fdwapi.h"
28 : : #include "funcapi.h"
29 : : #include "miscadmin.h"
30 : : #include "nodes/makefuncs.h"
31 : : #include "nodes/nodeFuncs.h"
32 : : #include "optimizer/appendinfo.h"
33 : : #include "optimizer/clauses.h"
34 : : #include "optimizer/cost.h"
35 : : #include "optimizer/inherit.h"
36 : : #include "optimizer/optimizer.h"
37 : : #include "optimizer/pathnode.h"
38 : : #include "optimizer/paths.h"
39 : : #include "optimizer/planmain.h"
40 : : #include "optimizer/prep.h"
41 : : #include "optimizer/restrictinfo.h"
42 : : #include "optimizer/tlist.h"
43 : : #include "parser/parsetree.h"
44 : : #include "pgstat.h"
45 : : #include "postgres_fdw.h"
46 : : #include "statistics/statistics.h"
47 : : #include "storage/latch.h"
48 : : #include "utils/builtins.h"
49 : : #include "utils/float.h"
50 : : #include "utils/fmgroids.h"
51 : : #include "utils/guc.h"
52 : : #include "utils/lsyscache.h"
53 : : #include "utils/memutils.h"
54 : : #include "utils/rel.h"
55 : : #include "utils/sampling.h"
56 : : #include "utils/selfuncs.h"
57 : : #include "utils/timestamp.h"
58 : : #include "utils/typcache.h"
59 : :
565 tgl@sss.pgh.pa.us 60 :CBC 38 : PG_MODULE_MAGIC_EXT(
61 : : .name = "postgres_fdw",
62 : : .version = PG_VERSION
63 : : );
64 : :
65 : : /* Default CPU cost to start up a foreign query. */
66 : : #define DEFAULT_FDW_STARTUP_COST 100.0
67 : :
68 : : /* Default CPU cost to process 1 row (above and beyond cpu_tuple_cost). */
69 : : #define DEFAULT_FDW_TUPLE_COST 0.2
70 : :
71 : : /* If no remote estimates, assume a sort costs 20% extra */
72 : : #define DEFAULT_FDW_SORT_MULTIPLIER 1.2
73 : :
74 : : /*
75 : : * Indexes of FDW-private information stored in fdw_private lists.
76 : : *
77 : : * These items are indexed with the enum FdwScanPrivateIndex, so an item
78 : : * can be fetched with list_nth(). For example, to get the SELECT statement:
79 : : * sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
80 : : */
81 : : enum FdwScanPrivateIndex
82 : : {
83 : : /* SQL statement to execute remotely (as a String node) */
84 : : FdwScanPrivateSelectSql,
85 : : /* Integer list of attribute numbers retrieved by the SELECT */
86 : : FdwScanPrivateRetrievedAttrs,
87 : : /* Integer representing the desired fetch_size */
88 : : FdwScanPrivateFetchSize,
89 : :
90 : : /*
91 : : * String describing join i.e. names of relations being joined and types
92 : : * of join, added when the scan is join
93 : : */
94 : : FdwScanPrivateRelations,
95 : :
96 : : /*
97 : : * List of per-RTE function metadata, indexed by base RTI offset. Each
98 : : * element is either NULL (for non-RTE_FUNCTION rels in this scan) or a
99 : : * list of three-element lists (funcid, funcrettype, funccollation) -- one
100 : : * inner list per function in the RTE. Allows the executor to rebuild
101 : : * TupleDesc entries for whole-row references to function RTEs.
102 : : */
103 : : FdwScanPrivateFunctions,
104 : :
105 : : /*
106 : : * Integer node: minimum base RT index covered by the scan, used to
107 : : * translate scan-local indexes to estate-rtable indexes after setrefs.c
108 : : * flattens rtables.
109 : : */
110 : : FdwScanPrivateMinRTIndex,
111 : : };
112 : :
113 : : /*
114 : : * Similarly, this enum describes what's kept in the fdw_private list for
115 : : * a ModifyTable node referencing a postgres_fdw foreign table. We store:
116 : : *
117 : : * 1) INSERT/UPDATE/DELETE statement text to be sent to the remote server
118 : : * 2) Integer list of target attribute numbers for INSERT/UPDATE
119 : : * (NIL for a DELETE)
120 : : * 3) Length till the end of VALUES clause for INSERT
121 : : * (-1 for a DELETE/UPDATE)
122 : : * 4) Boolean flag showing if the remote query has a RETURNING clause
123 : : * 5) Integer list of attribute numbers retrieved by RETURNING, if any
124 : : */
125 : : enum FdwModifyPrivateIndex
126 : : {
127 : : /* SQL statement to execute remotely (as a String node) */
128 : : FdwModifyPrivateUpdateSql,
129 : : /* Integer list of target attribute numbers for INSERT/UPDATE */
130 : : FdwModifyPrivateTargetAttnums,
131 : : /* Length till the end of VALUES clause (as an Integer node) */
132 : : FdwModifyPrivateLen,
133 : : /* has-returning flag (as a Boolean node) */
134 : : FdwModifyPrivateHasReturning,
135 : : /* Integer list of attribute numbers retrieved by RETURNING */
136 : : FdwModifyPrivateRetrievedAttrs,
137 : : };
138 : :
139 : : /*
140 : : * Similarly, this enum describes what's kept in the fdw_private list for
141 : : * a ForeignScan node that modifies a foreign table directly. We store:
142 : : *
143 : : * 1) UPDATE/DELETE statement text to be sent to the remote server
144 : : * 2) Boolean flag showing if the remote query has a RETURNING clause
145 : : * 3) Integer list of attribute numbers retrieved by RETURNING, if any
146 : : * 4) Boolean flag showing if we set the command es_processed
147 : : * 5) Per-RTE function metadata (mirrors FdwScanPrivateFunctions; lets
148 : : * the executor rebuild TupleDesc entries for whole-row Vars over
149 : : * function RTEs absorbed into a foreign join)
150 : : * 6) Integer node: minimum base RT index of the scan (mirrors
151 : : * FdwScanPrivateMinRTIndex)
152 : : */
153 : : enum FdwDirectModifyPrivateIndex
154 : : {
155 : : /* SQL statement to execute remotely (as a String node) */
156 : : FdwDirectModifyPrivateUpdateSql,
157 : : /* has-returning flag (as a Boolean node) */
158 : : FdwDirectModifyPrivateHasReturning,
159 : : /* Integer list of attribute numbers retrieved by RETURNING */
160 : : FdwDirectModifyPrivateRetrievedAttrs,
161 : : /* set-processed flag (as a Boolean node) */
162 : : FdwDirectModifyPrivateSetProcessed,
163 : : /* Per-RTE function metadata, indexed by base RTI offset */
164 : : FdwDirectModifyPrivateFunctions,
165 : : /* Integer node: minimum base RT index in the scan */
166 : : FdwDirectModifyPrivateMinRTIndex,
167 : : };
168 : :
169 : : /*
170 : : * Execution state of a foreign scan using postgres_fdw.
171 : : */
172 : : typedef struct PgFdwScanState
173 : : {
174 : : Relation rel; /* relcache entry for the foreign table. NULL
175 : : * for a foreign join scan. */
176 : : TupleDesc tupdesc; /* tuple descriptor of scan */
177 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
178 : :
179 : : /* extracted fdw_private data */
180 : : char *query; /* text of SELECT command */
181 : : List *retrieved_attrs; /* list of retrieved attribute numbers */
182 : :
183 : : /* for remote query execution */
184 : : PGconn *conn; /* connection for the scan */
185 : : PgFdwConnState *conn_state; /* extra per-connection state */
186 : : unsigned int cursor_number; /* quasi-unique ID for my cursor */
187 : : bool cursor_exists; /* have we created the cursor? */
188 : : int numParams; /* number of parameters passed to query */
189 : : FmgrInfo *param_flinfo; /* output conversion functions for them */
190 : : List *param_exprs; /* executable expressions for param values */
191 : : const char **param_values; /* textual values of query parameters */
192 : :
193 : : /* for storing result tuples */
194 : : HeapTuple *tuples; /* array of currently-retrieved tuples */
195 : : int num_tuples; /* # of tuples in array */
196 : : int next_tuple; /* index of next one to return */
197 : :
198 : : /* batch-level state, for optimizing rewinds and avoiding useless fetch */
199 : : int fetch_ct_2; /* Min(# of fetches done, 2) */
200 : : bool eof_reached; /* true if last fetch reached EOF */
201 : :
202 : : /* for asynchronous execution */
203 : : bool async_capable; /* engage asynchronous-capable logic? */
204 : :
205 : : /* working memory contexts */
206 : : MemoryContext batch_cxt; /* context holding current batch of tuples */
207 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
208 : :
209 : : int fetch_size; /* number of tuples per fetch */
210 : : } PgFdwScanState;
211 : :
212 : : /*
213 : : * Execution state of a foreign insert/update/delete operation.
214 : : */
215 : : typedef struct PgFdwModifyState
216 : : {
217 : : Relation rel; /* relcache entry for the foreign table */
218 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
219 : :
220 : : /* for remote query execution */
221 : : PGconn *conn; /* connection for the scan */
222 : : PgFdwConnState *conn_state; /* extra per-connection state */
223 : : char *p_name; /* name of prepared statement, if created */
224 : :
225 : : /* extracted fdw_private data */
226 : : char *query; /* text of INSERT/UPDATE/DELETE command */
227 : : char *orig_query; /* original text of INSERT command */
228 : : List *target_attrs; /* list of target attribute numbers */
229 : : int values_end; /* length up to the end of VALUES */
230 : : int batch_size; /* value of FDW option "batch_size" */
231 : : bool has_returning; /* is there a RETURNING clause? */
232 : : List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
233 : :
234 : : /* info about parameters for prepared statement */
235 : : AttrNumber ctidAttno; /* attnum of input resjunk ctid column */
236 : : int p_nums; /* number of parameters to transmit */
237 : : FmgrInfo *p_flinfo; /* output conversion functions for them */
238 : :
239 : : /* batch operation stuff */
240 : : int num_slots; /* number of slots to insert */
241 : :
242 : : /* working memory context */
243 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
244 : :
245 : : /* for update row movement if subplan result rel */
246 : : struct PgFdwModifyState *aux_fmstate; /* foreign-insert state, if
247 : : * created */
248 : : } PgFdwModifyState;
249 : :
250 : : /*
251 : : * Execution state of a foreign scan that modifies a foreign table directly.
252 : : */
253 : : typedef struct PgFdwDirectModifyState
254 : : {
255 : : Relation rel; /* relcache entry for the foreign table */
256 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
257 : :
258 : : /* extracted fdw_private data */
259 : : char *query; /* text of UPDATE/DELETE command */
260 : : bool has_returning; /* is there a RETURNING clause? */
261 : : List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
262 : : bool set_processed; /* do we set the command es_processed? */
263 : :
264 : : /* for remote query execution */
265 : : PGconn *conn; /* connection for the update */
266 : : PgFdwConnState *conn_state; /* extra per-connection state */
267 : : int numParams; /* number of parameters passed to query */
268 : : FmgrInfo *param_flinfo; /* output conversion functions for them */
269 : : List *param_exprs; /* executable expressions for param values */
270 : : const char **param_values; /* textual values of query parameters */
271 : :
272 : : /* for storing result tuples */
273 : : PGresult *result; /* result for query */
274 : : int num_tuples; /* # of result tuples */
275 : : int next_tuple; /* index of next one to return */
276 : : Relation resultRel; /* relcache entry for the target relation */
277 : : AttrNumber *attnoMap; /* array of attnums of input user columns */
278 : : AttrNumber ctidAttno; /* attnum of input ctid column */
279 : : AttrNumber oidAttno; /* attnum of input oid column */
280 : : bool hasSystemCols; /* are there system columns of resultRel? */
281 : :
282 : : /* working memory context */
283 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
284 : : } PgFdwDirectModifyState;
285 : :
286 : : /*
287 : : * Workspace for analyzing a foreign table.
288 : : */
289 : : typedef struct PgFdwAnalyzeState
290 : : {
291 : : Relation rel; /* relcache entry for the foreign table */
292 : : AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
293 : : List *retrieved_attrs; /* attr numbers retrieved by query */
294 : :
295 : : /* collected sample rows */
296 : : HeapTuple *rows; /* array of size targrows */
297 : : int targrows; /* target # of sample rows */
298 : : int numrows; /* # of sample rows collected */
299 : :
300 : : /* for random sampling */
301 : : double samplerows; /* # of rows fetched */
302 : : double rowstoskip; /* # of rows to skip before next sample */
303 : : ReservoirStateData rstate; /* state for reservoir sampling */
304 : :
305 : : /* working memory contexts */
306 : : MemoryContext anl_cxt; /* context for per-analyze lifespan data */
307 : : MemoryContext temp_cxt; /* context for per-tuple temporary data */
308 : : } PgFdwAnalyzeState;
309 : :
310 : : /*
311 : : * This enum describes what's kept in the fdw_private list for a ForeignPath.
312 : : * We store:
313 : : *
314 : : * 1) Boolean flag showing if the remote query has the final sort
315 : : * 2) Boolean flag showing if the remote query has the LIMIT clause
316 : : */
317 : : enum FdwPathPrivateIndex
318 : : {
319 : : /* has-final-sort flag (as a Boolean node) */
320 : : FdwPathPrivateHasFinalSort,
321 : : /* has-limit flag (as a Boolean node) */
322 : : FdwPathPrivateHasLimit,
323 : : };
324 : :
325 : : /* Struct for extra information passed to estimate_path_cost_size() */
326 : : typedef struct
327 : : {
328 : : PathTarget *target;
329 : : bool has_final_sort;
330 : : bool has_limit;
331 : : double limit_tuples;
332 : : int64 count_est;
333 : : int64 offset_est;
334 : : } PgFdwPathExtraData;
335 : :
336 : : /*
337 : : * Identify the attribute where data conversion fails.
338 : : */
339 : : typedef struct ConversionLocation
340 : : {
341 : : AttrNumber cur_attno; /* attribute number being processed, or 0 */
342 : : Relation rel; /* foreign table being processed, or NULL */
343 : : ForeignScanState *fsstate; /* plan node being processed, or NULL */
344 : : } ConversionLocation;
345 : :
346 : : /* Callback argument for ec_member_matches_foreign */
347 : : typedef struct
348 : : {
349 : : Expr *current; /* current expr, or NULL if not yet found */
350 : : List *already_used; /* expressions already dealt with */
351 : : } ec_member_foreign_arg;
352 : :
353 : : /* Column order in relation stats query */
354 : : enum RelStatsColumns
355 : : {
356 : : RELSTATS_RELPAGES = 0,
357 : : RELSTATS_RELTUPLES,
358 : : RELSTATS_RELKIND,
359 : : RELSTATS_RELHASSUBCLASS,
360 : : RELSTATS_NUM_FIELDS,
361 : : };
362 : :
363 : : /* Column order in attribute stats query */
364 : : enum AttStatsColumns
365 : : {
366 : : ATTSTATS_ATTNAME = 0,
367 : : ATTSTATS_NULL_FRAC,
368 : : ATTSTATS_AVG_WIDTH,
369 : : ATTSTATS_N_DISTINCT,
370 : : ATTSTATS_MOST_COMMON_VALS,
371 : : ATTSTATS_MOST_COMMON_FREQS,
372 : : ATTSTATS_HISTOGRAM_BOUNDS,
373 : : ATTSTATS_CORRELATION,
374 : : ATTSTATS_MOST_COMMON_ELEMS,
375 : : ATTSTATS_MOST_COMMON_ELEM_FREQS,
376 : : ATTSTATS_ELEM_COUNT_HISTOGRAM,
377 : : ATTSTATS_RANGE_LENGTH_HISTOGRAM,
378 : : ATTSTATS_RANGE_EMPTY_FRAC,
379 : : ATTSTATS_RANGE_BOUNDS_HISTOGRAM,
380 : : ATTSTATS_NUM_FIELDS,
381 : : };
382 : :
383 : : /* Results that are returned from a foreign statistics scan */
384 : : typedef struct
385 : : {
386 : : int version; /* version of remote server */
387 : : BlockNumber relpages; /* # of pages in remote table */
388 : : double reltuples; /* # of tuples in remote table */
389 : : PGresult *rel; /* result for relation stats query */
390 : : PGresult *att; /* result for attribute stats query */
391 : : } RemoteStatsResults;
392 : :
393 : : /* Pairs of remote columns with local columns */
394 : : typedef struct
395 : : {
396 : : AttrNumber local_attnum; /* attribute number of local column */
397 : : char *local_attname; /* attribute name of local column */
398 : : char *remote_attname; /* attribute name of remote column */
399 : : int res_index; /* index of row in attribute stats result */
400 : : } RemoteAttributeMapping;
401 : :
402 : : /*
403 : : * SQL functions
404 : : */
4981 405 : 19 : PG_FUNCTION_INFO_V1(postgres_fdw_handler);
406 : :
407 : : /*
408 : : * FDW callback routines
409 : : */
410 : : static void postgresGetForeignRelSize(PlannerInfo *root,
411 : : RelOptInfo *baserel,
412 : : Oid foreigntableid);
413 : : static void postgresGetForeignPaths(PlannerInfo *root,
414 : : RelOptInfo *baserel,
415 : : Oid foreigntableid);
416 : : static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
417 : : RelOptInfo *foreignrel,
418 : : Oid foreigntableid,
419 : : ForeignPath *best_path,
420 : : List *tlist,
421 : : List *scan_clauses,
422 : : Plan *outer_plan);
423 : : static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
424 : : static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
425 : : static void postgresReScanForeignScan(ForeignScanState *node);
426 : : static void postgresEndForeignScan(ForeignScanState *node);
427 : : static void postgresAddForeignUpdateTargets(PlannerInfo *root,
428 : : Index rtindex,
429 : : RangeTblEntry *target_rte,
430 : : Relation target_relation);
431 : : static List *postgresPlanForeignModify(PlannerInfo *root,
432 : : ModifyTable *plan,
433 : : Index resultRelation,
434 : : int subplan_index);
435 : : static void postgresBeginForeignModify(ModifyTableState *mtstate,
436 : : ResultRelInfo *resultRelInfo,
437 : : List *fdw_private,
438 : : int subplan_index,
439 : : int eflags);
440 : : static TupleTableSlot *postgresExecForeignInsert(EState *estate,
441 : : ResultRelInfo *resultRelInfo,
442 : : TupleTableSlot *slot,
443 : : TupleTableSlot *planSlot);
444 : : static TupleTableSlot **postgresExecForeignBatchInsert(EState *estate,
445 : : ResultRelInfo *resultRelInfo,
446 : : TupleTableSlot **slots,
447 : : TupleTableSlot **planSlots,
448 : : int *numSlots);
449 : : static int postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo);
450 : : static TupleTableSlot *postgresExecForeignUpdate(EState *estate,
451 : : ResultRelInfo *resultRelInfo,
452 : : TupleTableSlot *slot,
453 : : TupleTableSlot *planSlot);
454 : : static TupleTableSlot *postgresExecForeignDelete(EState *estate,
455 : : ResultRelInfo *resultRelInfo,
456 : : TupleTableSlot *slot,
457 : : TupleTableSlot *planSlot);
458 : : static void postgresEndForeignModify(EState *estate,
459 : : ResultRelInfo *resultRelInfo);
460 : : static void postgresBeginForeignInsert(ModifyTableState *mtstate,
461 : : ResultRelInfo *resultRelInfo);
462 : : static void postgresEndForeignInsert(EState *estate,
463 : : ResultRelInfo *resultRelInfo);
464 : : static int postgresIsForeignRelUpdatable(Relation rel);
465 : : static bool postgresPlanDirectModify(PlannerInfo *root,
466 : : ModifyTable *plan,
467 : : Index resultRelation,
468 : : int subplan_index);
469 : : static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
470 : : static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
471 : : static void postgresEndDirectModify(ForeignScanState *node);
472 : : static void postgresExplainForeignScan(ForeignScanState *node,
473 : : ExplainState *es);
474 : : static void postgresExplainForeignModify(ModifyTableState *mtstate,
475 : : ResultRelInfo *rinfo,
476 : : List *fdw_private,
477 : : int subplan_index,
478 : : ExplainState *es);
479 : : static void postgresExplainDirectModify(ForeignScanState *node,
480 : : ExplainState *es);
481 : : static void postgresExecForeignTruncate(List *rels,
482 : : DropBehavior behavior,
483 : : bool restart_seqs);
484 : : static bool postgresAnalyzeForeignTable(Relation relation,
485 : : AcquireSampleRowsFunc *func,
486 : : BlockNumber *totalpages);
487 : : static bool postgresImportForeignStatistics(Relation relation,
488 : : List *va_cols,
489 : : int elevel);
490 : : static List *postgresImportForeignSchema(ImportForeignSchemaStmt *stmt,
491 : : Oid serverOid);
492 : : static void postgresGetForeignJoinPaths(PlannerInfo *root,
493 : : RelOptInfo *joinrel,
494 : : RelOptInfo *outerrel,
495 : : RelOptInfo *innerrel,
496 : : JoinType jointype,
497 : : JoinPathExtraData *extra);
498 : : static bool postgresRecheckForeignScan(ForeignScanState *node,
499 : : TupleTableSlot *slot);
500 : : static void postgresGetForeignUpperPaths(PlannerInfo *root,
501 : : UpperRelationKind stage,
502 : : RelOptInfo *input_rel,
503 : : RelOptInfo *output_rel,
504 : : void *extra);
505 : : static bool postgresIsForeignPathAsyncCapable(ForeignPath *path);
506 : : static void postgresForeignAsyncRequest(AsyncRequest *areq);
507 : : static void postgresForeignAsyncConfigureWait(AsyncRequest *areq);
508 : : static void postgresForeignAsyncNotify(AsyncRequest *areq);
509 : :
510 : : /*
511 : : * Helper functions
512 : : */
513 : : static void estimate_path_cost_size(PlannerInfo *root,
514 : : RelOptInfo *foreignrel,
515 : : List *param_join_conds,
516 : : List *pathkeys,
517 : : PgFdwPathExtraData *fpextra,
518 : : double *p_rows, int *p_width,
519 : : int *p_disabled_nodes,
520 : : Cost *p_startup_cost, Cost *p_total_cost);
521 : : static void get_remote_estimate(const char *sql,
522 : : PGconn *conn,
523 : : double *rows,
524 : : int *width,
525 : : Cost *startup_cost,
526 : : Cost *total_cost);
527 : : static void adjust_foreign_grouping_path_cost(PlannerInfo *root,
528 : : List *pathkeys,
529 : : double retrieved_rows,
530 : : double width,
531 : : double limit_tuples,
532 : : int *p_disabled_nodes,
533 : : Cost *p_startup_cost,
534 : : Cost *p_run_cost);
535 : : static bool ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
536 : : EquivalenceClass *ec, EquivalenceMember *em,
537 : : void *arg);
538 : : static void create_cursor(ForeignScanState *node);
539 : : static void fetch_more_data(ForeignScanState *node);
540 : : static void close_cursor(PGconn *conn, unsigned int cursor_number,
541 : : PgFdwConnState *conn_state);
542 : : static PgFdwModifyState *create_foreign_modify(EState *estate,
543 : : RangeTblEntry *rte,
544 : : ResultRelInfo *resultRelInfo,
545 : : CmdType operation,
546 : : Plan *subplan,
547 : : char *query,
548 : : List *target_attrs,
549 : : int values_end,
550 : : bool has_returning,
551 : : List *retrieved_attrs);
552 : : static TupleTableSlot **execute_foreign_modify(EState *estate,
553 : : ResultRelInfo *resultRelInfo,
554 : : CmdType operation,
555 : : TupleTableSlot **slots,
556 : : TupleTableSlot **planSlots,
557 : : int *numSlots);
558 : : static void prepare_foreign_modify(PgFdwModifyState *fmstate);
559 : : static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
560 : : ItemPointer tupleid,
561 : : TupleTableSlot **slots,
562 : : int numSlots);
563 : : static void store_returning_result(PgFdwModifyState *fmstate,
564 : : TupleTableSlot *slot, PGresult *res);
565 : : static void finish_foreign_modify(PgFdwModifyState *fmstate);
566 : : static void deallocate_query(PgFdwModifyState *fmstate);
567 : : static List *build_remote_returning(Index rtindex, Relation rel,
568 : : List *returningList);
569 : : static void rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist);
570 : : static void execute_dml_stmt(ForeignScanState *node);
571 : : static TupleTableSlot *get_returning_data(ForeignScanState *node);
572 : : static void init_returning_filter(PgFdwDirectModifyState *dmstate,
573 : : List *fdw_scan_tlist,
574 : : Index rtindex);
575 : : static TupleTableSlot *apply_returning_filter(PgFdwDirectModifyState *dmstate,
576 : : ResultRelInfo *resultRelInfo,
577 : : TupleTableSlot *slot,
578 : : EState *estate);
579 : : static void prepare_query_params(PlanState *node,
580 : : List *fdw_exprs,
581 : : int numParams,
582 : : FmgrInfo **param_flinfo,
583 : : List **param_exprs,
584 : : const char ***param_values);
585 : : static void process_query_params(ExprContext *econtext,
586 : : FmgrInfo *param_flinfo,
587 : : List *param_exprs,
588 : : const char **param_values);
589 : : static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
590 : : HeapTuple *rows, int targrows,
591 : : double *totalrows,
592 : : double *totaldeadrows);
593 : : static void analyze_row_processor(PGresult *res, int row,
594 : : PgFdwAnalyzeState *astate);
595 : : static bool fetch_remote_statistics(Relation relation,
596 : : List *va_cols,
597 : : const char *local_schemaname,
598 : : const char *local_relname,
599 : : ForeignTable *table,
600 : : ForeignServer *server,
601 : : RemoteStatsResults *remstats,
602 : : RemoteAttributeMapping **p_remattrmap,
603 : : int *p_attrcnt);
604 : : static PGresult *fetch_relstats(PGconn *conn, Relation relation);
605 : : static PGresult *fetch_attstats(PGconn *conn, int server_version_num,
606 : : const char *remote_schemaname, const char *remote_relname,
607 : : const char *column_list);
608 : : static RemoteAttributeMapping *build_remattrmap(Relation relation, List *va_cols,
609 : : int *p_attrcnt, StringInfo column_list);
610 : : static void free_remattrmap(RemoteAttributeMapping *map, int len);
611 : : static bool attname_in_list(const char *attname, List *va_cols);
612 : : static int remattrmap_cmp(const void *v1, const void *v2);
613 : : static bool match_attrmap(PGresult *res,
614 : : const char *local_schemaname,
615 : : const char *local_relname,
616 : : const char *remote_schemaname,
617 : : const char *remote_relname,
618 : : RemoteAttributeMapping *remattrmap,
619 : : int attrcnt);
620 : : static bool import_fetched_statistics(Relation relation,
621 : : const char *schemaname,
622 : : const char *relname,
623 : : RemoteStatsResults *remstats,
624 : : const RemoteAttributeMapping *remattrmap,
625 : : int attrcnt);
626 : : static char *get_opt_value(PGresult *res, int row, int col);
627 : : static void set_text_arg(NullableDatum *arg, const char *s);
628 : : static void set_int32_arg(NullableDatum *arg, const char *s);
629 : : static void set_float_arg(NullableDatum *arg, const char *s);
630 : : static void set_floatarr_arg(NullableDatum *arg, const char *s);
631 : : static void produce_tuple_asynchronously(AsyncRequest *areq, bool fetch);
632 : : static void fetch_more_data_begin(AsyncRequest *areq);
633 : : static void complete_pending_request(AsyncRequest *areq);
634 : : static HeapTuple make_tuple_from_result_row(PGresult *res,
635 : : int row,
636 : : Relation rel,
637 : : AttInMetadata *attinmeta,
638 : : List *retrieved_attrs,
639 : : ForeignScanState *fsstate,
640 : : MemoryContext temp_context);
641 : : static void conversion_error_callback(void *arg);
642 : : static bool foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel,
643 : : JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel,
644 : : JoinPathExtraData *extra);
645 : : static bool foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
646 : : Node *havingQual);
647 : : static List *get_useful_pathkeys_for_relation(PlannerInfo *root,
648 : : RelOptInfo *rel);
649 : : static List *get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel);
650 : : static Relids get_base_relids(PlannerInfo *root, RelOptInfo *rel);
651 : : static int get_min_base_rti(PlannerInfo *root, RelOptInfo *rel);
652 : : static List *get_functions_data(PlannerInfo *root, RelOptInfo *rel);
653 : : static void add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
654 : : Path *epq_path, List *restrictlist);
655 : : static void add_foreign_grouping_paths(PlannerInfo *root,
656 : : RelOptInfo *input_rel,
657 : : RelOptInfo *grouped_rel,
658 : : GroupPathExtraData *extra);
659 : : static void add_foreign_ordered_paths(PlannerInfo *root,
660 : : RelOptInfo *input_rel,
661 : : RelOptInfo *ordered_rel);
662 : : static void add_foreign_final_paths(PlannerInfo *root,
663 : : RelOptInfo *input_rel,
664 : : RelOptInfo *final_rel,
665 : : FinalPathExtraData *extra);
666 : : static void apply_server_options(PgFdwRelationInfo *fpinfo);
667 : : static void apply_table_options(PgFdwRelationInfo *fpinfo);
668 : : static void merge_fdw_options(PgFdwRelationInfo *fpinfo,
669 : : const PgFdwRelationInfo *fpinfo_o,
670 : : const PgFdwRelationInfo *fpinfo_i);
671 : : static int get_batch_size_option(Relation rel);
672 : :
673 : :
674 : : /*
675 : : * Foreign-data wrapper handler function: return a struct with pointers
676 : : * to my callback routines.
677 : : */
678 : : Datum
679 : 748 : postgres_fdw_handler(PG_FUNCTION_ARGS)
680 : : {
681 : 748 : FdwRoutine *routine = makeNode(FdwRoutine);
682 : :
683 : : /* Functions for scanning foreign tables */
684 : 748 : routine->GetForeignRelSize = postgresGetForeignRelSize;
685 : 748 : routine->GetForeignPaths = postgresGetForeignPaths;
686 : 748 : routine->GetForeignPlan = postgresGetForeignPlan;
687 : 748 : routine->BeginForeignScan = postgresBeginForeignScan;
688 : 748 : routine->IterateForeignScan = postgresIterateForeignScan;
689 : 748 : routine->ReScanForeignScan = postgresReScanForeignScan;
690 : 748 : routine->EndForeignScan = postgresEndForeignScan;
691 : :
692 : : /* Functions for updating foreign tables */
4964 693 : 748 : routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets;
694 : 748 : routine->PlanForeignModify = postgresPlanForeignModify;
695 : 748 : routine->BeginForeignModify = postgresBeginForeignModify;
696 : 748 : routine->ExecForeignInsert = postgresExecForeignInsert;
2091 tomas.vondra@postgre 697 : 748 : routine->ExecForeignBatchInsert = postgresExecForeignBatchInsert;
698 : 748 : routine->GetForeignModifyBatchSize = postgresGetForeignModifyBatchSize;
4964 tgl@sss.pgh.pa.us 699 : 748 : routine->ExecForeignUpdate = postgresExecForeignUpdate;
700 : 748 : routine->ExecForeignDelete = postgresExecForeignDelete;
701 : 748 : routine->EndForeignModify = postgresEndForeignModify;
3111 rhaas@postgresql.org 702 : 748 : routine->BeginForeignInsert = postgresBeginForeignInsert;
703 : 748 : routine->EndForeignInsert = postgresEndForeignInsert;
4870 tgl@sss.pgh.pa.us 704 : 748 : routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
3860 rhaas@postgresql.org 705 : 748 : routine->PlanDirectModify = postgresPlanDirectModify;
706 : 748 : routine->BeginDirectModify = postgresBeginDirectModify;
707 : 748 : routine->IterateDirectModify = postgresIterateDirectModify;
708 : 748 : routine->EndDirectModify = postgresEndDirectModify;
709 : :
710 : : /* Function for EvalPlanQual rechecks */
3898 711 : 748 : routine->RecheckForeignScan = postgresRecheckForeignScan;
712 : : /* Support functions for EXPLAIN */
4964 tgl@sss.pgh.pa.us 713 : 748 : routine->ExplainForeignScan = postgresExplainForeignScan;
714 : 748 : routine->ExplainForeignModify = postgresExplainForeignModify;
3860 rhaas@postgresql.org 715 : 748 : routine->ExplainDirectModify = postgresExplainDirectModify;
716 : :
717 : : /* Support function for TRUNCATE */
2013 fujii@postgresql.org 718 : 748 : routine->ExecForeignTruncate = postgresExecForeignTruncate;
719 : :
720 : : /* Support functions for ANALYZE */
4981 tgl@sss.pgh.pa.us 721 : 748 : routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
187 efujita@postgresql.o 722 : 748 : routine->ImportForeignStatistics = postgresImportForeignStatistics;
723 : :
724 : : /* Support functions for IMPORT FOREIGN SCHEMA */
4477 tgl@sss.pgh.pa.us 725 : 748 : routine->ImportForeignSchema = postgresImportForeignSchema;
726 : :
727 : : /* Support functions for join push-down */
3898 rhaas@postgresql.org 728 : 748 : routine->GetForeignJoinPaths = postgresGetForeignJoinPaths;
729 : :
730 : : /* Support functions for upper relation push-down */
3643 731 : 748 : routine->GetForeignUpperPaths = postgresGetForeignUpperPaths;
732 : :
733 : : /* Support functions for asynchronous execution */
2021 efujita@postgresql.o 734 : 748 : routine->IsForeignPathAsyncCapable = postgresIsForeignPathAsyncCapable;
735 : 748 : routine->ForeignAsyncRequest = postgresForeignAsyncRequest;
736 : 748 : routine->ForeignAsyncConfigureWait = postgresForeignAsyncConfigureWait;
737 : 748 : routine->ForeignAsyncNotify = postgresForeignAsyncNotify;
738 : :
4981 tgl@sss.pgh.pa.us 739 : 748 : PG_RETURN_POINTER(routine);
740 : : }
741 : :
742 : : /*
743 : : * postgresGetForeignRelSize
744 : : * Estimate # of rows and width of the result of the scan
745 : : *
746 : : * We should consider the effect of all baserestrictinfo clauses here, but
747 : : * not any join clauses.
748 : : */
749 : : static void
750 : 1287 : postgresGetForeignRelSize(PlannerInfo *root,
751 : : RelOptInfo *baserel,
752 : : Oid foreigntableid)
753 : : {
754 : : PgFdwRelationInfo *fpinfo;
755 : : ListCell *lc;
756 : :
757 : : /*
758 : : * We use PgFdwRelationInfo to pass various information to subsequent
759 : : * functions.
760 : : */
305 michael@paquier.xyz 761 : 1287 : fpinfo = palloc0_object(PgFdwRelationInfo);
683 peter@eisentraut.org 762 : 1287 : baserel->fdw_private = fpinfo;
763 : :
764 : : /* Base foreign tables need to be pushed down always. */
3898 rhaas@postgresql.org 765 : 1287 : fpinfo->pushdown_safe = true;
766 : :
767 : : /* Look up foreign-table catalog info. */
4953 tgl@sss.pgh.pa.us 768 : 1287 : fpinfo->table = GetForeignTable(foreigntableid);
769 : 1287 : fpinfo->server = GetForeignServer(fpinfo->table->serverid);
770 : :
771 : : /*
772 : : * Extract user-settable option values. Note that per-table settings of
773 : : * use_remote_estimate, fetch_size and async_capable override per-server
774 : : * settings of them, respectively.
775 : : */
776 : 1287 : fpinfo->use_remote_estimate = false;
777 : 1287 : fpinfo->fdw_startup_cost = DEFAULT_FDW_STARTUP_COST;
778 : 1287 : fpinfo->fdw_tuple_cost = DEFAULT_FDW_TUPLE_COST;
3996 779 : 1287 : fpinfo->shippable_extensions = NIL;
3904 rhaas@postgresql.org 780 : 1287 : fpinfo->fetch_size = 100;
2021 efujita@postgresql.o 781 : 1287 : fpinfo->async_capable = false;
782 : :
3458 peter_e@gmx.net 783 : 1287 : apply_server_options(fpinfo);
784 : 1287 : apply_table_options(fpinfo);
785 : :
786 : : /*
787 : : * If the table or the server is configured to use remote estimates,
788 : : * identify which user to do remote access as during planning. This
789 : : * should match what ExecCheckPermissions() does. If we fail due to lack
790 : : * of permissions, the query would have failed at runtime anyway.
791 : : */
4953 tgl@sss.pgh.pa.us 792 [ + + ]: 1287 : if (fpinfo->use_remote_estimate)
793 : : {
794 : : Oid userid;
795 : :
1412 alvherre@alvh.no-ip. 796 [ + + ]: 317 : userid = OidIsValid(baserel->userid) ? baserel->userid : GetUserId();
4953 tgl@sss.pgh.pa.us 797 : 317 : fpinfo->user = GetUserMapping(userid, fpinfo->server->serverid);
798 : : }
799 : : else
800 : 970 : fpinfo->user = NULL;
801 : :
802 : : /*
803 : : * Identify which baserestrictinfo clauses can be sent to the remote
804 : : * server and which can't.
805 : : */
54 akorotkov@postgresql 806 :GNC 1285 : classifyConditions(root, baserel, fpinfo, baserel->baserestrictinfo,
807 : : &fpinfo->remote_conds, &fpinfo->local_conds);
808 : :
809 : : /*
810 : : * Identify which attributes will need to be retrieved from the remote
811 : : * server. These include all attrs needed for joins or final output, plus
812 : : * all attrs used in the local_conds. (Note: if we end up using a
813 : : * parameterized scan, it's possible that some of the join clauses will be
814 : : * sent to the remote and thus we wouldn't really need to retrieve the
815 : : * columns used in them. Doesn't seem worth detecting that case though.)
816 : : */
4953 tgl@sss.pgh.pa.us 817 :CBC 1285 : fpinfo->attrs_used = NULL;
3864 818 : 1285 : pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid,
819 : : &fpinfo->attrs_used);
4953 820 [ + + + + : 1366 : foreach(lc, fpinfo->local_conds)
+ + ]
821 : : {
3471 822 : 81 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
823 : :
4964 824 : 81 : pull_varattnos((Node *) rinfo->clause, baserel->relid,
825 : : &fpinfo->attrs_used);
826 : : }
827 : :
828 : : /*
829 : : * Compute the selectivity and cost of the local_conds, so we don't have
830 : : * to do it over again for each path. The best we can do for these
831 : : * conditions is to estimate selectivity on the basis of local statistics.
832 : : */
4953 833 : 2570 : fpinfo->local_conds_sel = clauselist_selectivity(root,
834 : : fpinfo->local_conds,
835 : 1285 : baserel->relid,
836 : : JOIN_INNER,
837 : : NULL);
838 : :
839 : 1285 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
840 : :
841 : : /*
842 : : * Set # of retrieved rows and cached relation costs to some negative
843 : : * value, so that we can detect when they are set to some sensible values,
844 : : * during one (usually the first) of the calls to estimate_path_cost_size.
845 : : */
2677 efujita@postgresql.o 846 : 1285 : fpinfo->retrieved_rows = -1;
3869 rhaas@postgresql.org 847 : 1285 : fpinfo->rel_startup_cost = -1;
848 : 1285 : fpinfo->rel_total_cost = -1;
849 : :
850 : : /*
851 : : * If the table or the server is configured to use remote estimates,
852 : : * connect to the foreign server and execute EXPLAIN to estimate the
853 : : * number of rows selected by the restriction clauses, as well as the
854 : : * average row width. Otherwise, estimate using whatever statistics we
855 : : * have locally, in a way similar to ordinary tables.
856 : : */
4953 tgl@sss.pgh.pa.us 857 [ + + ]: 1285 : if (fpinfo->use_remote_estimate)
858 : : {
859 : : /*
860 : : * Get cost/size estimates with help of remote server. Save the
861 : : * values in fpinfo so we don't need to do it again to generate the
862 : : * basic foreign path.
863 : : */
2750 efujita@postgresql.o 864 : 315 : estimate_path_cost_size(root, baserel, NIL, NIL, NULL,
865 : : &fpinfo->rows, &fpinfo->width,
866 : : &fpinfo->disabled_nodes,
867 : : &fpinfo->startup_cost, &fpinfo->total_cost);
868 : :
869 : : /* Report estimated baserel size to planner. */
4953 tgl@sss.pgh.pa.us 870 : 315 : baserel->rows = fpinfo->rows;
3864 871 : 315 : baserel->reltarget->width = fpinfo->width;
872 : : }
873 : : else
874 : : {
875 : : /*
876 : : * If the foreign table has never been ANALYZEd, it will have
877 : : * reltuples < 0, meaning "unknown". We can't do much if we're not
878 : : * allowed to consult the remote server, but we can use a hack similar
879 : : * to plancat.c's treatment of empty relations: use a minimum size
880 : : * estimate of 10 pages, and divide by the column-datatype-based width
881 : : * estimate to get the corresponding number of tuples.
882 : : */
2234 883 [ + + ]: 970 : if (baserel->tuples < 0)
884 : : {
4980 885 : 340 : baserel->pages = 10;
4981 886 : 340 : baserel->tuples =
3864 887 : 340 : (10 * BLCKSZ) / (baserel->reltarget->width +
888 : : MAXALIGN(SizeofHeapTupleHeader));
889 : : }
890 : :
891 : : /* Estimate baserel size as best we can with local statistics. */
4981 892 : 970 : set_baserel_size_estimates(root, baserel);
893 : :
894 : : /* Fill in basically-bogus cost estimates for use later. */
2750 efujita@postgresql.o 895 : 970 : estimate_path_cost_size(root, baserel, NIL, NIL, NULL,
896 : : &fpinfo->rows, &fpinfo->width,
897 : : &fpinfo->disabled_nodes,
898 : : &fpinfo->startup_cost, &fpinfo->total_cost);
899 : : }
900 : :
901 : : /*
902 : : * fpinfo->relation_name gets the numeric rangetable index of the foreign
903 : : * table RTE. (If this query gets EXPLAIN'd, we'll convert that to a
904 : : * human-readable string at that time.)
905 : : */
2506 tgl@sss.pgh.pa.us 906 : 1285 : fpinfo->relation_name = psprintf("%u", baserel->relid);
907 : :
908 : : /* No outer and inner relations. */
3497 rhaas@postgresql.org 909 : 1285 : fpinfo->make_outerrel_subquery = false;
910 : 1285 : fpinfo->make_innerrel_subquery = false;
911 : 1285 : fpinfo->lower_subquery_rels = NULL;
1042 akorotkov@postgresql 912 : 1285 : fpinfo->hidden_subquery_rels = NULL;
913 : : /* Set the relation index. */
3497 rhaas@postgresql.org 914 : 1285 : fpinfo->relation_index = baserel->relid;
4981 tgl@sss.pgh.pa.us 915 : 1285 : }
916 : :
917 : : /*
918 : : * get_useful_ecs_for_relation
919 : : * Determine which EquivalenceClasses might be involved in useful
920 : : * orderings of this relation.
921 : : *
922 : : * This function is in some respects a mirror image of the core function
923 : : * pathkeys_useful_for_merging: for a regular table, we know what indexes
924 : : * we have and want to test whether any of them are useful. For a foreign
925 : : * table, we don't know what indexes are present on the remote side but
926 : : * want to speculate about which ones we'd like to use if they existed.
927 : : *
928 : : * This function returns a list of potentially-useful equivalence classes,
929 : : * but it does not guarantee that an EquivalenceMember exists which contains
930 : : * Vars only from the given relation. For example, given ft1 JOIN t1 ON
931 : : * ft1.x + t1.x = 0, this function will say that the equivalence class
932 : : * containing ft1.x + t1.x is potentially useful. Supposing ft1 is remote and
933 : : * t1 is local (or on a different server), it will turn out that no useful
934 : : * ORDER BY clause can be generated. It's not our job to figure that out
935 : : * here; we're only interested in identifying relevant ECs.
936 : : */
937 : : static List *
3947 rhaas@postgresql.org 938 : 542 : get_useful_ecs_for_relation(PlannerInfo *root, RelOptInfo *rel)
939 : : {
940 : 542 : List *useful_eclass_list = NIL;
941 : : ListCell *lc;
942 : : Relids relids;
943 : :
944 : : /*
945 : : * First, consider whether any active EC is potentially useful for a merge
946 : : * join against this relation.
947 : : */
948 [ + + ]: 542 : if (rel->has_eclass_joins)
949 : : {
950 [ + - + + : 704 : foreach(lc, root->eq_classes)
+ + ]
951 : : {
952 : 481 : EquivalenceClass *cur_ec = (EquivalenceClass *) lfirst(lc);
953 : :
954 [ + + ]: 481 : if (eclass_useful_for_merging(root, cur_ec, rel))
955 : 257 : useful_eclass_list = lappend(useful_eclass_list, cur_ec);
956 : : }
957 : : }
958 : :
959 : : /*
960 : : * Next, consider whether there are any non-EC derivable join clauses that
961 : : * are merge-joinable. If the joininfo list is empty, we can exit
962 : : * quickly.
963 : : */
964 [ + + ]: 542 : if (rel->joininfo == NIL)
965 : 400 : return useful_eclass_list;
966 : :
967 : : /* If this is a child rel, we must use the topmost parent rel to search. */
3479 968 [ + + + - : 142 : if (IS_OTHER_REL(rel))
- + ]
969 : : {
970 [ - + ]: 20 : Assert(!bms_is_empty(rel->top_parent_relids));
971 : 20 : relids = rel->top_parent_relids;
972 : : }
973 : : else
3947 974 : 122 : relids = rel->relids;
975 : :
976 : : /* Check each join clause in turn. */
977 [ + - + + : 345 : foreach(lc, rel->joininfo)
+ + ]
978 : : {
979 : 203 : RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(lc);
980 : :
981 : : /* Consider only mergejoinable clauses */
982 [ + + ]: 203 : if (restrictinfo->mergeopfamilies == NIL)
983 : 14 : continue;
984 : :
985 : : /* Make sure we've got canonical ECs. */
986 : 189 : update_mergeclause_eclasses(root, restrictinfo);
987 : :
988 : : /*
989 : : * restrictinfo->mergeopfamilies != NIL is sufficient to guarantee
990 : : * that left_ec and right_ec will be initialized, per comments in
991 : : * distribute_qual_to_rels.
992 : : *
993 : : * We want to identify which side of this merge-joinable clause
994 : : * contains columns from the relation produced by this RelOptInfo. We
995 : : * test for overlap, not containment, because there could be extra
996 : : * relations on either side. For example, suppose we've got something
997 : : * like ((A JOIN B ON A.x = B.x) JOIN C ON A.y = C.y) LEFT JOIN D ON
998 : : * A.y = D.y. The input rel might be the joinrel between A and B, and
999 : : * we'll consider the join clause A.y = D.y. relids contains a
1000 : : * relation not involved in the join class (B) and the equivalence
1001 : : * class for the left-hand side of the clause contains a relation not
1002 : : * involved in the input rel (C). Despite the fact that we have only
1003 : : * overlap and not containment in either direction, A.y is potentially
1004 : : * useful as a sort column.
1005 : : *
1006 : : * Note that it's even possible that relids overlaps neither side of
1007 : : * the join clause. For example, consider A LEFT JOIN B ON A.x = B.x
1008 : : * AND A.x = 1. The clause A.x = 1 will appear in B's joininfo list,
1009 : : * but overlaps neither side of B. In that case, we just skip this
1010 : : * join clause, since it doesn't suggest a useful sort order for this
1011 : : * relation.
1012 : : */
3801 1013 [ + + ]: 189 : if (bms_overlap(relids, restrictinfo->right_ec->ec_relids))
3947 1014 : 86 : useful_eclass_list = list_append_unique_ptr(useful_eclass_list,
3400 tgl@sss.pgh.pa.us 1015 : 86 : restrictinfo->right_ec);
3801 rhaas@postgresql.org 1016 [ + + ]: 103 : else if (bms_overlap(relids, restrictinfo->left_ec->ec_relids))
3947 1017 : 94 : useful_eclass_list = list_append_unique_ptr(useful_eclass_list,
3400 tgl@sss.pgh.pa.us 1018 : 94 : restrictinfo->left_ec);
1019 : : }
1020 : :
3947 rhaas@postgresql.org 1021 : 142 : return useful_eclass_list;
1022 : : }
1023 : :
1024 : : /*
1025 : : * get_useful_pathkeys_for_relation
1026 : : * Determine which orderings of a relation might be useful.
1027 : : *
1028 : : * Getting data in sorted order can be useful either because the requested
1029 : : * order matches the final output ordering for the overall query we're
1030 : : * planning, or because it enables an efficient merge join. Here, we try
1031 : : * to figure out which pathkeys to consider.
1032 : : */
1033 : : static List *
1034 : 1658 : get_useful_pathkeys_for_relation(PlannerInfo *root, RelOptInfo *rel)
1035 : : {
1036 : 1658 : List *useful_pathkeys_list = NIL;
1037 : : List *useful_eclass_list;
1038 : 1658 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
1039 : 1658 : EquivalenceClass *query_ec = NULL;
1040 : : ListCell *lc;
1041 : :
1042 : : /*
1043 : : * Pushing the query_pathkeys to the remote server is always worth
1044 : : * considering, because it might let us avoid a local sort.
1045 : : */
2750 efujita@postgresql.o 1046 : 1658 : fpinfo->qp_is_pushdown_safe = false;
3947 rhaas@postgresql.org 1047 [ + + ]: 1658 : if (root->query_pathkeys)
1048 : : {
1049 : 662 : bool query_pathkeys_ok = true;
1050 : :
1051 [ + - + + : 1248 : foreach(lc, root->query_pathkeys)
+ + ]
1052 : : {
1053 : 831 : PathKey *pathkey = (PathKey *) lfirst(lc);
1054 : :
1055 : : /*
1056 : : * The planner and executor don't have any clever strategy for
1057 : : * taking data sorted by a prefix of the query's pathkeys and
1058 : : * getting it to be sorted by all of those pathkeys. We'll just
1059 : : * end up resorting the entire data set. So, unless we can push
1060 : : * down all of the query pathkeys, forget it.
1061 : : */
1656 tgl@sss.pgh.pa.us 1062 [ + + ]: 831 : if (!is_foreign_pathkey(root, rel, pathkey))
1063 : : {
3947 rhaas@postgresql.org 1064 : 245 : query_pathkeys_ok = false;
1065 : 245 : break;
1066 : : }
1067 : : }
1068 : :
1069 [ + + ]: 662 : if (query_pathkeys_ok)
1070 : : {
1071 : 417 : useful_pathkeys_list = list_make1(list_copy(root->query_pathkeys));
2750 efujita@postgresql.o 1072 : 417 : fpinfo->qp_is_pushdown_safe = true;
1073 : : }
1074 : : }
1075 : :
1076 : : /*
1077 : : * Even if we're not using remote estimates, having the remote side do the
1078 : : * sort generally won't be any worse than doing it locally, and it might
1079 : : * be much better if the remote side can generate data in the right order
1080 : : * without needing a sort at all. However, what we're going to do next is
1081 : : * try to generate pathkeys that seem promising for possible merge joins,
1082 : : * and that's more speculative. A wrong choice might hurt quite a bit, so
1083 : : * bail out if we can't use remote estimates.
1084 : : */
3947 rhaas@postgresql.org 1085 [ + + ]: 1658 : if (!fpinfo->use_remote_estimate)
1086 : 1116 : return useful_pathkeys_list;
1087 : :
1088 : : /* Get the list of interesting EquivalenceClasses. */
1089 : 542 : useful_eclass_list = get_useful_ecs_for_relation(root, rel);
1090 : :
1091 : : /* Extract unique EC for query, if any, so we don't consider it again. */
1092 [ + + ]: 542 : if (list_length(root->query_pathkeys) == 1)
1093 : : {
1094 : 181 : PathKey *query_pathkey = linitial(root->query_pathkeys);
1095 : :
1096 : 181 : query_ec = query_pathkey->pk_eclass;
1097 : : }
1098 : :
1099 : : /*
1100 : : * As a heuristic, the only pathkeys we consider here are those of length
1101 : : * one. It's surely possible to consider more, but since each one we
1102 : : * choose to consider will generate a round-trip to the remote side, we
1103 : : * need to be a bit cautious here. It would sure be nice to have a local
1104 : : * cache of information about remote index definitions...
1105 : : */
1106 [ + + + + : 952 : foreach(lc, useful_eclass_list)
+ + ]
1107 : : {
1108 : 410 : EquivalenceClass *cur_ec = lfirst(lc);
1109 : : PathKey *pathkey;
1110 : :
1111 : : /* If redundant with what we did above, skip it. */
1112 [ + + ]: 410 : if (cur_ec == query_ec)
1113 : 33 : continue;
1114 : :
1115 : : /* Can't push down the sort if the EC's opfamily is not shippable. */
1656 tgl@sss.pgh.pa.us 1116 [ - + ]: 377 : if (!is_shippable(linitial_oid(cur_ec->ec_opfamilies),
1117 : : OperatorFamilyRelationId, fpinfo))
1656 tgl@sss.pgh.pa.us 1118 :UBC 0 : continue;
1119 : :
1120 : : /* If no pushable expression for this rel, skip it. */
1656 tgl@sss.pgh.pa.us 1121 [ + + ]:CBC 377 : if (find_em_for_rel(root, cur_ec, rel) == NULL)
3947 rhaas@postgresql.org 1122 : 50 : continue;
1123 : :
1124 : : /* Looks like we can generate a pathkey, so let's do it. */
1125 : 327 : pathkey = make_canonical_pathkey(root, cur_ec,
1126 : 327 : linitial_oid(cur_ec->ec_opfamilies),
1127 : : COMPARE_LT,
1128 : : false);
1129 : 327 : useful_pathkeys_list = lappend(useful_pathkeys_list,
1130 : 327 : list_make1(pathkey));
1131 : : }
1132 : :
1133 : 542 : return useful_pathkeys_list;
1134 : : }
1135 : :
1136 : : /*
1137 : : * postgresGetForeignPaths
1138 : : * Create possible scan paths for a scan on the foreign table
1139 : : */
1140 : : static void
4981 tgl@sss.pgh.pa.us 1141 : 1285 : postgresGetForeignPaths(PlannerInfo *root,
1142 : : RelOptInfo *baserel,
1143 : : Oid foreigntableid)
1144 : : {
1145 : 1285 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) baserel->fdw_private;
1146 : : ForeignPath *path;
1147 : : List *ppi_list;
1148 : : ListCell *lc;
1149 : :
1150 : : /*
1151 : : * Create simplest ForeignScan path node and add it to baserel. This path
1152 : : * corresponds to SeqScan path of regular tables (though depending on what
1153 : : * baserestrict conditions we were able to send to remote, there might
1154 : : * actually be an indexscan happening there). We already did all the work
1155 : : * to estimate cost and size of this path.
1156 : : *
1157 : : * Although this path uses no join clauses, it could still have required
1158 : : * parameterization due to LATERAL refs in its tlist.
1159 : : */
4953 1160 : 1285 : path = create_foreignscan_path(root, baserel,
1161 : : NULL, /* default pathtarget */
1162 : : fpinfo->rows,
1163 : : fpinfo->disabled_nodes,
1164 : : fpinfo->startup_cost,
1165 : : fpinfo->total_cost,
1166 : : NIL, /* no pathkeys */
1167 : : baserel->lateral_relids,
1168 : : NULL, /* no extra plan */
1169 : : NIL, /* no fdw_restrictinfo list */
1170 : : NIL); /* no fdw_private list */
1171 : 1285 : add_path(baserel, (Path *) path);
1172 : :
1173 : : /* Add paths with pathkeys */
1154 efujita@postgresql.o 1174 : 1285 : add_paths_with_pathkeys_for_rel(root, baserel, NULL, NIL);
1175 : :
1176 : : /*
1177 : : * If we're not using remote estimates, stop here. We have no way to
1178 : : * estimate whether any join clauses would be worth sending across, so
1179 : : * don't bother building parameterized paths.
1180 : : */
4953 tgl@sss.pgh.pa.us 1181 [ + + ]: 1285 : if (!fpinfo->use_remote_estimate)
1182 : 970 : return;
1183 : :
1184 : : /*
1185 : : * Thumb through all join clauses for the rel to identify which outer
1186 : : * relations could supply one or more safe-to-send-to-remote join clauses.
1187 : : * We'll build a parameterized path for each such outer relation.
1188 : : *
1189 : : * It's convenient to manage this by representing each candidate outer
1190 : : * relation by the ParamPathInfo node for it. We can then use the
1191 : : * ppi_clauses list in the ParamPathInfo node directly as a list of the
1192 : : * interesting join clauses for that rel. This takes care of the
1193 : : * possibility that there are multiple safe join clauses for such a rel,
1194 : : * and also ensures that we account for unsafe join clauses that we'll
1195 : : * still have to enforce locally (since the parameterized-path machinery
1196 : : * insists that we handle all movable clauses).
1197 : : */
4602 1198 : 315 : ppi_list = NIL;
4953 1199 [ + + + + : 456 : foreach(lc, baserel->joininfo)
+ + ]
1200 : : {
1201 : 141 : RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
1202 : : Relids required_outer;
1203 : : ParamPathInfo *param_info;
1204 : :
1205 : : /* Check if clause can be moved to this rel */
4804 1206 [ + + ]: 141 : if (!join_clause_is_movable_to(rinfo, baserel))
4953 1207 : 96 : continue;
1208 : :
1209 : : /* See if it is safe to send to remote */
54 akorotkov@postgresql 1210 [ + + ]:GNC 45 : if (!is_foreign_expr(root, baserel, fpinfo, rinfo->clause))
4953 tgl@sss.pgh.pa.us 1211 :CBC 7 : continue;
1212 : :
1213 : : /* Calculate required outer rels for the resulting path */
1214 : 38 : required_outer = bms_union(rinfo->clause_relids,
1215 : 38 : baserel->lateral_relids);
1216 : : /* We do not want the foreign rel itself listed in required_outer */
1217 : 38 : required_outer = bms_del_member(required_outer, baserel->relid);
1218 : :
1219 : : /*
1220 : : * required_outer probably can't be empty here, but if it were, we
1221 : : * couldn't make a parameterized path.
1222 : : */
1223 [ - + ]: 38 : if (bms_is_empty(required_outer))
4602 tgl@sss.pgh.pa.us 1224 :UBC 0 : continue;
1225 : :
1226 : : /* Get the ParamPathInfo */
4602 tgl@sss.pgh.pa.us 1227 :CBC 38 : param_info = get_baserel_parampathinfo(root, baserel,
1228 : : required_outer);
1229 [ - + ]: 38 : Assert(param_info != NULL);
1230 : :
1231 : : /*
1232 : : * Add it to list unless we already have it. Testing pointer equality
1233 : : * is OK since get_baserel_parampathinfo won't make duplicates.
1234 : : */
1235 : 38 : ppi_list = list_append_unique_ptr(ppi_list, param_info);
1236 : : }
1237 : :
1238 : : /*
1239 : : * The above scan examined only "generic" join clauses, not those that
1240 : : * were absorbed into EquivalenceClauses. See if we can make anything out
1241 : : * of EquivalenceClauses.
1242 : : */
4953 1243 [ + + ]: 315 : if (baserel->has_eclass_joins)
1244 : : {
1245 : : /*
1246 : : * We repeatedly scan the eclass list looking for column references
1247 : : * (or expressions) belonging to the foreign rel. Each time we find
1248 : : * one, we generate a list of equivalence joinclauses for it, and then
1249 : : * see if any are safe to send to the remote. Repeat till there are
1250 : : * no more candidate EC members.
1251 : : */
1252 : : ec_member_foreign_arg arg;
1253 : :
1254 : 147 : arg.already_used = NIL;
1255 : : for (;;)
1256 : 149 : {
1257 : : List *clauses;
1258 : :
1259 : : /* Make clauses, skipping any that join to lateral_referencers */
1260 : 296 : arg.current = NULL;
1261 : 296 : clauses = generate_implied_equalities_for_column(root,
1262 : : baserel,
1263 : : ec_member_matches_foreign,
1264 : : &arg,
1265 : : baserel->lateral_referencers);
1266 : :
1267 : : /* Done if there are no more expressions in the foreign rel */
1268 [ + + ]: 296 : if (arg.current == NULL)
1269 : : {
1270 [ - + ]: 147 : Assert(clauses == NIL);
1271 : 147 : break;
1272 : : }
1273 : :
1274 : : /* Scan the extracted join clauses */
1275 [ + - + + : 334 : foreach(lc, clauses)
+ + ]
1276 : : {
1277 : 185 : RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
1278 : : Relids required_outer;
1279 : : ParamPathInfo *param_info;
1280 : :
1281 : : /* Check if clause can be moved to this rel */
4804 1282 [ - + ]: 185 : if (!join_clause_is_movable_to(rinfo, baserel))
4953 tgl@sss.pgh.pa.us 1283 :UBC 0 : continue;
1284 : :
1285 : : /* See if it is safe to send to remote */
54 akorotkov@postgresql 1286 [ + + ]:GNC 185 : if (!is_foreign_expr(root, baserel, fpinfo, rinfo->clause))
4953 tgl@sss.pgh.pa.us 1287 :CBC 7 : continue;
1288 : :
1289 : : /* Calculate required outer rels for the resulting path */
1290 : 178 : required_outer = bms_union(rinfo->clause_relids,
1291 : 178 : baserel->lateral_relids);
1292 : 178 : required_outer = bms_del_member(required_outer, baserel->relid);
1293 [ - + ]: 178 : if (bms_is_empty(required_outer))
4602 tgl@sss.pgh.pa.us 1294 :UBC 0 : continue;
1295 : :
1296 : : /* Get the ParamPathInfo */
4602 tgl@sss.pgh.pa.us 1297 :CBC 178 : param_info = get_baserel_parampathinfo(root, baserel,
1298 : : required_outer);
1299 [ - + ]: 178 : Assert(param_info != NULL);
1300 : :
1301 : : /* Add it to list unless we already have it */
1302 : 178 : ppi_list = list_append_unique_ptr(ppi_list, param_info);
1303 : : }
1304 : :
1305 : : /* Try again, now ignoring the expression we found this time */
4953 1306 : 149 : arg.already_used = lappend(arg.already_used, arg.current);
1307 : : }
1308 : : }
1309 : :
1310 : : /*
1311 : : * Now build a path for each useful outer relation.
1312 : : */
4602 1313 [ + + + + : 521 : foreach(lc, ppi_list)
+ + ]
1314 : : {
1315 : 206 : ParamPathInfo *param_info = (ParamPathInfo *) lfirst(lc);
1316 : : double rows;
1317 : : int width;
1318 : : int disabled_nodes;
1319 : : Cost startup_cost;
1320 : : Cost total_cost;
1321 : :
1322 : : /* Get a cost estimate from the remote */
1323 : 206 : estimate_path_cost_size(root, baserel,
1324 : : param_info->ppi_clauses, NIL, NULL,
1325 : : &rows, &width, &disabled_nodes,
1326 : : &startup_cost, &total_cost);
1327 : :
1328 : : /*
1329 : : * ppi_rows currently won't get looked at by anything, but still we
1330 : : * may as well ensure that it matches our idea of the rowcount.
1331 : : */
1332 : 206 : param_info->ppi_rows = rows;
1333 : :
1334 : : /* Make the path */
1335 : 206 : path = create_foreignscan_path(root, baserel,
1336 : : NULL, /* default pathtarget */
1337 : : rows,
1338 : : disabled_nodes,
1339 : : startup_cost,
1340 : : total_cost,
1341 : : NIL, /* no pathkeys */
1342 : : param_info->ppi_req_outer,
1343 : : NULL,
1344 : : NIL, /* no fdw_restrictinfo list */
1345 : : NIL); /* no fdw_private list */
1346 : 206 : add_path(baserel, (Path *) path);
1347 : : }
1348 : : }
1349 : :
1350 : : /*
1351 : : * postgresGetForeignPlan
1352 : : * Create ForeignScan plan node which implements selected best path
1353 : : */
1354 : : static ForeignScan *
4981 1355 : 1095 : postgresGetForeignPlan(PlannerInfo *root,
1356 : : RelOptInfo *foreignrel,
1357 : : Oid foreigntableid,
1358 : : ForeignPath *best_path,
1359 : : List *tlist,
1360 : : List *scan_clauses,
1361 : : Plan *outer_plan)
1362 : : {
3898 rhaas@postgresql.org 1363 : 1095 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
1364 : : Index scan_relid;
1365 : : List *fdw_private;
4015 1366 : 1095 : List *remote_exprs = NIL;
4981 tgl@sss.pgh.pa.us 1367 : 1095 : List *local_exprs = NIL;
4953 1368 : 1095 : List *params_list = NIL;
3471 1369 : 1095 : List *fdw_scan_tlist = NIL;
1370 : 1095 : List *fdw_recheck_quals = NIL;
1371 : : List *retrieved_attrs;
1372 : : StringInfoData sql;
2750 efujita@postgresql.o 1373 : 1095 : bool has_final_sort = false;
1374 : 1095 : bool has_limit = false;
1375 : : ListCell *lc;
1376 : :
1377 : : /*
1378 : : * Get FDW private data created by postgresGetForeignUpperPaths(), if any.
1379 : : */
1380 [ + + ]: 1095 : if (best_path->fdw_private)
1381 : : {
1732 peter@eisentraut.org 1382 : 154 : has_final_sort = boolVal(list_nth(best_path->fdw_private,
1383 : : FdwPathPrivateHasFinalSort));
1384 : 154 : has_limit = boolVal(list_nth(best_path->fdw_private,
1385 : : FdwPathPrivateHasLimit));
1386 : : }
1387 : :
3479 rhaas@postgresql.org 1388 [ + + + + ]: 1095 : if (IS_SIMPLE_REL(foreignrel))
1389 : : {
1390 : : /*
1391 : : * For base relations, set scan_relid as the relid of the relation.
1392 : : */
3898 1393 : 779 : scan_relid = foreignrel->relid;
1394 : :
1395 : : /*
1396 : : * In a base-relation scan, we must apply the given scan_clauses.
1397 : : *
1398 : : * Separate the scan_clauses into those that can be executed remotely
1399 : : * and those that can't. baserestrictinfo clauses that were
1400 : : * previously determined to be safe or unsafe by classifyConditions
1401 : : * are found in fpinfo->remote_conds and fpinfo->local_conds. Anything
1402 : : * else in the scan_clauses list will be a join clause, which we have
1403 : : * to check for remote-safety.
1404 : : *
1405 : : * Note: the join clauses we see here should be the exact same ones
1406 : : * previously examined by postgresGetForeignPaths. Possibly it'd be
1407 : : * worth passing forward the classification work done then, rather
1408 : : * than repeating it here.
1409 : : *
1410 : : * This code must match "extract_actual_clauses(scan_clauses, false)"
1411 : : * except for the additional decision about remote versus local
1412 : : * execution.
1413 : : */
3471 tgl@sss.pgh.pa.us 1414 [ + + + + : 1166 : foreach(lc, scan_clauses)
+ + ]
1415 : : {
1416 : 387 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
1417 : :
1418 : : /* Ignore any pseudoconstants, they're dealt with elsewhere */
1419 [ + + ]: 387 : if (rinfo->pseudoconstant)
1420 : 4 : continue;
1421 : :
1422 [ + + ]: 383 : if (list_member_ptr(fpinfo->remote_conds, rinfo))
1423 : 291 : remote_exprs = lappend(remote_exprs, rinfo->clause);
1424 [ + + ]: 92 : else if (list_member_ptr(fpinfo->local_conds, rinfo))
1425 : 77 : local_exprs = lappend(local_exprs, rinfo->clause);
54 akorotkov@postgresql 1426 [ + + ]:GNC 15 : else if (is_foreign_expr(root, foreignrel, fpinfo, rinfo->clause))
3471 tgl@sss.pgh.pa.us 1427 :CBC 13 : remote_exprs = lappend(remote_exprs, rinfo->clause);
1428 : : else
1429 : 2 : local_exprs = lappend(local_exprs, rinfo->clause);
1430 : : }
1431 : :
1432 : : /*
1433 : : * For a base-relation scan, we have to support EPQ recheck, which
1434 : : * should recheck all the remote quals.
1435 : : */
1436 : 779 : fdw_recheck_quals = remote_exprs;
1437 : : }
1438 : : else
1439 : : {
1440 : : /*
1441 : : * Join relation or upper relation - set scan_relid to 0.
1442 : : */
3898 rhaas@postgresql.org 1443 : 316 : scan_relid = 0;
1444 : :
1445 : : /*
1446 : : * For a join rel, baserestrictinfo is NIL and we are not considering
1447 : : * parameterization right now, so there should be no scan_clauses for
1448 : : * a joinrel or an upper rel either.
1449 : : */
1450 [ - + ]: 316 : Assert(!scan_clauses);
1451 : :
1452 : : /*
1453 : : * Instead we get the conditions to apply from the fdw_private
1454 : : * structure.
1455 : : */
3471 tgl@sss.pgh.pa.us 1456 : 316 : remote_exprs = extract_actual_clauses(fpinfo->remote_conds, false);
1457 : 316 : local_exprs = extract_actual_clauses(fpinfo->local_conds, false);
1458 : :
1459 : : /*
1460 : : * We leave fdw_recheck_quals empty in this case, since we never need
1461 : : * to apply EPQ recheck clauses. In the case of a joinrel, EPQ
1462 : : * recheck is handled elsewhere --- see postgresGetForeignJoinPaths().
1463 : : * If we're planning an upperrel (ie, remote grouping or aggregation)
1464 : : * then there's no EPQ to do because SELECT FOR UPDATE wouldn't be
1465 : : * allowed, and indeed we *can't* put the remote clauses into
1466 : : * fdw_recheck_quals because the unaggregated Vars won't be available
1467 : : * locally.
1468 : : */
1469 : :
1470 : : /* Build the list of columns to be fetched from the foreign server. */
3898 rhaas@postgresql.org 1471 : 316 : fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
1472 : :
1473 : : /*
1474 : : * Ensure that the outer plan produces a tuple whose descriptor
1475 : : * matches our scan tuple slot. Also, remove the local conditions
1476 : : * from outer plan's quals, lest they be evaluated twice, once by the
1477 : : * local plan and once by the scan.
1478 : : */
1479 [ + + ]: 316 : if (outer_plan)
1480 : : {
1481 : : /*
1482 : : * Right now, we only consider grouping and aggregation beyond
1483 : : * joins. Queries involving aggregates or grouping do not require
1484 : : * EPQ mechanism, hence should not have an outer plan here.
1485 : : */
3479 1486 [ + - - + ]: 29 : Assert(!IS_UPPER_REL(foreignrel));
1487 : :
1488 : : /*
1489 : : * First, update the plan's qual list if possible. In some cases
1490 : : * the quals might be enforced below the topmost plan level, in
1491 : : * which case we'll fail to remove them; it's not worth working
1492 : : * harder than this.
1493 : : */
3898 1494 [ + + + + : 32 : foreach(lc, local_exprs)
+ + ]
1495 : : {
1496 : 3 : Node *qual = lfirst(lc);
1497 : :
1498 : 3 : outer_plan->qual = list_delete(outer_plan->qual, qual);
1499 : :
1500 : : /*
1501 : : * For an inner join the local conditions of foreign scan plan
1502 : : * can be part of the joinquals as well. (They might also be
1503 : : * in the mergequals or hashquals, but we can't touch those
1504 : : * without breaking the plan.)
1505 : : */
2861 tgl@sss.pgh.pa.us 1506 [ + + ]: 3 : if (IsA(outer_plan, NestLoop) ||
1507 [ + - ]: 1 : IsA(outer_plan, MergeJoin) ||
1508 [ - + ]: 1 : IsA(outer_plan, HashJoin))
1509 : : {
1510 : 2 : Join *join_plan = (Join *) outer_plan;
1511 : :
1512 [ + - ]: 2 : if (join_plan->jointype == JOIN_INNER)
1513 : 2 : join_plan->joinqual = list_delete(join_plan->joinqual,
1514 : : qual);
1515 : : }
1516 : : }
1517 : :
1518 : : /*
1519 : : * Now fix the subplan's tlist --- this might result in inserting
1520 : : * a Result node atop the plan tree.
1521 : : */
1522 : 29 : outer_plan = change_plan_targetlist(outer_plan, fdw_scan_tlist,
1523 : 29 : best_path->path.parallel_safe);
1524 : : }
1525 : : }
1526 : :
1527 : : /*
1528 : : * Build the query string to be sent for execution, and identify
1529 : : * expressions to be sent as parameters.
1530 : : */
4953 1531 : 1095 : initStringInfo(&sql);
3898 rhaas@postgresql.org 1532 : 1095 : deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
1533 : : remote_exprs, best_path->path.pathkeys,
1534 : : has_final_sort, has_limit, false,
1535 : : &retrieved_attrs, ¶ms_list);
1536 : :
1537 : : /* Remember remote_exprs for possible use by postgresPlanDirectModify */
3471 tgl@sss.pgh.pa.us 1538 : 1095 : fpinfo->final_remote_exprs = remote_exprs;
1539 : :
1540 : : /*
1541 : : * Build the fdw_private list that will be available to the executor.
1542 : : * Items in the list must match order in enum FdwScanPrivateIndex.
1543 : : */
1544 : 1095 : fdw_private = list_make3(makeString(sql.data),
1545 : : retrieved_attrs,
1546 : : makeInteger(fpinfo->fetch_size));
1547 : :
1548 : : /*
1549 : : * Position FdwScanPrivateRelations: either the EXPLAIN relation string
1550 : : * (joins/upper rels) or a NULL placeholder, so that subsequent indexes
1551 : : * stay valid for the base-rel scan case.
1552 : : */
3479 rhaas@postgresql.org 1553 [ + + + + : 1095 : if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
+ + + + ]
3898 1554 : 316 : fdw_private = lappend(fdw_private,
2506 tgl@sss.pgh.pa.us 1555 : 316 : makeString(fpinfo->relation_name));
1556 : : else
54 akorotkov@postgresql 1557 :GNC 779 : fdw_private = lappend(fdw_private, NULL);
1558 : :
1559 : : /*
1560 : : * FdwScanPrivateFunctions / FdwScanPrivateMinRTIndex carry the metadata
1561 : : * the executor needs to rebuild TupleDesc entries for whole-row Vars
1562 : : * pointing at RTE_FUNCTION rels absorbed into the foreign scan.
1563 : : */
1564 : 1095 : fdw_private = lappend(fdw_private, get_functions_data(root, foreignrel));
1565 : 1095 : fdw_private = lappend(fdw_private,
1566 : 1095 : makeInteger(get_min_base_rti(root, foreignrel)));
1567 : :
1568 : : /*
1569 : : * Create the ForeignScan node for the given relation.
1570 : : *
1571 : : * Note that the remote parameter expressions are stored in the fdw_exprs
1572 : : * field of the finished plan node; we can't keep them in private state
1573 : : * because then they wouldn't be subject to later planner processing.
1574 : : */
4981 tgl@sss.pgh.pa.us 1575 :CBC 1095 : return make_foreignscan(tlist,
1576 : : local_exprs,
1577 : : scan_relid,
1578 : : params_list,
1579 : : fdw_private,
1580 : : fdw_scan_tlist,
1581 : : fdw_recheck_quals,
1582 : : outer_plan);
1583 : : }
1584 : :
1585 : : /*
1586 : : * Construct a tuple descriptor for the scan tuples handled by a foreign join.
1587 : : *
1588 : : * 'rtfuncdata' is the FdwScanPrivateFunctions list saved at plan time, and
1589 : : * 'rtoffset' is the difference between the executor's RT indexes and the
1590 : : * scan-local RT indexes captured in that list. Both may be 0/NIL when the
1591 : : * scan has no RTE_FUNCTION dependents.
1592 : : */
1593 : : static TupleDesc
54 akorotkov@postgresql 1594 :GNC 177 : get_tupdesc_for_join_scan_tuples(ForeignScanState *node,
1595 : : List *rtfuncdata, int rtoffset)
1596 : : {
1956 tgl@sss.pgh.pa.us 1597 :CBC 177 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
1598 : 177 : EState *estate = node->ss.ps.state;
1599 : : TupleDesc tupdesc;
1600 : :
1601 : : /*
1602 : : * The core code has already set up a scan tuple slot based on
1603 : : * fsplan->fdw_scan_tlist, and this slot's tupdesc is mostly good enough,
1604 : : * but there's one case where it isn't. If we have any whole-row row
1605 : : * identifier Vars, they may have vartype RECORD, and we need to replace
1606 : : * that with the associated table's actual composite type. This ensures
1607 : : * that when we read those ROW() expression values from the remote server,
1608 : : * we can convert them to a composite type the local server knows.
1609 : : */
1610 : 177 : tupdesc = CreateTupleDescCopy(node->ss.ss_ScanTupleSlot->tts_tupleDescriptor);
1611 [ + + ]: 732 : for (int i = 0; i < tupdesc->natts; i++)
1612 : : {
1613 : 555 : Form_pg_attribute att = TupleDescAttr(tupdesc, i);
1614 : : Var *var;
1615 : : RangeTblEntry *rte;
1616 : : Oid reltype;
1617 : :
1618 : : /* Nothing to do if it's not a generic RECORD attribute */
1619 [ + + - + ]: 555 : if (att->atttypid != RECORDOID || att->atttypmod >= 0)
1620 : 545 : continue;
1621 : :
1622 : : /*
1623 : : * If we can't identify the referenced table, do nothing. This'll
1624 : : * likely lead to failure later, but perhaps we can muddle through.
1625 : : */
1626 : 10 : var = (Var *) list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
1627 : : i)->expr;
1628 [ + - - + ]: 10 : if (!IsA(var, Var) || var->varattno != 0)
1956 tgl@sss.pgh.pa.us 1629 :UBC 0 : continue;
1956 tgl@sss.pgh.pa.us 1630 :CBC 10 : rte = list_nth(estate->es_range_table, var->varno - 1);
1631 : :
54 akorotkov@postgresql 1632 [ + + ]:GNC 10 : if (rte->rtekind == RTE_RELATION)
1633 : : {
1634 : 5 : reltype = get_rel_type_id(rte->relid);
1635 [ - + ]: 5 : if (!OidIsValid(reltype))
54 akorotkov@postgresql 1636 :UNC 0 : continue;
54 akorotkov@postgresql 1637 :GNC 5 : att->atttypid = reltype;
1638 : : /* shouldn't need to change anything else */
1639 : : }
1640 [ + - ]: 5 : else if (rte->rtekind == RTE_FUNCTION)
1641 : : {
1642 : : /*
1643 : : * A whole-row Var points at a FUNCTION RTE absorbed into the
1644 : : * foreign join. Synthesize an anonymous composite TupleDesc from
1645 : : * the per-function return-type metadata we saved at plan time;
1646 : : * the deparser emits these as ROW(f<rti>.c1, f<rti>.c2, ...).
1647 : : *
1648 : : * For an upperrel scan (the only path that reaches here)
1649 : : * postgresGetForeignPlan always builds rtfuncdata via
1650 : : * get_functions_data(), so it is never NIL; the per-RTE slot for
1651 : : * the function RTE referenced by this Var must likewise be
1652 : : * populated.
1653 : : */
1654 : : List *funcdata;
1655 : : TupleDesc rte_tupdesc;
1656 : : int num_funcs;
1657 : : int attnum;
1658 : : ListCell *lc1,
1659 : : *lc2;
1660 : :
1661 [ - + ]: 5 : Assert(rtfuncdata != NIL);
1662 : 5 : funcdata = list_nth(rtfuncdata, var->varno - rtoffset);
1663 [ - + ]: 5 : Assert(funcdata != NIL);
1664 : 5 : num_funcs = list_length(funcdata);
1665 [ - + ]: 5 : Assert(num_funcs == list_length(rte->eref->colnames));
1666 : 5 : rte_tupdesc = CreateTemplateTupleDesc(num_funcs);
1667 : :
1668 : 5 : attnum = 1;
1669 [ + - + + : 14 : forboth(lc1, funcdata, lc2, rte->eref->colnames)
+ - + + +
+ + - +
+ ]
1670 : : {
1671 : 9 : List *fdata = lfirst_node(List, lc1);
1672 : 9 : char *colname = strVal(lfirst(lc2));
1673 : : Oid funcrettype;
1674 : : Oid funccollation;
1675 : :
1676 : 9 : funcrettype = lsecond_node(Integer, fdata)->ival;
1677 : 9 : funccollation = lthird_node(Integer, fdata)->ival;
1678 : :
1679 : : /* get_functions_data() already validated the return type. */
1680 [ + - - + ]: 9 : Assert(OidIsValid(funcrettype) && funcrettype != RECORDOID);
1681 : :
1682 : 9 : TupleDescInitEntry(rte_tupdesc, (AttrNumber) attnum, colname,
1683 : : funcrettype, -1, 0);
1684 : 9 : TupleDescInitEntryCollation(rte_tupdesc, (AttrNumber) attnum,
1685 : : funccollation);
1686 : 9 : attnum++;
1687 : : }
1688 : :
1689 : 5 : assign_record_type_typmod(rte_tupdesc);
1690 : 5 : att->atttypmod = rte_tupdesc->tdtypmod;
1691 : : }
1692 : : }
1956 tgl@sss.pgh.pa.us 1693 :CBC 177 : return tupdesc;
1694 : : }
1695 : :
1696 : : /*
1697 : : * postgresBeginForeignScan
1698 : : * Initiate an executor scan of a foreign PostgreSQL table.
1699 : : */
1700 : : static void
4981 1701 : 985 : postgresBeginForeignScan(ForeignScanState *node, int eflags)
1702 : : {
1703 : 985 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
1704 : 985 : EState *estate = node->ss.ps.state;
1705 : : PgFdwScanState *fsstate;
1706 : : RangeTblEntry *rte;
1707 : : Oid userid;
1708 : : ForeignTable *table;
1709 : : UserMapping *user;
1710 : : int rtindex;
1711 : : int numParams;
1712 : :
1713 : : /*
1714 : : * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
1715 : : */
1716 [ + + ]: 985 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
1717 : 426 : return;
1718 : :
1719 : : /*
1720 : : * We'll save private state in node->fdw_state.
1721 : : */
305 michael@paquier.xyz 1722 : 559 : fsstate = palloc0_object(PgFdwScanState);
683 peter@eisentraut.org 1723 : 559 : node->fdw_state = fsstate;
1724 : :
1725 : : /*
1726 : : * Identify which user to do the remote access as. This should match what
1727 : : * ExecCheckPermissions() does. For a join, scan the base relids until we
1728 : : * find an RTE_RELATION (the foreign-table side); ignore any RTE_FUNCTION
1729 : : * absorbed into the join, which contributes no relation OID to look up.
1730 : : */
1412 alvherre@alvh.no-ip. 1731 [ + + ]: 559 : userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
54 akorotkov@postgresql 1732 :GNC 559 : rte = NULL;
3898 rhaas@postgresql.org 1733 [ + + ]:CBC 559 : if (fsplan->scan.scanrelid > 0)
1734 : : {
3741 tgl@sss.pgh.pa.us 1735 : 383 : rtindex = fsplan->scan.scanrelid;
54 akorotkov@postgresql 1736 :GNC 383 : rte = exec_rt_fetch(rtindex, estate);
1737 : : }
1738 : : else
1739 : : {
1740 : 176 : rtindex = -1;
1741 [ + - ]: 179 : while ((rtindex = bms_next_member(fsplan->fs_base_relids, rtindex)) >= 0)
1742 : : {
1743 : 179 : rte = exec_rt_fetch(rtindex, estate);
1744 [ + - + + ]: 179 : if (rte != NULL && rte->rtekind == RTE_RELATION)
1745 : 176 : break;
1746 : 3 : rte = NULL;
1747 : : }
1748 [ - + ]: 176 : if (rte == NULL)
54 akorotkov@postgresql 1749 [ # # ]:UNC 0 : elog(ERROR, "could not locate a foreign relation RTE in foreign scan");
1750 : : }
1751 : :
1752 : : /* Get info about foreign table. */
3741 tgl@sss.pgh.pa.us 1753 :CBC 559 : table = GetForeignTable(rte->relid);
1754 : 559 : user = GetUserMapping(userid, table->serverid);
1755 : :
1756 : : /*
1757 : : * Get connection to the foreign server. Connection manager will
1758 : : * establish new connection if necessary.
1759 : : */
2021 efujita@postgresql.o 1760 : 559 : fsstate->conn = GetConnection(user, false, &fsstate->conn_state);
1761 : :
1762 : : /* Assign a unique ID for my cursor */
4964 tgl@sss.pgh.pa.us 1763 : 549 : fsstate->cursor_number = GetCursorNumber(fsstate->conn);
1764 : 549 : fsstate->cursor_exists = false;
1765 : :
1766 : : /* Get private info created by planner functions. */
4952 1767 : 549 : fsstate->query = strVal(list_nth(fsplan->fdw_private,
1768 : : FdwScanPrivateSelectSql));
1769 : 549 : fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
1770 : : FdwScanPrivateRetrievedAttrs);
3904 rhaas@postgresql.org 1771 : 549 : fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
1772 : : FdwScanPrivateFetchSize));
1773 : :
1774 : : /* Create contexts for batches of tuples and per-tuple temp workspace. */
4964 tgl@sss.pgh.pa.us 1775 : 549 : fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
1776 : : "postgres_fdw tuple data",
1777 : : ALLOCSET_DEFAULT_SIZES);
1778 : 549 : fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
1779 : : "postgres_fdw temporary data",
1780 : : ALLOCSET_SMALL_SIZES);
1781 : :
1782 : : /*
1783 : : * Get info we'll need for converting data fetched from the foreign server
1784 : : * into local representation and error reporting during that process.
1785 : : */
3898 rhaas@postgresql.org 1786 [ + + ]: 549 : if (fsplan->scan.scanrelid > 0)
1787 : : {
3741 tgl@sss.pgh.pa.us 1788 : 374 : fsstate->rel = node->ss.ss_currentRelation;
3898 rhaas@postgresql.org 1789 : 374 : fsstate->tupdesc = RelationGetDescr(fsstate->rel);
1790 : : }
1791 : : else
1792 : : {
54 akorotkov@postgresql 1793 :GNC 175 : List *rtfuncdata = (List *) list_nth(fsplan->fdw_private,
1794 : : FdwScanPrivateFunctions);
1795 : 175 : int min_base_rti = intVal(list_nth(fsplan->fdw_private,
1796 : : FdwScanPrivateMinRTIndex));
1797 : 175 : int rtoffset = bms_next_member(fsplan->fs_base_relids, -1) -
1798 : : min_base_rti;
1799 : :
1800 [ - + ]: 175 : Assert(min_base_rti > 0);
1801 [ - + ]: 175 : Assert(rtoffset >= 0);
1802 : :
3741 tgl@sss.pgh.pa.us 1803 :CBC 175 : fsstate->rel = NULL;
54 akorotkov@postgresql 1804 :GNC 175 : fsstate->tupdesc = get_tupdesc_for_join_scan_tuples(node, rtfuncdata,
1805 : : rtoffset);
1806 : : }
1807 : :
3898 rhaas@postgresql.org 1808 :CBC 549 : fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
1809 : :
1810 : : /*
1811 : : * Prepare for processing of parameters used in remote query, if any.
1812 : : */
3860 1813 : 549 : numParams = list_length(fsplan->fdw_exprs);
1814 : 549 : fsstate->numParams = numParams;
4981 tgl@sss.pgh.pa.us 1815 [ + + ]: 549 : if (numParams > 0)
3860 rhaas@postgresql.org 1816 : 31 : prepare_query_params((PlanState *) node,
1817 : : fsplan->fdw_exprs,
1818 : : numParams,
1819 : : &fsstate->param_flinfo,
1820 : : &fsstate->param_exprs,
1821 : : &fsstate->param_values);
1822 : :
1823 : : /* Set the async-capable flag */
1979 efujita@postgresql.o 1824 : 549 : fsstate->async_capable = node->ss.ps.async_capable;
1825 : : }
1826 : :
1827 : : /*
1828 : : * postgresIterateForeignScan
1829 : : * Retrieve next row from the result set, or clear tuple slot to indicate
1830 : : * EOF.
1831 : : */
1832 : : static TupleTableSlot *
4981 tgl@sss.pgh.pa.us 1833 : 70961 : postgresIterateForeignScan(ForeignScanState *node)
1834 : : {
4964 1835 : 70961 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
4981 1836 : 70961 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
1837 : :
1838 : : /*
1839 : : * In sync mode, if this is the first call after Begin or ReScan, we need
1840 : : * to create the cursor on the remote side. In async mode, we would have
1841 : : * already created the cursor before we get here, even if this is the
1842 : : * first call after Begin or ReScan.
1843 : : */
4964 1844 [ + + ]: 70961 : if (!fsstate->cursor_exists)
4981 1845 : 831 : create_cursor(node);
1846 : :
1847 : : /*
1848 : : * Get some more tuples, if we've run out.
1849 : : */
4964 1850 [ + + ]: 70958 : if (fsstate->next_tuple >= fsstate->num_tuples)
1851 : : {
1852 : : /* In async mode, just clear tuple slot. */
2021 efujita@postgresql.o 1853 [ + + ]: 2139 : if (fsstate->async_capable)
1854 : 32 : return ExecClearTuple(slot);
1855 : : /* No point in another fetch if we already detected EOF, though. */
4964 tgl@sss.pgh.pa.us 1856 [ + + ]: 2107 : if (!fsstate->eof_reached)
4981 1857 : 1408 : fetch_more_data(node);
1858 : : /* If we didn't get any tuples, must be end of data. */
4964 1859 [ + + ]: 2093 : if (fsstate->next_tuple >= fsstate->num_tuples)
4981 1860 : 790 : return ExecClearTuple(slot);
1861 : : }
1862 : :
1863 : : /*
1864 : : * Return the next tuple.
1865 : : */
2939 andres@anarazel.de 1866 : 70122 : ExecStoreHeapTuple(fsstate->tuples[fsstate->next_tuple++],
1867 : : slot,
1868 : : false);
1869 : :
4981 tgl@sss.pgh.pa.us 1870 : 70122 : return slot;
1871 : : }
1872 : :
1873 : : /*
1874 : : * postgresReScanForeignScan
1875 : : * Restart the scan.
1876 : : */
1877 : : static void
1878 : 445 : postgresReScanForeignScan(ForeignScanState *node)
1879 : : {
4964 1880 : 445 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
1881 : : char sql[64];
1882 : : PGresult *res;
1883 : :
1884 : : /* If we haven't created the cursor yet, nothing to do. */
1885 [ + + ]: 445 : if (!fsstate->cursor_exists)
4981 1886 : 57 : return;
1887 : :
1888 : : /*
1889 : : * If the node is async-capable, any asynchronous fetch made for it should
1890 : : * have been processed before we get here (see ExecAppendAsyncReset()).
1891 : : */
66 efujita@postgresql.o 1892 [ + + - + : 401 : Assert(!fsstate->async_capable || !fsstate->conn_state->pendingAreq ||
- - ]
1893 : : fsstate->conn_state->pendingAreq->requestee != (PlanState *) node);
1894 : :
1895 : : /*
1896 : : * If any internal parameters affecting this node have changed, we'd
1897 : : * better destroy and recreate the cursor. Otherwise, if the remote
1898 : : * server is v14 or older, rewinding it should be good enough; if not,
1899 : : * rewind is only allowed for scrollable cursors, but we don't have a way
1900 : : * to check the scrollability of it, so destroy and recreate it in any
1901 : : * case. If we've only fetched zero or one batch, we needn't even rewind
1902 : : * the cursor, just rescan what we have.
1903 : : */
4981 tgl@sss.pgh.pa.us 1904 [ + + ]: 401 : if (node->ss.ps.chgParam != NULL)
1905 : : {
4964 1906 : 369 : fsstate->cursor_exists = false;
4981 1907 : 369 : snprintf(sql, sizeof(sql), "CLOSE c%u",
1908 : : fsstate->cursor_number);
1909 : : }
4964 1910 [ + + ]: 32 : else if (fsstate->fetch_ct_2 > 1)
1911 : : {
815 efujita@postgresql.o 1912 [ - + ]: 19 : if (PQserverVersion(fsstate->conn) < 150000)
815 efujita@postgresql.o 1913 :UBC 0 : snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u",
1914 : : fsstate->cursor_number);
1915 : : else
1916 : : {
815 efujita@postgresql.o 1917 :CBC 19 : fsstate->cursor_exists = false;
1918 : 19 : snprintf(sql, sizeof(sql), "CLOSE c%u",
1919 : : fsstate->cursor_number);
1920 : : }
1921 : : }
1922 : : else
1923 : : {
1924 : : /* Easy: just rescan what we already have in memory, if anything */
4964 tgl@sss.pgh.pa.us 1925 : 13 : fsstate->next_tuple = 0;
4981 1926 : 13 : return;
1927 : : }
1928 : :
2021 efujita@postgresql.o 1929 : 388 : res = pgfdw_exec_query(fsstate->conn, sql, fsstate->conn_state);
4981 tgl@sss.pgh.pa.us 1930 [ - + ]: 388 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 tgl@sss.pgh.pa.us 1931 :UBC 0 : pgfdw_report_error(res, fsstate->conn, sql);
4981 tgl@sss.pgh.pa.us 1932 :CBC 388 : PQclear(res);
1933 : :
1934 : : /* Now force a fresh FETCH. */
4964 1935 : 388 : fsstate->tuples = NULL;
1936 : 388 : fsstate->num_tuples = 0;
1937 : 388 : fsstate->next_tuple = 0;
1938 : 388 : fsstate->fetch_ct_2 = 0;
1939 : 388 : fsstate->eof_reached = false;
1940 : : }
1941 : :
1942 : : /*
1943 : : * postgresEndForeignScan
1944 : : * Finish scanning foreign table and dispose objects used for this scan
1945 : : */
1946 : : static void
4981 1947 : 945 : postgresEndForeignScan(ForeignScanState *node)
1948 : : {
4964 1949 : 945 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
1950 : :
1951 : : /* if fsstate is NULL, we are in EXPLAIN; nothing to do */
1952 [ + + ]: 945 : if (fsstate == NULL)
4981 1953 : 426 : return;
1954 : :
1955 : : /* Close the cursor if open, to prevent accumulation of cursors */
4964 1956 [ + + ]: 519 : if (fsstate->cursor_exists)
2021 efujita@postgresql.o 1957 : 490 : close_cursor(fsstate->conn, fsstate->cursor_number,
1958 : : fsstate->conn_state);
1959 : :
1960 : : /* Release remote connection */
4964 tgl@sss.pgh.pa.us 1961 : 518 : ReleaseConnection(fsstate->conn);
1962 : 518 : fsstate->conn = NULL;
1963 : :
1964 : : /* MemoryContexts will be deleted automatically. */
1965 : : }
1966 : :
1967 : : /*
1968 : : * postgresAddForeignUpdateTargets
1969 : : * Add resjunk column(s) needed for update/delete on a foreign table
1970 : : */
1971 : : static void
2021 1972 : 194 : postgresAddForeignUpdateTargets(PlannerInfo *root,
1973 : : Index rtindex,
1974 : : RangeTblEntry *target_rte,
1975 : : Relation target_relation)
1976 : : {
1977 : : Var *var;
1978 : :
1979 : : /*
1980 : : * In postgres_fdw, what we need is the ctid, same as for a regular table.
1981 : : */
1982 : :
1983 : : /* Make a Var representing the desired value */
1984 : 194 : var = makeVar(rtindex,
1985 : : SelfItemPointerAttributeNumber,
1986 : : TIDOID,
1987 : : -1,
1988 : : InvalidOid,
1989 : : 0);
1990 : :
1991 : : /* Register it as a row-identity column needed by this target rel */
1992 : 194 : add_row_identity_var(root, var, rtindex, "ctid");
4964 1993 : 194 : }
1994 : :
1995 : : /*
1996 : : * postgresPlanForeignModify
1997 : : * Plan an insert/update/delete operation on a foreign table
1998 : : */
1999 : : static List *
2000 : 173 : postgresPlanForeignModify(PlannerInfo *root,
2001 : : ModifyTable *plan,
2002 : : Index resultRelation,
2003 : : int subplan_index)
2004 : : {
2005 : 173 : CmdType operation = plan->operation;
4962 2006 [ + - ]: 173 : RangeTblEntry *rte = planner_rt_fetch(resultRelation, root);
2007 : : Relation targetrel;
2008 : : StringInfoData sql;
4964 2009 : 173 : List *targetAttrs = NIL;
3018 jdavis@postgresql.or 2010 : 173 : List *withCheckOptionList = NIL;
4964 tgl@sss.pgh.pa.us 2011 : 173 : List *returningList = NIL;
4952 2012 : 173 : List *retrieved_attrs = NIL;
4175 andres@anarazel.de 2013 : 173 : bool doNothing = false;
2091 tomas.vondra@postgre 2014 : 173 : int values_end_len = -1;
2015 : :
4964 tgl@sss.pgh.pa.us 2016 : 173 : initStringInfo(&sql);
2017 : :
2018 : : /*
2019 : : * Core code already has some lock on each rel being planned, so we can
2020 : : * use NoLock here.
2021 : : */
34 peter@eisentraut.org 2022 :GNC 173 : targetrel = table_open(rte->relid, NoLock);
2023 : :
2024 : : /*
2025 : : * In an INSERT, we transmit all columns that are defined in the foreign
2026 : : * table. In an UPDATE, if there are BEFORE ROW UPDATE triggers on the
2027 : : * foreign table, we transmit all columns like INSERT; else we transmit
2028 : : * only columns that were explicitly targets of the UPDATE, so as to avoid
2029 : : * unnecessary data transmission. (We can't do that for INSERT since we
2030 : : * would miss sending default values for columns not listed in the source
2031 : : * statement, and for UPDATE if there are BEFORE ROW UPDATE triggers since
2032 : : * those triggers might change values for non-target columns, in which
2033 : : * case we would miss sending changed values for those columns.)
2034 : : */
2678 efujita@postgresql.o 2035 [ + + + + ]:CBC 173 : if (operation == CMD_INSERT ||
2036 : 62 : (operation == CMD_UPDATE &&
34 peter@eisentraut.org 2037 [ + + ]:GNC 62 : targetrel->trigdesc &&
2038 [ + + ]: 18 : targetrel->trigdesc->trig_update_before_row))
4962 tgl@sss.pgh.pa.us 2039 :CBC 104 : {
34 peter@eisentraut.org 2040 :GNC 104 : TupleDesc tupdesc = RelationGetDescr(targetrel);
2041 : : int attnum;
2042 : :
4962 tgl@sss.pgh.pa.us 2043 [ + + ]:CBC 437 : for (attnum = 1; attnum <= tupdesc->natts; attnum++)
2044 : : {
661 drowley@postgresql.o 2045 : 333 : CompactAttribute *attr = TupleDescCompactAttr(tupdesc, attnum - 1);
2046 : :
4962 tgl@sss.pgh.pa.us 2047 [ + + ]: 333 : if (!attr->attisdropped)
2048 : 316 : targetAttrs = lappend_int(targetAttrs, attnum);
2049 : : }
2050 : : }
2051 [ + + ]: 69 : else if (operation == CMD_UPDATE)
2052 : : {
2053 : : int col;
34 peter@eisentraut.org 2054 :GNC 47 : RelOptInfo *baserel = find_base_rel(root, resultRelation);
2055 : 47 : Bitmapset *allUpdatedCols = get_rel_all_updated_cols(root, baserel);
2056 : :
4336 tgl@sss.pgh.pa.us 2057 :CBC 47 : col = -1;
2753 peter@eisentraut.org 2058 [ + + ]: 104 : while ((col = bms_next_member(allUpdatedCols, col)) >= 0)
2059 : : {
2060 : : /* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
4336 tgl@sss.pgh.pa.us 2061 : 57 : AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber;
2062 : :
3400 2063 [ - + ]: 57 : if (attno <= InvalidAttrNumber) /* shouldn't happen */
4964 tgl@sss.pgh.pa.us 2064 [ # # ]:UBC 0 : elog(ERROR, "system-column update is not supported");
4336 tgl@sss.pgh.pa.us 2065 :CBC 57 : targetAttrs = lappend_int(targetAttrs, attno);
2066 : : }
2067 : : }
2068 : :
2069 : : /*
2070 : : * Extract the relevant WITH CHECK OPTION list if any.
2071 : : */
3018 jdavis@postgresql.or 2072 [ + + ]: 173 : if (plan->withCheckOptionLists)
2073 : 16 : withCheckOptionList = (List *) list_nth(plan->withCheckOptionLists,
2074 : : subplan_index);
2075 : :
2076 : : /*
2077 : : * Extract the relevant RETURNING list if any.
2078 : : */
4964 tgl@sss.pgh.pa.us 2079 [ + + ]: 173 : if (plan->returningLists)
2080 : 34 : returningList = (List *) list_nth(plan->returningLists, subplan_index);
2081 : :
2082 : : /*
2083 : : * ON CONFLICT DO NOTHING/SELECT/UPDATE with inference specification
2084 : : * should have already been rejected in the optimizer, as presently there
2085 : : * is no way to recognize an arbiter index on a foreign table. Only DO
2086 : : * NOTHING is supported without an inference specification.
2087 : : */
4175 andres@anarazel.de 2088 [ + + ]: 173 : if (plan->onConflictAction == ONCONFLICT_NOTHING)
2089 : 1 : doNothing = true;
2090 [ - + ]: 172 : else if (plan->onConflictAction != ONCONFLICT_NONE)
4175 andres@anarazel.de 2091 [ # # ]:UBC 0 : elog(ERROR, "unexpected ON CONFLICT specification: %d",
2092 : : (int) plan->onConflictAction);
2093 : :
2094 : : /*
2095 : : * Construct the SQL command string.
2096 : : */
4964 tgl@sss.pgh.pa.us 2097 [ + + + - ]:CBC 173 : switch (operation)
2098 : : {
2099 : 89 : case CMD_INSERT:
34 peter@eisentraut.org 2100 :GNC 89 : deparseInsertSql(&sql, rte, resultRelation, targetrel,
2101 : : targetAttrs, doNothing,
2102 : : withCheckOptionList, returningList,
2103 : : &retrieved_attrs, &values_end_len);
4964 tgl@sss.pgh.pa.us 2104 :CBC 89 : break;
2105 : 62 : case CMD_UPDATE:
34 peter@eisentraut.org 2106 :GNC 62 : deparseUpdateSql(&sql, rte, resultRelation, targetrel,
2107 : : targetAttrs,
2108 : : withCheckOptionList, returningList,
2109 : : &retrieved_attrs);
4964 tgl@sss.pgh.pa.us 2110 :CBC 62 : break;
2111 : 22 : case CMD_DELETE:
34 peter@eisentraut.org 2112 :GNC 22 : deparseDeleteSql(&sql, rte, resultRelation, targetrel,
2113 : : returningList,
2114 : : &retrieved_attrs);
4964 tgl@sss.pgh.pa.us 2115 :CBC 22 : break;
4964 tgl@sss.pgh.pa.us 2116 :UBC 0 : default:
2117 [ # # ]: 0 : elog(ERROR, "unexpected operation: %d", (int) operation);
2118 : : break;
2119 : : }
2120 : :
34 peter@eisentraut.org 2121 :GNC 173 : table_close(targetrel, NoLock);
2122 : :
2123 : : /*
2124 : : * Build the fdw_private list that will be available to the executor.
2125 : : * Items in the list must match enum FdwModifyPrivateIndex, above.
2126 : : */
2091 tomas.vondra@postgre 2127 :CBC 173 : return list_make5(makeString(sql.data),
2128 : : targetAttrs,
2129 : : makeInteger(values_end_len),
2130 : : makeBoolean((retrieved_attrs != NIL)),
2131 : : retrieved_attrs);
2132 : : }
2133 : :
2134 : : /*
2135 : : * postgresBeginForeignModify
2136 : : * Begin an insert/update/delete operation on a foreign table
2137 : : */
2138 : : static void
4964 tgl@sss.pgh.pa.us 2139 : 174 : postgresBeginForeignModify(ModifyTableState *mtstate,
2140 : : ResultRelInfo *resultRelInfo,
2141 : : List *fdw_private,
2142 : : int subplan_index,
2143 : : int eflags)
2144 : : {
2145 : : PgFdwModifyState *fmstate;
2146 : : char *query;
2147 : : List *target_attrs;
2148 : : bool has_returning;
2149 : : int values_end_len;
2150 : : List *retrieved_attrs;
2151 : : RangeTblEntry *rte;
2152 : :
2153 : : /*
2154 : : * Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
2155 : : * stays NULL.
2156 : : */
2157 [ + + ]: 174 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
2158 : 47 : return;
2159 : :
2160 : : /* Deconstruct fdw_private data. */
3111 rhaas@postgresql.org 2161 : 127 : query = strVal(list_nth(fdw_private,
2162 : : FdwModifyPrivateUpdateSql));
2163 : 127 : target_attrs = (List *) list_nth(fdw_private,
2164 : : FdwModifyPrivateTargetAttnums);
2091 tomas.vondra@postgre 2165 : 127 : values_end_len = intVal(list_nth(fdw_private,
2166 : : FdwModifyPrivateLen));
1732 peter@eisentraut.org 2167 : 127 : has_returning = boolVal(list_nth(fdw_private,
2168 : : FdwModifyPrivateHasReturning));
3111 rhaas@postgresql.org 2169 : 127 : retrieved_attrs = (List *) list_nth(fdw_private,
2170 : : FdwModifyPrivateRetrievedAttrs);
2171 : :
2172 : : /* Find RTE. */
2930 tgl@sss.pgh.pa.us 2173 : 127 : rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex,
2174 : : mtstate->ps.state);
2175 : :
2176 : : /* Construct an execution state. */
3111 rhaas@postgresql.org 2177 : 127 : fmstate = create_foreign_modify(mtstate->ps.state,
2178 : : rte,
2179 : : resultRelInfo,
2180 : : mtstate->operation,
2021 tgl@sss.pgh.pa.us 2181 : 127 : outerPlanState(mtstate)->plan,
2182 : : query,
2183 : : target_attrs,
2184 : : values_end_len,
2185 : : has_returning,
2186 : : retrieved_attrs);
2187 : :
4964 2188 : 127 : resultRelInfo->ri_FdwState = fmstate;
2189 : : }
2190 : :
2191 : : /*
2192 : : * postgresExecForeignInsert
2193 : : * Insert one row into a foreign table
2194 : : */
2195 : : static TupleTableSlot *
2196 : 893 : postgresExecForeignInsert(EState *estate,
2197 : : ResultRelInfo *resultRelInfo,
2198 : : TupleTableSlot *slot,
2199 : : TupleTableSlot *planSlot)
2200 : : {
2728 efujita@postgresql.o 2201 : 893 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2202 : : TupleTableSlot **rslot;
1979 tgl@sss.pgh.pa.us 2203 : 893 : int numSlots = 1;
2204 : :
2205 : : /*
2206 : : * If the fmstate has aux_fmstate set, use the aux_fmstate (see
2207 : : * postgresBeginForeignInsert())
2208 : : */
2091 tomas.vondra@postgre 2209 [ - + ]: 893 : if (fmstate->aux_fmstate)
2091 tomas.vondra@postgre 2210 :UBC 0 : resultRelInfo->ri_FdwState = fmstate->aux_fmstate;
2091 tomas.vondra@postgre 2211 :CBC 893 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT,
2212 : : &slot, &planSlot, &numSlots);
2213 : : /* Revert that change */
2214 [ - + ]: 889 : if (fmstate->aux_fmstate)
2091 tomas.vondra@postgre 2215 :UBC 0 : resultRelInfo->ri_FdwState = fmstate;
2216 : :
2091 tomas.vondra@postgre 2217 [ + + ]:CBC 889 : return rslot ? *rslot : NULL;
2218 : : }
2219 : :
2220 : : /*
2221 : : * postgresExecForeignBatchInsert
2222 : : * Insert multiple rows into a foreign table
2223 : : */
2224 : : static TupleTableSlot **
2225 : 42 : postgresExecForeignBatchInsert(EState *estate,
2226 : : ResultRelInfo *resultRelInfo,
2227 : : TupleTableSlot **slots,
2228 : : TupleTableSlot **planSlots,
2229 : : int *numSlots)
2230 : : {
2231 : 42 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2232 : : TupleTableSlot **rslot;
2233 : :
2234 : : /*
2235 : : * If the fmstate has aux_fmstate set, use the aux_fmstate (see
2236 : : * postgresBeginForeignInsert())
2237 : : */
2728 efujita@postgresql.o 2238 [ - + ]: 42 : if (fmstate->aux_fmstate)
2728 efujita@postgresql.o 2239 :UBC 0 : resultRelInfo->ri_FdwState = fmstate->aux_fmstate;
2728 efujita@postgresql.o 2240 :CBC 42 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_INSERT,
2241 : : slots, planSlots, numSlots);
2242 : : /* Revert that change */
2243 [ - + ]: 41 : if (fmstate->aux_fmstate)
2728 efujita@postgresql.o 2244 :UBC 0 : resultRelInfo->ri_FdwState = fmstate;
2245 : :
2728 efujita@postgresql.o 2246 :CBC 41 : return rslot;
2247 : : }
2248 : :
2249 : : /*
2250 : : * postgresGetForeignModifyBatchSize
2251 : : * Determine the maximum number of tuples that can be inserted in bulk
2252 : : *
2253 : : * Returns the batch size specified for server or table. When batching is not
2254 : : * allowed (e.g. for tables with BEFORE/AFTER ROW triggers or with RETURNING
2255 : : * clause), returns 1.
2256 : : */
2257 : : static int
2091 tomas.vondra@postgre 2258 : 147 : postgresGetForeignModifyBatchSize(ResultRelInfo *resultRelInfo)
2259 : : {
2260 : : int batch_size;
1392 efujita@postgresql.o 2261 : 147 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2262 : :
2263 : : /* should be called only once */
2091 tomas.vondra@postgre 2264 [ - + ]: 147 : Assert(resultRelInfo->ri_BatchSize == 0);
2265 : :
2266 : : /*
2267 : : * Should never get called when the insert is being performed on a table
2268 : : * that is also among the target relations of an UPDATE operation, because
2269 : : * postgresBeginForeignInsert() currently rejects such insert attempts.
2270 : : */
2062 2271 [ + + - + ]: 147 : Assert(fmstate == NULL || fmstate->aux_fmstate == NULL);
2272 : :
2273 : : /*
2274 : : * In EXPLAIN without ANALYZE, ri_FdwState is NULL, so we have to lookup
2275 : : * the option directly in server/table options. Otherwise just use the
2276 : : * value we determined earlier.
2277 : : */
2278 [ + + ]: 147 : if (fmstate)
2279 : 134 : batch_size = fmstate->batch_size;
2280 : : else
2091 2281 : 13 : batch_size = get_batch_size_option(resultRelInfo->ri_RelationDesc);
2282 : :
2283 : : /*
2284 : : * Disable batching when we have to use RETURNING, there are any
2285 : : * BEFORE/AFTER ROW INSERT triggers on the foreign table, or there are any
2286 : : * WITH CHECK OPTION constraints from parent views.
2287 : : *
2288 : : * When there are any BEFORE ROW INSERT triggers on the table, we can't
2289 : : * support it, because such triggers might query the table we're inserting
2290 : : * into and act differently if the tuples that have already been processed
2291 : : * and prepared for insertion are not there.
2292 : : */
2293 [ + + ]: 147 : if (resultRelInfo->ri_projectReturning != NULL ||
1529 efujita@postgresql.o 2294 [ + + ]: 126 : resultRelInfo->ri_WithCheckOptions != NIL ||
2091 tomas.vondra@postgre 2295 [ + + ]: 117 : (resultRelInfo->ri_TrigDesc &&
1635 efujita@postgresql.o 2296 [ + + ]: 14 : (resultRelInfo->ri_TrigDesc->trig_insert_before_row ||
2297 [ + - ]: 1 : resultRelInfo->ri_TrigDesc->trig_insert_after_row)))
2091 tomas.vondra@postgre 2298 : 44 : return 1;
2299 : :
2300 : : /*
2301 : : * If the foreign table has no columns, disable batching as the INSERT
2302 : : * syntax doesn't allow batching multiple empty rows into a zero-column
2303 : : * table in a single statement. This is needed for COPY FROM, in which
2304 : : * case fmstate must be non-NULL.
2305 : : */
1460 efujita@postgresql.o 2306 [ + + + + ]: 103 : if (fmstate && list_length(fmstate->target_attrs) == 0)
2307 : 1 : return 1;
2308 : :
2309 : : /*
2310 : : * Otherwise use the batch size specified for server/table. The number of
2311 : : * parameters in a batch is limited to 65535 (uint16), so make sure we
2312 : : * don't exceed this limit by using the maximum batch_size possible.
2313 : : */
1952 tomas.vondra@postgre 2314 [ + + + - ]: 102 : if (fmstate && fmstate->p_nums > 0)
2315 : 94 : batch_size = Min(batch_size, PQ_QUERY_PARAM_MAX_LIMIT / fmstate->p_nums);
2316 : :
2091 2317 : 102 : return batch_size;
2318 : : }
2319 : :
2320 : : /*
2321 : : * postgresExecForeignUpdate
2322 : : * Update one row in a foreign table
2323 : : */
2324 : : static TupleTableSlot *
4964 tgl@sss.pgh.pa.us 2325 : 97 : postgresExecForeignUpdate(EState *estate,
2326 : : ResultRelInfo *resultRelInfo,
2327 : : TupleTableSlot *slot,
2328 : : TupleTableSlot *planSlot)
2329 : : {
2330 : : TupleTableSlot **rslot;
1979 2331 : 97 : int numSlots = 1;
2332 : :
2091 tomas.vondra@postgre 2333 : 97 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_UPDATE,
2334 : : &slot, &planSlot, &numSlots);
2335 : :
2336 [ + + ]: 97 : return rslot ? rslot[0] : NULL;
2337 : : }
2338 : :
2339 : : /*
2340 : : * postgresExecForeignDelete
2341 : : * Delete one row from a foreign table
2342 : : */
2343 : : static TupleTableSlot *
4964 tgl@sss.pgh.pa.us 2344 : 23 : postgresExecForeignDelete(EState *estate,
2345 : : ResultRelInfo *resultRelInfo,
2346 : : TupleTableSlot *slot,
2347 : : TupleTableSlot *planSlot)
2348 : : {
2349 : : TupleTableSlot **rslot;
1979 2350 : 23 : int numSlots = 1;
2351 : :
2091 tomas.vondra@postgre 2352 : 23 : rslot = execute_foreign_modify(estate, resultRelInfo, CMD_DELETE,
2353 : : &slot, &planSlot, &numSlots);
2354 : :
2355 [ + - ]: 23 : return rslot ? rslot[0] : NULL;
2356 : : }
2357 : :
2358 : : /*
2359 : : * postgresEndForeignModify
2360 : : * Finish an insert/update/delete operation on a foreign table
2361 : : */
2362 : : static void
4964 tgl@sss.pgh.pa.us 2363 : 160 : postgresEndForeignModify(EState *estate,
2364 : : ResultRelInfo *resultRelInfo)
2365 : : {
2366 : 160 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2367 : :
2368 : : /* If fmstate is NULL, we are in EXPLAIN; nothing to do */
2369 [ + + ]: 160 : if (fmstate == NULL)
2370 : 47 : return;
2371 : :
2372 : : /* Destroy the execution state */
3111 rhaas@postgresql.org 2373 : 113 : finish_foreign_modify(fmstate);
2374 : : }
2375 : :
2376 : : /*
2377 : : * postgresBeginForeignInsert
2378 : : * Begin an insert operation on a foreign table
2379 : : */
2380 : : static void
2381 : 64 : postgresBeginForeignInsert(ModifyTableState *mtstate,
2382 : : ResultRelInfo *resultRelInfo)
2383 : : {
2384 : : PgFdwModifyState *fmstate;
3086 2385 : 64 : ModifyTable *plan = castNode(ModifyTable, mtstate->ps.plan);
2386 : 64 : EState *estate = mtstate->ps.state;
2387 : : Index resultRelation;
3111 2388 : 64 : Relation rel = resultRelInfo->ri_RelationDesc;
2389 : : RangeTblEntry *rte;
2390 : 64 : TupleDesc tupdesc = RelationGetDescr(rel);
2391 : : int attnum;
2392 : : int values_end_len;
2393 : : StringInfoData sql;
2394 : 64 : List *targetAttrs = NIL;
2395 : 64 : List *retrieved_attrs = NIL;
2396 : 64 : bool doNothing = false;
2397 : :
2398 : : /*
2399 : : * If the foreign table we are about to insert routed rows into is also an
2400 : : * UPDATE subplan result rel that will be updated later, proceeding with
2401 : : * the INSERT will result in the later UPDATE incorrectly modifying those
2402 : : * routed rows, so prevent the INSERT --- it would be nice if we could
2403 : : * handle this case; but for now, throw an error for safety.
2404 : : */
2728 efujita@postgresql.o 2405 [ + + + + ]: 64 : if (plan && plan->operation == CMD_UPDATE &&
2406 [ + + ]: 9 : (resultRelInfo->ri_usesFdwDirectModify ||
2021 tgl@sss.pgh.pa.us 2407 [ + + ]: 5 : resultRelInfo->ri_FdwState))
2728 efujita@postgresql.o 2408 [ + - ]: 6 : ereport(ERROR,
2409 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
2410 : : errmsg("cannot route tuples into foreign table to be updated \"%s\"",
2411 : : RelationGetRelationName(rel))));
2412 : :
3111 rhaas@postgresql.org 2413 : 58 : initStringInfo(&sql);
2414 : :
2415 : : /* We transmit all columns that are defined in the foreign table. */
2416 [ + + ]: 173 : for (attnum = 1; attnum <= tupdesc->natts; attnum++)
2417 : : {
661 drowley@postgresql.o 2418 : 115 : CompactAttribute *attr = TupleDescCompactAttr(tupdesc, attnum - 1);
2419 : :
3111 rhaas@postgresql.org 2420 [ + + ]: 115 : if (!attr->attisdropped)
2421 : 113 : targetAttrs = lappend_int(targetAttrs, attnum);
2422 : : }
2423 : :
2424 : : /* Check if we add the ON CONFLICT clause to the remote query. */
2425 [ + + ]: 58 : if (plan)
2426 : : {
3085 2427 : 34 : OnConflictAction onConflictAction = plan->onConflictAction;
2428 : :
2429 : : /* We only support DO NOTHING without an inference specification. */
3111 2430 [ + + ]: 34 : if (onConflictAction == ONCONFLICT_NOTHING)
2431 : 2 : doNothing = true;
2432 [ - + ]: 32 : else if (onConflictAction != ONCONFLICT_NONE)
3111 rhaas@postgresql.org 2433 [ # # ]:UBC 0 : elog(ERROR, "unexpected ON CONFLICT specification: %d",
2434 : : (int) onConflictAction);
2435 : : }
2436 : :
2437 : : /*
2438 : : * If the foreign table is a partition that doesn't have a corresponding
2439 : : * RTE entry, we need to create a new RTE describing the foreign table for
2440 : : * use by deparseInsertSql and create_foreign_modify() below, after first
2441 : : * copying the parent's RTE and modifying some fields to describe the
2442 : : * foreign partition to work on. However, if this is invoked by UPDATE,
2443 : : * the existing RTE may already correspond to this partition if it is one
2444 : : * of the UPDATE subplan target rels; in that case, we can just use the
2445 : : * existing RTE as-is.
2446 : : */
2072 heikki.linnakangas@i 2447 [ + + ]:CBC 58 : if (resultRelInfo->ri_RangeTableIndex == 0)
2448 : : {
2449 : 40 : ResultRelInfo *rootResultRelInfo = resultRelInfo->ri_RootResultRelInfo;
2450 : :
2451 : 40 : rte = exec_rt_fetch(rootResultRelInfo->ri_RangeTableIndex, estate);
3086 rhaas@postgresql.org 2452 : 40 : rte = copyObject(rte);
2453 : 40 : rte->relid = RelationGetRelid(rel);
2454 : 40 : rte->relkind = RELKIND_FOREIGN_TABLE;
2455 : :
2456 : : /*
2457 : : * For UPDATE, we must use the RT index of the first subplan target
2458 : : * rel's RTE, because the core code would have built expressions for
2459 : : * the partition, such as RETURNING, using that RT index as varno of
2460 : : * Vars contained in those expressions.
2461 : : */
2462 [ + + + + ]: 40 : if (plan && plan->operation == CMD_UPDATE &&
2072 heikki.linnakangas@i 2463 [ + - ]: 3 : rootResultRelInfo->ri_RangeTableIndex == plan->rootRelation)
3086 rhaas@postgresql.org 2464 : 3 : resultRelation = mtstate->resultRelInfo[0].ri_RangeTableIndex;
2465 : : else
2072 heikki.linnakangas@i 2466 : 37 : resultRelation = rootResultRelInfo->ri_RangeTableIndex;
2467 : : }
2468 : : else
2469 : : {
2470 : 18 : resultRelation = resultRelInfo->ri_RangeTableIndex;
2471 : 18 : rte = exec_rt_fetch(resultRelation, estate);
2472 : : }
2473 : :
2474 : : /* Construct the SQL command string. */
3086 rhaas@postgresql.org 2475 : 58 : deparseInsertSql(&sql, rte, resultRelation, rel, targetAttrs, doNothing,
2476 : : resultRelInfo->ri_WithCheckOptions,
2477 : : resultRelInfo->ri_returningList,
2478 : : &retrieved_attrs, &values_end_len);
2479 : :
2480 : : /* Construct an execution state. */
3111 2481 : 58 : fmstate = create_foreign_modify(mtstate->ps.state,
2482 : : rte,
2483 : : resultRelInfo,
2484 : : CMD_INSERT,
2485 : : NULL,
2486 : : sql.data,
2487 : : targetAttrs,
2488 : : values_end_len,
2489 : : retrieved_attrs != NIL,
2490 : : retrieved_attrs);
2491 : :
2492 : : /*
2493 : : * If the given resultRelInfo already has PgFdwModifyState set, it means
2494 : : * the foreign table is an UPDATE subplan result rel; in which case, store
2495 : : * the resulting state into the aux_fmstate of the PgFdwModifyState.
2496 : : */
2728 efujita@postgresql.o 2497 [ - + ]: 58 : if (resultRelInfo->ri_FdwState)
2498 : : {
2728 efujita@postgresql.o 2499 [ # # # # ]:UBC 0 : Assert(plan && plan->operation == CMD_UPDATE);
2500 [ # # ]: 0 : Assert(resultRelInfo->ri_usesFdwDirectModify == false);
2501 : 0 : ((PgFdwModifyState *) resultRelInfo->ri_FdwState)->aux_fmstate = fmstate;
2502 : : }
2503 : : else
2728 efujita@postgresql.o 2504 :CBC 58 : resultRelInfo->ri_FdwState = fmstate;
3111 rhaas@postgresql.org 2505 : 58 : }
2506 : :
2507 : : /*
2508 : : * postgresEndForeignInsert
2509 : : * Finish an insert operation on a foreign table
2510 : : */
2511 : : static void
2512 : 50 : postgresEndForeignInsert(EState *estate,
2513 : : ResultRelInfo *resultRelInfo)
2514 : : {
2515 : 50 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2516 : :
2517 [ - + ]: 50 : Assert(fmstate != NULL);
2518 : :
2519 : : /*
2520 : : * If the fmstate has aux_fmstate set, get the aux_fmstate (see
2521 : : * postgresBeginForeignInsert())
2522 : : */
2728 efujita@postgresql.o 2523 [ - + ]: 50 : if (fmstate->aux_fmstate)
2728 efujita@postgresql.o 2524 :UBC 0 : fmstate = fmstate->aux_fmstate;
2525 : :
2526 : : /* Destroy the execution state */
3111 rhaas@postgresql.org 2527 :CBC 50 : finish_foreign_modify(fmstate);
2528 : 50 : }
2529 : :
2530 : : /*
2531 : : * postgresIsForeignRelUpdatable
2532 : : * Determine whether a foreign table supports INSERT, UPDATE and/or
2533 : : * DELETE.
2534 : : */
2535 : : static int
4870 tgl@sss.pgh.pa.us 2536 : 346 : postgresIsForeignRelUpdatable(Relation rel)
2537 : : {
2538 : : bool updatable;
2539 : : ForeignTable *table;
2540 : : ForeignServer *server;
2541 : : ListCell *lc;
2542 : :
2543 : : /*
2544 : : * By default, all postgres_fdw foreign tables are assumed updatable. This
2545 : : * can be overridden by a per-server setting, which in turn can be
2546 : : * overridden by a per-table setting.
2547 : : */
2548 : 346 : updatable = true;
2549 : :
2550 : 346 : table = GetForeignTable(RelationGetRelid(rel));
2551 : 346 : server = GetForeignServer(table->serverid);
2552 : :
2553 [ + - + + : 1545 : foreach(lc, server->options)
+ + ]
2554 : : {
2555 : 1199 : DefElem *def = (DefElem *) lfirst(lc);
2556 : :
2557 [ - + ]: 1199 : if (strcmp(def->defname, "updatable") == 0)
4870 tgl@sss.pgh.pa.us 2558 :UBC 0 : updatable = defGetBoolean(def);
2559 : : }
4870 tgl@sss.pgh.pa.us 2560 [ + - + + :CBC 830 : foreach(lc, table->options)
+ + ]
2561 : : {
2562 : 484 : DefElem *def = (DefElem *) lfirst(lc);
2563 : :
2564 [ - + ]: 484 : if (strcmp(def->defname, "updatable") == 0)
4870 tgl@sss.pgh.pa.us 2565 :UBC 0 : updatable = defGetBoolean(def);
2566 : : }
2567 : :
2568 : : /*
2569 : : * Currently "updatable" means support for INSERT, UPDATE and DELETE.
2570 : : */
2571 : : return updatable ?
4870 tgl@sss.pgh.pa.us 2572 [ + - ]:CBC 346 : (1 << CMD_INSERT) | (1 << CMD_UPDATE) | (1 << CMD_DELETE) : 0;
2573 : : }
2574 : :
2575 : : /*
2576 : : * postgresRecheckForeignScan
2577 : : * Execute a local join execution plan for a foreign join
2578 : : */
2579 : : static bool
3898 rhaas@postgresql.org 2580 : 5 : postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
2581 : : {
2582 : 5 : Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid;
2583 : 5 : PlanState *outerPlan = outerPlanState(node);
2584 : : TupleTableSlot *result;
2585 : :
2586 : : /* For base foreign relations, it suffices to set fdw_recheck_quals */
2587 [ + + ]: 5 : if (scanrelid > 0)
2588 : 3 : return true;
2589 : :
2590 [ - + ]: 2 : Assert(outerPlan != NULL);
2591 : :
2592 : : /* Execute a local join execution plan */
2593 : 2 : result = ExecProcNode(outerPlan);
2594 [ + + - + ]: 2 : if (TupIsNull(result))
2595 : 1 : return false;
2596 : :
2597 : : /* Store result in the given slot */
2598 : 1 : ExecCopySlot(slot, result);
2599 : :
2600 : 1 : return true;
2601 : : }
2602 : :
2603 : : /*
2604 : : * find_modifytable_subplan
2605 : : * Helper routine for postgresPlanDirectModify to find the
2606 : : * ModifyTable subplan node that scans the specified RTI.
2607 : : *
2608 : : * Returns NULL if the subplan couldn't be identified. That's not a fatal
2609 : : * error condition, we just abandon trying to do the update directly.
2610 : : */
2611 : : static ForeignScan *
2021 tgl@sss.pgh.pa.us 2612 : 136 : find_modifytable_subplan(PlannerInfo *root,
2613 : : ModifyTable *plan,
2614 : : Index rtindex,
2615 : : int subplan_index)
2616 : : {
2617 : 136 : Plan *subplan = outerPlan(plan);
2618 : :
2619 : : /*
2620 : : * The cases we support are (1) the desired ForeignScan is the immediate
2621 : : * child of ModifyTable, or (2) it is the subplan_index'th child of an
2622 : : * Append node that is the immediate child of ModifyTable. There is no
2623 : : * point in looking further down, as that would mean that local joins are
2624 : : * involved, so we can't do the update directly.
2625 : : *
2626 : : * There could be a Result atop the Append too, acting to compute the
2627 : : * UPDATE targetlist values. We ignore that here; the tlist will be
2628 : : * checked by our caller.
2629 : : *
2630 : : * In principle we could examine all the children of the Append, but it's
2631 : : * currently unlikely that the core planner would generate such a plan
2632 : : * with the children out-of-order. Moreover, such a search risks costing
2633 : : * O(N^2) time when there are a lot of children.
2634 : : */
2635 [ + + ]: 136 : if (IsA(subplan, Append))
2636 : : {
2637 : 35 : Append *appendplan = (Append *) subplan;
2638 : :
2639 [ + - ]: 35 : if (subplan_index < list_length(appendplan->appendplans))
2640 : 35 : subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
2641 : : }
1923 2642 [ + + ]: 101 : else if (IsA(subplan, Result) &&
2643 [ + + ]: 6 : outerPlan(subplan) != NULL &&
2644 [ + - ]: 5 : IsA(outerPlan(subplan), Append))
2645 : : {
2021 2646 : 5 : Append *appendplan = (Append *) outerPlan(subplan);
2647 : :
2648 [ + - ]: 5 : if (subplan_index < list_length(appendplan->appendplans))
2649 : 5 : subplan = (Plan *) list_nth(appendplan->appendplans, subplan_index);
2650 : : }
2651 : :
2652 : : /* Now, have we got a ForeignScan on the desired rel? */
2653 [ + + ]: 136 : if (IsA(subplan, ForeignScan))
2654 : : {
2655 : 119 : ForeignScan *fscan = (ForeignScan *) subplan;
2656 : :
1351 2657 [ + - ]: 119 : if (bms_is_member(rtindex, fscan->fs_base_relids))
2021 2658 : 119 : return fscan;
2659 : : }
2660 : :
2661 : 17 : return NULL;
2662 : : }
2663 : :
2664 : : /*
2665 : : * postgresPlanDirectModify
2666 : : * Consider a direct foreign table modification
2667 : : *
2668 : : * Decide whether it is safe to modify a foreign table directly, and if so,
2669 : : * rewrite subplan accordingly.
2670 : : */
2671 : : static bool
3860 rhaas@postgresql.org 2672 : 201 : postgresPlanDirectModify(PlannerInfo *root,
2673 : : ModifyTable *plan,
2674 : : Index resultRelation,
2675 : : int subplan_index)
2676 : : {
2677 : 201 : CmdType operation = plan->operation;
2678 : : RelOptInfo *foreignrel;
2679 : : RangeTblEntry *rte;
2680 : : PgFdwRelationInfo *fpinfo;
2681 : : Relation rel;
2682 : : StringInfoData sql;
2683 : : ForeignScan *fscan;
2021 tgl@sss.pgh.pa.us 2684 : 201 : List *processed_tlist = NIL;
3860 rhaas@postgresql.org 2685 : 201 : List *targetAttrs = NIL;
2686 : : List *remote_exprs;
2687 : 201 : List *params_list = NIL;
2688 : 201 : List *returningList = NIL;
2689 : 201 : List *retrieved_attrs = NIL;
2690 : :
2691 : : /*
2692 : : * Decide whether it is safe to modify a foreign table directly.
2693 : : */
2694 : :
2695 : : /*
2696 : : * The table modification must be an UPDATE or DELETE.
2697 : : */
2698 [ + + + + ]: 201 : if (operation != CMD_UPDATE && operation != CMD_DELETE)
2699 : 65 : return false;
2700 : :
2701 : : /*
2702 : : * Try to locate the ForeignScan subplan that's scanning resultRelation.
2703 : : */
2021 tgl@sss.pgh.pa.us 2704 : 136 : fscan = find_modifytable_subplan(root, plan, resultRelation, subplan_index);
2705 [ + + ]: 136 : if (!fscan)
3860 rhaas@postgresql.org 2706 : 17 : return false;
2707 : :
2708 : : /*
2709 : : * It's unsafe to modify a foreign table directly if there are any quals
2710 : : * that should be evaluated locally.
2711 : : */
2021 tgl@sss.pgh.pa.us 2712 [ + + ]: 119 : if (fscan->scan.plan.qual != NIL)
3860 rhaas@postgresql.org 2713 : 5 : return false;
2714 : :
2715 : : /* Safe to fetch data about the target foreign rel */
3169 2716 [ + + ]: 114 : if (fscan->scan.scanrelid == 0)
2717 : : {
2718 : 13 : foreignrel = find_join_rel(root, fscan->fs_relids);
2719 : : /* We should have a rel for this foreign join. */
2720 [ - + ]: 13 : Assert(foreignrel);
2721 : : }
2722 : : else
2723 : 101 : foreignrel = root->simple_rel_array[resultRelation];
3471 tgl@sss.pgh.pa.us 2724 : 114 : rte = root->simple_rte_array[resultRelation];
2725 : 114 : fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
2726 : :
2727 : : /*
2728 : : * It's unsafe to update a foreign table directly, if any expressions to
2729 : : * assign to the target columns are unsafe to evaluate remotely.
2730 : : */
3860 rhaas@postgresql.org 2731 [ + + ]: 114 : if (operation == CMD_UPDATE)
2732 : : {
2733 : : ListCell *lc,
2734 : : *lc2;
2735 : :
2736 : : /*
2737 : : * The expressions of concern are the first N columns of the processed
2738 : : * targetlist, where N is the length of the rel's update_colnos.
2739 : : */
2021 tgl@sss.pgh.pa.us 2740 : 55 : get_translated_update_targetlist(root, resultRelation,
2741 : : &processed_tlist, &targetAttrs);
2742 [ + - + - : 111 : forboth(lc, processed_tlist, lc2, targetAttrs)
+ - + + +
- + + +
+ ]
2743 : : {
2744 : 63 : TargetEntry *tle = lfirst_node(TargetEntry, lc);
2745 : 63 : AttrNumber attno = lfirst_int(lc2);
2746 : :
2747 : : /* update's new-value expressions shouldn't be resjunk */
2748 [ - + ]: 63 : Assert(!tle->resjunk);
2749 : :
3400 2750 [ - + ]: 63 : if (attno <= InvalidAttrNumber) /* shouldn't happen */
3860 rhaas@postgresql.org 2751 [ # # ]:UBC 0 : elog(ERROR, "system-column update is not supported");
2752 : :
54 akorotkov@postgresql 2753 [ + + ]:GNC 63 : if (!is_foreign_expr(root, foreignrel, fpinfo, (Expr *) tle->expr))
3860 rhaas@postgresql.org 2754 :CBC 7 : return false;
2755 : : }
2756 : : }
2757 : :
2758 : : /*
2759 : : * Ok, rewrite subplan so as to modify the foreign table directly.
2760 : : */
2761 : 107 : initStringInfo(&sql);
2762 : :
2763 : : /*
2764 : : * Core code already has some lock on each rel being planned, so we can
2765 : : * use NoLock here.
2766 : : */
2821 andres@anarazel.de 2767 : 107 : rel = table_open(rte->relid, NoLock);
2768 : :
2769 : : /*
2770 : : * Recall the qual clauses that must be evaluated remotely. (These are
2771 : : * bare clauses not RestrictInfos, but deparse.c's appendConditions()
2772 : : * doesn't care.)
2773 : : */
3471 tgl@sss.pgh.pa.us 2774 : 107 : remote_exprs = fpinfo->final_remote_exprs;
2775 : :
2776 : : /*
2777 : : * Extract the relevant RETURNING list if any.
2778 : : */
3860 rhaas@postgresql.org 2779 [ + + ]: 107 : if (plan->returningLists)
2780 : : {
2781 : 37 : returningList = (List *) list_nth(plan->returningLists, subplan_index);
2782 : :
2783 : : /*
2784 : : * When performing an UPDATE/DELETE .. RETURNING on a join directly,
2785 : : * we fetch from the foreign server any Vars specified in RETURNING
2786 : : * that refer not only to the target relation but to non-target
2787 : : * relations. So we'll deparse them into the RETURNING clause of the
2788 : : * remote query; use a targetlist consisting of them instead, which
2789 : : * will be adjusted to be new fdw_scan_tlist of the foreign-scan plan
2790 : : * node below.
2791 : : */
3169 2792 [ + + ]: 37 : if (fscan->scan.scanrelid == 0)
2793 : 5 : returningList = build_remote_returning(resultRelation, rel,
2794 : : returningList);
2795 : : }
2796 : :
2797 : : /*
2798 : : * Construct the SQL command string.
2799 : : */
3860 2800 [ + + - ]: 107 : switch (operation)
2801 : : {
2802 : 48 : case CMD_UPDATE:
2803 : 48 : deparseDirectUpdateSql(&sql, root, resultRelation, rel,
2804 : : foreignrel,
2805 : : processed_tlist,
2806 : : targetAttrs,
2807 : : remote_exprs, ¶ms_list,
2808 : : returningList, &retrieved_attrs);
2809 : 48 : break;
2810 : 59 : case CMD_DELETE:
2811 : 59 : deparseDirectDeleteSql(&sql, root, resultRelation, rel,
2812 : : foreignrel,
2813 : : remote_exprs, ¶ms_list,
2814 : : returningList, &retrieved_attrs);
2815 : 59 : break;
3860 rhaas@postgresql.org 2816 :UBC 0 : default:
2817 [ # # ]: 0 : elog(ERROR, "unexpected operation: %d", (int) operation);
2818 : : break;
2819 : : }
2820 : :
2821 : : /*
2822 : : * Update the operation and target relation info.
2823 : : */
3860 rhaas@postgresql.org 2824 :CBC 107 : fscan->operation = operation;
2189 heikki.linnakangas@i 2825 : 107 : fscan->resultRelation = resultRelation;
2826 : :
2827 : : /*
2828 : : * Update the fdw_exprs list that will be available to the executor.
2829 : : */
3860 rhaas@postgresql.org 2830 : 107 : fscan->fdw_exprs = params_list;
2831 : :
2832 : : /*
2833 : : * Update the fdw_private list that will be available to the executor.
2834 : : * Items in the list must match enum FdwDirectModifyPrivateIndex, above.
2835 : : */
2836 : 107 : fscan->fdw_private = list_make4(makeString(sql.data),
2837 : : makeBoolean((retrieved_attrs != NIL)),
2838 : : retrieved_attrs,
2839 : : makeBoolean(plan->canSetTag));
54 akorotkov@postgresql 2840 :GNC 107 : fscan->fdw_private = lappend(fscan->fdw_private,
2841 : 107 : get_functions_data(root, foreignrel));
2842 : 107 : fscan->fdw_private = lappend(fscan->fdw_private,
2843 : 107 : makeInteger(get_min_base_rti(root, foreignrel)));
2844 : :
2845 : : /*
2846 : : * Update the foreign-join-related fields.
2847 : : */
3169 rhaas@postgresql.org 2848 [ + + ]:CBC 107 : if (fscan->scan.scanrelid == 0)
2849 : : {
2850 : : /* No need for the outer subplan. */
2851 : 10 : fscan->scan.plan.lefttree = NULL;
2852 : :
2853 : : /* Build new fdw_scan_tlist if UPDATE/DELETE .. RETURNING. */
2854 [ + + ]: 10 : if (returningList)
2855 : 3 : rebuild_fdw_scan_tlist(fscan, returningList);
2856 : : }
2857 : :
2858 : : /*
2859 : : * Finally, unset the async-capable flag if it is set, as we currently
2860 : : * don't support asynchronous execution of direct modifications.
2861 : : */
1978 efujita@postgresql.o 2862 [ + + ]: 107 : if (fscan->scan.plan.async_capable)
2863 : 8 : fscan->scan.plan.async_capable = false;
2864 : :
2821 andres@anarazel.de 2865 : 107 : table_close(rel, NoLock);
3860 rhaas@postgresql.org 2866 : 107 : return true;
2867 : : }
2868 : :
2869 : : /*
2870 : : * postgresBeginDirectModify
2871 : : * Prepare a direct foreign table modification
2872 : : */
2873 : : static void
2874 : 108 : postgresBeginDirectModify(ForeignScanState *node, int eflags)
2875 : : {
2876 : 108 : ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
2877 : 108 : EState *estate = node->ss.ps.state;
2878 : : PgFdwDirectModifyState *dmstate;
2879 : : Index rtindex;
2880 : : Oid userid;
2881 : : ForeignTable *table;
2882 : : UserMapping *user;
2883 : : int numParams;
2884 : :
2885 : : /*
2886 : : * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
2887 : : */
2888 [ + + ]: 108 : if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
2889 : 33 : return;
2890 : :
2891 : : /*
2892 : : * We'll save private state in node->fdw_state.
2893 : : */
305 michael@paquier.xyz 2894 : 75 : dmstate = palloc0_object(PgFdwDirectModifyState);
683 peter@eisentraut.org 2895 : 75 : node->fdw_state = dmstate;
2896 : :
2897 : : /*
2898 : : * Identify which user to do the remote access as. This should match what
2899 : : * ExecCheckPermissions() does.
2900 : : */
1412 alvherre@alvh.no-ip. 2901 [ - + ]: 75 : userid = OidIsValid(fsplan->checkAsUser) ? fsplan->checkAsUser : GetUserId();
2902 : :
2903 : : /* Get info about foreign table. */
2904 : 75 : rtindex = node->resultRelInfo->ri_RangeTableIndex;
3169 rhaas@postgresql.org 2905 [ + + ]: 75 : if (fsplan->scan.scanrelid == 0)
2906 : 6 : dmstate->rel = ExecOpenScanRelation(estate, rtindex, eflags);
2907 : : else
2908 : 69 : dmstate->rel = node->ss.ss_currentRelation;
3860 2909 : 75 : table = GetForeignTable(RelationGetRelid(dmstate->rel));
2910 : 75 : user = GetUserMapping(userid, table->serverid);
2911 : :
2912 : : /*
2913 : : * Get connection to the foreign server. Connection manager will
2914 : : * establish new connection if necessary.
2915 : : */
2021 efujita@postgresql.o 2916 : 75 : dmstate->conn = GetConnection(user, false, &dmstate->conn_state);
2917 : :
2918 : : /* Update the foreign-join-related fields. */
3169 rhaas@postgresql.org 2919 [ + + ]: 75 : if (fsplan->scan.scanrelid == 0)
2920 : : {
2921 : : /* Save info about foreign table. */
2922 : 6 : dmstate->resultRel = dmstate->rel;
2923 : :
2924 : : /*
2925 : : * Set dmstate->rel to NULL to teach get_returning_data() and
2926 : : * make_tuple_from_result_row() that columns fetched from the remote
2927 : : * server are described by fdw_scan_tlist of the foreign-scan plan
2928 : : * node, not the tuple descriptor for the target relation.
2929 : : */
2930 : 6 : dmstate->rel = NULL;
2931 : : }
2932 : :
2933 : : /* Initialize state variable */
3841 tgl@sss.pgh.pa.us 2934 : 75 : dmstate->num_tuples = -1; /* -1 means not set yet */
2935 : :
2936 : : /* Get private info created by planner functions. */
3860 rhaas@postgresql.org 2937 : 75 : dmstate->query = strVal(list_nth(fsplan->fdw_private,
2938 : : FdwDirectModifyPrivateUpdateSql));
1732 peter@eisentraut.org 2939 : 75 : dmstate->has_returning = boolVal(list_nth(fsplan->fdw_private,
2940 : : FdwDirectModifyPrivateHasReturning));
3860 rhaas@postgresql.org 2941 : 75 : dmstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
2942 : : FdwDirectModifyPrivateRetrievedAttrs);
1732 peter@eisentraut.org 2943 : 75 : dmstate->set_processed = boolVal(list_nth(fsplan->fdw_private,
2944 : : FdwDirectModifyPrivateSetProcessed));
2945 : :
2946 : : /* Create context for per-tuple temp workspace. */
3860 rhaas@postgresql.org 2947 : 75 : dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
2948 : : "postgres_fdw temporary data",
2949 : : ALLOCSET_SMALL_SIZES);
2950 : :
2951 : : /* Prepare for input conversion of RETURNING results. */
2952 [ + + ]: 75 : if (dmstate->has_returning)
2953 : : {
2954 : : TupleDesc tupdesc;
2955 : :
3169 2956 [ + + ]: 18 : if (fsplan->scan.scanrelid == 0)
2957 : : {
2958 : : /*
2959 : : * DirectModify on a foreign join: use the per-RTE function
2960 : : * metadata saved at plan time so a whole-row Var pointing at a
2961 : : * function RTE absorbed into the join can be rebuilt into a
2962 : : * usable TupleDesc (e.g. RETURNING t for a join with UNNEST(...,
2963 : : * ...) AS t(bx, i)).
2964 : : */
54 akorotkov@postgresql 2965 :GNC 2 : List *rtfuncdata = (List *) list_nth(fsplan->fdw_private,
2966 : : FdwDirectModifyPrivateFunctions);
2967 : 2 : int min_base_rti = intVal(list_nth(fsplan->fdw_private,
2968 : : FdwDirectModifyPrivateMinRTIndex));
2969 : 2 : int rtoffset = bms_next_member(fsplan->fs_base_relids, -1) -
2970 : : min_base_rti;
2971 : :
2972 [ - + ]: 2 : Assert(min_base_rti > 0);
2973 [ - + ]: 2 : Assert(rtoffset >= 0);
2974 : :
2975 : 2 : tupdesc = get_tupdesc_for_join_scan_tuples(node, rtfuncdata, rtoffset);
2976 : : }
2977 : : else
3169 rhaas@postgresql.org 2978 :CBC 16 : tupdesc = RelationGetDescr(dmstate->rel);
2979 : :
2980 : 18 : dmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
2981 : :
2982 : : /*
2983 : : * When performing an UPDATE/DELETE .. RETURNING on a join directly,
2984 : : * initialize a filter to extract an updated/deleted tuple from a scan
2985 : : * tuple.
2986 : : */
2987 [ + + ]: 18 : if (fsplan->scan.scanrelid == 0)
2988 : 2 : init_returning_filter(dmstate, fsplan->fdw_scan_tlist, rtindex);
2989 : : }
2990 : :
2991 : : /*
2992 : : * Prepare for processing of parameters used in remote query, if any.
2993 : : */
3860 2994 : 75 : numParams = list_length(fsplan->fdw_exprs);
2995 : 75 : dmstate->numParams = numParams;
2996 [ + + ]: 75 : if (numParams > 0)
2997 : 1 : prepare_query_params((PlanState *) node,
2998 : : fsplan->fdw_exprs,
2999 : : numParams,
3000 : : &dmstate->param_flinfo,
3001 : : &dmstate->param_exprs,
3002 : : &dmstate->param_values);
3003 : : }
3004 : :
3005 : : /*
3006 : : * postgresIterateDirectModify
3007 : : * Execute a direct foreign table modification
3008 : : */
3009 : : static TupleTableSlot *
3010 : 423 : postgresIterateDirectModify(ForeignScanState *node)
3011 : : {
3012 : 423 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
3013 : 423 : EState *estate = node->ss.ps.state;
2189 heikki.linnakangas@i 3014 : 423 : ResultRelInfo *resultRelInfo = node->resultRelInfo;
3015 : :
3016 : : /*
3017 : : * If this is the first call after Begin, execute the statement.
3018 : : */
3860 rhaas@postgresql.org 3019 [ + + ]: 423 : if (dmstate->num_tuples == -1)
3020 : 74 : execute_dml_stmt(node);
3021 : :
3022 : : /*
3023 : : * If the local query doesn't specify RETURNING, just clear tuple slot.
3024 : : */
3025 [ + + ]: 419 : if (!resultRelInfo->ri_projectReturning)
3026 : : {
3027 : 51 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
190 andres@anarazel.de 3028 : 51 : NodeInstrumentation *instr = node->ss.ps.instrument;
3029 : :
3860 rhaas@postgresql.org 3030 [ - + ]: 51 : Assert(!dmstate->has_returning);
3031 : :
3032 : : /* Increment the command es_processed count if necessary. */
3033 [ + - ]: 51 : if (dmstate->set_processed)
3034 : 51 : estate->es_processed += dmstate->num_tuples;
3035 : :
3036 : : /* Increment the tuple count for EXPLAIN ANALYZE if necessary. */
3037 [ - + ]: 51 : if (instr)
3860 rhaas@postgresql.org 3038 :UBC 0 : instr->tuplecount += dmstate->num_tuples;
3039 : :
3860 rhaas@postgresql.org 3040 :CBC 51 : return ExecClearTuple(slot);
3041 : : }
3042 : :
3043 : : /*
3044 : : * Get the next RETURNING tuple.
3045 : : */
3046 : 368 : return get_returning_data(node);
3047 : : }
3048 : :
3049 : : /*
3050 : : * postgresEndDirectModify
3051 : : * Finish a direct foreign table modification
3052 : : */
3053 : : static void
3054 : 100 : postgresEndDirectModify(ForeignScanState *node)
3055 : : {
3056 : 100 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
3057 : :
3058 : : /* if dmstate is NULL, we are in EXPLAIN; nothing to do */
3059 [ + + ]: 100 : if (dmstate == NULL)
3060 : 33 : return;
3061 : :
3062 : : /* Release PGresult */
444 tgl@sss.pgh.pa.us 3063 : 67 : PQclear(dmstate->result);
3064 : :
3065 : : /* Release remote connection */
3860 rhaas@postgresql.org 3066 : 67 : ReleaseConnection(dmstate->conn);
3067 : 67 : dmstate->conn = NULL;
3068 : :
3069 : : /* MemoryContext will be deleted automatically. */
3070 : : }
3071 : :
3072 : : /*
3073 : : * postgresExplainForeignScan
3074 : : * Produce extra output for EXPLAIN of a ForeignScan on a foreign table
3075 : : */
3076 : : static void
4964 tgl@sss.pgh.pa.us 3077 : 436 : postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
3078 : : {
2506 3079 : 436 : ForeignScan *plan = castNode(ForeignScan, node->ss.ps.plan);
3080 : 436 : List *fdw_private = plan->fdw_private;
3081 : :
3082 : : /*
3083 : : * Identify foreign scans that are really joins or upper relations. The
3084 : : * input looks something like "(1) LEFT JOIN (2)", and we must replace the
3085 : : * digit string(s), which are RT indexes, with the correct relation names.
3086 : : * We do that here, not when the plan is created, because we can't know
3087 : : * what aliases ruleutils.c will assign at plan creation time.
3088 : : */
54 akorotkov@postgresql 3089 [ + - + + ]:GNC 872 : if (list_length(fdw_private) > FdwScanPrivateRelations &&
3090 : 436 : list_nth(fdw_private, FdwScanPrivateRelations) != NULL)
3091 : : {
3092 : : StringInfoData relations;
3093 : : char *rawrelations;
3094 : : char *ptr;
3095 : : int minrti,
3096 : : rtoffset;
3097 : :
2506 tgl@sss.pgh.pa.us 3098 :CBC 139 : rawrelations = strVal(list_nth(fdw_private, FdwScanPrivateRelations));
3099 : :
3100 : : /*
3101 : : * A difficulty with using a string representation of RT indexes is
3102 : : * that setrefs.c won't update the string when flattening the
3103 : : * rangetable. To find out what rtoffset was applied, identify the
3104 : : * minimum RT index appearing in the string and compare it to the
3105 : : * minimum member of plan->fs_base_relids. (We expect all the relids
3106 : : * in the join will have been offset by the same amount; the Asserts
3107 : : * below should catch it if that ever changes.)
3108 : : */
3109 : 139 : minrti = INT_MAX;
3110 : 139 : ptr = rawrelations;
3111 [ + + ]: 3264 : while (*ptr)
3112 : : {
3113 [ + + ]: 3125 : if (isdigit((unsigned char) *ptr))
3114 : : {
3115 : 276 : int rti = strtol(ptr, &ptr, 10);
3116 : :
3117 [ + + ]: 276 : if (rti < minrti)
3118 : 151 : minrti = rti;
3119 : : }
3120 : : else
3121 : 2849 : ptr++;
3122 : : }
1351 3123 : 139 : rtoffset = bms_next_member(plan->fs_base_relids, -1) - minrti;
3124 : :
3125 : : /* Now we can translate the string */
340 drowley@postgresql.o 3126 : 139 : initStringInfo(&relations);
2506 tgl@sss.pgh.pa.us 3127 : 139 : ptr = rawrelations;
3128 [ + + ]: 3264 : while (*ptr)
3129 : : {
3130 [ + + ]: 3125 : if (isdigit((unsigned char) *ptr))
3131 : : {
3132 : 276 : int rti = strtol(ptr, &ptr, 10);
3133 : : RangeTblEntry *rte;
54 akorotkov@postgresql 3134 :GNC 276 : char *relname = NULL;
3135 : : char *refname;
3136 : :
2506 tgl@sss.pgh.pa.us 3137 :CBC 276 : rti += rtoffset;
1351 3138 [ - + ]: 276 : Assert(bms_is_member(rti, plan->fs_base_relids));
2506 3139 : 276 : rte = rt_fetch(rti, es->rtable);
3140 : :
54 akorotkov@postgresql 3141 [ + + ]:GNC 276 : if (rte->rtekind == RTE_FUNCTION)
3142 : : {
3143 : 13 : List *rtfuncdata = list_nth(fdw_private, FdwScanPrivateFunctions);
3144 : : List *funcdata;
3145 : : bool multi_func;
3146 : 13 : bool first = true;
3147 : : ListCell *lc;
3148 : :
3149 : 13 : funcdata = list_nth(rtfuncdata, rti - rtoffset);
3150 : :
3151 : 13 : multi_func = list_length(funcdata) > 1;
3152 : :
3153 [ + + ]: 13 : if (multi_func)
3154 : 4 : appendStringInfoString(&relations, "ROWS FROM (");
3155 : :
3156 [ + - + + : 30 : foreach(lc, funcdata)
+ + ]
3157 : : {
3158 : : List *funcinfo;
3159 : : Oid funcid;
3160 : :
3161 : :
3162 [ + + ]: 17 : if (!first)
3163 : 4 : appendStringInfoString(&relations, ", ");
3164 : :
3165 : 17 : funcinfo = (List *) lfirst(lc);
3166 : :
3167 : 17 : funcid = linitial_node(Integer, funcinfo)->ival;
3168 : : /* Checked by function_rte_pushdown_ok() */
3169 [ - + ]: 17 : Assert(OidIsValid(funcid));
3170 : :
3171 : : /* Match RTE_RELATION behavior */
3172 : 17 : relname = get_func_name(funcid);
3173 [ - + ]: 17 : if (relname == NULL)
54 akorotkov@postgresql 3174 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for function %u", funcid);
54 akorotkov@postgresql 3175 [ + - ]:GNC 17 : if (es->verbose)
3176 : : {
3177 : : char *namespace;
3178 : 17 : Oid nsoid = get_func_namespace(funcid);
3179 : :
3180 : 17 : namespace = get_namespace_name(nsoid);
3181 [ - + ]: 17 : if (namespace == NULL)
54 akorotkov@postgresql 3182 [ # # ]:UNC 0 : elog(ERROR, "cache lookup failed for namespace %u", nsoid);
3183 : :
54 akorotkov@postgresql 3184 :GNC 17 : appendStringInfo(&relations, "%s.%s()",
3185 : : quote_identifier(namespace),
3186 : : quote_identifier(relname));
3187 : : }
3188 : : else
54 akorotkov@postgresql 3189 :UNC 0 : appendStringInfo(&relations, "%s()", quote_identifier(relname));
3190 : :
54 akorotkov@postgresql 3191 :GNC 17 : first = false;
3192 : : }
3193 : : /* Close ROWS FROM */
3194 [ + + ]: 13 : if (multi_func)
3195 : 4 : appendStringInfoChar(&relations, ')');
3196 : : }
3197 : : else
3198 : : {
3199 [ - + ]: 263 : Assert(rte->rtekind == RTE_RELATION);
3200 : :
3201 : : /*
3202 : : * This logic should agree with explain.c's
3203 : : * ExplainTargetRel
3204 : : */
3205 : 263 : relname = get_rel_name(rte->relid);
3206 [ + + ]: 263 : if (es->verbose)
3207 : : {
3208 : : char *namespace;
3209 : :
3210 : 247 : namespace = get_namespace_name_or_temp(get_rel_namespace(rte->relid));
3211 : 247 : appendStringInfo(&relations, "%s.%s",
3212 : : quote_identifier(namespace),
3213 : : quote_identifier(relname));
3214 : : }
3215 : : else
3216 : 16 : appendStringInfoString(&relations,
3217 : : quote_identifier(relname));
3218 : : }
3219 : :
2506 tgl@sss.pgh.pa.us 3220 :CBC 276 : refname = (char *) list_nth(es->rtable_names, rti - 1);
3221 [ - + ]: 276 : if (refname == NULL)
2506 tgl@sss.pgh.pa.us 3222 :UBC 0 : refname = rte->eref->aliasname;
54 akorotkov@postgresql 3223 [ + - + + ]:GNC 276 : if (relname == NULL || strcmp(refname, relname) != 0)
340 drowley@postgresql.o 3224 :CBC 177 : appendStringInfo(&relations, " %s",
3225 : : quote_identifier(refname));
3226 : : }
3227 : : else
3228 : 2849 : appendStringInfoChar(&relations, *ptr++);
3229 : : }
3230 : 139 : ExplainPropertyText("Relations", relations.data, es);
3231 : : }
3232 : :
3233 : : /*
3234 : : * Add remote query, when VERBOSE option is specified.
3235 : : */
4964 tgl@sss.pgh.pa.us 3236 [ + + ]: 436 : if (es->verbose)
3237 : : {
3238 : : char *sql;
3239 : :
3240 : 398 : sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
3241 : 398 : ExplainPropertyText("Remote SQL", sql, es);
3242 : : }
3243 : 436 : }
3244 : :
3245 : : /*
3246 : : * postgresExplainForeignModify
3247 : : * Produce extra output for EXPLAIN of a ModifyTable on a foreign table
3248 : : */
3249 : : static void
3250 : 47 : postgresExplainForeignModify(ModifyTableState *mtstate,
3251 : : ResultRelInfo *rinfo,
3252 : : List *fdw_private,
3253 : : int subplan_index,
3254 : : ExplainState *es)
3255 : : {
3256 [ + - ]: 47 : if (es->verbose)
3257 : : {
3258 : 47 : char *sql = strVal(list_nth(fdw_private,
3259 : : FdwModifyPrivateUpdateSql));
3260 : :
3261 : 47 : ExplainPropertyText("Remote SQL", sql, es);
3262 : :
3263 : : /*
3264 : : * For INSERT we should always have batch size >= 1, but UPDATE and
3265 : : * DELETE don't support batching so don't show the property.
3266 : : */
2091 tomas.vondra@postgre 3267 [ + + ]: 47 : if (rinfo->ri_BatchSize > 0)
3268 : 13 : ExplainPropertyInteger("Batch Size", NULL, rinfo->ri_BatchSize, es);
3269 : : }
4964 tgl@sss.pgh.pa.us 3270 : 47 : }
3271 : :
3272 : : /*
3273 : : * postgresExplainDirectModify
3274 : : * Produce extra output for EXPLAIN of a ForeignScan that modifies a
3275 : : * foreign table directly
3276 : : */
3277 : : static void
3860 rhaas@postgresql.org 3278 : 33 : postgresExplainDirectModify(ForeignScanState *node, ExplainState *es)
3279 : : {
3280 : : List *fdw_private;
3281 : : char *sql;
3282 : :
3283 [ + - ]: 33 : if (es->verbose)
3284 : : {
3285 : 33 : fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
3286 : 33 : sql = strVal(list_nth(fdw_private, FdwDirectModifyPrivateUpdateSql));
3287 : 33 : ExplainPropertyText("Remote SQL", sql, es);
3288 : : }
3289 : 33 : }
3290 : :
3291 : : /*
3292 : : * postgresExecForeignTruncate
3293 : : * Truncate one or more foreign tables
3294 : : */
3295 : : static void
2013 fujii@postgresql.org 3296 : 15 : postgresExecForeignTruncate(List *rels,
3297 : : DropBehavior behavior,
3298 : : bool restart_seqs)
3299 : : {
3300 : 15 : Oid serverid = InvalidOid;
3301 : 15 : UserMapping *user = NULL;
3302 : 15 : PGconn *conn = NULL;
3303 : : StringInfoData sql;
3304 : : ListCell *lc;
3305 : 15 : bool server_truncatable = true;
3306 : :
3307 : : /*
3308 : : * By default, all postgres_fdw foreign tables are assumed truncatable.
3309 : : * This can be overridden by a per-server setting, which in turn can be
3310 : : * overridden by a per-table setting.
3311 : : */
3312 [ + - + + : 29 : foreach(lc, rels)
+ + ]
3313 : : {
3314 : 17 : ForeignServer *server = NULL;
3315 : 17 : Relation rel = lfirst(lc);
3316 : 17 : ForeignTable *table = GetForeignTable(RelationGetRelid(rel));
3317 : : ListCell *cell;
3318 : : bool truncatable;
3319 : :
3320 : : /*
3321 : : * First time through, determine whether the foreign server allows
3322 : : * truncates. Since all specified foreign tables are assumed to belong
3323 : : * to the same foreign server, this result can be used for other
3324 : : * foreign tables.
3325 : : */
3326 [ + + ]: 17 : if (!OidIsValid(serverid))
3327 : : {
3328 : 15 : serverid = table->serverid;
3329 : 15 : server = GetForeignServer(serverid);
3330 : :
3331 [ + - + + : 60 : foreach(cell, server->options)
+ + ]
3332 : : {
3333 : 48 : DefElem *defel = (DefElem *) lfirst(cell);
3334 : :
3335 [ + + ]: 48 : if (strcmp(defel->defname, "truncatable") == 0)
3336 : : {
3337 : 3 : server_truncatable = defGetBoolean(defel);
3338 : 3 : break;
3339 : : }
3340 : : }
3341 : : }
3342 : :
3343 : : /*
3344 : : * Confirm that all specified foreign tables belong to the same
3345 : : * foreign server.
3346 : : */
3347 [ - + ]: 17 : Assert(table->serverid == serverid);
3348 : :
3349 : : /* Determine whether this foreign table allows truncations */
3350 : 17 : truncatable = server_truncatable;
3351 [ + - + + : 34 : foreach(cell, table->options)
+ + ]
3352 : : {
3353 : 24 : DefElem *defel = (DefElem *) lfirst(cell);
3354 : :
3355 [ + + ]: 24 : if (strcmp(defel->defname, "truncatable") == 0)
3356 : : {
3357 : 7 : truncatable = defGetBoolean(defel);
3358 : 7 : break;
3359 : : }
3360 : : }
3361 : :
3362 [ + + ]: 17 : if (!truncatable)
3363 [ + - ]: 3 : ereport(ERROR,
3364 : : (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
3365 : : errmsg("foreign table \"%s\" does not allow truncates",
3366 : : RelationGetRelationName(rel))));
3367 : : }
3368 [ - + ]: 12 : Assert(OidIsValid(serverid));
3369 : :
3370 : : /*
3371 : : * Get connection to the foreign server. Connection manager will
3372 : : * establish new connection if necessary.
3373 : : */
3374 : 12 : user = GetUserMapping(GetUserId(), serverid);
3375 : 12 : conn = GetConnection(user, false, NULL);
3376 : :
3377 : : /* Construct the TRUNCATE command string */
3378 : 12 : initStringInfo(&sql);
1994 3379 : 12 : deparseTruncateSql(&sql, rels, behavior, restart_seqs);
3380 : :
3381 : : /* Issue the TRUNCATE command to remote server */
2013 3382 : 12 : do_sql_command(conn, sql.data);
3383 : :
3384 : 11 : pfree(sql.data);
3385 : 11 : }
3386 : :
3387 : : /*
3388 : : * estimate_path_cost_size
3389 : : * Get cost and size estimates for a foreign scan on given foreign relation
3390 : : * either a base relation or a join between foreign relations or an upper
3391 : : * relation containing foreign relations.
3392 : : *
3393 : : * param_join_conds are the parameterization clauses with outer relations.
3394 : : * pathkeys specify the expected sort order if any for given path being costed.
3395 : : * fpextra specifies additional post-scan/join-processing steps such as the
3396 : : * final sort and the LIMIT restriction.
3397 : : *
3398 : : * The function returns the cost and size estimates in p_rows, p_width,
3399 : : * p_disabled_nodes, p_startup_cost and p_total_cost variables.
3400 : : */
3401 : : static void
4953 tgl@sss.pgh.pa.us 3402 : 2904 : estimate_path_cost_size(PlannerInfo *root,
3403 : : RelOptInfo *foreignrel,
3404 : : List *param_join_conds,
3405 : : List *pathkeys,
3406 : : PgFdwPathExtraData *fpextra,
3407 : : double *p_rows, int *p_width,
3408 : : int *p_disabled_nodes,
3409 : : Cost *p_startup_cost, Cost *p_total_cost)
3410 : : {
3898 rhaas@postgresql.org 3411 : 2904 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) foreignrel->fdw_private;
3412 : : double rows;
3413 : : double retrieved_rows;
3414 : : int width;
782 3415 : 2904 : int disabled_nodes = 0;
3416 : : Cost startup_cost;
3417 : : Cost total_cost;
3418 : :
3419 : : /* Make sure the core code has set up the relation's reltarget */
2818 efujita@postgresql.o 3420 [ - + ]: 2904 : Assert(foreignrel->reltarget);
3421 : :
3422 : : /*
3423 : : * If the table or the server is configured to use remote estimates,
3424 : : * connect to the foreign server and execute EXPLAIN to estimate the
3425 : : * number of rows selected by the restriction+join clauses. Otherwise,
3426 : : * estimate rows using whatever statistics we have locally, in a way
3427 : : * similar to ordinary tables.
3428 : : */
4953 tgl@sss.pgh.pa.us 3429 [ + + ]: 2904 : if (fpinfo->use_remote_estimate)
3430 : : {
3431 : : List *remote_param_join_conds;
3432 : : List *local_param_join_conds;
3433 : : StringInfoData sql;
3434 : : PGconn *conn;
3435 : : Selectivity local_sel;
3436 : : QualCost local_cost;
3898 rhaas@postgresql.org 3437 : 1333 : List *fdw_scan_tlist = NIL;
3438 : : List *remote_conds;
3439 : :
3440 : : /* Required only to be passed to deparseSelectStmtForRel */
3441 : : List *retrieved_attrs;
3442 : :
3443 : : /*
3444 : : * param_join_conds might contain both clauses that are safe to send
3445 : : * across, and clauses that aren't.
3446 : : */
54 akorotkov@postgresql 3447 :GNC 1333 : classifyConditions(root, foreignrel, fpinfo, param_join_conds,
3448 : : &remote_param_join_conds, &local_param_join_conds);
3449 : :
3450 : : /* Build the list of columns to be fetched from the foreign server. */
3479 rhaas@postgresql.org 3451 [ + + + + :CBC 1333 : if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
+ + - + ]
3898 3452 : 535 : fdw_scan_tlist = build_tlist_to_deparse(foreignrel);
3453 : : else
3454 : 798 : fdw_scan_tlist = NIL;
3455 : :
3456 : : /*
3457 : : * The complete list of remote conditions includes everything from
3458 : : * baserestrictinfo plus any extra join_conds relevant to this
3459 : : * particular path.
3460 : : */
2618 tgl@sss.pgh.pa.us 3461 : 1333 : remote_conds = list_concat(remote_param_join_conds,
3908 rhaas@postgresql.org 3462 : 1333 : fpinfo->remote_conds);
3463 : :
3464 : : /*
3465 : : * Construct EXPLAIN query including the desired SELECT, FROM, and
3466 : : * WHERE clauses. Params and other-relation Vars are replaced by dummy
3467 : : * values, so don't request params_list.
3468 : : */
4953 tgl@sss.pgh.pa.us 3469 : 1333 : initStringInfo(&sql);
3470 : 1333 : appendStringInfoString(&sql, "EXPLAIN ");
3898 rhaas@postgresql.org 3471 [ + + + + ]: 1419 : deparseSelectStmtForRel(&sql, root, foreignrel, fdw_scan_tlist,
3472 : : remote_conds, pathkeys,
2750 efujita@postgresql.o 3473 : 43 : fpextra ? fpextra->has_final_sort : false,
3474 : 43 : fpextra ? fpextra->has_limit : false,
3475 : : false, &retrieved_attrs, NULL);
3476 : :
3477 : : /* Get the remote estimate */
2021 3478 : 1333 : conn = GetConnection(fpinfo->user, false, NULL);
4953 tgl@sss.pgh.pa.us 3479 : 1333 : get_remote_estimate(sql.data, conn, &rows, &width,
3480 : : &startup_cost, &total_cost);
3481 : 1333 : ReleaseConnection(conn);
3482 : :
3483 : 1333 : retrieved_rows = rows;
3484 : :
3485 : : /* Factor in the selectivity of the locally-checked quals */
4602 3486 : 1333 : local_sel = clauselist_selectivity(root,
3487 : : local_param_join_conds,
3898 rhaas@postgresql.org 3488 : 1333 : foreignrel->relid,
3489 : : JOIN_INNER,
3490 : : NULL);
4602 tgl@sss.pgh.pa.us 3491 : 1333 : local_sel *= fpinfo->local_conds_sel;
3492 : :
3493 : 1333 : rows = clamp_row_est(rows * local_sel);
3494 : :
3495 : : /* Add in the eval cost of the locally-checked quals */
4953 3496 : 1333 : startup_cost += fpinfo->local_conds_cost.startup;
3497 : 1333 : total_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
3898 rhaas@postgresql.org 3498 : 1333 : cost_qual_eval(&local_cost, local_param_join_conds, root);
4602 tgl@sss.pgh.pa.us 3499 : 1333 : startup_cost += local_cost.startup;
3500 : 1333 : total_cost += local_cost.per_tuple * retrieved_rows;
3501 : :
3502 : : /*
3503 : : * Add in tlist eval cost for each output row. In case of an
3504 : : * aggregate, some of the tlist expressions such as grouping
3505 : : * expressions will be evaluated remotely, so adjust the costs.
3506 : : */
2818 efujita@postgresql.o 3507 : 1333 : startup_cost += foreignrel->reltarget->cost.startup;
3508 : 1333 : total_cost += foreignrel->reltarget->cost.startup;
3509 : 1333 : total_cost += foreignrel->reltarget->cost.per_tuple * rows;
3510 [ + + - + ]: 1333 : if (IS_UPPER_REL(foreignrel))
3511 : : {
3512 : : QualCost tlist_cost;
3513 : :
3514 : 44 : cost_qual_eval(&tlist_cost, fdw_scan_tlist, root);
3515 : 44 : startup_cost -= tlist_cost.startup;
3516 : 44 : total_cost -= tlist_cost.startup;
3517 : 44 : total_cost -= tlist_cost.per_tuple * rows;
3518 : : }
3519 : : }
3520 : : else
3521 : : {
3898 rhaas@postgresql.org 3522 : 1571 : Cost run_cost = 0;
3523 : :
3524 : : /*
3525 : : * We don't support join conditions in this mode (hence, no
3526 : : * parameterized paths can be made).
3527 : : */
3528 [ - + ]: 1571 : Assert(param_join_conds == NIL);
3529 : :
3530 : : /*
3531 : : * We will come here again and again with different set of pathkeys or
3532 : : * additional post-scan/join-processing steps that caller wants to
3533 : : * cost. We don't need to calculate the cost/size estimates for the
3534 : : * underlying scan, join, or grouping each time. Instead, use those
3535 : : * estimates if we have cached them already.
3536 : : */
2813 efujita@postgresql.o 3537 [ + + + - ]: 1571 : if (fpinfo->rel_startup_cost >= 0 && fpinfo->rel_total_cost >= 0)
3538 : : {
2075 3539 [ - + ]: 355 : Assert(fpinfo->retrieved_rows >= 0);
3540 : :
2677 3541 : 355 : rows = fpinfo->rows;
3542 : 355 : retrieved_rows = fpinfo->retrieved_rows;
3543 : 355 : width = fpinfo->width;
3869 rhaas@postgresql.org 3544 : 355 : startup_cost = fpinfo->rel_startup_cost;
3545 : 355 : run_cost = fpinfo->rel_total_cost - fpinfo->rel_startup_cost;
3546 : :
3547 : : /*
3548 : : * If we estimate the costs of a foreign scan or a foreign join
3549 : : * with additional post-scan/join-processing steps, the scan or
3550 : : * join costs obtained from the cache wouldn't yet contain the
3551 : : * eval costs for the final scan/join target, which would've been
3552 : : * updated by apply_scanjoin_target_to_paths(); add the eval costs
3553 : : * now.
3554 : : */
2750 efujita@postgresql.o 3555 [ + + + + : 355 : if (fpextra && !IS_UPPER_REL(foreignrel))
+ - ]
3556 : : {
3557 : : /* Shouldn't get here unless we have LIMIT */
3558 [ - + ]: 91 : Assert(fpextra->has_limit);
3559 [ + + - + ]: 91 : Assert(foreignrel->reloptkind == RELOPT_BASEREL ||
3560 : : foreignrel->reloptkind == RELOPT_JOINREL);
3561 : 91 : startup_cost += foreignrel->reltarget->cost.startup;
3562 : 91 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3563 : : }
3564 : : }
3479 rhaas@postgresql.org 3565 [ + + + + ]: 1216 : else if (IS_JOIN_REL(foreignrel))
3898 3566 : 146 : {
3567 : : PgFdwRelationInfo *fpinfo_i;
3568 : : PgFdwRelationInfo *fpinfo_o;
3569 : : QualCost join_cost;
3570 : : QualCost remote_conds_cost;
3571 : : double nrows;
3572 : :
3573 : : /* Use rows/width estimates made by the core code. */
2677 efujita@postgresql.o 3574 : 146 : rows = foreignrel->rows;
3575 : 146 : width = foreignrel->reltarget->width;
3576 : :
3577 : : /* For join we expect inner and outer relations set */
3898 rhaas@postgresql.org 3578 [ + - - + ]: 146 : Assert(fpinfo->innerrel && fpinfo->outerrel);
3579 : :
3580 : : /*
3581 : : * For a FUNCTION RTE absorbed into the join, use the stub fpinfo
3582 : : * we built in foreign_join_ok(), since the function rel itself
3583 : : * has no fdw_private.
3584 : : */
54 akorotkov@postgresql 3585 :GNC 292 : fpinfo_i = fpinfo->inner_func_fpinfo ?
3586 [ + + ]: 146 : fpinfo->inner_func_fpinfo :
3587 : 123 : (PgFdwRelationInfo *) fpinfo->innerrel->fdw_private;
3588 : 292 : fpinfo_o = fpinfo->outer_func_fpinfo ?
3589 [ + + ]: 146 : fpinfo->outer_func_fpinfo :
3590 : 140 : (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
3591 : :
3592 : : /* Estimate of number of rows in cross product */
3898 rhaas@postgresql.org 3593 :CBC 146 : nrows = fpinfo_i->rows * fpinfo_o->rows;
3594 : :
3595 : : /*
3596 : : * Back into an estimate of the number of retrieved rows. Just in
3597 : : * case this is nuts, clamp to at most nrows.
3598 : : */
2677 efujita@postgresql.o 3599 : 146 : retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
3898 rhaas@postgresql.org 3600 [ + + ]: 146 : retrieved_rows = Min(retrieved_rows, nrows);
3601 : :
3602 : : /*
3603 : : * The cost of foreign join is estimated as cost of generating
3604 : : * rows for the joining relations + cost for applying quals on the
3605 : : * rows.
3606 : : */
3607 : :
3608 : : /*
3609 : : * Calculate the cost of clauses pushed down to the foreign server
3610 : : */
3611 : 146 : cost_qual_eval(&remote_conds_cost, fpinfo->remote_conds, root);
3612 : : /* Calculate the cost of applying join clauses */
3613 : 146 : cost_qual_eval(&join_cost, fpinfo->joinclauses, root);
3614 : :
3615 : : /*
3616 : : * Startup cost includes startup cost of joining relations and the
3617 : : * startup cost for join and other clauses. We do not include the
3618 : : * startup cost specific to join strategy (e.g. setting up hash
3619 : : * tables) since we do not know what strategy the foreign server
3620 : : * is going to use.
3621 : : */
3622 : 146 : startup_cost = fpinfo_i->rel_startup_cost + fpinfo_o->rel_startup_cost;
3623 : 146 : startup_cost += join_cost.startup;
3624 : 146 : startup_cost += remote_conds_cost.startup;
3625 : 146 : startup_cost += fpinfo->local_conds_cost.startup;
3626 : :
3627 : : /*
3628 : : * Run time cost includes:
3629 : : *
3630 : : * 1. Run time cost (total_cost - startup_cost) of relations being
3631 : : * joined
3632 : : *
3633 : : * 2. Run time cost of applying join clauses on the cross product
3634 : : * of the joining relations.
3635 : : *
3636 : : * 3. Run time cost of applying pushed down other clauses on the
3637 : : * result of join
3638 : : *
3639 : : * 4. Run time cost of applying nonpushable other clauses locally
3640 : : * on the result fetched from the foreign server.
3641 : : */
3642 : 146 : run_cost = fpinfo_i->rel_total_cost - fpinfo_i->rel_startup_cost;
3643 : 146 : run_cost += fpinfo_o->rel_total_cost - fpinfo_o->rel_startup_cost;
3644 : 146 : run_cost += nrows * join_cost.per_tuple;
3645 : 146 : nrows = clamp_row_est(nrows * fpinfo->joinclause_sel);
3646 : 146 : run_cost += nrows * remote_conds_cost.per_tuple;
3647 : 146 : run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
3648 : :
3649 : : /* Add in tlist eval cost for each output row */
2818 efujita@postgresql.o 3650 : 146 : startup_cost += foreignrel->reltarget->cost.startup;
3651 : 146 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3652 : : }
3479 rhaas@postgresql.org 3653 [ + + + + ]: 1070 : else if (IS_UPPER_REL(foreignrel))
3643 3654 : 100 : {
2713 efujita@postgresql.o 3655 : 100 : RelOptInfo *outerrel = fpinfo->outerrel;
3656 : : PgFdwRelationInfo *ofpinfo;
571 peter@eisentraut.org 3657 : 100 : AggClauseCosts aggcosts = {0};
3658 : : double input_rows;
3659 : : int numGroupCols;
3643 rhaas@postgresql.org 3660 : 100 : double numGroups = 1;
3661 : :
3662 : : /* The upper relation should have its outer relation set */
2713 efujita@postgresql.o 3663 [ - + ]: 100 : Assert(outerrel);
3664 : : /* and that outer relation should have its reltarget set */
3665 [ - + ]: 100 : Assert(outerrel->reltarget);
3666 : :
3667 : : /*
3668 : : * This cost model is mixture of costing done for sorted and
3669 : : * hashed aggregates in cost_agg(). We are not sure which
3670 : : * strategy will be considered at remote side, thus for
3671 : : * simplicity, we put all startup related costs in startup_cost
3672 : : * and all finalization and run cost are added in total_cost.
3673 : : */
3674 : :
3675 : 100 : ofpinfo = (PgFdwRelationInfo *) outerrel->fdw_private;
3676 : :
3677 : : /* Get rows from input rel */
3643 rhaas@postgresql.org 3678 : 100 : input_rows = ofpinfo->rows;
3679 : :
3680 : : /* Collect statistics about aggregates for estimating costs. */
3681 [ + + ]: 100 : if (root->parse->hasAggs)
3682 : : {
2148 heikki.linnakangas@i 3683 : 96 : get_agg_clause_costs(root, AGGSPLIT_SIMPLE, &aggcosts);
3684 : : }
3685 : :
3686 : : /* Get number of grouping columns and possible number of groups */
1363 tgl@sss.pgh.pa.us 3687 : 100 : numGroupCols = list_length(root->processed_groupClause);
3643 rhaas@postgresql.org 3688 : 100 : numGroups = estimate_num_groups(root,
3689 : : get_sortgrouplist_exprs(root->processed_groupClause,
3690 : : fpinfo->grouped_tlist),
3691 : : input_rows, NULL, NULL);
3692 : :
3693 : : /*
3694 : : * Get the retrieved_rows and rows estimates. If there are HAVING
3695 : : * quals, account for their selectivity.
3696 : : */
1455 tgl@sss.pgh.pa.us 3697 [ + + ]: 100 : if (root->hasHavingQual)
3698 : : {
3699 : : /* Factor in the selectivity of the remotely-checked quals */
3700 : : retrieved_rows =
2869 efujita@postgresql.o 3701 : 14 : clamp_row_est(numGroups *
3702 : 14 : clauselist_selectivity(root,
3703 : : fpinfo->remote_conds,
3704 : : 0,
3705 : : JOIN_INNER,
3706 : : NULL));
3707 : : /* Factor in the selectivity of the locally-checked quals */
3708 : 14 : rows = clamp_row_est(retrieved_rows * fpinfo->local_conds_sel);
3709 : : }
3710 : : else
3711 : : {
3712 : 86 : rows = retrieved_rows = numGroups;
3713 : : }
3714 : :
3715 : : /* Use width estimate made by the core code. */
2677 3716 : 100 : width = foreignrel->reltarget->width;
3717 : :
3718 : : /*-----
3719 : : * Startup cost includes:
3720 : : * 1. Startup cost for underneath input relation, adjusted for
3721 : : * tlist replacement by apply_scanjoin_target_to_paths()
3722 : : * 2. Cost of performing aggregation, per cost_agg()
3723 : : *-----
3724 : : */
3643 rhaas@postgresql.org 3725 : 100 : startup_cost = ofpinfo->rel_startup_cost;
2713 efujita@postgresql.o 3726 : 100 : startup_cost += outerrel->reltarget->cost.startup;
3643 rhaas@postgresql.org 3727 : 100 : startup_cost += aggcosts.transCost.startup;
3728 : 100 : startup_cost += aggcosts.transCost.per_tuple * input_rows;
2802 tgl@sss.pgh.pa.us 3729 : 100 : startup_cost += aggcosts.finalCost.startup;
3643 rhaas@postgresql.org 3730 : 100 : startup_cost += (cpu_operator_cost * numGroupCols) * input_rows;
3731 : :
3732 : : /*-----
3733 : : * Run time cost includes:
3734 : : * 1. Run time cost of underneath input relation, adjusted for
3735 : : * tlist replacement by apply_scanjoin_target_to_paths()
3736 : : * 2. Run time cost of performing aggregation, per cost_agg()
3737 : : *-----
3738 : : */
3739 : 100 : run_cost = ofpinfo->rel_total_cost - ofpinfo->rel_startup_cost;
2713 efujita@postgresql.o 3740 : 100 : run_cost += outerrel->reltarget->cost.per_tuple * input_rows;
2802 tgl@sss.pgh.pa.us 3741 : 100 : run_cost += aggcosts.finalCost.per_tuple * numGroups;
3643 rhaas@postgresql.org 3742 : 100 : run_cost += cpu_tuple_cost * numGroups;
3743 : :
3744 : : /* Account for the eval cost of HAVING quals, if any */
1455 tgl@sss.pgh.pa.us 3745 [ + + ]: 100 : if (root->hasHavingQual)
3746 : : {
3747 : : QualCost remote_cost;
3748 : :
3749 : : /* Add in the eval cost of the remotely-checked quals */
2869 efujita@postgresql.o 3750 : 14 : cost_qual_eval(&remote_cost, fpinfo->remote_conds, root);
3751 : 14 : startup_cost += remote_cost.startup;
3752 : 14 : run_cost += remote_cost.per_tuple * numGroups;
3753 : : /* Add in the eval cost of the locally-checked quals */
3754 : 14 : startup_cost += fpinfo->local_conds_cost.startup;
3755 : 14 : run_cost += fpinfo->local_conds_cost.per_tuple * retrieved_rows;
3756 : : }
3757 : :
3758 : : /* Add in tlist eval cost for each output row */
2818 3759 : 100 : startup_cost += foreignrel->reltarget->cost.startup;
3760 : 100 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3761 : : }
3762 : : else
3763 : : {
3764 : : Cost cpu_per_tuple;
3765 : :
3766 : : /* Use rows/width estimates made by set_baserel_size_estimates. */
2677 3767 : 970 : rows = foreignrel->rows;
3768 : 970 : width = foreignrel->reltarget->width;
3769 : :
3770 : : /*
3771 : : * Back into an estimate of the number of retrieved rows. Just in
3772 : : * case this is nuts, clamp to at most foreignrel->tuples.
3773 : : */
3774 : 970 : retrieved_rows = clamp_row_est(rows / fpinfo->local_conds_sel);
3643 rhaas@postgresql.org 3775 [ + + ]: 970 : retrieved_rows = Min(retrieved_rows, foreignrel->tuples);
3776 : :
3777 : : /*
3778 : : * Cost as though this were a seqscan, which is pessimistic. We
3779 : : * effectively imagine the local_conds are being evaluated
3780 : : * remotely, too.
3781 : : */
3782 : 970 : startup_cost = 0;
3783 : 970 : run_cost = 0;
3784 : 970 : run_cost += seq_page_cost * foreignrel->pages;
3785 : :
3786 : 970 : startup_cost += foreignrel->baserestrictcost.startup;
3787 : 970 : cpu_per_tuple = cpu_tuple_cost + foreignrel->baserestrictcost.per_tuple;
3788 : 970 : run_cost += cpu_per_tuple * foreignrel->tuples;
3789 : :
3790 : : /* Add in tlist eval cost for each output row */
2818 efujita@postgresql.o 3791 : 970 : startup_cost += foreignrel->reltarget->cost.startup;
3792 : 970 : run_cost += foreignrel->reltarget->cost.per_tuple * rows;
3793 : : }
3794 : :
3795 : : /*
3796 : : * Without remote estimates, we have no real way to estimate the cost
3797 : : * of generating sorted output. It could be free if the query plan
3798 : : * the remote side would have chosen generates properly-sorted output
3799 : : * anyway, but in most cases it will cost something. Estimate a value
3800 : : * high enough that we won't pick the sorted path when the ordering
3801 : : * isn't locally useful, but low enough that we'll err on the side of
3802 : : * pushing down the ORDER BY clause when it's useful to do so.
3803 : : */
3996 rhaas@postgresql.org 3804 [ + + ]: 1571 : if (pathkeys != NIL)
3805 : : {
2750 efujita@postgresql.o 3806 [ + + - + ]: 298 : if (IS_UPPER_REL(foreignrel))
3807 : : {
3808 [ + - - + ]: 30 : Assert(foreignrel->reloptkind == RELOPT_UPPER_REL &&
3809 : : fpinfo->stage == UPPERREL_GROUP_AGG);
3810 : :
3811 : : /*
3812 : : * We can only get here when this function is called from
3813 : : * add_foreign_ordered_paths() or add_foreign_final_paths();
3814 : : * in which cases, the passed-in fpextra should not be NULL.
3815 : : */
463 3816 [ - + ]: 30 : Assert(fpextra);
2750 3817 : 30 : adjust_foreign_grouping_path_cost(root, pathkeys,
3818 : : retrieved_rows, width,
3819 : : fpextra->limit_tuples,
3820 : : &disabled_nodes,
3821 : : &startup_cost, &run_cost);
3822 : : }
3823 : : else
3824 : : {
3825 : 268 : startup_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
3826 : 268 : run_cost *= DEFAULT_FDW_SORT_MULTIPLIER;
3827 : : }
3828 : : }
3829 : :
4953 tgl@sss.pgh.pa.us 3830 : 1571 : total_cost = startup_cost + run_cost;
3831 : :
3832 : : /* Adjust the cost estimates if we have LIMIT */
2750 efujita@postgresql.o 3833 [ + + + + ]: 1571 : if (fpextra && fpextra->has_limit)
3834 : : {
3835 : 93 : adjust_limit_rows_costs(&rows, &startup_cost, &total_cost,
3836 : : fpextra->offset_est, fpextra->count_est);
3837 : 93 : retrieved_rows = rows;
3838 : : }
3839 : : }
3840 : :
3841 : : /*
3842 : : * If this includes the final sort step, the given target, which will be
3843 : : * applied to the resulting path, might have different expressions from
3844 : : * the foreignrel's reltarget (see make_sort_input_target()); adjust tlist
3845 : : * eval costs.
3846 : : */
3847 [ + + + + ]: 2904 : if (fpextra && fpextra->has_final_sort &&
3848 [ + + ]: 111 : fpextra->target != foreignrel->reltarget)
3849 : : {
3850 : 6 : QualCost oldcost = foreignrel->reltarget->cost;
3851 : 6 : QualCost newcost = fpextra->target->cost;
3852 : :
3853 : 6 : startup_cost += newcost.startup - oldcost.startup;
3854 : 6 : total_cost += newcost.startup - oldcost.startup;
3855 : 6 : total_cost += (newcost.per_tuple - oldcost.per_tuple) * rows;
3856 : : }
3857 : :
3858 : : /*
3859 : : * Cache the retrieved rows and cost estimates for scans, joins, or
3860 : : * groupings without any parameterization, pathkeys, or additional
3861 : : * post-scan/join-processing steps, before adding the costs for
3862 : : * transferring data from the foreign server. These estimates are useful
3863 : : * for costing remote joins involving this relation or costing other
3864 : : * remote operations on this relation such as remote sorts and remote
3865 : : * LIMIT restrictions, when the costs can not be obtained from the foreign
3866 : : * server. This function will be called at least once for every foreign
3867 : : * relation without any parameterization, pathkeys, or additional
3868 : : * post-scan/join-processing steps.
3869 : : */
3870 [ + + + + : 2904 : if (pathkeys == NIL && param_join_conds == NIL && fpextra == NULL)
+ + ]
3871 : : {
2677 3872 : 1788 : fpinfo->retrieved_rows = retrieved_rows;
3869 rhaas@postgresql.org 3873 : 1788 : fpinfo->rel_startup_cost = startup_cost;
3874 : 1788 : fpinfo->rel_total_cost = total_cost;
3875 : : }
3876 : :
3877 : : /*
3878 : : * Add some additional cost factors to account for connection overhead
3879 : : * (fdw_startup_cost), transferring data across the network
3880 : : * (fdw_tuple_cost per retrieved row), and local manipulation of the data
3881 : : * (cpu_tuple_cost per retrieved row).
3882 : : */
4953 tgl@sss.pgh.pa.us 3883 : 2904 : startup_cost += fpinfo->fdw_startup_cost;
3884 : 2904 : total_cost += fpinfo->fdw_startup_cost;
3885 : 2904 : total_cost += fpinfo->fdw_tuple_cost * retrieved_rows;
3886 : 2904 : total_cost += cpu_tuple_cost * retrieved_rows;
3887 : :
3888 : : /*
3889 : : * If we have LIMIT, we should prefer performing the restriction remotely
3890 : : * rather than locally, as the former avoids extra row fetches from the
3891 : : * remote that the latter might cause. But since the core code doesn't
3892 : : * account for such fetches when estimating the costs of the local
3893 : : * restriction (see create_limit_path()), there would be no difference
3894 : : * between the costs of the local restriction and the costs of the remote
3895 : : * restriction estimated above if we don't use remote estimates (except
3896 : : * for the case where the foreignrel is a grouping relation, the given
3897 : : * pathkeys is not NIL, and the effects of a bounded sort for that rel is
3898 : : * accounted for in costing the remote restriction). Tweak the costs of
3899 : : * the remote restriction to ensure we'll prefer it if LIMIT is a useful
3900 : : * one.
3901 : : */
2750 efujita@postgresql.o 3902 [ + + + + ]: 2904 : if (!fpinfo->use_remote_estimate &&
3903 [ + + ]: 123 : fpextra && fpextra->has_limit &&
3904 [ + - ]: 93 : fpextra->limit_tuples > 0 &&
3905 [ + + ]: 93 : fpextra->limit_tuples < fpinfo->rows)
3906 : : {
3907 [ - + ]: 87 : Assert(fpinfo->rows > 0);
3908 : 87 : total_cost -= (total_cost - startup_cost) * 0.05 *
3909 : 87 : (fpinfo->rows - fpextra->limit_tuples) / fpinfo->rows;
3910 : : }
3911 : :
3912 : : /* Return results. */
4953 tgl@sss.pgh.pa.us 3913 : 2904 : *p_rows = rows;
3914 : 2904 : *p_width = width;
782 rhaas@postgresql.org 3915 : 2904 : *p_disabled_nodes = disabled_nodes;
4953 tgl@sss.pgh.pa.us 3916 : 2904 : *p_startup_cost = startup_cost;
3917 : 2904 : *p_total_cost = total_cost;
3918 : 2904 : }
3919 : :
3920 : : /*
3921 : : * Estimate costs of executing a SQL statement remotely.
3922 : : * The given "sql" must be an EXPLAIN command.
3923 : : */
3924 : : static void
4981 3925 : 1333 : get_remote_estimate(const char *sql, PGconn *conn,
3926 : : double *rows, int *width,
3927 : : Cost *startup_cost, Cost *total_cost)
3928 : : {
3929 : : PGresult *res;
3930 : : char *line;
3931 : : char *p;
3932 : : int n;
3933 : :
3934 : : /*
3935 : : * Execute EXPLAIN remotely.
3936 : : */
444 3937 : 1333 : res = pgfdw_exec_query(conn, sql, NULL);
3938 [ - + ]: 1333 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 3939 :UBC 0 : pgfdw_report_error(res, conn, sql);
3940 : :
3941 : : /*
3942 : : * Extract cost numbers for topmost plan node. Note we search for a left
3943 : : * paren from the end of the line to avoid being confused by other uses of
3944 : : * parentheses.
3945 : : */
444 tgl@sss.pgh.pa.us 3946 :CBC 1333 : line = PQgetvalue(res, 0, 0);
3947 : 1333 : p = strrchr(line, '(');
3948 [ - + ]: 1333 : if (p == NULL)
444 tgl@sss.pgh.pa.us 3949 [ # # ]:UBC 0 : elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
444 tgl@sss.pgh.pa.us 3950 :CBC 1333 : n = sscanf(p, "(cost=%lf..%lf rows=%lf width=%d)",
3951 : : startup_cost, total_cost, rows, width);
3952 [ - + ]: 1333 : if (n != 4)
444 tgl@sss.pgh.pa.us 3953 [ # # ]:UBC 0 : elog(ERROR, "could not interpret EXPLAIN output: \"%s\"", line);
444 tgl@sss.pgh.pa.us 3954 :CBC 1333 : PQclear(res);
4981 3955 : 1333 : }
3956 : :
3957 : : /*
3958 : : * Adjust the cost estimates of a foreign grouping path to include the cost of
3959 : : * generating properly-sorted output.
3960 : : */
3961 : : static void
2750 efujita@postgresql.o 3962 : 30 : adjust_foreign_grouping_path_cost(PlannerInfo *root,
3963 : : List *pathkeys,
3964 : : double retrieved_rows,
3965 : : double width,
3966 : : double limit_tuples,
3967 : : int *p_disabled_nodes,
3968 : : Cost *p_startup_cost,
3969 : : Cost *p_run_cost)
3970 : : {
3971 : : /*
3972 : : * If the GROUP BY clause isn't sort-able, the plan chosen by the remote
3973 : : * side is unlikely to generate properly-sorted output, so it would need
3974 : : * an explicit sort; adjust the given costs with cost_sort(). Likewise,
3975 : : * if the GROUP BY clause is sort-able but isn't a superset of the given
3976 : : * pathkeys, adjust the costs with that function. Otherwise, adjust the
3977 : : * costs by applying the same heuristic as for the scan or join case.
3978 : : */
1363 tgl@sss.pgh.pa.us 3979 [ + - ]: 30 : if (!grouping_is_sortable(root->processed_groupClause) ||
2750 efujita@postgresql.o 3980 [ + + ]: 30 : !pathkeys_contained_in(pathkeys, root->group_pathkeys))
3981 : 22 : {
3982 : : Path sort_path; /* dummy for result of cost_sort */
3983 : :
3984 : 22 : cost_sort(&sort_path,
3985 : : root,
3986 : : pathkeys,
3987 : : 0,
3988 : 22 : *p_startup_cost + *p_run_cost,
3989 : : retrieved_rows,
3990 : : width,
3991 : : 0.0,
3992 : : work_mem,
3993 : : limit_tuples);
3994 : :
3995 : 22 : *p_startup_cost = sort_path.startup_cost;
3996 : 22 : *p_run_cost = sort_path.total_cost - sort_path.startup_cost;
3997 : : }
3998 : : else
3999 : : {
4000 : : /*
4001 : : * The default extra cost seems too large for foreign-grouping cases;
4002 : : * add 1/4th of that default.
4003 : : */
4004 : 8 : double sort_multiplier = 1.0 + (DEFAULT_FDW_SORT_MULTIPLIER
4005 : : - 1.0) * 0.25;
4006 : :
4007 : 8 : *p_startup_cost *= sort_multiplier;
4008 : 8 : *p_run_cost *= sort_multiplier;
4009 : : }
4010 : 30 : }
4011 : :
4012 : : /*
4013 : : * Detect whether we want to process an EquivalenceClass member.
4014 : : *
4015 : : * This is a callback for use by generate_implied_equalities_for_column.
4016 : : */
4017 : : static bool
4953 tgl@sss.pgh.pa.us 4018 : 314 : ec_member_matches_foreign(PlannerInfo *root, RelOptInfo *rel,
4019 : : EquivalenceClass *ec, EquivalenceMember *em,
4020 : : void *arg)
4021 : : {
4022 : 314 : ec_member_foreign_arg *state = (ec_member_foreign_arg *) arg;
4023 : 314 : Expr *expr = em->em_expr;
4024 : :
4025 : : /*
4026 : : * If we've identified what we're processing in the current scan, we only
4027 : : * want to match that expression.
4028 : : */
4029 [ - + ]: 314 : if (state->current != NULL)
4953 tgl@sss.pgh.pa.us 4030 :UBC 0 : return equal(expr, state->current);
4031 : :
4032 : : /*
4033 : : * Otherwise, ignore anything we've already processed.
4034 : : */
4953 tgl@sss.pgh.pa.us 4035 [ + + ]:CBC 314 : if (list_member(state->already_used, expr))
4036 : 165 : return false;
4037 : :
4038 : : /* This is the new target to process. */
4039 : 149 : state->current = expr;
4040 : 149 : return true;
4041 : : }
4042 : :
4043 : : /*
4044 : : * Create cursor for node's query with current parameter values.
4045 : : */
4046 : : static void
4981 4047 : 907 : create_cursor(ForeignScanState *node)
4048 : : {
4964 4049 : 907 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
4953 4050 : 907 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
4964 4051 : 907 : int numParams = fsstate->numParams;
4052 : 907 : const char **values = fsstate->param_values;
4053 : 907 : PGconn *conn = fsstate->conn;
4054 : : StringInfoData buf;
4055 : : PGresult *res;
4056 : :
4057 : : /* First, process a pending asynchronous request, if any. */
2021 efujita@postgresql.o 4058 [ + + ]: 907 : if (fsstate->conn_state->pendingAreq)
4059 : 1 : process_pending_request(fsstate->conn_state->pendingAreq);
4060 : :
4061 : : /*
4062 : : * Construct array of query parameter values in text format. We do the
4063 : : * conversions in the short-lived per-tuple context, so as not to cause a
4064 : : * memory leak over repeated scans.
4065 : : */
4953 tgl@sss.pgh.pa.us 4066 [ + + ]: 907 : if (numParams > 0)
4067 : : {
4068 : : MemoryContext oldcontext;
4069 : :
4070 : 389 : oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
4071 : :
3860 rhaas@postgresql.org 4072 : 389 : process_query_params(econtext,
4073 : : fsstate->param_flinfo,
4074 : : fsstate->param_exprs,
4075 : : values);
4076 : :
4953 tgl@sss.pgh.pa.us 4077 : 389 : MemoryContextSwitchTo(oldcontext);
4078 : : }
4079 : :
4080 : : /* Construct the DECLARE CURSOR command */
4981 4081 : 907 : initStringInfo(&buf);
4082 : 907 : appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
4083 : : fsstate->cursor_number, fsstate->query);
4084 : :
4085 : : /*
4086 : : * Notice that we pass NULL for paramTypes, thus forcing the remote server
4087 : : * to infer types for all parameters. Since we explicitly cast every
4088 : : * parameter (see deparse.c), the "inference" is trivial and will produce
4089 : : * the desired result. This allows us to avoid assuming that the remote
4090 : : * server has the same OIDs we do for the parameters' types.
4091 : : */
3826 rhaas@postgresql.org 4092 [ + + ]: 907 : if (!PQsendQueryParams(conn, buf.data, numParams,
4093 : : NULL, values, NULL, NULL, 0))
440 tgl@sss.pgh.pa.us 4094 : 1 : pgfdw_report_error(NULL, conn, buf.data);
4095 : :
4096 : : /*
4097 : : * Get the result, and check for success.
4098 : : */
1008 noah@leadboat.com 4099 : 906 : res = pgfdw_get_result(conn);
4981 tgl@sss.pgh.pa.us 4100 [ + + ]: 906 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 4101 : 3 : pgfdw_report_error(res, conn, fsstate->query);
4981 4102 : 903 : PQclear(res);
4103 : :
4104 : : /* Mark the cursor as created, and show no tuples have been retrieved */
4964 4105 : 903 : fsstate->cursor_exists = true;
4106 : 903 : fsstate->tuples = NULL;
4107 : 903 : fsstate->num_tuples = 0;
4108 : 903 : fsstate->next_tuple = 0;
4109 : 903 : fsstate->fetch_ct_2 = 0;
4110 : 903 : fsstate->eof_reached = false;
4111 : :
4112 : : /* Clean up */
4981 4113 : 903 : pfree(buf.data);
4114 : 903 : }
4115 : :
4116 : : /*
4117 : : * Fetch some more rows from the node's cursor.
4118 : : */
4119 : : static void
4120 : 1573 : fetch_more_data(ForeignScanState *node)
4121 : : {
4964 4122 : 1573 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
444 4123 : 1573 : PGconn *conn = fsstate->conn;
4124 : : PGresult *res;
4125 : : int numrows;
4126 : : int i;
4127 : : MemoryContext oldcontext;
4128 : :
4129 : : /*
4130 : : * We'll store the tuples in the batch_cxt. First, flush the previous
4131 : : * batch.
4132 : : */
4964 4133 : 1573 : fsstate->tuples = NULL;
4134 : 1573 : MemoryContextReset(fsstate->batch_cxt);
4135 : 1573 : oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt);
4136 : :
444 4137 [ + + ]: 1573 : if (fsstate->async_capable)
4138 : : {
4139 [ - + ]: 165 : Assert(fsstate->conn_state->pendingAreq);
4140 : :
4141 : : /*
4142 : : * The query was already sent by an earlier call to
4143 : : * fetch_more_data_begin. So now we just fetch the result.
4144 : : */
4145 : 165 : res = pgfdw_get_result(conn);
4146 : : /* On error, report the original query, not the FETCH. */
4147 [ - + ]: 165 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 4148 :UBC 0 : pgfdw_report_error(res, conn, fsstate->query);
4149 : :
4150 : : /* Reset per-connection state */
444 tgl@sss.pgh.pa.us 4151 :CBC 165 : fsstate->conn_state->pendingAreq = NULL;
4152 : : }
4153 : : else
4154 : : {
4155 : : char sql[64];
4156 : :
4157 : : /* This is a regular synchronous fetch. */
4158 : 1408 : snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
4159 : : fsstate->fetch_size, fsstate->cursor_number);
4160 : :
4161 : 1408 : res = pgfdw_exec_query(conn, sql, fsstate->conn_state);
4162 : : /* On error, report the original query, not the FETCH. */
4163 [ + + ]: 1407 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 4164 : 9 : pgfdw_report_error(res, conn, fsstate->query);
4165 : : }
4166 : :
4167 : : /* Convert the data into HeapTuples */
444 4168 : 1563 : numrows = PQntuples(res);
56 michael@paquier.xyz 4169 :GNC 1563 : fsstate->tuples = palloc0_array(HeapTuple, numrows);
444 tgl@sss.pgh.pa.us 4170 :CBC 1563 : fsstate->num_tuples = numrows;
4171 : 1563 : fsstate->next_tuple = 0;
4172 : :
4173 [ + + ]: 72974 : for (i = 0; i < numrows; i++)
4174 : : {
4175 [ - + ]: 71415 : Assert(IsA(node->ss.ps.plan, ForeignScan));
4176 : :
4177 : 71411 : fsstate->tuples[i] =
4178 : 71415 : make_tuple_from_result_row(res, i,
4179 : : fsstate->rel,
4180 : : fsstate->attinmeta,
4181 : : fsstate->retrieved_attrs,
4182 : : node,
4183 : : fsstate->temp_cxt);
4184 : : }
4185 : :
4186 : : /* Update fetch_ct_2 */
4187 [ + + ]: 1559 : if (fsstate->fetch_ct_2 < 2)
4188 : 1007 : fsstate->fetch_ct_2++;
4189 : :
4190 : : /* Must be EOF if we didn't get as many tuples as we asked for. */
4191 : 1559 : fsstate->eof_reached = (numrows < fsstate->fetch_size);
4192 : :
4193 : 1559 : PQclear(res);
4194 : :
4981 4195 : 1559 : MemoryContextSwitchTo(oldcontext);
4196 : 1559 : }
4197 : :
4198 : : /*
4199 : : * Force assorted GUC parameters to settings that ensure that we'll output
4200 : : * data values in a form that is unambiguous to the remote server.
4201 : : *
4202 : : * This is rather expensive and annoying to do once per row, but there's
4203 : : * little choice if we want to be sure values are transmitted accurately;
4204 : : * we can't leave the settings in place between rows for fear of affecting
4205 : : * user-visible computations.
4206 : : *
4207 : : * We use the equivalent of a function SET option to allow the settings to
4208 : : * persist only until the caller calls reset_transmission_modes(). If an
4209 : : * error is thrown in between, guc.c will take care of undoing the settings.
4210 : : *
4211 : : * The return value is the nestlevel that must be passed to
4212 : : * reset_transmission_modes() to undo things.
4213 : : */
4214 : : int
4963 4215 : 4384 : set_transmission_modes(void)
4216 : : {
4217 : 4384 : int nestlevel = NewGUCNestLevel();
4218 : :
4219 : : /*
4220 : : * The values set here should match what pg_dump does. See also
4221 : : * configure_remote_session in connection.c.
4222 : : */
4223 [ + + ]: 4384 : if (DateStyle != USE_ISO_DATES)
4224 : 4381 : (void) set_config_option("datestyle", "ISO",
4225 : : PGC_USERSET, PGC_S_SESSION,
4226 : : GUC_ACTION_SAVE, true, 0, false);
4227 [ + + ]: 4384 : if (IntervalStyle != INTSTYLE_POSTGRES)
4228 : 4381 : (void) set_config_option("intervalstyle", "postgres",
4229 : : PGC_USERSET, PGC_S_SESSION,
4230 : : GUC_ACTION_SAVE, true, 0, false);
4231 [ + + ]: 4384 : if (extra_float_digits < 3)
4232 : 4382 : (void) set_config_option("extra_float_digits", "3",
4233 : : PGC_USERSET, PGC_S_SESSION,
4234 : : GUC_ACTION_SAVE, true, 0, false);
4235 : :
4236 : : /*
4237 : : * In addition force restrictive search_path, in case there are any
4238 : : * regproc or similar constants to be printed.
4239 : : */
1548 4240 : 4384 : (void) set_config_option("search_path", "pg_catalog",
4241 : : PGC_USERSET, PGC_S_SESSION,
4242 : : GUC_ACTION_SAVE, true, 0, false);
4243 : :
4963 4244 : 4384 : return nestlevel;
4245 : : }
4246 : :
4247 : : /*
4248 : : * Undo the effects of set_transmission_modes().
4249 : : */
4250 : : void
4251 : 4384 : reset_transmission_modes(int nestlevel)
4252 : : {
4253 : 4384 : AtEOXact_GUC(true, nestlevel);
4254 : 4384 : }
4255 : :
4256 : : /*
4257 : : * Utility routine to close a cursor.
4258 : : */
4259 : : static void
2021 efujita@postgresql.o 4260 : 544 : close_cursor(PGconn *conn, unsigned int cursor_number,
4261 : : PgFdwConnState *conn_state)
4262 : : {
4263 : : char sql[64];
4264 : : PGresult *res;
4265 : :
4981 tgl@sss.pgh.pa.us 4266 : 544 : snprintf(sql, sizeof(sql), "CLOSE c%u", cursor_number);
2021 efujita@postgresql.o 4267 : 544 : res = pgfdw_exec_query(conn, sql, conn_state);
4981 tgl@sss.pgh.pa.us 4268 [ + + ]: 544 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 4269 : 1 : pgfdw_report_error(res, conn, sql);
4981 4270 : 543 : PQclear(res);
4271 : 543 : }
4272 : :
4273 : : /*
4274 : : * create_foreign_modify
4275 : : * Construct an execution state of a foreign insert/update/delete
4276 : : * operation
4277 : : */
4278 : : static PgFdwModifyState *
3111 rhaas@postgresql.org 4279 : 185 : create_foreign_modify(EState *estate,
4280 : : RangeTblEntry *rte,
4281 : : ResultRelInfo *resultRelInfo,
4282 : : CmdType operation,
4283 : : Plan *subplan,
4284 : : char *query,
4285 : : List *target_attrs,
4286 : : int values_end,
4287 : : bool has_returning,
4288 : : List *retrieved_attrs)
4289 : : {
4290 : : PgFdwModifyState *fmstate;
4291 : 185 : Relation rel = resultRelInfo->ri_RelationDesc;
4292 : 185 : TupleDesc tupdesc = RelationGetDescr(rel);
4293 : : Oid userid;
4294 : : ForeignTable *table;
4295 : : UserMapping *user;
4296 : : AttrNumber n_params;
4297 : : Oid typefnoid;
4298 : : bool isvarlena;
4299 : : ListCell *lc;
4300 : :
4301 : : /* Begin constructing PgFdwModifyState. */
305 michael@paquier.xyz 4302 : 185 : fmstate = palloc0_object(PgFdwModifyState);
3111 rhaas@postgresql.org 4303 : 185 : fmstate->rel = rel;
4304 : :
4305 : : /* Identify which user to do the remote access as. */
1406 alvherre@alvh.no-ip. 4306 : 185 : userid = ExecGetResultRelCheckAsUser(resultRelInfo, estate);
4307 : :
4308 : : /* Get info about foreign table. */
3111 rhaas@postgresql.org 4309 : 185 : table = GetForeignTable(RelationGetRelid(rel));
4310 : 185 : user = GetUserMapping(userid, table->serverid);
4311 : :
4312 : : /* Open connection; report that we'll create a prepared statement. */
2021 efujita@postgresql.o 4313 : 185 : fmstate->conn = GetConnection(user, true, &fmstate->conn_state);
3111 rhaas@postgresql.org 4314 : 185 : fmstate->p_name = NULL; /* prepared statement not made yet */
4315 : :
4316 : : /* Set up remote query information. */
4317 : 185 : fmstate->query = query;
2091 tomas.vondra@postgre 4318 [ + + ]: 185 : if (operation == CMD_INSERT)
4319 : : {
1984 4320 : 134 : fmstate->query = pstrdup(fmstate->query);
2091 4321 : 134 : fmstate->orig_query = pstrdup(fmstate->query);
4322 : : }
3111 rhaas@postgresql.org 4323 : 185 : fmstate->target_attrs = target_attrs;
2091 tomas.vondra@postgre 4324 : 185 : fmstate->values_end = values_end;
3111 rhaas@postgresql.org 4325 : 185 : fmstate->has_returning = has_returning;
4326 : 185 : fmstate->retrieved_attrs = retrieved_attrs;
4327 : :
4328 : : /* Create context for per-tuple temp workspace. */
4329 : 185 : fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
4330 : : "postgres_fdw temporary data",
4331 : : ALLOCSET_SMALL_SIZES);
4332 : :
4333 : : /* Prepare for input conversion of RETURNING results. */
4334 [ + + ]: 185 : if (fmstate->has_returning)
4335 : 64 : fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
4336 : :
4337 : : /* Prepare for output conversion of parameters used in prepared stmt. */
4338 : 185 : n_params = list_length(fmstate->target_attrs) + 1;
305 michael@paquier.xyz 4339 : 185 : fmstate->p_flinfo = palloc0_array(FmgrInfo, n_params);
3111 rhaas@postgresql.org 4340 : 185 : fmstate->p_nums = 0;
4341 : :
4342 [ + + + + ]: 185 : if (operation == CMD_UPDATE || operation == CMD_DELETE)
4343 : : {
4344 [ - + ]: 51 : Assert(subplan != NULL);
4345 : :
4346 : : /* Find the ctid resjunk column in the subplan's result */
4347 : 51 : fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
4348 : : "ctid");
4349 [ - + ]: 51 : if (!AttributeNumberIsValid(fmstate->ctidAttno))
3111 rhaas@postgresql.org 4350 [ # # ]:UBC 0 : elog(ERROR, "could not find junk ctid column");
4351 : :
4352 : : /* First transmittable parameter will be ctid */
3111 rhaas@postgresql.org 4353 :CBC 51 : getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
4354 : 51 : fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
4355 : 51 : fmstate->p_nums++;
4356 : : }
4357 : :
4358 [ + + + + ]: 185 : if (operation == CMD_INSERT || operation == CMD_UPDATE)
4359 : : {
4360 : : /* Set up for remaining transmittable parameters */
4361 [ + + + + : 575 : foreach(lc, fmstate->target_attrs)
+ + ]
4362 : : {
4363 : 403 : int attnum = lfirst_int(lc);
4364 : 403 : Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
4365 : :
4366 [ - + ]: 403 : Assert(!attr->attisdropped);
4367 : :
4368 : : /* Ignore generated columns; they are set to DEFAULT */
1894 efujita@postgresql.o 4369 [ + + ]: 403 : if (attr->attgenerated)
4370 : 8 : continue;
3111 rhaas@postgresql.org 4371 : 395 : getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
4372 : 395 : fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
4373 : 395 : fmstate->p_nums++;
4374 : : }
4375 : : }
4376 : :
4377 [ - + ]: 185 : Assert(fmstate->p_nums <= n_params);
4378 : :
4379 : : /* Set batch_size from foreign server/table options. */
2091 tomas.vondra@postgre 4380 [ + + ]: 185 : if (operation == CMD_INSERT)
4381 : 134 : fmstate->batch_size = get_batch_size_option(rel);
4382 : :
4383 : 185 : fmstate->num_slots = 1;
4384 : :
4385 : : /* Initialize auxiliary state */
2728 efujita@postgresql.o 4386 : 185 : fmstate->aux_fmstate = NULL;
4387 : :
3111 rhaas@postgresql.org 4388 : 185 : return fmstate;
4389 : : }
4390 : :
4391 : : /*
4392 : : * execute_foreign_modify
4393 : : * Perform foreign-table modification as required, and fetch RETURNING
4394 : : * result if any. (This is the shared guts of postgresExecForeignInsert,
4395 : : * postgresExecForeignBatchInsert, postgresExecForeignUpdate, and
4396 : : * postgresExecForeignDelete.)
4397 : : */
4398 : : static TupleTableSlot **
2825 efujita@postgresql.o 4399 : 1055 : execute_foreign_modify(EState *estate,
4400 : : ResultRelInfo *resultRelInfo,
4401 : : CmdType operation,
4402 : : TupleTableSlot **slots,
4403 : : TupleTableSlot **planSlots,
4404 : : int *numSlots)
4405 : : {
4406 : 1055 : PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
2804 tgl@sss.pgh.pa.us 4407 : 1055 : ItemPointer ctid = NULL;
4408 : : const char **p_values;
4409 : : PGresult *res;
4410 : : int n_rows;
4411 : : StringInfoData sql;
4412 : :
4413 : : /* The operation should be INSERT, UPDATE, or DELETE */
2825 efujita@postgresql.o 4414 [ + + + + : 1055 : Assert(operation == CMD_INSERT ||
- + ]
4415 : : operation == CMD_UPDATE ||
4416 : : operation == CMD_DELETE);
4417 : :
4418 : : /* First, process a pending asynchronous request, if any. */
2021 4419 [ + + ]: 1055 : if (fmstate->conn_state->pendingAreq)
4420 : 1 : process_pending_request(fmstate->conn_state->pendingAreq);
4421 : :
4422 : : /*
4423 : : * If the existing query was deparsed and prepared for a different number
4424 : : * of rows, rebuild it for the proper number.
4425 : : */
2091 tomas.vondra@postgre 4426 [ + + + + ]: 1055 : if (operation == CMD_INSERT && fmstate->num_slots != *numSlots)
4427 : : {
4428 : : /* Destroy the prepared statement created previously */
4429 [ + + ]: 26 : if (fmstate->p_name)
4430 : 11 : deallocate_query(fmstate);
4431 : :
4432 : : /* Build INSERT string with numSlots records in its VALUES clause. */
4433 : 26 : initStringInfo(&sql);
1894 efujita@postgresql.o 4434 : 26 : rebuildInsertSql(&sql, fmstate->rel,
4435 : : fmstate->orig_query, fmstate->target_attrs,
4436 : : fmstate->values_end, fmstate->p_nums,
4437 : 26 : *numSlots - 1);
2091 tomas.vondra@postgre 4438 : 26 : pfree(fmstate->query);
4439 : 26 : fmstate->query = sql.data;
4440 : 26 : fmstate->num_slots = *numSlots;
4441 : : }
4442 : :
4443 : : /* Set up the prepared statement on the remote server, if we didn't yet */
2825 efujita@postgresql.o 4444 [ + + ]: 1055 : if (!fmstate->p_name)
4445 : 190 : prepare_foreign_modify(fmstate);
4446 : :
4447 : : /*
4448 : : * For UPDATE/DELETE, get the ctid that was passed up as a resjunk column
4449 : : */
4450 [ + + + + ]: 1055 : if (operation == CMD_UPDATE || operation == CMD_DELETE)
4451 : : {
4452 : : Datum datum;
4453 : : bool isNull;
4454 : :
2091 tomas.vondra@postgre 4455 : 120 : datum = ExecGetJunkAttribute(planSlots[0],
2825 efujita@postgresql.o 4456 : 120 : fmstate->ctidAttno,
4457 : : &isNull);
4458 : : /* shouldn't ever get a null result... */
4459 [ - + ]: 120 : if (isNull)
2825 efujita@postgresql.o 4460 [ # # ]:UBC 0 : elog(ERROR, "ctid is NULL");
2825 efujita@postgresql.o 4461 :CBC 120 : ctid = (ItemPointer) DatumGetPointer(datum);
4462 : : }
4463 : :
4464 : : /* Convert parameters needed by prepared statement to text form */
2091 tomas.vondra@postgre 4465 : 1055 : p_values = convert_prep_stmt_params(fmstate, ctid, slots, *numSlots);
4466 : :
4467 : : /*
4468 : : * Execute the prepared statement.
4469 : : */
2825 efujita@postgresql.o 4470 [ - + ]: 1055 : if (!PQsendQueryPrepared(fmstate->conn,
4471 : 1055 : fmstate->p_name,
2091 tomas.vondra@postgre 4472 : 1055 : fmstate->p_nums * (*numSlots),
4473 : : p_values,
4474 : : NULL,
4475 : : NULL,
4476 : : 0))
440 tgl@sss.pgh.pa.us 4477 :UBC 0 : pgfdw_report_error(NULL, fmstate->conn, fmstate->query);
4478 : :
4479 : : /*
4480 : : * Get the result, and check for success.
4481 : : */
1008 noah@leadboat.com 4482 :CBC 1055 : res = pgfdw_get_result(fmstate->conn);
2825 efujita@postgresql.o 4483 [ + + ]: 2110 : if (PQresultStatus(res) !=
4484 [ + + ]: 1055 : (fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
440 tgl@sss.pgh.pa.us 4485 : 5 : pgfdw_report_error(res, fmstate->conn, fmstate->query);
4486 : :
4487 : : /* Check number of rows affected, and fetch RETURNING tuple if any */
2825 efujita@postgresql.o 4488 [ + + ]: 1050 : if (fmstate->has_returning)
4489 : : {
2091 tomas.vondra@postgre 4490 [ - + ]: 110 : Assert(*numSlots == 1);
2825 efujita@postgresql.o 4491 : 110 : n_rows = PQntuples(res);
4492 [ + + ]: 110 : if (n_rows > 0)
2091 tomas.vondra@postgre 4493 : 109 : store_returning_result(fmstate, slots[0], res);
4494 : : }
4495 : : else
2825 efujita@postgresql.o 4496 : 940 : n_rows = atoi(PQcmdTuples(res));
4497 : :
4498 : : /* And clean up */
4499 : 1050 : PQclear(res);
4500 : :
4501 : 1050 : MemoryContextReset(fmstate->temp_cxt);
4502 : :
2091 tomas.vondra@postgre 4503 : 1050 : *numSlots = n_rows;
4504 : :
4505 : : /*
4506 : : * Return NULL if nothing was inserted/updated/deleted on the remote end
4507 : : */
4508 [ + + ]: 1050 : return (n_rows > 0) ? slots : NULL;
4509 : : }
4510 : :
4511 : : /*
4512 : : * prepare_foreign_modify
4513 : : * Establish a prepared statement for execution of INSERT/UPDATE/DELETE
4514 : : */
4515 : : static void
4964 tgl@sss.pgh.pa.us 4516 : 190 : prepare_foreign_modify(PgFdwModifyState *fmstate)
4517 : : {
4518 : : char prep_name[NAMEDATALEN];
4519 : : char *p_name;
4520 : : PGresult *res;
4521 : :
4522 : : /*
4523 : : * The caller would already have processed a pending asynchronous request
4524 : : * if any, so no need to do it here.
4525 : : */
4526 : :
4527 : : /* Construct name we'll use for the prepared statement. */
4528 : 190 : snprintf(prep_name, sizeof(prep_name), "pgsql_fdw_prep_%u",
4529 : : GetPrepStmtNumber(fmstate->conn));
4530 : 190 : p_name = pstrdup(prep_name);
4531 : :
4532 : : /*
4533 : : * We intentionally do not specify parameter types here, but leave the
4534 : : * remote server to derive them by default. This avoids possible problems
4535 : : * with the remote server using different type OIDs than we do. All of
4536 : : * the prepared statements we use in this module are simple enough that
4537 : : * the remote server will make the right choices.
4538 : : */
3826 rhaas@postgresql.org 4539 [ - + ]: 190 : if (!PQsendPrepare(fmstate->conn,
4540 : : p_name,
4541 : 190 : fmstate->query,
4542 : : 0,
4543 : : NULL))
440 tgl@sss.pgh.pa.us 4544 :UBC 0 : pgfdw_report_error(NULL, fmstate->conn, fmstate->query);
4545 : :
4546 : : /*
4547 : : * Get the result, and check for success.
4548 : : */
1008 noah@leadboat.com 4549 :CBC 190 : res = pgfdw_get_result(fmstate->conn);
4964 tgl@sss.pgh.pa.us 4550 [ - + ]: 190 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 tgl@sss.pgh.pa.us 4551 :UBC 0 : pgfdw_report_error(res, fmstate->conn, fmstate->query);
4964 tgl@sss.pgh.pa.us 4552 :CBC 190 : PQclear(res);
4553 : :
4554 : : /* This action shows that the prepare has been done. */
4555 : 190 : fmstate->p_name = p_name;
4556 : 190 : }
4557 : :
4558 : : /*
4559 : : * convert_prep_stmt_params
4560 : : * Create array of text strings representing parameter values
4561 : : *
4562 : : * tupleid is ctid to send, or NULL if none
4563 : : * slot is slot to get remaining parameters from, or NULL if none
4564 : : *
4565 : : * Data is constructed in temp_cxt; caller should reset that after use.
4566 : : */
4567 : : static const char **
4568 : 1055 : convert_prep_stmt_params(PgFdwModifyState *fmstate,
4569 : : ItemPointer tupleid,
4570 : : TupleTableSlot **slots,
4571 : : int numSlots)
4572 : : {
4573 : : const char **p_values;
4574 : : int i;
4575 : : int j;
4576 : 1055 : int pindex = 0;
4577 : : MemoryContext oldcontext;
4578 : :
4579 : 1055 : oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt);
4580 : :
56 michael@paquier.xyz 4581 :GNC 1055 : p_values = palloc_array(const char *, fmstate->p_nums * numSlots);
4582 : :
4583 : : /* ctid is provided only for UPDATE/DELETE, which don't allow batching */
2091 tomas.vondra@postgre 4584 [ + + - + ]:CBC 1055 : Assert(!(tupleid != NULL && numSlots > 1));
4585 : :
4586 : : /* 1st parameter should be ctid, if it's in use */
4964 tgl@sss.pgh.pa.us 4587 [ + + ]: 1055 : if (tupleid != NULL)
4588 : : {
2091 tomas.vondra@postgre 4589 [ - + ]: 120 : Assert(numSlots == 1);
4590 : : /* don't need set_transmission_modes for TID output */
4964 tgl@sss.pgh.pa.us 4591 : 120 : p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
4592 : : PointerGetDatum(tupleid));
4593 : 120 : pindex++;
4594 : : }
4595 : :
4596 : : /* get following parameters from slots */
2091 tomas.vondra@postgre 4597 [ + - + + ]: 1055 : if (slots != NULL && fmstate->target_attrs != NIL)
4598 : : {
1894 efujita@postgresql.o 4599 : 1029 : TupleDesc tupdesc = RelationGetDescr(fmstate->rel);
4600 : : int nestlevel;
4601 : : ListCell *lc;
4602 : :
4963 tgl@sss.pgh.pa.us 4603 : 1029 : nestlevel = set_transmission_modes();
4604 : :
2091 tomas.vondra@postgre 4605 [ + + ]: 2180 : for (i = 0; i < numSlots; i++)
4606 : : {
4607 : 1151 : j = (tupleid != NULL) ? 1 : 0;
4608 [ + - + + : 4804 : foreach(lc, fmstate->target_attrs)
+ + ]
4609 : : {
4610 : 3653 : int attnum = lfirst_int(lc);
661 drowley@postgresql.o 4611 : 3653 : CompactAttribute *attr = TupleDescCompactAttr(tupdesc, attnum - 1);
4612 : : Datum value;
4613 : : bool isnull;
4614 : :
4615 : : /* Ignore generated columns; they are set to DEFAULT */
1894 efujita@postgresql.o 4616 [ + + ]: 3653 : if (attr->attgenerated)
4617 : 14 : continue;
2091 tomas.vondra@postgre 4618 : 3639 : value = slot_getattr(slots[i], attnum, &isnull);
4619 [ + + ]: 3639 : if (isnull)
4620 : 583 : p_values[pindex] = NULL;
4621 : : else
4622 : 3056 : p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[j],
4623 : : value);
4624 : 3639 : pindex++;
4625 : 3639 : j++;
4626 : : }
4627 : : }
4628 : :
4963 tgl@sss.pgh.pa.us 4629 : 1029 : reset_transmission_modes(nestlevel);
4630 : : }
4631 : :
2091 tomas.vondra@postgre 4632 [ - + ]: 1055 : Assert(pindex == fmstate->p_nums * numSlots);
4633 : :
4964 tgl@sss.pgh.pa.us 4634 : 1055 : MemoryContextSwitchTo(oldcontext);
4635 : :
4636 : 1055 : return p_values;
4637 : : }
4638 : :
4639 : : /*
4640 : : * store_returning_result
4641 : : * Store the result of a RETURNING clause
4642 : : */
4643 : : static void
4644 : 109 : store_returning_result(PgFdwModifyState *fmstate,
4645 : : TupleTableSlot *slot, PGresult *res)
4646 : : {
4647 : : HeapTuple newtup;
4648 : :
444 4649 : 109 : newtup = make_tuple_from_result_row(res, 0,
4650 : : fmstate->rel,
4651 : : fmstate->attinmeta,
4652 : : fmstate->retrieved_attrs,
4653 : : NULL,
4654 : : fmstate->temp_cxt);
4655 : :
4656 : : /*
4657 : : * The returning slot will not necessarily be suitable to store heaptuples
4658 : : * directly, so allow for conversion.
4659 : : */
4660 : 109 : ExecForceStoreHeapTuple(newtup, slot, true);
4964 4661 : 109 : }
4662 : :
4663 : : /*
4664 : : * finish_foreign_modify
4665 : : * Release resources for a foreign insert/update/delete operation
4666 : : */
4667 : : static void
3111 rhaas@postgresql.org 4668 : 163 : finish_foreign_modify(PgFdwModifyState *fmstate)
4669 : : {
4670 [ - + ]: 163 : Assert(fmstate != NULL);
4671 : :
4672 : : /* If we created a prepared statement, destroy it */
2091 tomas.vondra@postgre 4673 : 163 : deallocate_query(fmstate);
4674 : :
4675 : : /* Release remote connection */
3111 rhaas@postgresql.org 4676 : 163 : ReleaseConnection(fmstate->conn);
4677 : 163 : fmstate->conn = NULL;
4678 : 163 : }
4679 : :
4680 : : /*
4681 : : * deallocate_query
4682 : : * Deallocate a prepared statement for a foreign insert/update/delete
4683 : : * operation
4684 : : */
4685 : : static void
2091 tomas.vondra@postgre 4686 : 174 : deallocate_query(PgFdwModifyState *fmstate)
4687 : : {
4688 : : char sql[64];
4689 : : PGresult *res;
4690 : :
4691 : : /* do nothing if the query is not allocated */
4692 [ + + ]: 174 : if (!fmstate->p_name)
4693 : 4 : return;
4694 : :
4695 : 170 : snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
2021 efujita@postgresql.o 4696 : 170 : res = pgfdw_exec_query(fmstate->conn, sql, fmstate->conn_state);
2091 tomas.vondra@postgre 4697 [ - + ]: 170 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 tgl@sss.pgh.pa.us 4698 :UBC 0 : pgfdw_report_error(res, fmstate->conn, sql);
2091 tomas.vondra@postgre 4699 :CBC 170 : PQclear(res);
2085 michael@paquier.xyz 4700 : 170 : pfree(fmstate->p_name);
2091 tomas.vondra@postgre 4701 : 170 : fmstate->p_name = NULL;
4702 : : }
4703 : :
4704 : : /*
4705 : : * build_remote_returning
4706 : : * Build a RETURNING targetlist of a remote query for performing an
4707 : : * UPDATE/DELETE .. RETURNING on a join directly
4708 : : */
4709 : : static List *
3169 rhaas@postgresql.org 4710 : 5 : build_remote_returning(Index rtindex, Relation rel, List *returningList)
4711 : : {
4712 : 5 : bool have_wholerow = false;
4713 : 5 : List *tlist = NIL;
4714 : : List *vars;
4715 : : ListCell *lc;
4716 : :
4717 [ - + ]: 5 : Assert(returningList);
4718 : :
4719 : 5 : vars = pull_var_clause((Node *) returningList, PVC_INCLUDE_PLACEHOLDERS);
4720 : :
4721 : : /*
4722 : : * If there's a whole-row reference to the target relation, then we'll
4723 : : * need all the columns of the relation.
4724 : : */
4725 [ + + + + : 8 : foreach(lc, vars)
+ + ]
4726 : : {
4727 : 5 : Var *var = (Var *) lfirst(lc);
4728 : :
4729 [ + - ]: 5 : if (IsA(var, Var) &&
4730 [ + + ]: 5 : var->varno == rtindex &&
4731 [ + + ]: 4 : var->varattno == InvalidAttrNumber)
4732 : : {
4733 : 2 : have_wholerow = true;
4734 : 2 : break;
4735 : : }
4736 : : }
4737 : :
4738 [ + + ]: 5 : if (have_wholerow)
4739 : : {
4740 : 2 : TupleDesc tupdesc = RelationGetDescr(rel);
4741 : : int i;
4742 : :
4743 [ + + ]: 20 : for (i = 1; i <= tupdesc->natts; i++)
4744 : : {
4745 : 18 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1);
4746 : : Var *var;
4747 : :
4748 : : /* Ignore dropped attributes. */
4749 [ + + ]: 18 : if (attr->attisdropped)
4750 : 2 : continue;
4751 : :
4752 : 16 : var = makeVar(rtindex,
4753 : : i,
4754 : : attr->atttypid,
4755 : : attr->atttypmod,
4756 : : attr->attcollation,
4757 : : 0);
4758 : :
4759 : 16 : tlist = lappend(tlist,
4760 : 16 : makeTargetEntry((Expr *) var,
4761 : 16 : list_length(tlist) + 1,
4762 : : NULL,
4763 : : false));
4764 : : }
4765 : : }
4766 : :
4767 : : /* Now add any remaining columns to tlist. */
4768 [ + + + + : 34 : foreach(lc, vars)
+ + ]
4769 : : {
4770 : 29 : Var *var = (Var *) lfirst(lc);
4771 : :
4772 : : /*
4773 : : * No need for whole-row references to the target relation. We don't
4774 : : * need system columns other than ctid and oid either, since those are
4775 : : * set locally.
4776 : : */
4777 [ + - ]: 29 : if (IsA(var, Var) &&
4778 [ + + ]: 29 : var->varno == rtindex &&
4779 [ + + ]: 20 : var->varattno <= InvalidAttrNumber &&
2883 andres@anarazel.de 4780 [ + - ]: 2 : var->varattno != SelfItemPointerAttributeNumber)
3169 rhaas@postgresql.org 4781 : 2 : continue; /* don't need it */
4782 : :
4783 [ + + ]: 27 : if (tlist_member((Expr *) var, tlist))
4784 : 16 : continue; /* already got it */
4785 : :
4786 : 11 : tlist = lappend(tlist,
4787 : 11 : makeTargetEntry((Expr *) var,
4788 : 11 : list_length(tlist) + 1,
4789 : : NULL,
4790 : : false));
4791 : : }
4792 : :
4793 : 5 : list_free(vars);
4794 : :
4795 : 5 : return tlist;
4796 : : }
4797 : :
4798 : : /*
4799 : : * rebuild_fdw_scan_tlist
4800 : : * Build new fdw_scan_tlist of given foreign-scan plan node from given
4801 : : * tlist
4802 : : *
4803 : : * There might be columns that the fdw_scan_tlist of the given foreign-scan
4804 : : * plan node contains that the given tlist doesn't. The fdw_scan_tlist would
4805 : : * have contained resjunk columns such as 'ctid' of the target relation and
4806 : : * 'wholerow' of non-target relations, but the tlist might not contain them,
4807 : : * for example. So, adjust the tlist so it contains all the columns specified
4808 : : * in the fdw_scan_tlist; else setrefs.c will get confused.
4809 : : */
4810 : : static void
4811 : 3 : rebuild_fdw_scan_tlist(ForeignScan *fscan, List *tlist)
4812 : : {
4813 : 3 : List *new_tlist = tlist;
4814 : 3 : List *old_tlist = fscan->fdw_scan_tlist;
4815 : : ListCell *lc;
4816 : :
4817 [ + - + + : 20 : foreach(lc, old_tlist)
+ + ]
4818 : : {
4819 : 17 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
4820 : :
4821 [ + + ]: 17 : if (tlist_member(tle->expr, new_tlist))
4822 : 9 : continue; /* already got it */
4823 : :
4824 : 8 : new_tlist = lappend(new_tlist,
4825 : 8 : makeTargetEntry(tle->expr,
4826 : 8 : list_length(new_tlist) + 1,
4827 : : NULL,
4828 : : false));
4829 : : }
4830 : 3 : fscan->fdw_scan_tlist = new_tlist;
4831 : 3 : }
4832 : :
4833 : : /*
4834 : : * Execute a direct UPDATE/DELETE statement.
4835 : : */
4836 : : static void
3860 4837 : 74 : execute_dml_stmt(ForeignScanState *node)
4838 : : {
4839 : 74 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
4840 : 74 : ExprContext *econtext = node->ss.ps.ps_ExprContext;
4841 : 74 : int numParams = dmstate->numParams;
4842 : 74 : const char **values = dmstate->param_values;
4843 : :
4844 : : /* First, process a pending asynchronous request, if any. */
2021 efujita@postgresql.o 4845 [ + + ]: 74 : if (dmstate->conn_state->pendingAreq)
4846 : 1 : process_pending_request(dmstate->conn_state->pendingAreq);
4847 : :
4848 : : /*
4849 : : * Construct array of query parameter values in text format.
4850 : : */
3860 rhaas@postgresql.org 4851 [ + + ]: 74 : if (numParams > 0)
4852 : 1 : process_query_params(econtext,
4853 : : dmstate->param_flinfo,
4854 : : dmstate->param_exprs,
4855 : : values);
4856 : :
4857 : : /*
4858 : : * Notice that we pass NULL for paramTypes, thus forcing the remote server
4859 : : * to infer types for all parameters. Since we explicitly cast every
4860 : : * parameter (see deparse.c), the "inference" is trivial and will produce
4861 : : * the desired result. This allows us to avoid assuming that the remote
4862 : : * server has the same OIDs we do for the parameters' types.
4863 : : */
3826 4864 [ - + ]: 74 : if (!PQsendQueryParams(dmstate->conn, dmstate->query, numParams,
4865 : : NULL, values, NULL, NULL, 0))
440 tgl@sss.pgh.pa.us 4866 :UBC 0 : pgfdw_report_error(NULL, dmstate->conn, dmstate->query);
4867 : :
4868 : : /*
4869 : : * Get the result, and check for success.
4870 : : */
1008 noah@leadboat.com 4871 :CBC 74 : dmstate->result = pgfdw_get_result(dmstate->conn);
3860 rhaas@postgresql.org 4872 [ + + ]: 148 : if (PQresultStatus(dmstate->result) !=
4873 [ + + ]: 74 : (dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
440 tgl@sss.pgh.pa.us 4874 : 4 : pgfdw_report_error(dmstate->result, dmstate->conn,
3860 rhaas@postgresql.org 4875 : 4 : dmstate->query);
4876 : :
4877 : : /*
4878 : : * The result potentially needs to survive across multiple executor row
4879 : : * cycles, so move it to the context where the dmstate is.
4880 : : */
444 tgl@sss.pgh.pa.us 4881 : 70 : dmstate->result = libpqsrv_PGresultSetParent(dmstate->result,
4882 : : GetMemoryChunkContext(dmstate));
4883 : :
4884 : : /* Get the number of rows affected. */
3860 rhaas@postgresql.org 4885 [ + + ]: 70 : if (dmstate->has_returning)
4886 : 16 : dmstate->num_tuples = PQntuples(dmstate->result);
4887 : : else
4888 : 54 : dmstate->num_tuples = atoi(PQcmdTuples(dmstate->result));
4889 : 70 : }
4890 : :
4891 : : /*
4892 : : * Get the result of a RETURNING clause.
4893 : : */
4894 : : static TupleTableSlot *
4895 : 368 : get_returning_data(ForeignScanState *node)
4896 : : {
4897 : 368 : PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
4898 : 368 : EState *estate = node->ss.ps.state;
2189 heikki.linnakangas@i 4899 : 368 : ResultRelInfo *resultRelInfo = node->resultRelInfo;
3860 rhaas@postgresql.org 4900 : 368 : TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
4901 : : TupleTableSlot *resultSlot;
4902 : :
4903 [ - + ]: 368 : Assert(resultRelInfo->ri_projectReturning);
4904 : :
4905 : : /* If we didn't get any tuples, must be end of data. */
4906 [ + + ]: 368 : if (dmstate->next_tuple >= dmstate->num_tuples)
4907 : 19 : return ExecClearTuple(slot);
4908 : :
4909 : : /* Increment the command es_processed count if necessary. */
4910 [ + + ]: 349 : if (dmstate->set_processed)
4911 : 348 : estate->es_processed += 1;
4912 : :
4913 : : /*
4914 : : * Store a RETURNING tuple. If has_returning is false, just emit a dummy
4915 : : * tuple. (has_returning is false when the local query is of the form
4916 : : * "UPDATE/DELETE .. RETURNING 1" for example.)
4917 : : */
4918 [ + + ]: 349 : if (!dmstate->has_returning)
4919 : : {
4920 : 12 : ExecStoreAllNullTuple(slot);
3169 4921 : 12 : resultSlot = slot;
4922 : : }
4923 : : else
4924 : : {
4925 : : HeapTuple newtup;
4926 : :
500 tgl@sss.pgh.pa.us 4927 : 337 : newtup = make_tuple_from_result_row(dmstate->result,
4928 : : dmstate->next_tuple,
4929 : : dmstate->rel,
4930 : : dmstate->attinmeta,
4931 : : dmstate->retrieved_attrs,
4932 : : node,
4933 : : dmstate->temp_cxt);
4934 : 337 : ExecStoreHeapTuple(newtup, slot, false);
4935 : : /* Get the updated/deleted tuple. */
3169 rhaas@postgresql.org 4936 [ + + ]: 337 : if (dmstate->rel)
4937 : 320 : resultSlot = slot;
4938 : : else
2189 heikki.linnakangas@i 4939 : 17 : resultSlot = apply_returning_filter(dmstate, resultRelInfo, slot, estate);
4940 : : }
3860 rhaas@postgresql.org 4941 : 349 : dmstate->next_tuple++;
4942 : :
4943 : : /* Make slot available for evaluation of the local query RETURNING list. */
3169 4944 : 349 : resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple =
4945 : : resultSlot;
4946 : :
3860 4947 : 349 : return slot;
4948 : : }
4949 : :
4950 : : /*
4951 : : * Initialize a filter to extract an updated/deleted tuple from a scan tuple.
4952 : : */
4953 : : static void
3169 4954 : 2 : init_returning_filter(PgFdwDirectModifyState *dmstate,
4955 : : List *fdw_scan_tlist,
4956 : : Index rtindex)
4957 : : {
4958 : 2 : TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
4959 : : ListCell *lc;
4960 : : int i;
4961 : :
4962 : : /*
4963 : : * Calculate the mapping between the fdw_scan_tlist's entries and the
4964 : : * result tuple's attributes.
4965 : : *
4966 : : * The "map" is an array of indexes of the result tuple's attributes in
4967 : : * fdw_scan_tlist, i.e., one entry for every attribute of the result
4968 : : * tuple. We store zero for any attributes that don't have the
4969 : : * corresponding entries in that list, marking that a NULL is needed in
4970 : : * the result tuple.
4971 : : *
4972 : : * Also get the indexes of the entries for ctid and oid if any.
4973 : : */
56 michael@paquier.xyz 4974 :GNC 2 : dmstate->attnoMap = palloc0_array(AttrNumber, resultTupType->natts);
4975 : :
3169 rhaas@postgresql.org 4976 :CBC 2 : dmstate->ctidAttno = dmstate->oidAttno = 0;
4977 : :
4978 : 2 : i = 1;
4979 : 2 : dmstate->hasSystemCols = false;
4980 [ + - + + : 22 : foreach(lc, fdw_scan_tlist)
+ + ]
4981 : : {
4982 : 20 : TargetEntry *tle = (TargetEntry *) lfirst(lc);
4983 : 20 : Var *var = (Var *) tle->expr;
4984 : :
4985 [ - + ]: 20 : Assert(IsA(var, Var));
4986 : :
4987 : : /*
4988 : : * If the Var is a column of the target relation to be retrieved from
4989 : : * the foreign server, get the index of the entry.
4990 : : */
4991 [ + + + + ]: 34 : if (var->varno == rtindex &&
4992 : 14 : list_member_int(dmstate->retrieved_attrs, i))
4993 : : {
4994 : 10 : int attrno = var->varattno;
4995 : :
4996 [ - + ]: 10 : if (attrno < 0)
4997 : : {
4998 : : /*
4999 : : * We don't retrieve system columns other than ctid and oid.
5000 : : */
3169 rhaas@postgresql.org 5001 [ # # ]:UBC 0 : if (attrno == SelfItemPointerAttributeNumber)
5002 : 0 : dmstate->ctidAttno = i;
5003 : : else
5004 : 0 : Assert(false);
5005 : 0 : dmstate->hasSystemCols = true;
5006 : : }
5007 : : else
5008 : : {
5009 : : /*
5010 : : * We don't retrieve whole-row references to the target
5011 : : * relation either.
5012 : : */
3169 rhaas@postgresql.org 5013 [ - + ]:CBC 10 : Assert(attrno > 0);
5014 : :
5015 : 10 : dmstate->attnoMap[attrno - 1] = i;
5016 : : }
5017 : : }
5018 : 20 : i++;
5019 : : }
5020 : 2 : }
5021 : :
5022 : : /*
5023 : : * Extract and return an updated/deleted tuple from a scan tuple.
5024 : : */
5025 : : static TupleTableSlot *
5026 : 17 : apply_returning_filter(PgFdwDirectModifyState *dmstate,
5027 : : ResultRelInfo *resultRelInfo,
5028 : : TupleTableSlot *slot,
5029 : : EState *estate)
5030 : : {
5031 : 17 : TupleDesc resultTupType = RelationGetDescr(dmstate->resultRel);
5032 : : TupleTableSlot *resultSlot;
5033 : : Datum *values;
5034 : : bool *isnull;
5035 : : Datum *old_values;
5036 : : bool *old_isnull;
5037 : : int i;
5038 : :
5039 : : /*
5040 : : * Use the return tuple slot as a place to store the result tuple.
5041 : : */
2189 heikki.linnakangas@i 5042 : 17 : resultSlot = ExecGetReturningSlot(estate, resultRelInfo);
5043 : :
5044 : : /*
5045 : : * Extract all the values of the scan tuple.
5046 : : */
3169 rhaas@postgresql.org 5047 : 17 : slot_getallattrs(slot);
5048 : 17 : old_values = slot->tts_values;
5049 : 17 : old_isnull = slot->tts_isnull;
5050 : :
5051 : : /*
5052 : : * Prepare to build the result tuple.
5053 : : */
5054 : 17 : ExecClearTuple(resultSlot);
5055 : 17 : values = resultSlot->tts_values;
5056 : 17 : isnull = resultSlot->tts_isnull;
5057 : :
5058 : : /*
5059 : : * Transpose data into proper fields of the result tuple.
5060 : : */
5061 [ + + ]: 163 : for (i = 0; i < resultTupType->natts; i++)
5062 : : {
5063 : 146 : int j = dmstate->attnoMap[i];
5064 : :
5065 [ + + ]: 146 : if (j == 0)
5066 : : {
5067 : 16 : values[i] = (Datum) 0;
5068 : 16 : isnull[i] = true;
5069 : : }
5070 : : else
5071 : : {
5072 : 130 : values[i] = old_values[j - 1];
5073 : 130 : isnull[i] = old_isnull[j - 1];
5074 : : }
5075 : : }
5076 : :
5077 : : /*
5078 : : * Build the virtual tuple.
5079 : : */
5080 : 17 : ExecStoreVirtualTuple(resultSlot);
5081 : :
5082 : : /*
5083 : : * If we have any system columns to return, materialize a heap tuple in
5084 : : * the slot from column values set above and install system columns in
5085 : : * that tuple.
5086 : : */
5087 [ - + ]: 17 : if (dmstate->hasSystemCols)
5088 : : {
2888 andres@anarazel.de 5089 :UBC 0 : HeapTuple resultTup = ExecFetchSlotHeapTuple(resultSlot, true, NULL);
5090 : :
5091 : : /* ctid */
3169 rhaas@postgresql.org 5092 [ # # ]: 0 : if (dmstate->ctidAttno)
5093 : : {
5094 : 0 : ItemPointer ctid = NULL;
5095 : :
5096 : 0 : ctid = (ItemPointer) DatumGetPointer(old_values[dmstate->ctidAttno - 1]);
5097 : 0 : resultTup->t_self = *ctid;
5098 : : }
5099 : :
5100 : : /*
5101 : : * And remaining columns
5102 : : *
5103 : : * Note: since we currently don't allow the target relation to appear
5104 : : * on the nullable side of an outer join, any system columns wouldn't
5105 : : * go to NULL.
5106 : : *
5107 : : * Note: no need to care about tableoid here because it will be
5108 : : * initialized in ExecProcessReturning().
5109 : : */
5110 : 0 : HeapTupleHeaderSetXmin(resultTup->t_data, InvalidTransactionId);
5111 : 0 : HeapTupleHeaderSetXmax(resultTup->t_data, InvalidTransactionId);
5112 : 0 : HeapTupleHeaderSetCmin(resultTup->t_data, InvalidTransactionId);
5113 : : }
5114 : :
5115 : : /*
5116 : : * And return the result tuple.
5117 : : */
3169 rhaas@postgresql.org 5118 :CBC 17 : return resultSlot;
5119 : : }
5120 : :
5121 : : /*
5122 : : * Prepare for processing of parameters used in remote query.
5123 : : */
5124 : : static void
3860 5125 : 32 : prepare_query_params(PlanState *node,
5126 : : List *fdw_exprs,
5127 : : int numParams,
5128 : : FmgrInfo **param_flinfo,
5129 : : List **param_exprs,
5130 : : const char ***param_values)
5131 : : {
5132 : : int i;
5133 : : ListCell *lc;
5134 : :
5135 [ - + ]: 32 : Assert(numParams > 0);
5136 : :
5137 : : /* Prepare for output conversion of parameters used in remote query. */
305 michael@paquier.xyz 5138 : 32 : *param_flinfo = palloc0_array(FmgrInfo, numParams);
5139 : :
3860 rhaas@postgresql.org 5140 : 32 : i = 0;
5141 [ + - + + : 65 : foreach(lc, fdw_exprs)
+ + ]
5142 : : {
5143 : 33 : Node *param_expr = (Node *) lfirst(lc);
5144 : : Oid typefnoid;
5145 : : bool isvarlena;
5146 : :
5147 : 33 : getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena);
5148 : 33 : fmgr_info(typefnoid, &(*param_flinfo)[i]);
5149 : 33 : i++;
5150 : : }
5151 : :
5152 : : /*
5153 : : * Prepare remote-parameter expressions for evaluation. (Note: in
5154 : : * practice, we expect that all these expressions will be just Params, so
5155 : : * we could possibly do something more efficient than using the full
5156 : : * expression-eval machinery for this. But probably there would be little
5157 : : * benefit, and it'd require postgres_fdw to know more than is desirable
5158 : : * about Param evaluation.)
5159 : : */
3499 andres@anarazel.de 5160 : 32 : *param_exprs = ExecInitExprList(fdw_exprs, node);
5161 : :
5162 : : /* Allocate buffer for text form of query parameters. */
56 michael@paquier.xyz 5163 :GNC 32 : *param_values = palloc0_array(const char *, numParams);
3860 rhaas@postgresql.org 5164 :CBC 32 : }
5165 : :
5166 : : /*
5167 : : * Construct array of query parameter values in text format.
5168 : : */
5169 : : static void
5170 : 390 : process_query_params(ExprContext *econtext,
5171 : : FmgrInfo *param_flinfo,
5172 : : List *param_exprs,
5173 : : const char **param_values)
5174 : : {
5175 : : int nestlevel;
5176 : : int i;
5177 : : ListCell *lc;
5178 : :
5179 : 390 : nestlevel = set_transmission_modes();
5180 : :
5181 : 390 : i = 0;
5182 [ + - + + : 980 : foreach(lc, param_exprs)
+ + ]
5183 : : {
5184 : 590 : ExprState *expr_state = (ExprState *) lfirst(lc);
5185 : : Datum expr_value;
5186 : : bool isNull;
5187 : :
5188 : : /* Evaluate the parameter expression */
3553 andres@anarazel.de 5189 : 590 : expr_value = ExecEvalExpr(expr_state, econtext, &isNull);
5190 : :
5191 : : /*
5192 : : * Get string representation of each parameter value by invoking
5193 : : * type-specific output function, unless the value is null.
5194 : : */
3860 rhaas@postgresql.org 5195 [ - + ]: 590 : if (isNull)
3860 rhaas@postgresql.org 5196 :UBC 0 : param_values[i] = NULL;
5197 : : else
3860 rhaas@postgresql.org 5198 :CBC 590 : param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value);
5199 : :
3857 tgl@sss.pgh.pa.us 5200 : 590 : i++;
5201 : : }
5202 : :
3860 rhaas@postgresql.org 5203 : 390 : reset_transmission_modes(nestlevel);
5204 : 390 : }
5205 : :
5206 : : /*
5207 : : * postgresAnalyzeForeignTable
5208 : : * Test whether analyzing this foreign table is supported
5209 : : */
5210 : : static bool
4981 tgl@sss.pgh.pa.us 5211 : 55 : postgresAnalyzeForeignTable(Relation relation,
5212 : : AcquireSampleRowsFunc *func,
5213 : : BlockNumber *totalpages)
5214 : : {
5215 : : ForeignTable *table;
5216 : : UserMapping *user;
5217 : : PGconn *conn;
5218 : : StringInfoData sql;
5219 : : PGresult *res;
5220 : :
5221 : : /* Return the row-analysis function pointer */
5222 : 55 : *func = postgresAcquireSampleRowsFunc;
5223 : :
5224 : : /*
5225 : : * Now we have to get the number of pages. It's annoying that the ANALYZE
5226 : : * API requires us to return that now, because it forces some duplication
5227 : : * of effort between this routine and postgresAcquireSampleRowsFunc. But
5228 : : * it's probably not worth redefining that API at this point.
5229 : : */
5230 : :
5231 : : /*
5232 : : * Get the connection to use. We do the remote access as the table's
5233 : : * owner, even if the ANALYZE was started by some other user.
5234 : : */
4980 5235 : 55 : table = GetForeignTable(RelationGetRelid(relation));
3910 rhaas@postgresql.org 5236 : 55 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
2021 efujita@postgresql.o 5237 : 55 : conn = GetConnection(user, false, NULL);
5238 : :
5239 : : /*
5240 : : * Construct command to get page count for relation.
5241 : : */
4980 tgl@sss.pgh.pa.us 5242 : 55 : initStringInfo(&sql);
5243 : 55 : deparseAnalyzeSizeSql(&sql, relation);
5244 : :
444 5245 : 55 : res = pgfdw_exec_query(conn, sql.data, NULL);
5246 [ - + ]: 55 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 5247 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
5248 : :
444 tgl@sss.pgh.pa.us 5249 [ + - - + ]:CBC 55 : if (PQntuples(res) != 1 || PQnfields(res) != 1)
444 tgl@sss.pgh.pa.us 5250 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from deparseAnalyzeSizeSql query");
444 tgl@sss.pgh.pa.us 5251 :CBC 55 : *totalpages = strtoul(PQgetvalue(res, 0, 0), NULL, 10);
5252 : 55 : PQclear(res);
5253 : :
4980 5254 : 55 : ReleaseConnection(conn);
5255 : :
4981 5256 : 55 : return true;
5257 : : }
5258 : :
5259 : : /*
5260 : : * postgresGetAnalyzeInfoForForeignTable
5261 : : * Count tuples in foreign table (just get pg_class.reltuples).
5262 : : *
5263 : : * can_tablesample determines if the remote relation supports acquiring the
5264 : : * sample using TABLESAMPLE.
5265 : : */
5266 : : static double
1374 tomas.vondra@postgre 5267 : 48 : postgresGetAnalyzeInfoForForeignTable(Relation relation, bool *can_tablesample)
5268 : : {
5269 : : ForeignTable *table;
5270 : : UserMapping *user;
5271 : : PGconn *conn;
5272 : : StringInfoData sql;
5273 : : PGresult *res;
5274 : : double reltuples;
5275 : : char relkind;
5276 : :
5277 : : /* assume the remote relation does not support TABLESAMPLE */
5278 : 48 : *can_tablesample = false;
5279 : :
5280 : : /*
5281 : : * Get the connection to use. We do the remote access as the table's
5282 : : * owner, even if the ANALYZE was started by some other user.
5283 : : */
1382 5284 : 48 : table = GetForeignTable(RelationGetRelid(relation));
5285 : 48 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
5286 : 48 : conn = GetConnection(user, false, NULL);
5287 : :
5288 : : /*
5289 : : * Construct command to get page count for relation.
5290 : : */
5291 : 48 : initStringInfo(&sql);
1374 5292 : 48 : deparseAnalyzeInfoSql(&sql, relation);
5293 : :
444 tgl@sss.pgh.pa.us 5294 : 48 : res = pgfdw_exec_query(conn, sql.data, NULL);
5295 [ - + ]: 48 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 5296 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
5297 : :
187 efujita@postgresql.o 5298 [ + - - + ]:CBC 48 : if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
444 tgl@sss.pgh.pa.us 5299 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
5300 : : /* We don't use relpages/relhassubclass here */
187 efujita@postgresql.o 5301 :CBC 48 : reltuples = strtod(PQgetvalue(res, 0, RELSTATS_RELTUPLES), NULL);
5302 : 48 : relkind = *(PQgetvalue(res, 0, RELSTATS_RELKIND));
444 tgl@sss.pgh.pa.us 5303 : 48 : PQclear(res);
5304 : :
1382 tomas.vondra@postgre 5305 : 48 : ReleaseConnection(conn);
5306 : :
5307 : : /* TABLESAMPLE is supported only for regular tables and matviews */
1374 tomas.vondra@postgre 5308 [ # # ]:UBC 0 : *can_tablesample = (relkind == RELKIND_RELATION ||
1374 tomas.vondra@postgre 5309 [ - + - - ]:CBC 48 : relkind == RELKIND_MATVIEW ||
5310 : 48 : relkind == RELKIND_PARTITIONED_TABLE);
5311 : :
1382 5312 : 48 : return reltuples;
5313 : : }
5314 : :
5315 : : /*
5316 : : * Acquire a random sample of rows from foreign table managed by postgres_fdw.
5317 : : *
5318 : : * Selected rows are returned in the caller-allocated array rows[],
5319 : : * which must have at least targrows entries.
5320 : : * The actual number of rows selected is returned as the function result.
5321 : : * We also count the total number of rows in the table and return it into
5322 : : * *totalrows. Note that *totaldeadrows is always set to 0.
5323 : : *
5324 : : * Note that the returned list of rows is not always in order by physical
5325 : : * position in the table. Therefore, correlation estimates derived later
5326 : : * may be meaningless, but it's OK because we don't use the estimates
5327 : : * currently (the planner only pays attention to correlation for indexscans).
5328 : : */
5329 : : static int
4981 tgl@sss.pgh.pa.us 5330 : 55 : postgresAcquireSampleRowsFunc(Relation relation, int elevel,
5331 : : HeapTuple *rows, int targrows,
5332 : : double *totalrows,
5333 : : double *totaldeadrows)
5334 : : {
5335 : : PgFdwAnalyzeState astate;
5336 : : ForeignTable *table;
5337 : : ForeignServer *server;
5338 : : UserMapping *user;
5339 : : PGconn *conn;
5340 : : int server_version_num;
1382 tomas.vondra@postgre 5341 : 55 : PgFdwSamplingMethod method = ANALYZE_SAMPLE_AUTO; /* auto is default */
5342 : 55 : double sample_frac = -1.0;
440 tgl@sss.pgh.pa.us 5343 : 55 : double reltuples = -1.0;
5344 : : unsigned int cursor_number;
5345 : : StringInfoData sql;
5346 : : PGresult *res;
5347 : : char fetch_sql[64];
5348 : : int fetch_size;
5349 : : ListCell *lc;
5350 : :
5351 : : /* Initialize workspace state */
4981 5352 : 55 : astate.rel = relation;
5353 : 55 : astate.attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(relation));
5354 : :
5355 : 55 : astate.rows = rows;
5356 : 55 : astate.targrows = targrows;
5357 : 55 : astate.numrows = 0;
5358 : 55 : astate.samplerows = 0;
5359 : 55 : astate.rowstoskip = -1; /* -1 means not set yet */
4168 simon@2ndQuadrant.co 5360 : 55 : reservoir_init_selection_state(&astate.rstate, targrows);
5361 : :
5362 : : /* Remember ANALYZE context, and create a per-tuple temp context */
4981 tgl@sss.pgh.pa.us 5363 : 55 : astate.anl_cxt = CurrentMemoryContext;
5364 : 55 : astate.temp_cxt = AllocSetContextCreate(CurrentMemoryContext,
5365 : : "postgres_fdw temporary data",
5366 : : ALLOCSET_SMALL_SIZES);
5367 : :
5368 : : /*
5369 : : * Get the connection to use. We do the remote access as the table's
5370 : : * owner, even if the ANALYZE was started by some other user.
5371 : : */
5372 : 55 : table = GetForeignTable(RelationGetRelid(relation));
3904 rhaas@postgresql.org 5373 : 55 : server = GetForeignServer(table->serverid);
3910 5374 : 55 : user = GetUserMapping(relation->rd_rel->relowner, table->serverid);
2021 efujita@postgresql.o 5375 : 55 : conn = GetConnection(user, false, NULL);
5376 : :
5377 : : /* We'll need server version, so fetch it now. */
1382 tomas.vondra@postgre 5378 : 55 : server_version_num = PQserverVersion(conn);
5379 : :
5380 : : /*
5381 : : * What sampling method should we use?
5382 : : */
5383 [ + - + + : 257 : foreach(lc, server->options)
+ + ]
5384 : : {
5385 : 213 : DefElem *def = (DefElem *) lfirst(lc);
5386 : :
5387 [ + + ]: 213 : if (strcmp(def->defname, "analyze_sampling") == 0)
5388 : : {
5389 : 11 : char *value = defGetString(def);
5390 : :
5391 [ + + ]: 11 : if (strcmp(value, "off") == 0)
5392 : 7 : method = ANALYZE_SAMPLE_OFF;
5393 [ + + ]: 4 : else if (strcmp(value, "auto") == 0)
5394 : 1 : method = ANALYZE_SAMPLE_AUTO;
5395 [ + + ]: 3 : else if (strcmp(value, "random") == 0)
5396 : 1 : method = ANALYZE_SAMPLE_RANDOM;
5397 [ + + ]: 2 : else if (strcmp(value, "system") == 0)
5398 : 1 : method = ANALYZE_SAMPLE_SYSTEM;
5399 [ + - ]: 1 : else if (strcmp(value, "bernoulli") == 0)
5400 : 1 : method = ANALYZE_SAMPLE_BERNOULLI;
5401 : :
5402 : 11 : break;
5403 : : }
5404 : : }
5405 : :
5406 [ + - + + : 131 : foreach(lc, table->options)
+ + ]
5407 : : {
5408 : 76 : DefElem *def = (DefElem *) lfirst(lc);
5409 : :
5410 [ - + ]: 76 : if (strcmp(def->defname, "analyze_sampling") == 0)
5411 : : {
1382 tomas.vondra@postgre 5412 :UBC 0 : char *value = defGetString(def);
5413 : :
5414 [ # # ]: 0 : if (strcmp(value, "off") == 0)
5415 : 0 : method = ANALYZE_SAMPLE_OFF;
5416 [ # # ]: 0 : else if (strcmp(value, "auto") == 0)
5417 : 0 : method = ANALYZE_SAMPLE_AUTO;
5418 [ # # ]: 0 : else if (strcmp(value, "random") == 0)
5419 : 0 : method = ANALYZE_SAMPLE_RANDOM;
5420 [ # # ]: 0 : else if (strcmp(value, "system") == 0)
5421 : 0 : method = ANALYZE_SAMPLE_SYSTEM;
5422 [ # # ]: 0 : else if (strcmp(value, "bernoulli") == 0)
5423 : 0 : method = ANALYZE_SAMPLE_BERNOULLI;
5424 : :
5425 : 0 : break;
5426 : : }
5427 : : }
5428 : :
5429 : : /*
5430 : : * Error-out if explicitly required one of the TABLESAMPLE methods, but
5431 : : * the server does not support it.
5432 : : */
6 efujita@postgresql.o 5433 [ - + - - ]:CBC 55 : if ((server_version_num < 90500) &&
1382 tomas.vondra@postgre 5434 [ # # ]:UBC 0 : (method == ANALYZE_SAMPLE_SYSTEM ||
5435 : : method == ANALYZE_SAMPLE_BERNOULLI))
5436 [ # # ]: 0 : ereport(ERROR,
5437 : : (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5438 : : errmsg("remote server does not support TABLESAMPLE feature")));
5439 : :
5440 : : /*
5441 : : * If we've decided to do remote sampling, calculate the sampling rate. We
5442 : : * need to get the number of tuples from the remote server, but skip that
5443 : : * network round-trip if not needed.
5444 : : */
1382 tomas.vondra@postgre 5445 [ + + ]:CBC 55 : if (method != ANALYZE_SAMPLE_OFF)
5446 : : {
5447 : : bool can_tablesample;
5448 : :
1374 5449 : 48 : reltuples = postgresGetAnalyzeInfoForForeignTable(relation,
5450 : : &can_tablesample);
5451 : :
5452 : : /*
5453 : : * Make sure we're not choosing TABLESAMPLE when the remote relation
5454 : : * does not support that. But only do this for "auto" - if the user
5455 : : * explicitly requested BERNOULLI/SYSTEM, it's better to fail.
5456 : : */
5457 [ - + - - ]: 48 : if (!can_tablesample && (method == ANALYZE_SAMPLE_AUTO))
1374 tomas.vondra@postgre 5458 :UBC 0 : method = ANALYZE_SAMPLE_RANDOM;
5459 : :
5460 : : /*
5461 : : * Remote's reltuples could be 0 or -1 if the table has never been
5462 : : * vacuumed/analyzed. In that case, disable sampling after all.
5463 : : */
1382 tomas.vondra@postgre 5464 [ + + + - ]:CBC 48 : if ((reltuples <= 0) || (targrows >= reltuples))
5465 : 48 : method = ANALYZE_SAMPLE_OFF;
5466 : : else
5467 : : {
5468 : : /*
5469 : : * All supported sampling methods require sampling rate, not
5470 : : * target rows directly, so we calculate that using the remote
5471 : : * reltuples value. That's imperfect, because it might be off a
5472 : : * good deal, but that's not something we can (or should) address
5473 : : * here.
5474 : : *
5475 : : * If reltuples is too low (i.e. when table grew), we'll end up
5476 : : * sampling more rows - but then we'll apply the local sampling,
5477 : : * so we get the expected sample size. This is the same outcome as
5478 : : * without remote sampling.
5479 : : *
5480 : : * If reltuples is too high (e.g. after bulk DELETE), we will end
5481 : : * up sampling too few rows.
5482 : : *
5483 : : * We can't really do much better here - we could try sampling a
5484 : : * bit more rows, but we don't know how off the reltuples value is
5485 : : * so how much is "a bit more"?
5486 : : *
5487 : : * Furthermore, the targrows value for partitions is determined
5488 : : * based on table size (relpages), which can be off in different
5489 : : * ways too. Adjusting the sampling rate here might make the issue
5490 : : * worse.
5491 : : */
1382 tomas.vondra@postgre 5492 :UBC 0 : sample_frac = targrows / reltuples;
5493 : :
5494 : : /*
5495 : : * We should never get sampling rate outside the valid range
5496 : : * (between 0.0 and 1.0), because those cases should be covered by
5497 : : * the previous branch that sets ANALYZE_SAMPLE_OFF.
5498 : : */
1375 5499 [ # # # # ]: 0 : Assert(sample_frac >= 0.0 && sample_frac <= 1.0);
5500 : : }
5501 : : }
5502 : :
5503 : : /*
5504 : : * For "auto" method, pick the one we believe is best. For servers with
5505 : : * TABLESAMPLE support we pick BERNOULLI, for old servers we fall-back to
5506 : : * random() to at least reduce network transfer.
5507 : : */
1374 tomas.vondra@postgre 5508 [ - + ]:CBC 55 : if (method == ANALYZE_SAMPLE_AUTO)
5509 : : {
6 efujita@postgresql.o 5510 [ # # ]:UBC 0 : if (server_version_num < 90500)
1374 tomas.vondra@postgre 5511 : 0 : method = ANALYZE_SAMPLE_RANDOM;
5512 : : else
5513 : 0 : method = ANALYZE_SAMPLE_BERNOULLI;
5514 : : }
5515 : :
5516 : : /*
5517 : : * Construct cursor that retrieves whole rows from remote.
5518 : : */
4981 tgl@sss.pgh.pa.us 5519 :CBC 55 : cursor_number = GetCursorNumber(conn);
5520 : 55 : initStringInfo(&sql);
5521 : 55 : appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number);
5522 : :
1382 tomas.vondra@postgre 5523 : 55 : deparseAnalyzeSql(&sql, relation, method, sample_frac, &astate.retrieved_attrs);
5524 : :
444 tgl@sss.pgh.pa.us 5525 : 55 : res = pgfdw_exec_query(conn, sql.data, NULL);
5526 [ - + ]: 55 : if (PQresultStatus(res) != PGRES_COMMAND_OK)
440 tgl@sss.pgh.pa.us 5527 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
444 tgl@sss.pgh.pa.us 5528 :CBC 55 : PQclear(res);
5529 : :
5530 : : /*
5531 : : * Determine the fetch size. The default is arbitrary, but shouldn't be
5532 : : * enormous.
5533 : : */
5534 : 55 : fetch_size = 100;
5535 [ + - + + : 268 : foreach(lc, server->options)
+ + ]
5536 : : {
5537 : 213 : DefElem *def = (DefElem *) lfirst(lc);
5538 : :
5539 [ - + ]: 213 : if (strcmp(def->defname, "fetch_size") == 0)
5540 : : {
444 tgl@sss.pgh.pa.us 5541 :UBC 0 : (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
5542 : 0 : break;
5543 : : }
5544 : : }
444 tgl@sss.pgh.pa.us 5545 [ + - + + :CBC 131 : foreach(lc, table->options)
+ + ]
5546 : : {
5547 : 76 : DefElem *def = (DefElem *) lfirst(lc);
5548 : :
5549 [ - + ]: 76 : if (strcmp(def->defname, "fetch_size") == 0)
5550 : : {
444 tgl@sss.pgh.pa.us 5551 :UBC 0 : (void) parse_int(defGetString(def), &fetch_size, 0, NULL);
5552 : 0 : break;
5553 : : }
5554 : : }
5555 : :
5556 : : /* Construct command to fetch rows from remote. */
444 tgl@sss.pgh.pa.us 5557 :CBC 55 : snprintf(fetch_sql, sizeof(fetch_sql), "FETCH %d FROM c%u",
5558 : : fetch_size, cursor_number);
5559 : :
5560 : : /* Retrieve and process rows a batch at a time. */
5561 : : for (;;)
5562 : 232 : {
5563 : : int numrows;
5564 : : int i;
5565 : :
5566 : : /* Allow users to cancel long query */
5567 [ - + ]: 287 : CHECK_FOR_INTERRUPTS();
5568 : :
5569 : : /*
5570 : : * XXX possible future improvement: if rowstoskip is large, we could
5571 : : * issue a MOVE rather than physically fetching the rows, then just
5572 : : * adjust rowstoskip and samplerows appropriately.
5573 : : */
5574 : :
5575 : : /* Fetch some rows */
5576 : 287 : res = pgfdw_exec_query(conn, fetch_sql, NULL);
5577 : : /* On error, report the original query, not the FETCH. */
5578 [ - + ]: 287 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 5579 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
5580 : :
5581 : : /* Process whatever we got. */
444 tgl@sss.pgh.pa.us 5582 :CBC 287 : numrows = PQntuples(res);
5583 [ + + ]: 24067 : for (i = 0; i < numrows; i++)
5584 : 23781 : analyze_row_processor(res, i, &astate);
5585 : :
5586 : 286 : PQclear(res);
5587 : :
5588 : : /* Must be EOF if we didn't get all the rows requested. */
5589 [ + + ]: 286 : if (numrows < fetch_size)
5590 : 54 : break;
5591 : : }
5592 : :
5593 : : /* Close the cursor, just to be tidy. */
5594 : 54 : close_cursor(conn, cursor_number, NULL);
5595 : :
4981 5596 : 54 : ReleaseConnection(conn);
5597 : :
5598 : : /* We assume that we have no dead tuple. */
5599 : 54 : *totaldeadrows = 0.0;
5600 : :
5601 : : /*
5602 : : * Without sampling, we've retrieved all living tuples from foreign
5603 : : * server, so report that as totalrows. Otherwise use the reltuples
5604 : : * estimate we got from the remote side.
5605 : : */
1382 tomas.vondra@postgre 5606 [ + - ]: 54 : if (method == ANALYZE_SAMPLE_OFF)
5607 : 54 : *totalrows = astate.samplerows;
5608 : : else
1382 tomas.vondra@postgre 5609 :UBC 0 : *totalrows = reltuples;
5610 : :
5611 : : /*
5612 : : * Emit some interesting relation info
5613 : : */
4981 tgl@sss.pgh.pa.us 5614 [ - + ]:CBC 54 : ereport(elevel,
5615 : : (errmsg("\"%s\": table contains %.0f rows, %d rows in sample",
5616 : : RelationGetRelationName(relation),
5617 : : *totalrows, astate.numrows)));
5618 : :
5619 : 54 : return astate.numrows;
5620 : : }
5621 : :
5622 : : /*
5623 : : * Collect sample rows from the result of query.
5624 : : * - Use all tuples in sample until target # of samples are collected.
5625 : : * - Subsequently, replace already-sampled tuples randomly.
5626 : : */
5627 : : static void
5628 : 23781 : analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
5629 : : {
5630 : 23781 : int targrows = astate->targrows;
5631 : : int pos; /* array index to store tuple in */
5632 : : MemoryContext oldcontext;
5633 : :
5634 : : /* Always increment sample row counter. */
5635 : 23781 : astate->samplerows += 1;
5636 : :
5637 : : /*
5638 : : * Determine the slot where this sample row should be stored. Set pos to
5639 : : * negative value to indicate the row should be skipped.
5640 : : */
5641 [ + - ]: 23781 : if (astate->numrows < targrows)
5642 : : {
5643 : : /* First targrows rows are always included into the sample */
5644 : 23781 : pos = astate->numrows++;
5645 : : }
5646 : : else
5647 : : {
5648 : : /*
5649 : : * Now we start replacing tuples in the sample until we reach the end
5650 : : * of the relation. Same algorithm as in acquire_sample_rows in
5651 : : * analyze.c; see Jeff Vitter's paper.
5652 : : */
4981 tgl@sss.pgh.pa.us 5653 [ # # ]:UBC 0 : if (astate->rowstoskip < 0)
4168 simon@2ndQuadrant.co 5654 : 0 : astate->rowstoskip = reservoir_get_next_S(&astate->rstate, astate->samplerows, targrows);
5655 : :
4981 tgl@sss.pgh.pa.us 5656 [ # # ]: 0 : if (astate->rowstoskip <= 0)
5657 : : {
5658 : : /* Choose a random reservoir element to replace. */
1779 5659 : 0 : pos = (int) (targrows * sampler_random_fract(&astate->rstate.randstate));
4981 5660 [ # # # # ]: 0 : Assert(pos >= 0 && pos < targrows);
5661 : 0 : heap_freetuple(astate->rows[pos]);
5662 : : }
5663 : : else
5664 : : {
5665 : : /* Skip this tuple. */
5666 : 0 : pos = -1;
5667 : : }
5668 : :
5669 : 0 : astate->rowstoskip -= 1;
5670 : : }
5671 : :
4981 tgl@sss.pgh.pa.us 5672 [ + - ]:CBC 23781 : if (pos >= 0)
5673 : : {
5674 : : /*
5675 : : * Create sample tuple from current result row, and store it in the
5676 : : * position determined above. The tuple has to be created in anl_cxt.
5677 : : */
5678 : 23781 : oldcontext = MemoryContextSwitchTo(astate->anl_cxt);
5679 : :
5680 : 23781 : astate->rows[pos] = make_tuple_from_result_row(res, row,
5681 : : astate->rel,
5682 : : astate->attinmeta,
5683 : : astate->retrieved_attrs,
5684 : : NULL,
5685 : : astate->temp_cxt);
5686 : :
5687 : 23780 : MemoryContextSwitchTo(oldcontext);
5688 : : }
5689 : 23780 : }
5690 : :
5691 : : /*
5692 : : * postgresImportForeignStatistics
5693 : : * Attempt to import remote statistics instead of sampling.
5694 : : */
5695 : : static bool
187 efujita@postgresql.o 5696 : 48 : postgresImportForeignStatistics(Relation relation, List *va_cols, int elevel)
5697 : : {
5698 : 48 : const char *schemaname = NULL;
5699 : 48 : const char *relname = NULL;
5700 : : ForeignTable *table;
5701 : : ForeignServer *server;
152 tgl@sss.pgh.pa.us 5702 : 48 : RemoteStatsResults remstats = {.rel = NULL, .att = NULL};
187 efujita@postgresql.o 5703 : 48 : RemoteAttributeMapping *remattrmap = NULL;
5704 : 48 : int attrcnt = 0;
97 5705 : 48 : TimestampTz starttime = 0;
59 5706 : 48 : bool import_stats = false;
187 5707 : 48 : bool ok = false;
5708 : : ListCell *lc;
5709 : :
5710 : 48 : schemaname = get_namespace_name(RelationGetNamespace(relation));
5711 : 48 : relname = RelationGetRelationName(relation);
5712 : 48 : table = GetForeignTable(RelationGetRelid(relation));
5713 : 48 : server = GetForeignServer(table->serverid);
5714 : :
5715 : : /*
5716 : : * Check whether the import_stats option is enabled on the foreign table.
5717 : : * If not, silently ignore the foreign table.
5718 : : *
5719 : : * Server-level options can be overridden by table-level options, so check
5720 : : * server-level first.
5721 : : */
5722 [ + - + + : 255 : foreach(lc, server->options)
+ + ]
5723 : : {
5724 : 207 : DefElem *def = (DefElem *) lfirst(lc);
5725 : :
59 5726 [ - + ]: 207 : if (strcmp(def->defname, "import_stats") == 0)
5727 : : {
59 efujita@postgresql.o 5728 :UBC 0 : import_stats = defGetBoolean(def);
187 5729 : 0 : break;
5730 : : }
5731 : : }
187 efujita@postgresql.o 5732 [ + - + + :CBC 108 : foreach(lc, table->options)
+ + ]
5733 : : {
5734 : 73 : DefElem *def = (DefElem *) lfirst(lc);
5735 : :
59 5736 [ + + ]: 73 : if (strcmp(def->defname, "import_stats") == 0)
5737 : : {
5738 : 13 : import_stats = defGetBoolean(def);
187 5739 : 13 : break;
5740 : : }
5741 : : }
59 5742 [ + + ]: 48 : if (!import_stats)
187 5743 : 35 : return false;
5744 : :
5745 : : /*
5746 : : * We don't currently support statistics import for foreign tables with
5747 : : * extended statistics objects.
5748 : : */
5749 [ + + ]: 13 : if (HasRelationExtStatistics(relation))
5750 : : {
5751 [ + - ]: 1 : ereport(WARNING,
5752 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5753 : : errmsg("cannot import statistics for foreign table \"%s.%s\" --- this foreign table has extended statistics objects",
5754 : : schemaname, relname));
5755 : 1 : return false;
5756 : : }
5757 : :
5758 : : /*
5759 : : * OK, let's do it.
5760 : : */
5761 [ + + ]: 12 : ereport(elevel,
5762 : : (errmsg("importing statistics for foreign table \"%s.%s\"",
5763 : : schemaname, relname)));
5764 : :
97 5765 : 12 : starttime = GetCurrentTimestamp();
5766 : :
187 5767 : 12 : ok = fetch_remote_statistics(relation, va_cols,
5768 : : schemaname, relname, table, server,
5769 : : &remstats, &remattrmap, &attrcnt);
5770 : :
5771 [ + + ]: 12 : if (ok)
94 5772 : 8 : ok = import_fetched_statistics(relation, schemaname, relname,
5773 : : &remstats, remattrmap, attrcnt);
5774 : :
187 5775 [ + + ]: 12 : if (ok)
5776 : : {
38 5777 : 8 : pgstat_report_analyze(relation, remstats.reltuples, 0,
5778 : : (va_cols == NIL), starttime);
5779 : :
187 5780 [ + - ]: 8 : ereport(elevel,
5781 : : (errmsg("finished importing statistics for foreign table \"%s.%s\"",
5782 : : schemaname, relname)));
5783 : : }
5784 : :
5785 : 12 : PQclear(remstats.rel);
5786 : 12 : PQclear(remstats.att);
149 5787 : 12 : free_remattrmap(remattrmap, attrcnt);
5788 : :
187 5789 : 12 : return ok;
5790 : : }
5791 : :
5792 : : /*
5793 : : * Attempt to fetch statistics from a remote server.
5794 : : */
5795 : : static bool
5796 : 12 : fetch_remote_statistics(Relation relation,
5797 : : List *va_cols,
5798 : : const char *local_schemaname,
5799 : : const char *local_relname,
5800 : : ForeignTable *table,
5801 : : ForeignServer *server,
5802 : : RemoteStatsResults *remstats,
5803 : : RemoteAttributeMapping **p_remattrmap,
5804 : : int *p_attrcnt)
5805 : : {
5806 : 12 : const char *remote_schemaname = NULL;
5807 : 12 : const char *remote_relname = NULL;
5808 : : UserMapping *user;
5809 : : PGconn *conn;
5810 : 12 : PGresult *relstats = NULL;
5811 : 12 : PGresult *attstats = NULL;
5812 : : int server_version_num;
5813 : : char relkind;
5814 : : double reltuples;
5815 : 12 : bool ok = false;
5816 : : ListCell *lc;
5817 : :
5818 : : /*
5819 : : * Assume the remote schema/table names are the same as the local name
5820 : : * unless the foreign table's options tell us otherwise.
5821 : : */
5822 : 12 : remote_schemaname = local_schemaname;
5823 : 12 : remote_relname = local_relname;
5824 [ + - + + : 36 : foreach(lc, table->options)
+ + ]
5825 : : {
5826 : 24 : DefElem *def = (DefElem *) lfirst(lc);
5827 : :
5828 [ - + ]: 24 : if (strcmp(def->defname, "schema_name") == 0)
187 efujita@postgresql.o 5829 :UBC 0 : remote_schemaname = defGetString(def);
187 efujita@postgresql.o 5830 [ + + ]:CBC 24 : else if (strcmp(def->defname, "table_name") == 0)
5831 : 12 : remote_relname = defGetString(def);
5832 : : }
5833 : :
5834 : : /*
5835 : : * Get the connection to use. We do the remote access as the table's
5836 : : * owner. Note that unlike AnalyzeForeignTable(), the core code would
5837 : : * already have switched us to the table's owner, before we are called
5838 : : * from ImportForeignStatistics().
5839 : : */
5840 : 12 : user = GetUserMapping(GetUserId(), table->serverid);
5841 : 12 : conn = GetConnection(user, false, NULL);
94 5842 : 12 : remstats->version = server_version_num = PQserverVersion(conn);
5843 : :
5844 : : /* Fetch relation stats. */
187 5845 : 12 : remstats->rel = relstats = fetch_relstats(conn, relation);
5846 : :
5847 : : /*
5848 : : * Verify that the remote table is the sort that can have meaningful stats
5849 : : * in pg_stats.
5850 : : *
5851 : : * Note that while relations of kinds RELKIND_INDEX and
5852 : : * RELKIND_PARTITIONED_INDEX can have rows in pg_stats, they obviously
5853 : : * can't support a foreign table.
5854 : : */
5855 : 12 : relkind = *PQgetvalue(relstats, 0, RELSTATS_RELKIND);
5856 [ + + ]: 12 : switch (relkind)
5857 : : {
5858 : 11 : case RELKIND_RELATION:
5859 : : case RELKIND_FOREIGN_TABLE:
5860 : : case RELKIND_MATVIEW:
5861 : : case RELKIND_PARTITIONED_TABLE:
5862 : 11 : break;
5863 : 1 : default:
5864 [ + - ]: 1 : ereport(WARNING,
5865 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- remote table \"%s.%s\" is of relkind \"%c\" which cannot have statistics",
5866 : : local_schemaname, local_relname,
5867 : : remote_schemaname, remote_relname, relkind));
5868 : 1 : goto fetch_cleanup;
5869 : : }
5870 : :
5871 : : /*
5872 : : * For now, we don't support the case where the remote table is (or was
5873 : : * once) inherited; fallback to sampling in that case. XXX FIXME: for the
5874 : : * case where it is inherited, we could also support it by fetching and
5875 : : * adding the relation stats for child tables as well.
5876 : : */
38 5877 [ + + - + ]: 11 : if ((relkind == RELKIND_RELATION || relkind == RELKIND_FOREIGN_TABLE) &&
5878 [ - + ]: 10 : strcmp(PQgetvalue(relstats, 0, RELSTATS_RELHASSUBCLASS), "t") == 0)
5879 : : {
38 efujita@postgresql.o 5880 [ # # ]:UBC 0 : ereport(WARNING,
5881 : : errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
5882 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- remote table \"%s.%s\" is (or was once) inherited",
5883 : : local_schemaname, local_relname,
5884 : : remote_schemaname, remote_relname));
5885 : 0 : goto fetch_cleanup;
5886 : : }
5887 : :
5888 : : /*
5889 : : * If the reltuples value > 0, then we can expect to find attribute stats
5890 : : * for the remote table.
5891 : : *
5892 : : * In v14 or later, if the value is -1, it means the table had never been
5893 : : * analyzed, so we wouldn't expect to find the stats; fallback to sampling
5894 : : * in that case. If the value is 0, it means it was empty, in which case
5895 : : * we don't need the stats, so import relation stats only.
5896 : : *
5897 : : * In versions prior to v14, a value of 0 was ambiguous; it could mean
5898 : : * that the table had never been analyzed, or that it was empty. Assuming
5899 : : * the former, fallback to sampling.
5900 : : */
38 efujita@postgresql.o 5901 :CBC 11 : remstats->reltuples = reltuples =
5902 : 11 : strtod(PQgetvalue(relstats, 0, RELSTATS_RELTUPLES), NULL);
187 5903 [ + + ]: 11 : if (reltuples > 0)
5904 : : {
5905 : : RemoteAttributeMapping *remattrmap;
5906 : : int attrcnt;
5907 : : StringInfoData column_list;
5908 : :
5909 : : /* For columns to analyze, create mappings of local/remote columns. */
5910 : 9 : *p_remattrmap = remattrmap = build_remattrmap(relation, va_cols,
5911 : : &attrcnt, &column_list);
5912 : 9 : *p_attrcnt = attrcnt;
5913 : :
5914 : : /* Try to get attribute stats if needed. */
5915 [ + - ]: 9 : if (attrcnt > 0)
5916 : : {
5917 : : /*
5918 : : * The fetch_attstats query sends COLLATE "C" to the remote
5919 : : * server; if it hasn't got it, fallback to sampling.
5920 : : */
28 5921 [ - + ]: 9 : if (server_version_num < 90100)
5922 : : {
28 efujita@postgresql.o 5923 [ # # ]:UBC 0 : ereport(WARNING,
5924 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- foreign server \"%s\" is too old to support attribute statistics import",
5925 : : local_schemaname, local_relname,
5926 : : server->servername));
28 efujita@postgresql.o 5927 :CBC 2 : goto fetch_cleanup;
5928 : : }
5929 : :
5930 : : /* Fetch attribute stats. */
187 5931 : 18 : remstats->att = attstats = fetch_attstats(conn,
5932 : : server_version_num,
5933 : : remote_schemaname,
5934 : : remote_relname,
5935 : 9 : column_list.data);
5936 : :
5937 : : /* If any attribute stats are missing, fallback to sampling. */
5938 [ + + ]: 9 : if (!match_attrmap(attstats,
5939 : : local_schemaname, local_relname,
5940 : : remote_schemaname, remote_relname,
5941 : : remattrmap, attrcnt))
5942 : 2 : goto fetch_cleanup;
5943 : : }
5944 : : }
48 5945 [ - + - - : 2 : else if (((server_version_num < 140000) && (reltuples == 0)) ||
+ - ]
5946 [ + + ]: 2 : ((server_version_num >= 140000) && (reltuples == -1)))
5947 : : {
5948 [ + - ]: 1 : ereport(WARNING,
5949 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- remote table \"%s.%s\" has no relation statistics to import",
5950 : : local_schemaname, local_relname,
5951 : : remote_schemaname, remote_relname));
5952 : 1 : goto fetch_cleanup;
5953 : : }
5954 : :
5955 : : /*
5956 : : * If the remote table is partitioned, import relpages = 0, to match the
5957 : : * sampling path. Otherwise, import the relpages value as-is, regardless
5958 : : * of any difference between the remote and local block sizes. Note that
5959 : : * this is fine because it's only used for costing remote operations on
5960 : : * the foreign table.
5961 : : */
38 5962 [ + + ]: 8 : if (relkind == RELKIND_PARTITIONED_TABLE)
5963 : 1 : remstats->relpages = 0;
5964 : : else
5965 : 7 : remstats->relpages =
5966 : 7 : strtoul(PQgetvalue(relstats, 0, RELSTATS_RELPAGES), NULL, 10);
5967 : :
187 5968 : 8 : ok = true;
5969 : :
5970 : 12 : fetch_cleanup:
5971 : 12 : ReleaseConnection(conn);
5972 : 12 : return ok;
5973 : : }
5974 : :
5975 : : /*
5976 : : * Attempt to fetch remote relation stats.
5977 : : */
5978 : : static PGresult *
5979 : 12 : fetch_relstats(PGconn *conn, Relation relation)
5980 : : {
5981 : : StringInfoData sql;
5982 : : PGresult *res;
5983 : :
5984 : 12 : initStringInfo(&sql);
5985 : 12 : deparseAnalyzeInfoSql(&sql, relation);
5986 : :
5987 : 12 : res = pgfdw_exec_query(conn, sql.data, NULL);
5988 [ - + ]: 12 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
187 efujita@postgresql.o 5989 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
5990 : :
187 efujita@postgresql.o 5991 [ + - - + ]:CBC 12 : if (PQntuples(res) != 1 || PQnfields(res) != RELSTATS_NUM_FIELDS)
187 efujita@postgresql.o 5992 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from deparseAnalyzeInfoSql query");
5993 : :
187 efujita@postgresql.o 5994 :CBC 12 : return res;
5995 : : }
5996 : :
5997 : : /*
5998 : : * Attempt to fetch remote attribute stats.
5999 : : */
6000 : : static PGresult *
6001 : 9 : fetch_attstats(PGconn *conn, int server_version_num,
6002 : : const char *remote_schemaname, const char *remote_relname,
6003 : : const char *column_list)
6004 : : {
6005 : : StringInfoData sql;
6006 : : PGresult *res;
6007 : :
6008 : : /*
6009 : : * The caller guarantees the remote server is v9.1, which supported
6010 : : * COLLATE "C", or later
6011 : : */
28 6012 [ - + ]: 9 : Assert(server_version_num >= 90100);
6013 : :
187 6014 : 9 : initStringInfo(&sql);
6015 : :
6016 : : /* Type name is collatable since Postgres 12 */
24 6017 [ + - ]: 9 : if (server_version_num >= 120000)
6018 : 9 : appendStringInfoString(&sql,
6019 : : "SELECT DISTINCT ON (attname COLLATE \"C\") attname,");
6020 : : else
24 efujita@postgresql.o 6021 :UBC 0 : appendStringInfoString(&sql,
6022 : : "SELECT DISTINCT ON (attname::text COLLATE \"C\") attname,");
6023 : :
187 efujita@postgresql.o 6024 :CBC 9 : appendStringInfoString(&sql,
6025 : : " null_frac,"
6026 : : " avg_width,"
6027 : : " n_distinct,"
6028 : : " most_common_vals,"
6029 : : " most_common_freqs,"
6030 : : " histogram_bounds,"
6031 : : " correlation,");
6032 : :
6033 : : /* Elements stats are supported since Postgres 9.2 */
24 6034 [ + - ]: 9 : if (server_version_num >= 90200)
187 6035 : 9 : appendStringInfoString(&sql,
6036 : : " most_common_elems,"
6037 : : " most_common_elem_freqs,"
6038 : : " elem_count_histogram,");
6039 : : else
187 efujita@postgresql.o 6040 :UBC 0 : appendStringInfoString(&sql,
6041 : : " NULL, NULL, NULL,");
6042 : :
6043 : : /* Range stats are supported since Postgres 17 */
187 efujita@postgresql.o 6044 [ + - ]:CBC 9 : if (server_version_num >= 170000)
6045 : 9 : appendStringInfoString(&sql,
6046 : : " range_length_histogram,"
6047 : : " range_empty_frac,"
6048 : : " range_bounds_histogram");
6049 : : else
187 efujita@postgresql.o 6050 :UBC 0 : appendStringInfoString(&sql,
6051 : : " NULL, NULL, NULL");
6052 : :
187 efujita@postgresql.o 6053 :CBC 9 : appendStringInfoString(&sql,
6054 : : " FROM pg_catalog.pg_stats"
6055 : : " WHERE schemaname = ");
6056 : 9 : deparseStringLiteral(&sql, remote_schemaname);
6057 : 9 : appendStringInfoString(&sql,
6058 : : " AND tablename = ");
6059 : 9 : deparseStringLiteral(&sql, remote_relname);
6060 : 9 : appendStringInfo(&sql,
6061 : : " AND attname = ANY(%s)",
6062 : : column_list);
6063 : :
6064 : : /*
6065 : : * Type name is collatable since Postgres 12 (inherited is supported
6066 : : * since Postgres 9.0)
6067 : : */
24 6068 [ + - ]: 9 : if (server_version_num >= 120000)
6069 : 9 : appendStringInfoString(&sql,
6070 : : " ORDER BY attname COLLATE \"C\", inherited DESC");
6071 : : else
24 efujita@postgresql.o 6072 :UBC 0 : appendStringInfoString(&sql,
6073 : : " ORDER BY attname::text COLLATE \"C\", inherited DESC");
6074 : :
187 efujita@postgresql.o 6075 :CBC 9 : res = pgfdw_exec_query(conn, sql.data, NULL);
6076 [ - + ]: 9 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
187 efujita@postgresql.o 6077 :UBC 0 : pgfdw_report_error(res, conn, sql.data);
6078 : :
187 efujita@postgresql.o 6079 [ - + ]:CBC 9 : if (PQnfields(res) != ATTSTATS_NUM_FIELDS)
187 efujita@postgresql.o 6080 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from fetch_attstats query");
6081 : :
187 efujita@postgresql.o 6082 :CBC 9 : return res;
6083 : : }
6084 : :
6085 : : /*
6086 : : * For columns to analyze, build the mappings of local columns to remote
6087 : : * columns, and create a column list used for constructing the fetch_attstats
6088 : : * query.
6089 : : */
6090 : : static RemoteAttributeMapping *
6091 : 9 : build_remattrmap(Relation relation, List *va_cols,
6092 : : int *p_attrcnt, StringInfo column_list)
6093 : : {
6094 : 9 : TupleDesc tupdesc = RelationGetDescr(relation);
6095 : 9 : RemoteAttributeMapping *remattrmap = NULL;
6096 : 9 : int attrcnt = 0;
6097 : :
6098 : 9 : remattrmap = palloc_array(RemoteAttributeMapping, tupdesc->natts);
6099 : 9 : initStringInfo(column_list);
151 6100 : 9 : appendStringInfoString(column_list, "ARRAY[");
187 6101 [ + + ]: 34 : for (int i = 0; i < tupdesc->natts; i++)
6102 : : {
6103 : 25 : Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
6104 : 25 : char *attname = NameStr(attr->attname);
6105 : 25 : AttrNumber attnum = attr->attnum;
6106 : : char *colname;
6107 : : List *fc_options;
6108 : : ListCell *lc;
6109 : :
6110 : : /* If a list is specified, exclude any attnames not in it. */
6111 [ + + ]: 25 : if (!attname_in_list(attname, va_cols))
6112 : 6 : continue;
6113 : :
6114 [ - + ]: 19 : if (!attribute_is_analyzable(relation, attnum, attr, NULL))
187 efujita@postgresql.o 6115 :UBC 0 : continue;
6116 : :
6117 : : /*
6118 : : * Assume the remote column names are the same as the local name
6119 : : * unless the foreign column's options tell us otherwise.
6120 : : */
48 efujita@postgresql.o 6121 :CBC 19 : colname = attname;
187 6122 : 19 : fc_options = GetForeignColumnOptions(RelationGetRelid(relation), attnum);
6123 [ + + + - : 19 : foreach(lc, fc_options)
+ + ]
6124 : : {
6125 : 5 : DefElem *def = (DefElem *) lfirst(lc);
6126 : :
6127 [ + - ]: 5 : if (strcmp(def->defname, "column_name") == 0)
6128 : : {
48 6129 : 5 : colname = defGetString(def);
187 6130 : 5 : break;
6131 : : }
6132 : : }
6133 : :
6134 [ + + ]: 19 : if (attrcnt > 0)
6135 : 10 : appendStringInfoString(column_list, ", ");
48 6136 : 19 : deparseStringLiteral(column_list, colname);
6137 : :
187 6138 : 19 : remattrmap[attrcnt].local_attnum = attnum;
149 6139 : 19 : remattrmap[attrcnt].local_attname = pstrdup(attname);
48 6140 : 19 : remattrmap[attrcnt].remote_attname = pstrdup(colname);
187 6141 : 19 : remattrmap[attrcnt].res_index = -1;
6142 : 19 : attrcnt++;
6143 : : }
151 6144 : 9 : appendStringInfoChar(column_list, ']');
6145 : :
6146 : : /* Sort the mappings by remote_attname if needed. */
187 6147 [ + + ]: 9 : if (attrcnt > 1)
6148 : 7 : qsort(remattrmap, attrcnt, sizeof(RemoteAttributeMapping), remattrmap_cmp);
6149 : :
6150 : 9 : *p_attrcnt = attrcnt;
6151 : 9 : return remattrmap;
6152 : : }
6153 : :
6154 : : /*
6155 : : * Free the structure created by build_remattrmap().
6156 : : */
6157 : : static void
149 6158 : 12 : free_remattrmap(RemoteAttributeMapping *map, int len)
6159 : : {
6160 [ + + ]: 12 : if (!map)
6161 : 3 : return;
6162 : :
6163 [ + + ]: 28 : for (int i = 0; i < len; i++)
6164 : : {
6165 [ - + ]: 19 : Assert(map[i].local_attname);
6166 : 19 : pfree(map[i].local_attname);
6167 [ - + ]: 19 : Assert(map[i].remote_attname);
6168 : 19 : pfree(map[i].remote_attname);
6169 : : }
6170 : :
6171 : 9 : pfree(map);
6172 : : }
6173 : :
6174 : : /*
6175 : : * Test if an attribute name is in the list.
6176 : : *
6177 : : * An empty list means that all attribute names are in the list.
6178 : : */
6179 : : static bool
187 6180 : 25 : attname_in_list(const char *attname, List *va_cols)
6181 : : {
6182 : : ListCell *lc;
6183 : :
6184 [ + + ]: 25 : if (va_cols == NIL)
6185 : 13 : return true;
6186 : :
6187 [ + - + + : 22 : foreach(lc, va_cols)
+ + ]
6188 : : {
6189 : 16 : char *col = strVal(lfirst(lc));
6190 : :
6191 [ + + ]: 16 : if (strcmp(attname, col) == 0)
6192 : 6 : return true;
6193 : : }
6194 : 6 : return false;
6195 : : }
6196 : :
6197 : : /*
6198 : : * Compare two RemoteAttributeMappings for sorting.
6199 : : */
6200 : : static int
6201 : 13 : remattrmap_cmp(const void *v1, const void *v2)
6202 : : {
6203 : 13 : const RemoteAttributeMapping *r1 = v1;
6204 : 13 : const RemoteAttributeMapping *r2 = v2;
6205 : :
120 6206 : 13 : return strcmp(r1->remote_attname, r2->remote_attname);
6207 : : }
6208 : :
6209 : : /*
6210 : : * Match local columns to result set rows.
6211 : : *
6212 : : * As the result set consists of the attribute stats for some/all of distinct
6213 : : * mapped remote columns in the RemoteAttributeMapping, every entry in it
6214 : : * should have at most one match in the result set; which is also ordered by
6215 : : * remote_attname, so we find such pairs by doing a merge join.
6216 : : *
6217 : : * Returns true if every entry in it has a match, and false if not.
6218 : : */
6219 : : static bool
187 6220 : 9 : match_attrmap(PGresult *res,
6221 : : const char *local_schemaname,
6222 : : const char *local_relname,
6223 : : const char *remote_schemaname,
6224 : : const char *remote_relname,
6225 : : RemoteAttributeMapping *remattrmap,
6226 : : int attrcnt)
6227 : : {
6228 : 9 : int numrows = PQntuples(res);
6229 : 9 : int row = -1;
6230 : :
6231 : : /* No work if there are no stats rows. */
6232 [ + + ]: 9 : if (numrows == 0)
6233 : : {
6234 [ + - ]: 1 : ereport(WARNING,
6235 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- remote table \"%s.%s\" has no attribute statistics to import",
6236 : : local_schemaname, local_relname,
6237 : : remote_schemaname, remote_relname));
6238 : 1 : return false;
6239 : : }
6240 : :
6241 : : /* Scan all entries in the RemoteAttributeMapping. */
6242 [ + + ]: 23 : for (int mapidx = 0; mapidx < attrcnt; mapidx++)
6243 : : {
6244 : : /*
6245 : : * First, check whether the entry matches the current stats row, if it
6246 : : * is set.
6247 : : */
6248 [ + + ]: 16 : if (row >= 0 &&
6249 [ + + ]: 8 : strcmp(remattrmap[mapidx].remote_attname,
6250 : 8 : PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0)
6251 : : {
6252 : 3 : remattrmap[mapidx].res_index = row;
6253 : 3 : continue;
6254 : : }
6255 : :
6256 : : /*
6257 : : * If we've exhausted all stats rows, it means the stats for the entry
6258 : : * are missing.
6259 : : */
6260 [ + + ]: 13 : if (row >= numrows - 1)
6261 : : {
6262 [ + - ]: 1 : ereport(WARNING,
6263 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- no attribute statistics found for column \"%s\" of remote table \"%s.%s\"",
6264 : : local_schemaname, local_relname,
6265 : : remattrmap[mapidx].remote_attname,
6266 : : remote_schemaname, remote_relname));
6267 : 1 : return false;
6268 : : }
6269 : :
6270 : : /* Advance to the next stats row. */
6271 : 12 : row += 1;
6272 : :
6273 : : /*
6274 : : * If the attname in the entry is less than that in the next stats
6275 : : * row, it means the stats for the entry are missing.
6276 : : */
6277 [ - + ]: 12 : if (strcmp(remattrmap[mapidx].remote_attname,
6278 : 12 : PQgetvalue(res, row, ATTSTATS_ATTNAME)) < 0)
6279 : : {
187 efujita@postgresql.o 6280 [ # # ]:UBC 0 : ereport(WARNING,
6281 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- no attribute statistics found for column \"%s\" of remote table \"%s.%s\"",
6282 : : local_schemaname, local_relname,
6283 : : remattrmap[mapidx].remote_attname,
6284 : : remote_schemaname, remote_relname));
6285 : 0 : return false;
6286 : : }
6287 : :
6288 : : /* We should not have got a stats row we didn't expect. */
187 efujita@postgresql.o 6289 [ - + ]:CBC 12 : if (strcmp(remattrmap[mapidx].remote_attname,
6290 : 12 : PQgetvalue(res, row, ATTSTATS_ATTNAME)) > 0)
187 efujita@postgresql.o 6291 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from fetch_attstats query");
6292 : :
6293 : : /* We found a match. */
187 efujita@postgresql.o 6294 [ - + ]:CBC 12 : Assert(strcmp(remattrmap[mapidx].remote_attname,
6295 : : PQgetvalue(res, row, ATTSTATS_ATTNAME)) == 0);
6296 : 12 : remattrmap[mapidx].res_index = row;
6297 : : }
6298 : :
6299 : : /* We should have exhausted all stats rows. */
6300 [ - + ]: 7 : if (row < numrows - 1)
187 efujita@postgresql.o 6301 [ # # ]:UBC 0 : elog(ERROR, "unexpected result from fetch_attstats query");
6302 : :
187 efujita@postgresql.o 6303 :CBC 7 : return true;
6304 : : }
6305 : :
6306 : : /*
6307 : : * Import fetched statistics into the local statistics tables.
6308 : : */
6309 : : static bool
94 6310 : 8 : import_fetched_statistics(Relation relation,
6311 : : const char *schemaname,
6312 : : const char *relname,
6313 : : RemoteStatsResults *remstats,
6314 : : const RemoteAttributeMapping *remattrmap,
6315 : : int attrcnt)
6316 : : {
6317 : : NullableDatum args[ATTSTATS_NUM_FIELDS];
6318 : :
6319 : : /* Set the 'version' parameter, which is common to both statistics. */
39 michael@paquier.xyz 6320 : 8 : args[0].value = Int32GetDatum(remstats->version);
6321 : 8 : args[0].isnull = false;
6322 : :
6323 : : /*
6324 : : * We import attribute statistics first, if any, because those are more
6325 : : * prone to errors. This avoids making a modification of pg_class that
6326 : : * will just get rolled back by a failed attribute import.
6327 : : */
38 efujita@postgresql.o 6328 [ + + ]: 8 : if (remstats->att != NULL)
6329 : : {
6330 : 7 : PGresult *res = remstats->att;
6331 : :
94 6332 [ - + ]: 7 : Assert(PQnfields(res) == ATTSTATS_NUM_FIELDS);
6333 [ - + ]: 7 : Assert(PQntuples(res) >= 1);
6334 : :
187 6335 [ + + ]: 20 : for (int mapidx = 0; mapidx < attrcnt; mapidx++)
6336 : : {
6337 : 13 : int row = remattrmap[mapidx].res_index;
94 6338 : 13 : AttrNumber attnum = remattrmap[mapidx].local_attnum;
6339 : :
6340 : : /* All mappings should have been assigned a result set row. */
187 6341 [ - + ]: 13 : Assert(row >= 0);
6342 : :
6343 : : /* Check for user-requested abort. */
6344 [ - + ]: 13 : CHECK_FOR_INTERRUPTS();
6345 : :
6346 : : /* Clear existing attribute statistics. */
94 6347 : 13 : delete_attribute_statistics(relation, attnum, false);
6348 : :
6349 : : /* Set the remaining parameters. */
39 michael@paquier.xyz 6350 : 13 : set_float_arg(&args[1],
94 efujita@postgresql.o 6351 : 13 : get_opt_value(res, row, ATTSTATS_NULL_FRAC));
39 michael@paquier.xyz 6352 : 13 : set_int32_arg(&args[2],
94 efujita@postgresql.o 6353 : 13 : get_opt_value(res, row, ATTSTATS_AVG_WIDTH));
39 michael@paquier.xyz 6354 : 13 : set_float_arg(&args[3],
94 efujita@postgresql.o 6355 : 13 : get_opt_value(res, row, ATTSTATS_N_DISTINCT));
39 michael@paquier.xyz 6356 : 13 : set_text_arg(&args[4],
94 efujita@postgresql.o 6357 : 13 : get_opt_value(res, row, ATTSTATS_MOST_COMMON_VALS));
39 michael@paquier.xyz 6358 : 13 : set_floatarr_arg(&args[5],
94 efujita@postgresql.o 6359 : 13 : get_opt_value(res, row, ATTSTATS_MOST_COMMON_FREQS));
39 michael@paquier.xyz 6360 : 13 : set_text_arg(&args[6],
94 efujita@postgresql.o 6361 : 13 : get_opt_value(res, row, ATTSTATS_HISTOGRAM_BOUNDS));
39 michael@paquier.xyz 6362 : 13 : set_float_arg(&args[7],
94 efujita@postgresql.o 6363 : 13 : get_opt_value(res, row, ATTSTATS_CORRELATION));
39 michael@paquier.xyz 6364 : 13 : set_text_arg(&args[8],
94 efujita@postgresql.o 6365 : 13 : get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEMS));
39 michael@paquier.xyz 6366 : 13 : set_floatarr_arg(&args[9],
94 efujita@postgresql.o 6367 : 13 : get_opt_value(res, row, ATTSTATS_MOST_COMMON_ELEM_FREQS));
39 michael@paquier.xyz 6368 : 13 : set_floatarr_arg(&args[10],
94 efujita@postgresql.o 6369 : 13 : get_opt_value(res, row, ATTSTATS_ELEM_COUNT_HISTOGRAM));
39 michael@paquier.xyz 6370 : 13 : set_text_arg(&args[11],
94 efujita@postgresql.o 6371 : 13 : get_opt_value(res, row, ATTSTATS_RANGE_LENGTH_HISTOGRAM));
39 michael@paquier.xyz 6372 : 13 : set_float_arg(&args[12],
94 efujita@postgresql.o 6373 : 13 : get_opt_value(res, row, ATTSTATS_RANGE_EMPTY_FRAC));
39 michael@paquier.xyz 6374 : 13 : set_text_arg(&args[13],
94 efujita@postgresql.o 6375 : 13 : get_opt_value(res, row, ATTSTATS_RANGE_BOUNDS_HISTOGRAM));
6376 : :
6377 : : /* Try to import the statistics. */
6378 [ - + ]: 13 : if (!import_attribute_statistics(relation, attnum, false,
6379 : : &args[0], &args[1], &args[2],
6380 : : &args[3], &args[4], &args[5],
6381 : : &args[6], &args[7], &args[8],
6382 : : &args[9], &args[10], &args[11],
6383 : : &args[12], &args[13]))
6384 : : {
187 efujita@postgresql.o 6385 [ # # ]:UBC 0 : ereport(WARNING,
6386 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- attribute statistics import failed for column \"%s\" of this foreign table",
6387 : : schemaname, relname,
6388 : : remattrmap[mapidx].local_attname));
94 6389 : 0 : return false;
6390 : : }
6391 : : }
6392 : : }
6393 : :
6394 : : /*
6395 : : * Import relation statistics.
6396 : : */
6397 : :
6398 : : /* Set the remaining parameters. */
38 efujita@postgresql.o 6399 :GNC 8 : args[1].value = Int32GetDatum(remstats->relpages);
38 efujita@postgresql.o 6400 :CBC 8 : args[1].isnull = false;
6401 : 8 : args[2].value = Float4GetDatum(remstats->reltuples);
6402 : 8 : args[2].isnull = false;
6403 : : /* We don't import relallvisible/relallfrozen. */
39 michael@paquier.xyz 6404 : 8 : args[3].value = (Datum) 0;
6405 : 8 : args[3].isnull = true;
6406 : 8 : args[4].value = (Datum) 0;
6407 : 8 : args[4].isnull = true;
6408 : :
6409 : : /* Try to import the statistics. */
6410 [ - + ]: 8 : if (!import_relation_statistics(relation, &args[0], &args[1],
6411 : : &args[2], &args[3], &args[4]))
6412 : : {
187 efujita@postgresql.o 6413 [ # # ]:UBC 0 : ereport(WARNING,
6414 : : errmsg("could not import statistics for foreign table \"%s.%s\" --- relation statistics import failed for this foreign table",
6415 : : schemaname, relname));
94 6416 : 0 : return false;
6417 : : }
6418 : :
94 efujita@postgresql.o 6419 :CBC 8 : return true;
6420 : : }
6421 : :
6422 : : /*
6423 : : * Convenience routine to fetch the value for the row/column of the PGresult
6424 : : */
6425 : : static char *
6426 : 169 : get_opt_value(PGresult *res, int row, int col)
6427 : : {
6428 [ + + ]: 169 : if (PQgetisnull(res, row, col))
6429 : 93 : return NULL;
6430 : 76 : return PQgetvalue(res, row, col);
6431 : : }
6432 : :
6433 : : /*
6434 : : * Convenience routine for setting optional text arguments
6435 : : */
6436 : : static void
6437 : 65 : set_text_arg(NullableDatum *arg, const char *s)
6438 : : {
6439 [ + + ]: 65 : if (s)
6440 : : {
6441 : 13 : arg->value = CStringGetTextDatum(s);
6442 : 13 : arg->isnull = false;
6443 : : }
6444 : : else
6445 : : {
6446 : 52 : arg->value = (Datum) 0;
6447 : 52 : arg->isnull = true;
6448 : : }
6449 : 65 : }
6450 : :
6451 : : /*
6452 : : * Convenience routine for setting optional int32 arguments
6453 : : */
6454 : : static void
6455 : 13 : set_int32_arg(NullableDatum *arg, const char *s)
6456 : : {
6457 [ + - ]: 13 : if (s)
6458 : : {
6459 : 13 : int32 val = pg_strtoint32(s);
6460 : :
6461 : 13 : arg->value = Int32GetDatum(val);
6462 : 13 : arg->isnull = false;
6463 : : }
6464 : : else
6465 : : {
94 efujita@postgresql.o 6466 :UBC 0 : arg->value = (Datum) 0;
6467 : 0 : arg->isnull = true;
6468 : : }
94 efujita@postgresql.o 6469 :CBC 13 : }
6470 : :
6471 : : /*
6472 : : * Convenience routine for setting optional float arguments
6473 : : */
6474 : : static void
6475 : 52 : set_float_arg(NullableDatum *arg, const char *s)
6476 : : {
6477 [ + + ]: 52 : if (s)
6478 : : {
6479 : 39 : float4 val = float4in_internal((char *) s, NULL, "float", s, NULL);
6480 : :
6481 : 39 : arg->value = Float4GetDatum(val);
6482 : 39 : arg->isnull = false;
6483 : : }
6484 : : else
6485 : : {
6486 : 13 : arg->value = (Datum) 0;
6487 : 13 : arg->isnull = true;
6488 : : }
6489 : 52 : }
6490 : :
6491 : : /*
6492 : : * Convenience routine for setting optional float[] arguments
6493 : : */
6494 : : static void
6495 : 39 : set_floatarr_arg(NullableDatum *arg, const char *s)
6496 : : {
6497 [ + + ]: 39 : if (s)
6498 : : {
6499 : : FmgrInfo flinfo;
6500 : : Datum val;
6501 : :
6502 : 11 : fmgr_info(F_ARRAY_IN, &flinfo);
48 peter@eisentraut.org 6503 :GNC 11 : val = InputFunctionCall(&flinfo, s, FLOAT4OID, -1);
6504 : :
94 efujita@postgresql.o 6505 :CBC 11 : arg->value = val;
6506 : 11 : arg->isnull = false;
6507 : : }
6508 : : else
6509 : : {
6510 : 28 : arg->value = (Datum) 0;
6511 : 28 : arg->isnull = true;
6512 : : }
187 6513 : 39 : }
6514 : :
6515 : : /*
6516 : : * Import a foreign schema
6517 : : */
6518 : : static List *
4477 tgl@sss.pgh.pa.us 6519 : 10 : postgresImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid)
6520 : : {
6521 : 10 : List *commands = NIL;
6522 : 10 : bool import_collate = true;
6523 : 10 : bool import_default = false;
1894 efujita@postgresql.o 6524 : 10 : bool import_generated = true;
4477 tgl@sss.pgh.pa.us 6525 : 10 : bool import_not_null = true;
6526 : : ForeignServer *server;
6527 : : UserMapping *mapping;
6528 : : PGconn *conn;
6529 : : StringInfoData buf;
6530 : : PGresult *res;
6531 : : int numrows,
6532 : : i;
6533 : : ListCell *lc;
6534 : :
6535 : : /* Parse statement options */
6536 [ + + + + : 14 : foreach(lc, stmt->options)
+ + ]
6537 : : {
6538 : 4 : DefElem *def = (DefElem *) lfirst(lc);
6539 : :
6540 [ + + ]: 4 : if (strcmp(def->defname, "import_collate") == 0)
6541 : 1 : import_collate = defGetBoolean(def);
6542 [ + + ]: 3 : else if (strcmp(def->defname, "import_default") == 0)
6543 : 1 : import_default = defGetBoolean(def);
1894 efujita@postgresql.o 6544 [ + + ]: 2 : else if (strcmp(def->defname, "import_generated") == 0)
6545 : 1 : import_generated = defGetBoolean(def);
4477 tgl@sss.pgh.pa.us 6546 [ + - ]: 1 : else if (strcmp(def->defname, "import_not_null") == 0)
6547 : 1 : import_not_null = defGetBoolean(def);
6548 : : else
4477 tgl@sss.pgh.pa.us 6549 [ # # ]:UBC 0 : ereport(ERROR,
6550 : : (errcode(ERRCODE_FDW_INVALID_OPTION_NAME),
6551 : : errmsg("invalid option \"%s\"", def->defname)));
6552 : : }
6553 : :
6554 : : /*
6555 : : * Get connection to the foreign server. Connection manager will
6556 : : * establish new connection if necessary.
6557 : : */
4477 tgl@sss.pgh.pa.us 6558 :CBC 10 : server = GetForeignServer(serverOid);
6559 : 10 : mapping = GetUserMapping(GetUserId(), server->serverid);
2021 efujita@postgresql.o 6560 : 10 : conn = GetConnection(mapping, false, NULL);
6561 : :
6562 : : /* Don't attempt to import collation if remote server hasn't got it */
4477 tgl@sss.pgh.pa.us 6563 [ - + ]: 10 : if (PQserverVersion(conn) < 90100)
4477 tgl@sss.pgh.pa.us 6564 :UBC 0 : import_collate = false;
6565 : :
6566 : : /* Create workspace for strings */
4477 tgl@sss.pgh.pa.us 6567 :CBC 10 : initStringInfo(&buf);
6568 : :
6569 : : /* Check that the schema really exists */
444 6570 : 10 : appendStringInfoString(&buf, "SELECT 1 FROM pg_catalog.pg_namespace WHERE nspname = ");
6571 : 10 : deparseStringLiteral(&buf, stmt->remote_schema);
6572 : :
6573 : 10 : res = pgfdw_exec_query(conn, buf.data, NULL);
6574 [ - + ]: 10 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 6575 :UBC 0 : pgfdw_report_error(res, conn, buf.data);
6576 : :
444 tgl@sss.pgh.pa.us 6577 [ + + ]:CBC 10 : if (PQntuples(res) != 1)
6578 [ + - ]: 1 : ereport(ERROR,
6579 : : (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND),
6580 : : errmsg("schema \"%s\" is not present on foreign server \"%s\"",
6581 : : stmt->remote_schema, server->servername)));
6582 : :
6583 : 9 : PQclear(res);
6584 : 9 : resetStringInfo(&buf);
6585 : :
6586 : : /*
6587 : : * Fetch all table data from this schema, possibly restricted by EXCEPT or
6588 : : * LIMIT TO. (We don't actually need to pay any attention to EXCEPT/LIMIT
6589 : : * TO here, because the core code will filter the statements we return
6590 : : * according to those lists anyway. But it should save a few cycles to
6591 : : * not process excluded tables in the first place.)
6592 : : *
6593 : : * Import table data for partitions only when they are explicitly
6594 : : * specified in LIMIT TO clause. Otherwise ignore them and only include
6595 : : * the definitions of the root partitioned tables to allow access to the
6596 : : * complete remote data set locally in the schema imported.
6597 : : *
6598 : : * Note: because we run the connection with search_path restricted to
6599 : : * pg_catalog, the format_type() and pg_get_expr() outputs will always
6600 : : * include a schema name for types/functions in other schemas, which is
6601 : : * what we want.
6602 : : */
6603 : 9 : appendStringInfoString(&buf,
6604 : : "SELECT relname, "
6605 : : " attname, "
6606 : : " format_type(atttypid, atttypmod), "
6607 : : " attnotnull, "
6608 : : " pg_get_expr(adbin, adrelid), ");
6609 : :
6610 : : /* Generated columns are supported since Postgres 12 */
6611 [ + - ]: 9 : if (PQserverVersion(conn) >= 120000)
1894 efujita@postgresql.o 6612 : 9 : appendStringInfoString(&buf,
6613 : : " attgenerated, ");
6614 : : else
1867 tgl@sss.pgh.pa.us 6615 :UBC 0 : appendStringInfoString(&buf,
6616 : : " NULL, ");
6617 : :
444 tgl@sss.pgh.pa.us 6618 [ + + ]:CBC 9 : if (import_collate)
4477 6619 : 8 : appendStringInfoString(&buf,
6620 : : " collname, "
6621 : : " collnsp.nspname ");
6622 : : else
444 6623 : 1 : appendStringInfoString(&buf,
6624 : : " NULL, NULL ");
6625 : :
6626 : 9 : appendStringInfoString(&buf,
6627 : : "FROM pg_class c "
6628 : : " JOIN pg_namespace n ON "
6629 : : " relnamespace = n.oid "
6630 : : " LEFT JOIN pg_attribute a ON "
6631 : : " attrelid = c.oid AND attnum > 0 "
6632 : : " AND NOT attisdropped "
6633 : : " LEFT JOIN pg_attrdef ad ON "
6634 : : " adrelid = c.oid AND adnum = attnum ");
6635 : :
6636 [ + + ]: 9 : if (import_collate)
6637 : 8 : appendStringInfoString(&buf,
6638 : : " LEFT JOIN pg_collation coll ON "
6639 : : " coll.oid = attcollation "
6640 : : " LEFT JOIN pg_namespace collnsp ON "
6641 : : " collnsp.oid = collnamespace ");
6642 : :
6643 : 9 : appendStringInfoString(&buf,
6644 : : "WHERE c.relkind IN ("
6645 : : CppAsString2(RELKIND_RELATION) ","
6646 : : CppAsString2(RELKIND_VIEW) ","
6647 : : CppAsString2(RELKIND_FOREIGN_TABLE) ","
6648 : : CppAsString2(RELKIND_MATVIEW) ","
6649 : : CppAsString2(RELKIND_PARTITIONED_TABLE) ") "
6650 : : " AND n.nspname = ");
6651 : 9 : deparseStringLiteral(&buf, stmt->remote_schema);
6652 : :
6653 : : /* Partitions are supported since Postgres 10 */
6654 [ + - ]: 9 : if (PQserverVersion(conn) >= 100000 &&
6655 [ + + ]: 9 : stmt->list_type != FDW_IMPORT_SCHEMA_LIMIT_TO)
6656 : 5 : appendStringInfoString(&buf, " AND NOT c.relispartition ");
6657 : :
6658 : : /* Apply restrictions for LIMIT TO and EXCEPT */
6659 [ + + ]: 9 : if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO ||
6660 [ + + ]: 5 : stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
6661 : : {
6662 : 5 : bool first_item = true;
6663 : :
6664 : 5 : appendStringInfoString(&buf, " AND c.relname ");
6665 [ + + ]: 5 : if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT)
6666 : 1 : appendStringInfoString(&buf, "NOT ");
6667 : 5 : appendStringInfoString(&buf, "IN (");
6668 : :
6669 : : /* Append list of table names within IN clause */
6670 [ + - + + : 15 : foreach(lc, stmt->table_list)
+ + ]
6671 : : {
6672 : 10 : RangeVar *rv = (RangeVar *) lfirst(lc);
6673 : :
6674 [ + + ]: 10 : if (first_item)
6675 : 5 : first_item = false;
6676 : : else
6677 : 5 : appendStringInfoString(&buf, ", ");
6678 : 10 : deparseStringLiteral(&buf, rv->relname);
6679 : : }
6680 : 5 : appendStringInfoChar(&buf, ')');
6681 : : }
6682 : :
6683 : : /* Append ORDER BY at the end of query to ensure output ordering */
6684 : 9 : appendStringInfoString(&buf, " ORDER BY c.relname, a.attnum");
6685 : :
6686 : : /* Fetch the data */
6687 : 9 : res = pgfdw_exec_query(conn, buf.data, NULL);
6688 [ - + ]: 9 : if (PQresultStatus(res) != PGRES_TUPLES_OK)
440 tgl@sss.pgh.pa.us 6689 :UBC 0 : pgfdw_report_error(res, conn, buf.data);
6690 : :
6691 : : /* Process results */
444 tgl@sss.pgh.pa.us 6692 :CBC 9 : numrows = PQntuples(res);
6693 : : /* note: incrementation of i happens in inner loop's while() test */
6694 [ + + ]: 47 : for (i = 0; i < numrows;)
6695 : : {
6696 : 38 : char *tablename = PQgetvalue(res, i, 0);
6697 : 38 : bool first_item = true;
6698 : :
6699 : 38 : resetStringInfo(&buf);
6700 : 38 : appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n",
6701 : : quote_identifier(tablename));
6702 : :
6703 : : /* Scan all rows for this table */
6704 : : do
6705 : : {
6706 : : char *attname;
6707 : : char *typename;
6708 : : char *attnotnull;
6709 : : char *attgenerated;
6710 : : char *attdefault;
6711 : : char *collname;
6712 : : char *collnamespace;
6713 : :
6714 : : /* If table has no columns, we'll see nulls here */
6715 [ + + ]: 75 : if (PQgetisnull(res, i, 1))
6716 : 5 : continue;
6717 : :
6718 : 70 : attname = PQgetvalue(res, i, 1);
6719 : 70 : typename = PQgetvalue(res, i, 2);
6720 : 70 : attnotnull = PQgetvalue(res, i, 3);
6721 [ + + ]: 70 : attdefault = PQgetisnull(res, i, 4) ? NULL :
6722 : 15 : PQgetvalue(res, i, 4);
6723 [ + - ]: 70 : attgenerated = PQgetisnull(res, i, 5) ? NULL :
6724 : 70 : PQgetvalue(res, i, 5);
6725 [ + + ]: 70 : collname = PQgetisnull(res, i, 6) ? NULL :
6726 : 19 : PQgetvalue(res, i, 6);
6727 [ + + ]: 70 : collnamespace = PQgetisnull(res, i, 7) ? NULL :
6728 : 19 : PQgetvalue(res, i, 7);
6729 : :
6730 [ + + ]: 70 : if (first_item)
6731 : 33 : first_item = false;
6732 : : else
6733 : 37 : appendStringInfoString(&buf, ",\n");
6734 : :
6735 : : /* Print column name and type */
6736 : 70 : appendStringInfo(&buf, " %s %s",
6737 : : quote_identifier(attname),
6738 : : typename);
6739 : :
6740 : : /*
6741 : : * Add column_name option so that renaming the foreign table's
6742 : : * column doesn't break the association to the underlying column.
6743 : : */
6744 : 70 : appendStringInfoString(&buf, " OPTIONS (column_name ");
6745 : 70 : deparseStringLiteral(&buf, attname);
6746 : 70 : appendStringInfoChar(&buf, ')');
6747 : :
6748 : : /* Add COLLATE if needed */
6749 [ + + + + : 70 : if (import_collate && collname != NULL && collnamespace != NULL)
+ - ]
6750 : 19 : appendStringInfo(&buf, " COLLATE %s.%s",
6751 : : quote_identifier(collnamespace),
6752 : : quote_identifier(collname));
6753 : :
6754 : : /* Add DEFAULT if needed */
6755 [ + + + + : 70 : if (import_default && attdefault != NULL &&
+ - ]
6756 [ + + ]: 3 : (!attgenerated || !attgenerated[0]))
6757 : 2 : appendStringInfo(&buf, " DEFAULT %s", attdefault);
6758 : :
6759 : : /* Add GENERATED if needed */
6760 [ + + + - ]: 70 : if (import_generated && attgenerated != NULL &&
6761 [ + + ]: 57 : attgenerated[0] == ATTRIBUTE_GENERATED_STORED)
6762 : : {
6763 [ - + ]: 4 : Assert(attdefault != NULL);
6764 : 4 : appendStringInfo(&buf,
6765 : : " GENERATED ALWAYS AS (%s) STORED",
6766 : : attdefault);
6767 : : }
6768 : :
6769 : : /* Add NOT NULL if needed */
6770 [ + + + + ]: 70 : if (import_not_null && attnotnull[0] == 't')
6771 : 4 : appendStringInfoString(&buf, " NOT NULL");
6772 : : }
6773 [ + + ]: 75 : while (++i < numrows &&
6774 [ + + ]: 66 : strcmp(PQgetvalue(res, i, 0), tablename) == 0);
6775 : :
6776 : : /*
6777 : : * Add server name and table-level options. We specify remote schema
6778 : : * and table name as options (the latter to ensure that renaming the
6779 : : * foreign table doesn't break the association).
6780 : : */
6781 : 38 : appendStringInfo(&buf, "\n) SERVER %s\nOPTIONS (",
6782 : 38 : quote_identifier(server->servername));
6783 : :
6784 : 38 : appendStringInfoString(&buf, "schema_name ");
6785 : 38 : deparseStringLiteral(&buf, stmt->remote_schema);
6786 : 38 : appendStringInfoString(&buf, ", table_name ");
6787 : 38 : deparseStringLiteral(&buf, tablename);
6788 : :
6789 : 38 : appendStringInfoString(&buf, ");");
6790 : :
6791 : 38 : commands = lappend(commands, pstrdup(buf.data));
6792 : : }
6793 : 9 : PQclear(res);
6794 : :
4477 6795 : 9 : ReleaseConnection(conn);
6796 : :
6797 : 9 : return commands;
6798 : : }
6799 : :
6800 : : /*
6801 : : * Check if reltarget is safe enough to push down semi-join. Reltarget is not
6802 : : * safe, if it contains references to inner rel relids, which do not belong to
6803 : : * outer rel.
6804 : : */
6805 : : static bool
1042 akorotkov@postgresql 6806 : 65 : semijoin_target_ok(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel)
6807 : : {
6808 : : List *vars;
6809 : : ListCell *lc;
6810 : 65 : bool ok = true;
6811 : :
6812 [ - + ]: 65 : Assert(joinrel->reltarget);
6813 : :
6814 : 65 : vars = pull_var_clause((Node *) joinrel->reltarget->exprs, PVC_INCLUDE_PLACEHOLDERS);
6815 : :
6816 [ + - + + : 445 : foreach(lc, vars)
+ + ]
6817 : : {
6818 : 395 : Var *var = (Var *) lfirst(lc);
6819 : :
6820 [ - + ]: 395 : if (!IsA(var, Var))
1042 akorotkov@postgresql 6821 :UBC 0 : continue;
6822 : :
566 akorotkov@postgresql 6823 [ + + ]:CBC 395 : if (bms_is_member(var->varno, innerrel->relids))
6824 : : {
6825 : : /*
6826 : : * The planner can create semi-join, which refers to inner rel
6827 : : * vars in its target list. However, we deparse semi-join as an
6828 : : * exists() subquery, so can't handle references to inner rel in
6829 : : * the target list.
6830 : : */
6831 [ - + ]: 15 : Assert(!bms_is_member(var->varno, outerrel->relids));
1042 6832 : 15 : ok = false;
6833 : 15 : break;
6834 : : }
6835 : : }
6836 : 65 : return ok;
6837 : : }
6838 : :
6839 : : /*
6840 : : * get_base_relids
6841 : : * Return the set of base relids referenced by a foreign scan rel.
6842 : : *
6843 : : * For an upper rel we use the all-query relids minus the outer joins;
6844 : : * otherwise the rel's own relids minus the outer joins. The result matches
6845 : : * the relids that create_foreignscan_plan() ultimately uses for
6846 : : * ForeignScan.fs_base_relids, so it is suitable for any tagging we want to
6847 : : * store via plan-private state.
6848 : : */
6849 : : static Relids
54 akorotkov@postgresql 6850 :GNC 2404 : get_base_relids(PlannerInfo *root, RelOptInfo *rel)
6851 : : {
6852 : : Relids relids;
6853 : :
6854 [ + + ]: 2404 : if (rel->reloptkind == RELOPT_UPPER_REL)
6855 : 248 : relids = root->all_query_rels;
6856 : : else
6857 : 2156 : relids = rel->relids;
6858 : :
6859 : 2404 : return bms_difference(relids, root->outer_join_rels);
6860 : : }
6861 : :
6862 : : /*
6863 : : * get_min_base_rti
6864 : : * Lowest base RT index in the foreign scan rel.
6865 : : *
6866 : : * After setrefs.c flattens the rtable, the scan-local indexes saved in
6867 : : * plan-private data can be translated to estate indexes by adding
6868 : : * (ForeignScan.fs_base_relids min - this value). Captured at plan time
6869 : : * because create_foreignscan_plan() computes the same value internally.
6870 : : */
6871 : : static int
6872 : 1202 : get_min_base_rti(PlannerInfo *root, RelOptInfo *rel)
6873 : : {
6874 : 1202 : Relids relids = get_base_relids(root, rel);
6875 : :
6876 : 1202 : return bms_next_member(relids, -1);
6877 : : }
6878 : :
6879 : : /*
6880 : : * get_functions_data
6881 : : * Build the per-RTE function metadata list saved as
6882 : : * FdwScanPrivateFunctions.
6883 : : *
6884 : : * The result list is indexed by base RT index relative to the lowest base
6885 : : * RT index of the scan. Each element is either NULL (for non-RTE_FUNCTION
6886 : : * base rels in this scan) or a List of List of two Integer nodes:
6887 : : * (funcrettype, funccollation) -- one inner list per RangeTblFunction.
6888 : : *
6889 : : * Only RTE_FUNCTION relids actually appearing in the foreign scan's
6890 : : * fs_base_relids contribute; others are placeholders so that the consumer
6891 : : * can index into the result by RTI offset.
6892 : : */
6893 : : static List *
6894 : 1202 : get_functions_data(PlannerInfo *root, RelOptInfo *rel)
6895 : : {
6896 : 1202 : List *rtfuncdata = NIL;
6897 : 1202 : Relids fscan_relids = get_base_relids(root, rel);
6898 : : int i;
6899 : :
6900 [ + + ]: 5957 : for (i = 0; i < root->simple_rel_array_size; i++)
6901 : : {
6902 : 4755 : RangeTblEntry *rte = root->simple_rte_array[i];
6903 : 4755 : List *funcdata = NIL;
6904 : : ListCell *lc;
6905 : :
6906 [ + + + - ]: 4755 : if (rte == NULL || i == 0 ||
6907 [ + + ]: 3553 : !bms_is_member(i, fscan_relids) ||
6908 [ + + ]: 1498 : rte->rtekind != RTE_FUNCTION)
6909 : : {
6910 : 4727 : rtfuncdata = lappend(rtfuncdata, NULL);
6911 : 4727 : continue;
6912 : : }
6913 : :
6914 [ + - + + : 66 : foreach(lc, rte->functions)
+ + ]
6915 : : {
6916 : 38 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
6917 : : Oid funcrettype;
6918 : : Oid funccollation;
6919 : : TupleDesc tupdesc;
6920 : : Oid funcid;
6921 : :
6922 : 38 : get_expr_result_type(rtfunc->funcexpr, &funcrettype, &tupdesc);
6923 : :
6924 : : /*
6925 : : * function_rte_pushdown_ok() already rejected any function that
6926 : : * doesn't return a well-defined scalar type, so this is a mere
6927 : : * cross-check.
6928 : : */
6929 [ + - - + ]: 38 : Assert(OidIsValid(funcrettype) && funcrettype != RECORDOID);
6930 : :
6931 : 38 : funccollation = exprCollation(rtfunc->funcexpr);
6932 : :
6933 : 38 : funcid = ((FuncExpr *) rtfunc->funcexpr)->funcid;
6934 : :
6935 : 38 : funcdata = lappend(funcdata,
6936 : 38 : list_make3(makeInteger(funcid),
6937 : : makeInteger(funcrettype),
6938 : : makeInteger(funccollation)));
6939 : : }
6940 : :
6941 : 28 : rtfuncdata = lappend(rtfuncdata, funcdata);
6942 : : }
6943 : :
6944 : 1202 : return rtfuncdata;
6945 : : }
6946 : :
6947 : : /*
6948 : : * init_func_stub_fpinfo
6949 : : * Build a stub PgFdwRelationInfo for a FUNCTION RTE that is being
6950 : : * absorbed into a foreign join.
6951 : : *
6952 : : * A function RTE has no fdw_private of its own, but the joinrel-level cost
6953 : : * estimator, deparser and merge_fdw_options() all read server-level options
6954 : : * from the "outer" fpinfo unconditionally. So the stub must carry the same
6955 : : * server, shippable_extensions, and cost-related options as the real foreign
6956 : : * side of the join; otherwise the pushdown path is judged against zeroed
6957 : : * fdw_startup_cost/fdw_tuple_cost/etc. and looks artificially cheap.
6958 : : *
6959 : : * The stub is meaningful only for the specific (foreign, function) pairing
6960 : : * that produced it; it must live on the joinrel's PgFdwRelationInfo, never
6961 : : * on the function rel's fdw_private, since the same function RTE may pair
6962 : : * with different foreign servers in sibling joinrels.
6963 : : */
6964 : : static PgFdwRelationInfo *
6965 : 31 : init_func_stub_fpinfo(const PgFdwRelationInfo *fpinfo_foreign,
6966 : : RelOptInfo *funcrel)
6967 : : {
6968 : 31 : PgFdwRelationInfo *stub = palloc0_object(PgFdwRelationInfo);
6969 : :
6970 : 31 : stub->pushdown_safe = true;
6971 : :
6972 : : /* Connection information and options, inherited from the foreign side */
6973 : 31 : stub->server = fpinfo_foreign->server;
32 fujii@postgresql.org 6974 : 31 : stub->user = fpinfo_foreign->user;
54 akorotkov@postgresql 6975 : 31 : stub->shippable_extensions = fpinfo_foreign->shippable_extensions;
6976 : 31 : stub->fdw_startup_cost = fpinfo_foreign->fdw_startup_cost;
6977 : 31 : stub->fdw_tuple_cost = fpinfo_foreign->fdw_tuple_cost;
6978 : 31 : stub->use_remote_estimate = fpinfo_foreign->use_remote_estimate;
6979 : 31 : stub->fetch_size = fpinfo_foreign->fetch_size;
6980 : 31 : stub->async_capable = fpinfo_foreign->async_capable;
6981 : :
6982 : : /* Function-side identity and estimates from the local planner. */
6983 : 31 : stub->relation_name = psprintf("%u", funcrel->relid);
6984 : 31 : stub->rows = funcrel->rows;
6985 : 31 : stub->width = funcrel->reltarget->width;
6986 : 31 : stub->retrieved_rows = funcrel->rows;
6987 : :
6988 : : /*
6989 : : * The function is executed on the remote server, so these must carry its
6990 : : * cost the same way a foreign rel's do: what producing the rows costs the
6991 : : * far side, before connection setup and data transfer are added. The
6992 : : * local path for the same function is our best estimate of that.
6993 : : */
6994 : 31 : stub->rel_startup_cost = funcrel->cheapest_total_path->startup_cost;
6995 : 31 : stub->rel_total_cost = funcrel->cheapest_total_path->total_cost;
6996 : :
6997 : 31 : return stub;
6998 : : }
6999 : :
7000 : : /*
7001 : : * Check if a relation is a FUNCTION RTE that can be absorbed into a remote
7002 : : * join. Every function in the RTE must
7003 : : *
7004 : : * - return a well-defined scalar type -- we don't ship records/composite
7005 : : * since the remote server cannot reconstruct a column definition list
7006 : : * and our deparser does not emit one;
7007 : : * - have a shippable expression with no mutable subnodes -- is_foreign_expr()
7008 : : * rejects volatile/stable functions through contain_mutable_functions(),
7009 : : * so the IMMUTABLE-only restriction is implicit;
7010 : : * - not contain SubPlans -- we'd otherwise need to ship sub-results to
7011 : : * the remote, which we do not implement.
7012 : : *
7013 : : * WITH ORDINALITY is not supported yet.
7014 : : */
7015 : : static bool
7016 : 37 : function_rte_pushdown_ok(PlannerInfo *root, RelOptInfo *rel,
7017 : : RelOptInfo *fdwrel)
7018 : : {
7019 : : RangeTblEntry *rte;
7020 : : ListCell *lc;
7021 : :
7022 [ - + ]: 37 : if (rel->rtekind != RTE_FUNCTION)
54 akorotkov@postgresql 7023 :UNC 0 : return false;
54 akorotkov@postgresql 7024 [ + - ]:GNC 37 : rte = planner_rt_fetch(rel->relid, root);
7025 : :
7026 : : /*
7027 : : * build_simple_rel() copies rtekind straight from the RTE, so for a base
7028 : : * rel rel->rtekind always matches the RTE's; the check above is therefore
7029 : : * sufficient.
7030 : : */
7031 [ - + ]: 37 : Assert(rte->rtekind == RTE_FUNCTION);
7032 : :
7033 [ + + ]: 37 : if (rte->funcordinality)
7034 : 1 : return false;
7035 : :
7036 : : /*
7037 : : * Reject up-front any function RTE that lateral-references another
7038 : : * relation: foreign-join push-down would need to parameterise the remote
7039 : : * query per outer row, which we don't support, and even considering the
7040 : : * path is expensive on the planner side. The surrounding lateral_relids
7041 : : * check in postgresGetForeignJoinPaths() would normally bail out for the
7042 : : * joinrel, but doing the check here avoids walking the function
7043 : : * expression entirely.
7044 : : *
7045 : : * Note this rejects only lateral references to another relation at the
7046 : : * same query level. A function argument referencing an outer query level
7047 : : * (e.g. f(outer.col) inside a subquery) is a different case: it becomes a
7048 : : * PARAM_EXEC Param, the function rel's lateral_relids is empty, and
7049 : : * foreign_expr_walker() intentionally treats PARAM_EXEC as shippable. The
7050 : : * remote query then carries a parameter placeholder, and postgres_fdw's
7051 : : * ordinary parameter machinery re-sends it on each rescan -- i.e. it
7052 : : * works as a normal parameterized foreign scan, which is fine.
7053 : : */
7054 [ + + ]: 36 : if (!bms_is_empty(rel->lateral_relids))
7055 : 1 : return false;
7056 : :
7057 [ - + ]: 35 : Assert(list_length(rte->functions) >= 1);
7058 : :
7059 [ + - + + : 76 : foreach(lc, rte->functions)
+ + ]
7060 : : {
7061 : 45 : RangeTblFunction *rtfunc = (RangeTblFunction *) lfirst(lc);
7062 : : TypeFuncClass functypclass;
7063 : : Oid funcrettype;
7064 : : TupleDesc tupdesc;
7065 : :
7066 : : /* Refuse to deal with strange funcexprs */
7067 [ - + ]: 45 : if (!IsA(rtfunc->funcexpr, FuncExpr))
7068 : 4 : return false;
7069 : :
7070 [ - + ]: 45 : if (!OidIsValid(((FuncExpr *) rtfunc->funcexpr)->funcid))
54 akorotkov@postgresql 7071 :UNC 0 : return false;
7072 : :
54 akorotkov@postgresql 7073 :GNC 45 : functypclass = get_expr_result_type(rtfunc->funcexpr,
7074 : : &funcrettype, &tupdesc);
7075 [ + + ]: 45 : if (functypclass != TYPEFUNC_SCALAR)
7076 : 1 : return false;
7077 [ + - ]: 44 : if (!OidIsValid(funcrettype) ||
7078 [ + - ]: 44 : funcrettype == RECORDOID ||
7079 [ - + ]: 44 : funcrettype == VOIDOID)
54 akorotkov@postgresql 7080 :UNC 0 : return false;
7081 : :
54 akorotkov@postgresql 7082 [ - + ]:GNC 44 : if (contain_subplans(rtfunc->funcexpr))
54 akorotkov@postgresql 7083 :UNC 0 : return false;
54 akorotkov@postgresql 7084 [ + + ]:GNC 44 : if (!is_foreign_expr(root, fdwrel, fdwrel->fdw_private, (Expr *) rtfunc->funcexpr))
7085 : 3 : return false;
7086 : : }
7087 : :
7088 : 31 : return true;
7089 : : }
7090 : :
7091 : : /*
7092 : : * Assess whether the join between inner and outer relations can be pushed down
7093 : : * to the foreign server. As a side effect, save information we obtain in this
7094 : : * function to PgFdwRelationInfo passed in.
7095 : : */
7096 : : static bool
3898 rhaas@postgresql.org 7097 :CBC 442 : foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
7098 : : RelOptInfo *outerrel, RelOptInfo *innerrel,
7099 : : JoinPathExtraData *extra)
7100 : : {
7101 : : PgFdwRelationInfo *fpinfo;
7102 : : PgFdwRelationInfo *fpinfo_o;
7103 : : PgFdwRelationInfo *fpinfo_i;
7104 : : ListCell *lc;
7105 : : List *joinclauses;
7106 : :
7107 : : /*
7108 : : * We support pushing down INNER, LEFT, RIGHT, FULL OUTER and SEMI joins.
7109 : : * Constructing queries representing ANTI joins is hard, hence not
7110 : : * considered right now.
7111 : : */
7112 [ + + + + : 442 : if (jointype != JOIN_INNER && jointype != JOIN_LEFT &&
+ - ]
1042 akorotkov@postgresql 7113 [ + + + + ]: 130 : jointype != JOIN_RIGHT && jointype != JOIN_FULL &&
7114 : : jointype != JOIN_SEMI)
7115 : 19 : return false;
7116 : :
7117 : : /*
7118 : : * We can't push down semi-join if its reltarget is not safe
7119 : : */
7120 [ + + + + ]: 423 : if ((jointype == JOIN_SEMI) && !semijoin_target_ok(root, joinrel, outerrel, innerrel))
3898 rhaas@postgresql.org 7121 : 15 : return false;
7122 : :
7123 : : /*
7124 : : * Detect mixed (foreign x function-RTE) cases. Only INNER joins are
7125 : : * supported initially. We dispatch on rtekind here so that the same
7126 : : * function RTE can be absorbed into joins on multiple foreign servers
7127 : : * (each call gets its own stub fpinfo and rechecks shippability for the
7128 : : * specific server).
7129 : : *
7130 : : * A function rel has no fdw_private of its own, so when one side is a
7131 : : * function RTE we replace its NULL fpinfo with a stub, and the rest of
7132 : : * this function and the cost estimator can then treat both sides
7133 : : * uniformly. We hand the stub to the joinrel's deparser via the same
7134 : : * path the foreign side uses, but we never permanently attach it to the
7135 : : * function rel's fdw_private (different joinrels may pair the same
7136 : : * function RTE with different foreign servers).
7137 : : */
7138 : 408 : fpinfo = (PgFdwRelationInfo *) joinrel->fdw_private;
40 akorotkov@postgresql 7139 : 408 : fpinfo_o = (PgFdwRelationInfo *) outerrel->fdw_private;
7140 : 408 : fpinfo_i = (PgFdwRelationInfo *) innerrel->fdw_private;
7141 : :
54 akorotkov@postgresql 7142 [ + + + + :GNC 408 : if (jointype == JOIN_INNER && innerrel->rtekind == RTE_FUNCTION &&
+ - ]
40 7143 [ + - + + ]: 58 : fpinfo_o && fpinfo_o->pushdown_safe &&
54 7144 : 29 : function_rte_pushdown_ok(root, innerrel, outerrel))
7145 : : {
7146 : 23 : fpinfo_i = init_func_stub_fpinfo(fpinfo_o, innerrel);
7147 : :
7148 : : /*
7149 : : * Classify the function rel's own baserestrictinfo now, so that the
7150 : : * local_conds check below can bail out if any of it is unshippable.
7151 : : * We classify against the stub, not the joinrel's fpinfo, because the
7152 : : * latter's server/shippable_extensions aren't populated until
7153 : : * merge_fdw_options() runs further down.
7154 : : */
7155 : 23 : classifyConditions(root, innerrel, fpinfo_i, innerrel->baserestrictinfo,
7156 : : &fpinfo_i->remote_conds, &fpinfo_i->local_conds);
7157 : 23 : fpinfo->inner_func_fpinfo = fpinfo_i;
7158 : : }
40 7159 [ + + + + : 385 : else if (jointype == JOIN_INNER && outerrel->rtekind == RTE_FUNCTION &&
+ - ]
7160 [ + - + - ]: 16 : fpinfo_i && fpinfo_i->pushdown_safe &&
7161 : 8 : function_rte_pushdown_ok(root, outerrel, innerrel))
7162 : : {
54 7163 : 8 : fpinfo_o = init_func_stub_fpinfo(fpinfo_i, outerrel);
7164 : :
7165 : : /* See the comment in the branch above. */
7166 : 8 : classifyConditions(root, outerrel, fpinfo_o, outerrel->baserestrictinfo,
7167 : : &fpinfo_o->remote_conds, &fpinfo_o->local_conds);
7168 : 8 : fpinfo->outer_func_fpinfo = fpinfo_o;
7169 : : }
40 7170 [ + - + + : 377 : else if (!fpinfo_o || !fpinfo_o->pushdown_safe ||
+ + ]
7171 [ - + ]: 364 : !fpinfo_i || !fpinfo_i->pushdown_safe)
40 akorotkov@postgresql 7172 :CBC 13 : return false;
7173 : :
7174 : : /*
7175 : : * If joining relations have local conditions, those conditions are
7176 : : * required to be applied before joining the relations. Hence the join can
7177 : : * not be pushed down.
7178 : : */
3898 rhaas@postgresql.org 7179 [ + + + + ]: 395 : if (fpinfo_o->local_conds || fpinfo_i->local_conds)
7180 : 11 : return false;
7181 : :
7182 : : /*
7183 : : * Merge FDW options. We might be tempted to do this after we have deemed
7184 : : * the foreign join to be OK. But we must do this beforehand so that we
7185 : : * know which quals can be evaluated on the foreign server, which might
7186 : : * depend on shippable_extensions.
7187 : : */
3458 peter_e@gmx.net 7188 : 384 : fpinfo->server = fpinfo_o->server;
7189 : 384 : merge_fdw_options(fpinfo, fpinfo_o, fpinfo_i);
7190 : :
7191 : : /*
7192 : : * Separate restrict list into join quals and pushed-down (other) quals.
7193 : : *
7194 : : * Join quals belonging to an outer join must all be shippable, else we
7195 : : * cannot execute the join remotely. Add such quals to 'joinclauses'.
7196 : : *
7197 : : * Add other quals to fpinfo->remote_conds if they are shippable, else to
7198 : : * fpinfo->local_conds. In an inner join it's okay to execute conditions
7199 : : * either locally or remotely; the same is true for pushed-down conditions
7200 : : * at an outer join.
7201 : : *
7202 : : * Note we might return failure after having already scribbled on
7203 : : * fpinfo->remote_conds and fpinfo->local_conds. That's okay because we
7204 : : * won't consult those lists again if we deem the join unshippable.
7205 : : */
3471 tgl@sss.pgh.pa.us 7206 : 384 : joinclauses = NIL;
7207 [ + + + + : 763 : foreach(lc, extra->restrictlist)
+ + ]
7208 : : {
7209 : 382 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
54 akorotkov@postgresql 7210 :GNC 382 : bool is_remote_clause = is_foreign_expr(root, joinrel, fpinfo,
7211 : : rinfo->clause);
7212 : :
3097 tgl@sss.pgh.pa.us 7213 [ + + ]:CBC 382 : if (IS_OUTER_JOIN(jointype) &&
7214 [ + + + - ]: 133 : !RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
7215 : : {
3471 7216 [ + + ]: 117 : if (!is_remote_clause)
7217 : 3 : return false;
7218 : 114 : joinclauses = lappend(joinclauses, rinfo);
7219 : : }
7220 : : else
7221 : : {
7222 [ + + ]: 265 : if (is_remote_clause)
7223 : 253 : fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
7224 : : else
7225 : 12 : fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
7226 : : }
7227 : : }
7228 : :
7229 : : /*
7230 : : * deparseExplicitTargetList() isn't smart enough to handle anything other
7231 : : * than a Var. In particular, if there's some PlaceHolderVar that would
7232 : : * need to be evaluated within this join tree (because there's an upper
7233 : : * reference to a quantity that may go to NULL as a result of an outer
7234 : : * join), then we can't try to push the join down because we'll fail when
7235 : : * we get to deparseExplicitTargetList(). However, a PlaceHolderVar that
7236 : : * needs to be evaluated *at the top* of this join tree is OK, because we
7237 : : * can do that locally after fetching the results from the remote side.
7238 : : */
3772 rhaas@postgresql.org 7239 [ + + + + : 384 : foreach(lc, root->placeholder_list)
+ + ]
7240 : : {
7241 : 11 : PlaceHolderInfo *phinfo = lfirst(lc);
7242 : : Relids relids;
7243 : :
7244 : : /* PlaceHolderInfo refers to parent relids, not child relids. */
3154 7245 [ + + - + ]: 11 : relids = IS_OTHER_REL(joinrel) ?
7246 [ + - ]: 22 : joinrel->top_parent_relids : joinrel->relids;
7247 : :
3772 7248 [ + - + + ]: 22 : if (bms_is_subset(phinfo->ph_eval_at, relids) &&
7249 : 11 : bms_nonempty_difference(relids, phinfo->ph_eval_at))
7250 : 8 : return false;
7251 : : }
7252 : :
7253 : : /* Save the join clauses, for later use. */
3898 7254 : 373 : fpinfo->joinclauses = joinclauses;
7255 : :
7256 : 373 : fpinfo->outerrel = outerrel;
7257 : 373 : fpinfo->innerrel = innerrel;
7258 : 373 : fpinfo->jointype = jointype;
7259 : :
7260 : : /*
7261 : : * By default, both the input relations are not required to be deparsed as
7262 : : * subqueries, but there might be some relations covered by the input
7263 : : * relations that are required to be deparsed as subqueries, so save the
7264 : : * relids of those relations for later use by the deparser.
7265 : : */
3497 7266 : 373 : fpinfo->make_outerrel_subquery = false;
7267 : 373 : fpinfo->make_innerrel_subquery = false;
7268 [ - + ]: 373 : Assert(bms_is_subset(fpinfo_o->lower_subquery_rels, outerrel->relids));
7269 [ - + ]: 373 : Assert(bms_is_subset(fpinfo_i->lower_subquery_rels, innerrel->relids));
7270 : 746 : fpinfo->lower_subquery_rels = bms_union(fpinfo_o->lower_subquery_rels,
7271 : 373 : fpinfo_i->lower_subquery_rels);
1042 akorotkov@postgresql 7272 : 746 : fpinfo->hidden_subquery_rels = bms_union(fpinfo_o->hidden_subquery_rels,
7273 : 373 : fpinfo_i->hidden_subquery_rels);
7274 : :
7275 : : /*
7276 : : * Pull the other remote conditions from the joining relations into join
7277 : : * clauses or other remote clauses (remote_conds) of this relation
7278 : : * wherever possible. This avoids building subqueries at every join step.
7279 : : *
7280 : : * For an inner join, clauses from both the relations are added to the
7281 : : * other remote clauses. For LEFT and RIGHT OUTER join, the clauses from
7282 : : * the outer side are added to remote_conds since those can be evaluated
7283 : : * after the join is evaluated. The clauses from inner side are added to
7284 : : * the joinclauses, since they need to be evaluated while constructing the
7285 : : * join.
7286 : : *
7287 : : * For SEMI-JOIN clauses from inner relation can not be added to
7288 : : * remote_conds, but should be treated as join clauses (as they are
7289 : : * deparsed to EXISTS subquery, where inner relation can be referred). A
7290 : : * list of relation ids, which can't be referred to from higher levels, is
7291 : : * preserved as a hidden_subquery_rels list.
7292 : : *
7293 : : * For a FULL OUTER JOIN, the other clauses from either relation can not
7294 : : * be added to the joinclauses or remote_conds, since each relation acts
7295 : : * as an outer relation for the other.
7296 : : *
7297 : : * The joining sides can not have local conditions, thus no need to test
7298 : : * shippability of the clauses being pulled up.
7299 : : */
3898 rhaas@postgresql.org 7300 [ + + - + : 373 : switch (jointype)
+ - ]
7301 : : {
7302 : 223 : case JOIN_INNER:
7303 : 446 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
2618 tgl@sss.pgh.pa.us 7304 : 223 : fpinfo_i->remote_conds);
3898 rhaas@postgresql.org 7305 : 446 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
2618 tgl@sss.pgh.pa.us 7306 : 223 : fpinfo_o->remote_conds);
3898 rhaas@postgresql.org 7307 : 223 : break;
7308 : :
7309 : 64 : case JOIN_LEFT:
7310 : :
7311 : : /*
7312 : : * When semi-join is involved in the inner or outer part of the
7313 : : * left join, it's deparsed as a subquery, and we can't refer to
7314 : : * its vars on the upper level.
7315 : : */
566 akorotkov@postgresql 7316 [ + + ]: 64 : if (bms_is_empty(fpinfo_i->hidden_subquery_rels))
7317 : 60 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
7318 : 60 : fpinfo_i->remote_conds);
7319 [ + - ]: 64 : if (bms_is_empty(fpinfo_o->hidden_subquery_rels))
7320 : 64 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
7321 : 64 : fpinfo_o->remote_conds);
3898 rhaas@postgresql.org 7322 : 64 : break;
7323 : :
3898 rhaas@postgresql.org 7324 :UBC 0 : case JOIN_RIGHT:
7325 : :
7326 : : /*
7327 : : * When semi-join is involved in the inner or outer part of the
7328 : : * right join, it's deparsed as a subquery, and we can't refer to
7329 : : * its vars on the upper level.
7330 : : */
566 akorotkov@postgresql 7331 [ # # ]: 0 : if (bms_is_empty(fpinfo_o->hidden_subquery_rels))
7332 : 0 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
7333 : 0 : fpinfo_o->remote_conds);
7334 [ # # ]: 0 : if (bms_is_empty(fpinfo_i->hidden_subquery_rels))
7335 : 0 : fpinfo->remote_conds = list_concat(fpinfo->remote_conds,
7336 : 0 : fpinfo_i->remote_conds);
3898 rhaas@postgresql.org 7337 : 0 : break;
7338 : :
1042 akorotkov@postgresql 7339 :CBC 44 : case JOIN_SEMI:
7340 : 88 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
7341 : 44 : fpinfo_i->remote_conds);
7342 : 88 : fpinfo->joinclauses = list_concat(fpinfo->joinclauses,
7343 : 44 : fpinfo->remote_conds);
7344 : 44 : fpinfo->remote_conds = list_copy(fpinfo_o->remote_conds);
7345 : 88 : fpinfo->hidden_subquery_rels = bms_union(fpinfo->hidden_subquery_rels,
7346 : 44 : innerrel->relids);
7347 : 44 : break;
7348 : :
3898 rhaas@postgresql.org 7349 : 42 : case JOIN_FULL:
7350 : :
7351 : : /*
7352 : : * In this case, if any of the input relations has conditions, we
7353 : : * need to deparse that relation as a subquery so that the
7354 : : * conditions can be evaluated before the join. Remember it in
7355 : : * the fpinfo of this relation so that the deparser can take
7356 : : * appropriate action. Also, save the relids of base relations
7357 : : * covered by that relation for later use by the deparser.
7358 : : */
3497 7359 [ + + ]: 42 : if (fpinfo_o->remote_conds)
7360 : : {
7361 : 14 : fpinfo->make_outerrel_subquery = true;
7362 : 14 : fpinfo->lower_subquery_rels =
7363 : 14 : bms_add_members(fpinfo->lower_subquery_rels,
7364 : 14 : outerrel->relids);
7365 : : }
7366 [ + + ]: 42 : if (fpinfo_i->remote_conds)
7367 : : {
7368 : 14 : fpinfo->make_innerrel_subquery = true;
7369 : 14 : fpinfo->lower_subquery_rels =
7370 : 14 : bms_add_members(fpinfo->lower_subquery_rels,
7371 : 14 : innerrel->relids);
7372 : : }
3898 7373 : 42 : break;
7374 : :
3898 rhaas@postgresql.org 7375 :UBC 0 : default:
7376 : : /* Should not happen, we have just checked this above */
7377 [ # # ]: 0 : elog(ERROR, "unsupported join type %d", jointype);
7378 : : }
7379 : :
7380 : : /*
7381 : : * For an inner join, all restrictions can be treated alike. Treating the
7382 : : * pushed down conditions as join conditions allows a top level full outer
7383 : : * join to be deparsed without requiring subqueries.
7384 : : */
3827 rhaas@postgresql.org 7385 [ + + ]:CBC 373 : if (jointype == JOIN_INNER)
7386 : : {
7387 [ - + ]: 223 : Assert(!fpinfo->joinclauses);
7388 : 223 : fpinfo->joinclauses = fpinfo->remote_conds;
7389 : 223 : fpinfo->remote_conds = NIL;
7390 : : }
1042 akorotkov@postgresql 7391 [ + + + - : 150 : else if (jointype == JOIN_LEFT || jointype == JOIN_RIGHT || jointype == JOIN_FULL)
+ + ]
7392 : : {
7393 : : /*
7394 : : * Conditions, generated from semi-joins, should be evaluated before
7395 : : * LEFT/RIGHT/FULL join.
7396 : : */
7397 [ - + ]: 106 : if (!bms_is_empty(fpinfo_o->hidden_subquery_rels))
7398 : : {
1042 akorotkov@postgresql 7399 :UBC 0 : fpinfo->make_outerrel_subquery = true;
7400 : 0 : fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, outerrel->relids);
7401 : : }
7402 : :
1042 akorotkov@postgresql 7403 [ + + ]:CBC 106 : if (!bms_is_empty(fpinfo_i->hidden_subquery_rels))
7404 : : {
7405 : 4 : fpinfo->make_innerrel_subquery = true;
7406 : 4 : fpinfo->lower_subquery_rels = bms_add_members(fpinfo->lower_subquery_rels, innerrel->relids);
7407 : : }
7408 : : }
7409 : :
7410 : : /* Mark that this join can be pushed down safely */
3827 rhaas@postgresql.org 7411 : 373 : fpinfo->pushdown_safe = true;
7412 : :
7413 : : /* Get user mapping */
3741 tgl@sss.pgh.pa.us 7414 [ + + ]: 373 : if (fpinfo->use_remote_estimate)
7415 : : {
7416 [ + + ]: 227 : if (fpinfo_o->use_remote_estimate)
7417 : 159 : fpinfo->user = fpinfo_o->user;
7418 : : else
7419 : 68 : fpinfo->user = fpinfo_i->user;
7420 : : }
7421 : : else
7422 : 146 : fpinfo->user = NULL;
7423 : :
7424 : : /*
7425 : : * Set # of retrieved rows and cached relation costs to some negative
7426 : : * value, so that we can detect when they are set to some sensible values,
7427 : : * during one (usually the first) of the calls to estimate_path_cost_size.
7428 : : */
2677 efujita@postgresql.o 7429 : 373 : fpinfo->retrieved_rows = -1;
3827 rhaas@postgresql.org 7430 : 373 : fpinfo->rel_startup_cost = -1;
7431 : 373 : fpinfo->rel_total_cost = -1;
7432 : :
7433 : : /*
7434 : : * Set the string describing this join relation to be used in EXPLAIN
7435 : : * output of corresponding ForeignScan. Note that the decoration we add
7436 : : * to the base relation names mustn't include any digits, or it'll confuse
7437 : : * postgresExplainForeignScan.
7438 : : */
2506 tgl@sss.pgh.pa.us 7439 : 373 : fpinfo->relation_name = psprintf("(%s) %s JOIN (%s)",
7440 : : fpinfo_o->relation_name,
7441 : : get_jointype_name(fpinfo->jointype),
7442 : : fpinfo_i->relation_name);
7443 : :
7444 : : /*
7445 : : * Set the relation index. This is defined as the position of this
7446 : : * joinrel in the join_rel_list list plus the length of the rtable list.
7447 : : * Note that since this joinrel is at the end of the join_rel_list list
7448 : : * when we are called, we can get the position by list_length.
7449 : : */
3400 7450 [ - + ]: 373 : Assert(fpinfo->relation_index == 0); /* shouldn't be set yet */
3497 rhaas@postgresql.org 7451 : 373 : fpinfo->relation_index =
7452 : 373 : list_length(root->parse->rtable) + list_length(root->join_rel_list);
7453 : :
3898 7454 : 373 : return true;
7455 : : }
7456 : :
7457 : : static void
3869 7458 : 1658 : add_paths_with_pathkeys_for_rel(PlannerInfo *root, RelOptInfo *rel,
7459 : : Path *epq_path, List *restrictlist)
7460 : : {
3400 tgl@sss.pgh.pa.us 7461 : 1658 : List *useful_pathkeys_list = NIL; /* List of all pathkeys */
7462 : : ListCell *lc;
7463 : :
3869 rhaas@postgresql.org 7464 : 1658 : useful_pathkeys_list = get_useful_pathkeys_for_relation(root, rel);
7465 : :
7466 : : /*
7467 : : * Before creating sorted paths, arrange for the passed-in EPQ path, if
7468 : : * any, to return columns needed by the parent ForeignScan node so that
7469 : : * they will propagate up through Sort nodes injected below, if necessary.
7470 : : */
1489 efujita@postgresql.o 7471 [ + + + + ]: 1658 : if (epq_path != NULL && useful_pathkeys_list != NIL)
7472 : : {
7473 : 34 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
7474 : 34 : PathTarget *target = copy_pathtarget(epq_path->pathtarget);
7475 : :
7476 : : /* Include columns required for evaluating PHVs in the tlist. */
7477 : 34 : add_new_columns_to_pathtarget(target,
7478 : 34 : pull_var_clause((Node *) target->exprs,
7479 : : PVC_RECURSE_PLACEHOLDERS));
7480 : :
7481 : : /* Include columns required for evaluating the local conditions. */
7482 [ + + + + : 37 : foreach(lc, fpinfo->local_conds)
+ + ]
7483 : : {
7484 : 3 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
7485 : :
7486 : 3 : add_new_columns_to_pathtarget(target,
7487 : 3 : pull_var_clause((Node *) rinfo->clause,
7488 : : PVC_RECURSE_PLACEHOLDERS));
7489 : : }
7490 : :
7491 : : /*
7492 : : * If we have added any new columns, adjust the tlist of the EPQ path.
7493 : : *
7494 : : * Note: the plan created using this path will only be used to execute
7495 : : * EPQ checks, where accuracy of the plan cost and width estimates
7496 : : * would not be important, so we do not do set_pathtarget_cost_width()
7497 : : * for the new pathtarget here. See also postgresGetForeignPlan().
7498 : : */
7499 [ + + ]: 34 : if (list_length(target->exprs) > list_length(epq_path->pathtarget->exprs))
7500 : : {
7501 : : /* The EPQ path is a join path, so it is projection-capable. */
7502 [ - + ]: 4 : Assert(is_projection_capable_path(epq_path));
7503 : :
7504 : : /*
7505 : : * Use create_projection_path() here, so as to avoid modifying it
7506 : : * in place.
7507 : : */
7508 : 4 : epq_path = (Path *) create_projection_path(root,
7509 : : rel,
7510 : : epq_path,
7511 : : target);
7512 : : }
7513 : : }
7514 : :
7515 : : /* Create one path for each set of pathkeys we found above. */
3869 rhaas@postgresql.org 7516 [ + + + + : 2402 : foreach(lc, useful_pathkeys_list)
+ + ]
7517 : : {
7518 : : double rows;
7519 : : int width;
7520 : : int disabled_nodes;
7521 : : Cost startup_cost;
7522 : : Cost total_cost;
7523 : 744 : List *useful_pathkeys = lfirst(lc);
7524 : : Path *sorted_epq_path;
7525 : :
2750 efujita@postgresql.o 7526 : 744 : estimate_path_cost_size(root, rel, NIL, useful_pathkeys, NULL,
7527 : : &rows, &width, &disabled_nodes,
7528 : : &startup_cost, &total_cost);
7529 : :
7530 : : /*
7531 : : * The EPQ path must be at least as well sorted as the path itself, in
7532 : : * case it gets used as input to a mergejoin.
7533 : : */
3190 rhaas@postgresql.org 7534 : 744 : sorted_epq_path = epq_path;
7535 [ + + ]: 744 : if (sorted_epq_path != NULL &&
7536 [ + + ]: 34 : !pathkeys_contained_in(useful_pathkeys,
7537 : : sorted_epq_path->pathkeys))
7538 : : sorted_epq_path = (Path *)
7539 : 26 : create_sort_path(root,
7540 : : rel,
7541 : : sorted_epq_path,
7542 : : useful_pathkeys,
7543 : : -1.0);
7544 : :
2804 tgl@sss.pgh.pa.us 7545 [ + + + + ]: 744 : if (IS_SIMPLE_REL(rel))
7546 : 456 : add_path(rel, (Path *)
7547 : 456 : create_foreignscan_path(root, rel,
7548 : : NULL,
7549 : : rows,
7550 : : disabled_nodes,
7551 : : startup_cost,
7552 : : total_cost,
7553 : : useful_pathkeys,
7554 : : rel->lateral_relids,
7555 : : sorted_epq_path,
7556 : : NIL, /* no fdw_restrictinfo
7557 : : * list */
7558 : : NIL));
7559 : : else
7560 : 288 : add_path(rel, (Path *)
7561 : 288 : create_foreign_join_path(root, rel,
7562 : : NULL,
7563 : : rows,
7564 : : disabled_nodes,
7565 : : startup_cost,
7566 : : total_cost,
7567 : : useful_pathkeys,
7568 : : rel->lateral_relids,
7569 : : sorted_epq_path,
7570 : : restrictlist,
7571 : : NIL));
7572 : : }
3869 rhaas@postgresql.org 7573 : 1658 : }
7574 : :
7575 : : /*
7576 : : * Parse options from foreign server and apply them to fpinfo.
7577 : : *
7578 : : * New options might also require tweaking merge_fdw_options().
7579 : : */
7580 : : static void
3458 peter_e@gmx.net 7581 : 1287 : apply_server_options(PgFdwRelationInfo *fpinfo)
7582 : : {
7583 : : ListCell *lc;
7584 : :
7585 [ + - + + : 5463 : foreach(lc, fpinfo->server->options)
+ + ]
7586 : : {
7587 : 4176 : DefElem *def = (DefElem *) lfirst(lc);
7588 : :
7589 [ + + ]: 4176 : if (strcmp(def->defname, "use_remote_estimate") == 0)
7590 : 140 : fpinfo->use_remote_estimate = defGetBoolean(def);
7591 [ + + ]: 4036 : else if (strcmp(def->defname, "fdw_startup_cost") == 0)
1923 fujii@postgresql.org 7592 : 6 : (void) parse_real(defGetString(def), &fpinfo->fdw_startup_cost, 0,
7593 : : NULL);
3458 peter_e@gmx.net 7594 [ + + ]: 4030 : else if (strcmp(def->defname, "fdw_tuple_cost") == 0)
1923 fujii@postgresql.org 7595 : 2 : (void) parse_real(defGetString(def), &fpinfo->fdw_tuple_cost, 0,
7596 : : NULL);
3458 peter_e@gmx.net 7597 [ + + ]: 4028 : else if (strcmp(def->defname, "extensions") == 0)
7598 : 983 : fpinfo->shippable_extensions =
7599 : 983 : ExtractExtensionList(defGetString(def), false);
7600 [ - + ]: 3045 : else if (strcmp(def->defname, "fetch_size") == 0)
1923 fujii@postgresql.org 7601 :UBC 0 : (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
2021 efujita@postgresql.o 7602 [ + + ]:CBC 3045 : else if (strcmp(def->defname, "async_capable") == 0)
7603 : 133 : fpinfo->async_capable = defGetBoolean(def);
7604 : : }
3458 peter_e@gmx.net 7605 : 1287 : }
7606 : :
7607 : : /*
7608 : : * Parse options from foreign table and apply them to fpinfo.
7609 : : *
7610 : : * New options might also require tweaking merge_fdw_options().
7611 : : */
7612 : : static void
7613 : 1287 : apply_table_options(PgFdwRelationInfo *fpinfo)
7614 : : {
7615 : : ListCell *lc;
7616 : :
7617 [ + - + + : 3684 : foreach(lc, fpinfo->table->options)
+ + ]
7618 : : {
7619 : 2397 : DefElem *def = (DefElem *) lfirst(lc);
7620 : :
7621 [ + + ]: 2397 : if (strcmp(def->defname, "use_remote_estimate") == 0)
7622 : 350 : fpinfo->use_remote_estimate = defGetBoolean(def);
7623 [ - + ]: 2047 : else if (strcmp(def->defname, "fetch_size") == 0)
1923 fujii@postgresql.org 7624 :UBC 0 : (void) parse_int(defGetString(def), &fpinfo->fetch_size, 0, NULL);
2021 efujita@postgresql.o 7625 [ - + ]:CBC 2047 : else if (strcmp(def->defname, "async_capable") == 0)
2021 efujita@postgresql.o 7626 :UBC 0 : fpinfo->async_capable = defGetBoolean(def);
7627 : : }
3458 peter_e@gmx.net 7628 :CBC 1287 : }
7629 : :
7630 : : /*
7631 : : * Merge FDW options from input relations into a new set of options for a join
7632 : : * or an upper rel.
7633 : : *
7634 : : * For a join relation, FDW-specific information about the inner and outer
7635 : : * relations is provided using fpinfo_i and fpinfo_o. For an upper relation,
7636 : : * fpinfo_o provides the information for the input relation; fpinfo_i is
7637 : : * expected to NULL.
7638 : : */
7639 : : static void
7640 : 855 : merge_fdw_options(PgFdwRelationInfo *fpinfo,
7641 : : const PgFdwRelationInfo *fpinfo_o,
7642 : : const PgFdwRelationInfo *fpinfo_i)
7643 : : {
7644 : : /* We must always have fpinfo_o. */
7645 [ - + ]: 855 : Assert(fpinfo_o);
7646 : :
7647 : : /* fpinfo_i may be NULL, but if present the servers must both match. */
7648 [ + + - + ]: 855 : Assert(!fpinfo_i ||
7649 : : fpinfo_i->server->serverid == fpinfo_o->server->serverid);
7650 : :
7651 : : /*
7652 : : * Copy the server specific FDW options. (For a join, both relations come
7653 : : * from the same server, so the server options should have the same value
7654 : : * for both relations.)
7655 : : */
7656 : 855 : fpinfo->fdw_startup_cost = fpinfo_o->fdw_startup_cost;
7657 : 855 : fpinfo->fdw_tuple_cost = fpinfo_o->fdw_tuple_cost;
7658 : 855 : fpinfo->shippable_extensions = fpinfo_o->shippable_extensions;
7659 : 855 : fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate;
7660 : 855 : fpinfo->fetch_size = fpinfo_o->fetch_size;
2021 efujita@postgresql.o 7661 : 855 : fpinfo->async_capable = fpinfo_o->async_capable;
7662 : :
7663 : : /* Merge the table level options from either side of the join. */
3458 peter_e@gmx.net 7664 [ + + ]: 855 : if (fpinfo_i)
7665 : : {
7666 : : /*
7667 : : * We'll prefer to use remote estimates for this join if any table
7668 : : * from either side of the join is using remote estimates. This is
7669 : : * most likely going to be preferred since they're already willing to
7670 : : * pay the price of a round trip to get the remote EXPLAIN. In any
7671 : : * case it's not entirely clear how we might otherwise handle this
7672 : : * best.
7673 : : */
7674 [ + + ]: 604 : fpinfo->use_remote_estimate = fpinfo_o->use_remote_estimate ||
3435 bruce@momjian.us 7675 [ + + ]: 220 : fpinfo_i->use_remote_estimate;
7676 : :
7677 : : /*
7678 : : * Set fetch size to maximum of the joining sides, since we are
7679 : : * expecting the rows returned by the join to be proportional to the
7680 : : * relation sizes.
7681 : : */
3458 peter_e@gmx.net 7682 : 384 : fpinfo->fetch_size = Max(fpinfo_o->fetch_size, fpinfo_i->fetch_size);
7683 : :
7684 : : /*
7685 : : * We'll prefer to consider this join async-capable if any table from
7686 : : * either side of the join is considered async-capable. This would be
7687 : : * reasonable because in that case the foreign server would have its
7688 : : * own resources to scan that table asynchronously, and the join could
7689 : : * also be computed asynchronously using the resources.
7690 : : */
2021 efujita@postgresql.o 7691 [ + + ]: 760 : fpinfo->async_capable = fpinfo_o->async_capable ||
7692 [ - + ]: 760 : fpinfo_i->async_capable;
7693 : : }
3458 peter_e@gmx.net 7694 : 855 : }
7695 : :
7696 : : /*
7697 : : * postgresGetForeignJoinPaths
7698 : : * Add possible ForeignPath to joinrel, if join is safe to push down.
7699 : : */
7700 : : static void
3898 rhaas@postgresql.org 7701 : 1454 : postgresGetForeignJoinPaths(PlannerInfo *root,
7702 : : RelOptInfo *joinrel,
7703 : : RelOptInfo *outerrel,
7704 : : RelOptInfo *innerrel,
7705 : : JoinType jointype,
7706 : : JoinPathExtraData *extra)
7707 : : {
7708 : : PgFdwRelationInfo *fpinfo;
7709 : : ForeignPath *joinpath;
7710 : : double rows;
7711 : : int width;
7712 : : int disabled_nodes;
7713 : : Cost startup_cost;
7714 : : Cost total_cost;
7715 : : Path *epq_path; /* Path to create plan to be executed when
7716 : : * EvalPlanQual gets triggered. */
7717 : :
7718 : : /*
7719 : : * Skip if this join combination has been considered already.
7720 : : */
7721 [ + + ]: 1454 : if (joinrel->fdw_private)
7722 : 1081 : return;
7723 : :
7724 : : /*
7725 : : * This code does not work for joins with lateral references, since those
7726 : : * must have parameterized paths, which we don't generate yet.
7727 : : */
2804 tgl@sss.pgh.pa.us 7728 [ + + ]: 446 : if (!bms_is_empty(joinrel->lateral_relids))
7729 : 4 : return;
7730 : :
7731 : : /*
7732 : : * Create unfinished PgFdwRelationInfo entry which is used to indicate
7733 : : * that the join relation is already considered, so that we won't waste
7734 : : * time in judging safety of join pushdown and adding the same paths again
7735 : : * if found safe. Once we know that this join can be pushed down, we fill
7736 : : * the entry.
7737 : : */
305 michael@paquier.xyz 7738 : 442 : fpinfo = palloc0_object(PgFdwRelationInfo);
3898 rhaas@postgresql.org 7739 : 442 : fpinfo->pushdown_safe = false;
7740 : 442 : joinrel->fdw_private = fpinfo;
7741 : : /* attrs_used is only for base relations. */
7742 : 442 : fpinfo->attrs_used = NULL;
7743 : :
7744 : : /*
7745 : : * If there is a possibility that EvalPlanQual will be executed, we need
7746 : : * to be able to reconstruct the row using scans of the base relations.
7747 : : * GetExistingLocalJoinPath will find a suitable path for this purpose in
7748 : : * the path list of the joinrel, if one exists. We must be careful to
7749 : : * call it before adding any ForeignPath, since the ForeignPath might
7750 : : * dominate the only suitable local path available. We also do it before
7751 : : * calling foreign_join_ok(), since that function updates fpinfo and marks
7752 : : * it as pushable if the join is found to be pushable.
7753 : : */
7754 [ + + ]: 442 : if (root->parse->commandType == CMD_DELETE ||
7755 [ + + ]: 428 : root->parse->commandType == CMD_UPDATE ||
7756 [ + + ]: 399 : root->rowMarks)
7757 : : {
7758 : 81 : epq_path = GetExistingLocalJoinPath(joinrel);
7759 [ - + ]: 81 : if (!epq_path)
7760 : : {
3898 rhaas@postgresql.org 7761 [ # # ]:UBC 0 : elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found");
7762 : 0 : return;
7763 : : }
7764 : : }
7765 : : else
3898 rhaas@postgresql.org 7766 :CBC 361 : epq_path = NULL;
7767 : :
7768 [ + + ]: 442 : if (!foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra))
7769 : : {
7770 : : /* Free path required for EPQ if we copied one; we don't need it now */
7771 [ + + ]: 69 : if (epq_path)
7772 : 2 : pfree(epq_path);
7773 : 69 : return;
7774 : : }
7775 : :
7776 : : /*
7777 : : * Compute the selectivity and cost of the local_conds, so we don't have
7778 : : * to do it over again for each path. The best we can do for these
7779 : : * conditions is to estimate selectivity on the basis of local statistics.
7780 : : * The local conditions are applied after the join has been computed on
7781 : : * the remote side like quals in WHERE clause, so pass jointype as
7782 : : * JOIN_INNER.
7783 : : */
7784 : 373 : fpinfo->local_conds_sel = clauselist_selectivity(root,
7785 : : fpinfo->local_conds,
7786 : : 0,
7787 : : JOIN_INNER,
7788 : : NULL);
7789 : 373 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
7790 : :
7791 : : /*
7792 : : * If we are going to estimate costs locally, estimate the join clause
7793 : : * selectivity here while we have special join info.
7794 : : */
3741 tgl@sss.pgh.pa.us 7795 [ + + ]: 373 : if (!fpinfo->use_remote_estimate)
3898 rhaas@postgresql.org 7796 : 146 : fpinfo->joinclause_sel = clauselist_selectivity(root, fpinfo->joinclauses,
7797 : : 0, fpinfo->jointype,
7798 : : extra->sjinfo);
7799 : :
7800 : : /* Estimate costs for bare join relation */
2750 efujita@postgresql.o 7801 : 373 : estimate_path_cost_size(root, joinrel, NIL, NIL, NULL,
7802 : : &rows, &width, &disabled_nodes,
7803 : : &startup_cost, &total_cost);
7804 : : /* Now update this information in the joinrel */
3898 rhaas@postgresql.org 7805 : 373 : joinrel->rows = rows;
3864 tgl@sss.pgh.pa.us 7806 : 373 : joinrel->reltarget->width = width;
3898 rhaas@postgresql.org 7807 : 373 : fpinfo->rows = rows;
7808 : 373 : fpinfo->width = width;
782 7809 : 373 : fpinfo->disabled_nodes = disabled_nodes;
3898 7810 : 373 : fpinfo->startup_cost = startup_cost;
7811 : 373 : fpinfo->total_cost = total_cost;
7812 : :
7813 : : /*
7814 : : * Create a new join path and add it to the joinrel which represents a
7815 : : * join between foreign tables.
7816 : : */
2804 tgl@sss.pgh.pa.us 7817 : 373 : joinpath = create_foreign_join_path(root,
7818 : : joinrel,
7819 : : NULL, /* default pathtarget */
7820 : : rows,
7821 : : disabled_nodes,
7822 : : startup_cost,
7823 : : total_cost,
7824 : : NIL, /* no pathkeys */
7825 : : joinrel->lateral_relids,
7826 : : epq_path,
7827 : : extra->restrictlist,
7828 : : NIL); /* no fdw_private */
7829 : :
7830 : : /* Add generated path into joinrel by add_path(). */
3898 rhaas@postgresql.org 7831 : 373 : add_path(joinrel, (Path *) joinpath);
7832 : :
7833 : : /* Consider pathkeys for the join relation */
1154 efujita@postgresql.o 7834 : 373 : add_paths_with_pathkeys_for_rel(root, joinrel, epq_path,
7835 : : extra->restrictlist);
7836 : :
7837 : : /* XXX Consider parameterized paths for the join relation */
7838 : : }
7839 : :
7840 : : /*
7841 : : * Assess whether the aggregation, grouping and having operations can be pushed
7842 : : * down to the foreign server. As a side effect, save information we obtain in
7843 : : * this function to PgFdwRelationInfo of the input relation.
7844 : : */
7845 : : static bool
3115 rhaas@postgresql.org 7846 : 164 : foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel,
7847 : : Node *havingQual)
7848 : : {
3643 7849 : 164 : Query *query = root->parse;
7850 : 164 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) grouped_rel->fdw_private;
3115 7851 : 164 : PathTarget *grouping_target = grouped_rel->reltarget;
7852 : : PgFdwRelationInfo *ofpinfo;
7853 : : ListCell *lc;
7854 : : int i;
3643 7855 : 164 : List *tlist = NIL;
7856 : :
7857 : : /* We currently don't support pushing Grouping Sets. */
7858 [ + + ]: 164 : if (query->groupingSets)
7859 : 6 : return false;
7860 : :
7861 : : /* Get the fpinfo of the underlying scan relation. */
7862 : 158 : ofpinfo = (PgFdwRelationInfo *) fpinfo->outerrel->fdw_private;
7863 : :
7864 : : /*
7865 : : * If underlying scan relation has any local conditions, those conditions
7866 : : * are required to be applied before performing aggregation. Hence the
7867 : : * aggregate cannot be pushed down.
7868 : : */
7869 [ + + ]: 158 : if (ofpinfo->local_conds)
7870 : 9 : return false;
7871 : :
7872 : : /*
7873 : : * Examine grouping expressions, as well as other expressions we'd need to
7874 : : * compute, and check whether they are safe to push down to the foreign
7875 : : * server. All GROUP BY expressions will be part of the grouping target
7876 : : * and thus there is no need to search for them separately. Add grouping
7877 : : * expressions into target list which will be passed to foreign server.
7878 : : *
7879 : : * A tricky fine point is that we must not put any expression into the
7880 : : * target list that is just a foreign param (that is, something that
7881 : : * deparse.c would conclude has to be sent to the foreign server). If we
7882 : : * do, the expression will also appear in the fdw_exprs list of the plan
7883 : : * node, and setrefs.c will get confused and decide that the fdw_exprs
7884 : : * entry is actually a reference to the fdw_scan_tlist entry, resulting in
7885 : : * a broken plan. Somewhat oddly, it's OK if the expression contains such
7886 : : * a node, as long as it's not at top level; then no match is possible.
7887 : : */
7888 : 149 : i = 0;
7889 [ + - + + : 435 : foreach(lc, grouping_target->exprs)
+ + ]
7890 : : {
7891 : 304 : Expr *expr = (Expr *) lfirst(lc);
7892 [ + - ]: 304 : Index sgref = get_pathtarget_sortgroupref(grouping_target, i);
7893 : : ListCell *l;
7894 : :
7895 : : /*
7896 : : * Check whether this expression is part of GROUP BY clause. Note we
7897 : : * check the whole GROUP BY clause not just processed_groupClause,
7898 : : * because we will ship all of it, cf. appendGroupByClause.
7899 : : */
7900 [ + + + + ]: 304 : if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause))
7901 : 94 : {
7902 : : TargetEntry *tle;
7903 : :
7904 : : /*
7905 : : * If any GROUP BY expression is not shippable, then we cannot
7906 : : * push down aggregation to the foreign server.
7907 : : */
54 akorotkov@postgresql 7908 [ + + ]:GNC 97 : if (!is_foreign_expr(root, grouped_rel, fpinfo, expr))
3643 rhaas@postgresql.org 7909 :CBC 18 : return false;
7910 : :
7911 : : /*
7912 : : * If it would be a foreign param, we can't put it into the tlist,
7913 : : * so we have to fail.
7914 : : */
2725 tgl@sss.pgh.pa.us 7915 [ + + ]: 96 : if (is_foreign_param(root, grouped_rel, expr))
7916 : 2 : return false;
7917 : :
7918 : : /*
7919 : : * Pushable, so add to tlist. We need to create a TLE for this
7920 : : * expression and apply the sortgroupref to it. We cannot use
7921 : : * add_to_flat_tlist() here because that avoids making duplicate
7922 : : * entries in the tlist. If there are duplicate entries with
7923 : : * distinct sortgrouprefs, we have to duplicate that situation in
7924 : : * the output tlist.
7925 : : */
3195 7926 : 94 : tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false);
7927 : 94 : tle->ressortgroupref = sgref;
7928 : 94 : tlist = lappend(tlist, tle);
7929 : : }
7930 : : else
7931 : : {
7932 : : /*
7933 : : * Non-grouping expression we need to compute. Can we ship it
7934 : : * as-is to the foreign server?
7935 : : */
54 akorotkov@postgresql 7936 [ + + ]:GNC 207 : if (is_foreign_expr(root, grouped_rel, fpinfo, expr) &&
2725 tgl@sss.pgh.pa.us 7937 [ + + ]:CBC 186 : !is_foreign_param(root, grouped_rel, expr))
7938 : : {
7939 : : /* Yes, so add to tlist as-is; OK to suppress duplicates */
3643 rhaas@postgresql.org 7940 : 184 : tlist = add_to_flat_tlist(tlist, list_make1(expr));
7941 : : }
7942 : : else
7943 : : {
7944 : : /* Not pushable as a whole; extract its Vars and aggregates */
7945 : : List *aggvars;
7946 : :
7947 : 23 : aggvars = pull_var_clause((Node *) expr,
7948 : : PVC_INCLUDE_AGGREGATES);
7949 : :
7950 : : /*
7951 : : * If any aggregate expression is not shippable, then we
7952 : : * cannot push down aggregation to the foreign server. (We
7953 : : * don't have to check is_foreign_param, since that certainly
7954 : : * won't return true for any such expression.)
7955 : : */
54 akorotkov@postgresql 7956 [ + + ]:GNC 23 : if (!is_foreign_expr(root, grouped_rel, fpinfo, (Expr *) aggvars))
3643 rhaas@postgresql.org 7957 :CBC 15 : return false;
7958 : :
7959 : : /*
7960 : : * Add aggregates, if any, into the targetlist. Plain Vars
7961 : : * outside an aggregate can be ignored, because they should be
7962 : : * either same as some GROUP BY column or part of some GROUP
7963 : : * BY expression. In either case, they are already part of
7964 : : * the targetlist and thus no need to add them again. In fact
7965 : : * including plain Vars in the tlist when they do not match a
7966 : : * GROUP BY column would cause the foreign server to complain
7967 : : * that the shipped query is invalid.
7968 : : */
7969 [ + + + + : 14 : foreach(l, aggvars)
+ + ]
7970 : : {
1466 drowley@postgresql.o 7971 : 6 : Expr *aggref = (Expr *) lfirst(l);
7972 : :
7973 [ + + ]: 6 : if (IsA(aggref, Aggref))
7974 : 4 : tlist = add_to_flat_tlist(tlist, list_make1(aggref));
7975 : : }
7976 : : }
7977 : : }
7978 : :
3643 rhaas@postgresql.org 7979 : 286 : i++;
7980 : : }
7981 : :
7982 : : /*
7983 : : * Classify the pushable and non-pushable HAVING clauses and save them in
7984 : : * remote_conds and local_conds of the grouped rel's fpinfo.
7985 : : */
3115 7986 [ + + ]: 131 : if (havingQual)
7987 : : {
7988 [ + - + + : 34 : foreach(lc, (List *) havingQual)
+ + ]
7989 : : {
3643 7990 : 19 : Expr *expr = (Expr *) lfirst(lc);
7991 : : RestrictInfo *rinfo;
7992 : :
7993 : : /*
7994 : : * Currently, the core code doesn't wrap havingQuals in
7995 : : * RestrictInfos, so we must make our own.
7996 : : */
3471 tgl@sss.pgh.pa.us 7997 [ - + ]: 19 : Assert(!IsA(expr, RestrictInfo));
2090 7998 : 19 : rinfo = make_restrictinfo(root,
7999 : : expr,
8000 : : true,
8001 : : false,
8002 : : false,
8003 : : false,
8004 : : root->qual_security_level,
8005 : : grouped_rel->relids,
8006 : : NULL,
8007 : : NULL);
54 akorotkov@postgresql 8008 [ + + ]:GNC 19 : if (is_foreign_expr(root, grouped_rel, fpinfo, expr))
3471 tgl@sss.pgh.pa.us 8009 :CBC 16 : fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo);
8010 : : else
8011 : 3 : fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo);
8012 : : }
8013 : : }
8014 : :
8015 : : /*
8016 : : * If there are any local conditions, pull Vars and aggregates from it and
8017 : : * check whether they are safe to pushdown or not.
8018 : : */
3643 rhaas@postgresql.org 8019 [ + + ]: 131 : if (fpinfo->local_conds)
8020 : : {
3471 tgl@sss.pgh.pa.us 8021 : 3 : List *aggvars = NIL;
8022 : :
8023 [ + - + + : 6 : foreach(lc, fpinfo->local_conds)
+ + ]
8024 : : {
8025 : 3 : RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc);
8026 : :
8027 : 3 : aggvars = list_concat(aggvars,
8028 : 3 : pull_var_clause((Node *) rinfo->clause,
8029 : : PVC_INCLUDE_AGGREGATES));
8030 : : }
8031 : :
3643 rhaas@postgresql.org 8032 [ + - + + : 7 : foreach(lc, aggvars)
+ + ]
8033 : : {
8034 : 5 : Expr *expr = (Expr *) lfirst(lc);
8035 : :
8036 : : /*
8037 : : * If aggregates within local conditions are not safe to push
8038 : : * down, then we cannot push down the query. Vars are already
8039 : : * part of GROUP BY clause which are checked above, so no need to
8040 : : * access them again here. Again, we need not check
8041 : : * is_foreign_param for a foreign aggregate.
8042 : : */
8043 [ + - ]: 5 : if (IsA(expr, Aggref))
8044 : : {
54 akorotkov@postgresql 8045 [ + + ]:GNC 5 : if (!is_foreign_expr(root, grouped_rel, fpinfo, expr))
3643 rhaas@postgresql.org 8046 :CBC 1 : return false;
8047 : :
3471 tgl@sss.pgh.pa.us 8048 : 4 : tlist = add_to_flat_tlist(tlist, list_make1(expr));
8049 : : }
8050 : : }
8051 : : }
8052 : :
8053 : : /* Store generated targetlist */
3643 rhaas@postgresql.org 8054 : 130 : fpinfo->grouped_tlist = tlist;
8055 : :
8056 : : /* Safe to pushdown */
8057 : 130 : fpinfo->pushdown_safe = true;
8058 : :
8059 : : /*
8060 : : * Set # of retrieved rows and cached relation costs to some negative
8061 : : * value, so that we can detect when they are set to some sensible values,
8062 : : * during one (usually the first) of the calls to estimate_path_cost_size.
8063 : : */
2677 efujita@postgresql.o 8064 : 130 : fpinfo->retrieved_rows = -1;
3643 rhaas@postgresql.org 8065 : 130 : fpinfo->rel_startup_cost = -1;
8066 : 130 : fpinfo->rel_total_cost = -1;
8067 : :
8068 : : /*
8069 : : * Set the string describing this grouped relation to be used in EXPLAIN
8070 : : * output of corresponding ForeignScan. Note that the decoration we add
8071 : : * to the base relation name mustn't include any digits, or it'll confuse
8072 : : * postgresExplainForeignScan.
8073 : : */
2506 tgl@sss.pgh.pa.us 8074 : 130 : fpinfo->relation_name = psprintf("Aggregate on (%s)",
8075 : : ofpinfo->relation_name);
8076 : :
3643 rhaas@postgresql.org 8077 : 130 : return true;
8078 : : }
8079 : :
8080 : : /*
8081 : : * postgresGetForeignUpperPaths
8082 : : * Add paths for post-join operations like aggregation, grouping etc. if
8083 : : * corresponding operations are safe to push down.
8084 : : */
8085 : : static void
8086 : 1064 : postgresGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage,
8087 : : RelOptInfo *input_rel, RelOptInfo *output_rel,
8088 : : void *extra)
8089 : : {
8090 : : PgFdwRelationInfo *fpinfo;
8091 : :
8092 : : /*
8093 : : * If input rel is not safe to pushdown, then simply return as we cannot
8094 : : * perform any post-join operations on the foreign server.
8095 : : */
8096 [ + + ]: 1064 : if (!input_rel->fdw_private ||
8097 [ + + ]: 994 : !((PgFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe)
8098 : 136 : return;
8099 : :
8100 : : /* Ignore stages we don't support; and skip any duplicate calls. */
2750 efujita@postgresql.o 8101 [ + + + + ]: 928 : if ((stage != UPPERREL_GROUP_AGG &&
8102 [ + + ]: 593 : stage != UPPERREL_ORDERED &&
8103 : 909 : stage != UPPERREL_FINAL) ||
8104 [ - + ]: 909 : output_rel->fdw_private)
3643 rhaas@postgresql.org 8105 : 19 : return;
8106 : :
305 michael@paquier.xyz 8107 : 909 : fpinfo = palloc0_object(PgFdwRelationInfo);
3643 rhaas@postgresql.org 8108 : 909 : fpinfo->pushdown_safe = false;
2750 efujita@postgresql.o 8109 : 909 : fpinfo->stage = stage;
3643 rhaas@postgresql.org 8110 : 909 : output_rel->fdw_private = fpinfo;
8111 : :
2750 efujita@postgresql.o 8112 [ + + + - ]: 909 : switch (stage)
8113 : : {
8114 : 164 : case UPPERREL_GROUP_AGG:
8115 : 164 : add_foreign_grouping_paths(root, input_rel, output_rel,
8116 : : (GroupPathExtraData *) extra);
8117 : 164 : break;
8118 : 171 : case UPPERREL_ORDERED:
8119 : 171 : add_foreign_ordered_paths(root, input_rel, output_rel);
8120 : 171 : break;
8121 : 574 : case UPPERREL_FINAL:
8122 : 574 : add_foreign_final_paths(root, input_rel, output_rel,
8123 : : (FinalPathExtraData *) extra);
8124 : 574 : break;
2750 efujita@postgresql.o 8125 :UBC 0 : default:
8126 [ # # ]: 0 : elog(ERROR, "unexpected upper relation: %d", (int) stage);
8127 : : break;
8128 : : }
8129 : : }
8130 : :
8131 : : /*
8132 : : * add_foreign_grouping_paths
8133 : : * Add foreign path for grouping and/or aggregation.
8134 : : *
8135 : : * Given input_rel represents the underlying scan. The paths are added to the
8136 : : * given grouped_rel.
8137 : : */
8138 : : static void
3643 rhaas@postgresql.org 8139 :CBC 164 : add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
8140 : : RelOptInfo *grouped_rel,
8141 : : GroupPathExtraData *extra)
8142 : : {
8143 : 164 : Query *parse = root->parse;
8144 : 164 : PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
8145 : 164 : PgFdwRelationInfo *fpinfo = grouped_rel->fdw_private;
8146 : : ForeignPath *grouppath;
8147 : : double rows;
8148 : : int width;
8149 : : int disabled_nodes;
8150 : : Cost startup_cost;
8151 : : Cost total_cost;
8152 : :
8153 : : /* Nothing to be done, if there is no grouping or aggregation required. */
8154 [ + + + - : 164 : if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs &&
- + ]
3643 rhaas@postgresql.org 8155 [ # # ]:UBC 0 : !root->hasHavingQual)
3643 rhaas@postgresql.org 8156 :CBC 34 : return;
8157 : :
3115 8158 [ + + - + ]: 164 : Assert(extra->patype == PARTITIONWISE_AGGREGATE_NONE ||
8159 : : extra->patype == PARTITIONWISE_AGGREGATE_FULL);
8160 : :
8161 : : /* save the input_rel as outerrel in fpinfo */
3643 8162 : 164 : fpinfo->outerrel = input_rel;
8163 : :
8164 : : /*
8165 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
8166 : : * details from the input relation's fpinfo.
8167 : : */
8168 : 164 : fpinfo->table = ifpinfo->table;
8169 : 164 : fpinfo->server = ifpinfo->server;
8170 : 164 : fpinfo->user = ifpinfo->user;
3435 bruce@momjian.us 8171 : 164 : merge_fdw_options(fpinfo, ifpinfo, NULL);
8172 : :
8173 : : /*
8174 : : * Assess if it is safe to push down aggregation and grouping.
8175 : : *
8176 : : * Use HAVING qual from extra. In case of child partition, it will have
8177 : : * translated Vars.
8178 : : */
3115 rhaas@postgresql.org 8179 [ + + ]: 164 : if (!foreign_grouping_ok(root, grouped_rel, extra->havingQual))
3643 8180 : 34 : return;
8181 : :
8182 : : /*
8183 : : * Compute the selectivity and cost of the local_conds, so we don't have
8184 : : * to do it over again for each path. (Currently we create just a single
8185 : : * path here, but in future it would be possible that we build more paths
8186 : : * such as pre-sorted paths as in postgresGetForeignPaths and
8187 : : * postgresGetForeignJoinPaths.) The best we can do for these conditions
8188 : : * is to estimate selectivity on the basis of local statistics.
8189 : : */
2869 efujita@postgresql.o 8190 : 130 : fpinfo->local_conds_sel = clauselist_selectivity(root,
8191 : : fpinfo->local_conds,
8192 : : 0,
8193 : : JOIN_INNER,
8194 : : NULL);
8195 : :
8196 : 130 : cost_qual_eval(&fpinfo->local_conds_cost, fpinfo->local_conds, root);
8197 : :
8198 : : /* Estimate the cost of push down */
2750 8199 : 130 : estimate_path_cost_size(root, grouped_rel, NIL, NIL, NULL,
8200 : : &rows, &width, &disabled_nodes,
8201 : : &startup_cost, &total_cost);
8202 : :
8203 : : /* Now update this information in the fpinfo */
3643 rhaas@postgresql.org 8204 : 130 : fpinfo->rows = rows;
8205 : 130 : fpinfo->width = width;
782 8206 : 130 : fpinfo->disabled_nodes = disabled_nodes;
3643 8207 : 130 : fpinfo->startup_cost = startup_cost;
8208 : 130 : fpinfo->total_cost = total_cost;
8209 : :
8210 : : /* Create and add foreign path to the grouping relation. */
2804 tgl@sss.pgh.pa.us 8211 : 130 : grouppath = create_foreign_upper_path(root,
8212 : : grouped_rel,
8213 : 130 : grouped_rel->reltarget,
8214 : : rows,
8215 : : disabled_nodes,
8216 : : startup_cost,
8217 : : total_cost,
8218 : : NIL, /* no pathkeys */
8219 : : NULL,
8220 : : NIL, /* no fdw_restrictinfo list */
8221 : : NIL); /* no fdw_private */
8222 : :
8223 : : /* Add generated path into grouped_rel by add_path(). */
3643 rhaas@postgresql.org 8224 : 130 : add_path(grouped_rel, (Path *) grouppath);
8225 : : }
8226 : :
8227 : : /*
8228 : : * add_foreign_ordered_paths
8229 : : * Add foreign paths for performing the final sort remotely.
8230 : : *
8231 : : * Given input_rel contains the source-data Paths. The paths are added to the
8232 : : * given ordered_rel.
8233 : : */
8234 : : static void
2750 efujita@postgresql.o 8235 : 171 : add_foreign_ordered_paths(PlannerInfo *root, RelOptInfo *input_rel,
8236 : : RelOptInfo *ordered_rel)
8237 : : {
8238 : 171 : Query *parse = root->parse;
8239 : 171 : PgFdwRelationInfo *ifpinfo = input_rel->fdw_private;
8240 : 171 : PgFdwRelationInfo *fpinfo = ordered_rel->fdw_private;
8241 : : PgFdwPathExtraData *fpextra;
8242 : : double rows;
8243 : : int width;
8244 : : int disabled_nodes;
8245 : : Cost startup_cost;
8246 : : Cost total_cost;
8247 : : List *fdw_private;
8248 : : ForeignPath *ordered_path;
8249 : : ListCell *lc;
8250 : :
8251 : : /* Shouldn't get here unless the query has ORDER BY */
8252 [ - + ]: 171 : Assert(parse->sortClause);
8253 : :
8254 : : /* We don't support cases where there are any SRFs in the targetlist */
8255 [ - + ]: 171 : if (parse->hasTargetSRFs)
8256 : 127 : return;
8257 : :
8258 : : /* Save the input_rel as outerrel in fpinfo */
8259 : 171 : fpinfo->outerrel = input_rel;
8260 : :
8261 : : /*
8262 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
8263 : : * details from the input relation's fpinfo.
8264 : : */
8265 : 171 : fpinfo->table = ifpinfo->table;
8266 : 171 : fpinfo->server = ifpinfo->server;
8267 : 171 : fpinfo->user = ifpinfo->user;
8268 : 171 : merge_fdw_options(fpinfo, ifpinfo, NULL);
8269 : :
8270 : : /*
8271 : : * If the input_rel is a base or join relation, we would already have
8272 : : * considered pushing down the final sort to the remote server when
8273 : : * creating pre-sorted foreign paths for that relation, because the
8274 : : * query_pathkeys is set to the root->sort_pathkeys in that case (see
8275 : : * standard_qp_callback()).
8276 : : */
8277 [ + + ]: 171 : if (input_rel->reloptkind == RELOPT_BASEREL ||
8278 [ + + ]: 126 : input_rel->reloptkind == RELOPT_JOINREL)
8279 : : {
8280 [ - + ]: 123 : Assert(root->query_pathkeys == root->sort_pathkeys);
8281 : :
8282 : : /* Safe to push down if the query_pathkeys is safe to push down */
8283 : 123 : fpinfo->pushdown_safe = ifpinfo->qp_is_pushdown_safe;
8284 : :
8285 : 123 : return;
8286 : : }
8287 : :
8288 : : /* The input_rel should be a grouping relation */
8289 [ + - - + ]: 48 : Assert(input_rel->reloptkind == RELOPT_UPPER_REL &&
8290 : : ifpinfo->stage == UPPERREL_GROUP_AGG);
8291 : :
8292 : : /*
8293 : : * We try to create a path below by extending a simple foreign path for
8294 : : * the underlying grouping relation to perform the final sort remotely,
8295 : : * which is stored into the fdw_private list of the resulting path.
8296 : : */
8297 : :
8298 : : /* Assess if it is safe to push down the final sort */
8299 [ + + + + : 98 : foreach(lc, root->sort_pathkeys)
+ + ]
8300 : : {
8301 : 54 : PathKey *pathkey = (PathKey *) lfirst(lc);
8302 : 54 : EquivalenceClass *pathkey_ec = pathkey->pk_eclass;
8303 : :
8304 : : /*
8305 : : * is_foreign_expr would detect volatile expressions as well, but
8306 : : * checking ec_has_volatile here saves some cycles.
8307 : : */
8308 [ + + ]: 54 : if (pathkey_ec->ec_has_volatile)
8309 : 4 : return;
8310 : :
8311 : : /*
8312 : : * Can't push down the sort if pathkey's opfamily is not shippable.
8313 : : */
1656 tgl@sss.pgh.pa.us 8314 [ - + ]: 50 : if (!is_shippable(pathkey->pk_opfamily, OperatorFamilyRelationId,
8315 : : fpinfo))
1656 tgl@sss.pgh.pa.us 8316 :UBC 0 : return;
8317 : :
8318 : : /*
8319 : : * The EC must contain a shippable EM that is computed in input_rel's
8320 : : * reltarget, else we can't push down the sort.
8321 : : */
1656 tgl@sss.pgh.pa.us 8322 [ - + ]:CBC 50 : if (find_em_for_rel_target(root,
8323 : : pathkey_ec,
8324 : : input_rel) == NULL)
2750 efujita@postgresql.o 8325 :UBC 0 : return;
8326 : : }
8327 : :
8328 : : /* Safe to push down */
2750 efujita@postgresql.o 8329 :CBC 44 : fpinfo->pushdown_safe = true;
8330 : :
8331 : : /* Construct PgFdwPathExtraData */
305 michael@paquier.xyz 8332 : 44 : fpextra = palloc0_object(PgFdwPathExtraData);
2750 efujita@postgresql.o 8333 : 44 : fpextra->target = root->upper_targets[UPPERREL_ORDERED];
8334 : 44 : fpextra->has_final_sort = true;
8335 : :
8336 : : /* Estimate the costs of performing the final sort remotely */
8337 : 44 : estimate_path_cost_size(root, input_rel, NIL, root->sort_pathkeys, fpextra,
8338 : : &rows, &width, &disabled_nodes,
8339 : : &startup_cost, &total_cost);
8340 : :
8341 : : /*
8342 : : * Build the fdw_private list that will be used by postgresGetForeignPlan.
8343 : : * Items in the list must match order in enum FdwPathPrivateIndex.
8344 : : */
1732 peter@eisentraut.org 8345 : 44 : fdw_private = list_make2(makeBoolean(true), makeBoolean(false));
8346 : :
8347 : : /* Create foreign ordering path */
2750 efujita@postgresql.o 8348 : 44 : ordered_path = create_foreign_upper_path(root,
8349 : : input_rel,
8350 : 44 : root->upper_targets[UPPERREL_ORDERED],
8351 : : rows,
8352 : : disabled_nodes,
8353 : : startup_cost,
8354 : : total_cost,
8355 : : root->sort_pathkeys,
8356 : : NULL, /* no extra plan */
8357 : : NIL, /* no fdw_restrictinfo
8358 : : * list */
8359 : : fdw_private);
8360 : :
8361 : : /* and add it to the ordered_rel */
8362 : 44 : add_path(ordered_rel, (Path *) ordered_path);
8363 : : }
8364 : :
8365 : : /*
8366 : : * add_foreign_final_paths
8367 : : * Add foreign paths for performing the final processing remotely.
8368 : : *
8369 : : * Given input_rel contains the source-data Paths. The paths are added to the
8370 : : * given final_rel.
8371 : : */
8372 : : static void
8373 : 574 : add_foreign_final_paths(PlannerInfo *root, RelOptInfo *input_rel,
8374 : : RelOptInfo *final_rel,
8375 : : FinalPathExtraData *extra)
8376 : : {
8377 : 574 : Query *parse = root->parse;
8378 : 574 : PgFdwRelationInfo *ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private;
8379 : 574 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) final_rel->fdw_private;
8380 : 574 : bool has_final_sort = false;
8381 : 574 : List *pathkeys = NIL;
8382 : : PgFdwPathExtraData *fpextra;
8383 : 574 : bool save_use_remote_estimate = false;
8384 : : double rows;
8385 : : int width;
8386 : : int disabled_nodes;
8387 : : Cost startup_cost;
8388 : : Cost total_cost;
8389 : : List *fdw_private;
8390 : : ForeignPath *final_path;
8391 : :
8392 : : /*
8393 : : * Currently, we only support this for SELECT commands
8394 : : */
8395 [ + + ]: 574 : if (parse->commandType != CMD_SELECT)
8396 : 452 : return;
8397 : :
8398 : : /*
8399 : : * No work if there is no FOR UPDATE/SHARE clause and if there is no need
8400 : : * to add a LIMIT node
8401 : : */
8402 [ + + + + ]: 458 : if (!parse->rowMarks && !extra->limit_needed)
8403 : 322 : return;
8404 : :
8405 : : /* We don't support cases where there are any SRFs in the targetlist */
8406 [ - + ]: 136 : if (parse->hasTargetSRFs)
2750 efujita@postgresql.o 8407 :UBC 0 : return;
8408 : :
8409 : : /* Save the input_rel as outerrel in fpinfo */
2750 efujita@postgresql.o 8410 :CBC 136 : fpinfo->outerrel = input_rel;
8411 : :
8412 : : /*
8413 : : * Copy foreign table, foreign server, user mapping, FDW options etc.
8414 : : * details from the input relation's fpinfo.
8415 : : */
8416 : 136 : fpinfo->table = ifpinfo->table;
8417 : 136 : fpinfo->server = ifpinfo->server;
8418 : 136 : fpinfo->user = ifpinfo->user;
8419 : 136 : merge_fdw_options(fpinfo, ifpinfo, NULL);
8420 : :
8421 : : /*
8422 : : * If there is no need to add a LIMIT node, there might be a ForeignPath
8423 : : * in the input_rel's pathlist that implements all behavior of the query.
8424 : : * Note: we would already have accounted for the query's FOR UPDATE/SHARE
8425 : : * (if any) before we get here.
8426 : : */
8427 [ + + ]: 136 : if (!extra->limit_needed)
8428 : : {
8429 : : ListCell *lc;
8430 : :
8431 [ - + ]: 4 : Assert(parse->rowMarks);
8432 : :
8433 : : /*
8434 : : * Grouping and aggregation are not supported with FOR UPDATE/SHARE,
8435 : : * so the input_rel should be a base, join, or ordered relation; and
8436 : : * if it's an ordered relation, its input relation should be a base or
8437 : : * join relation.
8438 : : */
8439 [ - + - - : 4 : Assert(input_rel->reloptkind == RELOPT_BASEREL ||
- - - - -
- - - ]
8440 : : input_rel->reloptkind == RELOPT_JOINREL ||
8441 : : (input_rel->reloptkind == RELOPT_UPPER_REL &&
8442 : : ifpinfo->stage == UPPERREL_ORDERED &&
8443 : : (ifpinfo->outerrel->reloptkind == RELOPT_BASEREL ||
8444 : : ifpinfo->outerrel->reloptkind == RELOPT_JOINREL)));
8445 : :
8446 [ + - + - : 4 : foreach(lc, input_rel->pathlist)
+ - ]
8447 : : {
8448 : 4 : Path *path = (Path *) lfirst(lc);
8449 : :
8450 : : /*
8451 : : * apply_scanjoin_target_to_paths() uses create_projection_path()
8452 : : * to adjust each of its input paths if needed, whereas
8453 : : * create_ordered_paths() uses apply_projection_to_path() to do
8454 : : * that. So the former might have put a ProjectionPath on top of
8455 : : * the ForeignPath; look through ProjectionPath and see if the
8456 : : * path underneath it is ForeignPath.
8457 : : */
8458 [ - + ]: 4 : if (IsA(path, ForeignPath) ||
2750 efujita@postgresql.o 8459 [ # # ]:UBC 0 : (IsA(path, ProjectionPath) &&
8460 [ # # ]: 0 : IsA(((ProjectionPath *) path)->subpath, ForeignPath)))
8461 : : {
8462 : : /*
8463 : : * Create foreign final path; this gets rid of a
8464 : : * no-longer-needed outer plan (if any), which makes the
8465 : : * EXPLAIN output look cleaner
8466 : : */
2750 efujita@postgresql.o 8467 :CBC 4 : final_path = create_foreign_upper_path(root,
8468 : : path->parent,
8469 : : path->pathtarget,
8470 : : path->rows,
8471 : : path->disabled_nodes,
8472 : : path->startup_cost,
8473 : : path->total_cost,
8474 : : path->pathkeys,
8475 : : NULL, /* no extra plan */
8476 : : NIL, /* no fdw_restrictinfo
8477 : : * list */
8478 : : NIL); /* no fdw_private */
8479 : :
8480 : : /* and add it to the final_rel */
8481 : 4 : add_path(final_rel, (Path *) final_path);
8482 : :
8483 : : /* Safe to push down */
8484 : 4 : fpinfo->pushdown_safe = true;
8485 : :
8486 : 4 : return;
8487 : : }
8488 : : }
8489 : :
8490 : : /*
8491 : : * If we get here it means no ForeignPaths; since we would already
8492 : : * have considered pushing down all operations for the query to the
8493 : : * remote server, give up on it.
8494 : : */
2750 efujita@postgresql.o 8495 :UBC 0 : return;
8496 : : }
8497 : :
2750 efujita@postgresql.o 8498 [ - + ]:CBC 132 : Assert(extra->limit_needed);
8499 : :
8500 : : /*
8501 : : * If the input_rel is an ordered relation, replace the input_rel with its
8502 : : * input relation
8503 : : */
8504 [ + + ]: 132 : if (input_rel->reloptkind == RELOPT_UPPER_REL &&
8505 [ + - ]: 74 : ifpinfo->stage == UPPERREL_ORDERED)
8506 : : {
8507 : 74 : input_rel = ifpinfo->outerrel;
8508 : 74 : ifpinfo = (PgFdwRelationInfo *) input_rel->fdw_private;
8509 : 74 : has_final_sort = true;
8510 : 74 : pathkeys = root->sort_pathkeys;
8511 : : }
8512 : :
8513 : : /* The input_rel should be a base, join, or grouping relation */
8514 [ + + + + : 132 : Assert(input_rel->reloptkind == RELOPT_BASEREL ||
+ - - + ]
8515 : : input_rel->reloptkind == RELOPT_JOINREL ||
8516 : : (input_rel->reloptkind == RELOPT_UPPER_REL &&
8517 : : ifpinfo->stage == UPPERREL_GROUP_AGG));
8518 : :
8519 : : /*
8520 : : * We try to create a path below by extending a simple foreign path for
8521 : : * the underlying base, join, or grouping relation to perform the final
8522 : : * sort (if has_final_sort) and the LIMIT restriction remotely, which is
8523 : : * stored into the fdw_private list of the resulting path. (We
8524 : : * re-estimate the costs of sorting the underlying relation, if
8525 : : * has_final_sort.)
8526 : : */
8527 : :
8528 : : /*
8529 : : * Assess if it is safe to push down the LIMIT and OFFSET to the remote
8530 : : * server
8531 : : */
8532 : :
8533 : : /*
8534 : : * If the underlying relation has any local conditions, the LIMIT/OFFSET
8535 : : * cannot be pushed down.
8536 : : */
8537 [ + + ]: 132 : if (ifpinfo->local_conds)
8538 : 8 : return;
8539 : :
8540 : : /*
8541 : : * If the query has FETCH FIRST .. WITH TIES, 1) it must have ORDER BY as
8542 : : * well, which is used to determine which additional rows tie for the last
8543 : : * place in the result set, and 2) ORDER BY must already have been
8544 : : * determined to be safe to push down before we get here. So in that case
8545 : : * the FETCH clause is safe to push down with ORDER BY if the remote
8546 : : * server is v13 or later, but if not, the remote query will fail entirely
8547 : : * for lack of support for it. Since we do not currently have a way to do
8548 : : * a remote-version check (without accessing the remote server), disable
8549 : : * pushing the FETCH clause for now.
8550 : : */
857 8551 [ + + ]: 124 : if (parse->limitOption == LIMIT_OPTION_WITH_TIES)
8552 : 2 : return;
8553 : :
8554 : : /*
8555 : : * Also, the LIMIT/OFFSET cannot be pushed down, if their expressions are
8556 : : * not safe to remote.
8557 : : */
54 akorotkov@postgresql 8558 [ + - ]:GNC 122 : if (!is_foreign_expr(root, input_rel, ifpinfo, (Expr *) parse->limitOffset) ||
8559 [ - + ]: 122 : !is_foreign_expr(root, input_rel, ifpinfo, (Expr *) parse->limitCount))
2750 efujita@postgresql.o 8560 :UBC 0 : return;
8561 : :
8562 : : /* Safe to push down */
2750 efujita@postgresql.o 8563 :CBC 122 : fpinfo->pushdown_safe = true;
8564 : :
8565 : : /* Construct PgFdwPathExtraData */
305 michael@paquier.xyz 8566 : 122 : fpextra = palloc0_object(PgFdwPathExtraData);
2750 efujita@postgresql.o 8567 : 122 : fpextra->target = root->upper_targets[UPPERREL_FINAL];
8568 : 122 : fpextra->has_final_sort = has_final_sort;
8569 : 122 : fpextra->has_limit = extra->limit_needed;
8570 : 122 : fpextra->limit_tuples = extra->limit_tuples;
8571 : 122 : fpextra->count_est = extra->count_est;
8572 : 122 : fpextra->offset_est = extra->offset_est;
8573 : :
8574 : : /*
8575 : : * Estimate the costs of performing the final sort and the LIMIT
8576 : : * restriction remotely. If has_final_sort is false, we wouldn't need to
8577 : : * execute EXPLAIN anymore if use_remote_estimate, since the costs can be
8578 : : * roughly estimated using the costs we already have for the underlying
8579 : : * relation, in the same way as when use_remote_estimate is false. Since
8580 : : * it's pretty expensive to execute EXPLAIN, force use_remote_estimate to
8581 : : * false in that case.
8582 : : */
8583 [ + + ]: 122 : if (!fpextra->has_final_sort)
8584 : : {
8585 : 55 : save_use_remote_estimate = ifpinfo->use_remote_estimate;
8586 : 55 : ifpinfo->use_remote_estimate = false;
8587 : : }
8588 : 122 : estimate_path_cost_size(root, input_rel, NIL, pathkeys, fpextra,
8589 : : &rows, &width, &disabled_nodes,
8590 : : &startup_cost, &total_cost);
8591 [ + + ]: 122 : if (!fpextra->has_final_sort)
8592 : 55 : ifpinfo->use_remote_estimate = save_use_remote_estimate;
8593 : :
8594 : : /*
8595 : : * Build the fdw_private list that will be used by postgresGetForeignPlan.
8596 : : * Items in the list must match order in enum FdwPathPrivateIndex.
8597 : : */
1732 peter@eisentraut.org 8598 : 122 : fdw_private = list_make2(makeBoolean(has_final_sort),
8599 : : makeBoolean(extra->limit_needed));
8600 : :
8601 : : /*
8602 : : * Create foreign final path; this gets rid of a no-longer-needed outer
8603 : : * plan (if any), which makes the EXPLAIN output look cleaner
8604 : : */
2750 efujita@postgresql.o 8605 : 122 : final_path = create_foreign_upper_path(root,
8606 : : input_rel,
8607 : 122 : root->upper_targets[UPPERREL_FINAL],
8608 : : rows,
8609 : : disabled_nodes,
8610 : : startup_cost,
8611 : : total_cost,
8612 : : pathkeys,
8613 : : NULL, /* no extra plan */
8614 : : NIL, /* no fdw_restrictinfo list */
8615 : : fdw_private);
8616 : :
8617 : : /* and add it to the final_rel */
8618 : 122 : add_path(final_rel, (Path *) final_path);
8619 : : }
8620 : :
8621 : : /*
8622 : : * postgresIsForeignPathAsyncCapable
8623 : : * Check whether a given ForeignPath node is async-capable.
8624 : : */
8625 : : static bool
2021 8626 : 251 : postgresIsForeignPathAsyncCapable(ForeignPath *path)
8627 : : {
8628 : 251 : RelOptInfo *rel = ((Path *) path)->parent;
8629 : 251 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
8630 : :
8631 : 251 : return fpinfo->async_capable;
8632 : : }
8633 : :
8634 : : /*
8635 : : * postgresForeignAsyncRequest
8636 : : * Asynchronously request next tuple from a foreign PostgreSQL table.
8637 : : */
8638 : : static void
8639 : 6083 : postgresForeignAsyncRequest(AsyncRequest *areq)
8640 : : {
8641 : 6083 : produce_tuple_asynchronously(areq, true);
8642 : 6083 : }
8643 : :
8644 : : /*
8645 : : * postgresForeignAsyncConfigureWait
8646 : : * Configure a file descriptor event for which we wish to wait.
8647 : : */
8648 : : static void
8649 : 227 : postgresForeignAsyncConfigureWait(AsyncRequest *areq)
8650 : : {
8651 : 227 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
8652 : 227 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
8653 : 227 : AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq;
8654 : 227 : AppendState *requestor = (AppendState *) areq->requestor;
8655 : 227 : WaitEventSet *set = requestor->as_eventset;
8656 : :
8657 : : /* This should not be called unless callback_pending */
8658 [ - + ]: 227 : Assert(areq->callback_pending);
8659 : :
8660 : : /*
8661 : : * If process_pending_request() has been invoked on the given request
8662 : : * before we get here, we might have some tuples already; in which case
8663 : : * complete the request
8664 : : */
1900 8665 [ + + ]: 227 : if (fsstate->next_tuple < fsstate->num_tuples)
8666 : : {
8667 : 4 : complete_pending_request(areq);
8668 [ + + ]: 4 : if (areq->request_complete)
8669 : 2 : return;
8670 [ - + ]: 2 : Assert(areq->callback_pending);
8671 : : }
8672 : :
8673 : : /* We must have run out of tuples */
8674 [ - + ]: 225 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
8675 : :
8676 : : /* The core code would have registered postmaster death event */
2021 8677 [ - + ]: 225 : Assert(GetNumRegisteredWaitEvents(set) >= 1);
8678 : :
8679 : : /* Begin an asynchronous data fetch if not already done */
8680 [ + + ]: 225 : if (!pendingAreq)
8681 : 5 : fetch_more_data_begin(areq);
8682 [ + + ]: 220 : else if (pendingAreq->requestor != areq->requestor)
8683 : : {
8684 : : /*
8685 : : * This is the case when the in-process request was made by another
8686 : : * Append. Note that it might be useless to process the request made
8687 : : * by that Append, because the query might not need tuples from that
8688 : : * Append anymore; so we avoid processing it to begin a fetch for the
8689 : : * given request if possible. If there are any child subplans of the
8690 : : * same parent that are ready for new requests, skip the given
8691 : : * request. Likewise, if there are any configured events other than
8692 : : * the postmaster death event, skip it. Otherwise, process the
8693 : : * in-process request, then begin a fetch to configure the event
8694 : : * below, because we might otherwise end up with no configured events
8695 : : * other than the postmaster death event.
8696 : : */
1900 8697 [ - + ]: 5 : if (!bms_is_empty(requestor->as_needrequest))
1900 efujita@postgresql.o 8698 :UBC 0 : return;
2021 efujita@postgresql.o 8699 [ + + ]:CBC 5 : if (GetNumRegisteredWaitEvents(set) > 1)
8700 : 3 : return;
8701 : 2 : process_pending_request(pendingAreq);
8702 : 2 : fetch_more_data_begin(areq);
8703 : : }
8704 [ + + ]: 215 : else if (pendingAreq->requestee != areq->requestee)
8705 : : {
8706 : : /*
8707 : : * This is the case when the in-process request was made by the same
8708 : : * parent but for a different child. Since we configure only the
8709 : : * event for the request made for that child, skip the given request.
8710 : : */
8711 : 8 : return;
8712 : : }
8713 : : else
8714 [ - + ]: 207 : Assert(pendingAreq == areq);
8715 : :
8716 : 213 : AddWaitEventToSet(set, WL_SOCKET_READABLE, PQsocket(fsstate->conn),
8717 : : NULL, areq);
8718 : : }
8719 : :
8720 : : /*
8721 : : * postgresForeignAsyncNotify
8722 : : * Fetch some more tuples from a file descriptor that becomes ready,
8723 : : * requesting next tuple.
8724 : : */
8725 : : static void
8726 : 156 : postgresForeignAsyncNotify(AsyncRequest *areq)
8727 : : {
8728 : 156 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
8729 : 156 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
8730 : :
8731 : : /* The core code would have initialized the callback_pending flag */
8732 [ - + ]: 156 : Assert(!areq->callback_pending);
8733 : :
8734 : : /*
8735 : : * If process_pending_request() has been invoked on the given request
8736 : : * before we get here, we might have some tuples already; in which case
8737 : : * produce the next tuple
8738 : : */
1897 8739 [ + + ]: 156 : if (fsstate->next_tuple < fsstate->num_tuples)
8740 : : {
1897 efujita@postgresql.o 8741 :GBC 1 : produce_tuple_asynchronously(areq, true);
8742 : 1 : return;
8743 : : }
8744 : :
8745 : : /* We must have run out of tuples */
1897 efujita@postgresql.o 8746 [ - + ]:CBC 155 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
8747 : :
8748 : : /* The request should be currently in-process */
8749 [ - + ]: 155 : Assert(fsstate->conn_state->pendingAreq == areq);
8750 : :
8751 : : /* On error, report the original query, not the FETCH. */
2021 8752 [ - + ]: 155 : if (!PQconsumeInput(fsstate->conn))
440 tgl@sss.pgh.pa.us 8753 :UBC 0 : pgfdw_report_error(NULL, fsstate->conn, fsstate->query);
8754 : :
2021 efujita@postgresql.o 8755 :CBC 155 : fetch_more_data(node);
8756 : :
8757 : 155 : produce_tuple_asynchronously(areq, true);
8758 : : }
8759 : :
8760 : : /*
8761 : : * Asynchronously produce next tuple from a foreign PostgreSQL table.
8762 : : */
8763 : : static void
8764 : 6243 : produce_tuple_asynchronously(AsyncRequest *areq, bool fetch)
8765 : : {
8766 : 6243 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
8767 : 6243 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
8768 : 6243 : AsyncRequest *pendingAreq = fsstate->conn_state->pendingAreq;
8769 : : TupleTableSlot *result;
8770 : :
8771 : : /* This should not be called if the request is currently in-process */
8772 [ - + ]: 6243 : Assert(areq != pendingAreq);
8773 : :
8774 : : /* Fetch some more tuples, if we've run out */
8775 [ + + ]: 6243 : if (fsstate->next_tuple >= fsstate->num_tuples)
8776 : : {
8777 : : /* No point in another fetch if we already detected EOF, though */
8778 [ + + ]: 196 : if (!fsstate->eof_reached)
8779 : : {
8780 : : /* Mark the request as pending for a callback */
8781 : 135 : ExecAsyncRequestPending(areq);
8782 : : /* Begin another fetch if requested and if no pending request */
8783 [ + - + + ]: 135 : if (fetch && !pendingAreq)
8784 : 130 : fetch_more_data_begin(areq);
8785 : : }
8786 : : else
8787 : : {
8788 : : /* There's nothing more to do; just return a NULL pointer */
8789 : 61 : result = NULL;
8790 : : /* Mark the request as complete */
8791 : 61 : ExecAsyncRequestDone(areq, result);
8792 : : }
8793 : 196 : return;
8794 : : }
8795 : :
8796 : : /* Get a tuple from the ForeignScan node */
1979 8797 : 6047 : result = areq->requestee->ExecProcNodeReal(areq->requestee);
2021 8798 [ + - + + ]: 6047 : if (!TupIsNull(result))
8799 : : {
8800 : : /* Mark the request as complete */
8801 : 6015 : ExecAsyncRequestDone(areq, result);
8802 : 6015 : return;
8803 : : }
8804 : :
8805 : : /* We must have run out of tuples */
8806 [ - + ]: 32 : Assert(fsstate->next_tuple >= fsstate->num_tuples);
8807 : :
8808 : : /* Fetch some more tuples, if we've not detected EOF yet */
8809 [ + - ]: 32 : if (!fsstate->eof_reached)
8810 : : {
8811 : : /* Mark the request as pending for a callback */
8812 : 32 : ExecAsyncRequestPending(areq);
8813 : : /* Begin another fetch if requested and if no pending request */
8814 [ + + + - ]: 32 : if (fetch && !pendingAreq)
8815 : 30 : fetch_more_data_begin(areq);
8816 : : }
8817 : : else
8818 : : {
8819 : : /* There's nothing more to do; just return a NULL pointer */
2021 efujita@postgresql.o 8820 :UBC 0 : result = NULL;
8821 : : /* Mark the request as complete */
8822 : 0 : ExecAsyncRequestDone(areq, result);
8823 : : }
8824 : : }
8825 : :
8826 : : /*
8827 : : * Begin an asynchronous data fetch.
8828 : : *
8829 : : * Note: this function assumes there is no currently-in-progress asynchronous
8830 : : * data fetch.
8831 : : *
8832 : : * Note: fetch_more_data must be called to fetch the result.
8833 : : */
8834 : : static void
2021 efujita@postgresql.o 8835 :CBC 167 : fetch_more_data_begin(AsyncRequest *areq)
8836 : : {
8837 : 167 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
8838 : 167 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
8839 : : char sql[64];
8840 : :
8841 [ - + ]: 167 : Assert(!fsstate->conn_state->pendingAreq);
8842 : :
8843 : : /* Create the cursor synchronously. */
8844 [ + + ]: 167 : if (!fsstate->cursor_exists)
8845 : 76 : create_cursor(node);
8846 : :
8847 : : /* We will send this query, but not wait for the response. */
8848 : 166 : snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
8849 : : fsstate->fetch_size, fsstate->cursor_number);
8850 : :
1544 fujii@postgresql.org 8851 [ - + ]: 166 : if (!PQsendQuery(fsstate->conn, sql))
440 tgl@sss.pgh.pa.us 8852 :UBC 0 : pgfdw_report_error(NULL, fsstate->conn, fsstate->query);
8853 : :
8854 : : /* Remember that the request is in process */
2021 efujita@postgresql.o 8855 :CBC 166 : fsstate->conn_state->pendingAreq = areq;
8856 : 166 : }
8857 : :
8858 : : /*
8859 : : * Process a pending asynchronous request.
8860 : : */
8861 : : void
8862 : 10 : process_pending_request(AsyncRequest *areq)
8863 : : {
8864 : 10 : ForeignScanState *node = (ForeignScanState *) areq->requestee;
1777 dgustafsson@postgres 8865 : 10 : PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
8866 : :
8867 : : /* The request would have been pending for a callback */
1900 efujita@postgresql.o 8868 [ - + ]: 10 : Assert(areq->callback_pending);
8869 : :
8870 : : /* The request should be currently in-process */
2021 8871 [ - + ]: 10 : Assert(fsstate->conn_state->pendingAreq == areq);
8872 : :
1900 8873 : 10 : fetch_more_data(node);
8874 : :
8875 : : /*
8876 : : * If we didn't get any tuples, must be end of data; complete the request
8877 : : * now. Otherwise, we postpone completing the request until we are called
8878 : : * from postgresForeignAsyncConfigureWait()/postgresForeignAsyncNotify().
8879 : : */
8880 [ - + ]: 10 : if (fsstate->next_tuple >= fsstate->num_tuples)
8881 : : {
8882 : : /* Unlike AsyncNotify, we unset callback_pending ourselves */
1900 efujita@postgresql.o 8883 :UBC 0 : areq->callback_pending = false;
8884 : : /* Mark the request as complete */
8885 : 0 : ExecAsyncRequestDone(areq, NULL);
8886 : : /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */
8887 : 0 : ExecAsyncResponse(areq);
8888 : : }
1900 efujita@postgresql.o 8889 :CBC 10 : }
8890 : :
8891 : : /*
8892 : : * Complete a pending asynchronous request.
8893 : : */
8894 : : static void
8895 : 4 : complete_pending_request(AsyncRequest *areq)
8896 : : {
8897 : : /* The request would have been pending for a callback */
2021 8898 [ - + ]: 4 : Assert(areq->callback_pending);
8899 : :
8900 : : /* Unlike AsyncNotify, we unset callback_pending ourselves */
8901 : 4 : areq->callback_pending = false;
8902 : :
8903 : : /* We begin a fetch afterwards if necessary; don't fetch */
8904 : 4 : produce_tuple_asynchronously(areq, false);
8905 : :
8906 : : /* Unlike AsyncNotify, we call ExecAsyncResponse ourselves */
8907 : 4 : ExecAsyncResponse(areq);
8908 : :
8909 : : /* Also, we do instrumentation ourselves, if required */
1979 8910 [ + + ]: 4 : if (areq->requestee->instrument)
8911 : 1 : InstrUpdateTupleCount(areq->requestee->instrument,
8912 [ + - - + ]: 1 : TupIsNull(areq->result) ? 0.0 : 1.0);
2021 8913 : 4 : }
8914 : :
8915 : : /*
8916 : : * Create a tuple from the specified row of the PGresult.
8917 : : *
8918 : : * rel is the local representation of the foreign table, attinmeta is
8919 : : * conversion data for the rel's tupdesc, and retrieved_attrs is an
8920 : : * integer list of the table column numbers present in the PGresult.
8921 : : * fsstate is the ForeignScan plan node's execution state.
8922 : : * temp_context is a working context that can be reset after each tuple.
8923 : : *
8924 : : * Note: either rel or fsstate, but not both, can be NULL. rel is NULL
8925 : : * if we're processing a remote join, while fsstate is NULL in a non-query
8926 : : * context such as ANALYZE, or if we're processing a non-scan query node.
8927 : : */
8928 : : static HeapTuple
4981 tgl@sss.pgh.pa.us 8929 : 95642 : make_tuple_from_result_row(PGresult *res,
8930 : : int row,
8931 : : Relation rel,
8932 : : AttInMetadata *attinmeta,
8933 : : List *retrieved_attrs,
8934 : : ForeignScanState *fsstate,
8935 : : MemoryContext temp_context)
8936 : : {
8937 : : HeapTuple tuple;
8938 : : TupleDesc tupdesc;
8939 : : Datum *values;
8940 : : bool *nulls;
4964 8941 : 95642 : ItemPointer ctid = NULL;
8942 : : ConversionLocation errpos;
8943 : : ErrorContextCallback errcallback;
8944 : : MemoryContext oldcontext;
8945 : : ListCell *lc;
8946 : : int j;
8947 : :
4981 8948 [ - + ]: 95642 : Assert(row < PQntuples(res));
8949 : :
8950 : : /*
8951 : : * Do the following work in a temp context that we reset after each tuple.
8952 : : * This cleans up not only the data we have direct access to, but any
8953 : : * cruft the I/O functions might leak.
8954 : : */
8955 : 95642 : oldcontext = MemoryContextSwitchTo(temp_context);
8956 : :
8957 : : /*
8958 : : * Get the tuple descriptor for the row. Use the rel's tupdesc if rel is
8959 : : * provided, otherwise look to the scan node's ScanTupleSlot.
8960 : : */
3898 rhaas@postgresql.org 8961 [ + + ]: 95642 : if (rel)
8962 : 59597 : tupdesc = RelationGetDescr(rel);
8963 : : else
8964 : : {
8965 [ - + ]: 36045 : Assert(fsstate);
3169 8966 : 36045 : tupdesc = fsstate->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
8967 : : }
8968 : :
56 michael@paquier.xyz 8969 :GNC 95642 : values = palloc0_array(Datum, tupdesc->natts);
8970 : 95642 : nulls = palloc_array(bool, tupdesc->natts);
8971 : : /* Initialize to nulls for any columns not present in result */
4952 tgl@sss.pgh.pa.us 8972 :CBC 95642 : memset(nulls, true, tupdesc->natts * sizeof(bool));
8973 : :
8974 : : /*
8975 : : * Set up and install callback to report where conversion error occurs.
8976 : : */
4981 8977 : 95642 : errpos.cur_attno = 0;
1832 8978 : 95642 : errpos.rel = rel;
3898 rhaas@postgresql.org 8979 : 95642 : errpos.fsstate = fsstate;
4981 tgl@sss.pgh.pa.us 8980 : 95642 : errcallback.callback = conversion_error_callback;
683 peter@eisentraut.org 8981 : 95642 : errcallback.arg = &errpos;
4981 tgl@sss.pgh.pa.us 8982 : 95642 : errcallback.previous = error_context_stack;
8983 : 95642 : error_context_stack = &errcallback;
8984 : :
8985 : : /*
8986 : : * i indexes columns in the relation, j indexes columns in the PGresult.
8987 : : */
4952 8988 : 95642 : j = 0;
8989 [ + + + + : 364124 : foreach(lc, retrieved_attrs)
+ + ]
8990 : : {
8991 : 268487 : int i = lfirst_int(lc);
8992 : : const char *valstr;
8993 : :
8994 : : /* fetch next column's textual value */
4981 8995 [ + + ]: 268487 : if (PQgetisnull(res, row, j))
8996 : 10776 : valstr = NULL;
8997 : : else
8998 : 257711 : valstr = PQgetvalue(res, row, j);
8999 : :
9000 : : /*
9001 : : * convert value to internal representation
9002 : : *
9003 : : * Note: we ignore system columns other than ctid and oid in result
9004 : : */
3863 rhaas@postgresql.org 9005 : 268487 : errpos.cur_attno = i;
4952 tgl@sss.pgh.pa.us 9006 [ + + ]: 268487 : if (i > 0)
9007 : : {
9008 : : /* ordinary column */
9009 [ - + ]: 265369 : Assert(i <= tupdesc->natts);
9010 : 265369 : nulls[i - 1] = (valstr == NULL);
9011 : : /* Apply the input function even to nulls, to support domains */
9012 : 265364 : values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
9013 : : valstr,
9014 : 265369 : attinmeta->attioparams[i - 1],
9015 : 265369 : attinmeta->atttypmods[i - 1]);
9016 : : }
9017 [ + - ]: 3118 : else if (i == SelfItemPointerAttributeNumber)
9018 : : {
9019 : : /* ctid */
9020 [ + - ]: 3118 : if (valstr != NULL)
9021 : : {
9022 : : Datum datum;
9023 : :
9024 : 3118 : datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
9025 : 3118 : ctid = (ItemPointer) DatumGetPointer(datum);
9026 : : }
9027 : : }
3863 rhaas@postgresql.org 9028 : 268482 : errpos.cur_attno = 0;
9029 : :
4964 tgl@sss.pgh.pa.us 9030 : 268482 : j++;
9031 : : }
9032 : :
9033 : : /* Uninstall error context callback. */
4981 9034 : 95637 : error_context_stack = errcallback.previous;
9035 : :
9036 : : /*
9037 : : * Check we got the expected number of columns. Note: j == 0 and
9038 : : * PQnfields == 1 is expected, since deparse emits a NULL if no columns.
9039 : : */
4952 9040 [ + + - + ]: 95637 : if (j > 0 && j != PQnfields(res))
4981 tgl@sss.pgh.pa.us 9041 [ # # ]:UBC 0 : elog(ERROR, "remote query result does not match the foreign table");
9042 : :
9043 : : /*
9044 : : * Build the result tuple in caller's memory context.
9045 : : */
4981 tgl@sss.pgh.pa.us 9046 :CBC 95637 : MemoryContextSwitchTo(oldcontext);
9047 : :
9048 : 95637 : tuple = heap_form_tuple(tupdesc, values, nulls);
9049 : :
9050 : : /*
9051 : : * If we have a CTID to return, install it in both t_self and t_ctid.
9052 : : * t_self is the normal place, but if the tuple is converted to a
9053 : : * composite Datum, t_self will be lost; setting t_ctid allows CTID to be
9054 : : * preserved during EvalPlanQual re-evaluations (see ROW_MARK_COPY code).
9055 : : */
4964 9056 [ + + ]: 95637 : if (ctid)
4170 9057 : 3118 : tuple->t_self = tuple->t_data->t_ctid = *ctid;
9058 : :
9059 : : /*
9060 : : * Stomp on the xmin, xmax, and cmin fields from the tuple created by
9061 : : * heap_form_tuple. heap_form_tuple actually creates the tuple with
9062 : : * DatumTupleFields, not HeapTupleFields, but the executor expects
9063 : : * HeapTupleFields and will happily extract system columns on that
9064 : : * assumption. If we don't do this then, for example, the tuple length
9065 : : * ends up in the xmin field, which isn't what we want.
9066 : : */
3832 rhaas@postgresql.org 9067 : 95637 : HeapTupleHeaderSetXmax(tuple->t_data, InvalidTransactionId);
9068 : 95637 : HeapTupleHeaderSetXmin(tuple->t_data, InvalidTransactionId);
9069 : 95637 : HeapTupleHeaderSetCmin(tuple->t_data, InvalidTransactionId);
9070 : :
9071 : : /* Clean up */
4981 tgl@sss.pgh.pa.us 9072 : 95637 : MemoryContextReset(temp_context);
9073 : :
9074 : 95637 : return tuple;
9075 : : }
9076 : :
9077 : : /*
9078 : : * Callback function which is called when error occurs during column value
9079 : : * conversion. Print names of column and relation.
9080 : : *
9081 : : * Note that this function mustn't do any catalog lookups, since we are in
9082 : : * an already-failed transaction. Fortunately, we can get the needed info
9083 : : * from the relation or the query's rangetable instead.
9084 : : */
9085 : : static void
9086 : 5 : conversion_error_callback(void *arg)
9087 : : {
1924 9088 : 5 : ConversionLocation *errpos = (ConversionLocation *) arg;
1832 9089 : 5 : Relation rel = errpos->rel;
1924 9090 : 5 : ForeignScanState *fsstate = errpos->fsstate;
3898 rhaas@postgresql.org 9091 : 5 : const char *attname = NULL;
9092 : 5 : const char *relname = NULL;
3755 9093 : 5 : bool is_wholerow = false;
9094 : :
9095 : : /*
9096 : : * If we're in a scan node, always use aliases from the rangetable, for
9097 : : * consistency between the simple-relation and remote-join cases. Look at
9098 : : * the relation's tupdesc only if we're not in a scan node.
9099 : : */
1832 tgl@sss.pgh.pa.us 9100 [ + + ]: 5 : if (fsstate)
9101 : : {
9102 : : /* ForeignScan case */
9103 : 4 : ForeignScan *fsplan = castNode(ForeignScan, fsstate->ss.ps.plan);
9104 : 4 : int varno = 0;
9105 : 4 : AttrNumber colno = 0;
9106 : :
9107 [ + + ]: 4 : if (fsplan->scan.scanrelid > 0)
9108 : : {
9109 : : /* error occurred in a scan against a foreign table */
9110 : 1 : varno = fsplan->scan.scanrelid;
9111 : 1 : colno = errpos->cur_attno;
9112 : : }
9113 : : else
9114 : : {
9115 : : /* error occurred in a scan against a foreign join */
9116 : : TargetEntry *tle;
9117 : :
9118 : 3 : tle = list_nth_node(TargetEntry, fsplan->fdw_scan_tlist,
9119 : : errpos->cur_attno - 1);
9120 : :
9121 : : /*
9122 : : * Target list can have Vars and expressions. For Vars, we can
9123 : : * get some information, however for expressions we can't. Thus
9124 : : * for expressions, just show generic context message.
9125 : : */
9126 [ + + ]: 3 : if (IsA(tle->expr, Var))
9127 : : {
9128 : 2 : Var *var = (Var *) tle->expr;
9129 : :
9130 : 2 : varno = var->varno;
9131 : 2 : colno = var->varattno;
9132 : : }
9133 : : }
9134 : :
9135 [ + + ]: 4 : if (varno > 0)
9136 : : {
9137 : 3 : EState *estate = fsstate->ss.ps.state;
9138 : 3 : RangeTblEntry *rte = exec_rt_fetch(varno, estate);
9139 : :
9140 : 3 : relname = rte->eref->aliasname;
9141 : :
9142 [ + + ]: 3 : if (colno == 0)
9143 : 1 : is_wholerow = true;
9144 [ + - + - ]: 2 : else if (colno > 0 && colno <= list_length(rte->eref->colnames))
9145 : 2 : attname = strVal(list_nth(rte->eref->colnames, colno - 1));
1832 tgl@sss.pgh.pa.us 9146 [ # # ]:UBC 0 : else if (colno == SelfItemPointerAttributeNumber)
9147 : 0 : attname = "ctid";
9148 : : }
9149 : : }
1832 tgl@sss.pgh.pa.us 9150 [ + - ]:CBC 1 : else if (rel)
9151 : : {
9152 : : /* Non-ForeignScan case (we should always have a rel here) */
9153 : 1 : TupleDesc tupdesc = RelationGetDescr(rel);
9154 : :
9155 : 1 : relname = RelationGetRelationName(rel);
9156 [ + - + - ]: 1 : if (errpos->cur_attno > 0 && errpos->cur_attno <= tupdesc->natts)
9157 : 1 : {
9158 : 1 : Form_pg_attribute attr = TupleDescAttr(tupdesc,
9159 : 1 : errpos->cur_attno - 1);
9160 : :
9161 : 1 : attname = NameStr(attr->attname);
9162 : : }
1832 tgl@sss.pgh.pa.us 9163 [ # # ]:UBC 0 : else if (errpos->cur_attno == SelfItemPointerAttributeNumber)
1924 9164 : 0 : attname = "ctid";
9165 : : }
9166 : :
1924 tgl@sss.pgh.pa.us 9167 [ + + + + ]:CBC 5 : if (relname && is_wholerow)
9168 : 1 : errcontext("whole-row reference to foreign table \"%s\"", relname);
9169 [ + + + - ]: 4 : else if (relname && attname)
9170 : 3 : errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
9171 : : else
9172 : 1 : errcontext("processing expression at position %d in select list",
9173 : 1 : errpos->cur_attno);
4981 9174 : 5 : }
9175 : :
9176 : : /*
9177 : : * Given an EquivalenceClass and a foreign relation, find an EC member
9178 : : * that can be used to sort the relation remotely according to a pathkey
9179 : : * using this EC.
9180 : : *
9181 : : * If there is more than one suitable candidate, return an arbitrary
9182 : : * one of them. If there is none, return NULL.
9183 : : *
9184 : : * This checks that the EC member expression uses only Vars from the given
9185 : : * rel and is shippable. Caller must separately verify that the pathkey's
9186 : : * ordering operator is shippable.
9187 : : */
9188 : : EquivalenceMember *
1656 9189 : 1892 : find_em_for_rel(PlannerInfo *root, EquivalenceClass *ec, RelOptInfo *rel)
9190 : : {
1042 akorotkov@postgresql 9191 : 1892 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
9192 : : EquivalenceMemberIterator it;
9193 : : EquivalenceMember *em;
9194 : :
552 drowley@postgresql.o 9195 : 1892 : setup_eclass_member_iterator(&it, ec, rel->relids);
9196 [ + + ]: 3136 : while ((em = eclass_member_iterator_next(&it)) != NULL)
9197 : : {
9198 : : /*
9199 : : * Note we require !bms_is_empty, else we'd accept constant
9200 : : * expressions which are not suitable for the purpose.
9201 : : */
1656 tgl@sss.pgh.pa.us 9202 [ + + ]: 2848 : if (bms_is_subset(em->em_relids, rel->relids) &&
9203 [ + + + + ]: 3265 : !bms_is_empty(em->em_relids) &&
1042 akorotkov@postgresql 9204 [ + + ]: 3252 : bms_is_empty(bms_intersect(em->em_relids, fpinfo->hidden_subquery_rels)) &&
54 akorotkov@postgresql 9205 :GNC 1620 : is_foreign_expr(root, rel, fpinfo, em->em_expr))
1656 tgl@sss.pgh.pa.us 9206 :CBC 1604 : return em;
9207 : : }
9208 : :
9209 : 288 : return NULL;
9210 : : }
9211 : :
9212 : : /*
9213 : : * Find an EquivalenceClass member that is to be computed as a sort column
9214 : : * in the given rel's reltarget, and is shippable.
9215 : : *
9216 : : * If there is more than one suitable candidate, return an arbitrary
9217 : : * one of them. If there is none, return NULL.
9218 : : *
9219 : : * This checks that the EC member expression uses only Vars from the given
9220 : : * rel and is shippable. Caller must separately verify that the pathkey's
9221 : : * ordering operator is shippable.
9222 : : */
9223 : : EquivalenceMember *
9224 : 261 : find_em_for_rel_target(PlannerInfo *root, EquivalenceClass *ec,
9225 : : RelOptInfo *rel)
9226 : : {
54 akorotkov@postgresql 9227 :GNC 261 : PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) rel->fdw_private;
1656 tgl@sss.pgh.pa.us 9228 :CBC 261 : PathTarget *target = rel->reltarget;
9229 : : ListCell *lc1;
9230 : : int i;
9231 : :
2750 efujita@postgresql.o 9232 : 261 : i = 0;
9233 [ + - + - : 431 : foreach(lc1, target->exprs)
+ - ]
9234 : : {
9235 : 431 : Expr *expr = (Expr *) lfirst(lc1);
9236 [ + - ]: 431 : Index sgref = get_pathtarget_sortgroupref(target, i);
9237 : : ListCell *lc2;
9238 : :
9239 : : /* Ignore non-sort expressions */
9240 [ + + + + ]: 777 : if (sgref == 0 ||
9241 : 346 : get_sortgroupref_clause_noerr(sgref,
9242 : 346 : root->parse->sortClause) == NULL)
9243 : : {
9244 : 93 : i++;
9245 : 93 : continue;
9246 : : }
9247 : :
9248 : : /* We ignore binary-compatible relabeling on both ends */
9249 [ + - - + ]: 338 : while (expr && IsA(expr, RelabelType))
2750 efujita@postgresql.o 9250 :UBC 0 : expr = ((RelabelType *) expr)->arg;
9251 : :
9252 : : /*
9253 : : * Locate an EquivalenceClass member matching this expr, if any.
9254 : : * Ignore child members.
9255 : : */
2750 efujita@postgresql.o 9256 [ + - + + :CBC 419 : foreach(lc2, ec->ec_members)
+ + ]
9257 : : {
9258 : 342 : EquivalenceMember *em = (EquivalenceMember *) lfirst(lc2);
9259 : : Expr *em_expr;
9260 : :
9261 : : /* Don't match constants */
9262 [ - + ]: 342 : if (em->em_is_const)
2750 efujita@postgresql.o 9263 :UBC 0 : continue;
9264 : :
9265 : : /* Child members should not exist in ec_members */
552 drowley@postgresql.o 9266 [ - + ]:CBC 342 : Assert(!em->em_is_child);
9267 : :
9268 : : /* Match if same expression (after stripping relabel) */
2750 efujita@postgresql.o 9269 : 342 : em_expr = em->em_expr;
9270 [ + - + + ]: 354 : while (em_expr && IsA(em_expr, RelabelType))
9271 : 12 : em_expr = ((RelabelType *) em_expr)->arg;
9272 : :
1656 tgl@sss.pgh.pa.us 9273 [ + + ]: 342 : if (!equal(em_expr, expr))
9274 : 81 : continue;
9275 : :
9276 : : /* Check that expression (including relabels!) is shippable */
54 akorotkov@postgresql 9277 [ + - ]:GNC 261 : if (is_foreign_expr(root, rel, fpinfo, em->em_expr))
1656 tgl@sss.pgh.pa.us 9278 :CBC 261 : return em;
9279 : : }
9280 : :
2750 efujita@postgresql.o 9281 : 77 : i++;
9282 : : }
9283 : :
1656 tgl@sss.pgh.pa.us 9284 :UBC 0 : return NULL;
9285 : : }
9286 : :
9287 : : /*
9288 : : * Determine batch size for a given foreign table. The option specified for
9289 : : * a table has precedence.
9290 : : */
9291 : : static int
2091 tomas.vondra@postgre 9292 :CBC 147 : get_batch_size_option(Relation rel)
9293 : : {
1979 tgl@sss.pgh.pa.us 9294 : 147 : Oid foreigntableid = RelationGetRelid(rel);
9295 : : ForeignTable *table;
9296 : : ForeignServer *server;
9297 : : List *options;
9298 : : ListCell *lc;
9299 : :
9300 : : /* we use 1 by default, which means "no batching" */
9301 : 147 : int batch_size = 1;
9302 : :
9303 : : /*
9304 : : * Load options for table and server. We append server options after table
9305 : : * options, because table options take precedence.
9306 : : */
2091 tomas.vondra@postgre 9307 : 147 : table = GetForeignTable(foreigntableid);
9308 : 147 : server = GetForeignServer(table->serverid);
9309 : :
9310 : 147 : options = NIL;
9311 : 147 : options = list_concat(options, table->options);
9312 : 147 : options = list_concat(options, server->options);
9313 : :
9314 : : /* See if either table or server specifies batch_size. */
9315 [ + - + + : 773 : foreach(lc, options)
+ + ]
9316 : : {
9317 : 661 : DefElem *def = (DefElem *) lfirst(lc);
9318 : :
9319 [ + + ]: 661 : if (strcmp(def->defname, "batch_size") == 0)
9320 : : {
1923 fujii@postgresql.org 9321 : 35 : (void) parse_int(defGetString(def), &batch_size, 0, NULL);
2091 tomas.vondra@postgre 9322 : 35 : break;
9323 : : }
9324 : : }
9325 : :
9326 : 147 : return batch_size;
9327 : : }
|