pax_global_header00006660000000000000000000000064141454127750014524gustar00rootroot0000000000000052 comment=571afd0f67955635072af602216b042ccca88c86 mysql_fdw-REL-2_7_0/000077500000000000000000000000001414541277500143215ustar00rootroot00000000000000mysql_fdw-REL-2_7_0/.gitattributes000066400000000000000000000005221414541277500172130ustar00rootroot00000000000000* whitespace=space-before-tab,trailing-space *.[ch] whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4 # Avoid confusing ASCII underlines with leftover merge conflict markers README conflict-marker-size=32 README.* conflict-marker-size=32 # Test output files that contain extra whitespace *.out -whitespace mysql_fdw-REL-2_7_0/.gitignore000066400000000000000000000000561414541277500163120ustar00rootroot00000000000000# Generated subdirectories /results/ *.o *.so mysql_fdw-REL-2_7_0/CONTRIBUTING.md000066400000000000000000000053441414541277500165600ustar00rootroot00000000000000Contributing to `mysql_fdw` =========================== Following these guidelines helps to facilitate relevant discussion in pull requests and issues so the developers managing and developing this open source project can address patches and bugs as efficiently as possible. Using Issues ------------ `mysql_fdw`'s maintainers prefer that bug reports, feature requests, and pull requests are submitted as [GitHub Issues][1]. Bug Reports ----------- Before opening a bug report: 1. Search for a duplicate issue using GitHub's issue search 2. Check whether the bug remains in the latest `master` or `develop` commit 3. Create a reduced test case: remove code and data not relevant to the bug A contributor should be able to begin work on your bug without asking too many followup questions. If you include the following information, your bug will be serviced more quickly: * Short, descriptive title * Your OS * Versions of dependencies * Any custom modifications Once the background information is out of the way, you are free to present the bug itself. You should explain: * Steps you took to exercise the bug * The expected outcome * What actually occurred Feature Requests ---------------- We are open to adding features but ultimately control the scope and aims of the project. If a proposed feature is likely to incur high testing, maintenance, or performance costs it is also unlikely to be accepted. If a _strong_ case exists for a given feature, we may be persuaded on merit. Be specific. Pull Requests ------------- Well-constructed pull requests are very welcome. By _well-constructed_, we mean they do not introduce unrelated changes or break backwards compatibility. Just fork this repo and open a request against `develop`. Some examples of things likely to increase the likelihood a pull request is rejected: * Large structural changes, including: * Re-factoring for its own sake * Adding languages to the project * Unnecessary whitespace changes * Deviation from obvious conventions * Introduction of incompatible intellectual property Please do not change version numbers in your pull request: they will be updated by the project owners prior to the next release. License ------- By submitting a patch, you agree to allow the project owners to license your work under the terms of the [`LICENSE`][2]. Additionally, you grant the project owners a license under copyright covering your contribution to the extent permitted by law. Finally, you confirm that you own said copyright, have the legal authority to grant said license, and in doing so are not violating any grant of rights you have made to third parties, including your employer. [1]: https://github.com/EnterpriseDB/mysql_fdw/issues [2]: LICENSE mysql_fdw-REL-2_7_0/LICENSE000066400000000000000000000020151414541277500153240ustar00rootroot00000000000000MySQL Foreign Data Wrapper for PostgreSQL Copyright (c) 2011-2021, EnterpriseDB Corporation. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. IN NO EVENT SHALL ENTERPRISEDB CORPORATION BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ENTERPRISEDB CORPORATION HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ENTERPRISEDB CORPORATION SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND ENTERPRISEDB CORPORATION HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. mysql_fdw-REL-2_7_0/META.json000066400000000000000000000022031414541277500157370ustar00rootroot00000000000000{ "name": "mysql_fdw", "abstract": "MySQL FDW for PostgreSQL 9.3+", "description": "This extension implements a Foreign Data Wrapper for MySQL. It is supported on PostgreSQL 9.3 and above.", "version": "2.1.2", "maintainer": [ "mysql_fdw@enterprisedb.com" ], "license": "postgresql", "provides": { "mysql_fdw": { "abstract": "MySQL FDW for PostgreSQL 9.3+", "file": "mysql_fdw--1.0.sql", "docfile": "README.md", "version": "2.1.2" } }, "prereqs": { "runtime": { "requires": { "PostgreSQL": "9.3.0" } } }, "resources": { "bugtracker": { "web": "https://github.com/EnterpriseDB/mysql_fdw/issues" }, "repository": { "url": "https://github.com/EnterpriseDB/mysql_fdw.git", "web": "https://github.com/EnterpriseDB/mysql_fdw", "type": "git" } }, "generated_by": "mysql_fdw@enterprisedb.com", "meta-spec": { "version": "1.0.0", "url": "http://pgxn.org/meta/spec.txt" }, "tags": [ "fdw", "mysql", "foreign data wrapper" ] } mysql_fdw-REL-2_7_0/Makefile000066400000000000000000000025541414541277500157670ustar00rootroot00000000000000# mysql_fdw/Makefile # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. # MODULE_big = mysql_fdw OBJS = connection.o option.o deparse.o mysql_query.o mysql_fdw.o EXTENSION = mysql_fdw DATA = mysql_fdw--1.0.sql mysql_fdw--1.1.sql mysql_fdw--1.0--1.1.sql REGRESS = server_options connection_validation dml select pushdown join_pushdown aggregate_pushdown MYSQL_CONFIG = mysql_config PG_CPPFLAGS := $(shell $(MYSQL_CONFIG) --include) LIB := $(shell $(MYSQL_CONFIG) --libs) # In Debian based distros, libmariadbclient-dev provides mariadbclient (rather than mysqlclient) ifneq ($(findstring mariadbclient,$(LIB)),) MYSQL_LIB = mariadbclient else MYSQL_LIB = mysqlclient endif UNAME = uname OS := $(shell $(UNAME)) ifeq ($(OS), Darwin) DLSUFFIX = .dylib else DLSUFFIX = .so endif PG_CPPFLAGS += -D _MYSQL_LIBNAME=\"lib$(MYSQL_LIB)$(DLSUFFIX)\" ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) ifndef MAJORVERSION MAJORVERSION := $(basename $(VERSION)) endif ifeq (,$(findstring $(MAJORVERSION), 9.6 10 11 12 13 14)) $(error PostgreSQL 9.6, 10, 11, 12, 13 or 14 is required to compile this extension) endif else subdir = contrib/mysql_fdw top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif mysql_fdw-REL-2_7_0/README.md000066400000000000000000000212021414541277500155750ustar00rootroot00000000000000MySQL Foreign Data Wrapper for PostgreSQL ========================================= This PostgreSQL extension implements a Foreign Data Wrapper (FDW) for [MySQL][1]. Please note that this version of mysql_fdw works with PostgreSQL and EDB Postgres Advanced Server 9.6, 10, 11, 12, 13 and 14. Installation ------------ To compile the [MySQL][1] foreign data wrapper, MySQL's C client library is needed. This library can be downloaded from the official [MySQL website][1]. 1. To build on POSIX-compliant systems you need to ensure the `pg_config` executable is in your path when you run `make`. This executable is typically in your PostgreSQL installation's `bin` directory. For example: ``` $ export PATH=/usr/local/pgsql/bin/:$PATH ``` 2. The `mysql_config` must also be in the path, it resides in the MySQL `bin` directory. ``` $ export PATH=/usr/local/mysql/bin/:$PATH ``` 3. Compile the code using make. ``` $ make USE_PGXS=1 ``` 4. Finally install the foreign data wrapper. ``` $ make USE_PGXS=1 install ``` 5. Running regression test. ``` $ make USE_PGXS=1 installcheck ``` However, make sure to set the `MYSQL_HOST`, `MYSQL_PORT`, `MYSQL_USER_NAME`, and `MYSQL_PWD` environment variables correctly. The default settings can be found in the `mysql_init.sh` script. If you run into any issues, please [let us know][2]. Enhancements ------------ The following enhancements are added to the latest version of `mysql_fdw`: ### Write-able FDW The previous version was only read-only, the latest version provides the write capability. The user can now issue an insert, update, and delete statements for the foreign tables using the mysql_fdw. It uses the PG type casting mechanism to provide opposite type casting between MySQL and PG data types. ### Connection Pooling The latest version comes with a connection pooler that utilises the same MySQL database connection for all the queries in the same session. The previous version would open a new MySQL database connection for every query. This is a performance enhancement. ### WHERE clause push-down The latest version will push-down the foreign table where clause to the foreign server. The where condition on the foreign table will be executed on the foreign server hence there will be fewer rows to bring across to PostgreSQL. This is a performance feature. ### Column push-down The previous version was fetching all the columns from the target foreign table. The latest version does the column push-down and only brings back the columns that are part of the select target list. This is a performance feature. ### Prepared Statement (Refactoring for `select` queries to use prepared statement) The `select` queries are now using prepared statements instead of simple query protocol. ### JOIN push-down mysql_fdw now also supports join push-down. The joins between two foreign tables from the same remote MySQL server are pushed to a remote server, instead of fetching all the rows for both the tables and performing a join locally, thereby enhancing the performance. Currently, joins involving only relational and arithmetic operators in join-clauses are pushed down to avoid any potential join failure. Also, only the INNER and LEFT/RIGHT OUTER joins are supported, and not the FULL OUTER, SEMI, and ANTI join. This is a performance feature. ### AGGREGATE push-down mysql_fdw now also supports aggregate push-down. Push aggregates to the remote MySQL server instead of fetching all of the rows and aggregating them locally. This gives a very good performance boost for the cases where aggregates can be pushed down. The push-down is currently limited to aggregate functions min, max, sum, avg, and count, to avoid pushing down the functions that are not present on the MySQL server. Also, aggregate filters and orders are not pushed down. Usage ----- The following parameters can be set on a MySQL foreign server object: * `host`: Address or hostname of the MySQL server. Defaults to `127.0.0.1` * `port`: Port number of the MySQL server. Defaults to `3306` * `secure_auth`: Enable or disable secure authentication. Default is `true` * `init_command`: SQL statement to execute when connecting to the MySQL server. * `use_remote_estimate`: Controls whether mysql_fdw issues remote EXPLAIN commands to obtain cost estimates. Default is `false` * `reconnect`: Enable or disable automatic reconnection to the MySQL server if the existing connection is found to have been lost. Default is `false`. * `ssl_key`: The path name of the client private key file. * `ssl_cert`: The path name of the client public key certificate file. * `ssl_ca`: The path name of the Certificate Authority (CA) certificate file. This option, if used, must specify the same certificate used by the server. * `ssl_capath`: The path name of the directory that contains trusted SSL CA certificate files. * `ssl_cipher`: The list of permissible ciphers for SSL encryption. * `fetch_size`: This option specifies the number of rows mysql_fdw should get in each fetch operation. It can be specified for a foreign table or a foreign server. The option specified on a table overrides an option specified for the server. The default is `100`. The following parameters can be set on a MySQL foreign table object: * `dbname`: Name of the MySQL database to query. This is a mandatory option. * `table_name`: Name of the MySQL table, default is the same as foreign table. * `max_blob_size`: Max blob size to read without truncation. * `fetch_size`: Same as `fetch_size` parameter for foreign server. The following parameters need to supplied while creating user mapping. * `username`: Username to use when connecting to MySQL. * `password`: Password to authenticate to the MySQL server with. Examples -------- ```sql -- load extension first time after install CREATE EXTENSION mysql_fdw; -- create server object CREATE SERVER mysql_server FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host '127.0.0.1', port '3306'); -- create user mapping CREATE USER MAPPING FOR postgres SERVER mysql_server OPTIONS (username 'foo', password 'bar'); -- create foreign table CREATE FOREIGN TABLE warehouse ( warehouse_id int, warehouse_name text, warehouse_created timestamp ) SERVER mysql_server OPTIONS (dbname 'db', table_name 'warehouse'); -- insert new rows in table INSERT INTO warehouse values (1, 'UPS', current_date); INSERT INTO warehouse values (2, 'TV', current_date); INSERT INTO warehouse values (3, 'Table', current_date); -- select from table SELECT * FROM warehouse ORDER BY 1; warehouse_id | warehouse_name | warehouse_created -------------+----------------+------------------- 1 | UPS | 10-JUL-20 00:00:00 2 | TV | 10-JUL-20 00:00:00 3 | Table | 10-JUL-20 00:00:00 -- delete row from table DELETE FROM warehouse where warehouse_id = 3; -- update a row of table UPDATE warehouse set warehouse_name = 'UPS_NEW' where warehouse_id = 1; -- explain a table with verbose option EXPLAIN VERBOSE SELECT warehouse_id, warehouse_name FROM warehouse WHERE warehouse_name LIKE 'TV' limit 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------- Limit (cost=10.00..11.00 rows=1 width=36) Output: warehouse_id, warehouse_name -> Foreign Scan on public.warehouse (cost=10.00..1010.00 rows=1000 width=36) Output: warehouse_id, warehouse_name Local server startup cost: 10 Remote query: SELECT `warehouse_id`, `warehouse_name` FROM `db`.`warehouse` WHERE ((`warehouse_name` LIKE BINARY 'TV')) ``` Contributing ------------ If you experience any bug and have a fix for that, or have a new idea, create a ticket on github page. Before creating a pull request please read the [contributing guidelines][3]. Support ------- This project will be modified to maintain compatibility with new PostgreSQL and EDB Postgres Advanced Server releases. If you require commercial support, please contact the EnterpriseDB sales team, or check whether your existing PostgreSQL support provider can also support mysql_fdw. License ------- Copyright (c) 2011-2021, EnterpriseDB Corporation. Permission to use, copy, modify, and distribute this software and its documentation for any purpose, without fee, and without a written agreement is hereby granted, provided that the above copyright notice and this paragraph and the following two paragraphs appear in all copies. See the [`LICENSE`][4] file for full details. [1]: http://www.mysql.com [2]: https://github.com/enterprisedb/mysql_fdw/issues/new [3]: CONTRIBUTING.md [4]: LICENSE mysql_fdw-REL-2_7_0/connection.c000066400000000000000000000204751414541277500166340ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * connection.c * Connection management functions for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * connection.c * *------------------------------------------------------------------------- */ #include "postgres.h" #if PG_VERSION_NUM >= 130000 #include "common/hashfn.h" #endif #include "mb/pg_wchar.h" #include "mysql_fdw.h" #include "utils/hsearch.h" #include "utils/inval.h" #include "utils/memutils.h" #include "utils/syscache.h" /* Length of host */ #define HOST_LEN 256 /* * Connection cache hash table entry * * The lookup key in this hash table is the foreign server OID plus the user * mapping OID. (We use just one connection per user per foreign server, * so that we can ensure all scans use the same snapshot during a query.) */ typedef struct ConnCacheKey { Oid serverid; /* OID of foreign server */ Oid userid; /* OID of local user whose mapping we use */ } ConnCacheKey; typedef struct ConnCacheEntry { ConnCacheKey key; /* hash key (must be first) */ MYSQL *conn; /* connection to foreign server, or NULL */ bool invalidated; /* true if reconnect is pending */ uint32 server_hashvalue; /* hash value of foreign server OID */ uint32 mapping_hashvalue; /* hash value of user mapping OID */ } ConnCacheEntry; /* * Connection cache (initialized on first use) */ static HTAB *ConnectionHash = NULL; static void mysql_inval_callback(Datum arg, int cacheid, uint32 hashvalue); /* * mysql_get_connection: * Get a connection which can be used to execute queries on the remote * MySQL server with the user's authorization. A new connection is * established if we don't already have a suitable one. */ MYSQL * mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt) { bool found; ConnCacheEntry *entry; ConnCacheKey key; /* First time through, initialize connection cache hashtable */ if (ConnectionHash == NULL) { HASHCTL ctl; MemSet(&ctl, 0, sizeof(ctl)); ctl.keysize = sizeof(ConnCacheKey); ctl.entrysize = sizeof(ConnCacheEntry); ctl.hash = tag_hash; /* Allocate ConnectionHash in the cache context */ ctl.hcxt = CacheMemoryContext; ConnectionHash = hash_create("mysql_fdw connections", 8, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); /* * Register some callback functions that manage connection cleanup. * This should be done just once in each backend. */ CacheRegisterSyscacheCallback(FOREIGNSERVEROID, mysql_inval_callback, (Datum) 0); CacheRegisterSyscacheCallback(USERMAPPINGOID, mysql_inval_callback, (Datum) 0); } /* Create hash key for the entry. Assume no pad bytes in key struct */ key.serverid = server->serverid; key.userid = user->userid; /* * Find or create cached entry for requested connection. */ entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); if (!found) { /* Initialize new hashtable entry (key is already filled in) */ entry->conn = NULL; } /* If an existing entry has invalid connection then release it */ if (entry->conn != NULL && entry->invalidated) { elog(DEBUG3, "disconnecting mysql_fdw connection %p for option changes to take effect", entry->conn); mysql_close(entry->conn); entry->conn = NULL; } if (entry->conn == NULL) { #if PG_VERSION_NUM < 90600 Oid umoid; #endif entry->conn = mysql_connect(opt); elog(DEBUG3, "new mysql_fdw connection %p for server \"%s\"", entry->conn, server->servername); /* * Once the connection is established, then set the connection * invalidation flag to false, also set the server and user mapping * hash values. */ entry->invalidated = false; entry->server_hashvalue = GetSysCacheHashValue1(FOREIGNSERVEROID, ObjectIdGetDatum(server->serverid)); #if PG_VERSION_NUM >= 90600 entry->mapping_hashvalue = GetSysCacheHashValue1(USERMAPPINGOID, ObjectIdGetDatum(user->umid)); #else /* Pre-9.6, UserMapping doesn't store its OID, so look it up again */ umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER, ObjectIdGetDatum(user->userid), ObjectIdGetDatum(user->serverid)); if (!OidIsValid(umoid)) { /* Not found for the specific user -- try PUBLIC */ umoid = GetSysCacheOid2(USERMAPPINGUSERSERVER, ObjectIdGetDatum(InvalidOid), ObjectIdGetDatum(user->serverid)); } entry->mapping_hashvalue = GetSysCacheHashValue1(USERMAPPINGOID, ObjectIdGetDatum(umoid)); #endif } return entry->conn; } /* * mysql_cleanup_connection: * Delete all the cache entries on backend exists. */ void mysql_cleanup_connection(void) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; if (ConnectionHash == NULL) return; hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { if (entry->conn == NULL) continue; elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); mysql_close(entry->conn); entry->conn = NULL; } } /* * Release connection created by calling mysql_get_connection. */ void mysql_release_connection(MYSQL *conn) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; if (ConnectionHash == NULL) return; hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { if (entry->conn == NULL) continue; if (entry->conn == conn) { elog(DEBUG3, "disconnecting mysql_fdw connection %p", entry->conn); mysql_close(entry->conn); entry->conn = NULL; hash_seq_term(&scan); break; } } } MYSQL * mysql_connect(mysql_opt *opt) { MYSQL *conn; char *svr_database = opt->svr_database; bool svr_sa = opt->svr_sa; char *svr_init_command = opt->svr_init_command; char *ssl_cipher = opt->ssl_cipher; #if MYSQL_VERSION_ID < 80000 my_bool secure_auth = svr_sa; #endif /* Connect to the server */ conn = mysql_init(NULL); if (!conn) ereport(ERROR, (errcode(ERRCODE_FDW_OUT_OF_MEMORY), errmsg("failed to initialise the MySQL connection object"))); mysql_options(conn, MYSQL_SET_CHARSET_NAME, GetDatabaseEncodingName()); #if MYSQL_VERSION_ID < 80000 mysql_options(conn, MYSQL_SECURE_AUTH, &secure_auth); #endif if (!svr_sa) elog(WARNING, "MySQL secure authentication is off"); if (svr_init_command != NULL) mysql_options(conn, MYSQL_INIT_COMMAND, svr_init_command); /* * Enable or disable automatic reconnection to the MySQL server if * the existing connection is found to have been lost. */ mysql_options(conn, MYSQL_OPT_RECONNECT, &opt->reconnect); mysql_ssl_set(conn, opt->ssl_key, opt->ssl_cert, opt->ssl_ca, opt->ssl_capath, ssl_cipher); if (!mysql_real_connect(conn, opt->svr_address, opt->svr_username, opt->svr_password, svr_database, opt->svr_port, NULL, 0)) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_ESTABLISH_CONNECTION), errmsg("failed to connect to MySQL: %s", mysql_error(conn)))); /* Useful for verifying that the connection's secured */ elog(DEBUG1, "Successfully connected to MySQL database %s at server %s with cipher %s (server version: %s, protocol version: %d) ", (svr_database != NULL) ? svr_database : "", mysql_get_host_info(conn), (ssl_cipher != NULL) ? ssl_cipher : "", mysql_get_server_info(conn), mysql_get_proto_info(conn)); return conn; } /* * Connection invalidation callback function for mysql. * * After a change to a pg_foreign_server or pg_user_mapping catalog entry, * mark connections depending on that entry as needing to be remade. This * implementation is similar as pgfdw_inval_callback. */ static void mysql_inval_callback(Datum arg, int cacheid, uint32 hashvalue) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; Assert(cacheid == FOREIGNSERVEROID || cacheid == USERMAPPINGOID); /* ConnectionHash must exist already, if we're registered */ hash_seq_init(&scan, ConnectionHash); while ((entry = (ConnCacheEntry *) hash_seq_search(&scan))) { /* Ignore invalid entries */ if (entry->conn == NULL) continue; /* hashvalue == 0 means a cache reset, must clear all state */ if (hashvalue == 0 || (cacheid == FOREIGNSERVEROID && entry->server_hashvalue == hashvalue) || (cacheid == USERMAPPINGOID && entry->mapping_hashvalue == hashvalue)) entry->invalidated = true; } } mysql_fdw-REL-2_7_0/deparse.c000066400000000000000000001766171414541277500161320ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * deparse.c * Query deparser for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * deparse.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/heapam.h" #include "access/htup_details.h" #include "access/sysattr.h" #include "access/transam.h" #include "catalog/pg_aggregate.h" #include "catalog/pg_collation.h" #include "catalog/pg_namespace.h" #include "catalog/pg_operator.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "datatype/timestamp.h" #include "mysql_fdw.h" #include "nodes/nodeFuncs.h" #include "nodes/plannodes.h" #include "optimizer/clauses.h" #if PG_VERSION_NUM < 120000 #include "optimizer/var.h" #else #include "optimizer/optimizer.h" #endif #include "optimizer/prep.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" #include "pgtime.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #include "utils/timestamp.h" #include "utils/typcache.h" static char *mysql_quote_identifier(const char *str, char quotechar); /* * Global context for foreign_expr_walker's search of an expression tree. */ typedef struct foreign_glob_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ /* * For join pushdown, only a limited set of operators are allowed to be * pushed. This flag helps us identify if we are walking through the list * of join conditions. Also true for aggregate relations to restrict * aggregates for specified list. */ bool is_remote_cond; /* true for join or aggregate relations */ Relids relids; /* relids of base relations in the underlying * scan */ } foreign_glob_cxt; /* * Local (per-tree-level) context for foreign_expr_walker's search. * This is concerned with identifying collations used in the expression. */ typedef enum { FDW_COLLATE_NONE, /* expression is of a noncollatable type */ FDW_COLLATE_SAFE, /* collation derives from a foreign Var */ FDW_COLLATE_UNSAFE /* collation derives from something else */ } FDWCollateState; typedef struct foreign_loc_cxt { Oid collation; /* OID of current collation, if any */ FDWCollateState state; /* state of current collation choice */ } foreign_loc_cxt; /* * Context for deparseExpr */ typedef struct deparse_expr_cxt { PlannerInfo *root; /* global planner state */ RelOptInfo *foreignrel; /* the foreign relation we are planning for */ RelOptInfo *scanrel; /* the underlying scan relation. Same as * foreignrel, when that represents a join or * a base relation. */ StringInfo buf; /* output buffer to append to */ List **params_list; /* exprs that will become remote Params */ } deparse_expr_cxt; #define REL_ALIAS_PREFIX "r" /* Handy macro to add relation name qualification */ #define ADD_REL_QUALIFIER(buf, varno) \ appendStringInfo((buf), "%s%d.", REL_ALIAS_PREFIX, (varno)) /* * Functions to construct string representation of a node tree. */ static void deparseExpr(Expr *expr, deparse_expr_cxt *context); static void mysql_deparse_var(Var *node, deparse_expr_cxt *context); static void mysql_deparse_const(Const *node, deparse_expr_cxt *context); static void mysql_deparse_param(Param *node, deparse_expr_cxt *context); #if PG_VERSION_NUM < 120000 static void mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context); #else static void mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context); #endif static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context); static void mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context); static void mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform); static void mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context); static void mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *context); static void mysql_deparse_relabel_type(RelabelType *node, deparse_expr_cxt *context); static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context); static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context); static void mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context); static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context); static void mysql_deparse_relation(StringInfo buf, Relation rel); static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs); static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root, bool qualify_col); static void mysql_deparse_select_sql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context); static void mysql_append_conditions(List *exprs, deparse_expr_cxt *context); static void mysql_deparse_explicit_target_list(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context); static void mysql_deparse_from_expr_for_rel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, List **param_list); static void mysql_deparse_from_expr(List *quals, deparse_expr_cxt *context); static void mysql_append_function_name(Oid funcid, deparse_expr_cxt *context); static void mysql_deparse_aggref(Aggref *node, deparse_expr_cxt *context); #if PG_VERSION_NUM >= 100000 static void mysql_append_groupby_clause(List *tlist, deparse_expr_cxt *context); static Node *mysql_deparse_sort_group_clause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context); #endif /* * Functions to construct string representation of a specific types. */ static void deparse_interval(StringInfo buf, Datum datum); /* * Local variables. */ static char *cur_opname = NULL; /* * Append remote name of specified foreign table to buf. Use value of * table_name FDW option (if any) instead of relation's name. Similarly, * schema_name FDW option overrides schema name. */ static void mysql_deparse_relation(StringInfo buf, Relation rel) { ForeignTable *table; const char *nspname = NULL; const char *relname = NULL; ListCell *lc; /* Obtain additional catalog information. */ table = GetForeignTable(RelationGetRelid(rel)); /* * Use value of FDW options if any, instead of the name of object itself. */ foreach(lc, table->options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "dbname") == 0) nspname = defGetString(def); else if (strcmp(def->defname, "table_name") == 0) relname = defGetString(def); } /* * Note: we could skip printing the schema name if it's pg_catalog, but * that doesn't seem worth the trouble. */ if (nspname == NULL) nspname = get_namespace_name(RelationGetNamespace(rel)); if (relname == NULL) relname = RelationGetRelationName(rel); appendStringInfo(buf, "%s.%s", mysql_quote_identifier(nspname, '`'), mysql_quote_identifier(relname, '`')); } static char * mysql_quote_identifier(const char *str, char quotechar) { char *result = palloc(strlen(str) * 2 + 3); char *res = result; *res++ = quotechar; while (*str) { if (*str == quotechar) *res++ = *str; *res++ = *str; str++; } *res++ = quotechar; *res++ = '\0'; return result; } /* * mysql_deparse_select_stmt_for_rel * Deparse SELECT statement for given relation into buf. * * tlist contains the list of desired columns to be fetched from foreign * server. For a base relation fpinfo->attrs_used is used to construct * SELECT clause, hence the tlist is ignored for a base relation. * * remote_conds is the list of conditions to be deparsed into the WHERE clause. * * If params_list is not NULL, it receives a list of Params and other-relation * Vars used in the clauses; these values must be transmitted to the remote * server as parameter values. * * If params_list is NULL, we're generating the query for EXPLAIN purposes, * so Params and other-relation Vars should be replaced by dummy values. * * List of columns selected is returned in retrieved_attrs. */ 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) { deparse_expr_cxt context; List *quals; #if PG_VERSION_NUM >= 100000 MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) rel->fdw_private; #endif /* * We handle relations for foreign tables and joins between those and upper * relations. */ #if PG_VERSION_NUM >= 100000 Assert(IS_JOIN_REL(rel) || IS_SIMPLE_REL(rel) || IS_UPPER_REL(rel)); #else Assert(rel->reloptkind == RELOPT_JOINREL || rel->reloptkind == RELOPT_BASEREL || rel->reloptkind == RELOPT_UPPER_REL || rel->reloptkind == RELOPT_OTHER_MEMBER_REL); #endif /* Fill portions of context common to base relation */ context.buf = buf; context.root = root; context.foreignrel = rel; context.params_list = params_list; #if PG_VERSION_NUM >= 100000 context.scanrel = IS_UPPER_REL(rel) ? fpinfo->outerrel : rel; #else context.scanrel = rel; #endif /* Construct SELECT clause */ mysql_deparse_select_sql(tlist, retrieved_attrs, &context); /* * For upper relations, the WHERE clause is built from the remote * conditions of the underlying scan relation; otherwise, we can use the * supplied list of remote conditions directly. */ #if PG_VERSION_NUM >= 100000 if (IS_UPPER_REL(rel)) { MySQLFdwRelationInfo *ofpinfo; ofpinfo = (MySQLFdwRelationInfo *) fpinfo->outerrel->fdw_private; quals = ofpinfo->remote_conds; } else #endif quals = remote_conds; /* Construct FROM and WHERE clauses */ mysql_deparse_from_expr(quals, &context); #if PG_VERSION_NUM >= 100000 if (IS_UPPER_REL(rel)) { /* Append GROUP BY clause */ mysql_append_groupby_clause(fpinfo->grouped_tlist, &context); /* Append HAVING clause */ if (remote_conds) { appendStringInfoString(buf, " HAVING "); mysql_append_conditions(remote_conds, &context); } } #endif } /* * mysql_deparse_select_sql * Construct a simple SELECT statement that retrieves desired columns * of the specified foreign table, and append it to "buf". The output * contains just "SELECT ...". * * tlist is the list of desired columns. Read prologue of * mysql_deparse_select_stmt_for_rel() for details. * * We also create an integer List of the columns being retrieved, which is * returned to *retrieved_attrs. */ static void mysql_deparse_select_sql(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) { StringInfo buf = context->buf; RelOptInfo *foreignrel = context->foreignrel; PlannerInfo *root = context->root; /* * Construct SELECT list */ appendStringInfoString(buf, "SELECT "); #if PG_VERSION_NUM >= 100000 if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) #else if (foreignrel->reloptkind == RELOPT_JOINREL || foreignrel->reloptkind == RELOPT_UPPER_REL) #endif { /* * For a join or upper relation the input tlist gives the list of * columns required to be fetched from the foreign server. */ mysql_deparse_explicit_target_list(tlist, retrieved_attrs, context); } else { RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); Relation rel; MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private; /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ #if PG_VERSION_NUM < 130000 rel = heap_open(rte->relid, NoLock); #else rel = table_open(rte->relid, NoLock); #endif mysql_deparse_target_list(buf, root, foreignrel->relid, rel, fpinfo->attrs_used, retrieved_attrs); #if PG_VERSION_NUM < 130000 heap_close(rel, NoLock); #else table_close(rel, NoLock); #endif } } /* * mysql_deparse_explicit_target_list * Deparse given targetlist and append it to context->buf. * * retrieved_attrs is the list of continuously increasing integers starting * from 1. It has same number of entries as tlist. */ static void mysql_deparse_explicit_target_list(List *tlist, List **retrieved_attrs, deparse_expr_cxt *context) { ListCell *lc; StringInfo buf = context->buf; int i = 0; *retrieved_attrs = NIL; foreach(lc, tlist) { if (i > 0) appendStringInfoString(buf, ", "); deparseExpr((Expr *) lfirst(lc), context); *retrieved_attrs = lappend_int(*retrieved_attrs, i + 1); i++; } if (i == 0) appendStringInfoString(buf, "NULL"); } /* * mysql_deparse_from_expr * Construct a FROM clause and, if needed, a WHERE clause, and * append those to "buf". * * quals is the list of clauses to be included in the WHERE clause. */ static void mysql_deparse_from_expr(List *quals, deparse_expr_cxt *context) { StringInfo buf = context->buf; RelOptInfo *scanrel = context->scanrel; /* For upper relations, scanrel must be either a joinrel or a baserel */ #if PG_VERSION_NUM >= 100000 Assert(!IS_UPPER_REL(context->foreignrel) || IS_JOIN_REL(scanrel) || IS_SIMPLE_REL(scanrel)); #endif /* Construct FROM clause */ appendStringInfoString(buf, " FROM "); mysql_deparse_from_expr_for_rel(buf, context->root, scanrel, (bms_membership(scanrel->relids) == BMS_MULTIPLE), context->params_list); /* Construct WHERE clause */ if (quals != NIL) { appendStringInfoString(buf, " WHERE "); mysql_append_conditions(quals, context); } } /* * Deparse remote INSERT statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs) { ListCell *lc; appendStringInfoString(buf, "INSERT INTO "); mysql_deparse_relation(buf, rel); if (targetAttrs) { AttrNumber pindex; bool first; appendStringInfoChar(buf, '('); first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, attnum, root, false); } appendStringInfoString(buf, ") VALUES ("); pindex = 1; first = true; foreach(lc, targetAttrs) { if (!first) appendStringInfoString(buf, ", "); first = false; appendStringInfo(buf, "?"); pindex++; } appendStringInfoChar(buf, ')'); } else appendStringInfoString(buf, " DEFAULT VALUES"); } void mysql_deparse_analyze(StringInfo sql, char *dbname, char *relname) { appendStringInfo(sql, "SELECT"); appendStringInfo(sql, " round(((data_length + index_length)), 2)"); appendStringInfo(sql, " FROM information_schema.TABLES"); appendStringInfo(sql, " WHERE table_schema = '%s' AND table_name = '%s'", dbname, relname); } /* * Emit a target list that retrieves the columns specified in attrs_used. * This is used for both SELECT and RETURNING targetlists. */ static void mysql_deparse_target_list(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, Bitmapset *attrs_used, List **retrieved_attrs) { TupleDesc tupdesc = RelationGetDescr(rel); bool have_wholerow; bool first; int i; /* If there's a whole-row reference, we'll need all the columns. */ have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); first = true; *retrieved_attrs = NIL; for (i = 1; i <= tupdesc->natts; i++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, i - 1); /* Ignore dropped attributes. */ if (attr->attisdropped) continue; if (have_wholerow || bms_is_member(i - FirstLowInvalidHeapAttributeNumber, attrs_used)) { if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, i, root, false); *retrieved_attrs = lappend_int(*retrieved_attrs, i); } } /* Don't generate bad syntax if no undropped columns */ if (first) appendStringInfoString(buf, "NULL"); } /* * Construct name to use for given column, and emit it into buf. If it has a * column_name FDW option, use that instead of attribute name. */ static void mysql_deparse_column_ref(StringInfo buf, int varno, int varattno, PlannerInfo *root, bool qualify_col) { RangeTblEntry *rte; char *colname = NULL; List *options; ListCell *lc; /* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */ Assert(!IS_SPECIAL_VARNO(varno)); /* Get RangeTblEntry from array in PlannerInfo. */ rte = planner_rt_fetch(varno, root); /* * If it's a column of a foreign table, and it has the column_name FDW * option, use that value. */ options = GetForeignColumnOptions(rte->relid, varattno); foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "column_name") == 0) { colname = defGetString(def); break; } } /* * If it's a column of a regular table or it doesn't have column_name * FDW option, use attribute name. */ if (colname == NULL) #if PG_VERSION_NUM >= 110000 colname = get_attname(rte->relid, varattno, false); #else colname = get_relid_attribute_name(rte->relid, varattno); #endif if (qualify_col) ADD_REL_QUALIFIER(buf, varno); appendStringInfoString(buf, mysql_quote_identifier(colname, '`')); } static void mysql_deparse_string(StringInfo buf, const char *val, bool isstr) { const char *valptr; int i = 0; if (isstr) appendStringInfoChar(buf, '\''); for (valptr = val; *valptr; valptr++,i++) { char ch = *valptr; /* * Remove '{', '}', and \" character from the string. Because this * syntax is not recognize by the remote MySQL server. */ if ((ch == '{' && i == 0) || (ch == '}' && (i == (strlen(val) - 1))) || ch == '\"') continue; if (isstr && ch == ',') { appendStringInfoString(buf, "', '"); continue; } appendStringInfoChar(buf, ch); } if (isstr) appendStringInfoChar(buf, '\''); } /* * Append a SQL string literal representing "val" to buf. */ static void mysql_deparse_string_literal(StringInfo buf, const char *val) { const char *valptr; appendStringInfoChar(buf, '\''); for (valptr = val; *valptr; valptr++) { char ch = *valptr; if (SQL_STR_DOUBLE(ch, true)) appendStringInfoChar(buf, ch); appendStringInfoChar(buf, ch); } appendStringInfoChar(buf, '\''); } /* * Deparse given expression into context->buf. * * This function must support all the same node types that foreign_expr_walker * accepts. * * Note: unlike ruleutils.c, we just use a simple hard-wired parenthesization * scheme: anything more complex than a Var, Const, function call or cast * should be self-parenthesized. */ static void deparseExpr(Expr *node, deparse_expr_cxt *context) { if (node == NULL) return; switch (nodeTag(node)) { case T_Var: mysql_deparse_var((Var *) node, context); break; case T_Const: mysql_deparse_const((Const *) node, context); break; case T_Param: mysql_deparse_param((Param *) node, context); break; #if PG_VERSION_NUM < 120000 case T_ArrayRef: mysql_deparse_array_ref((ArrayRef *) node, context); #else case T_SubscriptingRef: mysql_deparse_array_ref((SubscriptingRef *) node, context); #endif break; case T_FuncExpr: mysql_deparse_func_expr((FuncExpr *) node, context); break; case T_OpExpr: mysql_deparse_op_expr((OpExpr *) node, context); break; case T_DistinctExpr: mysql_deparse_distinct_expr((DistinctExpr *) node, context); break; case T_ScalarArrayOpExpr: mysql_deparse_scalar_array_op_expr((ScalarArrayOpExpr *) node, context); break; case T_RelabelType: mysql_deparse_relabel_type((RelabelType *) node, context); break; case T_BoolExpr: mysql_deparse_bool_expr((BoolExpr *) node, context); break; case T_NullTest: mysql_deparse_null_test((NullTest *) node, context); break; case T_ArrayExpr: mysql_deparse_array_expr((ArrayExpr *) node, context); break; case T_Aggref: mysql_deparse_aggref((Aggref *) node, context); break; default: elog(ERROR, "unsupported expression type for deparse: %d", (int) nodeTag(node)); break; } } /* * Deparse Interval type into MySQL Interval representation. */ static void deparse_interval(StringInfo buf, Datum datum) { struct pg_tm tm; fsec_t fsec; bool is_first = true; #define append_interval(expr, unit) \ do { \ if (!is_first) \ appendStringInfo(buf, " %s ", cur_opname); \ appendStringInfo(buf, "INTERVAL %d %s", expr, unit); \ is_first = false; \ } while (0) /* Check saved opname. It could be only "+" and "-" */ Assert(cur_opname); if (interval2tm(*DatumGetIntervalP(datum), &tm, &fsec) != 0) elog(ERROR, "could not convert interval to tm"); if (tm.tm_year > 0) append_interval(tm.tm_year, "YEAR"); if (tm.tm_mon > 0) append_interval(tm.tm_mon, "MONTH"); if (tm.tm_mday > 0) append_interval(tm.tm_mday, "DAY"); if (tm.tm_hour > 0) append_interval(tm.tm_hour, "HOUR"); if (tm.tm_min > 0) append_interval(tm.tm_min, "MINUTE"); if (tm.tm_sec > 0) append_interval(tm.tm_sec, "SECOND"); if (fsec > 0) { if (!is_first) appendStringInfo(buf, " %s ", cur_opname); #ifdef HAVE_INT64_TIMESTAMP appendStringInfo(buf, "INTERVAL %d MICROSECOND", fsec); #else appendStringInfo(buf, "INTERVAL %f MICROSECOND", fsec); #endif } } /* * Deparse remote UPDATE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, char *attname) { AttrNumber pindex; bool first; ListCell *lc; appendStringInfoString(buf, "UPDATE "); mysql_deparse_relation(buf, rel); appendStringInfoString(buf, " SET "); pindex = 2; first = true; foreach(lc, targetAttrs) { int attnum = lfirst_int(lc); if (attnum == 1) continue; if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_column_ref(buf, rtindex, attnum, root, false); appendStringInfo(buf, " = ?"); pindex++; } appendStringInfo(buf, " WHERE %s = ?", attname); } /* * Deparse remote DELETE statement * * The statement text is appended to buf, and we also create an integer List * of the columns being retrieved by RETURNING (if any), which is returned * to *retrieved_attrs. */ void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, char *name) { appendStringInfoString(buf, "DELETE FROM "); mysql_deparse_relation(buf, rel); appendStringInfo(buf, " WHERE %s = ?", name); } /* * Deparse given Var node into context->buf. * * If the Var belongs to the foreign relation, just print its remote name. * Otherwise, it's effectively a Param (and will in fact be a Param at * run time). Handle it the same way we handle plain Params --- see * deparseParam for comments. */ static void mysql_deparse_var(Var *node, deparse_expr_cxt *context) { Relids relids = context->scanrel->relids; bool qualify_col; qualify_col = (bms_membership(relids) == BMS_MULTIPLE); if (bms_is_member(node->varno, relids) && node->varlevelsup == 0) { /* Var belongs to foreign table */ mysql_deparse_column_ref(context->buf, node->varno, node->varattno, context->root, qualify_col); } else { /* Treat like a Param */ if (context->params_list) { int pindex = 0; ListCell *lc; /* Find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* Not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } mysql_print_remote_param(pindex, node->vartype, node->vartypmod, context); } else mysql_print_remote_placeholder(node->vartype, node->vartypmod, context); } } /* * Deparse given constant value into context->buf. * * This function has to be kept in sync with ruleutils.c's get_const_expr. */ static void mysql_deparse_const(Const *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; Oid typoutput; bool typIsVarlena; char *extval; if (node->constisnull) { appendStringInfoString(buf, "NULL"); return; } getTypeOutputInfo(node->consttype, &typoutput, &typIsVarlena); switch (node->consttype) { case INT2OID: case INT4OID: case INT8OID: case OIDOID: case FLOAT4OID: case FLOAT8OID: case NUMERICOID: { extval = OidOutputFunctionCall(typoutput, node->constvalue); /* * No need to quote unless it's a special value such as 'NaN'. * See comments in get_const_expr(). */ if (strspn(extval, "0123456789+-eE.") == strlen(extval)) { if (extval[0] == '+' || extval[0] == '-') appendStringInfo(buf, "(%s)", extval); else appendStringInfoString(buf, extval); } else appendStringInfo(buf, "'%s'", extval); } break; case BITOID: case VARBITOID: extval = OidOutputFunctionCall(typoutput, node->constvalue); appendStringInfo(buf, "B'%s'", extval); break; case BOOLOID: extval = OidOutputFunctionCall(typoutput, node->constvalue); if (strcmp(extval, "t") == 0) appendStringInfoString(buf, "true"); else appendStringInfoString(buf, "false"); break; case INTERVALOID: deparse_interval(buf, node->constvalue); break; case BYTEAOID: /* * The string for BYTEA always seems to be in the format "\\x##" * where # is a hex digit, Even if the value passed in is * 'hi'::bytea we will receive "\x6869". Making this assumption * allows us to quickly convert postgres escaped strings to mysql * ones for comparison */ extval = OidOutputFunctionCall(typoutput, node->constvalue); appendStringInfo(buf, "X\'%s\'", extval + 2); break; default: extval = OidOutputFunctionCall(typoutput, node->constvalue); mysql_deparse_string_literal(buf, extval); break; } } /* * Deparse given Param node. * * If we're generating the query "for real", add the Param to * context->params_list if it's not already present, and then use its index * in that list as the remote parameter number. During EXPLAIN, there's * no need to identify a parameter number. */ static void mysql_deparse_param(Param *node, deparse_expr_cxt *context) { if (context->params_list) { int pindex = 0; ListCell *lc; /* Find its index in params_list */ foreach(lc, *context->params_list) { pindex++; if (equal(node, (Node *) lfirst(lc))) break; } if (lc == NULL) { /* Not in list, so add it */ pindex++; *context->params_list = lappend(*context->params_list, node); } mysql_print_remote_param(pindex, node->paramtype, node->paramtypmod, context); } else mysql_print_remote_placeholder(node->paramtype, node->paramtypmod, context); } /* * Deparse an array subscript expression. */ static void #if PG_VERSION_NUM < 120000 mysql_deparse_array_ref(ArrayRef *node, deparse_expr_cxt *context) #else mysql_deparse_array_ref(SubscriptingRef *node, deparse_expr_cxt *context) #endif { StringInfo buf = context->buf; ListCell *lowlist_item; ListCell *uplist_item; /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* * Deparse referenced array expression first. If that expression includes * a cast, we have to parenthesize to prevent the array subscript from * being taken as typename decoration. We can avoid that in the typical * case of subscripting a Var, but otherwise do it. */ if (IsA(node->refexpr, Var)) deparseExpr(node->refexpr, context); else { appendStringInfoChar(buf, '('); deparseExpr(node->refexpr, context); appendStringInfoChar(buf, ')'); } /* Deparse subscript expressions. */ lowlist_item = list_head(node->reflowerindexpr); /* could be NULL */ foreach(uplist_item, node->refupperindexpr) { appendStringInfoChar(buf, '['); if (lowlist_item) { deparseExpr(lfirst(lowlist_item), context); appendStringInfoChar(buf, ':'); #if PG_VERSION_NUM < 130000 lowlist_item = lnext(lowlist_item); #else lowlist_item = lnext(node->reflowerindexpr, lowlist_item); #endif } deparseExpr(lfirst(uplist_item), context); appendStringInfoChar(buf, ']'); } appendStringInfoChar(buf, ')'); } /* * This is possible that the name of function in PostgreSQL and mysql differ, * so return the mysql eloquent function name. */ static char * mysql_replace_function(char *in) { if (strcmp(in, "btrim") == 0) return "trim"; return in; } /* * Deparse a function call. */ static void mysql_deparse_func_expr(FuncExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool first; ListCell *arg; /* * If the function call came from an implicit coercion, then just show the * first argument. */ if (node->funcformat == COERCE_IMPLICIT_CAST) { deparseExpr((Expr *) linitial(node->args), context); return; } /* If the function call came from a cast, then show the first argument. */ if (node->funcformat == COERCE_EXPLICIT_CAST) { int32 coercedTypmod; /* Get the typmod if this is a length-coercion function */ (void) exprIsLengthCoercion((Node *) node, &coercedTypmod); deparseExpr((Expr *) linitial(node->args), context); return; } /* * Normal function: display as proname(args). */ mysql_append_function_name(node->funcid, context); appendStringInfoChar(buf, '('); /* ... and all the arguments */ first = true; foreach(arg, node->args) { if (!first) appendStringInfoString(buf, ", "); deparseExpr((Expr *) lfirst(arg), context); first = false; } appendStringInfoChar(buf, ')'); } /* * Deparse given operator expression. To avoid problems around * priority of operations, we always parenthesize the arguments. */ static void mysql_deparse_op_expr(OpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Form_pg_operator form; char oprkind; ListCell *arg; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); oprkind = form->oprkind; /* Sanity check. */ Assert((oprkind == 'r' && list_length(node->args) == 1) || (oprkind == 'l' && list_length(node->args) == 1) || (oprkind == 'b' && list_length(node->args) == 2)); /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); /* Deparse left operand. */ if (oprkind == 'r' || oprkind == 'b') { arg = list_head(node->args); deparseExpr(lfirst(arg), context); appendStringInfoChar(buf, ' '); } /* Deparse operator name. */ mysql_deparse_operator_name(buf, form); /* Deparse right operand. */ if (oprkind == 'l' || oprkind == 'b') { arg = list_tail(node->args); appendStringInfoChar(buf, ' '); deparseExpr(lfirst(arg), context); } appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } /* * Print the name of an operator. */ static void mysql_deparse_operator_name(StringInfo buf, Form_pg_operator opform) { /* opname is not a SQL identifier, so we should not quote it. */ cur_opname = NameStr(opform->oprname); /* Print schema name only if it's not pg_catalog */ if (opform->oprnamespace != PG_CATALOG_NAMESPACE) { const char *opnspname; opnspname = get_namespace_name(opform->oprnamespace); /* Print fully qualified operator name. */ appendStringInfo(buf, "OPERATOR(%s.%s)", mysql_quote_identifier(opnspname, '`'), cur_opname); } else { if (strcmp(cur_opname, "~~") == 0) appendStringInfoString(buf, "LIKE BINARY"); else if (strcmp(cur_opname, "~~*") == 0) appendStringInfoString(buf, "LIKE"); else if (strcmp(cur_opname, "!~~") == 0) appendStringInfoString(buf, "NOT LIKE BINARY"); else if (strcmp(cur_opname, "!~~*") == 0) appendStringInfoString(buf, "NOT LIKE"); else if (strcmp(cur_opname, "~") == 0) appendStringInfoString(buf, "REGEXP BINARY"); else if (strcmp(cur_opname, "~*") == 0) appendStringInfoString(buf, "REGEXP"); else if (strcmp(cur_opname, "!~") == 0) appendStringInfoString(buf, "NOT REGEXP BINARY"); else if (strcmp(cur_opname, "!~*") == 0) appendStringInfoString(buf, "NOT REGEXP"); else appendStringInfoString(buf, cur_opname); } } /* * Deparse IS DISTINCT FROM. */ static void mysql_deparse_distinct_expr(DistinctExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; Assert(list_length(node->args) == 2); appendStringInfoChar(buf, '('); deparseExpr(linitial(node->args), context); appendStringInfoString(buf, " IS DISTINCT FROM "); deparseExpr(lsecond(node->args), context); appendStringInfoChar(buf, ')'); } /* * Deparse given ScalarArrayOpExpr expression. To avoid problems * around priority of operations, we always parenthesize the arguments. */ static void mysql_deparse_scalar_array_op_expr(ScalarArrayOpExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple tuple; Expr *arg1; Expr *arg2; Form_pg_operator form; char *opname; Oid typoutput; bool typIsVarlena; char *extval; /* Retrieve information about the operator from system catalog. */ tuple = SearchSysCache1(OPEROID, ObjectIdGetDatum(node->opno)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for operator %u", node->opno); form = (Form_pg_operator) GETSTRUCT(tuple); /* Sanity check. */ Assert(list_length(node->args) == 2); /* Deparse left operand. */ arg1 = linitial(node->args); deparseExpr(arg1, context); appendStringInfoChar(buf, ' '); opname = NameStr(form->oprname); if (strcmp(opname, "<>") == 0) appendStringInfo(buf, " NOT "); /* Deparse operator name plus decoration. */ appendStringInfo(buf, " IN ("); /* Deparse right operand. */ arg2 = lsecond(node->args); switch (nodeTag((Node *) arg2)) { case T_Const: { Const *c = (Const *) arg2; if (c->constisnull) { appendStringInfoString(buf, " NULL"); ReleaseSysCache(tuple); return; } getTypeOutputInfo(c->consttype, &typoutput, &typIsVarlena); extval = OidOutputFunctionCall(typoutput, c->constvalue); switch (c->consttype) { case INT4ARRAYOID: case OIDARRAYOID: mysql_deparse_string(buf, extval, false); break; default: mysql_deparse_string(buf, extval, true); break; } } break; default: deparseExpr(arg2, context); break; } appendStringInfoChar(buf, ')'); ReleaseSysCache(tuple); } /* * Deparse a RelabelType (binary-compatible cast) node. */ static void mysql_deparse_relabel_type(RelabelType *node, deparse_expr_cxt *context) { deparseExpr(node->arg, context); } /* * Deparse a BoolExpr node. * * Note: by the time we get here, AND and OR expressions have been flattened * into N-argument form, so we'd better be prepared to deal with that. */ static void mysql_deparse_bool_expr(BoolExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; const char *op = NULL; /* keep compiler quiet */ bool first; ListCell *lc; switch (node->boolop) { case AND_EXPR: op = "AND"; break; case OR_EXPR: op = "OR"; break; case NOT_EXPR: appendStringInfoChar(buf, '('); appendStringInfoString(buf, "NOT "); deparseExpr(linitial(node->args), context); appendStringInfoChar(buf, ')'); return; } appendStringInfoChar(buf, '('); first = true; foreach(lc, node->args) { if (!first) appendStringInfo(buf, " %s ", op); deparseExpr((Expr *) lfirst(lc), context); first = false; } appendStringInfoChar(buf, ')'); } /* * Deparse IS [NOT] NULL expression. */ static void mysql_deparse_null_test(NullTest *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfoChar(buf, '('); deparseExpr(node->arg, context); if (node->nulltesttype == IS_NULL) appendStringInfoString(buf, " IS NULL"); else appendStringInfoString(buf, " IS NOT NULL"); appendStringInfoChar(buf, ')'); } /* * Deparse ARRAY[...] construct. */ static void mysql_deparse_array_expr(ArrayExpr *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool first = true; ListCell *lc; appendStringInfoString(buf, "ARRAY["); foreach(lc, node->elements) { if (!first) appendStringInfoString(buf, ", "); deparseExpr(lfirst(lc), context); first = false; } appendStringInfoChar(buf, ']'); } /* * Print the representation of a parameter to be sent to the remote side. * * Note: we always label the Param's type explicitly rather than relying on * transmitting a numeric type OID in PQexecParams(). This allows us to * avoid assuming that types have the same OIDs on the remote side as they * do locally --- they need only have the same names. */ static void mysql_print_remote_param(int paramindex, Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfo(buf, "?"); } static void mysql_print_remote_placeholder(Oid paramtype, int32 paramtypmod, deparse_expr_cxt *context) { StringInfo buf = context->buf; appendStringInfo(buf, "(SELECT null)"); } /* * Return true if given object is one of PostgreSQL's built-in objects. * * We use FirstBootstrapObjectId as the cutoff, so that we only consider * objects with hand-assigned OIDs to be "built in", not for instance any * function or type defined in the information_schema. * * Our constraints for dealing with types are tighter than they are for * functions or operators: we want to accept only types that are in pg_catalog, * else format_type might incorrectly fail to schema-qualify their names. * (This could be fixed with some changes to format_type, but for now there's * no need.) Thus we must exclude information_schema types. * * XXX there is a problem with this, which is that the set of built-in * objects expands over time. Something that is built-in to us might not * be known to the remote server, if it's of an older version. But keeping * track of that would be a huge exercise. */ static bool is_builtin(Oid oid) { return (oid < FirstBootstrapObjectId); } /* * Check if expression is safe to execute remotely, and return true if so. * * In addition, *outer_cxt is updated with collation information. * * We must check that the expression contains only node types we can deparse, * that all types/functions/operators are safe to send (which we approximate * as being built-in), and that all collations used in the expression derive * from Vars of the foreign table. Because of the latter, the logic is pretty * close to assign_collations_walker() in parse_collate.c, though we can assume * here that the given expression is valid. */ static bool foreign_expr_walker(Node *node, foreign_glob_cxt *glob_cxt, foreign_loc_cxt *outer_cxt) { bool check_type = true; foreign_loc_cxt inner_cxt; Oid collation; FDWCollateState state; /* Need do nothing for empty subexpressions */ if (node == NULL) return true; /* Set up inner_cxt for possible recursion to child nodes */ inner_cxt.collation = InvalidOid; inner_cxt.state = FDW_COLLATE_NONE; switch (nodeTag(node)) { case T_Var: { Var *var = (Var *) node; /* * If the Var is from the foreign table, we consider its * collation (if any) safe to use. If it is from another * table, we treat its collation the same way as we would a * Param's collation, i.e. it's not safe for it to have a * non-default collation. */ if (bms_is_member(var->varno, glob_cxt->relids) && var->varlevelsup == 0) { /* Var belongs to foreign table */ collation = var->varcollid; state = OidIsValid(collation) ? FDW_COLLATE_SAFE : FDW_COLLATE_NONE; } else { /* Var belongs to some other table */ if (var->varcollid != InvalidOid && var->varcollid != DEFAULT_COLLATION_OID) return false; /* We can consider that it doesn't set collation */ collation = InvalidOid; state = FDW_COLLATE_NONE; } } break; case T_Const: { Const *c = (Const *) node; /* * If the constant has non default collation, either it's of a * non-built in type, or it reflects folding of a CollateExpr; * either way, it's unsafe to send to the remote. */ if (c->constcollid != InvalidOid && c->constcollid != DEFAULT_COLLATION_OID) return false; /* Otherwise, we can consider that it doesn't set collation */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_Param: { Param *p = (Param *) node; /* * Collation rule is same as for Consts and non-foreign Vars. */ collation = p->paramcollid; if (collation == InvalidOid || collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; #if PG_VERSION_NUM < 120000 case T_ArrayRef: { ArrayRef *ar = (ArrayRef *) node; #else case T_SubscriptingRef: { SubscriptingRef *ar = (SubscriptingRef *) node; #endif /* Should not be in the join clauses of the Join-pushdown */ if (glob_cxt->is_remote_cond) return false; /* Assignment should not be in restrictions. */ if (ar->refassgnexpr != NULL) return false; /* * Recurse to remaining subexpressions. Since the array * subscripts must yield (noncollatable) integers, they won't * affect the inner_cxt state. */ if (!foreign_expr_walker((Node *) ar->refupperindexpr, glob_cxt, &inner_cxt)) return false; if (!foreign_expr_walker((Node *) ar->reflowerindexpr, glob_cxt, &inner_cxt)) return false; if (!foreign_expr_walker((Node *) ar->refexpr, glob_cxt, &inner_cxt)) return false; /* * Array subscripting should yield same collation as input, * but for safety use same logic as for function nodes. */ collation = ar->refcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_FuncExpr: { FuncExpr *fe = (FuncExpr *) node; /* Should not be in the join clauses of the Join-pushdown */ if (glob_cxt->is_remote_cond) return false; /* * If function used by the expression is not built-in, it * can't be sent to remote because it might have incompatible * semantics on remote side. */ if (!is_builtin(fe->funcid)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) fe->args, glob_cxt, &inner_cxt)) return false; /* * If function's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (fe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || fe->inputcollid != inner_cxt.collation) return false; /* * Detect whether node is introducing a collation not derived * from a foreign Var. (If so, we just mark it unsafe for now * rather than immediately returning false, since the parent * node might not care.) */ collation = fe->funccollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_OpExpr: case T_DistinctExpr: /* struct-equivalent to OpExpr */ { OpExpr *oe = (OpExpr *) node; const char *operatorName = get_opname(oe->opno); /* * Join-pushdown allows only a few operators to be pushed down. */ if (glob_cxt->is_remote_cond && (!(strcmp(operatorName, "<") == 0 || strcmp(operatorName, ">") == 0 || strcmp(operatorName, "<=") == 0 || strcmp(operatorName, ">=") == 0 || strcmp(operatorName, "<>") == 0 || strcmp(operatorName, "=") == 0 || strcmp(operatorName, "+") == 0 || strcmp(operatorName, "-") == 0 || strcmp(operatorName, "*") == 0 || strcmp(operatorName, "%") == 0 || strcmp(operatorName, "/") == 0))) return false; /* * Similarly, only built-in operators can be sent to remote. * (If the operator is, surely its underlying function is * too.) */ if (!is_builtin(oe->opno)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt)) return false; /* * If operator's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (oe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) return false; /* Result-collation handling is same as for functions */ collation = oe->opcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_ScalarArrayOpExpr: { ScalarArrayOpExpr *oe = (ScalarArrayOpExpr *) node; /* Should not be in the join clauses of the Join-pushdown */ if (glob_cxt->is_remote_cond) return false; /* * Again, only built-in operators can be sent to remote. */ if (!is_builtin(oe->opno)) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) oe->args, glob_cxt, &inner_cxt)) return false; /* * If operator's input collation is not derived from a foreign * Var, it can't be sent to remote. */ if (oe->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || oe->inputcollid != inner_cxt.collation) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_RelabelType: { RelabelType *r = (RelabelType *) node; /* * Recurse to input subexpression. */ if (!foreign_expr_walker((Node *) r->arg, glob_cxt, &inner_cxt)) return false; /* * RelabelType must not introduce a collation not derived from * an input foreign Var. */ collation = r->resultcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_BoolExpr: { BoolExpr *b = (BoolExpr *) node; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) b->args, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_NullTest: { NullTest *nt = (NullTest *) node; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) nt->arg, glob_cxt, &inner_cxt)) return false; /* Output is always boolean and so noncollatable. */ collation = InvalidOid; state = FDW_COLLATE_NONE; } break; case T_ArrayExpr: { ArrayExpr *a = (ArrayExpr *) node; /* Should not be in the join clauses of the Join-pushdown */ if (glob_cxt->is_remote_cond) return false; /* * Recurse to input subexpressions. */ if (!foreign_expr_walker((Node *) a->elements, glob_cxt, &inner_cxt)) return false; /* * ArrayExpr must not introduce a collation not derived from * an input foreign Var. */ collation = a->array_collid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else state = FDW_COLLATE_UNSAFE; } break; case T_List: { List *l = (List *) node; ListCell *lc; /* * Recurse to component subexpressions. */ foreach(lc, l) { if (!foreign_expr_walker((Node *) lfirst(lc), glob_cxt, &inner_cxt)) return false; } /* * When processing a list, collation state just bubbles up * from the list elements. */ collation = inner_cxt.collation; state = inner_cxt.state; /* Don't apply exprType() to the list. */ check_type = false; } break; case T_Aggref: { Aggref *agg = (Aggref *) node; ListCell *lc; const char *func_name = get_func_name(agg->aggfnoid); #if PG_VERSION_NUM >= 100000 /* Not safe to pushdown when not in grouping context */ if (!IS_UPPER_REL(glob_cxt->foreignrel)) return false; /* Only non-split aggregates are pushable. */ if (agg->aggsplit != AGGSPLIT_SIMPLE) return false; #endif /* As usual, it must be shippable. */ if (!is_builtin(agg->aggfnoid)) return false; if (!(strcmp(func_name, "min") == 0 || strcmp(func_name, "max") == 0 || strcmp(func_name, "sum") == 0 || strcmp(func_name, "avg") == 0 || strcmp(func_name, "count") == 0)) return false; /* * Recurse to input args. aggdirectargs, aggorder and * aggdistinct are all present in args, so no need to check * their shippability explicitly. */ foreach(lc, agg->args) { Node *n = (Node *) lfirst(lc); /* If TargetEntry, extract the expression from it */ if (IsA(n, TargetEntry)) { TargetEntry *tle = (TargetEntry *) n; n = (Node *) tle->expr; } if (!foreign_expr_walker(n, glob_cxt, &inner_cxt)) return false; } /* Aggregates with order are not supported on MySQL. */ if (agg->aggorder) return false; /* FILTER clause is not supported on MySQL. */ if (agg->aggfilter) return false; /* * If aggregate's input collation is not derived from a * foreign Var, it can't be sent to remote. */ if (agg->inputcollid == InvalidOid) /* OK, inputs are all noncollatable */ ; else if (inner_cxt.state != FDW_COLLATE_SAFE || agg->inputcollid != inner_cxt.collation) return false; /* * Detect whether node is introducing a collation not derived * from a foreign Var. (If so, we just mark it unsafe for now * rather than immediately returning false, since the parent * node might not care.) */ collation = agg->aggcollid; if (collation == InvalidOid) state = FDW_COLLATE_NONE; else if (inner_cxt.state == FDW_COLLATE_SAFE && collation == inner_cxt.collation) state = FDW_COLLATE_SAFE; else if (collation == DEFAULT_COLLATION_OID) state = FDW_COLLATE_NONE; else state = FDW_COLLATE_UNSAFE; } break; default: /* * If it's anything else, assume it's unsafe. This list can be * expanded later, but don't forget to add deparse support below. */ return false; } /* * If result type of given expression is not built-in, it can't be sent to * remote because it might have incompatible semantics on remote side. */ if (check_type && !is_builtin(exprType(node))) return false; /* * Now, merge my collation information into my parent's state. */ if (state > outer_cxt->state) { /* Override previous parent state */ outer_cxt->collation = collation; outer_cxt->state = state; } else if (state == outer_cxt->state) { /* Merge, or detect error if there's a collation conflict */ switch (state) { case FDW_COLLATE_NONE: /* Nothing + nothing is still nothing */ break; case FDW_COLLATE_SAFE: if (collation != outer_cxt->collation) { /* * Non-default collation always beats default. */ if (outer_cxt->collation == DEFAULT_COLLATION_OID) { /* Override previous parent state */ outer_cxt->collation = collation; } else if (collation != DEFAULT_COLLATION_OID) { /* * Conflict; show state as indeterminate. We don't * want to "return false" right away, since parent * node might not care about collation. */ outer_cxt->state = FDW_COLLATE_UNSAFE; } } break; case FDW_COLLATE_UNSAFE: /* We're still conflicted ... */ break; } } /* It looks OK */ return true; } /* * Returns true if given expr is safe to evaluate on the foreign server. */ bool mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr, bool is_remote_cond) { foreign_glob_cxt glob_cxt; foreign_loc_cxt loc_cxt; #if PG_VERSION_NUM >= 100000 MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) (baserel->fdw_private); #endif /* * Check that the expression consists of nodes that are safe to execute * remotely. */ glob_cxt.root = root; glob_cxt.foreignrel = baserel; glob_cxt.is_remote_cond = is_remote_cond; /* * For an upper relation, use relids from its underneath scan relation, * because the upperrel's own relids currently aren't set to anything * meaningful by the core code. For other relation, use their own relids. */ #if PG_VERSION_NUM >= 100000 if (IS_UPPER_REL(baserel)) glob_cxt.relids = fpinfo->outerrel->relids; else #endif glob_cxt.relids = baserel->relids; loc_cxt.collation = InvalidOid; loc_cxt.state = FDW_COLLATE_NONE; if (!foreign_expr_walker((Node *) expr, &glob_cxt, &loc_cxt)) return false; /* * If the expression has a valid collation that does not arise from a * foreign var, the expression can not be sent over. */ if (loc_cxt.state == FDW_COLLATE_UNSAFE) return false; /* * An expression which includes any mutable functions can't be sent over * because its result is not stable. For example, sending now() remote * side could cause confusion from clock offsets. Future versions might * be able to make this choice with more granularity. (We check this last * because it requires a lot of expensive catalog lookups.) */ if (contain_mutable_functions((Node *) expr)) return false; /* OK to evaluate on the remote server */ return true; } /* * mysql_append_conditions * Deparse conditions from the provided list and append them to buf. * * The conditions in the list are assumed to be ANDed. This function is used * to deparse WHERE clauses, JOIN .. ON clauses and HAVING clauses. * * Depending on the caller, the list elements might be either RestrictInfos * or bare clauses. */ static void mysql_append_conditions(List *exprs, deparse_expr_cxt *context) { ListCell *lc; bool is_first = true; StringInfo buf = context->buf; foreach(lc, exprs) { Expr *expr = (Expr *) lfirst(lc); /* * Extract clause from RestrictInfo, if required. See comments in * declaration of MySQLFdwRelationInfo for details. */ if (IsA(expr, RestrictInfo)) { RestrictInfo *ri = (RestrictInfo *) expr; expr = ri->clause; } /* Connect expressions with "AND" and parenthesize each condition. */ if (!is_first) appendStringInfoString(buf, " AND "); appendStringInfoChar(buf, '('); deparseExpr(expr, context); appendStringInfoChar(buf, ')'); is_first = false; } } /* * mysql_deparse_from_expr_for_rel * Construct a FROM clause */ void mysql_deparse_from_expr_for_rel(StringInfo buf, PlannerInfo *root, RelOptInfo *foreignrel, bool use_alias, List **params_list) { MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private; #if PG_VERSION_NUM >= 100000 if (IS_JOIN_REL(foreignrel)) #else if (foreignrel->reloptkind == RELOPT_JOINREL) #endif { RelOptInfo *rel_o = fpinfo->outerrel; RelOptInfo *rel_i = fpinfo->innerrel; StringInfoData join_sql_o; StringInfoData join_sql_i; /* Deparse outer relation */ initStringInfo(&join_sql_o); mysql_deparse_from_expr_for_rel(&join_sql_o, root, rel_o, true, params_list); /* Deparse inner relation */ initStringInfo(&join_sql_i); mysql_deparse_from_expr_for_rel(&join_sql_i, root, rel_i, true, params_list); /* * For a join relation FROM clause entry is deparsed as * * ((outer relation) (inner relation) ON (joinclauses) */ appendStringInfo(buf, "(%s %s JOIN %s ON ", join_sql_o.data, mysql_get_jointype_name(fpinfo->jointype), join_sql_i.data); /* Append join clause; (TRUE) if no join clause */ if (fpinfo->joinclauses) { deparse_expr_cxt context; context.buf = buf; context.foreignrel = foreignrel; context.scanrel = foreignrel; context.root = root; context.params_list = params_list; appendStringInfo(buf, "("); mysql_append_conditions(fpinfo->joinclauses, &context); appendStringInfo(buf, ")"); } else appendStringInfoString(buf, "(TRUE)"); /* End the FROM clause entry. */ appendStringInfo(buf, ")"); } else { RangeTblEntry *rte = planner_rt_fetch(foreignrel->relid, root); Relation rel; /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ #if PG_VERSION_NUM < 130000 rel = heap_open(rte->relid, NoLock); #else rel = table_open(rte->relid, NoLock); #endif mysql_deparse_relation(buf, rel); /* * Add a unique alias to avoid any conflict in relation names due to * pulled up subqueries in the query being built for a pushed down * join. */ if (use_alias) appendStringInfo(buf, " %s%d", REL_ALIAS_PREFIX, foreignrel->relid); #if PG_VERSION_NUM < 130000 heap_close(rel, NoLock); #else table_close(rel, NoLock); #endif } return; } /* * mysql_get_jointype_name * Output join name for given join type */ extern const char * mysql_get_jointype_name(JoinType jointype) { switch (jointype) { case JOIN_INNER: return "INNER"; case JOIN_LEFT: return "LEFT"; case JOIN_RIGHT: return "RIGHT"; default: /* Shouldn't come here, but protect from buggy code. */ elog(ERROR, "unsupported join type %d", jointype); } /* Keep compiler happy */ return NULL; } /* * mysql_append_function_name * Deparses function name from given function oid. */ static void mysql_append_function_name(Oid funcid, deparse_expr_cxt *context) { StringInfo buf = context->buf; HeapTuple proctup; Form_pg_proc procform; const char *proname; proctup = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); if (!HeapTupleIsValid(proctup)) elog(ERROR, "cache lookup failed for function %u", funcid); procform = (Form_pg_proc) GETSTRUCT(proctup); /* Always print the function name */ proname = NameStr(procform->proname); /* Translate PostgreSQL function into MySQL function */ proname = mysql_replace_function(NameStr(procform->proname)); appendStringInfoString(buf, proname); ReleaseSysCache(proctup); } /* * mysql_deparse_aggref * Deparse an Aggref node. */ static void mysql_deparse_aggref(Aggref *node, deparse_expr_cxt *context) { StringInfo buf = context->buf; bool use_variadic; /* Only basic, non-split aggregation accepted. */ Assert(node->aggsplit == AGGSPLIT_SIMPLE); /* Check if need to print VARIADIC (cf. ruleutils.c) */ use_variadic = node->aggvariadic; /* Find aggregate name from aggfnoid which is a pg_proc entry. */ mysql_append_function_name(node->aggfnoid, context); appendStringInfoChar(buf, '('); /* Add DISTINCT */ appendStringInfoString(buf, (node->aggdistinct != NIL) ? "DISTINCT " : ""); /* aggstar can be set only in zero-argument aggregates. */ if (node->aggstar) appendStringInfoChar(buf, '*'); else { ListCell *arg; bool first = true; /* Add all the arguments. */ foreach(arg, node->args) { TargetEntry *tle = (TargetEntry *) lfirst(arg); Node *n = (Node *) tle->expr; if (tle->resjunk) continue; if (!first) appendStringInfoString(buf, ", "); first = false; /* Add VARIADIC */ #if PG_VERSION_NUM >= 130000 if (use_variadic && lnext(node->args, arg) == NULL) #else if (use_variadic && lnext(arg) == NULL) #endif appendStringInfoString(buf, "VARIADIC "); deparseExpr((Expr *) n, context); } } appendStringInfoChar(buf, ')'); } /* * mysql_append_groupby_clause * Deparse GROUP BY clause. */ #if PG_VERSION_NUM >= 100000 static void mysql_append_groupby_clause(List *tlist, deparse_expr_cxt *context) { StringInfo buf = context->buf; Query *query = context->root->parse; ListCell *lc; bool first = true; /* Nothing to be done, if there's no GROUP BY clause in the query. */ if (!query->groupClause) return; appendStringInfoString(buf, " GROUP BY "); /* * Queries with grouping sets are not pushed down, so we don't expect * grouping sets here. */ Assert(!query->groupingSets); foreach(lc, query->groupClause) { SortGroupClause *grp = (SortGroupClause *) lfirst(lc); if (!first) appendStringInfoString(buf, ", "); first = false; mysql_deparse_sort_group_clause(grp->tleSortGroupRef, tlist, true, context); } } /* * mysql_deparse_sort_group_clause * Appends a sort or group clause. */ static Node * mysql_deparse_sort_group_clause(Index ref, List *tlist, bool force_colno, deparse_expr_cxt *context) { StringInfo buf = context->buf; TargetEntry *tle; Expr *expr; tle = get_sortgroupref_tle(ref, tlist); expr = tle->expr; if (force_colno) { /* Use column-number form when requested by caller. */ Assert(!tle->resjunk); appendStringInfo(buf, "%d", tle->resno); } else if (expr && IsA(expr, Const)) { /* * Force a typecast here so that we don't emit something like "GROUP * BY 2", which will be misconstrued as a column position rather than * a constant. */ mysql_deparse_const((Const *) expr, context); } else if (!expr || IsA(expr, Var)) deparseExpr(expr, context); else { /* Always parenthesize the expression. */ appendStringInfoChar(buf, '('); deparseExpr(expr, context); appendStringInfoChar(buf, ')'); } return (Node *) expr; } #endif /* * mysql_is_foreign_param * Returns true if given expr is something we'd have to send the * value of to the foreign server. * * This should return true when the expression is a shippable node that * deparseExpr would add to context->params_list. Note that we don't care * if the expression *contains* such a node, only whether one appears at top * level. We need this to detect cases where setrefs.c would recognize a * false match between an fdw_exprs item (which came from the params_list) * and an entry in fdw_scan_tlist (which we're considering putting the given * expression into). */ bool mysql_is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr) { if (expr == NULL) return false; switch (nodeTag(expr)) { case T_Var: { /* It would have to be sent unless it's a foreign Var. */ Var *var = (Var *) expr; Relids relids; #if PG_VERSION_NUM >= 100000 MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) (baserel->fdw_private); if (IS_UPPER_REL(baserel)) relids = fpinfo->outerrel->relids; else #endif relids = baserel->relids; if (bms_is_member(var->varno, relids) && var->varlevelsup == 0) return false; /* foreign Var, so not a param. */ else return true; /* it'd have to be a param. */ break; } case T_Param: /* Params always have to be sent to the foreign server. */ return true; default: break; } return false; } mysql_fdw-REL-2_7_0/expected/000077500000000000000000000000001414541277500161225ustar00rootroot00000000000000mysql_fdw-REL-2_7_0/expected/aggregate_pushdown.out000066400000000000000000001454401414541277500225400ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------- Result Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 -> Sort Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Sort Key: (sum(fdw132_t1.c1)), (avg(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), avg(`c1`), min(`c2`), max(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 5)) GROUP BY 5 (9 rows) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; sum | avg | min | max | sum2 -----+--------+-----+-----+------ 14 | 4.6667 | 100 | 11 | 14 (1 row) -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; QUERY PLAN ------------------------------------------------------------------------------- Aggregate Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (5 rows) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; sum | avg -----+-------------------- 14 | 4.6666666666666667 (1 row) -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT count(*), sum(r1.`c1`), avg(r2.`c1`) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 2)) AND ((r1.`c1` = 2)) (4 rows) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; count | sum | avg -------+-----+-------- 1 | 2 | 2.0000 (1 row) -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(t1.c1), count(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; sum | count -----+------- 3 | 2 (1 row) -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Sort Key: ((fdw132_t1.c2 + 2)) -> Foreign Scan Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (`c2` + 2), (sum(`c2`) * (`c2` + 2)) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (7 rows) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; ?column? | ?column? ----------+---------- 102 | 30600 (1 row) -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; QUERY PLAN ------------------------------------------------------------------------------------------------ Aggregate Output: count(fdw132_t1.c2), sum(fdw132_t1.c2) -> Sort Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Sort Key: fdw132_t1.c2, (sum(fdw132_t1.c1)) -> Foreign Scan Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (9 rows) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; count | sum -------+----- 1 | 100 (1 row) -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 Sort Key: ((fdw132_t1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(fdw132_t1.c1) * fdw132_t1.c2)) -> Foreign Scan Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (sum(`c1`) * `c2`), `c2` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 (7 rows) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; sum1 | sum2 ------+------ 100 | 1400 (1 row) -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)) Sort Key: ((fdw132_t2.c2 * ((random() <= '1'::double precision))::integer)) -> HashAggregate Output: ((c2 * ((random() <= '1'::double precision))::integer)) Group Key: (fdw132_t2.c2 * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t2 Output: (c2 * ((random() <= '1'::double precision))::integer) Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (9 rows) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; c2 ----- 200 (1 row) -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Sort Output: (count(c2)), c2, 5, 7.0, 9 Sort Key: fdw132_t1.c2 -> Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT count(`c2`), `c2`, 5, 7.0, 9 FROM `mysql_fdw_regress`.`test1` GROUP BY 2, 3, 5 (7 rows) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; w | x | y | z ---+-----+---+----- 3 | 100 | 5 | 7.0 (1 row) -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t2.c2 -> Foreign Scan Output: c2, (sum(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c1`) < 500)) AND ((sum(`c1`) < 49800)) (7 rows) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; c2 | sum -----+----- 200 | 15 (1 row) -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Sort Output: c3, (count(c1)) Sort Key: fdw132_t1.c3 COLLATE "C", (count(fdw132_t1.c1)) -> Foreign Scan Output: c3, (count(c1)) Filter: (sqrt(((max(fdw132_t1.c1)))::double precision) = '1.4142135623730951'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`), max(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (8 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; c3 | count ------+------- AAA2 | 1 (1 row) SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*) -> Foreign Scan Output: fdw132_t1.c3, NULL::bigint Filter: (((((avg(fdw132_t1.c1)) / (avg(fdw132_t1.c1))))::double precision * random()) <= '1'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, NULL, avg(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c1`) < 500)) (7 rows) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; count ------- 3 (1 row) -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 Filter: (avg((fdw132_t1.c1 * ((random() <= '1'::double precision))::integer)) > '1'::numeric) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; sum ----- 14 (1 row) -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1 ORDER BY c1)), c2 Sort Key: (sum(fdw132_t1.c1 ORDER BY fdw132_t1.c1)) -> GroupAggregate Output: sum(c1 ORDER BY c1), c2 Group Key: fdw132_t1.c2 -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) (12 rows) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; sum ----- 14 (1 row) -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(c2 ORDER BY c1 DESC) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) AND ((`c2` > 50)) (5 rows) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; sum ----- 400 (1 row) -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT (c1 % 5))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT (`c1` % 5)) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 3 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (sum(DISTINCT (t1.c1 % 5))) -> Foreign Scan Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT sum(DISTINCT (r1.`c1` % 5)), (r2.`c1` % 3) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) GROUP BY 2 (7 rows) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; sum ----- 1 2 (2 rows) -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT `c1`) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 15 (1 row) -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) -> GroupAggregate Output: array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r2.`c1`, r1.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; array_agg ----------- {1} {2} (2 rows) -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------- GroupAggregate Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) (6 rows) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; sum | sum | c2 -----+-----+----- 5 | 1 | 100 (1 row) -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN ---------------------------------------------------------------------------------------------- Sort Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 Sort Key: (sum(fdw132_t1.c1) FILTER (WHERE ((fdw132_t1.c1 < 100) AND (fdw132_t1.c2 > 5)))) -> HashAggregate Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; sum ----- 14 (1 row) -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Aggregate Output: (SubPlan 1) -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Foreign Scan on public.fdw132_t1 t1 Output: count(*) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 2 (1 row) -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Foreign Scan on public.fdw132_t2 t2 Output: (SubPlan 1) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Aggregate Output: count(t1.c1) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 0 1 (2 rows) -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, rank('10'::text) WITHIN GROUP (ORDER BY c3), percentile_cont((((c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)) Group Key: fdw132_t2.c2 Filter: (percentile_cont((((fdw132_t2.c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((fdw132_t2.c1)::double precision)) < '500'::double precision) -> Sort Output: c2, c3, c1 Sort Key: fdw132_t2.c2 -> Foreign Scan on public.fdw132_t2 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; c2 | rank | percentile_cont -----+------+----------------- 200 | 1 | 12 (1 row) -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c1, rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2), c2 Group Key: fdw132_t1.c1, fdw132_t1.c2 -> Sort Output: c1, c2 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (9 rows) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; c1 | rank ----+------ 2 | 1 (1 row) -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b -> HashAggregate Output: count(*), x.b Group Key: x.b -> Hash Join Output: x.b Inner Unique: true Hash Cond: (fdw132_t1.c1 = x.a) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: x.b, x.a -> Subquery Scan on x Output: x.b, x.a -> Foreign Scan Output: fdw132_t2.c1, (sum(fdw132_t2.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (21 rows) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; count | b -------+--- 1 | 1 1 | 2 (2 rows) -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Sort Key: (avg(t1.c1)), (sum(t2.c1)) -> Foreign Scan Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT avg(r1.`c1`), sum(r2.`c1`), r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) GROUP BY 3 HAVING ((((avg(r1.`c1`) IS NULL) AND (sum(r2.`c1`) > 10)) OR (sum(r2.`c1`) IS NULL))) (7 rows) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; avg | sum -----+----- (0 rows) -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) Sort Key: (((sum(fdw132_t1.c2)) * ((random() <= '1'::double precision))::integer)) -> Foreign Scan Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c2`) FROM `mysql_fdw_regress`.`test1` (7 rows) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; sum ----- 300 (1 row) -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Sort Output: t1.c2, qry.sum Sort Key: t1.c2 -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) -> Subquery Scan on qry Output: qry.sum, t2.c1 Filter: ((t1.c2 * 2) = qry.sum) -> Foreign Scan Output: (sum((t2.c1 + t1.c1))), t2.c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT sum((`c1` + ?)), `c1` FROM `mysql_fdw_regress`.`test2` GROUP BY 2 (15 rows) -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: q.b, (count(fdw132_t1.c1)), (sum(q.a)) Sort Key: q.b, (count(fdw132_t1.c1)) -> HashAggregate Output: q.b, count(fdw132_t1.c1), sum(q.a) Group Key: q.b -> Hash Left Join Output: q.b, fdw132_t1.c1, q.a Inner Unique: true Hash Cond: ((fdw132_t1.c1)::numeric = q.b) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Hash Output: q.b, q.a -> Subquery Scan on q Output: q.b, q.a -> Foreign Scan Output: (min(13)), (avg(fdw132_t1_1.c1)), NULL::bigint Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1) INNER JOIN (mysql_fdw_regress.fdw132_t2)) Remote query: SELECT min(13), avg(r1.`c1`), NULL FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 12)) AND ((r1.`c1` = 12)) (21 rows) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; b | count | sum ---+-------+----- | 1 | (1 row) -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------------- Sort Output: c2, c3, (sum(c1)) Sort Key: fdw132_t1.c2, fdw132_t1.c3 COLLATE "C" -> HashAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c2 Hash Key: fdw132_t1.c3 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; c2 | c3 | sum -----+-------+----- 100 | | 14 | AAA1 | 1 | AAA11 | 11 | AAA2 | 2 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)), (GROUPING(c2)) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, sum(c1), GROUPING(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (9 rows) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; c2 | sum | grouping -----+-----+---------- 100 | 14 | 0 (1 row) -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 6)) GROUP BY 2 (9 rows) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; s ---- 14 (1 row) -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)), (sum(c2)) Sort Key: ((fdw132_t1.c2 % 2)) -> Foreign Scan Output: c2, ((c2 % 2)), (sum(c2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2), sum(`c2`) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | sum | count -----+-----+------- 100 | 300 | 1 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 DESC -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: c2, (least_agg(VARIADIC ARRAY[c1])) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, least_agg(VARIADIC ARRAY[c1]) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------ Sort Output: fprt1.c1, (sum(fprt1.c1)) Sort Key: (sum(fprt1.c1)) -> Append -> Foreign Scan Output: fprt1.c1, (sum(fprt1.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 -> Foreign Scan Output: fprt1_1.c1, (sum(fprt1_1.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (12 rows) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; c1 | sum ----+----- 1 | 1 2 | 2 3 | 3 4 | 4 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: fprt1.c1, (sum(fprt1.c2)), (min(fprt1.c2)), (count(*)) Sort Key: (sum(fprt1.c2)) -> Append -> Foreign Scan Output: fprt1.c1, (sum(fprt1.c2)), (min(fprt1.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c2`) < 22)) -> Foreign Scan Output: fprt1_1.c1, (sum(fprt1_1.c2)), (min(fprt1_1.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c2`) < 22)) (12 rows) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | sum | min | count ----+-----+-----+------- 1 | 1 | 1 | 1 2 | 2 | 2 | 1 3 | 3 | 3 | 1 4 | 4 | 4 | 1 (4 rows) -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------- GroupAggregate Output: t1.c1, count(((t1.*)::fprt1)) Group Key: t1.c1 Filter: (avg(t1.c2) < '22'::numeric) -> Sort Output: t1.c1, ((t1.*)::fprt1), t1.c2 Sort Key: t1.c1 -> Append -> Foreign Scan on public.ftprt1_p1 t1_1 Output: t1_1.c1, t1_1.*, t1_1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_2 Output: t1_2.c1, t1_2.*, t1_2.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | count ----+------- 1 | 1 2 | 1 3 | 1 4 | 1 (4 rows) -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: fprt1.c2, (avg(fprt1.c1)), (max(fprt1.c1)), (count(*)) Sort Key: fprt1.c2 -> HashAggregate Output: fprt1.c2, avg(fprt1.c1), max(fprt1.c1), count(*) Group Key: fprt1.c2 Filter: (sum(fprt1.c1) < 700) -> Append -> Foreign Scan on public.ftprt1_p1 fprt1_1 Output: fprt1_1.c2, fprt1_1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 fprt1_2 Output: fprt1_2.c2, fprt1_2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; c2 | avg | max | count ----+------------------------+-----+------- 1 | 1.00000000000000000000 | 1 | 1 2 | 2.0000000000000000 | 2 | 1 3 | 3.0000000000000000 | 3 | 1 4 | 4.0000000000000000 | 4 | 1 (4 rows) SET enable_partitionwise_aggregate TO off; -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; DROP FOREIGN TABLE ftprt1_p2; DROP TABLE IF EXISTS fprt1; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/aggregate_pushdown_1.out000066400000000000000000001455451414541277500227660ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------- Result Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 -> Sort Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Sort Key: (sum(fdw132_t1.c1)), (avg(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), avg(`c1`), min(`c2`), max(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 5)) GROUP BY 5 (9 rows) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; sum | avg | min | max | sum2 -----+--------+-----+-----+------ 14 | 4.6667 | 100 | 11 | 14 (1 row) -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; QUERY PLAN ------------------------------------------------------------------------------- Aggregate Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (5 rows) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; sum | avg -----+-------------------- 14 | 4.6666666666666667 (1 row) -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT count(*), sum(r1.`c1`), avg(r2.`c1`) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 2)) AND ((r1.`c1` = 2)) (4 rows) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; count | sum | avg -------+-----+-------- 1 | 2 | 2.0000 (1 row) -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(t1.c1), count(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; sum | count -----+------- 3 | 2 (1 row) -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Sort Key: ((fdw132_t1.c2 + 2)) -> Foreign Scan Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (`c2` + 2), (sum(`c2`) * (`c2` + 2)) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (7 rows) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; ?column? | ?column? ----------+---------- 102 | 30600 (1 row) -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; QUERY PLAN ------------------------------------------------------------------------------------------------ Aggregate Output: count(fdw132_t1.c2), sum(fdw132_t1.c2) -> Sort Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Sort Key: fdw132_t1.c2, (sum(fdw132_t1.c1)) -> Foreign Scan Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (9 rows) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; count | sum -------+----- 1 | 100 (1 row) -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 Sort Key: ((fdw132_t1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(fdw132_t1.c1) * fdw132_t1.c2)) -> Foreign Scan Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (sum(`c1`) * `c2`), `c2` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 (7 rows) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; sum1 | sum2 ------+------ 100 | 1400 (1 row) -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)) Sort Key: ((fdw132_t2.c2 * ((random() <= '1'::double precision))::integer)) -> HashAggregate Output: ((c2 * ((random() <= '1'::double precision))::integer)) Group Key: (fdw132_t2.c2 * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t2 Output: (c2 * ((random() <= '1'::double precision))::integer) Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (9 rows) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; c2 ----- 200 (1 row) -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Sort Output: (count(c2)), c2, 5, 7.0, 9 Sort Key: fdw132_t1.c2 -> Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT count(`c2`), `c2`, 5, 7.0, 9 FROM `mysql_fdw_regress`.`test1` GROUP BY 2, 3, 5 (7 rows) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; w | x | y | z ---+-----+---+----- 3 | 100 | 5 | 7.0 (1 row) -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t2.c2 -> Foreign Scan Output: c2, (sum(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c1`) < 500)) AND ((sum(`c1`) < 49800)) (7 rows) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; c2 | sum -----+----- 200 | 15 (1 row) -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: c3, (count(c1)) Sort Key: fdw132_t1.c3 COLLATE "C", (count(fdw132_t1.c1)) -> Foreign Scan Output: c3, (count(c1)) Filter: (sqrt(((max(fdw132_t1.c1)))::double precision) = '1.4142135623731'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`), max(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (8 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; c3 | count ------+------- AAA2 | 1 (1 row) SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*) -> Foreign Scan Output: fdw132_t1.c3, NULL::bigint Filter: (((((avg(fdw132_t1.c1)) / (avg(fdw132_t1.c1))))::double precision * random()) <= '1'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, NULL, avg(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c1`) < 500)) (7 rows) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; count ------- 3 (1 row) -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 Filter: (avg((fdw132_t1.c1 * ((random() <= '1'::double precision))::integer)) > '1'::numeric) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; sum ----- 14 (1 row) -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1 ORDER BY c1)), c2 Sort Key: (sum(fdw132_t1.c1 ORDER BY fdw132_t1.c1)) -> GroupAggregate Output: sum(c1 ORDER BY c1), c2 Group Key: fdw132_t1.c2 -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) (12 rows) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; sum ----- 14 (1 row) -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(c2 ORDER BY c1 DESC) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) AND ((`c2` > 50)) (5 rows) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; sum ----- 400 (1 row) -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT (c1 % 5))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT (`c1` % 5)) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 3 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (sum(DISTINCT (t1.c1 % 5))) -> Foreign Scan Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT sum(DISTINCT (r1.`c1` % 5)), (r2.`c1` % 3) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) GROUP BY 2 (7 rows) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; sum ----- 1 2 (2 rows) -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT `c1`) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 15 (1 row) -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) -> GroupAggregate Output: array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r2.`c1`, r1.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; array_agg ----------- {1} {2} (2 rows) -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------- GroupAggregate Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) (6 rows) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; sum | sum | c2 -----+-----+----- 5 | 1 | 100 (1 row) -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN ---------------------------------------------------------------------------------------------- Sort Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 Sort Key: (sum(fdw132_t1.c1) FILTER (WHERE ((fdw132_t1.c1 < 100) AND (fdw132_t1.c2 > 5)))) -> HashAggregate Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; sum ----- 14 (1 row) -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Aggregate Output: (SubPlan 1) -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Foreign Scan on public.fdw132_t1 t1 Output: count(*) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 2 (1 row) -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Foreign Scan on public.fdw132_t2 t2 Output: (SubPlan 1) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Aggregate Output: count(t1.c1) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 0 1 (2 rows) -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, rank('10'::text) WITHIN GROUP (ORDER BY c3), percentile_cont((((c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)) Group Key: fdw132_t2.c2 Filter: (percentile_cont((((fdw132_t2.c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((fdw132_t2.c1)::double precision)) < '500'::double precision) -> Sort Output: c2, c3, c1 Sort Key: fdw132_t2.c2 -> Foreign Scan on public.fdw132_t2 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; c2 | rank | percentile_cont -----+------+----------------- 200 | 1 | 12 (1 row) -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c1, rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2), c2 Group Key: fdw132_t1.c1, fdw132_t1.c2 -> Sort Output: c1, c2 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (9 rows) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; c1 | rank ----+------ 2 | 1 (1 row) -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b -> HashAggregate Output: count(*), x.b Group Key: x.b -> Hash Join Output: x.b Inner Unique: true Hash Cond: (fdw132_t1.c1 = x.a) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: x.b, x.a -> Subquery Scan on x Output: x.b, x.a -> Foreign Scan Output: fdw132_t2.c1, (sum(fdw132_t2.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (21 rows) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; count | b -------+--- 1 | 1 1 | 2 (2 rows) -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Sort Key: (avg(t1.c1)), (sum(t2.c1)) -> Foreign Scan Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT avg(r1.`c1`), sum(r2.`c1`), r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) GROUP BY 3 HAVING ((((avg(r1.`c1`) IS NULL) AND (sum(r2.`c1`) > 10)) OR (sum(r2.`c1`) IS NULL))) (7 rows) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; avg | sum -----+----- (0 rows) -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) Sort Key: (((sum(fdw132_t1.c2)) * ((random() <= '1'::double precision))::integer)) -> Foreign Scan Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c2`) FROM `mysql_fdw_regress`.`test1` (7 rows) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; sum ----- 300 (1 row) -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Sort Output: t1.c2, qry.sum Sort Key: t1.c2 -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) -> Subquery Scan on qry Output: qry.sum, t2.c1 Filter: ((t1.c2 * 2) = qry.sum) -> Foreign Scan Output: (sum((t2.c1 + t1.c1))), t2.c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT sum((`c1` + ?)), `c1` FROM `mysql_fdw_regress`.`test2` GROUP BY 2 (15 rows) -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: q.b, (count(fdw132_t1.c1)), (sum(q.a)) Sort Key: q.b, (count(fdw132_t1.c1)) -> HashAggregate Output: q.b, count(fdw132_t1.c1), sum(q.a) Group Key: q.b -> Hash Left Join Output: q.b, fdw132_t1.c1, q.a Inner Unique: true Hash Cond: ((fdw132_t1.c1)::numeric = q.b) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Hash Output: q.b, q.a -> Subquery Scan on q Output: q.b, q.a -> Foreign Scan Output: (min(13)), (avg(fdw132_t1_1.c1)), NULL::bigint Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1) INNER JOIN (mysql_fdw_regress.fdw132_t2)) Remote query: SELECT min(13), avg(r1.`c1`), NULL FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 12)) AND ((r1.`c1` = 12)) (21 rows) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; b | count | sum ---+-------+----- | 1 | (1 row) -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------------- Sort Output: c2, c3, (sum(c1)) Sort Key: fdw132_t1.c2, fdw132_t1.c3 COLLATE "C" -> HashAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c2 Hash Key: fdw132_t1.c3 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; c2 | c3 | sum -----+-------+----- 100 | | 14 | AAA1 | 1 | AAA11 | 11 | AAA2 | 2 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)), (GROUPING(c2)) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, sum(c1), GROUPING(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (9 rows) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; c2 | sum | grouping -----+-----+---------- 100 | 14 | 0 (1 row) -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 6)) GROUP BY 2 (9 rows) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; s ---- 14 (1 row) -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)), (sum(c2)) Sort Key: ((fdw132_t1.c2 % 2)) -> Foreign Scan Output: c2, ((c2 % 2)), (sum(c2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2), sum(`c2`) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | sum | count -----+-----+------- 100 | 300 | 1 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 DESC -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: c2, (least_agg(VARIADIC ARRAY[c1])) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, least_agg(VARIADIC ARRAY[c1]) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------ Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c1)) Sort Key: (sum(ftprt1_p1.c1)) -> Append -> Foreign Scan Output: ftprt1_p1.c1, (sum(ftprt1_p1.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 -> Foreign Scan Output: ftprt1_p2.c1, (sum(ftprt1_p2.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (12 rows) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; c1 | sum ----+----- 1 | 1 2 | 2 3 | 3 4 | 4 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c2)), (min(ftprt1_p1.c2)), (count(*)) Sort Key: (sum(ftprt1_p1.c2)) -> Append -> Foreign Scan Output: ftprt1_p1.c1, (sum(ftprt1_p1.c2)), (min(ftprt1_p1.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c2`) < 22)) -> Foreign Scan Output: ftprt1_p2.c1, (sum(ftprt1_p2.c2)), (min(ftprt1_p2.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c2`) < 22)) (12 rows) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | sum | min | count ----+-----+-----+------- 1 | 1 | 1 | 1 2 | 2 | 2 | 1 3 | 3 | 3 | 1 4 | 4 | 4 | 1 (4 rows) -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------- GroupAggregate Output: t1.c1, count(((t1.*)::fprt1)) Group Key: t1.c1 Filter: (avg(t1.c2) < '22'::numeric) -> Sort Output: t1.c1, ((t1.*)::fprt1), t1.c2 Sort Key: t1.c1 -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.c1, t1.*, t1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.c1, t1_1.*, t1_1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | count ----+------- 1 | 1 2 | 1 3 | 1 4 | 1 (4 rows) -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c2, (avg(ftprt1_p1.c1)), (max(ftprt1_p1.c1)), (count(*)) Sort Key: ftprt1_p1.c2 -> HashAggregate Output: ftprt1_p1.c2, avg(ftprt1_p1.c1), max(ftprt1_p1.c1), count(*) Group Key: ftprt1_p1.c2 Filter: (sum(ftprt1_p1.c1) < 700) -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c2, ftprt1_p1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c2, ftprt1_p2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; c2 | avg | max | count ----+------------------------+-----+------- 1 | 1.00000000000000000000 | 1 | 1 2 | 2.0000000000000000 | 2 | 1 3 | 3.0000000000000000 | 3 | 1 4 | 4.0000000000000000 | 4 | 1 (4 rows) SET enable_partitionwise_aggregate TO off; -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; DROP FOREIGN TABLE ftprt1_p2; DROP TABLE IF EXISTS fprt1; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/aggregate_pushdown_2.out000066400000000000000000001453521414541277500227630ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------- Result Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 -> Sort Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Sort Key: (sum(fdw132_t1.c1)), (avg(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), avg(`c1`), min(`c2`), max(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 5)) GROUP BY 5 (9 rows) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; sum | avg | min | max | sum2 -----+--------+-----+-----+------ 14 | 4.6667 | 100 | 11 | 14 (1 row) -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; QUERY PLAN ------------------------------------------------------------------------------- Aggregate Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (5 rows) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; sum | avg -----+-------------------- 14 | 4.6666666666666667 (1 row) -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT count(*), sum(r1.`c1`), avg(r2.`c1`) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 2)) AND ((r1.`c1` = 2)) (4 rows) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; count | sum | avg -------+-----+-------- 1 | 2 | 2.0000 (1 row) -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(t1.c1), count(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; sum | count -----+------- 3 | 2 (1 row) -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Sort Key: ((fdw132_t1.c2 + 2)) -> Foreign Scan Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (`c2` + 2), (sum(`c2`) * (`c2` + 2)) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (7 rows) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; ?column? | ?column? ----------+---------- 102 | 30600 (1 row) -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; QUERY PLAN ------------------------------------------------------------------------------------------------ Aggregate Output: count(fdw132_t1.c2), sum(fdw132_t1.c2) -> Sort Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Sort Key: fdw132_t1.c2, (sum(fdw132_t1.c1)) -> Foreign Scan Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (9 rows) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; count | sum -------+----- 1 | 100 (1 row) -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 Sort Key: ((fdw132_t1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(fdw132_t1.c1) * fdw132_t1.c2)) -> Foreign Scan Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (sum(`c1`) * `c2`), `c2` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 (7 rows) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; sum1 | sum2 ------+------ 100 | 1400 (1 row) -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)) Sort Key: ((fdw132_t2.c2 * ((random() <= '1'::double precision))::integer)) -> HashAggregate Output: ((c2 * ((random() <= '1'::double precision))::integer)) Group Key: (fdw132_t2.c2 * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t2 Output: (c2 * ((random() <= '1'::double precision))::integer) Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (9 rows) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; c2 ----- 200 (1 row) -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Sort Output: (count(c2)), c2, 5, 7.0, 9 Sort Key: fdw132_t1.c2 -> Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT count(`c2`), `c2`, 5, 7.0, 9 FROM `mysql_fdw_regress`.`test1` GROUP BY 2, 3, 5 (7 rows) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; w | x | y | z ---+-----+---+----- 3 | 100 | 5 | 7.0 (1 row) -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t2.c2 -> Foreign Scan Output: c2, (sum(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c1`) < 500)) AND ((sum(`c1`) < 49800)) (7 rows) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; c2 | sum -----+----- 200 | 15 (1 row) -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: c3, (count(c1)) Sort Key: fdw132_t1.c3 COLLATE "C", (count(fdw132_t1.c1)) -> Foreign Scan Output: c3, (count(c1)) Filter: (sqrt(((max(fdw132_t1.c1)))::double precision) = '1.4142135623731'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`), max(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (8 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; c3 | count ------+------- AAA2 | 1 (1 row) SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*) -> Foreign Scan Output: fdw132_t1.c3, NULL::bigint Filter: (((((avg(fdw132_t1.c1)) / (avg(fdw132_t1.c1))))::double precision * random()) <= '1'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, NULL, avg(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c1`) < 500)) (7 rows) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; count ------- 3 (1 row) -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 Filter: (avg((fdw132_t1.c1 * ((random() <= '1'::double precision))::integer)) > '1'::numeric) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; sum ----- 14 (1 row) -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1 ORDER BY c1)), c2 Sort Key: (sum(fdw132_t1.c1 ORDER BY fdw132_t1.c1)) -> GroupAggregate Output: sum(c1 ORDER BY c1), c2 Group Key: fdw132_t1.c2 -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) (12 rows) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; sum ----- 14 (1 row) -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(c2 ORDER BY c1 DESC) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) AND ((`c2` > 50)) (5 rows) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; sum ----- 400 (1 row) -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT (c1 % 5))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT (`c1` % 5)) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 3 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (sum(DISTINCT (t1.c1 % 5))) -> Foreign Scan Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT sum(DISTINCT (r1.`c1` % 5)), (r2.`c1` % 3) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) GROUP BY 2 (7 rows) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; sum ----- 1 2 (2 rows) -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT `c1`) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 15 (1 row) -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) -> GroupAggregate Output: array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; array_agg ----------- {1} {2} (2 rows) -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------- GroupAggregate Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) (6 rows) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; sum | sum | c2 -----+-----+----- 5 | 1 | 100 (1 row) -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN ---------------------------------------------------------------------------------------------- Sort Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 Sort Key: (sum(fdw132_t1.c1) FILTER (WHERE ((fdw132_t1.c1 < 100) AND (fdw132_t1.c2 > 5)))) -> HashAggregate Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; sum ----- 14 (1 row) -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Aggregate Output: (SubPlan 1) -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Foreign Scan on public.fdw132_t1 t1 Output: count(*) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 2 (1 row) -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Foreign Scan on public.fdw132_t2 t2 Output: (SubPlan 1) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Aggregate Output: count(t1.c1) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 0 1 (2 rows) -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, rank('10'::text) WITHIN GROUP (ORDER BY c3), percentile_cont((((c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)) Group Key: fdw132_t2.c2 Filter: (percentile_cont((((fdw132_t2.c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((fdw132_t2.c1)::double precision)) < '500'::double precision) -> Sort Output: c2, c3, c1 Sort Key: fdw132_t2.c2 -> Foreign Scan on public.fdw132_t2 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; c2 | rank | percentile_cont -----+------+----------------- 200 | 1 | 12 (1 row) -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c1, rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2), c2 Group Key: fdw132_t1.c1, fdw132_t1.c2 -> Sort Output: c1, c2 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (9 rows) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; c1 | rank ----+------ 2 | 1 (1 row) -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b -> HashAggregate Output: count(*), x.b Group Key: x.b -> Hash Join Output: x.b Inner Unique: true Hash Cond: (fdw132_t1.c1 = x.a) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: x.b, x.a -> Subquery Scan on x Output: x.b, x.a -> Foreign Scan Output: fdw132_t2.c1, (sum(fdw132_t2.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (21 rows) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; count | b -------+--- 1 | 1 1 | 2 (2 rows) -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Sort Key: (avg(t1.c1)), (sum(t2.c1)) -> Foreign Scan Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT avg(r1.`c1`), sum(r2.`c1`), r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) GROUP BY 3 HAVING ((((avg(r1.`c1`) IS NULL) AND (sum(r2.`c1`) > 10)) OR (sum(r2.`c1`) IS NULL))) (7 rows) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; avg | sum -----+----- (0 rows) -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) Sort Key: (((sum(fdw132_t1.c2)) * ((random() <= '1'::double precision))::integer)) -> Foreign Scan Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c2`) FROM `mysql_fdw_regress`.`test1` (7 rows) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; sum ----- 300 (1 row) -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Sort Output: t1.c2, qry.sum Sort Key: t1.c2 -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) -> Subquery Scan on qry Output: qry.sum, t2.c1 Filter: ((t1.c2 * 2) = qry.sum) -> Foreign Scan Output: (sum((t2.c1 + t1.c1))), t2.c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT sum((`c1` + ?)), `c1` FROM `mysql_fdw_regress`.`test2` GROUP BY 2 (15 rows) -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: q.b, (count(fdw132_t1.c1)), (sum(q.a)) Sort Key: q.b, (count(fdw132_t1.c1)) -> HashAggregate Output: q.b, count(fdw132_t1.c1), sum(q.a) Group Key: q.b -> Hash Left Join Output: q.b, fdw132_t1.c1, q.a Inner Unique: true Hash Cond: ((fdw132_t1.c1)::numeric = q.b) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Hash Output: q.b, q.a -> Subquery Scan on q Output: q.b, q.a -> Foreign Scan Output: (min(13)), (avg(fdw132_t1_1.c1)), NULL::bigint Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1) INNER JOIN (mysql_fdw_regress.fdw132_t2)) Remote query: SELECT min(13), avg(r1.`c1`), NULL FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 12)) AND ((r1.`c1` = 12)) (21 rows) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; b | count | sum ---+-------+----- | 1 | (1 row) -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------------- Sort Output: c2, c3, (sum(c1)) Sort Key: fdw132_t1.c2, fdw132_t1.c3 COLLATE "C" -> HashAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c2 Hash Key: fdw132_t1.c3 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; c2 | c3 | sum -----+-------+----- 100 | | 14 | AAA1 | 1 | AAA11 | 11 | AAA2 | 2 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)), (GROUPING(c2)) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, sum(c1), GROUPING(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (9 rows) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; c2 | sum | grouping -----+-----+---------- 100 | 14 | 0 (1 row) -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 6)) GROUP BY 2 (9 rows) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; s ---- 14 (1 row) -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)), (sum(c2)) Sort Key: ((fdw132_t1.c2 % 2)) -> Foreign Scan Output: c2, ((c2 % 2)), (sum(c2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2), sum(`c2`) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | sum | count -----+-----+------- 100 | 300 | 1 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 DESC -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: c2, (least_agg(VARIADIC ARRAY[c1])) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, least_agg(VARIADIC ARRAY[c1]) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; ERROR: unrecognized configuration parameter "enable_partitionwise_aggregate" -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c1)) Sort Key: (sum(ftprt1_p1.c1)) -> HashAggregate Output: ftprt1_p1.c1, sum(ftprt1_p1.c1) Group Key: ftprt1_p1.c1 -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (13 rows) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; c1 | sum ----+----- 1 | 1 2 | 2 3 | 3 4 | 4 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c2)), (min(ftprt1_p1.c2)), (count(*)) Sort Key: (sum(ftprt1_p1.c2)) -> HashAggregate Output: ftprt1_p1.c1, sum(ftprt1_p1.c2), min(ftprt1_p1.c2), count(*) Group Key: ftprt1_p1.c1 Filter: (avg(ftprt1_p1.c2) < '22'::numeric) -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c1, ftprt1_p1.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c1, ftprt1_p2.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | sum | min | count ----+-----+-----+------- 1 | 1 | 1 | 1 2 | 2 | 2 | 1 3 | 3 | 3 | 1 4 | 4 | 4 | 1 (4 rows) -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: t1.c1, (count(((t1.*)::fprt1))) Sort Key: t1.c1 -> HashAggregate Output: t1.c1, count(((t1.*)::fprt1)) Group Key: t1.c1 Filter: (avg(t1.c2) < '22'::numeric) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.c1, t1.*, t1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.c1, t1_1.*, t1_1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | count ----+------- 1 | 1 2 | 1 3 | 1 4 | 1 (4 rows) -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c2, (avg(ftprt1_p1.c1)), (max(ftprt1_p1.c1)), (count(*)) Sort Key: ftprt1_p1.c2 -> HashAggregate Output: ftprt1_p1.c2, avg(ftprt1_p1.c1), max(ftprt1_p1.c1), count(*) Group Key: ftprt1_p1.c2 Filter: (sum(ftprt1_p1.c1) < 700) -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c2, ftprt1_p1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c2, ftprt1_p2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; c2 | avg | max | count ----+------------------------+-----+------- 1 | 1.00000000000000000000 | 1 | 1 2 | 2.0000000000000000 | 2 | 1 3 | 3.0000000000000000 | 3 | 1 4 | 4.0000000000000000 | 4 | 1 (4 rows) SET enable_partitionwise_aggregate TO off; ERROR: unrecognized configuration parameter "enable_partitionwise_aggregate" -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; DROP FOREIGN TABLE ftprt1_p2; DROP TABLE IF EXISTS fprt1; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/aggregate_pushdown_3.out000066400000000000000000001420171414541277500227570ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------ Result Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 -> Sort Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Sort Key: (sum(fdw132_t1.c1)), (avg(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), avg(c1), min(c2), max(c1), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 5)) (11 rows) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; sum | avg | min | max | sum2 -----+--------------------+-----+-----+------ 14 | 4.6666666666666667 | 100 | 11 | 14 (1 row) -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; QUERY PLAN ------------------------------------------------------------------------------- Aggregate Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (5 rows) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; sum | avg -----+-------------------- 14 | 4.6666666666666667 (1 row) -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*), sum(t1.c1), avg(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 2)) AND ((r1.`c1` = 2)) (6 rows) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; count | sum | avg -------+-----+-------------------- 1 | 2 | 2.0000000000000000 (1 row) -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(t1.c1), count(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; sum | count -----+------- 3 | 2 (1 row) -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; QUERY PLAN -------------------------------------------------------------------------- Sort Output: ((c2 + 2)), ((sum(c2) * ((c2 + 2)))) Sort Key: ((fdw132_t1.c2 + 2)) -> HashAggregate Output: ((c2 + 2)), (sum(c2) * ((c2 + 2))) Group Key: (fdw132_t1.c2 + 2) -> Foreign Scan on public.fdw132_t1 Output: (c2 + 2), c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; ?column? | ?column? ----------+---------- 102 | 30600 (1 row) -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; QUERY PLAN -------------------------------------------------------------------------------------- Aggregate Output: count(fdw132_t1.c2), sum(fdw132_t1.c2) -> Sort Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Sort Key: fdw132_t1.c2, (sum(fdw132_t1.c1)) -> HashAggregate Output: fdw132_t1.c2, sum(fdw132_t1.c1) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (11 rows) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; count | sum -------+----- 1 | 100 (1 row) -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 Sort Key: ((fdw132_t1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(fdw132_t1.c1) * fdw132_t1.c2)) -> HashAggregate Output: (c2 * ((random() <= '1'::double precision))::integer), (sum(c1) * c2), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; sum1 | sum2 ------+------ 100 | 1400 (1 row) -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)) Sort Key: ((fdw132_t2.c2 * ((random() <= '1'::double precision))::integer)) -> HashAggregate Output: ((c2 * ((random() <= '1'::double precision))::integer)) Group Key: (fdw132_t2.c2 * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t2 Output: (c2 * ((random() <= '1'::double precision))::integer) Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (9 rows) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; c2 ----- 200 (1 row) -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; QUERY PLAN -------------------------------------------------------------------------- Sort Output: (count(c2)), c2, 5, 7.0, 9 Sort Key: fdw132_t1.c2 -> HashAggregate Output: count(c2), c2, (5), 7.0, (9) Group Key: fdw132_t1.c2, 5, 9 -> Foreign Scan on public.fdw132_t1 Output: c2, 5, 9 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; w | x | y | z ---+-----+---+----- 3 | 100 | 5 | 7.0 (1 row) -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; QUERY PLAN ---------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t2.c2 -> HashAggregate Output: c2, sum(c1) Group Key: fdw132_t2.c2 Filter: ((avg(fdw132_t2.c1) < '500'::numeric) AND (sum(fdw132_t2.c1) < 49800)) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; c2 | sum -----+----- 200 | 15 (1 row) -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; QUERY PLAN ----------------------------------------------------------------------------------------------------- Sort Output: c3, (count(c1)) Sort Key: fdw132_t1.c3 COLLATE "C", (count(fdw132_t1.c1)) -> HashAggregate Output: c3, count(c1) Group Key: fdw132_t1.c3 Filter: (sqrt((max(fdw132_t1.c1))::double precision) = '1.4142135623731'::double precision) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; c3 | count ------+------- AAA2 | 1 (1 row) SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*) -> HashAggregate Output: fdw132_t1.c3, NULL::bigint Group Key: fdw132_t1.c3 Filter: ((avg(fdw132_t1.c1) < '500'::numeric) AND ((((avg(fdw132_t1.c1) / avg(fdw132_t1.c1)))::double precision * random()) <= '1'::double precision)) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; count ------- 3 (1 row) -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 Filter: (avg((fdw132_t1.c1 * ((random() <= '1'::double precision))::integer)) > '1'::numeric) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; sum ----- 14 (1 row) -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1 ORDER BY c1)), c2 Sort Key: (sum(fdw132_t1.c1 ORDER BY fdw132_t1.c1)) -> GroupAggregate Output: sum(c1 ORDER BY c1), c2 Group Key: fdw132_t1.c2 -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) (12 rows) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; sum ----- 14 (1 row) -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(c2 ORDER BY c1 DESC) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) AND ((`c2` > 50)) (5 rows) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; sum ----- 400 (1 row) -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Aggregate Output: sum(DISTINCT (c1 % 5)) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (5 rows) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 3 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (sum(DISTINCT (t1.c1 % 5))) -> GroupAggregate Output: sum(DISTINCT (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; sum ----- 1 2 (2 rows) -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Aggregate Output: sum(DISTINCT c1) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (5 rows) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 15 (1 row) -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) -> GroupAggregate Output: array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; array_agg ----------- {1} {2} (2 rows) -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------- GroupAggregate Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) (6 rows) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; sum | sum | c2 -----+-----+----- 5 | 1 | 100 (1 row) -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN ---------------------------------------------------------------------------------------------- Sort Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 Sort Key: (sum(fdw132_t1.c1) FILTER (WHERE ((fdw132_t1.c1 < 100) AND (fdw132_t1.c2 > 5)))) -> HashAggregate Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; sum ----- 14 (1 row) -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Aggregate Output: (SubPlan 1) -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Foreign Scan on public.fdw132_t1 t1 Output: count(*) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 2 (1 row) -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Foreign Scan on public.fdw132_t2 t2 Output: (SubPlan 1) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Aggregate Output: count(t1.c1) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 0 1 (2 rows) -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, rank('10'::text) WITHIN GROUP (ORDER BY c3), percentile_cont((((c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)) Group Key: fdw132_t2.c2 Filter: (percentile_cont((((fdw132_t2.c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((fdw132_t2.c1)::double precision)) < '500'::double precision) -> Sort Output: c2, c3, c1 Sort Key: fdw132_t2.c2 -> Foreign Scan on public.fdw132_t2 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; c2 | rank | percentile_cont -----+------+----------------- 200 | 1 | 12 (1 row) -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c1, rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2), c2 Group Key: fdw132_t1.c1, fdw132_t1.c2 -> Sort Output: c1, c2 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (9 rows) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; c1 | rank ----+------ 2 | 1 (1 row) -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b -> HashAggregate Output: count(*), x.b Group Key: x.b -> Hash Join Output: x.b Hash Cond: (fdw132_t1.c1 = x.a) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: x.b, x.a -> Subquery Scan on x Output: x.b, x.a -> HashAggregate Output: fdw132_t2.c1, sum(fdw132_t2.c1) Group Key: fdw132_t2.c1 -> Foreign Scan on public.fdw132_t2 Output: fdw132_t2.c1, fdw132_t2.c2, fdw132_t2.c3, fdw132_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (22 rows) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; count | b -------+--- 1 | 1 1 | 2 (2 rows) -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Sort Key: (avg(t1.c1)), (sum(t2.c1)) -> HashAggregate Output: avg(t1.c1), sum(t2.c1), t2.c1 Group Key: t2.c1 Filter: (((avg(t1.c1) IS NULL) AND (sum(t2.c1) > 10)) OR (sum(t2.c1) IS NULL)) -> Foreign Scan Output: t2.c1, t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (11 rows) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; avg | sum -----+----- (0 rows) -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((sum(c2) * ((random() <= '1'::double precision))::integer)) Sort Key: ((sum(fdw132_t1.c2) * ((random() <= '1'::double precision))::integer)) -> Aggregate Output: (sum(c2) * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` (8 rows) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; sum ----- 300 (1 row) -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; QUERY PLAN ---------------------------------------------------------------------------------------------------- Sort Output: t1.c2, qry.sum Sort Key: t1.c2 -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) -> Subquery Scan on qry Output: qry.sum, t2.c1 Filter: ((t1.c2 * 2) = qry.sum) -> HashAggregate Output: sum((t2.c1 + t1.c1)), t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (17 rows) -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: q.b, (count(fdw132_t1.c1)), (sum(q.a)) Sort Key: q.b, (count(fdw132_t1.c1)) -> HashAggregate Output: q.b, count(fdw132_t1.c1), sum(q.a) Group Key: q.b -> Hash Left Join Output: q.b, fdw132_t1.c1, q.a Hash Cond: ((fdw132_t1.c1)::numeric = q.b) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Hash Output: q.b, q.a -> Subquery Scan on q Output: q.b, q.a -> Aggregate Output: min(13), avg(fdw132_t1_1.c1), NULL::bigint -> Foreign Scan Output: fdw132_t1_1.c1 Relations: (mysql_fdw_regress.fdw132_t1) INNER JOIN (mysql_fdw_regress.fdw132_t2) Remote query: SELECT r1.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 12)) AND ((r1.`c1` = 12)) (22 rows) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; b | count | sum ---+-------+----- | 1 | (1 row) -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group Key: fdw132_t1.c2 Group Key: () -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, sum(c1) Group Key: fdw132_t1.c2 Group Key: () -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: c2, c3, (sum(c1)) Sort Key: fdw132_t1.c2, fdw132_t1.c3 COLLATE "C" -> GroupAggregate Output: c2, c3, sum(c1) Group Key: fdw132_t1.c2 Sort Key: fdw132_t1.c3 COLLATE "C" Group Key: fdw132_t1.c3 -> Sort Output: c2, c3, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (14 rows) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; c2 | c3 | sum -----+-------+----- 100 | | 14 | AAA1 | 1 | AAA11 | 11 | AAA2 | 2 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)), (GROUPING(c2)) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, sum(c1), GROUPING(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (9 rows) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; c2 | sum | grouping -----+-----+---------- 100 | 14 | 0 (1 row) -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 6)) (11 rows) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; s ---- 14 (1 row) -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)), (sum(c2)) Sort Key: ((fdw132_t1.c2 % 2)) -> HashAggregate Output: c2, (c2 % 2), sum(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) (14 rows) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | sum | count -----+-----+------- 100 | 300 | 1 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 DESC -> HashAggregate Output: c2, (c2 % 2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) (14 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 -> HashAggregate Output: c2, (c2 % 2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) (14 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: c2, (least_agg(VARIADIC ARRAY[c1])) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, least_agg(VARIADIC ARRAY[c1]) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; ERROR: unrecognized configuration parameter "enable_partitionwise_aggregate" -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; NOTICE: syntax error CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES... ^ CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES... ^ -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; ERROR: relation "fprt1" does not exist LINE 2: SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; ^ SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; ERROR: relation "fprt1" does not exist LINE 1: SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; ^ EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; ERROR: relation "fprt1" does not exist LINE 2: SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1... ^ SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; ERROR: relation "fprt1" does not exist LINE 1: SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1... ^ -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; ERROR: relation "fprt1" does not exist LINE 2: SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2... ^ SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; ERROR: relation "fprt1" does not exist LINE 1: SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2... ^ -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; ERROR: relation "fprt1" does not exist LINE 2: SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2... ^ SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; ERROR: relation "fprt1" does not exist LINE 1: SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2... ^ SET enable_partitionwise_aggregate TO off; ERROR: unrecognized configuration parameter "enable_partitionwise_aggregate" -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; ERROR: foreign table "ftprt1_p1" does not exist DROP FOREIGN TABLE ftprt1_p2; ERROR: foreign table "ftprt1_p2" does not exist DROP TABLE IF EXISTS fprt1; NOTICE: table "fprt1" does not exist, skipping DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/aggregate_pushdown_4.out000066400000000000000000001455561414541277500227730ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------- Result Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2 -> Sort Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Sort Key: (sum(fdw132_t1.c1)), (avg(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), avg(`c1`), min(`c2`), max(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 5)) GROUP BY 5 (9 rows) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; sum | avg | min | max | sum2 -----+--------+-----+-----+------ 14 | 4.6667 | 100 | 11 | 14 (1 row) -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; QUERY PLAN ------------------------------------------------------------------------------- Aggregate Output: sum((c1 * ((random() <= '1'::double precision))::integer)), avg(c1) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (5 rows) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; sum | avg -----+-------------------- 14 | 4.6666666666666667 (1 row) -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (count(*)), (sum(t1.c1)), (avg(t2.c1)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT count(*), sum(r1.`c1`), avg(r2.`c1`) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 2)) AND ((r1.`c1` = 2)) (4 rows) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; count | sum | avg -------+-----+-------- 1 | 2 | 2.0000 (1 row) -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(t1.c1), count(t2.c1) -> Foreign Scan Output: t1.c1, t2.c1 Filter: (((((t1.c1 * t2.c1) / (t1.c1 * t2.c1)))::double precision * random()) <= '1'::double precision) Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; sum | count -----+------- 3 | 2 (1 row) -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Sort Key: ((fdw132_t1.c2 + 2)) -> Foreign Scan Output: ((c2 + 2)), ((sum(c2) * (c2 + 2))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (`c2` + 2), (sum(`c2`) * (`c2` + 2)) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (7 rows) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; ?column? | ?column? ----------+---------- 102 | 30600 (1 row) -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; QUERY PLAN ------------------------------------------------------------------------------------------------ Aggregate Output: count(fdw132_t1.c2), sum(fdw132_t1.c2) -> Sort Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Sort Key: fdw132_t1.c2, (sum(fdw132_t1.c1)) -> Foreign Scan Output: fdw132_t1.c2, (sum(fdw132_t1.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (9 rows) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; count | sum -------+----- 1 | 100 (1 row) -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)), ((sum(c1) * c2)), c2 Sort Key: ((fdw132_t1.c2 * ((random() <= '1'::double precision))::integer)), ((sum(fdw132_t1.c1) * fdw132_t1.c2)) -> Foreign Scan Output: (c2 * ((random() <= '1'::double precision))::integer), ((sum(c1) * c2)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT (sum(`c1`) * `c2`), `c2` FROM `mysql_fdw_regress`.`test1` GROUP BY 2 (7 rows) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; sum1 | sum2 ------+------ 100 | 1400 (1 row) -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------ Sort Output: ((c2 * ((random() <= '1'::double precision))::integer)) Sort Key: ((fdw132_t2.c2 * ((random() <= '1'::double precision))::integer)) -> HashAggregate Output: ((c2 * ((random() <= '1'::double precision))::integer)) Group Key: (fdw132_t2.c2 * ((random() <= '1'::double precision))::integer) -> Foreign Scan on public.fdw132_t2 Output: (c2 * ((random() <= '1'::double precision))::integer) Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (9 rows) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; c2 ----- 200 (1 row) -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------- Sort Output: (count(c2)), c2, 5, 7.0, 9 Sort Key: fdw132_t1.c2 -> Foreign Scan Output: (count(c2)), c2, 5, 7.0, 9 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT count(`c2`), `c2`, 5, 7.0, 9 FROM `mysql_fdw_regress`.`test1` GROUP BY 2, 3, 5 (7 rows) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; w | x | y | z ---+-----+---+----- 3 | 100 | 5 | 7.0 (1 row) -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t2.c2 -> Foreign Scan Output: c2, (sum(c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c2`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c1`) < 500)) AND ((sum(`c1`) < 49800)) (7 rows) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; c2 | sum -----+----- 200 | 15 (1 row) -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; QUERY PLAN ---------------------------------------------------------------------------------------------------------- Sort Output: c3, (count(c1)) Sort Key: fdw132_t1.c3 COLLATE "C", (count(fdw132_t1.c1)) -> Foreign Scan Output: c3, (count(c1)) Filter: (sqrt(((max(fdw132_t1.c1)))::double precision) = '1.4142135623730951'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, count(`c1`), max(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 (8 rows) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; c3 | count ------+------- AAA2 | 1 (1 row) SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Aggregate Output: count(*) -> Foreign Scan Output: fdw132_t1.c3, NULL::bigint Filter: (((((avg(fdw132_t1.c1)) / (avg(fdw132_t1.c1))))::double precision * random()) <= '1'::double precision) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c3`, NULL, avg(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c1`) < 500)) (7 rows) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; count ------- 3 (1 row) -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> HashAggregate Output: sum(c1), c2 Group Key: fdw132_t1.c2 Filter: (avg((fdw132_t1.c1 * ((random() <= '1'::double precision))::integer)) > '1'::numeric) -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (10 rows) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; sum ----- 14 (1 row) -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Sort Output: (sum(c1 ORDER BY c1)), c2 Sort Key: (sum(fdw132_t1.c1 ORDER BY fdw132_t1.c1)) -> GroupAggregate Output: sum(c1 ORDER BY c1), c2 Group Key: fdw132_t1.c2 -> Sort Output: c2, c1 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c2, c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 100)) (12 rows) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; sum ----- 14 (1 row) -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Aggregate Output: sum(c2 ORDER BY c1 DESC) -> Foreign Scan on public.fdw132_t2 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) AND ((`c2` > 50)) (5 rows) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; sum ----- 400 (1 row) -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT (c1 % 5))) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT (`c1` % 5)) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 3 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (sum(DISTINCT (t1.c1 % 5))) -> Foreign Scan Output: (sum(DISTINCT (t1.c1 % 5))), ((t2.c1 % 3)) Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT sum(DISTINCT (r1.`c1` % 5)), (r2.`c1` % 3) FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) GROUP BY 2 (7 rows) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; sum ----- 1 2 (2 rows) -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: (sum(DISTINCT c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT sum(DISTINCT `c1`) FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 50)) AND ((`c2` = 200)) (4 rows) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; sum ----- 15 (1 row) -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))), ((t2.c1 % 3)) Sort Key: (array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5))) -> GroupAggregate Output: array_agg(DISTINCT (t1.c1 % 5) ORDER BY (t1.c1 % 5)), ((t2.c1 % 3)) Group Key: ((t2.c1 % 3)) -> Sort Output: ((t2.c1 % 3)), t1.c1 Sort Key: ((t2.c1 % 3)) -> Foreign Scan Output: (t2.c1 % 3), t1.c1 Relations: (mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT r2.`c1`, r1.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON ((((r1.`c1` < 20) OR ((r1.`c1` IS NULL) AND (r2.`c1` < 5)))) AND ((r1.`c1` = r2.`c1`)))) WHERE (((r1.`c1` < 20) OR (r1.`c1` IS NULL))) (13 rows) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; array_agg ----------- {1} {2} (2 rows) -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------- GroupAggregate Output: sum((c1 % 3)), sum(DISTINCT (c1 % 3) ORDER BY (c1 % 3)) FILTER (WHERE ((c1 % 3) < 2)), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) (6 rows) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; sum | sum | c2 -----+-----+----- 5 | 1 | 100 (1 row) -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN ---------------------------------------------------------------------------------------------- Sort Output: (sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5)))), c2 Sort Key: (sum(fdw132_t1.c1) FILTER (WHERE ((fdw132_t1.c1 < 100) AND (fdw132_t1.c2 > 5)))) -> HashAggregate Output: sum(c1) FILTER (WHERE ((c1 < 100) AND (c2 > 5))), c2 Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; sum ----- 14 (1 row) -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Aggregate Output: (SubPlan 1) -> Foreign Scan on public.fdw132_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Foreign Scan on public.fdw132_t1 t1 Output: count(*) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 2 (1 row) -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Unique Output: ((SubPlan 1)) -> Sort Output: ((SubPlan 1)) Sort Key: ((SubPlan 1)) -> Foreign Scan on public.fdw132_t2 t2 Output: (SubPlan 1) Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` SubPlan 1 -> Aggregate Output: count(t1.c1) FILTER (WHERE ((t2.c2 = 200) AND (t2.c1 < 10))) -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (14 rows) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; count ------- 0 1 (2 rows) -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- GroupAggregate Output: c2, rank('10'::text) WITHIN GROUP (ORDER BY c3), percentile_cont((((c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((c1)::double precision)) Group Key: fdw132_t2.c2 Filter: (percentile_cont((((fdw132_t2.c2)::numeric / '200'::numeric))::double precision) WITHIN GROUP (ORDER BY ((fdw132_t2.c1)::double precision)) < '500'::double precision) -> Sort Output: c2, c3, c1 Sort Key: fdw132_t2.c2 -> Foreign Scan on public.fdw132_t2 Output: c2, c3, c1 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test2` (10 rows) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; c2 | rank | percentile_cont -----+------+----------------- 200 | 1 | 12 (1 row) -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------- GroupAggregate Output: c1, rank(c1, c2) WITHIN GROUP (ORDER BY c1, c2), c2 Group Key: fdw132_t1.c1, fdw132_t1.c2 -> Sort Output: c1, c2 Sort Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 2)) (9 rows) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; c1 | rank ----+------ 2 | 1 (1 row) -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Sort Output: (count(*)), x.b Sort Key: (count(*)), x.b -> HashAggregate Output: count(*), x.b Group Key: x.b -> Hash Join Output: x.b Inner Unique: true Hash Cond: (fdw132_t1.c1 = x.a) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: x.b, x.a -> Subquery Scan on x Output: x.b, x.a -> Foreign Scan Output: fdw132_t2.c1, (sum(fdw132_t2.c1)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t2) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (21 rows) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; count | b -------+--- 1 | 1 1 | 2 (2 rows) -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Sort Key: (avg(t1.c1)), (sum(t2.c1)) -> Foreign Scan Output: (avg(t1.c1)), (sum(t2.c1)), t2.c1 Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1 t1) INNER JOIN (mysql_fdw_regress.fdw132_t2 t2)) Remote query: SELECT avg(r1.`c1`), sum(r2.`c1`), r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) GROUP BY 3 HAVING ((((avg(r1.`c1`) IS NULL) AND (sum(r2.`c1`) > 10)) OR (sum(r2.`c1`) IS NULL))) (7 rows) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; avg | sum -----+----- (0 rows) -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: (((sum(c2)) * ((random() <= '1'::double precision))::integer)) Sort Key: (((sum(fdw132_t1.c2)) * ((random() <= '1'::double precision))::integer)) -> Foreign Scan Output: ((sum(c2)) * ((random() <= '1'::double precision))::integer) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c2`) FROM `mysql_fdw_regress`.`test1` (7 rows) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; sum ----- 300 (1 row) -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Sort Output: t1.c2, qry.sum Sort Key: t1.c2 -> Nested Loop Output: t1.c2, qry.sum -> Foreign Scan on public.fdw132_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) -> Subquery Scan on qry Output: qry.sum, t2.c1 Filter: ((t1.c2 * 2) = qry.sum) -> Foreign Scan Output: (sum((t2.c1 + t1.c1))), t2.c1 Relations: Aggregate on (mysql_fdw_regress.fdw132_t2 t2) Remote query: SELECT sum((`c1` + ?)), `c1` FROM `mysql_fdw_regress`.`test2` GROUP BY 2 (15 rows) -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Sort Output: q.b, (count(fdw132_t1.c1)), (sum(q.a)) Sort Key: q.b, (count(fdw132_t1.c1)) -> HashAggregate Output: q.b, count(fdw132_t1.c1), sum(q.a) Group Key: q.b -> Hash Left Join Output: q.b, fdw132_t1.c1, q.a Inner Unique: true Hash Cond: ((fdw132_t1.c1)::numeric = q.b) -> Foreign Scan on public.fdw132_t1 Output: fdw132_t1.c1, fdw132_t1.c2, fdw132_t1.c3, fdw132_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Hash Output: q.b, q.a -> Subquery Scan on q Output: q.b, q.a -> Foreign Scan Output: (min(13)), (avg(fdw132_t1_1.c1)), NULL::bigint Relations: Aggregate on ((mysql_fdw_regress.fdw132_t1) INNER JOIN (mysql_fdw_regress.fdw132_t2)) Remote query: SELECT min(13), avg(r1.`c1`), NULL FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` = 12)) AND ((r1.`c1` = 12)) (21 rows) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; b | count | sum ---+-------+----- | 1 | (1 row) -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)) Sort Key: fdw132_t1.c2 -> MixedAggregate Output: c2, sum(c1) Hash Key: fdw132_t1.c2 Group Key: () -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; c2 | sum -----+----- 100 | 14 | 14 (2 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------------- Sort Output: c2, c3, (sum(c1)) Sort Key: fdw132_t1.c2, fdw132_t1.c3 COLLATE "C" -> HashAggregate Output: c2, c3, sum(c1) Hash Key: fdw132_t1.c2 Hash Key: fdw132_t1.c3 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (10 rows) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; c2 | c3 | sum -----+-------+----- 100 | | 14 | AAA1 | 1 | AAA11 | 11 | AAA2 | 2 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c1)), (GROUPING(c2)) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, sum(c1), GROUPING(c2) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 3)) (9 rows) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; c2 | sum | grouping -----+-----+---------- 100 | 14 | 0 (1 row) -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------- Unique Output: (sum(c1)), c2 -> Sort Output: (sum(c1)), c2 Sort Key: (sum(fdw132_t1.c1)) -> Foreign Scan Output: (sum(c1)), c2 Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT sum(`c1`), `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 6)) GROUP BY 2 (9 rows) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; s ---- 14 (1 row) -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (sum(c2)), (count(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, (sum(c2)), count(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)), (sum(c2)) Sort Key: ((fdw132_t1.c2 % 2)) -> Foreign Scan Output: c2, ((c2 % 2)), (sum(c2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2), sum(`c2`) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | sum | count -----+-----+------- 100 | 300 | 1 (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 DESC -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c2, (array_agg(c2) OVER (?)), ((c2 % 2)) Sort Key: fdw132_t1.c2 -> WindowAgg Output: c2, array_agg(c2) OVER (?), ((c2 % 2)) -> Sort Output: c2, ((c2 % 2)) Sort Key: ((fdw132_t1.c2 % 2)), fdw132_t1.c2 -> Foreign Scan Output: c2, ((c2 % 2)) Relations: Aggregate on (mysql_fdw_regress.fdw132_t1) Remote query: SELECT `c2`, (`c2` % 2) FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` > 10)) GROUP BY 1 (12 rows) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; c2 | array_agg -----+----------- 100 | {100} (1 row) -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: c2, (least_agg(VARIADIC ARRAY[c1])) Sort Key: fdw132_t1.c2 -> HashAggregate Output: c2, least_agg(VARIADIC ARRAY[c1]) Group Key: fdw132_t1.c2 -> Foreign Scan on public.fdw132_t1 Output: c1, c2, c3, c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` (9 rows) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; c2 | least_agg -----+----------- 100 | (1 row) -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; QUERY PLAN ------------------------------------------------------------------------------------------------ Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c1)) Sort Key: (sum(ftprt1_p1.c1)) -> Append -> Foreign Scan Output: ftprt1_p1.c1, (sum(ftprt1_p1.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 -> Foreign Scan Output: ftprt1_p2.c1, (sum(ftprt1_p2.c1)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c1`) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 (12 rows) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; c1 | sum ----+----- 1 | 1 2 | 2 3 | 3 4 | 4 (4 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, (sum(ftprt1_p1.c2)), (min(ftprt1_p1.c2)), (count(*)) Sort Key: (sum(ftprt1_p1.c2)) -> Append -> Foreign Scan Output: ftprt1_p1.c1, (sum(ftprt1_p1.c2)), (min(ftprt1_p1.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p1 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test1` GROUP BY 1 HAVING ((avg(`c2`) < 22)) -> Foreign Scan Output: ftprt1_p2.c1, (sum(ftprt1_p2.c2)), (min(ftprt1_p2.c2)), (count(*)) Relations: Aggregate on (mysql_fdw_regress.ftprt1_p2 fprt1) Remote query: SELECT `c1`, sum(`c2`), min(`c2`), count(*) FROM `mysql_fdw_regress`.`test2` GROUP BY 1 HAVING ((avg(`c2`) < 22)) (12 rows) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | sum | min | count ----+-----+-----+------- 1 | 1 | 1 | 1 2 | 2 | 2 | 1 3 | 3 | 3 | 1 4 | 4 | 4 | 1 (4 rows) -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------------------- GroupAggregate Output: t1.c1, count(((t1.*)::fprt1)) Group Key: t1.c1 Filter: (avg(t1.c2) < '22'::numeric) -> Sort Output: t1.c1, ((t1.*)::fprt1), t1.c2 Sort Key: t1.c1 -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.c1, t1.*, t1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.c1, t1_1.*, t1_1.c2 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; c1 | count ----+------- 1 | 1 2 | 1 3 | 1 4 | 1 (4 rows) -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c2, (avg(ftprt1_p1.c1)), (max(ftprt1_p1.c1)), (count(*)) Sort Key: ftprt1_p1.c2 -> HashAggregate Output: ftprt1_p1.c2, avg(ftprt1_p1.c1), max(ftprt1_p1.c1), count(*) Group Key: ftprt1_p1.c2 Filter: (sum(ftprt1_p1.c1) < 700) -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c2, ftprt1_p1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c2, ftprt1_p2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; c2 | avg | max | count ----+------------------------+-----+------- 1 | 1.00000000000000000000 | 1 | 1 2 | 2.0000000000000000 | 2 | 1 3 | 3.0000000000000000 | 3 | 1 4 | 4.0000000000000000 | 4 | 1 (4 rows) SET enable_partitionwise_aggregate TO off; -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; DROP FOREIGN TABLE ftprt1_p2; DROP TABLE IF EXISTS fprt1; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/connection_validation.out000066400000000000000000000060211414541277500232230ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign table and Validate CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); SELECT * FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) -- FDW-121: After a change to a pg_foreign_server or pg_user_mapping catalog -- entry, existing connection should be invalidated and should make new -- connection using the updated connection details. -- Alter SERVER option. -- Set wrong host, subsequent operation on this server should use updated -- details and fail as the host address is not correct. The error code in error -- message is different for different server versions and platform, so check -- that through plpgsql block and give the generic error message. ALTER SERVER mysql_svr OPTIONS (SET host 'localhos'); DO $$ BEGIN SELECT * FROM f_mysql_test ORDER BY 1, 2; EXCEPTION WHEN others THEN IF SQLERRM LIKE 'failed to connect to MySQL: Unknown MySQL server host ''localhos'' (%)' THEN RAISE NOTICE 'failed to connect to MySQL: Unknown MySQL server host ''localhos'''; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; NOTICE: failed to connect to MySQL: Unknown MySQL server host 'localhos' -- Set the correct host-name, next operation should succeed. ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST); SELECT * FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) -- Alter USER MAPPING option. -- Set wrong password, next operation should fail. ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (SET username :MYSQL_USER_NAME, SET password 'bar1'); DO $$ BEGIN SELECT * FROM f_mysql_test ORDER BY 1, 2; EXCEPTION WHEN others THEN IF SQLERRM LIKE 'failed to connect to MySQL: Access denied for user ''%''@''%'' (using password: YES)' THEN RAISE NOTICE 'failed to connect to MySQL: Access denied for MYSQL_USER_NAME'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; NOTICE: failed to connect to MySQL: Access denied for MYSQL_USER_NAME -- Set correct user-name and password, next operation should succeed. ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (SET username :MYSQL_USER_NAME, SET password :MYSQL_PASS); SELECT * FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) -- Cleanup DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/dml.out000066400000000000000000000256721414541277500174430ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign tables CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student'); CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student'); CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'numbers'); CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'nosuchtable'); CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress2', table_name 'numbers'); CREATE FOREIGN TABLE fdw126_ft6(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'mysql_fdw_regress1.student'); CREATE FOREIGN TABLE f_empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'empdata'); CREATE FOREIGN TABLE fdw193_ft1(stu_id varchar(10), stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student1'); -- Operation on blob data. INSERT INTO f_empdata VALUES (1, decode ('01234567', 'hex')); INSERT INTO f_empdata VALUES (2, 'abc'); SELECT count(*) FROM f_empdata ORDER BY 1; count ------- 2 (1 row) SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; emp_id | emp_dat --------+------------ 1 | \x01234567 2 | \x616263 (2 rows) UPDATE f_empdata SET emp_dat = decode ('0123', 'hex') WHERE emp_id = 1; SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; emp_id | emp_dat --------+---------- 1 | \x0123 2 | \x616263 (2 rows) -- FDW-126: Insert/update/delete statement failing in mysql_fdw by picking -- wrong database name. -- Verify the INSERT/UPDATE/DELETE operations on another foreign table which -- resides in the another database in MySQL. The previous commands performs -- the operation on foreign table created for tables in mysql_fdw_regress -- MySQL database. Below operations will be performed for foreign table -- created for table in mysql_fdw_regress1 MySQL database. INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1; DELETE FROM fdw126_ft1 WHERE stu_id = 1; -- Select on f_mysql_test foreign table which is created for mysql_test table -- from mysql_fdw_regress MySQL database. This call is just to cross verify if -- everything is working correctly. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) -- Insert into fdw126_ft2 table which does not have dbname specified while -- creating the foreign table, so it will consider the schema name of foreign -- table as database name and try to connect/lookup into that database. Will -- throw an error. The error message is different on old mysql and mariadb -- servers so give the generic message. DO $$ BEGIN INSERT INTO fdw126_ft2 VALUES(2, 'Two'); EXCEPTION WHEN others THEN IF SQLERRM LIKE '%SELECT command denied to user ''%''@''%'' for table ''student''' THEN RAISE NOTICE E'failed to execute the MySQL query: \nUnknown database ''public'''; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; NOTICE: failed to execute the MySQL query: Unknown database 'public' -- Check with the same table name from different database. fdw126_ft3 is -- pointing to the mysql_fdw_regress1.numbers and not mysql_fdw_regress.numbers -- table. INSERT/UPDATE/DELETE should be failing. SELECT will return no rows. INSERT INTO fdw126_ft3 VALUES(1, 'One'); ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation SELECT a, b FROM fdw126_ft3 ORDER BY 1, 2 LIMIT 1; a | b ---+--- (0 rows) UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1; ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation DELETE FROM fdw126_ft3 WHERE a = 1; ERROR: first column of remote table must be unique for INSERT/UPDATE/DELETE operation -- Check when table_name is given in database.table form in foreign table -- should error out as syntax error. The error contains server name like -- MySQL or MariaDB, so give the generic message by removing the server name, so -- that it should pass on both the servers. DO $$ BEGIN INSERT INTO fdw126_ft6 VALUES(1, 'One'); EXCEPTION WHEN others THEN IF SQLERRM LIKE '%You have an error in your SQL syntax; check the manual % for the right syntax to use near ''.student'' at line 1' THEN RAISE NOTICE E'failed to execute the MySQL query: \nYou have an error in your SQL syntax; check the manual that corresponds to your server version for the right syntax to use near ''.student'' at line 1'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; NOTICE: failed to execute the MySQL query: You have an error in your SQL syntax; check the manual that corresponds to your server version for the right syntax to use near '.student' at line 1 -- Perform the ANALYZE on the foreign table which is not present on the remote -- side. Should not crash. -- The database is present but not the target table. ANALYZE fdw126_ft4; ERROR: relation mysql_fdw_regress1.nosuchtable does not exist -- The database itself is not present. ANALYZE fdw126_ft5; ERROR: relation mysql_fdw_regress2.numbers does not exist -- Some other variant of analyze and vacuum. -- when table exists, should give skip-warning VACUUM f_empdata; WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables VACUUM FULL f_empdata; WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables VACUUM FREEZE f_empdata; WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables ANALYZE f_empdata; WARNING: skipping "f_empdata" --- cannot analyze this foreign table ANALYZE f_empdata(emp_id); WARNING: skipping "f_empdata" --- cannot analyze this foreign table VACUUM ANALYZE f_empdata; WARNING: skipping "f_empdata" --- cannot vacuum non-tables or special system tables -- Verify the before update trigger which modifies the column value which is not -- part of update statement. CREATE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; RETURN NEW; END $$ language plpgsql; CREATE TRIGGER before_row_update_trig BEFORE UPDATE ON fdw126_ft1 FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; SELECT * FROM fdw126_ft1 ORDER BY stu_id; stu_id | stu_name | stu_dept --------+----------------------+---------- 1 | One trigger updated! | 201 (1 row) -- Throw an error when target list has row identifier column. UPDATE fdw126_ft1 SET stu_dept = 201, stu_id = 10 WHERE stu_id = 1; ERROR: row identifier column update is not supported -- Throw an error when before row update trigger modify the row identifier -- column (int column) value. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = 20; RETURN NEW; END $$ language plpgsql; UPDATE fdw126_ft1 SET stu_dept = 301 WHERE stu_id = 1; ERROR: row identifier column update is not supported -- Verify the before update trigger which modifies the column value which is -- not part of update statement. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; RETURN NEW; END $$ language plpgsql; CREATE TRIGGER before_row_update_trig1 BEFORE UPDATE ON fdw193_ft1 FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); INSERT INTO fdw193_ft1 VALUES('aa', 'One', 101); UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; SELECT * FROM fdw193_ft1 ORDER BY stu_id; stu_id | stu_name | stu_dept --------+----------------------+---------- aa | One trigger updated! | 201 (1 row) -- Throw an error when before row update trigger modify the row identifier -- column (varchar column) value. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = 'bb'; RETURN NEW; END $$ language plpgsql; UPDATE fdw193_ft1 SET stu_dept = 301 WHERE stu_id = 'aa'; ERROR: row identifier column update is not supported -- Verify the NULL assignment scenario. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = NULL; RETURN NEW; END $$ language plpgsql; UPDATE fdw193_ft1 SET stu_dept = 401 WHERE stu_id = 'aa'; ERROR: row identifier column update is not supported -- FDW-224 Fix COPY FROM and foreign partition routing result in server crash -- Should fail as foreign table direct copy not supported COPY f_mysql_test TO stdout; ERROR: cannot copy from foreign table "f_mysql_test" HINT: Try the COPY (SELECT ...) TO variant. COPY f_mysql_test (a) TO stdout; ERROR: cannot copy from foreign table "f_mysql_test" HINT: Try the COPY (SELECT ...) TO variant. -- Should pass COPY (SELECT * FROM f_mysql_test) TO stdout; 1 1 COPY (SELECT a FROM f_mysql_test) TO '/tmp/copy_test.txt' delimiter ','; -- Should give error message as copy from with foreign table not supported DO $$ BEGIN COPY f_mysql_test(a) FROM '/tmp/copy_test.txt' delimiter ','; EXCEPTION WHEN others THEN IF SQLERRM = 'COPY and foreign partition routing not supported in mysql_fdw' OR SQLERRM = 'cannot copy to foreign table "f_mysql_test"' THEN RAISE NOTICE 'ERROR: COPY and foreign partition routing not supported in mysql_fdw'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; NOTICE: ERROR: COPY and foreign partition routing not supported in mysql_fdw -- Cleanup DELETE FROM fdw126_ft1; DELETE FROM f_empdata; DELETE FROM fdw193_ft1; DROP FOREIGN TABLE f_mysql_test; DROP FOREIGN TABLE fdw126_ft1; DROP FOREIGN TABLE fdw126_ft2; DROP FOREIGN TABLE fdw126_ft3; DROP FOREIGN TABLE fdw126_ft4; DROP FOREIGN TABLE fdw126_ft5; DROP FOREIGN TABLE fdw126_ft6; DROP FOREIGN TABLE f_empdata; DROP FOREIGN TABLE fdw193_ft1; DROP FUNCTION before_row_update_func(); DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown.out000066400000000000000000001644351414541277500215560ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) WHERE ((r1.`c2` = 100)) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r1.`c2` = 100)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) INNER JOIN `mysql_fdw_regress`.`test3` r4 ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c1`, r3.`c1` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) INNER JOIN `mysql_fdw_regress`.`test3` r3 ON (TRUE)) WHERE ((r3.`c1` = 13)) AND ((r2.`c1` = 12)) AND ((r1.`c1` = 11)) (4 rows) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; c1 | c1 | c1 ----+----+---- 11 | 12 | 13 (1 row) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` > 1)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE (((r4.`c1` < 10) OR (r4.`c1` IS NULL))) AND ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t2 t2) LEFT JOIN (mysql_fdw_regress.fdw139_t1 t1) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test2` r2 LEFT JOIN `mysql_fdw_regress`.`test1` r1 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: (mysql_fdw_regress.fdw139_t3 t3) LEFT JOIN ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM (`mysql_fdw_regress`.`test3` r4 LEFT JOIN (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r2.`c1`, r3.`c1`, r2.`c3` FROM (`mysql_fdw_regress`.`test1` r2 INNER JOIN `mysql_fdw_regress`.`test2` r3 ON (((r2.`c1` = r3.`c1`)))) (7 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.*, t2.*, t1.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c2`, r1.`c3`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (9 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (13 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c4 = t2.c4) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c4`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r6.`c1`, r9.`c2`, r9.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r9 ON (((r6.`c1` = r9.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Foreign Scan Output: fdw139_t1.c1, t2.c2, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r6.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r6.`c1` = r2.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c1, 13 Relations: (mysql_fdw_regress.fdw139_t1) INNER JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r4.`c1`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test2` r5 ON (TRUE)) WHERE ((r5.`c1` = 11)) AND ((r4.`c1` = 11)) (10 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3 Sort Key: t1.c1, t3.c3 -> Append -> Foreign Scan Output: t1_1.c1, t2_1.c2, t3_1.c3 Relations: ((mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p1 t3) Remote query: SELECT r6.`c1`, r8.`c2`, r10.`c3` FROM ((`mysql_fdw_regress`.`test1` r6 INNER JOIN `mysql_fdw_regress`.`test3` r8 ON (((r6.`c1` = r8.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test1` r10 ON (((r6.`c1` = r10.`c1`)))) WHERE (((r6.`c1` % 2) = 0)) -> Foreign Scan Output: t1_2.c1, t2_2.c2, t3_2.c3 Relations: ((mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p2 t3) Remote query: SELECT r7.`c1`, r9.`c2`, r11.`c3` FROM ((`mysql_fdw_regress`.`test2` r7 INNER JOIN `mysql_fdw_regress`.`test4` r9 ON (((r7.`c1` = r9.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test2` r11 ON (((r7.`c1` = r11.`c1`)))) WHERE (((r7.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; c1 | c2 | c3 ----+----+------- 2 | 2 | AAA2 4 | 4 | AAA12 6 | 6 | BBB2 8 | 8 | BBB12 (4 rows) -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Sort Key: t1.c3, t1.c1 -> Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Append -> Foreign Scan on public.ftprt1_p1 t1_1 Output: t1_1.*, t1_1.c1, t1_1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_2 Output: t1_2.*, t1_2.c1, t1_2.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: ((t2.*)::fprt2), t2.c2 -> Append -> Foreign Scan on public.ftprt2_p1 t2_1 Output: t2_1.*, t2_1.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_2 Output: t2_2.*, t2_2.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test4` (22 rows) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 -----------------+-------------+---- (1,1,AAA1,foo) | (1,1,CCC1) | 1 (3,3,AAA11,foo) | (3,3,CCC13) | 3 (4,4,AAA12,foo) | (4,4,CCC14) | 4 (2,2,AAA2,bar) | (2,2,CCC2) | 2 (5,5,BBB1,foo) | (5,5,CCC1) | 5 (7,7,BBB11,foo) | (7,7,CCC13) | 7 (8,8,BBB12,foo) | (8,8,CCC13) | 8 (6,6,BBB2,bar) | (6,6,CCC2) | 6 (8 rows) -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t1.c2 Sort Key: t1.c1, t1.c2 -> Append -> Foreign Scan Output: t1_1.c1, t1_1.c2 Relations: (mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2) Remote query: SELECT r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test3` r6 ON (((r4.`c1` = r6.`c2`)) AND ((r4.`c2` = r6.`c1`)))) WHERE (((r4.`c1` % 2) = 0)) -> Foreign Scan Output: t1_2.c1, t1_2.c2 Relations: (mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2) Remote query: SELECT r5.`c1`, r5.`c2` FROM (`mysql_fdw_regress`.`test2` r5 INNER JOIN `mysql_fdw_regress`.`test4` r7 ON (((r5.`c1` = r7.`c2`)) AND ((r5.`c2` = r7.`c1`)))) WHERE (((r5.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; c1 | c2 ----+---- 2 | 2 4 | 4 6 | 6 8 | 8 (4 rows) -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: fprt1.c1, 't1_phv'::text, fprt2.c2, ('t2_phv'::text) Sort Key: fprt1.c1, fprt2.c2 -> Append -> Nested Loop Left Join Output: fprt1_1.c1, 't1_phv'::text, fprt2_1.c2, ('t2_phv'::text) Join Filter: (fprt1_1.c1 = fprt2_1.c2) -> Foreign Scan on public.ftprt1_p1 fprt1_1 Output: fprt1_1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Materialize Output: fprt2_1.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p1 fprt2_1 Output: fprt2_1.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test3` WHERE (((`c2` % 2) = 0)) -> Nested Loop Left Join Output: fprt1_2.c1, 't1_phv'::text, fprt2_2.c2, ('t2_phv'::text) Join Filter: (fprt1_2.c1 = fprt2_2.c2) -> Foreign Scan on public.ftprt1_p2 fprt1_2 Output: fprt1_2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: fprt2_2.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p2 fprt2_2 Output: fprt2_2.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test4` WHERE (((`c2` % 2) = 0)) (26 rows) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; c1 | phv | c2 | phv ----+--------+----+-------- 2 | t1_phv | 2 | t2_phv 4 | t1_phv | 4 | t2_phv 6 | t1_phv | 6 | t2_phv 8 | t1_phv | 8 | t2_phv (4 rows) SET enable_partitionwise_join TO off; -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; DROP TABLE IF EXISTS fprt2; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown_1.out000066400000000000000000001644211414541277500217710ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) WHERE ((r1.`c2` = 100)) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r1.`c2` = 100)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) INNER JOIN `mysql_fdw_regress`.`test3` r4 ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c1`, r3.`c1` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) INNER JOIN `mysql_fdw_regress`.`test3` r3 ON (TRUE)) WHERE ((r3.`c1` = 13)) AND ((r2.`c1` = 12)) AND ((r1.`c1` = 11)) (4 rows) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; c1 | c1 | c1 ----+----+---- 11 | 12 | 13 (1 row) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` > 1)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE (((r4.`c1` < 10) OR (r4.`c1` IS NULL))) AND ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t2 t2) LEFT JOIN (mysql_fdw_regress.fdw139_t1 t1) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test2` r2 LEFT JOIN `mysql_fdw_regress`.`test1` r1 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: (mysql_fdw_regress.fdw139_t3 t3) LEFT JOIN ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM (`mysql_fdw_regress`.`test3` r4 LEFT JOIN (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r2.`c1`, r3.`c1`, r2.`c3` FROM (`mysql_fdw_regress`.`test1` r2 INNER JOIN `mysql_fdw_regress`.`test2` r3 ON (((r2.`c1` = r3.`c1`)))) (7 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.*, t2.*, t1.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c2`, r1.`c3`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (9 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (13 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c4 = t2.c4) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c4`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r6.`c1`, r9.`c2`, r9.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r9 ON (((r6.`c1` = r9.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Foreign Scan Output: fdw139_t1.c1, t2.c2, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r6.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r6.`c1` = r2.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c1, 13 Relations: (mysql_fdw_regress.fdw139_t1) INNER JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r4.`c1`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test2` r5 ON (TRUE)) WHERE ((r5.`c1` = 11)) AND ((r4.`c1` = 11)) (10 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3 Sort Key: t1.c1, t3.c3 -> Append -> Foreign Scan Output: t1.c1, t2.c2, t3.c3 Relations: ((mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p1 t3) Remote query: SELECT r6.`c1`, r8.`c2`, r10.`c3` FROM ((`mysql_fdw_regress`.`test1` r6 INNER JOIN `mysql_fdw_regress`.`test3` r8 ON (((r6.`c1` = r8.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test1` r10 ON (((r6.`c1` = r10.`c1`)))) WHERE (((r6.`c1` % 2) = 0)) -> Foreign Scan Output: t1_1.c1, t2_1.c2, t3_1.c3 Relations: ((mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p2 t3) Remote query: SELECT r7.`c1`, r9.`c2`, r11.`c3` FROM ((`mysql_fdw_regress`.`test2` r7 INNER JOIN `mysql_fdw_regress`.`test4` r9 ON (((r7.`c1` = r9.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test2` r11 ON (((r7.`c1` = r11.`c1`)))) WHERE (((r7.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; c1 | c2 | c3 ----+----+------- 2 | 2 | AAA2 4 | 4 | AAA12 6 | 6 | BBB2 8 | 8 | BBB12 (4 rows) -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Sort Key: t1.c3, t1.c1 -> Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.*, t1.c1, t1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.*, t1_1.c1, t1_1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: ((t2.*)::fprt2), t2.c2 -> Append -> Foreign Scan on public.ftprt2_p1 t2 Output: t2.*, t2.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_1 Output: t2_1.*, t2_1.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test4` (22 rows) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 -----------------+-------------+---- (1,1,AAA1,foo) | (1,1,CCC1) | 1 (3,3,AAA11,foo) | (3,3,CCC13) | 3 (4,4,AAA12,foo) | (4,4,CCC14) | 4 (2,2,AAA2,bar) | (2,2,CCC2) | 2 (5,5,BBB1,foo) | (5,5,CCC1) | 5 (7,7,BBB11,foo) | (7,7,CCC13) | 7 (8,8,BBB12,foo) | (8,8,CCC13) | 8 (6,6,BBB2,bar) | (6,6,CCC2) | 6 (8 rows) -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t1.c2 Sort Key: t1.c1, t1.c2 -> Append -> Foreign Scan Output: t1.c1, t1.c2 Relations: (mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2) Remote query: SELECT r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test3` r6 ON (((r4.`c1` = r6.`c2`)) AND ((r4.`c2` = r6.`c1`)))) WHERE (((r4.`c1` % 2) = 0)) -> Foreign Scan Output: t1_1.c1, t1_1.c2 Relations: (mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2) Remote query: SELECT r5.`c1`, r5.`c2` FROM (`mysql_fdw_regress`.`test2` r5 INNER JOIN `mysql_fdw_regress`.`test4` r7 ON (((r5.`c1` = r7.`c2`)) AND ((r5.`c2` = r7.`c1`)))) WHERE (((r5.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; c1 | c2 ----+---- 2 | 2 4 | 4 6 | 6 8 | 8 (4 rows) -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Sort Key: ftprt1_p1.c1, ftprt2_p1.c2 -> Append -> Nested Loop Left Join Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Join Filter: (ftprt1_p1.c1 = ftprt2_p1.c2) -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Materialize Output: ftprt2_p1.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p1 Output: ftprt2_p1.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test3` WHERE (((`c2` % 2) = 0)) -> Nested Loop Left Join Output: ftprt1_p2.c1, 't1_phv'::text, ftprt2_p2.c2, ('t2_phv'::text) Join Filter: (ftprt1_p2.c1 = ftprt2_p2.c2) -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: ftprt2_p2.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p2 Output: ftprt2_p2.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test4` WHERE (((`c2` % 2) = 0)) (26 rows) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; c1 | phv | c2 | phv ----+--------+----+-------- 2 | t1_phv | 2 | t2_phv 4 | t1_phv | 4 | t2_phv 6 | t1_phv | 6 | t2_phv 8 | t1_phv | 8 | t2_phv (4 rows) SET enable_partitionwise_join TO off; -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; DROP TABLE IF EXISTS fprt2; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown_2.out000066400000000000000000001645651414541277500220030ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) WHERE ((r1.`c2` = 100)) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r1.`c2` = 100)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) INNER JOIN `mysql_fdw_regress`.`test3` r4 ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c1`, r3.`c1` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) INNER JOIN `mysql_fdw_regress`.`test3` r3 ON (TRUE)) WHERE ((r3.`c1` = 13)) AND ((r2.`c1` = 12)) AND ((r1.`c1` = 11)) (4 rows) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; c1 | c1 | c1 ----+----+---- 11 | 12 | 13 (1 row) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` > 1)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE (((r4.`c1` < 10) OR (r4.`c1` IS NULL))) AND ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t2 t2) LEFT JOIN (mysql_fdw_regress.fdw139_t1 t1) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test2` r2 LEFT JOIN `mysql_fdw_regress`.`test1` r1 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: (mysql_fdw_regress.fdw139_t3 t3) LEFT JOIN ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) Remote query: SELECT r1.`c1`, r2.`c2`, r4.`c3`, r1.`c3` FROM (`mysql_fdw_regress`.`test3` r4 LEFT JOIN (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3 COLLATE "C", t.c1_1 CTE t -> Foreign Scan Output: t1.c1, t1.c3, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> CTE Scan on t Output: t.c1_1, t.c2_1, t.c1_3 (10 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.*, t2.*, t1.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c2`, r1.`c3`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (9 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (13 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c4 = t2.c4) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1`, r1.`c3`, r1.`c4`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r6.`c1`, r9.`c2`, r9.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r9 ON (((r6.`c1` = r9.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Foreign Scan Output: fdw139_t1.c1, t2.c2, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r6.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r6.`c1` = r2.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c1, 13 Relations: (mysql_fdw_regress.fdw139_t1) INNER JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r4.`c1`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test2` r5 ON (TRUE)) WHERE ((r5.`c1` = 11)) AND ((r4.`c1` = 11)) (10 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3 Sort Key: t1.c1, t3.c3 -> Append -> Foreign Scan Output: t1.c1, t2.c2, t3.c3 Relations: ((mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p1 t3) Remote query: SELECT r7.`c1`, r10.`c2`, r13.`c3` FROM ((`mysql_fdw_regress`.`test1` r7 INNER JOIN `mysql_fdw_regress`.`test3` r10 ON (((r7.`c1` = r10.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test1` r13 ON (((r7.`c1` = r13.`c1`)))) WHERE (((r7.`c1` % 2) = 0)) -> Foreign Scan Output: t1_1.c1, t2_1.c2, t3_1.c3 Relations: ((mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2)) INNER JOIN (mysql_fdw_regress.ftprt1_p2 t3) Remote query: SELECT r8.`c1`, r11.`c2`, r14.`c3` FROM ((`mysql_fdw_regress`.`test2` r8 INNER JOIN `mysql_fdw_regress`.`test4` r11 ON (((r8.`c1` = r11.`c2`)))) INNER JOIN `mysql_fdw_regress`.`test2` r14 ON (((r8.`c1` = r14.`c1`)))) WHERE (((r8.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; c1 | c2 | c3 ----+----+------- 2 | 2 | AAA2 4 | 4 | AAA12 6 | 6 | BBB2 8 | 8 | BBB12 (4 rows) -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Sort Key: t1.c3, t1.c1 -> Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.*, t1.c1, t1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.*, t1_1.c1, t1_1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: ((t2.*)::fprt2), t2.c2 -> Append -> Foreign Scan on public.ftprt2_p1 t2 Output: t2.*, t2.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_1 Output: t2_1.*, t2_1.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test4` (22 rows) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 -----------------+-------------+---- (1,1,AAA1,foo) | (1,1,CCC1) | 1 (3,3,AAA11,foo) | (3,3,CCC13) | 3 (4,4,AAA12,foo) | (4,4,CCC14) | 4 (2,2,AAA2,bar) | (2,2,CCC2) | 2 (5,5,BBB1,foo) | (5,5,CCC1) | 5 (7,7,BBB11,foo) | (7,7,CCC13) | 7 (8,8,BBB12,foo) | (8,8,CCC13) | 8 (6,6,BBB2,bar) | (6,6,CCC2) | 6 (8 rows) -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t1.c2 Sort Key: t1.c1, t1.c2 -> Append -> Foreign Scan Output: t1.c1, t1.c2 Relations: (mysql_fdw_regress.ftprt1_p1 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p1 t2) Remote query: SELECT r5.`c1`, r5.`c2` FROM (`mysql_fdw_regress`.`test1` r5 INNER JOIN `mysql_fdw_regress`.`test3` r8 ON (((r5.`c1` = r8.`c2`)) AND ((r5.`c2` = r8.`c1`)))) WHERE (((r5.`c1` % 2) = 0)) -> Foreign Scan Output: t1_1.c1, t1_1.c2 Relations: (mysql_fdw_regress.ftprt1_p2 t1) INNER JOIN (mysql_fdw_regress.ftprt2_p2 t2) Remote query: SELECT r6.`c1`, r6.`c2` FROM (`mysql_fdw_regress`.`test2` r6 INNER JOIN `mysql_fdw_regress`.`test4` r9 ON (((r6.`c1` = r9.`c2`)) AND ((r6.`c2` = r9.`c1`)))) WHERE (((r6.`c1` % 2) = 0)) (12 rows) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; c1 | c2 ----+---- 2 | 2 4 | 4 6 | 6 8 | 8 (4 rows) -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Sort Key: ftprt1_p1.c1, ftprt2_p1.c2 -> Append -> Nested Loop Left Join Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Join Filter: (ftprt1_p1.c1 = ftprt2_p1.c2) -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Materialize Output: ftprt2_p1.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p1 Output: ftprt2_p1.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test3` WHERE (((`c2` % 2) = 0)) -> Nested Loop Left Join Output: ftprt1_p2.c1, 't1_phv'::text, ftprt2_p2.c2, ('t2_phv'::text) Join Filter: (ftprt1_p2.c1 = ftprt2_p2.c2) -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: ftprt2_p2.c2, ('t2_phv'::text) -> Foreign Scan on public.ftprt2_p2 Output: ftprt2_p2.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test4` WHERE (((`c2` % 2) = 0)) (26 rows) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; c1 | phv | c2 | phv ----+--------+----+-------- 2 | t1_phv | 2 | t2_phv 4 | t1_phv | 4 | t2_phv 6 | t1_phv | 6 | t2_phv 8 | t1_phv | 8 | t2_phv (4 rows) SET enable_partitionwise_join TO off; -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; DROP TABLE IF EXISTS fprt2; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown_3.out000066400000000000000000001652131414541277500217730ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) WHERE ((r1.`c2` = 100)) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r1.`c2` = 100)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c2`, r4.`c3` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) INNER JOIN `mysql_fdw_regress`.`test3` r4 ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c1`, r3.`c1` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) INNER JOIN `mysql_fdw_regress`.`test3` r3 ON (TRUE)) WHERE ((r3.`c1` = 13)) AND ((r2.`c1` = 12)) AND ((r1.`c1` = 11)) (4 rows) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; c1 | c1 | c1 ----+----+---- 11 | 12 | 13 (1 row) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` > 1)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE (((r4.`c1` < 10) OR (r4.`c1` IS NULL))) AND ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t2 t2) LEFT JOIN (mysql_fdw_regress.fdw139_t1 t1) Remote query: SELECT r2.`c1`, r1.`c1` FROM (`mysql_fdw_regress`.`test2` r2 LEFT JOIN `mysql_fdw_regress`.`test1` r1 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: (mysql_fdw_regress.fdw139_t3 t3) LEFT JOIN ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) Remote query: SELECT r4.`c3`, r1.`c1`, r1.`c3`, r2.`c2` FROM (`mysql_fdw_regress`.`test3` r4 LEFT JOIN (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3 COLLATE "C", t.c1_1 CTE t -> Foreign Scan Output: t1.c1, t1.c3, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> CTE Scan on t Output: t.c1_1, t.c2_1, t.c1_3 (10 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.*, t2.*, t1.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c2`, r1.`c3`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (9 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (13 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c4 = t2.c4) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1`, r1.`c4`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r6.`c1`, r9.`c2`, r9.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r9 ON (((r6.`c1` = r9.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Foreign Scan Output: fdw139_t1.c1, t2.c2, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r6.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r6.`c1` = r2.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c1, 13 Relations: (mysql_fdw_regress.fdw139_t1) INNER JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r4.`c1`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test2` r5 ON (TRUE)) WHERE ((r5.`c1` = 11)) AND ((r4.`c1` = 11)) (10 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3 Sort Key: t1.c1, t3.c3 -> Nested Loop Output: t1.c1, t2.c2, t3.c3 Join Filter: (t1.c1 = t3.c1) -> Nested Loop Output: t1.c1, t2.c2 Join Filter: (t1.c1 = t2.c2) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: t2.c2 -> Append -> Foreign Scan on public.ftprt2_p1 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_1 Output: t2_1.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test4` -> Materialize Output: t3.c3, t3.c1 -> Append -> Foreign Scan on public.ftprt1_p1 t3 Output: t3.c3, t3.c1 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t3_1 Output: t3_1.c3, t3_1.c1 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test2` (34 rows) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; c1 | c2 | c3 ----+----+------- 2 | 2 | AAA2 4 | 4 | AAA12 6 | 6 | BBB2 8 | 8 | BBB12 (4 rows) -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Sort Key: t1.c3, t1.c1 -> Nested Loop Output: ((t1.*)::fprt1), ((t2.*)::fprt2), t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c2) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.*, t1.c1, t1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.*, t1_1.c1, t1_1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: ((t2.*)::fprt2), t2.c2 -> Append -> Foreign Scan on public.ftprt2_p1 t2 Output: t2.*, t2.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_1 Output: t2_1.*, t2_1.c2 Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test4` (22 rows) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 -----------------+-------------+---- (1,1,AAA1,foo) | (1,1,CCC1) | 1 (3,3,AAA11,foo) | (3,3,CCC13) | 3 (4,4,AAA12,foo) | (4,4,CCC14) | 4 (2,2,AAA2,bar) | (2,2,CCC2) | 2 (5,5,BBB1,foo) | (5,5,CCC1) | 5 (7,7,BBB11,foo) | (7,7,CCC13) | 7 (8,8,BBB12,foo) | (8,8,CCC13) | 8 (6,6,BBB2,bar) | (6,6,CCC2) | 6 (8 rows) -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t1.c2 Sort Key: t1.c1, t1.c2 -> Nested Loop Output: t1.c1, t1.c2 Join Filter: ((t1.c1 = t2.c2) AND (t1.c2 = t2.c1)) -> Append -> Foreign Scan on public.ftprt1_p1 t1 Output: t1.c1, t1.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Foreign Scan on public.ftprt1_p2 t1_1 Output: t1_1.c1, t1_1.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: t2.c2, t2.c1 -> Append -> Foreign Scan on public.ftprt2_p1 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test3` -> Foreign Scan on public.ftprt2_p2 t2_1 Output: t2_1.c2, t2_1.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test4` (22 rows) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; c1 | c2 ----+---- 2 | 2 4 | 4 6 | 6 8 | 8 (4 rows) -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; QUERY PLAN --------------------------------------------------------------------------------------------------------------- Sort Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Sort Key: ftprt1_p1.c1, ftprt2_p1.c2 -> Nested Loop Left Join Output: ftprt1_p1.c1, 't1_phv'::text, ftprt2_p1.c2, ('t2_phv'::text) Join Filter: (ftprt1_p1.c1 = ftprt2_p1.c2) -> Append -> Foreign Scan on public.ftprt1_p1 Output: ftprt1_p1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE (((`c1` % 2) = 0)) -> Foreign Scan on public.ftprt1_p2 Output: ftprt1_p2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE (((`c1` % 2) = 0)) -> Materialize Output: ftprt2_p1.c2, ('t2_phv'::text) -> Append -> Foreign Scan on public.ftprt2_p1 Output: ftprt2_p1.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test3` WHERE (((`c2` % 2) = 0)) -> Foreign Scan on public.ftprt2_p2 Output: ftprt2_p2.c2, 't2_phv'::text Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test4` WHERE (((`c2` % 2) = 0)) (22 rows) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; c1 | phv | c2 | phv ----+--------+----+-------- 2 | t1_phv | 2 | t2_phv 4 | t1_phv | 4 | t2_phv 6 | t1_phv | 6 | t2_phv 8 | t1_phv | 8 | t2_phv (4 rows) SET enable_partitionwise_join TO off; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; DROP TABLE IF EXISTS fprt2; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown_4.out000066400000000000000000001532761414541277500220020ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) WHERE ((r1.`c2` = 100)) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r1.`c2` = 100)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c2`, r4.`c3` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) INNER JOIN `mysql_fdw_regress`.`test3` r4 ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t2.c1, t3.c1 Relations: ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) INNER JOIN (mysql_fdw_regress.fdw139_t3 t3) Remote query: SELECT r1.`c1`, r2.`c1`, r3.`c1` FROM ((`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) INNER JOIN `mysql_fdw_regress`.`test3` r3 ON (TRUE)) WHERE ((r3.`c1` = 13)) AND ((r2.`c1` = 12)) AND ((r1.`c1` = 11)) (4 rows) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; c1 | c1 | c1 ----+----+---- 11 | 12 | 13 (1 row) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Filter: (abs(t1.c1) = t2.c1) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) WHERE ((r2.`c1` > 1)) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Foreign Scan Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Relations: (mysql_fdw_regress.fdw139_t1 t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r1.`c1`, r1.`c2`, r4.`c1`, r4.`c2` FROM (`mysql_fdw_regress`.`test1` r1 LEFT JOIN `mysql_fdw_regress`.`test2` r4 ON (((r1.`c1` = r4.`c1`)) AND ((r4.`c1` < 10)))) WHERE (((r4.`c1` < 10) OR (r4.`c1` IS NULL))) AND ((r1.`c1` < 10)) (4 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t2 t2) LEFT JOIN (mysql_fdw_regress.fdw139_t1 t1) Remote query: SELECT r2.`c1`, r1.`c1` FROM (`mysql_fdw_regress`.`test2` r2 LEFT JOIN `mysql_fdw_regress`.`test1` r1 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Foreign Scan Output: t1.c1, t2.c2, t3.c3, t1.c3 Relations: (mysql_fdw_regress.fdw139_t3 t3) LEFT JOIN ((mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2)) Remote query: SELECT r4.`c3`, r1.`c1`, r1.`c3`, r2.`c2` FROM (`mysql_fdw_regress`.`test3` r4 LEFT JOIN (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) ON (((r1.`c1` = r4.`c1`)))) (7 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r1.`c2`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> Nested Loop Output: t1.c1, t1.c3, t1.*, t2.c1, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (20 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3 COLLATE "C", t.c1_1 CTE t -> Foreign Scan Output: t1.c1, t1.c3, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) -> CTE Scan on t Output: t.c1_1, t.c2_1, t.c1_3 (10 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.*, t2.*, t1.c1, t1.c3 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c2`, r1.`c3`, r1.`c4`, r2.`c1`, r2.`c2`, r2.`c3`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (7 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (9 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 -> Foreign Scan Output: t1.c1, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (TRUE)) (13 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Foreign Scan Output: t1.c1, t2.c1, t1.c3 Filter: (t1.c4 = t2.c4) Relations: (mysql_fdw_regress.fdw139_t1 t1) INNER JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r1.`c1`, r1.`c3`, r2.`c1`, r1.`c4`, r2.`c4` FROM (`mysql_fdw_regress`.`test1` r1 INNER JOIN `mysql_fdw_regress`.`test2` r2 ON (((r1.`c1` = r2.`c1`)))) (8 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r6.`c1`, r9.`c2`, r9.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r9 ON (((r6.`c1` = r9.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Foreign Scan Output: fdw139_t1.c1, t2.c2, t2.c1 Relations: (mysql_fdw_regress.fdw139_t1) LEFT JOIN (mysql_fdw_regress.fdw139_t2 t2) Remote query: SELECT r6.`c1`, r2.`c2`, r2.`c1` FROM (`mysql_fdw_regress`.`test1` r6 LEFT JOIN `mysql_fdw_regress`.`test2` r2 ON (((r6.`c1` = r2.`c1`)))) (9 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Foreign Scan Output: fdw139_t1.c1, fdw139_t2.c1, 13 Relations: (mysql_fdw_regress.fdw139_t1) INNER JOIN (mysql_fdw_regress.fdw139_t2) Remote query: SELECT r4.`c1`, r5.`c1` FROM (`mysql_fdw_regress`.`test1` r4 INNER JOIN `mysql_fdw_regress`.`test2` r5 ON (TRUE)) WHERE ((r5.`c1` = 11)) AND ((r4.`c1` = 11)) (10 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; NOTICE: syntax error CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES... ^ CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES... ^ DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; NOTICE: syntax error CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES... ^ CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES... ^ -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER... ^ SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER... ^ -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ^ SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ^ -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 ^ SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 ^ -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; ERROR: relation "fprt1" does not exist LINE 3: FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) ... ^ SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; ERROR: relation "fprt1" does not exist LINE 2: FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) ... ^ SET enable_partitionwise_join TO off; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; NOTICE: table "fprt1" does not exist, skipping DROP TABLE IF EXISTS fprt2; NOTICE: table "fprt2" does not exist, skipping DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/join_pushdown_5.out000066400000000000000000001456661414541277500220070ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; QUERY PLAN ----------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` WHERE ((`c2` = 100)) -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c2, t3.c3, t1.c3 Join Filter: (t1.c1 = t3.c1) -> Nested Loop Output: t1.c1, t1.c3, t2.c2, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t3.c3, t3.c1 -> Foreign Scan on public.fdw139_t3 t3 Output: t3.c3, t3.c1 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test3` (22 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------ 1 | 200 | CCC1 2 | 200 | CCC2 (2 rows) -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; QUERY PLAN --------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` > 1)) (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 2 | 2 (1 row) -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (abs(t1.c1) = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | (3 rows) -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; QUERY PLAN ---------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Join Filter: (t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 10)) -> Materialize Output: fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 10)) (11 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; QUERY PLAN ---------------------------------------------------------------------------------------------------- Nested Loop Left Join Output: t1.c1, t1.c2, fdw139_t2.c1, fdw139_t2.c2 Join Filter: (t1.c1 = fdw139_t2.c1) Filter: ((fdw139_t2.c1 < 10) OR (fdw139_t2.c1 IS NULL)) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` < 10)) -> Materialize Output: fdw139_t2.c1, fdw139_t2.c2 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` < 10)) (12 rows) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; c1 | c2 | c1 | c2 ----+-----+----+----- 1 | 100 | 1 | 200 2 | 100 | 2 | 200 (2 rows) -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t2.c1, t1.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 2 | 2 | 12 (3 rows) -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c2, t3.c3, t1.c3 Sort Key: t1.c1, t1.c3 COLLATE "C" -> Nested Loop Left Join Output: t1.c1, t2.c2, t3.c3, t1.c3 Join Filter: (t1.c1 = t3.c1) -> Foreign Scan on public.fdw139_t3 t3 Output: t3.c1, t3.c2, t3.c3 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test3` -> Materialize Output: t1.c1, t1.c3, t2.c2 -> Nested Loop Output: t1.c1, t1.c3, t2.c2 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (22 rows) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; c1 | c2 | c3 ----+-----+------- 1 | 200 | CCC1 2 | 200 | CCC2 | | CCC13 (3 rows) -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Hash Full Join Output: t1.c1, t2.c1 Hash Cond: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Hash Output: t2.c1 -> Foreign Scan on public.fdw139_t1 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 2 | 2 11 | 11 (3 rows) -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; QUERY PLAN -------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; QUERY PLAN -------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; QUERY PLAN -------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; QUERY PLAN -------------------------------------------------------------------------------------------------------- LockRows Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* -> Sort Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3, t1.*, t2.* Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c3, t1.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.* -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.* Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; QUERY PLAN ---------------------------------------------------------------------------------- Sort Output: t.c1_1, t.c2_1, t.c1_3 Sort Key: t.c1_3 COLLATE "C", t.c1_1 CTE t -> Nested Loop Output: t1.c1, t1.c3, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> CTE Scan on t Output: t.c1_1, t.c2_1, t.c1_3 (17 rows) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; c1_1 | c2_1 ------+------ 1 | 1 2 | 2 (2 rows) -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------------------- Sort Output: t1.*, t2.*, t1.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.*, t2.*, t1.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.*, t1.c1, t1.c3 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.*, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.*, t2.c1 Remote query: SELECT `c1`, `c2`, `c3`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; t1 | t2 | c1 ------------------+------------------+---- (1,100,AAA1,foo) | (1,200,BBB1,foo) | 1 (2,100,AAA2,bar) | (2,200,BBB2,bar) | 2 (2 rows) -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Output: t1.c1 Join Filter: (t1.c1 = t2.c1) -> HashAggregate Output: t2.c1 Group Key: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c2, t2.c3, t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: t1.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` (19 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 (2 rows) -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1 -> Sort Output: t1.c1 Sort Key: t1.c1 -> Nested Loop Anti Join Output: t1.c1 Join Filter: (t1.c1 = t2.c2) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2 Remote query: SELECT `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; c1 ---- 1 2 11 (3 rows) -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1 -> Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Output: t1.c1, t2.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; c1 | c1 ----+---- 1 | 1 1 | 2 1 | 12 2 | 1 2 | 2 2 | 12 11 | 1 11 | 2 11 | 12 (9 rows) -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: t1.c1, t2.c1, l1.c1 -> Sort Output: t1.c1, t2.c1, l1.c1 Sort Key: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1, l1.c1 -> Nested Loop Output: t1.c1, t2.c1 -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` -> Materialize Output: l1.c1 -> Seq Scan on public.local_t1 l1 Output: l1.c1 (21 rows) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; c1 | c1 | c1 ----+----+---- 1 | 1 | 1 1 | 1 | 2 1 | 2 | 1 1 | 2 | 2 1 | 12 | 1 1 | 12 | 2 2 | 1 | 1 2 | 1 | 2 2 | 2 | 1 2 | 2 | 2 (10 rows) SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; count ------- 18 (1 row) -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t4 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c4 = t2.c4) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; c1 | c1 ----+---- 1 | 1 1 | 12 2 | 2 11 | 1 11 | 12 (5 rows) -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; QUERY PLAN -------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1 Sort Key: t1.c1, t2.c1 -> Nested Loop Left Join Output: t1.c1, t2.c1 Join Filter: (t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Filter: (t1.c4 = 'foo'::user_enum) Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` (15 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; c1 | c1 ----+---- 1 | 1 11 | (2 rows) -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; QUERY PLAN -------------------------------------------------------------------------------------- Sort Output: t1.c1, t2.c1, t1.c3 Sort Key: t1.c3 COLLATE "C", t1.c1 -> Nested Loop Output: t1.c1, t2.c1, t1.c3 Join Filter: ((t1.c1 = t2.c1) AND (t1.c4 = t2.c4)) -> Foreign Scan on public.fdw139_t1 t1 Output: t1.c1, t1.c2, t1.c3, t1.c4 Remote query: SELECT `c1`, `c3`, `c4` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c1, t2.c4 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c1, t2.c4 Remote query: SELECT `c1`, `c4` FROM `mysql_fdw_regress`.`test2` (14 rows) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; c1 | c1 ----+---- 1 | 1 2 | 2 (2 rows) -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 -> Sort Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Sort Key: fdw139_t1.c1, fdw139_t2.c1, fdw139_t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, fdw139_t2.c2, fdw139_t2.c1 Join Filter: (fdw139_t1.c1 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: fdw139_t2.c2, fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c2, fdw139_t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down QUERY PLAN -------------------------------------------------------------------------------------------- Limit Output: fdw139_t1.c1, t2.c2, t2.c1 -> Sort Output: fdw139_t1.c1, t2.c2, t2.c1 Sort Key: fdw139_t1.c1, t2.c1, t2.c2 -> Nested Loop Left Join Output: fdw139_t1.c1, t2.c2, t2.c1 Join Filter: (fdw139_t1.c1 = t2.c1) -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` -> Materialize Output: t2.c2, t2.c1 -> Foreign Scan on public.fdw139_t2 t2 Output: t2.c2, t2.c1 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test2` (16 rows) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; c1 | c2 ----+----- 1 | 200 2 | 200 11 | (3 rows) ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: (13), fdw139_t2.c1 Join Filter: (13 = fdw139_t2.c1) -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1, fdw139_t2.c2, fdw139_t2.c3, fdw139_t2.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: (13) -> Foreign Scan on public.fdw139_t1 Output: 13 Remote query: SELECT NULL FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 13)) (11 rows) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; a | c1 ---+---- | 12 (1 row) -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; QUERY PLAN ------------------------------------------------------------------------------------------------------------ Nested Loop Left Join Output: fdw139_t3.c1, (13), fdw139_t1.c1, fdw139_t2.c1 Join Filter: (fdw139_t3.c1 = fdw139_t1.c1) -> Foreign Scan on public.fdw139_t3 Output: fdw139_t3.c1, fdw139_t3.c2, fdw139_t3.c3 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test3` WHERE ((`c1` >= 10)) AND ((`c1` <= 15)) -> Materialize Output: fdw139_t1.c1, fdw139_t2.c1, (13) -> Nested Loop Output: fdw139_t1.c1, fdw139_t2.c1, 13 -> Foreign Scan on public.fdw139_t1 Output: fdw139_t1.c1, fdw139_t1.c2, fdw139_t1.c3, fdw139_t1.c4 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test1` WHERE ((`c1` = 11)) -> Materialize Output: fdw139_t2.c1 -> Foreign Scan on public.fdw139_t2 Output: fdw139_t2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test2` WHERE ((`c1` = 11)) (18 rows) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; c1 | a | b | c ----+---+---+--- 13 | | | (1 row) -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; NOTICE: syntax error CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES... ^ CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES... ^ DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; NOTICE: syntax error CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES... ^ CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); ERROR: syntax error at or near "PARTITION" LINE 1: CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES... ^ -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER... ^ SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER... ^ -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ^ SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ^ -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; ERROR: relation "fprt1" does not exist LINE 3: FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 ^ SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; ERROR: relation "fprt1" does not exist LINE 2: FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 ^ -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; ERROR: relation "fprt1" does not exist LINE 3: FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) ... ^ SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; ERROR: relation "fprt1" does not exist LINE 2: FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) ... ^ SET enable_partitionwise_join TO off; ERROR: unrecognized configuration parameter "enable_partitionwise_join" -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; NOTICE: table "fprt1" does not exist, skipping DROP TABLE IF EXISTS fprt2; NOTICE: table "fprt2" does not exist, skipping DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/pushdown.out000066400000000000000000000302631414541277500205260ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign tables CREATE FOREIGN TABLE f_test_tbl1 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9), c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); CREATE FOREIGN TABLE f_test_tbl2 (c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); -- Insert data in mysql db using foreign tables INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); SET datestyle TO ISO; -- WHERE clause pushdown EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e WHERE c6 IN (800,2450) ORDER BY c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c6` IN ('800', '2450')) (6 rows) SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e WHERE c6 IN (800,2450) ORDER BY c1; c1 | c2 | salary | c8 ----+----+--------+---- (0 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl1 e WHERE c6 > 3000 ORDER BY c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c3, c4, c5, c6, c7, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c3, c4, c5, c6, c7, c8 Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` > 3000)) (6 rows) SELECT * FROM f_test_tbl1 e WHERE c6 > 3000 ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+------+----+------------+------------+----+---- 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 (1 row) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 = 1500 ORDER BY c1; QUERY PLAN ---------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` = 1500)) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 = 1500 ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 1000 | EMP10 | 1500.00000 | 30 (1 row) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 BETWEEN 1000 AND 4000 ORDER BY c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c6` >= 1000)) AND ((`c6` <= 4000)) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 BETWEEN 1000 AND 4000 ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 200 | EMP2 | 1600.00000 | 30 300 | EMP3 | 1250.00000 | 30 400 | EMP4 | 2975.12000 | 20 500 | EMP5 | 1250.00000 | 30 600 | EMP6 | 2850.00000 | 30 700 | EMP7 | 2450.45000 | 10 800 | EMP8 | 3000.00000 | 20 1000 | EMP10 | 1500.00000 | 30 1100 | EMP11 | 1100.00000 | 20 1300 | EMP13 | 3000.00000 | 20 1400 | EMP14 | 1300.00000 | 10 (11 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IS NOT NULL ORDER BY c1; QUERY PLAN --------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c2` IS NOT NULL)) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IS NOT NULL ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 100 | EMP1 | 800.23000 | 20 200 | EMP2 | 1600.00000 | 30 300 | EMP3 | 1250.00000 | 30 400 | EMP4 | 2975.12000 | 20 500 | EMP5 | 1250.00000 | 30 600 | EMP6 | 2850.00000 | 30 700 | EMP7 | 2450.45000 | 10 800 | EMP8 | 3000.00000 | 20 900 | EMP9 | 5000.00000 | 10 1000 | EMP10 | 1500.00000 | 30 1100 | EMP11 | 1100.00000 | 20 1200 | EMP12 | 950.00000 | 30 1300 | EMP13 | 3000.00000 | 20 1400 | EMP14 | 1300.00000 | 10 (14 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl1 e WHERE c5 <= '1980-12-17' ORDER BY c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c3, c4, c5, c6, c7, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c3, c4, c5, c6, c7, c8 Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c5` <= '1980-12-17')) (6 rows) SELECT * FROM f_test_tbl1 e WHERE c5 <= '1980-12-17' ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+------+------------+------------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 (2 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c2` IN ('EMP6', 'EMP12', 'EMP5')) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 500 | EMP5 | 1250.00000 | 30 600 | EMP6 | 2850.00000 | 30 1200 | EMP12 | 950.00000 | 30 (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE (`c2` IN ('EMP6', 'EMP12', 'EMP5')) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 500 | EMP5 | 1250.00000 | 30 600 | EMP6 | 2850.00000 | 30 1200 | EMP12 | 950.00000 | 30 (3 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'SALESMAN' ORDER BY c1; QUERY PLAN -------------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c3` LIKE BINARY 'SALESMAN')) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'SALESMAN' ORDER BY c1; c1 | c2 | c6 | c8 ------+-------+------------+---- 200 | EMP2 | 1600.00000 | 30 300 | EMP3 | 1250.00000 | 30 500 | EMP5 | 1250.00000 | 30 1000 | EMP10 | 1500.00000 | 30 (4 rows) EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'MANA%' ORDER BY c1; QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Sort Output: c1, c2, c6, c8 Sort Key: e.c1 -> Foreign Scan on public.f_test_tbl1 e Output: c1, c2, c6, c8 Remote query: SELECT `c1`, `c2`, `c6`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c3` LIKE BINARY 'MANA%')) (6 rows) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'MANA%' ORDER BY c1; c1 | c2 | c6 | c8 -----+------+------------+---- 400 | EMP4 | 2975.12000 | 20 600 | EMP6 | 2850.00000 | 30 700 | EMP7 | 2450.45000 | 10 (3 rows) -- Cleanup DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/select.out000066400000000000000000001515671414541277500201510ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Check version SELECT mysql_fdw_version(); mysql_fdw_version ------------------- 20700 (1 row) -- Create foreign tables CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); CREATE FOREIGN TABLE f_numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'numbers'); CREATE FOREIGN TABLE f_test_tbl1 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9),c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); CREATE FOREIGN TABLE f_test_tbl2 (c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE TYPE size_t AS enum('small','medium','large'); CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); CREATE FOREIGN TABLE test5_1(c1 INT, c2 CHAR, c3 VARCHAR, c4 BOOLEAN, c5 TEXT, c6 INTERVAL, c7 BYTEA, c8 pg_catalog.DATE, c9 NUMERIC, c10 NAME) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test5'); CREATE FOREIGN TABLE test5_2(c1 INT, c2 BYTEA, c3 BYTEA, c4 BYTEA, c5 BYTEA, c6 BYTEA, c7 BYTEA, c8 BYTEA, c9 BYTEA, c10 BYTEA) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test5'); -- Insert data in MySQL db using foreign tables INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); SET datestyle TO ISO; -- Retrieve Data from Foreign Table using SELECT Statement. SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 ORDER BY c1 DESC, c8; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+------+------------+------------+------+---- 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 1200 | EMP12 | ADMIN | 600 | 1981-12-03 | 950.00000 | | 30 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 (14 rows) SELECT DISTINCT c8 FROM f_test_tbl1 ORDER BY 1; c8 ---- 10 20 30 (3 rows) SELECT c2 AS "Employee Name" FROM f_test_tbl1 ORDER BY 1; Employee Name --------------- EMP1 EMP10 EMP11 EMP12 EMP13 EMP14 EMP2 EMP3 EMP4 EMP5 EMP6 EMP7 EMP8 EMP9 (14 rows) SELECT c8, c6, c7 FROM f_test_tbl1 ORDER BY 1, 2, 3; c8 | c6 | c7 ----+------------+------ 10 | 1300.00000 | 10 | 2450.45000 | 10 | 5000.00000 | 20 | 800.23000 | 20 | 1100.00000 | 20 | 2975.12000 | 20 | 3000.00000 | 20 | 3000.00000 | 30 | 950.00000 | 30 | 1250.00000 | 500 30 | 1250.00000 | 1400 30 | 1500.00000 | 0 30 | 1600.00000 | 300 30 | 2850.00000 | (14 rows) SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c1 = 100 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+-------+------+------------+-----------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 (1 row) SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c1 = 100 OR c1 = 700 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+---------+------+------------+------------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 (2 rows) SELECT * FROM f_test_tbl1 WHERE c3 like 'SALESMAN' ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+-----+------------+------------+------+---- 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 (4 rows) SELECT * FROM f_test_tbl1 WHERE c1 IN (100, 700) ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+---------+------+------------+------------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 (2 rows) SELECT * FROM f_test_tbl1 WHERE c1 NOT IN (100, 700) ORDER BY 1 LIMIT 5; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+----------+-----+------------+------------+------+---- 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 (5 rows) SELECT * FROM f_test_tbl1 WHERE c8 BETWEEN 10 AND 20 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+---------+------+------------+------------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 (8 rows) SELECT * FROM f_test_tbl1 ORDER BY 1 OFFSET 5; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+-----+------------+------------+----+---- 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 1200 | EMP12 | ADMIN | 600 | 1981-12-03 | 950.00000 | | 30 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 (9 rows) -- Retrieve Data from Foreign Table using Group By Clause. SELECT c8 "Department", COUNT(c1) "Total Employees" FROM f_test_tbl1 GROUP BY c8 ORDER BY c8; Department | Total Employees ------------+----------------- 10 | 3 20 | 5 30 | 6 (3 rows) SELECT c8, SUM(c6) FROM f_test_tbl1 GROUP BY c8 HAVING c8 IN (10, 30) ORDER BY c8; c8 | sum ----+------------ 10 | 8750.45000 30 | 9400.00000 (2 rows) SELECT c8, SUM(c6) FROM f_test_tbl1 GROUP BY c8 HAVING SUM(c6) > 9400 ORDER BY c8; c8 | sum ----+------------- 20 | 10875.35000 (1 row) -- Row Level Functions SELECT UPPER(c2), LOWER(c2) FROM f_test_tbl2 ORDER BY 1, 2; upper | lower ----------------+---------------- ADMINISTRATION | administration DEVELOPMENT | development HR | hr SALES | sales (4 rows) -- Retrieve Data from Foreign Table using Sub Queries. SELECT * FROM f_test_tbl1 WHERE c8 <> ALL (SELECT c1 FROM f_test_tbl2 WHERE c1 IN (10, 30, 40)) ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+---------+------+------------+------------+----+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 (5 rows) SELECT c1, c2, c3 FROM f_test_tbl2 WHERE EXISTS (SELECT 1 FROM f_test_tbl1 WHERE f_test_tbl2.c1 = f_test_tbl1.c8) ORDER BY 1, 2; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI (3 rows) SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c8 NOT IN (SELECT c1 FROM f_test_tbl2) ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ----+----+----+----+----+----+----+---- (0 rows) -- Retrieve Data from Foreign Table using UNION Operator. SELECT c1, c2 FROM f_test_tbl2 UNION SELECT c1, c2 FROM f_test_tbl1 ORDER BY c1; c1 | c2 ------+---------------- 10 | DEVELOPMENT 20 | ADMINISTRATION 30 | SALES 40 | HR 100 | EMP1 200 | EMP2 300 | EMP3 400 | EMP4 500 | EMP5 600 | EMP6 700 | EMP7 800 | EMP8 900 | EMP9 1000 | EMP10 1100 | EMP11 1200 | EMP12 1300 | EMP13 1400 | EMP14 (18 rows) SELECT c2 FROM f_test_tbl2 UNION ALL SELECT c2 FROM f_test_tbl1 ORDER BY c2; c2 ---------------- ADMINISTRATION DEVELOPMENT EMP1 EMP10 EMP11 EMP12 EMP13 EMP14 EMP2 EMP3 EMP4 EMP5 EMP6 EMP7 EMP8 EMP9 HR SALES (18 rows) -- Retrieve Data from Foreign Table using INTERSECT Operator. SELECT c2 FROM f_test_tbl1 WHERE c1 >= 800 INTERSECT SELECT c2 FROM f_test_tbl1 WHERE c1 >= 400 ORDER BY c2; c2 ------- EMP10 EMP11 EMP12 EMP13 EMP14 EMP8 EMP9 (7 rows) SELECT c2 FROM f_test_tbl1 WHERE c1 >= 800 INTERSECT ALL SELECT c2 FROM f_test_tbl1 WHERE c1 >= 400 ORDER BY c2; c2 ------- EMP10 EMP11 EMP12 EMP13 EMP14 EMP8 EMP9 (7 rows) -- Retrieve Data from Foreign Table using EXCEPT. SELECT c2 FROM f_test_tbl1 EXCEPT SELECT c2 FROM f_test_tbl1 WHERE c1 > 900 ORDER BY c2; c2 ------ EMP1 EMP2 EMP3 EMP4 EMP5 EMP6 EMP7 EMP8 EMP9 (9 rows) SELECT c2 FROM f_test_tbl1 EXCEPT ALL SELECT c2 FROM f_test_tbl1 WHERE c1 > 900 ORDER BY c2; c2 ------ EMP1 EMP2 EMP3 EMP4 EMP5 EMP6 EMP7 EMP8 EMP9 (9 rows) -- Retrieve Data from Foreign Table using CTE (With Clause). WITH with_qry AS (SELECT c1, c2, c3 FROM f_test_tbl2) SELECT e.c2, e.c6, w.c1, w.c2 FROM f_test_tbl1 e, with_qry w WHERE e.c8 = w.c1 ORDER BY e.c8, e.c2; c2 | c6 | c1 | c2 -------+------------+----+---------------- EMP14 | 1300.00000 | 10 | DEVELOPMENT EMP7 | 2450.45000 | 10 | DEVELOPMENT EMP9 | 5000.00000 | 10 | DEVELOPMENT EMP1 | 800.23000 | 20 | ADMINISTRATION EMP11 | 1100.00000 | 20 | ADMINISTRATION EMP13 | 3000.00000 | 20 | ADMINISTRATION EMP4 | 2975.12000 | 20 | ADMINISTRATION EMP8 | 3000.00000 | 20 | ADMINISTRATION EMP10 | 1500.00000 | 30 | SALES EMP12 | 950.00000 | 30 | SALES EMP2 | 1600.00000 | 30 | SALES EMP3 | 1250.00000 | 30 | SALES EMP5 | 1250.00000 | 30 | SALES EMP6 | 2850.00000 | 30 | SALES (14 rows) WITH test_tbl2_costs AS (SELECT d.c2, SUM(c6) test_tbl2_total FROM f_test_tbl1 e, f_test_tbl2 d WHERE e.c8 = d.c1 GROUP BY 1), avg_cost AS (SELECT SUM(test_tbl2_total)/COUNT(*) avg FROM test_tbl2_costs) SELECT * FROM test_tbl2_costs WHERE test_tbl2_total > (SELECT avg FROM avg_cost) ORDER BY c2; c2 | test_tbl2_total ----------------+----------------- ADMINISTRATION | 10875.35000 (1 row) -- Retrieve Data from Foreign Table using Window Clause. SELECT c8, c1, c6, AVG(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 ORDER BY c8, c1; c8 | c1 | c6 | avg ----+------+------------+----------------------- 10 | 700 | 2450.45000 | 2916.8166666666666667 10 | 900 | 5000.00000 | 2916.8166666666666667 10 | 1400 | 1300.00000 | 2916.8166666666666667 20 | 100 | 800.23000 | 2175.0700000000000000 20 | 400 | 2975.12000 | 2175.0700000000000000 20 | 800 | 3000.00000 | 2175.0700000000000000 20 | 1100 | 1100.00000 | 2175.0700000000000000 20 | 1300 | 3000.00000 | 2175.0700000000000000 30 | 200 | 1600.00000 | 1566.6666666666666667 30 | 300 | 1250.00000 | 1566.6666666666666667 30 | 500 | 1250.00000 | 1566.6666666666666667 30 | 600 | 2850.00000 | 1566.6666666666666667 30 | 1000 | 1500.00000 | 1566.6666666666666667 30 | 1200 | 950.00000 | 1566.6666666666666667 (14 rows) SELECT c8, c1, c6, COUNT(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 WHERE c8 IN (10, 30, 40, 50, 60, 70) ORDER BY c8, c1; c8 | c1 | c6 | count ----+------+------------+------- 10 | 700 | 2450.45000 | 3 10 | 900 | 5000.00000 | 3 10 | 1400 | 1300.00000 | 3 30 | 200 | 1600.00000 | 6 30 | 300 | 1250.00000 | 6 30 | 500 | 1250.00000 | 6 30 | 600 | 2850.00000 | 6 30 | 1000 | 1500.00000 | 6 30 | 1200 | 950.00000 | 6 (9 rows) SELECT c8, c1, c6, SUM(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 ORDER BY c8, c1; c8 | c1 | c6 | sum ----+------+------------+------------- 10 | 700 | 2450.45000 | 8750.45000 10 | 900 | 5000.00000 | 8750.45000 10 | 1400 | 1300.00000 | 8750.45000 20 | 100 | 800.23000 | 10875.35000 20 | 400 | 2975.12000 | 10875.35000 20 | 800 | 3000.00000 | 10875.35000 20 | 1100 | 1100.00000 | 10875.35000 20 | 1300 | 3000.00000 | 10875.35000 30 | 200 | 1600.00000 | 9400.00000 30 | 300 | 1250.00000 | 9400.00000 30 | 500 | 1250.00000 | 9400.00000 30 | 600 | 2850.00000 | 9400.00000 30 | 1000 | 1500.00000 | 9400.00000 30 | 1200 | 950.00000 | 9400.00000 (14 rows) -- Views CREATE VIEW smpl_vw AS SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 ORDER BY c1; SELECT * FROM smpl_vw ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+------+------------+------------+------+---- 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 1200 | EMP12 | ADMIN | 600 | 1981-12-03 | 950.00000 | | 30 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 (14 rows) CREATE VIEW comp_vw (s1, s2, s3, s6, s7, s8, d2) AS SELECT s.c1, s.c2, s.c3, s.c6, s.c7, s.c8, d.c2 FROM f_test_tbl2 d, f_test_tbl1 s WHERE d.c1 = s.c8 AND d.c1 = 10 ORDER BY s.c1; SELECT * FROM comp_vw ORDER BY 1; s1 | s2 | s3 | s6 | s7 | s8 | d2 ------+-------+---------+------------+----+----+------------- 700 | EMP7 | MANAGER | 2450.45000 | | 10 | DEVELOPMENT 900 | EMP9 | HEAD | 5000.00000 | | 10 | DEVELOPMENT 1400 | EMP14 | ADMIN | 1300.00000 | | 10 | DEVELOPMENT (3 rows) CREATE TEMPORARY VIEW ttest_tbl1_vw AS SELECT c1, c2, c3 FROM f_test_tbl2; SELECT * FROM ttest_tbl1_vw ORDER BY 1, 2; c1 | c2 | c3 ----+----------------+---------- 10 | DEVELOPMENT | PUNE 20 | ADMINISTRATION | BANGLORE 30 | SALES | MUMBAI 40 | HR | NAGPUR (4 rows) CREATE VIEW mul_tbl_view AS SELECT d.c1 dc1, d.c2 dc2, e.c1 ec1, e.c2 ec2, e.c6 ec6 FROM f_test_tbl2 d INNER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY d.c1; SELECT * FROM mul_tbl_view ORDER BY 1, 2,3; dc1 | dc2 | ec1 | ec2 | ec6 -----+----------------+------+-------+------------ 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 30 | SALES | 200 | EMP2 | 1600.00000 30 | SALES | 300 | EMP3 | 1250.00000 30 | SALES | 500 | EMP5 | 1250.00000 30 | SALES | 600 | EMP6 | 2850.00000 30 | SALES | 1000 | EMP10 | 1500.00000 30 | SALES | 1200 | EMP12 | 950.00000 (14 rows) -- Insert Some records in numbers table. INSERT INTO f_numbers VALUES (1, 'One'); INSERT INTO f_numbers VALUES (2, 'Two'); INSERT INTO f_numbers VALUES (3, 'Three'); INSERT INTO f_numbers VALUES (4, 'Four'); INSERT INTO f_numbers VALUES (5, 'Five'); INSERT INTO f_numbers VALUES (6, 'Six'); INSERT INTO f_numbers VALUES (7, 'Seven'); INSERT INTO f_numbers VALUES (8, 'Eight'); INSERT INTO f_numbers VALUES (9, 'Nine'); -- Retrieve Data From foreign tables in functions. CREATE OR REPLACE FUNCTION test_param_where() RETURNS void AS $$ DECLARE n varchar; BEGIN FOR x IN 1..9 LOOP SELECT b INTO n FROM f_numbers WHERE a = x; RAISE NOTICE 'Found number %', n; END LOOP; return; END $$ LANGUAGE plpgsql; SELECT test_param_where(); NOTICE: Found number One NOTICE: Found number Two NOTICE: Found number Three NOTICE: Found number Four NOTICE: Found number Five NOTICE: Found number Six NOTICE: Found number Seven NOTICE: Found number Eight NOTICE: Found number Nine test_param_where ------------------ (1 row) CREATE OR REPLACE FUNCTION test_param_where2(int, text) RETURNS integer AS ' SELECT a FROM f_numbers WHERE a = $1 AND b = $2; ' LANGUAGE sql; SELECT test_param_where2(1, 'One'); test_param_where2 ------------------- 1 (1 row) -- Foreign-Foreign table joins -- CROSS JOIN. SELECT f_test_tbl2.c2, f_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN f_test_tbl1 ORDER BY 1, 2; c2 | c2 ----------------+------- ADMINISTRATION | EMP1 ADMINISTRATION | EMP10 ADMINISTRATION | EMP11 ADMINISTRATION | EMP12 ADMINISTRATION | EMP13 ADMINISTRATION | EMP14 ADMINISTRATION | EMP2 ADMINISTRATION | EMP3 ADMINISTRATION | EMP4 ADMINISTRATION | EMP5 ADMINISTRATION | EMP6 ADMINISTRATION | EMP7 ADMINISTRATION | EMP8 ADMINISTRATION | EMP9 DEVELOPMENT | EMP1 DEVELOPMENT | EMP10 DEVELOPMENT | EMP11 DEVELOPMENT | EMP12 DEVELOPMENT | EMP13 DEVELOPMENT | EMP14 DEVELOPMENT | EMP2 DEVELOPMENT | EMP3 DEVELOPMENT | EMP4 DEVELOPMENT | EMP5 DEVELOPMENT | EMP6 DEVELOPMENT | EMP7 DEVELOPMENT | EMP8 DEVELOPMENT | EMP9 HR | EMP1 HR | EMP10 HR | EMP11 HR | EMP12 HR | EMP13 HR | EMP14 HR | EMP2 HR | EMP3 HR | EMP4 HR | EMP5 HR | EMP6 HR | EMP7 HR | EMP8 HR | EMP9 SALES | EMP1 SALES | EMP10 SALES | EMP11 SALES | EMP12 SALES | EMP13 SALES | EMP14 SALES | EMP2 SALES | EMP3 SALES | EMP4 SALES | EMP5 SALES | EMP6 SALES | EMP7 SALES | EMP8 SALES | EMP9 (56 rows) -- INNER JOIN. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d, f_test_tbl1 e WHERE d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d INNER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) -- OUTER JOINS. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d LEFT OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 40 | HR | | | | (15 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d RIGHT OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d FULL OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 40 | HR | | | | (15 rows) -- Local-Foreign table joins. CREATE TABLE l_test_tbl1 AS SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1; CREATE TABLE l_test_tbl2 AS SELECT c1, c2, c3 FROM f_test_tbl2; -- CROSS JOIN. SELECT f_test_tbl2.c2, l_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN l_test_tbl1 ORDER BY 1, 2; c2 | c2 ----------------+------- ADMINISTRATION | EMP1 ADMINISTRATION | EMP10 ADMINISTRATION | EMP11 ADMINISTRATION | EMP12 ADMINISTRATION | EMP13 ADMINISTRATION | EMP14 ADMINISTRATION | EMP2 ADMINISTRATION | EMP3 ADMINISTRATION | EMP4 ADMINISTRATION | EMP5 ADMINISTRATION | EMP6 ADMINISTRATION | EMP7 ADMINISTRATION | EMP8 ADMINISTRATION | EMP9 DEVELOPMENT | EMP1 DEVELOPMENT | EMP10 DEVELOPMENT | EMP11 DEVELOPMENT | EMP12 DEVELOPMENT | EMP13 DEVELOPMENT | EMP14 DEVELOPMENT | EMP2 DEVELOPMENT | EMP3 DEVELOPMENT | EMP4 DEVELOPMENT | EMP5 DEVELOPMENT | EMP6 DEVELOPMENT | EMP7 DEVELOPMENT | EMP8 DEVELOPMENT | EMP9 HR | EMP1 HR | EMP10 HR | EMP11 HR | EMP12 HR | EMP13 HR | EMP14 HR | EMP2 HR | EMP3 HR | EMP4 HR | EMP5 HR | EMP6 HR | EMP7 HR | EMP8 HR | EMP9 SALES | EMP1 SALES | EMP10 SALES | EMP11 SALES | EMP12 SALES | EMP13 SALES | EMP14 SALES | EMP2 SALES | EMP3 SALES | EMP4 SALES | EMP5 SALES | EMP6 SALES | EMP7 SALES | EMP8 SALES | EMP9 (56 rows) -- INNER JOIN. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM l_test_tbl2 d, f_test_tbl1 e WHERE d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d INNER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) -- OUTER JOINS. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d LEFT OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 40 | HR | | | | (15 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d RIGHT OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 (14 rows) SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d FULL OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; c1 | c2 | c1 | c2 | c6 | c8 ----+----------------+------+-------+------------+---- 10 | DEVELOPMENT | 700 | EMP7 | 2450.45000 | 10 10 | DEVELOPMENT | 900 | EMP9 | 5000.00000 | 10 10 | DEVELOPMENT | 1400 | EMP14 | 1300.00000 | 10 20 | ADMINISTRATION | 100 | EMP1 | 800.23000 | 20 20 | ADMINISTRATION | 400 | EMP4 | 2975.12000 | 20 20 | ADMINISTRATION | 800 | EMP8 | 3000.00000 | 20 20 | ADMINISTRATION | 1100 | EMP11 | 1100.00000 | 20 20 | ADMINISTRATION | 1300 | EMP13 | 3000.00000 | 20 30 | SALES | 200 | EMP2 | 1600.00000 | 30 30 | SALES | 300 | EMP3 | 1250.00000 | 30 30 | SALES | 500 | EMP5 | 1250.00000 | 30 30 | SALES | 600 | EMP6 | 2850.00000 | 30 30 | SALES | 1000 | EMP10 | 1500.00000 | 30 30 | SALES | 1200 | EMP12 | 950.00000 | 30 40 | HR | | | | (15 rows) -- FDW-206: LEFT JOIN LATERAL case should not crash EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_mysql_test t1 LEFT JOIN LATERAL ( SELECT t2.a, t1.a AS t1_a FROM f_mysql_test t2) t3 ON t1.a = t3.a ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------------ Sort Output: t1.a, t1.b, t2.a, (t1.a) Sort Key: t1.a -> Nested Loop Left Join Output: t1.a, t1.b, t2.a, (t1.a) -> Foreign Scan on public.f_mysql_test t1 Output: t1.a, t1.b Remote query: SELECT `a`, `b` FROM `mysql_fdw_regress`.`mysql_test` -> Foreign Scan on public.f_mysql_test t2 Output: t2.a, t1.a Remote query: SELECT `a` FROM `mysql_fdw_regress`.`mysql_test` WHERE ((? = `a`)) (11 rows) SELECT * FROM f_mysql_test t1 LEFT JOIN LATERAL ( SELECT t2.a, t1.a AS t1_a FROM f_mysql_test t2) t3 ON t1.a = t3.a ORDER BY 1; a | b | a | t1_a ---+---+---+------ 1 | 1 | 1 | 1 (1 row) SELECT t1.c1, t3.c1, t3.t1_c8 FROM f_test_tbl1 t1 INNER JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; c1 | c1 | t1_c8 ------+----+------- 100 | 20 | 20 200 | 30 | 30 300 | 30 | 30 400 | 20 | 20 500 | 30 | 30 600 | 30 | 30 700 | 10 | 10 800 | 20 | 20 900 | 10 | 10 1000 | 30 | 30 1100 | 20 | 20 1200 | 30 | 30 1300 | 20 | 20 1400 | 10 | 10 (14 rows) SELECT t1.c1, t3.c1, t3.t1_c8 FROM l_test_tbl1 t1 LEFT JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; c1 | c1 | t1_c8 ------+----+------- 100 | 20 | 20 200 | 30 | 30 300 | 30 | 30 400 | 20 | 20 500 | 30 | 30 600 | 30 | 30 700 | 10 | 10 800 | 20 | 20 900 | 10 | 10 1000 | 30 | 30 1100 | 20 | 20 1200 | 30 | 30 1300 | 20 | 20 1400 | 10 | 10 (14 rows) SELECT *, (SELECT r FROM (SELECT c1 AS c1) x, LATERAL (SELECT c1 AS r) y) FROM f_test_tbl1 ORDER BY 1, 2, 3; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | r ------+-------+----------+------+------------+------------+------+----+------ 100 | EMP1 | ADMIN | 1300 | 1980-12-17 | 800.23000 | | 20 | 100 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 | 200 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 | 300 400 | EMP4 | MANAGER | 900 | 1981-04-02 | 2975.12000 | | 20 | 400 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 | 500 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 | 600 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 | 700 800 | EMP8 | FINANCE | 400 | 1987-04-19 | 3000.00000 | | 20 | 800 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 | 900 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 | 1000 1100 | EMP11 | ADMIN | 800 | 1987-05-23 | 1100.00000 | | 20 | 1100 1200 | EMP12 | ADMIN | 600 | 1981-12-03 | 950.00000 | | 30 | 1200 1300 | EMP13 | FINANCE | 400 | 1981-12-03 | 3000.00000 | | 20 | 1300 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 | 1400 (14 rows) -- LATERAL JOIN with RIGHT should throw error SELECT t1.c1, t3.c1, t3.t1_c8 FROM f_test_tbl1 t1 RIGHT JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; ERROR: invalid reference to FROM-clause entry for table "t1" LINE 2: SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3... ^ DETAIL: The combining JOIN type must be INNER or LEFT for a LATERAL reference. -- FDW-207: NATURAL JOIN should give correct output SELECT t1.c1, t2.c1, t3.c1 FROM f_test_tbl1 t1 NATURAL JOIN f_test_tbl1 t2 NATURAL JOIN f_test_tbl1 t3 ORDER BY 1, 2, 3; c1 | c1 | c1 ------+------+------ 200 | 200 | 200 300 | 300 | 300 500 | 500 | 500 1000 | 1000 | 1000 (4 rows) -- FDW-208: IS NULL and LIKE should give the correct output with -- use_remote_estimate set to true. INSERT INTO f_test_tbl2 VALUES (50, 'TEMP1', NULL); INSERT INTO f_test_tbl2 VALUES (60, 'TEMP2', NULL); ALTER SERVER mysql_svr OPTIONS (use_remote_estimate 'true'); SELECT t1.c1, t2.c1 FROM f_test_tbl2 t1 INNER JOIN f_test_tbl2 t2 ON t1.c1 = t2.c1 WHERE t1.c3 IS NULL ORDER BY 1, 2; c1 | c1 ----+---- 50 | 50 60 | 60 (2 rows) SELECT t1.c1, t2.c1 FROM f_test_tbl2 t1 INNER JOIN f_test_tbl2 t2 ON t1.c1 = t2.c1 AND t1.c2 LIKE 'TEMP%' ORDER BY 1, 2; c1 | c1 ----+---- 50 | 50 60 | 60 (2 rows) DELETE FROM f_test_tbl2 WHERE c1 IN (50, 60); ALTER SERVER mysql_svr OPTIONS (SET use_remote_estimate 'false'); -- FDW-169: Insert/Update/Delete on enum column. INSERT INTO f_enum_t1 VALUES (1, 'small'), (2, 'medium'), (3, 'medium'), (4, 'small'); SELECT * FROM f_enum_t1 WHERE id = 4; id | size ----+------- 4 | small (1 row) UPDATE f_enum_t1 SET size = 'large' WHERE id = 4; SELECT * FROM f_enum_t1 WHERE id = 4; id | size ----+------- 4 | large (1 row) DELETE FROM f_enum_t1 WHERE size = 'large'; SELECT * FROM f_enum_t1 WHERE id = 4; id | size ----+------ (0 rows) -- Negative scenarios for ENUM handling. -- Test that if we insert the ENUM value which is not present on MySQL side, -- but present on Postgres side. DROP FOREIGN TABLE f_enum_t1; DROP TYPE size_t; -- Create the type with extra enum values. CREATE TYPE size_t AS enum('small', 'medium', 'large', 'largest', ''); CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); -- If we insert the enum value which is not present on MySQL side then it -- inserts empty string in ANSI_QUOTES sql_mode, so verify that. INSERT INTO f_enum_t1 VALUES (4, 'largest'); SELECT * from f_enum_t1; id | size ----+-------- 1 | small 2 | medium 3 | medium 4 | (4 rows) DELETE FROM f_enum_t1 WHERE size = ''; -- Postgres should throw an error as the value which we are inserting for enum -- column is not present in enum on Postgres side, no matter whether it is -- present on MySQL side or not. PG's sanity check itself throws an error. INSERT INTO f_enum_t1 VALUES (4, 'big'); ERROR: invalid input value for enum size_t: "big" LINE 1: INSERT INTO f_enum_t1 VALUES (4, 'big'); ^ -- FDW-155: Enum data type can be handled correctly in select statements on -- foreign table. SELECT * FROM f_enum_t1 WHERE size = 'medium' ORDER BY id; id | size ----+-------- 2 | medium 3 | medium (2 rows) -- Remote aggregate in combination with a local Param (for the output -- of an initplan) SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; exists | sum --------+----- t | 6 (1 row) SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1 GROUP BY 1; exists | sum --------+----- t | 6 (1 row) -- Check with the IMPORT FOREIGN SCHEMA command. Also, check ENUM types with -- the IMPORT FOREIGN SCHEMA command. If the enum name is the same for multiple -- tables, then it should handle correctly by prefixing the table name. CREATE TYPE enum_t1_size_t AS enum('small', 'medium', 'large'); CREATE TYPE enum_t2_size_t AS enum('S', 'M', 'L'); IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (enum_t1, enum_t2) FROM SERVER mysql_svr INTO public; NOTICE: error while generating the table definition HINT: If you encounter an error, you may need to execute the following first: DO $$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE typname = 'enum_t1_size_t') THEN CREATE TYPE enum_t1_size_t AS enum('small','medium','large'); END IF; END$$; NOTICE: error while generating the table definition HINT: If you encounter an error, you may need to execute the following first: DO $$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE typname = 'enum_t2_size_t') THEN CREATE TYPE enum_t2_size_t AS enum('S','M','L'); END IF; END$$; SELECT attrelid::regclass, atttypid::regtype FROM pg_attribute WHERE (attrelid = 'enum_t1'::regclass OR attrelid = 'enum_t2'::regclass) AND attnum > 1 ORDER BY 1; attrelid | atttypid ----------+---------------- enum_t1 | enum_t1_size_t enum_t2 | enum_t2_size_t (2 rows) SELECT * FROM enum_t1 ORDER BY id; id | size ----+-------- 1 | small 2 | medium 3 | medium (3 rows) SELECT * FROM enum_t2 ORDER BY id; id | size ----+------ 10 | S 20 | M 30 | M (3 rows) DROP FOREIGN TABLE enum_t1; DROP FOREIGN TABLE enum_t2; -- FDW-248: IMPORT FOREIGN SCHEMA command should work correctly if called -- multiple times. Earlier we wrongly used PG_TRY/CATCH block to get the server -- options without clearing the error state and that exceeded -- ERRORDATA_STACK_SIZE hard coded to 5. DO $DO$ DECLARE i int; BEGIN FOR i IN 1..5 LOOP IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (mysql_test) FROM SERVER mysql_svr INTO public; DROP FOREIGN TABLE mysql_test; END LOOP; END; $DO$; -- Parameterized queries should work correctly. EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; QUERY PLAN ------------------------------------------------------------------------------------------------- Sort Output: f_test_tbl1.c1, f_test_tbl1.c2 Sort Key: f_test_tbl1.c1 InitPlan 2 (returns $1) -> Foreign Scan on public.f_test_tbl2 Output: f_test_tbl2.c1 Remote query: SELECT `c1` FROM `mysql_fdw_regress`.`test_tbl2` WHERE ((`c1` = ?)) InitPlan 1 (returns $0) -> Result Output: 20 -> Foreign Scan on public.f_test_tbl1 Output: f_test_tbl1.c1, f_test_tbl1.c2 Remote query: SELECT `c1`, `c2` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c8` = ?)) (13 rows) SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; c1 | c2 ------+------- 100 | EMP1 400 | EMP4 800 | EMP8 1100 | EMP11 1300 | EMP13 (5 rows) SELECT * FROM f_test_tbl1 WHERE c8 NOT IN (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 ------+-------+----------+-----+------------+------------+------+---- 200 | EMP2 | SALESMAN | 600 | 1981-02-20 | 1600.00000 | 300 | 30 300 | EMP3 | SALESMAN | 600 | 1981-02-22 | 1250.00000 | 500 | 30 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 600 | EMP6 | MANAGER | 900 | 1981-05-01 | 2850.00000 | | 30 700 | EMP7 | MANAGER | 900 | 1981-06-09 | 2450.45000 | | 10 900 | EMP9 | HEAD | | 1981-11-17 | 5000.00000 | | 10 1000 | EMP10 | SALESMAN | 600 | 1980-09-08 | 1500.00000 | 0 | 30 1200 | EMP12 | ADMIN | 600 | 1981-12-03 | 950.00000 | | 30 1400 | EMP14 | ADMIN | 700 | 1982-01-23 | 1300.00000 | | 10 (9 rows) -- Check parameterized queries with text/varchar column, should not crash. CREATE FOREIGN TABLE f_test_tbl3 (c1 INTEGER, c2 text, c3 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE TABLE local_t1 (c1 INTEGER, c2 text); INSERT INTO local_t1 VALUES (1, 'SALES'); SELECT c1, c2 FROM f_test_tbl3 WHERE c3 = (SELECT 'PUNE'::text) ORDER BY c1; c1 | c2 ----+------------- 10 | DEVELOPMENT (1 row) SELECT c1, c2 FROM f_test_tbl2 WHERE c3 = (SELECT 'PUNE'::varchar) ORDER BY c1; c1 | c2 ----+------------- 10 | DEVELOPMENT (1 row) SELECT * FROM local_t1 lt1 WHERE lt1.c1 = (SELECT count(*) FROM f_test_tbl3 ft1 WHERE ft1.c2 = lt1.c2) ORDER BY lt1.c1; c1 | c2 ----+------- 1 | SALES (1 row) SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = ( SELECT c1 FROM f_test_tbl2 WHERE c1 = ( SELECT min(c1) + 1 FROM f_test_tbl2)) ORDER BY c1; c1 | c2 ----+---- (0 rows) SELECT * FROM f_test_tbl1 WHERE c1 = (SELECT 500) AND c2 = ( SELECT max(c2) FROM f_test_tbl1 WHERE c4 = (SELECT 600)) ORDER BY 1, 2; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 -----+------+----------+-----+------------+------------+------+---- 500 | EMP5 | SALESMAN | 600 | 1981-09-28 | 1250.00000 | 1400 | 30 (1 row) SELECT t1.c1, (SELECT c2 FROM f_test_tbl1 WHERE c1 =(SELECT 500)) FROM f_test_tbl2 t1, ( SELECT c1, c2 FROM f_test_tbl2 WHERE c1 > ANY (SELECT 20)) t2 ORDER BY 1, 2; c1 | c2 ----+------ 10 | EMP5 10 | EMP5 20 | EMP5 20 | EMP5 30 | EMP5 30 | EMP5 40 | EMP5 40 | EMP5 (8 rows) -- FDW-255: Should throw an error when we select system attribute. SELECT xmin FROM f_test_tbl1; ERROR: system attribute "xmin" can't be fetched from remote relation SELECT ctid, xmax, tableoid FROM f_test_tbl1; ERROR: system attribute "ctid" can't be fetched from remote relation SELECT xmax, c1 FROM f_test_tbl1; ERROR: system attribute "xmax" can't be fetched from remote relation SELECT count(tableoid) FROM f_test_tbl1; ERROR: system attribute "tableoid" can't be fetched from remote relation -- FDW-333: MySQL BINARY and VARBINARY data type should map to BYTEA in -- Postgres while importing the schema. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO ("test5") FROM SERVER mysql_svr INTO public; SELECT attrelid::regclass, atttypid::regtype FROM pg_attribute WHERE attrelid = 'test5'::regclass AND attnum > 1 ORDER BY 1; attrelid | atttypid ----------+---------- test5 | bytea test5 | bytea test5 | bytea test5 | bytea test5 | bytea test5 | bytea test5 | bytea test5 | bytea test5 | bytea (9 rows) SELECT * FROM test5 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+------+----------+------+------------------------+--------+------+------------------------+----+----- 1 | \x63 | \x633363 | \x74 | \x63356335633500000000 | \x3034 | \x31 | \x30312d31302d32303231 | | \x (1 row) -- Test Mapping of MySQL BINARY and VARBINARY data type with various -- Postgres data types. SELECT * FROM test5_1 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+----+-----+----+--------+----------+------+------------+----+----- 1 | c | c3c | t | c5c5c5 | @ 4 secs | \x31 | 2021-01-10 | | (1 row) SELECT * FROM test5_1 WHERE c9 IS NULL ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+----+-----+----+--------+----------+------+------------+----+----- 1 | c | c3c | t | c5c5c5 | @ 4 secs | \x31 | 2021-01-10 | | (1 row) SELECT * FROM test5_1 WHERE c10 IS NULL ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+----+----+----+----+----+----+----+----+----- (0 rows) -- Test MYSQL BINARY(n) and VARBINARY(n) variants mapping to Postgres BYTEA. SELECT * FROM test5_2 ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+------+----------+------+------------------------+--------+------+------------------------+----+----- 1 | \x63 | \x633363 | \x74 | \x63356335633500000000 | \x3034 | \x31 | \x30312d31302d32303231 | | \x (1 row) SELECT * FROM test5_2 WHERE c9 IS NULL ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+------+----------+------+------------------------+--------+------+------------------------+----+----- 1 | \x63 | \x633363 | \x74 | \x63356335633500000000 | \x3034 | \x31 | \x30312d31302d32303231 | | \x (1 row) SELECT * FROM test5_2 WHERE c10 IS NULL ORDER BY 1; c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 | c9 | c10 ----+----+----+----+----+----+----+----+----+----- (0 rows) -- FDW-400: Test the parameterized query by enabling use_remote_estimate -- option. ALTER SERVER mysql_svr options (SET use_remote_estimate 'true'); SELECT c1, sum(c7) FROM f_test_tbl1 t1 GROUP BY c1 HAVING EXISTS (SELECT 1 FROM f_test_tbl1 t2 WHERE (t1.c1 = t2.c1)) ORDER BY 1,2; c1 | sum ------+------ 100 | 200 | 300 300 | 500 400 | 500 | 1400 600 | 700 | 800 | 900 | 1000 | 0 1100 | 1200 | 1300 | 1400 | (14 rows) ALTER SERVER mysql_svr options (SET use_remote_estimate 'false'); -- FDW-411: Volatile/immutable functions should not get pushed down to remote -- MySQL server. EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, c2, c3 FROM f_test_tbl1 WHERE pg_catalog.timeofday() IS NOT NULL ORDER BY 1 limit 5; QUERY PLAN ------------------------------------------------------------------------------------------ Limit Output: c1, c2, c3 -> Sort Output: c1, c2, c3 Sort Key: f_test_tbl1.c1 -> Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3 Filter: (timeofday() IS NOT NULL) Remote query: SELECT `c1`, `c2`, `c3` FROM `mysql_fdw_regress`.`test_tbl1` (9 rows) SELECT c1, c2, c3 FROM f_test_tbl1 WHERE pg_catalog.timeofday() IS NOT NULL ORDER BY 1 limit 5; c1 | c2 | c3 -----+------+---------- 100 | EMP1 | ADMIN 200 | EMP2 | SALESMAN 300 | EMP3 | SALESMAN 400 | EMP4 | MANAGER 500 | EMP5 | SALESMAN (5 rows) -- FDW-447: Fix function implicit/explicit coercion. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 WHERE c1 = 12.2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3, c4, c5, c6, c7, c8 Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c1` = 12.2)) (3 rows) EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 WHERE c1::numeric = 12.2; QUERY PLAN ---------------------------------------------------------------------------------------------------------------------------------- Foreign Scan on public.f_test_tbl1 Output: c1, c2, c3, c4, c5, c6, c7, c8 Remote query: SELECT `c1`, `c2`, `c3`, `c4`, `c5`, `c6`, `c7`, `c8` FROM `mysql_fdw_regress`.`test_tbl1` WHERE ((`c1` = 12.2)) (3 rows) -- FDW-408: Skip importing relations that have SET type because Postgres -- doesn't have equivalent datatype which can be mapped to MySQL SET. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (test_set, mysql_test, test_tbl1) FROM SERVER mysql_svr INTO public; WARNING: skipping import for relation "test_set" DETAIL: MySQL SET columns are not supported. SELECT relname FROM pg_class WHERE relname IN ('test_set', 'mysql_test', 'test_tbl1') AND relnamespace = 'public'::regnamespace; relname ------------ mysql_test test_tbl1 (2 rows) -- Cleanup DROP TABLE l_test_tbl1; DROP TABLE l_test_tbl2; DROP TABLE local_t1; DROP VIEW smpl_vw; DROP VIEW comp_vw; DROP VIEW ttest_tbl1_vw; DROP VIEW mul_tbl_view; DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DELETE FROM f_numbers; DELETE FROM f_enum_t1; DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP FOREIGN TABLE f_numbers; DROP FOREIGN TABLE f_mysql_test; DROP FOREIGN TABLE f_enum_t1; DROP FOREIGN TABLE f_test_tbl3; DROP FOREIGN TABLE test5; DROP FOREIGN TABLE test5_1; DROP FOREIGN TABLE test5_2; DROP FOREIGN TABLE mysql_test; DROP FOREIGN TABLE test_tbl1; DROP TYPE size_t; DROP TYPE enum_t1_size_t; DROP TYPE enum_t2_size_t; DROP FUNCTION test_param_where(); DROP FUNCTION test_param_where2(int, text); DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/expected/server_options.out000066400000000000000000000206621414541277500217420ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Validate extension, server and mapping details CREATE OR REPLACE FUNCTION show_details(host TEXT, port TEXT, uid TEXT, pwd TEXT) RETURNS int AS $$ DECLARE ext TEXT; srv TEXT; sopts TEXT; uopts TEXT; BEGIN SELECT e.fdwname, srvname, array_to_string(s.srvoptions, ','), array_to_string(u.umoptions, ',') INTO ext, srv, sopts, uopts FROM pg_foreign_data_wrapper e LEFT JOIN pg_foreign_server s ON e.oid = s.srvfdw LEFT JOIN pg_user_mapping u ON s.oid = u.umserver WHERE e.fdwname = 'mysql_fdw' ORDER BY 1, 2, 3, 4; raise notice 'Extension : %', ext; raise notice 'Server : %', srv; IF strpos(sopts, host) <> 0 AND strpos(sopts, port) <> 0 THEN raise notice 'Server_Options : matched'; END IF; IF strpos(uopts, uid) <> 0 AND strpos(uopts, pwd) <> 0 THEN raise notice 'User_Mapping_Options : matched'; END IF; return 1; END; $$ language plpgsql; SELECT show_details(:MYSQL_HOST, :MYSQL_PORT, :MYSQL_USER_NAME, :MYSQL_PASS); NOTICE: Extension : mysql_fdw NOTICE: Server : mysql_svr NOTICE: Server_Options : matched NOTICE: User_Mapping_Options : matched show_details -------------- 1 (1 row) -- Create foreign table and perform basic SQL operations CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) INSERT INTO f_mysql_test (a, b) VALUES (2, 2); SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 2 | 2 (2 rows) UPDATE f_mysql_test SET b = 3 WHERE a = 2; SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 2 | 3 (2 rows) DELETE FROM f_mysql_test WHERE a = 2; SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; -- Server with init_command. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT, init_command 'create table init_command_check(a int)'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test (a int, b int) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- This will create init_command_check table in mysql_fdw_regress database. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; a | b ---+--- 1 | 1 (1 row) -- init_command_check table created mysql_fdw_regress database can be verified -- by creating corresponding foreign table here. CREATE FOREIGN TABLE f_init_command_check(a int) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'init_command_check'); SELECT a FROM f_init_command_check ORDER BY 1; a --- (0 rows) -- Changing init_command to drop init_command_check table from -- mysql_fdw_regress database ALTER SERVER mysql_svr1 OPTIONS (SET init_command 'drop table init_command_check'); SELECT a, b FROM f_mysql_test; a | b ---+--- 1 | 1 (1 row) DROP FOREIGN TABLE f_init_command_check; DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- Server with use_remote_estimate. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, use_remote_estimate 'TRUE'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- Below explain will return actual rows from MySQL, but keeping costs off -- here for consistent regression result. EXPLAIN (VERBOSE, COSTS OFF) SELECT a FROM f_mysql_test WHERE a < 2 ORDER BY 1; QUERY PLAN ------------------------------------------------------------------------------------------ Sort Output: a Sort Key: f_mysql_test.a -> Foreign Scan on public.f_mysql_test Output: a Remote query: SELECT `a` FROM `mysql_fdw_regress`.`mysql_test` WHERE ((`a` < 2)) (6 rows) DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- Create server with secure_auth. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, secure_auth 'FALSE'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- Below should fail with Warning of secure_auth is false. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; WARNING: MySQL secure authentication is off a | b ---+--- 1 | 1 (1 row) DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- FDW-335: Support for fetch_size option at server level and table level. CREATE SERVER fetch101 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( fetch_size '101' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=101']; count ------- 1 (1 row) ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=101']; count ------- 0 (1 row) SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=202']; count ------- 1 (1 row) CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=30000']; count ------- 1 (1 row) ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=30000']; count ------- 0 (1 row) SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=60000']; count ------- 1 (1 row) -- Make sure that changing the table level fetch-size value did not change the -- server level value. SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=202']; count ------- 1 (1 row) -- Negative test cases for fetch_size option, should error out. ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '-60000'); ERROR: "fetch_size" requires an integer value between 1 to 18446744073709551615 ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '123abc'); ERROR: "fetch_size" requires an integer value between 1 to 18446744073709551615 ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '999999999999999999999'); ERROR: "fetch_size" requires an integer value between 1 to 18446744073709551615 -- Cleanup fetch_size test objects. DROP FOREIGN TABLE table30000; DROP SERVER fetch101; -- FDW-350: Support for reconnect option at server level. CREATE SERVER reconnect1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( reconnect 'true' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'reconnect1' AND srvoptions @> array['reconnect=true']; count ------- 1 (1 row) ALTER SERVER reconnect1 OPTIONS( SET reconnect 'false' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'reconnect1' AND srvoptions @> array['reconnect=false']; count ------- 1 (1 row) -- Negative test case for reconnect option, should error out. ALTER SERVER reconnect1 OPTIONS ( SET reconnect 'abc1' ); ERROR: reconnect requires a Boolean value -- Cleanup reconnect option test objects. DROP SERVER reconnect1; -- Cleanup DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/mysql_fdw--1.0--1.1.sql000066400000000000000000000002341414541277500177710ustar00rootroot00000000000000/* mysql_fdw/mysql_fdw--1.0--1.1.sql */ CREATE OR REPLACE FUNCTION mysql_fdw_version() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; mysql_fdw-REL-2_7_0/mysql_fdw--1.0.sql000066400000000000000000000013221414541277500174160ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw--1.0.sql * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.0.sql * *------------------------------------------------------------------------- */ CREATE FUNCTION mysql_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION mysql_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; mysql_fdw-REL-2_7_0/mysql_fdw--1.1.sql000066400000000000000000000015061414541277500174230ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw--1.1.sql * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw--1.1.sql * *------------------------------------------------------------------------- */ CREATE FUNCTION mysql_fdw_handler() RETURNS fdw_handler AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FUNCTION mysql_fdw_validator(text[], oid) RETURNS void AS 'MODULE_PATHNAME' LANGUAGE C STRICT; CREATE FOREIGN DATA WRAPPER mysql_fdw HANDLER mysql_fdw_handler VALIDATOR mysql_fdw_validator; CREATE OR REPLACE FUNCTION mysql_fdw_version() RETURNS pg_catalog.int4 STRICT AS 'MODULE_PATHNAME' LANGUAGE C; mysql_fdw-REL-2_7_0/mysql_fdw.c000066400000000000000000003335331414541277500165040ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.c * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw.c * *------------------------------------------------------------------------- */ #include "postgres.h" /* * Must be included before mysql.h as it has some conflicting definitions like * list_length, etc. */ #include "mysql_fdw.h" #include #include #include #include #include #include #include "access/htup_details.h" #include "access/sysattr.h" #include "access/reloptions.h" #if PG_VERSION_NUM >= 120000 #include "access/table.h" #endif #include "commands/defrem.h" #include "commands/explain.h" #include "catalog/heap.h" #include "foreign/fdwapi.h" #include "miscadmin.h" #include "mysql_query.h" #include "nodes/makefuncs.h" #include "nodes/nodeFuncs.h" #include "nodes/nodes.h" #if PG_VERSION_NUM >= 140000 #include "optimizer/appendinfo.h" #endif #include "optimizer/pathnode.h" #include "optimizer/planmain.h" #if PG_VERSION_NUM < 120000 #include "optimizer/var.h" #else #include "optimizer/optimizer.h" #endif #include "optimizer/restrictinfo.h" #include "optimizer/tlist.h" #include "parser/parsetree.h" #include "storage/ipc.h" #include "utils/builtins.h" #include "utils/datum.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" #include "utils/selfuncs.h" #include "utils/syscache.h" /* Declarations for dynamic loading */ PG_MODULE_MAGIC; int ((mysql_options) (MYSQL *mysql, enum mysql_option option, const void *arg)); int ((mysql_stmt_prepare) (MYSQL_STMT *stmt, const char *query, unsigned long length)); int ((mysql_stmt_execute) (MYSQL_STMT *stmt)); int ((mysql_stmt_fetch) (MYSQL_STMT *stmt)); int ((mysql_query) (MYSQL *mysql, const char *q)); bool ((mysql_stmt_attr_set) (MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr)); bool ((mysql_stmt_close) (MYSQL_STMT *stmt)); bool ((mysql_stmt_reset) (MYSQL_STMT *stmt)); bool ((mysql_free_result) (MYSQL_RES *result)); bool ((mysql_stmt_bind_param) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); bool ((mysql_stmt_bind_result) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); MYSQL_STMT *((mysql_stmt_init) (MYSQL *mysql)); MYSQL_RES *((mysql_stmt_result_metadata) (MYSQL_STMT *stmt)); int ((mysql_stmt_store_result) (MYSQL *mysql)); MYSQL_ROW((mysql_fetch_row) (MYSQL_RES *result)); MYSQL_FIELD *((mysql_fetch_field) (MYSQL_RES *result)); MYSQL_FIELD *((mysql_fetch_fields) (MYSQL_RES *result)); const char *((mysql_error) (MYSQL *mysql)); void ((mysql_close) (MYSQL *sock)); MYSQL_RES *((mysql_store_result) (MYSQL *mysql)); MYSQL *((mysql_init) (MYSQL *mysql)); bool ((mysql_ssl_set) (MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher)); MYSQL *((mysql_real_connect) (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)); const char *((mysql_get_host_info) (MYSQL *mysql)); const char *((mysql_get_server_info) (MYSQL *mysql)); int ((mysql_get_proto_info) (MYSQL *mysql)); unsigned int ((mysql_stmt_errno) (MYSQL_STMT *stmt)); unsigned int ((mysql_errno) (MYSQL *mysql)); unsigned int ((mysql_num_fields) (MYSQL_RES *result)); unsigned int ((mysql_num_rows) (MYSQL_RES *result)); #define DEFAULTE_NUM_ROWS 1000 /* * In PG 9.5.1 the number will be 90501, * our version is 2.7.0 so number will be 20700 */ #define CODE_VERSION 20700 /* * Indexes of FDW-private information stored in fdw_private lists. * * These items are indexed with the enum mysqlFdwScanPrivateIndex, so an item * can be fetched with list_nth(). For example, to get the SELECT statement: * sql = strVal(list_nth(fdw_private, mysqlFdwScanPrivateSelectSql)); */ enum mysqlFdwScanPrivateIndex { /* SQL statement to execute remotely (as a String node) */ mysqlFdwScanPrivateSelectSql, /* Integer list of attribute numbers retrieved by the SELECT */ mysqlFdwScanPrivateRetrievedAttrs, /* * String describing join i.e. names of relations being joined and types * of join, added when the scan is join */ mysqlFdwScanPrivateRelations, /* * List of Var node lists for constructing the whole-row references of * base relations involved in pushed down join. */ mysqlFdwPrivateWholeRowLists, /* * Targetlist representing the result fetched from the foreign server if * whole-row references are involved. */ mysqlFdwPrivateScanTList }; extern PGDLLEXPORT void _PG_init(void); extern Datum mysql_fdw_handler(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(mysql_fdw_handler); PG_FUNCTION_INFO_V1(mysql_fdw_version); /* * FDW callback routines */ static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es); static void mysqlBeginForeignScan(ForeignScanState *node, int eflags); static TupleTableSlot *mysqlIterateForeignScan(ForeignScanState *node); static void mysqlReScanForeignScan(ForeignScanState *node); static void mysqlEndForeignScan(ForeignScanState *node); static List *mysqlPlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index); static void mysqlBeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, List *fdw_private, int subplan_index, int eflags); static TupleTableSlot *mysqlExecForeignInsert(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot); #if PG_VERSION_NUM >= 140000 static void mysqlAddForeignUpdateTargets(PlannerInfo *root, Index rtindex, RangeTblEntry *target_rte, Relation target_relation); #else static void mysqlAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation); #endif static TupleTableSlot *mysqlExecForeignUpdate(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot); static TupleTableSlot *mysqlExecForeignDelete(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot); static void mysqlEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo); static void mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); static void mysqlGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); static bool mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); #if PG_VERSION_NUM >= 90500 static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan); #else static ForeignScan *mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses); #endif static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid); #if PG_VERSION_NUM >= 90600 static void mysqlGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra); static bool mysqlRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot); #endif #if PG_VERSION_NUM >= 110000 static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra); #elif PG_VERSION_NUM >= 100000 static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel); #endif #if PG_VERSION_NUM >= 90500 static List *mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid); #endif #if PG_VERSION_NUM >= 110000 static void mysqlBeginForeignInsert(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo); static void mysqlEndForeignInsert(EState *estate, ResultRelInfo *resultRelInfo); #endif /* * Helper functions */ bool mysql_load_library(void); static void mysql_fdw_exit(int code, Datum arg); static bool mysql_is_column_unique(Oid foreigntableid); static void prepare_query_params(PlanState *node, List *fdw_exprs, int numParams, FmgrInfo **param_flinfo, List **param_exprs, const char ***param_values, Oid **param_types); static void process_query_params(ExprContext *econtext, FmgrInfo *param_flinfo, List *param_exprs, const char **param_values, MYSQL_BIND **mysql_bind_buf, Oid *param_types); static void bind_stmt_params_and_exec(ForeignScanState *node); #if PG_VERSION_NUM >= 90600 static bool mysql_foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinPathExtraData *extra); static List *mysql_adjust_whole_row_ref(PlannerInfo *root, List *scan_var_list, List **whole_row_lists, Bitmapset *relids); static List *mysql_build_scan_list_for_baserel(Oid relid, Index varno, Bitmapset *attrs_used, List **retrieved_attrs); #endif static void mysql_build_whole_row_constr_info(MySQLFdwExecState *festate, TupleDesc tupdesc, Bitmapset *relids, int max_relid, List *whole_row_lists, List *scan_tlist, List *fdw_scan_tlist); static HeapTuple mysql_get_tuple_with_whole_row(MySQLFdwExecState *festate, Datum *values,bool *nulls); static HeapTuple mysql_form_whole_row(MySQLWRState *wr_state, Datum *values, bool *nulls); #if PG_VERSION_NUM >= 110000 static bool mysql_foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual); static void mysql_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra); #elif PG_VERSION_NUM >= 100000 static bool mysql_foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel); static void mysql_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel); #endif void *mysql_dll_handle = NULL; static int wait_timeout = WAIT_TIMEOUT; static int interactive_timeout = INTERACTIVE_TIMEOUT; static void mysql_error_print(MYSQL *conn); static void mysql_stmt_error_print(MySQLFdwExecState *festate, const char *msg); static List *getUpdateTargetAttrs(RangeTblEntry *rte); /* * mysql_load_library function dynamically load the mysql's library * libmysqlclient.so. The only reason to load the library using dlopen * is that, mysql and postgres both have function with same name like * "list_delete", "list_delete" and "list_free" which cause compiler * error "duplicate function name" and erroneously linking with a function. * This port of the code is used to avoid the compiler error. * * #define list_delete mysql_list_delete * #include * #undef list_delete * * But system crashed on function mysql_stmt_close function because * mysql_stmt_close internally calling "list_delete" function which * wrongly binds to postgres' "list_delete" function. * * The dlopen function provides a parameter "RTLD_DEEPBIND" which * solved the binding issue. * * RTLD_DEEPBIND: * Place the lookup scope of the symbols in this library ahead of the * global scope. This means that a self-contained library will use its * own symbols in preference to global symbols with the same name contained * in libraries that have already been loaded. */ bool mysql_load_library(void) { #if defined(__APPLE__) || defined(__FreeBSD__) /* * Mac OS/BSD does not support RTLD_DEEPBIND, but it still works without * the RTLD_DEEPBIND */ mysql_dll_handle = dlopen(_MYSQL_LIBNAME, RTLD_LAZY); #else mysql_dll_handle = dlopen(_MYSQL_LIBNAME, RTLD_LAZY | RTLD_DEEPBIND); #endif if (mysql_dll_handle == NULL) return false; _mysql_stmt_bind_param = dlsym(mysql_dll_handle, "mysql_stmt_bind_param"); _mysql_stmt_bind_result = dlsym(mysql_dll_handle, "mysql_stmt_bind_result"); _mysql_stmt_init = dlsym(mysql_dll_handle, "mysql_stmt_init"); _mysql_stmt_prepare = dlsym(mysql_dll_handle, "mysql_stmt_prepare"); _mysql_stmt_execute = dlsym(mysql_dll_handle, "mysql_stmt_execute"); _mysql_stmt_fetch = dlsym(mysql_dll_handle, "mysql_stmt_fetch"); _mysql_query = dlsym(mysql_dll_handle, "mysql_query"); _mysql_stmt_result_metadata = dlsym(mysql_dll_handle, "mysql_stmt_result_metadata"); _mysql_stmt_store_result = dlsym(mysql_dll_handle, "mysql_stmt_store_result"); _mysql_fetch_row = dlsym(mysql_dll_handle, "mysql_fetch_row"); _mysql_fetch_field = dlsym(mysql_dll_handle, "mysql_fetch_field"); _mysql_fetch_fields = dlsym(mysql_dll_handle, "mysql_fetch_fields"); _mysql_stmt_close = dlsym(mysql_dll_handle, "mysql_stmt_close"); _mysql_stmt_reset = dlsym(mysql_dll_handle, "mysql_stmt_reset"); _mysql_free_result = dlsym(mysql_dll_handle, "mysql_free_result"); _mysql_error = dlsym(mysql_dll_handle, "mysql_error"); _mysql_options = dlsym(mysql_dll_handle, "mysql_options"); _mysql_ssl_set = dlsym(mysql_dll_handle, "mysql_ssl_set"); _mysql_real_connect = dlsym(mysql_dll_handle, "mysql_real_connect"); _mysql_close = dlsym(mysql_dll_handle, "mysql_close"); _mysql_init = dlsym(mysql_dll_handle, "mysql_init"); _mysql_stmt_attr_set = dlsym(mysql_dll_handle, "mysql_stmt_attr_set"); _mysql_store_result = dlsym(mysql_dll_handle, "mysql_store_result"); _mysql_stmt_errno = dlsym(mysql_dll_handle, "mysql_stmt_errno"); _mysql_errno = dlsym(mysql_dll_handle, "mysql_errno"); _mysql_num_fields = dlsym(mysql_dll_handle, "mysql_num_fields"); _mysql_num_rows = dlsym(mysql_dll_handle, "mysql_num_rows"); _mysql_get_host_info = dlsym(mysql_dll_handle, "mysql_get_host_info"); _mysql_get_server_info = dlsym(mysql_dll_handle, "mysql_get_server_info"); _mysql_get_proto_info = dlsym(mysql_dll_handle, "mysql_get_proto_info"); if (_mysql_stmt_bind_param == NULL || _mysql_stmt_bind_result == NULL || _mysql_stmt_init == NULL || _mysql_stmt_prepare == NULL || _mysql_stmt_execute == NULL || _mysql_stmt_fetch == NULL || _mysql_query == NULL || _mysql_stmt_result_metadata == NULL || _mysql_stmt_store_result == NULL || _mysql_fetch_row == NULL || _mysql_fetch_field == NULL || _mysql_fetch_fields == NULL || _mysql_stmt_close == NULL || _mysql_stmt_reset == NULL || _mysql_free_result == NULL || _mysql_error == NULL || _mysql_options == NULL || _mysql_ssl_set == NULL || _mysql_real_connect == NULL || _mysql_close == NULL || _mysql_init == NULL || _mysql_stmt_attr_set == NULL || _mysql_store_result == NULL || _mysql_stmt_errno == NULL || _mysql_errno == NULL || _mysql_num_fields == NULL || _mysql_num_rows == NULL || _mysql_get_host_info == NULL || _mysql_get_server_info == NULL || _mysql_get_proto_info == NULL) return false; return true; } /* * Library load-time initialization, sets on_proc_exit() callback for * backend shutdown. */ void _PG_init(void) { if (!mysql_load_library()) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to load the mysql query: \n%s", dlerror()), errhint("Export LD_LIBRARY_PATH to locate the library."))); DefineCustomIntVariable("mysql_fdw.wait_timeout", "Server-side wait_timeout", "Set the maximum wait_timeout" "use to set the MySQL session timeout", &wait_timeout, WAIT_TIMEOUT, 0, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL); DefineCustomIntVariable("mysql_fdw.interactive_timeout", "Server-side interactive timeout", "Set the maximum interactive timeout" "use to set the MySQL session timeout", &interactive_timeout, INTERACTIVE_TIMEOUT, 0, INT_MAX, PGC_USERSET, 0, NULL, NULL, NULL); on_proc_exit(&mysql_fdw_exit, PointerGetDatum(NULL)); } /* * mysql_fdw_exit * Exit callback function. */ static void mysql_fdw_exit(int code, Datum arg) { mysql_cleanup_connection(); } /* * Foreign-data wrapper handler function: return * a struct with pointers to my callback routines. */ Datum mysql_fdw_handler(PG_FUNCTION_ARGS) { FdwRoutine *fdwroutine = makeNode(FdwRoutine); /* Functions for scanning foreign tables */ fdwroutine->GetForeignRelSize = mysqlGetForeignRelSize; fdwroutine->GetForeignPaths = mysqlGetForeignPaths; fdwroutine->GetForeignPlan = mysqlGetForeignPlan; fdwroutine->BeginForeignScan = mysqlBeginForeignScan; fdwroutine->IterateForeignScan = mysqlIterateForeignScan; fdwroutine->ReScanForeignScan = mysqlReScanForeignScan; fdwroutine->EndForeignScan = mysqlEndForeignScan; /* Functions for updating foreign tables */ fdwroutine->AddForeignUpdateTargets = mysqlAddForeignUpdateTargets; fdwroutine->PlanForeignModify = mysqlPlanForeignModify; fdwroutine->BeginForeignModify = mysqlBeginForeignModify; fdwroutine->ExecForeignInsert = mysqlExecForeignInsert; fdwroutine->ExecForeignUpdate = mysqlExecForeignUpdate; fdwroutine->ExecForeignDelete = mysqlExecForeignDelete; fdwroutine->EndForeignModify = mysqlEndForeignModify; /* Function for EvalPlanQual rechecks */ #if PG_VERSION_NUM >= 90600 fdwroutine->RecheckForeignScan = mysqlRecheckForeignScan; #endif /* Support functions for EXPLAIN */ fdwroutine->ExplainForeignScan = mysqlExplainForeignScan; /* Support functions for ANALYZE */ fdwroutine->AnalyzeForeignTable = mysqlAnalyzeForeignTable; /* Support functions for IMPORT FOREIGN SCHEMA */ #if PG_VERSION_NUM >= 90500 fdwroutine->ImportForeignSchema = mysqlImportForeignSchema; #endif #if PG_VERSION_NUM >= 110000 /* Partition routing and/or COPY from */ fdwroutine->BeginForeignInsert = mysqlBeginForeignInsert; fdwroutine->EndForeignInsert = mysqlEndForeignInsert; #endif #if PG_VERSION_NUM >= 90600 /* Support functions for join push-down */ fdwroutine->GetForeignJoinPaths = mysqlGetForeignJoinPaths; #endif #if PG_VERSION_NUM >= 100000 /* Support functions for upper relation push-down */ fdwroutine->GetForeignUpperPaths = mysqlGetForeignUpperPaths; #endif PG_RETURN_POINTER(fdwroutine); } /* * mysqlBeginForeignScan * Initiate access to the database */ static void mysqlBeginForeignScan(ForeignScanState *node, int eflags) { TupleTableSlot *tupleSlot = node->ss.ss_ScanTupleSlot; TupleDesc tupleDescriptor = tupleSlot->tts_tupleDescriptor; MYSQL *conn; RangeTblEntry *rte; MySQLFdwExecState *festate; EState *estate = node->ss.ps.state; ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; mysql_opt *options; ListCell *lc; int atindex = 0; unsigned long type = (unsigned long) CURSOR_TYPE_READ_ONLY; Oid userid; ForeignServer *server; UserMapping *user; ForeignTable *table; char timeout[255]; int numParams; int rtindex; List *fdw_private = fsplan->fdw_private; /* * We'll save private state in node->fdw_state. */ festate = (MySQLFdwExecState *) palloc(sizeof(MySQLFdwExecState)); node->fdw_state = (void *) festate; /* * If whole-row references are involved in pushed down join extract the * information required to construct those. */ if (list_length(fdw_private) >= mysqlFdwPrivateScanTList) { List *whole_row_lists = list_nth(fdw_private, mysqlFdwPrivateWholeRowLists); List *scan_tlist = list_nth(fdw_private, mysqlFdwPrivateScanTList); #if PG_VERSION_NUM >= 120000 TupleDesc scan_tupdesc = ExecTypeFromTL(scan_tlist); #else TupleDesc scan_tupdesc = ExecTypeFromTL(scan_tlist, false); #endif mysql_build_whole_row_constr_info(festate, tupleDescriptor, fsplan->fs_relids, list_length(node->ss.ps.state->es_range_table), whole_row_lists, scan_tlist, fsplan->fdw_scan_tlist); /* Change tuple descriptor to match the result from foreign server. */ tupleDescriptor = scan_tupdesc; } /* * Identify which user to do the remote access as. This should match what * ExecCheckRTEPerms() does. In case of a join use the lowest-numbered * member RTE as a representative; we would get the same result from any. */ if (fsplan->scan.scanrelid > 0) rtindex = fsplan->scan.scanrelid; else rtindex = bms_next_member(fsplan->fs_relids, -1); rte = rt_fetch(rtindex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); /* Get info about foreign table. */ table = GetForeignTable(rte->relid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch the options */ options = mysql_get_options(rte->relid, true); /* * Get the already connected connection, otherwise connect and get the * connection handle. */ conn = mysql_get_connection(server, user, options); /* Stash away the state info we have already */ festate->query = strVal(list_nth(fsplan->fdw_private, mysqlFdwScanPrivateSelectSql)); festate->retrieved_attrs = list_nth(fsplan->fdw_private, mysqlFdwScanPrivateRetrievedAttrs); festate->conn = conn; festate->query_executed = false; festate->attinmeta = TupleDescGetAttInMetadata(tupleDescriptor); #if PG_VERSION_NUM >= 110000 festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_DEFAULT_SIZES); #else festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); #endif if (wait_timeout > 0) { /* Set the session timeout in seconds */ sprintf(timeout, "SET wait_timeout = %d", wait_timeout); mysql_query(festate->conn, timeout); } if (interactive_timeout > 0) { /* Set the session timeout in seconds */ sprintf(timeout, "SET interactive_timeout = %d", interactive_timeout); mysql_query(festate->conn, timeout); } mysql_query(festate->conn, "SET sql_mode='ANSI_QUOTES'"); /* Initialize the MySQL statement */ festate->stmt = mysql_stmt_init(festate->conn); if (festate->stmt == NULL) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to initialize the mysql query: \n%s", mysql_error(festate->conn)))); /* Prepare MySQL statement */ if (mysql_stmt_prepare(festate->stmt, festate->query, strlen(festate->query)) != 0) mysql_stmt_error_print(festate, "failed to prepare the MySQL query"); /* Prepare for output conversion of parameters used in remote query. */ numParams = list_length(fsplan->fdw_exprs); festate->numParams = numParams; if (numParams > 0) prepare_query_params((PlanState *) node, fsplan->fdw_exprs, numParams, &festate->param_flinfo, &festate->param_exprs, &festate->param_values, &festate->param_types); /* int column_count = mysql_num_fields(festate->meta); */ /* Set the statement as cursor type */ mysql_stmt_attr_set(festate->stmt, STMT_ATTR_CURSOR_TYPE, (void *) &type); /* Set the pre-fetch rows */ mysql_stmt_attr_set(festate->stmt, STMT_ATTR_PREFETCH_ROWS, (void *) &options->fetch_size); festate->table = (mysql_table *) palloc0(sizeof(mysql_table)); festate->table->column = (mysql_column *) palloc0(sizeof(mysql_column) * tupleDescriptor->natts); festate->table->mysql_bind = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * tupleDescriptor->natts); festate->table->mysql_res = mysql_stmt_result_metadata(festate->stmt); if (NULL == festate->table->mysql_res) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to retrieve query result set metadata: \n%s", mysql_error(festate->conn)))); festate->table->mysql_fields = mysql_fetch_fields(festate->table->mysql_res); foreach(lc, festate->retrieved_attrs) { int attnum = lfirst_int(lc) - 1; Oid pgtype = TupleDescAttr(tupleDescriptor, attnum)->atttypid; int32 pgtypmod = TupleDescAttr(tupleDescriptor, attnum)->atttypmod; if (TupleDescAttr(tupleDescriptor, attnum)->attisdropped) continue; festate->table->column[atindex].mysql_bind = &festate->table->mysql_bind[atindex]; mysql_bind_result(pgtype, pgtypmod, &festate->table->mysql_fields[atindex], &festate->table->column[atindex]); atindex++; } /* Bind the results pointers for the prepare statements */ if (mysql_stmt_bind_result(festate->stmt, festate->table->mysql_bind) != 0) mysql_stmt_error_print(festate, "failed to bind the MySQL query"); } /* * mysqlIterateForeignScan * Iterate and get the rows one by one from MySQL and placed in tuple * slot */ static TupleTableSlot * mysqlIterateForeignScan(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; TupleTableSlot *tupleSlot = node->ss.ss_ScanTupleSlot; int attid; ListCell *lc; int rc = 0; Datum *dvalues; bool *nulls; int natts; AttInMetadata *attinmeta = festate->attinmeta; HeapTuple tup; int i; ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; List *fdw_private = fsplan->fdw_private; natts = attinmeta->tupdesc->natts; dvalues = palloc0(natts * sizeof(Datum)); nulls = palloc(natts * sizeof(bool)); /* Initialize to nulls for any columns not present in result */ memset(nulls, true, natts * sizeof(bool)); ExecClearTuple(tupleSlot); /* * If this is the first call after Begin or ReScan, we need to bind the * params and execute the query. */ if (!festate->query_executed) bind_stmt_params_and_exec(node); attid = 0; rc = mysql_stmt_fetch(festate->stmt); if (rc == 0) { foreach(lc, festate->retrieved_attrs) { int attnum = lfirst_int(lc) - 1; Oid pgtype = TupleDescAttr(attinmeta->tupdesc, attnum)->atttypid; int32 pgtypmod = TupleDescAttr(attinmeta->tupdesc, attnum)->atttypmod; nulls[attnum] = festate->table->column[attid].is_null; if (!festate->table->column[attid].is_null) dvalues[attnum] = mysql_convert_to_pg(pgtype, pgtypmod, &festate->table->column[attid]); attid++; } ExecClearTuple(tupleSlot); if (list_length(fdw_private) >= mysqlFdwPrivateScanTList) { /* Construct tuple with whole-row references. */ tup = mysql_get_tuple_with_whole_row(festate, dvalues, nulls); } else { /* Form the Tuple using Datums */ tup = heap_form_tuple(attinmeta->tupdesc, dvalues, nulls); } if (tup) #if PG_VERSION_NUM >= 120000 ExecStoreHeapTuple(tup, tupleSlot, false); #else ExecStoreTuple(tup, tupleSlot, InvalidBuffer, false); #endif else mysql_stmt_close(festate->stmt); /* * Release locally palloc'd space and values of pass-by-reference * datums, as well. */ for (i = 0; i < natts; i++) { if (dvalues[i] && !TupleDescAttr(attinmeta->tupdesc, i)->attbyval) pfree(DatumGetPointer(dvalues[i])); } pfree(dvalues); pfree(nulls); } else if (rc == 1) { /* * Error occurred. Error code and message can be obtained by calling * mysql_stmt_errno() and mysql_stmt_error(). */ } else if (rc == MYSQL_NO_DATA) { /* * No more rows/data exists */ } else if (rc == MYSQL_DATA_TRUNCATED) { /* Data truncation occurred */ /* * MYSQL_DATA_TRUNCATED is returned when truncation reporting is * enabled. To determine which column values were truncated when this * value is returned, check the error members of the MYSQL_BIND * structures used for fetching values. Truncation reporting is * enabled by default, but can be controlled by calling * mysql_options() with the MYSQL_REPORT_DATA_TRUNCATION option. */ } return tupleSlot; } /* * mysqlExplainForeignScan * Produce extra output for EXPLAIN */ static void mysqlExplainForeignScan(ForeignScanState *node, ExplainState *es) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; RangeTblEntry *rte; ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; int rtindex; EState *estate = node->ss.ps.state; List *fdw_private = fsplan->fdw_private; if (fsplan->scan.scanrelid > 0) rtindex = fsplan->scan.scanrelid; else rtindex = bms_next_member(fsplan->fs_relids, -1); rte = rt_fetch(rtindex, estate->es_range_table); if (list_length(fdw_private) > mysqlFdwScanPrivateRelations) { char *relations = strVal(list_nth(fdw_private, mysqlFdwScanPrivateRelations)); ExplainPropertyText("Relations", relations, es); } /* Give some possibly useful info about startup costs */ if (es->costs) { mysql_opt *options = mysql_get_options(rte->relid, true); if (strcmp(options->svr_address, "127.0.0.1") == 0 || strcmp(options->svr_address, "localhost") == 0) #if PG_VERSION_NUM >= 110000 ExplainPropertyInteger("Local server startup cost", NULL, 10, es); #else ExplainPropertyLong("Local server startup cost", 10, es); #endif else #if PG_VERSION_NUM >= 110000 ExplainPropertyInteger("Remote server startup cost", NULL, 25, es); #else ExplainPropertyLong("Remote server startup cost", 25, es); #endif } /* Show the remote query in verbose mode */ if (es->verbose) ExplainPropertyText("Remote query", festate->query, es); } /* * mysqlEndForeignScan * Finish scanning foreign table and dispose objects used for this scan */ static void mysqlEndForeignScan(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; if (festate->table && festate->table->mysql_res) { mysql_free_result(festate->table->mysql_res); festate->table->mysql_res = NULL; } if (festate->stmt) { mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* * mysqlReScanForeignScan * Rescan table, possibly with new parameters */ static void mysqlReScanForeignScan(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; /* * Set the query_executed flag to false so that the query will be executed * in mysqlIterateForeignScan(). */ festate->query_executed = false; } /* * mysqlGetForeignRelSize * Create a FdwPlan for a scan on the foreign table */ static void mysqlGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { double rows = 0; double filtered = 0; MYSQL *conn; MYSQL_ROW row; Bitmapset *attrs_used = NULL; mysql_opt *options; Oid userid = GetUserId(); ForeignServer *server; UserMapping *user; ForeignTable *table; MySQLFdwRelationInfo *fpinfo; ListCell *lc; RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); const char *database; const char *relname; const char *refname; fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); baserel->fdw_private = (void *) fpinfo; /* Base foreign tables need to be push down always. */ fpinfo->pushdown_safe = true; table = GetForeignTable(foreigntableid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch options */ options = mysql_get_options(foreigntableid, true); /* Connect to the server */ conn = mysql_get_connection(server, user, options); mysql_query(conn, "SET sql_mode='ANSI_QUOTES'"); #if PG_VERSION_NUM >= 90600 pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &attrs_used); #else pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &attrs_used); #endif foreach(lc, baserel->baserestrictinfo) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); if (mysql_is_foreign_expr(root, baserel, ri->clause, false)) fpinfo->remote_conds = lappend(fpinfo->remote_conds, ri); else fpinfo->local_conds = lappend(fpinfo->local_conds, ri); } #if PG_VERSION_NUM >= 90600 pull_varattnos((Node *) baserel->reltarget->exprs, baserel->relid, &fpinfo->attrs_used); #else pull_varattnos((Node *) baserel->reltargetlist, baserel->relid, &fpinfo->attrs_used); #endif foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); pull_varattnos((Node *) rinfo->clause, baserel->relid, &fpinfo->attrs_used); } if (options->use_remote_estimate) { StringInfoData sql; MYSQL_RES *result = NULL; List *retrieved_attrs = NULL; initStringInfo(&sql); appendStringInfo(&sql, "EXPLAIN "); mysql_deparse_select_stmt_for_rel(&sql, root, baserel, NULL, fpinfo->remote_conds, &retrieved_attrs, NULL); if (mysql_query(conn, sql.data) != 0) mysql_error_print(conn); result = mysql_store_result(conn); if (result) { int num_fields; /* * MySQL provide numbers of rows per table invole in the statement, * but we don't have problem with it because we are sending * separate query per table in FDW. */ row = mysql_fetch_row(result); num_fields = mysql_num_fields(result); if (row) { MYSQL_FIELD *field; int i; for (i = 0; i < num_fields; i++) { field = mysql_fetch_field(result); if (!row[i]) continue; else if (strcmp(field->name, "rows") == 0) rows = atof(row[i]); else if (strcmp(field->name, "filtered") == 0) filtered = atof(row[i]); } } mysql_free_result(result); } } if (rows > 0) rows = ((rows + 1) * filtered) / 100; else rows = DEFAULTE_NUM_ROWS; baserel->rows = rows; baserel->tuples = rows; /* * Set the name of relation in fpinfo, while we are constructing it here. * It will be used to build the string describing the join relation in * EXPLAIN output. We can't know whether VERBOSE option is specified or * not, so always schema-qualify the foreign table name. */ fpinfo->relation_name = makeStringInfo(); database = options->svr_database; relname = get_rel_name(foreigntableid); refname = rte->eref->aliasname; appendStringInfo(fpinfo->relation_name, "%s.%s", quote_identifier(database), quote_identifier(relname)); if (*refname && strcmp(refname, relname) != 0) appendStringInfo(fpinfo->relation_name, " %s", quote_identifier(rte->eref->aliasname)); } static bool mysql_is_column_unique(Oid foreigntableid) { StringInfoData sql; MYSQL *conn; MYSQL_RES *result; mysql_opt *options; Oid userid = GetUserId(); ForeignServer *server; UserMapping *user; ForeignTable *table; table = GetForeignTable(foreigntableid); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* Fetch the options */ options = mysql_get_options(foreigntableid, true); /* Connect to the server */ conn = mysql_get_connection(server, user, options); /* Build the query */ initStringInfo(&sql); /* * Construct the query by prefixing the database name so that it can lookup * in correct database. */ appendStringInfo(&sql, "EXPLAIN %s.%s", options->svr_database, options->svr_table); if (mysql_query(conn, sql.data) != 0) mysql_error_print(conn); result = mysql_store_result(conn); if (result) { int num_fields = mysql_num_fields(result); MYSQL_ROW row; row = mysql_fetch_row(result); if (row && num_fields > 3) { if ((strcmp(row[3], "PRI") == 0) || (strcmp(row[3], "UNI")) == 0) { mysql_free_result(result); return true; } } mysql_free_result(result); } return false; } /* * mysqlEstimateCosts * Estimate the remote query cost */ static void mysqlEstimateCosts(PlannerInfo *root, RelOptInfo *baserel, Cost *startup_cost, Cost *total_cost, Oid foreigntableid) { mysql_opt *options; /* Fetch options */ options = mysql_get_options(foreigntableid, true); /* Local databases are probably faster */ if (strcmp(options->svr_address, "127.0.0.1") == 0 || strcmp(options->svr_address, "localhost") == 0) *startup_cost = 10; else *startup_cost = 25; *total_cost = baserel->rows + *startup_cost; } /* * mysqlGetForeignPaths * Get the foreign paths */ static void mysqlGetForeignPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { Cost startup_cost; Cost total_cost; /* Estimate costs */ mysqlEstimateCosts(root, baserel, &startup_cost, &total_cost, foreigntableid); /* Create a ForeignPath node and add it as only possible path */ add_path(baserel, (Path *) create_foreignscan_path(root, baserel, #if PG_VERSION_NUM >= 90600 NULL, /* default pathtarget */ #endif baserel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ baserel->lateral_relids, #if PG_VERSION_NUM >= 90500 NULL, /* no extra plan */ #endif NULL)); /* no fdw_private data */ } /* * mysqlGetForeignPlan * Get a foreign scan plan node */ #if PG_VERSION_NUM >= 90500 static ForeignScan * mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses, Plan *outer_plan) #else static ForeignScan * mysqlGetForeignPlan(PlannerInfo *root, RelOptInfo *foreignrel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses) #endif { MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) foreignrel->fdw_private; Index scan_relid; List *fdw_private; List *local_exprs = NIL; List *params_list = NIL; List *remote_conds = NIL; StringInfoData sql; List *retrieved_attrs; ListCell *lc; List *scan_var_list; List *fdw_scan_tlist = NIL; List *whole_row_lists = NIL; if (foreignrel->reloptkind == RELOPT_BASEREL || foreignrel->reloptkind == RELOPT_OTHER_MEMBER_REL) scan_relid = foreignrel->relid; else { scan_relid = 0; Assert(!scan_clauses); remote_conds = fpinfo->remote_conds; local_exprs = extract_actual_clauses(fpinfo->local_conds, false); } /* * Separate the scan_clauses into those that can be executed remotely and * those that can't. baserestrictinfo clauses that were previously * determined to be safe or unsafe are shown in fpinfo->remote_conds and * fpinfo->local_conds. Anything else in the scan_clauses list will be * a join clause, which we have to check for remote-safety. * * This code must match "extract_actual_clauses(scan_clauses, false)" * except for the additional decision about remote versus local execution. * Note however that we only strip the RestrictInfo nodes from the * local_exprs list, since appendWhereClause expects a list of * RestrictInfos. */ foreach(lc, scan_clauses) { RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc); Assert(IsA(rinfo, RestrictInfo)); /* Ignore any pseudoconstants, they're dealt with elsewhere */ if (rinfo->pseudoconstant) continue; if (list_member_ptr(fpinfo->remote_conds, rinfo)) remote_conds = lappend(remote_conds, rinfo); else if (list_member_ptr(fpinfo->local_conds, rinfo)) local_exprs = lappend(local_exprs, rinfo->clause); else if (mysql_is_foreign_expr(root, foreignrel, rinfo->clause, false)) remote_conds = lappend(remote_conds, rinfo); else local_exprs = lappend(local_exprs, rinfo->clause); } #if PG_VERSION_NUM >= 100000 if (IS_UPPER_REL(foreignrel)) scan_var_list = pull_var_clause((Node *) fpinfo->grouped_tlist, PVC_RECURSE_AGGREGATES); else #endif scan_var_list = pull_var_clause((Node *) foreignrel->reltarget->exprs, PVC_RECURSE_PLACEHOLDERS); /* System attributes are not allowed. */ foreach(lc, scan_var_list) { Var *var = lfirst(lc); const FormData_pg_attribute *attr; Assert(IsA(var, Var)); if (var->varattno >= 0) continue; #if PG_VERSION_NUM >= 120000 attr = SystemAttributeDefinition(var->varattno); #else attr = SystemAttributeDefinition(var->varattno, false); #endif ereport(ERROR, (errcode(ERRCODE_FDW_COLUMN_NAME_NOT_FOUND), errmsg("system attribute \"%s\" can't be fetched from remote relation", attr->attname.data))); } #if PG_VERSION_NUM >= 90600 #if PG_VERSION_NUM >= 100000 if (IS_JOIN_REL(foreignrel)) #else if (foreignrel->reloptkind == RELOPT_JOINREL) #endif { scan_var_list = list_concat_unique(NIL, scan_var_list); scan_var_list = list_concat_unique(scan_var_list, pull_var_clause((Node *) local_exprs, PVC_RECURSE_PLACEHOLDERS)); /* * For join relations, planner needs targetlist, which represents the * output of ForeignScan node. Prepare this before we modify * scan_var_list to include Vars required by whole row references, if * any. Note that base foreign scan constructs the whole-row reference * at the time of projection. Joins are required to get them from the * underlying base relations. For a pushed down join the underlying * relations do not exist, hence the whole-row references need to be * constructed separately. */ fdw_scan_tlist = add_to_flat_tlist(NIL, scan_var_list); /* * MySQL does not allow row value constructors to be part of SELECT * list. Hence, whole row reference in join relations need to be * constructed by combining all the attributes of required base * relations into a tuple after fetching the result from the foreign * server. So adjust the targetlist to include all attributes for * required base relations. The function also returns list of Var node * lists required to construct the whole-row references of the * involved relations. */ scan_var_list = mysql_adjust_whole_row_ref(root, scan_var_list, &whole_row_lists, foreignrel->relids); if (outer_plan) { ListCell *lc; /* * Right now, we only consider grouping and aggregation beyond * joins. Queries involving aggregates or grouping do not require * EPQ mechanism, hence should not have an outer plan here. */ #if PG_VERSION_NUM >= 100000 Assert(!IS_UPPER_REL(foreignrel)); #endif foreach(lc, local_exprs) { Node *qual = lfirst(lc); outer_plan->qual = list_delete(outer_plan->qual, qual); /* * For an inner join the local conditions of foreign scan plan * can be part of the joinquals as well. (They might also be * in the mergequals or hashquals, but we can't touch those * without breaking the plan.) */ if (IsA(outer_plan, NestLoop) || IsA(outer_plan, MergeJoin) || IsA(outer_plan, HashJoin)) { Join *join_plan = (Join *) outer_plan; if (join_plan->jointype == JOIN_INNER) join_plan->joinqual = list_delete(join_plan->joinqual, qual); } } } } #endif /* PG_VERSION_NUM >= 90600 */ #if PG_VERSION_NUM >= 100000 else if (IS_UPPER_REL(foreignrel)) { /* * scan_var_list should have expressions and not TargetEntry nodes. * However grouped_tlist created has TLEs, thus retrieve them into * scan_var_list. */ scan_var_list = list_concat_unique(NIL, get_tlist_exprs(fpinfo->grouped_tlist, false)); /* * The targetlist computed while assessing push-down safety represents * the result we expect from the foreign server. */ fdw_scan_tlist = fpinfo->grouped_tlist; local_exprs = extract_actual_clauses(fpinfo->local_conds, false); } #endif /* * Build the query string to be sent for execution, and identify * expressions to be sent as parameters. */ initStringInfo(&sql); mysql_deparse_select_stmt_for_rel(&sql, root, foreignrel, scan_var_list, remote_conds, &retrieved_attrs, ¶ms_list); #if PG_VERSION_NUM >= 140000 if (bms_is_member(foreignrel->relid, root->all_result_relids) && #else if (foreignrel->relid == root->parse->resultRelation && #endif (root->parse->commandType == CMD_UPDATE || root->parse->commandType == CMD_DELETE)) { /* Relation is UPDATE/DELETE target, so use FOR UPDATE */ appendStringInfoString(&sql, " FOR UPDATE"); } /* * Build the fdw_private list that will be available to the executor. * Items in the list must match enum FdwScanPrivateIndex, above. */ fdw_private = list_make2(makeString(sql.data), retrieved_attrs); #if PG_VERSION_NUM >= 100000 if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel)) #else if (foreignrel->reloptkind == RELOPT_JOINREL || foreignrel->reloptkind == RELOPT_UPPER_REL) #endif { fdw_private = lappend(fdw_private, makeString(fpinfo->relation_name->data)); /* * To construct whole row references we need: * * 1. The lists of Var nodes required for whole-row references of * joining relations * 2. targetlist corresponding the result expected from the foreign * server. */ if (whole_row_lists) { fdw_private = lappend(fdw_private, whole_row_lists); fdw_private = lappend(fdw_private, add_to_flat_tlist(NIL, scan_var_list)); } } /* * Create the ForeignScan node from target list, local filtering * expressions, remote parameter expressions, and FDW private information. * * Note that the remote parameter expressions are stored in the fdw_exprs * field of the finished plan node; we can't keep them in private state * because then they wouldn't be subject to later planner processing. */ #if PG_VERSION_NUM >= 90500 return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private, fdw_scan_tlist, NIL, outer_plan); #else return make_foreignscan(tlist, local_exprs, scan_relid, params_list, fdw_private); #endif } /* * mysqlAnalyzeForeignTable * Implement stats collection */ static bool mysqlAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages) { StringInfoData sql; double table_size = 0; MYSQL *conn; MYSQL_RES *result; Oid foreignTableId = RelationGetRelid(relation); mysql_opt *options; ForeignServer *server; UserMapping *user; ForeignTable *table; table = GetForeignTable(foreignTableId); server = GetForeignServer(table->serverid); user = GetUserMapping(relation->rd_rel->relowner, server->serverid); /* Fetch options */ options = mysql_get_options(foreignTableId, true); Assert(options->svr_database != NULL && options->svr_table != NULL); /* Connect to the server */ conn = mysql_get_connection(server, user, options); /* Build the query */ initStringInfo(&sql); mysql_deparse_analyze(&sql, options->svr_database, options->svr_table); if (mysql_query(conn, sql.data) != 0) mysql_error_print(conn); result = mysql_store_result(conn); /* * To get the table size in ANALYZE operation, we run a SELECT query by * passing the database name and table name. So if the remote table is not * present, then we end up getting zero rows. Throw an error in that case. */ if (mysql_num_rows(result) == 0) ereport(ERROR, (errcode(ERRCODE_FDW_TABLE_NOT_FOUND), errmsg("relation %s.%s does not exist", options->svr_database, options->svr_table))); if (result) { MYSQL_ROW row; row = mysql_fetch_row(result); table_size = atof(row[0]); mysql_free_result(result); } *totalpages = table_size / MYSQL_BLKSIZ; return false; } static List * mysqlPlanForeignModify(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index) { CmdType operation = plan->operation; RangeTblEntry *rte = planner_rt_fetch(resultRelation, root); Relation rel; List *targetAttrs = NIL; StringInfoData sql; char *attname; Oid foreignTableId; initStringInfo(&sql); /* * Core code already has some lock on each rel being planned, so we can * use NoLock here. */ #if PG_VERSION_NUM < 130000 rel = heap_open(rte->relid, NoLock); #else rel = table_open(rte->relid, NoLock); #endif foreignTableId = RelationGetRelid(rel); if (!mysql_is_column_unique(foreignTableId)) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("first column of remote table must be unique for INSERT/UPDATE/DELETE operation"))); /* * In an INSERT, we transmit all columns that are defined in the foreign * table. In an UPDATE, if there are BEFORE ROW UPDATE triggers on the * foreign table, we transmit all columns like INSERT; else we transmit * only columns that were explicitly targets of the UPDATE, so as to avoid * unnecessary data transmission. (We can't do that for INSERT since we * would miss sending default values for columns not listed in the source * statement, and for UPDATE if there are BEFORE ROW UPDATE triggers since * those triggers might change values for non-target columns, in which * case we would miss sending changed values for those columns.) */ if (operation == CMD_INSERT || (operation == CMD_UPDATE && rel->trigdesc && rel->trigdesc->trig_update_before_row)) { TupleDesc tupdesc = RelationGetDescr(rel); int attnum; /* * If it is an UPDATE operation, check for row identifier column in * target attribute list by calling getUpdateTargetAttrs(). */ if (operation == CMD_UPDATE) getUpdateTargetAttrs(rte); for (attnum = 1; attnum <= tupdesc->natts; attnum++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); if (!attr->attisdropped) targetAttrs = lappend_int(targetAttrs, attnum); } } else if (operation == CMD_UPDATE) { targetAttrs = getUpdateTargetAttrs(rte); /* We also want the rowid column to be available for the update */ targetAttrs = lcons_int(1, targetAttrs); } else targetAttrs = lcons_int(1, targetAttrs); #if PG_VERSION_NUM >= 110000 attname = get_attname(foreignTableId, 1, false); #else attname = get_relid_attribute_name(foreignTableId, 1); #endif /* * Construct the SQL command string. */ switch (operation) { case CMD_INSERT: mysql_deparse_insert(&sql, root, resultRelation, rel, targetAttrs); break; case CMD_UPDATE: mysql_deparse_update(&sql, root, resultRelation, rel, targetAttrs, attname); break; case CMD_DELETE: mysql_deparse_delete(&sql, root, resultRelation, rel, attname); break; default: elog(ERROR, "unexpected operation: %d", (int) operation); break; } if (plan->returningLists) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("RETURNING is not supported by this FDW"))); #if PG_VERSION_NUM < 130000 heap_close(rel, NoLock); #else table_close(rel, NoLock); #endif return list_make2(makeString(sql.data), targetAttrs); } /* * mysqlBeginForeignModify * Begin an insert/update/delete operation on a foreign table */ static void mysqlBeginForeignModify(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo, List *fdw_private, int subplan_index, int eflags) { MySQLFdwExecState *fmstate; EState *estate = mtstate->ps.state; Relation rel = resultRelInfo->ri_RelationDesc; AttrNumber n_params; Oid typefnoid = InvalidOid; bool isvarlena = false; ListCell *lc; Oid foreignTableId = InvalidOid; RangeTblEntry *rte; Oid userid; ForeignServer *server; UserMapping *user; ForeignTable *table; rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table); userid = rte->checkAsUser ? rte->checkAsUser : GetUserId(); foreignTableId = RelationGetRelid(rel); table = GetForeignTable(foreignTableId); server = GetForeignServer(table->serverid); user = GetUserMapping(userid, server->serverid); /* * Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState * stays NULL. */ if (eflags & EXEC_FLAG_EXPLAIN_ONLY) return; /* Begin constructing MySQLFdwExecState. */ fmstate = (MySQLFdwExecState *) palloc0(sizeof(MySQLFdwExecState)); fmstate->mysqlFdwOptions = mysql_get_options(foreignTableId, true); fmstate->conn = mysql_get_connection(server, user, fmstate->mysqlFdwOptions); fmstate->query = strVal(list_nth(fdw_private, 0)); fmstate->retrieved_attrs = (List *) list_nth(fdw_private, 1); n_params = list_length(fmstate->retrieved_attrs) + 1; fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params); fmstate->p_nums = 0; #if PG_VERSION_NUM >= 110000 fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_DEFAULT_SIZES); #else fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, "mysql_fdw temporary data", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); #endif if (mtstate->operation == CMD_UPDATE) { Form_pg_attribute attr; #if PG_VERSION_NUM >= 140000 Plan *subplan = outerPlanState(mtstate)->plan; #else Plan *subplan = mtstate->mt_plans[subplan_index]->plan; #endif Assert(subplan != NULL); attr = TupleDescAttr(RelationGetDescr(rel), 0); /* Find the rowid resjunk column in the subplan's result */ fmstate->rowidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist, NameStr(attr->attname)); if (!AttributeNumberIsValid(fmstate->rowidAttno)) elog(ERROR, "could not find junk row identifier column"); } /* Set up for remaining transmittable parameters */ foreach(lc, fmstate->retrieved_attrs) { int attnum = lfirst_int(lc); Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(rel), attnum - 1); Assert(!attr->attisdropped); getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena); fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]); fmstate->p_nums++; } Assert(fmstate->p_nums <= n_params); n_params = list_length(fmstate->retrieved_attrs); /* Initialize mysql statment */ fmstate->stmt = mysql_stmt_init(fmstate->conn); if (!fmstate->stmt) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to initialize the MySQL query: \n%s", mysql_error(fmstate->conn)))); /* Prepare mysql statment */ if (mysql_stmt_prepare(fmstate->stmt, fmstate->query, strlen(fmstate->query)) != 0) mysql_stmt_error_print(fmstate, "failed to prepare the MySQL query"); resultRelInfo->ri_FdwState = fmstate; } /* * mysqlExecForeignInsert * Insert one row into a foreign table */ static TupleTableSlot * mysqlExecForeignInsert(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate; MYSQL_BIND *mysql_bind_buffer; ListCell *lc; int n_params; MemoryContext oldcontext; bool *isnull; fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; n_params = list_length(fmstate->retrieved_attrs); oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt); mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * n_params); isnull = (bool *) palloc0(sizeof(bool) * n_params); mysql_query(fmstate->conn, "SET sql_mode='ANSI_QUOTES'"); foreach(lc, fmstate->retrieved_attrs) { int attnum = lfirst_int(lc) - 1; Oid type = TupleDescAttr(slot->tts_tupleDescriptor, attnum)->atttypid; Datum value; value = slot_getattr(slot, attnum + 1, &isnull[attnum]); mysql_bind_sql_var(type, attnum, value, mysql_bind_buffer, &isnull[attnum]); } /* Bind values */ if (mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) mysql_stmt_error_print(fmstate, "failed to bind the MySQL query"); /* Execute the query */ if (mysql_stmt_execute(fmstate->stmt) != 0) mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); MemoryContextSwitchTo(oldcontext); MemoryContextReset(fmstate->temp_cxt); return slot; } static TupleTableSlot * mysqlExecForeignUpdate(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; Relation rel = resultRelInfo->ri_RelationDesc; MYSQL_BIND *mysql_bind_buffer; Oid foreignTableId = RelationGetRelid(rel); bool is_null = false; ListCell *lc; int bindnum = 0; Oid typeoid; Datum value; int n_params; bool *isnull; Datum new_value; HeapTuple tuple; Form_pg_attribute attr; bool found_row_id_col = false; n_params = list_length(fmstate->retrieved_attrs); mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * n_params); isnull = (bool *) palloc0(sizeof(bool) * n_params); /* Bind the values */ foreach(lc, fmstate->retrieved_attrs) { int attnum = lfirst_int(lc); Oid type; /* * The first attribute cannot be in the target list attribute. Set the * found_row_id_col to true once we find it so that we can fetch the * value later. */ if (attnum == 1) { found_row_id_col = true; continue; } type = TupleDescAttr(slot->tts_tupleDescriptor, attnum - 1)->atttypid; value = slot_getattr(slot, attnum, (bool *) (&isnull[bindnum])); mysql_bind_sql_var(type, bindnum, value, mysql_bind_buffer, &isnull[bindnum]); bindnum++; } /* * Since we add a row identifier column in the target list always, so * found_row_id_col flag should be true. */ if (!found_row_id_col) elog(ERROR, "missing row identifier column value in UPDATE"); new_value = slot_getattr(slot, 1, &is_null); /* * Get the row identifier column value that was passed up as a resjunk * column and compare that value with the new value to identify if that * value is changed. */ value = ExecGetJunkAttribute(planSlot, fmstate->rowidAttno, &is_null); tuple = SearchSysCache2(ATTNUM, ObjectIdGetDatum(foreignTableId), Int16GetDatum(1)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for attribute %d of relation %u", 1, foreignTableId); attr = (Form_pg_attribute) GETSTRUCT(tuple); typeoid = attr->atttypid; if (DatumGetPointer(new_value) != NULL && DatumGetPointer(value) != NULL) { Datum n_value = new_value; Datum o_value = value; /* If the attribute type is varlena then need to detoast the datums. */ if (attr->attlen == -1) { n_value = PointerGetDatum(PG_DETOAST_DATUM(new_value)); o_value = PointerGetDatum(PG_DETOAST_DATUM(value)); } if (!datumIsEqual(o_value, n_value, attr->attbyval, attr->attlen)) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("row identifier column update is not supported"))); /* Free memory if it's a copy made above */ if (DatumGetPointer(n_value) != DatumGetPointer(new_value)) pfree(DatumGetPointer(n_value)); if (DatumGetPointer(o_value) != DatumGetPointer(value)) pfree(DatumGetPointer(o_value)); } else if (!(DatumGetPointer(new_value) == NULL && DatumGetPointer(value) == NULL)) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("row identifier column update is not supported"))); ReleaseSysCache(tuple); /* Bind qual */ mysql_bind_sql_var(typeoid, bindnum, value, mysql_bind_buffer, &is_null); if (mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to bind the MySQL query: %s", mysql_error(fmstate->conn)))); /* Execute the query */ if (mysql_stmt_execute(fmstate->stmt) != 0) mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); /* Return NULL if nothing was updated on the remote end */ return slot; } /* * mysqlAddForeignUpdateTargets * Add column(s) needed for update/delete on a foreign table, we are * using first column as row identification column, so we are adding * that into target list. */ #if PG_VERSION_NUM >= 140000 static void mysqlAddForeignUpdateTargets(PlannerInfo *root, Index rtindex, RangeTblEntry *target_rte, Relation target_relation) #else static void mysqlAddForeignUpdateTargets(Query *parsetree, RangeTblEntry *target_rte, Relation target_relation) #endif { Var *var; const char *attrname; #if PG_VERSION_NUM < 140000 TargetEntry *tle; #endif /* * What we need is the rowid which is the first column */ Form_pg_attribute attr = TupleDescAttr(RelationGetDescr(target_relation), 0); /* Make a Var representing the desired value */ #if PG_VERSION_NUM >= 140000 var = makeVar(rtindex, #else var = makeVar(parsetree->resultRelation, #endif 1, attr->atttypid, attr->atttypmod, InvalidOid, 0); /* Get name of the row identifier column */ attrname = NameStr(attr->attname); #if PG_VERSION_NUM >= 140000 /* Register it as a row-identity column needed by this target rel */ add_row_identity_var(root, var, rtindex, attrname); #else /* Wrap it in a TLE with the right name ... */ tle = makeTargetEntry((Expr *) var, list_length(parsetree->targetList) + 1, pstrdup(attrname), true); /* ... and add it to the query's targetlist */ parsetree->targetList = lappend(parsetree->targetList, tle); #endif } /* * mysqlExecForeignDelete * Delete one row from a foreign table */ static TupleTableSlot * mysqlExecForeignDelete(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { MySQLFdwExecState *fmstate = (MySQLFdwExecState *) resultRelInfo->ri_FdwState; Relation rel = resultRelInfo->ri_RelationDesc; MYSQL_BIND *mysql_bind_buffer; Oid foreignTableId = RelationGetRelid(rel); bool is_null = false; Oid typeoid; Datum value; mysql_bind_buffer = (MYSQL_BIND *) palloc(sizeof(MYSQL_BIND)); /* Get the id that was passed up as a resjunk column */ value = ExecGetJunkAttribute(planSlot, 1, &is_null); typeoid = get_atttype(foreignTableId, 1); /* Bind qual */ mysql_bind_sql_var(typeoid, 0, value, mysql_bind_buffer, &is_null); if (mysql_stmt_bind_param(fmstate->stmt, mysql_bind_buffer) != 0) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to execute the MySQL query: %s", mysql_error(fmstate->conn)))); /* Execute the query */ if (mysql_stmt_execute(fmstate->stmt) != 0) mysql_stmt_error_print(fmstate, "failed to execute the MySQL query"); /* Return NULL if nothing was updated on the remote end */ return slot; } /* * mysqlEndForeignModify * Finish an insert/update/delete operation on a foreign table */ static void mysqlEndForeignModify(EState *estate, ResultRelInfo *resultRelInfo) { MySQLFdwExecState *festate = resultRelInfo->ri_FdwState; if (festate && festate->stmt) { mysql_stmt_close(festate->stmt); festate->stmt = NULL; } } /* * mysqlImportForeignSchema * Import a foreign schema (9.5+) */ #if PG_VERSION_NUM >= 90500 static List * mysqlImportForeignSchema(ImportForeignSchemaStmt *stmt, Oid serverOid) { List *commands = NIL; bool import_default = false; bool import_not_null = true; ForeignServer *server; UserMapping *user; mysql_opt *options; MYSQL *conn; StringInfoData buf; MYSQL_RES *volatile res = NULL; MYSQL_ROW row; ListCell *lc; /* Parse statement options */ foreach(lc, stmt->options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "import_default") == 0) import_default = defGetBoolean(def); else if (strcmp(def->defname, "import_not_null") == 0) import_not_null = defGetBoolean(def); else ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname))); } /* * Get connection to the foreign server. Connection manager will * establish new connection if necessary. */ server = GetForeignServer(serverOid); user = GetUserMapping(GetUserId(), server->serverid); options = mysql_get_options(serverOid, false); conn = mysql_get_connection(server, user, options); /* Create workspace for strings */ initStringInfo(&buf); /* Check that the schema really exists */ appendStringInfo(&buf, "SELECT 1 FROM information_schema.TABLES WHERE TABLE_SCHEMA = '%s'", stmt->remote_schema); if (mysql_query(conn, buf.data) != 0) mysql_error_print(conn); res = mysql_store_result(conn); if (!res || mysql_num_rows(res) < 1) ereport(ERROR, (errcode(ERRCODE_FDW_SCHEMA_NOT_FOUND), errmsg("schema \"%s\" is not present on foreign server \"%s\"", stmt->remote_schema, server->servername))); mysql_free_result(res); res = NULL; resetStringInfo(&buf); /* * Fetch all table data from this schema, possibly restricted by EXCEPT or * LIMIT TO. */ appendStringInfo(&buf, " SELECT" " t.TABLE_NAME," " c.COLUMN_NAME," " CASE" " WHEN c.DATA_TYPE = 'enum' THEN LOWER(CONCAT(t.TABLE_NAME, '_', c.COLUMN_NAME, '_t'))" " WHEN c.DATA_TYPE = 'tinyint' THEN 'smallint'" " WHEN c.DATA_TYPE = 'mediumint' THEN 'integer'" " WHEN c.DATA_TYPE = 'tinyint unsigned' THEN 'smallint'" " WHEN c.DATA_TYPE = 'smallint unsigned' THEN 'integer'" " WHEN c.DATA_TYPE = 'mediumint unsigned' THEN 'integer'" " WHEN c.DATA_TYPE = 'int unsigned' THEN 'bigint'" " WHEN c.DATA_TYPE = 'bigint unsigned' THEN 'numeric(20)'" " WHEN c.DATA_TYPE = 'double' THEN 'double precision'" " WHEN c.DATA_TYPE = 'float' THEN 'real'" " WHEN c.DATA_TYPE = 'datetime' THEN 'timestamp'" " WHEN c.DATA_TYPE = 'longtext' THEN 'text'" " WHEN c.DATA_TYPE = 'mediumtext' THEN 'text'" " WHEN c.DATA_TYPE = 'tinytext' THEN 'text'" " WHEN c.DATA_TYPE = 'blob' THEN 'bytea'" " WHEN c.DATA_TYPE = 'mediumblob' THEN 'bytea'" " WHEN c.DATA_TYPE = 'longblob' THEN 'bytea'" " WHEN c.DATA_TYPE = 'binary' THEN 'bytea'" " WHEN c.DATA_TYPE = 'varbinary' THEN 'bytea'" " ELSE c.DATA_TYPE" " END," " c.COLUMN_TYPE," " IF(c.IS_NULLABLE = 'NO', 't', 'f')," " c.COLUMN_DEFAULT" " FROM" " information_schema.TABLES AS t" " JOIN" " information_schema.COLUMNS AS c" " ON" " t.TABLE_CATALOG <=> c.TABLE_CATALOG AND t.TABLE_SCHEMA <=> c.TABLE_SCHEMA AND t.TABLE_NAME <=> c.TABLE_NAME" " WHERE" " t.TABLE_SCHEMA = '%s'", stmt->remote_schema); /* Apply restrictions for LIMIT TO and EXCEPT */ if (stmt->list_type == FDW_IMPORT_SCHEMA_LIMIT_TO || stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) { bool first_item = true; appendStringInfoString(&buf, " AND t.TABLE_NAME "); if (stmt->list_type == FDW_IMPORT_SCHEMA_EXCEPT) appendStringInfoString(&buf, "NOT "); appendStringInfoString(&buf, "IN ("); /* Append list of table names within IN clause */ foreach(lc, stmt->table_list) { RangeVar *rv = (RangeVar *) lfirst(lc); if (first_item) first_item = false; else appendStringInfoString(&buf, ", "); appendStringInfo(&buf, "'%s'", rv->relname); } appendStringInfoChar(&buf, ')'); } /* Append ORDER BY at the end of query to ensure output ordering */ appendStringInfo(&buf, " ORDER BY t.TABLE_NAME, c.ORDINAL_POSITION"); /* Fetch the data */ if (mysql_query(conn, buf.data) != 0) mysql_error_print(conn); res = mysql_store_result(conn); row = mysql_fetch_row(res); while (row) { char *tablename = row[0]; bool first_item = true; bool has_set = false; resetStringInfo(&buf); appendStringInfo(&buf, "CREATE FOREIGN TABLE %s (\n", quote_identifier(tablename)); /* Scan all rows for this table */ do { char *attname; char *typename; char *typedfn; char *attnotnull; char *attdefault; /* * If the table has no columns, we'll see nulls here. Also, if we * have already discovered this table has a SET type column, we * better skip the rest of the checking. */ if (row[1] == NULL || has_set) continue; attname = row[1]; typename = row[2]; if (strcmp(typename, "char") == 0 || strcmp(typename, "varchar") == 0) typename = row[3]; typedfn = row[3]; attnotnull = row[4]; attdefault = row[5] == NULL ? (char *) NULL : row[5]; if (strncmp(typedfn, "enum(", 5) == 0) ereport(NOTICE, (errmsg("error while generating the table definition"), errhint("If you encounter an error, you may need to execute the following first:\nDO $$BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_type WHERE typname = '%s') THEN CREATE TYPE %s AS %s; END IF; END$$;\n", typename, typename, typedfn))); /* * PostgreSQL does not have an equivalent data type to map with * SET, so skip the table definitions for the ones having SET type * column. */ if (strncmp(typedfn, "set", 3) == 0) { ereport(WARNING, (errmsg("skipping import for relation \"%s\"", quote_identifier(tablename)), errdetail("MySQL SET columns are not supported."))); has_set = true; continue; } if (first_item) first_item = false; else appendStringInfoString(&buf, ",\n"); /* Print column name and type */ appendStringInfo(&buf, " %s %s", quote_identifier(attname), typename); /* Add DEFAULT if needed */ if (import_default && attdefault != NULL) appendStringInfo(&buf, " DEFAULT %s", attdefault); /* Add NOT NULL if needed */ if (import_not_null && attnotnull[0] == 't') appendStringInfoString(&buf, " NOT NULL"); } while ((row = mysql_fetch_row(res)) && (strcmp(row[0], tablename) == 0)); /* * As explained above, skip importing relations that have SET type * column. */ if (has_set) continue; /* * Add server name and table-level options. We specify remote * database and table name as options (the latter to ensure that * renaming the foreign table doesn't break the association). */ appendStringInfo(&buf, "\n) SERVER %s OPTIONS (dbname '%s', table_name '%s');\n", quote_identifier(server->servername), stmt->remote_schema, tablename); commands = lappend(commands, pstrdup(buf.data)); } /* Clean up */ mysql_free_result(res); res = NULL; resetStringInfo(&buf); mysql_release_connection(conn); return commands; } #endif #if PG_VERSION_NUM >= 110000 /* * mysqlBeginForeignInsert * Prepare for an insert operation triggered by partition routing * or COPY FROM. * * This is not yet supported, so raise an error. */ static void mysqlBeginForeignInsert(ModifyTableState *mtstate, ResultRelInfo *resultRelInfo) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("COPY and foreign partition routing not supported in mysql_fdw"))); } /* * mysqlEndForeignInsert * BeginForeignInsert() is not yet implemented, hence we do not * have anything to cleanup as of now. We throw an error here just * to make sure when we do that we do not forget to cleanup * resources. */ static void mysqlEndForeignInsert(EState *estate, ResultRelInfo *resultRelInfo) { ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("COPY and foreign partition routing not supported in mysql_fdw"))); } #endif /* * Prepare for processing of parameters used in remote query. */ static void prepare_query_params(PlanState *node, List *fdw_exprs, int numParams, FmgrInfo **param_flinfo, List **param_exprs, const char ***param_values, Oid **param_types) { int i; ListCell *lc; Assert(numParams > 0); /* Prepare for output conversion of parameters used in remote query. */ *param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * numParams); *param_types = (Oid *) palloc0(sizeof(Oid) * numParams); i = 0; foreach(lc, fdw_exprs) { Node *param_expr = (Node *) lfirst(lc); Oid typefnoid; bool isvarlena; (*param_types)[i] = exprType(param_expr); getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena); fmgr_info(typefnoid, &(*param_flinfo)[i]); i++; } /* * Prepare remote-parameter expressions for evaluation. (Note: in * practice, we expect that all these expressions will be just Params, so * we could possibly do something more efficient than using the full * expression-eval machinery for this. But probably there would be little * benefit, and it'd require postgres_fdw to know more than is desirable * about Param evaluation.) */ #if PG_VERSION_NUM >= 100000 *param_exprs = ExecInitExprList(fdw_exprs, node); #else *param_exprs = (List *) ExecInitExpr((Expr *) fdw_exprs, node); #endif /* Allocate buffer for text form of query parameters. */ *param_values = (const char **) palloc0(numParams * sizeof(char *)); } /* * Construct array of query parameter values in text format. */ static void process_query_params(ExprContext *econtext, FmgrInfo *param_flinfo, List *param_exprs, const char **param_values, MYSQL_BIND **mysql_bind_buf, Oid *param_types) { int i; ListCell *lc; i = 0; foreach(lc, param_exprs) { ExprState *expr_state = (ExprState *) lfirst(lc); Datum expr_value; bool isNull; /* Evaluate the parameter expression */ #if PG_VERSION_NUM >= 100000 expr_value = ExecEvalExpr(expr_state, econtext, &isNull); #else expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL); #endif mysql_bind_sql_var(param_types[i], i, expr_value, *mysql_bind_buf, &isNull); /* * Get string representation of each parameter value by invoking * type-specific output function, unless the value is null. */ if (isNull) param_values[i] = NULL; else param_values[i] = OutputFunctionCall(¶m_flinfo[i], expr_value); i++; } } /* * Process the query params and bind the same with the statement, if any. * Also, execute the statement. */ static void bind_stmt_params_and_exec(ForeignScanState *node) { MySQLFdwExecState *festate = (MySQLFdwExecState *) node->fdw_state; ExprContext *econtext = node->ss.ps.ps_ExprContext; int numParams = festate->numParams; const char **values = festate->param_values; MYSQL_BIND *mysql_bind_buffer = NULL; /* * Construct array of query parameter values in text format. We do the * conversions in the short-lived per-tuple context, so as not to cause a * memory leak over repeated scans. */ if (numParams > 0) { MemoryContext oldcontext; oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); mysql_bind_buffer = (MYSQL_BIND *) palloc0(sizeof(MYSQL_BIND) * numParams); process_query_params(econtext, festate->param_flinfo, festate->param_exprs, values, &mysql_bind_buffer, festate->param_types); mysql_stmt_bind_param(festate->stmt, mysql_bind_buffer); MemoryContextSwitchTo(oldcontext); } /* * Finally, execute the query. The result will be placed in the array we * already bind. */ if (mysql_stmt_execute(festate->stmt) != 0) mysql_stmt_error_print(festate, "failed to execute the MySQL query"); /* Mark the query as executed */ festate->query_executed = true; } Datum mysql_fdw_version(PG_FUNCTION_ARGS) { PG_RETURN_INT32(CODE_VERSION); } static void mysql_error_print(MYSQL *conn) { switch (mysql_errno(conn)) { case CR_NO_ERROR: /* Should not happen, though give some message */ elog(ERROR, "unexpected error code"); break; case CR_OUT_OF_MEMORY: case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: case CR_UNKNOWN_ERROR: mysql_release_connection(conn); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to execute the MySQL query: \n%s", mysql_error(conn)))); break; case CR_COMMANDS_OUT_OF_SYNC: default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("failed to execute the MySQL query: \n%s", mysql_error(conn)))); } } static void mysql_stmt_error_print(MySQLFdwExecState *festate, const char *msg) { switch (mysql_stmt_errno(festate->stmt)) { case CR_NO_ERROR: /* Should not happen, though give some message */ elog(ERROR, "unexpected error code"); break; case CR_OUT_OF_MEMORY: case CR_SERVER_GONE_ERROR: case CR_SERVER_LOST: case CR_UNKNOWN_ERROR: mysql_release_connection(festate->conn); ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("%s: \n%s", msg, mysql_error(festate->conn)))); break; case CR_COMMANDS_OUT_OF_SYNC: default: ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("%s: \n%s", msg, mysql_error(festate->conn)))); break; } } /* * getUpdateTargetAttrs * Returns the list of attribute numbers of the columns being updated. */ static List * getUpdateTargetAttrs(RangeTblEntry *rte) { List *targetAttrs = NIL; #if PG_VERSION_NUM >= 90500 Bitmapset *tmpset = bms_copy(rte->updatedCols); #else Bitmapset *tmpset = bms_copy(rte->modifiedCols); #endif AttrNumber col; while ((col = bms_first_member(tmpset)) >= 0) { col += FirstLowInvalidHeapAttributeNumber; if (col <= InvalidAttrNumber) /* shouldn't happen */ elog(ERROR, "system-column update is not supported"); /* We also disallow updates to the first column */ if (col == 1) ereport(ERROR, (errcode(ERRCODE_FDW_UNABLE_TO_CREATE_EXECUTION), errmsg("row identifier column update is not supported"))); targetAttrs = lappend_int(targetAttrs, col); } return targetAttrs; } /* * mysqlGetForeignJoinPaths * Add possible ForeignPath to joinrel, if join is safe to push down. */ #if PG_VERSION_NUM >= 90600 static void mysqlGetForeignJoinPaths(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra) { MySQLFdwRelationInfo *fpinfo; ForeignPath *joinpath; Cost startup_cost; Cost total_cost; Path *epq_path = NULL; /* Path to create plan to be executed when * EvalPlanQual gets triggered. */ /* * Skip if this join combination has been considered already. */ if (joinrel->fdw_private) return; /* * Create unfinished MySQLFdwRelationInfo entry which is used to indicate * that the join relation is already considered, so that we won't waste * time in judging safety of join pushdown and adding the same paths again * if found safe. Once we know that this join can be pushed down, we fill * the entry. */ fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); fpinfo->pushdown_safe = false; joinrel->fdw_private = fpinfo; /* attrs_used is only for base relations. */ fpinfo->attrs_used = NULL; /* * In case there is a possibility that EvalPlanQual will be executed, we * should be able to reconstruct the row, from base relations applying all * the conditions. We create a local plan from a suitable local path * available in the path list. In case such a path doesn't exist, we can * not push the join to the foreign server since we won't be able to * reconstruct the row for EvalPlanQual(). Find an alternative local path * before we add ForeignPath, lest the new path would kick possibly the * only local path. Do this before calling mysql_foreign_join_ok(), since * that function updates fpinfo and marks it as pushable if the join is * found to be pushable. */ if (root->parse->commandType == CMD_DELETE || root->parse->commandType == CMD_UPDATE || root->rowMarks) { epq_path = GetExistingLocalJoinPath(joinrel); if (!epq_path) { elog(DEBUG3, "could not push down foreign join because a local path suitable for EPQ checks was not found"); return; } } else epq_path = NULL; if (!mysql_foreign_join_ok(root, joinrel, jointype, outerrel, innerrel, extra)) { /* Free path required for EPQ if we copied one; we don't need it now */ if (epq_path) pfree(epq_path); return; } /* TODO: Put accurate estimates here */ startup_cost = 15.0; total_cost = 20 + startup_cost; /* * Create a new join path and add it to the joinrel which represents a * join between foreign tables. */ #if PG_VERSION_NUM >= 120000 joinpath = create_foreign_join_path(root, joinrel, NULL, /* default pathtarget */ joinrel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ joinrel->lateral_relids, epq_path, NIL); /* no fdw_private */ #else joinpath = create_foreignscan_path(root, joinrel, NULL, /* default pathtarget */ joinrel->rows, startup_cost, total_cost, NIL, /* no pathkeys */ joinrel->lateral_relids, epq_path, NIL); /* no fdw_private */ #endif /* PG_VERSION_NUM >= 120000 */ /* Add generated path into joinrel by add_path(). */ add_path(joinrel, (Path *) joinpath); /* XXX Consider pathkeys for the join relation */ /* XXX Consider parameterized paths for the join relation */ } /* * mysql_foreign_join_ok * Assess whether the join between inner and outer relations can be * pushed down to the foreign server. * * As a side effect, save information we obtain in this function to * MySQLFdwRelationInfo passed in. */ static bool mysql_foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinPathExtraData *extra) { MySQLFdwRelationInfo *fpinfo; MySQLFdwRelationInfo *fpinfo_o; MySQLFdwRelationInfo *fpinfo_i; ListCell *lc; List *joinclauses; /* * We support pushing down INNER, LEFT and RIGHT joins. * Constructing queries representing SEMI and ANTI joins is hard, hence * not considered right now. */ if (jointype != JOIN_INNER && jointype != JOIN_LEFT && jointype != JOIN_RIGHT) return false; /* * If either of the joining relations is marked as unsafe to pushdown, the * join cannot be pushed down. */ fpinfo = (MySQLFdwRelationInfo *) joinrel->fdw_private; fpinfo_o = (MySQLFdwRelationInfo *) outerrel->fdw_private; fpinfo_i = (MySQLFdwRelationInfo *) innerrel->fdw_private; if (!fpinfo_o || !fpinfo_o->pushdown_safe || !fpinfo_i || !fpinfo_i->pushdown_safe) return false; /* * If joining relations have local conditions, those conditions are * required to be applied before joining the relations. Hence the join can * not be pushed down. */ if (fpinfo_o->local_conds || fpinfo_i->local_conds) return false; /* * Separate restrict list into join quals and pushed-down (other) quals. * * Join quals belonging to an outer join must all be shippable, else we * cannot execute the join remotely. Add such quals to 'joinclauses'. * * Add other quals to fpinfo->remote_conds if they are shippable, else to * fpinfo->local_conds. In an inner join it's okay to execute conditions * either locally or remotely; the same is true for pushed-down conditions * at an outer join. * * Note we might return failure after having already scribbled on * fpinfo->remote_conds and fpinfo->local_conds. That's okay because we * won't consult those lists again if we deem the join unshippable. */ joinclauses = NIL; foreach(lc, extra->restrictlist) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); bool is_remote_clause = mysql_is_foreign_expr(root, joinrel, rinfo->clause, true); if (IS_OUTER_JOIN(jointype) && !RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) { if (!is_remote_clause) return false; joinclauses = lappend(joinclauses, rinfo); } else { if (is_remote_clause) { /* * Unlike postgres_fdw, don't append the join clauses to * remote_conds, instead keep the join clauses separate. * Currently, we are providing limited operator pushability * support for join pushdown, hence we keep those clauses * separate to avoid INNER JOIN not getting pushdown if any * of the WHERE clause is not shippable as per join pushdown * shippability. */ if (jointype == JOIN_INNER) joinclauses = lappend(joinclauses, rinfo); else fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); } else fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); } } /* * mysqlDeparseExplicitTargetList() isn't smart enough to handle anything * other than a Var. In particular, if there's some PlaceHolderVar that * would need to be evaluated within this join tree (because there's an * upper reference to a quantity that may go to NULL as a result of an * outer join), then we can't try to push the join down because we'll fail * when we get to mysqlDeparseExplicitTargetList(). However, a * PlaceHolderVar that needs to be evaluated *at the top* of this join tree * is OK, because we can do that locally after fetching the results from * the remote side. */ foreach(lc, root->placeholder_list) { PlaceHolderInfo *phinfo = lfirst(lc); Relids relids; /* PlaceHolderInfo refers to parent relids, not child relids. */ #if PG_VERSION_NUM >= 100000 relids = IS_OTHER_REL(joinrel) ? joinrel->top_parent_relids : joinrel->relids; #else relids = joinrel->relids; #endif /* PG_VERSION_NUM >= 100000 */ if (bms_is_subset(phinfo->ph_eval_at, relids) && bms_nonempty_difference(relids, phinfo->ph_eval_at)) return false; } /* Save the join clauses, for later use. */ fpinfo->joinclauses = joinclauses; /* * Pull the other remote conditions from the joining relations into join * clauses or other remote clauses (remote_conds) of this relation. This * avoids building subqueries at every join step. * * For an inner join, clauses from both the relations are added to the * other remote clauses. For an OUTER join, the clauses from the outer * side are added to remote_conds since those can be evaluated after the * join is evaluated. The clauses from inner side are added to the * joinclauses, since they need to evaluated while constructing the join. * * The joining sides cannot have local conditions, thus no need to test * shippability of the clauses being pulled up. */ switch (jointype) { case JOIN_INNER: fpinfo->remote_conds = mysql_list_concat(fpinfo->remote_conds, fpinfo_i->remote_conds); fpinfo->remote_conds = mysql_list_concat(fpinfo->remote_conds, fpinfo_o->remote_conds); break; case JOIN_LEFT: /* Check that clauses from the inner side are pushable or not. */ foreach(lc, fpinfo_i->remote_conds) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); if (!mysql_is_foreign_expr(root, joinrel, ri->clause, true)) return false; } fpinfo->joinclauses = mysql_list_concat(fpinfo->joinclauses, fpinfo_i->remote_conds); fpinfo->remote_conds = mysql_list_concat(fpinfo->remote_conds, fpinfo_o->remote_conds); break; case JOIN_RIGHT: /* Check that clauses from the outer side are pushable or not. */ foreach(lc, fpinfo_o->remote_conds) { RestrictInfo *ri = (RestrictInfo *) lfirst(lc); if (!mysql_is_foreign_expr(root, joinrel, ri->clause, true)) return false; } fpinfo->joinclauses = mysql_list_concat(fpinfo->joinclauses, fpinfo_o->remote_conds); fpinfo->remote_conds = mysql_list_concat(fpinfo->remote_conds, fpinfo_i->remote_conds); break; default: /* Should not happen, we have just check this above */ elog(ERROR, "unsupported join type %d", jointype); } fpinfo->outerrel = outerrel; fpinfo->innerrel = innerrel; fpinfo->jointype = jointype; /* Mark that this join can be pushed down safely */ fpinfo->pushdown_safe = true; /* * Set the string describing this join relation to be used in EXPLAIN * output of corresponding ForeignScan. */ fpinfo->relation_name = makeStringInfo(); appendStringInfo(fpinfo->relation_name, "(%s) %s JOIN (%s)", fpinfo_o->relation_name->data, mysql_get_jointype_name(fpinfo->jointype), fpinfo_i->relation_name->data); return true; } /* * mysqlRecheckForeignScan * Execute a local join execution plan for a foreign join. */ static bool mysqlRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot) { Index scanrelid = ((Scan *) node->ss.ps.plan)->scanrelid; PlanState *outerPlan = outerPlanState(node); TupleTableSlot *result; /* For base foreign relations, it suffices to set fdw_recheck_quals */ if (scanrelid > 0) return true; Assert(outerPlan != NULL); /* Execute a local join execution plan */ result = ExecProcNode(outerPlan); if (TupIsNull(result)) return false; /* Store result in the given slot */ ExecCopySlot(slot, result); return true; } /* * mysql_adjust_whole_row_ref * If the given list of Var nodes has whole-row reference, add Var * nodes corresponding to all the attributes of the corresponding * base relation. * * The function also returns an array of lists of var nodes. The array is * indexed by the RTI and entry there contains the list of Var nodes which * make up the whole-row reference for corresponding base relation. * The relations not covered by given join and the relations which do not * have whole-row references will have NIL entries. * * If there are no whole-row references in the given list, the given list is * returned unmodified and the other list is NIL. */ static List * mysql_adjust_whole_row_ref(PlannerInfo *root, List *scan_var_list, List **whole_row_lists, Bitmapset *relids) { ListCell *lc; bool has_whole_row = false; List **wr_list_array = NULL; int cnt_rt; List *wr_scan_var_list = NIL; *whole_row_lists = NIL; /* Check if there exists at least one whole row reference. */ foreach(lc, scan_var_list) { Var *var = (Var *) lfirst(lc); Assert(IsA(var, Var)); if (var->varattno == 0) { has_whole_row = true; break; } } if (!has_whole_row) return scan_var_list; /* * Allocate large enough memory to hold whole-row Var lists for all the * relations. This array will then be converted into a list of lists. * Since all the base relations are marked by range table index, it's easy * to keep track of the ones whose whole-row references have been taken * care of. */ wr_list_array = (List **) palloc0(sizeof(List *) * list_length(root->parse->rtable)); /* Adjust the whole-row references as described in the prologue. */ foreach(lc, scan_var_list) { Var *var = (Var *) lfirst(lc); Assert(IsA(var, Var)); if (var->varattno == 0 && !wr_list_array[var->varno - 1]) { List *wr_var_list; List *retrieved_attrs; RangeTblEntry *rte = rt_fetch(var->varno, root->parse->rtable); Bitmapset *attrs_used; Assert(OidIsValid(rte->relid)); /* * Get list of Var nodes for all undropped attributes of the base * relation. */ attrs_used = bms_make_singleton(0 - FirstLowInvalidHeapAttributeNumber); /* * If the whole-row reference falls on the nullable side of the * outer join and that side is null in a given result row, the * whole row reference should be set to NULL. In this case, all * the columns of that relation will be NULL, but that does not * help since those columns can be genuinely NULL in a row. */ wr_var_list = mysql_build_scan_list_for_baserel(rte->relid, var->varno, attrs_used, &retrieved_attrs); wr_list_array[var->varno - 1] = wr_var_list; wr_scan_var_list = list_concat_unique(wr_scan_var_list, wr_var_list); bms_free(attrs_used); list_free(retrieved_attrs); } else wr_scan_var_list = list_append_unique(wr_scan_var_list, var); } /* * Collect the required Var node lists into a list of lists ordered by the * base relations' range table indexes. */ cnt_rt = -1; while ((cnt_rt = bms_next_member(relids, cnt_rt)) >= 0) *whole_row_lists = lappend(*whole_row_lists, wr_list_array[cnt_rt - 1]); pfree(wr_list_array); return wr_scan_var_list; } /* * mysql_build_scan_list_for_baserel * Build list of nodes corresponding to the attributes requested for * given base relation. * * The list contains Var nodes corresponding to the attributes specified in * attrs_used. If whole-row reference is required, the functions adds Var * nodes corresponding to all the attributes in the relation. */ static List * mysql_build_scan_list_for_baserel(Oid relid, Index varno, Bitmapset *attrs_used, List **retrieved_attrs) { int attno; List *tlist = NIL; Node *node; bool wholerow_requested = false; Relation relation; TupleDesc tupdesc; Assert(OidIsValid(relid)); *retrieved_attrs = NIL; /* Planner must have taken a lock, so request no lock here */ #if PG_VERSION_NUM < 130000 relation = heap_open(relid, NoLock); #else relation = table_open(relid, NoLock); #endif tupdesc = RelationGetDescr(relation); /* Is whole-row reference requested? */ wholerow_requested = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, attrs_used); /* Handle user defined attributes. */ for (attno = 1; attno <= tupdesc->natts; attno++) { Form_pg_attribute attr = TupleDescAttr(tupdesc, attno - 1); /* Ignore dropped attributes. */ if (attr->attisdropped) continue; /* * For a required attribute create a Var node and add corresponding * attribute number to the retrieved_attrs list. */ if (wholerow_requested || bms_is_member(attno - FirstLowInvalidHeapAttributeNumber, attrs_used)) { node = (Node *) makeVar(varno, attno, attr->atttypid, attr->atttypmod, attr->attcollation, 0); tlist = lappend(tlist, node); *retrieved_attrs = lappend_int(*retrieved_attrs, attno); } } #if PG_VERSION_NUM < 130000 heap_close(relation, NoLock); #else table_close(relation, NoLock); #endif return tlist; } #endif /* PG_VERSION_NUM >= 90600 */ /* * mysql_build_whole_row_constr_info * Calculate and save the information required to construct whole row * references of base foreign relations involved in the pushed down join. * * tupdesc is the tuple descriptor describing the result returned by the * ForeignScan node. It is expected to be same as * ForeignScanState::ss::ss_ScanTupleSlot, which is constructed using * fdw_scan_tlist. * * relids is the the set of relations participating in the pushed down join. * * max_relid is the maximum number of relation index expected. * * whole_row_lists is the list of Var node lists constituting the whole-row * reference for base relations in the relids in the same order. * * scan_tlist is the targetlist representing the result fetched from the * foreign server. * * fdw_scan_tlist is the targetlist representing the result returned by the * ForeignScan node. */ static void mysql_build_whole_row_constr_info(MySQLFdwExecState *festate, TupleDesc tupdesc, Bitmapset *relids, int max_relid, List *whole_row_lists, List *scan_tlist, List *fdw_scan_tlist) { int cnt_rt; int cnt_vl; int cnt_attr; ListCell *lc; int *fs_attr_pos = NULL; MySQLWRState **mysqlwrstates = NULL; int fs_num_atts; /* * Allocate memory to hold whole-row reference state for each relation. * Indexing by the range table index is faster than maintaining an * associative map. */ mysqlwrstates = (MySQLWRState **) palloc0(sizeof(MySQLWRState *) * max_relid); /* * Set the whole-row reference state for the relations whose whole-row * reference needs to be constructed. */ cnt_rt = -1; cnt_vl = 0; while ((cnt_rt = bms_next_member(relids, cnt_rt)) >= 0) { MySQLWRState *wr_state = (MySQLWRState *) palloc0(sizeof(MySQLWRState)); List *var_list = list_nth(whole_row_lists, cnt_vl++); int natts; /* Skip the relations without whole-row references. */ if (list_length(var_list) <= 0) continue; natts = list_length(var_list); wr_state->attr_pos = (int *) palloc(sizeof(int) * natts); /* * Create a map of attributes required for whole-row reference to * their positions in the result fetched from the foreign server. */ cnt_attr = 0; foreach(lc, var_list) { Var *var = lfirst(lc); TargetEntry *tle_sl; Assert(IsA(var, Var) &&var->varno == cnt_rt); #if PG_VERSION_NUM >= 100000 tle_sl = tlist_member((Expr *) var, scan_tlist); #else tle_sl = tlist_member((Node *) var, scan_tlist); #endif Assert(tle_sl); wr_state->attr_pos[cnt_attr++] = tle_sl->resno - 1; } Assert(natts == cnt_attr); /* Build rest of the state */ wr_state->tupdesc = ExecTypeFromExprList(var_list); Assert(natts == wr_state->tupdesc->natts); wr_state->values = (Datum *) palloc(sizeof(Datum) * natts); wr_state->nulls = (bool *) palloc(sizeof(bool) * natts); BlessTupleDesc(wr_state->tupdesc); mysqlwrstates[cnt_rt - 1] = wr_state; } /* * Construct the array mapping columns in the ForeignScan node output to * their positions in the result fetched from the foreign server. Positive * values indicate the locations in the result and negative values * indicate the range table indexes of the base table whose whole-row * reference values are requested in that place. */ fs_num_atts = list_length(fdw_scan_tlist); fs_attr_pos = (int *) palloc(sizeof(int) * fs_num_atts); cnt_attr = 0; foreach(lc, fdw_scan_tlist) { TargetEntry *tle_fsl = lfirst(lc); Var *var = (Var *) tle_fsl->expr; Assert(IsA(var, Var)); if (var->varattno == 0) fs_attr_pos[cnt_attr] = -var->varno; else { #if PG_VERSION_NUM >= 100000 TargetEntry *tle_sl = tlist_member((Expr *) var, scan_tlist); #else TargetEntry *tle_sl = tlist_member((Node *) var, scan_tlist); #endif Assert(tle_sl); fs_attr_pos[cnt_attr] = tle_sl->resno - 1; } cnt_attr++; } /* * The tuple descriptor passed in should have same number of attributes as * the entries in fdw_scan_tlist. */ Assert(fs_num_atts == tupdesc->natts); festate->mysqlwrstates = mysqlwrstates; festate->wr_attrs_pos = fs_attr_pos; festate->wr_tupdesc = tupdesc; festate->wr_values = (Datum *) palloc(sizeof(Datum) * tupdesc->natts); festate->wr_nulls = (bool *) palloc(sizeof(bool) * tupdesc->natts); return; } /* * mysql_get_tuple_with_whole_row * Construct the result row with whole-row references. */ static HeapTuple mysql_get_tuple_with_whole_row(MySQLFdwExecState *festate, Datum *values, bool *nulls) { TupleDesc tupdesc = festate->wr_tupdesc; Datum *wr_values = festate->wr_values; bool *wr_nulls = festate->wr_nulls; int cnt_attr; HeapTuple tuple = NULL; for (cnt_attr = 0; cnt_attr < tupdesc->natts; cnt_attr++) { int attr_pos = festate->wr_attrs_pos[cnt_attr]; if (attr_pos >= 0) { wr_values[cnt_attr] = values[attr_pos]; wr_nulls[cnt_attr] = nulls[attr_pos]; } else { /* * The RTI of relation whose whole row reference is to be * constructed is stored as -ve attr_pos. */ MySQLWRState *wr_state = festate->mysqlwrstates[-attr_pos - 1]; wr_nulls[cnt_attr] = nulls[wr_state->wr_null_ind_pos]; if (!wr_nulls[cnt_attr]) { HeapTuple wr_tuple = mysql_form_whole_row(wr_state, values, nulls); wr_values[cnt_attr] = HeapTupleGetDatum(wr_tuple); } } } tuple = heap_form_tuple(tupdesc, wr_values, wr_nulls); return tuple; } /* * mysql_form_whole_row * The function constructs whole-row reference for a base relation * with the information given in wr_state. * * wr_state contains the information about which attributes from values and * nulls are to be used and in which order to construct the whole-row * reference. */ static HeapTuple mysql_form_whole_row(MySQLWRState *wr_state, Datum *values, bool *nulls) { int cnt_attr; for (cnt_attr = 0; cnt_attr < wr_state->tupdesc->natts; cnt_attr++) { int attr_pos = wr_state->attr_pos[cnt_attr]; wr_state->values[cnt_attr] = values[attr_pos]; wr_state->nulls[cnt_attr] = nulls[attr_pos]; } return heap_form_tuple(wr_state->tupdesc, wr_state->values, wr_state->nulls); } /* * Aggregate pushdown is supported from V10 onwards, so for compiling on * branches less than V10, define the aggregate pushdown related functions only * for versions later to V10. */ #if PG_VERSION_NUM >= 100000 /* * mysql_foreign_grouping_ok * Assess whether the aggregation, grouping and having operations can * be pushed down to the foreign server. As a side effect, save * information we obtain in this function to MySQLFdwRelationInfo of * the input relation. */ #if PG_VERSION_NUM >= 110000 static bool mysql_foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel, Node *havingQual) #elif PG_VERSION_NUM >= 100000 static bool mysql_foreign_grouping_ok(PlannerInfo *root, RelOptInfo *grouped_rel) #endif { Query *query = root->parse; #if PG_VERSION_NUM >= 110000 PathTarget *grouping_target = grouped_rel->reltarget; #elif PG_VERSION_NUM >= 100000 PathTarget *grouping_target = root->upper_targets[UPPERREL_GROUP_AGG]; #endif MySQLFdwRelationInfo *fpinfo = (MySQLFdwRelationInfo *) grouped_rel->fdw_private; MySQLFdwRelationInfo *ofpinfo; ListCell *lc; int i; List *tlist = NIL; /* Grouping Sets are not pushable */ if (query->groupingSets) return false; /* Get the fpinfo of the underlying scan relation. */ ofpinfo = (MySQLFdwRelationInfo *) fpinfo->outerrel->fdw_private; /* * If underneath input relation has any local conditions, those conditions * are required to be applied before performing aggregation. Hence the * aggregate cannot be pushed down. */ if (ofpinfo->local_conds) return false; /* * Evaluate grouping targets and check whether they are safe to push down * to the foreign side. All GROUP BY expressions will be part of the * grouping target and thus there is no need to evaluate it separately. * While doing so, add required expressions into target list which can * then be used to pass to foreign server. */ i = 0; foreach(lc, grouping_target->exprs) { Expr *expr = (Expr *) lfirst(lc); Index sgref = get_pathtarget_sortgroupref(grouping_target, i); ListCell *l; /* Check whether this expression is part of GROUP BY clause */ if (sgref && get_sortgroupref_clause_noerr(sgref, query->groupClause)) { TargetEntry *tle; /* * If any of the GROUP BY expression is not shippable we can not * push down aggregation to the foreign server. */ if (!mysql_is_foreign_expr(root, grouped_rel, expr, true)) return false; /* * If it would be a foreign param, we can't put it into the tlist, * so we have to fail. */ if (mysql_is_foreign_param(root, grouped_rel, expr)) return false; /* * Pushable, so add to tlist. We need to create a TLE for this * expression and apply the sortgroupref to it. We cannot use * add_to_flat_tlist() here because that avoids making duplicate * entries in the tlist. If there are duplicate entries with * distinct sortgrouprefs, we have to duplicate that situation in * the output tlist. */ tle = makeTargetEntry(expr, list_length(tlist) + 1, NULL, false); tle->ressortgroupref = sgref; tlist = lappend(tlist, tle); } else { /* Check entire expression whether it is pushable or not */ if (mysql_is_foreign_expr(root, grouped_rel, expr, true) && !mysql_is_foreign_param(root, grouped_rel, expr)) { /* Pushable, add to tlist */ tlist = add_to_flat_tlist(tlist, list_make1(expr)); } else { List *aggvars; /* Not matched exactly, pull the var with aggregates then */ aggvars = pull_var_clause((Node *) expr, PVC_INCLUDE_AGGREGATES); /* * If any aggregate expression is not shippable, then we * cannot push down aggregation to the foreign server. (We * don't have to check is_foreign_param, since that certainly * won't return true for any such expression.) */ if (!mysql_is_foreign_expr(root, grouped_rel, (Expr *) aggvars, true)) return false; /* * Add aggregates, if any, into the targetlist. Plain var * nodes should be either same as some GROUP BY expression or * part of some GROUP BY expression. In later case, the query * cannot refer plain var nodes without the surrounding * expression. In both the cases, they are already part of * the targetlist and thus no need to add them again. In fact * adding pulled plain var nodes in SELECT clause will cause * an error on the foreign server if they are not same as some * GROUP BY expression. */ foreach(l, aggvars) { Expr *expr = (Expr *) lfirst(l); if (IsA(expr, Aggref)) tlist = add_to_flat_tlist(tlist, list_make1(expr)); } } } i++; } /* * Classify the pushable and non-pushable having clauses and save them in * remote_conds and local_conds of the grouped rel's fpinfo. */ #if PG_VERSION_NUM >= 110000 if (havingQual) { ListCell *lc; foreach(lc, (List *) havingQual) #elif PG_VERSION_NUM >= 100000 if (root->hasHavingQual && query->havingQual) { ListCell *lc; foreach(lc, (List *) query->havingQual) #endif { Expr *expr = (Expr *) lfirst(lc); RestrictInfo *rinfo; /* * Currently, the core code doesn't wrap havingQuals in * RestrictInfos, so we must make our own. */ Assert(!IsA(expr, RestrictInfo)); #if PG_VERSION_NUM >= 140000 rinfo = make_restrictinfo(root, expr, true, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); #else rinfo = make_restrictinfo(expr, true, false, false, root->qual_security_level, grouped_rel->relids, NULL, NULL); #endif if (!mysql_is_foreign_expr(root, grouped_rel, expr, true)) fpinfo->local_conds = lappend(fpinfo->local_conds, rinfo); else fpinfo->remote_conds = lappend(fpinfo->remote_conds, rinfo); } } /* * If there are any local conditions, pull Vars and aggregates from it and * check whether they are safe to pushdown or not. */ if (fpinfo->local_conds) { List *aggvars = NIL; ListCell *lc; foreach(lc, fpinfo->local_conds) { RestrictInfo *rinfo = lfirst_node(RestrictInfo, lc); aggvars = list_concat(aggvars, pull_var_clause((Node *) rinfo->clause, PVC_INCLUDE_AGGREGATES)); } foreach(lc, aggvars) { Expr *expr = (Expr *) lfirst(lc); /* * If aggregates within local conditions are not safe to push * down, then we cannot push down the query. Vars are already * part of GROUP BY clause which are checked above, so no need to * access them again here. Again, we need not check * is_foreign_param for a foreign aggregate. */ if (IsA(expr, Aggref)) { if (!mysql_is_foreign_expr(root, grouped_rel, expr, true)) return false; tlist = add_to_flat_tlist(tlist, list_make1(expr)); } } } /* Store generated targetlist */ fpinfo->grouped_tlist = tlist; /* Safe to pushdown */ fpinfo->pushdown_safe = true; /* * Set the string describing this grouped relation to be used in EXPLAIN * output of corresponding ForeignScan. */ fpinfo->relation_name = makeStringInfo(); appendStringInfo(fpinfo->relation_name, "Aggregate on (%s)", ofpinfo->relation_name->data); return true; } /* * mysqlGetForeignUpperPaths * Add paths for post-join operations like aggregation, grouping etc. if * corresponding operations are safe to push down. * * Right now, we only support aggregate, grouping and having clause pushdown. */ #if PG_VERSION_NUM >= 110000 static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel, void *extra) #elif PG_VERSION_NUM >= 100000 static void mysqlGetForeignUpperPaths(PlannerInfo *root, UpperRelationKind stage, RelOptInfo *input_rel, RelOptInfo *output_rel) #endif { MySQLFdwRelationInfo *fpinfo; /* * If input rel is not safe to pushdown, then simply return as we cannot * perform any post-join operations on the foreign server. */ if (!input_rel->fdw_private || !((MySQLFdwRelationInfo *) input_rel->fdw_private)->pushdown_safe) return; /* Ignore stages we don't support; and skip any duplicate calls. */ if (stage != UPPERREL_GROUP_AGG || output_rel->fdw_private) return; fpinfo = (MySQLFdwRelationInfo *) palloc0(sizeof(MySQLFdwRelationInfo)); fpinfo->pushdown_safe = false; output_rel->fdw_private = fpinfo; #if PG_VERSION_NUM >= 110000 mysql_add_foreign_grouping_paths(root, input_rel, output_rel, (GroupPathExtraData *) extra); #elif PG_VERSION_NUM >= 100000 mysql_add_foreign_grouping_paths(root, input_rel, output_rel); #endif } /* * mysql_add_foreign_grouping_paths * Add foreign path for grouping and/or aggregation. * * Given input_rel represents the underlying scan. The paths are added to the * given grouped_rel. */ #if PG_VERSION_NUM >= 110000 static void mysql_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel, GroupPathExtraData *extra) #elif PG_VERSION_NUM >= 100000 static void mysql_add_foreign_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel, RelOptInfo *grouped_rel) #endif { Query *parse = root->parse; MySQLFdwRelationInfo *fpinfo = grouped_rel->fdw_private; ForeignPath *grouppath; Cost startup_cost; Cost total_cost; double num_groups; /* Nothing to be done, if there is no grouping or aggregation required. */ if (!parse->groupClause && !parse->groupingSets && !parse->hasAggs && !root->hasHavingQual) return; /* save the input_rel as outerrel in fpinfo */ fpinfo->outerrel = input_rel; /* Assess if it is safe to push down aggregation and grouping. */ #if PG_VERSION_NUM >= 110000 if (!mysql_foreign_grouping_ok(root, grouped_rel, extra->havingQual)) #elif PG_VERSION_NUM >= 100000 if (!mysql_foreign_grouping_ok(root, grouped_rel)) #endif return; /* * TODO: Put accurate estimates here. * * Cost used here is minimum of the cost estimated for base and join * relation. */ startup_cost = 15; total_cost = 10 + startup_cost; /* Estimate output tuples which should be same as number of groups */ #if PG_VERSION_NUM >= 140000 num_groups = estimate_num_groups(root, get_sortgrouplist_exprs(root->parse->groupClause, fpinfo->grouped_tlist), input_rel->rows, NULL, NULL); #else num_groups = estimate_num_groups(root, get_sortgrouplist_exprs(root->parse->groupClause, fpinfo->grouped_tlist), input_rel->rows, NULL); #endif /* Create and add foreign path to the grouping relation. */ #if PG_VERSION_NUM >= 120000 grouppath = create_foreign_upper_path(root, grouped_rel, grouped_rel->reltarget, num_groups, startup_cost, total_cost, NIL, /* no pathkeys */ NULL, NIL); /* no fdw_private */ #elif PG_VERSION_NUM >= 110000 grouppath = create_foreignscan_path(root, grouped_rel, grouped_rel->reltarget, num_groups, startup_cost, total_cost, NIL, /* no pathkeys */ grouped_rel->lateral_relids, NULL, NIL); /* no fdw_private */ #else grouppath = create_foreignscan_path(root, grouped_rel, root->upper_targets[UPPERREL_GROUP_AGG], num_groups, startup_cost, total_cost, NIL, /* no pathkeys */ grouped_rel->lateral_relids, NULL, NIL); /* no fdw_private */ #endif /* Add generated path into grouped_rel by add_path(). */ add_path(grouped_rel, (Path *) grouppath); } #endif /* PG_VERSION_NUM >= 100000. End of APD related functions. */ mysql_fdw-REL-2_7_0/mysql_fdw.control000066400000000000000000000010421414541277500177250ustar00rootroot00000000000000########################################################################## # # mysql_fdw.control # Foreign-data wrapper for remote MySQL servers # # Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group # Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. # # IDENTIFICATION # mysql_fdw.control # ########################################################################## comment = 'Foreign data wrapper for querying a MySQL server' default_version = '1.1' module_pathname = '$libdir/mysql_fdw' relocatable = true mysql_fdw-REL-2_7_0/mysql_fdw.h000066400000000000000000000267321414541277500165110ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_fdw.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_fdw.h * *------------------------------------------------------------------------- */ #ifndef MYSQL_FDW_H #define MYSQL_FDW_H #define list_length mysql_list_length #define list_delete mysql_list_delete #define list_free mysql_list_free #include #undef list_length #undef list_delete #undef list_free #include "access/tupdesc.h" #include "fmgr.h" #include "foreign/foreign.h" #include "funcapi.h" #include "lib/stringinfo.h" #if PG_VERSION_NUM < 120000 #include "nodes/relation.h" #else #include "nodes/pathnodes.h" #endif #include "utils/rel.h" #define MYSQL_PREFETCH_ROWS 100 #define MYSQL_BLKSIZ (1024 * 4) #define MYSQL_SERVER_PORT 3306 #define MAXDATALEN 1024 * 64 #define WAIT_TIMEOUT 0 #define INTERACTIVE_TIMEOUT 0 #define CR_NO_ERROR 0 #define mysql_options (*_mysql_options) #define mysql_stmt_prepare (*_mysql_stmt_prepare) #define mysql_stmt_execute (*_mysql_stmt_execute) #define mysql_stmt_fetch (*_mysql_stmt_fetch) #define mysql_query (*_mysql_query) #define mysql_stmt_attr_set (*_mysql_stmt_attr_set) #define mysql_stmt_close (*_mysql_stmt_close) #define mysql_stmt_reset (*_mysql_stmt_reset) #define mysql_free_result (*_mysql_free_result) #define mysql_stmt_bind_param (*_mysql_stmt_bind_param) #define mysql_stmt_bind_result (*_mysql_stmt_bind_result) #define mysql_stmt_init (*_mysql_stmt_init) #define mysql_stmt_result_metadata (*_mysql_stmt_result_metadata) #define mysql_stmt_store_result (*_mysql_stmt_store_result) #define mysql_fetch_row (*_mysql_fetch_row) #define mysql_fetch_field (*_mysql_fetch_field) #define mysql_fetch_fields (*_mysql_fetch_fields) #define mysql_error (*_mysql_error) #define mysql_close (*_mysql_close) #define mysql_store_result (*_mysql_store_result) #define mysql_init (*_mysql_init) #define mysql_ssl_set (*_mysql_ssl_set) #define mysql_real_connect (*_mysql_real_connect) #define mysql_get_host_info (*_mysql_get_host_info) #define mysql_get_server_info (*_mysql_get_server_info) #define mysql_get_proto_info (*_mysql_get_proto_info) #define mysql_stmt_errno (*_mysql_stmt_errno) #define mysql_errno (*_mysql_errno) #define mysql_num_fields (*_mysql_num_fields) #define mysql_num_rows (*_mysql_num_rows) /* Macro for list API backporting. */ #if PG_VERSION_NUM < 130000 #define mysql_list_concat(l1, l2) list_concat(l1, list_copy(l2)) #else #define mysql_list_concat(l1, l2) list_concat((l1), (l2)) #endif /* * Options structure to store the MySQL * server information */ typedef struct mysql_opt { int svr_port; /* MySQL port number */ char *svr_address; /* MySQL server ip address */ char *svr_username; /* MySQL user name */ char *svr_password; /* MySQL password */ char *svr_database; /* MySQL database name */ char *svr_table; /* MySQL table name */ bool svr_sa; /* MySQL secure authentication */ char *svr_init_command; /* MySQL SQL statement to execute when * connecting to the MySQL server. */ unsigned long max_blob_size; /* Max blob size to read without * truncation */ bool use_remote_estimate; /* use remote estimate for rows */ unsigned long fetch_size; /* Number of rows to fetch from remote server */ bool reconnect; /* set to true for automatic reconnection */ /* SSL parameters; unused options may be given as NULL */ char *ssl_key; /* MySQL SSL: path to the key file */ char *ssl_cert; /* MySQL SSL: path to the certificate file */ char *ssl_ca; /* MySQL SSL: path to the certificate * authority file */ char *ssl_capath; /* MySQL SSL: path to a directory that * contains trusted SSL CA certificates in PEM * format */ char *ssl_cipher; /* MySQL SSL: list of permissible ciphers to * use for SSL encryption */ } mysql_opt; typedef struct mysql_column { Datum value; unsigned long length; bool is_null; bool error; MYSQL_BIND *mysql_bind; } mysql_column; typedef struct mysql_table { MYSQL_RES *mysql_res; MYSQL_FIELD *mysql_fields; mysql_column *column; MYSQL_BIND *mysql_bind; } mysql_table; /* * Structure to hold information for constructing a whole-row reference value * for a single base relation involved in a pushed down join. */ typedef struct { /* * Tuple descriptor for whole-row reference. We can not use the base * relation's tuple descriptor as it is, since it might have information * about dropped attributes. */ TupleDesc tupdesc; /* * Positions of the required attributes in the tuple fetched from the * foreign server. */ int *attr_pos; /* Position of attribute indicating NULL-ness of whole-row reference */ int wr_null_ind_pos; /* Values and null array for holding column values. */ Datum *values; bool *nulls; } MySQLWRState; /* * FDW-specific information for ForeignScanState * fdw_state. */ typedef struct MySQLFdwExecState { MYSQL *conn; /* MySQL connection handle */ MYSQL_STMT *stmt; /* MySQL prepared stament handle */ mysql_table *table; char *query; /* Query string */ List *retrieved_attrs; /* list of target attribute numbers */ bool query_executed; /* have we executed the query? */ int numParams; /* number of parameters passed to query */ FmgrInfo *param_flinfo; /* output conversion functions for them */ List *param_exprs; /* executable expressions for param values */ const char **param_values; /* textual values of query parameters */ Oid *param_types; /* type of query parameters */ int p_nums; /* number of parameters to transmit */ FmgrInfo *p_flinfo; /* output conversion functions for them */ mysql_opt *mysqlFdwOptions; /* MySQL FDW options */ MemoryContext temp_cxt; /* context for per-tuple temporary data */ AttInMetadata *attinmeta; AttrNumber rowidAttno; /* attnum of resjunk rowid column */ /* * Members used for constructing the ForeignScan result row when whole-row * references are involved in a pushed down join. */ MySQLWRState **mysqlwrstates; /* whole-row construction information for * each base relation involved in the pushed * down join. */ int *wr_attrs_pos; /* Array mapping the attributes in the * ForeignScan result to those in the rows * fetched from the foreign server. The array * is indexed by the attribute numbers in the * ForeignScan. */ TupleDesc wr_tupdesc; /* Tuple descriptor describing the result of * ForeignScan node. Should be same as that in * ForeignScanState::ss::ss_ScanTupleSlot */ /* Array for holding column values. */ Datum *wr_values; bool *wr_nulls; } MySQLFdwExecState; typedef struct MySQLFdwRelationInfo { /* * True means that the relation can be pushed down. Always true for simple * foreign scan. */ bool pushdown_safe; /* baserestrictinfo clauses, broken down into safe and unsafe subsets. */ List *remote_conds; List *local_conds; /* Bitmap of attr numbers we need to fetch from the remote server. */ Bitmapset *attrs_used; /* * Name of the relation while EXPLAINing ForeignScan. It is used for join * relations but is set for all relations. For join relation, the name * indicates which foreign tables are being joined and the join type used. */ StringInfo relation_name; /* Join information */ RelOptInfo *outerrel; RelOptInfo *innerrel; JoinType jointype; List *joinclauses; /* Grouping information */ List *grouped_tlist; } MySQLFdwRelationInfo; /* MySQL Column List */ typedef struct MySQLColumn { int attnum; /* Attribute number */ char *attname; /* Attribute name */ int atttype; /* Attribute type */ } MySQLColumn; extern int ((mysql_options) (MYSQL *mysql, enum mysql_option option, const void *arg)); extern int ((mysql_stmt_prepare) (MYSQL_STMT *stmt, const char *query, unsigned long length)); extern int ((mysql_stmt_execute) (MYSQL_STMT *stmt)); extern int ((mysql_stmt_fetch) (MYSQL_STMT *stmt)); extern int ((mysql_query) (MYSQL *mysql, const char *q)); extern bool ((mysql_stmt_attr_set) (MYSQL_STMT *stmt, enum enum_stmt_attr_type attr_type, const void *attr)); extern bool ((mysql_stmt_close) (MYSQL_STMT *stmt)); extern bool ((mysql_stmt_reset) (MYSQL_STMT *stmt)); extern bool ((mysql_free_result) (MYSQL_RES *result)); extern bool ((mysql_stmt_bind_param) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); extern bool ((mysql_stmt_bind_result) (MYSQL_STMT *stmt, MYSQL_BIND *bnd)); extern MYSQL_STMT *((mysql_stmt_init) (MYSQL *mysql)); extern MYSQL_RES *((mysql_stmt_result_metadata) (MYSQL_STMT *stmt)); extern int ((mysql_stmt_store_result) (MYSQL *mysql)); extern MYSQL_ROW((mysql_fetch_row) (MYSQL_RES *result)); extern MYSQL_FIELD *((mysql_fetch_field) (MYSQL_RES *result)); extern MYSQL_FIELD *((mysql_fetch_fields) (MYSQL_RES *result)); extern const char *((mysql_error) (MYSQL *mysql)); extern void ((mysql_close) (MYSQL *sock)); extern MYSQL_RES *((mysql_store_result) (MYSQL *mysql)); extern MYSQL *((mysql_init) (MYSQL *mysql)); extern bool ((mysql_ssl_set) (MYSQL *mysql, const char *key, const char *cert, const char *ca, const char *capath, const char *cipher)); extern MYSQL *((mysql_real_connect) (MYSQL *mysql, const char *host, const char *user, const char *passwd, const char *db, unsigned int port, const char *unix_socket, unsigned long clientflag)); extern const char *((mysql_get_host_info) (MYSQL *mysql)); extern const char *((mysql_get_server_info) (MYSQL *mysql)); extern int ((mysql_get_proto_info) (MYSQL *mysql)); extern unsigned int ((mysql_stmt_errno) (MYSQL_STMT *stmt)); extern unsigned int ((mysql_errno) (MYSQL *mysql)); extern unsigned int ((mysql_num_fields) (MYSQL_RES *result)); extern unsigned int ((mysql_num_rows) (MYSQL_RES *result)); /* option.c headers */ extern bool mysql_is_valid_option(const char *option, Oid context); extern mysql_opt *mysql_get_options(Oid foreigntableid, bool is_foreigntable); /* depare.c headers */ extern void mysql_deparse_insert(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs); extern void mysql_deparse_update(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, List *targetAttrs, char *attname); extern void mysql_deparse_delete(StringInfo buf, PlannerInfo *root, Index rtindex, Relation rel, char *name); extern void mysql_deparse_analyze(StringInfo buf, char *dbname, char *relname); extern bool mysql_is_foreign_expr(PlannerInfo *root, RelOptInfo *baserel, Expr *expr, bool is_remote_cond); 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); extern const char *mysql_get_jointype_name(JoinType jointype); extern bool mysql_is_foreign_param(PlannerInfo *root, RelOptInfo *baserel, Expr *expr); /* connection.c headers */ MYSQL *mysql_get_connection(ForeignServer *server, UserMapping *user, mysql_opt *opt); MYSQL *mysql_connect(mysql_opt *opt); void mysql_cleanup_connection(void); void mysql_release_connection(MYSQL *conn); #if PG_VERSION_NUM < 110000 /* TupleDescAttr is defined from PG version 11 */ #define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)]) #endif #endif /* MYSQL_FDW_H */ mysql_fdw-REL-2_7_0/mysql_init.sh000077500000000000000000000131341414541277500170520ustar00rootroot00000000000000#!/bin/sh export MYSQL_PWD="edb" export MYSQL_HOST="localhost" export MYSQL_PORT="3306" export MYSQL_USER_NAME="edb" # Below commands must be run first time to create mysql_fdw_regress and mysql_fdw_regress1 databases # used in regression tests with edb user and edb password. # --connect to mysql with root user # mysql -u root -p # --run below # CREATE DATABASE mysql_fdw_regress; # CREATE DATABASE mysql_fdw_regress1; # SET GLOBAL validate_password.policy = LOW; # SET GLOBAL validate_password.length = 1; # SET GLOBAL validate_password.mixed_case_count = 0; # SET GLOBAL validate_password.number_count = 0; # SET GLOBAL validate_password.special_char_count = 0; # CREATE USER 'edb'@'localhost' IDENTIFIED BY 'edb'; # GRANT ALL PRIVILEGES ON mysql_fdw_regress.* TO 'edb'@'localhost'; # GRANT ALL PRIVILEGES ON mysql_fdw_regress1.* TO 'edb'@'localhost'; mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS mysql_test;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS empdata;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS numbers;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test_tbl2;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test_tbl1;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS student;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS numbers;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS enum_t1;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "DROP TABLE IF EXISTS student1;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS enum_t2;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test1;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test2;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test3;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test4;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test5;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "DROP TABLE IF EXISTS test_set;" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE mysql_test(a int primary key, b int);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO mysql_test(a,b) VALUES (1,1);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE empdata (emp_id int, emp_dat blob, PRIMARY KEY (emp_id));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE numbers (a int PRIMARY KEY, b varchar(255));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test_tbl1 (c1 INT primary key, c2 VARCHAR(10), c3 CHAR(9), c4 MEDIUMINT, c5 DATE, c6 DECIMAL(10,5), c7 INT, c8 SMALLINT);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test_tbl2 (c1 INT primary key, c2 TEXT, c3 TEXT);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE student (stu_id int PRIMARY KEY, stu_name text, stu_dept int);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE numbers (a int, b varchar(255));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE enum_t1 (id int PRIMARY KEY, size ENUM('small', 'medium', 'large'));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress1 -e "CREATE TABLE student1 (stu_id varchar(10) PRIMARY KEY, stu_name text, stu_dept int);" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE enum_t2 (id int PRIMARY KEY, size ENUM('S', 'M', 'L'));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO enum_t2 VALUES (10, 'S'),(20, 'M'),(30, 'M');" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test1 (c1 int PRIMARY KEY, c2 int, c3 varchar(255), c4 ENUM ('foo', 'bar', 'buz'))" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test2 (c1 int PRIMARY KEY, c2 int, c3 varchar(255), c4 ENUM ('foo', 'bar', 'buz'))" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test3 (c1 int PRIMARY KEY, c2 int, c3 varchar(255))" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test4 (c1 int PRIMARY KEY, c2 int, c3 varchar(255))" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test5 (c1 int primary key, c2 binary, c3 binary(3), c4 binary(1), c5 binary(10), c6 varbinary(3), c7 varbinary(1), c8 varbinary(10), c9 binary(0), c10 varbinary(0));" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "INSERT INTO test5 VALUES (1, 'c', 'c3c', 't', 'c5c5c5', '04', '1', '01-10-2021', NULL, '');" mysql -h $MYSQL_HOST -u $MYSQL_USER_NAME -P $MYSQL_PORT -D mysql_fdw_regress -e "CREATE TABLE test_set (c1 int primary key, c2 SET('a', 'b', 'c', 'd'), c3 int);" mysql_fdw-REL-2_7_0/mysql_query.c000066400000000000000000000254471414541277500170730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.c * Type handling for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_query.c * *------------------------------------------------------------------------- */ #include "postgres.h" /* * Must be included before mysql.h as it has some conflicting definitions like * list_length, etc. */ #include "mysql_fdw.h" #include #include #include #include #include "access/htup_details.h" #include "catalog/pg_type.h" #include "mysql_query.h" #if PG_VERSION_NUM < 120000 #include "optimizer/var.h" #else #include "optimizer/optimizer.h" #endif #include "utils/builtins.h" #include "utils/date.h" #include "utils/datetime.h" #include "utils/lsyscache.h" #include "utils/syscache.h" #define DATE_MYSQL_PG(x, y) \ do { \ x->year = y.tm_year; \ x->month = y.tm_mon; \ x->day= y.tm_mday; \ x->hour = y.tm_hour; \ x->minute = y.tm_min; \ x->second = y.tm_sec; \ } while(0); static int32 mysql_from_pgtyp(Oid type); static int dec_bin(int number); static int bin_dec(int binarynumber); /* * convert_mysql_to_pg: * Convert MySQL data into PostgreSQL's compatible data types */ Datum mysql_convert_to_pg(Oid pgtyp, int pgtypmod, mysql_column *column) { Datum value_datum; Datum valueDatum; regproc typeinput; HeapTuple tuple; int typemod; char str[MAXDATELEN]; bytea *result; /* get the type's output function */ tuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(pgtyp)); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for type%u", pgtyp); typeinput = ((Form_pg_type) GETSTRUCT(tuple))->typinput; typemod = ((Form_pg_type) GETSTRUCT(tuple))->typtypmod; ReleaseSysCache(tuple); switch (pgtyp) { /* * MySQL gives BIT / BIT(n) data type as decimal value. The only way * to retrieve this value is to use BIN, OCT or HEX function in MySQL, * otherwise mysql client shows the actual decimal value, which could * be a non - printable character. For exmple in MySQL * * CREATE TABLE t (b BIT(8)); * INSERT INTO t SET b = b'1001'; * SELECT BIN(b) FROM t; * +--------+ * | BIN(b) | * +--------+ * | 1001 | * +--------+ * * PostgreSQL expacts all binary data to be composed of either '0' or * '1'. MySQL gives value 9 hence PostgreSQL reports error. The * solution is to convert the decimal number into equivalent binary * string. */ case BYTEAOID: result = (bytea *) palloc(column->length + VARHDRSZ); memcpy(VARDATA(result), VARDATA(column->value), column->length); SET_VARSIZE(result, column->length + VARHDRSZ); return PointerGetDatum(result); case BITOID: sprintf(str, "%d", dec_bin(*((int *) column->value))); valueDatum = CStringGetDatum((char *) str); break; default: valueDatum = CStringGetDatum((char *) column->value); } value_datum = OidFunctionCall3(typeinput, valueDatum, ObjectIdGetDatum(pgtyp), Int32GetDatum(typemod)); return value_datum; } /* * mysql_from_pgtyp: * Give MySQL data type for PG type */ static int32 mysql_from_pgtyp(Oid type) { switch (type) { case INT2OID: return MYSQL_TYPE_SHORT; case INT4OID: return MYSQL_TYPE_LONG; case INT8OID: return MYSQL_TYPE_LONGLONG; case FLOAT4OID: return MYSQL_TYPE_FLOAT; case FLOAT8OID: return MYSQL_TYPE_DOUBLE; case NUMERICOID: return MYSQL_TYPE_DOUBLE; case BOOLOID: return MYSQL_TYPE_LONG; case BPCHAROID: case VARCHAROID: case TEXTOID: case JSONOID: case ANYENUMOID: return MYSQL_TYPE_STRING; case NAMEOID: return MYSQL_TYPE_STRING; case DATEOID: return MYSQL_TYPE_DATE; case TIMEOID: case TIMESTAMPOID: case TIMESTAMPTZOID: return MYSQL_TYPE_TIMESTAMP; case BITOID: return MYSQL_TYPE_LONG; case BYTEAOID: return MYSQL_TYPE_BLOB; default: ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_DATA_TYPE), errmsg("cannot convert constant value to MySQL value"), errhint("Constant value data type: %u", type))); break; } } /* * bind_sql_var: * Bind the values provided as DatumBind the values and nulls to * modify the target table (INSERT/UPDATE) */ void mysql_bind_sql_var(Oid type, int attnum, Datum value, MYSQL_BIND *binds, bool *isnull) { /* Clear the bind buffer and attributes */ memset(&binds[attnum], 0x0, sizeof(MYSQL_BIND)); #if MYSQL_VERSION_ID < 80000 || MARIADB_VERSION_ID >= 100000 binds[attnum].is_null = (my_bool *) isnull; #else binds[attnum].is_null = isnull; #endif /* Avoid to bind buffer in case value is NULL */ if (*isnull) return; /* * If type is an enum, use ANYENUMOID. We will send string containing the * enum value to the MySQL. */ if (type_is_enum(type)) type = ANYENUMOID; /* Assign the buffer type if value is not null */ binds[attnum].buffer_type = mysql_from_pgtyp(type); switch (type) { case INT2OID: { int16 dat = DatumGetInt16(value); int16 *bufptr = palloc(sizeof(int16)); memcpy(bufptr, (char *) &dat, sizeof(int16)); binds[attnum].buffer = bufptr; } break; case INT4OID: { int32 dat = DatumGetInt32(value); int32 *bufptr = palloc(sizeof(int32)); memcpy(bufptr, (char *) &dat, sizeof(int32)); binds[attnum].buffer = bufptr; } break; case INT8OID: { int64 dat = DatumGetInt64(value); int64 *bufptr = palloc(sizeof(int64)); memcpy(bufptr, (char *) &dat, sizeof(int64)); binds[attnum].buffer = bufptr; } break; case FLOAT4OID: { float4 dat = DatumGetFloat4(value); float4 *bufptr = palloc(sizeof(float4)); memcpy(bufptr, (char *) &dat, sizeof(float4)); binds[attnum].buffer = bufptr; } break; case FLOAT8OID: { float8 dat = DatumGetFloat8(value); float8 *bufptr = palloc(sizeof(float8)); memcpy(bufptr, (char *) &dat, sizeof(float8)); binds[attnum].buffer = bufptr; } break; case NUMERICOID: { Datum valueDatum = DirectFunctionCall1(numeric_float8, value); float8 dat = DatumGetFloat8(valueDatum); float8 *bufptr = palloc(sizeof(float8)); memcpy(bufptr, (char *) &dat, sizeof(float8)); binds[attnum].buffer = bufptr; } break; case BOOLOID: { int32 dat = DatumGetInt32(value); int32 *bufptr = palloc(sizeof(int32)); memcpy(bufptr, (char *) &dat, sizeof(int32)); binds[attnum].buffer = bufptr; } break; case BPCHAROID: case VARCHAROID: case TEXTOID: case JSONOID: case ANYENUMOID: { char *outputString = NULL; Oid outputFunctionId = InvalidOid; bool typeVarLength = false; getTypeOutputInfo(type, &outputFunctionId, &typeVarLength); outputString = OidOutputFunctionCall(outputFunctionId, value); binds[attnum].buffer = outputString; binds[attnum].buffer_length = strlen(outputString); } break; case NAMEOID: { char *outputString = NULL; Oid outputFunctionId = InvalidOid; bool typeVarLength = false; getTypeOutputInfo(type, &outputFunctionId, &typeVarLength); outputString = OidOutputFunctionCall(outputFunctionId, value); binds[attnum].buffer = outputString; binds[attnum].buffer_length = strlen(outputString); } break; case DATEOID: { int tz; struct pg_tm tt, *tm = &tt; fsec_t fsec; const char *tzn; Datum valueDatum = DirectFunctionCall1(date_timestamp, value); Timestamp valueTimestamp = DatumGetTimestamp(valueDatum); MYSQL_TIME *ts = palloc0(sizeof(MYSQL_TIME)); timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, pg_tzset("UTC")); DATE_MYSQL_PG(ts, tt); binds[attnum].buffer = ts; binds[attnum].buffer_length = sizeof(MYSQL_TIME); } break; case TIMEOID: case TIMESTAMPOID: case TIMESTAMPTZOID: { Timestamp valueTimestamp = DatumGetTimestamp(value); MYSQL_TIME *ts = palloc0(sizeof(MYSQL_TIME)); int tz; struct pg_tm tt, *tm = &tt; fsec_t fsec; const char *tzn; timestamp2tm(valueTimestamp, &tz, tm, &fsec, &tzn, pg_tzset("UTC")); DATE_MYSQL_PG(ts, tt); binds[attnum].buffer = ts; binds[attnum].buffer_length = sizeof(MYSQL_TIME); } break; case BITOID: { int32 dat; int32 *bufptr = palloc0(sizeof(int32)); char *outputString = NULL; Oid outputFunctionId = InvalidOid; bool typeVarLength = false; getTypeOutputInfo(type, &outputFunctionId, &typeVarLength); outputString = OidOutputFunctionCall(outputFunctionId, value); dat = bin_dec(atoi(outputString)); memcpy(bufptr, (char *) &dat, sizeof(int32)); binds[attnum].buffer = bufptr; } break; case BYTEAOID: { int len; char *dat = NULL; char *bufptr; char *result = DatumGetPointer(value); if (VARATT_IS_1B(result)) { len = VARSIZE_1B(result) - VARHDRSZ_SHORT; dat = VARDATA_1B(result); } else { len = VARSIZE_4B(result) - VARHDRSZ; dat = VARDATA_4B(result); } bufptr = palloc(len); memcpy(bufptr, (char *) dat, len); binds[attnum].buffer = bufptr; binds[attnum].buffer_length = len; } break; default: ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_DATA_TYPE), errmsg("cannot convert constant value to MySQL value"), errhint("Constant value data type: %u", type))); break; } } /* * mysql_bind_result: * Bind the value and null pointers to get the data from * remote mysql table (SELECT) */ void mysql_bind_result(Oid pgtyp, int pgtypmod, MYSQL_FIELD *field, mysql_column *column) { MYSQL_BIND *mbind = column->mysql_bind; #if MYSQL_VERSION_ID < 80000 || MARIADB_VERSION_ID >= 100000 mbind->is_null = (my_bool *) &column->is_null; mbind->error = (my_bool *) &column->error; #else mbind->is_null = &column->is_null; mbind->error = &column->error; #endif mbind->length = &column->length; switch (pgtyp) { case BYTEAOID: mbind->buffer_type = MYSQL_TYPE_BLOB; /* Leave room at front for bytea buffer length prefix */ column->value = (Datum) palloc0(MAX_BLOB_WIDTH + VARHDRSZ); mbind->buffer = VARDATA(column->value); mbind->buffer_length = MAX_BLOB_WIDTH; break; default: mbind->buffer_type = MYSQL_TYPE_VAR_STRING; column->value = (Datum) palloc0(MAXDATALEN); mbind->buffer = (char *) column->value; mbind->buffer_length = MAXDATALEN; } } static int dec_bin(int number) { int rem; int i = 1; int bin = 0; while (number != 0) { rem = number % 2; number /= 2; bin += rem * i; i *= 10; } return bin; } static int bin_dec(int binarynumber) { int dec = 0; int i = 0; int rem; while (binarynumber != 0) { rem = binarynumber % 10; binarynumber /= 10; dec += rem * pow(2, i); ++i; } return dec; } mysql_fdw-REL-2_7_0/mysql_query.h000066400000000000000000000016551414541277500170730ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * mysql_query.h * Foreign-data wrapper for remote MySQL servers * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * mysql_query.h * *------------------------------------------------------------------------- */ #ifndef MYSQL_QUERY_H #define MYSQL_QUERY_H #include "foreign/foreign.h" #include "lib/stringinfo.h" #if PG_VERSION_NUM < 120000 #include "nodes/relation.h" #else #include "nodes/pathnodes.h" #endif #include "utils/rel.h" Datum mysql_convert_to_pg(Oid pgtyp, int pgtypmod, mysql_column *column); void mysql_bind_sql_var(Oid type, int attnum, Datum value, MYSQL_BIND *binds, bool *isnull); void mysql_bind_result(Oid pgtyp, int pgtypmod, MYSQL_FIELD *field, mysql_column *column); #endif /* MYSQL_QUERY_H */ mysql_fdw-REL-2_7_0/option.c000066400000000000000000000171171414541277500160040ustar00rootroot00000000000000/*------------------------------------------------------------------------- * * option.c * FDW option handling for mysql_fdw * * Portions Copyright (c) 2012-2014, PostgreSQL Global Development Group * Portions Copyright (c) 2004-2021, EnterpriseDB Corporation. * * IDENTIFICATION * option.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/reloptions.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_foreign_table.h" #include "catalog/pg_user_mapping.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "miscadmin.h" #include "mysql_fdw.h" #include "utils/lsyscache.h" /* * Describes the valid options for objects that use this wrapper. */ struct MySQLFdwOption { const char *optname; Oid optcontext; /* Oid of catalog in which option may appear */ }; /* * Valid options for mysql_fdw. */ static struct MySQLFdwOption valid_options[] = { /* Connection options */ {"host", ForeignServerRelationId}, {"port", ForeignServerRelationId}, {"init_command", ForeignServerRelationId}, {"username", UserMappingRelationId}, {"password", UserMappingRelationId}, {"dbname", ForeignTableRelationId}, {"table_name", ForeignTableRelationId}, {"secure_auth", ForeignServerRelationId}, {"max_blob_size", ForeignTableRelationId}, {"use_remote_estimate", ForeignServerRelationId}, /* fetch_size is available on both server and table */ {"fetch_size", ForeignServerRelationId}, {"fetch_size", ForeignTableRelationId}, {"reconnect", ForeignServerRelationId}, {"ssl_key", ForeignServerRelationId}, {"ssl_cert", ForeignServerRelationId}, {"ssl_ca", ForeignServerRelationId}, {"ssl_capath", ForeignServerRelationId}, {"ssl_cipher", ForeignServerRelationId}, /* Sentinel */ {NULL, InvalidOid} }; extern Datum mysql_fdw_validator(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(mysql_fdw_validator); /* * Validate the generic options given to a FOREIGN DATA WRAPPER, SERVER, * USER MAPPING or FOREIGN TABLE that uses file_fdw. * * Raise an ERROR if the option or its value is considered invalid. */ Datum mysql_fdw_validator(PG_FUNCTION_ARGS) { List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); Oid catalog = PG_GETARG_OID(1); ListCell *cell; /* * Check that only options supported by mysql_fdw, and allowed for the * current object type, are given. */ foreach(cell, options_list) { DefElem *def = (DefElem *) lfirst(cell); if (!mysql_is_valid_option(def->defname, catalog)) { struct MySQLFdwOption *opt; StringInfoData buf; /* * Unknown option specified, complain about it. Provide a hint * with list of valid options for the object. */ initStringInfo(&buf); for (opt = valid_options; opt->optname; opt++) { if (catalog == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), errhint("Valid options in this context are: %s", buf.len ? buf.data : ""))); } /* Validate fetch_size option value */ if (strcmp(def->defname, "fetch_size") == 0) { unsigned long fetch_size; char *endptr; char *inputVal = defGetString(def); while(inputVal && isspace((unsigned char) *inputVal)) inputVal++; if (inputVal && *inputVal == '-') ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("\"%s\" requires an integer value between 1 to %lu", def->defname, ULONG_MAX))); errno = 0; fetch_size = strtoul(inputVal, &endptr, 10); if (*endptr != '\0' || (errno == ERANGE && fetch_size == ULONG_MAX) || fetch_size == 0) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("\"%s\" requires an integer value between 1 to %lu", def->defname, ULONG_MAX))); } else if (strcmp(def->defname, "reconnect") == 0) { /* accept only boolean values */ (void) defGetBoolean(def); } } PG_RETURN_VOID(); } /* * Check if the provided option is one of the valid options. * context is the Oid of the catalog holding the object the option is for. */ bool mysql_is_valid_option(const char *option, Oid context) { struct MySQLFdwOption *opt; for (opt = valid_options; opt->optname; opt++) { if (context == opt->optcontext && strcmp(opt->optname, option) == 0) return true; } return false; } /* * Fetch the options for a mysql_fdw foreign table. */ mysql_opt * mysql_get_options(Oid foreignoid, bool is_foreigntable) { ForeignTable *f_table; ForeignServer *f_server; UserMapping *f_mapping; List *options; ListCell *lc; mysql_opt *opt; opt = (mysql_opt *) palloc0(sizeof(mysql_opt)); /* * Extract options from FDW objects. */ if (is_foreigntable) { f_table = GetForeignTable(foreignoid); f_server = GetForeignServer(f_table->serverid); } else { f_table = NULL; f_server = GetForeignServer(foreignoid); } f_mapping = GetUserMapping(GetUserId(), f_server->serverid); options = NIL; options = mysql_list_concat(options, f_server->options); options = mysql_list_concat(options, f_mapping->options); if (f_table) options = mysql_list_concat(options, f_table->options); /* Default secure authentication is true */ opt->svr_sa = true; opt->use_remote_estimate = false; opt->reconnect = false; /* Loop through the options */ foreach(lc, options) { DefElem *def = (DefElem *) lfirst(lc); if (strcmp(def->defname, "host") == 0) opt->svr_address = defGetString(def); if (strcmp(def->defname, "port") == 0) opt->svr_port = atoi(defGetString(def)); if (strcmp(def->defname, "username") == 0) opt->svr_username = defGetString(def); if (strcmp(def->defname, "password") == 0) opt->svr_password = defGetString(def); if (strcmp(def->defname, "dbname") == 0) opt->svr_database = defGetString(def); if (strcmp(def->defname, "table_name") == 0) opt->svr_table = defGetString(def); if (strcmp(def->defname, "secure_auth") == 0) opt->svr_sa = defGetBoolean(def); if (strcmp(def->defname, "init_command") == 0) opt->svr_init_command = defGetString(def); if (strcmp(def->defname, "max_blob_size") == 0) opt->max_blob_size = strtoul(defGetString(def), NULL, 0); if (strcmp(def->defname, "use_remote_estimate") == 0) opt->use_remote_estimate = defGetBoolean(def); if (strcmp(def->defname, "fetch_size") == 0) opt->fetch_size = strtoul(defGetString(def), NULL, 10); if (strcmp(def->defname, "reconnect") == 0) opt->reconnect = defGetBoolean(def); if (strcmp(def->defname, "ssl_key") == 0) opt->ssl_key = defGetString(def); if (strcmp(def->defname, "ssl_cert") == 0) opt->ssl_cert = defGetString(def); if (strcmp(def->defname, "ssl_ca") == 0) opt->ssl_ca = defGetString(def); if (strcmp(def->defname, "ssl_capath") == 0) opt->ssl_capath = defGetString(def); if (strcmp(def->defname, "ssl_cipher") == 0) opt->ssl_cipher = defGetString(def); } /* Default values, if required */ if (!opt->svr_address) opt->svr_address = "127.0.0.1"; if (!opt->svr_port) opt->svr_port = MYSQL_SERVER_PORT; /* * When we don't have a table name or database name provided in the * FOREIGN TABLE options, then use a foreign table name as the target table * name and the namespace of the foreign table as a database name. */ if (f_table) { if (!opt->svr_table) opt->svr_table = get_rel_name(foreignoid); if (!opt->svr_database) opt->svr_database = get_namespace_name(get_rel_namespace(foreignoid)); } /* Default value for fetch_size */ if (!opt->fetch_size) opt->fetch_size = MYSQL_PREFETCH_ROWS; return opt; } mysql_fdw-REL-2_7_0/sql/000077500000000000000000000000001414541277500151205ustar00rootroot00000000000000mysql_fdw-REL-2_7_0/sql/aggregate_pushdown.sql000066400000000000000000000405721414541277500215260ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-132: Support for aggregate pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw132_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw132_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); INSERT INTO fdw132_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw132_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw132_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw132_t2 values(12, 200, 'BBB12', 'foo'); ALTER FOREIGN TABLE fdw132_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw132_t2 ALTER COLUMN c4 type user_enum; -- Simple aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; SELECT sum(c1), avg(c1), min(c2), max(c1), sum(c1) * (random() <= 1)::int AS sum2 FROM fdw132_t1 WHERE c2 > 5 GROUP BY c2 ORDER BY 1, 2; -- Aggregate is not pushed down as aggregation contains random() EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; SELECT sum(c1 * (random() <= 1)::int) AS sum, avg(c1) FROM fdw132_t1; -- Aggregate over join query EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; SELECT count(*), sum(t1.c1), avg(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 = 2; -- Not pushed down due to local conditions present in underneath input rel EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; SELECT sum(t1.c1), count(t2.c1) FROM fdw132_t1 t1 INNER JOIN fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE ((t1.c1 * t2.c1)/(t1.c1 * t2.c1)) * random() <= 1; -- GROUP BY clause HAVING expressions EXPLAIN (VERBOSE, COSTS OFF) SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; SELECT c2+2, sum(c2) * (c2+2) FROM fdw132_t1 GROUP BY c2+2 ORDER BY c2+2; -- Aggregates in subquery are pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; SELECT count(x.a), sum(x.a) FROM (SELECT c2 a, sum(c1) b FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2) x; -- Aggregate is still pushed down by taking unshippable expression out EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; SELECT c2 * (random() <= 1)::int AS sum1, sum(c1) * c2 AS sum2 FROM fdw132_t1 GROUP BY c2 ORDER BY 1, 2; -- Aggregate with unshippable GROUP BY clause are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; SELECT c2 * (random() <= 1)::int AS c2 FROM fdw132_t2 GROUP BY c2 * (random() <= 1)::int ORDER BY 1; -- GROUP BY clause in various forms, cardinal, alias and constant expression EXPLAIN (VERBOSE, COSTS OFF) SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; SELECT count(c2) w, c2 x, 5 y, 7.0 z FROM fdw132_t1 GROUP BY 2, y, 9.0::int ORDER BY 2; -- Testing HAVING clause shippability SET enable_sort TO ON; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; SELECT c2, sum(c1) FROM fdw132_t2 GROUP BY c2 HAVING avg(c1) < 500 and sum(c1) < 49800 ORDER BY c2; -- Using expressions in HAVING clause EXPLAIN (VERBOSE, COSTS OFF) SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING sqrt(max(c1)) = sqrt(2) ORDER BY 1, 2; SET enable_sort TO off; -- Unshippable HAVING clause will be evaluated locally, and other qual in HAVING clause is pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; SELECT count(*) FROM (SELECT c3, count(c1) FROM fdw132_t1 GROUP BY c3 HAVING (avg(c1) / avg(c1)) * random() <= 1 and avg(c1) < 500) x; -- Aggregate in HAVING clause is not pushable, and thus aggregation is not pushed down EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; SELECT sum(c1) FROM fdw132_t1 GROUP BY c2 HAVING avg(c1 * (random() <= 1)::int) > 1 ORDER BY 1; -- Testing ORDER BY, DISTINCT, FILTER, Ordered-sets and VARIADIC within aggregates -- ORDER BY within aggregates (same column used to order) are not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; SELECT sum(c1 ORDER BY c1) FROM fdw132_t1 WHERE c1 < 100 GROUP BY c2 ORDER BY 1; -- ORDER BY within aggregate (different column used to order also using DESC) -- are not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; SELECT sum(c2 ORDER BY c1 desc) FROM fdw132_t2 WHERE c1 > 1 and c2 > 50; -- DISTINCT within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; SELECT sum(DISTINCT (c1)%5) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; SELECT sum(DISTINCT (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; -- DISTINCT with aggregate within aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; SELECT sum(DISTINCT c1) FROM fdw132_t2 WHERE c2 = 200 and c1 < 50; -- DISTINCT combined with ORDER BY within aggregate is not pushed. EXPLAIN (VERBOSE, COSTS OFF) SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; SELECT array_agg(DISTINCT (t1.c1)%5 ORDER BY (t1.c1)%5) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 20 or (t1.c1 is null and t2.c1 < 5) GROUP BY (t2.c1)%3 ORDER BY 1; -- DISTINCT, ORDER BY and FILTER within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; SELECT sum(c1%3), sum(DISTINCT c1%3 ORDER BY c1%3) filter (WHERE c1%3 < 2), c2 FROM fdw132_t1 WHERE c2 = 100 GROUP BY c2; -- FILTER within aggregate, not pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; SELECT sum(c1) filter (WHERE c1 < 100 and c2 > 5) FROM fdw132_t1 GROUP BY c2 ORDER BY 1 nulls last; -- Outer query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; SELECT DISTINCT (SELECT count(*) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; -- Inner query is aggregation query EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; SELECT DISTINCT (SELECT count(t1.c1) filter (WHERE t2.c2 = 200 and t2.c1 < 10) FROM fdw132_t1 t1 WHERE t1.c1 = 2) FROM fdw132_t2 t2 ORDER BY 1; -- Ordered-sets within aggregate, not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; SELECT c2, rank('10'::varchar) within group (ORDER BY c3), percentile_cont(c2/200::numeric) within group (ORDER BY c1) FROM fdw132_t2 GROUP BY c2 HAVING percentile_cont(c2/200::numeric) within group (ORDER BY c1) < 500 ORDER BY c2; -- Using multiple arguments within aggregates EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; SELECT c1, rank(c1, c2) within group (ORDER BY c1, c2) FROM fdw132_t1 GROUP BY c1, c2 HAVING c1 = 2 ORDER BY 1; -- Subquery in FROM clause HAVING aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; SELECT count(*), x.b FROM fdw132_t1, (SELECT c1 a, sum(c1) b FROM fdw132_t2 GROUP BY c1) x WHERE fdw132_t1.c1 = x.a GROUP BY x.b ORDER BY 1, 2; -- Join with IS NULL check in HAVING EXPLAIN (VERBOSE, COSTS OFF) SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; SELECT avg(t1.c1), sum(t2.c1) FROM fdw132_t1 t1 join fdw132_t2 t2 ON (t1.c1 = t2.c1) GROUP BY t2.c1 HAVING (avg(t1.c1) is null and sum(t2.c1) > 10) or sum(t2.c1) is null ORDER BY 1 nulls last, 2; -- ORDER BY expression is part of the target list but not pushed down to -- foreign server. EXPLAIN (VERBOSE, COSTS OFF) SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; SELECT sum(c2) * (random() <= 1)::int AS sum FROM fdw132_t1 ORDER BY 1; -- LATERAL join, with parameterization EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum FROM fdw132_t1 t1, lateral (SELECT sum(t2.c1 + t1.c1) sum FROM fdw132_t2 t2 GROUP BY t2.c1) qry WHERE t1.c2 * 2 = qry.sum and t1.c2 > 10 ORDER BY 1; -- Check with placeHolderVars EXPLAIN (VERBOSE, COSTS OFF) SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; SELECT q.b, count(fdw132_t1.c1), sum(q.a) FROM fdw132_t1 left join (SELECT min(13), avg(fdw132_t1.c1), sum(fdw132_t2.c1) FROM fdw132_t1 right join fdw132_t2 ON (fdw132_t1.c1 = fdw132_t2.c1) WHERE fdw132_t1.c1 = 12) q(a, b, c) ON (fdw132_t1.c1 = q.b) WHERE fdw132_t1.c1 between 10 and 15 GROUP BY q.b ORDER BY 1 nulls last, 2; -- Not supported cases -- Grouping sets EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY rollup(c2) ORDER BY 1 nulls last; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; SELECT c2, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY cube(c2) ORDER BY 1 nulls last; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; SELECT c2, c3, sum(c1) FROM fdw132_t1 WHERE c2 > 3 GROUP BY grouping sets(c2, c3) ORDER BY 1 nulls last, 2 nulls last; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; SELECT c2, sum(c1), grouping(c2) FROM fdw132_t1 WHERE c2 > 3 GROUP BY c2 ORDER BY 1 nulls last; -- DISTINCT itself is not pushed down, whereas underneath aggregate is pushed EXPLAIN (VERBOSE, COSTS OFF) SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; SELECT DISTINCT sum(c1) s FROM fdw132_t1 WHERE c2 > 6 GROUP BY c2 ORDER BY 1; -- WindowAgg EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; SELECT c2, sum(c2), count(c2) over (partition by c2%2) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 desc) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; SELECT c2, array_agg(c2) over (partition by c2%2 ORDER BY c2 range between current row and unbounded following) FROM fdw132_t1 WHERE c2 > 10 GROUP BY c2 ORDER BY 1; -- User defined function for user defined aggregate, VARIADIC CREATE FUNCTION least_accum(anyelement, variadic anyarray) returns anyelement language sql AS 'SELECT least($1, min($2[i])) FROM generate_subscripts($2,2) g(i)'; CREATE aggregate least_agg(variadic items anyarray) ( stype = anyelement, sfunc = least_accum ); -- Not pushed down due to user defined aggregate EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; SELECT c2, least_agg(c1) FROM fdw132_t1 GROUP BY c2 ORDER BY c2; -- Delete existing data and load new data for partition-wise aggregate test -- cases. DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; INSERT INTO fdw132_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw132_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw132_t2 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw132_t2 values(4, 4, 'AAA12', 'foo'); -- Test partition-wise aggregates SET enable_partitionwise_aggregate TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 as -- partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (2) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (3) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); -- Plan with partitionwise aggregates is enabled EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; SELECT c1, sum(c1) FROM fprt1 GROUP BY c1 ORDER BY 2; EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 2; SELECT c1, sum(c2), min(c2), count(*) FROM fprt1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; -- Check with whole-row reference -- Should have all the columns in the target list for the given relation EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; SELECT c1, count(t1) FROM fprt1 t1 GROUP BY c1 HAVING avg(c2) < 22 ORDER BY 1; -- When GROUP BY clause does not match with PARTITION KEY. EXPLAIN (VERBOSE, COSTS OFF) SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; SELECT c2, avg(c1), max(c1), count(*) FROM fprt1 GROUP BY c2 HAVING sum(c1) < 700 ORDER BY 1; SET enable_partitionwise_aggregate TO off; -- Cleanup DROP aggregate least_agg(variadic items anyarray); DROP FUNCTION least_accum(anyelement, variadic anyarray); DELETE FROM fdw132_t1; DELETE FROM fdw132_t2; DROP FOREIGN TABLE fdw132_t1; DROP FOREIGN TABLE fdw132_t2; DROP FOREIGN TABLE ftprt1_p1; DROP FOREIGN TABLE ftprt1_p2; DROP TABLE IF EXISTS fprt1; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/connection_validation.sql000066400000000000000000000054511414541277500222170ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign table and Validate CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); SELECT * FROM f_mysql_test ORDER BY 1, 2; -- FDW-121: After a change to a pg_foreign_server or pg_user_mapping catalog -- entry, existing connection should be invalidated and should make new -- connection using the updated connection details. -- Alter SERVER option. -- Set wrong host, subsequent operation on this server should use updated -- details and fail as the host address is not correct. The error code in error -- message is different for different server versions and platform, so check -- that through plpgsql block and give the generic error message. ALTER SERVER mysql_svr OPTIONS (SET host 'localhos'); DO $$ BEGIN SELECT * FROM f_mysql_test ORDER BY 1, 2; EXCEPTION WHEN others THEN IF SQLERRM LIKE 'failed to connect to MySQL: Unknown MySQL server host ''localhos'' (%)' THEN RAISE NOTICE 'failed to connect to MySQL: Unknown MySQL server host ''localhos'''; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; -- Set the correct host-name, next operation should succeed. ALTER SERVER mysql_svr OPTIONS (SET host :MYSQL_HOST); SELECT * FROM f_mysql_test ORDER BY 1, 2; -- Alter USER MAPPING option. -- Set wrong password, next operation should fail. ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (SET username :MYSQL_USER_NAME, SET password 'bar1'); DO $$ BEGIN SELECT * FROM f_mysql_test ORDER BY 1, 2; EXCEPTION WHEN others THEN IF SQLERRM LIKE 'failed to connect to MySQL: Access denied for user ''%''@''%'' (using password: YES)' THEN RAISE NOTICE 'failed to connect to MySQL: Access denied for MYSQL_USER_NAME'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; -- Set correct user-name and password, next operation should succeed. ALTER USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (SET username :MYSQL_USER_NAME, SET password :MYSQL_PASS); SELECT * FROM f_mysql_test ORDER BY 1, 2; -- Cleanup DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/dml.sql000066400000000000000000000215401414541277500164170ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign tables CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); CREATE FOREIGN TABLE fdw126_ft1(stu_id int, stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student'); CREATE FOREIGN TABLE fdw126_ft2(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'student'); CREATE FOREIGN TABLE fdw126_ft3(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'numbers'); CREATE FOREIGN TABLE fdw126_ft4(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'nosuchtable'); CREATE FOREIGN TABLE fdw126_ft5(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress2', table_name 'numbers'); CREATE FOREIGN TABLE fdw126_ft6(stu_id int, stu_name varchar(255)) SERVER mysql_svr OPTIONS (table_name 'mysql_fdw_regress1.student'); CREATE FOREIGN TABLE f_empdata(emp_id int, emp_dat bytea) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'empdata'); CREATE FOREIGN TABLE fdw193_ft1(stu_id varchar(10), stu_name varchar(255), stu_dept int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress1', table_name 'student1'); -- Operation on blob data. INSERT INTO f_empdata VALUES (1, decode ('01234567', 'hex')); INSERT INTO f_empdata VALUES (2, 'abc'); SELECT count(*) FROM f_empdata ORDER BY 1; SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; UPDATE f_empdata SET emp_dat = decode ('0123', 'hex') WHERE emp_id = 1; SELECT emp_id, emp_dat FROM f_empdata ORDER BY 1; -- FDW-126: Insert/update/delete statement failing in mysql_fdw by picking -- wrong database name. -- Verify the INSERT/UPDATE/DELETE operations on another foreign table which -- resides in the another database in MySQL. The previous commands performs -- the operation on foreign table created for tables in mysql_fdw_regress -- MySQL database. Below operations will be performed for foreign table -- created for table in mysql_fdw_regress1 MySQL database. INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); UPDATE fdw126_ft1 SET stu_name = 'one' WHERE stu_id = 1; DELETE FROM fdw126_ft1 WHERE stu_id = 1; -- Select on f_mysql_test foreign table which is created for mysql_test table -- from mysql_fdw_regress MySQL database. This call is just to cross verify if -- everything is working correctly. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; -- Insert into fdw126_ft2 table which does not have dbname specified while -- creating the foreign table, so it will consider the schema name of foreign -- table as database name and try to connect/lookup into that database. Will -- throw an error. The error message is different on old mysql and mariadb -- servers so give the generic message. DO $$ BEGIN INSERT INTO fdw126_ft2 VALUES(2, 'Two'); EXCEPTION WHEN others THEN IF SQLERRM LIKE '%SELECT command denied to user ''%''@''%'' for table ''student''' THEN RAISE NOTICE E'failed to execute the MySQL query: \nUnknown database ''public'''; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; -- Check with the same table name from different database. fdw126_ft3 is -- pointing to the mysql_fdw_regress1.numbers and not mysql_fdw_regress.numbers -- table. INSERT/UPDATE/DELETE should be failing. SELECT will return no rows. INSERT INTO fdw126_ft3 VALUES(1, 'One'); SELECT a, b FROM fdw126_ft3 ORDER BY 1, 2 LIMIT 1; UPDATE fdw126_ft3 SET b = 'one' WHERE a = 1; DELETE FROM fdw126_ft3 WHERE a = 1; -- Check when table_name is given in database.table form in foreign table -- should error out as syntax error. The error contains server name like -- MySQL or MariaDB, so give the generic message by removing the server name, so -- that it should pass on both the servers. DO $$ BEGIN INSERT INTO fdw126_ft6 VALUES(1, 'One'); EXCEPTION WHEN others THEN IF SQLERRM LIKE '%You have an error in your SQL syntax; check the manual % for the right syntax to use near ''.student'' at line 1' THEN RAISE NOTICE E'failed to execute the MySQL query: \nYou have an error in your SQL syntax; check the manual that corresponds to your server version for the right syntax to use near ''.student'' at line 1'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; -- Perform the ANALYZE on the foreign table which is not present on the remote -- side. Should not crash. -- The database is present but not the target table. ANALYZE fdw126_ft4; -- The database itself is not present. ANALYZE fdw126_ft5; -- Some other variant of analyze and vacuum. -- when table exists, should give skip-warning VACUUM f_empdata; VACUUM FULL f_empdata; VACUUM FREEZE f_empdata; ANALYZE f_empdata; ANALYZE f_empdata(emp_id); VACUUM ANALYZE f_empdata; -- Verify the before update trigger which modifies the column value which is not -- part of update statement. CREATE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; RETURN NEW; END $$ language plpgsql; CREATE TRIGGER before_row_update_trig BEFORE UPDATE ON fdw126_ft1 FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); INSERT INTO fdw126_ft1 VALUES(1, 'One', 101); UPDATE fdw126_ft1 SET stu_dept = 201 WHERE stu_id = 1; SELECT * FROM fdw126_ft1 ORDER BY stu_id; -- Throw an error when target list has row identifier column. UPDATE fdw126_ft1 SET stu_dept = 201, stu_id = 10 WHERE stu_id = 1; -- Throw an error when before row update trigger modify the row identifier -- column (int column) value. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = 20; RETURN NEW; END $$ language plpgsql; UPDATE fdw126_ft1 SET stu_dept = 301 WHERE stu_id = 1; -- Verify the before update trigger which modifies the column value which is -- not part of update statement. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; RETURN NEW; END $$ language plpgsql; CREATE TRIGGER before_row_update_trig1 BEFORE UPDATE ON fdw193_ft1 FOR EACH ROW EXECUTE PROCEDURE before_row_update_func(); INSERT INTO fdw193_ft1 VALUES('aa', 'One', 101); UPDATE fdw193_ft1 SET stu_dept = 201 WHERE stu_id = 'aa'; SELECT * FROM fdw193_ft1 ORDER BY stu_id; -- Throw an error when before row update trigger modify the row identifier -- column (varchar column) value. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = 'bb'; RETURN NEW; END $$ language plpgsql; UPDATE fdw193_ft1 SET stu_dept = 301 WHERE stu_id = 'aa'; -- Verify the NULL assignment scenario. CREATE OR REPLACE FUNCTION before_row_update_func() RETURNS TRIGGER AS $$ BEGIN NEW.stu_name := NEW.stu_name || ' trigger updated!'; NEW.stu_id = NULL; RETURN NEW; END $$ language plpgsql; UPDATE fdw193_ft1 SET stu_dept = 401 WHERE stu_id = 'aa'; -- FDW-224 Fix COPY FROM and foreign partition routing result in server crash -- Should fail as foreign table direct copy not supported COPY f_mysql_test TO stdout; COPY f_mysql_test (a) TO stdout; -- Should pass COPY (SELECT * FROM f_mysql_test) TO stdout; COPY (SELECT a FROM f_mysql_test) TO '/tmp/copy_test.txt' delimiter ','; -- Should give error message as copy from with foreign table not supported DO $$ BEGIN COPY f_mysql_test(a) FROM '/tmp/copy_test.txt' delimiter ','; EXCEPTION WHEN others THEN IF SQLERRM = 'COPY and foreign partition routing not supported in mysql_fdw' OR SQLERRM = 'cannot copy to foreign table "f_mysql_test"' THEN RAISE NOTICE 'ERROR: COPY and foreign partition routing not supported in mysql_fdw'; ELSE RAISE NOTICE '%', SQLERRM; END IF; END; $$ LANGUAGE plpgsql; -- Cleanup DELETE FROM fdw126_ft1; DELETE FROM f_empdata; DELETE FROM fdw193_ft1; DROP FOREIGN TABLE f_mysql_test; DROP FOREIGN TABLE fdw126_ft1; DROP FOREIGN TABLE fdw126_ft2; DROP FOREIGN TABLE fdw126_ft3; DROP FOREIGN TABLE fdw126_ft4; DROP FOREIGN TABLE fdw126_ft5; DROP FOREIGN TABLE fdw126_ft6; DROP FOREIGN TABLE f_empdata; DROP FOREIGN TABLE fdw193_ft1; DROP FUNCTION before_row_update_func(); DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/join_pushdown.sql000066400000000000000000000466141414541277500205420ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; -- FDW-139: Support for JOIN pushdown. CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE TYPE user_enum AS ENUM ('foo', 'bar', 'buz'); CREATE FOREIGN TABLE fdw139_t1(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE fdw139_t2(c1 int, c2 int, c3 text COLLATE "C", c4 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test2'); CREATE FOREIGN TABLE fdw139_t3(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE fdw139_t4(c1 int, c2 int, c3 text COLLATE "C") SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test3'); INSERT INTO fdw139_t1 values(1, 100, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 100, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(11, 100, 'AAA11', 'foo'); INSERT INTO fdw139_t2 values(1, 200, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(2, 200, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(12, 200, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 300, 'CCC1'); INSERT INTO fdw139_t3 values(2, 300, 'CCC2'); INSERT INTO fdw139_t3 values(13, 300, 'CCC13'); SET enable_mergejoin TO off; SET enable_hashjoin TO off; SET enable_sort TO off; ALTER FOREIGN TABLE fdw139_t1 ALTER COLUMN c4 type user_enum; ALTER FOREIGN TABLE fdw139_t2 ALTER COLUMN c4 type user_enum; -- Join two tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; -- INNER JOIN with where condition. Should execute where condition separately -- on remote side. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; -- INNER JOIN in which join clause is not pushable. -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t1.c2 = 100 ORDER BY t1.c3, t1.c1; -- Join three tables -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) JOIN fdw139_t3 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1; EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; SELECT t1.c1, t2.c1, t3.c1 FROM fdw139_t1 t1, fdw139_t2 t2, fdw139_t3 t3 WHERE t1.c1 = 11 AND t2.c1 = 12 AND t3.c1 = 13 ORDER BY t1.c1; -- LEFT OUTER JOIN EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1 NULLS LAST; -- LEFT JOIN evaluating as INNER JOIN, having unsafe join clause. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) WHERE t2.c1 > 1 ORDER BY t1.c1, t2.c1; -- LEFT OUTER JOIN in which join clause is not pushable. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (abs(t1.c1) = t2.c1) ORDER BY t1.c1, t2.c1; -- LEFT OUTER JOIN + placement of clauses. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE t1.c1 < 10; -- Clauses within the nullable side are not pulled up, but the top level clause -- on nullable side is not pushed down into nullable side EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; SELECT t1.c1, t1.c2, t2.c1, t2.c2 FROM fdw139_t1 t1 LEFT JOIN (SELECT * FROM fdw139_t2 WHERE c1 < 10) t2 ON (t1.c1 = t2.c1) WHERE (t2.c1 < 10 OR t2.c1 IS NULL) AND t1.c1 < 10; -- RIGHT OUTER JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 RIGHT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t2.c1, t1.c1 NULLS LAST; -- Combinations of various joins -- INNER JOIN + RIGHT JOIN -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; SELECT t1.c1, t2.c2, t3.c3 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) RIGHT JOIN fdw139_t3 t3 ON (t1.c1 = t3.c1) ORDER BY t1.c1 NULLS LAST, t1.c3, t1.c1; -- FULL OUTER JOIN, should not be pushdown as target database doesn't support -- it. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 FULL JOIN fdw139_t1 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1; -- Join two tables with FOR UPDATE clause -- tests whole-row reference for row marks -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE OF t1; -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR UPDATE; -- Join two tables with FOR SHARE clause -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE OF t1; -- target list order is different for v10 and v96. EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 FOR SHARE; -- Join in CTE. -- Explain plan difference between v11 (or pre) and later. EXPLAIN (COSTS false, VERBOSE) WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; WITH t (c1_1, c1_3, c2_1) AS ( SELECT t1.c1, t1.c3, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ) SELECT c1_1, c2_1 FROM t ORDER BY c1_3, c1_1; -- Whole-row reference EXPLAIN (COSTS false, VERBOSE) SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; SELECT t1, t2, t1.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; -- SEMI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; SELECT t1.c1 FROM fdw139_t1 t1 WHERE EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c1) ORDER BY t1.c1 LIMIT 10; -- ANTI JOIN, not pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; SELECT t1.c1 FROM fdw139_t1 t1 WHERE NOT EXISTS (SELECT 1 FROM fdw139_t2 t2 WHERE t1.c1 = t2.c2) ORDER BY t1.c1 LIMIT 10; -- CROSS JOIN can be pushed down EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 ORDER BY t1.c1, t2.c1 LIMIT 10; -- CROSS JOIN combined with local table. CREATE TABLE local_t1(c1 int); INSERT INTO local_t1 VALUES (1), (2); EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; SELECT t1.c1, t2.c1, l1.c1 FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1 ORDER BY t1.c1, t2.c1, l1.c1 LIMIT 10; SELECT count(t1.c1) FROM fdw139_t1 t1 CROSS JOIN fdw139_t2 t2 CROSS JOIN local_t1 l1; -- Join two tables from two different foreign table EXPLAIN (COSTS false, VERBOSE) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t4 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1; -- Unsafe join conditions (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c4 = t2.c4) ORDER BY t1.c1, t2.c1; -- Unsafe conditions on one side (c4 has a UDT), not pushed down. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = 'foo' ORDER BY t1.c1, t2.c1 NULLS LAST; -- Join where unsafe to pushdown condition in WHERE clause has a column not -- in the SELECT clause. In this test unsafe clause needs to have column -- references from both joining sides so that the clause is not pushed down -- into one of the joining sides. -- target list order is different for v10 and v96. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; SELECT t1.c1, t2.c1 FROM fdw139_t1 t1 JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) WHERE t1.c4 = t2.c4 ORDER BY t1.c3, t1.c1; -- Check join pushdown in situations where multiple userids are involved CREATE ROLE regress_view_owner SUPERUSER; CREATE USER MAPPING FOR regress_view_owner SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); GRANT SELECT ON fdw139_t1 TO regress_view_owner; GRANT SELECT ON fdw139_t2 TO regress_view_owner; CREATE VIEW v1 AS SELECT * FROM fdw139_t1; CREATE VIEW v2 AS SELECT * FROM fdw139_t2; ALTER VIEW v2 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, different view owners SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; ALTER VIEW v1 OWNER TO regress_view_owner; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN v2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- not pushed down, view owner not current user SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; ALTER VIEW v1 OWNER TO CURRENT_USER; EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; -- pushed down SELECT t1.c1, t2.c2 FROM v1 t1 LEFT JOIN fdw139_t2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c1, t2.c1, t2.c2 NULLS LAST LIMIT 10; ALTER VIEW v1 OWNER TO regress_view_owner; -- Non-Var items in targetlist of the nullable rel of a join preventing -- push-down in some cases -- Unable to push {fdw139_t1, fdw139_t2} EXPLAIN (VERBOSE, COSTS OFF) SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; SELECT q.a, fdw139_t2.c1 FROM (SELECT 13 FROM fdw139_t1 WHERE c1 = 13) q(a) RIGHT JOIN fdw139_t2 ON (q.a = fdw139_t2.c1) WHERE fdw139_t2.c1 BETWEEN 10 AND 15; -- Ok to push {fdw139_t1, fdw139_t2 but not {fdw139_t1, fdw139_t2, fdw139_t3} EXPLAIN (VERBOSE, COSTS OFF) SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; SELECT fdw139_t3.c1, q.* FROM fdw139_t3 LEFT JOIN ( SELECT 13, fdw139_t1.c1, fdw139_t2.c1 FROM fdw139_t1 RIGHT JOIN fdw139_t2 ON (fdw139_t1.c1 = fdw139_t2.c1) WHERE fdw139_t1.c1 = 11 ) q(a, b, c) ON (fdw139_t3.c1 = q.b) WHERE fdw139_t3.c1 BETWEEN 10 AND 15; -- Delete existing data and load new data for partition-wise join test cases. DROP OWNED BY regress_view_owner; DROP ROLE regress_view_owner; DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; INSERT INTO fdw139_t1 values(1, 1, 'AAA1', 'foo'); INSERT INTO fdw139_t1 values(2, 2, 'AAA2', 'bar'); INSERT INTO fdw139_t1 values(3, 3, 'AAA11', 'foo'); INSERT INTO fdw139_t1 values(4, 4, 'AAA12', 'foo'); INSERT INTO fdw139_t2 values(5, 5, 'BBB1', 'foo'); INSERT INTO fdw139_t2 values(6, 6, 'BBB2', 'bar'); INSERT INTO fdw139_t2 values(7, 7, 'BBB11', 'foo'); INSERT INTO fdw139_t2 values(8, 8, 'BBB12', 'foo'); INSERT INTO fdw139_t3 values(1, 1, 'CCC1'); INSERT INTO fdw139_t3 values(2, 2, 'CCC2'); INSERT INTO fdw139_t3 values(3, 3, 'CCC13'); INSERT INTO fdw139_t3 values(4, 4, 'CCC14'); DROP FOREIGN TABLE fdw139_t4; CREATE FOREIGN TABLE tmp_t4(c1 int, c2 int, c3 text) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'test4'); INSERT INTO tmp_t4 values(5, 5, 'CCC1'); INSERT INTO tmp_t4 values(6, 6, 'CCC2'); INSERT INTO tmp_t4 values(7, 7, 'CCC13'); INSERT INTO tmp_t4 values(8, 8, 'CCC13'); -- Test partition-wise join SET enable_partitionwise_join TO on; -- Create the partition table in plpgsql block as those are failing with -- different error messages on back-branches. -- All test cases related to partition-wise join gives an error on v96 and v95 -- as partition syntax is not supported there. DO $$ BEGIN EXECUTE 'CREATE TABLE fprt1 (c1 int, c2 int, c3 varchar, c4 varchar) PARTITION BY RANGE(c1)'; EXCEPTION WHEN others THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt1_p1 PARTITION OF fprt1 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test1'); CREATE FOREIGN TABLE ftprt1_p2 PARTITION OF fprt1 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test2'); DO $$ BEGIN EXECUTE 'CREATE TABLE fprt2 (c1 int, c2 int, c3 varchar) PARTITION BY RANGE(c2)'; EXCEPTION WHEN syntax_error THEN RAISE NOTICE 'syntax error'; END; $$ LANGUAGE plpgsql; CREATE FOREIGN TABLE ftprt2_p1 PARTITION OF fprt2 FOR VALUES FROM (1) TO (4) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test3'); CREATE FOREIGN TABLE ftprt2_p2 PARTITION OF fprt2 FOR VALUES FROM (5) TO (8) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', TABLE_NAME 'test4'); -- Inner join three tables -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; SELECT t1.c1,t2.c2,t3.c3 FROM fprt1 t1 INNER JOIN fprt2 t2 ON (t1.c1 = t2.c2) INNER JOIN fprt1 t3 ON (t2.c2 = t3.c1) WHERE t1.c1 % 2 =0 ORDER BY 1,2,3; -- With whole-row reference; partitionwise join does not apply -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS false) SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; SELECT t1, t2, t1.c1 FROM fprt1 t1 JOIN fprt2 t2 ON (t1.c1 = t2.c2) ORDER BY t1.c3, t1.c1; -- Join with lateral reference -- Different explain plan on v10 as partition-wise join is not supported there. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; SELECT t1.c1,t1.c2 FROM fprt1 t1, LATERAL (SELECT t2.c1, t2.c2 FROM fprt2 t2 WHERE t1.c1 = t2.c2 AND t1.c2 = t2.c1) q WHERE t1.c1 % 2 = 0 ORDER BY 1,2; -- With PHVs, partitionwise join selected but no join pushdown -- Table alias in foreign scan is different for v12, v11 and v10. EXPLAIN (VERBOSE, COSTS OFF) SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; SELECT t1.c1, t1.phv, t2.c2, t2.phv FROM (SELECT 't1_phv' phv, * FROM fprt1 WHERE c1 % 2 = 0) t1 LEFT JOIN (SELECT 't2_phv' phv, * FROM fprt2 WHERE c2 % 2 = 0) t2 ON (t1.c1 = t2.c2) ORDER BY t1.c1, t2.c2; SET enable_partitionwise_join TO off; -- Cleanup DELETE FROM fdw139_t1; DELETE FROM fdw139_t2; DELETE FROM fdw139_t3; DELETE FROM tmp_t4; DROP FOREIGN TABLE fdw139_t1; DROP FOREIGN TABLE fdw139_t2; DROP FOREIGN TABLE fdw139_t3; DROP FOREIGN TABLE tmp_t4; DROP TABLE IF EXISTS fprt1; DROP TABLE IF EXISTS fprt2; DROP TYPE user_enum; DROP USER MAPPING FOR public SERVER mysql_svr; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr; DROP SERVER mysql_svr1; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/pushdown.sql000066400000000000000000000114161414541277500175130ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- mysql with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Create foreign tables CREATE FOREIGN TABLE f_test_tbl1 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9), c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); CREATE FOREIGN TABLE f_test_tbl2 (c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); -- Insert data in mysql db using foreign tables INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); SET datestyle TO ISO; -- WHERE clause pushdown EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e WHERE c6 IN (800,2450) ORDER BY c1; SELECT c1, c2, c6 AS "salary", c8 FROM f_test_tbl1 e WHERE c6 IN (800,2450) ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl1 e WHERE c6 > 3000 ORDER BY c1; SELECT * FROM f_test_tbl1 e WHERE c6 > 3000 ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 = 1500 ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 = 1500 ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 BETWEEN 1000 AND 4000 ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c6 BETWEEN 1000 AND 4000 ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IS NOT NULL ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IS NOT NULL ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT * FROM f_test_tbl1 e WHERE c5 <= '1980-12-17' ORDER BY c1; SELECT * FROM f_test_tbl1 e WHERE c5 <= '1980-12-17' ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c2 IN ('EMP6', 'EMP12', 'EMP5') ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'SALESMAN' ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'SALESMAN' ORDER BY c1; EXPLAIN (VERBOSE, COSTS FALSE) SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'MANA%' ORDER BY c1; SELECT c1, c2, c6, c8 FROM f_test_tbl1 e WHERE c3 LIKE 'MANA%' ORDER BY c1; -- Cleanup DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/select.sql000066400000000000000000000460041414541277500171240ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR PUBLIC SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Check version SELECT mysql_fdw_version(); -- Create foreign tables CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); CREATE FOREIGN TABLE f_numbers(a int, b varchar(255)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'numbers'); CREATE FOREIGN TABLE f_test_tbl1 (c1 INTEGER, c2 VARCHAR(10), c3 CHAR(9),c4 BIGINT, c5 pg_catalog.Date, c6 DECIMAL, c7 INTEGER, c8 SMALLINT) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl1'); CREATE FOREIGN TABLE f_test_tbl2 (c1 INTEGER, c2 VARCHAR(14), c3 VARCHAR(13)) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE TYPE size_t AS enum('small','medium','large'); CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); CREATE FOREIGN TABLE test5_1(c1 INT, c2 CHAR, c3 VARCHAR, c4 BOOLEAN, c5 TEXT, c6 INTERVAL, c7 BYTEA, c8 pg_catalog.DATE, c9 NUMERIC, c10 NAME) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test5'); CREATE FOREIGN TABLE test5_2(c1 INT, c2 BYTEA, c3 BYTEA, c4 BYTEA, c5 BYTEA, c6 BYTEA, c7 BYTEA, c8 BYTEA, c9 BYTEA, c10 BYTEA) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test5'); -- Insert data in MySQL db using foreign tables INSERT INTO f_test_tbl1 VALUES (100, 'EMP1', 'ADMIN', 1300, '1980-12-17', 800.23, NULL, 20); INSERT INTO f_test_tbl1 VALUES (200, 'EMP2', 'SALESMAN', 600, '1981-02-20', 1600.00, 300, 30); INSERT INTO f_test_tbl1 VALUES (300, 'EMP3', 'SALESMAN', 600, '1981-02-22', 1250, 500, 30); INSERT INTO f_test_tbl1 VALUES (400, 'EMP4', 'MANAGER', 900, '1981-04-02', 2975.12, NULL, 20); INSERT INTO f_test_tbl1 VALUES (500, 'EMP5', 'SALESMAN', 600, '1981-09-28', 1250, 1400, 30); INSERT INTO f_test_tbl1 VALUES (600, 'EMP6', 'MANAGER', 900, '1981-05-01', 2850, NULL, 30); INSERT INTO f_test_tbl1 VALUES (700, 'EMP7', 'MANAGER', 900, '1981-06-09', 2450.45, NULL, 10); INSERT INTO f_test_tbl1 VALUES (800, 'EMP8', 'FINANCE', 400, '1987-04-19', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (900, 'EMP9', 'HEAD', NULL, '1981-11-17', 5000, NULL, 10); INSERT INTO f_test_tbl1 VALUES (1000, 'EMP10', 'SALESMAN', 600, '1980-09-08', 1500, 0, 30); INSERT INTO f_test_tbl1 VALUES (1100, 'EMP11', 'ADMIN', 800, '1987-05-23', 1100, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1200, 'EMP12', 'ADMIN', 600, '1981-12-03', 950, NULL, 30); INSERT INTO f_test_tbl1 VALUES (1300, 'EMP13', 'FINANCE', 400, '1981-12-03', 3000, NULL, 20); INSERT INTO f_test_tbl1 VALUES (1400, 'EMP14', 'ADMIN', 700, '1982-01-23', 1300, NULL, 10); INSERT INTO f_test_tbl2 VALUES(10, 'DEVELOPMENT', 'PUNE'); INSERT INTO f_test_tbl2 VALUES(20, 'ADMINISTRATION', 'BANGLORE'); INSERT INTO f_test_tbl2 VALUES(30, 'SALES', 'MUMBAI'); INSERT INTO f_test_tbl2 VALUES(40, 'HR', 'NAGPUR'); SET datestyle TO ISO; -- Retrieve Data from Foreign Table using SELECT Statement. SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 ORDER BY c1 DESC, c8; SELECT DISTINCT c8 FROM f_test_tbl1 ORDER BY 1; SELECT c2 AS "Employee Name" FROM f_test_tbl1 ORDER BY 1; SELECT c8, c6, c7 FROM f_test_tbl1 ORDER BY 1, 2, 3; SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c1 = 100 ORDER BY 1; SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c1 = 100 OR c1 = 700 ORDER BY 1; SELECT * FROM f_test_tbl1 WHERE c3 like 'SALESMAN' ORDER BY 1; SELECT * FROM f_test_tbl1 WHERE c1 IN (100, 700) ORDER BY 1; SELECT * FROM f_test_tbl1 WHERE c1 NOT IN (100, 700) ORDER BY 1 LIMIT 5; SELECT * FROM f_test_tbl1 WHERE c8 BETWEEN 10 AND 20 ORDER BY 1; SELECT * FROM f_test_tbl1 ORDER BY 1 OFFSET 5; -- Retrieve Data from Foreign Table using Group By Clause. SELECT c8 "Department", COUNT(c1) "Total Employees" FROM f_test_tbl1 GROUP BY c8 ORDER BY c8; SELECT c8, SUM(c6) FROM f_test_tbl1 GROUP BY c8 HAVING c8 IN (10, 30) ORDER BY c8; SELECT c8, SUM(c6) FROM f_test_tbl1 GROUP BY c8 HAVING SUM(c6) > 9400 ORDER BY c8; -- Row Level Functions SELECT UPPER(c2), LOWER(c2) FROM f_test_tbl2 ORDER BY 1, 2; -- Retrieve Data from Foreign Table using Sub Queries. SELECT * FROM f_test_tbl1 WHERE c8 <> ALL (SELECT c1 FROM f_test_tbl2 WHERE c1 IN (10, 30, 40)) ORDER BY c1; SELECT c1, c2, c3 FROM f_test_tbl2 WHERE EXISTS (SELECT 1 FROM f_test_tbl1 WHERE f_test_tbl2.c1 = f_test_tbl1.c8) ORDER BY 1, 2; SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 WHERE c8 NOT IN (SELECT c1 FROM f_test_tbl2) ORDER BY c1; -- Retrieve Data from Foreign Table using UNION Operator. SELECT c1, c2 FROM f_test_tbl2 UNION SELECT c1, c2 FROM f_test_tbl1 ORDER BY c1; SELECT c2 FROM f_test_tbl2 UNION ALL SELECT c2 FROM f_test_tbl1 ORDER BY c2; -- Retrieve Data from Foreign Table using INTERSECT Operator. SELECT c2 FROM f_test_tbl1 WHERE c1 >= 800 INTERSECT SELECT c2 FROM f_test_tbl1 WHERE c1 >= 400 ORDER BY c2; SELECT c2 FROM f_test_tbl1 WHERE c1 >= 800 INTERSECT ALL SELECT c2 FROM f_test_tbl1 WHERE c1 >= 400 ORDER BY c2; -- Retrieve Data from Foreign Table using EXCEPT. SELECT c2 FROM f_test_tbl1 EXCEPT SELECT c2 FROM f_test_tbl1 WHERE c1 > 900 ORDER BY c2; SELECT c2 FROM f_test_tbl1 EXCEPT ALL SELECT c2 FROM f_test_tbl1 WHERE c1 > 900 ORDER BY c2; -- Retrieve Data from Foreign Table using CTE (With Clause). WITH with_qry AS (SELECT c1, c2, c3 FROM f_test_tbl2) SELECT e.c2, e.c6, w.c1, w.c2 FROM f_test_tbl1 e, with_qry w WHERE e.c8 = w.c1 ORDER BY e.c8, e.c2; WITH test_tbl2_costs AS (SELECT d.c2, SUM(c6) test_tbl2_total FROM f_test_tbl1 e, f_test_tbl2 d WHERE e.c8 = d.c1 GROUP BY 1), avg_cost AS (SELECT SUM(test_tbl2_total)/COUNT(*) avg FROM test_tbl2_costs) SELECT * FROM test_tbl2_costs WHERE test_tbl2_total > (SELECT avg FROM avg_cost) ORDER BY c2; -- Retrieve Data from Foreign Table using Window Clause. SELECT c8, c1, c6, AVG(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 ORDER BY c8, c1; SELECT c8, c1, c6, COUNT(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 WHERE c8 IN (10, 30, 40, 50, 60, 70) ORDER BY c8, c1; SELECT c8, c1, c6, SUM(c6) OVER (PARTITION BY c8) FROM f_test_tbl1 ORDER BY c8, c1; -- Views CREATE VIEW smpl_vw AS SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1 ORDER BY c1; SELECT * FROM smpl_vw ORDER BY 1; CREATE VIEW comp_vw (s1, s2, s3, s6, s7, s8, d2) AS SELECT s.c1, s.c2, s.c3, s.c6, s.c7, s.c8, d.c2 FROM f_test_tbl2 d, f_test_tbl1 s WHERE d.c1 = s.c8 AND d.c1 = 10 ORDER BY s.c1; SELECT * FROM comp_vw ORDER BY 1; CREATE TEMPORARY VIEW ttest_tbl1_vw AS SELECT c1, c2, c3 FROM f_test_tbl2; SELECT * FROM ttest_tbl1_vw ORDER BY 1, 2; CREATE VIEW mul_tbl_view AS SELECT d.c1 dc1, d.c2 dc2, e.c1 ec1, e.c2 ec2, e.c6 ec6 FROM f_test_tbl2 d INNER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY d.c1; SELECT * FROM mul_tbl_view ORDER BY 1, 2,3; -- Insert Some records in numbers table. INSERT INTO f_numbers VALUES (1, 'One'); INSERT INTO f_numbers VALUES (2, 'Two'); INSERT INTO f_numbers VALUES (3, 'Three'); INSERT INTO f_numbers VALUES (4, 'Four'); INSERT INTO f_numbers VALUES (5, 'Five'); INSERT INTO f_numbers VALUES (6, 'Six'); INSERT INTO f_numbers VALUES (7, 'Seven'); INSERT INTO f_numbers VALUES (8, 'Eight'); INSERT INTO f_numbers VALUES (9, 'Nine'); -- Retrieve Data From foreign tables in functions. CREATE OR REPLACE FUNCTION test_param_where() RETURNS void AS $$ DECLARE n varchar; BEGIN FOR x IN 1..9 LOOP SELECT b INTO n FROM f_numbers WHERE a = x; RAISE NOTICE 'Found number %', n; END LOOP; return; END $$ LANGUAGE plpgsql; SELECT test_param_where(); CREATE OR REPLACE FUNCTION test_param_where2(int, text) RETURNS integer AS ' SELECT a FROM f_numbers WHERE a = $1 AND b = $2; ' LANGUAGE sql; SELECT test_param_where2(1, 'One'); -- Foreign-Foreign table joins -- CROSS JOIN. SELECT f_test_tbl2.c2, f_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN f_test_tbl1 ORDER BY 1, 2; -- INNER JOIN. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d, f_test_tbl1 e WHERE d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d INNER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; -- OUTER JOINS. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d LEFT OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d RIGHT OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d FULL OUTER JOIN f_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; -- Local-Foreign table joins. CREATE TABLE l_test_tbl1 AS SELECT c1, c2, c3, c4, c5, c6, c7, c8 FROM f_test_tbl1; CREATE TABLE l_test_tbl2 AS SELECT c1, c2, c3 FROM f_test_tbl2; -- CROSS JOIN. SELECT f_test_tbl2.c2, l_test_tbl1.c2 FROM f_test_tbl2 CROSS JOIN l_test_tbl1 ORDER BY 1, 2; -- INNER JOIN. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM l_test_tbl2 d, f_test_tbl1 e WHERE d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d INNER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; -- OUTER JOINS. SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d LEFT OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d RIGHT OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; SELECT d.c1, d.c2, e.c1, e.c2, e.c6, e.c8 FROM f_test_tbl2 d FULL OUTER JOIN l_test_tbl1 e ON d.c1 = e.c8 ORDER BY 1, 3; -- FDW-206: LEFT JOIN LATERAL case should not crash EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_mysql_test t1 LEFT JOIN LATERAL ( SELECT t2.a, t1.a AS t1_a FROM f_mysql_test t2) t3 ON t1.a = t3.a ORDER BY 1; SELECT * FROM f_mysql_test t1 LEFT JOIN LATERAL ( SELECT t2.a, t1.a AS t1_a FROM f_mysql_test t2) t3 ON t1.a = t3.a ORDER BY 1; SELECT t1.c1, t3.c1, t3.t1_c8 FROM f_test_tbl1 t1 INNER JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; SELECT t1.c1, t3.c1, t3.t1_c8 FROM l_test_tbl1 t1 LEFT JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; SELECT *, (SELECT r FROM (SELECT c1 AS c1) x, LATERAL (SELECT c1 AS r) y) FROM f_test_tbl1 ORDER BY 1, 2, 3; -- LATERAL JOIN with RIGHT should throw error SELECT t1.c1, t3.c1, t3.t1_c8 FROM f_test_tbl1 t1 RIGHT JOIN LATERAL ( SELECT t2.c1, t1.c8 AS t1_c8 FROM f_test_tbl2 t2) t3 ON t3.c1 = t3.t1_c8 ORDER BY 1, 2, 3; -- FDW-207: NATURAL JOIN should give correct output SELECT t1.c1, t2.c1, t3.c1 FROM f_test_tbl1 t1 NATURAL JOIN f_test_tbl1 t2 NATURAL JOIN f_test_tbl1 t3 ORDER BY 1, 2, 3; -- FDW-208: IS NULL and LIKE should give the correct output with -- use_remote_estimate set to true. INSERT INTO f_test_tbl2 VALUES (50, 'TEMP1', NULL); INSERT INTO f_test_tbl2 VALUES (60, 'TEMP2', NULL); ALTER SERVER mysql_svr OPTIONS (use_remote_estimate 'true'); SELECT t1.c1, t2.c1 FROM f_test_tbl2 t1 INNER JOIN f_test_tbl2 t2 ON t1.c1 = t2.c1 WHERE t1.c3 IS NULL ORDER BY 1, 2; SELECT t1.c1, t2.c1 FROM f_test_tbl2 t1 INNER JOIN f_test_tbl2 t2 ON t1.c1 = t2.c1 AND t1.c2 LIKE 'TEMP%' ORDER BY 1, 2; DELETE FROM f_test_tbl2 WHERE c1 IN (50, 60); ALTER SERVER mysql_svr OPTIONS (SET use_remote_estimate 'false'); -- FDW-169: Insert/Update/Delete on enum column. INSERT INTO f_enum_t1 VALUES (1, 'small'), (2, 'medium'), (3, 'medium'), (4, 'small'); SELECT * FROM f_enum_t1 WHERE id = 4; UPDATE f_enum_t1 SET size = 'large' WHERE id = 4; SELECT * FROM f_enum_t1 WHERE id = 4; DELETE FROM f_enum_t1 WHERE size = 'large'; SELECT * FROM f_enum_t1 WHERE id = 4; -- Negative scenarios for ENUM handling. -- Test that if we insert the ENUM value which is not present on MySQL side, -- but present on Postgres side. DROP FOREIGN TABLE f_enum_t1; DROP TYPE size_t; -- Create the type with extra enum values. CREATE TYPE size_t AS enum('small', 'medium', 'large', 'largest', ''); CREATE FOREIGN TABLE f_enum_t1(id int, size size_t) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'enum_t1'); -- If we insert the enum value which is not present on MySQL side then it -- inserts empty string in ANSI_QUOTES sql_mode, so verify that. INSERT INTO f_enum_t1 VALUES (4, 'largest'); SELECT * from f_enum_t1; DELETE FROM f_enum_t1 WHERE size = ''; -- Postgres should throw an error as the value which we are inserting for enum -- column is not present in enum on Postgres side, no matter whether it is -- present on MySQL side or not. PG's sanity check itself throws an error. INSERT INTO f_enum_t1 VALUES (4, 'big'); -- FDW-155: Enum data type can be handled correctly in select statements on -- foreign table. SELECT * FROM f_enum_t1 WHERE size = 'medium' ORDER BY id; -- Remote aggregate in combination with a local Param (for the output -- of an initplan) SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1; SELECT EXISTS(SELECT 1 FROM pg_enum), sum(id) from f_enum_t1 GROUP BY 1; -- Check with the IMPORT FOREIGN SCHEMA command. Also, check ENUM types with -- the IMPORT FOREIGN SCHEMA command. If the enum name is the same for multiple -- tables, then it should handle correctly by prefixing the table name. CREATE TYPE enum_t1_size_t AS enum('small', 'medium', 'large'); CREATE TYPE enum_t2_size_t AS enum('S', 'M', 'L'); IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (enum_t1, enum_t2) FROM SERVER mysql_svr INTO public; SELECT attrelid::regclass, atttypid::regtype FROM pg_attribute WHERE (attrelid = 'enum_t1'::regclass OR attrelid = 'enum_t2'::regclass) AND attnum > 1 ORDER BY 1; SELECT * FROM enum_t1 ORDER BY id; SELECT * FROM enum_t2 ORDER BY id; DROP FOREIGN TABLE enum_t1; DROP FOREIGN TABLE enum_t2; -- FDW-248: IMPORT FOREIGN SCHEMA command should work correctly if called -- multiple times. Earlier we wrongly used PG_TRY/CATCH block to get the server -- options without clearing the error state and that exceeded -- ERRORDATA_STACK_SIZE hard coded to 5. DO $DO$ DECLARE i int; BEGIN FOR i IN 1..5 LOOP IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (mysql_test) FROM SERVER mysql_svr INTO public; DROP FOREIGN TABLE mysql_test; END LOOP; END; $DO$; -- Parameterized queries should work correctly. EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; SELECT * FROM f_test_tbl1 WHERE c8 NOT IN (SELECT c1 FROM f_test_tbl2 WHERE c1 = (SELECT 20)) ORDER BY c1; -- Check parameterized queries with text/varchar column, should not crash. CREATE FOREIGN TABLE f_test_tbl3 (c1 INTEGER, c2 text, c3 text) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'test_tbl2'); CREATE TABLE local_t1 (c1 INTEGER, c2 text); INSERT INTO local_t1 VALUES (1, 'SALES'); SELECT c1, c2 FROM f_test_tbl3 WHERE c3 = (SELECT 'PUNE'::text) ORDER BY c1; SELECT c1, c2 FROM f_test_tbl2 WHERE c3 = (SELECT 'PUNE'::varchar) ORDER BY c1; SELECT * FROM local_t1 lt1 WHERE lt1.c1 = (SELECT count(*) FROM f_test_tbl3 ft1 WHERE ft1.c2 = lt1.c2) ORDER BY lt1.c1; SELECT c1, c2 FROM f_test_tbl1 WHERE c8 = ( SELECT c1 FROM f_test_tbl2 WHERE c1 = ( SELECT min(c1) + 1 FROM f_test_tbl2)) ORDER BY c1; SELECT * FROM f_test_tbl1 WHERE c1 = (SELECT 500) AND c2 = ( SELECT max(c2) FROM f_test_tbl1 WHERE c4 = (SELECT 600)) ORDER BY 1, 2; SELECT t1.c1, (SELECT c2 FROM f_test_tbl1 WHERE c1 =(SELECT 500)) FROM f_test_tbl2 t1, ( SELECT c1, c2 FROM f_test_tbl2 WHERE c1 > ANY (SELECT 20)) t2 ORDER BY 1, 2; -- FDW-255: Should throw an error when we select system attribute. SELECT xmin FROM f_test_tbl1; SELECT ctid, xmax, tableoid FROM f_test_tbl1; SELECT xmax, c1 FROM f_test_tbl1; SELECT count(tableoid) FROM f_test_tbl1; -- FDW-333: MySQL BINARY and VARBINARY data type should map to BYTEA in -- Postgres while importing the schema. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO ("test5") FROM SERVER mysql_svr INTO public; SELECT attrelid::regclass, atttypid::regtype FROM pg_attribute WHERE attrelid = 'test5'::regclass AND attnum > 1 ORDER BY 1; SELECT * FROM test5 ORDER BY 1; -- Test Mapping of MySQL BINARY and VARBINARY data type with various -- Postgres data types. SELECT * FROM test5_1 ORDER BY 1; SELECT * FROM test5_1 WHERE c9 IS NULL ORDER BY 1; SELECT * FROM test5_1 WHERE c10 IS NULL ORDER BY 1; -- Test MYSQL BINARY(n) and VARBINARY(n) variants mapping to Postgres BYTEA. SELECT * FROM test5_2 ORDER BY 1; SELECT * FROM test5_2 WHERE c9 IS NULL ORDER BY 1; SELECT * FROM test5_2 WHERE c10 IS NULL ORDER BY 1; -- FDW-400: Test the parameterized query by enabling use_remote_estimate -- option. ALTER SERVER mysql_svr options (SET use_remote_estimate 'true'); SELECT c1, sum(c7) FROM f_test_tbl1 t1 GROUP BY c1 HAVING EXISTS (SELECT 1 FROM f_test_tbl1 t2 WHERE (t1.c1 = t2.c1)) ORDER BY 1,2; ALTER SERVER mysql_svr options (SET use_remote_estimate 'false'); -- FDW-411: Volatile/immutable functions should not get pushed down to remote -- MySQL server. EXPLAIN (VERBOSE, COSTS OFF) SELECT c1, c2, c3 FROM f_test_tbl1 WHERE pg_catalog.timeofday() IS NOT NULL ORDER BY 1 limit 5; SELECT c1, c2, c3 FROM f_test_tbl1 WHERE pg_catalog.timeofday() IS NOT NULL ORDER BY 1 limit 5; -- FDW-447: Fix function implicit/explicit coercion. EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 WHERE c1 = 12.2; EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM f_test_tbl1 WHERE c1::numeric = 12.2; -- FDW-408: Skip importing relations that have SET type because Postgres -- doesn't have equivalent datatype which can be mapped to MySQL SET. IMPORT FOREIGN SCHEMA mysql_fdw_regress LIMIT TO (test_set, mysql_test, test_tbl1) FROM SERVER mysql_svr INTO public; SELECT relname FROM pg_class WHERE relname IN ('test_set', 'mysql_test', 'test_tbl1') AND relnamespace = 'public'::regnamespace; -- Cleanup DROP TABLE l_test_tbl1; DROP TABLE l_test_tbl2; DROP TABLE local_t1; DROP VIEW smpl_vw; DROP VIEW comp_vw; DROP VIEW ttest_tbl1_vw; DROP VIEW mul_tbl_view; DELETE FROM f_test_tbl1; DELETE FROM f_test_tbl2; DELETE FROM f_numbers; DELETE FROM f_enum_t1; DROP FOREIGN TABLE f_test_tbl1; DROP FOREIGN TABLE f_test_tbl2; DROP FOREIGN TABLE f_numbers; DROP FOREIGN TABLE f_mysql_test; DROP FOREIGN TABLE f_enum_t1; DROP FOREIGN TABLE f_test_tbl3; DROP FOREIGN TABLE test5; DROP FOREIGN TABLE test5_1; DROP FOREIGN TABLE test5_2; DROP FOREIGN TABLE mysql_test; DROP FOREIGN TABLE test_tbl1; DROP TYPE size_t; DROP TYPE enum_t1_size_t; DROP TYPE enum_t2_size_t; DROP FUNCTION test_param_where(); DROP FUNCTION test_param_where2(int, text); DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; DROP EXTENSION mysql_fdw; mysql_fdw-REL-2_7_0/sql/server_options.sql000066400000000000000000000160071414541277500207260ustar00rootroot00000000000000\set MYSQL_HOST `echo \'"$MYSQL_HOST"\'` \set MYSQL_PORT `echo \'"$MYSQL_PORT"\'` \set MYSQL_USER_NAME `echo \'"$MYSQL_USER_NAME"\'` \set MYSQL_PASS `echo \'"$MYSQL_PWD"\'` -- Before running this file User must create database mysql_fdw_regress on -- MySQL with all permission for MYSQL_USER_NAME user with MYSQL_PWD password -- and ran mysql_init.sh file to create tables. \c contrib_regression CREATE EXTENSION IF NOT EXISTS mysql_fdw; CREATE SERVER mysql_svr FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT); CREATE USER MAPPING FOR public SERVER mysql_svr OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); -- Validate extension, server and mapping details CREATE OR REPLACE FUNCTION show_details(host TEXT, port TEXT, uid TEXT, pwd TEXT) RETURNS int AS $$ DECLARE ext TEXT; srv TEXT; sopts TEXT; uopts TEXT; BEGIN SELECT e.fdwname, srvname, array_to_string(s.srvoptions, ','), array_to_string(u.umoptions, ',') INTO ext, srv, sopts, uopts FROM pg_foreign_data_wrapper e LEFT JOIN pg_foreign_server s ON e.oid = s.srvfdw LEFT JOIN pg_user_mapping u ON s.oid = u.umserver WHERE e.fdwname = 'mysql_fdw' ORDER BY 1, 2, 3, 4; raise notice 'Extension : %', ext; raise notice 'Server : %', srv; IF strpos(sopts, host) <> 0 AND strpos(sopts, port) <> 0 THEN raise notice 'Server_Options : matched'; END IF; IF strpos(uopts, uid) <> 0 AND strpos(uopts, pwd) <> 0 THEN raise notice 'User_Mapping_Options : matched'; END IF; return 1; END; $$ language plpgsql; SELECT show_details(:MYSQL_HOST, :MYSQL_PORT, :MYSQL_USER_NAME, :MYSQL_PASS); -- Create foreign table and perform basic SQL operations CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); SELECT a, b FROM f_mysql_test ORDER BY 1, 2; INSERT INTO f_mysql_test (a, b) VALUES (2, 2); SELECT a, b FROM f_mysql_test ORDER BY 1, 2; UPDATE f_mysql_test SET b = 3 WHERE a = 2; SELECT a, b FROM f_mysql_test ORDER BY 1, 2; DELETE FROM f_mysql_test WHERE a = 2; SELECT a, b FROM f_mysql_test ORDER BY 1, 2; DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr; DROP SERVER mysql_svr; -- Server with init_command. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS (host :MYSQL_HOST, port :MYSQL_PORT, init_command 'create table init_command_check(a int)'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS (username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test (a int, b int) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- This will create init_command_check table in mysql_fdw_regress database. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; -- init_command_check table created mysql_fdw_regress database can be verified -- by creating corresponding foreign table here. CREATE FOREIGN TABLE f_init_command_check(a int) SERVER mysql_svr1 OPTIONS (dbname 'mysql_fdw_regress', table_name 'init_command_check'); SELECT a FROM f_init_command_check ORDER BY 1; -- Changing init_command to drop init_command_check table from -- mysql_fdw_regress database ALTER SERVER mysql_svr1 OPTIONS (SET init_command 'drop table init_command_check'); SELECT a, b FROM f_mysql_test; DROP FOREIGN TABLE f_init_command_check; DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- Server with use_remote_estimate. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, use_remote_estimate 'TRUE'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- Below explain will return actual rows from MySQL, but keeping costs off -- here for consistent regression result. EXPLAIN (VERBOSE, COSTS OFF) SELECT a FROM f_mysql_test WHERE a < 2 ORDER BY 1; DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- Create server with secure_auth. CREATE SERVER mysql_svr1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS(host :MYSQL_HOST, port :MYSQL_PORT, secure_auth 'FALSE'); CREATE USER MAPPING FOR public SERVER mysql_svr1 OPTIONS(username :MYSQL_USER_NAME, password :MYSQL_PASS); CREATE FOREIGN TABLE f_mysql_test(a int, b int) SERVER mysql_svr1 OPTIONS(dbname 'mysql_fdw_regress', table_name 'mysql_test'); -- Below should fail with Warning of secure_auth is false. SELECT a, b FROM f_mysql_test ORDER BY 1, 2; DROP FOREIGN TABLE f_mysql_test; DROP USER MAPPING FOR public SERVER mysql_svr1; DROP SERVER mysql_svr1; -- FDW-335: Support for fetch_size option at server level and table level. CREATE SERVER fetch101 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( fetch_size '101' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=101']; ALTER SERVER fetch101 OPTIONS( SET fetch_size '202' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=101']; SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=202']; CREATE FOREIGN TABLE table30000 ( x int ) SERVER fetch101 OPTIONS ( fetch_size '30000' ); SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=30000']; ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '60000'); SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=30000']; SELECT COUNT(*) FROM pg_foreign_table WHERE ftrelid = 'table30000'::regclass AND ftoptions @> array['fetch_size=60000']; -- Make sure that changing the table level fetch-size value did not change the -- server level value. SELECT count(*) FROM pg_foreign_server WHERE srvname = 'fetch101' AND srvoptions @> array['fetch_size=202']; -- Negative test cases for fetch_size option, should error out. ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '-60000'); ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '123abc'); ALTER FOREIGN TABLE table30000 OPTIONS ( SET fetch_size '999999999999999999999'); -- Cleanup fetch_size test objects. DROP FOREIGN TABLE table30000; DROP SERVER fetch101; -- FDW-350: Support for reconnect option at server level. CREATE SERVER reconnect1 FOREIGN DATA WRAPPER mysql_fdw OPTIONS( reconnect 'true' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'reconnect1' AND srvoptions @> array['reconnect=true']; ALTER SERVER reconnect1 OPTIONS( SET reconnect 'false' ); SELECT count(*) FROM pg_foreign_server WHERE srvname = 'reconnect1' AND srvoptions @> array['reconnect=false']; -- Negative test case for reconnect option, should error out. ALTER SERVER reconnect1 OPTIONS ( SET reconnect 'abc1' ); -- Cleanup reconnect option test objects. DROP SERVER reconnect1; -- Cleanup DROP EXTENSION mysql_fdw;