1 /*-------------------------------------------------------------------------
2  *
3  * deparse.c
4  * 		Query deparser for mysql_fdw
5  *
6  * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group
7  * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation.
8  *
9  * IDENTIFICATION
10  * 		deparse.c
11  *
12  *-------------------------------------------------------------------------
13  */
14 #include "postgres.h"
15 
16 #include "access/heapam.h"
17 #include "access/htup_details.h"
18 #include "access/sysattr.h"
19 #include "access/transam.h"
20 #include "catalog/pg_collation.h"
21 #include "catalog/pg_namespace.h"
22 #include "catalog/pg_operator.h"
23 #include "catalog/pg_proc.h"
24 #include "catalog/pg_type.h"
25 #include "commands/defrem.h"
26 #include "datatype/timestamp.h"
27 #include "mysql_fdw.h"
28 #include "nodes/nodeFuncs.h"
29 #include "nodes/plannodes.h"
30 #include "optimizer/clauses.h"
31 #if PG_VERSION_NUM < 120000
32 #include "optimizer/var.h"
33 #else
34 #include "optimizer/optimizer.h"
35 #endif
36 #include "optimizer/prep.h"
37 #include "optimizer/tlist.h"
38 #include "parser/parsetree.h"
39 #include "pgtime.h"
40 #include "utils/builtins.h"
41 #include "utils/lsyscache.h"
42 #include "utils/syscache.h"
43 #include "utils/timestamp.h"
44 
45 
46 static char *mysql_quote_identifier(const char *str, char quotechar);
47 
48 /*
49  * Global context for foreign_expr_walker's search of an expression tree.
50  */
51 typedef struct foreign_glob_cxt
52 {
53 	PlannerInfo *root;			/* global planner state */
54 	RelOptInfo *foreignrel;		/* the foreign relation we are planning for */
55 
56 	/*
57 	 * For join pushdown, only a limited set of operators are allowed to be
58 	 * pushed.  This flag helps us identify if we are walking through the list
59 	 * of join conditions.
60 	 */
61 	bool		is_join_cond;	/* true for join relations */
62 } foreign_glob_cxt;
63 
64 /*
65  * Local (per-tree-level) context for foreign_expr_walker's search.
66  * This is concerned with identifying collations used in the expression.
67  */
68 typedef enum
69 {
70 	FDW_COLLATE_NONE,			/* expression is of a noncollatable type */
71 	FDW_COLLATE_SAFE,			/* collation derives from a foreign Var */
72 	FDW_COLLATE_UNSAFE			/* collation derives from something else */
73 } FDWCollateState;
74 
75 typedef struct foreign_loc_cxt
76 {
77 	Oid			collation;		/* OID of current collation, if any */
78 	FDWCollateState state;		/* state of current collation choice */
79 } foreign_loc_cxt;
80 
81 /*
82  * Context for deparseExpr
83  */
84 typedef struct deparse_expr_cxt
85 {
86 	PlannerInfo *root;			/* global planner state */
87 	RelOptInfo *foreignrel;		/* the foreign relation we are planning for */
88 	StringInfo	buf;			/* output buffer to append to */
89 	List	  **params_list;	/* exprs that will become remote Params */
90 } deparse_expr_cxt;
91 
92 #define REL_ALIAS_PREFIX	"r"
93 /* Handy macro to add relation name qualification */
94 #define ADD_REL_QUALIFIER(buf, varno)	\
95 		appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno))
96 
97 /*
98  * Functions to construct string representation of a node tree.
99  */
100 static void deparseExpr(Expr *expr, deparse_expr_cxt *context);
101 static void mysql_deparse_var(Var *node, deparse_expr_cxt *context);
102 static void mysql_deparse_const(Const *node, deparse_expr_cxt *context);
103 static void mysql_deparse_param(Param *node, deparse_expr_cxt *context);
104 #if PG_VERSION_NUM < 120000
105 static void mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context);
106 #else
107 static void mysql_deparse_array_ref(SubscriptingRef *node,
108 									deparse_expr_cxt *context);
109 #endif
110 static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context);
111 static void mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context);
112 static void mysql_deparse_operator_name(StringInfo buf,
113 										Form_pg_operator opform);
114 static void mysql_deparse_distinct_expr(DistinctExpr *node,
115 										deparse_expr_cxt *context);
116 static void mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node,
117 											   deparse_expr_cxt *context);
118 static void mysql_deparse_relabel_type(RelabelType *node,
119 									   deparse_expr_cxt *context);
120 static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context);
121 static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context);
122 static void mysql_deparse_array_expr(ArrayExpr *node,
123 									 deparse_expr_cxt *context);
124 static void mysql_print_remote_param(int paramindex, Oid paramtype,
125 									 int32 paramtypmod,
126 									 deparse_expr_cxt *context);
127 static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod,
128 										   deparse_expr_cxt *context);
129 static void mysql_deparse_relation(StringInfo buf, Relation rel);
130 static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root,
131 									  Index rtindex, Relation rel,
132 									  Bitmapset *attrs_used,
133 									  List **retrieved_attrs);
134 static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno,
135 									 PlannerInfo *root, bool qualify_col);
136 static void mysql_deparse_select_sql(List *tlist, List **retrieved_attrs,
137 									 deparse_expr_cxt *context);
138 static void mysql_append_conditions(List *exprs, deparse_expr_cxt *context);
139 static void mysql_deparse_explicit_target_list(List *tlist,
140 											   List **retrieved_attrs,
141 											   deparse_expr_cxt *context);
142 static void mysql_deparse_from_expr(StringInfo buf, PlannerInfo *root,
143 									RelOptInfo *foreignrel, bool use_alias,
144 									List **param_list);
145 
146 /*
147  * Functions to construct string representation of a specific types.
148  */
149 static void deparse_interval(StringInfo buf, Datum datum);
150 
151 /*
152  * Local variables.
153  */
154 static char *cur_opname = NULL;
155 
156 /*
157  * Append remote name of specified foreign table to buf.  Use value of
158  * table_name FDW option (if any) instead of relation's name.  Similarly,
159  * schema_name FDW option overrides schema name.
160  */
161 static void
mysql_deparse_relation(StringInfo buf,Relation rel)162 mysql_deparse_relation(StringInfo buf, Relation rel)
163 {
164 	ForeignTable *table;
165 	const char *nspname = NULL;
166 	const char *relname = NULL;
167 	ListCell   *lc;
168 
169 	/* Obtain additional catalog information. */
170 	table = GetForeignTable(RelationGetRelid(rel));
171 
172 	/*
173 	 * Use value of FDW options if any, instead of the name of object itself.
174 	 */
175 	foreach(lc, table->options)
176 	{
177 		DefElem    *def = (DefElem *) lfirst(lc);
178 
179 		if (strcmp(def->defname, "dbname") == 0)
180 			nspname = defGetString(def);
181 		else if (strcmp(def->defname, "table_name") == 0)
182 			relname = defGetString(def);
183 	}
184 
185 	/*
186 	 * Note: we could skip printing the schema name if it's pg_catalog, but
187 	 * that doesn't seem worth the trouble.
188 	 */
189 	if (nspname == NULL)
190 		nspname = get_namespace_name(RelationGetNamespace(rel));
191 	if (relname == NULL)
192 		relname = RelationGetRelationName(rel);
193 
194 	appendStringInfo(buf, "%s.%s", mysql_quote_identifier(nspname, '`'),
195 					 mysql_quote_identifier(relname, '`'));
196 }
197 
198 static char *
mysql_quote_identifier(const char * str,char quotechar)199 mysql_quote_identifier(const char *str, char quotechar)
200 {
201 	char	   *result = palloc(strlen(str) * 2 + 3);
202 	char	   *res = result;
203 
204 	*res++ = quotechar;
205 	while (*str)
206 	{
207 		if (*str == quotechar)
208 			*res++ = *str;
209 		*res++ = *str;
210 		str++;
211 	}
212 	*res++ = quotechar;
213 	*res++ = '\0';
214 
215 	return result;
216 }
217 
218 /*
219  * mysql_deparse_select_stmt_for_rel
220  * 		Deparse SELECT statement for given relation into buf.
221  *
222  * tlist contains the list of desired columns to be fetched from foreign
223  * server.  For a base relation fpinfo->attrs_used is used to construct
224  * SELECT clause, hence the tlist is ignored for a base relation.
225  *
226  * remote_conds is the list of conditions to be deparsed into the WHERE clause.
227  *
228  * If params_list is not NULL, it receives a list of Params and other-relation
229  * Vars used in the clauses; these values must be transmitted to the remote
230  * server as parameter values.
231  *
232  * If params_list is NULL, we're generating the query for EXPLAIN purposes,
233  * so Params and other-relation Vars should be replaced by dummy values.
234  *
235  * List of columns selected is returned in retrieved_attrs.
236  */
237 extern void
mysql_deparse_select_stmt_for_rel(StringInfo buf,PlannerInfo * root,RelOptInfo * rel,List * tlist,List * remote_conds,List ** retrieved_attrs,List ** params_list)238 mysql_deparse_select_stmt_for_rel(StringInfo buf, PlannerInfo *root,
239 								  RelOptInfo *rel, List *tlist,
240 								  List *remote_conds, List **retrieved_attrs,
241 								  List **params_list)
242 {
243 	deparse_expr_cxt context;
244 
245 	/* We handle relations for foreign tables and joins between those */
246 #if PG_VERSION_NUM >= 100000
247 	Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel));
248 #else
249 	Assert(rel->reloptkind == RELOPT_JOINREL ||
250 		   rel->reloptkind == RELOPT_BASEREL ||
251 		   rel->reloptkind == RELOPT_OTHER_MEMBER_REL);
252 #endif
253 
254 	/* Fill portions of context common to base relation */
255 	context.buf = buf;
256 	context.root = root;
257 	context.foreignrel = rel;
258 	context.params_list = params_list;
259 
260 	/* Construct SELECT clause and FROM clause */
261 	mysql_deparse_select_sql(tlist, retrieved_attrs, &context);
262 
263 	/* Construct WHERE clause */
264 	if (remote_conds != NIL)
265 	{
266 		appendStringInfoString(buf, " WHERE ");
267 		mysql_append_conditions(remote_conds, &context);
268 	}
269 }
270 
271 /*
272  * mysql_deparse_select_sql
273  * 		Construct a simple SELECT statement that retrieves desired columns
274  * 		of the specified foreign table, and append it to "buf".  The output
275  * 		contains just "SELECT ... FROM ....".
276  *
277  * tlist is the list of desired columns.  Read prologue of
278  * mysql_deparse_select_stmt_for_rel() for details.
279  *
280  * We also create an integer List of the columns being retrieved, which is
281  * returned to *retrieved_attrs.
282  */
283 static void
mysql_deparse_select_sql(List * tlist,List ** retrieved_attrs,deparse_expr_cxt * context)284 mysql_deparse_select_sql(List *tlist, List **retrieved_attrs,
285 						 deparse_expr_cxt *context)
286 {
287 	StringInfo	buf = context->buf;
288 	RelOptInfo *foreignrel = context->foreignrel;
289 	PlannerInfo *root = context->root;
290 
291 	/*
292 	 * Construct SELECT list
293 	 */
294 	appendStringInfoString(buf, "SELECT ");
295 
296 #if PG_VERSION_NUM >= 100000
297 	if (IS_JOIN_REL(foreignrel))
298 #else
299 	if (foreignrel->reloptkind == RELOPT_JOINREL)
300 #endif
301 	{
302 		/* For a join relation use the input tlist */
303 		mysql_deparse_explicit_target_list(tlist, retrieved_attrs, context);
304 
305 		/*
306 		 * Construct FROM clause
307 		 */
308 		appendStringInfoString(buf, " FROM ");
309 		mysql_deparse_from_expr(buf, root, foreignrel, true, context->params_list);
310 	}
311 	else
312 	{
313 		RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root);
314 		Relation	rel;
315 		MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private;
316 
317 		/*
318 		 * Core code already has some lock on each rel being planned, so we can
319 		 * use NoLock here.
320 		 */
321 #if PG_VERSION_NUM < 130000
322 		rel = heap_open(rte->relid, NoLock);
323 #else
324 		rel = table_open(rte->relid, NoLock);
325 #endif
326 
327 		mysql_deparse_target_list(buf, root, foreignrel->relid, rel,
328 								  fpinfo->attrs_used, retrieved_attrs);
329 
330 		/*
331 		 * Construct FROM clause
332 		 */
333 		appendStringInfoString(buf, " FROM ");
334 		mysql_deparse_relation(buf, rel);
335 
336 #if PG_VERSION_NUM < 130000
337 		heap_close(rel, NoLock);
338 #else
339 		table_close(rel, NoLock);
340 #endif
341 	}
342 }
343 
344 /*
345  * mysql_deparse_explicit_target_list
346  * 		Deparse given targetlist and append it to context->buf.
347  *
348  * retrieved_attrs is the list of continuously increasing integers starting
349  * from 1.  It has same number of entries as tlist.
350  */
351 static void
mysql_deparse_explicit_target_list(List * tlist,List ** retrieved_attrs,deparse_expr_cxt * context)352 mysql_deparse_explicit_target_list(List *tlist, List **retrieved_attrs,
353 								   deparse_expr_cxt *context)
354 {
355 	ListCell   *lc;
356 	StringInfo	buf = context->buf;
357 	int			i = 0;
358 
359 	*retrieved_attrs = NIL;
360 
361 	foreach(lc, tlist)
362 	{
363 		Var		   *var;
364 
365 		var = (Var *) lfirst(lc);
366 		/* We expect only Var nodes here */
367 		Assert(IsA(var, Var));
368 
369 		if (i > 0)
370 			appendStringInfoString(buf, ", ");
371 		mysql_deparse_var(var, context);
372 
373 		*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
374 
375 		i++;
376 	}
377 
378 	if (i == 0)
379 		appendStringInfoString(buf, "NULL");
380 }
381 
382 /*
383  * Deparse remote INSERT statement
384  *
385  * The statement text is appended to buf, and we also create an integer List
386  * of the columns being retrieved by RETURNING (if any), which is returned
387  * to *retrieved_attrs.
388  */
389 void
mysql_deparse_insert(StringInfo buf,PlannerInfo * root,Index rtindex,Relation rel,List * targetAttrs)390 mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex,
391 					 Relation rel, List *targetAttrs)
392 {
393 	ListCell   *lc;
394 
395 	appendStringInfoString(buf, "INSERT INTO ");
396 	mysql_deparse_relation(buf, rel);
397 
398 	if (targetAttrs)
399 	{
400 		AttrNumber	pindex;
401 		bool		first;
402 
403 		appendStringInfoChar(buf, '(');
404 
405 		first = true;
406 		foreach(lc, targetAttrs)
407 		{
408 			int			attnum = lfirst_int(lc);
409 
410 			if (!first)
411 				appendStringInfoString(buf, ", ");
412 			first = false;
413 
414 			mysql_deparse_column_ref(buf, rtindex, attnum, root, false);
415 		}
416 
417 		appendStringInfoString(buf, ") VALUES (");
418 
419 		pindex = 1;
420 		first = true;
421 		foreach(lc, targetAttrs)
422 		{
423 			if (!first)
424 				appendStringInfoString(buf, ", ");
425 			first = false;
426 
427 			appendStringInfo(buf, "?");
428 			pindex++;
429 		}
430 
431 		appendStringInfoChar(buf, ')');
432 	}
433 	else
434 		appendStringInfoString(buf, " DEFAULT VALUES");
435 }
436 
437 void
mysql_deparse_analyze(StringInfo sql,char * dbname,char * relname)438 mysql_deparse_analyze(StringInfo sql, char *dbname, char *relname)
439 {
440 	appendStringInfo(sql, "SELECT");
441 	appendStringInfo(sql, " round(((data_length + index_length)), 2)");
442 	appendStringInfo(sql, " FROM information_schema.TABLES");
443 	appendStringInfo(sql, " WHERE table_schema = '%s' AND table_name = '%s'",
444 					 dbname, relname);
445 }
446 
447 /*
448  * Emit a target list that retrieves the columns specified in attrs_used.
449  * This is used for both SELECT and RETURNING targetlists.
450  */
451 static void
mysql_deparse_target_list(StringInfo buf,PlannerInfo * root,Index rtindex,Relation rel,Bitmapset * attrs_used,List ** retrieved_attrs)452 mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex,
453 						  Relation rel, Bitmapset *attrs_used,
454 						  List **retrieved_attrs)
455 {
456 	TupleDesc	tupdesc = RelationGetDescr(rel);
457 	bool		have_wholerow;
458 	bool		first;
459 	int			i;
460 
461 	/* If there's a whole-row reference, we'll need all the columns. */
462 	have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
463 								  attrs_used);
464 
465 	first = true;
466 
467 	*retrieved_attrs = NIL;
468 	for (i = 1; i <= tupdesc->natts; i++)
469 	{
470 		Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1);
471 
472 		/* Ignore dropped attributes. */
473 		if (attr->attisdropped)
474 			continue;
475 
476 		if (have_wholerow ||
477 			bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used))
478 		{
479 			if (!first)
480 				appendStringInfoString(buf, ", ");
481 			first = false;
482 
483 			mysql_deparse_column_ref(buf, rtindex, i, root, false);
484 			*retrieved_attrs = lappend_int(*retrieved_attrs, i);
485 		}
486 	}
487 
488 	/* Don't generate bad syntax if no undropped columns */
489 	if (first)
490 		appendStringInfoString(buf, "NULL");
491 }
492 
493 /*
494  * Construct name to use for given column, and emit it into buf.  If it has a
495  * column_name FDW option, use that instead of attribute name.
496  */
497 static void
mysql_deparse_column_ref(StringInfo buf,int varno,int varattno,PlannerInfo * root,bool qualify_col)498 mysql_deparse_column_ref(StringInfo buf, int varno, int varattno,
499 						 PlannerInfo *root, bool qualify_col)
500 {
501 	RangeTblEntry *rte;
502 	char	   *colname = NULL;
503 	List	   *options;
504 	ListCell   *lc;
505 
506 	/* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */
507 	Assert(!IS_SPECIAL_VARNO(varno));
508 
509 	/* Get RangeTblEntry from array in PlannerInfo. */
510 	rte = planner_rt_fetch(varno, root);
511 
512 	/*
513 	 * If it's a column of a foreign table, and it has the column_name FDW
514 	 * option, use that value.
515 	 */
516 	options = GetForeignColumnOptions(rte->relid, varattno);
517 	foreach(lc, options)
518 	{
519 		DefElem    *def = (DefElem *) lfirst(lc);
520 
521 		if (strcmp(def->defname, "column_name") == 0)
522 		{
523 			colname = defGetString(def);
524 			break;
525 		}
526 	}
527 
528 	/*
529 	 * If it's a column of a regular table or it doesn't have column_name
530 	 * FDW option, use attribute name.
531 	 */
532 	if (colname == NULL)
533 #if PG_VERSION_NUM >= 110000
534 		colname = get_attname(rte->relid, varattno, false);
535 #else
536 		colname = get_relid_attribute_name(rte->relid, varattno);
537 #endif
538 
539 	if (qualify_col)
540 		ADD_REL_QUALIFIER(buf, varno);
541 
542 	appendStringInfoString(buf, mysql_quote_identifier(colname, '`'));
543 }
544 
545 static void
mysql_deparse_string(StringInfo buf,const char * val,bool isstr)546 mysql_deparse_string(StringInfo buf, const char *val, bool isstr)
547 {
548 	const char *valptr;
549 	int			i = 0;
550 
551 	if (isstr)
552 		appendStringInfoChar(buf, '\'');
553 
554 	for (valptr = val; *valptr; valptr++,i++)
555 	{
556 		char		ch = *valptr;
557 
558 		/*
559 		 * Remove '{', '}', and \" character from the string. Because this
560 		 * syntax is not recognize by the remote MySQL server.
561 		 */
562 		if ((ch == '{' && i == 0) || (ch == '}' && (i == (strlen(val) - 1))) ||
563 			ch == '\"')
564 			continue;
565 
566 		if (isstr && ch == ',')
567 		{
568 			appendStringInfoString(buf, "', '");
569 			continue;
570 		}
571 		appendStringInfoChar(buf, ch);
572 	}
573 
574 	if (isstr)
575 		appendStringInfoChar(buf, '\'');
576 }
577 
578 /*
579 * Append a SQL string literal representing "val" to buf.
580 */
581 static void
mysql_deparse_string_literal(StringInfo buf,const char * val)582 mysql_deparse_string_literal(StringInfo buf, const char *val)
583 {
584 	const char *valptr;
585 
586 	appendStringInfoChar(buf, '\'');
587 
588 	for (valptr = val; *valptr; valptr++)
589 	{
590 		char		ch = *valptr;
591 
592 		if (SQL_STR_DOUBLE(ch, true))
593 			appendStringInfoChar(buf, ch);
594 		appendStringInfoChar(buf, ch);
595 	}
596 
597 	appendStringInfoChar(buf, '\'');
598 }
599 
600 /*
601  * Deparse given expression into context->buf.
602  *
603  * This function must support all the same node types that foreign_expr_walker
604  * accepts.
605  *
606  * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization
607  * scheme: anything more complex than a Var, Const, function call or cast
608  * should be self-parenthesized.
609  */
610 static void
deparseExpr(Expr * node,deparse_expr_cxt * context)611 deparseExpr(Expr *node, deparse_expr_cxt *context)
612 {
613 	if (node == NULL)
614 		return;
615 
616 	switch (nodeTag(node))
617 	{
618 		case T_Var:
619 			mysql_deparse_var((Var *) node, context);
620 			break;
621 		case T_Const:
622 			mysql_deparse_const((Const *) node, context);
623 			break;
624 		case T_Param:
625 			mysql_deparse_param((Param *) node, context);
626 			break;
627 #if PG_VERSION_NUM < 120000
628 		case T_ArrayRef:
629 			mysql_deparse_array_ref((ArrayRef *) node, context);
630 #else
631 		case T_SubscriptingRef:
632 			mysql_deparse_array_ref((SubscriptingRef *) node, context);
633 #endif
634 			break;
635 		case T_FuncExpr:
636 			mysql_deparse_func_expr((FuncExpr *) node, context);
637 			break;
638 		case T_OpExpr:
639 			mysql_deparse_op_expr((OpExpr *) node, context);
640 			break;
641 		case T_DistinctExpr:
642 			mysql_deparse_distinct_expr((DistinctExpr *) node, context);
643 			break;
644 		case T_ScalarArrayOpExpr:
645 			mysql_deparse_scalar_array_op_expr((ScalarArrayOpExpr *) node,
646 											   context);
647 			break;
648 		case T_RelabelType:
649 			mysql_deparse_relabel_type((RelabelType *) node, context);
650 			break;
651 		case T_BoolExpr:
652 			mysql_deparse_bool_expr((BoolExpr *) node, context);
653 			break;
654 		case T_NullTest:
655 			mysql_deparse_null_test((NullTest *) node, context);
656 			break;
657 		case T_ArrayExpr:
658 			mysql_deparse_array_expr((ArrayExpr *) node, context);
659 			break;
660 		default:
661 			elog(ERROR, "unsupported expression type for deparse: %d",
662 				 (int) nodeTag(node));
663 			break;
664 	}
665 }
666 
667 /*
668  * Deparse Interval type into MySQL Interval representation.
669  */
670 static void
deparse_interval(StringInfo buf,Datum datum)671 deparse_interval(StringInfo buf, Datum datum)
672 {
673 	struct pg_tm tm;
674 	fsec_t		fsec;
675 	bool		is_first = true;
676 
677 #define append_interval(expr, unit) \
678 do { \
679 	if (!is_first) \
680 		appendStringInfo(buf, " %s ", cur_opname); \
681 	appendStringInfo(buf, "INTERVAL %d %s", expr, unit); \
682 	is_first = false; \
683 } while (0)
684 
685 	/* Check saved opname. It could be only "+" and "-" */
686 	Assert(cur_opname);
687 
688 	if (interval2tm(*DatumGetIntervalP(datum), &tm, &fsec) != 0)
689 		elog(ERROR, "could not convert interval to tm");
690 
691 	if (tm.tm_year > 0)
692 		append_interval(tm.tm_year, "YEAR");
693 
694 	if (tm.tm_mon > 0)
695 		append_interval(tm.tm_mon, "MONTH");
696 
697 	if (tm.tm_mday > 0)
698 		append_interval(tm.tm_mday, "DAY");
699 
700 	if (tm.tm_hour > 0)
701 		append_interval(tm.tm_hour, "HOUR");
702 
703 	if (tm.tm_min > 0)
704 		append_interval(tm.tm_min, "MINUTE");
705 
706 	if (tm.tm_sec > 0)
707 		append_interval(tm.tm_sec, "SECOND");
708 
709 	if (fsec > 0)
710 	{
711 		if (!is_first)
712 			appendStringInfo(buf, " %s ", cur_opname);
713 #ifdef HAVE_INT64_TIMESTAMP
714 		appendStringInfo(buf, "INTERVAL %d MICROSECOND", fsec);
715 #else
716 		appendStringInfo(buf, "INTERVAL %f MICROSECOND", fsec);
717 #endif
718 	}
719 }
720 
721 /*
722  * Deparse remote UPDATE statement
723  *
724  * The statement text is appended to buf, and we also create an integer List
725  * of the columns being retrieved by RETURNING (if any), which is returned
726  * to *retrieved_attrs.
727  */
728 void
mysql_deparse_update(StringInfo buf,PlannerInfo * root,Index rtindex,Relation rel,List * targetAttrs,char * attname)729 mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex,
730 					 Relation rel, List *targetAttrs, char *attname)
731 {
732 	AttrNumber	pindex;
733 	bool		first;
734 	ListCell   *lc;
735 
736 	appendStringInfoString(buf, "UPDATE ");
737 	mysql_deparse_relation(buf, rel);
738 	appendStringInfoString(buf, " SET ");
739 
740 	pindex = 2;
741 	first = true;
742 	foreach(lc, targetAttrs)
743 	{
744 		int			attnum = lfirst_int(lc);
745 
746 		if (attnum == 1)
747 			continue;
748 
749 		if (!first)
750 			appendStringInfoString(buf, ", ");
751 		first = false;
752 
753 		mysql_deparse_column_ref(buf, rtindex, attnum, root, false);
754 		appendStringInfo(buf, " = ?");
755 		pindex++;
756 	}
757 
758 	appendStringInfo(buf, " WHERE %s = ?", attname);
759 }
760 
761 /*
762  * Deparse remote DELETE statement
763  *
764  * The statement text is appended to buf, and we also create an integer List
765  * of the columns being retrieved by RETURNING (if any), which is returned
766  * to *retrieved_attrs.
767  */
768 void
mysql_deparse_delete(StringInfo buf,PlannerInfo * root,Index rtindex,Relation rel,char * name)769 mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex,
770 					 Relation rel, char *name)
771 {
772 	appendStringInfoString(buf, "DELETE FROM ");
773 	mysql_deparse_relation(buf, rel);
774 	appendStringInfo(buf, " WHERE %s = ?", name);
775 }
776 
777 /*
778  * Deparse given Var node into context->buf.
779  *
780  * If the Var belongs to the foreign relation, just print its remote name.
781  * Otherwise, it's effectively a Param (and will in fact be a Param at
782  * run time).  Handle it the same way we handle plain Params --- see
783  * deparseParam for comments.
784  */
785 static void
mysql_deparse_var(Var * node,deparse_expr_cxt * context)786 mysql_deparse_var(Var *node, deparse_expr_cxt *context)
787 {
788 	Relids		relids = context->foreignrel->relids;
789 	bool		qualify_col = (bms_membership(relids) == BMS_MULTIPLE);
790 
791 	if (bms_is_member(node->varno, relids) && node->varlevelsup == 0)
792 	{
793 		/* Var belongs to foreign table */
794 		mysql_deparse_column_ref(context->buf, node->varno, node->varattno,
795 								 context->root, qualify_col);
796 	}
797 	else
798 	{
799 		/* Treat like a Param */
800 		if (context->params_list)
801 		{
802 			int			pindex = 0;
803 			ListCell   *lc;
804 
805 			/* Find its index in params_list */
806 			foreach(lc, *context->params_list)
807 			{
808 				pindex++;
809 				if (equal(node, (Node *) lfirst(lc)))
810 					break;
811 			}
812 			if (lc == NULL)
813 			{
814 				/* Not in list, so add it */
815 				pindex++;
816 				*context->params_list = lappend(*context->params_list, node);
817 			}
818 			mysql_print_remote_param(pindex, node->vartype, node->vartypmod,
819 									 context);
820 		}
821 		else
822 			mysql_print_remote_placeholder(node->vartype, node->vartypmod,
823 										   context);
824 	}
825 }
826 
827 /*
828  * Deparse given constant value into context->buf.
829  *
830  * This function has to be kept in sync with ruleutils.c's get_const_expr.
831  */
832 static void
mysql_deparse_const(Const * node,deparse_expr_cxt * context)833 mysql_deparse_const(Const *node, deparse_expr_cxt *context)
834 {
835 	StringInfo	buf = context->buf;
836 	Oid			typoutput;
837 	bool		typIsVarlena;
838 	char	   *extval;
839 
840 	if (node->constisnull)
841 	{
842 		appendStringInfoString(buf, "NULL");
843 		return;
844 	}
845 
846 	getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena);
847 
848 	switch (node->consttype)
849 	{
850 		case INT2OID:
851 		case INT4OID:
852 		case INT8OID:
853 		case OIDOID:
854 		case FLOAT4OID:
855 		case FLOAT8OID:
856 		case NUMERICOID:
857 			{
858 				extval = OidOutputFunctionCall(typoutput, node->constvalue);
859 
860 				/*
861 				 * No need to quote unless it's a special value such as 'NaN'.
862 				 * See comments in get_const_expr().
863 				 */
864 				if (strspn(extval, "0123456789+-eE.") == strlen(extval))
865 				{
866 					if (extval[0] == '+' || extval[0] == '-')
867 						appendStringInfo(buf, "(%s)", extval);
868 					else
869 						appendStringInfoString(buf, extval);
870 				}
871 				else
872 					appendStringInfo(buf, "'%s'", extval);
873 			}
874 			break;
875 		case BITOID:
876 		case VARBITOID:
877 			extval = OidOutputFunctionCall(typoutput, node->constvalue);
878 			appendStringInfo(buf, "B'%s'", extval);
879 			break;
880 		case BOOLOID:
881 			extval = OidOutputFunctionCall(typoutput, node->constvalue);
882 			if (strcmp(extval, "t") == 0)
883 				appendStringInfoString(buf, "true");
884 			else
885 				appendStringInfoString(buf, "false");
886 			break;
887 		case INTERVALOID:
888 			deparse_interval(buf, node->constvalue);
889 			break;
890 		case BYTEAOID:
891 			/*
892 			 * The string for BYTEA always seems to be in the format "\\x##"
893 			 * where # is a hex digit, Even if the value passed in is
894 			 * 'hi'::bytea we will receive "\x6869". Making this assumption
895 			 * allows us to quickly convert postgres escaped strings to mysql
896 			 * ones for comparison
897 			 */
898 			extval = OidOutputFunctionCall(typoutput, node->constvalue);
899 			appendStringInfo(buf, "X\'%s\'", extval + 2);
900 			break;
901 		default:
902 			extval = OidOutputFunctionCall(typoutput, node->constvalue);
903 			mysql_deparse_string_literal(buf, extval);
904 			break;
905 	}
906 }
907 
908 /*
909  * Deparse given Param node.
910  *
911  * If we're generating the query "for real", add the Param to
912  * context->params_list if it's not already present, and then use its index
913  * in that list as the remote parameter number.  During EXPLAIN, there's
914  * no need to identify a parameter number.
915  */
916 static void
mysql_deparse_param(Param * node,deparse_expr_cxt * context)917 mysql_deparse_param(Param *node, deparse_expr_cxt *context)
918 {
919 	if (context->params_list)
920 	{
921 		int			pindex = 0;
922 		ListCell   *lc;
923 
924 		/* Find its index in params_list */
925 		foreach(lc, *context->params_list)
926 		{
927 			pindex++;
928 			if (equal(node, (Node *) lfirst(lc)))
929 				break;
930 		}
931 		if (lc == NULL)
932 		{
933 			/* Not in list, so add it */
934 			pindex++;
935 			*context->params_list = lappend(*context->params_list, node);
936 		}
937 
938 		mysql_print_remote_param(pindex, node->paramtype, node->paramtypmod,
939 								 context);
940 	}
941 	else
942 		mysql_print_remote_placeholder(node->paramtype, node->paramtypmod,
943 									   context);
944 }
945 
946 /*
947  * Deparse an array subscript expression.
948  */
949 static void
950 #if PG_VERSION_NUM < 120000
mysql_deparse_array_ref(ArrayRef * node,deparse_expr_cxt * context)951 mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context)
952 #else
953 mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context)
954 #endif
955 {
956 	StringInfo	buf = context->buf;
957 	ListCell   *lowlist_item;
958 	ListCell   *uplist_item;
959 
960 	/* Always parenthesize the expression. */
961 	appendStringInfoChar(buf, '(');
962 
963 	/*
964 	 * Deparse referenced array expression first.  If that expression includes
965 	 * a cast, we have to parenthesize to prevent the array subscript from
966 	 * being taken as typename decoration.  We can avoid that in the typical
967 	 * case of subscripting a Var, but otherwise do it.
968 	 */
969 	if (IsA(node->refexpr, Var))
970 		deparseExpr(node->refexpr, context);
971 	else
972 	{
973 		appendStringInfoChar(buf, '(');
974 		deparseExpr(node->refexpr, context);
975 		appendStringInfoChar(buf, ')');
976 	}
977 
978 	/* Deparse subscript expressions. */
979 	lowlist_item = list_head(node->reflowerindexpr);	/* could be NULL */
980 	foreach(uplist_item, node->refupperindexpr)
981 	{
982 		appendStringInfoChar(buf, '[');
983 		if (lowlist_item)
984 		{
985 			deparseExpr(lfirst(lowlist_item), context);
986 			appendStringInfoChar(buf, ':');
987 #if PG_VERSION_NUM < 130000
988 			lowlist_item = lnext(lowlist_item);
989 #else
990 			lowlist_item = lnext(node->reflowerindexpr, lowlist_item);
991 #endif
992 		}
993 		deparseExpr(lfirst(uplist_item), context);
994 		appendStringInfoChar(buf, ']');
995 	}
996 
997 	appendStringInfoChar(buf, ')');
998 }
999 
1000 /*
1001  * This is possible that the name of function in PostgreSQL and mysql differ,
1002  * so return the mysql eloquent function name.
1003  */
1004 static char *
mysql_replace_function(char * in)1005 mysql_replace_function(char *in)
1006 {
1007 	if (strcmp(in, "btrim") == 0)
1008 		return "trim";
1009 
1010 	return in;
1011 }
1012 
1013 /*
1014  * Deparse a function call.
1015  */
1016 static void
mysql_deparse_func_expr(FuncExpr * node,deparse_expr_cxt * context)1017 mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context)
1018 {
1019 	StringInfo	buf = context->buf;
1020 	HeapTuple	proctup;
1021 	Form_pg_proc procform;
1022 	const char *proname;
1023 	bool		first;
1024 	ListCell   *arg;
1025 
1026 	/*
1027 	 * Normal function: display as proname(args).
1028 	 */
1029 	proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(node->funcid));
1030 	if (!HeapTupleIsValid(proctup))
1031 		elog(ERROR, "cache lookup failed for function %u", node->funcid);
1032 
1033 	procform = (Form_pg_proc) GETSTRUCT(proctup);
1034 
1035 	/* Translate PostgreSQL function into mysql function */
1036 	proname = mysql_replace_function(NameStr(procform->proname));
1037 
1038 	/* Deparse the function name ... */
1039 	appendStringInfo(buf, "%s(", proname);
1040 
1041 	/* ... and all the arguments */
1042 	first = true;
1043 	foreach(arg, node->args)
1044 	{
1045 		if (!first)
1046 			appendStringInfoString(buf, ", ");
1047 		deparseExpr((Expr *) lfirst(arg), context);
1048 		first = false;
1049 	}
1050 
1051 	appendStringInfoChar(buf, ')');
1052 
1053 	ReleaseSysCache(proctup);
1054 }
1055 
1056 /*
1057  * Deparse given operator expression.  To avoid problems around
1058  * priority of operations, we always parenthesize the arguments.
1059  */
1060 static void
mysql_deparse_op_expr(OpExpr * node,deparse_expr_cxt * context)1061 mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context)
1062 {
1063 	StringInfo	buf = context->buf;
1064 	HeapTuple	tuple;
1065 	Form_pg_operator form;
1066 	char		oprkind;
1067 	ListCell   *arg;
1068 
1069 	/* Retrieve information about the operator from system catalog. */
1070 	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
1071 	if (!HeapTupleIsValid(tuple))
1072 		elog(ERROR, "cache lookup failed for operator %u", node->opno);
1073 
1074 	form = (Form_pg_operator) GETSTRUCT(tuple);
1075 	oprkind = form->oprkind;
1076 
1077 	/* Sanity check. */
1078 	Assert((oprkind == 'r' && list_length(node->args) == 1) ||
1079 		   (oprkind == 'l' && list_length(node->args) == 1) ||
1080 		   (oprkind == 'b' && list_length(node->args) == 2));
1081 
1082 	/* Always parenthesize the expression. */
1083 	appendStringInfoChar(buf, '(');
1084 
1085 	/* Deparse left operand. */
1086 	if (oprkind == 'r' || oprkind == 'b')
1087 	{
1088 		arg = list_head(node->args);
1089 		deparseExpr(lfirst(arg), context);
1090 		appendStringInfoChar(buf, ' ');
1091 	}
1092 
1093 	/* Deparse operator name. */
1094 	mysql_deparse_operator_name(buf, form);
1095 
1096 	/* Deparse right operand. */
1097 	if (oprkind == 'l' || oprkind == 'b')
1098 	{
1099 		arg = list_tail(node->args);
1100 		appendStringInfoChar(buf, ' ');
1101 		deparseExpr(lfirst(arg), context);
1102 	}
1103 
1104 	appendStringInfoChar(buf, ')');
1105 
1106 	ReleaseSysCache(tuple);
1107 }
1108 
1109 /*
1110  * Print the name of an operator.
1111  */
1112 static void
mysql_deparse_operator_name(StringInfo buf,Form_pg_operator opform)1113 mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform)
1114 {
1115 	/* opname is not a SQL identifier, so we should not quote it. */
1116 	cur_opname = NameStr(opform->oprname);
1117 
1118 	/* Print schema name only if it's not pg_catalog */
1119 	if (opform->oprnamespace != PG_CATALOG_NAMESPACE)
1120 	{
1121 		const char *opnspname;
1122 
1123 		opnspname = get_namespace_name(opform->oprnamespace);
1124 		/* Print fully qualified operator name. */
1125 		appendStringInfo(buf, "OPERATOR(%s.%s)",
1126 						 mysql_quote_identifier(opnspname, '`'), cur_opname);
1127 	}
1128 	else
1129 	{
1130 		if (strcmp(cur_opname, "~~") == 0)
1131 			appendStringInfoString(buf, "LIKE BINARY");
1132 		else if (strcmp(cur_opname, "~~*") == 0)
1133 			appendStringInfoString(buf, "LIKE");
1134 		else if (strcmp(cur_opname, "!~~") == 0)
1135 			appendStringInfoString(buf, "NOT LIKE BINARY");
1136 		else if (strcmp(cur_opname, "!~~*") == 0)
1137 			appendStringInfoString(buf, "NOT LIKE");
1138 		else if (strcmp(cur_opname, "~") == 0)
1139 			appendStringInfoString(buf, "REGEXP BINARY");
1140 		else if (strcmp(cur_opname, "~*") == 0)
1141 			appendStringInfoString(buf, "REGEXP");
1142 		else if (strcmp(cur_opname, "!~") == 0)
1143 			appendStringInfoString(buf, "NOT REGEXP BINARY");
1144 		else if (strcmp(cur_opname, "!~*") == 0)
1145 			appendStringInfoString(buf, "NOT REGEXP");
1146 		else
1147 			appendStringInfoString(buf, cur_opname);
1148 	}
1149 }
1150 
1151 /*
1152  * Deparse IS DISTINCT FROM.
1153  */
1154 static void
mysql_deparse_distinct_expr(DistinctExpr * node,deparse_expr_cxt * context)1155 mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context)
1156 {
1157 	StringInfo	buf = context->buf;
1158 
1159 	Assert(list_length(node->args) == 2);
1160 
1161 	appendStringInfoChar(buf, '(');
1162 	deparseExpr(linitial(node->args), context);
1163 	appendStringInfoString(buf, " IS DISTINCT FROM ");
1164 	deparseExpr(lsecond(node->args), context);
1165 	appendStringInfoChar(buf, ')');
1166 }
1167 
1168 /*
1169  * Deparse given ScalarArrayOpExpr expression.  To avoid problems
1170  * around priority of operations, we always parenthesize the arguments.
1171  */
1172 static void
mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr * node,deparse_expr_cxt * context)1173 mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node,
1174 								   deparse_expr_cxt *context)
1175 {
1176 	StringInfo	buf = context->buf;
1177 	HeapTuple	tuple;
1178 	Expr	   *arg1;
1179 	Expr	   *arg2;
1180 	Form_pg_operator form;
1181 	char	   *opname;
1182 	Oid			typoutput;
1183 	bool		typIsVarlena;
1184 	char	   *extval;
1185 
1186 	/* Retrieve information about the operator from system catalog. */
1187 	tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno));
1188 	if (!HeapTupleIsValid(tuple))
1189 		elog(ERROR, "cache lookup failed for operator %u", node->opno);
1190 	form = (Form_pg_operator) GETSTRUCT(tuple);
1191 
1192 	/* Sanity check. */
1193 	Assert(list_length(node->args) == 2);
1194 
1195 	/* Deparse left operand. */
1196 	arg1 = linitial(node->args);
1197 	deparseExpr(arg1, context);
1198 	appendStringInfoChar(buf, ' ');
1199 
1200 	opname = NameStr(form->oprname);
1201 	if (strcmp(opname, "<>") == 0)
1202 		appendStringInfo(buf, " NOT ");
1203 
1204 	/* Deparse operator name plus decoration. */
1205 	appendStringInfo(buf, " IN (");
1206 
1207 	/* Deparse right operand. */
1208 	arg2 = lsecond(node->args);
1209 	switch (nodeTag((Node *) arg2))
1210 	{
1211 		case T_Const:
1212 			{
1213 				Const	   *c = (Const *) arg2;
1214 
1215 				if (c->constisnull)
1216 				{
1217 					appendStringInfoString(buf, " NULL");
1218 					ReleaseSysCache(tuple);
1219 					return;
1220 				}
1221 
1222 				getTypeOutputInfo(c->consttype, &typoutput, &typIsVarlena);
1223 				extval = OidOutputFunctionCall(typoutput, c->constvalue);
1224 
1225 				switch (c->consttype)
1226 				{
1227 					case INT4ARRAYOID:
1228 					case OIDARRAYOID:
1229 						mysql_deparse_string(buf, extval, false);
1230 						break;
1231 					default:
1232 						mysql_deparse_string(buf, extval, true);
1233 						break;
1234 				}
1235 			}
1236 			break;
1237 		default:
1238 			deparseExpr(arg2, context);
1239 			break;
1240 	}
1241 	appendStringInfoChar(buf, ')');
1242 
1243 	ReleaseSysCache(tuple);
1244 }
1245 
1246 /*
1247  * Deparse a RelabelType (binary-compatible cast) node.
1248  */
1249 static void
mysql_deparse_relabel_type(RelabelType * node,deparse_expr_cxt * context)1250 mysql_deparse_relabel_type(RelabelType *node, deparse_expr_cxt *context)
1251 {
1252 	deparseExpr(node->arg, context);
1253 }
1254 
1255 /*
1256  * Deparse a BoolExpr node.
1257  *
1258  * Note: by the time we get here, AND and OR expressions have been flattened
1259  * into N-argument form, so we'd better be prepared to deal with that.
1260  */
1261 static void
mysql_deparse_bool_expr(BoolExpr * node,deparse_expr_cxt * context)1262 mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context)
1263 {
1264 	StringInfo	buf = context->buf;
1265 	const char *op = NULL;		/* keep compiler quiet */
1266 	bool		first;
1267 	ListCell   *lc;
1268 
1269 	switch (node->boolop)
1270 	{
1271 		case AND_EXPR:
1272 			op = "AND";
1273 			break;
1274 		case OR_EXPR:
1275 			op = "OR";
1276 			break;
1277 		case NOT_EXPR:
1278 			appendStringInfoChar(buf, '(');
1279 			appendStringInfoString(buf, "NOT ");
1280 			deparseExpr(linitial(node->args), context);
1281 			appendStringInfoChar(buf, ')');
1282 			return;
1283 	}
1284 
1285 	appendStringInfoChar(buf, '(');
1286 	first = true;
1287 	foreach(lc, node->args)
1288 	{
1289 		if (!first)
1290 			appendStringInfo(buf, " %s ", op);
1291 		deparseExpr((Expr *) lfirst(lc), context);
1292 		first = false;
1293 	}
1294 	appendStringInfoChar(buf, ')');
1295 }
1296 
1297 /*
1298  * Deparse IS [NOT] NULL expression.
1299  */
1300 static void
mysql_deparse_null_test(NullTest * node,deparse_expr_cxt * context)1301 mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context)
1302 {
1303 	StringInfo	buf = context->buf;
1304 
1305 	appendStringInfoChar(buf, '(');
1306 	deparseExpr(node->arg, context);
1307 	if (node->nulltesttype == IS_NULL)
1308 		appendStringInfoString(buf, " IS NULL");
1309 	else
1310 		appendStringInfoString(buf, " IS NOT NULL");
1311 	appendStringInfoChar(buf, ')');
1312 }
1313 
1314 /*
1315  * Deparse ARRAY[...] construct.
1316  */
1317 static void
mysql_deparse_array_expr(ArrayExpr * node,deparse_expr_cxt * context)1318 mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context)
1319 {
1320 	StringInfo	buf = context->buf;
1321 	bool		first = true;
1322 	ListCell   *lc;
1323 
1324 	appendStringInfoString(buf, "ARRAY[");
1325 	foreach(lc, node->elements)
1326 	{
1327 		if (!first)
1328 			appendStringInfoString(buf, ", ");
1329 		deparseExpr(lfirst(lc), context);
1330 		first = false;
1331 	}
1332 	appendStringInfoChar(buf, ']');
1333 }
1334 
1335 /*
1336  * Print the representation of a parameter to be sent to the remote side.
1337  *
1338  * Note: we always label the Param's type explicitly rather than relying on
1339  * transmitting a numeric type OID in PQexecParams().  This allows us to
1340  * avoid assuming that types have the same OIDs on the remote side as they
1341  * do locally --- they need only have the same names.
1342  */
1343 static void
mysql_print_remote_param(int paramindex,Oid paramtype,int32 paramtypmod,deparse_expr_cxt * context)1344 mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod,
1345 						 deparse_expr_cxt *context)
1346 {
1347 	StringInfo	buf = context->buf;
1348 
1349 	appendStringInfo(buf, "?");
1350 }
1351 
1352 static void
mysql_print_remote_placeholder(Oid paramtype,int32 paramtypmod,deparse_expr_cxt * context)1353 mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod,
1354 							   deparse_expr_cxt *context)
1355 {
1356 	StringInfo	buf = context->buf;
1357 
1358 	appendStringInfo(buf, "(SELECT null)");
1359 }
1360 
1361 /*
1362  * Return true if given object is one of PostgreSQL's built-in objects.
1363  *
1364  * We use FirstBootstrapObjectId as the cutoff, so that we only consider
1365  * objects with hand-assigned OIDs to be "built in", not for instance any
1366  * function or type defined in the information_schema.
1367  *
1368  * Our constraints for dealing with types are tighter than they are for
1369  * functions or operators: we want to accept only types that are in pg_catalog,
1370  * else format_type might incorrectly fail to schema-qualify their names.
1371  * (This could be fixed with some changes to format_type, but for now there's
1372  * no need.)  Thus we must exclude information_schema types.
1373  *
1374  * XXX there is a problem with this, which is that the set of built-in
1375  * objects expands over time.  Something that is built-in to us might not
1376  * be known to the remote server, if it's of an older version.  But keeping
1377  * track of that would be a huge exercise.
1378  */
1379 static bool
is_builtin(Oid oid)1380 is_builtin(Oid oid)
1381 {
1382 	return (oid < FirstBootstrapObjectId);
1383 }
1384 
1385 /*
1386  * Check if expression is safe to execute remotely, and return true if so.
1387  *
1388  * In addition, *outer_cxt is updated with collation information.
1389  *
1390  * We must check that the expression contains only node types we can deparse,
1391  * that all types/functions/operators are safe to send (which we approximate
1392  * as being built-in), and that all collations used in the expression derive
1393  * from Vars of the foreign table.  Because of the latter, the logic is pretty
1394  * close to assign_collations_walker() in parse_collate.c, though we can assume
1395  * here that the given expression is valid.
1396  */
1397 static bool
foreign_expr_walker(Node * node,foreign_glob_cxt * glob_cxt,foreign_loc_cxt * outer_cxt)1398 foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt,
1399 					foreign_loc_cxt *outer_cxt)
1400 {
1401 	bool		check_type = true;
1402 	foreign_loc_cxt inner_cxt;
1403 	Oid			collation;
1404 	FDWCollateState state;
1405 
1406 	/* Need do nothing for empty subexpressions */
1407 	if (node == NULL)
1408 		return true;
1409 
1410 	/* Set up inner_cxt for possible recursion to child nodes */
1411 	inner_cxt.collation = InvalidOid;
1412 	inner_cxt.state = FDW_COLLATE_NONE;
1413 
1414 	switch (nodeTag(node))
1415 	{
1416 		case T_Var:
1417 			{
1418 				Var		   *var = (Var *) node;
1419 
1420 				/*
1421 				 * If the Var is from the foreign table, we consider its
1422 				 * collation (if any) safe to use.  If it is from another
1423 				 * table, we treat its collation the same way as we would a
1424 				 * Param's collation, i.e. it's not safe for it to have a
1425 				 * non-default collation.
1426 				 */
1427 				if (bms_is_member(var->varno, glob_cxt->foreignrel->relids) &&
1428 					var->varlevelsup == 0)
1429 				{
1430 					/* Var belongs to foreign table */
1431 					collation = var->varcollid;
1432 					state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE;
1433 				}
1434 				else
1435 				{
1436 					/* Var belongs to some other table */
1437 					if (var->varcollid != InvalidOid &&
1438 						var->varcollid != DEFAULT_COLLATION_OID)
1439 						return false;
1440 
1441 					/* We can consider that it doesn't set collation */
1442 					collation = InvalidOid;
1443 					state = FDW_COLLATE_NONE;
1444 				}
1445 			}
1446 			break;
1447 		case T_Const:
1448 			{
1449 				Const	   *c = (Const *) node;
1450 
1451 				/*
1452 				 * If the constant has non default collation, either it's of a
1453 				 * non-built in type, or it reflects folding of a CollateExpr;
1454 				 * either way, it's unsafe to send to the remote.
1455 				 */
1456 				if (c->constcollid != InvalidOid &&
1457 					c->constcollid != DEFAULT_COLLATION_OID)
1458 					return false;
1459 
1460 				/* Otherwise, we can consider that it doesn't set collation */
1461 				collation = InvalidOid;
1462 				state = FDW_COLLATE_NONE;
1463 			}
1464 			break;
1465 		case T_Param:
1466 			{
1467 				Param	   *p = (Param *) node;
1468 
1469 				/*
1470 				 * Collation rule is same as for Consts and non-foreign Vars.
1471 				 */
1472 				collation = p->paramcollid;
1473 				if (collation == InvalidOid ||
1474 					collation == DEFAULT_COLLATION_OID)
1475 					state = FDW_COLLATE_NONE;
1476 				else
1477 					state = FDW_COLLATE_UNSAFE;
1478 			}
1479 			break;
1480 #if PG_VERSION_NUM < 120000
1481 		case T_ArrayRef:
1482 			{
1483 				ArrayRef   *ar = (ArrayRef *) node;
1484 #else
1485 		case T_SubscriptingRef:
1486 			{
1487 				SubscriptingRef *ar = (SubscriptingRef *) node;
1488 #endif
1489 
1490 				/* Should not be in the join clauses of the Join-pushdown */
1491 				if (glob_cxt->is_join_cond)
1492 					return false;
1493 
1494 				/* Assignment should not be in restrictions. */
1495 				if (ar->refassgnexpr != NULL)
1496 					return false;
1497 
1498 				/*
1499 				 * Recurse to remaining subexpressions.  Since the array
1500 				 * subscripts must yield (noncollatable) integers, they won't
1501 				 * affect the inner_cxt state.
1502 				 */
1503 				if (!foreign_expr_walker((Node *) ar->refupperindexpr,
1504 										 glob_cxt, &inner_cxt))
1505 					return false;
1506 				if (!foreign_expr_walker((Node *) ar->reflowerindexpr,
1507 										 glob_cxt, &inner_cxt))
1508 					return false;
1509 				if (!foreign_expr_walker((Node *) ar->refexpr,
1510 										 glob_cxt, &inner_cxt))
1511 					return false;
1512 
1513 				/*
1514 				 * Array subscripting should yield same collation as input,
1515 				 * but for safety use same logic as for function nodes.
1516 				 */
1517 				collation = ar->refcollid;
1518 				if (collation == InvalidOid)
1519 					state = FDW_COLLATE_NONE;
1520 				else if (inner_cxt.state == FDW_COLLATE_SAFE &&
1521 						 collation == inner_cxt.collation)
1522 					state = FDW_COLLATE_SAFE;
1523 				else
1524 					state = FDW_COLLATE_UNSAFE;
1525 			}
1526 			break;
1527 		case T_FuncExpr:
1528 			{
1529 				FuncExpr   *fe = (FuncExpr *) node;
1530 
1531 				/* Should not be in the join clauses of the Join-pushdown */
1532 				if (glob_cxt->is_join_cond)
1533 					return false;
1534 
1535 				/*
1536 				 * If function used by the expression is not built-in, it
1537 				 * can't be sent to remote because it might have incompatible
1538 				 * semantics on remote side.
1539 				 */
1540 				if (!is_builtin(fe->funcid))
1541 					return false;
1542 
1543 				/*
1544 				 * Recurse to input subexpressions.
1545 				 */
1546 				if (!foreign_expr_walker((Node *) fe->args,
1547 										 glob_cxt, &inner_cxt))
1548 					return false;
1549 
1550 				/*
1551 				 * If function's input collation is not derived from a foreign
1552 				 * Var, it can't be sent to remote.
1553 				 */
1554 				if (fe->inputcollid == InvalidOid)
1555 					 /* OK, inputs are all noncollatable */ ;
1556 				else if (inner_cxt.state != FDW_COLLATE_SAFE ||
1557 						 fe->inputcollid != inner_cxt.collation)
1558 					return false;
1559 
1560 				/*
1561 				 * Detect whether node is introducing a collation not derived
1562 				 * from a foreign Var.  (If so, we just mark it unsafe for now
1563 				 * rather than immediately returning false, since the parent
1564 				 * node might not care.)
1565 				 */
1566 				collation = fe->funccollid;
1567 				if (collation == InvalidOid)
1568 					state = FDW_COLLATE_NONE;
1569 				else if (inner_cxt.state == FDW_COLLATE_SAFE &&
1570 						 collation == inner_cxt.collation)
1571 					state = FDW_COLLATE_SAFE;
1572 				else
1573 					state = FDW_COLLATE_UNSAFE;
1574 			}
1575 			break;
1576 		case T_OpExpr:
1577 		case T_DistinctExpr:	/* struct-equivalent to OpExpr */
1578 			{
1579 				OpExpr	   *oe = (OpExpr *) node;
1580 				const char *operatorName = get_opname(oe->opno);
1581 
1582 				/*
1583 				 * Join-pushdown allows only a few operators to be pushed down.
1584 				 */
1585 				if (glob_cxt->is_join_cond &&
1586 					(!(strcmp(operatorName, "<") == 0 ||
1587 					   strcmp(operatorName, ">") == 0 ||
1588 					   strcmp(operatorName, "<=") == 0 ||
1589 					   strcmp(operatorName, ">=") == 0 ||
1590 					   strcmp(operatorName, "<>") == 0 ||
1591 					   strcmp(operatorName, "=") == 0 ||
1592 					   strcmp(operatorName, "+") == 0 ||
1593 					   strcmp(operatorName, "-") == 0 ||
1594 					   strcmp(operatorName, "*") == 0 ||
1595 					   strcmp(operatorName, "%") == 0 ||
1596 					   strcmp(operatorName, "/") == 0)))
1597 					return false;
1598 
1599 				/*
1600 				 * Similarly, only built-in operators can be sent to remote.
1601 				 * (If the operator is, surely its underlying function is
1602 				 * too.)
1603 				 */
1604 				if (!is_builtin(oe->opno))
1605 					return false;
1606 
1607 				/*
1608 				 * Recurse to input subexpressions.
1609 				 */
1610 				if (!foreign_expr_walker((Node *) oe->args,
1611 										 glob_cxt, &inner_cxt))
1612 					return false;
1613 
1614 				/*
1615 				 * If operator's input collation is not derived from a foreign
1616 				 * Var, it can't be sent to remote.
1617 				 */
1618 				if (oe->inputcollid == InvalidOid)
1619 					 /* OK, inputs are all noncollatable */ ;
1620 				else if (inner_cxt.state != FDW_COLLATE_SAFE ||
1621 						 oe->inputcollid != inner_cxt.collation)
1622 					return false;
1623 
1624 				/* Result-collation handling is same as for functions */
1625 				collation = oe->opcollid;
1626 				if (collation == InvalidOid)
1627 					state = FDW_COLLATE_NONE;
1628 				else if (inner_cxt.state == FDW_COLLATE_SAFE &&
1629 						 collation == inner_cxt.collation)
1630 					state = FDW_COLLATE_SAFE;
1631 				else
1632 					state = FDW_COLLATE_UNSAFE;
1633 			}
1634 			break;
1635 		case T_ScalarArrayOpExpr:
1636 			{
1637 				ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node;
1638 
1639 				/* Should not be in the join clauses of the Join-pushdown */
1640 				if (glob_cxt->is_join_cond)
1641 					return false;
1642 
1643 				/*
1644 				 * Again, only built-in operators can be sent to remote.
1645 				 */
1646 				if (!is_builtin(oe->opno))
1647 					return false;
1648 
1649 				/*
1650 				 * Recurse to input subexpressions.
1651 				 */
1652 				if (!foreign_expr_walker((Node *) oe->args,
1653 										 glob_cxt, &inner_cxt))
1654 					return false;
1655 
1656 				/*
1657 				 * If operator's input collation is not derived from a foreign
1658 				 * Var, it can't be sent to remote.
1659 				 */
1660 				if (oe->inputcollid == InvalidOid)
1661 					 /* OK, inputs are all noncollatable */ ;
1662 				else if (inner_cxt.state != FDW_COLLATE_SAFE ||
1663 						 oe->inputcollid != inner_cxt.collation)
1664 					return false;
1665 
1666 				/* Output is always boolean and so noncollatable. */
1667 				collation = InvalidOid;
1668 				state = FDW_COLLATE_NONE;
1669 			}
1670 			break;
1671 		case T_RelabelType:
1672 			{
1673 				RelabelType *r = (RelabelType *) node;
1674 
1675 				/*
1676 				 * Recurse to input subexpression.
1677 				 */
1678 				if (!foreign_expr_walker((Node *) r->arg,
1679 										 glob_cxt, &inner_cxt))
1680 					return false;
1681 
1682 				/*
1683 				 * RelabelType must not introduce a collation not derived from
1684 				 * an input foreign Var.
1685 				 */
1686 				collation = r->resultcollid;
1687 				if (collation == InvalidOid)
1688 					state = FDW_COLLATE_NONE;
1689 				else if (inner_cxt.state == FDW_COLLATE_SAFE &&
1690 						 collation == inner_cxt.collation)
1691 					state = FDW_COLLATE_SAFE;
1692 				else
1693 					state = FDW_COLLATE_UNSAFE;
1694 			}
1695 			break;
1696 		case T_BoolExpr:
1697 			{
1698 				BoolExpr   *b = (BoolExpr *) node;
1699 
1700 				/*
1701 				 * Recurse to input subexpressions.
1702 				 */
1703 				if (!foreign_expr_walker((Node *) b->args,
1704 										 glob_cxt, &inner_cxt))
1705 					return false;
1706 
1707 				/* Output is always boolean and so noncollatable. */
1708 				collation = InvalidOid;
1709 				state = FDW_COLLATE_NONE;
1710 			}
1711 			break;
1712 		case T_NullTest:
1713 			{
1714 				NullTest   *nt = (NullTest *) node;
1715 
1716 				/*
1717 				 * Recurse to input subexpressions.
1718 				 */
1719 				if (!foreign_expr_walker((Node *) nt->arg,
1720 										 glob_cxt, &inner_cxt))
1721 					return false;
1722 
1723 				/* Output is always boolean and so noncollatable. */
1724 				collation = InvalidOid;
1725 				state = FDW_COLLATE_NONE;
1726 			}
1727 			break;
1728 		case T_ArrayExpr:
1729 			{
1730 				ArrayExpr  *a = (ArrayExpr *) node;
1731 
1732 				/* Should not be in the join clauses of the Join-pushdown */
1733 				if (glob_cxt->is_join_cond)
1734 					return false;
1735 
1736 				/*
1737 				 * Recurse to input subexpressions.
1738 				 */
1739 				if (!foreign_expr_walker((Node *) a->elements,
1740 										 glob_cxt, &inner_cxt))
1741 					return false;
1742 
1743 				/*
1744 				 * ArrayExpr must not introduce a collation not derived from
1745 				 * an input foreign Var.
1746 				 */
1747 				collation = a->array_collid;
1748 				if (collation == InvalidOid)
1749 					state = FDW_COLLATE_NONE;
1750 				else if (inner_cxt.state == FDW_COLLATE_SAFE &&
1751 						 collation == inner_cxt.collation)
1752 					state = FDW_COLLATE_SAFE;
1753 				else
1754 					state = FDW_COLLATE_UNSAFE;
1755 			}
1756 			break;
1757 		case T_List:
1758 			{
1759 				List	   *l = (List *) node;
1760 				ListCell   *lc;
1761 
1762 				/*
1763 				 * Recurse to component subexpressions.
1764 				 */
1765 				foreach(lc, l)
1766 				{
1767 					if (!foreign_expr_walker((Node *) lfirst(lc),
1768 											 glob_cxt, &inner_cxt))
1769 						return false;
1770 				}
1771 
1772 				/*
1773 				 * When processing a list, collation state just bubbles up
1774 				 * from the list elements.
1775 				 */
1776 				collation = inner_cxt.collation;
1777 				state = inner_cxt.state;
1778 
1779 				/* Don't apply exprType() to the list. */
1780 				check_type = false;
1781 			}
1782 			break;
1783 		default:
1784 
1785 			/*
1786 			 * If it's anything else, assume it's unsafe.  This list can be
1787 			 * expanded later, but don't forget to add deparse support below.
1788 			 */
1789 			return false;
1790 	}
1791 
1792 	/*
1793 	 * If result type of given expression is not built-in, it can't be sent to
1794 	 * remote because it might have incompatible semantics on remote side.
1795 	 */
1796 	if (check_type && !is_builtin(exprType(node)))
1797 		return false;
1798 
1799 	/*
1800 	 * Now, merge my collation information into my parent's state.
1801 	 */
1802 	if (state > outer_cxt->state)
1803 	{
1804 		/* Override previous parent state */
1805 		outer_cxt->collation = collation;
1806 		outer_cxt->state = state;
1807 	}
1808 	else if (state == outer_cxt->state)
1809 	{
1810 		/* Merge, or detect error if there's a collation conflict */
1811 		switch (state)
1812 		{
1813 			case FDW_COLLATE_NONE:
1814 				/* Nothing + nothing is still nothing */
1815 				break;
1816 			case FDW_COLLATE_SAFE:
1817 				if (collation != outer_cxt->collation)
1818 				{
1819 					/*
1820 					 * Non-default collation always beats default.
1821 					 */
1822 					if (outer_cxt->collation == DEFAULT_COLLATION_OID)
1823 					{
1824 						/* Override previous parent state */
1825 						outer_cxt->collation = collation;
1826 					}
1827 					else if (collation != DEFAULT_COLLATION_OID)
1828 					{
1829 						/*
1830 						 * Conflict; show state as indeterminate.  We don't
1831 						 * want to "return false" right away, since parent
1832 						 * node might not care about collation.
1833 						 */
1834 						outer_cxt->state = FDW_COLLATE_UNSAFE;
1835 					}
1836 				}
1837 				break;
1838 			case FDW_COLLATE_UNSAFE:
1839 				/* We're still conflicted ... */
1840 				break;
1841 		}
1842 	}
1843 
1844 	/* It looks OK */
1845 	return true;
1846 }
1847 
1848 /*
1849  * Returns true if given expr is safe to evaluate on the foreign server.
1850  */
1851 bool
1852 mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr,
1853 					  bool is_join_cond)
1854 {
1855 	foreign_glob_cxt glob_cxt;
1856 	foreign_loc_cxt loc_cxt;
1857 
1858 	/*
1859 	 * Check that the expression consists of nodes that are safe to execute
1860 	 * remotely.
1861 	 */
1862 	glob_cxt.root = root;
1863 	glob_cxt.foreignrel = baserel;
1864 	glob_cxt.is_join_cond = is_join_cond;
1865 	loc_cxt.collation = InvalidOid;
1866 	loc_cxt.state = FDW_COLLATE_NONE;
1867 	if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt))
1868 		return false;
1869 
1870 	/* Expressions examined here should be boolean, ie noncollatable */
1871 	Assert(loc_cxt.collation == InvalidOid);
1872 	Assert(loc_cxt.state == FDW_COLLATE_NONE);
1873 
1874 	/* OK to evaluate on the remote server */
1875 	return true;
1876 }
1877 
1878 /*
1879  * mysql_append_conditions
1880  * 		Deparse conditions from the provided list and append them to buf.
1881  *
1882  * The conditions in the list are assumed to be ANDed.
1883  *
1884  * Depending on the caller, the list elements might be either RestrictInfos
1885  * or bare clauses.
1886  */
1887 static void
1888 mysql_append_conditions(List *exprs, deparse_expr_cxt *context)
1889 {
1890 	ListCell   *lc;
1891 	bool		is_first = true;
1892 	StringInfo	buf = context->buf;
1893 
1894 	foreach(lc, exprs)
1895 	{
1896 		Expr	   *expr = (Expr *) lfirst(lc);
1897 
1898 		/*
1899 		 * Extract clause from RestrictInfo, if required. See comments in
1900 		 * declaration of MySQLFdwRelationInfo for details.
1901 		 */
1902 		if (IsA(expr, RestrictInfo))
1903 		{
1904 			RestrictInfo *ri = (RestrictInfo *) expr;
1905 
1906 			expr = ri->clause;
1907 		}
1908 
1909 		/* Connect expressions with "AND" and parenthesize each condition. */
1910 		if (!is_first)
1911 			appendStringInfoString(buf, " AND ");
1912 
1913 		appendStringInfoChar(buf, '(');
1914 		deparseExpr(expr, context);
1915 		appendStringInfoChar(buf, ')');
1916 
1917 		is_first = false;
1918 	}
1919 }
1920 
1921 /*
1922  * mysql_deparse_from_expr
1923  * 		Construct a FROM clause
1924  */
1925 void
1926 mysql_deparse_from_expr(StringInfo buf, PlannerInfo *root,
1927 						RelOptInfo *foreignrel, bool use_alias,
1928 						List **params_list)
1929 {
1930 	MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private;
1931 
1932 #if PG_VERSION_NUM >= 100000
1933 	if (IS_JOIN_REL(foreignrel))
1934 #else
1935 	if (foreignrel->reloptkind == RELOPT_JOINREL)
1936 #endif
1937 	{
1938 		RelOptInfo *rel_o = fpinfo->outerrel;
1939 		RelOptInfo *rel_i = fpinfo->innerrel;
1940 		StringInfoData join_sql_o;
1941 		StringInfoData join_sql_i;
1942 
1943 		/* Deparse outer relation */
1944 		initStringInfo(&join_sql_o);
1945 		mysql_deparse_from_expr(&join_sql_o, root, rel_o, true, params_list);
1946 
1947 		/* Deparse inner relation */
1948 		initStringInfo(&join_sql_i);
1949 		mysql_deparse_from_expr(&join_sql_i, root, rel_i, true, params_list);
1950 
1951 		/*
1952 		 * For a join relation FROM clause entry is deparsed as
1953 		 *
1954 		 * ((outer relation) <join type> (inner relation) ON (joinclauses)
1955 		 */
1956 		appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data,
1957 						 mysql_get_jointype_name(fpinfo->jointype),
1958 						 join_sql_i.data);
1959 
1960 		/* Append join clause; (TRUE) if no join clause */
1961 		if (fpinfo->joinclauses)
1962 		{
1963 			deparse_expr_cxt context;
1964 
1965 			context.buf = buf;
1966 			context.foreignrel = foreignrel;
1967 			context.root = root;
1968 			context.params_list = params_list;
1969 
1970 			appendStringInfo(buf, "(");
1971 			mysql_append_conditions(fpinfo->joinclauses, &context);
1972 			appendStringInfo(buf, ")");
1973 		}
1974 		else
1975 			appendStringInfoString(buf, "(TRUE)");
1976 
1977 		/* End the FROM clause entry. */
1978 		appendStringInfo(buf, ")");
1979 	}
1980 	else
1981 	{
1982 		RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root);
1983 		Relation	rel;
1984 
1985 		/*
1986 		 * Core code already has some lock on each rel being planned, so we can
1987 		 * use NoLock here.
1988 		 */
1989 #if PG_VERSION_NUM < 130000
1990 		rel = heap_open(rte->relid, NoLock);
1991 #else
1992 		rel = table_open(rte->relid, NoLock);
1993 #endif
1994 
1995 		mysql_deparse_relation(buf, rel);
1996 
1997 		/*
1998 		 * Add a unique alias to avoid any conflict in relation names due to
1999 		 * pulled up subqueries in the query being built for a pushed down
2000 		 * join.
2001 		 */
2002 		if (use_alias)
2003 			appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX,
2004 							 foreignrel->relid);
2005 
2006 #if PG_VERSION_NUM < 130000
2007 		heap_close(rel, NoLock);
2008 #else
2009 		table_close(rel, NoLock);
2010 #endif
2011 	}
2012 	return;
2013 }
2014 
2015 /*
2016  * mysql_get_jointype_name
2017  * 		Output join name for given join type
2018  */
2019 extern const char *
2020 mysql_get_jointype_name(JoinType jointype)
2021 {
2022 	switch (jointype)
2023 	{
2024 		case JOIN_INNER:
2025 			return "INNER";
2026 
2027 		case JOIN_LEFT:
2028 			return "LEFT";
2029 
2030 		case JOIN_RIGHT:
2031 			return "RIGHT";
2032 
2033 		default:
2034 			/* Shouldn't come here, but protect from buggy code. */
2035 			elog(ERROR, "unsupported join type %d", jointype);
2036 	}
2037 
2038 	/* Keep compiler happy */
2039 	return NULL;
2040 }
2041