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