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